@carllee1983/dbcli 1.20.2 → 1.25.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.
@@ -161,6 +161,40 @@ dbcli query "SELECT * FROM orders" --format html > orders.html # pipe to stdou
161
161
  > - Hits are flattened: each result row contains `_id` plus dotted-path fields from `_source`. Pass `--format json` to keep nested structures readable.
162
162
  > - Query-only mode caps at 1000 hits; `--no-limit` is internally capped at 10 000 (use saved searches / `search_after` for deeper pagination).
163
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
+
164
198
  ### plan
165
199
 
166
200
  Static SQL risk analyzer. Classifies a statement into the same permission tiers
@@ -480,13 +514,20 @@ dbcli export "SELECT * FROM users" --format csv --output users.csv --force # Sk
480
514
  dbcli export "SELECT * FROM users" --format json | jq '.[]'
481
515
  dbcli export "SELECT * FROM users" --format jsonl --output users.ndjson
482
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
483
522
  ```
484
523
 
485
- **Options:** `--format <json|jsonl|csv|html>` (required), `--output <path>`, `--force`, `--recovery`
486
- **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.
487
526
 
488
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.
489
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
+
490
531
  ### blacklist
491
532
 
492
533
  Manage sensitive data blacklist to prevent AI access to restricted tables/columns.
@@ -531,6 +572,50 @@ dbcli diff --against before.json --format json
531
572
  **Options:** `--snapshot <path>`, `--against <path>`, `--format <json|table>`
532
573
  **Permission:** query-only+
533
574
 
575
+ ### snapshot
576
+
577
+ Capture a **result fingerprint** of a query (not schema): `rowCount` plus per-column
578
+ aggregates (null/distinct counts, min/max/sum, an order-independent checksum) and a
579
+ top-level `resultChecksum`. Blacklisted columns are masked at the source by QueryExecutor,
580
+ so the fingerprint is safe to store and share. Use it as a baseline for `assert --against`.
581
+
582
+ ```bash
583
+ dbcli snapshot "SELECT * FROM orders WHERE created_at >= '2026-05-01'" # → .dbcli/snapshots/snap-<timestamp>.json
584
+ dbcli snapshot @analytics/daily-revenue --out base.json # saved query → explicit path
585
+ dbcli snapshot "SELECT status, count(*) FROM orders GROUP BY status" --stdout
586
+ dbcli snapshot "SELECT * FROM orders" --rows --out full.json # also store masked rows
587
+ ```
588
+
589
+ **Options:** `--out <path>` (default `.dbcli/snapshots/snap-<timestamp>.json`), `--rows`, `--stdout`, `--format <json|table>`, `--no-limit`
590
+ **Engines:** SQL only (PostgreSQL / MySQL / MariaDB)
591
+ **Permission:** query-only+
592
+
593
+ ### assert
594
+
595
+ Assert an **invariant** on a query result. Exits `1` on failure (so it composes in
596
+ scripts / CI) unless `--no-fail` is given. Three modes (combinable):
597
+
598
+ - `--expect <condition>` — inline check against the result:
599
+ - `rows > 0` / `rows == 1` … (row count vs operators `> >= < <= == !=`)
600
+ - `value == 5000` / `value == "done"` (single-cell result; project to one column)
601
+ - `col:email not null` · `col:id unique` · `col:amount between 0 and 100` · `col:age >= 18`
602
+ - `--vs <query> --compare rows|value` — reconcile against a second query (cross-check totals/counts).
603
+ - `--against <snapshot> --tolerance <pct>` — compare the current result fingerprint to a saved snapshot. `tolerance 0` requires an exact (order-independent) checksum match; `tolerance 0.01` allows ±1% drift on rowCount and each numeric column sum.
604
+
605
+ ```bash
606
+ dbcli assert "SELECT count(*) FROM orders" --expect "value > 0"
607
+ dbcli assert "SELECT * FROM orders WHERE total < 0" --expect "rows == 0" # no negative totals
608
+ dbcli assert "SELECT email FROM users" --expect "col:email not null"
609
+ dbcli assert "SELECT sum(amount) FROM ledger_a" --vs "SELECT sum(amount) FROM ledger_b" --compare value
610
+ dbcli assert "SELECT * FROM orders" --against base.json --tolerance 0.01
611
+ dbcli assert "SELECT count(*) FROM orders" --expect "value > 100" --no-fail # report only, exit 0
612
+ ```
613
+
614
+ **Options:** `--expect <condition>`, `--vs <query>`, `--compare <rows|value>` (default `value`), `--against <path>`, `--tolerance <pct>` (default `0`), `--no-fail`, `--format <json|table>`
615
+ **Output:** `AssertVerdict` = `{ pass, checks: [{ name, expected, actual, pass }] }`
616
+ **Engines:** SQL only (PostgreSQL / MySQL / MariaDB)
617
+ **Permission:** query-only+
618
+
534
619
  ### status
535
620
 
536
621
  Show current configuration status (safe for AI agents, no credentials exposed).
@@ -554,6 +639,8 @@ Read-only snapshot for AI agents. Never emits credentials or blacklisted values.
554
639
  | `--for-agent` | Shortcut for `--format json --brief` |
555
640
  | `--no-connect` | Skip the cheap version/object probe (no DB traffic) |
556
641
  | `--probe-timeout <ms>` | Hard timeout for the version/object probe (default 1500) |
642
+ | `--require-schema-cache` | Throw `SCHEMA_CACHE_MISSING` (recovery code) when the active SQL connection has no usable schema cache |
643
+ | `--recovery` | On failure, emit a structured `RecoveryEnvelope` to stdout |
557
644
 
558
645
  Example:
559
646
 
@@ -561,7 +648,14 @@ Example:
561
648
  dbcli inspect --for-agent
562
649
  ```
