Every key, pinned
to its account.
keypin keeps a map of every app key across your projects, then publishes each one to GitHub Actions and Cloudflare Workers. It checks the account before every write and shows you the plan first. Values stay in your vault.
$ bun add -g keypin
Needs Bun 1.4.4 or newer and the 1Password desktop app.
Publishing to Cloudflare
works on macOS only for now. Setup is in the
README.
- Values stay in your vault1Password
- No .env fileskeypin run
- Account checked before every writepublish
- A plan before every writepublish
- Receipts, so reruns skip what's donelocal state
- Open sourceMIT
$ keypin status keypin: reading rotation fields (no values) from 1Password account my.1password.com, vault "App Secrets": "api dev api token", "api prod api token", "web prod session secret" KEY ENV VARIABLE OWNER ITEM ROTATED EVERY DUE # /Users/dev/code/api api-token dev API_TOKEN api api dev api token 2026-08-01 180d 2027-01-28 api-token prod API_TOKEN api api prod api token 2026-05-20 180d 2026-11-16 -> github-actions:acme/api:API_TOKEN (account acme): no receipt -> cloudflare-worker:api-edge:API_TOKEN (account 3b1f0c9a7d2e4f6a8b5c1d0e9f7a6b4c): no receipt # /Users/dev/code/web session-secret prod SESSION_SECRET web web prod session secret 2026-08-02 90d 2026-10-31 -> cloudflare-worker:web:SESSION_SECRET (account 3b1f0c9a7d2e4f6a8b5c1d0e9f7a6b4c): last published 2026-08-02T10:14:51.330Z by dev@acme.example, generation a5e3c8f1d7092b64 Problems (3): missing-receipt: api-token (prod): no receipt for github-actions:acme/api:API_TOKEN, so what it holds is unknown missing-receipt: api-token (prod): no receipt for cloudflare-worker:api-edge:API_TOKEN, so what it holds is unknown overdue: session-secret (prod): was due 2026-10-31 (rotated 2026-08-02, every 90d) Notices (2): due-soon: api-token (prod): is due 2026-11-16 (rotated 2026-05-20, every 180d) authority-unknown: 2 keys (api-token, session-secret): what each grants is unknown, because keypin didn't create it
How it works
map
Each project lists its keys in a committed keypin.toml: the owner, the
vault item, and every place the key is published. It never holds a value.
# names and places, never values
[vaults.personal]
kind = "1password"
account = "my.1password.com"
vault = "App Secrets"
[keys.api-token]
env = "API_TOKEN"
owner = "api"
[keys.api-token.prod]
vault = "personal"
item = "api prod api token"
[[keys.api-token.prod.publish]]
to = "github-actions"
repo = "acme/api"
run
Run a command with the project's dev keys in its environment. keypin reads them from 1Password while the command runs and writes them nowhere on disk, so there's no .env to leak.
$ keypin run -- bun dev keypin: reading dev values from 1Password account my.1password.com, vault "App Secrets": "api dev api token" ➜ Local: http://localhost:3000/
publish
keypin checks each destination is the account the map pins, shows the plan, and waits for you. Receipts let a rerun skip what's already written.
Account checks: ok github-actions:acme/api:… ok cloudflare-worker:api-edge:… Plan for generation 87c5ad3105ddfcc3: write github-actions:acme/api:… needs approval: first write write cloudflare-worker:api-edge:… needs approval: first write Type yes to write: yes wrote github-actions:acme/api:… wrote cloudflare-worker:api-edge:…
Commands
keypin init [folder]-
Register a project and write a commented
keypin.toml. An existing one stays as it is. keypin unregister [folder]- Stop listing a project. Its
keypin.tomlstays. keypin status [--json]- Every key, its rotation dates and receipts, then problems. Exit 0 means none.
keypin run -- <command>- Run a command with the project's dev keys in its environment.
keypin publish <key-id>-
Check accounts, show the plan, then write the key to every destination: GitHub Actions
secrets and Cloudflare Worker secrets. A first write or an account change waits for you
to type
yes;--yesnever gives that approval.