CLI

Daily use

Using fd0 through a coding agent? Install the fd0 skill to give it the commands and workflows for your vault.

The CLI works mostly from local state. fd0 sync is the explicit network command; the agent can also sync after unlock when on_unlock = true.

One grammar for every module

Six modules hold items: secret, pass, ssh, key, kube, and talos. They take the same verbs, so learning one teaches the rest.

$ fd0 <module> add <name> ...              # create; refuses an existing name
$ fd0 <module> edit <name> --flag value    # change only the fields you name
$ fd0 <module> show <name>                 # human-readable, secrets masked
$ fd0 <module> list                        # alias: ls
$ fd0 <module> rename <name> <new-name>
$ fd0 <module> move <name> --to-scope <scope>
$ fd0 <module> rm <name>                   # tombstone
$ fd0 <module> history show <name>         # versions, newest first
$ fd0 <module> history restore <name> <seq>

list takes --json on every module, with the same key names throughout and [] rather than null when empty. Secret material is never in that output — fd0 key list --json carries the fingerprint and the authorized_keys line, never the private half.

edit patches; add --force replaces. An edit leaves every field you did not name alone, and passing an empty value clears exactly that one field. A forced add rewrites the whole record, so anything the command did not carry returns to its default. An edit that changes nothing writes nothing and does not burn a revision.

Restore writes forward. history restore appends a new version carrying the old content instead of rewinding the chain, so the history stays append-only and the undo is itself auditable.

Module-specific commands sit alongside the shared verbs, not instead of them: pass field/notes/totp/file, ssh connect/tag/enable, kube sync, talos secrets.

Plain secrets

A plain secret is a name and a value. Everything richer belongs to one of the other modules.

fd0 secret set <NAME> <value> [--scope <scope>]

Store a string secret in a scope. Use - as the value to read it from stdin. Without --scope, fd0 uses the only live scope, asks interactively, or requires --scope in non-interactive use.

fd0 secret get [<NAME>] [--raw] [--scope <scope>]

Print a secret. Without a name, fd0 opens the interactive picker. --raw drops the trailing newline.

fd0 secret copy <NAME> [--clear-after=30s]

Copy a secret to the clipboard and clear it after the timeout.

fd0 secret list [--json] [--all]

List plain secret names across scopes. --all also lists records owned by other modules — hosts, keys, pass items, clusters. Values stay encrypted until you request one.

fd0 secret rm <NAME> [--scope <scope>]

Write a tombstone for a secret. The old event remains audit history.

Each module owns its own records, and the secret commands say so rather than acting on someone else's:

$ fd0 secret rm host:prod
✗ "host:prod" is a host, not a plain secret
  use: fd0 ssh rm prod
The older top-level spellings — fd0 get, fd0 set, fd0 rm, fd0 ls, and fd0 copy — still work and will keep working. They no longer appear in --help. Prefer the fd0 secret form in anything you write down.

Item history

Every module keeps earlier versions of its items. The listing gives you the sequence numbers restore takes.

$ fd0 secret history show DEPLOY_KEY
$ fd0 secret history restore DEPLOY_KEY 4
$ fd0 ssh history show prod-db --json

Scopes and sharing

A scope is the sharing boundary. Add a member to share every current secret in the scope. Remove a member to rotate the scope key for future writes.

# Alice exports her card.
$ fd0 card export

# Bob imports Alice, Alice imports Bob, then Alice grants access.
$ fd0 card import "fd0://card/..." --label bob
$ fd0 scope add-member bob --scope work
$ fd0 sync

# Bob discovers the scope on his next sync.
$ fd0 sync
$ fd0 secret list
fd0 card export

Print your signed card and safety number. Share the card over any channel; verify the safety number over a trusted channel.

fd0 card import <fd0://card/...> --label <name>

Pin another identity under a local label.

fd0 scope add-member <label> --scope <scope>

Grant a pinned card access to the scope.

fd0 scope remove-member <label> --scope <scope>

Remove access and rotate the scope key.

Roles

Every member can read every item in the scope. A role limits what a member may change. Members without an explicit role are admins, so existing scopes keep working as before.

role     read   change items   change members and roles
admin    yes    yes            yes
writer   yes    yes            no
reader   yes    no             no
$ fd0 scope add-member ci-runner --scope deploy --role reader
$ fd0 scope role ci-runner writer --scope deploy
$ fd0 scope members deploy

The server and every member's client check roles against the signed scope history, so a modified client or server cannot slip in a change the role forbids. A scope always keeps at least one admin: the last admin can neither leave nor demote themselves while other members remain. Writers and readers cannot leave on their own; fd0 scope leave hides the scope on that device, and an admin removes them.

Roles do not limit reading. To take away read access, remove the member and rotate the values they could see. Assigning writer or reader needs fd0 0.19.0 or later and a server with role support (fd0.sh has it); update every device in the scope first.

Values readable while locked

A grant keeps one value readable on this device while fd0 is locked, for a job or tool that runs as you and needs exactly that value. You create it in a terminal and authenticate again; it lasts 30 days unless you choose another --ttl, at most 365 days.

$ fd0 secret grant cld-client-secret --scope private-pass
$ fd0 pass grant mail password --scope work
$ fd0 service grant app token --scope ops

# while locked, with the exact name and --scope
$ fd0 secret get cld-client-secret --raw --scope private-pass
fd0 secret grants

List this device's grants for secrets, pass and service fields, with vault and expiry. Never shows values.

fd0 secret revoke <id>

Remove a grant. fd0 lock --all stops all grants until the next unlock.

While a grant is active, every program running as your user can read that value. Renaming or deleting the value, or removing the field, removes the grant; a new value with the same name needs a new grant.

Unlock methods

Auth methods are stored in the vault. The default unlock method is a local device preference in ~/.fd0/config.toml; it is not synced to other machines. When several methods are available, interactive unlock and SSH grant authorization show the same method chooser, with the default preselected. Only an explicit --method skips it.

fd0 auth ls

List enrolled unlock methods. The current session is marked with *, and the local default is marked with default.

fd0 auth default

Show the default unlock method for this device.

fd0 auth default yubikey

Use YubiKey unlock by default on this device. Use passphrase or a method_id instead when needed.

fd0 auth default --clear

Clear the local default. Interactive unlocks ask which enrolled method to use; non-interactive commands retain a deterministic fallback.

Local health

fd0 status

Show whether the agent is running and whether the vault is unlocked.

fd0 doctor

Replay local chains, check vault tips, auth wraps, scope keys, YubiKey flavor state, orphan chain files, and SSH socket health.

fd0 lock

Lock the vault in the running agent and zeroize in-memory keys.

fd0 agent status

Show fd0-agent process, vault, agent socket, and SSH socket state.

fd0 agent restart

Replace fd0-agent with the current binary and repair stale agent sockets.

fd0 agent stop

Stop fd0-agent and clean stale sockets when safe.

If a command needs an unlocked vault in an interactive terminal, fd0 prompts for the passphrase instead of failing immediately.