CLI
The OpenKey CLI (openkey) is a command-line interface for developers who keep secrets, API tokens, SSH keys, and .env material in an OpenKey vault. It can operate fully offline for password generation, talk to an unlocked OpenKey desktop app over a local native bridge, and optionally authenticate to a self-hosted sync server for ciphertext pull and a short-lived CLI session.
Requires Node.js 20+.
Architecture
The diagram below shows what talks to what. Password generation stays offline. Vault commands prefer the unlocked desktop app. Server sync is optional.
| Mode | When it applies | What it can do |
|---|---|---|
| Offline | Always | gen — no app, no server |
| Native bridge | Desktop app unlocked on this machine | Secrets CRUD, discovery import, search/get/copy across secrets and logins |
| CLI session | After login + eval $(openkey unlock) | Same vault operations against a local ciphertext cache; sync pulls from the server |
How a vault command picks a backend
- If the desktop bridge responds → use native mode (preferred; no server registration required).
- Else if
OPENKEY_SESSIONis set and valid → use session mode (local cache / server-backed material). - Else → commands that need the vault fail with a tip to unlock the app or run
eval $(openkey unlock).
The bridge accepts connections only from the local machine and only while the vault is unlocked. On Unix it uses a socket under known OpenKey paths (override with OPENKEY_NATIVE_SOCKET). On Windows it uses a localhost port file under %LOCALAPPDATA%\OpenKey\ (override with OPENKEY_NATIVE_PORT).
Install
cd openkey_cli
npm install
npm run build
npm link # optional: puts `openkey` on your PATHWithout linking:
npx tsx src/cli.ts --help
# after build:
node dist/cli.js --helpVerify:
openkey --version
openkey statusConfiguration and storage
Local CLI state is stored in a platform config directory (file mode 600 when supported):
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/OpenKey/config.json |
| Linux | ~/.config/openkey/config.json (or $XDG_CONFIG_HOME/openkey/) |
| Windows | %APPDATA%\OpenKey\config.json |
The file may contain: server URL, email, access/refresh tokens, salt and KDF params, wrapped vault key, session lock duration, server revision, and a ciphertext cache of entries/collections after sync. It does not store the master password in plaintext.
config commands
openkey config set-server https://openkey.example.com
openkey config show
openkey config set-lock 30 # session lifetime in minutes (1–1440, default 15)set-serverrequires a URL starting withhttp://orhttps://(trailing slash stripped).- Default server URL before first set:
http://localhost:8000.
Global options
| Flag | Effect |
|---|---|
--json | Machine-readable JSON on stdout for scripting |
--help / --version | Help and version |
Place --json before the subcommand when using Commander globals, e.g. openkey --json status.
Password generation (gen)
Fully offline. Does not require the app or a server.
openkey gen
openkey gen -l 24 --no-symbols
openkey gen -l 32 -a -c
openkey --json gen -l 20| Option | Description | Default |
|---|---|---|
-l, --length <n> | Length (practical range 4–64) | 20 |
--no-upper | Exclude uppercase letters | off |
--no-lower | Exclude lowercase letters | off |
--no-digits | Exclude digits | off |
--no-symbols | Exclude symbols | off |
-a, --avoid-ambiguous | Avoid ambiguous characters Il1O0o | off |
-c, --copy | Copy to clipboard instead of printing | off |
With -c, human mode prints a confirmation; JSON mode returns { "copied": true, "length": N }. Without -c, the password is printed (or { "password": "..." } in JSON mode).
Status and hygiene
openkey status
openkey forgetstatus reports server URL, email, login state, bridge availability, unlock mode (native / session), remaining session time, and cached entry count.
forget wipes local CLI config and the cached ciphertext. It does not delete secrets inside the desktop app vault. After forget, re-run config set-server / login if you use server mode.
Developer secrets (secret)
Secrets live in the vault’s reserved Secrets area (__dev_secrets__), grouped by device (machine label; default hostname). Commands require the desktop app unlocked or a valid OPENKEY_SESSION.
Kinds
| Kind | Typical use | Notes |
|---|---|---|
apiToken | PAT, API keys | Default |
sshKey | Private keys | Prefer --file / --public-key-file |
envSnippet | Full .env bodies | Prefer --file |
other | Catch-all | — |
Aliases such as ssh, api, token, env, .env normalize to the kinds above.
secret add
openkey secret add --name "GitHub PAT" --kind apiToken --secret ghp_...
openkey secret add -n "deploy key" -k sshKey -f ~/.ssh/id_ed25519 \
--public-key-file ~/.ssh/id_ed25519.pub -H git.example.com -u git
openkey secret add -n "acme .env" -k envSnippet -f ./apps/api/.env -d laptop| Option | Description |
|---|---|
-n, --name | Display name (required) |
-k, --kind | sshKey | apiToken | envSnippet | other |
-s, --secret | Inline secret value (- reads stdin) |
-f, --file | Read secret body from a file |
--stdin | Read secret from stdin (prefer over putting tokens in argv) |
-u, --username | Optional username |
-H, --host | Optional host |
-d, --device | Device collection label (default: hostname) |
--public-key / --public-key-file | SSH public key |
--passphrase | Key passphrase |
--notes | Free-form notes |
Provide --secret, --file, or --stdin (non-empty). Created records return a UUID.
printf '%s' "$TOKEN" | openkey secret add -n "CI token" --stdinsecret list / get / copy / rm / update / export / devices
openkey secret list
openkey secret list -d laptop -k apiToken
openkey secret get "GitHub"
openkey secret copy ghp
openkey secret update "GitHub PAT" --secret ghp_new...
printf '%s' "$TOKEN" | openkey secret update "GitHub PAT" --stdin
openkey secret export -d laptop -o .env.local
openkey secret export --format exports # for eval
openkey secret devices
openkey secret rm "old token" -y- list — table of UUID prefix, name, kind, device, masked secret. Optional
-d/--deviceand-k/--kindfilters. - get / copy / rm / update — match by name, host, or UUID prefix. When several substrings match, an exact name/title, host, or unique UUID prefix (≥4 chars) wins; otherwise the command errors with candidates.
- update — patch only the flags you pass (
--name,--secret/--file/--stdin,--kind,--device, …). Requires the desktop bridgeupdateSecrethandler (OpenKey app with this release) or a CLI session. - export — write secrets as dotenv (
KEY=value;envSnippetbodies inlined) or--format exportsshell lines.-owrites a mode-600file when supported. - devices — list device collection labels and counts.
- get prints plaintext (or full JSON object in
--jsonmode). - copy writes plaintext to the clipboard.
- rm prompts unless
-y/--yes.
Inject secrets into the shell (env / run)
# Print export lines for eval (NAME or NAME=query)
eval $(openkey env DATABASE_URL)
eval $(openkey env DB=DATABASE_URL GH="GitHub PAT")
# Or run a child process with secrets in its environment
openkey run -e DATABASE_URL -e GH="GitHub PAT" -- npm start| Form | Meaning |
|---|---|
NAME | Env var NAME; look up vault item by that name |
NAME=query | Env var NAME; look up by query (name / host / UUID) |
--json on env returns objects with env, query, name, uuid, and value. --raw prints a single plaintext value (exactly one binding).
Discovery (discover)
Scans this machine and imports new secrets into the device group. Deduplicates against values already in the vault (by kind + name + content fingerprint).
openkey discover --dry-run
openkey discover -y
openkey discover -d workstation -p ~/src/acme -p ~/src/labs --depth 3
openkey discover --no-aws --no-env-vars| Option | Description | Default |
|---|---|---|
-d, --device | Device collection name | hostname |
-p, --path <dir> | Project root(s) for .env walk (repeatable) | cwd |
--depth <n> | Max directory depth for .env | 4 |
--no-ssh | Skip ~/.ssh private keys | scan on |
--no-env-files | Skip .env / .env.* files | scan on |
--no-env-vars | Skip process environment | scan on |
--no-aws | Skip ~/.aws/credentials | scan on |
--no-gh | Skip GitHub CLI hosts.yml tokens | scan on |
--no-docker | Skip ~/.docker/config.json registry auth | scan on |
--dry-run | List only; do not save | off |
-y, --yes | Import without interactive confirm | off |
What is scanned
- SSH — private keys under
~/.ssh(skipsknown_hosts,authorized_keys,config,.pubfiles); attaches sibling.pubwhen present. - Environment variables — well-known names (
GITHUB_TOKEN,OPENAI_API_KEY,DATABASE_URL, …) and names matching secret-like suffixes; skipsPATH,HOME,OPENKEY_SESSION,OPENKEY_PASSWORD, etc. - AWS — profiles in
~/.aws/credentials. - GitHub CLI —
oauth_token/tokenentries in~/.config/gh/hosts.yml. - Docker — decoded
authsfrom~/.docker/config.json. .envfiles — walk from roots, skippingnode_modules,.git,dist, virtualenvs, etc.; size and file-count limits apply.
Dry-run works even if the vault is locked (listing only). Saving requires bridge or session unlock. Already-imported secrets are reported as skipped.
Search across secrets and logins
These commands search developer secrets and login entries:
openkey search github
openkey get "GitHub"
openkey get "GitHub" --field username
openkey copy api.example.com --field totp
openkey totp "GitHub" -c
openkey logins| Command | Output |
|---|---|
search <query> | Masked table (or JSON previews); shows TOTP availability |
get <query> | Best-match field (--field password|username|url|totp|notes) |
copy <query> | Clipboard copy of that field (auto-clears in 45s; --keep to disable) |
totp <query> | Live TOTP code (-c copy, -w watch until Ctrl+C) |
logins | List logins with username / URL / TOTP flag |
doctor | Diagnose Node, config permissions, bridge, session, server /health, clipboard |
Ambiguous substring matches prefer an exact name/title, host, or unique UUID prefix; otherwise they list UUID, kind, and label — refine the query. Prefer secret get / secret copy when you only want the Secrets section.
Use secret set to upsert by name + device (create or update). sync --push pushes the local ciphertext cache before pulling.
Optional self-hosted server
Use this path when the desktop app is not available on the machine (for example phone-only vault access via sync), or when you want a CLI ciphertext cache.
openkey config set-server http://localhost:8000
openkey login --email [email protected]
eval $(openkey unlock)
openkey syncServer install: Install the server.
Authentication flow
login— prompts for email (or-e) and master password (orOPENKEY_PASSWORD). Performs prelogin for salt/KDF, derivesauth_hashwith Argon2id, obtains JWTs, fetches wrapped vault key material, verifies the password by unwrapping, then pulls ciphertext into the local cache. Never send the master password as a CLI flag.unlock— derives the vault key again, refreshes tokens/sync when the server is reachable, and prints a shell export forOPENKEY_SESSION(useeval $(openkey unlock)). Options:-e/--email,--raw(token only). JSON mode emits the session fields.lock— printsunset OPENKEY_SESSION(or JSON hint) so you caneval $(openkey lock).logout— clears access/refresh tokens; keeps the local ciphertext cache. Pair withlockto clear the session env.sync— requires login; pulls entries/collections and updatesserverRevision.
Session lifetime defaults to 15 minutes (config set-lock). Expired sessions require unlock again.
Environment variables
| Variable | Purpose |
|---|---|
OPENKEY_SESSION | Short-lived encrypted session blob from unlock |
OPENKEY_PASSWORD | Master password for non-interactive login / unlock (scripts/CI only) |
OPENKEY_EMAIL | Account email for non-interactive login / unlock |
OPENKEY_NATIVE_SOCKET | Override Unix bridge socket path |
OPENKEY_NATIVE_PORT | Override Windows bridge port |
Prefer the interactive password prompt on personal machines. Treat OPENKEY_PASSWORD and session tokens as secret material in CI logs.
Shell completions
eval "$(openkey completion bash)"
eval "$(openkey completion zsh)"
openkey completion fish | sourceCommand reference
| Command | Needs vault access? | Description |
|---|---|---|
gen | No | Offline password generation |
discover | Save: yes* / dry-run: no | Scan SSH / .env / env / AWS → device group |
secret add|list|get|copy|rm|update|export|devices | Yes* | Developer secrets |
get / copy / search / totp / logins | Yes* | Secrets + logins (TOTP, field select) |
doctor | No | Diagnose bridge / session / server |
env / run | Yes* | Export secrets into shell / child process |
completion | No | Bash / zsh / fish completions |
status | No | Bridge / session / server state |
config set-server|show|set-lock | No | CLI configuration |
login / logout | — | Optional server auth |
unlock / lock | — | Optional CLI session |
sync | Login required | Pull ciphertext from server |
forget | No | Wipe local CLI config + cache |
*Desktop app unlocked, or valid OPENKEY_SESSION after server login.
Security model
- List/search commands mask values; use
get/copyonly when you need plaintext. - The sync server stores ciphertext only; the CLI derives keys locally like other OpenKey clients.
- Do not pass the master password as a flag; avoid logging
OPENKEY_PASSWORDorOPENKEY_SESSION. - Bridge traffic is local-only and requires an unlocked vault.
- Session tokens expire; reduce lifetime with
config set-lockon shared machines. forgetclears CLI state on disk; rotate server tokens withlogoutif the machine is untrusted afterward.
Development
cd openkey_cli
npm test
npm run typecheck
npm run buildRelated guides
- Using the app — desktop unlock, Secrets section, autofill
- Install the server — self-hosted sync
- Security — Argon2id, tokens, threat model
- Packages — repository layout