# ai-catalog-cli [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/Agent-Card/ai-catalog-cli/badge)](https://securityscorecards.dev/viewer/?uri=github.com/Agent-Card/ai-catalog-cli) Command-line tool for inspecting, validating, and packaging [AI Catalog](https://agent-card.github.io/ai-catalog/) documents. | Resource | Link | |---|---| | Specification | | | Upstream repository | | | Rust libraries | | The crate is named `ai-catalog-cli`; the installed binary is `ai-catalog`. ## Install On macOS and Linux, install from the tap in this repository: ```sh brew tap Agent-Card/ai-catalog-cli https://github.com/Agent-Card/ai-catalog-cli brew trust Agent-Card/ai-catalog-cli brew install ai-catalog ``` On Windows, install with [WinGet](https://learn.microsoft.com/windows/package-manager/winget/): ```powershell winget install --id AgentCard.AICatalog --exact ``` Open a new terminal afterwards so the `ai-catalog` command is on your `PATH`. Upgrade later with `winget upgrade --id AgentCard.AICatalog --exact`. Prebuilt binaries for Linux, macOS, and Windows are attached to each [release](https://github.com/Agent-Card/ai-catalog-cli/releases). Download the archive for your platform, verify it against the accompanying `.sha256` file, and place `ai-catalog` on your `PATH`: ```sh VERSION=v0.1.0 NAME=ai-catalog-darwin-arm64 # see the release page for other platforms BASE=https://github.com/Agent-Card/ai-catalog-cli/releases/download/$VERSION curl -fsSLO "$BASE/$NAME.tar.gz" -O "$BASE/$NAME.sha256" shasum -a 256 -c "$NAME.sha256" tar -xzf "$NAME.tar.gz" install -m 0755 ai-catalog /usr/local/bin/ ``` Linux archives come in `gnu` (glibc 2.28 or newer) and `musl` (statically linked) flavours; pick `musl` for Alpine or other non-glibc distributions. With a Rust toolchain installed: ```sh cargo install ai-catalog-cli ``` From a checkout of this repository: ```sh cargo install --path . ``` Or run without installing, replacing `ai-catalog` with `cargo run --` in any example below: ```sh cargo run -- --help ``` ## Agent skill [`SKILL.md`](SKILL.md) follows the [Agent Skills standard](https://agentskills.io) and teaches an AI agent to use this CLI to discover, inspect, and install AI artifacts from a catalog. It requires the `ai-catalog` binary on `PATH`. Each skill is loaded from a directory named after it, so install it as: ```sh mkdir -p ~/.agents/skills/ai-catalog-cli cp SKILL.md ~/.agents/skills/ai-catalog-cli/ ``` `~/.agents/skills/` is read by Cursor, Copilot, VS Code, Codex, and Gemini CLI. For Claude Code, use `~/.claude/skills/` instead. ## Development ```sh just build # cargo build just lint # fmt --check + clippy -D warnings just test # cargo test just coverage # llvm-cov summary ``` This CLI builds on the four AI Catalog library crates, released separately from [`Agent-Card/ai-catalog-rust`](https://github.com/Agent-Card/ai-catalog-rust): [`ai-catalog`](https://crates.io/crates/ai-catalog), [`ai-catalog-validate`](https://crates.io/crates/ai-catalog-validate), [`ai-catalog-trust`](https://crates.io/crates/ai-catalog-trust), and [`ai-catalog-oci`](https://crates.io/crates/ai-catalog-oci). ## CLI reference ``` ai-catalog validate [--json] ai-catalog verify [--json] [--did-document ]... [--offline] [--strict] ai-catalog format ai-catalog oci pack ai-catalog oci unpack ai-catalog oci export-layout [--tag ] [--cosign-key ] [--cosign-public-key ] [--sigstore-services ] [--signing-config ] ai-catalog oci unpack-layout [--ref-name ] ai-catalog oci push [--tag ] [--plain-http] [--insecure] [--to-oci-layout-path ] [--cosign-key ] [--cosign-public-key ] [--sigstore-services ] [--signing-config ] ai-catalog oci add [--ref-name ] ai-catalog oci search [--regex] [-n ] [--json] ai-catalog oci show [--json] [--media-type ] ai-catalog oci pull [--output ] [--media-type ] ai-catalog catalog add ai-catalog catalog list [--json] ai-catalog catalog remove ai-catalog catalog update ai-catalog search [--regex] [-n ] [--json] ai-catalog show [--scope ] [--json] [--media-type ] ai-catalog pull [--output ] [--scope ] [--media-type ] ai-catalog --version ``` Use `-` as `` to read from stdin. Every command accepts `--help`. --- ### `validate` Validates a catalog document against the AI Catalog specification and reports its structural conformance level (Minimal or Discoverable). The Trusted level requires verifying signatures and artifacts; use [`verify`](#verify) for that. ```sh ai-catalog validate catalog.json # text report ai-catalog validate --json catalog.json # JSON report cat catalog.json | ai-catalog validate - # from stdin ``` Exits with code `0` on success, `1` on validation errors. With `--json`, each error and warning has a stable `code` (for example `entry-duplicate`) next to its `path` and `message`. Match on the code rather than the message. `code` is `null` when the input could not be parsed. --- ### `verify` Validates a catalog and verifies the signatures and artifacts of its root entries, using `ai_catalog_validate::verify`. ```sh ai-catalog verify catalog.json ai-catalog verify --json catalog.json ai-catalog verify --offline --did-document did:web:example.com=did.json catalog.json ``` For each trust manifest it reports the `did-web-v1` signature (signer, protected header, `assertionMethod` key in the signer's root `did:web` document, validity period, and the detached ES256 JWS) and the release binding of its `subject` to the entry. For each entry it reports whether the publisher endorsed it and, once a manifest is accepted, whether the artifact matches `digest`. The catalog's conformance level is Trusted only when an entry has a verified publisher endorsement and none fails. DID documents and artifacts are fetched over HTTPS only, without redirects, with size and time limits, and refusing private, loopback, and link-local addresses. `--did-document DID=PATH` supplies a DID document locally, and `--offline` never fetches. Each check is `verified`, `failed`, or `unverifiable` (it could not run, for example a fetch skipped with `--offline`). Exits with `1` when the catalog is invalid or any check fails; with `--strict`, also when a check is unverifiable. --- ### `format` Pretty-prints a catalog document to stdout without modifying the source file. ```sh ai-catalog format catalog.json cat catalog.json | ai-catalog format - ``` --- ### `catalog add` Fetches a catalog from a URL, a domain or a local file, stores all catalog blobs in the local content-addressed cache, and registers the catalog in the local registry. Nested catalogs are fetched recursively (up to depth 4). ```sh ai-catalog catalog add my-registry https://example.com/ai-catalog.json ai-catalog catalog add example https://example.com # or just: example.com ai-catalog catalog add local-demo file:///path/to/catalog.json ``` Makes network requests. All other consumer commands operate on the local cache. A domain or an `https://` URL does not have to be the catalog itself. If it isn't one, the CLI follows the specification's [discovery procedure](https://ai-catalog.io/spec/#discovery): a `Link` header with `rel="ai-catalog"`, then an HTML `` element, then `/.well-known/ai-catalog.json`. The catalog that is found is the one that gets registered, under its own URL, which is what `catalog update` fetches again. If nothing is found, the error lists every URL that was tried. Files and `http://` URLs are used as they are, without discovery. References inside a catalog must be `https` (or relative to the catalog). A local `file://` path is followed only from a local catalog, so a remote catalog can't read your disk. Cycles, `http://` references and nesting beyond depth 4 are skipped with a warning, and catalogs are limited to 10 MiB. --- ### `catalog list` Lists all catalogs registered in the local registry. ```sh ai-catalog catalog list ai-catalog catalog list --json ``` --- ### `catalog update` Re-fetches a registered catalog from its source URL and refreshes the local cache. Accepts a catalog name. ```sh ai-catalog catalog update my-registry ``` --- ### `catalog remove` Removes a catalog from the local registry by name or source URL. Also removes the cached object blob if no other registered catalog references it. ```sh ai-catalog catalog remove my-registry ai-catalog catalog remove https://example.com/ai-catalog.json ``` --- ### `search` Searches entries across all registered catalogs (all sources). ```sh ai-catalog search "finance agent" # substring match ai-catalog search --regex "urn:example:(agent|data).*" ai-catalog search --json dataset # JSON output ai-catalog search -n 5 embeddings # limit to 5 results ``` Matches against `identifier`, `displayName`, `description`, and `tags`. Default limit is 50. --- ### `show` Shows full details of a single catalog entry by identifier. ```sh ai-catalog show urn:example:agent:v1 ai-catalog show --json urn:example:agent:v1 ai-catalog show --scope my-registry urn:example:agent:v1 # restrict to one catalog ai-catalog show --scope https://example.com/ai-catalog.json urn:example:agent:v1 ai-catalog show --media-type application/json urn:example:catalog:nested ``` `--scope` accepts a registered catalog name or source, or a catalog URL, which is fetched and cached on the fly without registering it. For an entry that is itself a nested catalog, `--media-type` resolves its children and shows the single child of that type. Omitting it, or passing `application/ai-catalog+json`, shows the catalog entry itself. --- ### `pull` Downloads an entry's content to disk. For nested-catalog entries the full catalog JSON is written; for other types the raw bytes from `entry.url` are fetched. An `http(s)` URL given as the identifier is fetched as a catalog if it is not registered. ```sh ai-catalog pull urn:example:data:dataset-v1 ai-catalog pull --output ./downloads urn:example:data:dataset-v1 ai-catalog pull --output ./report.json urn:example:data:dataset-v1 ai-catalog pull --output - urn:example:data:dataset-v1 # stream to stdout ai-catalog pull --scope https://example.com/ai-catalog.json urn:example:agent:v1 ``` If `--output` is a directory, a filename is derived from the identifier. If it is a file path, that path is used directly. `-` streams the artifact bytes to stdout. Omitting `--output` writes to the current directory. `--scope` follows the same name-or-URI rules as `show`. If the entry has a `digest`, the content is checked against it first, and a mismatch fails the pull. Signatures are not verified. When the identifier resolves to a nested catalog entry (`application/ai-catalog+json`), `--media-type` is required: | `--media-type` | Result | |---|---| | `application/ai-catalog+json` | Writes the catalog JSON document itself | | any other type | Pulls the single child entry of that type; errors on 0 or more than 1 match | | omitted | Errors, suggesting `--media-type` | --- ### `oci pack` Packs a catalog into the internal JSON artifact-set envelope used by the Rust library. Useful for debugging or pipeline integration when you need to inspect how the CLI represents a catalog before pushing it to an OCI registry. ```sh ai-catalog oci pack catalog.json cat catalog.json | ai-catalog oci pack - ``` Output is JSON written to stdout. --- ### `oci unpack` Unpacks an internal JSON artifact-set envelope back into AI Catalog JSON. ```sh ai-catalog oci unpack artifacts.json ``` Output is JSON written to stdout. --- ### `oci export-layout` Exports a catalog as a standard [OCI image layout](https://github.com/opencontainers/image-spec/blob/main/image-layout.md) directory. Each catalog entry is stored as a separate OCI manifest; the catalog itself becomes an OCI image index tagged with `--tag`. ```sh ai-catalog oci export-layout catalog.json /path/to/layout ai-catalog oci export-layout --tag v1.0 catalog.json /path/to/layout ``` With Cosign signing (see [Trust manifest signing](#trust-manifest-signing)): ```sh ai-catalog oci export-layout \ --cosign-key cosign.key \ --cosign-public-key cosign.pub \ catalog.json /path/to/layout ``` | Flag | Description | |---|---| | `--tag ` | OCI tag to apply (default: `latest`) | | `--cosign-key ` | Path to Cosign private key; triggers signing of trust manifests | | `--cosign-public-key ` | Path to PEM public key (derived from `--cosign-key` if omitted) | | `--sigstore-services ` | Public Sigstore services to sign with, comma-separated: `tlog` (Rekor transparency log), `tsa` (RFC 3161 timestamp). Default: none | | `--signing-config ` | Cosign signing config to use instead, for example a private Sigstore deployment | Reads from stdin when `` is `-`. --- ### `oci unpack-layout` Imports a standard OCI image layout back into AI Catalog JSON. Prints the reconstructed catalog to stdout. ```sh ai-catalog oci unpack-layout /path/to/layout ai-catalog oci unpack-layout --ref-name v1.0 /path/to/layout ``` | Flag | Description | |---|---| | `--ref-name ` | Tag to import (default: first entry in `index.json`) | --- ### `oci push` Pushes a catalog to an OCI registry. Internally calls `oci export-layout` into a temporary directory, then delegates distribution to `oras cp -r`. ```sh ai-catalog oci push catalog.json ghcr.io/example/ai-catalog:latest ai-catalog oci push --tag v1.0 catalog.json ghcr.io/example/ai-catalog:v1.0 ``` With signing and registry options: ```sh ai-catalog oci push \ --cosign-key cosign.key \ --cosign-public-key cosign.pub \ catalog.json ghcr.io/example/ai-catalog:latest ``` | Flag | Description | |---|---| | `--tag ` | OCI tag (default: `latest`) | | `--plain-http` | Use plain HTTP instead of HTTPS | | `--insecure` | Skip TLS certificate verification | | `--to-oci-layout-path ` | Also write the exported layout to this directory | | `--cosign-key ` | Path to Cosign private key | | `--cosign-public-key ` | Path to PEM public key | | `--sigstore-services ` | Public Sigstore services to sign with, comma-separated: `tlog` (Rekor transparency log), `tsa` (RFC 3161 timestamp). Default: none | | `--signing-config ` | Cosign signing config to use instead, for example a private Sigstore deployment | Reads from stdin when `` is `-`. Requires `oras` on `PATH`. --- ### `oci add` Imports a local OCI image layout into the local registry. Unpacks all catalog entries from the layout and registers the catalog with a `urn:ai-catalog:oci:` identifier prefix. ```sh ai-catalog oci add my-layout /path/to/layout ai-catalog oci add my-layout /path/to/layout --ref-name v1.0 ``` | Flag | Description | |---|---| | `--ref-name ` | Tag to import from the layout (default: first entry) | --- ### `oci search` Searches entries across OCI-sourced catalogs only (those added via `oci add`). Accepts the same flags as `search`. ```sh ai-catalog oci search embeddings ai-catalog oci search --regex "^urn:ai-catalog:oci:" ai-catalog oci search --json nlp ai-catalog oci search -n 10 agent ``` --- ### `oci show` Shows full details of an entry from OCI-sourced catalogs only. Accepts the same `--media-type` flag as `show`. ```sh ai-catalog oci show urn:ai-catalog:oci:abc12345 ai-catalog oci show --json urn:ai-catalog:oci:abc12345 ai-catalog oci show --media-type application/json urn:ai-catalog:oci:abc12345 ``` --- ### `oci pull` Pulls an entry from OCI-sourced catalogs to disk. Accepts the same `--output` and `--media-type` flags as `pull`. ```sh ai-catalog oci pull urn:ai-catalog:oci:abc12345 ai-catalog oci pull --output ./artifact.json urn:ai-catalog:oci:abc12345 ai-catalog oci pull --media-type application/json urn:ai-catalog:oci:abc12345 ``` --- ## Local storage The local registry lives at `~/.ai-catalog/` by default. Override with the `AI_CATALOG_CACHE_DIR` environment variable. ``` ~/.ai-catalog/ ├── catalog.json # registry index (AiCatalog document) ├── refs.json # source URL → SHA-256 hash map └── objects/ └── .json # cached catalog blobs ``` Entries added via `catalog add` are stored under their source URL and use the `urn:ai-catalog:local:` convention. Entries added via `oci add` use the `urn:ai-catalog:oci:` prefix and are scoped separately. The plain `search`, `show`, and `pull` commands span all sources; the `oci search / show / pull` variants are restricted to OCI-sourced entries. See [`docs/storage.md`](docs/storage.md) for details. --- ## Trust manifest signing When `--cosign-key` is supplied to `oci export-layout` or `oci push`, the CLI: 1. Canonicalizes each entry trust manifest (key-sorted JSON, `signature.jws` and `additionalSignatures` stripped) 2. Signs the canonical blob with `cosign sign-blob` and a Sigstore signing config that lists only the services you choose. By default it lists none, so nothing leaves the machine. `--sigstore-services tlog` records each signature in the public Rekor log (permanent and public); `--sigstore-services tsa` adds an RFC 3161 timestamp from the Sigstore timestamp authority, which receives only a hash. `--signing-config ` passes your own config, for example a private Rekor or timestamp authority. Log entries and timestamps are kept in the Cosign bundle stored with each signature. 3. Attaches three OCI referrer artifacts to each signed entry: - the canonical trust manifest (`application/vnd.ai-catalog.trust-manifest.v1+json`) - the detached Cosign signature (`application/vnd.ai-catalog.cosign.signature.v1`), with the Sigstore bundle from `cosign sign-blob --bundle` as a second layer (`application/vnd.dev.sigstore.bundle.v0.3+json`) - the public key (`application/vnd.ai-catalog.cosign.public-key.v1`) Signatures live in the OCI layout as referrers, not embedded in the AI Catalog JSON. Use `oras discover` to inspect the referrer tree and `cosign verify-blob --key cosign.pub --bundle ` to verify. A bundle signed without a transparency-log entry also needs `--insecure-ignore-tlog=true`. See [`demo/trust-walkthrough.sh`](demo/trust-walkthrough.sh) for a complete end-to-end example. --- ## Environment variables | Variable | Description | |---|---| | `AI_CATALOG_CACHE_DIR` | Override the default cache directory (`~/.ai-catalog/`) | | `AI_CATALOG_COSIGN_BIN` | Path to the `cosign` binary (default: `cosign`) | | `AI_CATALOG_ORAS_BIN` | Path to the `oras` binary (default: `oras`) | | `COSIGN_PASSWORD` | Password for an encrypted Cosign private key | --- ## Demo All demos create a temporary workspace and clean up on exit. | Demo | Command | Prerequisites | |---|---|---| | OCI image layout | `just demo-oci-layout` | `cargo`, `cosign`, `oras` | | Consumer workflow | `just demo-consumer` | `cargo` | | Trust sign & verify | `just demo-trust` | `cargo`, `cosign`, `oras` | ### OCI layout walkthrough (`just demo-oci-layout`) Exercises the full OCI publish/verify flow: validate → generate Cosign key pair → `oci export-layout --cosign-key` → `oras discover` referrers → print signature and public key → `oci unpack-layout` round-trip → `oci push` to a second layout. Script: [`demo/oci-layout-walkthrough.sh`](demo/oci-layout-walkthrough.sh). ### Consumer workflow walkthrough (`just demo-consumer`) Exercises every author and consumer command without external tools or network calls: validate, format, catalog add/list/update/remove, search (keyword, regex, JSON, limit), show (text, JSON, scoped), pull (inline-data and file-URL entries), and the full OCI consumer path (oci add, oci search, oci show, oci pull). Script: [`demo/consumer-walkthrough.sh`](demo/consumer-walkthrough.sh). ### Trust sign & verify walkthrough (`just demo-trust`) Demonstrates the complete trust manifest lifecycle: validate → generate Cosign key pair → sign via `oci export-layout --cosign-key` → `oras discover` referrer tree → extract canonical trust manifest and detached signature → `cosign verify-blob` → tamper detection → `oci unpack-layout` round-trip. Script: [`demo/trust-walkthrough.sh`](demo/trust-walkthrough.sh). This walkthrough is POSIX only; there is no PowerShell port yet. --- ## Governance | File | Purpose | |---|---| | `LICENSE` | Apache License 2.0 | | `CONTRIBUTING.md` | Contribution workflow and local checks | | `CODE_OF_CONDUCT.md` | Collaboration expectations | | `SECURITY.md` | Vulnerability reporting guidance | | `GOVERNANCE.md` | Project decision-making and branch policy |