9. Config, auth, and secrets
Source 09-config-auth-secrets.md
- Precedence: flags, then env vars, then project config, then user config, then system config. Which of these a CLI implements is optional. It may implement any subset, and the order among the ones it does implement always holds. This spec does not define where project or system config live.
- Standard locations. Each CLI keeps its files in a folder named after the tool (the binary name, without a
-read/-writesuffix, see below):- Config:
~/.config/<cli>/(settings). - Data:
~/.local/share/<cli>/(content the user would not want to lose). - State:
~/.local/state/<cli>/(logs, lock files; safe to delete). - An organisation that wants one shared root for all its CLIs (for example
~/.config/<org>/<cli>/) fixes that in its own profile. The principles do not. - Names we define are singular. Folder and file names use the singular:
backup/,log/, notbackups/,logs/. - Inside the data folder:
backup/holds the previous versions kept by writes (section 6a). - Inside the state folder:
log/holds diagnostic log files, if a CLI keeps any. By default a CLI writes none and prints diagnostics only in verbose mode (section 12).- The lock file (section 7) sits beside
log/in the state folder, not inside it.
- Log file convention (if a CLI keeps a log file). One file per day, named
<cli>-YYYY-MM-DD.log(for exampletracker-2026-10-30.log), using the UTC date. Plain text lines, the same content as verbose diagnostics. Secrets are never written. Each process appends whole lines, so two processes running at once do not need a lock to share the day’s file. - Retention for
backup/andlog/: a default time (for example 7 days, an adopter fixes the value). A write that adds a backup, or a CLI that writes a log line, first deletes files in that folder older than that. No background job is needed. - Examples:
~/.config/tracker/,~/.local/share/notebook/,~/.local/state/acme/. - A read/write pair (where an adopter splits a tool in two) shares one folder set.
acme-readandacme-writeboth use~/.config/acme/,~/.local/share/acme/, and~/.local/state/acme/. The folder name is the tool name. A single-binary tool such astrackeruses its own name.- A query command never changes data in the shared folders (section 2). It may write lock files, and log entries if the CLI keeps a log, in the state folder.
- Only the write binary writes data and backups. The read binary may read them.
- One
config.yamlserves both binaries.
XDG_CONFIG_HOME,XDG_DATA_HOME,XDG_STATE_HOME, if set, replace~/.config,~/.local/share,~/.local/state. A per-CLI override (TRACKER_CONFIG_DIR,TRACKER_DATA_DIR,TRACKER_STATE_DIR) beats both. Useful for tests.- Platforms. The layout is the same on Windows, macOS, and Linux, so there is one predictable location everywhere. On Windows,
~is the user profile, so the paths are%USERPROFILE%\.config\<cli>and so on, rather than%APPDATA%. - Config format: YAML. Main file
config.yaml. - Create folders on first write only. A query never creates them.
<cli> pathprints all three folders in use, so people and agents can find them.<cli> config get|listis a query.<cli> config set|unsetis a mutation and follows section 6a (one key per command, showsbefore -> after, needs-confirm).- Config holds settings only. No secrets (see below).
- Config:
- Never take secrets as flag values.
- No secrets in environment variables. A CLI never reads a credential from an env var, in CI, in an agent run, or anywhere else. (This follows clig.dev and rejects the token env var that some agent CLIs allow.) Env vars still carry non-secret settings.
- Where credentials come from. A credential file whose path the caller passes as a parameter, or a dedicated credential CLI that an adopter provides. Never a flag value, never config, never an env var.
- How a credential CLI hands over a credential. The CLI that needs one runs the credential CLI and reads the credential from its stdout.
- The credential command is a query and follows every rule in this spec:
-output jsongives{"result": ...}, and errors use the standard error shape. - The value is never written to logs, error
details, or the output of any other command. A CLI holds it in memory only for the call that needs it. - If the credential CLI is not found or refuses, the CLI fails with
auth(exit 2), and the fix says how to set up the credential.
- The credential command is a query and follows every rule in this spec: