> {
+ let (status, code, details) = match &self {
+ BrightStaffError::ModelNotFound(model_name) => (
+ StatusCode::NOT_FOUND,
+ "ModelNotFound",
+ json!({ "rejected_model_id": model_name }),
+ ),
+
+ BrightStaffError::NoModelSpecified => {
+ (StatusCode::BAD_REQUEST, "NoModelSpecified", json!({}))
+ }
+
+ BrightStaffError::ConversationStateNotFound(prev_resp_id) => (
+ StatusCode::CONFLICT,
+ "ConversationStateNotFound",
+ json!({ "previous_response_id": prev_resp_id }),
+ ),
+
+ BrightStaffError::InternalServerError(reason) => (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ "InternalServerError",
+ // Passing the reason into details for easier debugging
+ json!({ "reason": reason }),
+ ),
+
+ BrightStaffError::InvalidRequest(reason) => (
+ StatusCode::BAD_REQUEST,
+ "InvalidRequest",
+ json!({ "reason": reason }),
+ ),
+
+ BrightStaffError::ForwardedError {
+ status_code,
+ message,
+ } => (*status_code, "ForwardedError", json!({ "reason": message })),
+
+ BrightStaffError::StreamError(reason) => (
+ StatusCode::BAD_REQUEST,
+ "StreamError",
+ json!({ "reason": reason }),
+ ),
+
+ BrightStaffError::ResponseCreationFailed(reason) => (
+ StatusCode::BAD_REQUEST,
+ "ResponseCreationFailed",
+ json!({ "reason": reason.to_string() }),
+ ),
+ };
+
+ let body_json = json!({
+ "error": {
+ "code": code,
+ "message": self.to_string(),
+ "details": details
+ }
+ });
+
+ // 1. Create the concrete body
+ let full_body = Full::new(Bytes::from(body_json.to_string()));
+
+ // 2. Convert it to BoxBody
+ // We map_err because Full never fails, but BoxBody expects a HyperError
+ let boxed_body = full_body
+ .map_err(|never| match never {}) // This handles the "Infallible" error type
+ .boxed();
+
+ Response::builder()
+ .status(status)
+ .header("content-type", "application/json")
+ .body(boxed_body)
+ .unwrap_or_else(|_| {
+ Response::new(
+ Full::new(Bytes::from("Internal Error"))
+ .map_err(|never| match never {})
+ .boxed(),
+ )
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use http_body_util::BodyExt; // For .collect().await
+
+ #[tokio::test]
+ async fn test_model_not_found_format() {
+ let err = BrightStaffError::ModelNotFound("gpt-5-secret".to_string());
+ let response = err.into_response();
+
+ assert_eq!(response.status(), StatusCode::NOT_FOUND);
+
+ // Helper to extract body as JSON
+ let body_bytes = response.into_body().collect().await.unwrap().to_bytes();
+ let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
+
+ assert_eq!(body["error"]["code"], "ModelNotFound");
+ assert_eq!(
+ body["error"]["details"]["rejected_model_id"],
+ "gpt-5-secret"
+ );
+ assert!(body["error"]["message"]
+ .as_str()
+ .unwrap()
+ .contains("gpt-5-secret"));
+ }
+
+ #[tokio::test]
+ async fn test_forwarded_error_preserves_status() {
+ let err = BrightStaffError::ForwardedError {
+ status_code: StatusCode::TOO_MANY_REQUESTS,
+ message: "Rate limit exceeded on agent side".to_string(),
+ };
+
+ let response = err.into_response();
+ assert_eq!(response.status(), StatusCode::TOO_MANY_REQUESTS);
+
+ let body_bytes = response.into_body().collect().await.unwrap().to_bytes();
+ let body: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
+
+ assert_eq!(body["error"]["code"], "ForwardedError");
+ }
+
+ #[tokio::test]
+ async fn test_hyper_error_wrapping() {
+ // Manually trigger a hyper error by creating an invalid URI/Header
+ let hyper_err = hyper::http::Response::builder()
+ .status(1000) // Invalid status
+ .body(())
+ .unwrap_err();
+
+ let err = BrightStaffError::ResponseCreationFailed(hyper_err);
+ let response = err.into_response();
+
+ assert_eq!(response.status(), StatusCode::BAD_REQUEST);
+ }
+}
diff --git a/demos/agent_orchestration/travel_agents/config.yaml b/demos/agent_orchestration/travel_agents/config.yaml
index 2cb24d71..911baf89 100644
--- a/demos/agent_orchestration/travel_agents/config.yaml
+++ b/demos/agent_orchestration/travel_agents/config.yaml
@@ -55,3 +55,6 @@ listeners:
tracing:
random_sampling: 100
+ span_attributes:
+ header_prefixes:
+ - x-acme-
diff --git a/demos/agent_orchestration/travel_agents/test.rest b/demos/agent_orchestration/travel_agents/test.rest
index f3ecaf66..b6348f28 100644
--- a/demos/agent_orchestration/travel_agents/test.rest
+++ b/demos/agent_orchestration/travel_agents/test.rest
@@ -3,9 +3,16 @@
### Travel Agent Chat Completion Request
POST {{llm_endpoint}}/v1/chat/completions HTTP/1.1
Content-Type: application/json
+X-Acme-Workspace-Id: ws_7e2c5d91b4224f59b0e6a4e0125c21b3
+X-Acme-Tenant-Id: ten_4102a8c7fa6542b084b395d2df184a9a
+X-Acme-User-Id: usr_19df7e6751b846f9ba026776e3c12abe
+X-Acme-Admin-Level: 3
+X-Acme-Environment: production
+X-Acme-Is-Internal: false
+X-Acme-Cost-Center: HD100
{
- "model": "gpt-4o",
+ "model": "gpt-5.2",
"messages": [
{
"role": "user",
@@ -20,7 +27,28 @@ Content-Type: application/json
"content": "What is one Alaska flight that goes direct to Atlanta from Seattle?"
}
],
- "max_tokens": 1000,
+ "max_completion_tokens": 1000,
+ "stream": false,
+ "temperature": 1.0
+}
+
+
+### Travel Agent Request (prefix mismatch - ignored)
+POST {{llm_endpoint}}/v1/chat/completions HTTP/1.1
+Content-Type: application/json
+X-Other-Workspace-Id: ws_7e2c5d91b4224f59b0e6a4e0125c21b3
+X-Other-Tenant-Id: ten_4102a8c7fa6542b084b395d2df184a9a
+X-Other-User-Id: usr_19df7e6751b846f9ba026776e3c12abe
+
+{
+ "model": "gpt-5.2",
+ "messages": [
+ {
+ "role": "user",
+ "content": "What's the weather in Seattle?"
+ }
+ ],
+ "max_completion_tokens": 1000,
"stream": false,
"temperature": 1.0
}
diff --git a/demos/llm_routing/preference_based_routing/README.md b/demos/llm_routing/preference_based_routing/README.md
index e1e16ec0..bfee4e34 100644
--- a/demos/llm_routing/preference_based_routing/README.md
+++ b/demos/llm_routing/preference_based_routing/README.md
@@ -15,9 +15,9 @@ Make sure your machine is up to date with [latest version of plano]([url](https:
```bash
(venv) $ planoai up --service plano --foreground
# Or if installed with uv: uvx planoai up --service plano --foreground
-2025-05-30 18:00:09,953 - planoai.main - INFO - Starting plano cli version: 0.4.8
+2025-05-30 18:00:09,953 - planoai.main - INFO - Starting plano cli version: 0.4.9
2025-05-30 18:00:09,953 - planoai.main - INFO - Validating /Users/adilhafeez/src/intelligent-prompt-gateway/demos/llm_routing/preference_based_routing/config.yaml
-2025-05-30 18:00:10,422 - cli.core - INFO - Starting plano gateway, image name: plano, tag: katanemo/plano:0.4.8
+2025-05-30 18:00:10,422 - cli.core - INFO - Starting plano gateway, image name: plano, tag: katanemo/plano:0.4.9
2025-05-30 18:00:10,662 - cli.core - INFO - plano status: running, health status: starting
2025-05-30 18:00:11,712 - cli.core - INFO - plano status: running, health status: starting
2025-05-30 18:00:12,761 - cli.core - INFO - plano is running and is healthy!
diff --git a/docs/source/_static/img/cli-default-command.png b/docs/source/_static/img/cli-default-command.png
new file mode 100644
index 00000000..a69dbe86
Binary files /dev/null and b/docs/source/_static/img/cli-default-command.png differ
diff --git a/docs/source/_static/img/cli-init-command.png b/docs/source/_static/img/cli-init-command.png
new file mode 100644
index 00000000..b9176a29
Binary files /dev/null and b/docs/source/_static/img/cli-init-command.png differ
diff --git a/docs/source/_static/img/cli-trace-command.png b/docs/source/_static/img/cli-trace-command.png
new file mode 100644
index 00000000..0efa04b8
Binary files /dev/null and b/docs/source/_static/img/cli-trace-command.png differ
diff --git a/docs/source/_static/js/fix-copy.js b/docs/source/_static/js/fix-copy.js
new file mode 100644
index 00000000..0999cc3e
--- /dev/null
+++ b/docs/source/_static/js/fix-copy.js
@@ -0,0 +1,18 @@
+/* Fix: Prevent "Copy code" button label from appearing in clipboard content.
+ *
+ * sphinxawesome_theme inserts a copy button inside elements. When
+ * clipboard.js selects all children of the to copy, the button's
+ * sr-only text ("Copy code") is included in the selection. This listener
+ * intercepts the copy event and strips that trailing label from the data
+ * written to the clipboard.
+ */
+document.addEventListener('copy', function (e) {
+ if (!e.clipboardData) { return; }
+ var selection = window.getSelection();
+ if (!selection) { return; }
+ var text = selection.toString();
+ var clean = text.replace(/\nCopy code\s*$/, '');
+ if (clean === text) { return; }
+ e.clipboardData.setData('text/plain', clean);
+ e.preventDefault();
+}, true);
diff --git a/docs/source/conf.py b/docs/source/conf.py
index c4f20ea0..2ec59aa8 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -17,7 +17,7 @@ from sphinxawesome_theme.postprocess import Icons
project = "Plano Docs"
copyright = "2025, Katanemo Labs, Inc"
author = "Katanemo Labs, Inc"
-release = " v0.4.8"
+release = " v0.4.9"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
@@ -116,6 +116,7 @@ html_theme_options = asdict(theme_options)
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_css_files = ["css/custom.css"]
+html_js_files = ["js/fix-copy.js"]
pygments_style = "lovelace"
pygments_style_dark = "github-dark"
diff --git a/docs/source/get_started/quickstart.rst b/docs/source/get_started/quickstart.rst
index e52e349b..de68cbe3 100644
--- a/docs/source/get_started/quickstart.rst
+++ b/docs/source/get_started/quickstart.rst
@@ -37,7 +37,7 @@ Plano's CLI allows you to manage and interact with the Plano efficiently. To ins
.. code-block:: console
- $ uv tool install planoai==0.4.8
+ $ uv tool install planoai==0.4.9
**Option 2: Install with pip (Traditional)**
@@ -45,7 +45,7 @@ Plano's CLI allows you to manage and interact with the Plano efficiently. To ins
$ python -m venv venv
$ source venv/bin/activate # On Windows, use: venv\Scripts\activate
- $ pip install planoai==0.4.8
+ $ pip install planoai==0.4.9
.. _llm_routing_quickstart:
@@ -90,7 +90,7 @@ Start Plano:
$ planoai up plano_config.yaml
# Or if installed with uv tool: uvx planoai up plano_config.yaml
- 2024-12-05 11:24:51,288 - planoai.main - INFO - Starting plano cli version: 0.4.8
+ 2024-12-05 11:24:51,288 - planoai.main - INFO - Starting plano cli version: 0.4.9
2024-12-05 11:24:51,825 - planoai.utils - INFO - Schema validation successful!
2024-12-05 11:24:51,825 - planoai.main - INFO - Starting plano
...
diff --git a/docs/source/guides/observability/tracing.rst b/docs/source/guides/observability/tracing.rst
index 02723b23..950befd2 100644
--- a/docs/source/guides/observability/tracing.rst
+++ b/docs/source/guides/observability/tracing.rst
@@ -142,6 +142,109 @@ In your observability platform (Jaeger, Grafana Tempo, Datadog, etc.), filter tr
For complete details on all available signals, detection methods, and best practices, see the :doc:`../../concepts/signals` guide.
+Custom Span Attributes
+-------------------------------------------
+
+Plano can automatically attach **custom span attributes** derived from request headers and **static** attributes
+defined in configuration. This lets you stamp
+traces with identifiers like workspace, tenant, or user IDs without changing application code or adding
+custom instrumentation.
+
+**Why This Is Useful**
+
+- **Tenant-aware debugging**: Filter traces by ``workspace.id`` or ``tenant.id``.
+- **Customer-specific visibility**: Attribute performance or errors to a specific customer.
+- **Low overhead**: No code changes in agents or clients—just headers.
+
+How It Works
+~~~~~~~~~~~~
+
+You configure one or more header prefixes. Any incoming HTTP header whose name starts with one of these
+prefixes is captured as a span attribute. You can also provide static attributes that are always injected.
+
+- The **prefix is only for matching**, not the resulting attribute key.
+- The attribute key is the header name **with the prefix removed**, then hyphens converted to dots.
+
+.. note::
+
+ Custom span attributes are attached to LLM spans when handling ``/v1/...`` requests via ``llm_chat``. For orchestrator requests to ``/agents/...``,
+ these attributes are added to both the orchestrator selection span and to each agent span created by ``agent_chat``.
+
+**Example**
+
+Configured prefix::
+
+ tracing:
+ span_attributes:
+ header_prefixes:
+ - x-katanemo-
+
+Incoming headers::
+
+ X-Katanemo-Workspace-Id: ws_123
+ X-Katanemo-Tenant-Id: ten_456
+
+Resulting span attributes::
+
+ workspace.id = "ws_123"
+ tenant.id = "ten_456"
+
+Configuration
+~~~~~~~~~~~~~
+
+Add the prefix list under ``tracing`` in your config:
+
+.. code-block:: yaml
+
+ tracing:
+ random_sampling: 100
+ span_attributes:
+ header_prefixes:
+ - x-katanemo-
+ static:
+ environment: production
+ service.version: "1.0.0"
+
+Static attributes are always injected alongside any header-derived attributes. If a header-derived
+attribute key matches a static key, the header value overrides the static value.
+
+You can provide multiple prefixes:
+
+.. code-block:: yaml
+
+ tracing:
+ span_attributes:
+ header_prefixes:
+ - x-katanemo-
+ - x-tenant-
+ static:
+ environment: production
+ service.version: "1.0.0"
+
+Notes and Examples
+~~~~~~~~~~~~~~~~~~
+
+- **Prefix must match exactly**: ``katanemo-`` does not match ``x-katanemo-`` headers.
+- **Trailing dash is recommended**: Without it, ``x-katanemo`` would also match ``x-katanemo-foo`` and
+ ``x-katanemofoo``.
+- **Keys are always strings**: Values are captured as string attributes.
+
+**Prefix mismatch example**
+
+Config::
+
+ tracing:
+ span_attributes:
+ header_prefixes:
+ - x-katanemo-
+
+Request headers::
+
+ X-Other-User-Id: usr_999
+
+Result: no attributes are captured from ``X-Other-User-Id``.
+
+
Benefits of Using ``Traceparent`` Headers
-----------------------------------------
@@ -497,55 +600,7 @@ tools like AWS X-Ray and Datadog, enhancing observability and facilitating faste
Additional Resources
--------------------
-CLI Reference
-~~~~~~~~~~~~~
-
-``planoai trace``
- Trace requests captured by the local OTLP listener.
-
- **Synopsis**
-
- .. code-block:: console
-
- $ planoai trace [TARGET] [OPTIONS]
-
- **Targets**
-
- - ``last`` (default): show the most recent trace.
- - ``any``: allow interactive selection when available.
- - ````: full 32-hex trace ID.
- - ````: first 8 hex characters.
-
- **Options**
-
- - ``--filter ``: limit displayed attributes to matching keys (supports ``*``).
- - ``--where ``: match traces containing a specific attribute (repeatable, AND).
- - ``--list``: list trace IDs only.
- - ``--no-interactive``: disable interactive prompts/selections.
- - ``--limit ``: limit the number of traces returned.
- - ``--since ``: look back window (``5m``, ``2h``, ``1d``).
- - ``--json``: output raw JSON instead of formatted output.
- - ``--verbose, -v``: show all span attributes. By default, inbound/outbound
- spans are displayed in a compact view.
-
- **Environment**
-
- - ``PLANO_TRACE_PORT``: gRPC port used by ``planoai trace`` to query traces
- (defaults to ``4317``).
-
-``planoai trace listen``
- Start a local OTLP/gRPC listener.
-
- **Synopsis**
-
- .. code-block:: console
-
- $ planoai trace listen [OPTIONS]
-
- **Options**
-
- - ``--host ``: bind address (default: ``0.0.0.0``).
- - ``--port ``: gRPC listener port (default: ``4317``).
+For full command documentation (including ``planoai trace`` and all other CLI commands), see :ref:`cli_reference`.
External References
~~~~~~~~~~~~~~~~~~~
diff --git a/docs/source/guides/state.rst b/docs/source/guides/state.rst
index 7cc6b20a..3f875ce1 100644
--- a/docs/source/guides/state.rst
+++ b/docs/source/guides/state.rst
@@ -165,7 +165,7 @@ Then set the environment variable before running Plano:
./plano
.. warning::
- **Special Characters in Passwords**: If your password contains special characters like ``#``, ``@``, or ``&``, you must URL-encode them in the connection string. For example, ``MyPass#123`` becomes ``MyPass%23123``.
+ **Special Characters in Passwords**: If your password contains special characters like ``#``, ``@``, or ``&``, you must URL-encode them in the connection string. For example, ``P@ss#123`` becomes ``P%40ss%23123``.
Supabase Connection Strings
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -202,14 +202,14 @@ Use the direct connection (port 5432):
state_storage:
type: postgres
- connection_string: "postgresql://postgres.myproject:$DB_PASSWORD@aws-0-us-west-2.pooler.supabase.com:5432/postgres"
+ connection_string: "postgresql://postgres.[YOUR-PROJECT-REF]:$DB_PASSWORD@aws-0-[REGION].pooler.supabase.com:5432/postgres"
Then set the environment variable:
.. code-block:: bash
- # If your password is "MyPass#123", encode it as "MyPass%23123"
- export DB_PASSWORD="MyPass%23123"
+ # If your password is "P@ss#123", encode it as "P%40ss%23123"
+ export DB_PASSWORD=""
Troubleshooting
---------------
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 57952c92..7a2e5b60 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -62,4 +62,5 @@ Built by contributors to the widely adopted `Envoy Proxy ] [--foreground] [--with-tracing] [--tracing-port ]
+
+**Arguments**
+
+- ``FILE`` (optional): explicit path to config file.
+
+**Options**
+
+- ``--path ``: directory to search for config (default ``.``).
+- ``--foreground``: run Plano in foreground.
+- ``--with-tracing``: start local OTLP/gRPC trace collector.
+- ``--tracing-port ``: collector port (default ``4317``).
+
+.. note::
+
+ If you use ``--with-tracing``, ensure that port 4317 is free and not already in use by Jaeger or any other observability services or processes. If port 4317 is occupied, the command will fail to start the trace collector.
+
+**Examples**
+
+.. code-block:: console
+
+ $ planoai up config.yaml
+ $ planoai up --path ./deploy
+ $ planoai up --with-tracing
+ $ planoai up --with-tracing --tracing-port 4318
+
+
+.. _cli_reference_down:
+
+planoai down
+------------
+
+Stop Plano (container/process stack managed by the CLI).
+
+**Synopsis**
+
+.. code-block:: console
+
+ $ planoai down
+
+
+.. _cli_reference_build:
+
+planoai build
+-------------
+
+Build Plano Docker image from repository source.
+
+**Synopsis**
+
+.. code-block:: console
+
+ $ planoai build
+
+
+.. _cli_reference_logs:
+
+planoai logs
+------------
+
+Stream Plano logs.
+
+**Synopsis**
+
+.. code-block:: console
+
+ $ planoai logs [--follow] [--debug]
+
+**Options**
+
+- ``--follow``: stream logs continuously.
+- ``--debug``: include additional gateway/debug streams.
+
+**Examples**
+
+.. code-block:: console
+
+ $ planoai logs
+ $ planoai logs --follow
+ $ planoai logs --follow --debug
+
+
+.. _cli_reference_init:
+
+planoai init
+------------
+
+Generate a new ``config.yaml`` using an interactive wizard, built-in templates, or a clean empty file.
+
+**Synopsis**
+
+.. code-block:: console
+
+ $ planoai init [--template | --clean] [--output ] [--force] [--list-templates]
+
+**Options**
+
+- ``--template ``: create config from a built-in template id.
+- ``--clean``: create an empty config file.
+- ``--output, -o ``: output path (default ``config.yaml``).
+- ``--force``: overwrite existing output file.
+- ``--list-templates``: print available template IDs and exit.
+
+**Examples**
+
+.. code-block:: console
+
+ $ planoai init
+ $ planoai init --list-templates
+ $ planoai init --template coding_agent_routing
+ $ planoai init --clean --output ./config/config.yaml
+
+.. figure:: /_static/img/cli-init-command.png
+ :width: 100%
+ :alt: planoai init command screenshot
+
+ ``planoai init --list-templates`` showing built-in starter templates.
+
+
+.. _cli_reference_trace:
+
+planoai trace
+-------------
+
+Inspect request traces from the local OTLP listener.
+
+**Synopsis**
+
+.. code-block:: console
+
+ $ planoai trace [TARGET] [OPTIONS]
+
+**Targets**
+
+- ``last`` (default): show most recent trace.
+- ``any``: consider all traces (interactive selection when terminal supports it).
+- ``listen``: start local OTLP listener.
+- ``down``: stop background listener.
+- ````: full 32-hex trace id.
+- ````: first 8 hex chars of trace id.
+
+**Display options**
+
+- ``--filter ``: keep only matching attribute keys (supports ``*`` via "glob" syntax).
+- ``--where ``: locate traces containing key/value (repeatable, AND semantics).
+- ``--list``: list trace IDs instead of full trace output (use with ``--no-interactive`` to fetch plain-text trace IDs only).
+- ``--no-interactive``: disable interactive selection prompts.
+- ``--limit ``: limit returned traces.
+- ``--since ``: lookback window such as ``5m``, ``2h``, ``1d``.
+- ``--json``: emit JSON payloads.
+- ``--verbose``, ``-v``: show full attribute output (disable compact trimming). Useful for debugging internal attributes.
+
+**Listener options (for ``TARGET=listen``)**
+
+- ``--host ``: bind host (default ``0.0.0.0``).
+- ``--port ``: bind port (default ``4317``).
+
+.. note::
+
+ When using ``listen``, ensure that port 4317 is free and not already in use by Jaeger or any other observability services or processes. If port 4317 is occupied, the command will fail to start the trace collector. You cannot use other services on the same port when running.
+
+
+**Environment**
+
+- ``PLANO_TRACE_PORT``: query port used by ``planoai trace`` when reading traces (default ``4317``).
+
+**Examples**
+
+.. code-block:: console
+
+ # Start/stop listener
+ $ planoai trace listen
+ $ planoai trace down
+
+ # Basic inspection
+ $ planoai trace
+ $ planoai trace 7f4e9a1c
+ $ planoai trace 7f4e9a1c0d9d4a0bb9bf5a8a7d13f62a
+
+ # Filtering and automation
+ $ planoai trace --where llm.model=openai/gpt-5.2 --since 30m
+ $ planoai trace --filter "http.*"
+ $ planoai trace --list --limit 5
+ $ planoai trace --where http.status_code=500 --json
+
+.. figure:: /_static/img/cli-trace-command.png
+ :width: 100%
+ :alt: planoai trace command screenshot
+
+ ``planoai trace`` command showing trace inspection and filtering capabilities.
+
+**Operational notes**
+
+- ``--host`` and ``--port`` are valid only when ``TARGET`` is ``listen``.
+- ``--list`` cannot be combined with a specific trace-id target.
+
+
+.. _cli_reference_prompt_targets:
+
+planoai prompt_targets
+----------------------
+
+Generate prompt-target metadata from Python methods.
+
+**Synopsis**
+
+.. code-block:: console
+
+ $ planoai prompt_targets --file
+
+**Options**
+
+- ``--file, --f ``: required path to a ``.py`` source file.
+
+
+.. _cli_reference_cli_agent:
+
+planoai cli_agent
+-----------------
+
+Start an interactive CLI agent session against a running Plano deployment.
+
+**Synopsis**
+
+.. code-block:: console
+
+ $ planoai cli_agent claude [FILE] [--path ] [--settings '']
+
+**Arguments**
+
+- ``type``: currently ``claude``.
+- ``FILE`` (optional): config file path.
+
+**Options**
+
+- ``--path ``: directory containing config file.
+- ``--settings ``: JSON settings payload for agent startup.
diff --git a/docs/source/resources/db_setup/README.md b/docs/source/resources/db_setup/README.md
index 34aff973..2936d1d6 100644
--- a/docs/source/resources/db_setup/README.md
+++ b/docs/source/resources/db_setup/README.md
@@ -64,8 +64,8 @@ After setting up the database table, configure your application to use Supabase
**Example:**
```bash
-# If your password is "MyPass#123", encode it as "MyPass%23123"
-export DATABASE_URL="postgresql://postgres.myproject:MyPass%23123@aws-0-us-west-2.pooler.supabase.com:5432/postgres"
+# If your password is "P@ss#123", encode it as "P%40ss%23123"
+export DATABASE_URL="postgresql://postgres.[YOUR-PROJECT-REF]:@aws-0-[REGION].pooler.supabase.com:5432/postgres"
```
### Testing the Connection
diff --git a/docs/source/resources/deployment.rst b/docs/source/resources/deployment.rst
index c9c75886..7e72e578 100644
--- a/docs/source/resources/deployment.rst
+++ b/docs/source/resources/deployment.rst
@@ -25,7 +25,7 @@ Create a ``docker-compose.yml`` file with the following configuration:
# docker-compose.yml
services:
plano:
- image: katanemo/plano:0.4.8
+ image: katanemo/plano:0.4.9
container_name: plano
ports:
- "10000:10000" # ingress (client -> plano)
diff --git a/docs/source/resources/includes/agents/flights.py b/docs/source/resources/includes/agents/flights.py
index 69c93f25..069f06dd 100644
--- a/docs/source/resources/includes/agents/flights.py
+++ b/docs/source/resources/includes/agents/flights.py
@@ -28,7 +28,7 @@ EXTRACTION_MODEL = "openai/gpt-4o-mini"
# FlightAware AeroAPI configuration
AEROAPI_BASE_URL = "https://aeroapi.flightaware.com/aeroapi"
-AEROAPI_KEY = os.getenv("AEROAPI_KEY", "ESVFX7TJLxB7OTuayUv0zTQBryA3tOPr")
+AEROAPI_KEY = os.getenv("AEROAPI_KEY")
# HTTP client for API calls
http_client = httpx.AsyncClient(timeout=30.0)