@carllee1983/dbcli 1.56.0 → 1.58.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carllee1983/dbcli",
3
- "version": "1.56.0",
3
+ "version": "1.58.0",
4
4
  "description": "Database CLI for AI agents",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -74,12 +74,14 @@
74
74
  "test:unit": "bun test tests/unit tests/core",
75
75
  "test:integration": "bun test tests/integration",
76
76
  "test:gherkin": "bun test tests/gherkin",
77
- "test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration/adapters; docker compose -f docker-compose.test.yml down",
77
+ "test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration; docker compose -f docker-compose.test.yml down",
78
+ "services:check": "bun run scripts/check-test-services.ts",
78
79
  "docs:check": "bun run scripts/check-user-docs.ts",
79
80
  "contract:check": "bun run scripts/check-cli-contract.ts",
80
81
  "skill:check": "bun run scripts/check-skill-parity.ts",
81
82
  "platform:check": "bun run scripts/check-platform-parity.ts",
82
83
  "agent-core:check": "bun run scripts/check-agent-core-purity.ts",
84
+ "core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
83
85
  "typecheck": "tsc --noEmit --pretty false",
84
86
  "test:perf": "bun test ./tests/perf/*.bench.ts",
85
87
  "lint": "eslint src tests scripts --ext .ts --max-warnings=0",
@@ -310,6 +310,19 @@ dbcli init --rename staging:stg # rename
310
310
  dbcli init --remove stg # remove
311
311
  ```
312
312
 
313
+ Rotating one connection's password — nothing else in the config moves:
314
+
315
+ ```bash
316
+ dbcli password prod # masked prompt
317
+ rotate-secret | dbcli password prod --stdin # for scheduled rotation scripts
318
+ ```
319
+
320
+ The value goes to the env var the config actually references (a literal password
321
+ is converted to `{ "$env": ... }` on first use, and a connection with no
322
+ `envFile` gets one recorded so the reader loads it), is verified by connecting
323
+ before it is saved (`--skip-test` to opt out), and the env file is written
324
+ `0600` on POSIX.
325
+
313
326
  For a connection shared across projects, use the explicit root-level `--global` scope. It stores a v2 registry at `~/.config/dbcli/config.json`; it does not create or modify a project binding:
314
327
 
315
328
  ```bash
