monkeys

Skill

plugins/monkeys/skills/monkeys/SKILL.md, the file a coding agent loads, served verbatim at monk3ys.dev/skill.

monkeys

When a command needs an API key, token or password, monkeys gives it one without showing it to you:

monkeys run OPENROUTER_API_KEY ./hello.sh

Each secret sits in the operating system's own vault, the macOS keychain or the Linux Secret Service, under the name of the environment variable that carries it. No command prints a remembered secret, and none should: one that reaches you is in this transcript for good, and a vault cannot take it back.

If monkeys is not on PATH, say so and point at https://github.com/eastriverlee/monkeys. Do not work around it by asking for the secret yourself.

Use a secret

Name the keys the command reads, then the command:

monkeys run OPENROUTER_API_KEY ./hello.sh
monkeys run OPENROUTER_API_KEY,GITHUB_TOKEN ./deploy

Nothing in that line holds a secret, so nothing you write can spill one. The exit status and the signals are the command's own. Its output comes back through monkeys, and a remembered secret in it comes back as [redacted KEY]. Reach for monkeys run --all <command> only when you cannot tell which keys the command reads.

The command you start is what expands the variable, since that is where it exists. $KEY written into the monkeys run line is expanded by the shell you are already in, which does not have the secret:

monkeys run OPENROUTER_API_KEY curl -H "Authorization: Bearer $OPENROUTER_API_KEY" ...
# sends an empty Bearer

monkeys run OPENROUTER_API_KEY sh -c 'curl -H "Authorization: Bearer $OPENROUTER_API_KEY" ...'
# the single quotes reach the child intact

A file that has to end up with the secret in it takes the redirect inside the command, for the same reason. The secret then goes from the child straight into the file, never passing monkeys, which would redact it on the way.

monkeys run OPENROUTER_API_KEY sh -c 'envsubst < template > config'

Inside a project

A .monkeys file lists the keys the project needs. Read it before adding a key; it is the list.

+foo
@test,production
DATABASE_URL
STRIPE_SECRET_KEY
PORT=3000

In that directory, or below it within the git checkout, run takes only the command and reads the file's first profile. A leading @profile picks another one the file declares, and a KEY=value line is a value that is not secret, which run puts in the environment too:

monkeys run ./hello.sh
monkeys run @production ./deploy

A profile never falls back to the keys with no profile. A key missing in @production stays missing there even when a copy with no profile exists, so one project never quietly reads another's secret. Reaching those keys from inside a project is a bare @, with the key named again:

monkeys run @ ANTHROPIC_API_KEY claude

When the code you write starts reading a new variable, the key belongs in that file. remember puts it there itself, under the profile the secret went to, so ask a human to run it rather than editing the file around them:

monkeys remember STRIPE_SECRET_KEY

When the code stops reading one, monkeys drop STRIPE_SECRET_KEY forgets the secret and removes the key from the file. It asks a person before it does, so that one is theirs to run as well.

Adding a profile is the one edit remember cannot make for you, since no command writes an @ line. A new @test goes at the top, because the first profile is the one run takes when none is given and the default should be the harmless one; anything else goes after the profiles already there. Where the person says it goes wins.

The rest of the file's grammar, namespaces and several profiles among it, is in monkeys help. Reach for it when a file surprises you; the file is usually written already, and reading it is enough.

Never write a .env

Every environment variable a project needs belongs in .monkeys, secret or not. Do not create a .env, do not add a line to one, and do not tell someone to put a variable there.

  • A variable that is not secret goes in as KEY=value, which you write yourself: monkeys remember --public PORT.
  • A secret goes in as a bare key, and a human remembers it.

When a tool insists on a .env file, monkeys poo writes one from that same list, keys only and no secrets, and monkeys run still carries the values. monkeys poo @profile --WITH_SECRETS writes each key as a vault lookup for a shell to resolve, which is also safe to read. Both land in /tmp; add --path . to put one in the project, which asks first. Write the .env one of those two ways or not at all.

Eat a .env you find

A project that still has .env files is one command away. Name the keys that are not secret; every other key becomes a secret in the vault, and the files are deleted once everything is remembered:

monkeys eat --public PORT,NODE_ENV

Run this yourself when you find a .env in a project you are working in. eat reads the files, so no secret passes through you, and --public answers every question before it is asked. Without it eat asks one question per key and needs a terminal, which is a human's run.

When a key is missing

run stops before anything happens and names what to ask for:

monkeys: SUPER_SECRET is not remembered yet in @foo.test
nothing happened. a human types the secret into:
  monkeys remember @foo.test SUPER_SECRET
then try again.

Pass that on. monkeys doctor --short prints one missing @profile: A,B line per profile with a gap, nothing when there is none, and exits non-zero while any remains.

Check without reading

monkeys list gives the keys, as blocks by profile. monkeys preview gives each secret masked, with its length, which is enough to tell one pasted whole from one that lost a character.

Leave to a human

Remembering is a human's job: typing a secret yourself puts it in the conversation before it reaches the vault. Say what is needed and stop.

  • monkeys remember remembers a secret, and monkeys remember --public PORT remembers a value that is not secret, which you may run yourself.
  • monkeys pack writes every secret of a profile into one file and takes over the clipboard to hand out its passphrase. Sharing is a decision, not a step, and a bundle that would carry @personal asks in a word before it goes.
  • monkeys unpack fills a profile from a shared bundle. It asks for a passphrase and deletes the bundle.
  • monkeys fill @production --with @test copies what is missing between profiles, which may put a test secret into production.
  • monkeys forget and monkeys drop delete the only copy of a secret there is, so both ask first and neither can be answered from a pipe.

Never

  • echo "$SOME_KEY", env, printenv
  • writing a secret out yourself, into a file, a log, a commit or a bug report
  • monkeys poo --EXPAND_DANGEROUSLY, which puts the secrets themselves in a file; it refuses anything but a terminal, so this is a human's to run

Where a config file wants the secret, write whatever reference its format offers, such as ${OPENROUTER_API_KEY}, and let the program expand it.

monkeys help carries the full contract.

On this page