Named bundles of environment variables, stored in the OS keychain, an encrypted file, or an age-encrypted vault — and injected into a child process without ever printing them.
npm i -g @phnx-labs/secrets-cli
secrets create prod --description "Production keys for the api stack"
secrets add prod STRIPE_API_KEY # prompts; value never echoes
secrets add prod LOG_LEVEL --value info # a non-sensitive literal
secrets exec prod -- ./deploy.sh # values ride the child env only
secrets runs on its own. It has no daemon, no fleet registry, and no
dependency on any other tool; other programs drive it through a bounded
private pipe (secrets __serve) or the SDK (@phnx-labs/secrets-cli).
Everything hangs off SECRETS_HOME (default ~/.secrets):
| Path | What |
|---|---|
secrets.yaml |
defaults (backend, policy, agent.*) and named provider connections |
.cache/secrets/ |
the AES-256-GCM encrypted file store |
.secrets-key/ |
the 0600 machine-local key for that store |
vault.age |
the age-encrypted synced vault |
.cache/helpers/secrets-agent/ |
the macOS broker's socket, pid and token |
.cache/events.jsonl |
the value-free event log (secrets activity) |
Point SECRETS_HOME at a directory that already holds this layout and the
stores are adopted in place — nothing is copied or re-encrypted, and macOS
Keychain items are keyed by service name, not by path.
- keychain (macOS) — items live in the login keychain behind Touch ID. The
broker (
secrets unlock,secrets status) holds an unlocked bundle in memory for ~7 days so concurrent readers do not each prompt.secrets unlock <bundle> --scope <scope>narrows that grant to one opaque scope (a harness name, a job id); a reader passes the same scope (SECRETS_SCOPE) to see it. Without--scopethe grant is global. - file — AES-256-GCM under a machine-local key; readable headlessly.
- vault — age-encrypted file you sync yourself (
secrets vault unlock).
secrets list [query] secrets view <name> secrets activity [name]
secrets create [name] secrets add <bundle> <key> secrets rotate <bundle> <key>
secrets remove <bundle> <key> secrets rename <old> <new> secrets delete <name>
secrets describe <name> [text] secrets policy <bundle> [p] secrets generate [length]
secrets exec <bundle> -- <cmd> secrets get <item> [key] secrets set <item>
secrets import [bundle] … secrets export [bundle] … secrets migrate
secrets unlock [names…] secrets lock [names…] secrets status
secrets start | stop secrets vault unlock | lock secrets push | pull | remote-list
secrets import-keyring secrets migrate-acl secrets rekey | rotate-passphrase
secrets mcp
secrets <command> --help prints an ordered example sequence before the flags.
--host <target> on exec, import, export, list, view and unlock
takes an OpenSSH alias or user@host, exactly as ssh would. exec --host
resolves the bundle on that host and runs the child locally; to run the
child remotely, put secrets exec on the other side of ssh. Credential
transfer refuses a host whose key is not pinned (secrets hosts pin <target>).
bun install
bash scripts/test.sh # vitest, real stores and processes, no mocks
bash scripts/build.sh # tsc -> dist/
bash scripts/install.sh # side-by-side dev install as `secrets-dev`
bash scripts/release.sh <ver> # dry run; --apply publishes and smoke-installs
See AGENTS.md for the contract this repository holds itself to.