monkeys

run

Runs the command with the named secrets in its environment, and nowhere else. Nothing in the line holds a secret, so nothing you write can spill one.

monkeys run [@profile] <KEY[,KEY...]> <command> [argument...]
monkeys run [@profile] --all <command> [argument...]
monkeys run <command> [argument...]                    # inside a project

Runs the command with the named secrets in its environment, and nowhere else. Nothing in the line holds a secret, so nothing you write can spill one:

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

Once every secret is in place, run stays between the command and your terminal to redact what comes back; the exit status and the signals are the command's own.

Inside a project

In a directory that holds a .monkeys file, or below it within the checkout, run takes only the command and reads the file's keys from the default profile. A leading @profile picks another declared one:

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

A key the file already lists is refused rather than run as a program:

monkeys run STRIPE_SECRET_KEY ./hello.sh
monkeys: ~/foo/.monkeys already lists STRIPE_SECRET_KEY for @test
inside a project, run takes only the command: monkeys run <command>

--all

--all means every key stored under the profile, listed or not, for when you would rather not say which:

monkeys run --all ./bench

Outside a project that is every key with no profile, which is the wide end of the tool. Name the keys the command reads when you can.

When a secret is missing

A key you have not stored a secret for stops the run before it starts, and says where it is missing from:

monkeys run OPENROUTER_API_KEY,ANTHROPIC_API_KEY ./hello.sh
monkeys: ANTHROPIC_API_KEY is not stored yet
nothing ran. a human has to store it, then try again:
  monkeys set ANTHROPIC_API_KEY

Inside a project the message names the profile, and the set it asks for works from any directory:

monkeys: STRIPE_SECRET_KEY is not stored yet in @foo.test
nothing ran. a human has to store it, then try again:
  monkeys set @foo.test STRIPE_SECRET_KEY

That message is written to be passed on. An agent that meets it knows which keys are missing, that nothing happened, and what a human has to store.

Where the variable is expanded

run sets the variable for the command it starts, so that command is what expands it. Written into the monkeys run line itself, your own shell gets there first, and yours does not have the secret:

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

Single quotes pass the text through untouched, so a shell that run starts is the one that expands it:

monkeys run OPENROUTER_API_KEY sh -c 'curl -H "Authorization: Bearer $OPENROUTER_API_KEY" ...'

A script file works for the same reason, and reads better.

A variable the shell already exported is overridden for that command. Keys you leave out are passed through untouched.

What comes back

A stored secret in the command's output comes back as [redacted KEY]:

monkeys run OPENROUTER_API_KEY sh -c 'echo "key=$OPENROUTER_API_KEY"'
key=[redacted OPENROUTER_API_KEY]

That is the reflex this exists for. An agent that meets an empty variable will echo it, and now the echo says which secret was there and nothing else. The output is streamed as it arrives: a byte is held back only while it could still be the start of a secret, and on a terminal that moment shows as * until the next byte settles it.

It catches the secret written whole or in pieces, on stdout or stderr. It does not catch the secret transformed, so echo $KEY | base64 goes through; this is for the reflex, not for someone trying.

A short secret is redacted wherever it appears, so store secrets here and keep PORT=3000 in the repository.

--no-redact

--no-redact turns redaction off and runs the command in monkeys's place, for the one case that needs the secret in the output, such as writing it into a file a program will read:

monkeys run --no-redact OPENROUTER_API_KEY envsubst < template > config

A shell with a profile

monkeys run @production zsh hands a whole profile to one shell, which forgets it on exit. That is the way to work with a profile for a while without putting it into every shell you open.

On this page