Secrets
Per-skill credentials that never live inside the skill directory.
There is no official credential mechanism for skills, and skill directories get zipped, uploaded, and shared; anything inside them leaks. So API-calling skills mostly don't get written, because the primitive is missing.
steer secrets is that primitive. Resolution order:
- Environment variable with the exact key name (
GITHUB_TOKEN) - OS keychain: macOS
security, Linuxsecret-tool - Steer's file store:
~/.steer/skills/<skill>/secrets.json,0600
Values never touch the skill directory, and steer validate /
steer package treat credential-looking files inside a skill as findings.
When to use it
- The skill calls anything authenticated: a payments API, a deploy target, a webhook. If a run can fail with a 401, wire secrets in.
- This is the pattern real skills hand-roll at the most length. Vercel's
vercel-cli-with-tokens
spends its first ~90 lines on a credential cascade (check
VERCEL_TOKEN, grep.env, recognizevca_prefixes, ask the user last), and the.envgrep it documents echoes the token value into the conversation transcript.steer secrets checkis that cascade as one names-only command; the value never reaches the agent. - Skip it when the skill touches nothing authenticated. An unused component is one more SKILL.md section for the agent to read.
The agent ↔ human handoff
The pattern a SKILL.md gets from steer new --with secrets (name the real
keys with --secrets GITHUB_TOKEN; without it the name derives from the
skill name):
Check credentials. Run `python3 scripts/steer.py secrets check GITHUB_TOKEN`.
If one is missing, ask the user to run
`python3 scripts/steer.py secrets set GITHUB_TOKEN`
(never ask them to paste the value into the chat), then re-check.check exits 0/1 and, when missing, prints the exact remediation command
in the spelling it was invoked with; the bundled runtime spells it as a
full path to itself, so the command works from whatever directory the
agent is in. The agent never needs to see the value; scripts read it at
execution time.
CLI
steer secrets set KEY # hidden prompt on a TTY, stdin otherwise
echo "$VALUE" | steer secrets set KEY --stdin
steer secrets set KEY --backend file # skip the keychain
steer secrets check KEY # status + remediation, exit 0/1
steer secrets get KEY # prints the raw value (for scripts)
steer secrets list # names + which backend resolves each
steer secrets unset KEY # removes from keychain + file storeEvery subcommand takes --json for machine-readable output. A value can
also be passed as an argument (steer secrets set KEY VALUE), though that
is discouraged outside scripts since it lands in shell history.
The skill is resolved from --skill, the STEER_SKILL env var, or the
nearest SKILL.md above the working directory. A skill's bundled runtime
(python3 scripts/steer.py secrets ...) resolves the skill from its own
location instead, and an inherited STEER_SKILL cannot redirect it.
Python
from steer import Secrets, MissingSecretError
secrets = Secrets("pr-review")
key = secrets.require("GITHUB_TOKEN", hint="github.com/settings/tokens")
# raises MissingSecretError whose message contains the exact
# `steer secrets set ...` command to relay to the human
token = secrets.get("OPTIONAL_TOKEN") # None when absent
origin = secrets.status("GITHUB_TOKEN") # "env" | "keychain" | "file" | NoneNotes
setdefaults to the keychain when one is available, the file store otherwise; values passed as CLI arguments are discouraged (shell history).listenumerates steer-managed keys plus env vars steer has seen for them; it cannot enumerate env-only secrets it was never told about.- Keychain entries use service
steer.<skill>, account<key>.