563
650
 
564
- Output schema is locked at `schemaVersion: 1`. Sections: `connection`, `permission`, `blacklist`, `objects`, `schemaCache`, `snippets`, `suggestedCommands`, `warnings`.
651
+ Output schema is locked at `schemaVersion: 1`. Sections: `connection`, `permission`, `blacklist`, `objects`, `schemaCache`, `snippets`, `suggestedCommands`, `hints` **(v1.23)**, `warnings`.
652
+
653
+ **`suggestedCommands` (context-aware, v1.23)** — a three-tier weighted list:
654
+ 1. *Bootstrap* — always-safe orientation commands (`blacklist list`, `schema <table>`, ...).
655
+ 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.
656
+ 3. *Discovery* — broader exploration commands.
657
+
658
+ **`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).
565
659
 
566
660
  **Permission:** query-only+
567
661
 
@@ -633,6 +727,30 @@ Boundaries:
633
727
 
634
728
  **Permission:** query-only+
635
729
 
730
+ #### guide missing-index-for (v1.23)
731
+
732
+ A single-query composite-index advisor. Parses one `SELECT`, combines a real
733
+ `EXPLAIN` plan with existing indexes, and emits index candidates each carrying a
734
+ `confidence` (`high` / `medium` / `low`) and a `reason`. Read-only (EXPLAIN +
735
+ index introspection only). MySQL/MariaDB + PostgreSQL.
736
+
737
+ ```bash
738
+ dbcli guide missing-index-for "SELECT ... FROM betting_logs b JOIN hoster_machines hm ON ..."
739
+ dbcli guide missing-index-for @analytics/live-summary # @saved-query
740
+ dbcli guide missing-index-for "..." --format json # yaml (default) | json | markdown
741
+ dbcli guide missing-index-for "..." --min-confidence medium # drop candidates below low|medium|high
742
+ ```
743
+
744
+ **Options:** `--format <yaml|json|markdown>` (default `yaml`), `--min-confidence <low|medium|high>`.
745
+
746
+ Behaviour:
747
+ - Detects existing-index collisions (a single-column index that can be extended into a composite).
748
+ - Functional/expression columns (e.g. `DATE(settled_at)`) and SQL it cannot parse are reported under `warnings`, never as recommendations.
749
+ - Single `SELECT` only — no INSERT/UPDATE/DELETE, stored procedures, or view bodies.
750
+ - Dialects beyond node-sql-parser support fall back to EXPLAIN-only heuristics.
751
+
752
+ **Permission:** query-only+
753
+
636
754
  ### recovery
637
755
 
638
756
  Machine-readable error envelope. Two surfaces share one `RecoveryEnvelope`
@@ -1005,6 +1123,11 @@ Inside the shell:
1005
1123
  - Multi-line SQL: keeps accumulating until `;` is found
1006
1124
  - History persists across sessions (~/.dbcli_history)
1007
1125
 
1126
+ The REPL flavor depends on the active engine: SQL engines and MongoDB use the
1127
+ form above; **Redis** opens a single-line command REPL (see [Redis › Interactive
1128
+ shell](#interactive-shell)); **Elasticsearch** opens a Kibana Dev Tools-style
1129
+ REPL (v1.22, see [Elasticsearch › Interactive shell](#interactive-shell-v122)).
1130
+
1008
1131
  ### migrate
1009
1132
 
1010
1133
  Schema DDL operations. **All commands default to dry-run** — use `--execute` to actually run the SQL. Destructive operations (DROP) also require `--force`.
@@ -1059,26 +1182,28 @@ dbcli migrate drop-enum status --execute --force
1059
1182
 
1060
1183
  ### skill
1061
1184
 
1062
- Emit `SKILL.md` (and the companion `reference.md`) to stdout, a file, or one of
1063
- four AI-agent platform directories. The skill is the source of truth that lets
1064
- Claude Code / Gemini / Copilot / Cursor know how to drive dbcli safely.
1185
+ Emit `SKILL.md` (and the companion `reference.md`) to stdout, a file, or an
1186
+ AI-agent platform directory. The skill is the source of truth that lets
1187
+ Claude Code / Gemini / Antigravity / Copilot / Cursor know how to drive dbcli safely.
1065
1188
 
1066
1189
  ```bash
