Skip to content

Commit 7616135

Browse files
hongyi-chenclaudeoz-agent
committed
docs(cli): document oz logout, whoami, federate, and artifact commands
The missing_docs audit flagged five undocumented CLI commands. This adds coverage for the four user-facing ones (logout, whoami, federate, artifact) and marks oz harness-support as internal so it stops being flagged. * Extend reference/cli/index.mdx with `oz whoami` and `oz logout` next to the existing `oz login` section, plus pointers from "Additional commands" to the two new dedicated pages. * Add reference/cli/federate.mdx covering `oz federate issue-token`, including the supported subject-template components and AWS/GCP exchange flows. * Add reference/cli/artifacts.mdx covering `oz artifact get` and `oz artifact download`. * Wire both new pages into the CLI sidebar. * Fix the audit script for the current repo layout (Astro `src/content/docs`, `crates/warp_features`, `crates/warp_cli`) and let CLI surface-map entries use the same `internal` sentinel the API audit already supports. * Update the surface map: refresh CLI mappings to point at real `.mdx` paths and mark `oz harness-support` as internal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent d59ef0d commit 7616135

6 files changed

Lines changed: 295 additions & 32 deletions

File tree

.agents/skills/missing_docs/references/feature_surface_map.md

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -137,17 +137,22 @@ SkillArguments -> src/content/docs/agent-platform/warp-agents/skills.md
137137
## CLI commands -> doc pages
138138

139139
# Top-level Oz CLI commands
140-
oz agent -> src/content/docs/reference/cli/README.md
141-
oz environment -> src/content/docs/reference/cli/integration-setup.md
142-
oz mcp -> src/content/docs/reference/cli/mcp-servers.md
143-
oz run -> src/content/docs/reference/cli/README.md
144-
oz model -> src/content/docs/reference/cli/README.md
145-
oz login -> src/content/docs/reference/cli/README.md
146-
oz logout -> src/content/docs/reference/cli/README.md
147-
oz integration -> src/content/docs/reference/cli/integration-setup.md
148-
oz schedule -> src/content/docs/reference/cli/README.md
149-
oz secret -> src/content/docs/reference/cli/README.md
150-
oz provider -> src/content/docs/reference/cli/README.md
140+
oz agent -> src/content/docs/reference/cli/index.mdx
141+
oz environment -> src/content/docs/reference/cli/integration-setup.mdx
142+
oz mcp -> src/content/docs/reference/cli/mcp-servers.mdx
143+
oz run -> src/content/docs/reference/cli/index.mdx
144+
oz model -> src/content/docs/reference/cli/index.mdx
145+
oz login -> src/content/docs/reference/cli/index.mdx
146+
oz logout -> src/content/docs/reference/cli/index.mdx
147+
oz whoami -> src/content/docs/reference/cli/index.mdx
148+
oz integration -> src/content/docs/reference/cli/integration-setup.mdx
149+
oz schedule -> src/content/docs/reference/cli/index.mdx
150+
oz secret -> src/content/docs/reference/cli/index.mdx
151+
oz provider -> src/content/docs/reference/cli/index.mdx
152+
oz federate -> src/content/docs/reference/cli/federate.mdx
153+
oz artifact -> src/content/docs/reference/cli/artifacts.mdx
154+
# Internal/hidden command — not a user-facing surface, so no public docs.
155+
oz harness-support -> internal
151156

152157
## API endpoints -> doc pages
153158

.agents/skills/missing_docs/scripts/audit_docs.py

Lines changed: 60 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424

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

