Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions .agents/skills/missing_docs/references/feature_surface_map.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,22 @@ SkillArguments -> src/content/docs/agent-platform/warp-agents/skills.md
## CLI commands -> doc pages

# Top-level Oz CLI commands
oz agent -> src/content/docs/reference/cli/README.md
oz environment -> src/content/docs/reference/cli/integration-setup.md
oz mcp -> src/content/docs/reference/cli/mcp-servers.md
oz run -> src/content/docs/reference/cli/README.md
oz model -> src/content/docs/reference/cli/README.md
oz login -> src/content/docs/reference/cli/README.md
oz logout -> src/content/docs/reference/cli/README.md
oz integration -> src/content/docs/reference/cli/integration-setup.md
oz schedule -> src/content/docs/reference/cli/README.md
oz secret -> src/content/docs/reference/cli/README.md
oz provider -> src/content/docs/reference/cli/README.md
oz agent -> src/content/docs/reference/cli/index.mdx
oz environment -> src/content/docs/reference/cli/integration-setup.mdx
oz mcp -> src/content/docs/reference/cli/mcp-servers.mdx
oz run -> src/content/docs/reference/cli/index.mdx
oz model -> src/content/docs/reference/cli/index.mdx
oz login -> src/content/docs/reference/cli/index.mdx
oz logout -> src/content/docs/reference/cli/index.mdx
oz whoami -> src/content/docs/reference/cli/index.mdx
oz integration -> src/content/docs/reference/cli/integration-setup.mdx
oz schedule -> src/content/docs/reference/cli/index.mdx
oz secret -> src/content/docs/reference/cli/index.mdx
oz provider -> src/content/docs/reference/cli/index.mdx
oz federate -> src/content/docs/reference/cli/federate.mdx
oz artifact -> src/content/docs/reference/cli/artifacts.mdx
# Internal/hidden command — not a user-facing surface, so no public docs.
oz harness-support -> internal

## API endpoints -> doc pages

Expand Down
81 changes: 60 additions & 21 deletions .agents/skills/missing_docs/scripts/audit_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@

SKIP_DIRECTORIES = {"_book", "node_modules", ".git", ".docs"}

# Mutable holder for the docs repo root, set by main()
DOCS_REPO_ROOT: list = [None]

