bwx 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +136 -10
- package/package.json +1 -1
- package/skill/bwx/SKILL.md +107 -14
- package/src/bw/encoding.ts +16 -4
- package/src/bw/fields.ts +326 -27
- package/src/bw/folders.ts +96 -0
- package/src/bw/generate.ts +91 -0
- package/src/bw/items.ts +8 -0
- package/src/bw/session.ts +36 -10
- package/src/bw/totp.ts +246 -0
- package/src/cli/commands/config.ts +1 -10
- package/src/cli/commands/create.ts +60 -5
- package/src/cli/commands/delete.ts +48 -21
- package/src/cli/commands/edit.ts +88 -5
- package/src/cli/commands/folders.ts +34 -0
- package/src/cli/commands/generate.ts +57 -0
- package/src/cli/commands/get.ts +130 -7
- package/src/cli/commands/list.ts +25 -8
- package/src/cli/commands/run.ts +14 -9
- package/src/cli/commands/search.ts +16 -12
- package/src/cli/commands/status.ts +32 -5
- package/src/cli/commands/trash.ts +92 -0
- package/src/cli/input.ts +55 -2
- package/src/cli/io.ts +12 -2
- package/src/cli/items.ts +56 -10
- package/src/cli/program.ts +6 -0
package/README.md
CHANGED
|
@@ -54,28 +54,114 @@ bwx doctor
|
|
|
54
54
|
|
|
55
55
|
| Command | Description |
|
|
56
56
|
|---------|-------------|
|
|
57
|
-
| `bwx status` | Show vault state |
|
|
57
|
+
| `bwx status` | Show vault state and whether reads work right now |
|
|
58
58
|
| `bwx unlock` | Unlock vault & cache session |
|
|
59
59
|
| `bwx lock` | Lock vault & clear session |
|
|
60
60
|
| `bwx sync` | Sync vault from server |
|
|
61
61
|
| `bwx doctor` | Check setup (bw CLI, email, password, config) |
|
|
62
62
|
|
|
63
|
+
`status` reports a `session` alongside the vault state, because `bw` answers for the
|
|
64
|
+
vault and not for bwx: a `locked` vault with a `valid` session reads fine and needs no
|
|
65
|
+
unlock. `valid` means reads work now, `stale` means the cached session was rejected, and
|
|
66
|
+
`none` means the next read unlocks.
|
|
67
|
+
|
|
63
68
|
### Read
|
|
64
69
|
|
|
65
70
|
| Command | Description |
|
|
66
71
|
|---------|-------------|
|
|
67
72
|
| `bwx get <field> <item>` | Get field (`password`, `username`, `totp`, `notes`, `uri`, `item`) |
|
|
68
|
-
| `bwx
|
|
73
|
+
| `bwx get totp <item>` | TOTP code (`--fresh [n]`, `--seed`) |
|
|
74
|
+
| `bwx field <name> <item>` | Get custom field by name (same argument order as `get`) |
|
|
69
75
|
| `bwx search <query>` | Search items (`--type`, `--folder`, `--limit`, `--full-items`) |
|
|
70
|
-
| `bwx list [type]` | List items (`--folder`, `--limit`, `--full-items`) |
|
|
76
|
+
| `bwx list [type]` | List items (`--type`, `--folder`, `--limit`, `--full-items`) |
|
|
77
|
+
| `bwx folders` | List folder names and IDs |
|
|
78
|
+
| `bwx trash` | List items in the trash |
|
|
71
79
|
| `bwx run --env NAME=<field>:<item> -- <cmd>` | Run a command with secrets in its environment |
|
|
72
80
|
| `bwx attach list <item>` | List attachments for an item |
|
|
73
81
|
| `bwx attach get <item> <attachment>` | Download attachment by ID or filename |
|
|
74
82
|
|
|
75
|
-
`search` and `list` print the same reduced projection — id, type, name, username
|
|
76
|
-
human, `--plain`, and `--json` form, so asking for machine-readable output
|
|
77
|
-
what is exposed. `--full-items` is the explicit opt-in to complete item
|
|
78
|
-
carry passwords, notes, and custom fields.
|
|
83
|
+
`search` and `list` print the same reduced projection — id, type, name, username,
|
|
84
|
+
hasTotp — in human, `--plain`, and `--json` form, so asking for machine-readable output
|
|
85
|
+
never widens what is exposed. `--full-items` is the explicit opt-in to complete item
|
|
86
|
+
objects, which carry passwords, notes, and custom fields.
|
|
87
|
+
|
|
88
|
+
`hasTotp` reports that an item has a TOTP secret without carrying it, so finding your 2FA
|
|
89
|
+
items does not mean dumping the vault:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
bwx list --json --limit 0 | jq -r '.data[] | select(.hasTotp) | .name'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
A lookup that finds nothing suggests what you probably meant, and one that matches
|
|
96
|
+
several items lists them, so neither ends in a guessing round:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
$ bwx get password "GitHub PAT vforsh old"
|
|
100
|
+
error: No item matching "GitHub PAT vforsh old". Did you mean:
|
|
101
|
+
|
|
102
|
+
934c1f57-… note GitHub PAT (vforsh)
|
|
103
|
+
3683fb9a-… note GitHub PAT (yaforsh) [codex-monitor-pr]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### TOTP
|
|
107
|
+
|
|
108
|
+
Codes are computed locally from the stored secret. A TOTP therefore costs no extra `bw`
|
|
109
|
+
spawn and batches with the other fields of the same item, and bwx can report how long the
|
|
110
|
+
code has left.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
bwx get totp "AWS" # code on stdout, "Valid for 17s" on stderr
|
|
114
|
+
bwx get totp "AWS" --json # { "data": "123456", "meta": { "secondsRemaining": 17, "period": 30 } }
|
|
115
|
+
bwx get totp "AWS" --fresh # wait for the next window if under 5s remain
|
|
116
|
+
bwx get totp "AWS" --fresh 15 # ...or under 15s
|
|
117
|
+
bwx get totp "AWS" --seed # the stored secret, for moving it to another authenticator
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The remainder matters more than it looks: a vault read plus the time to actually use the
|
|
121
|
+
code often leaves single-digit seconds, and a code that expires in transit fails the login
|
|
122
|
+
without saying why. `--fresh` sleeps at most one period and hands back a full window.
|
|
123
|
+
|
|
124
|
+
Bare base32 and `otpauth://` URIs are both understood, including non-default `period`,
|
|
125
|
+
`digits`, and `algorithm` (SHA1/SHA256/SHA512). Secrets bwx will not compute exactly —
|
|
126
|
+
`steam://`, unknown algorithms, anything that fails to decode — fall back to `bw get totp`
|
|
127
|
+
rather than being guessed at, since a wrong code is indistinguishable from a right one
|
|
128
|
+
until the login fails.
|
|
129
|
+
|
|
130
|
+
Storing a secret:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
BWX_SEED="JBSWY3DPEHPK3PXP" bwx create --type login --name "AWS" --username me --totp-env BWX_SEED
|
|
134
|
+
bwx edit "AWS" --totp "otpauth://totp/AWS:me?secret=JBSWY3DPEHPK3PXP&issuer=AWS"
|
|
135
|
+
bwx edit "AWS" --rm-totp
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Spaces, hyphens, and `=` padding are stripped and case is normalized; an `otpauth://` URI
|
|
139
|
+
is stored verbatim so its parameters survive. Malformed secrets are rejected before the
|
|
140
|
+
write, because a bad seed is otherwise discovered at the login it was supposed to unlock.
|
|
141
|
+
|
|
142
|
+
### Listings are capped
|
|
143
|
+
|
|
144
|
+
Listings stop at 50 items by default — an uncapped `bwx list` returns the whole vault,
|
|
145
|
+
and with `--full-items` that is every secret in one call. `--limit 0` opts out. Truncation
|
|
146
|
+
is never silent: it warns on stderr (surviving `--json`) and adds `meta` to the JSON
|
|
147
|
+
envelope.
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
bwx list --limit 0 # everything
|
|
151
|
+
bwx list --json # { "data": [...], "meta": { "total": 491, "shown": 50, "truncated": true } }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Folders
|
|
155
|
+
|
|
156
|
+
`--folder` takes a name or an ID on `list`, `search`, `create`, and `edit`; `none` means
|
|
157
|
+
the unfiled bucket. Names are resolved against the vault, so `bwx folders` is only needed
|
|
158
|
+
to see what exists.
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bwx folders
|
|
162
|
+
bwx list --folder "API Keys"
|
|
163
|
+
bwx edit "Acme" --folder none # unfile
|
|
164
|
+
```
|
|
79
165
|
|
|
80
166
|
### Secrets into a child process
|
|
81
167
|
|
|
@@ -111,9 +197,37 @@ The check is cached in `~/.config/bwx/state.json`, so a fresh vault costs no ext
|
|
|
111
197
|
|
|
112
198
|
| Command | Description |
|
|
113
199
|
|---------|-------------|
|
|
114
|
-
| `bwx create` | Create item (`--type`, `--name`, `--username`, `--password-stdin`, `--field-file k=path`) |
|
|
115
|
-
| `bwx edit <item>` | Edit item (`--name`, `--password-env`, `--add-field-file k=path`, `--rm-field name`) |
|
|
200
|
+
| `bwx create` | Create item (`--type`, `--name`, `--username`, `--password-stdin`, `--totp-env`, `--field-file k=path`) |
|
|
201
|
+
| `bwx edit <item>` | Edit item (`--name`, `--password-env`, `--totp`, `--rm-totp`, `--add-field-file k=path`, `--rm-field name`) |
|
|
116
202
|
| `bwx delete <item>` | Delete item (`--permanent`, `--force`) |
|
|
203
|
+
| `bwx restore <item>` | Restore an item from the trash |
|
|
204
|
+
| `bwx generate` | Generate a password or passphrase (local, no vault access) |
|
|
205
|
+
|
|
206
|
+
Login-only flags (`--username`, any `--password-*` or `--totp-*`, `--uri`) require a login
|
|
207
|
+
item. A secure note has nowhere to put them, so passing them to one is an error rather
|
|
208
|
+
than an exit 0 over a credential that was silently dropped.
|
|
209
|
+
|
|
210
|
+
`bwx delete` requires `--force` whenever it is not talking to a terminal. The confirmation
|
|
211
|
+
prompt cannot render for a script or an agent, so without it `--force` would be decorative
|
|
212
|
+
and every non-interactive delete unguarded. Deletes go to the trash unless `--permanent`,
|
|
213
|
+
and `bwx trash` / `bwx restore` are the way back.
|
|
214
|
+
|
|
215
|
+
### Generated passwords
|
|
216
|
+
|
|
217
|
+
`--password-generate` on `create` and `edit` sends a new secret straight to the vault. It
|
|
218
|
+
is never printed, never echoed back in `--json`, and never reaches a terminal, a
|
|
219
|
+
transcript, or a log — which is what makes it safe to use from an agent.
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
bwx create --type login --name "Deploy" --username svc --password-generate
|
|
223
|
+
bwx edit "Deploy" --password-generate --generate-length 32
|
|
224
|
+
bwx generate --length 32 # print one instead
|
|
225
|
+
bwx generate --passphrase --words 5
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`bwx generate` runs locally and needs no unlocked vault. Flags: `--length <n>`,
|
|
229
|
+
`--no-uppercase`, `--no-lowercase`, `--no-numbers`, `-s/--special`, `--ambiguous`,
|
|
230
|
+
`--passphrase`, `--words <n>`, `--separator <char>`, `--capitalize`, `--include-number`.
|
|
117
231
|
|
|
118
232
|
### Config
|
|
119
233
|
|
|
@@ -147,6 +261,7 @@ bwx create --name "Acme" --field-file '!API Key=./token.txt' --field-env account
|
|
|
147
261
|
Supported source flags:
|
|
148
262
|
|
|
149
263
|
- Passwords: `--password-stdin`, `--password-file <path>`, `--password-env <name>`
|
|
264
|
+
- TOTP secrets: `--totp-stdin`, `--totp-file <path>`, `--totp-env <name>`
|
|
150
265
|
- Notes: `--notes-file <path>`
|
|
151
266
|
- Create fields: `--field-file k=path`, `--field-env k=ENV`
|
|
152
267
|
- Edit fields: `--add-field-file k=path`, `--add-field-env k=ENV`
|
|
@@ -156,7 +271,7 @@ Supported source flags:
|
|
|
156
271
|
|
|
157
272
|
| Flag | Effect |
|
|
158
273
|
|------|--------|
|
|
159
|
-
| `--json` | JSON output: `{ "data": ... }` / `{ "error": ... }` |
|
|
274
|
+
| `--json` | JSON output: `{ "data": ... }` / `{ "error": ... }`, plus `meta` where a response describes itself |
|
|
160
275
|
| `--plain` | Tab-separated, one item per line |
|
|
161
276
|
| `-q, --quiet` | Suppress logs and warnings |
|
|
162
277
|
| `-v, --verbose` | Include stack traces |
|
|
@@ -191,6 +306,17 @@ Field syntax for `--field`, `--field-file`, `--field-env`, and edit variants:
|
|
|
191
306
|
| 8 | Timeout |
|
|
192
307
|
| 9 | User cancelled |
|
|
193
308
|
|
|
309
|
+
## Upgrading to 0.5.0
|
|
310
|
+
|
|
311
|
+
Breaking changes, all of them cases where the old behavior failed quietly:
|
|
312
|
+
|
|
313
|
+
- `bwx field` takes `<name> <item>`, matching `bwx get`. It was `<item> <name>`.
|
|
314
|
+
- `bwx delete` needs `--force` when not interactive; it used to delete unprompted.
|
|
315
|
+
- `bwx list` and `bwx search` stop at 50 items unless given `--limit`. Pass `--limit 0`
|
|
316
|
+
for the old behavior.
|
|
317
|
+
- `--limit` rejects non-numeric values instead of ignoring them (and returning everything).
|
|
318
|
+
- Login-only flags on a secure note are an error instead of a silent drop.
|
|
319
|
+
|
|
194
320
|
## Stack
|
|
195
321
|
|
|
196
322
|
Bun, TypeScript, Commander, Zod, picocolors.
|
package/package.json
CHANGED
package/skill/bwx/SKILL.md
CHANGED
|
@@ -11,13 +11,14 @@ Extended Bitwarden CLI. Wraps `bw` with auto-unlock, session caching, and struct
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
bwx get password "GitHub" # retrieve a password (auto-unlocks)
|
|
14
|
-
bwx get totp "AWS (user@example.com)" # TOTP code
|
|
14
|
+
bwx get totp "AWS (user@example.com)" # TOTP code (+ seconds left, on stderr)
|
|
15
|
+
bwx get totp "AWS" --fresh # wait out a nearly-expired code first
|
|
15
16
|
bwx get username "GitHub" # username
|
|
16
17
|
bwx get notes "Deploy key" # secure note content
|
|
17
18
|
bwx get uri "GitHub" # first URI
|
|
18
19
|
bwx get item "GitHub" # full item JSON
|
|
19
20
|
bwx get "API Key" "Acme" # custom field by name
|
|
20
|
-
bwx field "
|
|
21
|
+
bwx field "API Key" "Acme" # custom-field-only lookup, same arg order
|
|
21
22
|
|
|
22
23
|
bwx search github # fuzzy search, human table
|
|
23
24
|
bwx search github --json # id/type/name/username only — no secrets
|
|
@@ -26,6 +27,9 @@ bwx search github --type login --limit 5
|
|
|
26
27
|
# hand a secret to a child process instead of printing it
|
|
27
28
|
bwx run --env KEENETIC_PASS=password:'http://my.keenetic.net' -- expect ./recovery.exp
|
|
28
29
|
|
|
30
|
+
# generate straight into the vault — the value never enters your context
|
|
31
|
+
bwx create --type login --name "Deploy" --username svc --password-generate
|
|
32
|
+
|
|
29
33
|
bwx attach list "Apple Developer" # list item attachments
|
|
30
34
|
bwx attach get "Apple Developer" AuthKey.p8
|
|
31
35
|
bwx attach get "Apple Developer" AuthKey.p8 --output ./secrets/
|
|
@@ -43,17 +47,56 @@ echo "secret" | bwx create --name "Piped Note"
|
|
|
43
47
|
Built-in fields: `password | username | totp | notes | uri | item`.
|
|
44
48
|
Anything else is looked up as a custom field. Lists available fields on miss.
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
A miss suggests near matches; an ambiguous name lists candidates with IDs. Read the
|
|
51
|
+
suggestions instead of retrying variations — the answer is usually in them.
|
|
52
|
+
|
|
53
|
+
### `bwx get totp <item>`
|
|
54
|
+
|
|
55
|
+
Codes are computed locally from the stored secret, so a TOTP costs no extra `bw` spawn and
|
|
56
|
+
batches with other fields from the same item.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
bwx get totp "AWS" # code on stdout, "Valid for 17s" on stderr
|
|
60
|
+
bwx get totp "AWS" --json # { "data": "123456", "meta": { "secondsRemaining", "period" } }
|
|
61
|
+
bwx get totp "AWS" --fresh # wait for the next window if under 5s remain
|
|
62
|
+
bwx get totp "AWS" --fresh 15 # ...or under 15s
|
|
63
|
+
bwx get totp "AWS" --seed # the stored secret, for migrating to another app
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Check `secondsRemaining` before using a code.** A `bw` read plus the time to hand the code
|
|
67
|
+
over routinely leaves under 5 seconds, and a code that expires in transit fails the login
|
|
68
|
+
with no useful error. `--fresh` sleeps at most one period and returns a full-window code.
|
|
47
69
|
|
|
48
|
-
|
|
70
|
+
Accepts bare base32 and `otpauth://` URIs, honoring their `period`, `digits`, and
|
|
71
|
+
`algorithm` (SHA1/SHA256/SHA512). `steam://` secrets and anything else bwx cannot compute
|
|
72
|
+
exactly are delegated to `bw get totp` instead of guessed at.
|
|
73
|
+
|
|
74
|
+
### `bwx field <name> <item>`
|
|
75
|
+
|
|
76
|
+
Custom fields only, so an item whose custom field is named `password` is still reachable.
|
|
77
|
+
Takes its arguments in the same order as `get` (this changed in 0.5.0).
|
|
49
78
|
|
|
50
79
|
### `bwx search <query>` / `bwx list [type]`
|
|
51
80
|
|
|
52
|
-
Flags: `--type login|note|card|identity
|
|
81
|
+
Flags: `--type login|note|card|identity`, `--folder <name|id>`, `--limit <n>`, `--full-items`
|
|
53
82
|
|
|
54
83
|
Human, `--plain`, and `--json` output all carry the same reduced projection — `id`, `type`,
|
|
55
|
-
`name`, `username` — so listings never leak passwords, notes, or custom fields.
|
|
56
|
-
opts into complete item objects and **does** include secrets; avoid it in
|
|
84
|
+
`name`, `username`, `hasTotp` — so listings never leak passwords, notes, or custom fields.
|
|
85
|
+
`--full-items` opts into complete item objects and **does** include secrets; avoid it in
|
|
86
|
+
logged sessions.
|
|
87
|
+
|
|
88
|
+
`hasTotp` says whether an item has a TOTP secret, never what it is. Use it to find 2FA items
|
|
89
|
+
(`bwx list --json --limit 0 | jq '.data[]|select(.hasTotp)'`) instead of reaching for
|
|
90
|
+
`--full-items`, which would dump the whole vault in plaintext to answer a boolean.
|
|
91
|
+
|
|
92
|
+
**Listings stop at 50 items** unless you pass `--limit` (`--limit 0` for all). Truncation is
|
|
93
|
+
reported on stderr and in `meta` on the JSON envelope — check it before concluding a listing
|
|
94
|
+
was complete. `--limit` rejects non-numeric values rather than ignoring them.
|
|
95
|
+
|
|
96
|
+
### `bwx folders`
|
|
97
|
+
|
|
98
|
+
Lists folder names and IDs. `--folder` accepts either on `list`, `search`, `create`, and
|
|
99
|
+
`edit`; `none` selects unfiled items.
|
|
57
100
|
|
|
58
101
|
### `bwx run --env NAME=<field>:<item> -- <command>`
|
|
59
102
|
|
|
@@ -70,6 +113,8 @@ bwx run --env TOKEN='API Key:Acme' --env USER=username:Acme -- ./deploy.sh
|
|
|
70
113
|
- Put `--` before the command when it takes its own flags.
|
|
71
114
|
- Child stdio is inherited; bwx exits with the child's status (`128 + signal` if killed).
|
|
72
115
|
- `-v` logs injected variable names only, never values.
|
|
116
|
+
- Several variables from one item cost a single vault read, so group them here rather
|
|
117
|
+
than issuing separate `bwx get` calls.
|
|
73
118
|
|
|
74
119
|
### `bwx attach list <item>`
|
|
75
120
|
|
|
@@ -81,7 +126,10 @@ Downloads an attachment by ID or filename. Defaults to saving as the attachment
|
|
|
81
126
|
|
|
82
127
|
### `bwx create [options]`
|
|
83
128
|
|
|
84
|
-
Flags: `--type login|note`, `--name`, `--notes`, `--notes-file`, `--username`, `--password`, `--password-stdin`, `--password-file`, `--password-env`, `--uri` (repeatable), `--field k=v` (repeatable), `--field-file k=path`, `--field-env k=ENV`, `--folder
|
|
129
|
+
Flags: `--type login|note`, `--name`, `--notes`, `--notes-file`, `--username`, `--password`, `--password-stdin`, `--password-file`, `--password-env`, `--password-generate`, `--generate-length <n>`, `--totp`, `--totp-stdin`, `--totp-file`, `--totp-env`, `--uri` (repeatable), `--field k=v` (repeatable), `--field-file k=path`, `--field-env k=ENV`, `--folder <name|id>`, `--favorite`, `--from-json`
|
|
130
|
+
|
|
131
|
+
`--username`, any `--password-*` or `--totp-*`, and `--uri` require `--type login`; a note
|
|
132
|
+
cannot hold them and passing them to one is now an error rather than a silent drop.
|
|
85
133
|
|
|
86
134
|
Field syntax: `k=v` (text), `!k=v` (hidden), `bool:k=v` (boolean). The same key syntax works for `--field-file` and `--field-env`, where the value is a path or environment variable name. Stdin is read as `--notes` when piped; prefer `--password-stdin` for login passwords.
|
|
87
135
|
|
|
@@ -93,26 +141,68 @@ bwx edit "API Key" --add-field region=us-east-1
|
|
|
93
141
|
printf '%s' "$TOKEN" | bwx edit "GitHub" --password-stdin --uri https://github.com
|
|
94
142
|
bwx edit "Note" --rm-field old-key
|
|
95
143
|
```
|
|
96
|
-
Flags: `--name`, `--notes`, `--notes-file`, `--username`, `--password`, `--password-stdin`, `--password-file`, `--password-env`, `--uri` (replaces all), `--add-field k=v`, `--add-field-file k=path`, `--add-field-env k=ENV`, `--rm-field name`, `--folder
|
|
144
|
+
Flags: `--name`, `--notes`, `--notes-file`, `--username`, `--password`, `--password-stdin`, `--password-file`, `--password-env`, `--password-generate`, `--generate-length <n>`, `--totp`, `--totp-stdin`, `--totp-file`, `--totp-env`, `--rm-totp`, `--uri` (replaces all), `--add-field k=v`, `--add-field-file k=path`, `--add-field-env k=ENV`, `--rm-field name`, `--folder <name|id>` (`none` unfiles), `--favorite/--no-favorite`, `--from-json`
|
|
145
|
+
|
|
146
|
+
### Storing a TOTP secret
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
BWX_SEED="JBSWY3DPEHPK3PXP" bwx create --type login --name "AWS" --username me --totp-env BWX_SEED
|
|
150
|
+
bwx edit "AWS" --totp "otpauth://totp/AWS:me?secret=JBSWY3DPEHPK3PXP&issuer=AWS"
|
|
151
|
+
bwx edit "AWS" --rm-totp
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Takes a bare base32 secret (spaces, hyphens, and `=` padding are stripped; case is
|
|
155
|
+
normalized) or a full `otpauth://` URI, which is stored verbatim so its `period`/`digits`/
|
|
156
|
+
`algorithm` survive. Malformed secrets are rejected **before** the write — a bad seed
|
|
157
|
+
otherwise sits in the vault until it fails the login it was meant to unlock, which is the
|
|
158
|
+
one moment it cannot be fixed.
|
|
159
|
+
|
|
160
|
+
Prefer `--totp-env` or `--totp-stdin` over `--totp`: a seed on the command line lands in
|
|
161
|
+
shell history and `ps` output.
|
|
97
162
|
|
|
98
163
|
### `bwx delete <item>`
|
|
99
164
|
|
|
100
165
|
```bash
|
|
101
|
-
bwx delete "Test Note"
|
|
102
|
-
bwx delete "Test Note" --force
|
|
103
|
-
bwx
|
|
166
|
+
bwx delete "Test Note" --force # --force is REQUIRED when not a TTY
|
|
167
|
+
bwx delete "Test Note" --permanent --force # skip the trash
|
|
168
|
+
bwx trash # what is recoverable
|
|
169
|
+
bwx restore "Test Note" # undo a non-permanent delete
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The prompt only exists for a human at a terminal, so an agent must pass `--force`
|
|
173
|
+
deliberately. Deletes are recoverable through `bwx restore` unless `--permanent`.
|
|
174
|
+
|
|
175
|
+
### `bwx generate`
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
bwx generate --length 32
|
|
179
|
+
bwx generate --passphrase --words 5 --separator -
|
|
104
180
|
```
|
|
105
181
|
|
|
182
|
+
Local, no vault access, no unlock. Flags: `--length <n>`, `--no-uppercase`,
|
|
183
|
+
`--no-lowercase`, `--no-numbers`, `-s/--special`, `--ambiguous`, `--passphrase`,
|
|
184
|
+
`--words <n>`, `--separator <char>`, `--capitalize`, `--include-number`.
|
|
185
|
+
|
|
186
|
+
Prefer `--password-generate` on `create`/`edit` over generating and passing the value:
|
|
187
|
+
it goes to the vault without ever entering your context, and `--json` returns
|
|
188
|
+
`***GENERATED***` in place of the password.
|
|
189
|
+
|
|
106
190
|
### Other
|
|
107
191
|
|
|
108
192
|
| Command | Description |
|
|
109
193
|
|---------|-------------|
|
|
110
|
-
| `bwx status` | Vault state (no auto-unlock) |
|
|
194
|
+
| `bwx status` | Vault state + session usability (no auto-unlock) |
|
|
111
195
|
| `bwx sync` | Force vault sync |
|
|
112
196
|
| `bwx unlock` / `bwx lock` | Manual session control |
|
|
197
|
+
| `bwx folders` | Folder names and IDs |
|
|
198
|
+
| `bwx trash` / `bwx restore <item>` | Inspect and undo deletes |
|
|
113
199
|
| `bwx config list` / `bwx cfg ls` | Show config path + redacted content |
|
|
114
200
|
| `bwx config master-password` | Set/update master password in Keychain |
|
|
115
201
|
|
|
202
|
+
`bwx status` reports `session: valid | stale | none` separately from the vault state.
|
|
203
|
+
**A `locked` vault with a `valid` session reads fine — do not run `bwx unlock` on it.**
|
|
204
|
+
Only `stale`/`none` mean the next read pays for an unlock.
|
|
205
|
+
|
|
116
206
|
## Global flags
|
|
117
207
|
|
|
118
208
|
| Flag | Effect |
|
|
@@ -137,9 +227,12 @@ even under `--json`; `-q` silences them.
|
|
|
137
227
|
|
|
138
228
|
- Never use `--full-items` (or `get item` for discovery) in logged sessions.
|
|
139
229
|
- Prefer `bwx run --env ...` over `export VAR="$(bwx get ...)"`; secrets never touch stdout or argv.
|
|
230
|
+
- Prefer `--password-generate` over generating a secret yourself and passing it in.
|
|
140
231
|
- Request the narrowest field, resolving an exact item ID first.
|
|
141
232
|
- Use `-q` for command substitution so unlock logs cannot contaminate merged output.
|
|
142
|
-
- On writes, prefer `--password-stdin`, `--password-env`, `--field-file`, `--field-env`.
|
|
233
|
+
- On writes, prefer `--password-stdin`, `--password-env`, `--totp-stdin`, `--totp-env`, `--field-file`, `--field-env`.
|
|
234
|
+
- Check `secondsRemaining` on a TOTP, or pass `--fresh`, before handing the code anywhere.
|
|
235
|
+
- Use `hasTotp` on a listing to find 2FA items; never `--full-items` for that.
|
|
143
236
|
- Never echo, log, checksum, or commit retrieved values; `unset` temporary variables.
|
|
144
237
|
|
|
145
238
|
## Auth
|
package/src/bw/encoding.ts
CHANGED
|
@@ -40,6 +40,7 @@ export interface NewItemOptions {
|
|
|
40
40
|
notes?: string | null;
|
|
41
41
|
username?: string | null;
|
|
42
42
|
password?: string | null;
|
|
43
|
+
totp?: string | null;
|
|
43
44
|
uris?: string[];
|
|
44
45
|
fields?: ParsedField[];
|
|
45
46
|
folderId?: string | null;
|
|
@@ -65,7 +66,7 @@ export function buildNewItem(options: NewItemOptions): Record<string, unknown> {
|
|
|
65
66
|
item.login = {
|
|
66
67
|
username: options.username ?? null,
|
|
67
68
|
password: options.password ?? null,
|
|
68
|
-
totp: null,
|
|
69
|
+
totp: options.totp ?? null,
|
|
69
70
|
uris: options.uris?.length
|
|
70
71
|
? options.uris.map((uri) => ({ match: null, uri }))
|
|
71
72
|
: null,
|
|
@@ -95,10 +96,13 @@ export interface PatchOptions {
|
|
|
95
96
|
notes?: string;
|
|
96
97
|
username?: string;
|
|
97
98
|
password?: string;
|
|
99
|
+
/** `null` clears the stored secret; `undefined` leaves it alone. */
|
|
100
|
+
totp?: string | null;
|
|
98
101
|
uris?: string[];
|
|
99
102
|
addFields?: ParsedField[];
|
|
100
103
|
rmFields?: string[];
|
|
101
|
-
|
|
104
|
+
/** `null` unfiles the item; `undefined` leaves its folder alone. */
|
|
105
|
+
folderId?: string | null;
|
|
102
106
|
favorite?: boolean;
|
|
103
107
|
}
|
|
104
108
|
|
|
@@ -114,10 +118,18 @@ export function patchItem(
|
|
|
114
118
|
if (patch.favorite !== undefined) result.favorite = patch.favorite;
|
|
115
119
|
|
|
116
120
|
// Login fields
|
|
117
|
-
if (
|
|
118
|
-
|
|
121
|
+
if (
|
|
122
|
+
patch.username !== undefined ||
|
|
123
|
+
patch.password !== undefined ||
|
|
124
|
+
patch.totp !== undefined ||
|
|
125
|
+
patch.uris !== undefined
|
|
126
|
+
) {
|
|
127
|
+
// Copied, not aliased: `{ ...item }` above is shallow, so assigning into
|
|
128
|
+
// the original `login` would edit the caller's item as a side effect.
|
|
129
|
+
const login = { ...((result.login as Record<string, unknown>) ?? {}) };
|
|
119
130
|
if (patch.username !== undefined) login.username = patch.username;
|
|
120
131
|
if (patch.password !== undefined) login.password = patch.password;
|
|
132
|
+
if (patch.totp !== undefined) login.totp = patch.totp;
|
|
121
133
|
if (patch.uris !== undefined) {
|
|
122
134
|
login.uris = patch.uris.map((uri) => ({ match: null, uri }));
|
|
123
135
|
}
|