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>
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).
unlockprintsexport 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, andlockremoves 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.