ATHANOR
RU GitHub ↗

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 a loaded flag), not on every read;
  • Set/Delete — update the in-memory value map and immediately rewrite the whole secrets_file via Encrypt (chapter 2) — the whole file at once, not field by field;
  • Run — runs an external command, injecting every secret into its environment via exec.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.