diff --git a/.github/assets/openkey-cli-install.png b/.github/assets/openkey-cli-install.png
new file mode 100644
index 0000000..0e96e03
Binary files /dev/null and b/.github/assets/openkey-cli-install.png differ
diff --git a/README.md b/README.md
index e45d7b0..ad27326 100644
--- a/README.md
+++ b/README.md
@@ -1,128 +1,577 @@
+
+
+
+
# OpenKey CLI
-open-source developer command-line interface for the
-[OpenKey](https://github.com/OpenSelfHosting/OpenKey) self-hosted,
-end-to-end encrypted password manager.
+OpenKey CLI is the command-line companion for the self-hosted, end-to-end encrypted [OpenKey](https://github.com/OpenSelfHosting/OpenKey) password manager.
+
+It helps you:
+
+- generate strong passwords entirely offline
+- manage secrets, logins, cards, and crypto records from a local unlocked OpenKey app
+- discover SSH keys, `.env` files, API tokens, AWS credentials, and other likely leaks on your machine
+- export selected values into your shell or a child process
+- sync encrypted data with a self-hosted OpenKey server when you want server-backed access
+
+This README is a practical quick-start guide. For deeper product docs and platform setup notes, see the [CLI guide](https://openkey.openselfhosting.com/guide/cli).
-Generate passwords offline, manage developer secrets through the unlocked **desktop or Android** app (native bridge), discover SSH / `.env` / API / AWS material into a device-grouped Secrets section, and optionally authenticate to a self-hosted sync server for ciphertext pull and a short-lived `OPENKEY_SESSION`.
+## Why use OpenKey CLI?
-Full documentation: [CLI guide](https://openkey.openselfhosting.com/guide/cli).
+OpenKey CLI is designed for developers who want a fast, local-first workflow without uploading secrets to a third-party service.
+
+Typical use cases:
+
+- generate and rotate app passwords without leaving the terminal
+- keep API tokens, SSH keys, and `.env` values in a structured vault
+- retrieve a secret for a single command or shell session without exposing it in your shell history
+- discover secrets already sitting on disk and bring them under versioned vault control
+- operate in a self-hosted setup where the server only stores encrypted payloads
## Requirements
-- Node.js **20+**
-- For vault commands without a server: OpenKey **desktop** (or Android) app unlocked on this machine
-- For server mode: a running [OpenKey_server](https://github.com/OpenSelfHosting/OpenKey_server) instance
+- Node.js 20 or newer
+- For local vault access without a server: an unlocked OpenKey desktop app or Android app on the same machine
+- For server-backed usage: a running [OpenKey_server](https://github.com/OpenSelfHosting/OpenKey_server) instance
## Install
-**From npm** (recommended):
+### From npm (recommended)
```bash
npm install -g openkey-cli
openkey --version
```
-**From source** (development):
+### Run without installing globally
+
+```bash
+npx openkey-cli --help
+npx tsx src/cli.ts --help
+node dist/cli.js --help
+```
+
+### From source
```bash
-cd openkey_cli
+git clone https://github.com/OpenSelfHosting/OpenKey_cli.git
+cd OpenKey_cli
npm install
npm run build
-npm link # optional: puts `openkey` on your PATH
+npm link
```
-Without linking:
+`npm link` is optional, but it makes the `openkey` binary available globally on your PATH.
+
+## Common workflows
+
+A good default workflow looks like this:
```bash
-npx openkey-cli --help
-npx tsx src/cli.ts --help
-node dist/cli.js --help
+# 1) check whether a local bridge or server session is available
+openkey status
+
+# 2) generate a password without touching your vault
+openkey gen -l 24 --no-symbols
+
+# 3) scan for likely secrets and review the results
+openkey discover --dry-run
+
+# 4) save approved findings into a device collection
+openkey discover -y
+
+# 5) list or fetch the values you need
+openkey secret list
+openkey get "GitHub" --field username
+openkey secret copy "GitHub PAT"
+
+# 6) export selected values for the current shell session
+eval "$(openkey env DATABASE_URL GITHUB_TOKEN)"
```
-## Quick start (desktop bridge)
+OpenKey CLI also supports a server-backed flow for self-hosted deployments:
```bash
-# Unlock the OpenKey desktop app first
+openkey config set-server http://localhost:8000
+openkey login --email you@example.com
+eval "$(openkey unlock)"
+openkey sync
+```
+
+## Quick start
+
+These examples assume the OpenKey desktop app is already unlocked and running locally.
+
+```bash
+# health check
openkey status
+
+# generate a password without needing a vault
openkey gen -l 24 --no-symbols
+
+# preview discovered secrets without saving anything
openkey discover --dry-run
+
+# import discovered values into a device collection
openkey discover -y
+
+# add a secret manually
openkey secret add --name "GitHub PAT" --kind apiToken --secret ghp_...
+
+# add a secret from stdin
printf '%s' 'ghp_...' | openkey secret add -n "GitHub PAT" --stdin
+
+# list secrets in the vault
openkey secret list
openkey secret list -d laptop -k apiToken
-openkey secret update "GitHub PAT" --stdin < token.txt
+
+# export a device-specific .env file
openkey secret export -d laptop -o .env.local
-openkey secret devices
+
+# copy a secret to the clipboard and clear it after 45s
openkey secret copy "GitHub"
+
+# show a TOTP code and copy it
openkey totp "GitHub" -c
+
+# fetch a field from a matching item
openkey get "GitHub" --field username
+
+# search by name or content
openkey search api
-eval $(openkey env DATABASE_URL GITHUB_TOKEN)
+
+# export env vars for the current shell
+eval "$(openkey env DATABASE_URL GITHUB_TOKEN)"
+
+# run a command with secrets injected into its environment
openkey run -e DATABASE_URL -e GH=GitHub -- npm start
```
-Shell completions: `eval "$(openkey completion zsh)"` (also `bash` / `fish`).
+Shell completion is available for Bash, Zsh, and Fish:
+
+```bash
+eval "$(openkey completion zsh)"
+```
-## Android (Termux)
+## Android / Termux setup
-Unlock OpenKey → **Settings → Data → Termux / CLI**, then:
+If you use the OpenKey Android app, unlock it and open the relevant terminal configuration screen, then:
```bash
pkg install nodejs
npm install -g openkey-cli
-export OPENKEY_NATIVE_PORT=... # from the sheet
-export OPENKEY_NATIVE_TOKEN=... # from the sheet
+export OPENKEY_NATIVE_PORT=...
+export OPENKEY_NATIVE_TOKEN=...
openkey status
```
-Port and token last only for that unlock. Copy them again after you lock the vault.
+The port and token are short-lived and are generated for the current unlocked session. Re-copy them after locking the vault again.
-## Optional: self-hosted server
+## Optional: self-hosted server mode
+
+This mode uses a self-hosted OpenKey server for encrypted sync and session-based access.
```bash
openkey config set-server http://localhost:8000
openkey login --email you@example.com
-eval $(openkey unlock)
+eval "$(openkey unlock)"
+openkey sync
+eval "$(openkey lock)"
+```
+
+Notes:
+
+- the master password is entered interactively, or provided via `OPENKEY_PASSWORD` in scripts/CI
+- the email can also be supplied through `OPENKEY_EMAIL`
+- the password must never be passed as a CLI flag
+
+## Backends and access model
+
+OpenKey CLI supports two primary backends.
+
+1. Native bridge
+ - uses an unlocked OpenKey desktop app or Android app locally
+ - this is the default path for vault-backed commands
+ - works for secrets, logins, cards, crypto wallets, password generation, and discovery
+
+2. CLI session
+ - obtained with `openkey login` + `openkey unlock`
+ - exports `OPENKEY_SESSION` for the current shell
+ - allows CLI commands to operate without the local desktop app being open
+
+`openkey gen` works without either backend because it generates passwords locally and does not access the vault.
+
+The config file is stored in the standard user config directory:
+
+- macOS: `~/Library/Application Support/OpenKey/config.json`
+- Linux: `~/.config/openkey/config.json`
+- Windows: `%APPDATA%\OpenKey\config.json`
+
+## Command reference
+
+These are the commands most people use day to day.
+
+### At a glance
+
+- `openkey gen` — generate a strong password offline
+- `openkey discover` — scan a machine for likely secrets and onboard them
+- `openkey secret ...` — add, list, get, update, and export secrets
+- `openkey logins`, `openkey cards`, `openkey crypto` — inspect non-secret vault records
+- `openkey get`, `openkey copy`, `openkey totp` — fetch values for a single item
+- `openkey env` / `openkey run` — inject values into shells or child processes
+- `openkey sync`, `openkey login`, `openkey unlock` — use self-hosted server mode
+- `openkey doctor` — check local bridge/session/server health
+
+### 1) Password generation
+
+#### `openkey gen`
+
+Generate a password completely offline. This command does not require login, app unlock, or a server connection.
+
+Common options:
+
+```bash
+openkey gen --help
+openkey gen -l 24
+openkey gen -l 32 --no-symbols
+openkey gen -l 20 --no-upper --no-digits
+openkey gen -l 28 -a -c
+```
+
+What it does:
+
+- creates a secure password of the requested length
+- supports uppercase, lowercase, digits, and symbols toggles
+- can avoid ambiguous characters such as `I`, `l`, `1`, `O`, `0`
+- can copy the result to your clipboard with `-c`
+
+Use this when you need a new strong password for a service or for testing local credentials without touching any vault state.
+
+### 2) Discovering secrets on-device
+
+#### `openkey discover`
+
+Search the current machine for likely secrets and import them into the OpenKey vault under a device collection.
+
+Examples:
+
+```bash
+openkey discover --dry-run
+openkey discover -d laptop --depth 5
+openkey discover --no-ssh --no-aws -y
+```
+
+This command can scan:
+
+- SSH keys and config
+- `.env` files
+- exported environment variables
+- AWS config/credentials
+- GitHub CLI token data
+- Docker registry auth
+
+Flags:
+
+- `-d, --device`: choose the target device collection name
+- `-p, --path`: scan specific directories
+- `--depth`: max directory recursion depth for `.env` discovery
+- `--dry-run`: show candidates without saving them
+- `-y, --yes`: save new findings without prompting
+
+Use this to quickly onboard secrets into a structured, device-based vault instead of manually adding each item.
+
+### 3) Managing developer secrets
+
+OpenKey stores secret entries in device collections and allows typed secret kinds.
+
+#### Secret kinds
+
+- `apiToken` (default)
+- `sshKey`
+- `envSnippet`
+- `other`
+
+#### `openkey secret add`
+
+Create a new secret object.
+
+```bash
+openkey secret add --name "GitHub PAT" --kind apiToken --secret ghp_...
+openkey secret add -n "SSH key" -k sshKey --file ~/.ssh/id_ed25519
+openkey secret add -n "My token" --stdin --secret -
+```
+
+Useful options:
+
+- `-n, --name`: display name
+- `-k, --kind`: secret type
+- `-s, --secret`: raw value
+- `-f, --file`: read a value from a file
+- `--stdin`: read from stdin
+- `-u, --username`: related username
+- `-H, --host`: hostname or service target
+- `-d, --device`: assign a device collection
+- `--public-key`, `--public-key-file`: SSH-related metadata
+- `--notes`: description notes
+
+#### `openkey secret set`
+
+Create or update a secret matched by the combination of name and device.
+
+```bash
+openkey secret set -n "GitHub PAT" -k apiToken -s ghp_...
+```
+
+This is useful when you want to upsert a secret without worrying whether the item already exists.
+
+#### `openkey secret list`
+
+List all secrets with masked values.
+
+```bash
+openkey secret list
+openkey secret list -d laptop
+openkey secret list -k apiToken
+```
+
+This is safe for inspection because secret values are masked rather than printed in full.
+
+#### `openkey secret get`
+
+Print the raw secret value for a matched item.
+
+```bash
+openkey secret get "GitHub PAT"
+```
+
+Use this when you need the actual credential value in a script or terminal. Because it prints the plaintext secret, use it deliberately.
+
+#### `openkey copy`
+
+Copy a best-match vault item by name, host, or UUID prefix without requiring the `secret` subcommand. This is the shorthand for the common “copy a secret to clipboard” workflow.
+
+```bash
+openkey copy "GitHub PAT"
+openkey copy "GitHub" --clear 60
+```
+
+When the item is not a secret, OpenKey copies the matched value in the same way the generic `get` command resolves it.
+
+#### `openkey secret copy`
+
+Copy a secret directly to the system clipboard.
+
+```bash
+openkey secret copy "GitHub PAT"
+openkey secret copy "GitHub PAT" --clear 120
+openkey secret copy "GitHub PAT" --keep
+```
+
+The clipboard auto-clears after 45 seconds by default unless you disable that behavior with `--keep`.
+
+#### `openkey secret update`
+
+Modify fields on an existing secret without recreating it.
+
+```bash
+openkey secret update "GitHub PAT" --secret ghp_newvalue
+openkey secret update "GitHub PAT" --notes "Rotated on 2026-10-29"
+```
+
+#### `openkey secret export`
+
+Export secrets in a format ready for shell use.
+
+```bash
+openkey secret export -d laptop -o .env.local
+openkey secret export --format exports
+```
+
+This creates either a `.env`-style file or shell-export format, depending on the selected mode.
+
+#### `openkey secret devices`
+
+List device collections and the number of secrets assigned to each one.
+
+```bash
+openkey secret devices
+```
+
+#### `openkey secret rm`
+
+Delete a secret.
+
+```bash
+openkey secret rm "GitHub PAT" -y
+```
+
+This command prompts by default unless `-y` is used.
+
+### 4) Working with logins, cards, and crypto items
+
+In addition to typed secrets, OpenKey CLI can query other item categories already present in the vault.
+
+#### `openkey logins`
+
+List login entries with masked passwords.
+
+#### `openkey cards`
+
+List payment cards with numbers masked.
+
+#### `openkey crypto`
+
+List crypto wallets with masked keys or seed material.
+
+#### `openkey search`
+
+Search across secrets, logins, cards, and crypto items using a keyword query.
+
+```bash
+openkey search api
+openkey search github
+```
+
+#### `openkey get`
+
+Retrieve a value from a matching item when the item is not a plain secret.
+
+```bash
+openkey get "GitHub" --field username
+```
+
+This is the general-purpose “fetch a known field from a credential item” command.
+
+#### `openkey totp`
+
+Generate a time-based one-time password for a matching login or secret record.
+
+```bash
+openkey totp "GitHub"
+openkey totp "GitHub" -c
+```
+
+Use `-c` to copy the TOTP code to the clipboard.
+
+### 5) Exporting values into shell commands
+
+#### `openkey env`
+
+Print environment-export statements for one or more vault items.
+
+```bash
+eval "$(openkey env DATABASE_URL GITHUB_TOKEN)"
+openkey env DATABASE_URL
+openkey env DATABASE_URL=GitHub
+```
+
+This is intended for shell workflows where you want to inject secrets into your current session.
+
+#### `openkey run`
+
+Run a child process with selected secret values injected into its environment.
+
+```bash
+openkey run -e DATABASE_URL -e GH=GitHub -- npm start
+```
+
+The command after `--` receives the environment variables you configured and runs normally.
+
+### 6) Server / session commands
+
+These commands are used for self-hosted server mode.
+
+#### `openkey login`
+
+Log in to the configured self-hosted server.
+
+```bash
+openkey login --email you@example.com
+```
+
+#### `openkey logout`
+
+Clear server access tokens.
+
+#### `openkey unlock`
+
+Unlock the server-backed vault and print a shell-ready `OPENKEY_SESSION` export.
+
+```bash
+eval "$(openkey unlock)"
+```
+
+#### `openkey lock`
+
+Print the command to clear the active `OPENKEY_SESSION`.
+
+#### `openkey sync`
+
+Pull ciphertext from the server and optionally push the local cache.
+
+```bash
openkey sync
-eval $(openkey lock)
+openkey sync --push
+```
+
+#### `openkey config set-server`
+
+Set the OpenKey server URL.
+
+```bash
+openkey config set-server http://localhost:8000
```
-Master password: interactive prompt, or `OPENKEY_PASSWORD` for scripts/CI (never a CLI flag). Email can come from `OPENKEY_EMAIL`.
+#### `openkey config show`
-## Commands (summary)
+Display the current CLI configuration.
-| Command | Vault access? | Description |
-|---------|---------------|-------------|
-| `gen` | No | Offline password generation (`-l`, `--no-*`, `-a`, `-c`) |
-| `discover` | Save: yes\* | Scan SSH / `.env` / env / AWS → device group |
-| `secret add\|list\|get\|copy\|rm\|update\|export\|devices\|set` | Yes\* | Typed developer secrets (`--stdin` / `--secret -`) |
-| `get` / `copy` / `search` / `totp` / `logins` / `cards` / `crypto` | Yes\* | Secrets + logins + cards + wallets |
-| `env` / `run` | Yes\* | Export secrets into the shell or a child process |
-| `doctor` | No | Diagnose bridge / session / server / clipboard |
-| `completion` | No | Bash / zsh / fish completion scripts |
-| `status` / `config` / `forget` | No | Diagnostics and local state |
-| `login` / `logout` / `unlock` / `lock` / `sync` | Server path | Optional ciphertext sync + session (`sync --push`) |
+#### `openkey config set-lock`
-\*Desktop or Android app unlocked, **or** `eval $(openkey unlock)` after `login`.
+Set the unlock session lifetime in minutes.
-Global: `--json` for machine-readable output.
+### 7) Diagnostics and utilities
-Clipboard copies auto-clear after **45s** by default (`--keep` to disable; `--clear N` to change).
+#### `openkey status`
-### Secret kinds
+Show the current status of the local bridge, running session, and server connection.
-`apiToken` (default), `sshKey`, `envSnippet`, `other` — see the [CLI guide](../openkey_docs/guide/cli.md) for flags (`--file`, `--public-key-file`, `--device`, …).
+#### `openkey doctor`
-## Backends
+Run a focused diagnosis of CLI, bridge, session, and server health.
-1. **Native bridge** — unlocked desktop (or Android Termux) app (default).
-2. **CLI session** — `OPENKEY_SESSION` after `unlock`.
+```bash
+openkey doctor
+```
+
+#### `openkey completion`
+
+Print a shell completion script.
+
+```bash
+openkey completion bash
+openkey completion zsh
+openkey completion fish
+```
+
+#### `openkey forget`
-`openkey gen` needs neither.
+Wipe local config and cached ciphertext from the CLI.
-Config file (mode `600` when supported): macOS `~/Library/Application Support/OpenKey/config.json`, Linux `~/.config/openkey/config.json`, Windows `%APPDATA%\OpenKey\config.json`.
+## Global behavior
+
+- `--json` makes output machine-readable
+- clipboard copies are cleared automatically after 45 seconds by default
+- use `--keep` if you want the copied value to remain in the clipboard
+- `--clear N` changes the clipboard timeout in seconds
+
+## Security notes
+
+- `list` and `search` mask secret values; use `get` or `copy` when you need the plaintext
+- never pass the master password as a CLI flag
+- session lifetimes default to a limited window and can be set with `openkey config set-lock`
+- bridge access is local-only while the vault remains unlocked
+- server mode stores ciphertext; the real key material remains client-side
## Development
@@ -132,14 +581,6 @@ npm run typecheck
npm run build
```
-## Security notes
-
-- `list` / `search` mask values; use `get` / `copy` for plaintext
-- Do not pass the master password as a CLI flag
-- Sessions expire (default 15 minutes; `config set-lock`)
-- Bridge is local-only while the vault is unlocked
-- Server stores ciphertext only; keys are derived on the client
-
## License
OpenKey CLI is licensed under the [MIT License](./LICENSE).
diff --git a/src/commands/auth.test.ts b/src/commands/auth.test.ts
index 10a494b..9cf6485 100644
--- a/src/commands/auth.test.ts
+++ b/src/commands/auth.test.ts
@@ -64,13 +64,13 @@ describe("cmdUnlock", () => {
const requestedPaths: string[] = [];
const server = createServer(async (request, response) => {
requestedPaths.push(request.url ?? "");
- if (request.method === "POST" && request.url === "/api/v1/auth/prelogin") {
+ if (request.method === "POST" && request.url === "/auth/prelogin") {
await readJson(request);
respond(response, {
salt: toB64Url(salt),
kdf_params: serverKdfParams,
});
- } else if (request.method === "POST" && request.url === "/api/v1/auth/login") {
+ } else if (request.method === "POST" && request.url === "/auth/login") {
const body = await readJson(request);
expect(body.auth_hash).toBe(deriveAuthHash(masterKey));
respond(response, {
@@ -78,14 +78,14 @@ describe("cmdUnlock", () => {
refresh_token: "test-refresh",
user_id: "user-id",
});
- } else if (request.method === "GET" && request.url === "/api/v1/auth/me") {
+ } else if (request.method === "GET" && request.url === "/auth/me") {
respond(response, {
email,
salt: toB64Url(salt),
kdf_params: serverKdfParams,
encrypted_vault_key: wrapped,
});
- } else if (request.method === "POST" && request.url === "/api/v1/sync") {
+ } else if (request.method === "POST" && request.url === "/sync") {
respond(response, {
entries: [],
collections: [],
@@ -119,10 +119,10 @@ describe("cmdUnlock", () => {
expect(log).toHaveBeenCalledOnce();
expect(requestedPaths).toEqual([
- "/api/v1/auth/prelogin",
- "/api/v1/auth/login",
- "/api/v1/auth/me",
- "/api/v1/sync",
+ "/auth/prelogin",
+ "/auth/login",
+ "/auth/me",
+ "/sync",
]);
} finally {
process.env.XDG_CONFIG_HOME = previousEnv.XDG_CONFIG_HOME;
@@ -133,5 +133,5 @@ describe("cmdUnlock", () => {
);
await rm(configRoot, { recursive: true, force: true });
}
- });
+ }, 15_000);
});
diff --git a/src/lib/api.test.ts b/src/lib/api.test.ts
index fe7511d..19551a9 100644
--- a/src/lib/api.test.ts
+++ b/src/lib/api.test.ts
@@ -38,7 +38,7 @@ describe("server network errors", () => {
"user@example.com",
),
).rejects.toThrow(
- "Could not reach OpenKey server at https://server.example/api/v1/auth/prelogin: connect ECONNREFUSED (ECONNREFUSED)",
+ "Could not reach OpenKey server at https://server.example/auth/prelogin: connect ECONNREFUSED (ECONNREFUSED)",
);
});
});