Stop pasting API keys into Claude Code, Cursor, and other AI agents.
keypop pops a native dialog so your agent collects a secret it never sees β the value goes to a local file, never the model's context.
π macOS Β· π 60 lines of shell Β· π no infra, no daemon, no account
A secure secret-intake channel for AI coding agents (Claude Code, Cursor, etc.).
The problem: when an agent needs a credential, the obvious flow is "paste the key into chat." That value then lives in your transcript history, in logs, and in the model's context window β everywhere except where you want it.
The fix: the agent requests a secret by name; a native OS dialog collects the value out-of-band; the value goes straight to a local, gitignored file. The secret never transits the model's context.
agent ββ"I need OPENAI_API_KEY"βββΆ keypop βββΆ native macOS dialog (hidden input)
β you paste, hit Save
βΌ
~/.keypop/vault.env (chmod 600)
The agent sees only the confirmation (saved: OPENAI_API_KEY (164 chars)) β never the value.
git clone https://github.com/<you>/keypop.git
ln -s "$PWD/keypop/keypop" /usr/local/bin/keypop # or add to PATHmacOS only for now (uses osascript). Linux port via zenity/kdialog is a welcome PR.
# one dialog per secret, each labeled with what's needed and why
keypop OPENAI_API_KEY:"for embeddings" SUPABASE_SECRET_KEY- Check-first: a name the vault already has is skipped silently (no dialog) and reported as
already set. keypop only pops a dialog for names it's missing, so re-running is safe and cheap. Pass--forceto prompt and replace an existing value (rotating a key). - Save (or Enter) writes the value to the vault.
- Reveal / Hide toggles masking in place β unmask to check a paste, re-mask when done. Masked by default.
- Blank + Save skips just this one.
- Cancel all (or Esc) stops the rest.
Output reports names and character counts only β never values:
saved: OPENAI_API_KEY (164 chars)
skipped: SUPABASE_SECRET_KEY
Change the vault location with VAULT_FILE=/path/to/vault.env keypop ....
Got several keys, or a .env snippet someone sent you? Skip the one-at-a-time dialogs:
keypop --pasteOne dialog opens. Paste a block like:
OPENAI_API_KEY=sk-...
export SUPABASE_SECRET_KEY="ey..."
STRIPE_KEY=sk_live_...
keypop extracts every NAME=value line (handles export, quotes, and = inside values),
then shows a confirmation listing the key names it found β never the values β so a
misparse can't silently land in your vault. Confirm, and all of them save at once. The pasted
block never enters your shell history or the model's context.
Lines that aren't NAME=value (comments, prose, invalid names) are ignored.
Import an existing .env you already have on disk straight into the vault:
keypop --import ./.envIt parses the file, skips any names the vault already has (so you don't clobber),
and shows a names-only confirmation before writing. Add --force to replace existing values.
Put a project .env "in play" while collecting keys:
keypop --env ./.env OPENAI_API_KEY STRIPE_KEYWith --env, a name already in that .env counts as set (no dialog), and every key you do
save is written to both the vault and that .env β so your app can read it at runtime
without you copying anything by hand.
The vault is a plain KEY=value file, so consumption is boring on purpose:
# check before ever asking
grep -i openai ~/.keypop/vault.env
# load into a shell / project
set -a && source ~/.keypop/vault.env && set +aThe script is only half the system. The other half is a short doctrine you paste into your
agent's instructions (CLAUDE.md, system prompt, rules file) so it reaches for the popup
instead of asking for the value in chat. See AGENT.md β copy it in verbatim.
- The value never enters the model's context β accountability for the secret stays with the human at the keyboard, not the transcript.
- Requests are named and explained β you approve
PROD_DB_PASSWORD:"for the migration", not an opaque prompt. - The vault is a dumb file β no daemon, no service, no lock-in.
grepit,sourceit, delete it.
keypop holds your credentials, so it's explicit about what it does and doesn't protect. Short
version: it keeps secret values out of your agent's context, but the vault is a plaintext
file guarded by filesystem permissions, not an encrypted secrets manager. Read
SECURITY.md before trusting it with anything real β including one honest
disclosure about the Reveal toggle and argv.
MIT β see LICENSE.
