@carllee1983/dbcli 0.3.1-beta → 0.4.0-beta

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.
Files changed (3) hide show
  1. package/assets/SKILL.md +327 -0
  2. package/dist/cli.mjs +389 -115
  3. package/package.json +2 -1
@@ -0,0 +1,327 @@
1
+ ---
2
+ name: dbcli
3
+ description: Database CLI for AI agents with permission-based access control. Use to query, inspect schemas, insert/update/delete data, export results, and manage sensitive data blacklists. Supports MySQL, PostgreSQL, MariaDB. Trigger when working with databases, running SQL, exploring table structures, or protecting sensitive columns/tables from AI access.
4
+ ---
5
+
6
+ # dbcli
7
+
8
+ Database CLI for AI agents with permission-based access control.
9
+
10
+ ## Quick Start
11
+
12
+ ```bash
13
+ dbcli init # Initialize .dbcli config (parses .env automatically)
14
+ dbcli schema # Scan all tables and save to .dbcli
15
+ dbcli query "SELECT * FROM users" # Execute SQL
16
+ ```
17
+
18
+ ## Commands
19
+
20
+ ### init
21
+
22
+ Initialize `.dbcli` configuration file. Typically run manually by the developer — avoid running on behalf of the user unless explicitly requested.
23
+
24
+ ```bash
25
+ dbcli init
26
+ dbcli init --system mysql --host localhost --port 3306 --user root --name mydb
27
+ dbcli init --use-env-refs # Store env var references instead of values
28
+ dbcli init --no-interactive --force # Non-interactive, skip overwrite confirmation
29
+ ```
30
+
31
+ **Key options:** `--system <postgresql|mysql|mariadb>`, `--permission <query-only|read-write|data-admin|admin>`, `--use-env-refs`, `--skip-test`, `--no-interactive`, `--force`
32
+
33
+ ### list
34
+
35
+ List all tables.
36
+
37
+ ```bash
38
+ dbcli list
39
+ dbcli list --format json
40
+ ```
41
+
42
+ **Permission:** query-only+
43
+
44
+ ### schema
45
+
46
+ Display table schema or scan entire database.
47
+
48
+ ```bash
49
+ dbcli schema # Scan all tables, save to .dbcli
50
+ dbcli schema users # Show single table schema
51
+ dbcli schema users --format json
52
+ dbcli schema --refresh # Detect and apply schema changes
53
+ dbcli schema --reset # Clear all schema data and re-fetch
54
+ dbcli schema --reset --force # Skip confirmation
55
+ ```
56
+
57
+ **Options:** `--format <table|json>`, `--refresh`, `--reset`, `--force`
58
+ **Permission:** query-only+
59
+
60
+ ### query
61
+
62
+ Execute SQL query.
63
+
64
+ ```bash
65
+ dbcli query "SELECT * FROM users LIMIT 10"
66
+ dbcli query "SELECT id, email FROM users" --format json
67
+ dbcli query "SELECT * FROM logs" --no-limit
68
+ ```
69
+
70
+ **Options:** `--format <table|json|csv>`, `--limit <number>`, `--no-limit`
71
+ **Permission:** query-only+
72
+
73
+ ### insert
74
+
75
+ Insert data into a table.
76
+
77
+ ```bash
78
+ dbcli insert users --data '{"name":"Alice","email":"alice@example.com"}'
79
+ dbcli insert users --data '{"name":"Alice"}' --dry-run
80
+ dbcli insert users --data '{"name":"Alice"}' --force
81
+ ```
82
+
83
+ **Options:** `--data <json>`, `--dry-run`, `--force`
84
+ **Permission:** read-write+
85
+
86
+ ### update
87
+
88
+ Update existing data.
89
+
90
+ ```bash
91
+ dbcli update users --where "id=1" --set '{"name":"Bob"}'
92
+ dbcli update users --where "id=1" --set '{"name":"Bob"}' --dry-run
93
+ ```
94
+
95
+ **Options:** `--where <condition>` (required), `--set <json>` (required), `--dry-run`, `--force`
96
+ **Permission:** read-write+
97
+
98
+ ### delete
99
+
100
+ Delete data from a table.
101
+
102
+ ```bash
103
+ dbcli delete users --where "id=1"
104
+ dbcli delete users --where "id=1" --dry-run
105
+ dbcli delete users --where "id=1" --force
106
+ ```
107
+
108
+ **Options:** `--where <condition>` (required), `--dry-run`, `--force`
109
+ **Permission:** data-admin+
110
+
111
+ ### export
112
+
113
+ Export query results to file or stdout.
114
+
115
+ ```bash
116
+ dbcli export "SELECT * FROM users" --format csv --output users.csv
117
+ dbcli export "SELECT * FROM users" --format json | jq '.[]'
118
+ ```
119
+
120
+ **Options:** `--format <json|csv>` (required), `--output <path>`
121
+ **Permission:** query-only+
122
+
123
+ ### blacklist
124
+
125
+ Manage sensitive data blacklist to prevent AI access to restricted tables/columns.
126
+
127
+ ```bash
128
+ dbcli blacklist list # Show current blacklist
129
+ dbcli blacklist table add payments # Block entire table
130
+ dbcli blacklist table remove payments # Unblock table
131
+ dbcli blacklist column add users.password # Block specific column
132
+ dbcli blacklist column remove users.password
133
+ ```
134
+
135
+ **Subcommands:** `list`, `table add <name>`, `table remove <name>`, `column add <table.column>`, `column remove <table.column>`
136
+
137
+ ### check
138
+
139
+ Run data health checks on tables.
140
+
141
+ ```bash
142
+ dbcli check users # Check single table
143
+ dbcli check users --format json # JSON output (default)
144
+ dbcli check --all # Check all tables (huge tables auto-skipped)
145
+ dbcli check --all --include-large # Include huge tables
146
+ dbcli check orders --checks nulls,orphans # Specific checks only
147
+ dbcli check orders --sample 10000 # Sample size for large tables
148
+ ```
149
+
150
+ **Checks:** `nulls`, `duplicates`, `orphans`, `emptyStrings`, `rowCount`, `size`
151
+ **Options:** `--all`, `--include-large`, `--checks <types>`, `--sample <number>`, `--format <json|table>`
152
+ **Permission:** query-only+
153
+
154
+ ### diff
155
+
156
+ Compare schema snapshots to detect changes.
157
+
158
+ ```bash
159
+ dbcli diff --snapshot before.json # Save current schema snapshot
160
+ dbcli diff --against before.json # Compare current vs snapshot
161
+ dbcli diff --against before.json --format json
162
+ ```
163
+
164
+ **Options:** `--snapshot <path>`, `--against <path>`, `--format <json|table>`
165
+ **Permission:** query-only+
166
+
167
+ ### status
168
+
169
+ Show current configuration status (safe for AI agents, no credentials exposed).
170
+
171
+ ```bash
172
+ dbcli status # JSON output (default)
173
+ dbcli status --format text # Human-readable text output
174
+ ```
175
+
176
+ **Output:** `permission`, `system`, `blacklist` summary, `version`
177
+ **Permission:** query-only+
178
+
179
+ ## Permission Levels
180
+
181
+ | Level | Allowed Operations |
182
+ |-------|-------------------|
183
+ | query-only | SELECT, list, schema, export |
184
+ | read-write | query-only + INSERT, UPDATE |
185
+ | data-admin | read-write + DELETE (full DML, no DDL) |
186
+ | admin | data-admin + DROP, ALTER, CREATE, TRUNCATE |
187
+
188
+ Set via `dbcli init --permission <level>` or in `.dbcli` config.
189
+
190
+ ## Global Options
191
+
192
+ All commands support `--config <path>` to specify a custom config file (default: `.dbcli`).
193
+
194
+ ## AI Agent Workflow
195
+
196
+ **Before any database operation, follow this sequence:**
197
+
198
+ 1. `dbcli status` — Check current permission level and system info (safe — no credentials exposed)
199
+ 2. `dbcli blacklist list` — Confirm sensitive data is protected
200
+ 3. `dbcli schema <table> --format json` — Verify actual column names
201
+ 4. Then execute `query` / `insert` / `update` / `export` / `delete` according to your permission level
202
+
203
+ **Never guess column names.** Naming conventions vary across projects (e.g. `frozen_balance` vs `freeze`, `amount` vs `balance_variable`). Always confirm with `schema` first.
204
+
205
+ ## Debugging Workflow
206
+
207
+ When investigating a bug related to database state:
208
+
209
+ 1. `dbcli schema <table> --format json` — Confirm actual columns and types
210
+ 2. `dbcli check <table> --format json` — Quick health scan (nulls, orphans, duplicates)
211
+ 3. `dbcli query "SELECT * FROM <table> WHERE <condition>" --format json` — Inspect the specific record
212
+ 4. Follow foreign keys from schema to trace related tables
213
+ 5. Repeat step 3 for each related table to verify referential integrity
214
+
215
+ **Key principle:** Let the data tell the story. Don't hypothesize before seeing actual state.
216
+
217
+ ## Write Verification Workflow
218
+
219
+ After any INSERT or UPDATE:
220
+
221
+ 1. `dbcli insert <table> --data '...' --dry-run` — Preview SQL first
222
+ 2. Execute the actual insert/update (remove --dry-run)
223
+ 3. `dbcli query "SELECT * FROM <table> WHERE <condition>" --format json` — Read back the written record
224
+ 4. Compare the returned data against the intended values
225
+ 5. If mismatch, check for triggers, default values, or blacklisted columns that may alter the result
226
+
227
+ ## Migration Safety Workflow
228
+
229
+ Before and after running database migrations:
230
+
231
+ 1. `dbcli diff --snapshot before.json` — Capture current schema
232
+ 2. Run the migration
233
+ 3. `dbcli diff --against before.json --format json` — Compare changes
234
+ 4. Verify: added/removed/modified columns match the migration intent
235
+ 5. `dbcli check <affected-tables> --format json` — Ensure no orphaned data from column drops or FK changes
236
+
237
+ ## Health Check Workflow
238
+
239
+ Periodic or on-demand database health scan:
240
+
241
+ 1. `dbcli check --all --format json` — Scan all tables (huge tables auto-skipped)
242
+ 2. Review the summary: focus on orphans (broken FKs) and unexpected nulls
243
+ 3. For any flagged issues, drill down with `dbcli query` to inspect specific records
244
+ 4. Use `estimatedRowCount` and `sizeCategory` from schema to gauge table growth
245
+
246
+ ## Code Generation from Schema
247
+
248
+ When setting up a new project or migrating frameworks (e.g., Laravel to Bun + Drizzle):
249
+
250
+ 1. `dbcli schema --format json` — Export full database schema with FK, indexes, defaults, enums
251
+ 2. Use the JSON output to generate ORM schema definitions (Drizzle, Prisma, TypeORM, etc.)
252
+ 3. For each table, map:
253
+ - `primaryKey` + `autoIncrement` to ORM primary key decorator
254
+ - `foreignKey` to relation/reference definitions
255
+ - `indexes` to index declarations
256
+ - `enumValues` to TypeScript enums or union types
257
+ - `nullable` + `defaultValue` to column options
258
+ - `comment` to JSDoc or schema comments
259
+ 4. `dbcli check --all --format json` — Verify data health before trusting existing data
260
+ 5. After ORM setup, run a test query through the new ORM and compare results with `dbcli query` to validate correctness
261
+
262
+ ## Logic Verification Workflow
263
+
264
+ Validate that application logic produces correct database state:
265
+
266
+ 1. `dbcli query "SELECT * FROM <table> WHERE <condition>" --format json` — Capture state BEFORE
267
+ 2. Execute the application logic (API call, script, etc.)
268
+ 3. `dbcli query "SELECT * FROM <table> WHERE <condition>" --format json` — Capture state AFTER
269
+ 4. Compare before/after:
270
+ - Were the expected rows created/updated/deleted?
271
+ - Are computed values correct (totals, balances, counters)?
272
+ - Did related tables update consistently?
273
+ 5. For complex transactions, check ALL affected tables
274
+ 6. `dbcli check <affected-tables> --format json` — Ensure no orphaned data post-operation
275
+
276
+ **When to use:** Unit tests mock the DB and may miss real constraint violations, triggers, and default values. dbcli verifies actual DB state — catches what mocks hide. Best practice: unit tests for logic, dbcli for integration truth.
277
+
278
+ ## Natural Language Operations Workflow
279
+
280
+ When the user describes a database operation in plain language:
281
+
282
+ 1. **Parse intent** — Identify the operation type:
283
+ - "查今天的訂單" → query (SELECT)
284
+ - "幫我新增一筆記事" → insert (INSERT)
285
+ - "把這筆訂單改成已出貨" → update (UPDATE)
286
+
287
+ 2. **Resolve context** — Use schema to map natural language to actual columns:
288
+ - `dbcli schema <table> --format json` — Get real column names
289
+ - "今天的訂單" → `WHERE created_at >= CURDATE()` (verify column name from schema)
290
+ - "已出貨" → check status column's enum values or existing data patterns
291
+
292
+ 3. **Infer missing fields** — Use schema defaults and context:
293
+ - `defaultValue` from schema → skip fields with sensible defaults
294
+ - `autoIncrement` → don't include primary key in INSERT
295
+ - `nullable: false` without default → MUST ask user for this value
296
+
297
+ 4. **Safety gate**:
298
+ - `dbcli blacklist list` — Ensure no blacklisted columns in the operation
299
+ - Check `sizeCategory` — if querying a huge table without filter, warn and suggest conditions
300
+ - For writes: ALWAYS use `--dry-run` first, show the SQL, then confirm
301
+
302
+ 5. **Execute and verify**:
303
+ - Run the operation
304
+ - For INSERT/UPDATE: read back with `dbcli query` to confirm
305
+ - Report result in natural language back to user
306
+
307
+ **Key principle:** Never guess column names or values. Always schema-first, dry-run-first.
308
+
309
+ ## Notes
310
+
311
+ - **Use `--format json`**: More reliable for AI parsing than table format
312
+ - **Use `--dry-run` before writes**: Preview generated SQL before executing
313
+ - **auto-limit**: Query-only mode appends `LIMIT 1000` automatically. Use `--no-limit` for `information_schema` queries or statements incompatible with LIMIT
314
+ - **Blacklist scope**: Blacklisted tables/columns are automatically filtered from query results
315
+
316
+ ## Data Volume Protection
317
+
318
+ Schema output includes `estimatedRowCount` and `sizeCategory` for each table:
319
+
320
+ | Category | Rows | Behavior |
321
+ |----------|------|----------|
322
+ | small | < 10K | No restrictions |
323
+ | medium | 10K - 100K | Suggest adding LIMIT/WHERE |
324
+ | large | 100K - 1M | Warning displayed |
325
+ | huge | > 1M | Full-table SELECT blocked without WHERE/LIMIT — use `--no-limit` to override |
326
+
327
+ **Always check `sizeCategory` before querying.** For `large`/`huge` tables, add WHERE conditions or reasonable LIMIT.
package/dist/cli.mjs CHANGED
@@ -42758,7 +42758,7 @@ var {
42758
42758
  // package.json
42759
42759
  var package_default = {
42760
42760
  name: "@carllee1983/dbcli",
42761
- version: "0.3.1-beta",
42761
+ version: "0.4.0-beta",
42762
42762
  description: "Database CLI for AI agents",
42763
42763
  type: "module",
42764
42764
  publishConfig: {
@@ -42773,6 +42773,7 @@ var package_default = {
42773
42773
  },
42774
42774
  files: [
42775
42775
  "dist/",
42776
+ "assets/",
42776
42777
  "README.md",
42777
42778
  "CHANGELOG.md",
42778
42779
  "LICENSE"
@@ -42812,8 +42813,231 @@ var package_default = {
42812
42813
  vitest: "^1.2.0"
42813
42814
  }
42814
42815
  };
42816
+ // resources/lang/en/messages.json
42817
+ var messages_default = {
42818
+ init: {
42819
+ description: "Initialize dbcli configuration with .env parsing and interactive prompts",
42820
+ welcome: "Welcome to dbcli",
42821
+ select_system: "Select database system:",
42822
+ prompt_host: "Database host: ",
42823
+ prompt_port: "Database port: ",
42824
+ prompt_user: "Database user: ",
42825
+ prompt_password: "Database password: ",
42826
+ prompt_name: "Database name: ",
42827
+ prompt_permission: "Permission level (query-only, read-write, data-admin, admin): ",
42828
+ env_parse_note: "Note: Unable to parse .env configuration, using interactive prompts",
42829
+ connection_testing: "Testing database connection...",
42830
+ connection_success: "\u2713 Database connection successful",
42831
+ connection_failed: "\u2717 Database connection failed",
42832
+ config_saved: "Configuration saved to .dbcli",
42833
+ config_exists_overwrite: "Configuration file .dbcli already exists. Overwrite? (y/n): ",
42834
+ cancelled: "Cancelled. Configuration not changed."
42835
+ },
42836
+ schema: {
42837
+ description: "Retrieve table structure or list all tables",
42838
+ fetching: "Fetching schema...",
42839
+ success: "Schema updated",
42840
+ not_found: "Table not found in database",
42841
+ refresh_prompt: "Schema has changes. Apply? (y/n): ",
42842
+ columns_header: "Columns",
42843
+ scanning_database: "\uD83D\uDD0D Scanning database schema...",
42844
+ tables_found: "\uD83D\uDCCD Found {count} table(s). Fetching schema details...",
42845
+ processing_tables: " Processed {processed}/{total} table(s)",
42846
+ schema_exists_warning: "\u26A0\uFE0F Database schema already exists in .dbcli",
42847
+ use_force_to_override: " Use --force to override without confirmation"
42848
+ },
42849
+ list: {
42850
+ description: "List all tables in the database",
42851
+ no_tables: "No tables found in database"
42852
+ },
42853
+ query: {
42854
+ description: "Execute SQL query",
42855
+ executing: "Executing query...",
42856
+ no_results: "No results returned",
42857
+ result_count: "Results: {count} row(s)"
42858
+ },
42859
+ errors: {
42860
+ message: "Error: {message}",
42861
+ invalid_config: "Invalid configuration: {field}",
42862
+ connection_failed: "Failed to connect to database: {message}",
42863
+ permission_denied: "Permission denied (required: {required})",
42864
+ table_not_found: "Table not found: {table}",
42865
+ unsupported_lang: "Language '{lang}' not supported, using English",
42866
+ invalid_json: "Invalid JSON: {message}",
42867
+ table_blacklisted: "Error: Table '{table}' is blacklisted for {operation} operations",
42868
+ table_already_blacklisted: "Error: Table '{table}' is already blacklisted",
42869
+ table_not_in_blacklist: "Error: Table '{table}' is not in the blacklist",
42870
+ column_already_blacklisted: "Error: Column '{table}.{column}' is already blacklisted",
42871
+ column_not_in_blacklist: "Error: Column '{table}.{column}' is not in the blacklist",
42872
+ invalid_table_name: "Error: Invalid table name: {table}",
42873
+ invalid_column_format: "Error: Invalid column format. Use 'table.column'"
42874
+ },
42875
+ success: {
42876
+ inserted: "Successfully inserted {count} row(s)",
42877
+ updated: "Successfully updated {count} row(s)",
42878
+ deleted: "Successfully deleted {count} row(s)",
42879
+ no_changes: "No changes to apply"
42880
+ },
42881
+ insert: {
42882
+ description: "Insert data into a table",
42883
+ prompt_data: "Enter JSON data to insert: ",
42884
+ confirm: "Insert {count} row(s) into {table}? (y/n): "
42885
+ },
42886
+ update: {
42887
+ description: "Update data in a table",
42888
+ prompt_where: "Enter WHERE clause (e.g., id=1): ",
42889
+ prompt_set: 'Enter JSON updates (e.g., {"status": "active"}): ',
42890
+ confirm: "Update rows in {table}? (y/n): "
42891
+ },
42892
+ delete: {
42893
+ description: "Delete data from a table",
42894
+ prompt_where: "Enter WHERE clause (e.g., id=1): ",
42895
+ confirm: "Delete rows from {table}? (y/n): ",
42896
+ admin_only: "Delete command requires data-admin or admin permission"
42897
+ },
42898
+ export: {
42899
+ description: "Export query results",
42900
+ formats: "Supported formats: json, csv",
42901
+ exported: "Exported {count} rows to {file}"
42902
+ },
42903
+ skill: {
42904
+ description: "Generate AI skill documentation",
42905
+ installed: "Skill installed to {path}"
42906
+ },
42907
+ blacklist: {
42908
+ description: "Manage sensitive data blacklist to prevent AI access",
42909
+ list_title: "Current Blacklist Configuration",
42910
+ tables_label: "Blacklisted tables",
42911
+ columns_label: "Blacklisted columns",
42912
+ none: "No tables or columns are currently blacklisted",
42913
+ table_added: "\u2713 Table '{table}' added to blacklist",
42914
+ table_removed: "\u2713 Table '{table}' removed from blacklist",
42915
+ column_added: "\u2713 Column '{table}.{column}' added to blacklist",
42916
+ column_removed: "\u2713 Column '{table}.{column}' removed from blacklist"
42917
+ },
42918
+ security: {
42919
+ columns_omitted: "Security: {count} column(s) were omitted based on your blacklist"
42920
+ },
42921
+ warnings: {
42922
+ blacklist_override_used: "Warning: Blacklist override enabled (DBCLI_OVERRIDE_BLACKLIST=true). Executing {operation} on blacklisted table '{table}'"
42923
+ }
42924
+ };
42925
+ // resources/lang/zh-TW/messages.json
42926
+ var messages_default2 = {
42927
+ init: {
42928
+ description: "\u521D\u59CB\u5316 dbcli \u914D\u7F6E\uFF0C\u89E3\u6790 .env \u4E26\u9032\u884C\u4E92\u52D5\u5F0F\u63D0\u793A",
42929
+ welcome: "\u6B61\u8FCE\u4F7F\u7528 dbcli",
42930
+ select_system: "\u9078\u64C7\u8CC7\u6599\u5EAB\u7CFB\u7D71\uFF1A",
42931
+ prompt_host: "\u8CC7\u6599\u5EAB\u4E3B\u6A5F\uFF1A",
42932
+ prompt_port: "\u8CC7\u6599\u5EAB\u57E0\u865F\uFF1A",
42933
+ prompt_user: "\u8CC7\u6599\u5EAB\u4F7F\u7528\u8005\uFF1A",
42934
+ prompt_password: "\u8CC7\u6599\u5EAB\u5BC6\u78BC\uFF1A",
42935
+ prompt_name: "\u8CC7\u6599\u5EAB\u540D\u7A31\uFF1A",
42936
+ prompt_permission: "\u6B0A\u9650\u7B49\u7D1A\uFF08query-only, read-write, data-admin, admin\uFF09\uFF1A",
42937
+ env_parse_note: "\u63D0\u793A\uFF1A\u7121\u6CD5\u89E3\u6790 .env \u914D\u7F6E\uFF0C\u4F7F\u7528\u4E92\u52D5\u5F0F\u63D0\u793A",
42938
+ connection_testing: "\u6B63\u5728\u6E2C\u8A66\u8CC7\u6599\u5EAB\u9023\u63A5...",
42939
+ connection_success: "\u2713 \u8CC7\u6599\u5EAB\u9023\u63A5\u6210\u529F",
42940
+ connection_failed: "\u2717 \u8CC7\u6599\u5EAB\u9023\u63A5\u5931\u6557",
42941
+ config_saved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 .dbcli",
42942
+ config_exists_overwrite: "\u914D\u7F6E\u6A94\u6848 .dbcli \u5DF2\u5B58\u5728\u3002\u662F\u5426\u8986\u84CB\uFF1F (y/n)\uFF1A",
42943
+ cancelled: "\u5DF2\u53D6\u6D88\u3002\u914D\u7F6E\u672A\u66F4\u6539\u3002"
42944
+ },
42945
+ schema: {
42946
+ description: "\u6AA2\u7D22\u8868\u683C\u7D50\u69CB\u6216\u5217\u51FA\u6240\u6709\u8868\u683C",
42947
+ fetching: "\u6B63\u5728\u6293\u53D6\u6A21\u5F0F...",
42948
+ success: "\u6A21\u5F0F\u5DF2\u66F4\u65B0",
42949
+ not_found: "\u8CC7\u6599\u5EAB\u4E2D\u627E\u4E0D\u5230\u8868\u683C",
42950
+ refresh_prompt: "\u6A21\u5F0F\u6709\u8B8A\u66F4\u3002\u662F\u5426\u5957\u7528\uFF1F (y/n)\uFF1A",
42951
+ columns_header: "\u6B04\u4F4D",
42952
+ scanning_database: "\uD83D\uDD0D \u6B63\u5728\u6383\u63CF\u8CC7\u6599\u5EAB\u67B6\u69CB...",
42953
+ tables_found: "\uD83D\uDCCD \u627E\u5230 {count} \u500B\u8868\u683C\u3002\u6B63\u5728\u7372\u53D6\u67B6\u69CB\u8A73\u60C5...",
42954
+ processing_tables: " \u8655\u7406\u4E86 {processed}/{total} \u500B\u8868\u683C",
42955
+ schema_exists_warning: "\u26A0\uFE0F \u8CC7\u6599\u5EAB\u67B6\u69CB\u5DF2\u5B58\u5728\u65BC .dbcli",
42956
+ use_force_to_override: " \u4F7F\u7528 --force \u9032\u884C\u8986\u84CB\u800C\u7121\u9700\u78BA\u8A8D"
42957
+ },
42958
+ list: {
42959
+ description: "\u5217\u51FA\u8CC7\u6599\u5EAB\u4E2D\u7684\u6240\u6709\u8868\u683C",
42960
+ no_tables: "\u8CC7\u6599\u5EAB\u4E2D\u672A\u627E\u5230\u8868\u683C"
42961
+ },
42962
+ query: {
42963
+ description: "\u57F7\u884C SQL \u67E5\u8A62",
42964
+ executing: "\u6B63\u5728\u57F7\u884C\u67E5\u8A62...",
42965
+ no_results: "\u672A\u8FD4\u56DE\u4EFB\u4F55\u7D50\u679C",
42966
+ result_count: "\u7D50\u679C\uFF1A{count} \u5217"
42967
+ },
42968
+ errors: {
42969
+ message: "\u932F\u8AA4\uFF1A{message}",
42970
+ invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
42971
+ connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
42972
+ permission_denied: "\u6B0A\u9650\u88AB\u62D2\uFF08\u9700\u8981\uFF1A{required}\uFF09",
42973
+ table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
42974
+ unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
42975
+ invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
42976
+ table_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u7684 {operation} \u64CD\u4F5C\u5DF2\u88AB\u9ED1\u540D\u55AE",
42977
+ table_already_blacklisted: "\u932F\u8AA4: \u8868\u683C '{table}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
42978
+ table_not_in_blacklist: "\u932F\u8AA4: \u8868\u683C '{table}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
42979
+ column_already_blacklisted: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
42980
+ column_not_in_blacklist: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
42981
+ invalid_table_name: "\u932F\u8AA4: \u7121\u6548\u7684\u8868\u683C\u540D\u7A31: {table}",
42982
+ invalid_column_format: "\u932F\u8AA4: \u7121\u6548\u7684\u6B04\u4F4D\u683C\u5F0F\u3002\u4F7F\u7528 'table.column'"
42983
+ },
42984
+ success: {
42985
+ inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
42986
+ updated: "\u6210\u529F\u66F4\u65B0 {count} \u5217",
42987
+ deleted: "\u6210\u529F\u522A\u9664 {count} \u5217",
42988
+ no_changes: "\u6C92\u6709\u8B8A\u66F4\u8981\u5957\u7528"
42989
+ },
42990
+ insert: {
42991
+ description: "\u63D2\u5165\u8CC7\u6599\u5230\u8868\u683C",
42992
+ prompt_data: "\u8F38\u5165\u8981\u63D2\u5165\u7684 JSON \u8CC7\u6599\uFF1A",
42993
+ confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A"
42994
+ },
42995
+ update: {
42996
+ description: "\u66F4\u65B0\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
42997
+ prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
42998
+ prompt_set: '\u8F38\u5165 JSON \u66F4\u65B0 (\u4F8B\u5982\uFF1A{"status": "active"})\uFF1A',
42999
+ confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A"
43000
+ },
43001
+ delete: {
43002
+ description: "\u522A\u9664\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
43003
+ prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
43004
+ confirm: "\u522A\u9664 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
43005
+ admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650"
43006
+ },
43007
+ export: {
43008
+ description: "\u532F\u51FA\u67E5\u8A62\u7D50\u679C",
43009
+ formats: "\u652F\u63F4\u7684\u683C\u5F0F\uFF1Ajson\u3001csv",
43010
+ exported: "\u5DF2\u5C07 {count} \u5217\u532F\u51FA\u81F3 {file}"
43011
+ },
43012
+ skill: {
43013
+ description: "\u751F\u6210 AI \u6280\u80FD\u6587\u6A94",
43014
+ installed: "\u6280\u80FD\u5DF2\u5B89\u88DD\u81F3 {path}"
43015
+ },
43016
+ blacklist: {
43017
+ description: "\u7BA1\u7406\u654F\u611F\u8CC7\u6599\u9ED1\u540D\u55AE\u4EE5\u9632\u6B62 AI \u5B58\u53D6",
43018
+ list_title: "\u76EE\u524D\u9ED1\u540D\u55AE\u914D\u7F6E",
43019
+ tables_label: "\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C",
43020
+ columns_label: "\u5DF2\u9ED1\u540D\u55AE\u7684\u6B04\u4F4D",
43021
+ none: "\u76EE\u524D\u6C92\u6709\u9ED1\u540D\u55AE\u8868\u683C\u6216\u6B04\u4F4D",
43022
+ table_added: "\u2713 \u8868\u683C '{table}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
43023
+ table_removed: "\u2713 \u8868\u683C '{table}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
43024
+ column_added: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
43025
+ column_removed: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664"
43026
+ },
43027
+ security: {
43028
+ columns_omitted: "\u5B89\u5168: {count} \u500B\u6B04\u4F4D\u6839\u64DA\u4F60\u7684\u9ED1\u540D\u55AE\u5DF2\u88AB\u96B1\u85CF"
43029
+ },
43030
+ warnings: {
43031
+ blacklist_override_used: "\u8B66\u544A: \u5DF2\u555F\u7528\u9ED1\u540D\u55AE\u8986\u84CB (DBCLI_OVERRIDE_BLACKLIST=true)\u3002\u57F7\u884C {operation} \u5728\u5DF2\u9ED1\u540D\u55AE\u7684\u8868\u683C '{table}' \u4E0A"
43032
+ }
43033
+ };
42815
43034
 
42816
43035
  // src/i18n/message-loader.ts
43036
+ var BUNDLED_MESSAGES = {
43037
+ en: messages_default,
43038
+ "zh-TW": messages_default2
43039
+ };
43040
+
42817
43041
  class MessageLoader {
42818
43042
  static instance = null;
42819
43043
  messages = {};
@@ -42830,31 +43054,10 @@ class MessageLoader {
42830
43054
  return MessageLoader.instance;
42831
43055
  }
42832
43056
  loadMessages() {
42833
- try {
42834
- if (this.currentLang !== "en") {
42835
- this.messages = this.loadLanguageFile(this.currentLang);
42836
- }
42837
- this.fallbackMessages = this.loadLanguageFile("en");
42838
- } catch (error) {
42839
- console.error("Failed to load messages:", error);
42840
- throw new Error("Failed to initialize message loader");
42841
- }
42842
- }
42843
- loadLanguageFile(lang) {
42844
- try {
42845
- const dir = import.meta.dir;
42846
- const filePath = `${dir}/../../resources/lang/${lang}/messages.json`;
42847
- const messages = __require(filePath);
42848
- if (!messages || typeof messages !== "object") {
42849
- throw new Error(`Language file contains invalid data: ${lang}`);
42850
- }
42851
- return messages;
42852
- } catch (error) {
42853
- if (error instanceof Error) {
42854
- throw new Error(`Language file error (${lang}): ${error.message}`);
42855
- }
42856
- throw error;
43057
+ if (this.currentLang !== "en") {
43058
+ this.messages = BUNDLED_MESSAGES[this.currentLang] || {};
42857
43059
  }
43060
+ this.fallbackMessages = BUNDLED_MESSAGES["en"] || {};
42858
43061
  }
42859
43062
  t(key) {
42860
43063
  const parts = key.split(".");
@@ -42954,7 +43157,7 @@ function parseConnectionUrl(url) {
42954
43157
  } else if (protocol === "mariadb") {
42955
43158
  system = "mariadb";
42956
43159
  } else {
42957
- throw new Error(`\u4E0D\u652F\u63F4\u7684\u5354\u8B70: ${protocol}`);
43160
+ throw new Error(`Unsupported protocol: ${protocol}`);
42958
43161
  }
42959
43162
  const host = parsed.hostname || "localhost";
42960
43163
  const port = parsed.port !== "" ? parseInt(parsed.port, 10) : getDefaultsForSystem(system).port || 5432;
@@ -42963,7 +43166,7 @@ function parseConnectionUrl(url) {
42963
43166
  const database = parsed.pathname.slice(1);
42964
43167
  return { system, host, port, user, password, database };
42965
43168
  } catch (error) {
42966
- throw new EnvParseError(`\u7121\u6CD5\u89E3\u6790 DATABASE_URL: ${error instanceof Error ? error.message : String(error)}`);
43169
+ throw new EnvParseError(`Failed to parse DATABASE_URL: ${error instanceof Error ? error.message : String(error)}`);
42967
43170
  }
42968
43171
  }
42969
43172
  function parseEnvDatabase(env) {
@@ -42975,15 +43178,15 @@ function parseEnvDatabase(env) {
42975
43178
  const user = env.DB_USER;
42976
43179
  const database = env.DB_NAME;
42977
43180
  if (!user) {
42978
- throw new EnvParseError("\u4F7F\u7528\u5143\u4EF6\u683C\u5F0F\u6642 DB_USER \u70BA\u5FC5\u9700");
43181
+ throw new EnvParseError("DB_USER is required when using component format");
42979
43182
  }
42980
43183
  if (!database) {
42981
- throw new EnvParseError("\u4F7F\u7528\u5143\u4EF6\u683C\u5F0F\u6642 DB_NAME \u70BA\u5FC5\u9700");
43184
+ throw new EnvParseError("DB_NAME is required when using component format");
42982
43185
  }
42983
43186
  const defaults = getDefaultsForSystem(system);
42984
43187
  const port = env.DB_PORT ? parseInt(env.DB_PORT, 10) : defaults.port || 5432;
42985
43188
  if (isNaN(port) || port < 1 || port > 65535) {
42986
- throw new EnvParseError(`DB_PORT \u5FC5\u9808\u5728 1 \u5230 65535 \u4E4B\u9593\uFF0C\u5F97\u5230: ${env.DB_PORT}`);
43189
+ throw new EnvParseError(`DB_PORT must be between 1 and 65535, got: ${env.DB_PORT}`);
42987
43190
  }
42988
43191
  return {
42989
43192
  system,
@@ -46990,7 +47193,7 @@ var ConnectionConfigSchema = exports_external.object({
46990
47193
  password: exports_external.union([exports_external.string(), EnvRefSchema]).default(""),
46991
47194
  database: StringOrEnvRef
46992
47195
  });
46993
- var PermissionSchema = exports_external.enum(["query-only", "read-write", "admin"]).default("query-only");
47196
+ var PermissionSchema = exports_external.enum(["query-only", "read-write", "data-admin", "admin"]).default("query-only");
46994
47197
  var MetadataSchema = exports_external.object({
46995
47198
  createdAt: exports_external.string().datetime().optional(),
46996
47199
  version: exports_external.string().default("1.0")
@@ -47035,14 +47238,14 @@ function resolveEnvReferences(config, env, parentKey, strict = false) {
47035
47238
  if (!strict) {
47036
47239
  return config;
47037
47240
  }
47038
- throw new ConfigError(`\u74B0\u5883\u8B8A\u91CF\u672A\u5B9A\u7FA9: ${envKey}
47039
- ` + `\u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u91CF\u4E2D\u8A2D\u7F6E ${envKey}\u3002
47040
- ` + `\u63D0\u793A: \u6AA2\u67E5 .env \u6587\u4EF6\u6216\u57F7\u884C 'export ${envKey}=<\u503C>'`);
47241
+ throw new ConfigError(`Environment variable not defined: ${envKey}
47242
+ ` + `Please set ${envKey} in .env or your environment.
47243
+ ` + `Hint: check your .env file or run 'export ${envKey}=<value>'`);
47041
47244
  }
47042
47245
  if (parentKey === "port") {
47043
47246
  const portNum = parseInt(value, 10);
47044
47247
  if (isNaN(portNum) || portNum < 1 || portNum > 65535) {
47045
- throw new ConfigError(`${envKey} \u5FC5\u9808\u662F\u6709\u6548\u7684\u7AEF\u53E3\u865F (1-65535)\uFF0C\u5F97\u5230: ${value}`);
47248
+ throw new ConfigError(`${envKey} must be a valid port number (1-65535), got: ${value}`);
47046
47249
  }
47047
47250
  return portNum;
47048
47251
  }
@@ -47105,9 +47308,9 @@ var configModule = {
47105
47308
  return { ...DEFAULT_CONFIG };
47106
47309
  } catch (error) {
47107
47310
  if (error instanceof Error && error.message.includes("JSON")) {
47108
- throw new ConfigError(`\u7121\u6CD5\u89E3\u6790 .dbcli \u6587\u4EF6\uFF1A${error.message}`);
47311
+ throw new ConfigError(`Failed to parse .dbcli file: ${error.message}`);
47109
47312
  }
47110
- throw new ConfigError(`\u7121\u6CD5\u8B80\u53D6 .dbcli \u914D\u7F6E: ${error instanceof Error ? error.message : String(error)}`);
47313
+ throw new ConfigError(`Failed to read .dbcli config: ${error instanceof Error ? error.message : String(error)}`);
47111
47314
  }
47112
47315
  },
47113
47316
  validate(raw) {
@@ -47115,7 +47318,7 @@ var configModule = {
47115
47318
  return DbcliConfigSchema.parse(raw);
47116
47319
  } catch (error) {
47117
47320
  const errorMessage = error instanceof Error ? error.message : String(error);
47118
- throw new ConfigError(`\u7121\u6548\u7684 .dbcli \u914D\u7F6E\u7D50\u69CB: ${errorMessage}`);
47321
+ throw new ConfigError(`Invalid .dbcli config structure: ${errorMessage}`);
47119
47322
  }
47120
47323
  },
47121
47324
  merge(existing, updates) {
@@ -47165,8 +47368,7 @@ var configModule = {
47165
47368
  await Bun.file(configPath).write(configJson);
47166
47369
  if (password) {
47167
47370
  const envPath = join(path, ".env.local");
47168
- const envContent = `# \u8CC7\u6599\u5EAB\u654F\u611F\u4FE1\u606F - \u8ACB\u52FF\u63D0\u4EA4\u5230 git
47169
- # Database Credentials - DO NOT commit to git
47371
+ const envContent = `# Database Credentials - DO NOT commit to git
47170
47372
 
47171
47373
  DBCLI_PASSWORD=${password}
47172
47374
  `;
@@ -47181,7 +47383,7 @@ DBCLI_PASSWORD=${password}
47181
47383
  if (error instanceof ConfigError) {
47182
47384
  throw error;
47183
47385
  }
47184
- throw new ConfigError(`\u7121\u6CD5\u5BEB\u5165 .dbcli \u914D\u7F6E: ${error instanceof Error ? error.message : String(error)}`);
47386
+ throw new ConfigError(`Failed to write .dbcli config: ${error instanceof Error ? error.message : String(error)}`);
47185
47387
  }
47186
47388
  }
47187
47389
  };
@@ -47296,37 +47498,37 @@ function mapError(error, system, options) {
47296
47498
  const errMsg = String(err?.message || String(error));
47297
47499
  const errCode = String(err?.code || "");
47298
47500
  if (errCode === "ECONNREFUSED" || errMsg.includes("refused")) {
47299
- return new ConnectionError("ECONNREFUSED", `\u7121\u6CD5\u9023\u63A5\u81F3 ${options.host}:${options.port} \u2014 \u4F3A\u670D\u5668\u672A\u904B\u884C\u6216\u672A\u76E3\u807D\u8A72\u57E0\u865F`, [
47300
- `\u78BA\u8A8D ${system} \u670D\u52D9\u5DF2\u555F\u52D5: ${system === "postgresql" ? "systemctl status postgresql" : "systemctl status mysql"}`,
47301
- `\u78BA\u8A8D\u57E0\u865F\u6B63\u78BA: ${system === "postgresql" ? "\u9810\u8A2D 5432" : "\u9810\u8A2D 3306"}`,
47302
- `\u6AA2\u67E5 ${options.host} \u662F\u5426\u53EF\u9054: ping ${options.host} \u6216 telnet ${options.host} ${options.port}`
47501
+ return new ConnectionError("ECONNREFUSED", `Cannot connect to ${options.host}:${options.port} \u2014 server is not running or not listening on this port`, [
47502
+ `Check that the ${system} service is running: ${system === "postgresql" ? "systemctl status postgresql" : "systemctl status mysql"}`,
47503
+ `Verify the port is correct: ${system === "postgresql" ? "default 5432" : "default 3306"}`,
47504
+ `Check that ${options.host} is reachable: ping ${options.host} or telnet ${options.host} ${options.port}`
47303
47505
  ]);
47304
47506
  }
47305
47507
  if (errCode === "ETIMEDOUT" || errMsg.includes("timeout") || errMsg.includes("timed out")) {
47306
- return new ConnectionError("ETIMEDOUT", `\u9023\u63A5\u8D85\u6642 (${options.timeout || 5000}ms) \u2014 \u53EF\u80FD\u662F\u9632\u706B\u7246\u963B\u64CB\u6216\u7DB2\u8DEF\u5EF6\u9072`, [
47307
- `\u6AA2\u67E5\u9632\u706B\u7246: ${system === "postgresql" ? "\u5141\u8A31 TCP 5432" : "\u5141\u8A31 TCP 3306"}`,
47308
- `\u589E\u52A0\u8D85\u6642\u6642\u9593: \u7DE8\u8F2F .dbcli \u4E26\u65B0\u589E "timeout": 15000`,
47309
- `\u78BA\u8A8D\u7DB2\u8DEF\u9023\u63A5: ping ${options.host} -c 3`
47508
+ return new ConnectionError("ETIMEDOUT", `Connection timed out (${options.timeout || 5000}ms) \u2014 may be blocked by a firewall or slow network`, [
47509
+ `Check firewall rules: ${system === "postgresql" ? "allow TCP 5432" : "allow TCP 3306"}`,
47510
+ `Increase timeout: edit .dbcli and add "timeout": 15000`,
47511
+ `Verify network connectivity: ping ${options.host} -c 3`
47310
47512
  ]);
47311
47513
  }
47312
47514
  if (errMsg.includes("authentication") || errMsg.includes("auth") || errMsg.includes("password") || errMsg.includes("access denied") || errMsg.includes("FATAL")) {
47313
- return new ConnectionError("AUTH_FAILED", `\u8A8D\u8B49\u5931\u6557 \u2014 \u6AA2\u67E5\u4F7F\u7528\u8005\u540D\u7A31\u6216\u5BC6\u78BC`, [
47314
- `\u9A57\u8B49\u8A8D\u8B49: ${system === "postgresql" ? `psql -U ${options.user} -h ${options.host}` : `mysql -u ${options.user} -h ${options.host}`}`,
47315
- `\u6AA2\u67E5 pg_hba.conf (PostgreSQL) \u6216 user privileges (MySQL)`,
47316
- `\u91CD\u65B0\u57F7\u884C dbcli init \u4EE5\u66F4\u65B0\u8A8D\u8B49`
47515
+ return new ConnectionError("AUTH_FAILED", `Authentication failed \u2014 check your username or password`, [
47516
+ `Verify credentials: ${system === "postgresql" ? `psql -U ${options.user} -h ${options.host}` : `mysql -u ${options.user} -h ${options.host}`}`,
47517
+ `Check pg_hba.conf (PostgreSQL) or user privileges (MySQL)`,
47518
+ `Re-run dbcli init to update credentials`
47317
47519
  ]);
47318
47520
  }
47319
47521
  if (errCode === "ENOTFOUND" || errMsg.includes("not found") || errMsg.includes("getaddrinfo")) {
47320
- return new ConnectionError("ENOTFOUND", `\u627E\u4E0D\u5230\u4E3B\u6A5F: ${options.host}`, [
47321
- `\u6AA2\u67E5\u4E3B\u6A5F\u540D\u62FC\u5BEB: ${options.host}`,
47322
- `\u78BA\u8A8D DNS \u53EF\u89E3\u6790: nslookup ${options.host}`,
47323
- `\u82E5\u4F7F\u7528 localhost\uFF0C\u8A66\u8A66 127.0.0.1 (IPv4 vs IPv6 \u554F\u984C)`
47522
+ return new ConnectionError("ENOTFOUND", `Host not found: ${options.host}`, [
47523
+ `Check the hostname spelling: ${options.host}`,
47524
+ `Verify DNS resolution: nslookup ${options.host}`,
47525
+ `If using localhost, try 127.0.0.1 (IPv4 vs IPv6 issue)`
47324
47526
  ]);
47325
47527
  }
47326
- return new ConnectionError("UNKNOWN", `\u9023\u63A5\u5931\u6557: ${errMsg}`, [
47327
- `\u6AA2\u67E5\u9023\u63A5\u53C3\u6578: host=${options.host}, port=${options.port}, user=${options.user}`,
47328
- `\u67E5\u770B\u4F3A\u670D\u5668\u65E5\u8A8C: ${system === "postgresql" ? "postgresql.log" : "mysql.log"}`,
47329
- `\u5617\u8A66\u76F4\u63A5\u7528 ${system === "postgresql" ? "psql" : "mysql"} \u547D\u4EE4\u884C\u5DE5\u5177\u6E2C\u8A66`
47528
+ return new ConnectionError("UNKNOWN", `Connection failed: ${errMsg}`, [
47529
+ `Check connection parameters: host=${options.host}, port=${options.port}, user=${options.user}`,
47530
+ `View server logs: ${system === "postgresql" ? "postgresql.log" : "mysql.log"}`,
47531
+ `Try connecting directly with the ${system === "postgresql" ? "psql" : "mysql"} command-line tool`
47330
47532
  ]);
47331
47533
  }
47332
47534
 
@@ -47352,7 +47554,7 @@ class PostgreSQLAdapter {
47352
47554
  constructor(options) {
47353
47555
  this.options = options;
47354
47556
  if (options.port < 1 || options.port > 65535) {
47355
- throw new Error(`\u7121\u6548\u7684\u57E0\u865F: ${options.port}`);
47557
+ throw new Error(`Invalid port number: ${options.port}`);
47356
47558
  }
47357
47559
  }
47358
47560
  async connect() {
@@ -47385,7 +47587,7 @@ class PostgreSQLAdapter {
47385
47587
  }
47386
47588
  async testConnection() {
47387
47589
  if (!this.pool) {
47388
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47590
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47389
47591
  }
47390
47592
  try {
47391
47593
  const result = await this.execute("SELECT 1 as count");
@@ -47396,7 +47598,7 @@ class PostgreSQLAdapter {
47396
47598
  }
47397
47599
  async execute(sql, params) {
47398
47600
  if (!this.pool) {
47399
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47601
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47400
47602
  }
47401
47603
  try {
47402
47604
  const result = params ? await this.pool.query(sql, params) : await this.pool.query(sql);
@@ -47407,7 +47609,7 @@ class PostgreSQLAdapter {
47407
47609
  }
47408
47610
  async listTables() {
47409
47611
  if (!this.pool) {
47410
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47612
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47411
47613
  }
47412
47614
  try {
47413
47615
  const query = `
@@ -47419,7 +47621,9 @@ class PostgreSQLAdapter {
47419
47621
  WHEN 'v' THEN 'view'
47420
47622
  WHEN 'm' THEN 'view'
47421
47623
  ELSE 'table'
47422
- END as table_type
47624
+ END as table_type,
47625
+ (SELECT COUNT(*) FROM information_schema.columns ic
47626
+ WHERE ic.table_schema = 'public' AND ic.table_name = c.relname)::int as column_count
47423
47627
  FROM pg_class c
47424
47628
  JOIN pg_namespace n ON n.oid = c.relnamespace
47425
47629
  WHERE n.nspname = 'public'
@@ -47430,6 +47634,7 @@ class PostgreSQLAdapter {
47430
47634
  return results.map((row) => ({
47431
47635
  name: row.table_name,
47432
47636
  columns: [],
47637
+ columnCount: row.column_count,
47433
47638
  rowCount: Math.max(0, row.estimated_rows || 0),
47434
47639
  engine: "PostgreSQL",
47435
47640
  estimatedRowCount: Math.max(0, row.estimated_rows || 0),
@@ -47441,7 +47646,7 @@ class PostgreSQLAdapter {
47441
47646
  }
47442
47647
  async getTableSchema(tableName) {
47443
47648
  if (!this.pool) {
47444
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47649
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47445
47650
  }
47446
47651
  try {
47447
47652
  const columnQuery = `
@@ -47599,7 +47804,7 @@ class MySQLAdapter {
47599
47804
  this.options = options;
47600
47805
  this.system = options.system === "mariadb" ? "mariadb" : "mysql";
47601
47806
  if (options.port < 1 || options.port > 65535) {
47602
- throw new Error(`\u7121\u6548\u7684\u57E0\u865F: ${options.port}`);
47807
+ throw new Error(`Invalid port number: ${options.port}`);
47603
47808
  }
47604
47809
  }
47605
47810
  async connect() {
@@ -47626,7 +47831,7 @@ class MySQLAdapter {
47626
47831
  }
47627
47832
  async testConnection() {
47628
47833
  if (!this.db) {
47629
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47834
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47630
47835
  }
47631
47836
  try {
47632
47837
  const result = await this.execute("SELECT 1 as count");
@@ -47637,7 +47842,7 @@ class MySQLAdapter {
47637
47842
  }
47638
47843
  async execute(sql, params) {
47639
47844
  if (!this.db) {
47640
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47845
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47641
47846
  }
47642
47847
  try {
47643
47848
  const [rows] = params ? await this.db.execute(sql, params) : await this.db.execute(sql);
@@ -47648,7 +47853,7 @@ class MySQLAdapter {
47648
47853
  }
47649
47854
  async listTables() {
47650
47855
  if (!this.db) {
47651
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47856
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47652
47857
  }
47653
47858
  try {
47654
47859
  const query = `
@@ -47668,7 +47873,8 @@ class MySQLAdapter {
47668
47873
  const results = await this.execute(query);
47669
47874
  return results.map((row) => ({
47670
47875
  name: row.table_name,
47671
- columns: Array(row.column_count).fill(null),
47876
+ columns: [],
47877
+ columnCount: row.column_count,
47672
47878
  rowCount: row.row_count || 0,
47673
47879
  engine: row.engine,
47674
47880
  estimatedRowCount: row.row_count || 0,
@@ -47680,7 +47886,7 @@ class MySQLAdapter {
47680
47886
  }
47681
47887
  async getTableSchema(tableName) {
47682
47888
  if (!this.db) {
47683
- throw new ConnectionError("UNKNOWN", "\u8CC7\u6599\u5EAB\u9023\u63A5\u672A\u5EFA\u7ACB", ["\u547C\u53EB connect() \u4EE5\u5EFA\u7ACB\u9023\u63A5"]);
47889
+ throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
47684
47890
  }
47685
47891
  try {
47686
47892
  const columnQuery = `
@@ -47791,12 +47997,12 @@ class AdapterFactory {
47791
47997
  case "mariadb":
47792
47998
  return new MySQLAdapter(options);
47793
47999
  default:
47794
- throw new Error(`\u4E0D\u652F\u6301\u7684\u8CC7\u6599\u5EAB\u7CFB\u7D71: ${options.system}`);
48000
+ throw new Error(`Unsupported database system: ${options.system}`);
47795
48001
  }
47796
48002
  }
47797
48003
  }
47798
48004
  // src/commands/init.ts
47799
- var initCommand = new Command("init").description("Initialize dbcli configuration with .env parsing and interactive prompts").option("--host <host>", "Database host").option("--port <port>", "Database port").option("--user <user>", "Database user").option("--password <password>", "Database password").option("--name <name>", "Database name").option("--system <system>", "Database system (postgresql, mysql, mariadb)").option("--permission <permission>", "Permission level (query-only, read-write, admin)", "query-only").option("--use-env-refs", "Generate config with environment variable references (for .env)", false).option("--env-host <var>", "Environment variable name for database host (when using --use-env-refs)").option("--env-port <var>", "Environment variable name for database port (when using --use-env-refs)").option("--env-user <var>", "Environment variable name for database user (when using --use-env-refs)").option("--env-password <var>", "Environment variable name for database password (when using --use-env-refs)").option("--env-database <var>", "Environment variable name for database name (when using --use-env-refs)").option("--skip-test", "Skip database connection test").option("--no-interactive", "Non-interactive mode (requires all values via flags)").option("--force", "Skip overwrite confirmation if .dbcli exists").action(async (options) => {
48005
+ var initCommand = new Command("init").description("Initialize dbcli configuration with .env parsing and interactive prompts").option("--host <host>", "Database host").option("--port <port>", "Database port").option("--user <user>", "Database user").option("--password <password>", "Database password").option("--name <name>", "Database name").option("--system <system>", "Database system (postgresql, mysql, mariadb)").option("--permission <permission>", "Permission level (query-only, read-write, data-admin, admin)", "query-only").option("--use-env-refs", "Generate config with environment variable references (for .env)", false).option("--env-host <var>", "Environment variable name for database host (when using --use-env-refs)").option("--env-port <var>", "Environment variable name for database port (when using --use-env-refs)").option("--env-user <var>", "Environment variable name for database user (when using --use-env-refs)").option("--env-password <var>", "Environment variable name for database password (when using --use-env-refs)").option("--env-database <var>", "Environment variable name for database name (when using --use-env-refs)").option("--skip-test", "Skip database connection test").option("--no-interactive", "Non-interactive mode (requires all values via flags)").option("--force", "Skip overwrite confirmation if .dbcli exists").action(async (options) => {
47800
48006
  try {
47801
48007
  await initCommandHandler(options);
47802
48008
  } catch (error) {
@@ -47859,7 +48065,7 @@ async function initCommandHandler(options) {
47859
48065
  "admin"
47860
48066
  ]);
47861
48067
  }
47862
- if (!["query-only", "read-write", "admin"].includes(permission2)) {
48068
+ if (!["query-only", "read-write", "data-admin", "admin"].includes(permission2)) {
47863
48069
  throw new Error(`Invalid permission level: ${permission2}`);
47864
48070
  }
47865
48071
  const newConfig2 = configModule.merge(existingConfig, {
@@ -47905,10 +48111,11 @@ async function initCommandHandler(options) {
47905
48111
  permission = await promptUser.select(t("init.prompt_permission"), [
47906
48112
  "query-only",
47907
48113
  "read-write",
48114
+ "data-admin",
47908
48115
  "admin"
47909
48116
  ]);
47910
48117
  }
47911
- if (!["query-only", "read-write", "admin"].includes(permission)) {
48118
+ if (!["query-only", "read-write", "data-admin", "admin"].includes(permission)) {
47912
48119
  throw new Error(`Invalid permission level: ${permission}`);
47913
48120
  }
47914
48121
  configForWrite = connection;
@@ -48036,7 +48243,7 @@ class TableListFormatter {
48036
48243
  tables.forEach((t2) => {
48037
48244
  table.push([
48038
48245
  t2.name,
48039
- t2.columns.length.toString(),
48246
+ (t2.columnCount ?? t2.columns.length).toString(),
48040
48247
  (t2.rowCount ?? "?").toString(),
48041
48248
  t2.engine || "N/A"
48042
48249
  ]);
@@ -48046,14 +48253,6 @@ class TableListFormatter {
48046
48253
  }
48047
48254
  // src/formatters/json-formatter.ts
48048
48255
  init_size_category();
48049
-
48050
- class JSONFormatter {
48051
- format(data, options) {
48052
- const spacing = options?.compact ? undefined : 2;
48053
- return JSON.stringify(data, null, spacing);
48054
- }
48055
- }
48056
-
48057
48256
  class TableSchemaJSONFormatter {
48058
48257
  format(table, options) {
48059
48258
  const spacing = options?.compact ? undefined : 2;
@@ -48211,8 +48410,15 @@ async function listAction(options) {
48211
48410
  return;
48212
48411
  }
48213
48412
  if (options.format === "json") {
48214
- const formatter = new JSONFormatter;
48215
- console.log(formatter.format(tables, { compact: false }));
48413
+ const listOutput = tables.map((t2) => ({
48414
+ name: t2.name,
48415
+ columnCount: t2.columnCount ?? t2.columns.length,
48416
+ rowCount: t2.rowCount ?? 0,
48417
+ engine: t2.engine,
48418
+ estimatedRowCount: t2.estimatedRowCount ?? t2.rowCount ?? 0,
48419
+ tableType: t2.tableType ?? "table"
48420
+ }));
48421
+ console.log(JSON.stringify(listOutput, null, 2));
48216
48422
  } else {
48217
48423
  const formatter = new TableListFormatter;
48218
48424
  console.log(formatter.format(tables));
@@ -48507,9 +48713,9 @@ async function handleFullDatabaseScan(adapter, config, options) {
48507
48713
  };
48508
48714
  await configModule.write(options.config, updatedConfig);
48509
48715
  console.log(`
48510
- \u2705 \u67B6\u69CB\u5DF2\u5728 .dbcli \u4E2D\u66F4\u65B0`);
48511
- console.log(` ${tables.length} \u500B\u8868\u683C\u53CA\u5B8C\u6574\u5217\u8A73\u60C5\u548C\u95DC\u4FC2`);
48512
- console.log(` \u6642\u9593\u6233: ${updatedConfig.metadata.schemaLastUpdated}`);
48716
+ \u2705 Schema updated in .dbcli`);
48717
+ console.log(` ${tables.length} tables with full column details and relationships`);
48718
+ console.log(` Timestamp: ${updatedConfig.metadata.schemaLastUpdated}`);
48513
48719
  }
48514
48720
 
48515
48721
  // src/core/permission-guard.ts
@@ -48732,6 +48938,29 @@ function checkPermission(sql, permission) {
48732
48938
  classification
48733
48939
  };
48734
48940
  }
48941
+ if (permission === "data-admin") {
48942
+ const allowedTypes = [
48943
+ "SELECT",
48944
+ "INSERT",
48945
+ "UPDATE",
48946
+ "DELETE",
48947
+ "SHOW",
48948
+ "DESCRIBE",
48949
+ "EXPLAIN"
48950
+ ];
48951
+ if (allowedTypes.includes(classification.type)) {
48952
+ return {
48953
+ allowed: true,
48954
+ reason: `${classification.type} operation allowed in data-admin mode`,
48955
+ classification
48956
+ };
48957
+ }
48958
+ return {
48959
+ allowed: false,
48960
+ reason: `${classification.type} operation requires admin permission`,
48961
+ classification
48962
+ };
48963
+ }
48735
48964
  if (permission === "read-write") {
48736
48965
  const allowedTypes = [
48737
48966
  "SELECT",
@@ -48750,7 +48979,7 @@ function checkPermission(sql, permission) {
48750
48979
  }
48751
48980
  return {
48752
48981
  allowed: false,
48753
- reason: `${classification.type} operation requires admin permission`,
48982
+ reason: `${classification.type} operation requires data-admin or admin permission`,
48754
48983
  classification
48755
48984
  };
48756
48985
  }
@@ -49236,7 +49465,7 @@ class DataExecutor {
49236
49465
  const dataKeys = Object.keys(data);
49237
49466
  for (const key2 of dataKeys) {
49238
49467
  if (!columnNames.includes(key2)) {
49239
- throw new Error(`\u8CC7\u6599\u8868 "${tableName}" \u4E2D\u627E\u4E0D\u5230\u6B04\u4F4D "${key2}"\u3002\u6709\u6548\u7684\u6B04\u4F4D: ${columnNames.join(", ")}`);
49468
+ throw new Error(`Column "${key2}" not found in table "${tableName}". Valid columns: ${columnNames.join(", ")}`);
49240
49469
  }
49241
49470
  }
49242
49471
  const columns = dataKeys;
@@ -49269,12 +49498,12 @@ class DataExecutor {
49269
49498
  }
49270
49499
  if (!options?.force) {
49271
49500
  console.log(`
49272
- \u751F\u6210\u7684 SQL:`);
49501
+ Generated SQL:`);
49273
49502
  console.log(` ${sql}`);
49274
49503
  console.log(`
49275
- \u53C3\u6578:`);
49504
+ Parameters:`);
49276
49505
  console.log(` ${JSON.stringify(params, null, 2)}`);
49277
- const confirmed = await promptUser.confirm("\u662F\u5426\u57F7\u884C\u6B64\u64CD\u4F5C?");
49506
+ const confirmed = await promptUser.confirm("Proceed with this operation?");
49278
49507
  if (!confirmed) {
49279
49508
  return {
49280
49509
  status: "success",
@@ -49305,7 +49534,7 @@ class DataExecutor {
49305
49534
  operation: "insert",
49306
49535
  rows_affected: 0,
49307
49536
  timestamp,
49308
- error: "\u6B0A\u9650\u88AB\u62D2: Query-only \u6A21\u5F0F\u50C5\u5141\u8A31 SELECT\u3002\u4F7F\u7528 Read-Write \u6216 Admin \u6A21\u5F0F\u57F7\u884C INSERT\u3002"
49537
+ error: "Permission denied: Query-only mode only allows SELECT. Use Read-Write or Admin mode to execute INSERT."
49309
49538
  };
49310
49539
  }
49311
49540
  return {
@@ -49313,7 +49542,7 @@ class DataExecutor {
49313
49542
  operation: "insert",
49314
49543
  rows_affected: 0,
49315
49544
  timestamp,
49316
- error: `INSERT \u5931\u6557: ${errorMessage}`
49545
+ error: `INSERT failed: ${errorMessage}`
49317
49546
  };
49318
49547
  }
49319
49548
  }
@@ -49336,12 +49565,12 @@ class DataExecutor {
49336
49565
  }
49337
49566
  if (!options?.force) {
49338
49567
  console.log(`
49339
- \u751F\u6210\u7684 SQL:`);
49568
+ Generated SQL:`);
49340
49569
  console.log(` ${sql}`);
49341
49570
  console.log(`
49342
- \u53C3\u6578:`);
49571
+ Parameters:`);
49343
49572
  console.log(` ${JSON.stringify(params, null, 2)}`);
49344
- const confirmed = await promptUser.confirm("\u662F\u5426\u57F7\u884C\u6B64\u64CD\u4F5C?");
49573
+ const confirmed = await promptUser.confirm("Proceed with this operation?");
49345
49574
  if (!confirmed) {
49346
49575
  return {
49347
49576
  status: "success",
@@ -49372,7 +49601,7 @@ class DataExecutor {
49372
49601
  operation: "update",
49373
49602
  rows_affected: 0,
49374
49603
  timestamp,
49375
- error: "\u6B0A\u9650\u88AB\u62D2: Query-only \u6A21\u5F0F\u50C5\u5141\u8A31 SELECT\u3002\u4F7F\u7528 Read-Write \u6216 Admin \u6A21\u5F0F\u57F7\u884C UPDATE\u3002"
49604
+ error: "Permission denied: Query-only mode only allows SELECT. Use Read-Write or Admin mode to execute UPDATE."
49376
49605
  };
49377
49606
  }
49378
49607
  return {
@@ -49380,7 +49609,7 @@ class DataExecutor {
49380
49609
  operation: "update",
49381
49610
  rows_affected: 0,
49382
49611
  timestamp,
49383
- error: `UPDATE \u5931\u6557: ${errorMessage}`
49612
+ error: `UPDATE failed: ${errorMessage}`
49384
49613
  };
49385
49614
  }
49386
49615
  }
@@ -49390,13 +49619,13 @@ class DataExecutor {
49390
49619
  if (this.blacklistValidator) {
49391
49620
  this.blacklistValidator.checkTableBlacklist("DELETE", tableName, []);
49392
49621
  }
49393
- if (this.permission !== "admin") {
49622
+ if (this.permission !== "data-admin" && this.permission !== "admin") {
49394
49623
  return {
49395
49624
  status: "error",
49396
49625
  operation: "delete",
49397
49626
  rows_affected: 0,
49398
49627
  timestamp,
49399
- error: "\u6B0A\u9650\u88AB\u62D2: DELETE \u64CD\u4F5C\u9700\u8981 Admin \u6B0A\u9650\u3002"
49628
+ error: "Permission denied: DELETE operation requires Data-Admin or Admin permission."
49400
49629
  };
49401
49630
  }
49402
49631
  const { sql, params } = this.buildDeleteSql(tableName, where, schema);
@@ -49411,14 +49640,14 @@ class DataExecutor {
49411
49640
  }
49412
49641
  if (!options?.force) {
49413
49642
  console.log(`
49414
- \u26A0\uFE0F \u8B66\u544A: DELETE \u64CD\u4F5C\u662F\u7834\u58DE\u6027\u7684\uFF0C\u7121\u6CD5\u64A4\u92B7\uFF01`);
49643
+ \u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!`);
49415
49644
  console.log(`
49416
- \u751F\u6210\u7684 SQL:`);
49645
+ Generated SQL:`);
49417
49646
  console.log(` ${sql}`);
49418
49647
  console.log(`
49419
- \u53C3\u6578:`);
49648
+ Parameters:`);
49420
49649
  console.log(` ${JSON.stringify(params, null, 2)}`);
49421
- const confirmed = await promptUser.confirm("\u662F\u5426\u771F\u7684\u8981\u57F7\u884C\u6B64 DELETE \u64CD\u4F5C? \u6B64\u64CD\u4F5C\u7121\u6CD5\u64A4\u92B7\u3002");
49650
+ const confirmed = await promptUser.confirm("Are you sure you want to execute this DELETE operation? This cannot be undone.");
49422
49651
  if (!confirmed) {
49423
49652
  return {
49424
49653
  status: "success",
@@ -49448,7 +49677,7 @@ class DataExecutor {
49448
49677
  operation: "delete",
49449
49678
  rows_affected: 0,
49450
49679
  timestamp,
49451
- error: `DELETE \u5931\u6557: ${errorMessage}`
49680
+ error: `DELETE failed: ${errorMessage}`
49452
49681
  };
49453
49682
  }
49454
49683
  }
@@ -49464,7 +49693,7 @@ class DataExecutor {
49464
49693
  const columnNames = schema.columns.map((col) => col.name);
49465
49694
  for (const key2 of [...dataKeys, ...whereKeys]) {
49466
49695
  if (!columnNames.includes(key2)) {
49467
- throw new Error(`\u8CC7\u6599\u8868 "${tableName}" \u4E2D\u627E\u4E0D\u5230\u6B04\u4F4D "${key2}"`);
49696
+ throw new Error(`Column "${key2}" not found in table "${tableName}"`);
49468
49697
  }
49469
49698
  }
49470
49699
  const systemType = this.getSystemType();
@@ -49487,7 +49716,7 @@ class DataExecutor {
49487
49716
  const columnNames = schema.columns.map((col) => col.name);
49488
49717
  for (const key2 of whereKeys) {
49489
49718
  if (!columnNames.includes(key2)) {
49490
- throw new Error(`\u8CC7\u6599\u8868 "${tableName}" \u4E2D\u627E\u4E0D\u5230\u6B04\u4F4D "${key2}"`);
49719
+ throw new Error(`Column "${key2}" not found in table "${tableName}"`);
49491
49720
  }
49492
49721
  }
49493
49722
  const systemType = this.getSystemType();
@@ -49781,7 +50010,7 @@ async function deleteCommand(table, options) {
49781
50010
  if (!config.connection) {
49782
50011
  throw new Error('Run "dbcli init" to configure database connection');
49783
50012
  }
49784
- if (config.permission !== "admin") {
50013
+ if (config.permission !== "data-admin" && config.permission !== "admin") {
49785
50014
  throw new PermissionError(t("delete.admin_only"), { type: "DELETE", isDangerous: true, keywords: ["DELETE"], isComposite: false, confidence: "HIGH" }, config.permission);
49786
50015
  }
49787
50016
  const adapter = AdapterFactory.createAdapter(config.connection);
@@ -49823,7 +50052,7 @@ async function deleteCommand(table, options) {
49823
50052
  process.exit(1);
49824
50053
  }
49825
50054
  if (error instanceof PermissionError) {
49826
- console.error(t_vars("errors.permission_denied", { required: "admin" }));
50055
+ console.error(t_vars("errors.permission_denied", { required: "data-admin" }));
49827
50056
  console.error(` Operation: ${error.classification.type}`);
49828
50057
  console.error(` Message: ${error.message}`);
49829
50058
  process.exit(1);
@@ -49897,7 +50126,17 @@ async function exportCommand(sql, options) {
49897
50126
  // src/commands/skill.ts
49898
50127
  import * as path from "path";
49899
50128
  import { homedir } from "os";
49900
- var SKILL_SOURCE_PATH = path.resolve(import.meta.dir, "../../assets/SKILL.md");
50129
+ function findPackageRoot() {
50130
+ let dir = import.meta.dir;
50131
+ for (let i = 0;i < 5; i++) {
50132
+ if (Bun.file(path.join(dir, "package.json")).size > 0) {
50133
+ return dir;
50134
+ }
50135
+ dir = path.dirname(dir);
50136
+ }
50137
+ return path.resolve(import.meta.dir, "../..");
50138
+ }
50139
+ var SKILL_SOURCE_PATH = path.join(findPackageRoot(), "assets", "SKILL.md");
49901
50140
  async function skillCommand(_program, options) {
49902
50141
  try {
49903
50142
  const skillFile = Bun.file(SKILL_SOURCE_PATH);
@@ -50551,6 +50790,40 @@ Schema diff (${beforeSnapshot.createdAt} -> ${currentSnapshot.createdAt}):`);
50551
50790
  }
50552
50791
  }
50553
50792
 
50793
+ // src/commands/status.ts
50794
+ var statusCommand = new Command("status").description("Show current configuration status (safe for AI agents, no credentials exposed)").option("--format <type>", "Output format: text, json", "json").action(async (options) => {
50795
+ try {
50796
+ const config = await configModule.read(".dbcli");
50797
+ if (!config.connection) {
50798
+ console.error('No configuration found. Run "dbcli init" first.');
50799
+ process.exit(1);
50800
+ }
50801
+ const blacklistTables = config.blacklist?.tables ?? [];
50802
+ const blacklistColumns = config.blacklist?.columns ?? {};
50803
+ const columnCount = Object.values(blacklistColumns).reduce((sum, cols) => sum + cols.length, 0);
50804
+ const status = {
50805
+ permission: config.permission,
50806
+ system: config.connection.system,
50807
+ blacklist: {
50808
+ tables: blacklistTables.length,
50809
+ columns: columnCount
50810
+ },
50811
+ version: config.metadata?.version ?? "unknown"
50812
+ };
50813
+ if (options.format === "text") {
50814
+ console.log(`Permission: ${status.permission}`);
50815
+ console.log(`System: ${status.system}`);
50816
+ console.log(`Blacklist: ${status.blacklist.tables} table(s), ${status.blacklist.columns} column(s)`);
50817
+ console.log(`Version: ${status.version}`);
50818
+ } else {
50819
+ console.log(JSON.stringify(status, null, 2));
50820
+ }
50821
+ } catch (error) {
50822
+ console.error(`Error: ${error.message}`);
50823
+ process.exit(1);
50824
+ }
50825
+ });
50826
+
50554
50827
  // src/cli.ts
50555
50828
  var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--config <path>", "Path to .dbcli config file", ".dbcli");
50556
50829
  program2.addCommand(initCommand);
@@ -50611,6 +50884,7 @@ program2.command("skill").description(t("skill.description")).option("--install
50611
50884
  program2.addCommand(blacklistCommand);
50612
50885
  program2.addCommand(checkCommand);
50613
50886
  program2.addCommand(diffCommand);
50887
+ program2.addCommand(statusCommand);
50614
50888
  if (!process.argv.slice(2).length) {
50615
50889
  program2.outputHelp();
50616
50890
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carllee1983/dbcli",
3
- "version": "0.3.1-beta",
3
+ "version": "0.4.0-beta",
4
4
  "description": "Database CLI for AI agents",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "files": [
17
17
  "dist/",
18
+ "assets/",
18
19
  "README.md",
19
20
  "CHANGELOG.md",
20
21
  "LICENSE"