@carllee1983/dbcli 1.11.0 → 1.18.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.
@@ -135,9 +135,13 @@ dbcli query "DEL stale:key" # requires data-admin+
135
135
  # Elasticsearch: DSL body or Lucene q-string
136
136
  dbcli query '{"query":{"match":{"status":"active"}}}' --collection orders
137
137
  dbcli query 'status:active AND amount:>100' --index orders --limit 50
138
+
139
+ # Interactive HTML dashboard (see "Interactive HTML dashboard" below)
140
+ dbcli query "SELECT day, dau FROM dau_daily" --ui # open in browser
141
+ dbcli query "SELECT * FROM orders" --format html > orders.html # pipe to stdout
138
142
  ```
139
143
 
140
- **Options:** `--format <table|json|csv>`, `--limit <number>`, `--no-limit`, `--collection <name>` (MongoDB / Elasticsearch), `--index <name>` (Elasticsearch alias for `--collection`)
144
+ **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`), `--recovery`
141
145
  **Permission:** query-only+ (Redis: per-command; Elasticsearch: per HTTP method/path)
142
146
 
143
147
  > **MongoDB notes:**
@@ -156,6 +160,31 @@ dbcli query 'status:active AND amount:>100' --index orders --limit 50
156
160
  > - Hits are flattened: each result row contains `_id` plus dotted-path fields from `_source`. Pass `--format json` to keep nested structures readable.
157
161
  > - Query-only mode caps at 1000 hits; `--no-limit` is internally capped at 10 000 (use saved searches / `search_after` for deeper pagination).
158
162
 
163
+ ### plan
164
+
165
+ Static SQL risk analyzer. Classifies a statement into the same permission tiers
166
+ used by `query` (`query-only` / `read-write` / `data-admin` / `admin`) and lists
167
+ the underlying signals (DML / DDL / multi-statement / unsafe constructs) without
168
+ ever connecting to the database.
169
+
170
+ ```bash
171
+ dbcli plan "SELECT * FROM users"
172
+ dbcli plan "UPDATE users SET name='x'" # human-readable text classification
173
+ dbcli plan "DROP TABLE users" --format json # machine-readable risk report
174
+ ```
175
+
176
+ **Options:** `--format <text|json>` (default `text`).
177
+ **Permission:** n/a (offline analyzer; no connection opened).
178
+
179
+ Use cases:
180
+ - Agents that want to decide whether to call `query` vs `insert` / `update` /
181
+ `delete` before sending SQL.
182
+ - Pre-flight safety check before binding parameters into a saved snippet.
183
+ - Lint hook for code review pipelines that store SQL in source.
184
+
185
+ `plan` does not enforce blacklist or auto-`LIMIT`; those still apply when the
186
+ SQL is actually executed via `query` / `q`.
187
+
159
188
  ### q
160
189
 
161
190
  Run a saved query snippet by `@name`. Snippets are parameterised SELECT/WITH statements resolved from three layers, with **local > shared > builtin** precedence (a local file always shadows shared and builtin variants of the same key):
@@ -172,15 +201,19 @@ dbcli q @dau --param days=30 --format json # override a param
172
201
  dbcli q @analytics/revenue --param-file params.json
173
202
  dbcli q @dau --dry-run # show final SQL + bind values
174
203
  dbcli q @dau --no-limit # disable size guard wrap
204
+ dbcli q @analytics/revenue --param days=30 --ui # open dashboard
205
+ dbcli q @analytics/revenue --param days=30 --format html > report.html
175
206
  ```
176
207
 
177
208
  **Options:**
178
- - `--format <table|json|csv>` — output format (default: `table`)
209
+ - `--format <table|json|csv|html>` — output format (default: `table`)
210
+ - `--ui` — open the rendered HTML dashboard in the system browser (implies `--format html`; writes to a temp file then invokes `open` / `xdg-open` / `start`)
179
211
  - `--param <key=value>` — pass a parameter (repeatable)
180
212
  - `--param-file <path>` — JSON object whose keys are param names
181
213
  - `--no-limit` — skip the `SELECT * FROM (…) AS _dbcli_guard LIMIT 1000` wrap
182
214
  - `--dry-run` — print the bound SQL + values without executing
183
215
  - `--use <name>` — pick a v2 named connection
216
+ - `--recovery` — emit a `RecoveryEnvelope` on failure (see `recover`)
184
217
 
185
218
  **Permission:** query-only+
186
219
 
@@ -201,6 +234,13 @@ Each `.sql` file is plain SQL with optional YAML frontmatter inside a leading `-
201
234
  -- description: lookback window in days