1067
1190
  dbcli skill # print SKILL.md to stdout
1068
1191
  dbcli skill --output ./SKILL.md # write to a file (no platform install)
1069
1192
  dbcli skill --install claude # install to ~/.claude/skills/dbcli/
1070
- dbcli skill --install gemini # install to ~/.gemini/skills/dbcli/
1193
+ dbcli skill --install gemini # install to ~/.gemini/skills/dbcli/ (being phased out)
1194
+ dbcli skill --install antigravity # install to ~/.gemini/antigravity-cli/skills/dbcli/
1071
1195
  dbcli skill --install copilot # install to .github/skills/dbcli/ (repo-local)
1072
1196
  dbcli skill --install cursor # install to .cursor/skills/dbcli/ (repo-local)
1073
1197
  ```
1074
1198
 
1075
1199
  **Options:**
1076
- - `--install <platform>` — `claude` | `gemini` | `copilot` | `cursor`. Writes `SKILL.md` plus `reference.md` next to it so the agent gets progressive disclosure.
1200
+ - `--install <platform>` — `claude` | `gemini` | `antigravity` | `copilot` | `cursor` | `codex` | `windsurf`. Writes `SKILL.md` plus `reference.md` next to it so the agent gets progressive disclosure.
1077
1201
  - `--output <path>` — write `SKILL.md` to a file instead of stdout. Does not install `reference.md`.
1078
1202
 
1079
1203
  **Notes:**
1080
1204
  - 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.
1081
- - `claude` / `gemini` install paths are user-global; `copilot` / `cursor` are repo-local under `.github/` / `.cursor/`.
1205
+ - `claude` / `gemini` / `antigravity` install paths are user-global; `copilot` / `cursor` are repo-local under `.github/` / `.cursor/`.
1206
+ - `gemini` (Gemini CLI) is retained for now but is being phased out in favour of `antigravity` (Antigravity CLI), Google's successor terminal agent.
1082
1207
  - Re-running `--install` overwrites the existing skill atomically; no prompt.
1083
1208
 
1084
1209
  **Permission:** n/a.
@@ -1099,6 +1224,16 @@ dbcli skill tasks plan diagnose-slow-query --param query="..." --format json
1099
1224
  - **show:** prints the full task definition (frontmatter + Agent Notes). Use `--format json` for an agent-friendly contract.
1100
1225
  - **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.
1101
1226
 
1227
+ **Builtin packs:** `diagnose-slow-query` and **(v1.23)** `analyze-table-perf` —
1228
+ a read-only (`plan-only`) pack taking a required `table` parameter that walks
1229
+ `blacklist list` → `schema <table> --format json` → `guide index-usage --format json`.
1230
+ `dbcli inspect` suggests `analyze-table-perf` automatically for the hottest table
1231
+ in recent audit activity.
1232
+
1233
+ ```bash
1234
+ dbcli skill tasks plan analyze-table-perf --param table=betting_logs --format json
1235
+ ```
1236
+
1102
1237
  Task storage layers:
1103
1238
 
1104
1239
  | Source | Path | Notes |
@@ -1531,19 +1666,19 @@ dbcli delete orders --where '{"status":"cancelled"}' --force
1531
1666
 
1532
1667
  ## Redis Support
1533
1668
 
1534
- Redis connections speak Redis commands rather than SQL. The adapter uses the `ioredis` driver and exposes a narrow, permission-gated surface.
1669
+ 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.
1535
1670
 
1536
- **Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
1671
+ **Supported commands:** `init`, `use`, `list`, `schema`, `query`, `shell`, `status`, `doctor`, `upgrade`, `completion`
1537
1672
 
1538
1673
  **Saved queries:** `q` is supported for read-only Redis commands (see "Redis snippets" below).
1539
1674
 
1540
- **Not supported (exit with error or unsupported error):** `insert`, `update`, `delete`, `export`, `check`, `diff`, `migrate`, `shell`. For writes, run the equivalent Redis command via `query` — the same permission gate applies.
1675
+ **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.
1541
1676
 
1542
1677
  ### Connection and configuration
1543
1678
 
1544
1679
  - Required fields: `system: redis`, `host`, `port`. `password` and `database` are optional.
1545
1680
  - `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.
