bwx 0.4.0 → 0.5.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 +85 -4
- package/package.json +3 -2
- package/skill/bwx/SKILL.md +196 -0
- package/src/bw/encoding.ts +2 -1
- package/src/bw/fields.ts +246 -27
- package/src/bw/folders.ts +96 -0
- package/src/bw/generate.ts +91 -0
- package/src/bw/session.ts +36 -10
- package/src/cli/commands/config.ts +1 -10
- package/src/cli/commands/create.ts +51 -5
- package/src/cli/commands/delete.ts +48 -21
- package/src/cli/commands/edit.ts +53 -5
- package/src/cli/commands/folders.ts +34 -0
- package/src/cli/commands/generate.ts +57 -0
- package/src/cli/commands/get.ts +28 -4
- 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 +30 -1
- package/src/cli/io.ts +12 -2
- package/src/cli/items.ts +52 -8
- package/src/cli/program.ts +6 -0
package/README.md
CHANGED
|
@@ -54,20 +54,27 @@ 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 field <
|
|
73
|
+
| `bwx field <name> <item>` | Get custom field by name (same argument order as `get`) |
|
|
69
74
|
| `bwx search <query>` | Search items (`--type`, `--folder`, `--limit`, `--full-items`) |
|
|
70
|
-
| `bwx list [type]` | List items (`--folder`, `--limit`, `--full-items`) |
|
|
75
|
+
| `bwx list [type]` | List items (`--type`, `--folder`, `--limit`, `--full-items`) |
|
|
76
|
+
| `bwx folders` | List folder names and IDs |
|
|
77
|
+
| `bwx trash` | List items in the trash |
|
|
71
78
|
| `bwx run --env NAME=<field>:<item> -- <cmd>` | Run a command with secrets in its environment |
|
|
72
79
|
| `bwx attach list <item>` | List attachments for an item |
|
|
73
80
|
| `bwx attach get <item> <attachment>` | Download attachment by ID or filename |
|
|
@@ -77,6 +84,41 @@ human, `--plain`, and `--json` form, so asking for machine-readable output never
|
|
|
77
84
|
what is exposed. `--full-items` is the explicit opt-in to complete item objects, which
|
|
78
85
|
carry passwords, notes, and custom fields.
|
|
79
86
|
|
|
87
|
+
A lookup that finds nothing suggests what you probably meant, and one that matches
|
|
88
|
+
several items lists them, so neither ends in a guessing round:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
$ bwx get password "GitHub PAT vforsh old"
|
|
92
|
+
error: No item matching "GitHub PAT vforsh old". Did you mean:
|
|
93
|
+
|
|
94
|
+
934c1f57-… note GitHub PAT (vforsh)
|
|
95
|
+
3683fb9a-… note GitHub PAT (yaforsh) [codex-monitor-pr]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Listings are capped
|
|
99
|
+
|
|
100
|
+
Listings stop at 50 items by default — an uncapped `bwx list` returns the whole vault,
|
|
101
|
+
and with `--full-items` that is every secret in one call. `--limit 0` opts out. Truncation
|
|
102
|
+
is never silent: it warns on stderr (surviving `--json`) and adds `meta` to the JSON
|
|
103
|
+
envelope.
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
bwx list --limit 0 # everything
|
|
107
|
+
bwx list --json # { "data": [...], "meta": { "total": 491, "shown": 50, "truncated": true } }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Folders
|
|
111
|
+
|
|
112
|
+
`--folder` takes a name or an ID on `list`, `search`, `create`, and `edit`; `none` means
|
|
113
|
+
the unfiled bucket. Names are resolved against the vault, so `bwx folders` is only needed
|
|
114
|
+
to see what exists.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
bwx folders
|
|
118
|
+
bwx list --folder "API Keys"
|
|
119
|
+
bwx edit "Acme" --folder none # unfile
|
|
120
|
+
```
|
|
121
|
+
|
|
80
122
|
### Secrets into a child process
|
|
81
123
|
|
|
82
124
|
`bwx run` resolves secrets and places them only in the child's environment: nothing is
|
|
@@ -114,6 +156,34 @@ The check is cached in `~/.config/bwx/state.json`, so a fresh vault costs no ext
|
|
|
114
156
|
| `bwx create` | Create item (`--type`, `--name`, `--username`, `--password-stdin`, `--field-file k=path`) |
|
|
115
157
|
| `bwx edit <item>` | Edit item (`--name`, `--password-env`, `--add-field-file k=path`, `--rm-field name`) |
|
|
116
158
|
| `bwx delete <item>` | Delete item (`--permanent`, `--force`) |
|
|
159
|
+
| `bwx restore <item>` | Restore an item from the trash |
|
|
160
|
+
| `bwx generate` | Generate a password or passphrase (local, no vault access) |
|
|
161
|
+
|
|
162
|
+
Login-only flags (`--username`, any `--password-*`, `--uri`) require a login item. A
|
|
163
|
+
secure note has nowhere to put them, so passing them to one is an error rather than an
|
|
164
|
+
exit 0 over a credential that was silently dropped.
|
|
165
|
+
|
|
166
|
+
`bwx delete` requires `--force` whenever it is not talking to a terminal. The confirmation
|
|
167
|
+
prompt cannot render for a script or an agent, so without it `--force` would be decorative
|
|
168
|
+
and every non-interactive delete unguarded. Deletes go to the trash unless `--permanent`,
|
|
169
|
+
and `bwx trash` / `bwx restore` are the way back.
|
|
170
|
+
|
|
171
|
+
### Generated passwords
|
|
172
|
+
|
|
173
|
+
`--password-generate` on `create` and `edit` sends a new secret straight to the vault. It
|
|
174
|
+
is never printed, never echoed back in `--json`, and never reaches a terminal, a
|
|
175
|
+
transcript, or a log — which is what makes it safe to use from an agent.
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
bwx create --type login --name "Deploy" --username svc --password-generate
|
|
179
|
+
bwx edit "Deploy" --password-generate --generate-length 32
|
|
180
|
+
bwx generate --length 32 # print one instead
|
|
181
|
+
bwx generate --passphrase --words 5
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`bwx generate` runs locally and needs no unlocked vault. Flags: `--length <n>`,
|
|
185
|
+
`--no-uppercase`, `--no-lowercase`, `--no-numbers`, `-s/--special`, `--ambiguous`,
|
|
186
|
+
`--passphrase`, `--words <n>`, `--separator <char>`, `--capitalize`, `--include-number`.
|
|
117
187
|
|
|
118
188
|
### Config
|
|
119
189
|
|
|
@@ -156,7 +226,7 @@ Supported source flags:
|
|
|
156
226
|
|
|
157
227
|
| Flag | Effect |
|
|
158
228
|
|------|--------|
|
|
159
|
-
| `--json` | JSON output: `{ "data": ... }` / `{ "error": ... }` |
|
|
229
|
+
| `--json` | JSON output: `{ "data": ... }` / `{ "error": ... }`, plus `meta` where a response describes itself |
|
|
160
230
|
| `--plain` | Tab-separated, one item per line |
|
|
161
231
|
| `-q, --quiet` | Suppress logs and warnings |
|
|
162
232
|
| `-v, --verbose` | Include stack traces |
|
|
@@ -191,6 +261,17 @@ Field syntax for `--field`, `--field-file`, `--field-env`, and edit variants:
|
|
|
191
261
|
| 8 | Timeout |
|
|
192
262
|
| 9 | User cancelled |
|
|
193
263
|
|
|
264
|
+
## Upgrading to 0.5.0
|
|
265
|
+
|
|
266
|
+
Breaking changes, all of them cases where the old behavior failed quietly:
|
|
267
|
+
|
|
268
|
+
- `bwx field` takes `<name> <item>`, matching `bwx get`. It was `<item> <name>`.
|
|
269
|
+
- `bwx delete` needs `--force` when not interactive; it used to delete unprompted.
|
|
270
|
+
- `bwx list` and `bwx search` stop at 50 items unless given `--limit`. Pass `--limit 0`
|
|
271
|
+
for the old behavior.
|
|
272
|
+
- `--limit` rejects non-numeric values instead of ignoring them (and returning everything).
|
|
273
|
+
- Login-only flags on a secure note are an error instead of a silent drop.
|
|
274
|
+
|
|
194
275
|
## Stack
|
|
195
276
|
|
|
196
277
|
Bun, TypeScript, Commander, Zod, picocolors.
|
package/package.json
CHANGED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bwx
|
|
3
|
+
description: Bitwarden Extended CLI — retrieve, create, edit, delete vault items with auto-unlock. Use when the agent needs passwords, API keys, TOTP codes, custom fields, or vault management. Triggers on mentions of Bitwarden, bw, bwx, vault, password, secrets, TOTP, save key/token.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# bwx
|
|
7
|
+
|
|
8
|
+
Extended Bitwarden CLI. Wraps `bw` with auto-unlock, session caching, and structured output.
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
bwx get password "GitHub" # retrieve a password (auto-unlocks)
|
|
14
|
+
bwx get totp "AWS (user@example.com)" # TOTP code
|
|
15
|
+
bwx get username "GitHub" # username
|
|
16
|
+
bwx get notes "Deploy key" # secure note content
|
|
17
|
+
bwx get uri "GitHub" # first URI
|
|
18
|
+
bwx get item "GitHub" # full item JSON
|
|
19
|
+
bwx get "API Key" "Acme" # custom field by name
|
|
20
|
+
bwx field "API Key" "Acme" # custom-field-only lookup, same arg order
|
|
21
|
+
|
|
22
|
+
bwx search github # fuzzy search, human table
|
|
23
|
+
bwx search github --json # id/type/name/username only — no secrets
|
|
24
|
+
bwx search github --type login --limit 5
|
|
25
|
+
|
|
26
|
+
# hand a secret to a child process instead of printing it
|
|
27
|
+
bwx run --env KEENETIC_PASS=password:'http://my.keenetic.net' -- expect ./recovery.exp
|
|
28
|
+
|
|
29
|
+
# generate straight into the vault — the value never enters your context
|
|
30
|
+
bwx create --type login --name "Deploy" --username svc --password-generate
|
|
31
|
+
|
|
32
|
+
bwx attach list "Apple Developer" # list item attachments
|
|
33
|
+
bwx attach get "Apple Developer" AuthKey.p8
|
|
34
|
+
bwx attach get "Apple Developer" AuthKey.p8 --output ./secrets/
|
|
35
|
+
|
|
36
|
+
bwx create --name "API Key" --notes "sk_live_abc123" --field account=acme
|
|
37
|
+
printf '%s' "$TOKEN" | bwx create --type login --name "GitHub" --username user --password-stdin --uri https://github.com
|
|
38
|
+
bwx create --name "Acme" --field-file '!API Key=./token.txt' --field-env account=ACME_ACCOUNT
|
|
39
|
+
echo "secret" | bwx create --name "Piped Note"
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Commands
|
|
43
|
+
|
|
44
|
+
### `bwx get <field> <item>`
|
|
45
|
+
|
|
46
|
+
Built-in fields: `password | username | totp | notes | uri | item`.
|
|
47
|
+
Anything else is looked up as a custom field. Lists available fields on miss.
|
|
48
|
+
|
|
49
|
+
A miss suggests near matches; an ambiguous name lists candidates with IDs. Read the
|
|
50
|
+
suggestions instead of retrying variations — the answer is usually in them.
|
|
51
|
+
|
|
52
|
+
### `bwx field <name> <item>`
|
|
53
|
+
|
|
54
|
+
Custom fields only, so an item whose custom field is named `password` is still reachable.
|
|
55
|
+
Takes its arguments in the same order as `get` (this changed in 0.5.0).
|
|
56
|
+
|
|
57
|
+
### `bwx search <query>` / `bwx list [type]`
|
|
58
|
+
|
|
59
|
+
Flags: `--type login|note|card|identity`, `--folder <name|id>`, `--limit <n>`, `--full-items`
|
|
60
|
+
|
|
61
|
+
Human, `--plain`, and `--json` output all carry the same reduced projection — `id`, `type`,
|
|
62
|
+
`name`, `username` — so listings never leak passwords, notes, or custom fields. `--full-items`
|
|
63
|
+
opts into complete item objects and **does** include secrets; avoid it in logged sessions.
|
|
64
|
+
|
|
65
|
+
**Listings stop at 50 items** unless you pass `--limit` (`--limit 0` for all). Truncation is
|
|
66
|
+
reported on stderr and in `meta` on the JSON envelope — check it before concluding a listing
|
|
67
|
+
was complete. `--limit` rejects non-numeric values rather than ignoring them.
|
|
68
|
+
|
|
69
|
+
### `bwx folders`
|
|
70
|
+
|
|
71
|
+
Lists folder names and IDs. `--folder` accepts either on `list`, `search`, `create`, and
|
|
72
|
+
`edit`; `none` selects unfiled items.
|
|
73
|
+
|
|
74
|
+
### `bwx run --env NAME=<field>:<item> -- <command>`
|
|
75
|
+
|
|
76
|
+
Resolves secrets and injects them into the child's environment only — not stdout, not argv.
|
|
77
|
+
This is the preferred way to give a subprocess a credential.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
bwx run --env DB_PASS=password:'Prod DB' -- ./migrate.sh
|
|
81
|
+
bwx run --env TOKEN='API Key:Acme' --env USER=username:Acme -- ./deploy.sh
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- `--env` is repeatable; the field resolves as in `bwx get` (built-in, else custom field).
|
|
85
|
+
- Only the first `:` splits field from item, so `password:http://my.router` works.
|
|
86
|
+
- Put `--` before the command when it takes its own flags.
|
|
87
|
+
- Child stdio is inherited; bwx exits with the child's status (`128 + signal` if killed).
|
|
88
|
+
- `-v` logs injected variable names only, never values.
|
|
89
|
+
- Several variables from one item cost a single vault read, so group them here rather
|
|
90
|
+
than issuing separate `bwx get` calls.
|
|
91
|
+
|
|
92
|
+
### `bwx attach list <item>`
|
|
93
|
+
|
|
94
|
+
Lists attachments for an item. Aliases: `bwx attachment`, `bwx attachments`; `list` has `ls`.
|
|
95
|
+
|
|
96
|
+
### `bwx attach get <item> <attachment>`
|
|
97
|
+
|
|
98
|
+
Downloads an attachment by ID or filename. Defaults to saving as the attachment filename in the current directory. Use `--output <path>` to choose a file or directory.
|
|
99
|
+
|
|
100
|
+
### `bwx create [options]`
|
|
101
|
+
|
|
102
|
+
Flags: `--type login|note`, `--name`, `--notes`, `--notes-file`, `--username`, `--password`, `--password-stdin`, `--password-file`, `--password-env`, `--password-generate`, `--generate-length <n>`, `--uri` (repeatable), `--field k=v` (repeatable), `--field-file k=path`, `--field-env k=ENV`, `--folder <name|id>`, `--favorite`, `--from-json`
|
|
103
|
+
|
|
104
|
+
`--username`, any `--password-*`, and `--uri` require `--type login`; a note cannot hold
|
|
105
|
+
them and passing them to one is now an error rather than a silent drop.
|
|
106
|
+
|
|
107
|
+
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.
|
|
108
|
+
|
|
109
|
+
### `bwx edit <item> [options]`
|
|
110
|
+
|
|
111
|
+
Fetch → patch → push.
|
|
112
|
+
```bash
|
|
113
|
+
bwx edit "API Key" --add-field region=us-east-1
|
|
114
|
+
printf '%s' "$TOKEN" | bwx edit "GitHub" --password-stdin --uri https://github.com
|
|
115
|
+
bwx edit "Note" --rm-field old-key
|
|
116
|
+
```
|
|
117
|
+
Flags: `--name`, `--notes`, `--notes-file`, `--username`, `--password`, `--password-stdin`, `--password-file`, `--password-env`, `--password-generate`, `--generate-length <n>`, `--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`
|
|
118
|
+
|
|
119
|
+
### `bwx delete <item>`
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
bwx delete "Test Note" --force # --force is REQUIRED when not a TTY
|
|
123
|
+
bwx delete "Test Note" --permanent --force # skip the trash
|
|
124
|
+
bwx trash # what is recoverable
|
|
125
|
+
bwx restore "Test Note" # undo a non-permanent delete
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
The prompt only exists for a human at a terminal, so an agent must pass `--force`
|
|
129
|
+
deliberately. Deletes are recoverable through `bwx restore` unless `--permanent`.
|
|
130
|
+
|
|
131
|
+
### `bwx generate`
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
bwx generate --length 32
|
|
135
|
+
bwx generate --passphrase --words 5 --separator -
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Local, no vault access, no unlock. Flags: `--length <n>`, `--no-uppercase`,
|
|
139
|
+
`--no-lowercase`, `--no-numbers`, `-s/--special`, `--ambiguous`, `--passphrase`,
|
|
140
|
+
`--words <n>`, `--separator <char>`, `--capitalize`, `--include-number`.
|
|
141
|
+
|
|
142
|
+
Prefer `--password-generate` on `create`/`edit` over generating and passing the value:
|
|
143
|
+
it goes to the vault without ever entering your context, and `--json` returns
|
|
144
|
+
`***GENERATED***` in place of the password.
|
|
145
|
+
|
|
146
|
+
### Other
|
|
147
|
+
|
|
148
|
+
| Command | Description |
|
|
149
|
+
|---------|-------------|
|
|
150
|
+
| `bwx status` | Vault state + session usability (no auto-unlock) |
|
|
151
|
+
| `bwx sync` | Force vault sync |
|
|
152
|
+
| `bwx unlock` / `bwx lock` | Manual session control |
|
|
153
|
+
| `bwx folders` | Folder names and IDs |
|
|
154
|
+
| `bwx trash` / `bwx restore <item>` | Inspect and undo deletes |
|
|
155
|
+
| `bwx config list` / `bwx cfg ls` | Show config path + redacted content |
|
|
156
|
+
| `bwx config master-password` | Set/update master password in Keychain |
|
|
157
|
+
|
|
158
|
+
`bwx status` reports `session: valid | stale | none` separately from the vault state.
|
|
159
|
+
**A `locked` vault with a `valid` session reads fine — do not run `bwx unlock` on it.**
|
|
160
|
+
Only `stale`/`none` mean the next read pays for an unlock.
|
|
161
|
+
|
|
162
|
+
## Global flags
|
|
163
|
+
|
|
164
|
+
| Flag | Effect |
|
|
165
|
+
|------|--------|
|
|
166
|
+
| `--json` | `{ "data": ... }` on success, `{ "error": { "message", "exitCode" } }` on failure |
|
|
167
|
+
| `--plain` | Stable parseable lines on stdout |
|
|
168
|
+
| `-q` | Suppress logs and warnings |
|
|
169
|
+
| `-v` | Verbose (stack traces on error) |
|
|
170
|
+
| `--timeout <duration>` | Deadline per `bw` call (default `1m`, `0` disables) → exit 8 |
|
|
171
|
+
| `--sync-if-older-than <duration>` | Sync first when the vault last synced longer ago |
|
|
172
|
+
|
|
173
|
+
Durations: `250ms`, `30s`, `15m`, `2h`, `1d`; a bare number is seconds.
|
|
174
|
+
|
|
175
|
+
## Vault freshness
|
|
176
|
+
|
|
177
|
+
Reads come from the local vault copy. Reads warn on stderr when the last sync is over 24h
|
|
178
|
+
old — a rotated secret can otherwise look valid or missing. Pass
|
|
179
|
+
`--sync-if-older-than 15m` to sync first instead, or run `bwx sync`. Warnings reach stderr
|
|
180
|
+
even under `--json`; `-q` silences them.
|
|
181
|
+
|
|
182
|
+
## Safety rules
|
|
183
|
+
|
|
184
|
+
- Never use `--full-items` (or `get item` for discovery) in logged sessions.
|
|
185
|
+
- Prefer `bwx run --env ...` over `export VAR="$(bwx get ...)"`; secrets never touch stdout or argv.
|
|
186
|
+
- Prefer `--password-generate` over generating a secret yourself and passing it in.
|
|
187
|
+
- Request the narrowest field, resolving an exact item ID first.
|
|
188
|
+
- Use `-q` for command substitution so unlock logs cannot contaminate merged output.
|
|
189
|
+
- On writes, prefer `--password-stdin`, `--password-env`, `--field-file`, `--field-env`.
|
|
190
|
+
- Never echo, log, checksum, or commit retrieved values; `unset` temporary variables.
|
|
191
|
+
|
|
192
|
+
## Auth
|
|
193
|
+
|
|
194
|
+
- Master password: macOS Keychain (account `bitwarden`, service `bitwarden-master`)
|
|
195
|
+
- Session cached to `~/.config/bwx/session` (mode 600), given to `bw` via `BW_SESSION`
|
|
196
|
+
- Freshness snapshot cached to `~/.config/bwx/state.json`
|
package/src/bw/encoding.ts
CHANGED
|
@@ -98,7 +98,8 @@ export interface PatchOptions {
|
|
|
98
98
|
uris?: string[];
|
|
99
99
|
addFields?: ParsedField[];
|
|
100
100
|
rmFields?: string[];
|
|
101
|
-
|
|
101
|
+
/** `null` unfiles the item; `undefined` leaves its folder alone. */
|
|
102
|
+
folderId?: string | null;
|
|
102
103
|
favorite?: boolean;
|
|
103
104
|
}
|
|
104
105
|
|