4 / 8
CLI and Store
The last two chapters covered what gets stored and who can see what. This one is about what you actually touch, day to day, sitting at a terminal.
Store: the one place a secret lives in memory
internal/store.Store sits between the encrypted file and everything else —
CLI, TUI, MCP server. It does three things:
- lazy decryption — the file is decrypted once per process, on first
access (
load(), guarded by aloadedflag), not on every read; Set/Delete— update the in-memory value map and immediately rewrite the wholesecrets_fileviaEncrypt(chapter 2) — the whole file at once, not field by field;Run— runs an external command, injecting every secret into its environment viaexec.Cmd.Env, and logs the call (chapter 6).
Everything else in the project is thin wiring around these three moves.
Commands
athanor init
athanor init [--own-key] [--secrets-file PATH]
Creates athanor.yaml in the current directory. If
~/.config/athanor/identity.txt doesn't exist yet, it generates a fresh age
key pair and warns you to back it up; if it's already there, it's reused —
which is where "one key for every project" from chapter
2 comes from. --own-key creates the
identity locally (./identity.txt) instead of using the shared one, for
projects that need stronger isolation. --secrets-file changes the default
path (./secrets.enc.yaml), say to point straight at a file shared with
another project.
athanor key backup
Prints the identity file's contents to stdout with a warning — and, deliberately, does nothing clever beyond that. Its only job is to hand you the raw text so you can put it in a password manager or on paper yourself.
athanor add NAME [--expose]
athanor add DB_PASSWORD
value for DB_PASSWORD (hidden): ***
Saved DB_PASSWORD (expose=false)
The value is never a command argument — only hidden input
(term.ReadPassword if stdin is a terminal, otherwise a plain line from
stdin, for cases like echo "val" | athanor add KEY). That's not
overcaution: command-line arguments are visible via ps to any process on
the machine, and hidden input or stdin avoids that entirely.
For a new name, expose defaults to false. When updating an existing
secret, omitting the flag preserves its current policy; an explicit
--expose enables retrieval through get_secret. To disable it again, set
expose to false in the manifest.
athanor list
Prints secret names and their expose status — never values. The same rule
applies here as to the manifest itself: the list of secrets isn't secret,
only their values are.
athanor run -- COMMAND [ARGS...]
athanor run -- npm run dev
Everything after -- is the command's argv, not a shell string. Secrets go
into the process's environment and nowhere else — the exact mechanism
run_with_secrets uses over MCP, just invoked directly from a terminal,
with no agent standing in between.
Checkpoint question. Why doesn't athanor add take a secret's value as a
command-line argument (athanor add KEY value), prompting for it separately
every time instead?
Answer: command arguments are visible to any local process via ps — the
value would sit in the process list in plain sight, if only briefly. Hidden
input and stdin avoid that.