1546
- - `connection.timeout` (ms, default 5000) maps to ioredis's `connectTimeout`.
1681
+ - `connection.timeout` (ms, default 5000) maps to the client's `connectionTimeout`.
1547
1682
 
1548
1683
  ### Permission classification
1549
1684
 
@@ -1590,22 +1725,83 @@ dbcli query "DEL temp:lock"
1590
1725
  dbcli query "HDEL user:42 lastLogin"
1591
1726
  ```
1592
1727
 
1728
+ ### Size guard (`query --no-limit` / shell `.no-limit`)
1729
+
1730
+ The adapter rewrites unbounded reads before dispatch and truncates oversized replies after:
1731
+
1732
+ | Strategy | Commands | Behavior |
1733
+ |----------|----------|----------|
1734
+ | inject/cap `COUNT` | `SCAN`, `HSCAN`, `SSCAN`, `ZSCAN` | adds `COUNT 1000` when absent; caps a larger `COUNT` to 1000 |
1735
+ | clamp `stop` | `LRANGE`, `ZRANGE`, `ZREVRANGE` | rewrites `stop` so the span ≤ 1000 (`-1` becomes `start+999`) |
1736
+ | inject/cap `LIMIT` | `ZRANGEBYSCORE` | appends `LIMIT 0 1000` when absent; caps a larger count |
1737
+ | client truncate | `HGETALL`, `HKEYS`, `HVALS`, `SMEMBERS`, `KEYS` | keeps the first 1000 entries |
1738
+
1739
+ 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.
1740
+
1741
+ ### Blacklist enforcement
1742
+
1743
+ Blacklist rules are enforced as **Redis-native key globs** (`*`, `?`, `[abc]`, `[a-z]`):
1744
+
1745
+ ```bash
1746
+ dbcli blacklist add 'secrets:*' # register a key-glob rule
1747
+ dbcli query "GET secrets:api_key" # → BlacklistRejection (exit non-zero)
1748
+ dbcli query "MGET safe:k secrets:api" # → rejected (any matching key fails the whole command)
1749
+ dbcli query "KEYS secrets:*" # → rejected (pattern overlaps a rule)
1750
+ dbcli query "KEYS *" # → returns only non-blacklisted keys
1751
+ ```
1752
+
1753
+ Rejections are written to the audit log with `success: false` and `metadata.rejection_reason: 'blacklist'` + `matched_pattern`.
1754
+
1755
+ ### Value / hash-field masking (v1.22)
1756
+
1757
+ Where the key-glob blacklist *rejects*, masking instead *redacts*: a matched read still
1758
+ runs, but the sensitive value comes back as `[REDACTED]` so an agent can use the command
1759
+ without ever seeing it. Add an optional `redis.mask` block to `.dbcli`:
1760
+
1761
+ ```yaml
1762
+ redis:
1763
+ mask:
1764
+ - keyPattern: 'session:*' # whole value redacted on read
1765
+ - keyPattern: 'user:*'
1766
+ fields: [password, token] # only these hash fields redacted
1767
+ ```
1768
+
1769
+ - Applies on reads: `GET`, `GETRANGE`, `HGETALL`, `HGET`, `HMGET`, `HVALS`.
1770
+ - A rule without `fields` redacts the entire value; with `fields` only the named hash fields are redacted.
1771
+ - Masking and key-glob rejection coexist, and **rejection always wins over masking** — a key that matches a blacklist rule is rejected, never merely masked.
1772
+
1773
+ ### Interactive shell
1774
+
1775
+ `dbcli shell` on a Redis connection opens a single-line REPL:
1776
+
1777
+ ```text
1778
+ $ dbcli --use local-redis shell
1779
+ Redis shell: single-line commands; SCAN/LRANGE auto-capped at 1000. Type `.no-limit on` to bypass (unsafe).
1780
+ redis> SCAN 0 # wire args become: SCAN 0 COUNT 1000 (REDIS_SIZE_REWRITE)
1781
+ redis> HGETALL bighash # >1000 fields → kept 1000 (REDIS_SIZE_TRUNCATE)
1782
+ redis> .no-limit on # bypass size guard for this session
1783
+ redis> GET secrets:api_key # → REDIS_BLACKLIST / BlacklistRejection if blacklisted
1784
+ redis> .exit
1785
+ ```
1786
+
1787
+ Tab completion offers Redis command names and known key prefixes; history persists to `~/.dbcli_history`.
1788
+
1593
1789
  ### Limitations
1594
1790
 
1595
1791
  - No `--dry-run` for writes — Redis commands execute immediately. Pair writes with a confirming read (`GET`, `HGETALL`, `EXISTS`).
1596
1792
  - No transaction wrapping (`MULTI`/`EXEC`). Submit one command at a time.
1597
1793
  - `KEYS` requires `admin`. Prefer `SCAN` for routine work.
1598
- - Blacklist rules are not enforced for Redis (there is no concept of "column" / "table" the validator can map). Be careful with sensitive key prefixes.
1794
+ - Blacklist enforcement covers **keys** (Redis-native globs); value / hash-field **masking** is available via the `redis.mask` config block (v1.22).
1599
1795
 
1600
1796
  ## Elasticsearch Support
1601
1797
 
1602
1798
  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.
1603
1799
 
1604
- **Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
1800
+ **Supported commands:** `init`, `use`, `list`, `schema`, `query`, `export` (v1.22), `shell` (v1.22), `status`, `doctor`, `upgrade`, `completion`
1605
1801
 
1606
1802
  **Saved queries:** `q` is supported for ES JSON DSL bodies (see "Elasticsearch snippets" below).
1607
1803
 
1608
- **Not supported (use external tooling):** `insert`, `update`, `delete`, `export`, `check`, `diff`, `migrate`, `shell`. The permission classifier already understands `_doc` / `_update` / `_bulk` so future write surfaces can be wired in without changing tiers.
1804
+ **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.
1609
1805
 
1610
1806
  ### Connection and configuration
1611
1807
 
@@ -1658,6 +1854,38 @@ dbcli query '{"size":0,"aggs":{"by_status":{"terms":{"field":"status.keyword"}}}
1658
1854
  dbcli query 'status:active AND amount:>100' --index orders --limit 100
1659
1855
  ```
