KeyPick
A cross-platform, biometric-secured CLI for managing reusable API keys across multiple machines.
Built on SOPS + age encryption with a private Git repo as the sync backbone.
Quick Start •
Tutorial •
Usage •
How It Works •
Troubleshooting •
Contributing
Overview
New to KeyPick? Start with TUTORIAL.md. It walks through installation, setup, multi-machine vaults, project workflows, direnv, recovery strategy, and advanced usage patterns.
KeyPick is a terminal-based secrets manager designed for developers who work across multiple machines. Instead of copying .env files around, texting yourself API keys, or storing them in plaintext notes, KeyPick gives you:
- One encrypted vault synced via a private Git repo
- Biometric authentication (Windows Hello, Touch ID, Linux polkit, WSL→Hello via interop) before any secret is decrypted
- Per-machine age keys so compromising one machine doesn't compromise them all
- A guided setup wizard that installs prerequisites, generates keys, and configures everything for you
- Shell integration via direnv for automatic environment variable injection
keypick setup # One-command setup wizard
keypick add # Store secrets in encrypted groups
keypick extract # Export to .env files
keypick copy # Copy a single key to clipboard
keypick auto # Non-interactive export for direnv/CI
keypick vault # Manage vault selection
Why KeyPick?
The problem: Every developer accumulates API keys, database credentials, and service tokens across projects. The common "solutions" are all terrible:
.env files on each machine | Out of sync, easy to accidentally commit |
| Cloud password managers | Not designed for developer workflows, no CLI integration |
| Environment variables in shell profiles | Plaintext, no grouping, no sync |
| Shared team vaults (1Password, etc.) | Overkill for personal keys, subscription cost |
| Copy-pasting from Slack/email | Insecure, no audit trail, keys get lost |
KeyPick's approach: Encrypt everything with age, store it in a private Git repo you control, and gate decryption behind your fingerprint. Each machine gets its own encryption key. Syncing is just git pull.
How It Works
graph TB
subgraph repo["Private Git Repository"]
vault["vault.yaml<br/><i>SOPS-encrypted</i>"]
sops_cfg[".sops.yaml<br/><i>public keys only</i>"]
ci[".github/workflows/vault-sync.yml<br/><i>auto re-encrypt CI</i>"]
end
repo <-->|"git pull / push"| d1
repo <-->|"git pull / push"| d2
repo <-->|"git pull / push"| laptop
subgraph machines["Your Machines"]
direction LR
subgraph d1["Desktop 1"]
k1["age key #1"]
end
subgraph d2["Desktop 2"]
k2["age key #2"]
end
subgraph laptop["Laptop"]
k3["age key #3"]
end
end
d1 & d2 & laptop --> kp
subgraph kp["keypick"]
direction LR
bio["Biometric Gate<br/><i>Windows Hello / Touch ID / polkit</i>"]
bio --> decrypt["SOPS Decrypt<br/><i>age private key</i>"]
decrypt --> menu["Interactive Menu<br/><i>add / extract / list / copy</i>"]
end
subgraph outputs["Outputs"]
direction LR
env[".env file"]
clip["Clipboard"]
shell["Shell exports<br/><i>direnv auto-inject</i>"]
end
menu --> env & clip & shell
subgraph security["Security Layers"]
direction LR
s1["1. GitHub Auth<br/><i>repo access control</i>"]
s2["2. age Encryption<br/><i>per-machine keys</i>"]
s3["3. Biometric Gate<br/><i>fingerprint / face</i>"]
end
style repo fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff
style vault fill:#0d7377,stroke:#14ffec,color:#fff
style sops_cfg fill:#0d7377,stroke:#14ffec,color:#fff
style ci fill:#0d7377,stroke:#14ffec,color:#fff
style machines fill:#1a1a2e,stroke:#e94560,stroke-width:2px,color:#fff
style d1 fill:#16213e,stroke:#e94560,color:#fff
style d2 fill:#16213e,stroke:#e94560,color:#fff
style laptop fill:#16213e,stroke:#e94560,color:#fff
style k1 fill:#533483,stroke:#e94560,color:#fff
style k2 fill:#533483,stroke:#e94560,color:#fff
style k3 fill:#533483,stroke:#e94560,color:#fff
style kp fill:#1a1a2e,stroke:#0f3460,stroke-width:2px,color:#fff
style bio fill:#e94560,stroke:#e94560,color:#fff
style decrypt fill:#0d7377,stroke:#14ffec,color:#fff
style menu fill:#533483,stroke:#9b59b6,color:#fff
style outputs fill:#1a1a2e,stroke:#00d4ff,stroke-width:2px,color:#fff
style env fill:#16213e,stroke:#14ffec,color:#fff
style clip fill:#16213e,stroke:#14ffec,color:#fff
style shell fill:#16213e,stroke:#14ffec,color:#fff
style security fill:#0f3460,stroke:#00d4ff,stroke-width:2px,color:#fff
style s1 fill:#e94560,stroke:#e94560,color:#fff
style s2 fill:#0d7377,stroke:#14ffec,color:#fff
style s3 fill:#533483,stroke:#9b59b6,color:#fff
Three layers of security:
- GitHub authentication controls who can access the encrypted file
- age encryption controls who can decrypt it (each machine has a unique private key)
- Biometric gate (Windows Hello / Touch ID / polkit) protects every interactive decryption
Secrets are only ever unencrypted in memory during a keypick session. They are never written to disk in plaintext (except when you explicitly export a .env file).
Tech Stack
| Runtime | Bun ≥ 1.1 | TypeScript runtime |
| Encryption | age | Modern, audited, no-config file encryption |
| Secret management | SOPS | Encrypted file editing with multiple recipients |
| Sync backbone | Git + GitHub | Encrypted vault synced across machines |
| CI automation | GitHub Actions | Auto re-encryption when recipients change |
| Biometrics | PowerShell + WinRT (Windows / WSL), Swift + LocalAuthentication (macOS), pkexec (Linux) | |
| CLI framework | commander | |
| Interactive prompts | @inquirer/prompts | |
| Progress indicators | ora | |
| YAML | yaml | |
| Clipboard | clipboardy | |
Quick Start
1. Install
Installs keypick onto your PATH via an interactive, cross-platform wizard. Requires Bun and Node 18+ (npx ships with Node).
npx github:seanrobertwright/KeyPick install
Shell-script alternatives (no Node/npx required):
curl -fsSL https://raw.githubusercontent.com/seanrobertwright/KeyPick/master/install.sh | sh
# Windows PowerShell
irm https://raw.githubusercontent.com/seanrobertwright/KeyPick/master/install.ps1 | iex
2. Run the setup wizard
keypick setup
The wizard will:
- Check for
age and sops, downloading them automatically if missing
- Generate (or detect) your machine's age encryption key
- Ask whether this is your first machine or you're joining an existing vault
- Create or clone your encrypted vault repository under
~/.keypick/vaults/ by default
- Optionally configure GitHub Actions auto-sync and a recovery key
For a guided experience with detailed explanations of every step, use walkthrough mode:
keypick setup --walkthrough
Setup Walkthrough
This section mirrors what keypick setup --walkthrough shows in your terminal, explaining each step of the setup process so you understand what is happening and why.
Phase 1: Prerequisites (age + sops)
What are these tools?
KeyPick doesn't implement its own cryptography. Instead, it uses two well-audited open-source tools:
| age | Modern file encryption (like GPG, but simpler) | Each machine gets its own age keypair. The private key decrypts your vault; the public key lets others encrypt for this machine. |
| sops | Encrypts individual values inside structured files (YAML/JSON) | Handles multi-recipient encryption so one vault can be decrypted by many machines. Key names stay visible; only values are encrypted. |
What happens: The wizard checks if age and sops are on your PATH. If either is missing, it downloads the correct binary for your OS and architecture from the official GitHub releases and places it in ~/.local/bin/ (or next to the keypick binary on Windows).
[1/4] Checking prerequisites...
✓ age already installed (v1.2.0)
✓ sops already installed (v3.9.4)
If they need to be downloaded, you'll see a progress bar for each.
Phase 2: Machine Identity (age keypair)
Why does each machine need its own key?
This is a core security property. Each machine has a unique age keypair:
- Private key — stored locally at the platform-specific path below, never shared, never committed
- Public key — shared with your vault (listed in
.sops.yaml) so secrets can be encrypted for this machine
If a machine is compromised, you revoke just that machine's key from .sops.yaml without affecting any others.
| Windows | %APPDATA%\sops\age\keys.txt |
| macOS | ~/.config/sops/age/keys.txt |
| Linux | ~/.config/sops/age/keys.txt |
What happens: The wizard checks if an age key already exists. If so, you can reuse it (recommended) or generate a new one (the old one is backed up with a .bak extension). If no key exists, age-keygen generates a fresh keypair.
[2/4] Machine identity...
✓ Key generated: age1abc123def456...
Saved to: C:\Users\you\AppData\Roaming\sops\age\keys.txt
Important: Never share, commit, or copy your private key (keys.txt). If this machine is lost or compromised, remove its public key from .sops.yaml to revoke access.
Phase 3: Vault Repository
What is the vault?
Your vault is a Git repository containing two key files:
vault.yaml | Your secrets, encrypted by sops+age | Yes (values are encrypted) |
.sops.yaml | List of public keys that can decrypt the vault | Yes (public keys only) |
The repo should be private — even though values are encrypted, key names are visible in the YAML structure.
What happens: You choose one of two paths:
Path A: New vault (first machine)
Choose this if you've never used KeyPick before.
[3/4] Vault repository...
? Is this your first machine, or joining an existing vault? New vault (first machine)
? Vault repo name? my-keys
? Create a private GitHub repo automatically? Yes
✓ Created and cloned my-keys
✓ Created .sops.yaml
✓ Created and encrypted vault.yaml
✓ Initial commit created
✓ Pushed to remote
Vault directory: C:\Users\you\.keypick\vaults\my-keys
Path B: Join existing vault (additional machine)
Choose this if you already set up KeyPick on another machine.
- Clone (or locate) your vault repo — provide a GitHub
owner/repo slug, a git clone URL, or a local path. New clones go into ~/.keypick/vaults/ by default.
- Verify
.sops.yaml exists — confirms this is a valid vault repo
- Check recipients — shows all public keys currently in
.sops.yaml
- Register this machine — if your public key isn't already listed:
- Adds your key to
.sops.yaml
- Runs
sops updatekeys -y vault.yaml to re-encrypt the vault for all recipients (including this new machine)
- Commits and pushes so other machines see the change
[3/4] Vault repository...
? Is this your first machine, or joining an existing vault? Join existing vault
? GitHub repo to clone? yourusername/my-keys
✓ Cloned yourusername/my-keys
Current recipients:
- age1abc123def456ghi789...
✓ Added key age1xyz987wvu654... to recipients
✓ Vault re-encrypted
✓ Changes committed
✓ Pushed to remote
Vault directory: C:\Users\you\.keypick\vaults\my-keys
Phase 4: Optional Enhancements
After the core setup, the wizard offers two optional features:
GitHub Actions Auto-Sync
The problem it solves: When you add a new machine, you update .sops.yaml with its public key. But vault.yaml is still encrypted for the old set of recipients — the new machine can't decrypt it until someone with existing access runs sops updatekeys.
The solution: A GitHub Actions workflow watches for changes to .sops.yaml. When it detects a change, it automatically:
- Downloads age and sops
- Imports a dedicated CI age key from GitHub Secrets
- Runs
sops updatekeys -y vault.yaml to re-encrypt for all current recipients
- Commits and pushes the re-encrypted vault
Setup steps:
- Generate a CI age keypair — separate from your machine keys, used only by GitHub Actions
- Add the CI public key to
.sops.yaml — so the workflow can decrypt during re-encryption
- Store the CI private key as a GitHub Secret (
SOPS_AGE_KEY) — piped to gh secret set, encrypted by GitHub with libsodium
- Install the workflow file —
.github/workflows/vault-sync.yml is created in your repo
- Commit and push — activates the workflow
? Set up GitHub Actions auto-sync? Yes
✓ Generated Actions key: age1actionskey123...
✓ Added Actions key to .sops.yaml
✓ Set SOPS_AGE_KEY secret on GitHub
✓ Installed .github/workflows/vault-sync.yml
✓ Pushed to remote
Recovery Key
The problem it solves: If you lose access to all your machines (laptop stolen, desktop dies), you lose access to your vault forever. A recovery key is your safety net.
How it works:
- Generate a recovery age keypair — not tied to any machine
- You choose a strong passphrase — this protects the recovery key itself
- Encrypt the private key with your passphrase — saved as
recovery_key.age
- Add the recovery public key to
.sops.yaml — so the recovery key can decrypt the vault
- Re-encrypt the vault — includes the recovery key as a recipient
Storage rules (two-factor recovery):
recovery_key.age (encrypted file) | Cloud storage (Google Drive, iCloud, Dropbox) | Accessible from anywhere, but useless without the passphrase |
| Passphrase | Paper in a safe or lockbox | Physical security, but useless without the file |
Store the file and passphrase in separate physical locations. An attacker would need to compromise both to access your secrets.
To use the recovery key later:
age -d recovery_key.age > temp_key.txt
SOPS_AGE_KEY_FILE=temp_key.txt keypick list
rm temp_key.txt
? Create a recovery key? Yes
✓ Generated recovery key: age1recoverykey123...
? Enter a strong passphrase for the recovery key: ********
? Confirm passphrase: ********
✓ Encrypted recovery key saved to recovery_key.age
✓ Added recovery key to .sops.yaml recipients
✓ Vault re-encrypted with recovery key
✓ Changes committed and pushed
After Setup
Once setup completes, you're ready to use KeyPick:
keypick add
keypick vault list
keypick vault current
keypick vault select
cd ~/projects/my-app
keypick extract
keypick setup
Your secrets are encrypted at rest and protected by biometric authentication. They are only ever decrypted in memory during a keypick session.
Installation
KeyPick is a TypeScript CLI that runs on Bun. The recommended installer is a cross-platform interactive wizard; shell-script installers are available for systems without Node.
npx installer (recommended)
One command on every platform — macOS, Linux, Windows, WSL:
npx github:seanrobertwright/KeyPick install
The wizard walks you through each step with a short explanation of why, fetches the latest release from GitHub, drops the bundle at ~/.local/share/keypick and a shim at ~/.local/bin/keypick, checks your PATH, and optionally installs the Claude Code skill (global or project scope). Requires Node 18+ (npx ships with Node) and Bun on your PATH.
To uninstall:
npx github:seanrobertwright/KeyPick uninstall
Shell-script installers (no Node required)
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/seanrobertwright/KeyPick/master/install.sh | sh
Windows (PowerShell):
irm https://raw.githubusercontent.com/seanrobertwright/KeyPick/master/install.ps1 | iex
Direct install
If you already have Bun installed:
bun install -g keypick
WSL (Windows Subsystem for Linux)
KeyPick runs in WSL. WSL looks like Linux to the process, but:
- Biometric auth routes through Windows Hello on the host via
powershell.exe (exposed by WSL interop). You get the same fingerprint/PIN prompt as native Windows — no polkit required.
age and sops install as Linux binaries inside WSL. Your age keypair lives at ~/.config/sops/age/keys.txt in the WSL distro, not the Windows host. Machines sharing the same vault still need distinct keys.
- Clipboard works through WSL interop via
clip.exe.
Install via the Linux one-liner from inside your WSL shell:
curl -fsSL https://raw.githubusercontent.com/seanrobertwright/KeyPick/master/install.sh | sh
Prerequisites
KeyPick needs Git for vault syncing and Bun as its runtime. The first-run keypick setup wizard installs age and sops automatically; if you'd rather do it by hand:
Uninstalling
Cross-platform npx uninstaller:
npx github:seanrobertwright/KeyPick uninstall
Shell-script alternatives:
curl -fsSL https://raw.githubusercontent.com/seanrobertwright/KeyPick/master/uninstall.sh | sh
# Windows (PowerShell)
irm https://raw.githubusercontent.com/seanrobertwright/KeyPick/master/uninstall.ps1 | iex
All of them remove the KeyPick bundle, shim, any legacy installs (Bun global or cargo-era Rust), and — after confirmation — your config dir (~/.keypick/, including cloned vault repos). Each prompts separately before deleting your age private key; if you have no recovery key or other machines, deleting it makes your vault contents permanently inaccessible.
Usage
First-Time Setup
keypick setup
The interactive wizard walks you through everything. On your first machine it will:
- Install
age and sops (if missing)
- Generate your machine's age encryption key
- Create a private vault repository under
~/.keypick/vaults/
- Optionally set up GitHub Actions and a recovery key
On additional machines, choose "Join existing vault" to clone your repo and register the new machine's key.
Setup Subcommands
keypick setup
keypick setup --walkthrough
keypick setup actions
keypick setup recovery
Vault Commands
keypick vault list
keypick vault current
keypick vault select
KeyPick resolves a vault in this order:
KEYPICK_VAULT_DIR
- The current directory if you are already inside a vault repo
- The remembered active vault
- Vaults under
~/.keypick/vaults/
- An interactive selector if multiple candidates are available
If your machine blocks writes to ~/.keypick, set KEYPICK_HOME to a writable directory. On Windows, a good default is:
setx KEYPICK_HOME "$env:USERPROFILE\OneDrive\Documents\KeyPick"
Then open a new shell and run:
keypick vault select
keypick vault current
keypick
When run without arguments, KeyPick shows a menu after biometric verification:
? What would you like to do?
> Extract keys to .env
Add / Update a key group
List vault contents
Copy a key to clipboard
Exit
Add Secrets
keypick add
Secrets are organized into groups (e.g., Supabase_Prod, Google_AI, Stripe_Test).
Example session:
? Select a group:
> [ + New Group ]
Supabase_Prod
Google_AI
? Service/Group name: Stripe_Test
Adding keys to group: Stripe_Test
? Key Name: STRIPE_SECRET_KEY
? Value for STRIPE_SECRET_KEY: sk_test_xxxxxxxxxxxxx
+ Added: STRIPE_SECRET_KEY
? Add another key to this group? Yes
? Key Name: STRIPE_PUBLISHABLE_KEY
? Value for STRIPE_PUBLISHABLE_KEY: pk_test_xxxxxxxxxxxxx
+ Added: STRIPE_PUBLISHABLE_KEY
? Add another key to this group? No
Vault updated successfully.
Remember to sync: cd ~/.keypick/vaults/my-keys && git add vault.yaml && git commit -m "Add Stripe_Test" && git push
cd my-project
keypick extract
Select one or more groups to export:
? Select the groups to extract (Space to toggle, Enter to confirm):
> [x] Supabase_Prod
[x] Stripe_Test
[ ] Google_AI
5 keys from 2 group(s) written to .env
WARNING: Add .env to your .gitignore so secrets are never committed.
The generated .env:
# --- Supabase_Prod ---
DB_HOST=db.xxxxx.supabase.co
DB_PASSWORD=secret_value
SUPABASE_SECRET=service_role_key_abc
# --- Stripe_Test ---
STRIPE_SECRET_KEY=sk_test_xxxxxxxxxxxxx
STRIPE_PUBLISHABLE_KEY=pk_test_xxxxxxxxxxxxx
List Vault Contents
keypick list
Shows all groups and key names with values hidden:
Vault Contents (values hidden):
Google_AI
- API_KEY
- PROJECT_ID
Stripe_Test
- STRIPE_PUBLISHABLE_KEY
- STRIPE_SECRET_KEY
Supabase_Prod
- DB_HOST
- DB_PASSWORD
- SUPABASE_SECRET
3 group(s), 7 key(s) total.
Copy to Clipboard
keypick copy
Select a group, then a key. The value is copied to your clipboard without ever being written to disk.
? Select a group: Supabase_Prod
? Select a key: DB_PASSWORD
Copied DB_PASSWORD to clipboard.
Automatic Shell Injection (direnv)
keypick auto Supabase_Prod Google_AI
Outputs export KEY='VALUE' statements to stdout. Designed for use with direnv:
Create a .envrc in your project:
eval $(keypick auto Supabase_Prod Google_AI)
Allow it once:
direnv allow
Now every time you cd into the project, your keys are automatically loaded as environment variables. When you cd out, they're removed.
Note: keypick auto skips the biometric gate for non-interactive use. Your secrets are still protected by Git authentication and age encryption at rest.
direnv installation:
winget install direnv.direnv
brew install direnv
apt install direnv
Add the hook to your shell profile:
eval "$(direnv hook bash)"
eval "$(direnv hook zsh)"
Invoke-Expression "$(direnv hook pwsh)"
Syncing Between Machines
cd ~/.keypick/vaults/my-keys
git pull
git add vault.yaml
git commit -m "Add Stripe_Test keys"
git push
Adding a new machine is straightforward:
- Run
keypick setup and choose "Join existing vault"
- The wizard clones your repo, registers the machine's key, and pushes
If you've set up GitHub Actions, the vault is automatically re-encrypted for the new recipient.
Recovery Key
If you lose access to all your machines, a recovery key lets you restore access.
Create one during setup (or run later):
keypick setup recovery
The wizard:
- Generates a recovery keypair
- Encrypts it with a passphrase you choose
- Adds the recovery public key to your vault recipients
- Saves
recovery_key.age for you to upload to cloud storage
Storage rules:
recovery_key.age (encrypted file) | Google Drive, iCloud, etc. |
| Passphrase | Written on paper, in a safe/lockbox |
Store the file and passphrase in separate physical locations. Both are required to recover.
Using the recovery key:
age -d recovery_key.age > temp_key.txt
SOPS_AGE_KEY_FILE=temp_key.txt keypick list
rm temp_key.txt
Command Reference
keypick | Interactive menu (biometric required) |
keypick add | Add or update keys in a group |
keypick extract | Export groups to a .env file |
keypick list | List all groups and key names (values hidden) |
keypick copy | Copy a single key to clipboard |
keypick auto <groups...> | Non-interactive export for direnv/shell eval |
keypick vault list | Show known vault repositories |
keypick vault current | Print the active vault repository |
keypick vault select | Interactively choose the active vault repository |
keypick setup | Full setup wizard |
keypick setup --walkthrough | Setup wizard with detailed explanations of each step |
keypick setup actions | Configure GitHub Actions auto-sync |
keypick setup actions --walkthrough | Actions setup with detailed explanations |
keypick setup recovery | Generate a recovery key |
keypick setup recovery --walkthrough | Recovery key setup with detailed explanations |
Project Structure
KeyPick/
├── .sops.yaml.example # Template for secrets repos
├── .github/
│ └── workflows/
│ └── vault-sync.yml # Template: auto re-encryption CI
└── ts/
├── package.json
└── src/
├── main.ts # Entry point, CLI routing
├── lib/
│ ├── auth.ts # Biometric authentication
│ ├── vault.ts # SOPS encrypt/decrypt, data model
│ ├── terminal.ts # Terminal state + cleanup
│ └── wsl.ts # WSL detection + Hello bridge
└── commands/
├── add.ts # keypick add
├── extract.ts # keypick extract
├── list.ts # keypick list
├── copy.ts # keypick copy
├── auto_export.ts # keypick auto
├── interactive.ts # No-argument menu mode
├── vaults.ts # keypick vault
├── env/ # keypick env push/pull/status
└── setup/
├── index.ts # Setup wizard orchestrator
├── utils.ts # Shared helpers (spinners, downloads, platform)
├── prerequisites.ts # age/sops auto-installer
├── keygen.ts # Age key generation
├── init.ts # First machine flow
├── join.ts # Additional machine flow
├── actions.ts # GitHub Actions wizard
└── recovery.ts # Recovery key wizard
Troubleshooting
sops or age not found after setup
The setup wizard installs binaries to ~/.local/bin/ or next to the keypick executable. If your shell can't find them:
which age sops
where age sops
export PATH="$HOME/.local/bin:$PATH"
$env:PATH += ";$env:USERPROFILE\.local\bin"
SOPS decryption failed
SOPS decryption failed: ...
This means your machine's age private key can't decrypt the vault. Common causes:
Authentication failed
Authentication failed: ...
The biometric prompt was cancelled or failed. Possible causes:
- Windows Hello not configured: Set up a PIN/fingerprint in Settings > Accounts > Sign-in options
- Touch ID not enabled: Enable in System Preferences > Touch ID
- Linux polkit missing: Install
policykit-1 or your distro's equivalent
- Remote/SSH session: Biometrics require a local display. Use
keypick auto for non-interactive access
GitHub Actions workflow not triggering
- Verify the
SOPS_AGE_KEY secret is set in your repo's Settings > Secrets > Actions
- Check that
.github/workflows/vault-sync.yml exists in your secrets repo
- The workflow only triggers on pushes to
.sops.yaml or vault.yaml
- Run
keypick setup actions to reconfigure
gh CLI not authenticated
If the setup wizard can't create repos or set secrets:
gh auth login
gh auth status
Vault shows as empty after cloning on a new machine
You need to register this machine's key first. The vault is encrypted for specific recipients:
keypick setup
Multiple vaults are available
keypick vault list
keypick vault select
Or override a single command explicitly:
KEYPICK_VAULT_DIR=/path/to/vault keypick list
If KeyPick cannot save the selected vault because its state directory is not writable, set KEYPICK_HOME first:
setx KEYPICK_HOME "$env:USERPROFILE\OneDrive\Documents\KeyPick"
Security Model
| Git repo (private) | Controls who can access the encrypted file |
| age encryption | Each machine has a unique keypair; only authorized machines can decrypt |
| Biometric gate | Windows Hello / Touch ID required before any decryption |
| SOPS | Manages multi-recipient encryption; individual values encrypted in YAML |
| GitHub Actions key | Separate keypair for CI, stored only in GitHub Secrets |
| Recovery key | Passphrase-protected, stored offline in separate physical locations |
What is safe to commit: vault.yaml (encrypted), .sops.yaml (public keys only), workflow files.
What must never be committed: .env files, keys.txt, recovery_key.age plaintext, any file containing private keys.
Contributing
Contributions are welcome! Here's how to get started:
Repo layout
KeyPick/
├── bin/ # Cross-platform installer wizard (Node, zero deps)
│ ├── keypick.mjs # dispatcher — bin entry for `npx github:…`
│ ├── installer.mjs # install wizard
│ ├── uninstaller.mjs # uninstall wizard
│ └── ui.mjs # colors, boxes, prompts, OSC-52 clipboard
├── package.json # Root package.json — makes `npx github:…` work
├── ts/ # TypeScript keypick CLI (Bun project)
│ ├── src/
│ │ ├── lib/ # vault, auth, terminal, wsl
│ │ ├── commands/
│ │ └── main.ts
│ └── package.json
├── install.sh # Unix shell-script fallback installer
├── install.ps1 # Windows PowerShell fallback installer
├── uninstall.sh
├── uninstall.ps1
└── .github/workflows/
├── release.yml # Tag-triggered release build
└── vault-sync.yml # Auto re-encryption for vault repos
Development Setup
git clone https://github.com/seanrobertwright/KeyPick.git
cd KeyPick/ts
bun install
bun run typecheck
bun run src/main.ts --help
Guidelines
- Fork the repo and create your branch from
master
- Write clear commit messages following Conventional Commits (e.g.,
feat:, fix:, docs:)
- Test your changes — make sure
bun run typecheck (in ts/) succeeds with zero warnings
- Keep it simple — KeyPick values simplicity over feature count
- Security first — never log, print, or write secrets to disk unless the user explicitly requests it
Submitting Changes
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature
- Make your changes and commit them
- Push to your fork:
git push origin feature/my-feature
- Open a Pull Request with a clear description of what and why
Reporting Issues
- Use GitHub Issues to report bugs or request features
- Include your OS and the version (
keypick --version)
License
This project is licensed under the MIT License. See LICENSE for details.