@carllee1983/dbcli 1.20.1 → 1.23.1
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/CHANGELOG.md +104 -0
- package/README.md +11 -6
- package/README.zh-TW.md +7 -4
- package/assets/SKILL.md +35 -12
- package/assets/SKILL.zh-TW.md +19 -9
- package/assets/reference.md +531 -15
- package/assets/snippets/diag/active-users.mongodb.sql +14 -0
- package/assets/snippets/diag/top-orders-by-city.mongodb.sql +20 -0
- package/assets/tasks/analyze-table-perf.md +35 -0
- package/dist/cli.mjs +55324 -2077
- package/package.json +11 -7
package/assets/reference.md
CHANGED
|
@@ -100,13 +100,14 @@ dbcli schema --use staging # Scan staging DB; saves to .dbcli/schemas/s
|
|
|
100
100
|
dbcli schema --use prod # Scan prod DB; saves to .dbcli/schemas/prod/
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
**Options:** `--format <table|json>`, `--refresh`, `--reset`, `--force`, `--use <connection>`
|
|
103
|
+
**Options:** `--format <table|json>`, `--refresh`, `--reset`, `--force`, `--use <connection>`, `--sample-size <n>` (mongo only), `--sample-method <random|natural>` (mongo only)
|
|
104
104
|
**Permission:** query-only+
|
|
105
105
|
|
|
106
106
|
**Schema storage (v1.4+):** Schema is persisted as layered files under `.dbcli/schemas/`. With v2 multi-connection config each connection gets its own subdirectory (`.dbcli/schemas/<connection>/`). Run `dbcli schema --use <connection>` once per connection before querying it — otherwise `schema <table>` may return data from the wrong connection's cache.
|
|
107
107
|
|
|
108
108
|
> **Redis:** `schema <key>` is required (no full scan). The output exposes `type`, `ttl`, `size`, and a small `sample` (e.g. first 5 hash keys). `--reset` / `--refresh` are rejected — Redis caches no schema.
|
|
109
109
|
> **Elasticsearch:** `schema [index]` flattens the `_mapping` properties (nested `a.b.c`) and emits each `.fields` multi-field as a separate column (e.g. `text` + `text.keyword`). Full scan iterates all non-system indices and stores per-connection caches alongside SQL engines.
|
|
110
|
+
> **MongoDB:** schema is sampled via `$sample` (default 100, max 1000). `--sample-method natural` switches to `find().limit()`; `random` (default) falls back to natural order on driver error. Output columns surface nested dot-paths with `presence` (0..1) and `redacted: true` flags for blacklist-matched paths. The persisted cache records `sampleMethod` and `sampleSize`; `dbcli doctor` reports them via a `sampled: method=…, size=…` line.
|
|
110
111
|
|
|
111
112
|
### query
|
|
112
113
|
|
|
@@ -160,6 +161,40 @@ dbcli query "SELECT * FROM orders" --format html > orders.html # pipe to stdou
|
|
|
160
161
|
> - Hits are flattened: each result row contains `_id` plus dotted-path fields from `_source`. Pass `--format json` to keep nested structures readable.
|
|
161
162
|
> - Query-only mode caps at 1000 hits; `--no-limit` is internally capped at 10 000 (use saved searches / `search_after` for deeper pagination).
|
|
162
163
|
|
|
164
|
+
### explain
|
|
165
|
+
|
|
166
|
+
**(v1.23)** Read-only query-plan inspection across MySQL/MariaDB and PostgreSQL,
|
|
167
|
+
wrapping `EXPLAIN` / `EXPLAIN ANALYZE` / MariaDB `ANALYZE SELECT` behind one
|
|
168
|
+
interface. Output is a unified `ExplainRow` schema plus severity-coded
|
|
169
|
+
annotations. SQL `SELECT` only.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
dbcli explain "SELECT * FROM betting_logs WHERE settled_at >= '2026-03-01'"
|
|
173
|
+
dbcli explain @analytics/live-summary # saved query
|
|
174
|
+
dbcli explain @file.sql # @file reference
|
|
175
|
+
dbcli explain --analyze "SELECT ..." # MariaDB ANALYZE SELECT / PG EXPLAIN ANALYZE
|
|
176
|
+
dbcli explain --format json "..." # markdown (default) | json | table
|
|
177
|
+
dbcli explain --bulk @queries.sql # batch from file
|
|
178
|
+
dbcli explain --bulk @analytics/* # glob over saved queries
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Options:** `--analyze` (run the query for real — EXPLAIN ANALYZE / ANALYZE SELECT), `--format <markdown|json|table>` (default `markdown`), `--bulk <input>` (comma-separated `@file` / `@glob` / `@saved-query`).
|
|
182
|
+
**Permission:** query-only+ (no upgrade required).
|
|
183
|
+
|
|
184
|
+
**Annotations:**
|
|
185
|
+
|
|
186
|
+
| Rule | Severity | Triggered when |
|
|
187
|
+
|---|---|---|
|
|
188
|
+
| `full-scan` | red | MySQL `type=ALL` or `key=NULL`; PG `Seq Scan` |
|
|
189
|
+
| `temp-table` | yellow | MySQL `Using temporary` |
|
|
190
|
+
| `filesort` | yellow | MySQL `Using filesort`; PG `Sort Method: external merge` |
|
|
191
|
+
| `cost-estimate-skew` | gray | `--analyze` actual rows / planner rows > 10× |
|
|
192
|
+
| `nested-loop-large` | yellow | PG `Nested Loop` with planner rows > 10,000 |
|
|
193
|
+
|
|
194
|
+
> Notes:
|
|
195
|
+
> - `--analyze` executes the statement — do not use against destructive SQL.
|
|
196
|
+
> - Auto-`LIMIT` is **not** applied to EXPLAIN statements (since v1.23 P1).
|
|
197
|
+
|
|
163
198
|
### plan
|
|
164
199
|
|
|
165
200
|
Static SQL risk analyzer. Classifies a statement into the same permission tiers
|
|
@@ -328,6 +363,57 @@ Substitution rules: pure raw text — `:name` becomes the value's `String()` for
|
|
|
328
363
|
|
|
329
364
|
Size guard: `LRANGE` / `ZRANGE` stop overridden when `< 0` or `> 1000`; `SCAN` / `HSCAN` / `SSCAN` / `ZSCAN` get `COUNT 1000` injected if absent. `--no-limit` disables.
|
|
330
365
|
|
|
366
|
+
##### MongoDB snippets
|
|
367
|
+
|
|
368
|
+
File extension: `.mongodb.sql`. Frontmatter must declare `engine: mongodb` and
|
|
369
|
+
`operation: find` or `operation: aggregate`. `target: <collection>` provides a default
|
|
370
|
+
collection that `dbcli q --collection <name>` can override. The body is JSON: an object
|
|
371
|
+
for `find` and an array for `aggregate`. Each `{{param}}` placeholder is JSON-encoded
|
|
372
|
+
at substitution time — strings are quoted and escaped, so an attacker-supplied string
|
|
373
|
+
cannot escape into operator position.
|
|
374
|
+
|
|
375
|
+
Find example (`active-users.mongodb.sql`):
|
|
376
|
+
|
|
377
|
+
-- ---
|
|
378
|
+
-- name: active-users
|
|
379
|
+
-- engine: mongodb
|
|
380
|
+
-- operation: find
|
|
381
|
+
-- target: users
|
|
382
|
+
-- description: Active users matching the given status
|
|
383
|
+
-- params:
|
|
384
|
+
-- status:
|
|
385
|
+
-- type: string
|
|
386
|
+
-- required: true
|
|
387
|
+
-- ---
|
|
388
|
+
{
|
|
389
|
+
"status": {{status}}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
Aggregate example (`top-orders-by-city.mongodb.sql`):
|
|
393
|
+
|
|
394
|
+
-- ---
|
|
395
|
+
-- name: top-orders-by-city
|
|
396
|
+
-- engine: mongodb
|
|
397
|
+
-- operation: aggregate
|
|
398
|
+
-- target: orders
|
|
399
|
+
-- description: Top order counts per city for a given status
|
|
400
|
+
-- params:
|
|
401
|
+
-- status:
|
|
402
|
+
-- type: string
|
|
403
|
+
-- required: true
|
|
404
|
+
-- limit:
|
|
405
|
+
-- type: int
|
|
406
|
+
-- default: 10
|
|
407
|
+
-- ---
|
|
408
|
+
[
|
|
409
|
+
{ "$match": { "status": {{status}} } },
|
|
410
|
+
{ "$group": { "_id": "$city", "n": { "$sum": 1 } } },
|
|
411
|
+
{ "$sort": { "n": -1 } },
|
|
412
|
+
{ "$limit": {{limit}} }
|
|
413
|
+
]
|
|
414
|
+
|
|
415
|
+
Run with `dbcli q @active-users -p status=active` or `dbcli q @top-orders-by-city -p status=open -p limit=5`. The `q` command applies the same nested-blacklist redaction to results that `query` and `export` do.
|
|
416
|
+
|
|
331
417
|
### queries
|
|
332
418
|
|
|
333
419
|
Manage saved snippets — discover, inspect, scaffold, and edit local copies. Mutating
|
|
@@ -428,13 +514,20 @@ dbcli export "SELECT * FROM users" --format csv --output users.csv --force # Sk
|
|
|
428
514
|
dbcli export "SELECT * FROM users" --format json | jq '.[]'
|
|
429
515
|
dbcli export "SELECT * FROM users" --format jsonl --output users.ndjson
|
|
430
516
|
dbcli export "SELECT * FROM orders" --format html --output orders.html # standalone dashboard
|
|
517
|
+
|
|
518
|
+
# Elasticsearch (v1.22)
|
|
519
|
+
dbcli export '{"query":{"match":{"status":"active"}}}' --index orders --format jsonl --output orders.ndjson
|
|
520
|
+
dbcli export orders --format csv --output orders.csv # index name as query → match_all + scroll
|
|
521
|
+
dbcli export orders --no-limit --format jsonl # scroll the whole index in batches
|
|
431
522
|
```
|
|
432
523
|
|
|
433
|
-
**Options:** `--format <json|jsonl|csv|html>` (required), `--output <path>`, `--force`, `--recovery`
|
|
434
|
-
**Permission:** query-only+
|
|
524
|
+
**Options:** `--format <json|jsonl|csv|html>` (required), `--output <path>`, `--force`, `--recovery`, `--index <name>` (Elasticsearch), `--no-limit` (Elasticsearch full-index scroll)
|
|
525
|
+
**Permission:** query-only+ — SQL, MongoDB, and **(v1.22)** Elasticsearch.
|
|
435
526
|
|
|
436
527
|
The `html` format emits the same self-contained dashboard as `query --ui` (see [Interactive HTML dashboard](#interactive-html-dashboard)). Because `export` runs raw SQL (no snippet metadata), the HTML report is always rendered as a sortable / filterable table — no KPIs or charts. Use `dbcli q @<name> --format html` (or `--ui`) for the charted view.
|
|
437
528
|
|
|
529
|
+
> **Elasticsearch export (v1.22):** pass a search DSL with `--index <index>` to export the hits, or pass an index name as the query to scroll the whole index via `match_all`. Default cap is 1000 rows; `--no-limit` streams the full index via scroll in batches. Index-level blacklist is checked before export and an audit record is written.
|
|
530
|
+
|
|
438
531
|
### blacklist
|
|
439
532
|
|
|
440
533
|
Manage sensitive data blacklist to prevent AI access to restricted tables/columns.
|
|
@@ -502,6 +595,8 @@ Read-only snapshot for AI agents. Never emits credentials or blacklisted values.
|
|
|
502
595
|
| `--for-agent` | Shortcut for `--format json --brief` |
|
|
503
596
|
| `--no-connect` | Skip the cheap version/object probe (no DB traffic) |
|
|
504
597
|
| `--probe-timeout <ms>` | Hard timeout for the version/object probe (default 1500) |
|
|
598
|
+
| `--require-schema-cache` | Throw `SCHEMA_CACHE_MISSING` (recovery code) when the active SQL connection has no usable schema cache |
|
|
599
|
+
| `--recovery` | On failure, emit a structured `RecoveryEnvelope` to stdout |
|
|
505
600
|
|
|
506
601
|
Example:
|
|
507
602
|
|
|
@@ -509,7 +604,14 @@ Example:
|
|
|
509
604
|
dbcli inspect --for-agent
|
|
510
605
|
```
|
|
511
606
|
|
|
512
|
-
Output schema is locked at `schemaVersion: 1`. Sections: `connection`, `permission`, `blacklist`, `objects`, `schemaCache`, `snippets`, `suggestedCommands`, `warnings`.
|
|
607
|
+
Output schema is locked at `schemaVersion: 1`. Sections: `connection`, `permission`, `blacklist`, `objects`, `schemaCache`, `snippets`, `suggestedCommands`, `hints` **(v1.23)**, `warnings`.
|
|
608
|
+
|
|
609
|
+
**`suggestedCommands` (context-aware, v1.23)** — a three-tier weighted list:
|
|
610
|
+
1. *Bootstrap* — always-safe orientation commands (`blacklist list`, `schema <table>`, ...).
|
|
611
|
+
2. *Context-aware* — driven by recent activity. When a hot table is detected in the audit log **and** task packs are available, suggests `dbcli skill tasks plan analyze-table-perf --param table=<table>` plus `dbcli queries suggest <intent>` from your snippet intents.
|
|
612
|
+
3. *Discovery* — broader exploration commands.
|
|
613
|
+
|
|
614
|
+
**`hints` (v1.23)** — a parallel array of human-readable, non-executable notes: the most-queried table from recent audit, the number of available task packs, and the schema-cache size with its last-refresh timestamp. In markdown output they render as a `## Hints` section. Audit reads here are read-only and never throw. Both `suggestedCommands` and `hints` are trimmed under `--for-agent` / `--brief` (≤ 3 hints, single safest command).
|
|
513
615
|
|
|
514
616
|
**Permission:** query-only+
|
|
515
617
|
|
|
@@ -581,6 +683,30 @@ Boundaries:
|
|
|
581
683
|
|
|
582
684
|
**Permission:** query-only+
|
|
583
685
|
|
|
686
|
+
#### guide missing-index-for (v1.23)
|
|
687
|
+
|
|
688
|
+
A single-query composite-index advisor. Parses one `SELECT`, combines a real
|
|
689
|
+
`EXPLAIN` plan with existing indexes, and emits index candidates each carrying a
|
|
690
|
+
`confidence` (`high` / `medium` / `low`) and a `reason`. Read-only (EXPLAIN +
|
|
691
|
+
index introspection only). MySQL/MariaDB + PostgreSQL.
|
|
692
|
+
|
|
693
|
+
```bash
|
|
694
|
+
dbcli guide missing-index-for "SELECT ... FROM betting_logs b JOIN hoster_machines hm ON ..."
|
|
695
|
+
dbcli guide missing-index-for @analytics/live-summary # @saved-query
|
|
696
|
+
dbcli guide missing-index-for "..." --format json # yaml (default) | json | markdown
|
|
697
|
+
dbcli guide missing-index-for "..." --min-confidence medium # drop candidates below low|medium|high
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
**Options:** `--format <yaml|json|markdown>` (default `yaml`), `--min-confidence <low|medium|high>`.
|
|
701
|
+
|
|
702
|
+
Behaviour:
|
|
703
|
+
- Detects existing-index collisions (a single-column index that can be extended into a composite).
|
|
704
|
+
- Functional/expression columns (e.g. `DATE(settled_at)`) and SQL it cannot parse are reported under `warnings`, never as recommendations.
|
|
705
|
+
- Single `SELECT` only — no INSERT/UPDATE/DELETE, stored procedures, or view bodies.
|
|
706
|
+
- Dialects beyond node-sql-parser support fall back to EXPLAIN-only heuristics.
|
|
707
|
+
|
|
708
|
+
**Permission:** query-only+
|
|
709
|
+
|
|
584
710
|
### recovery
|
|
585
711
|
|
|
586
712
|
Machine-readable error envelope. Two surfaces share one `RecoveryEnvelope`
|
|
@@ -727,8 +853,9 @@ deterministically.
|
|
|
727
853
|
| Flag | Required | Description |
|
|
728
854
|
|---|---|---|
|
|
729
855
|
| `--next` | yes | Activate the multi-turn lookup. |
|
|
730
|
-
| `--after-step <n>` | yes | 1-based order of the step the agent just executed. Range: `[1, envelope.recovery.length]
|
|
856
|
+
| `--after-step <n>` | yes | 1-based order of the step the agent just executed. Range: `[1, envelope.recovery.length]` (or `[1, branches[id].steps.length]` when `--branch` is set). |
|
|
731
857
|
| `--result <value>` | yes | JSON `StepResultSummary` (inline) or `@<path>` to read from a file. |
|
|
858
|
+
| `--branch <id>` | no | Walk a specific branch by id (required on `--next` calls after a fork). See *Connection branching* below. |
|
|
732
859
|
| `--from <path>` | no | Override the auto-saved envelope. |
|
|
733
860
|
| `--format <fmt>` | no | `json` (default) or `markdown`. |
|
|
734
861
|
|
|
@@ -760,9 +887,24 @@ interface NextResult {
|
|
|
760
887
|
cursor: number // step.order when kind='step'; totalSteps when 'done'
|
|
761
888
|
totalSteps: number
|
|
762
889
|
step?: GuideStep // present iff kind='step'
|
|
890
|
+
branchId?: string // set iff agent is currently traversing a branch
|
|
891
|
+
branchDescription?: string // mirror of branches[branchId].description
|
|
763
892
|
}
|
|
764
893
|
```
|
|
765
894
|
|
|
895
|
+
**Connection branching**
|
|
896
|
+
|
|
897
|
+
For `CONN_*` recovery codes, the envelope ships an additional `branches` map and a `branchFork` descriptor. Step 1 (`dbcli doctor --format json`) is the fork point: pass the doctor JSON in `--result.stdoutSummary` and `--next` will pick one of four labeled branches:
|
|
898
|
+
|
|
899
|
+
| Branch id | When chosen |
|
|
900
|
+
|---|---|
|
|
901
|
+
| `doctor-clean` | Doctor reports no errors — likely transient; verify baseline state, then retry. |
|
|
902
|
+
| `doctor-config-missing` | Doctor flagged a config-level failure (missing / invalid config). Re-init before reconnecting. |
|
|
903
|
+
| `doctor-auth-error` | Doctor confirms credentials were rejected. Re-init with `--force` to overwrite credentials. |
|
|
904
|
+
| `doctor-network-error` | Doctor confirms a network-level failure (host / port / DNS / timeout). Inspect and re-init host/port. |
|
|
905
|
+
|
|
906
|
+
NextResult sets `branchId` and `branchDescription` after the fork; subsequent `--next` calls must echo `--branch <id>` to walk that branch. If the doctor JSON cannot be parsed or no keyword matches, `--next` falls back to the linear `recovery` plan — branching never causes `--next` to fail. `--apply` ignores `branches` entirely (linear walk unchanged).
|
|
907
|
+
|
|
766
908
|
**Exit codes**
|
|
767
909
|
|
|
768
910
|
| Exit | Condition |
|
|
@@ -937,6 +1079,11 @@ Inside the shell:
|
|
|
937
1079
|
- Multi-line SQL: keeps accumulating until `;` is found
|
|
938
1080
|
- History persists across sessions (~/.dbcli_history)
|
|
939
1081
|
|
|
1082
|
+
The REPL flavor depends on the active engine: SQL engines and MongoDB use the
|
|
1083
|
+
form above; **Redis** opens a single-line command REPL (see [Redis › Interactive
|
|
1084
|
+
shell](#interactive-shell)); **Elasticsearch** opens a Kibana Dev Tools-style
|
|
1085
|
+
REPL (v1.22, see [Elasticsearch › Interactive shell](#interactive-shell-v122)).
|
|
1086
|
+
|
|
940
1087
|
### migrate
|
|
941
1088
|
|
|
942
1089
|
Schema DDL operations. **All commands default to dry-run** — use `--execute` to actually run the SQL. Destructive operations (DROP) also require `--force`.
|
|
@@ -1031,6 +1178,16 @@ dbcli skill tasks plan diagnose-slow-query --param query="..." --format json
|
|
|
1031
1178
|
- **show:** prints the full task definition (frontmatter + Agent Notes). Use `--format json` for an agent-friendly contract.
|
|
1032
1179
|
- **plan:** resolves `{{param}}` placeholders, validates required parameters, and emits a stable plan. Plans are **plan-only** in this version — dbcli will never execute the resulting commands automatically.
|
|
1033
1180
|
|
|
1181
|
+
**Builtin packs:** `diagnose-slow-query` and **(v1.23)** `analyze-table-perf` —
|
|
1182
|
+
a read-only (`plan-only`) pack taking a required `table` parameter that walks
|
|
1183
|
+
`blacklist list` → `schema <table> --format json` → `guide index-usage --format json`.
|
|
1184
|
+
`dbcli inspect` suggests `analyze-table-perf` automatically for the hottest table
|
|
1185
|
+
in recent audit activity.
|
|
1186
|
+
|
|
1187
|
+
```bash
|
|
1188
|
+
dbcli skill tasks plan analyze-table-perf --param table=betting_logs --format json
|
|
1189
|
+
```
|
|
1190
|
+
|
|
1034
1191
|
Task storage layers:
|
|
1035
1192
|
|
|
1036
1193
|
| Source | Path | Notes |
|
|
@@ -1042,6 +1199,270 @@ Task storage layers:
|
|
|
1042
1199
|
Higher tiers override lower tiers by task name. Task name is derived from the
|
|
1043
1200
|
file path under the tier root (e.g. `diag/inspect.md` → `diag/inspect`).
|
|
1044
1201
|
|
|
1202
|
+
## Recovery Cookbook (agent walkthroughs)
|
|
1203
|
+
|
|
1204
|
+
End-to-end recovery sessions for the most common failure codes. All examples
|
|
1205
|
+
assume the agent invoked a `--recovery`-capable command and received a
|
|
1206
|
+
`RecoveryEnvelope` (or hit the same envelope via `dbcli recovery --code <CODE>`
|
|
1207
|
+
lookup). See [§recovery](#recovery) for the envelope shape, [§recover](#recover)
|
|
1208
|
+
for `--apply` / `--next` / risk-gate semantics, and [§audit](#audit) for the
|
|
1209
|
+
bi-directional `audit_ref` ⇄ `recovery_ref` pivot.
|
|
1210
|
+
|
|
1211
|
+
### Scenario index
|
|
1212
|
+
|
|
1213
|
+
| Code | Trigger | Primary remediation | Risk tier |
|
|
1214
|
+
|------|---------|---------------------|-----------|
|
|
1215
|
+
| `CONN_REFUSED` | Database process down or wrong host/port. | `dbcli doctor` → fix host/port → retry. | `readonly` |
|
|
1216
|
+
| `CONN_AUTH_FAILED` | Credentials rejected. | Re-check `.dbcli`/env, rotate credentials, `dbcli init --force` only on explicit user nod. | `readonly` → `interactive` |
|
|
1217
|
+
| `PERMISSION_DENIED` | Active permission level forbids the verb. | `dbcli inspect` to confirm level → escalate via `dbcli init` (human) or run a `--dry-run` instead. | `readonly` + `dry-run` |
|
|
1218
|
+
| `BLACKLIST_TABLE` | Target table is blacklisted. | `dbcli blacklist list` → `blacklist table remove <name>` (local-write tier). | `readonly` + `local-write` |
|
|
1219
|
+
| `BLACKLIST_COLUMN_WRITE` | INSERT/UPDATE touches a blacklisted column. | Re-shape payload to drop the column, or `blacklist column remove`. Envelope prepends a `--dry-run` preview step. | `dry-run` + `local-write` |
|
|
1220
|
+
| `SCHEMA_CACHE_MISSING` | Fresh checkout / new v2 connection / cache wiped. | `dbcli schema --refresh --force` (or `--use <conn>` per-connection). | `readonly` |
|
|
1221
|
+
| `SNIPPET_NOT_FOUND` / `SNIPPET_AMBIGUOUS` | Typo or duplicate snippet name. | `dbcli queries list` → `queries search <kw>` → run correct `@name`. | `readonly` |
|
|
1222
|
+
| `SNIPPET_PARAM_MISSING` | `--param k=v` not supplied. | `dbcli queries show @name` lists required params → re-run with full set. | `readonly` |
|
|
1223
|
+
| `CONFIG_MISSING` | No `.dbcli` in cwd. | `dbcli init` (human-driven). | `interactive` |
|
|
1224
|
+
|
|
1225
|
+
`risk` enum: `readonly` / `dry-run` / `write` / `unknown` (see §recovery boundaries).
|
|
1226
|
+
Allowlist tier: `readonly` / `dry-run` / `local-write` / `db-write` / `interactive` (see [§recover Risk gate matrix](#risk-gate-matrix)).
|
|
1227
|
+
|
|
1228
|
+
### S1 — CONN_REFUSED end-to-end
|
|
1229
|
+
|
|
1230
|
+
```bash
|
|
1231
|
+
# 1. Failing call writes envelope to stdout AND .dbcli/last-recovery.json
|
|
1232
|
+
$ dbcli query "SELECT 1" --recovery --format json
|
|
1233
|
+
{
|
|
1234
|
+
"schemaVersion": 1,
|
|
1235
|
+
"error": { "code": "CONN_REFUSED", "message": "..." },
|
|
1236
|
+
"audit_ref": "1f8e...c4d2",
|
|
1237
|
+
"recovery": [
|
|
1238
|
+
{ "order": 1, "command": "dbcli doctor --format json", "risk": "readonly", ... },
|
|
1239
|
+
{ "order": 2, "command": "dbcli inspect --for-agent", "risk": "readonly", ... }
|
|
1240
|
+
],
|
|
1241
|
+
"verify": { "command": "dbcli doctor --format json", "risk": "readonly", ... }
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
# 2. One-shot apply (only readonly + dry-run run by default)
|
|
1245
|
+
$ dbcli recover --apply --format json
|
|
1246
|
+
{ "finalStatus": "ok", "executed": [...], "verifyStatus": "passed" }
|
|
1247
|
+
# Exit 0 → root cause cleared (verify probe succeeded).
|
|
1248
|
+
|
|
1249
|
+
# 3. If verify reported `failed` / `indeterminate`, drop into --next for control
|
|
1250
|
+
$ dbcli recover --next --after-step 1 --result '{"status":"failed","exitCode":1}'
|
|
1251
|
+
# → returns a refined step 2 or `kind:"done"` based on the prevResult
|
|
1252
|
+
```
|
|
1253
|
+
|
|
1254
|
+
### S2 — PERMISSION_DENIED with implicit `--dry-run` preview
|
|
1255
|
+
|
|
1256
|
+
```bash
|
|
1257
|
+
$ dbcli update orders --where "id=1" --set '{"status":"shipped"}' --recovery --format json
|
|
1258
|
+
{
|
|
1259
|
+
"error": { "code": "PERMISSION_DENIED", ... },
|
|
1260
|
+
"audit_ref": "9ab0...e711",
|
|
1261
|
+
"recovery": [
|
|
1262
|
+
{ "order": 1, "command": "dbcli update orders --where 'id=1' --set '<redacted>' --dry-run", "risk": "dry-run" },
|
|
1263
|
+
{ "order": 2, "command": "dbcli inspect --for-agent", "risk": "readonly" }
|
|
1264
|
+
]
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
# Default apply runs both steps (dry-run is in-tier).
|
|
1268
|
+
$ dbcli recover --apply
|
|
1269
|
+
```
|
|
1270
|
+
|
|
1271
|
+
When the failing operation is INSERT/UPDATE/DELETE, the envelope prepends a
|
|
1272
|
+
`risk: 'dry-run'` step (the same write subcommand with `--dry-run`). Run it
|
|
1273
|
+
before any escalation — it both teaches the agent what the SQL looks like and
|
|
1274
|
+
proves the change is well-formed before raising the permission tier.
|
|
1275
|
+
|
|
1276
|
+
### S3 — BLACKLIST_TABLE (local-write remediation)
|
|
1277
|
+
|
|
1278
|
+
```bash
|
|
1279
|
+
$ dbcli query "SELECT * FROM audit_logs" --recovery --format json
|
|
1280
|
+
# error.code: BLACKLIST_TABLE
|
|
1281
|
+
# recovery[0]: dbcli blacklist list (risk: readonly)
|
|
1282
|
+
# recovery[1]: dbcli blacklist table remove audit_logs (risk: write — local-write tier)
|
|
1283
|
+
|
|
1284
|
+
# Default --apply: step 1 runs, step 2 skipped:risk → exit 3.
|
|
1285
|
+
$ dbcli recover --apply
|
|
1286
|
+
# To proceed: open the gate to local-write tier ONLY (does not touch DB).
|
|
1287
|
+
$ dbcli recover --apply --allow-write=readonly-cmd
|
|
1288
|
+
{ "finalStatus": "ok", "executed": [step1, step2], "verifyStatus": "passed" }
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
### S4 — BLACKLIST_COLUMN_WRITE (preview-then-drop)
|
|
1292
|
+
|
|
1293
|
+
```bash
|
|
1294
|
+
$ dbcli insert users --data '{"name":"a","ssn":"123"}' --recovery
|
|
1295
|
+
# recovery[0]: dbcli insert users --data '<redacted>' --dry-run (risk: dry-run)
|
|
1296
|
+
# recovery[1]: dbcli blacklist list (risk: readonly)
|
|
1297
|
+
# recovery[2]: dbcli blacklist column remove users.ssn (risk: write — local-write)
|
|
1298
|
+
|
|
1299
|
+
# Preferred path: don't widen the blacklist — re-shape the agent's payload to drop ssn.
|
|
1300
|
+
# Apply only the diagnostic prefix (steps 1+2) to confirm what columns are masked:
|
|
1301
|
+
$ dbcli recover --apply
|
|
1302
|
+
# Then re-issue insert without `ssn`.
|
|
1303
|
+
```
|
|
1304
|
+
|
|
1305
|
+
### S5 — SCHEMA_CACHE_MISSING (fresh / multi-conn)
|
|
1306
|
+
|
|
1307
|
+
```bash
|
|
1308
|
+
$ dbcli inspect --require-schema-cache --recovery --format json
|
|
1309
|
+
# error.code: SCHEMA_CACHE_MISSING
|
|
1310
|
+
# recovery[0]: dbcli schema --refresh --force (risk: readonly — populates .dbcli/schemas/)
|
|
1311
|
+
# verify: dbcli inspect --format json (schemaCache.available === true)
|
|
1312
|
+
|
|
1313
|
+
$ dbcli recover --apply
|
|
1314
|
+
# Per-connection cache lives at .dbcli/schemas/<connection>/. If the failure was on
|
|
1315
|
+
# a v2 named connection, the envelope's command already carries `--use <name>`.
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
### S6 — SNIPPET_NOT_FOUND with disambiguation
|
|
1319
|
+
|
|
1320
|
+
```bash
|
|
1321
|
+
$ dbcli q @anaytics/revenue --recovery
|
|
1322
|
+
# typo: anaytics → analytics
|
|
1323
|
+
# recovery[0]: dbcli queries list --format json
|
|
1324
|
+
# recovery[1]: dbcli queries search analytics (or whatever --hint suggests)
|
|
1325
|
+
$ dbcli recover --apply
|
|
1326
|
+
# Agent reads stdoutSummary, identifies the correct @name, then re-issues:
|
|
1327
|
+
$ dbcli q @analytics/revenue --param days=30
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
### Multi-turn `--next` walkthrough (3-step plan)
|
|
1331
|
+
|
|
1332
|
+
Use `--next` instead of `--apply` when:
|
|
1333
|
+
|
|
1334
|
+
- `--apply` is too coarse-grained (the agent wants step-by-step inspection).
|
|
1335
|
+
- The plan contains an `interactive` step that `--apply` would skip.
|
|
1336
|
+
- The agent uses its own runner / sandbox and just wants dbcli to drive cursoring.
|
|
1337
|
+
|
|
1338
|
+
`--next` returns one step at a time, given which step the agent **just executed**
|
|
1339
|
+
and a `StepResultSummary` of how it went. dbcli does not persist the cursor —
|
|
1340
|
+
the agent owns `--after-step`.
|
|
1341
|
+
|
|
1342
|
+
```bash
|
|
1343
|
+
# Envelope already saved at .dbcli/last-recovery.json (3-step plan, CONN_REFUSED).
|
|
1344
|
+
|
|
1345
|
+
# Round 1 — agent reads step 1 from the envelope, executes it itself, then asks
|
|
1346
|
+
# dbcli for the next step.
|
|
1347
|
+
$ dbcli recover --next --after-step 1 --result '{"status":"ok","exitCode":0}' --format json
|
|
1348
|
+
{
|
|
1349
|
+
"schemaVersion": 1,
|
|
1350
|
+
"kind": "step",
|
|
1351
|
+
"errorCode": "CONN_REFUSED",
|
|
1352
|
+
"cursor": 2,
|
|
1353
|
+
"totalSteps": 3,
|
|
1354
|
+
"step": { "order": 2, "command": "dbcli inspect --for-agent", "risk": "readonly", ... }
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
# Round 2 — bigger stdout, save to file and reference it.
|
|
1358
|
+
$ ./run-step.sh > /tmp/r2.json # agent's own runner; result is StepResultSummary JSON
|
|
1359
|
+
$ dbcli recover --next --after-step 2 --result @/tmp/r2.json
|
|
1360
|
+
{ "kind": "step", "cursor": 3, "step": { "order": 3, ... } }
|
|
1361
|
+
|
|
1362
|
+
# Round 3 — last step done.
|
|
1363
|
+
$ dbcli recover --next --after-step 3 --result '{"status":"ok"}'
|
|
1364
|
+
{ "kind": "done", "cursor": 3, "totalSteps": 3 }
|
|
1365
|
+
```
|
|
1366
|
+
|
|
1367
|
+
`StepResultSummary` contract (recap of [§recover Multi-turn](#multi-turn---next-p2)):
|
|
1368
|
+
|
|
1369
|
+
```ts
|
|
1370
|
+
interface StepResultSummary {
|
|
1371
|
+
status: 'ok' | 'failed' | 'skipped'
|
|
1372
|
+
exitCode?: number
|
|
1373
|
+
stdoutSummary?: string // last 4 KB
|
|
1374
|
+
stderrSummary?: string // last 4 KB
|
|
1375
|
+
}
|
|
1376
|
+
```
|
|
1377
|
+
|
|
1378
|
+
Truncate to the **last** 4 KB before passing — the head of a huge stdout is
|
|
1379
|
+
usually not what disambiguates next steps.
|
|
1380
|
+
|
|
1381
|
+
Verification is **not** automatic under `--next`. If the agent wants the same
|
|
1382
|
+
verify probe `--apply` runs, it must execute the envelope's `verify` step
|
|
1383
|
+
itself after the plan completes.
|
|
1384
|
+
|
|
1385
|
+
### Bi-directional pivot (envelope ⇄ audit)
|
|
1386
|
+
|
|
1387
|
+
Every `--recovery`-capable failure (`query`, `inspect`, `insert`, `update`,
|
|
1388
|
+
`delete`, `export`, `q`, `schema`) writes **both** sides of a UUID link:
|
|
1389
|
+
|
|
1390
|
+
- `RecoveryEnvelope.audit_ref` → the `audit.id` for the same failure.
|
|
1391
|
+
- `AuditEntry.recovery_ref` → the envelope's id (also the auto-saved
|
|
1392
|
+
`.dbcli/last-recovery.json` filename trace).
|
|
1393
|
+
|
|
1394
|
+
```bash
|
|
1395
|
+
# From envelope → audit (forensics on a saved failure)
|
|
1396
|
+
$ ENV_ID=$(jq -r '.id' .dbcli/last-recovery.json) # or read from stdout
|
|
1397
|
+
$ dbcli audit show --recovery-ref "$ENV_ID" --format json
|
|
1398
|
+
# Returns the matching audit entry (full, not brief).
|
|
1399
|
+
|
|
1400
|
+
# From audit → envelope (you have an audit hit, want the structured plan)
|
|
1401
|
+
$ AUDIT_ID=$(dbcli audit tail --for-agent --n 1 | jq -r '.[0].id')
|
|
1402
|
+
$ dbcli audit show "$AUDIT_ID" --format json
|
|
1403
|
+
# Read `recovery_ref` from the entry, then either re-run --recovery against
|
|
1404
|
+
# the original command or load the saved envelope:
|
|
1405
|
+
$ jq '.recovery_ref' .dbcli/last-recovery.json | grep -q "$RECOVERY_REF" \
|
|
1406
|
+
&& dbcli recover --format markdown # human inspect
|
|
1407
|
+
|| dbcli recover --from /path/to/archived.json --format markdown
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
Session handoff: a fresh agent that opens `dbcli inspect --for-agent`,
|
|
1411
|
+
`dbcli guide`, `dbcli recover`, or `dbcli recover --apply` gets an
|
|
1412
|
+
`audit_recent: AuditEntryBrief[]` field (last 5 entries) embedded in the JSON
|
|
1413
|
+
output — no extra round-trip to the audit CLI needed for immediate history
|
|
1414
|
+
context.
|
|
1415
|
+
|
|
1416
|
+
### Risk gate cheat sheet
|
|
1417
|
+
|
|
1418
|
+
Quick reference for what `--apply` runs at each `--allow-write` level. The
|
|
1419
|
+
canonical matrix lives at [§recover Risk gate matrix](#risk-gate-matrix); this
|
|
1420
|
+
table maps it onto common agent intents.
|
|
1421
|
+
|
|
1422
|
+
| Agent intent | Recommended flag | What runs | What's skipped |
|
|
1423
|
+
|---|---|---|---|
|
|
1424
|
+
| Probe-only (read state, learn) | `--apply` (default) | `readonly` + `dry-run` steps | `local-write`, `db-write`, `interactive` |
|
|
1425
|
+
| Local config remediation (e.g. `blacklist remove`) | `--apply --allow-write=readonly-cmd` | + `local-write` | `db-write`, `interactive` |
|
|
1426
|
+
| Database write recovery (rare; trusted plan) | `--apply --allow-write=write-cmd` | + `db-write` | `interactive` |
|
|
1427
|
+
| Interactive step (e.g. `dbcli init`) | Drive manually OR use `--next` | n/a | All interactive steps always skip under `--apply` |
|
|
1428
|
+
| Walk plan step-by-step with own runner | `--next --after-step N --result …` | one step per call | n/a — agent owns cursor + execution |
|
|
1429
|
+
|
|
1430
|
+
Three rules that always apply regardless of `--allow-write`:
|
|
1431
|
+
|
|
1432
|
+
1. **Tier is code-owned, not envelope-claimed.** The risk gate reads the
|
|
1433
|
+
per-error-code allowlist after parsing argv. An envelope cannot escalate
|
|
1434
|
+
itself by setting `risk: 'readonly'` on a write subcommand — argv decides.
|
|
1435
|
+
2. **Placeholders block.** A step with unresolved `<token>` placeholders is
|
|
1436
|
+
skipped as `skipped:placeholder` even at `--allow-write=write-cmd`. Bind
|
|
1437
|
+
them at `recovery` lookup time with `--hint` / `--snippet` / `--table`, or
|
|
1438
|
+
ask the user.
|
|
1439
|
+
3. **Verify is signal, not gate.** `verifyStatus` ∈ `{passed, failed,
|
|
1440
|
+
indeterminate}` reports whether the original failure looks resolved.
|
|
1441
|
+
`recover --apply` exit code is set by step execution, not verification.
|
|
1442
|
+
|
|
1443
|
+
### Common pitfalls
|
|
1444
|
+
|
|
1445
|
+
- **Stale `.dbcli/last-recovery.json`.** `recover` (no `--apply`) shows the
|
|
1446
|
+
*saved* plan, which may be hours old. Re-run the original command with
|
|
1447
|
+
`--recovery` to refresh it, or pass `--from <file>` to load an archived one.
|
|
1448
|
+
- **`.dbcli/` is gitignored.** Do not check `last-recovery.json` into a repo
|
|
1449
|
+
for "reproducibility"; it contains sanitized command snapshots but the
|
|
1450
|
+
workspace `cwd` only makes sense locally. Use `recover --from <archived.json>`
|
|
1451
|
+
for cross-machine replay.
|
|
1452
|
+
- **`--apply` exit 3 means every step skipped.** Not a failure — it means the
|
|
1453
|
+
default gate was too tight. Either widen with `--allow-write`, fill
|
|
1454
|
+
placeholders, or fall back to `--next` and drive steps manually.
|
|
1455
|
+
- **`--next` does not run verify.** Re-run the original failing command with
|
|
1456
|
+
`--recovery` once the plan is done; if it now succeeds (no envelope on
|
|
1457
|
+
stdout), recovery is complete. Or invoke `envelope.verify.command` yourself.
|
|
1458
|
+
- **Audit writer failures are non-fatal.** If `audit health` reports
|
|
1459
|
+
`lastWriteOk: false`, the main command still completed — but `recovery_ref`
|
|
1460
|
+
⇄ `audit_ref` linkage is broken for that one call. `audit health` surfaces
|
|
1461
|
+
the underlying error (disk full, EACCES, etc.).
|
|
1462
|
+
- **Cross-connection forensics.** `audit tail --all --for-agent` merges all
|
|
1463
|
+
connections; `audit show <id-prefix> --all` returns an envelope `{connection,
|
|
1464
|
+
entry}` so a fresh agent can tell which DB the failure was against.
|
|
1465
|
+
|
|
1045
1466
|
## Interactive HTML dashboard
|
|
1046
1467
|
|
|
1047
1468
|
`query`, `q`, and `export` can render results as a single, fully self-contained
|
|
@@ -1150,19 +1571,21 @@ MongoDB connections use a JSON-based query model instead of SQL. Treat MongoDB s
|
|
|
1150
1571
|
|
|
1151
1572
|
Atlas-style `mongodb+srv://` URIs are supported. `list` and `query` run against the database configured for the connection, and `query` always requires `--collection <name>`.
|
|
1152
1573
|
|
|
1153
|
-
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `insert`, `update`, `delete`, `status`, `shell`, `doctor`, `upgrade`, `completion`
|
|
1574
|
+
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `q`, `insert`, `update`, `delete`, `export`, `status`, `shell`, `doctor`, `upgrade`, `completion`
|
|
1154
1575
|
|
|
1155
1576
|
**Limited support:**
|
|
1156
1577
|
|
|
1157
1578
|
- `schema` samples collection documents to infer field names/types. It does not provide relational constraints, primary keys, foreign keys, or reliable index metadata.
|
|
1158
1579
|
- `query` accepts only JSON object filters or aggregation pipeline arrays and always requires `--collection <name>`.
|
|
1580
|
+
- `q` saved-query execution accepts JSON `find` / `aggregate` bodies, requires a `collection` frontmatter field (CLI `--collection` overrides), JSON-encodes every `{{param}}` substitution, and enforces table-level blacklist plus document field masking before rendering.
|
|
1159
1581
|
- `insert` inserts one JSON document into the named collection.
|
|
1160
1582
|
- `update` accepts a JSON filter in `--where` or simple `key=value` conditions. If `--set` does not use MongoDB update operators such as `$set`, dbcli wraps it in `$set`.
|
|
1161
1583
|
- `delete` deletes all documents matching the JSON/simple filter.
|
|
1584
|
+
- `export` accepts the same JSON filter / aggregation syntax as `query`.
|
|
1162
1585
|
- MongoDB write paths do not currently provide the same SQL dry-run, relational schema validation, or column-level blacklist filtering guarantees as SQL writes.
|
|
1163
1586
|
- `shell` blocks raw SQL for MongoDB; use `query <json> --collection <name>` inside the shell.
|
|
1164
1587
|
|
|
1165
|
-
**Not supported (exit with error):** `
|
|
1588
|
+
**Not supported (exit with error):** `diff`, `migrate`
|
|
1166
1589
|
|
|
1167
1590
|
**Not a supported MongoDB target:** `check` is designed for relational health checks and emits SQL-style checks.
|
|
1168
1591
|
|
|
@@ -1197,19 +1620,19 @@ dbcli delete orders --where '{"status":"cancelled"}' --force
|
|
|
1197
1620
|
|
|
1198
1621
|
## Redis Support
|
|
1199
1622
|
|
|
1200
|
-
Redis connections speak Redis commands rather than SQL. The adapter uses
|
|
1623
|
+
Redis connections speak Redis commands rather than SQL. The adapter uses Bun's native `Bun.RedisClient` and exposes a permission-gated surface with a query size guard and key-glob blacklist enforcement.
|
|
1201
1624
|
|
|
1202
|
-
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
|
|
1625
|
+
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `shell`, `status`, `doctor`, `upgrade`, `completion`
|
|
1203
1626
|
|
|
1204
1627
|
**Saved queries:** `q` is supported for read-only Redis commands (see "Redis snippets" below).
|
|
1205
1628
|
|
|
1206
|
-
**Not supported (exit with error or unsupported error):** `insert`, `update`, `delete`, `export`, `check`, `diff`, `migrate
|
|
1629
|
+
**Not supported (exit with error or unsupported error):** `insert`, `update`, `delete`, `export`, `check`, `diff`, `migrate`. For writes, run the equivalent Redis command via `query` — the same permission gate applies.
|
|
1207
1630
|
|
|
1208
1631
|
### Connection and configuration
|
|
1209
1632
|
|
|
1210
1633
|
- Required fields: `system: redis`, `host`, `port`. `password` and `database` are optional.
|
|
1211
1634
|
- `database` is the **logical DB index** (`"0"` … `"15"`), kept as a string to play nicely with env-ref bindings. `list` and the connection metadata both label it as the active DB.
|
|
1212
|
-
- `connection.timeout` (ms, default 5000) maps to
|
|
1635
|
+
- `connection.timeout` (ms, default 5000) maps to the client's `connectionTimeout`.
|
|
1213
1636
|
|
|
1214
1637
|
### Permission classification
|
|
1215
1638
|
|
|
@@ -1256,22 +1679,83 @@ dbcli query "DEL temp:lock"
|
|
|
1256
1679
|
dbcli query "HDEL user:42 lastLogin"
|
|
1257
1680
|
```
|
|
1258
1681
|
|
|
1682
|
+
### Size guard (`query --no-limit` / shell `.no-limit`)
|
|
1683
|
+
|
|
1684
|
+
The adapter rewrites unbounded reads before dispatch and truncates oversized replies after:
|
|
1685
|
+
|
|
1686
|
+
| Strategy | Commands | Behavior |
|
|
1687
|
+
|----------|----------|----------|
|
|
1688
|
+
| inject/cap `COUNT` | `SCAN`, `HSCAN`, `SSCAN`, `ZSCAN` | adds `COUNT 1000` when absent; caps a larger `COUNT` to 1000 |
|
|
1689
|
+
| clamp `stop` | `LRANGE`, `ZRANGE`, `ZREVRANGE` | rewrites `stop` so the span ≤ 1000 (`-1` becomes `start+999`) |
|
|
1690
|
+
| inject/cap `LIMIT` | `ZRANGEBYSCORE` | appends `LIMIT 0 1000` when absent; caps a larger count |
|
|
1691
|
+
| client truncate | `HGETALL`, `HKEYS`, `HVALS`, `SMEMBERS`, `KEYS` | keeps the first 1000 entries |
|
|
1692
|
+
|
|
1693
|
+
Rewrites emit a `REDIS_SIZE_REWRITE` warning; truncations emit `REDIS_SIZE_TRUNCATE`. Both surface in the result's `warnings[]`. Pass `--no-limit` (CLI) or toggle `.no-limit on` (shell) to disable all guards.
|
|
1694
|
+
|
|
1695
|
+
### Blacklist enforcement
|
|
1696
|
+
|
|
1697
|
+
Blacklist rules are enforced as **Redis-native key globs** (`*`, `?`, `[abc]`, `[a-z]`):
|
|
1698
|
+
|
|
1699
|
+
```bash
|
|
1700
|
+
dbcli blacklist add 'secrets:*' # register a key-glob rule
|
|
1701
|
+
dbcli query "GET secrets:api_key" # → BlacklistRejection (exit non-zero)
|
|
1702
|
+
dbcli query "MGET safe:k secrets:api" # → rejected (any matching key fails the whole command)
|
|
1703
|
+
dbcli query "KEYS secrets:*" # → rejected (pattern overlaps a rule)
|
|
1704
|
+
dbcli query "KEYS *" # → returns only non-blacklisted keys
|
|
1705
|
+
```
|
|
1706
|
+
|
|
1707
|
+
Rejections are written to the audit log with `success: false` and `metadata.rejection_reason: 'blacklist'` + `matched_pattern`.
|
|
1708
|
+
|
|
1709
|
+
### Value / hash-field masking (v1.22)
|
|
1710
|
+
|
|
1711
|
+
Where the key-glob blacklist *rejects*, masking instead *redacts*: a matched read still
|
|
1712
|
+
runs, but the sensitive value comes back as `[REDACTED]` so an agent can use the command
|
|
1713
|
+
without ever seeing it. Add an optional `redis.mask` block to `.dbcli`:
|
|
1714
|
+
|
|
1715
|
+
```yaml
|
|
1716
|
+
redis:
|
|
1717
|
+
mask:
|
|
1718
|
+
- keyPattern: 'session:*' # whole value redacted on read
|
|
1719
|
+
- keyPattern: 'user:*'
|
|
1720
|
+
fields: [password, token] # only these hash fields redacted
|
|
1721
|
+
```
|
|
1722
|
+
|
|
1723
|
+
- Applies on reads: `GET`, `GETRANGE`, `HGETALL`, `HGET`, `HMGET`, `HVALS`.
|
|
1724
|
+
- A rule without `fields` redacts the entire value; with `fields` only the named hash fields are redacted.
|
|
1725
|
+
- Masking and key-glob rejection coexist, and **rejection always wins over masking** — a key that matches a blacklist rule is rejected, never merely masked.
|
|
1726
|
+
|
|
1727
|
+
### Interactive shell
|
|
1728
|
+
|
|
1729
|
+
`dbcli shell` on a Redis connection opens a single-line REPL:
|
|
1730
|
+
|
|
1731
|
+
```text
|
|
1732
|
+
$ dbcli --use local-redis shell
|
|
1733
|
+
Redis shell: single-line commands; SCAN/LRANGE auto-capped at 1000. Type `.no-limit on` to bypass (unsafe).
|
|
1734
|
+
redis> SCAN 0 # wire args become: SCAN 0 COUNT 1000 (REDIS_SIZE_REWRITE)
|
|
1735
|
+
redis> HGETALL bighash # >1000 fields → kept 1000 (REDIS_SIZE_TRUNCATE)
|
|
1736
|
+
redis> .no-limit on # bypass size guard for this session
|
|
1737
|
+
redis> GET secrets:api_key # → REDIS_BLACKLIST / BlacklistRejection if blacklisted
|
|
1738
|
+
redis> .exit
|
|
1739
|
+
```
|
|
1740
|
+
|
|
1741
|
+
Tab completion offers Redis command names and known key prefixes; history persists to `~/.dbcli_history`.
|
|
1742
|
+
|
|
1259
1743
|
### Limitations
|
|
1260
1744
|
|
|
1261
1745
|
- No `--dry-run` for writes — Redis commands execute immediately. Pair writes with a confirming read (`GET`, `HGETALL`, `EXISTS`).
|
|
1262
1746
|
- No transaction wrapping (`MULTI`/`EXEC`). Submit one command at a time.
|
|
1263
1747
|
- `KEYS` requires `admin`. Prefer `SCAN` for routine work.
|
|
1264
|
-
- Blacklist
|
|
1748
|
+
- Blacklist enforcement covers **keys** (Redis-native globs); value / hash-field **masking** is available via the `redis.mask` config block (v1.22).
|
|
1265
1749
|
|
|
1266
1750
|
## Elasticsearch Support
|
|
1267
1751
|
|
|
1268
1752
|
Elasticsearch connections speak the REST API. The adapter is fetch-based (no SDK) and supports HTTPS, custom CA, API key, basic auth, and Cloud ID.
|
|
1269
1753
|
|
|
1270
|
-
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
|
|
1754
|
+
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `export` (v1.22), `shell` (v1.22), `status`, `doctor`, `upgrade`, `completion`
|
|
1271
1755
|
|
|
1272
1756
|
**Saved queries:** `q` is supported for ES JSON DSL bodies (see "Elasticsearch snippets" below).
|
|
1273
1757
|
|
|
1274
|
-
**Not supported (use external tooling):** `insert`, `update`, `delete`, `
|
|
1758
|
+
**Not supported (use external tooling):** `insert`, `update`, `delete`, `check`, `diff`, `migrate`. The permission classifier already understands `_doc` / `_update` / `_bulk` so future write surfaces can be wired in without changing tiers.
|
|
1275
1759
|
|
|
1276
1760
|
### Connection and configuration
|
|
1277
1761
|
|
|
@@ -1324,6 +1808,38 @@ dbcli query '{"size":0,"aggs":{"by_status":{"terms":{"field":"status.keyword"}}}
|
|
|
1324
1808
|
dbcli query 'status:active AND amount:>100' --index orders --limit 100
|
|
1325
1809
|
```
|
|
1326
1810
|
|
|
1811
|
+
### Export (v1.22)
|
|
1812
|
+
|
|
1813
|
+
`dbcli export` supports two shapes on an ES connection:
|
|
1814
|
+
|
|
1815
|
+
```bash
|
|
1816
|
+
# (a) search DSL + --index → export the hits
|
|
1817
|
+
dbcli export '{"query":{"match":{"status":"active"}}}' --index orders --format jsonl --output orders.ndjson
|
|
1818
|
+
|
|
1819
|
+
# (b) index name as the query → match_all over the whole index (scroll)
|
|
1820
|
+
dbcli export orders --format csv --output orders.csv
|
|
1821
|
+
dbcli export orders --no-limit --format jsonl # full index, scrolled in batches
|
|
1822
|
+
```
|
|
1823
|
+
|
|
1824
|
+
- Outputs JSON / JSONL / CSV. Default cap is 1000 rows; `--no-limit` streams the full index via the scroll API in batches.
|
|
1825
|
+
- Index-level blacklist is checked before export and the run is written to the audit log.
|
|
1826
|
+
|
|
1827
|
+
### Interactive shell (v1.22)
|
|
1828
|
+
|
|
1829
|
+
`dbcli shell` on an ES connection opens a Kibana Dev Tools-style REPL:
|
|
1830
|
+
|
|
1831
|
+
```text
|
|
1832
|
+
$ dbcli --use local-es shell
|
|
1833
|
+
GET /orders/_search
|
|
1834
|
+
{
|
|
1835
|
+
"query": { "match": { "status": "active" } }
|
|
1836
|
+
}
|
|
1837
|
+
# ← blank line submits the whole block
|
|
1838
|
+
```
|
|
1839
|
+
|
|
1840
|
+
- Enter a request line `<METHOD> /<path>`, then an optional multi-line JSON body; a **blank line** submits the block. Responses render as pretty-printed JSON.
|
|
1841
|
+
- Read-focused: index-level blacklist rejects protected indices at the front end; a `_search` whose body omits `size` is auto-capped at 1000 hits.
|
|
1842
|
+
|
|
1327
1843
|
### Doctor and diagnostics
|
|
1328
1844
|
|
|
1329
1845
|
`dbcli doctor` runs a dedicated Elasticsearch path:
|
|
@@ -1335,7 +1851,7 @@ dbcli query 'status:active AND amount:>100' --index orders --limit 100
|
|
|
1335
1851
|
|
|
1336
1852
|
### Limitations
|
|
1337
1853
|
|
|
1338
|
-
- Writes (`insert`/`update`/`delete
|
|
1854
|
+
- Writes (`insert`/`update`/`delete`) are not exposed yet — the adapter implements them, but the CLI currently only routes them for SQL and MongoDB. Read-only `export` (v1.22) and the interactive `shell` (v1.22) are available.
|
|
1339
1855
|
- No `_search/scroll` or PIT pagination at the CLI layer; large pulls need a saved external script.
|
|
1340
1856
|
- `check`, `diff`, `migrate`, and `q` are SQL-only and exit with errors (or fall through to a generic "unsupported" path).
|
|
1341
1857
|
- Blacklist column rules are applied to flattened hit rows on `query`; table-level blacklist rejects an index up front.
|