1660
1856
 
1857
+ ### Export (v1.22)
1858
+
1859
+ `dbcli export` supports two shapes on an ES connection:
1860
+
1861
+ ```bash
1862
+ # (a) search DSL + --index → export the hits
1863
+ dbcli export '{"query":{"match":{"status":"active"}}}' --index orders --format jsonl --output orders.ndjson
1864
+
1865
+ # (b) index name as the query → match_all over the whole index (scroll)
1866
+ dbcli export orders --format csv --output orders.csv
1867
+ dbcli export orders --no-limit --format jsonl # full index, scrolled in batches
1868
+ ```
1869
+
1870
+ - Outputs JSON / JSONL / CSV. Default cap is 1000 rows; `--no-limit` streams the full index via the scroll API in batches.
1871
+ - Index-level blacklist is checked before export and the run is written to the audit log.
1872
+
1873
+ ### Interactive shell (v1.22)
1874
+
1875
+ `dbcli shell` on an ES connection opens a Kibana Dev Tools-style REPL:
1876
+
1877
+ ```text
1878
+ $ dbcli --use local-es shell
1879
+ GET /orders/_search
1880
+ {
1881
+ "query": { "match": { "status": "active" } }
1882
+ }
1883
+ # ← blank line submits the whole block
1884
+ ```
1885
+
1886
+ - 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.
1887
+ - Read-focused: index-level blacklist rejects protected indices at the front end; a `_search` whose body omits `size` is auto-capped at 1000 hits.
1888
+
1661
1889
  ### Doctor and diagnostics