# Paths to reference files (relative to this script)
SCRIPT_DIR = Path(__file__).resolve().parent
SKILL_DIR = SCRIPT_DIR.parent
Expand Down Expand Up @@ -179,13 +182,17 @@ def search_docs_for_terms(docs_text: dict[str, str], terms: list[str]) -> list[s

def parse_feature_flags(warp_internal: Path) -> list[str]:
"""Parse FeatureFlag enum variants from features.rs."""
# The FeatureFlag enum lives in the warp_features crate
features_rs = warp_internal / "crates" / "warp_features" / "src" / "lib.rs"
if not features_rs.exists():
# Fall back to legacy path
features_rs = warp_internal / "warp_core" / "src" / "features.rs"
if not features_rs.exists():
print(f"Warning: {features_rs} not found", file=sys.stderr)
# Try known locations in order
candidates = [
warp_internal / "crates" / "warp_features" / "src" / "lib.rs",
warp_internal / "crates" / "warp_core" / "src" / "features.rs",
warp_internal / "app" / "src" / "features.rs",
warp_internal / "warp_core" / "src" / "features.rs",
]
features_rs = next((c for c in candidates if c.exists()), None)
if features_rs is None:
print(f"Warning: features.rs not found. Tried: {[str(c) for c in candidates]}",
file=sys.stderr)
return []

content = features_rs.read_text()
Expand Down Expand Up @@ -213,9 +220,14 @@ def parse_feature_flags(warp_internal: Path) -> list[str]:

def parse_default_features(warp_internal: Path) -> set[str]:
"""Parse the default feature list from app/Cargo.toml."""
cargo_toml = warp_internal / "app" / "Cargo.toml"
if not cargo_toml.exists():
print(f"Warning: {cargo_toml} not found", file=sys.stderr)
candidates = [
warp_internal / "app" / "Cargo.toml",
warp_internal / "crates" / "warp_features" / "Cargo.toml",
]
cargo_toml = next((c for c in candidates if c.exists()), None)
if cargo_toml is None:
print(f"Warning: app/Cargo.toml not found. Tried: {[str(c) for c in candidates]}",
file=sys.stderr)
return set()

content = cargo_toml.read_text()
Expand Down Expand Up @@ -292,8 +304,14 @@ def audit_features(warp_internal: Path, docs_root: Path, surface_map: dict,

def parse_cli_commands(warp_internal: Path) -> list[dict]:
"""Parse CLI subcommands from warp_cli/src/lib.rs."""
lib_rs = warp_internal / "warp_cli" / "src" / "lib.rs"
if not lib_rs.exists():
candidates = [
warp_internal / "crates" / "warp_cli" / "src" / "lib.rs",
warp_internal / "warp_cli" / "src" / "lib.rs",
]
lib_rs = next((c for c in candidates if c.exists()), None)
if lib_rs is None:
print(f"Warning: warp_cli/src/lib.rs not found. Tried: {[str(c) for c in candidates]}",
file=sys.stderr)
return []

content = lib_rs.read_text()
Expand Down Expand Up @@ -329,8 +347,12 @@ def parse_cli_commands(warp_internal: Path) -> list[dict]:

def parse_subcommands_from_file(warp_internal: Path, filename: str) -> list[str]:
"""Parse subcommand names from a CLI command file (e.g., agent.rs)."""
filepath = warp_internal / "warp_cli" / "src" / filename
if not filepath.exists():
candidates = [
warp_internal / "crates" / "warp_cli" / "src" / filename,
warp_internal / "warp_cli" / "src" / filename,
]
filepath = next((c for c in candidates if c.exists()), None)
if filepath is None:
return []

content = filepath.read_text()
Expand Down Expand Up @@ -366,6 +388,10 @@ def audit_cli(warp_internal: Path, docs_root: Path, surface_map: dict,
# Check surface map
if cmd_str in cli_to_doc:
doc_path = cli_to_doc[cmd_str]
# `internal` is a sentinel for hidden/internal commands that
# intentionally have no public docs (matches API audit semantics).
if doc_path == "internal":
continue
if (docs_root.parent / doc_path).exists():
continue # Mapped and exists

Expand Down Expand Up @@ -437,8 +463,13 @@ def audit_api(warp_server: Path, docs_root: Path, surface_map: dict,
except Exception:
pass

# Also check OpenAPI spec
openapi_path = docs_root / "developers" / "agent-api-openapi.yaml"
# Also check OpenAPI spec (lives at repo root, not under content/docs)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably replace this with the warp-server version, unless it's synced from there?

repo_root = DOCS_REPO_ROOT[0] or docs_root.parent
openapi_candidates = [
repo_root / "developers" / "agent-api-openapi.yaml",
docs_root / "developers" / "agent-api-openapi.yaml",
]
openapi_path = next((c for c in openapi_candidates if c.exists()), openapi_candidates[0])
openapi_text = ""
if openapi_path.exists():
try:
Expand Down Expand Up @@ -640,12 +671,20 @@ def main():
args = parser.parse_args()

# Find repos
docs_root = SKILL_DIR.parent.parent.parent # .warp/skills/missing_docs -> docs root
docs_root = docs_root / "docs"

if not docs_root.exists():
print(f"Error: docs directory not found at {docs_root}", file=sys.stderr)
# SKILL_DIR is at <repo>/.agents/skills/missing_docs (or legacy <repo>/.warp/skills/...)
repo_root = SKILL_DIR.parent.parent.parent
# Astro Starlight docs live at src/content/docs
candidates = [
repo_root / "src" / "content" / "docs",
repo_root / "docs",
]
docs_root = next((c for c in candidates if c.exists()), None)
if docs_root is None:
print(f"Error: docs directory not found. Tried: {[str(c) for c in candidates]}",
file=sys.stderr)
sys.exit(1)
# repo_root carries the developers/ openapi spec etc.
DOCS_REPO_ROOT[0] = repo_root

warp_internal = find_repo("warp-internal", args.warp_internal, docs_root)
warp_server = find_repo("warp-server", args.warp_server, docs_root)
Expand Down
77 changes: 77 additions & 0 deletions src/content/docs/reference/cli/artifacts.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
title: Artifacts
description: >-
Get metadata for and download files produced by an Oz agent run using the
`oz artifact` subcommands.
sidebar:
label: "Artifacts"
---

Artifacts are files that an agent produces during a run and uploads to Oz — screenshots, generated reports, build outputs, logs, or any other file the agent saves alongside its conversation. Use `oz artifact` to inspect those files from outside the run and pull them down to your machine.

## When to use artifacts

Use artifacts when you need to retrieve files an agent produced after a run completes, without re-running the agent or scraping the conversation output.

* **Hand-offs between runs** - A scheduled agent produces a report; a follow-up workflow downloads and processes it.
* **Local inspection** - Pull a generated file (HTML, image, CSV) onto your laptop to review.
* **CI integration** - Fetch an agent-produced build artifact from a pipeline step that runs after the agent finishes.

Artifacts are referenced by an artifact UID. You can find UIDs in the agent's run detail view, in the JSON returned by [`oz run get`](/reference/cli/), or in the response from the [Oz API](/reference/api-and-sdk/).

## `oz artifact get`

Print metadata for an artifact without downloading it.

```sh
oz artifact get <ARTIFACT_UID>
```

The output describes the artifact (file name, content type, size, the run or conversation it's associated with, and the description supplied when it was uploaded). Use `--output-format json` for a structured response that's easy to parse from a script:

```sh
oz artifact get <ARTIFACT_UID> --output-format json
```

This command is useful for confirming an artifact exists and inspecting its size before you decide to download it.

## `oz artifact download`

Download the file contents of an artifact.

```sh
oz artifact download <ARTIFACT_UID> [--out <PATH>]
```

### Flags

* **`<ARTIFACT_UID>`** - The UID of the artifact to download. Required positional argument.
* **`--out <PATH>`** (`-o`) - Write the downloaded artifact to a specific file path. When omitted, the file is written to the current directory using the artifact's stored file name.

### Examples

Download an artifact to the current directory, preserving its original name:

```sh
oz artifact download <ARTIFACT_UID>
```

Download to a specific path:

```sh
oz artifact download <ARTIFACT_UID> --out ./reports/nightly.html
```

Use the artifact in a pipeline by combining `oz run get` and `oz artifact download`:

```sh
# Pull the latest run for a scheduled agent, grab its first artifact
RUN_ID=$(oz run list --limit 1 --output-format json | jq -r '.[0].uid')
ARTIFACT_UID=$(oz run get "$RUN_ID" --output-format json | jq -r '.artifacts[0].uid')
oz artifact download "$ARTIFACT_UID" --out ./latest-report.html
```

## Related

* [Oz API & SDK](/reference/api-and-sdk/) - retrieve artifacts programmatically over HTTP.
* [Scheduled cloud agents](/agent-platform/cloud-agents/triggers/scheduled-agents/) - common producer of recurring artifacts that downstream tooling consumes.
98 changes: 98 additions & 0 deletions src/content/docs/reference/cli/federate.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
---
title: Federated identity tokens
description: >-
Issue short-lived OIDC identity tokens from a running Oz agent so it can
authenticate to cloud providers without long-lived credentials.
sidebar:
label: "Federated identity"
---

`oz federate` issues short-lived OIDC identity tokens for the agent that's currently running. Use these tokens to authenticate to cloud providers (AWS, GCP, Azure, and other OIDC-aware systems) without baking long-lived credentials into your environment.

This command can only be called from inside a running Oz agent session — typically as part of a [skill](/agent-platform/capabilities/skills/), a tool, or a script the agent executes while a run is in progress.

## When to use federation

Use federated identity tokens when you want an agent to act against a cloud account without storing service-account keys, access keys, or refresh tokens in the environment.

* **Short-lived credentials** - Tokens expire on a schedule you choose. Even if a token leaks, its blast radius is bounded.
* **No secret rotation** - Federation removes the need to rotate static keys in environments or secrets.
* **Per-run identity** - Each run can claim a different subject (user, team, environment, skill, run ID), giving you fine-grained IAM policies.

For background on federation, see your cloud provider's workload identity federation guide (for example, [Google Cloud's workload identity federation](https://cloud.google.com/iam/docs/workload-identity-federation) or [AWS IAM Identity Center](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_oidc.html)).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably link to https://docs.warp.dev/agent-platform/cloud-agents/integrations/cloud-providers/ as well here - there are streamlined paths for AWS and GCP OIDC


## `oz federate issue-token`

Issue an OIDC identity token for the current run.

```sh
oz federate issue-token \
--run-id <RUN_ID> \
--audience <AUDIENCE> \
[--duration <DURATION>] \
[--subject-template <COMPONENT> ...]
```

### Flags

* **`--run-id <RUN_ID>`** - The ID of the Oz run requesting the token. The token is bound to this run.
* **`--audience <AUDIENCE>`** - The `aud` claim for the issued token. Set this to the value your cloud provider's identity pool expects (for example, an AWS IAM Identity Center audience or a GCP workload identity pool URL).
* **`--duration <DURATION>`** - Requested token lifetime. Accepts human-readable durations like `15m`, `1h`, or `2h30m`. Defaults to `1h`.
* **`--subject-template <COMPONENT> ...`** - Controls how the OIDC token's `sub` claim is formatted. Pass one or more components, which are joined to form the subject. Defaults to `principal` (for example, `user:my-user-id`).

### Subject template components

The subject claim is what your cloud provider's policy will match on, so pick the combination that gives you the IAM granularity you need. Supported components:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OIDC token also includes all of these as structured claims, which is strongly preferable to squishing them all into the subject claim. We support this for providers like AWS that only handle the subject claim, and not any custom claims


* **`principal`** - The acting principal, like `user:my-user-id` or `service_account:my-sa-id`.
* **`scoped_principal`** - The principal scoped to a team, like `principal:my-team-id/user:my-user-id`.
* **`email`** - The principal's email, like `email:user@warp.dev`.
* **`teams`** - The principal's team, like `teams:my-team-id`.
* **`environment`** - The [cloud environment](/agent-platform/cloud-agents/environments/) the run is using, like `environment:my-environment-id`.
* **`agent_name`** - The configured name of the run, like `agent_name:my-agent`.
* **`skill_spec`** - The skill the run was launched from, like `skill_spec:warpdotdev/repo_path_to_skill`.
* **`run_id`** - The run's unique ID, like `run_id:abc123`.
* **`host`** - The self-hosted worker the run is on, like `host:my-worker-id`.

When you pass multiple components, the resulting subject joins them in the order you specified.

### Examples

Issue a one-hour token bound to the current user, for an AWS audience:

```sh
oz federate issue-token \
--run-id "$OZ_RUN_ID" \
--audience "sts.amazonaws.com"
```

Issue a 15-minute token whose subject identifies the team and environment, so a GCP IAM policy can grant access only to that pair:

```sh
oz federate issue-token \
--run-id "$OZ_RUN_ID" \
--audience "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/oz/providers/oz-oidc" \
--duration 15m \
--subject-template teams environment
```

## Using tokens with cloud providers

Once you have a token, exchange it for cloud credentials using your provider's standard OIDC federation flow. The exchange happens between the cloud provider and your script — Oz only issues the OIDC token.

A typical AWS flow:

1. Run `oz federate issue-token` to get the OIDC JWT.
2. Call `sts:AssumeRoleWithWebIdentity` with the JWT and an IAM role ARN.
3. Use the temporary AWS credentials returned by STS.

A typical GCP flow:

1. Run `oz federate issue-token` to get the OIDC JWT.
2. Call the [Security Token Service `token` endpoint](https://cloud.google.com/iam/docs/reference/sts/rest/v1/TopLevel/token) to exchange the JWT for a federated access token.
3. Optionally impersonate a service account for the final credentials.

## Related

* [Cloud environments](/agent-platform/cloud-agents/environments/) - configure the environment your agent runs in.
* [Secrets](/agent-platform/cloud-agents/secrets/) - alternative for credentials that can't be federated.
Loading
Loading