# ai-catalog-cli
[](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 |