1662
1890
 
1663
1891
  `dbcli doctor` runs a dedicated Elasticsearch path:
@@ -1669,7 +1897,7 @@ dbcli query 'status:active AND amount:>100' --index orders --limit 100
1669
1897
 
1670
1898
  ### Limitations
1671
1899
 
1672
- - Writes (`insert`/`update`/`delete`/`export`) are not exposed yet — the adapter implements them, but the CLI currently only routes them for SQL and MongoDB.
1900
+ - 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.
1673
1901
  - No `_search/scroll` or PIT pagination at the CLI layer; large pulls need a saved external script.
1674
1902
  - `check`, `diff`, `migrate`, and `q` are SQL-only and exit with errors (or fall through to a generic "unsupported" path).
1675
1903
  - Blacklist column rules are applied to flattened hit rows on `query`; table-level blacklist rejects an index up front.
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: analyze-table-perf
3
+ description: Inspect a single table's structure, indexes and read-only performance signals.
4
+ tags: [diagnostics, performance, readonly]
5
+ engines: [postgres, mysql]
6
+ params:
7
+ table:
8
+ type: string
9
+ required: true
10
+ description: The table to analyze (exact name; confirm via `dbcli list`).
11
+ safety:
12
+ mode: plan-only
13
+ requires:
14
+ - blacklist-list
15
+ - schema-check
16
+ steps:
17
+ - type: command
18
+ command: blacklist list
19
+ reason: Confirm sensitive tables and columns are protected before inspection.
20
+ risk: readonly
21
+ - type: command
22
+ command: schema {{table}} --format json
23
+ reason: Inspect the table's columns and existing indexes.
24
+ risk: readonly
25
+ - type: command
26
+ command: guide index-usage --format json
27
+ reason: Review index-usage guidance to spot missing or unused indexes.
28
+ risk: readonly
29
+ ---
30
+ # Agent Notes
31
+
32
+ Use this task when a table shows up as the hottest target in recent activity and
33
+ you want safe, read-only next steps to assess its performance characteristics.
34
+ Do not run write operations. For a single heavy query, prefer
35
+ `dbcli guide missing-index-for "<sql>"` instead.