27+
# Mutable holder for the docs repo root, set by main()
28+
DOCS_REPO_ROOT: list = [None]
29+
2730
# Paths to reference files (relative to this script)
2831
SCRIPT_DIR = Path(__file__).resolve().parent
2932
SKILL_DIR = SCRIPT_DIR.parent
@@ -179,13 +182,17 @@ def search_docs_for_terms(docs_text: dict[str, str], terms: list[str]) -> list[s
179182

180183
def parse_feature_flags(warp_internal: Path) -> list[str]:
181184
"""Parse FeatureFlag enum variants from features.rs."""
182-
# The FeatureFlag enum lives in the warp_features crate
183-
features_rs = warp_internal / "crates" / "warp_features" / "src" / "lib.rs"
184-
if not features_rs.exists():
185-
# Fall back to legacy path
186-
features_rs = warp_internal / "warp_core" / "src" / "features.rs"
187-
if not features_rs.exists():
188-
print(f"Warning: {features_rs} not found", file=sys.stderr)
185+
# Try known locations in order
186+
candidates = [
187+
warp_internal / "crates" / "warp_features" / "src" / "lib.rs",
188+
warp_internal / "crates" / "warp_core" / "src" / "features.rs",
189+
warp_internal / "app" / "src" / "features.rs",
190+
warp_internal / "warp_core" / "src" / "features.rs",
191+
]
192+
features_rs = next((c for c in candidates if c.exists()), None)
193+
if features_rs is None:
194+
print(f"Warning: features.rs not found. Tried: {[str(c) for c in candidates]}",
195+
file=sys.stderr)
189196
return []
190197

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

214221
def parse_default_features(warp_internal: Path) -> set[str]:
215222
"""Parse the default feature list from app/Cargo.toml."""
216-
cargo_toml = warp_internal / "app" / "Cargo.toml"
217-
if not cargo_toml.exists():
218-
print(f"Warning: {cargo_toml} not found", file=sys.stderr)
223+
candidates = [
224+
warp_internal / "app" / "Cargo.toml",
225+
warp_internal / "crates" / "warp_features" / "Cargo.toml",
226+
]
227+
cargo_toml = next((c for c in candidates if c.exists()), None)
228+
if cargo_toml is None:
229+
print(f"Warning: app/Cargo.toml not found. Tried: {[str(c) for c in candidates]}",
230+
file=sys.stderr)
219231
return set()
220232

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

293305
def parse_cli_commands(warp_internal: Path) -> list[dict]:
294306
"""Parse CLI subcommands from warp_cli/src/lib.rs."""
295-
lib_rs = warp_internal / "warp_cli" / "src" / "lib.rs"
296-
if not lib_rs.exists():
307+
candidates = [
308+
warp_internal / "crates" / "warp_cli" / "src" / "lib.rs",
309+
warp_internal / "warp_cli" / "src" / "lib.rs",
310+
]
311+
lib_rs = next((c for c in candidates if c.exists()), None)
312+
if lib_rs is None:
313+
print(f"Warning: warp_cli/src/lib.rs not found. Tried: {[str(c) for c in candidates]}",
314+
file=sys.stderr)
297315
return []
298316

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

330348
def parse_subcommands_from_file(warp_internal: Path, filename: str) -> list[str]:
331349
"""Parse subcommand names from a CLI command file (e.g., agent.rs)."""
332-
filepath = warp_internal / "warp_cli" / "src" / filename
333-
if not filepath.exists():
350+
candidates = [
351+
warp_internal / "crates" / "warp_cli" / "src" / filename,
352+
warp_internal / "warp_cli" / "src" / filename,
353+
]
354+
filepath = next((c for c in candidates if c.exists()), None)
355+
if filepath is None:
334356
return []
335357

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

@@ -437,8 +463,13 @@ def audit_api(warp_server: Path, docs_root: Path, surface_map: dict,
437463
except Exception:
438464
pass
439465

440-
# Also check OpenAPI spec
441-
openapi_path = docs_root / "developers" / "agent-api-openapi.yaml"
466+
# Also check OpenAPI spec (lives at repo root, not under content/docs)
467+
repo_root = DOCS_REPO_ROOT[0] or docs_root.parent
468+
openapi_candidates = [
469+
repo_root / "developers" / "agent-api-openapi.yaml",
470+
docs_root / "developers" / "agent-api-openapi.yaml",
471+
]
472+
openapi_path = next((c for c in openapi_candidates if c.exists()), openapi_candidates[0])
442473
openapi_text = ""
443474
if openapi_path.exists():
444475
try:
@@ -640,12 +671,20 @@ def main():
640671
args = parser.parse_args()
641672

642673
# Find repos
643-
docs_root = SKILL_DIR.parent.parent.parent # .warp/skills/missing_docs -> docs root
644-
docs_root = docs_root / "docs"
645-
646-
if not docs_root.exists():
647-
print(f"Error: docs directory not found at {docs_root}", file=sys.stderr)
674+
# SKILL_DIR is at <repo>/.agents/skills/missing_docs (or legacy <repo>/.warp/skills/...)
675+
repo_root = SKILL_DIR.parent.parent.parent
676+
# Astro Starlight docs live at src/content/docs
677+
candidates = [
678+
repo_root / "src" / "content" / "docs",
679+
repo_root / "docs",
680+
]
681+
docs_root = next((c for c in candidates if c.exists()), None)
682+
if docs_root is None:
683+
print(f"Error: docs directory not found. Tried: {[str(c) for c in candidates]}",
684+
file=sys.stderr)
648685
sys.exit(1)
686+
# repo_root carries the developers/ openapi spec etc.
687+
DOCS_REPO_ROOT[0] = repo_root
649688

650689
warp_internal = find_repo("warp-internal", args.warp_internal, docs_root)
651690
warp_server = find_repo("warp-server", args.warp_server, docs_root)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
title: Artifacts
3+
description: >-
4+
Get metadata for and download files produced by an Oz agent run using the
5+
`oz artifact` subcommands.
6+
sidebar:
7+
label: "Artifacts"
8+
---
9+
10+
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.
11+
12+
## When to use artifacts
13+
14+
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.
15+
16+
* **Hand-offs between runs** - A scheduled agent produces a report; a follow-up workflow downloads and processes it.
17+
* **Local inspection** - Pull a generated file (HTML, image, CSV) onto your laptop to review.
18+
* **CI integration** - Fetch an agent-produced build artifact from a pipeline step that runs after the agent finishes.
19+
20+
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/).
21+
22+
## `oz artifact get`
23+
24+
Print metadata for an artifact without downloading it.
25+
26+
```sh
27+
oz artifact get <ARTIFACT_UID>
28+
```
29+
30+
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:
31+
32+
```sh
33+
oz artifact get <ARTIFACT_UID> --output-format json
34+
```
35+
36+
This command is useful for confirming an artifact exists and inspecting its size before you decide to download it.
37+
38+
## `oz artifact download`
39+
40+
Download the file contents of an artifact.
41+
42+
```sh
43+
oz artifact download <ARTIFACT_UID> [--out <PATH>]
44+
```
45+
46+
### Flags
47+
48+
* **`<ARTIFACT_UID>`** - The UID of the artifact to download. Required positional argument.
49+
* **`--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.
50+
51+
### Examples
52+
53+
Download an artifact to the current directory, preserving its original name:
54+
55+
```sh
56+
oz artifact download <ARTIFACT_UID>
57+
```
58+
59+
Download to a specific path:
60+
61+
```sh
62+
oz artifact download <ARTIFACT_UID> --out ./reports/nightly.html
63+
```
64+
65+
Use the artifact in a pipeline by combining `oz run get` and `oz artifact download`:
66+
67+
```sh
68+
# Pull the latest run for a scheduled agent, grab its first artifact
69+
RUN_ID=$(oz run list --limit 1 --output-format json | jq -r '.[0].uid')
70+
ARTIFACT_UID=$(oz run get "$RUN_ID" --output-format json | jq -r '.artifacts[0].uid')
71+
oz artifact download "$ARTIFACT_UID" --out ./latest-report.html
72+
```
73+
74+
## Related
75+
76+
* [Oz API & SDK](/reference/api-and-sdk/) - retrieve artifacts programmatically over HTTP.
77+
* [Scheduled cloud agents](/agent-platform/cloud-agents/triggers/scheduled-agents/) - common producer of recurring artifacts that downstream tooling consumes.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
title: Federated identity tokens
3+
description: >-
4+
Issue short-lived OIDC identity tokens from a running Oz agent so it can
5+
authenticate to cloud providers without long-lived credentials.
6+
sidebar:
7+
label: "Federated identity"
8+
---
9+
10+
`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.
11+
12+
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.
13+
14+
## When to use federation
15+
16+
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.
17+
18+
* **Short-lived credentials** - Tokens expire on a schedule you choose. Even if a token leaks, its blast radius is bounded.
19+
* **No secret rotation** - Federation removes the need to rotate static keys in environments or secrets.
20+
* **Per-run identity** - Each run can claim a different subject (user, team, environment, skill, run ID), giving you fine-grained IAM policies.
21+
22+
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)).
23+
24+
## `oz federate issue-token`
25+
26+
Issue an OIDC identity token for the current run.
27+
28+
```sh
29+
oz federate issue-token \
30+
--run-id <RUN_ID> \
31+
--audience <AUDIENCE> \
32+
[--duration <DURATION>] \
33+
[--subject-template <COMPONENT> ...]
34+
```
35+
36+
### Flags
37+
38+
* **`--run-id <RUN_ID>`** - The ID of the Oz run requesting the token. The token is bound to this run.
39+
* **`--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).
40+
* **`--duration <DURATION>`** - Requested token lifetime. Accepts human-readable durations like `15m`, `1h`, or `2h30m`. Defaults to `1h`.
41+
* **`--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`).
42+
43+
### Subject template components
44+
45+
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:
46+
47+
* **`principal`** - The acting principal, like `user:my-user-id` or `service_account:my-sa-id`.
48+
* **`scoped_principal`** - The principal scoped to a team, like `principal:my-team-id/user:my-user-id`.
49+
* **`email`** - The principal's email, like `email:user@warp.dev`.
50+
* **`teams`** - The principal's team, like `teams:my-team-id`.
51+
* **`environment`** - The [cloud environment](/agent-platform/cloud-agents/environments/) the run is using, like `environment:my-environment-id`.
52+
* **`agent_name`** - The configured name of the run, like `agent_name:my-agent`.
53+
* **`skill_spec`** - The skill the run was launched from, like `skill_spec:warpdotdev/repo_path_to_skill`.
54+
* **`run_id`** - The run's unique ID, like `run_id:abc123`.
55+
* **`host`** - The self-hosted worker the run is on, like `host:my-worker-id`.
56+
57+
When you pass multiple components, the resulting subject joins them in the order you specified.
58+
59+
### Examples
60+
61+
Issue a one-hour token bound to the current user, for an AWS audience:
62+
63+
```sh
64+
oz federate issue-token \
65+
--run-id "$OZ_RUN_ID" \
66+
--audience "sts.amazonaws.com"
67+
```
68+
69+
Issue a 15-minute token whose subject identifies the team and environment, so a GCP IAM policy can grant access only to that pair:
70+
71+
```sh
72+
oz federate issue-token \
73+
--run-id "$OZ_RUN_ID" \
74+
--audience "//iam.googleapis.com/projects/123/locations/global/workloadIdentityPools/oz/providers/oz-oidc" \
75+
--duration 15m \
76+
--subject-template teams environment
77+
```
78+
79+
## Using tokens with cloud providers
80+
81+
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.
82+
83+
A typical AWS flow:
84+
85+
1. Run `oz federate issue-token` to get the OIDC JWT.
86+
2. Call `sts:AssumeRoleWithWebIdentity` with the JWT and an IAM role ARN.
87+
3. Use the temporary AWS credentials returned by STS.
88+
89+
A typical GCP flow:
90+
91+
1. Run `oz federate issue-token` to get the OIDC JWT.
92+
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.
93+
3. Optionally impersonate a service account for the final credentials.
94+
95+
## Related
96+
97+
* [Cloud environments](/agent-platform/cloud-agents/environments/) - configure the environment your agent runs in.
98+
* [Secrets](/agent-platform/cloud-agents/secrets/) - alternative for credentials that can't be federated.

0 commit comments

Comments
 (0)