@@ -436,8 +449,13 @@ changing the default. `--recovery` is honoured by `query`, `q`, `insert`, `updat
436
449
  (no `>=`, `!=`, `LIKE`, `OR`). MongoDB `--where` takes a full JSON filter
437
450
  (`'{"status":"pending"}'`), falling back to `col=val` when it is not valid JSON.
438
451
  - `--dry-run` prints the parameterized SQL (with `$1` / `?` placeholders, not real values)
439
- and `rows_affected: 0`; proceed once `status:"success"` and the SQL shape matches the
440
- intended `--where` / `--set`. MongoDB prints a shell-style preview.
452
+ and `status:"dry_run"` with `rows_affected: 0` never `success`, which now means the
453
+ write really ran. Proceed once the SQL shape matches the intended `--where` / `--set`.
454
+ Declining at the confirmation prompt reports `status:"cancelled"`, also not `success`.
455
+ MongoDB prints a shell-style preview.
456
+ - `--force` skips the confirmation prompt. Every `insert` / `update` / `delete` asks
457
+ first — SQL, MongoDB and Redis alike — and a non-interactive run cannot answer, so an
458
+ unattended write without `--force` ends as `status:"cancelled"` having changed nothing.
441
459
  - `--recovery` is recommended for automated agent pipelines (enables `dbcli recover --apply`
442
460
  after a failure); optional for one-off manual writes.
443
461
 
@@ -22,6 +22,7 @@ never the right move.
22
22
  **Commands** —
23
23
  [init](#init) ·
24
24
  [use](#use) ·
25
+ [password](#password) ·
25
26
  [list](#list) ·
26
27
  [schema](#schema) ·
27
28
  [query](#query) ·
@@ -196,6 +197,46 @@ dbcli list --use prod
196
197
 
197
198
  **Options:** `--list`, `--format <text|json>`, `--confirm-production <name>` (required when explicitly selecting a production connection as the default).
198
199
 
200
+ ### password
201
+
202
+ Change one connection's password without touching any other setting — built for
203
+ environments where credentials rotate on a schedule.
204
+
205
+ ```bash
206
+ dbcli password # Masked prompt, rotates the default connection
207
+ dbcli password prod # Masked prompt, rotates 'prod'
208
+ rotate-secret | dbcli password prod --stdin # Non-interactive, nothing lands in shell history
209
+ dbcli password prod --password "$NEW" --skip-test --format json
210
+ ```
211
+
212
+ Where the value lands is read from the config, never guessed: a connection whose
213
+ `password` is `{ "$env": "NAME" }` gets `NAME` rewritten in its `envFile`. A
214
+ connection that declares no `envFile` has one recorded (`.env.local`) as part of
215
+ the rotation — without it the reader would never load the file. A connection
216
+ still holding a literal password is converted to
217
+ `{ "$env": "DBCLI_<CONN>_PASSWORD" }` once, so later rotations only touch the env
218
+ file. Values are written quoted (`NAME="..."`), so leading and trailing
219
+ whitespace survives the round trip.
220
+
221
+ v1 configs rewrite `DBCLI_PASSWORD` in `.env.local`, matching the v1 reader. A v1
222
+ config whose password comes from some other environment variable is refused with
223
+ an explanation: v1 has no per-connection env file, so no file dbcli writes could
224
+ make that variable resolve — set it in the environment, or migrate to v2.
225
+
226
+ The new password is verified by connecting with it before anything is written,
227
+ so a bad rotation fails without leaving broken credentials behind. Pass
228
+ `--skip-test` when the database is unreachable from where the command runs. The
229
+ env file is written with `0600` permissions on POSIX systems (Windows has no
230
+ equivalent mode bit — the file inherits the directory's ACL), and the value is
231
+ never echoed or
232
+ logged.
233
+
234
+ **Options:** `[connection]`, `--stdin`, `--password <value>` (visible in shell
235
+ history and the process list — prefer `--stdin`), `--skip-test`,
236
+ `--format <text|json>`.
237
+
238
+ Blocked under `DBCLI_AGENT_MODE=1` like every other credential mutation.
239
+
199
240
  ### Agent configuration trust boundary
200
241
 
201
242
  When `DBCLI_AGENT_MODE=1`, configuration, permission, and credential mutations
@@ -296,6 +337,13 @@ dbcli query "SELECT * FROM orders" --format html > orders.html # pipe to stdou
296
337
  **Options:** `--format <table|json|csv|html>`, `--ui` (open the dashboard in the system browser; implies `--format html`), `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`), `--fields <list>`, `--truncate <number>` / `--no-truncate`, `-f, --query-file <path>`, `--use <name[,name]>`, `--slow-ms <number>`, `--recovery`
297
338
  **Permission:** query-only+ (Redis: per-command; Elasticsearch: per HTTP method/path)
298
339
 
340
+ > **Elasticsearch tiers by scope.** A read (`GET` / `HEAD`, plus `POST _search` and
341
+ > `POST _count`) is query-only. Indexing or updating a document is read-write.
342
+ > `DELETE /<index>/_doc/<id>` — one document — is data-admin. Everything that removes or
343
+ > reshapes a container is **admin**: `DELETE /<index>`, a wildcard or `_all` delete,
344
+ > templates and aliases, and `PUT` against `_mapping` or `_settings`. A request whose
345
+ > scope cannot be established is treated as admin rather than guessed downward.
346
+
299
347
  > **Server-side scripts are rejected on every path.** MongoDB `$where`,
300
348
  > `$function`, and `$accumulator`, and Elasticsearch `script` / `script_fields`,
301
349
  > execute code on the database server. The adapters reject them anywhere in a
@@ -884,7 +932,7 @@ dbcli insert users --data '{"name":"Alice"}' --force
884
932
  dbcli insert users --data '{"name":"Alice"}' --plan --format json # risk analysis only; no DB connection
885
933
  ```
