diff --git a/.agents/skills/missing_docs/references/feature_surface_map.md b/.agents/skills/missing_docs/references/feature_surface_map.md index 70019d69..ccf0eb02 100644 --- a/.agents/skills/missing_docs/references/feature_surface_map.md +++ b/.agents/skills/missing_docs/references/feature_surface_map.md @@ -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 diff --git a/.agents/skills/missing_docs/scripts/audit_docs.py b/.agents/skills/missing_docs/scripts/audit_docs.py index f6c02377..e2cd9acd 100755 --- a/.agents/skills/missing_docs/scripts/audit_docs.py +++ b/.agents/skills/missing_docs/scripts/audit_docs.py @@ -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 @@ -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() @@ -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() @@ -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() @@ -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() @@ -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 @@ -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) + 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: @@ -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 /.agents/skills/missing_docs (or legacy /.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) diff --git a/src/content/docs/reference/cli/artifacts.mdx b/src/content/docs/reference/cli/artifacts.mdx new file mode 100644 index 00000000..b497da97 --- /dev/null +++ b/src/content/docs/reference/cli/artifacts.mdx @@ -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 +``` + +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 --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 [--out ] +``` + +### Flags + +* **``** - The UID of the artifact to download. Required positional argument. +* **`--out `** (`-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 +``` + +Download to a specific path: + +```sh +oz artifact download --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. diff --git a/src/content/docs/reference/cli/federate.mdx b/src/content/docs/reference/cli/federate.mdx new file mode 100644 index 00000000..4f1e0fad --- /dev/null +++ b/src/content/docs/reference/cli/federate.mdx @@ -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)). + +## `oz federate issue-token` + +Issue an OIDC identity token for the current run. + +```sh +oz federate issue-token \ + --run-id \ + --audience \ + [--duration ] \ + [--subject-template ...] +``` + +### Flags + +* **`--run-id `** - The ID of the Oz run requesting the token. The token is bound to this run. +* **`--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 `** - Requested token lifetime. Accepts human-readable durations like `15m`, `1h`, or `2h30m`. Defaults to `1h`. +* **`--subject-template ...`** - 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: + +* **`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. diff --git a/src/content/docs/reference/cli/index.mdx b/src/content/docs/reference/cli/index.mdx index 349d36fe..f37ef770 100644 --- a/src/content/docs/reference/cli/index.mdx +++ b/src/content/docs/reference/cli/index.mdx @@ -148,6 +148,40 @@ oz login The CLI prints out a URL that you can open in any browser to login to Warp. +### Checking who you're signed in as + +Use `oz whoami` to print information about the currently authenticated principal: + +```sh +oz whoami +``` + +The output includes your user or service account ID, display name, email, and team (when available). For machine-readable output, set `--output-format json` or `--output-format text`. + +```sh +# JSON output for scripts +oz whoami --output-format json + +# Compact "type:id" output +oz whoami --output-format text +``` + +If `oz whoami` reports that it could not determine your user ID, your credentials are missing or invalid. Run `oz login` (or set `WARP_API_KEY`) and try again. + +### Logging out + +Use `oz logout` to clear the CLI's cached credentials on this machine: + +```sh +oz logout +``` + +If you weren't logged in, the command prints `You are not logged in.` and exits. After logging out, run `oz login` to authenticate again. + +:::note +`oz logout` only affects credentials stored by the CLI. API keys authenticated through `WARP_API_KEY` or `--api-key` are not affected — unset the environment variable or omit the flag to stop using them. +::: + ### API key authentication Use an API key when the environment must authenticate on its own, such as CI pipelines, headless servers, VMs, Codespaces, or containers. API keys let the CLI authenticate non-interactively. @@ -367,6 +401,14 @@ List suggested base images for cloud environments: oz environment image list ``` +### `oz artifact get` / `oz artifact download` + +Inspect and retrieve files an agent produced during a run. See [Artifacts](/reference/cli/artifacts/) for details. + +### `oz federate issue-token` + +Issue a short-lived OIDC identity token from inside a running agent to authenticate to cloud providers without long-lived credentials. See [Federated identity tokens](/reference/cli/federate/) for details. + --- ## Troubleshooting diff --git a/src/sidebar.ts b/src/sidebar.ts index 56c6a105..7519fa22 100644 --- a/src/sidebar.ts +++ b/src/sidebar.ts @@ -396,6 +396,8 @@ export const sidebarTopics: StarlightSidebarTopicsUserConfig = [ { slug: 'reference/cli/skills', label: 'Skills' }, { slug: 'reference/cli/warp-drive', label: 'Warp Drive Context' }, { slug: 'reference/cli/integration-setup', label: 'Integration Setup' }, + { slug: 'reference/cli/artifacts', label: 'Artifacts' }, + { slug: 'reference/cli/federate', label: 'Federated identity' }, 'reference/cli/troubleshooting', ], },