@carllee1983/dbcli 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -0
- package/assets/SKILL.md +116 -1
- package/assets/reference.md +62 -2
- package/assets/snippets/diag/es-cluster-health.elasticsearch.sql +15 -0
- package/assets/snippets/diag/redis-key-stats.redis.sql +11 -0
- package/dist/cli.mjs +989 -9959
- package/package.json +3 -7
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,48 @@ All notable changes to dbcli are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.10.0] - 2026-05-08
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Saved Queries 擴展至 Elasticsearch 與 Redis**:`dbcli q @<name>` 與 `queries` 子命令現在能依 frontmatter `engine` 自動切換到對應引擎,並走各引擎專屬的安全管線。
|
|
13
|
+
- **Engine strategy 重構**:runner 透過 `EngineStrategy` 介面分派到 SQL / Elasticsearch / Redis 三個獨立 strategy;既有 SQL 行為以 strategy 形式保留,無行為變更。
|
|
14
|
+
- **Elasticsearch strategy**:
|
|
15
|
+
- Frontmatter 接受 `engine: elasticsearch` 與 `index` 欄位;body 必須是合法 JSON,含 `script` 欄位的 query 直接拒絕。
|
|
16
|
+
- JSON-aware 參數注入:`:name` 僅在 JSON 字串脈絡裡替換,避免破壞語法。
|
|
17
|
+
- Size guard:自動補 `size` 上限;`aggs` 模式下放行但加註警告,分頁 (`from + size`) 過大時提示。
|
|
18
|
+
- **Redis strategy**:
|
|
19
|
+
- 命令白名單(read-only 為主)+ body validation;直接拒絕 unsupported 或寫入命令。
|
|
20
|
+
- Raw 參數注入:`:name` 直接代入字面量並打印 foot-gun 警告,提醒使用者 saved query 內不可放使用者輸入。
|
|
21
|
+
- Size guard:對 range / SCAN 命令的 `COUNT` / `LIMIT` 加上保險上限。
|
|
22
|
+
- **`q` 命令分派**:根據 prepared execution 的 engine family 呼叫對應 adapter,`--dry-run` 依 engine 用對應格式輸出(SQL 維持 SQL、ES 印 JSON body、Redis 印 argv)。
|
|
23
|
+
- **內建診斷 snippet**:
|
|
24
|
+
- `assets/snippets/diag/es-cluster-health.elasticsearch.sql` — ES 叢集健康度摘要。
|
|
25
|
+
- `assets/snippets/diag/redis-key-stats.redis.sql` — Redis key 數量 / type 分佈快照。
|
|
26
|
+
- **整合測試**:新增 ES / Redis end-to-end saved query 測試(依本機是否有 Docker 而 skip,與既有 PG / MySQL 測試一致)。
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **Redis 驅動**:改用 Bun 內建 `RedisClient`,移除外部 `ioredis` 依賴。
|
|
31
|
+
- **Elasticsearch adapter**:refactor 並收斂錯誤訊息與 ExecutionResult 形狀,與 SQL / Mongo / Redis 對齊。
|
|
32
|
+
- **文件**:`assets/SKILL.md` 與 `assets/reference.md` 補上 ES / Redis snippet 工作流;`docs/feature-matrix.md` 更新 saved-queries 欄位。
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- **`dbcli export`(Redis 分支)**:`result.rowCount` 在 Redis 上可能 undefined 時導致 `tsc --noEmit` 報 TS2322;改為 `result.rowCount ?? result.rows.length ?? 0`,release gate 中的 typecheck 回到 0 錯誤。
|
|
37
|
+
|
|
38
|
+
## [1.9.1] - 2026-05-07
|
|
39
|
+
|
|
40
|
+
### Changed
|
|
41
|
+
|
|
42
|
+
- **Skill 連線設定指引**:`assets/SKILL.md` 加入「Connection setup」章節,補齊 AI agent 協助使用者建立資料庫連線時所需的決策樹與各 engine essentials。
|
|
43
|
+
- 決策樹:v1 vs v2、credentials 來源(`.env` / env-refs / 明文)、權限 tier、`status` + `doctor` 驗證。
|
|
44
|
+
- Per-engine essentials:PostgreSQL / MySQL / MariaDB / MongoDB(含 `mongodb+srv://`)/ Redis(`--name` 為 logical DB index)/ Elasticsearch(basic / Cloud ID / API key)。
|
|
45
|
+
- v2 multi-connection 範例(`--conn-name`、`--env-file`、`use --list`、`--rename`、`--remove`)與 per-connection schema cache 注意事項。
|
|
46
|
+
- env-refs(`{ "$env": "..." }`)說明,以及「不要用 `--force` 把 env-refs 蓋成明文」的 guard。
|
|
47
|
+
- 常見陷阱:SRV DNS、URL 中特殊字元編碼、Redis `--name` 限制、Elasticsearch TLS 設定需手動編輯 `.dbcli`。
|
|
48
|
+
- 同步擴充 frontmatter `description`,加入 `init` / `.dbcli` / auth modes 觸發詞,提升 skill 觸發精準度。
|
|
49
|
+
|
|
8
50
|
## [1.9.0] - 2026-05-06
|
|
9
51
|
|
|
10
52
|
### Added
|
package/assets/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dbcli
|
|
3
|
-
description: Database CLI for AI agents with permission-based access control. Use to query, inspect schemas, insert/update/delete, export results, and blacklist sensitive columns/tables. Supports MySQL, PostgreSQL, MariaDB, MongoDB, Redis, and Elasticsearch with multiple named connections per project and custom env files. Trigger when
|
|
3
|
+
description: Database CLI for AI agents with permission-based access control. Use to set up new connections, query, inspect schemas, insert/update/delete, export results, and blacklist sensitive columns/tables. Supports MySQL, PostgreSQL, MariaDB, MongoDB, Redis, and Elasticsearch with multiple named connections per project and custom env files. Trigger when configuring a database connection (`.dbcli` / `.env`), choosing between v1 single and v2 multi-connection layouts, picking auth modes (URI, env refs, Cloud ID, API key), running SQL / MongoDB JSON / Redis commands / Elasticsearch DSL, exploring table/collection/key/index structures, switching database environments, or protecting sensitive data from AI access. For exhaustive flags and examples, read the sibling `reference.md`.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# dbcli
|
|
@@ -46,6 +46,107 @@ dbcli schema # Scan all tables → .dbcli/schemas/
|
|
|
46
46
|
dbcli query "SELECT * FROM users" # Execute SQL (auto LIMIT 1000)
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
If `.dbcli` does not yet exist, route through **Connection setup** below before
|
|
50
|
+
touching `schema` / `query`.
|
|
51
|
+
|
|
52
|
+
## Connection setup (helping the user wire up a database)
|
|
53
|
+
|
|
54
|
+
When the user asks "how do I connect to X?", "set up dbcli for our staging DB",
|
|
55
|
+
or `doctor` / `status` reports a missing or invalid config, follow this flow.
|
|
56
|
+
|
|
57
|
+
> **Default to guiding, not running.** `init` writes credentials to disk. Only
|
|
58
|
+
> execute it for the user with explicit permission and confirmed values.
|
|
59
|
+
> If a `.dbcli` already contains `{"$env": "..."}` references, **do not** rerun
|
|
60
|
+
> `init` to "fill them in" — the env-ref form is intentional for CI/multi-env.
|
|
61
|
+
|
|
62
|
+
### Decision tree (ask before writing)
|
|
63
|
+
|
|
64
|
+
1. **One DB or many environments?** One → v1 (single connection). Multiple
|
|
65
|
+
environments / tenants / replicas → v2 (`--conn-name <name>`, optionally
|
|
66
|
+
`--env-file <path>` per connection).
|
|
67
|
+
2. **Where do credentials live?**
|
|
68
|
+
- Already in a `.env` (`DATABASE_URL` or `DB_HOST` / `DB_PORT` / `DB_USER` /
|
|
69
|
+
`DB_PASSWORD` / `DB_NAME` | `DB_DATABASE`) → `init` parses it automatically.
|
|
70
|
+
- Need to keep secrets out of `.dbcli` (CI/CD, multi-env) → `--use-env-refs`
|
|
71
|
+
plus `--env-host` / `--env-port` / `--env-user` / `--env-password` / `--env-database`.
|
|
72
|
+
- Plain values are acceptable → pass `--host` / `--port` / `--user` /
|
|
73
|
+
`--password` / `--name` (and `--system`).
|
|
74
|
+
3. **What permission tier?** Default to the **lowest** that satisfies the task:
|
|
75
|
+
`query-only` → `read-write` → `data-admin` → `admin`. Set with `--permission`.
|
|
76
|
+
4. **Verify, never assume.** After init: `dbcli status` (system + permission +
|
|
77
|
+
blacklist summary, no creds) and `dbcli doctor --format json` (env, config
|
|
78
|
+
shape, connectivity, schema-cache age, Mongo SRV path).
|
|
79
|
+
|
|
80
|
+
### Per-engine essentials
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# PostgreSQL / MySQL / MariaDB (v1, plain values)
|
|
84
|
+
dbcli init --system postgresql --host localhost --port 5432 \
|
|
85
|
+
--user app --password '<secret>' --name appdb --permission query-only
|
|
86
|
+
|
|
87
|
+
# Reuse an existing .env (DATABASE_URL=postgresql://user:pw@host:5432/db)
|
|
88
|
+
dbcli init # parses .env in cwd
|
|
89
|
+
|
|
90
|
+
# MongoDB — full URI (Atlas / replica sets / authSource)
|
|
91
|
+
dbcli init --system mongodb \
|
|
92
|
+
--uri "mongodb+srv://user:pw@cluster.example.mongodb.net/mydb?authSource=admin"
|
|
93
|
+
# MongoDB — discrete params (no auth = omit --user/--password)
|
|
94
|
+
dbcli init --system mongodb --host localhost --port 27017 --name mydb
|
|
95
|
+
|
|
96
|
+
# Redis — `--name` is the LOGICAL DB INDEX ("0".."15"), not a database name
|
|
97
|
+
dbcli init --system redis --host localhost --port 6379 --password '<secret>' --name 0
|
|
98
|
+
|
|
99
|
+
# Elasticsearch — basic auth, Cloud ID, or API key
|
|
100
|
+
dbcli init --system elasticsearch --host localhost --port 9200 \
|
|
101
|
+
--user elastic --password '<secret>'
|
|
102
|
+
dbcli init --system elasticsearch \
|
|
103
|
+
--cloud-id "myCluster:dXMtZWFzdC0xLmF3..." --api-key "<base64>"
|
|
104
|
+
# Multi-node / custom CA / self-signed: edit `.dbcli` directly to add
|
|
105
|
+
# `nodes: [...]`, `protocol: https`, `caPath`, `rejectUnauthorized: false`.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Multi-connection (v2)
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
dbcli init --conn-name staging --env-file .env.staging --permission query-only
|
|
112
|
+
dbcli init --conn-name prod --env-file .env.production --use-env-refs --skip-test
|
|
113
|
+
dbcli use --list # show all, * marks default
|
|
114
|
+
dbcli use prod # switch default
|
|
115
|
+
dbcli query --use staging "SELECT 1" # one-shot override
|
|
116
|
+
dbcli init --rename staging:stg # rename
|
|
117
|
+
dbcli init --remove stg # remove
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Per-connection schema cache lives at `.dbcli/schemas/<connection>/`. Run
|
|
121
|
+
`dbcli schema --use <name>` once per connection before `schema <table>` —
|
|
122
|
+
otherwise the cache may serve another connection's columns.
|
|
123
|
+
|
|
124
|
+
### env-refs (keep secrets out of `.dbcli`)
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
dbcli init --use-env-refs \
|
|
128
|
+
--env-host DB_HOST --env-port DB_PORT \
|
|
129
|
+
--env-user DB_USER --env-password DB_PASSWORD --env-database DB_NAME
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Stored as `{ "$env": "DB_HOST" }` etc. and resolved at runtime. Pair with
|
|
133
|
+
`--env-file <path>` (v2) when each connection has its own env file.
|
|
134
|
+
|
|
135
|
+
### Common gotchas
|
|
136
|
+
|
|
137
|
+
- **MongoDB `mongodb+srv://`** — `dbcli doctor` reports whether SRV resolves
|
|
138
|
+
natively or via the DoH fallback; useful when the runtime restricts DNS.
|
|
139
|
+
- **MySQL/Postgres password with `@` `:` `/`** — when using `DATABASE_URL`,
|
|
140
|
+
percent-encode (`@` → `%40`); discrete `--password` flags do not need encoding.
|
|
141
|
+
- **Redis `--name`** — accepts only the logical DB index string; non-numeric
|
|
142
|
+
values are rejected.
|
|
143
|
+
- **Elasticsearch TLS** — `caPath` and `rejectUnauthorized` are not exposed as
|
|
144
|
+
flags; edit `.dbcli` after `init` to add them.
|
|
145
|
+
- **Re-running `init`** — refuses to overwrite without `--force`; never use
|
|
146
|
+
`--force` to "fix" a config full of `{ "$env": "..." }` refs.
|
|
147
|
+
|
|
148
|
+
Full flags and edge cases: see [reference.md](reference.md) `init` section.
|
|
149
|
+
|
|
49
150
|
## Command overview
|
|
50
151
|
|
|
51
152
|
| Command | Min permission | Summary |
|
|
@@ -136,6 +237,18 @@ Each `.sql` file may declare YAML frontmatter inside `-- ---` blocks
|
|
|
136
237
|
(name, description, engine, params, tags). See `dbcli queries show @<name> --format json`
|
|
137
238
|
for the machine-readable contract.
|
|
138
239
|
|
|
240
|
+
### Engine-specific bodies
|
|
241
|
+
|
|
242
|
+
Each snippet's body format is determined by the `engine` frontmatter field:
|
|
243
|
+
|
|
244
|
+
| Engine | Body format | Notes |
|
|
245
|
+
|-------------------|------------------------|-------|
|
|
246
|
+
| postgres / mysql | Single SELECT or WITH | `:name` → driver bind (`$1` / `?`) |
|
|
247
|
+
| elasticsearch | JSON DSL | `:name` → JSON-aware substitution; `index:` field required |
|
|
248
|
+
| redis | Single Redis command | `:name` → raw text; only read commands allowed |
|
|
249
|
+
|
|
250
|
+
Mixed-family `engine` arrays (e.g. `[postgres, elasticsearch]`) are rejected at parse time.
|
|
251
|
+
|
|
139
252
|
### Built-in diagnostic snippets
|
|
140
253
|
|
|
141
254
|
dbcli ships ready-made diagnostic queries. Run with `dbcli q @diag/<topic>`:
|
|
@@ -150,6 +263,8 @@ dbcli ships ready-made diagnostic queries. Run with `dbcli q @diag/<topic>`:
|
|
|
150
263
|
| `@diag/locks` | lock-wait chains |
|
|
151
264
|
| `@diag/db-size` | database size summary |
|
|
152
265
|
| `@diag/cache-hit` | buffer cache hit ratios |
|
|
266
|
+
| `@diag/es-cluster-health` | document counts per index (ES connections) |
|
|
267
|
+
| `@diag/redis-key-stats` | sample SCAN over keyspace (Redis connections) |
|
|
153
268
|
|
|
154
269
|
Engine variants are picked automatically based on the active connection.
|
|
155
270
|
Override any of them by placing a same-named file under `.dbcli-shared/queries/`
|
package/assets/reference.md
CHANGED
|
@@ -230,6 +230,62 @@ Param placeholders use `:name`. They are rewritten to `$1, $2, …` (Postgres) o
|
|
|
230
230
|
- Files exceeding 64 KiB are rejected.
|
|
231
231
|
- `--no-limit` is honoured only at the outermost level; nested subqueries are still wrapped by the size guard.
|
|
232
232
|
|
|
233
|
+
##### Elasticsearch snippets
|
|
234
|
+
|
|
235
|
+
Body is a JSON DSL `_search` request body. Frontmatter requires an `index:` field (may contain `:param`).
|
|
236
|
+
|
|
237
|
+
Example:
|
|
238
|
+
|
|
239
|
+
-- ---
|
|
240
|
+
-- name: events-by-day
|
|
241
|
+
-- engine: elasticsearch
|
|
242
|
+
-- index: 'events-:date'
|
|
243
|
+
-- params:
|
|
244
|
+
-- date: { type: date, required: true }
|
|
245
|
+
-- user_id: { type: int, required: true }
|
|
246
|
+
-- ---
|
|
247
|
+
{
|
|
248
|
+
"query": {
|
|
249
|
+
"bool": {
|
|
250
|
+
"filter": [
|
|
251
|
+
{ "term": { "user_id": :user_id } }
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
"size": 100
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
Substitution rules (type-aware JSON injection):
|
|
259
|
+
|
|
260
|
+
- `int` / `float` / `bool` outside string literals → bare value (`42`, `1.5`, `true`)
|
|
261
|
+
- `string` / `date` / `datetime` outside string literals → JSON-quoted (`"Alice"`, `"2026-05-08"`)
|
|
262
|
+
- Any param inside a JSON string literal → escaped inner form (`"prefix-:name"` works)
|
|
263
|
+
|
|
264
|
+
`script` and `script_fields` are rejected anywhere in the body.
|
|
265
|
+
|
|
266
|
+
Size guard: if `size` is missing, `1000` is injected (or `0` when `aggs` is present); explicit `size > 1000` is overridden with a warning unless `--no-limit`.
|
|
267
|
+
|
|
268
|
+
##### Redis snippets
|
|
269
|
+
|
|
270
|
+
Body is a single Redis command on one line. Only read-only commands are allowed:
|
|
271
|
+
`GET MGET HGET HGETALL HMGET HKEYS HVALS HLEN HEXISTS LRANGE LLEN LINDEX SMEMBERS SISMEMBER SCARD ZRANGE ZRANGEBYSCORE ZRANGEBYLEX ZSCORE ZCARD ZCOUNT ZRANK TYPE EXISTS TTL PTTL STRLEN OBJECT SCAN HSCAN SSCAN ZSCAN`.
|
|
272
|
+
|
|
273
|
+
`KEYS`, `EVAL`, `FLUSHDB`, `FLUSHALL`, `CONFIG`, `DEBUG`, `SHUTDOWN`, `SCRIPT` and any write command are rejected.
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
|
|
277
|
+
-- ---
|
|
278
|
+
-- name: cache-user
|
|
279
|
+
-- engine: redis
|
|
280
|
+
-- params:
|
|
281
|
+
-- id: { type: int, required: true }
|
|
282
|
+
-- ---
|
|
283
|
+
HGETALL user::id
|
|
284
|
+
|
|
285
|
+
Substitution rules: pure raw text — `:name` becomes the value's `String()` form. **Quoting is the snippet author's responsibility**: wrap `:name` in double quotes if the value may contain whitespace. The parser warns when a `string`-typed `:name` is adjacent to non-whitespace and unquoted.
|
|
286
|
+
|
|
287
|
+
Size guard: `LRANGE` / `ZRANGE` stop overridden when `< 0` or `> 1000`; `SCAN` / `HSCAN` / `SSCAN` / `ZSCAN` get `COUNT 1000` injected if absent. `--no-limit` disables.
|
|
288
|
+
|
|
233
289
|
### queries
|
|
234
290
|
|
|
235
291
|
Manage saved snippets — discover, inspect, scaffold, and edit local copies. Mutating
|
|
@@ -582,7 +638,9 @@ Redis connections speak Redis commands rather than SQL. The adapter uses the `io
|
|
|
582
638
|
|
|
583
639
|
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
|
|
584
640
|
|
|
585
|
-
**
|
|
641
|
+
**Saved queries:** `q` is supported for read-only Redis commands (see "Redis snippets" below).
|
|
642
|
+
|
|
643
|
+
**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.
|
|
586
644
|
|
|
587
645
|
### Connection and configuration
|
|
588
646
|
|
|
@@ -648,7 +706,9 @@ Elasticsearch connections speak the REST API. The adapter is fetch-based (no SDK
|
|
|
648
706
|
|
|
649
707
|
**Supported commands:** `init`, `use`, `list`, `schema`, `query`, `status`, `doctor`, `upgrade`, `completion`
|
|
650
708
|
|
|
651
|
-
**
|
|
709
|
+
**Saved queries:** `q` is supported for ES JSON DSL bodies (see "Elasticsearch snippets" below).
|
|
710
|
+
|
|
711
|
+
**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.
|
|
652
712
|
|
|
653
713
|
### Connection and configuration
|
|
654
714
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- ---
|
|
2
|
+
-- name: es-cluster-health
|
|
3
|
+
-- description: Document counts per index across the cluster
|
|
4
|
+
-- engine: elasticsearch
|
|
5
|
+
-- index: '*'
|
|
6
|
+
-- tags: [diag, elasticsearch]
|
|
7
|
+
-- ---
|
|
8
|
+
{
|
|
9
|
+
"size": 0,
|
|
10
|
+
"aggs": {
|
|
11
|
+
"by_index": {
|
|
12
|
+
"terms": { "field": "_index", "size": 50 }
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|