886
934
 
887
- **Options:** `--data <json>`, `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
935
+ **Options:** `--data <json>`, `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output; `json` also keeps the result envelope instead of prose in a terminal), `--recovery`
888
936
  **Permission:** read-write+
889
937
 
890
938
  ### update
@@ -897,7 +945,7 @@ dbcli update users --where "id=1" --set '{"name":"Bob"}' --dry-run
897
945
  dbcli update users --where "id=1" --set '{"name":"Bob"}' --plan --format json # risk analysis only; no DB connection
898
946
  ```
899
947
 
900
- **Options:** `--where <condition>` (required), `--set <json>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
948
+ **Options:** `--where <condition>` (required), `--set <json>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output; `json` also keeps the result envelope instead of prose in a terminal), `--recovery`
901
949
  **Permission:** read-write+
902
950
 
903
951
  > **`--where` grammar (SQL `update` / `delete`)** — equality only: `col=val` or
@@ -918,7 +966,7 @@ dbcli delete users --where "id=1" --force
918
966
  dbcli delete users --where "id=1" --plan --format json # risk analysis only; no DB connection
919
967
  ```
920
968
 
921
- **Options:** `--where <condition>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
969
+ **Options:** `--where <condition>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output; `json` also keeps the result envelope instead of prose in a terminal), `--recovery`
922
970
  **Permission:** data-admin+
923
971
 
924
972
  ### export
@@ -2499,6 +2547,8 @@ dbcli migrate add-enum status active inactive suspended
2499
2547
  dbcli migrate alter-enum status --add-value archived
2500
2548
  dbcli migrate drop-enum status --execute --force
2501
2549
  ```
2550
+ A destructive `migrate` action (`drop`, `drop-column`, `drop-index`, `drop-enum`) asks for confirmation on stderr before it runs, and reports `status: "cancelled"` if you decline — not `success`, which it used to claim with the cancellation buried in `warnings`. `--force` skips the question; a non-interactive run that omits it cannot answer and therefore cancels.
2551
+
2502
2552
 
2503
2553
  **Column spec format:** `name:type[:modifier[:modifier...]]`
2504
2554
  - Modifiers: `pk`, `not-null`, `unique`, `auto-increment`, `default=<value>`, `references=<table>.<column>`
@@ -310,6 +310,19 @@ dbcli init --rename staging:stg # rename
310
310
  dbcli init --remove stg # remove
311
311
  ```
312
312
 
313
+ Rotating one connection's password — nothing else in the config moves:
314
+
315
+ ```bash
316
+ dbcli password prod # masked prompt
317
+ rotate-secret | dbcli password prod --stdin # for scheduled rotation scripts
318
+ ```
319
+
320
+ The value goes to the env var the config actually references (a literal password
321
+ is converted to `{ "$env": ... }` on first use, and a connection with no
322
+ `envFile` gets one recorded so the reader loads it), is verified by connecting
323
+ before it is saved (`--skip-test` to opt out), and the env file is written
324
+ `0600` on POSIX.
325
+
313
326
  For a connection shared across projects, use the explicit root-level `--global` scope. It stores a v2 registry at `~/.config/dbcli/config.json`; it does not create or modify a project binding:
314
327
 
315
328
  ```bash
@@ -436,8 +449,13 @@ changing the default. `--recovery` is honoured by `query`, `q`, `insert`, `updat
436
449
  (no `>=`, `!=`, `LIKE`, `OR`). MongoDB `--where` takes a full JSON filter
437
450
  (`'{"status":"pending"}'`), falling back to `col=val` when it is not valid JSON.
438
451
  - `--dry-run` prints the parameterized SQL (with `$1` / `?` placeholders, not real values)