202
235
  -- enum: [7, 30, 90]
203
236
  -- tags: [analytics]
237
+ -- intent: perf.slow-query # optional; consumed by `queries suggest`
238
+ -- visual: # optional; consumed by `--ui` / `--format html`
239
+ -- title: Daily Active Users
240
+ -- kpis:
241
+ -- - { label: DAU, value_column: dau, format: number }
242
+ -- charts:
243
+ -- - { type: line, title: DAU trend, x: day, y: [dau] }
204
244
  -- ---
205
245
  SELECT COUNT(DISTINCT user_id) AS dau
206
246
  FROM events
@@ -209,6 +249,8 @@ WHERE created_at > NOW() - (:days || ' days')::interval;
209
249
 
210
250
  Param placeholders use `:name`. They are rewritten to `$1, $2, …` (Postgres) or `?, ?, …` (MySQL) at execution time and passed as bind values — string interpolation is never used.
211
251
 
252
+ The `visual:` block is documented in detail under [Interactive HTML dashboard](#interactive-html-dashboard) below. Unknown / malformed fields are silently dropped at parse time; the snippet still runs and the dashboard falls back to a sortable table.
253
+
212
254
  #### Param type coercion
213
255
 
214
256
  | Declared `type` | Accepts |
@@ -299,6 +341,10 @@ dbcli queries list --tag analytics --engine postgres --format json
299
341
  dbcli queries list --source local # only personal overrides
300
342
  dbcli queries show @dau # frontmatter + SQL
301
343
  dbcli queries show @dau --format json # MCP-shaped contract
344
+ dbcli queries search slow query # fuzzy-ranked keyword search across snippets
345
+ dbcli queries search cache --engine postgres --source builtin --limit 5
346
+ dbcli queries suggest perf # browse snippets by intent prefix (v1.11+)
347
+ dbcli queries suggest perf.cache-hit --format json
302
348
 
303
349
  # Authoring
304
350
  dbcli queries new @new/sample # scaffold under .dbcli-shared/queries/
@@ -319,8 +365,10 @@ dbcli queries export @dau --output dau.sql # write snippet body to a fi
319
365
  dbcli queries export @diag/connections --engine postgres # pick a variant when multiple engines exist
320
366
  ```
321
367
 
322
- **`list` options:** `--format <table|json|csv>`, `--tag <tag>`, `--engine <postgres|mysql>`, `--source <local|shared>`
368
+ **`list` options:** `--format <table|json|csv>`, `--tag <tag>`, `--engine <postgres|mysql|redis|elasticsearch|all>`, `--source <local|shared|builtin|all>`
323
369
  **`show` options:** `--format <table|json|csv>`
370
+ **`search` options:** `--format <table|json>`, `--engine <postgres|mysql|redis|elasticsearch|all>`, `--source <local|shared|builtin|all>`, `--limit <n>` (default 10), `--include-internal` (show fuzzy ranking score). Keyword(s) are fuzzy-matched against name, description, tags, intent.
371
+ **`suggest` options:** `--format <table|json>`, `--engine <postgres|mysql|redis|elasticsearch|all>`, `--source <local|shared|builtin|all>`. Intent prefix-matched against the snippet's `intent` frontmatter field. Common intents: `perf.slow-query`, `perf.cache-hit`, `capacity.size`, `safety.connections`, `monitor.cluster-health`.
324
372
  **`new` options:** `--local`, `--edit`
325
373
  **`edit` options:** `--shared`
326
374
  **`check` options:** `--strict`, `--format <table|json|csv>`
@@ -378,11 +426,15 @@ Export query results to file or stdout.
378
426
  dbcli export "SELECT * FROM users" --format csv --output users.csv
379
427
  dbcli export "SELECT * FROM users" --format csv --output users.csv --force # Skip overwrite confirmation
380
428
  dbcli export "SELECT * FROM users" --format json | jq '.[]'
429
+ dbcli export "SELECT * FROM users" --format jsonl --output users.ndjson
430
+ dbcli export "SELECT * FROM orders" --format html --output orders.html # standalone dashboard
381
431
  ```
382
432
 
383
- **Options:** `--format <json|csv>` (required), `--output <path>`, `--force`
433
+ **Options:** `--format <json|jsonl|csv|html>` (required), `--output <path>`, `--force`, `--recovery`
384
434
  **Permission:** query-only+
385
435
 
436
+ 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
+
386
438
  ### blacklist
387
439
 
388
440
  Manage sensitive data blacklist to prevent AI access to restricted tables/columns.
@@ -439,6 +491,302 @@ dbcli status --format text # Human-readable text output
439
491
  **Output:** `permission`, `system`, `blacklist` summary, `version`
440
492
  **Permission:** query-only+
441
493
 
494
+ ### inspect
495
+
496
+ Read-only snapshot for AI agents. Never emits credentials or blacklisted values.
497
+
498
+ | Flag | Purpose |
499
+ |------|---------|
500
+ | `--format <json\|markdown>` | Output format (default `json`) |
501
+ | `--brief` | Drop sample arrays and trim suggested commands to ≤3 |
502
+ | `--for-agent` | Shortcut for `--format json --brief` |
503
+ | `--no-connect` | Skip the cheap version/object probe (no DB traffic) |
504
+ | `--probe-timeout <ms>` | Hard timeout for the version/object probe (default 1500) |
505
+
506
+ Example:
507
+
508
+ ```bash
509
+ dbcli inspect --for-agent
510
+ ```
511
+
512
+ Output schema is locked at `schemaVersion: 1`. Sections: `connection`, `permission`, `blacklist`, `objects`, `schemaCache`, `snippets`, `suggestedCommands`, `warnings`.
513
+
514
+ **Permission:** query-only+
515
+
516
+ ### report
517
+
518
+ Diagnostic report built on top of `inspect`. Reuses inspect context (connection,
519
+ permission, blacklist, snippet inventory) and additionally runs curated built-in
520
+ `@diag/*` snippets grouped into sections.
521
+
522
+ Flags:
523
+ - `--format json|markdown` (default: json)
524
+ - `--section health,capacity,perf` (default: all three)
525
+ - `--brief` — drop evidence rows; keep counts and statuses
526
+ - `--for-agent` — shortcut for `--format json --brief`
527
+ - `--no-connect` — context-only snapshot (skip diagnostics + inspect probe)
528
+ - `--per-snippet-timeout <ms>` (default 3000)
529
+ - `--max-rows-per-evidence <n>` (default 50)
530
+ - `--probe-timeout <ms>` (default 1500, inherited from inspect)
531
+
532
+ Examples:
533
+
534
+ dbcli report --format json
535
+ dbcli report --format markdown --section health,capacity
536
+ dbcli report --for-agent
537
+ dbcli report --no-connect
538
+
539
+ Boundaries:
540
+ - Read-only. Skips snippets whose required params have no default value.
541
+ - Never connects in `--no-connect` mode.
542
+ - MongoDB and no-config workspaces emit a context-only snapshot with a warning.
543
+
544
+ **Permission:** query-only+
545
+
546
+ ### guide
547
+
548
+ Deterministic next-command planner for a fixed set of database goals. Reuses
549
+ `inspect` context (cache-first) and the workspace's saved-query inventory to
550
+ emit an ordered, read-only plan that an AI agent can follow step-by-step.
551
+
552
+ Goals (fixed list):
553
+ - `slow-query` — diagnose slow queries (long-running, locks, cache, indexes).
554
+ - `capacity` — audit storage and memory.
555
+ - `health` — connections, locks, cluster status.
556
+ - `index-usage` — index effectiveness audit.
557
+ - `permissions` — review permission level, blacklist, snippet inventory.
558
+ - `schema-overview` — orient in an unfamiliar database.
559
+
560
+ Flags:
561
+ - `--format json|markdown` (default: json)
562
+ - `--brief` — drop rationale + expects fields
563
+ - `--for-agent` — shortcut for `--format json --brief`
564
+ - `--list` — list available goals and exit
565
+ - `--probe` — refresh inspect context via live probe (default: cache-first)
566
+ - `--probe-timeout <ms>` (default 1500, inherited from inspect)
567
+
568
+ Examples:
569
+
570
+ dbcli guide slow-query
571
+ dbcli guide capacity --format markdown
572
+ dbcli guide --list
573
+ dbcli guide health --for-agent
574
+ dbcli guide schema-overview --probe
575
+
576
+ Boundaries:
577
+ - Read-only. Guide plans commands; it does not execute them.
578
+ - Goal vocabulary is fixed in v1.14.0; user-supplied goals are rejected.
579
+ - Each step carries `risk: 'readonly'` in v1.14.0 (forward-compatible with v1.15.0 recovery).
580
+ - Coexists with `dbcli skill tasks plan` (template-driven). Use guide for ad-hoc goals; use task packs for repeatable workflows.
581
+
582
+ **Permission:** query-only+
583
+
584
+ ### recovery
585
+
586
+ Machine-readable error envelope. Two surfaces share one `RecoveryEnvelope`
587
+ shape (`schemaVersion: 1`):
588
+
589
+ 1. **Standalone lookup**: `dbcli recovery --code <CODE>` synthesizes an
590
+ envelope for any known recovery code without needing a real failure.
591
+ 2. **Failing-command opt-in**: pass `--recovery` to `dbcli query` or
592
+ `dbcli q`. On failure, the envelope is written to stdout as JSON, the
593
+ human stderr message is suppressed, and the process exits non-zero.
594
+
595
+ Recovery codes (fixed in v1.15.0):
596
+ - `CONFIG_MISSING` — no `.dbcli` config; run `dbcli init`.
597
+ - `CONN_REFUSED` / `CONN_AUTH_FAILED` / `CONN_TIMEOUT` / `CONN_HOST_NOT_FOUND` / `CONN_UNKNOWN` — connection failure variants.
598
+ - `PERMISSION_DENIED` — active permission level forbids the operation.
599
+ - `BLACKLIST_TABLE` / `BLACKLIST_COLUMN_WRITE` — blacklist violations.
600
+ - `SNIPPET_NOT_FOUND` / `SNIPPET_AMBIGUOUS` / `SNIPPET_PARAM_MISSING` — saved-query failures.
601
+ - `SCHEMA_CACHE_MISSING` — local schema cache missing or stale.
602
+ - `UNKNOWN` — fallback for unclassified errors.
603
+
604
+ Flags (lookup mode):
605
+ - `--code <CODE>` — required unless `--list` is set.
606
+ - `--list` — list all codes and exit.
607
+ - `--format json|markdown` (default: json).
608
+ - `--brief` — drop `rationale` + `expects` from steps.
609
+ - `--for-agent` — shortcut for `--format json --brief`.
610
+ - `--hint <text>` — bind into placeholder steps.
611
+ - `--snippet <name>` — bind snippet placeholder.
612
+ - `--table <name>` — bind table placeholder.
613
+
614
+ Examples:
615
+
616
+ dbcli recovery --code CONN_REFUSED
617
+ dbcli recovery --code BLACKLIST_TABLE --table users --format markdown
618
+ dbcli recovery --list --for-agent
619
+ dbcli query "SELECT * FROM users" --recovery
620
+ dbcli q @diag/missing --recovery
621
+
622
+ Boundaries:
623
+ - Recovery only **suggests** commands; agents (or humans) execute them. No automatic remediation in v1.15.0.
624
+ - As of v1.16.0, `--recovery` is honored on `query`, `q`, `insert`, `update`, `delete`, `export`, `schema`, and `inspect`. Other commands (`report`, `guide`, `doctor`, `migrate`, `init`, `use`, `status`, `list`, `check`, `diff`, `plan`, `shell`, `blacklist`, `completion`, `upgrade`, `skill`) keep their existing error behavior.
625
+ - `dbcli inspect --require-schema-cache` throws `SCHEMA_CACHE_MISSING` when the active SQL connection has no usable schema cache. Combine with `--recovery` for the structured envelope.
626
+ - `BLACKLIST_COLUMN_WRITE` and `PERMISSION_DENIED` envelopes prepend a `risk: 'dry-run'` step (e.g. `dbcli insert <table> --dry-run`) when the failing operation was an INSERT / UPDATE / DELETE.
627
+ - Recovery steps reuse the v1.14.0 `GuideStep` shape, including the full `risk` enum (`readonly` / `dry-run` / `write` / `unknown`).
628
+
629
+ **Permission:** n/a
630
+
631
+ ### recover
632
+
633
+ (v1.17.0+) Inspect or apply the last recovery plan saved by `--recovery`.
634
+
635
+ | Flag | Purpose | Default |
636
+ |---|---|---|
637
+ | `--apply` | Execute the saved plan under risk gating. | off (inspect only) |
638
+ | `--from <path>` | Read the envelope from this file instead of `.dbcli/last-recovery.json`. Accepts raw `RecoveryEnvelope` or `SavedRecoveryEnvelope`. | — |
639
+ | `--allow-write <tier>` | Open the risk gate. Values: `readonly-cmd` (local-side writes) \| `write-cmd` (database writes). | `none` |
640
+ | `--no-verify` | Skip the verify step appended after a successful `--apply`. | off (verify runs by default) |
641
+ | `--format <format>` | `markdown` \| `json`. | `markdown` for inspect, `json` for `--apply` |
642
+
643
+ #### Plan source resolution
644
+
645
+ 1. `--from <path>` if provided. The file must be either a raw `RecoveryEnvelope` or a `SavedRecoveryEnvelope` wrapper. When the file is a `SavedRecoveryEnvelope`, its `cwd` is reused for child-process execution. Strict zod validation; malformed → exit 2 with structured reason.
646
+ 2. Otherwise, `.dbcli/last-recovery.json` (auto-saved on every recovery emission). Validated with the same schema; missing fields, unknown `error.code`, or `cwd` that no longer exists → exit 2.
647
+ 3. Otherwise, exits 2 with `No recovery plan available. Run a command with --recovery to generate one, or pass --from <file>.`
648
+
649
+ #### Code-owned tier (trust boundary)
650
+
651
+ `--apply` derives the canonical execution tier from the per-`error.code` allowlist after parsing argv, **not** from the envelope's `risk` / `dbWrite` / `interactive` fields. Envelope hints can only widen safety (skip more steps); they cannot escalate execution.
652
+
653
+ | Allowlist tier | Meaning | Example commands |
654
+ |---|---|---|
655
+ | `readonly` | local read-only | `dbcli inspect`, `dbcli doctor`, `dbcli blacklist list`, `dbcli schema <table>` |
656
+ | `dry-run` | write subcommand invoked with `--dry-run` | `dbcli update orders --where id=1 --dry-run`, `dbcli q @x --dry-run` |
657
+ | `local-write` | writes local config / cache / blacklist | `dbcli blacklist remove <table>`, `dbcli use <name>`, `dbcli schema --refresh` |
658
+ | `db-write` | mutates the connected database | `dbcli update orders --where id=1 --set …` (no `--dry-run`), `dbcli q @x` (no `--dry-run`) |
659
+ | `interactive` | requires TTY | `dbcli init`, `dbcli init --force` |
660
+
661
+ `insert` / `update` / `delete` / `q` are tier `dry-run` only when argv contains `--dry-run`; otherwise they are tier `db-write` regardless of envelope `risk` claim.
662
+
663
+ #### Risk gate matrix
664
+
665
+ | Allowlist tier | Default | `--allow-write=readonly-cmd` | `--allow-write=write-cmd` |
666
+ |---|---|---|---|
667
+ | `readonly` | run | run | run |
668
+ | `dry-run` | run | run | run |
669
+ | `local-write` | `skipped:risk` | run | run |
670
+ | `db-write` | `skipped:risk` | `skipped:risk` | run |
671
+ | `interactive` | `skipped:interactive` | `skipped:interactive` | `skipped:interactive` |
672
+ | unresolved placeholder in `command` | `skipped:placeholder` | `skipped:placeholder` | `skipped:placeholder` |
673
+ | command fails parse / allowlist | `skipped:unsafe-command` | `skipped:unsafe-command` | `skipped:unsafe-command` |
674
+
675
+ Precedence: envelope `interactive: true` > `placeholder` > `unsafe-command` > allowlist `interactive` > tier-based gating.
676
+
677
+ #### Exit codes
678
+
679
+ | Code | Condition |
680
+ |---|---|
681
+ | 0 | At least one step ran successfully and no step failed. |
682
+ | 1 | A step exited non-zero (fail-fast); see `stoppedAt`. |
683
+ | 2 | Envelope missing or malformed (failed schema validation, or saved `cwd` missing). |
684
+ | 3 | Every step was skipped — open `--allow-write` or fill placeholders. |
685
+
686
+ #### Auto-saved envelope
687
+
688
+ Every command that emits a `RecoveryEnvelope` (`query`, `q`, `insert`, `update`, `delete`, `export`, `schema`, `inspect` — all with `--recovery`) atomically writes the envelope to `.dbcli/last-recovery.json`. The wrapper carries `schemaVersion`, `savedAt`, a sanitized `command` summary, the workspace `cwd`, and the envelope itself. SQL text and `--where` / `--set` / `--data` / `--param` values are redacted as `<sql>` or `<redacted>`. `.dbcli/` is gitignored.
689
+
690
+ #### Verification (P4)
691
+
692
+ Each `RecoveryEnvelope` now carries an optional `verify: GuideStep` (always
693
+ `risk: 'readonly'`, never carries placeholders). `dbcli recover --apply` runs
694
+ the verify step after the main plan, only when `finalStatus === 'ok'` and
695
+ `--no-verify` is not set.
696
+
697
+ | Recovery code | Verify command | Heuristic |
698
+ |---|---|---|
699
+ | CONFIG_MISSING | `dbcli inspect --no-connect --format json` | `connection.name` truthy → passed |
700
+ | CONN_REFUSED / CONN_TIMEOUT / CONN_UNKNOWN / CONN_AUTH_FAILED / CONN_HOST_NOT_FOUND | `dbcli doctor --format json` | exit 0 → passed |
701
+ | PERMISSION_DENIED | `dbcli inspect --for-agent` | exit 0 → passed |
702
+ | BLACKLIST_TABLE | `dbcli inspect --for-agent` | exit 0 → passed |
703
+ | BLACKLIST_COLUMN_WRITE | `dbcli inspect --for-agent` | exit 0 → passed |
704
+ | SNIPPET_NOT_FOUND / SNIPPET_AMBIGUOUS / SNIPPET_PARAM_MISSING | `dbcli queries list --format json` | exit 0 → passed |
705
+ | SCHEMA_CACHE_MISSING | `dbcli inspect --format json` | `schemaCache.available === true` → passed |
706
+ | UNKNOWN | `dbcli doctor --format json` | exit 0 → passed |
707
+
708
+ `verifyStatus` values:
709
+
710
+ - `passed` — heuristic confirmed.
711
+ - `failed` — verifier exited non-zero or timed out.
712
+ - `indeterminate` — verifier exited 0 but expected shape not present, or the
713
+ step was gated (placeholder / unsafe-command); agents should re-check.
714
+
715
+ Exit codes are unchanged — `verifyStatus` is signal, not gate.
716
+
717
+ **Schema additions.** `RecoveryEnvelope.verify?: GuideStep` is additive (no
718
+ `schemaVersion` bump). v1.16 consumers ignore the field.
719
+
720
+ #### Multi-turn `--next` (P2)
721
+
722
+ `dbcli recover --next` returns the single next step in a saved recovery plan,
723
+ given which step the agent just executed and the result of that step. v1 walks
724
+ the plan linearly; future codes may branch on `prevResult.stdoutSummary`
725
+ deterministically.
726
+
727
+ | Flag | Required | Description |
728
+ |---|---|---|
729
+ | `--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]`. |
731
+ | `--result <value>` | yes | JSON `StepResultSummary` (inline) or `@<path>` to read from a file. |
732
+ | `--from <path>` | no | Override the auto-saved envelope. |
733
+ | `--format <fmt>` | no | `json` (default) or `markdown`. |
734
+
735
+ `--next` and `--apply` cannot be combined. `--allow-write` and `--no-verify`
736
+ are silently ignored under `--next` (no execution, no verification).
737
+
738
+ **`StepResultSummary` shape**
739
+
740
+ ```ts
741
+ interface StepResultSummary {
742
+ status: 'ok' | 'failed' | 'skipped'
743
+ exitCode?: number
744
+ stdoutSummary?: string // last 4 KB; longer rejected
745
+ stderrSummary?: string // last 4 KB; longer rejected
746
+ }
747
+ ```
748
+
749
+ `@<path>` resolves relative to the dbcli invocation cwd. File whole-size cap is
750
+ 64 KB; per-field 4 KB cap still applies.
751
+
752
+ **`NextResult` shape (output)**
753
+
754
+ ```ts
755
+ interface NextResult {
756
+ schemaVersion: 1
757
+ kind: 'step' | 'done'
758
+ source: { kind: 'auto' | 'from'; path: string }
759
+ errorCode: RecoveryCode
760
+ cursor: number // step.order when kind='step'; totalSteps when 'done'
761
+ totalSteps: number
762
+ step?: GuideStep // present iff kind='step'
763
+ }
764
+ ```
765
+
766
+ **Exit codes**
767
+
768
+ | Exit | Condition |
769
+ |---|---|
770
+ | 0 | Returned a step or `done`. |
771
+ | 2 | Envelope missing/malformed; `--after-step` missing/out-of-range; `--result` missing/malformed; `--next` combined with `--apply`. |
772
+
773
+ **Examples**
774
+
775
+ ```bash
776
+ # Walk a 3-step plan to completion
777
+ dbcli recover --next --after-step 1 --result '{"status":"ok"}' # → step 2
778
+ dbcli recover --next --after-step 2 --result '{"status":"ok"}' # → step 3
779
+ dbcli recover --next --after-step 3 --result '{"status":"ok"}' # → done
780
+
781
+ # Result read from file (when stdout is large)
782
+ dbcli recover --next --after-step 1 --result @/tmp/r1.json
783
+
784
+ # Markdown for human inspection
785
+ dbcli recover --next --after-step 1 --result '{"status":"ok"}' --format markdown
786
+ ```
787
+
788
+ **Permission:** n/a (always-allowed lookup; child processes inherit the active permission level).
789
+
442
790
  ### doctor
443
791
 
444
792
  Run diagnostic checks on environment, configuration, connection, and data.
@@ -554,6 +902,32 @@ dbcli migrate drop-enum status --execute --force
554
902
 
555
903
  **AI agent note:** Always use dry-run first (no `--execute`) to preview generated SQL. Only add `--execute` after confirming the SQL is correct. For DROP operations, both `--execute` and `--force` are required.
556
904
 
905
+ ### skill
906
+
907
+ Emit `SKILL.md` (and the companion `reference.md`) to stdout, a file, or one of
908
+ four AI-agent platform directories. The skill is the source of truth that lets
909
+ Claude Code / Gemini / Copilot / Cursor know how to drive dbcli safely.
910
+
911
+ ```bash
912
+ dbcli skill # print SKILL.md to stdout
913
+ dbcli skill --output ./SKILL.md # write to a file (no platform install)
914
+ dbcli skill --install claude # install to ~/.claude/skills/dbcli/
915
+ dbcli skill --install gemini # install to ~/.gemini/skills/dbcli/
916
+ dbcli skill --install copilot # install to .github/skills/dbcli/ (repo-local)
917
+ dbcli skill --install cursor # install to .cursor/skills/dbcli/ (repo-local)
918
+ ```
919
+
920
+ **Options:**
921
+ - `--install <platform>` — `claude` | `gemini` | `copilot` | `cursor`. Writes `SKILL.md` plus `reference.md` next to it so the agent gets progressive disclosure.
922
+ - `--output <path>` — write `SKILL.md` to a file instead of stdout. Does not install `reference.md`.
923
+
924
+ **Notes:**
925
+ - Both files come straight from `assets/SKILL.md` + `assets/reference.md` inside the dbcli package — no runtime rendering. Keep these in sync when shipping a release.
926
+ - `claude` / `gemini` install paths are user-global; `copilot` / `cursor` are repo-local under `.github/` / `.cursor/`.
927
+ - Re-running `--install` overwrites the existing skill atomically; no prompt.
928
+
929
+ **Permission:** n/a.
930
+
557
931
  ### skill tasks (Agent Task Packs)
558
932
 
559
933
  ```bash
@@ -581,6 +955,108 @@ Task storage layers:
581
955
  Higher tiers override lower tiers by task name. Task name is derived from the
582
956
  file path under the tier root (e.g. `diag/inspect.md` → `diag/inspect`).
583
957
 
958
+ ## Interactive HTML dashboard
959
+
960
+ `query`, `q`, and `export` can render results as a single, fully self-contained
961
+ HTML file backed by a bundled React + Recharts template. The template lives at
962
+ `assets/ui-template.html` and is installed alongside the binary; no external
963
+ network, CDN, or runtime is required to view the report.
964
+
965
+ ### Entry points
966
+
967
+ | Command form | Behaviour |
968
+ |--------------|-----------|
969
+ | `dbcli query "<sql>" --ui` | Render to a temp file under `$TMPDIR/dbcli-query-<ts>.html` and open with `open` / `xdg-open` / `start`. |
970
+ | `dbcli q @<name> --ui` | Same, with snippet metadata (`name`, `description`, `visual:` block). |
971
+ | `dbcli query "<sql>" --format html` | Print HTML to stdout (pipe, redirect, attach). |
972
+ | `dbcli q @<name> --format html` | Same, snippet-aware. |
973
+ | `dbcli export "<sql>" --format html --output report.html` | Write HTML to an explicit path; respects `--force` / overwrite confirmation. |
974
+
975
+ `--ui` is a convenience flag — it implies `--format html` and then opens the
976
+ file. `--ui` and `--format` are mutually compatible; passing both is allowed and
977
+ behaves as `--ui`.
978
+
979
+ ### Data injection contract
980
+
981
+ The template ships with a single placeholder, `/*DBCLI_PAYLOAD*/`, which dbcli
982
+ replaces with:
983
+
984
+ ```js
985
+ window.__DBCLI_PAYLOAD__ = { "meta": {...}, "rows": [...] };
986
+ ```
987
+
988
+ Hardening rules applied before injection:
989
+
990
+ - Payload is `JSON.stringify(...)`-encoded.
991
+ - Every `<` is replaced with `<` so a malicious row containing `</script>`
992
+ cannot terminate the inline script tag.
993
+ - Blacklist redaction (`dbcli blacklist`) runs **before** the formatter — masked
994
+ columns never reach the dashboard.
995
+ - The replacement uses a function callback (`html.replace(..., () => injection)`)
996
+ so `$&`-style backreferences in the payload are not interpreted.
997
+
998
+ ### `meta` shape
999
+
1000
+ `meta` is the `SavedQueryMeta` object (see `dbcli queries show @<name> --format json`):
1001
+
1002
+ ```jsonc
1003
+ {
1004
+ "name": "Revenue Trend", // display title
1005
+ "key": "@analytics/revenue", // snippet key, or "raw-sql" / "export"
1006
+ "description": "...", // free text (SQL preview for raw query)
1007
+ "params": [...], // ParamSpec[]
1008
+ "tags": ["analytics"],
1009
+ "intent": "perf.slow-query",
1010
+ "visual": { ... } // optional, see below
1011
+ }
1012
+ ```
1013
+
1014
+ For raw `query` / `export` invocations, `meta.params` is `[]` and
1015
+ `meta.visual` is absent — the dashboard renders a sortable / filterable table.
1016
+
1017
+ ### `visual:` block (snippet frontmatter)
1018
+
1019
+ ```yaml
1020
+ visual:
1021
+ title: Revenue (last :days days) # optional override of meta.name
1022
+ kpis:
1023
+ - label: Total Revenue
1024
+ value_column: total_revenue # must exist in result rows
1025
+ format: currency # currency | number | percent (optional)
1026
+ - label: Orders
1027
+ value_column: order_count
1028
+ format: number
1029
+ charts:
1030
+ - type: line # line | bar | area | pie | scatter
1031
+ title: Daily Revenue
1032
+ x: day # column for X axis
1033
+ y: [revenue] # 1..N columns for series
1034
+ - type: bar
1035
+ title: By Channel
1036
+ x: channel
1037
+ y: [revenue, refunds]
1038
+ ```
1039
+
1040
+ Parser behaviour (`src/core/saved-queries/parser.ts::normaliseVisual`):
1041
+
1042
+ - The block is **optional**. Missing → table-only render.
1043
+ - Items missing required fields (`kpi.label` + `kpi.value_column`, or
1044
+ `chart.type` + `chart.x` + `chart.y[]`) are silently dropped.
1045
+ - Unknown `format` / `type` values are forwarded as strings; the dashboard
1046
+ decides how to render them (unknown chart types fall back gracefully).
1047
+ - The snippet still executes as a normal SQL/DSL query — `visual:` only affects
1048
+ the HTML renderer.
1049
+
1050
+ ### Limitations
1051
+
1052
+ - The dashboard is read-only; there is no in-page editor or re-run button.
1053
+ - Raw `query` / `export` HTML output never shows KPIs or charts (no snippet
1054
+ metadata is available). Use `dbcli q @<name>` for the charted view.
1055
+ - Engine support follows the underlying command: SQL, MongoDB (`--collection`),
1056
+ Redis, and Elasticsearch (`--collection`) all render through the same template.
1057
+ - Very wide / very long result sets render as a single client-side table; for
1058
+ >10k rows prefer `--format csv` / `--format jsonl` and a downstream tool.
1059
+
584
1060
  ## MongoDB Support
585
1061
 
586
1062
  MongoDB connections use a JSON-based query model instead of SQL. Treat MongoDB support as a narrower document-database path, not as a full SQL feature equivalent.