Directory CLI Command Reference
Command-line reference for dirctl. Install the CLI in the Quickstart
only; workflow examples live in Features and Usage Scenarios.
Global options
Output formats
Most data-producing commands support --output / -o. Exceptions include validate, context, auth, daemon, mcp serve, and version.
| Format | Description | Use case |
|---|---|---|
human |
Formatted tables and colors (default) | Interactive use |
json |
Pretty-printed JSON | Debugging, single-record scripts |
jsonl |
One JSON object per line | Streaming, batch processing |
raw |
Values only (CIDs, IDs) | Shell pipelines |
Structured formats send data to stdout and messages to stderr, so piping to jq
works cleanly.
CID=$(dirctl push record.json -o raw)
dirctl routing search --skill "AI" -o json | jq '.[] | .record_ref.cid'
dirctl events listen -o jsonl | jq -c 'select(.type == "EVENT_TYPE_RECORD_PUSHED")'
Server connection
dirctl --server-addr localhost:8888 routing list
export DIRECTORY_CLIENT_SERVER_ADDRESS=localhost:8888
dirctl --context dev routing list # see Context Operations below
Authentication
For remote servers, authenticate with OIDC before running commands. The full auth model, gateway endpoints, and SPIFFE/SPIRE integration are documented in OIDC Authentication.
| Command | Description |
|---|---|
dirctl auth login |
OIDC login (PKCE, --no-browser, or --device) |
dirctl auth logout |
Clear cached credentials |
dirctl auth status |
Show current auth state |
dirctl auth login --oidc-issuer "https://idp.ads.outshift.io" --oidc-client-id "dirctl"
Set `oidc_scopes` in the context (or `DIRECTORY_CLIENT_OIDC_SCOPES` / `--oidc-scopes`) to request extra claims. After changing scopes, run `dirctl auth login --force` so the cached token is re-minted.
dirctl --auth-mode=oidc --server-addr ads.outshift.io:443 search --skill "AI"
Pre-issued tokens: --auth-token or DIRECTORY_CLIENT_AUTH_TOKEN for CI and automation.
A pre-issued token cannot be renewed, so a command that runs longer than the token's lifetime
fails partway through. Inside GitHub Actions, prefer --oidc-audience (with job permission
id-token: write) and omit --auth-token: dirctl then mints its own tokens and renews them
for as long as the command runs.
Other modes: --auth-mode=x509, jwt, token, tls, oidc, insecure, or none. When
--auth-mode is empty, the client auto-detects OIDC (cached token, issuer, or client ID) and
falls back to insecure for local development. SPIFFE modes (x509, jwt, token) require an
explicit --auth-mode.
Command groups
| Group | Commands |
|---|---|
| Setup | init |
| Daemon | daemon start, stop, status, config init |
| Auth | auth login, logout, status |
| Context | context list, current, set, show, validate |
| Storage | push, pull, delete, info |
| Import / Export | import, export |
| Routing | routing publish, unpublish, list, search, info |
| Search | search |
| Security | sign, verify, validate, naming verify |
| Sync | sync create, status, list, delete |
| Events | events listen |
| MCP | mcp serve |
| Install | install run, install uninstall (or top-level uninstall), install list, install agents, install outdated, install upgrade, install pin, install unpin, install prune |
| Diagnostics | doctor, version |
Getting help
dirctl --help
dirctl routing --help
dirctl routing search --help
Setup
dirctl init [flags]
Guided first-run setup for a new environment — run it once after installing
dirctl. It provisions the OASF taxonomy extractor: a small
sentence-transformer model (all-MiniLM-L6-v2, ~89 MB) plus the OASF taxonomy,
downloaded to a local asset directory. Those assets power local, LLM-free
record enrichment and natural language search that runs in-process — no
Python, no external inference service, no LLM API. The chosen OASF endpoint and
asset directory are saved to the dirctl config so other commands load the
provisioned assets automatically.
Provisioning is opt-out in an interactive terminal — the prompt defaults to
yes, so pressing Enter provisions. To avoid an unattended ~89 MB download, a
non-interactive run (no TTY, e.g. CI or a pipe) skips unless you pass
--yes. Re-running is idempotent: nothing is re-downloaded when the assets are
present and current, and the taxonomy is re-embedded only when it changed.
Its last step wires this Directory into your AI coding agents: an MCP server
entry so an agent can push, search, and pull records, plus the DIR skill as a
usage guide. Both come from a record built into the dirctl binary —
org.agntcy/directory — so no Directory connection is made, and the MCP entry
carries the DIRECTORY_CLIENT_* environment for the context this invocation
resolved, because dirctl mcp serve reads its target from nothing else. That
is the context --context names, or the one Step 1 has just configured; the
two secrets, auth_token and spiffe_token, are never written into an agent's
config file.
That install is recorded in the install manifest like
any other package, with origin: builtin. It therefore shows up in
dirctl install list, dirctl install outdated compares it against this
binary rather than a Directory, and dirctl uninstall org.agntcy/directory
removes it. dirctl init --remove clears both the artifacts and the rows.
| Flag | Description | Default |
|---|---|---|
--oasf-url |
OASF schema endpoint to pull the taxonomy from | https://schema.oasf.outshift.com |
--asset-dir |
Local directory for the provisioned assets | ~/.agntcy/oasf-sdk/extractor |
--extractor-remote-addr |
gRPC OASF-SDK server to use instead of local assets | - |
--agents |
Agents to configure in the MCP server & skills step | all |
--yes / -y |
Provision without prompting (required for non-interactive runs) | false |
--remove |
Remove the provisioned assets and clear the saved config | false |
# Interactive setup (prompts; Enter accepts)
dirctl init
# Non-interactive (CI): provision with defaults
dirctl init --yes
# Provision from a local OASF instance
dirctl init --oasf-url http://localhost:8080 --yes
# Tear down what init provisioned
dirctl init --remove --yes
Diagnostics
dirctl doctor [flags]
Runs connectivity and configuration checks against the configured Directory server and routing layer.
| Flag | Description | Default |
|---|---|---|
--timeout |
Per-check timeout | 2s |
--bootstrap-peer |
Bootstrap peer multiaddr to validate (repeatable) | - |
dirctl version
Prints the dirctl build version.
MCP Server
dirctl mcp serve
Starts the built-in MCP server used by external AI tooling. Delegates to the
dir-mcp module.
Agent Install
dirctl install <cid-or-name> pulls a record from the active Directory and
installs its artifacts — an MCP server entry and/or an Agent Skill, derived from
the record's OASF modules — directly into the configuration of detected AI coding
agents.
Which artifacts are installed is determined by the record's modules, not a flag:
core/language_model/agentskills→ an Agent Skill (SKILL.md), rendered into whatever persistent-instruction mechanism each agent supports (a native skill folder, a per-tool rules file, or a managed block in a shared instruction file).integration/mcp→ an MCP server entry (command/args/env) placed under each agent's MCP servers key.integration/a2a→ not installable into agent configs; the command errors and points you todirctl export.
A multi-module record installs all applicable artifacts. The per-record identity
is the sanitized record name, so re-running install with a newer version of the
same-named record replaces the old artifacts cleanly, and different records
coexist.
Supported agents: Claude Code, Claude Desktop, Cursor, VS Code (Copilot), Windsurf, Cline, Roo Code, Gemini CLI, OpenCode, Zed, Continue, and Codex CLI.
Writes are atomic and surgical: only our own MCP entry, skill file/folder, or delimited managed block is added/updated/removed — all of your existing configuration is preserved.
Plans and summaries name only the agents something actually happens to. An agent where nothing changes is left out of the per-agent lines, because listing its path next to the record's name reads as a claim that the package is installed there. Skips and failures are always shown, since each explains why an agent you asked for got nothing, and the closing tally still counts every outcome. A plan in which nothing would change is reported as such and asks for no confirmation.
The install manifest
Installed artifacts carry no provenance of their own: an Agent Skill is marked
only with its slug, with no version and no CID. So every install records what
it wrote in $XDG_CONFIG_HOME/dirctl/installed.json (~/.config when
XDG_CONFIG_HOME is unset) — one row per record, agent, and scope, holding the
version and CID that were installed, the Directory it came from, the skill
path and its file list, and the MCP server keys.
scope is global, or the absolute path of the repository a --project
install wrote into. Naming the repository is what lets one manifest hold every
install on this machine: the row key is (name, agent, scope), so a bare
"project" would make the same package installed into two repositories collide
on one key. It also means a project install can be listed and removed from
anywhere, since each row's paths resolve against the repository it names rather
than the current directory.
The manifest is a local record of what happened, never a file to commit — it is full of absolute paths. A committed file pinning what a team should have is a different, declarative thing, and not this one.
The Directory is recorded because "is there a newer version?" is a question
about one Directory. A row installed from a different one is reported as
skipped rather than compared against the Directory configured now.
It is the server address, not the context name, because a name is not an
identity: --server-addr and DIRECTORY_CLIENT_SERVER_ADDRESS both replace a
context's endpoint while leaving its name in place, and two contexts can point
at one Directory. An empty address on either side means "no claim" and matches
anything, so a row written by an older dirctl stays checkable.
The row lists the artifacts that were actually written, not the ones the record's modules imply. An agent that received nothing — skipped or failed — is not recorded.
The manifest is the single source of truth for what is installed, so
uninstall reads it and contacts no Directory at all. That covers the case
with no other answer: the record has been deleted upstream, or the server is
simply down, and the artifacts are still on this machine. It also means only
the agents that actually hold the package are touched, including one no longer
detected here — the files it was given are still on disk.
A reference with no row is therefore not installed, and says so. Asking a Directory about it would answer a different question — whether some record exists — and would fail for reasons that say nothing about what is on this machine.
A row names every artifact dirctl wrote and has not since removed, so
reinstalling carries forward anything the previous row named that the new
install did not write. That covers a reinstall where one artifact failed, and a
new version that renames its MCP server while the old key stays in the config.
dirctl install upgrade is the one thing
that subtracts: a key it has just stripped from the config stops being named.
A skill folder is dirctl's, named after the record and owned outright, so
installing a skill replaces the folder's whole contents rather than writing one
file into it. That is what keeps a reference file dropped between versions from
being orphaned, and it means nothing you add inside a skill folder survives a
reinstall. To change an installed skill, push a new version of the record and
upgrade to it.
The manifest is bookkeeping, not the product: if it cannot be read or written,
dirctl prints a warning and the install still succeeds.
dirctl install agents
Lists every supported agent, whether it is detected on this machine, and the config files that install would touch. Makes no changes and does not contact the Directory.
Renamed in v1.8.0
This view was dirctl install list up to v1.7.0. dirctl install list now
lists installed packages.
dirctl install list [name] [flags]
Lists what install has put on this machine, read from the install manifest.
Reads only local state and does not contact the Directory.
With no argument it prints one row per installed package and agent: name,
agent, artifact kind (skill, mcp, or skill+mcp), scope, version, and
flags (pinned, builtin, missing). The agent comes second, next to the
name it qualifies, because one package occupies one row per agent.
With a package name it prints that package's installed artifacts — the skill folder with each of its files, and each MCP server key with the config file it lives in — which answers "what did this actually put on my machine?", otherwise unanswerable without reading every agent config by hand.
Every row is checked against disk, because the manifest is the only provenance
an installed artifact has and a user can move or delete one behind dirctl's
back. An artifact the row names but disk does not have is marked missing.
| Flag | Description | Default |
|---|---|---|
--output / -o |
Output format: human, json, jsonl |
human |
dirctl install list
dirctl install list cisco.com/agent
dirctl install list -o json
dirctl install outdated [name...] [flags]
Compares each installed package against the Directory it was installed from and reports what has moved on.
By default only two groups are listed: packages that are upgradable, and
packages that could not be assessed at all. The second group is there
because its absence from the upgrade list needs explaining — a silently omitted
missing or not found row reads as "fine". --all prints the full table.
| Status | Meaning |
|---|---|
upgradable |
A higher version exists. Also covers the same version resolving to different content, shown as content changed: an author who re-pushes without bumping still needs the new content installed. |
up to date |
Nothing newer. Includes an upstream that is older than what is installed — a downgrade is never offered, and the upstream version is shown in parentheses instead. |
pinned |
Held at the installed version. The newer version is still shown, so you can see what you are missing. |
missing |
The recorded artifacts are gone, so nothing can be upgraded. |
not found |
No record under that name in the configured Directory. |
non-semver |
The versions carry no ordering, so no claim is made. |
skipped |
The row was installed from a different Directory. |
Version enumeration is one lightweight call per distinct package name — a
package installed into three agents costs one call, not three — and pulls no
record bodies. Packages with origin builtin are compared against this
dirctl binary and make no call at all.
| Flag | Description | Default |
|---|---|---|
--all |
Show every installed package, not only the ones needing attention | false |
--exit-code |
Exit 1 when any package is upgradable, for CI gating | false |
--pre |
Consider prerelease versions | false |
--include-pinned |
Check pinned packages too | false |
--output / -o |
Output format: human, json, jsonl |
human |
--exit-code is gated on the unfiltered set, so --all cannot change the exit
code, and on upgradable alone: a missing or not found row is real, but no
upgrade would fix it, so failing CI on it would be a dead end.
dirctl install outdated
dirctl install outdated cisco.com/agent --all
dirctl install outdated --exit-code --include-pinned
dirctl install upgrade [name...] [flags]
Installs the newer version of every package that has one, in place. What counts
as upgradable is exactly what dirctl install outdated reports, decided the
same way: the highest version published under the name, or the same version now
resolving to different content. A downgrade is never offered, and a package
whose versions carry no ordering is never moved.
With no arguments it upgrades everything upgradable and skips pinned rows.
Naming a package upgrades it even if pinned, and releases the pin — asking
for it by name is a clearer statement than the hold it overrides.
--include-pinned upgrades held packages without releasing anything, so the
hold lands on the new version.
An upgrade is a reconcile, not an overwrite, and the order is load-bearing:
- Every replacement is fetched and derived first, so a record that cannot be pulled leaves every existing install exactly as it was. A working skill is never deleted before its replacement is in hand.
- Artifacts the manifest row names and the new version does not are removed. A
skill folder is
dirctl's, so installing a skill replaces its whole contents; an MCP entry sits in a config file shared with you and other tools, so a server key that v2 renamed is stripped by name from the row. Only the row knows what v1 wrote — deriving it from the new record would leave v1's key behind. - The new artifacts are installed and the row is rewritten, naming exactly what is now on disk.
One package failing does not strand the rest of the run: it is reported under Skipped records and the others go ahead.
Every scope is upgraded — the same rows outdated checks, --project
installs in other repositories included. Pass --project to narrow the run to
the repository you are in.
The built-in org.agntcy/directory package is rebuilt from this dirctl binary
rather than pulled, even though a record of the same name is published. The
published record's MCP module carries no environment, so installing it would
silently repoint dirctl mcp serve at the default address; rebuilding also
recomputes the DIRECTORY_CLIENT_* overlay from the context this invocation
resolved — --context and the connection flags included — instead of replaying
what it held when the package was installed.
| Flag | Description | Default |
|---|---|---|
--pre |
Consider prerelease versions | false |
--include-pinned |
Upgrade pinned packages too, keeping their pins | false |
--agents |
Agents to act on: all or a comma-separated list of agent IDs |
all |
--project |
Narrow the run to the repository you are in | false |
--dry-run |
Show what would move, without writing | false |
--yes / -y |
Skip the confirmation prompt | false |
dirctl install upgrade --dry-run
dirctl install upgrade
dirctl install upgrade cisco.com/agent
dirctl install upgrade --include-pinned --yes
dirctl install pin <name> / dirctl install unpin <name>
Holds a package at the version it is on, so a bare upgrade skips it, and
releases that hold again. Unpinning does not move the installed version.
The hold applies to every agent the package is installed into: one package is one package from your point of view. Both write only to the install manifest — no agent config is touched, and the Directory is not contacted.
Install-time pinning alone is not enough for the common case: holding a version you already have, because you upgraded, something broke, and you want to sit still.
dirctl install pin cisco.com/agent
dirctl install unpin cisco.com/agent
dirctl install prune [flags]
Drops manifest rows whose recorded artifacts are no longer on disk — the ones
dirctl install list marks as missing.
Rows go stale whenever something changes outside dirctl: a skill folder
deleted by hand, or a repository moved after a --project install. Pruning
removes only the bookkeeping. Nothing on disk is touched, and a row is dropped
only when every artifact it names is gone, since a package that lost one of
two artifacts is still installed and an upgrade can put it right.
| Flag | Description | Default |
|---|---|---|
--dry-run |
List the rows that would be dropped, without writing | false |
dirctl install prune --dry-run
dirctl install prune
dirctl install <cid-or-name> / dirctl install run <cid-or-name>
Pulls the record, derives its artifacts, prints the planned changes (per agent and
artifact, marked add / updated / unchanged), asks for confirmation, then installs.
dirctl install <cid-or-name> is shorthand for dirctl install run <cid-or-name>.
By default it acts on all detected agents. Detection is always required — an agent
is never installed into unless it is detected on this machine; an explicitly
requested agent that is not detected is reported as skipped.
The artifacts to install are determined entirely by the record's modules (above),
not by flags. A record carrying neither an MCP nor an Agent Skill module has
nothing installable: the command errors (making no changes) and, for an
A2A-only record, points you to dirctl export.
| Flag | Description | Default |
|---|---|---|
--agents |
Agents to target: all (every detected agent) or a comma-separated list of agent IDs (e.g. --agents claude-code,cursor; repeatable) |
all |
--project |
Write into the current repo (project scope) instead of the user's global config | false |
--dry-run |
Preview the plan without writing | false |
--yes / -y |
Skip the confirmation prompt | false |
--pin |
Hold the package at the version that was installed. Implied when the reference names an explicit :version |
false |
Valid agent IDs: claude-code, claude-desktop, cursor, vscode, windsurf,
cline, roo, gemini, opencode, zed, continue, codex (see
dirctl install agents). After completion, a summary lists every location added,
updated, removed, or skipped with its absolute path.
By default artifacts go into each agent's global/user config. With
--project, they are written into the current repository instead — under
each agent's project-local MCP config (e.g. .cursor/mcp.json, .vscode/mcp.json,
.mcp.json) and its project skill folder — so a record can be wired into the
agents for just one project. Run dirctl install agents --project to see the exact
paths per agent. Agents with no project-scope location for an artifact are skipped
with a note; detection is unchanged (an undetected agent is still skipped).
# Preview what installing a record would change
dirctl install cisco.com/agent:v1.0.0 --dry-run
# Install a record's artifacts into all detected agents
dirctl install cisco.com/agent:v1.0.0 --yes
# Install into specific agents only
dirctl install cisco.com/agent --agents claude-code,cursor
# Install into the current repo instead of the global config
dirctl install cisco.com/agent --project
Installing several records: pipe from dirctl search
With no positional argument and something on stdin, install reads one
reference per line. Blank lines and # comments are ignored.
dirctl search -o raw is the format to pipe: one CID per line. -o jsonl
works too, since its quotes are stripped. Record names are accepted as well,
so a hand-written list can be piped in.
dirctl search --module integration/mcp -o raw | dirctl install --agents all --yes
dirctl search --skill "code*" -o raw | dirctl install --dry-run
Filtering belongs to dirctl search, so install carries no
copy of its flags. Two consequences worth knowing:
- A piped run cannot prompt, because stdin is the reference list. It needs
--yes, or--dry-runto preview. Without either it errors, rather than reading a CID as the answer to a confirmation. - Only the highest version of each name is installed. Search returns every matching version, and there is no flag to install them all: the skill slug and the MCP server key both derive from the record name alone, so two versions resolve to the same folder and the same config key. Only one version of a package can be live and reachable by an agent.
A reference that cannot be resolved or pulled is reported and skipped, so one bad entry does not abort the run. At most 1000 references are accepted in one run.
dirctl install uninstall <cid-or-name> [flags]
Removes what install recorded for that record — its MCP entry and/or skill —
leaving all other content intact. Shares the selection flags with install
(--agents, --project, --dry-run, --yes). Idempotent: removing something
already gone is not an error.
What to remove comes from the install manifest, so no Directory is contacted
and only the agents that actually hold the package appear in the plan. A row
whose artifacts have already been deleted by hand is cleared from the manifest,
so a package can never get stuck. dirctl uninstall <name>:<version> removes
only rows at that version, and a bare CID matches the exact record installed.
A reference with no matching row reports "<name>" is not installed globally,
or not installed in <repo> under --project. When the package does exist at
another scope the message says where, since a global uninstall does not touch a
repository's rows and a --project one touches only the repository it runs in.
The record's row is dropped for every agent whose artifacts are confirmed gone, meaning at least one was removed or already absent and none failed. An agent keeps its row when a removal failed, and when every artifact was skipped because its location could not be resolved — in both cases something may still be on disk, and the row is the only note of what it is.
It takes one reference. Removing several is a loop over
dirctl install list; the manifest is what lists them, and search filters
would answer a different question — what exists upstream, rather than what you
have.
dirctl uninstall <cid-or-name> is a top-level shorthand for
dirctl install uninstall <cid-or-name> (same flags and behavior).
dirctl install uninstall cisco.com/agent --yes
dirctl uninstall cisco.com/agent --agents cursor
Daemon Operations
The daemon commands run a self-contained local directory server that bundles the gRPC apiserver and reconciler into a single process with embedded SQLite and a filesystem OCI store. All state is stored under ~/.agntcy/dir/ by default.
dirctl daemon start
Starts the local directory daemon in the foreground. The process blocks until SIGINT or SIGTERM is received.
The daemon starts a gRPC apiserver on localhost:8888 and runs all reconciler tasks (regsync, indexer, name resolution, signature verification) in-process. It uses SQLite for persistence and a local persistent OCI store, so no external dependencies (PostgreSQL, container registry, etc.) are required.
A PID file is written to the data directory to prevent multiple instances from running simultaneously.
Without --config, built-in defaults are used. When --config is provided, the file is read as the complete configuration.
| Flag | Description | Default |
|---|---|---|
--data-dir |
Data directory for daemon state | ~/.agntcy/dir/ |
--config |
Path to daemon config file | Built-in embedded defaults |
Example
# Start the daemon (foreground, blocks until signal)
dirctl daemon start
# Start with a custom data directory
dirctl daemon start --data-dir /path/to/data
# Start with a custom config file
dirctl daemon start --config /path/to/config.yaml
# Run in the background using shell job control
dirctl daemon start &
Data directory layout:
~/.agntcy/dir/
├── dir.db # SQLite database
├── store/ # Persistent OCI store
├── routing/ # DHT routing datastore
└── daemon.pid # PID file for lifecycle management
Configuration:
The daemon ships with sensible built-in defaults. To customize, pass a YAML config file via --config. Relative paths in the config (e.g. store, dir.db) are resolved against --data-dir. Credentials can be set via environment variables prefixed with DIRECTORY_DAEMON_ (e.g. DIRECTORY_DAEMON_SERVER_DATABASE_POSTGRES_PASSWORD).
Reference config file
server:
listen_address: "localhost:8888"
oasf_api_validation:
schema_url: "https://schema.oasf.outshift.com"
store:
provider: "oci"
oci:
local_dir: "store"
registry_address: "localhost:5555"
repository_name: "dir"
auth_config:
insecure: true
verification:
enabled: true
routing:
listen_address: "/ip4/0.0.0.0/tcp/8999"
datastore_dir: "routing"
gossipsub:
enabled: true
database:
type: "sqlite"
sqlite:
path: "dir.db"
postgres:
host: "localhost"
port: 5432
database: "dir"
ssl_mode: "disable"
publication:
scheduler_interval: 5m
worker_count: 1
worker_timeout: 30m
naming:
ttl: 168h
reconciler:
regsync:
enabled: true
interval: 1m
indexer:
enabled: true
interval: 1m
signature:
enabled: true
interval: 1m
ttl: 168h
record_timeout: 30s
name:
enabled: true
interval: 1m
ttl: 168h
record_timeout: 30s
local_registry:
registry_address: "localhost:5555"
repository_name: "dir"
auth_config:
insecure: true
database:
type: "sqlite"
sqlite:
path: "dir.db"
dirctl daemon stop
Stops a running daemon by sending SIGTERM to the process recorded in the PID file. The command waits for the process to exit gracefully and cleans up the PID file.
| Flag | Description | Default |
|---|---|---|
--data-dir |
Data directory for daemon state | ~/.agntcy/dir/ |
Example
# Stop the running daemon
dirctl daemon stop
# Stop a daemon using a custom data directory
dirctl daemon stop --data-dir /path/to/data
dirctl daemon status
Checks whether the daemon is currently running by inspecting the PID file.
| Flag | Description | Default |
|---|---|---|
--data-dir |
Data directory for daemon state | ~/.agntcy/dir/ |
Example
# Check daemon status
dirctl daemon status
Example output when running:
Daemon is running (PID 12345)
Example output when stopped:
Daemon is not running
dirctl daemon config init
Writes the daemon's built-in default configuration to disk, giving you a complete, valid file to start customizing instead of authoring one from scratch.
Without --output, the file is written to the daemon's resolved config path: <data-dir>/daemon.config.yaml by default, or the path passed via --config on the daemon command if one is set. Unless --force is given, the command refuses to overwrite a file that already exists. The file is created with mode 0600. When the command creates the daemon's data directory it uses 0700, matching daemon start; a directory that already exists keeps its current mode, and a parent created for a path you passed to --output uses 0755, since that is an ordinary location rather than the daemon's private state. The dumped file is accepted unchanged by dirctl daemon start --config <file>.
| Flag | Description | Default |
|---|---|---|
--data-dir |
Data directory for daemon state | ~/.agntcy/dir/ |
--config |
Path to daemon config file (used as the write target if set) | Built-in embedded defaults |
--output, -o |
Path to write the default config to | Resolved daemon config path |
--force |
Overwrite the file if it already exists | false |
Example
# Write the default config to the default location
dirctl daemon config init
# Write the default config to a specific file
dirctl daemon config init --output /path/to/daemon.config.yaml
# Overwrite an existing config file
dirctl daemon config init --force
Context Operations
Starting with Directory v1.4.0, the context commands manage reusable dirctl client contexts. Contexts describe Directory endpoints and their client-side authentication, TLS, and SPIFFE settings.
The default config file is ~/.config/dirctl/config.yaml, or $XDG_CONFIG_HOME/dirctl/config.yaml when XDG_CONFIG_HOME is set.
current_context: prod
contexts:
prod:
server_address: gateway.example.com:443
auth_mode: oidc
oidc_issuer: https://idp.example.com
oidc_client_id: dirctl
oidc_scopes:
- openid
- email
- profile
- offline_access
- groups
staging:
server_address: staging.gateway.example.com:443
auth_mode: oidc
oidc_issuer: https://staging.idp.example.com
oidc_client_id: dirctl
Regular dirctl commands select a context in this order:
--context <name>DIRECTORY_CLIENT_CONTEXTcurrent_contextfrom the config file
After context selection, environment variables and explicit root flags such as --server-addr, --auth-mode, --oidc-issuer, and --auth-token override the selected context for that invocation.
Sensitive values
dirctl context show redacts auth_token and spiffe_token values. Prefer environment variables or a secret manager for bearer tokens instead of storing long-lived tokens in config.yaml.
dirctl context list
Lists configured contexts in sorted order and marks the persisted current_context with *. This command reads local config only; it does not contact a Directory server.
Example
dirctl context list
Example output:
* prod
staging
dirctl context current
Prints the persisted current_context from the config file. It intentionally ignores one-off overrides such as --context and DIRECTORY_CLIENT_CONTEXT, so it matches the marker shown by dirctl context list.
| Flag | Description | Default |
|---|---|---|
--quiet |
Print only the context name; print nothing when no context is set | false |
--json |
Print current context details as JSON | false |
Example
# Print the current context with a trailing newline
dirctl context current
# Prompt-friendly output
dirctl context current --quiet
# Machine-readable output
dirctl context current --json
Example JSON output:
{
"name": "prod",
"source": "current_context",
"path": "/home/user/.config/dirctl/config.yaml"
}
dirctl context set <name>
Sets the persisted active context by updating current_context in the client config file. The target context must already exist.
Example
# Persist prod as the active context
dirctl context set prod
# Run one command against staging without changing current_context
dirctl --context staging info <record-cid>
dirctl context show [name]
Shows the effective client configuration for a context with sensitive values redacted. If [name] is omitted, the command uses the same context selection rules as other dirctl commands. Environment variables and explicitly changed root flags are included in the effective output.
Example
# Show the active effective context
dirctl context show
# Show a specific context
dirctl context show prod
# Preview a context with a one-off server override
dirctl --server-addr localhost:8888 context show prod
Example output:
name: prod
source: current_context
path: /home/user/.config/dirctl/config.yaml
config:
auth_mode: oidc
oidc_client_id: dirctl
oidc_issuer: https://idp.example.com
server_address: gateway.example.com:443
tls_skip_verify: false
dirctl context validate [name]
Validates stored context definitions without applying environment variable overrides. It catches missing required fields, unsupported auth modes, and auth-mode-specific configuration mistakes before a context is used.
If [name] is provided, only that context is validated. If omitted, all configured contexts are validated in sorted order.
Example
# Validate all contexts
dirctl context validate
# Validate one context
dirctl context validate prod
Example output:
prod: ok
staging: ok
Storage Operations
dirctl push <file>
Stores records in the content-addressable store. Has the following features:
- Supports OASF v1, v2, v3 record formats
- Content-addressable storage with CID generation
- Optional cryptographic signing
- Data integrity validation
Example
# Push from file
dirctl push agent-model.json
# Push from stdin
cat agent-model.json | dirctl push --stdin
# Push with signature
dirctl push agent-model.json --sign --key private.key
dirctl pull <reference>
Retrieves records by their Content Identifier (CID) or name reference.
Supported Reference Formats:
| Format | Description |
|---|---|
<cid> |
Direct lookup by CID |
<name> |
Retrieves the highest version (see Version Resolution below) |
<name>:<version> |
Retrieves the specified version |
<name>@<cid> |
Hash-verified lookup (fails if resolved CID doesn't match) |
<name>:<version>@<cid> |
Hash-verified lookup for a specific version |
Example
# Pull by CID
dirctl pull baeareihdr6t7s6sr2q4zo456sza66eewqc7huzatyfgvoupaqyjw23ilvi
# Pull by name (latest version)
dirctl pull cisco.com/agent
# Pull by name with specific version
dirctl pull cisco.com/agent:v1.0.0
# Pull with hash verification
dirctl pull cisco.com/agent@bafyreib...
dirctl pull cisco.com/agent:v1.0.0@bafyreib...
# Pull signature and public-key referrer artifacts from the server
dirctl pull <cid> --signature --public-key
# Pull security scan reports for the record
dirctl pull <cid> --scan-report
Output and batch retrieval:
pull defaults to --output json (records are primarily machine-consumed); -o human|jsonl|raw are also available. Use --output-file to write a single record's JSON to a file, or --output-dir with search filters to pull many records at once (one <name>.json per record).
Referrer flags — these extend the output with OCI referrer artifacts attached to the record:
| Flag | Description |
|---|---|
--public-key |
Include the public key referrer attached to the record |
--signature |
Include the signature referrer attached to the record |
--scan-report |
Include security scan reports for the record |
When any referrer flag is set, the response is a structured JSON object with a record key and one or more of publicKeys, signatures, scanReports.
Output and batch retrieval:
| Flag | Description | Default |
|---|---|---|
--output-file |
Write the record JSON to a file instead of stdout (single record) | - |
--output-dir |
Directory for batch pull from search results (one JSON file per record) | - |
--limit |
Maximum number of records to pull in batch mode | 100 |
--all-versions |
Keep all versions in batch pull (default: latest per name wins) | false |
When --output-dir is used, at least one search filter is required. All standard search filters are available (--name, --version, --module, --skill, --author, etc.). A positional reference and --output-dir are mutually exclusive.
Example
# Single record JSON to stdout (default) or a file
dirctl pull cisco.com/agent
dirctl pull cisco.com/agent --output-file=./agent.json
# Batch: pull every matching record into a directory
dirctl pull --output-dir=./records/ --name "web*"
dirctl pull --output-dir=./records/ --module "integration/mcp" --all-versions
Hash Verification:
The @<cid> suffix enables hash verification. This command fails if the resolved CID doesn't match the expected digest:
# Succeeds if cisco.com/agent:v1.0.0 resolves to bafyreib...
dirctl pull cisco.com/agent:v1.0.0@bafyreib...
# Fails with error if CIDs don't match
dirctl pull cisco.com/agent@wrong-cid
# Error: hash verification failed: resolved CID "bafyreib..." does not match expected digest "wrong-cid"
Version Resolution:
When no version is specified, commands return the highest semantic version
published under the name. A release beats a prerelease, so publishing
v2.0.0-rc.1 does not change what a bare name means; a name carrying nothing
but prereleases resolves to the highest of those.
Versions that semver cannot order — non-semver tags like latest, dev, or
stable — fall back to the most recently created record (by the record's
created_at field), which is what lets a name tagged that way keep working. In
a mix of orderable and unorderable versions the highest orderable one wins,
since an unordered tag cannot be shown to be newer than a real version.
Changed in v1.8.0
Bare names used to resolve to the most recently created record. Pushing
v1.9.0 after v2.0.0 therefore made dirctl pull cisco.com/agent return
v1.9.0, which also meant dirctl install outdated reported a package one
version behind as up to date. Pin an exact version with <name>:<version>
if you need the old record.
dirctl delete <cid> [cid...]
Removes records from storage. Multiple CIDs can be passed as arguments or piped in
with --stdin, in which case they are deleted over a single stream.
The following flags are available:
--stdin- Read CIDs from standard input, either as a JSON array produced bydirctl search --format cid --output jsonor as line-delimited CIDs
Example
# Delete a record
dirctl delete baeareihdr6t7s6sr2q4zo456sza66eewqc7huzatyfgvoupaqyjw23ilvi
# Delete several records in one request
dirctl delete baeareihdr6t7... baeareiabc123...
# Delete every record matching a search
dirctl search --format cid --limit 100 --output json | dirctl delete --stdin
dirctl info <reference>
Displays metadata about stored records using CID or name reference.
Supported Reference Formats:
| Format | Description |
|---|---|
<cid> |
Direct lookup by content address |
<name> |
Displays the highest version (see Version Resolution under dirctl pull) |
<name>:<version> |
Displays the specified version |
<name>@<cid> |
Hash-verified lookup |
<name>:<version>@<cid> |
Hash-verified lookup for a specific version |
Example
# Info by CID (existing)
dirctl info baeareihdr6t7s6sr2q4zo456sza66eewqc7huzatyfgvoupaqyjw23ilvi
# Info by name (latest version)
dirctl info cisco.com/agent --output json
# Info by name with specific version
dirctl info cisco.com/agent:v1.0.0 --output json
Import Operations
Import records from external registries or local files into DIR. Supports automated batch imports with optional LLM enrichment and security scanning.
dirctl import [flags]
Fetch and import records from registries or local sources.
Import kinds (--type):
| Type | Source | Required flags |
|---|---|---|
mcp-registry |
HTTP MCP registry (e.g. v0.1 list API) | --url |
mcp |
Local JSON (one server or array) | --file-path |
a2a |
Local A2A AgentCard JSON | --file-path |
agent-skill |
Local Agent Skills directory with SKILL.md |
--file-path |
Configuration Options:
| Flag | Environment Variable | Description | Required | Default |
|---|---|---|---|---|
--config |
- | Path to a YAML import config file (enricher, scanner, authors, and more); values are overridden by command-line flags | No | - |
--type |
- | Import kind (mcp-registry, mcp, a2a, agent-skill) |
No | - |
--url |
- | Registry base URL (required when --type=mcp-registry) |
No | - |
--file-path |
- | Path to local JSON file or skill directory | No | - |
--output-cids |
- | Write imported CIDs to a file (one per line) | No | - |
--filter |
- | Registry-specific filters (key=value, repeatable) | No | - |
--limit |
- | Maximum records to import (0 = no limit) | No | 0 |
--dry-run |
- | Preview without importing; transformed records are written to --output-dir (one JSON file per record) so they can be reviewed and re-imported later via dirctl push or dirctl import |
No | false |
--output-dir |
- | Directory to write per-record JSON files when --dry-run is set. Each record is written as <cid>.record.json |
No | ./import-dry-run-<timestamp> in the current working directory |
--debug |
- | Enable debug output (shows MCP source and OASF record for failures) | No | false |
--force |
- | Force reimport of existing records (skip deduplication) | No | false |
--sign |
- | Sign records after pushing (uses OIDC by default) | No | false |
--key |
- | Path to private key file for signing (requires --sign) |
No | - |
--oidc-token |
- | OIDC token for non-interactive signing (requires --sign) |
No | - |
--fulcio-url |
- | Sigstore Fulcio URL (requires --sign) |
No | https://fulcio.sigstore.dev |
--rekor-url |
- | Sigstore Rekor URL (requires --sign) |
No | https://rekor.sigstore.dev |
--server-addr |
DIRECTORY_CLIENT_SERVER_ADDRESS | DIR server address | No | localhost:8888 |
Note
By default, the importer performs deduplication: it builds a cache of existing records (by name and version) and skips importing records that already exist. This prevents duplicate imports when running the import command multiple times. Use --force to bypass deduplication and reimport existing records. Use --debug to see detailed output including which records were skipped and why imports failed.
Example
# Import from MCP registry
dirctl import --type=mcp-registry --url=https://registry.modelcontextprotocol.io/v0.1
# Import with debug output (shows detailed diagnostics for failures)
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--debug
# Force reimport of existing records (skips deduplication)
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--force
# Import with time-based filter
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--filter=updated_since=2025-08-07T13:15:04.280Z
# Combine multiple filters
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--filter=search=github \
--filter=version=latest \
--filter=updated_since=2025-08-07T13:15:04.280Z
# Limit number of records
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--limit=50
# Preview without importing (dry run)
# Records are written as <cid>.record.json into a timestamped directory
# (default: ./import-dry-run-<timestamp>) so they can be reviewed and
# re-imported later via `dirctl push` or `dirctl import`.
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--dry-run
# Dry run with a custom output directory
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--dry-run \
--output-dir=./out
# Import and sign records with OIDC (opens browser for authentication)
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--sign
# Import and sign records with a private key
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--sign \
--key=/path/to/cosign.key
MCP Registry Filters:
For the Model Context Protocol registry, available filters include:
search- Filter by server name (substring match)version- Filter by version ('latest' for latest version, or an exact version like '1.2.3')updated_since- Filter by updated time (RFC3339 datetime format, e.g., '2025-08-07T13:15:04.280Z')
See the MCP Registry API docs for the complete list of supported filters.
Enrichment
The import command enriches records by mapping them to OASF skills and domains. Three enrichment methods are available, configured under the enricher key in the --config YAML file. When no config file is provided, LLM enrichment is used by default (azure:gpt-4o, 2 RPM).
Extractor enrichment (LLM-free, recommended)
Uses the OASF sentence-transformer model to classify records — no API key and no LLM runtime. The model runs in-process from locally provisioned assets, or on a remote OASF-SDK server.
Requires: either dirctl init to have been run at least once to provision the model assets, or a reachable OASF-SDK server.
enricher:
extractor: {} # uses whatever dirctl init saved
With no fields set, the extractor uses the configuration saved by dirctl init — including a remote server address, if one was configured there.
To override the local asset location:
enricher:
extractor:
oasf_url: https://schema.oasf.outshift.com # optional
asset_dir: /path/to/custom/assets # optional
To enrich against a running OASF-SDK server instead of local assets:
enricher:
extractor:
remote_addr: oasf-sdk:31234 # selects the remote backend
Setting remote_addr selects the remote backend; leaving it empty uses the local in-process extractor. Each extraction call is bounded by a timeout, so an unreachable server fails the record rather than stalling the import.
Static enrichment
Assigns the same fixed skills and domains to every imported record. No LLM or model assets required.
enricher:
static:
skills:
- name: natural_language_processing/text_completion
id: 10201
domains:
- name: technology
id: 1
LLM enrichment
Runs an LLM with tool-calling support against the OASF schema tools exposed by dirctl mcp serve. Produces the most semantically accurate skill and domain assignments but requires LLM credentials or a local runtime.
Requirements:
dirctlbinary (includes the built-in MCP server withagntcy_oasf_get_schema_skillsandagntcy_oasf_get_schema_domainstools)- An LLM with tool-calling support (GPT-4o, Claude, or compatible Ollama models)
How it works:
- The enricher starts an MCP server using
dirctl mcp serve - The LLM uses the
agntcy_oasf_get_schema_skillstool to browse available OASF skills - The LLM uses the
agntcy_oasf_get_schema_domainstool to browse available OASF domains - Based on the record description and capabilities, the LLM selects appropriate skills and domains
enricher:
llm:
requests_per_minute: 5
tool_host:
model: azure:gpt-4o
max_steps: 10
mcp_servers:
dir-mcp-server:
command: dirctl
args: [mcp, serve]
env:
OASF_API_VALIDATION_SCHEMA_URL: https://schema.oasf.outshift.com
DIRECTORY_CLIENT_AUTH_MODE: insecure
skills_prompt_template: ./prompts/skills.md # optional custom prompt
domains_prompt_template: ./prompts/domains.md # optional custom prompt
See cli/cmd/import/import.config.yaml in the repository for a fully annotated reference configuration.
Recommended LLM providers:
azure:gpt-4o— Azure OpenAI GPT-4o (recommended for speed and accuracy)ollama:qwen3:8b— Local Qwen3 via Ollama
Environment variables for LLM providers:
- Azure OpenAI:
AZURE_OPENAI_API_KEY,AZURE_OPENAI_ENDPOINT,AZURE_OPENAI_DEPLOYMENT
Dry Run Output Directory
When --dry-run is set, the importer does not push records to the Directory node. Instead, every transformed record is written to disk as a separate JSON file, one per record. This produces a reviewable artifact set that is drop-in compatible with the existing import / push commands, so users who do not want the importer to push directly to a Directory node can review or modify the records first and upload them later.
Output layout:
- Target directory: value of
--output-dir, or — when not set —./import-dry-run-<timestamp>/in the current working directory. - The directory is created if it does not exist.
- Each record is written as
<cid>.record.json, where<cid>is the record's content identifier. The naming scheme is deterministic and filesystem-safe so files can be diffed, signed, or selectively re-imported.
Typical workflow:
# 1. Dry run — write transformed records to ./out
dirctl import --type=mcp-registry \
--url=https://registry.modelcontextprotocol.io/v0.1 \
--dry-run \
--output-dir=./out
# 2. Review / diff / sign the per-record files on disk
ls ./out
# bafkrei...record.json
# bafkrei...record.json
# ...
# 3. Re-import the artifacts later via dirctl push (or another import flow)
for f in ./out/*.record.json; do
dirctl push "$f"
done
Signing Records During Import
Records can be signed during import using the --sign flag. Signing options work the same as the standalone dirctl sign command (see Security & Verification).
# Sign with OIDC (opens browser)
dirctl import --type=mcp-registry --url=https://registry.modelcontextprotocol.io/v0.1 --sign
# Sign with a private key
dirctl import --type=mcp-registry --url=https://registry.modelcontextprotocol.io/v0.1 --sign --key=/path/to/cosign.key
Export Operations
Export records from the Directory into formats consumable by external tools and agentic CLIs. Supports single-record export by CID/name and batch export from search results.
dirctl export <cid-or-name[:version][@digest]> [flags]
Pull a record and transform it to the requested format.
Supported Formats:
| Format | Output | Description |
|---|---|---|
a2a |
.json |
A2A AgentCard JSON for Agent-to-Agent protocol interop |
agent-skill |
.md |
SKILL.md artifact for agentic CLI consumption (Cursor, Claude Code, etc.) |
mcp-ghcopilot |
.json |
GitHub Copilot MCP configuration JSON |
mcp-claudecode |
.json |
Claude Code MCP configuration JSON (.mcp.json mcpServers shape) |
mcp-cursor |
.json |
Cursor IDE MCP configuration JSON (.cursor/mcp.json mcpServers shape) |
Note: For raw OASF record JSON, use
dirctl pull— it supports--output-file,--output-dir, and search filters for batch retrieval.dirctl exportno longer accepts--format=oasf.
Single-record Flags:
| Flag | Description | Default |
|---|---|---|
--format |
Export format (see table above); required | - |
--output-file |
File path to write the exported data (default: stdout) | - |
Batch Export Flags:
| Flag | Description | Default |
|---|---|---|
--output-dir |
Directory for batch export from search results | - |
--all-versions |
Keep all versions (default: only latest semver per name) | false |
--limit |
Maximum number of records to export | 100 |
When --output-dir is used, at least one search filter is required. All standard search filters are available (--name, --version, --module, --skill, --author, etc.).
Batch behaviour varies by format
For different export formats, the batch behaviour varies:
- a2a / oasf: One file per record (
<name>.json). - agent-skill: One subdirectory per skill (
<name>/SKILL.md). - mcp-ghcopilot: All matched MCP servers are merged into a single
mcp.jsonwith combinedserversandinputsmaps. - mcp-claudecode: All matched MCP servers are merged into a single
mcp.jsonwith a combinedmcpServersmap. - mcp-cursor: All matched MCP servers are merged into a single
mcp.jsonwith a combinedmcpServersmap.
Example
# Export a single record as A2A AgentCard to stdout
dirctl export baeareihdr6t7s6... --format=a2a
# Export to a file (auto-appends extension if omitted)
dirctl export my-agent:1.0 --format=a2a --output-file=./agent-card.json
dirctl export my-agent:1.0 --format=agent-skill --output-file=./SKILL.md
# Batch export A2A records to a directory
dirctl export --output-dir=./exports/ --format=a2a --module "integration/a2a"
# Batch export skills (creates subdirectories with SKILL.md)
dirctl export --output-dir=./exports/ --format=agent-skill \
--module "core/language_model/agentskills"
# Batch export MCP servers (merged into a single config)
dirctl export --output-dir=./exports/ --format=mcp-ghcopilot \
--module "integration/mcp"
# Export a single record as a Claude Code .mcp.json
dirctl export my-mcp-server --format=mcp-claudecode --output-file=.mcp.json
# Export a single record as a Cursor IDE mcp.json
dirctl export my-mcp-server --format=mcp-cursor --output-file=.cursor/mcp.json
# Export all versions instead of only the latest
dirctl export --output-dir=./exports/ --format=a2a \
--name "my-agent" --all-versions
# Combine multiple search filters
dirctl export --output-dir=./exports/ --format=a2a \
--author "acme" --skill "natural_language_processing"
Routing Operations
The routing commands manage record announcement and discovery across the peer-to-peer network.
dirctl routing publish <cid>
Announces records to the network for discovery by other peers. The command does the following:
- Announces record to DHT network.
- Makes record discoverable by other peers.
- Stores routing metadata locally.
- Enables network-wide discovery.
Example
# Publish a record to the network
dirctl routing publish baeareihdr6t7s6sr2q4zo456sza66eewqc7huzatyfgvoupaqyjw23ilvi
dirctl routing unpublish <cid>
Removes records from network discovery while keeping them in local storage. The command does the following:
- Removes DHT announcements.
- Stops network discovery.
- Keeps record in local storage.
- Cleans up routing metadata.
Example
# Remove from network discovery
dirctl routing unpublish baeareihdr6t7s6sr2q4zo456sza66eewqc7huzatyfgvoupaqyjw23ilvi
dirctl routing list [flags]
Queries local published records with optional filtering.
The following flags are available:
--skill <skill>- Filter by skill (repeatable)--locator <type>- Filter by locator type (repeatable)--domain <domain>- Filter by domain (repeatable)--module <module>- Filter by module name (repeatable)--cid <cid>- List specific record by CID--limit <number>- Limit number of results
Example
# List all local published records
dirctl routing list
# List by skill
dirctl routing list --skill "AI"
dirctl routing list --skill "Natural Language Processing"
# List by locator type
dirctl routing list --locator "docker-image"
# List by module
dirctl routing list --module "runtime/framework"
# Multiple criteria (AND logic)
dirctl routing list --skill "AI" --locator "docker-image"
dirctl routing list --domain "healthcare" --module "runtime/language"
# Specific record by CID
dirctl routing list --cid baeareihdr6t7s6sr2q4zo456sza66eewqc7huzatyfgvoupaqyjw23ilvi
# Limit results
dirctl routing list --skill "AI" --limit 5
dirctl routing search [query] [flags]
Discovers records from other peers across the DHT network. Two modes are available:
| Mode | Invocation | Requires |
|---|---|---|
| Natural-language | dirctl routing search "free-text query" |
dirctl init (OASF extractor) |
| Structured | dirctl routing search --skill "..." --domain "..." |
None |
Natural-language routing search
Pass a free-text phrase as a positional argument. The OASF extractor decomposes the phrase into skill and domain signals. Keyword signals are dropped because the DHT only indexes skills, domains, locators, and modules — there is no name or description index at the routing layer.
Each signal is queried against the DHT independently; the discovered window of results is then reordered by match score, best-first. This is not a global top-N: only the records returned within --limit are reordered.
dirctl routing search "Github MCP server that manages issues"
dirctl routing search "real-time fraud detection for banking"
Use --schema-version to restrict NL extraction to a specific OASF schema version:
dirctl routing search "code review agent" --schema-version 1.0.0
Use --verbose to print the extracted signals to stderr:
dirctl routing search "fraud detection for banking" --verbose
# stderr output:
# [nl-routing-search] signals extracted (2 usable of 3 total):
# RECORD_QUERY_TYPE_SKILL fraud_detection
# RECORD_QUERY_TYPE_DOMAIN finance
# (keyword signal "banking" dropped — no DHT equivalent)
Structured routing search
Omit the positional argument and use filter flags. All flags are repeatable and combined with OR logic: a record matches if it satisfies at least --min-score of the specified queries.
Flags:
| Flag | Description | Default |
|---|---|---|
--skill |
Search by skill name (repeatable) | — |
--domain |
Search by domain name (repeatable) | — |
--locator |
Search by locator type (repeatable) | — |
--module |
Search by module path (repeatable) | — |
--limit |
Maximum results to return | 10 |
--min-score |
Minimum number of queries that must match | 1 |
--schema-version |
Restrict NL extraction to specific OASF schema version(s) (repeatable; NL mode only) | all versions |
--verbose |
Print extracted NL signals and per-signal details to stderr (NL mode only) | false |
Output includes record CID, provider peer information, match score, and the specific queries that matched.
Example
# Natural-language search across the network
dirctl routing search "Github MCP server that manages issues"
# Structured: search for AI records
dirctl routing search --skill "AI"
# Structured: search with multiple criteria (record must match at least 2)
dirctl routing search --skill "AI" --skill "ML" --min-score 2
# Structured: search by locator type
dirctl routing search --locator "docker-image"
# Structured: search by module
dirctl routing search --module "runtime/framework"
# Structured: combined filters
dirctl routing search --skill "web-development" --limit 10 --min-score 1
dirctl routing search --domain "finance" --module "validation" --min-score 2
# Pipe results into sync
dirctl routing search --skill "AI" --output json | dirctl sync create --stdin
dirctl routing info
Shows routing statistics and summary information.
The output includes the following:
- Total published records count
- Skills distribution with counts
- Locators distribution with counts
- Helpful usage tips
Example
# Show local routing statistics
dirctl routing info
Search & Discovery
dirctl search [query] [flags]
Search for records in the local index. Two modes are available depending on whether a positional argument is provided:
| Mode | Invocation | Requires |
|---|---|---|
| Natural-language | dirctl search "free-text query" |
dirctl init (OASF extractor) |
| Structured | dirctl search --name "..." --skill "..." |
None |
Natural-language search
Pass a free-text phrase as a positional argument. The OASF extractor (provisioned by dirctl init) decomposes the phrase into typed signals — skill names, domain names, and keywords. Each signal is queried against the index independently and concurrently; results are ranked by how many signals matched, with the best-matching records returned first.
dirctl search "Github MCP server that manages issues"
dirctl search "real-time fraud detection for banking" --format record
dirctl search "code review assistant" --limit 5 --format record
If the extractor has not been provisioned, the command fails with a prompt to run dirctl init.
Use --verbose to print signal decomposition and per-signal hit counts to stderr — useful for understanding why a query returns particular results:
dirctl search "fraud detection for banking" --verbose
# stderr output:
# [nl-search] signals extracted (3):
# skill fraud_detection score=0.91
# domain finance score=0.87
# keyword banking score=0.74
# [nl-search] per-signal hits:
# skill fraud_detection → 12 CIDs
# domain finance → 31 CIDs
# keyword banking → 8 CIDs
# [nl-search] ranked results (36 unique, 3 signals):
# sha256:abc... hits=3/3 signals=[skill:fraud_detection, domain:finance, keyword:banking]
# ...
Note:
--sortis ignored in NL mode — results are always ranked by signal-hit-count.
Structured search
Omit the positional argument and use filter flags to query specific fields. All filter flags are repeatable and combinable.
Filter flags:
| Flag | Description |
|---|---|
--name |
Record name (supports wildcards, e.g. web-*) |
--version |
Record version (e.g. v1.0.0, v1.*) |
--skill-id |
Skill ID (e.g. 10201) |
--skill |
Skill name (e.g. natural_language_processing) |
--locator |
Locator type (e.g. docker-image) |
--module |
Module path (e.g. core/llm/model) |
--domain-id |
Domain ID |
--domain |
Domain name |
--created-at |
Record creation timestamp (e.g. 2024-*, >=2024-01-01) |
--author |
Author name |
--schema-version |
OASF schema version |
--module-id |
Module ID |
--annotation |
Annotation key=value |
--verified |
Only verified records; --verified=false for records without verified name ownership |
--trusted |
Only trusted records (signature verification passed); --trusted=false for records without a trusted signature |
--safe |
Only records where all security scanners reported is_safe=true; --safe=false for records where at least one scanner did not |
--scan-severity |
Only records whose highest scan severity meets or exceeds a threshold (NONE, INFO, LOW, MEDIUM, HIGH, CRITICAL) |
--verified, --trusted and --safe are tri-state: omitting the flag does not filter on that property at all, while an explicit =false filters for records that failed the check.
Exclude flags:
Every filter above except the three booleans has an --exclude- twin —
--exclude-name, --exclude-version, --exclude-skill-id, --exclude-skill,
--exclude-locator, --exclude-module, --exclude-domain-id, --exclude-domain,
--exclude-created-at, --exclude-author, --exclude-schema-version,
--exclude-module-id, --exclude-annotation and --exclude-scan-severity. Each is
repeatable and takes the same values as the flag it mirrors; wildcards, comparison
operators and : behave identically, and ! is an ordinary character.
dirctl search --exclude-skill "natural_language_processing"
dirctl search --domain "life_science/*" --exclude-author "bot*"
dirctl search --skill python --exclude-skill nlp # has python, does not have nlp
Values of one filter are combined with OR, while every excluded value must
not match. Excluding a multi-valued field drops the record if any of its
values match, so --exclude-skill nlp drops a record whose skills are
[nlp, python].
To exclude on a boolean, use the tri-state =false form (--trusted=false) rather
than an --exclude- flag.
--exclude-scan-severity also keeps never-scanned records
It excludes records that have a scan report at or above the given threshold.
A record that was never scanned has no such report, so it is kept. Combine it
with --safe when you want scanned-and-clean only.
Output and pagination flags:
| Flag | Description | Default |
|---|---|---|
--format |
Output format: cid or record |
cid |
--sort |
Result ordering for structured search: relevance, popularity, or recency |
recency |
--limit |
Maximum results | 100 |
--offset |
Pagination offset | 0 |
--verbose |
Print NL signal decomposition and per-signal hit counts to stderr (NL mode only) | false |
Example
# Search by record name
dirctl search --name my-agent
# Search by version
dirctl search --version v1.0.0
# Search by skill ID
dirctl search --skill-id 10201
# Complex search with multiple criteria
dirctl search --limit 10 --offset 0 \
--name my-agent \
--skill "Text Completion" \
--locator docker-image \
--format record
# Sort by most recently published
dirctl search --skill "code-review" --sort recency
# Sort by most popular (pull frequency)
dirctl search --domain finance --sort popularity
# Security scan filters
dirctl search --safe
dirctl search --scan-severity HIGH
dirctl search --safe --scan-severity MEDIUM
Security & Verification
Security Scanning
The Directory reconciler automatically runs security scanners against records and stores the results as OCI referrers. Scan results are indexed in the local database so they can be queried as search filters. Three scanners are supported: mcp-scanner for MCP server source code, skill-scanner for agent skill bundles, and a2a-scanner for A2A AgentCards.
Pull scan reports for a record:
dirctl pull <cid> --scan-report
The response includes a scanReports array. Each entry covers one scanner type and contains:
| Field | Description |
|---|---|
scanner_type |
Scanner that produced this report (SCANNER_TYPE_MCP, SCANNER_TYPE_SKILL, SCANNER_TYPE_A2A) |
scanner_version |
Version of the scanner binary |
scanned_at |
RFC 3339 timestamp of when the scan ran |
is_safe |
true if the scanner found no security issues |
max_severity |
Highest severity across all findings (SEVERITY_NONE, SEVERITY_INFO, SEVERITY_MEDIUM, SEVERITY_HIGH, SEVERITY_CRITICAL) |
analyzers |
Analyzer modules that were invoked |
findings |
List of individual findings (rule ID, severity, message, location, remediation) |
Filter records by scan status:
# Records where all scanners reported is_safe=true
dirctl search --safe
# Records whose highest severity meets or exceeds a threshold
dirctl search --scan-severity HIGH
dirctl search --scan-severity MEDIUM
# Combine with other filters
dirctl search --name "cisco.com/*" --safe
dirctl search --safe --scan-severity MEDIUM
A record appears in --safe results only when at least one scanner has run and no scanner has reported is_safe=false. Records where all scanners were skipped (no source repo, no skill bundle, no A2A AgentCard) are not included.
Name Verification
Record name verification proves that the signing key is authorized by the domain claimed in the record's name field.
Requirements:
- Record name must include a protocol prefix:
https://domain/pathorhttp://domain/path - A JWKS file must be hosted at
<scheme>://<domain>/.well-known/jwks.json - The record must be signed with the private key corresponding to a public key present in that JWKS file
Workflow:
-
Push a record with a verifiable name.
dirctl push record.json --output raw # Returns: bafyreib... -
Sign the record (triggers automatic verification).
dirctl sign <cid> --key private.key -
Check verification status using
dirctl naming verify.
dirctl sign <cid> [flags]
Signs records for integrity and authenticity. When signing a record with a verifiable name (e.g., https://domain/path), the system automatically attempts to verify domain authorization via JWKS. See Name Verification for details.
For encrypted private keys, COSIGN_PASSWORD is used when it is set, including when
it is explicitly empty. Use --password-stdin to opt in to reading a password from
standard input. Otherwise, an interactive terminal prompts for the password; a
non-interactive process does not read standard input implicitly.
Local and inline PEM keys must use the encrypted Cosign/Sigstore format produced by
cosign generate-key-pair.
The --key flag accepts PEM content, a local file, an HTTP(S) URL, an environment
variable reference, or a KMS URI. The supported KMS URI formats are:
| Provider | URI format |
|---|---|
| AWS KMS | awskms://[ENDPOINT]/[ID/ALIAS/ARN] |
| Google Cloud KMS | gcpkms://projects/[PROJECT]/locations/[LOC]/keyRings/[RING]/cryptoKeys/[KEY] |
| Azure Key Vault | azurekms://[VAULT_NAME][VAULT_URI]/[KEY] |
| HashiCorp Vault | hashivault://[KEY] |
Configure the selected provider's credentials before running dirctl.
Example
# Sign with private key
dirctl sign <cid> --key private.key
# Sign with OIDC (keyless signing; default when --key is omitted)
dirctl sign <cid> --fulcio-url https://fulcio.example.com
# Sign with a pre-issued OIDC token (non-interactive)
dirctl sign <cid> --oidc-token "$OIDC_TOKEN"
# Explicitly read an encrypted private-key password from standard input
printf '%s' "$KEY_PASSWORD" | dirctl sign <cid> --key cosign.key --password-stdin
# Sign with a key managed by Google Cloud KMS
dirctl sign <cid> --key "gcpkms://projects/PROJECT/locations/LOCATION/keyRings/RING/cryptoKeys/KEY"
dirctl naming verify <reference>
Verifies that a record's signing key is authorized by the domain claimed in its name field. Checks if the signing key matches a public key in the domain's JWKS file hosted at /.well-known/jwks.json.
Supported Reference Formats:
| Format | Description |
|---|---|
<cid> |
Verify by content address |
<name> |
Verify the highest version (see Version Resolution under dirctl pull) |
<name>:<version> |
Verify a specific version |
Example
# Verify by CID
dirctl naming verify bafyreib... --output json
# Verify by name (latest version)
dirctl naming verify cisco.com/agent --output json
# Verify by name with specific version
dirctl naming verify cisco.com/agent:v1.0.0 --output json
Example verification response:
{
"cid": "bafyreib...",
"verified": true,
"domain": "cisco.com",
"method": "jwks",
"key_id": "key-1",
"verified_at": "2026-01-21T10:30:00Z"
}
dirctl verify <record-cid> [flags]
Verifies a record signature. Signatures are fetched from the directory.
| Flag | Description |
|---|---|
--key |
Public key file path, URL, or KMS URI |
--oidc-issuer |
OIDC issuer to match (supports regexp) |
--oidc-subject |
OIDC subject to match (supports regexp) |
--from-server |
Use the server's cached verification result |
--ignore-tlog |
Skip transparency log verification |
Example
# Verify any valid signature on the record
dirctl verify <record-cid>
# Verify against a specific public key
dirctl verify <record-cid> --key /path/to/cosign.pub
# Verify against an OIDC identity
dirctl verify <record-cid> \
--oidc-issuer https://github.com/login/oauth \
--oidc-subject user@example.com
dirctl validate [<file>] [flags]
Validates OASF record JSON from a file or stdin against the OASF schema. The JSON can be provided as a file path or piped from stdin (e.g., from dirctl pull). A schema URL must be provided via --url for API-based validation.
| Flag | Description |
|---|---|
--url <url> |
OASF schema URL for API-based validation (required) |
Example
# Validate a file with API-based validation
dirctl validate record.json --url https://schema.oasf.outshift.com
# Validate JSON piped from stdin
cat record.json | dirctl validate --url https://schema.oasf.outshift.com
# Validate a record pulled from directory
dirctl pull <cid> --output json | dirctl validate --url https://schema.oasf.outshift.com
# Validate all records (using shell scripting)
for cid in $(dirctl search --output jsonl | jq -r '.record_cid'); do
dirctl pull "$cid" | dirctl validate --url https://schema.oasf.outshift.com
done
Synchronization
dirctl sync create <url>
Creates peer-to-peer synchronization.
Example
# Create sync with remote peer
dirctl sync create https://peer.example.com
dirctl sync list
Lists active synchronizations.
Example
# Show all active syncs
dirctl sync list
dirctl sync status <sync-id>
Checks synchronization status.
Example
# Check specific sync status
dirctl sync status abc123-def456-ghi789
dirctl sync delete <sync-id>
Removes synchronization.
Example
# Delete a sync
dirctl sync delete abc123-def456-ghi789