steer

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:

  1. Environment variable with the exact key name (GITHUB_TOKEN)
  2. OS keychain: macOS security, Linux secret-tool
  3. 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, recognize vca_ prefixes, ask the user last), and the .env grep it documents echoes the token value into the conversation transcript. steer secrets check is 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 store

Every 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" | None

Notes

  • set defaults to the keychain when one is available, the file store otherwise; values passed as CLI arguments are discouraged (shell history).
  • list enumerates 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>.

On this page