logicsrc/docs/opencreds/cli.md
Anthony Ettinger a0f8f2c125 vault + teams: filter secrets by category, export to CSV, simpler help
Every secret now has a category derived from its name (db, social, server,
api, cloud, finance, crypto, ai, email, messaging, storage, dns, analytics,
devtools, auth, config, other). One rule table in @logicsrc/opencreds serves
both vaults; services win over generic words, so STRIPE_WEBHOOK_SECRET is
finance, not auth. Checked against the 1,208 distinct key names in the
profullstack team: 74 fall to "other".

Team vaults (where the shared .env secrets live):
- teams categories [team]    the filter words, with per-category counts
- teams secrets <team> [project] [env] --category/-c --search/-s
                             names + categories, never decrypts; --format csv
- teams export  <team> [project] [env] --category -o file.csv [--yes]
                             decrypts into team,project,env,category,key,
                             value,updated_at (0600); skips vaults without a
                             grant and names them

Personal vault (OpenCreds):
- vault list --category, and the category column in list output
- vault export --format csv: one flat row per item, keeps key/account
  secrets that a Bitwarden CSV drops; --category on every export format

DX:
- examples in `logicsrc vault help` / -h / --help that start by saying which
  of the two vaults you want, plus examples on teams and each subcommand
- password prompts go to stderr, so eval "$(logicsrc vault unlock)" works
- hints name the command you actually ran (logicsrc vault init, not
  opencreds init)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-24 03:57:32 +00:00

6.7 KiB

The OpenCreds CLI

The CLI is part of the conformance surface: flags, output shapes and exit codes are specified, not incidental. The same commands ship twice — as logicsrc vault … and as the standalone opencreds binary — from one implementation, so the two can never drift.

Exit codes

Code Meaning
0 Success.
1 Usage error — unknown flag, missing argument, unreadable file.
2 Validation failure — a document did not conform.
3 Crypto failure — wrong password, failed tag, manifest mismatch.
4 Refused — the operation needs a confirmation that was not given.

Vault

opencreds init [--namespace opencreds] [--iterations 600000] [--password-stdin]

Creates a vault. Prompts for a master password twice, prints a recovery key once, and never prints it again. Refuses if a vault already exists at the target unless --force. --password-stdin reads one line instead and skips the confirmation — for scripted provisioning, where there is nobody to mistype.

opencreds unlock                    # prints a session token to export
opencreds unlock --persist [--timeout 15]
opencreds lock                      # drops a persisted session
opencreds status                    # vault present? locked? counts by type

Unlocking has two shapes, and the difference is a flag rather than a default because it is a real trade:

  • Token (default). unlock prints export OPENCREDS_SESSION="…". Nothing touches disk and the session dies with the shell.
  • Persisted (--persist). The same token in a 0600 file with an expiry, so a script can unlock once and run many commands. A readable user key on disk is the vault; the command says so when you use it, and lock removes it.

status is the one command that works locked. It reports counts and never values, because counts are already observable to whoever holds the storage.

opencreds recover         # unlock with the recovery key, set a new password

A password change re-wraps the user key. Not one item is re-encrypted, which is why it is instant on a vault of any size.

Items

opencreds add <type> --name <name> [type flags…]
opencreds list [--type <type>] [--category <names>] [--folder <name>] [--search <text>] [--json]
opencreds get <id|name> [--field <path>] [--reveal]
opencreds edit <id|name> [flags…]
opencreds rm <id|name> [--purge]
opencreds restore <id>

list and get MUST NOT print secret values by default. get prints the item with every secret field masked; --reveal prints one field named by --field, so revealing is always a deliberate act naming a single value. --json output is masked identically — a pipeline is not an authorization.

list prints one line per item: short id, type, category, name. The category (db, social, server, api, finance, … and other) is derived from the item type, name, account provider and login hosts, and is never stored, so it cannot disagree between implementations that share the rule table. --category db,social filters on it and accepts aliases (database, payments); an unknown word exits 1 and names the valid ones.

Type flags follow the field group names, kebab-cased: --username, --password, --totp, --url, --cardholder-name, --number, --exp-month, --exp-year, --code, --first-name, --last-name, --email, --phone, --address1, …, --key-type, --algorithm, --public-key, --private-key, --file, --path, --mode, --provider, --account-id, --handle, --access-token, --refresh-token, --scope.

--password - and every other secret flag read from stdin when given -, so a secret need not appear in the shell history or the process list.

Database

opencreds export [--out vault.opencreds] [--passphrase-stdin]
opencreds export --plaintext --out vault.json --yes
opencreds export --format csv --out vault.csv --yes
opencreds export --format bitwarden-csv --out vault.csv --yes
opencreds export --format csv --category db --out db.csv --yes

opencreds import <file> [--dry-run] [--merge skip|replace|duplicate]
opencreds import <file> --source bitwarden|onepassword|chrome|lastpass|keepass

export writes the encrypted form. --plaintext prints what it is about to do and exits 4 without --yes.

--format csv writes one flat row per item — folder,category,type,name,username,password,url,value,totp,notes — where value is the single opaque secret of a key, account or card. It keeps the key and account items a Bitwarden CSV has no column for, and is meant for people and scripts, not re-import. --format bitwarden-csv is the one to hand another password manager. --category limits any export format to those categories.

import with --dry-run reports counts by type, folders to be created, duplicates detected and rows that could not be mapped, and writes nothing. A manifest mismatch exits 3 and writes nothing regardless of flags.

Output of a dry run:

opencreds import vault.opencreds --dry-run

  Source      vault.opencreds (opencreds 0.1, encrypted, namespace opencreds)
  Exported    2026-08-29T18:00:00.000Z by @logicsrc/opencreds 0.1.0
  Manifest    verified — 42 items, 3 folders

  login       38    2 already present (skip)
  card         2
  key          1
  account      1

  Folders     Work, Personal (new), Archive
  Skipped     0

  Nothing written. Re-run without --dry-run to import.

Validation

opencreds validate <file>          # a database, or a plaintext item document
opencreds validate --stdin

Exits 0 when the document conforms, 2 when it does not, and prints one diagnostic per failure with a JSON pointer into the document:

/items/17/login/uris/0/match   "fuzzy" is not a valid match rule
/manifest/itemCount            says 42, payload has 41

Conformance

opencreds conformance                          # a table, one row per requirement
opencreds conformance --json                   # the report, for CI
opencreds conformance --emit-fixtures <dir>    # generate the fixture set

Runs the suite against this implementation and reports each requirement as pass, fail or skip. Exits 2 when a MUST does not pass, so it can gate CI directly. An implementation claiming conformance SHOULD run it there.

--emit-fixtures writes the generated fixture set, so another implementation can be tested against exactly what this one produces and accepts. See conformance.md.

What the CLI never does

  • It never prints a secret value except through get --reveal --field.
  • It never writes a plaintext file without an explicit flag and a confirmation.
  • It never sends anything anywhere. There is no telemetry, no account, and no network call in any command listed on this page.