Skip to content

Managing secrets

secrets is the operator’s door to the credential store. It writes through whichever backend the deployment configured, so it seals when the deployment seals and it holds no format knowledge of its own.

bun run src/cli/index.ts secrets set <key> [flags]
bun run src/cli/index.ts secrets list [flags]
bun run src/cli/index.ts secrets delete <key> [flags]
Flag Description Default
--scope <scope> instance, workspace or user instance
--workspace <id> Workspace id — required for --scope workspace —
--user <id> User id — required for --scope user —
--config <path> Config file path Auto-resolved (see config resolution)

Every command exits 0 on success, 2 for a usage error, and 1 for anything else.

secrets finds its config the way the server does (see config resolution), so a project-local ./.nimblebrain/nimblebrain.json wins over the work directory’s. When you run it from a directory that has one, and the work directory has a different one, it cannot tell which deployment you mean: it exits 2, names both files, and writes nothing. Pass --config with the one you mean.

Before it acts, set and delete print one line to stderr naming the config file read, the work directory, the backend, and whether it seals:

config /home/op/.nimblebrain/nimblebrain.json · work dir /home/op/.nimblebrain · backend file · seals yes

The line never carries the value. Read it before you type one.

The value comes from stdin, or from a hidden prompt when stdin is a terminal.

Terminal window
# piped
printf '%s' 'sk-…' | bun run src/cli/index.ts secrets set anthropic.api_key
# prompted — input is not echoed
bun run src/cli/index.ts secrets set anthropic.api_key

On a sealed deployment, set first checks its key against what is already stored. If the key in your shell opens none of the sealed secrets, it exits 1 before asking for the value and writes nothing, because a value sealed under that key is one the server cannot read. With nothing sealed yet there is nothing to check against: the value is sealed under your shell’s key, and if that is not the server’s key, the server refuses that secret on use, and refuses to start if nothing else on disk is sealed under its key. Delete the file or load the right key.

One trailing newline is trimmed, because echo and every heredoc add one. Everything else is stored byte-for-byte, so a PEM key or a JSON blob survives intact. An empty value is refused rather than written: it is almost always a pipeline that produced nothing, and storing it would replace a working credential with a blank that fails at a vendor hours later.

Terminal window
bun run src/cli/index.ts secrets list
# anthropic.api_key 2026-03-01T12:00:00.000Z
bun run src/cli/index.ts secrets delete anthropic.api_key

list prints one key and its last-write time per line, tab-separated, on stdout; “no secrets set in this scope” goes to stderr, so secrets list | wc -l counts only keys.

No command prints a secret, and there is no get. Using a secret is a connector’s job, at the moment it makes a request, through the path that writes an audit line when the value is revealed.

Terminal window
bun run src/cli/index.ts secrets set acme.db_url --scope workspace --workspace ws_00043a6dd4196727
bun run src/cli/index.ts secrets set acme.token --scope user --user usr_alex01

In practice you will mostly want instance, which is the default and the only scope with no other surface. A workspace secret is normally set on the connector’s page under Settings → Connectors, and a user secret is written by the OAuth flow that acquired it. See Secrets for what each scope owns.

  • Secrets — the store, its scopes, sealing and rotation.
  • Credentials — the three shapes a connector’s credentials take.
  • CLI overview — config resolution and the work directory.