439
- and `rows_affected: 0`; proceed once `status:"success"` and the SQL shape matches the
440
- intended `--where` / `--set`. MongoDB prints a shell-style preview.
452
+ and `status:"dry_run"` with `rows_affected: 0` never `success`, which now means the
453
+ write really ran. Proceed once the SQL shape matches the intended `--where` / `--set`.
454
+ Declining at the confirmation prompt reports `status:"cancelled"`, also not `success`.
455
+ MongoDB prints a shell-style preview.
456
+ - `--force` skips the confirmation prompt. Every `insert` / `update` / `delete` asks
457
+ first — SQL, MongoDB and Redis alike — and a non-interactive run cannot answer, so an
458
+ unattended write without `--force` ends as `status:"cancelled"` having changed nothing.
441
459
  - `--recovery` is recommended for automated agent pipelines (enables `dbcli recover --apply`
442
460
  after a failure); optional for one-off manual writes.
443
461
 
@@ -22,6 +22,7 @@ never the right move.
22
22
  **Commands** —
23
23
  [init](#init) ·
24
24
  [use](#use) ·
25
+ [password](#password) ·
25
26
  [list](#list) ·
26
27
  [schema](#schema) ·
27
28
  [query](#query) ·
@@ -196,6 +197,46 @@ dbcli list --use prod
196
197
 
197
198
  **Options:** `--list`, `--format <text|json>`, `--confirm-production <name>` (required when explicitly selecting a production connection as the default).
198
199
 
200
+ ### password
201
+
202
+ Change one connection's password without touching any other setting — built for
203
+ environments where credentials rotate on a schedule.
204
+
205
+ ```bash
206
+ dbcli password # Masked prompt, rotates the default connection
207
+ dbcli password prod # Masked prompt, rotates 'prod'
208
+ rotate-secret | dbcli password prod --stdin # Non-interactive, nothing lands in shell history
209
+ dbcli password prod --password "$NEW" --skip-test --format json
210
+ ```
211
+
212
+ Where the value lands is read from the config, never guessed: a connection whose
213
+ `password` is `{ "$env": "NAME" }` gets `NAME` rewritten in its `envFile`. A
214
+ connection that declares no `envFile` has one recorded (`.env.local`) as part of
215
+ the rotation — without it the reader would never load the file. A connection
216
+ still holding a literal password is converted to
217
+ `{ "$env": "DBCLI_<CONN>_PASSWORD" }` once, so later rotations only touch the env
218
+ file. Values are written quoted (`NAME="..."`), so leading and trailing
219
+ whitespace survives the round trip.
220
+
221
+ v1 configs rewrite `DBCLI_PASSWORD` in `.env.local`, matching the v1 reader. A v1
222
+ config whose password comes from some other environment variable is refused with
223
+ an explanation: v1 has no per-connection env file, so no file dbcli writes could
224
+ make that variable resolve — set it in the environment, or migrate to v2.
225
+
226
+ The new password is verified by connecting with it before anything is written,
227
+ so a bad rotation fails without leaving broken credentials behind. Pass
228
+ `--skip-test` when the database is unreachable from where the command runs. The
229
+ env file is written with `0600` permissions on POSIX systems (Windows has no
230
+ equivalent mode bit — the file inherits the directory's ACL), and the value is
231
+ never echoed or
232
+ logged.
233
+
234
+ **Options:** `[connection]`, `--stdin`, `--password <value>` (visible in shell
235
+ history and the process list — prefer `--stdin`), `--skip-test`,
236
+ `--format <text|json>`.
237
+
238
+ Blocked under `DBCLI_AGENT_MODE=1` like every other credential mutation.
239
+
199
240
  ### Agent configuration trust boundary
200
241
 
201
242
  When `DBCLI_AGENT_MODE=1`, configuration, permission, and credential mutations
@@ -296,6 +337,13 @@ dbcli query "SELECT * FROM orders" --format html > orders.html # pipe to stdou
296
337
  **Options:** `--format <table|json|csv|html>`, `--ui` (open the dashboard in the system browser; implies `--format html`), `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`), `--fields <list>`, `--truncate <number>` / `--no-truncate`, `-f, --query-file <path>`, `--use <name[,name]>`, `--slow-ms <number>`, `--recovery`
297
338
  **Permission:** query-only+ (Redis: per-command; Elasticsearch: per HTTP method/path)
298
339
 
340
+ > **Elasticsearch tiers by scope.** A read (`GET` / `HEAD`, plus `POST _search` and
341
+ > `POST _count`) is query-only. Indexing or updating a document is read-write.
342
+ > `DELETE /<index>/_doc/<id>` — one document — is data-admin. Everything that removes or
343
+ > reshapes a container is **admin**: `DELETE /<index>`, a wildcard or `_all` delete,
344
+ > templates and aliases, and `PUT` against `_mapping` or `_settings`. A request whose
345
+ > scope cannot be established is treated as admin rather than guessed downward.
346
+
299
347
  > **Server-side scripts are rejected on every path.** MongoDB `$where`,
300
348
  > `$function`, and `$accumulator`, and Elasticsearch `script` / `script_fields`,
301
349
  > execute code on the database server. The adapters reject them anywhere in a
@@ -884,7 +932,7 @@ dbcli insert users --data '{"name":"Alice"}' --force
884
932
  dbcli insert users --data '{"name":"Alice"}' --plan --format json # risk analysis only; no DB connection
885
933
  ```
886
934
 
887
- **Options:** `--data <json>`, `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
935
+ **Options:** `--data <json>`, `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output; `json` also keeps the result envelope instead of prose in a terminal), `--recovery`
888
936
  **Permission:** read-write+
889
937
 
890
938
  ### update
@@ -897,7 +945,7 @@ dbcli update users --where "id=1" --set '{"name":"Bob"}' --dry-run
897
945
  dbcli update users --where "id=1" --set '{"name":"Bob"}' --plan --format json # risk analysis only; no DB connection
898
946
  ```
899
947
 
900
- **Options:** `--where <condition>` (required), `--set <json>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
948
+ **Options:** `--where <condition>` (required), `--set <json>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output; `json` also keeps the result envelope instead of prose in a terminal), `--recovery`
901
949
  **Permission:** read-write+
902
950
 
903
951
  > **`--where` grammar (SQL `update` / `delete`)** — equality only: `col=val` or
@@ -918,7 +966,7 @@ dbcli delete users --where "id=1" --force
918
966
  dbcli delete users --where "id=1" --plan --format json # risk analysis only; no DB connection
919
967
  ```
920
968
 
921
- **Options:** `--where <condition>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output), `--recovery`
969
+ **Options:** `--where <condition>` (required), `--dry-run`, `--force`, `--plan` (analyze risk without connecting or executing), `--format <text|json>` (`--plan` output; `json` also keeps the result envelope instead of prose in a terminal), `--recovery`
922
970
  **Permission:** data-admin+
923
971
 
924
972
  ### export
@@ -2499,6 +2547,8 @@ dbcli migrate add-enum status active inactive suspended
2499
2547
  dbcli migrate alter-enum status --add-value archived
2500
2548
  dbcli migrate drop-enum status --execute --force
2501
2549
  ```
2550
+ A destructive `migrate` action (`drop`, `drop-column`, `drop-index`, `drop-enum`) asks for confirmation on stderr before it runs, and reports `status: "cancelled"` if you decline — not `success`, which it used to claim with the cancellation buried in `warnings`. `--force` skips the question; a non-interactive run that omits it cannot answer and therefore cancels.
2551
+
2502
2552
 
2503
2553
  **Column spec format:** `name:type[:modifier[:modifier...]]`
2504
2554
  - Modifiers: `pk`, `not-null`, `unique`, `auto-increment`, `default=<value>`, `references=<table>.<column>`