@carllee1983/dbcli 0.5.0-beta → 0.5.2-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.
- package/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +31 -1
- package/README.zh-TW.md +11 -0
- package/assets/SKILL.md +2 -0
- package/dist/cli.mjs +202 -54
- package/package.json +22 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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
|
+
## [0.5.2-beta] - 2026-03-27
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **`init --use-env-refs` permission bug**: Interactive env-ref mode now correctly offers all 4 permission levels (was missing `data-admin`)
|
|
13
|
+
- **`init` i18n completeness**: All 10 hardcoded English messages replaced with i18n keys (supports en/zh-TW)
|
|
14
|
+
- **`init` duplicate code**: Extracted shared `.dbcli exists` overwrite check into `checkOverwrite()` helper
|
|
15
|
+
- **`--use-env-refs` help text**: Improved option description to clarify CI/CD and multi-env use case
|
|
16
|
+
- **Documentation**: Added `--use-env-refs` to README (en/zh-TW), CHANGELOG, and SKILL.md with AI agent guidance
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [0.5.1-beta] - 2026-03-27
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **Database version check**: Warns on stderr when connected database version is below minimum supported (PostgreSQL 12+, MySQL 8.0+, MariaDB 10.5+). Non-blocking — connection proceeds normally.
|
|
25
|
+
- **`dbcli doctor` DB version check**: New "Database version" item in Connection & Data group.
|
|
26
|
+
- **`dbcli init --use-env-refs`**: Store environment variable references (`{"$env": "DB_HOST"}`) in config instead of actual values. Supports interactive and non-interactive modes with `--env-host`, `--env-port`, `--env-user`, `--env-password`, `--env-database` options. Suitable for CI/CD and multi-environment deployments.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **`init` permission bug**: Interactive env-ref mode now correctly offers all 4 permission levels (was missing `data-admin`)
|
|
31
|
+
- **`init` i18n**: All hardcoded English messages in init command replaced with i18n keys (10 messages)
|
|
32
|
+
- **`init` duplicate code**: Extracted shared `.dbcli exists` overwrite check into `checkOverwrite()` helper
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
8
36
|
## [0.5.0-beta] - 2026-03-27
|
|
9
37
|
|
|
10
38
|
### UX & Developer Experience — Colors, Logging, Diagnostics, and Tooling
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Carl Lee
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -96,9 +96,27 @@ Initialize a new dbcli project with database connection configuration.
|
|
|
96
96
|
|
|
97
97
|
**Usage:**
|
|
98
98
|
```bash
|
|
99
|
-
dbcli init
|
|
99
|
+
dbcli init [OPTIONS]
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
**Options:**
|
|
103
|
+
- `--system <type>` — Database system: `postgresql`, `mysql`, `mariadb`
|
|
104
|
+
- `--host <host>` — Database host
|
|
105
|
+
- `--port <port>` — Database port
|
|
106
|
+
- `--user <user>` — Database user
|
|
107
|
+
- `--password <pass>` — Database password
|
|
108
|
+
- `--name <db>` — Database name
|
|
109
|
+
- `--permission <level>` — Permission level: `query-only`, `read-write`, `data-admin`, `admin`
|
|
110
|
+
- `--use-env-refs` — Store environment variable references instead of actual values in config
|
|
111
|
+
- `--env-host <var>` — Env var name for host (with `--use-env-refs`)
|
|
112
|
+
- `--env-port <var>` — Env var name for port (with `--use-env-refs`)
|
|
113
|
+
- `--env-user <var>` — Env var name for user (with `--use-env-refs`)
|
|
114
|
+
- `--env-password <var>` — Env var name for password (with `--use-env-refs`)
|
|
115
|
+
- `--env-database <var>` — Env var name for database (with `--use-env-refs`)
|
|
116
|
+
- `--skip-test` — Skip connection test
|
|
117
|
+
- `--no-interactive` — Non-interactive mode (requires all options)
|
|
118
|
+
- `--force` — Overwrite existing config without confirmation
|
|
119
|
+
|
|
102
120
|
**Behavior:**
|
|
103
121
|
- Reads `.env` file if present (auto-fills DATABASE_URL, DB_* variables)
|
|
104
122
|
- Prompts for missing values (host, port, user, password, database name, permission level)
|
|
@@ -116,8 +134,20 @@ dbcli init
|
|
|
116
134
|
|
|
117
135
|
# Specify permission level
|
|
118
136
|
echo "PERMISSION_LEVEL=admin" >> .env && dbcli init
|
|
137
|
+
|
|
138
|
+
# Store env var references instead of values (interactive)
|
|
139
|
+
dbcli init --use-env-refs
|
|
140
|
+
|
|
141
|
+
# Store env var references (non-interactive)
|
|
142
|
+
dbcli init --use-env-refs --system mysql \
|
|
143
|
+
--env-host DB_HOST --env-port DB_PORT \
|
|
144
|
+
--env-user DB_USER --env-password DB_PASSWORD \
|
|
145
|
+
--env-database DB_DATABASE \
|
|
146
|
+
--no-interactive
|
|
119
147
|
```
|
|
120
148
|
|
|
149
|
+
> **`--use-env-refs`:** When enabled, the config stores environment variable names (e.g., `{"$env": "DB_HOST"}`) instead of actual values. This avoids writing sensitive credentials into the config file, making it suitable for multi-environment deployments and CI/CD pipelines. At connection time, dbcli automatically reads the actual values from the referenced environment variables.
|
|
150
|
+
|
|
121
151
|
---
|
|
122
152
|
|
|
123
153
|
#### `dbcli list`
|
package/README.zh-TW.md
CHANGED
|
@@ -238,8 +238,19 @@ dbcli init
|
|
|
238
238
|
|
|
239
239
|
dbcli init --host db.example.com --port 5432 --user admin --password secret --name prod_db --system postgresql
|
|
240
240
|
# 非交互式初始化
|
|
241
|
+
|
|
242
|
+
dbcli init --use-env-refs
|
|
243
|
+
# 交互式:提示輸入環境變數名稱,config 中儲存 {"$env": "DB_HOST"} 而非實際值
|
|
244
|
+
|
|
245
|
+
dbcli init --use-env-refs --system mysql \
|
|
246
|
+
--env-host DB_HOST --env-port DB_PORT \
|
|
247
|
+
--env-user DB_USER --env-password DB_PASSWORD \
|
|
248
|
+
--env-database DB_DATABASE --no-interactive
|
|
249
|
+
# 非交互式環境變數參照模式,適合 CI/CD
|
|
241
250
|
```
|
|
242
251
|
|
|
252
|
+
> **`--use-env-refs` 說明:** 使用此選項時,config 中儲存的是環境變數名稱(如 `{"$env": "DB_HOST"}`)而非實際值。這樣可以避免將敏感資訊寫入 config 檔案,適合多環境部署或 CI/CD 場景。連線時 dbcli 會自動從環境變數讀取實際值。
|
|
253
|
+
|
|
243
254
|
### 列出表
|
|
244
255
|
|
|
245
256
|
```bash
|
package/assets/SKILL.md
CHANGED
|
@@ -30,6 +30,8 @@ dbcli init --no-interactive --force # Non-interactive, skip overwrite confirmati
|
|
|
30
30
|
|
|
31
31
|
**Key options:** `--system <postgresql|mysql|mariadb>`, `--permission <query-only|read-write|data-admin|admin>`, `--use-env-refs`, `--skip-test`, `--no-interactive`, `--force`
|
|
32
32
|
|
|
33
|
+
> **AI agent note on `--use-env-refs`:** If an existing `.dbcli` config contains `{"$env": "DB_HOST"}` style references, the connection values are read from environment variables at runtime. Do NOT re-run `init` to replace these references with actual values — the env-ref format is intentional for CI/CD and multi-environment setups.
|
|
34
|
+
|
|
33
35
|
### list
|
|
34
36
|
|
|
35
37
|
List all tables.
|
package/dist/cli.mjs
CHANGED
|
@@ -42828,7 +42828,7 @@ var {
|
|
|
42828
42828
|
// package.json
|
|
42829
42829
|
var package_default = {
|
|
42830
42830
|
name: "@carllee1983/dbcli",
|
|
42831
|
-
version: "0.5.
|
|
42831
|
+
version: "0.5.2-beta",
|
|
42832
42832
|
description: "Database CLI for AI agents",
|
|
42833
42833
|
type: "module",
|
|
42834
42834
|
publishConfig: {
|
|
@@ -42837,6 +42837,27 @@ var package_default = {
|
|
|
42837
42837
|
bin: {
|
|
42838
42838
|
dbcli: "./dist/cli.mjs"
|
|
42839
42839
|
},
|
|
42840
|
+
license: "MIT",
|
|
42841
|
+
author: "Carl Lee",
|
|
42842
|
+
repository: {
|
|
42843
|
+
type: "git",
|
|
42844
|
+
url: "git+https://github.com/CarlLee1983/dbcli.git"
|
|
42845
|
+
},
|
|
42846
|
+
homepage: "https://github.com/CarlLee1983/dbcli#readme",
|
|
42847
|
+
bugs: {
|
|
42848
|
+
url: "https://github.com/CarlLee1983/dbcli/issues"
|
|
42849
|
+
},
|
|
42850
|
+
keywords: [
|
|
42851
|
+
"database",
|
|
42852
|
+
"cli",
|
|
42853
|
+
"ai",
|
|
42854
|
+
"agent",
|
|
42855
|
+
"postgresql",
|
|
42856
|
+
"mysql",
|
|
42857
|
+
"mariadb",
|
|
42858
|
+
"permissions",
|
|
42859
|
+
"blacklist"
|
|
42860
|
+
],
|
|
42840
42861
|
engines: {
|
|
42841
42862
|
node: ">=18.0.0",
|
|
42842
42863
|
bun: ">=1.3.3"
|
|
@@ -42902,7 +42923,11 @@ var messages_default = {
|
|
|
42902
42923
|
connection_failed: "\u2717 Database connection failed",
|
|
42903
42924
|
config_saved: "Configuration saved to .dbcli",
|
|
42904
42925
|
config_exists_overwrite: "Configuration file .dbcli already exists. Overwrite? (y/n): ",
|
|
42905
|
-
cancelled: "Cancelled. Configuration not changed."
|
|
42926
|
+
cancelled: "Cancelled. Configuration not changed.",
|
|
42927
|
+
skip_test_env_ref: "Skipping connection test in env-ref mode",
|
|
42928
|
+
skip_test: "Skipping connection test (--skip-test)",
|
|
42929
|
+
connection_hints: "Hints:",
|
|
42930
|
+
config_exists_use_force: ".dbcli exists. Use --force option to overwrite."
|
|
42906
42931
|
},
|
|
42907
42932
|
schema: {
|
|
42908
42933
|
description: "Retrieve table structure or list all tables",
|
|
@@ -42941,7 +42966,18 @@ var messages_default = {
|
|
|
42941
42966
|
column_already_blacklisted: "Error: Column '{table}.{column}' is already blacklisted",
|
|
42942
42967
|
column_not_in_blacklist: "Error: Column '{table}.{column}' is not in the blacklist",
|
|
42943
42968
|
invalid_table_name: "Error: Invalid table name: {table}",
|
|
42944
|
-
invalid_column_format: "Error: Invalid column format. Use 'table.column'"
|
|
42969
|
+
invalid_column_format: "Error: Invalid column format. Use 'table.column'",
|
|
42970
|
+
invalid_system: "Invalid database system: {system}",
|
|
42971
|
+
invalid_permission: "Invalid permission level: {permission}",
|
|
42972
|
+
invalid_port: "Invalid port: {port}",
|
|
42973
|
+
require_user: "Non-interactive mode requires --user option",
|
|
42974
|
+
require_name: "Non-interactive mode requires --name option",
|
|
42975
|
+
env_refs_missing_options: `When using --use-env-refs, environment variable names must be specified.
|
|
42976
|
+
Provide options: --env-host, --env-port, --env-user, --env-password, --env-database
|
|
42977
|
+
Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactive`,
|
|
42978
|
+
env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
|
|
42979
|
+
Set {envKey} in .env or environment variables.
|
|
42980
|
+
Hint: run 'export {envKey}=<value>' and retry`
|
|
42945
42981
|
},
|
|
42946
42982
|
success: {
|
|
42947
42983
|
inserted: "Successfully inserted {count} row(s)",
|
|
@@ -42991,6 +43027,11 @@ var messages_default = {
|
|
|
42991
43027
|
},
|
|
42992
43028
|
warnings: {
|
|
42993
43029
|
blacklist_override_used: "Warning: Blacklist override enabled (DBCLI_OVERRIDE_BLACKLIST=true). Executing {operation} on blacklisted table '{table}'"
|
|
43030
|
+
},
|
|
43031
|
+
version: {
|
|
43032
|
+
unsupported_warning: "{system} {version} is below minimum supported version {minVersion}. Some features may not work correctly.",
|
|
43033
|
+
doctor_pass: "{system} {version} (meets >= {minVersion})",
|
|
43034
|
+
doctor_warn: "{system} {version} is below supported >= {minVersion}"
|
|
42994
43035
|
}
|
|
42995
43036
|
};
|
|
42996
43037
|
// resources/lang/zh-TW/messages.json
|
|
@@ -43011,7 +43052,11 @@ var messages_default2 = {
|
|
|
43011
43052
|
connection_failed: "\u2717 \u8CC7\u6599\u5EAB\u9023\u63A5\u5931\u6557",
|
|
43012
43053
|
config_saved: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\u81F3 .dbcli",
|
|
43013
43054
|
config_exists_overwrite: "\u914D\u7F6E\u6A94\u6848 .dbcli \u5DF2\u5B58\u5728\u3002\u662F\u5426\u8986\u84CB\uFF1F (y/n)\uFF1A",
|
|
43014
|
-
cancelled: "\u5DF2\u53D6\u6D88\u3002\u914D\u7F6E\u672A\u66F4\u6539\u3002"
|
|
43055
|
+
cancelled: "\u5DF2\u53D6\u6D88\u3002\u914D\u7F6E\u672A\u66F4\u6539\u3002",
|
|
43056
|
+
skip_test_env_ref: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u6A21\u5F0F\uFF09",
|
|
43057
|
+
skip_test: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08--skip-test\uFF09",
|
|
43058
|
+
connection_hints: "\u63D0\u793A\uFF1A",
|
|
43059
|
+
config_exists_use_force: ".dbcli \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u9078\u9805\u8986\u84CB\u3002"
|
|
43015
43060
|
},
|
|
43016
43061
|
schema: {
|
|
43017
43062
|
description: "\u6AA2\u7D22\u8868\u683C\u7D50\u69CB\u6216\u5217\u51FA\u6240\u6709\u8868\u683C",
|
|
@@ -43050,7 +43095,18 @@ var messages_default2 = {
|
|
|
43050
43095
|
column_already_blacklisted: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u5DF2\u5728\u9ED1\u540D\u55AE\u4E2D",
|
|
43051
43096
|
column_not_in_blacklist: "\u932F\u8AA4: \u6B04\u4F4D '{table}.{column}' \u4E0D\u5728\u9ED1\u540D\u55AE\u4E2D",
|
|
43052
43097
|
invalid_table_name: "\u932F\u8AA4: \u7121\u6548\u7684\u8868\u683C\u540D\u7A31: {table}",
|
|
43053
|
-
invalid_column_format: "\u932F\u8AA4: \u7121\u6548\u7684\u6B04\u4F4D\u683C\u5F0F\u3002\u4F7F\u7528 'table.column'"
|
|
43098
|
+
invalid_column_format: "\u932F\u8AA4: \u7121\u6548\u7684\u6B04\u4F4D\u683C\u5F0F\u3002\u4F7F\u7528 'table.column'",
|
|
43099
|
+
invalid_system: "\u7121\u6548\u7684\u8CC7\u6599\u5EAB\u7CFB\u7D71\uFF1A{system}",
|
|
43100
|
+
invalid_permission: "\u7121\u6548\u7684\u6B0A\u9650\u7B49\u7D1A\uFF1A{permission}",
|
|
43101
|
+
invalid_port: "\u7121\u6548\u7684\u57E0\u865F\uFF1A{port}",
|
|
43102
|
+
require_user: "\u975E\u4E92\u52D5\u6A21\u5F0F\u9700\u8981 --user \u9078\u9805",
|
|
43103
|
+
require_name: "\u975E\u4E92\u52D5\u6A21\u5F0F\u9700\u8981 --name \u9078\u9805",
|
|
43104
|
+
env_refs_missing_options: `\u4F7F\u7528 --use-env-refs \u6642\uFF0C\u5FC5\u9808\u6307\u5B9A\u74B0\u5883\u8B8A\u6578\u540D\u7A31\u3002
|
|
43105
|
+
\u8ACB\u63D0\u4F9B\u9078\u9805\uFF1A--env-host\u3001--env-port\u3001--env-user\u3001--env-password\u3001--env-database
|
|
43106
|
+
\u6216\u4F7F\u7528\u4E92\u52D5\u6A21\u5F0F\uFF1A\u57F7\u884C "dbcli init --use-env-refs"\uFF08\u4E0D\u52A0 --no-interactive\uFF09`,
|
|
43107
|
+
env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
|
|
43108
|
+
\u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
|
|
43109
|
+
\u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`
|
|
43054
43110
|
},
|
|
43055
43111
|
success: {
|
|
43056
43112
|
inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
|
|
@@ -43100,6 +43156,11 @@ var messages_default2 = {
|
|
|
43100
43156
|
},
|
|
43101
43157
|
warnings: {
|
|
43102
43158
|
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"
|
|
43159
|
+
},
|
|
43160
|
+
version: {
|
|
43161
|
+
unsupported_warning: "{system} {version} \u4F4E\u65BC\u6700\u4F4E\u652F\u63F4\u7248\u672C {minVersion}\u3002\u90E8\u5206\u529F\u80FD\u53EF\u80FD\u7121\u6CD5\u6B63\u5E38\u904B\u4F5C\u3002",
|
|
43162
|
+
doctor_pass: "{system} {version}\uFF08\u7B26\u5408 >= {minVersion}\uFF09",
|
|
43163
|
+
doctor_warn: "{system} {version} \u4F4E\u65BC\u652F\u63F4\u7248\u672C >= {minVersion}"
|
|
43103
43164
|
}
|
|
43104
43165
|
};
|
|
43105
43166
|
|
|
@@ -47658,6 +47719,61 @@ var Result = import_lib.default.Result;
|
|
|
47658
47719
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
47659
47720
|
var defaults = import_lib.default.defaults;
|
|
47660
47721
|
|
|
47722
|
+
// src/utils/db-version-check.ts
|
|
47723
|
+
var MIN_SUPPORTED_VERSIONS = {
|
|
47724
|
+
postgresql: "12.0",
|
|
47725
|
+
mysql: "8.0",
|
|
47726
|
+
mariadb: "10.5"
|
|
47727
|
+
};
|
|
47728
|
+
function parseVersionSegments(version) {
|
|
47729
|
+
const match = version.match(/^(\d+(?:\.\d+)*)/);
|
|
47730
|
+
if (!match)
|
|
47731
|
+
return [];
|
|
47732
|
+
return match[1].split(".").map(Number);
|
|
47733
|
+
}
|
|
47734
|
+
function isMariaDBVersion(versionString) {
|
|
47735
|
+
return /mariadb/i.test(versionString);
|
|
47736
|
+
}
|
|
47737
|
+
function extractMariaDBVersion(versionString) {
|
|
47738
|
+
const match = versionString.match(/(\d+\.\d+\.\d+)-MariaDB/i);
|
|
47739
|
+
if (match)
|
|
47740
|
+
return match[1];
|
|
47741
|
+
const prefixMatch = versionString.match(/^5\.5\.5-(\d+\.\d+\.\d+)/);
|
|
47742
|
+
if (prefixMatch)
|
|
47743
|
+
return prefixMatch[1];
|
|
47744
|
+
return versionString;
|
|
47745
|
+
}
|
|
47746
|
+
function compareVersions(a, b) {
|
|
47747
|
+
const segA = parseVersionSegments(a);
|
|
47748
|
+
const segB = parseVersionSegments(b);
|
|
47749
|
+
const len = Math.max(segA.length, segB.length);
|
|
47750
|
+
for (let i = 0;i < len; i++) {
|
|
47751
|
+
const diff = (segA[i] ?? 0) - (segB[i] ?? 0);
|
|
47752
|
+
if (diff !== 0)
|
|
47753
|
+
return diff;
|
|
47754
|
+
}
|
|
47755
|
+
return 0;
|
|
47756
|
+
}
|
|
47757
|
+
function checkDbVersion(rawVersion, declaredSystem) {
|
|
47758
|
+
const isMariaDB = isMariaDBVersion(rawVersion);
|
|
47759
|
+
const system = isMariaDB ? "mariadb" : declaredSystem;
|
|
47760
|
+
const serverVersion = isMariaDB ? extractMariaDBVersion(rawVersion) : rawVersion;
|
|
47761
|
+
const minVersion = MIN_SUPPORTED_VERSIONS[system];
|
|
47762
|
+
const supported = compareVersions(serverVersion, minVersion) >= 0;
|
|
47763
|
+
return { serverVersion, system, supported, minVersion };
|
|
47764
|
+
}
|
|
47765
|
+
function warnIfUnsupported(result) {
|
|
47766
|
+
if (result.supported)
|
|
47767
|
+
return;
|
|
47768
|
+
const message = t_vars("version.unsupported_warning", {
|
|
47769
|
+
system: result.system,
|
|
47770
|
+
version: result.serverVersion,
|
|
47771
|
+
minVersion: result.minVersion
|
|
47772
|
+
});
|
|
47773
|
+
process.stderr.write(colors.warn(`\u26A0 ${message}`) + `
|
|
47774
|
+
`);
|
|
47775
|
+
}
|
|
47776
|
+
|
|
47661
47777
|
// src/adapters/postgresql-adapter.ts
|
|
47662
47778
|
class PostgreSQLAdapter {
|
|
47663
47779
|
pool = null;
|
|
@@ -47681,6 +47797,11 @@ class PostgreSQLAdapter {
|
|
|
47681
47797
|
statement_timeout: this.options.timeout || 5000
|
|
47682
47798
|
});
|
|
47683
47799
|
await this.testConnection();
|
|
47800
|
+
try {
|
|
47801
|
+
const rawVersion = await this.getServerVersion();
|
|
47802
|
+
const result = checkDbVersion(rawVersion, "postgresql");
|
|
47803
|
+
warnIfUnsupported(result);
|
|
47804
|
+
} catch {}
|
|
47684
47805
|
} catch (error) {
|
|
47685
47806
|
throw mapError(error, "postgresql", this.options);
|
|
47686
47807
|
}
|
|
@@ -47719,6 +47840,10 @@ class PostgreSQLAdapter {
|
|
|
47719
47840
|
throw mapError(error, "postgresql", this.options);
|
|
47720
47841
|
}
|
|
47721
47842
|
}
|
|
47843
|
+
async getServerVersion() {
|
|
47844
|
+
const rows = await this.execute("SHOW server_version");
|
|
47845
|
+
return rows[0]?.server_version ?? "unknown";
|
|
47846
|
+
}
|
|
47722
47847
|
async listTables() {
|
|
47723
47848
|
if (!this.pool) {
|
|
47724
47849
|
throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
|
|
@@ -47929,6 +48054,11 @@ class MySQLAdapter {
|
|
|
47929
48054
|
database: this.options.database
|
|
47930
48055
|
});
|
|
47931
48056
|
await this.testConnection();
|
|
48057
|
+
try {
|
|
48058
|
+
const rawVersion = await this.getServerVersion();
|
|
48059
|
+
const result = checkDbVersion(rawVersion, this.system);
|
|
48060
|
+
warnIfUnsupported(result);
|
|
48061
|
+
} catch {}
|
|
47932
48062
|
} catch (error) {
|
|
47933
48063
|
throw mapError(error, this.system, this.options);
|
|
47934
48064
|
}
|
|
@@ -47963,6 +48093,10 @@ class MySQLAdapter {
|
|
|
47963
48093
|
throw mapError(error, this.system, this.options);
|
|
47964
48094
|
}
|
|
47965
48095
|
}
|
|
48096
|
+
async getServerVersion() {
|
|
48097
|
+
const rows = await this.execute("SELECT VERSION() as version");
|
|
48098
|
+
return rows[0]?.version ?? "unknown";
|
|
48099
|
+
}
|
|
47966
48100
|
async listTables() {
|
|
47967
48101
|
if (!this.db) {
|
|
47968
48102
|
throw new ConnectionError("UNKNOWN", "Database connection not established", ["Call connect() to establish a connection"]);
|
|
@@ -48114,7 +48248,23 @@ class AdapterFactory {
|
|
|
48114
48248
|
}
|
|
48115
48249
|
}
|
|
48116
48250
|
// src/commands/init.ts
|
|
48117
|
-
var
|
|
48251
|
+
var VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
|
|
48252
|
+
async function checkOverwrite(shouldPrompt, force) {
|
|
48253
|
+
const configFile = Bun.file(".dbcli");
|
|
48254
|
+
const fileExists = await configFile.exists();
|
|
48255
|
+
if (!fileExists || force)
|
|
48256
|
+
return true;
|
|
48257
|
+
if (shouldPrompt) {
|
|
48258
|
+
const overwrite = await promptUser.confirm(t("init.config_exists_overwrite"));
|
|
48259
|
+
if (!overwrite) {
|
|
48260
|
+
console.log(t("init.cancelled"));
|
|
48261
|
+
return false;
|
|
48262
|
+
}
|
|
48263
|
+
return true;
|
|
48264
|
+
}
|
|
48265
|
+
throw new Error(t("init.config_exists_use_force"));
|
|
48266
|
+
}
|
|
48267
|
+
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", "Store env var references in config instead of actual values (for CI/CD or multi-env)", false).option("--env-host <var>", "Env var name for host (with --use-env-refs)").option("--env-port <var>", "Env var name for port (with --use-env-refs)").option("--env-user <var>", "Env var name for user (with --use-env-refs)").option("--env-password <var>", "Env var name for password (with --use-env-refs)").option("--env-database <var>", "Env var name for database (with --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) => {
|
|
48118
48268
|
try {
|
|
48119
48269
|
await initCommandHandler(options);
|
|
48120
48270
|
} catch (error) {
|
|
@@ -48148,7 +48298,7 @@ async function initCommandHandler(options) {
|
|
|
48148
48298
|
]);
|
|
48149
48299
|
}
|
|
48150
48300
|
if (!["postgresql", "mysql", "mariadb"].includes(system)) {
|
|
48151
|
-
throw new Error(
|
|
48301
|
+
throw new Error(t_vars("errors.invalid_system", { system }));
|
|
48152
48302
|
}
|
|
48153
48303
|
const defaults2 = getDefaultsForSystem(system);
|
|
48154
48304
|
const connection = {
|
|
@@ -48170,34 +48320,25 @@ async function initCommandHandler(options) {
|
|
|
48170
48320
|
database: { $env: envDatabase }
|
|
48171
48321
|
};
|
|
48172
48322
|
let permission2 = options.permission || "query-only";
|
|
48173
|
-
if (
|
|
48323
|
+
if (!options.permission) {
|
|
48174
48324
|
permission2 = await promptUser.select(t("init.prompt_permission"), [
|
|
48175
48325
|
"query-only",
|
|
48176
48326
|
"read-write",
|
|
48327
|
+
"data-admin",
|
|
48177
48328
|
"admin"
|
|
48178
48329
|
]);
|
|
48179
48330
|
}
|
|
48180
|
-
if (!
|
|
48181
|
-
throw new Error(
|
|
48331
|
+
if (!VALID_PERMISSIONS.includes(permission2)) {
|
|
48332
|
+
throw new Error(t_vars("errors.invalid_permission", { permission: permission2 }));
|
|
48182
48333
|
}
|
|
48183
48334
|
const newConfig2 = configModule.merge(existingConfig, {
|
|
48184
48335
|
connection: configForWrite,
|
|
48185
48336
|
permission: permission2
|
|
48186
48337
|
});
|
|
48187
|
-
const
|
|
48188
|
-
|
|
48189
|
-
|
|
48190
|
-
|
|
48191
|
-
const overwrite = await promptUser.confirm(t("init.config_exists_overwrite"));
|
|
48192
|
-
if (!overwrite) {
|
|
48193
|
-
console.log(t("init.cancelled"));
|
|
48194
|
-
return;
|
|
48195
|
-
}
|
|
48196
|
-
} else {
|
|
48197
|
-
throw new Error(".dbcli exists. Use --force option to overwrite.");
|
|
48198
|
-
}
|
|
48199
|
-
}
|
|
48200
|
-
console.log("\u23ED\uFE0F Skipping connection test in env-ref mode");
|
|
48338
|
+
const canProceed2 = await checkOverwrite(shouldPrompt, !!options.force);
|
|
48339
|
+
if (!canProceed2)
|
|
48340
|
+
return;
|
|
48341
|
+
console.log(`\u23ED\uFE0F ${t("init.skip_test_env_ref")}`);
|
|
48201
48342
|
await configModule.write(".dbcli", newConfig2);
|
|
48202
48343
|
console.log(t("init.config_saved"));
|
|
48203
48344
|
return;
|
|
@@ -48206,17 +48347,17 @@ async function initCommandHandler(options) {
|
|
|
48206
48347
|
const portStr = options.port || (envConfig?.port ? String(envConfig.port) : null) || (shouldPrompt ? await promptUser.text(t("init.prompt_port"), String(defaults2.port || 5432)) : String(defaults2.port || 5432));
|
|
48207
48348
|
const port = parseInt(portStr, 10);
|
|
48208
48349
|
if (isNaN(port) || port < 1 || port > 65535) {
|
|
48209
|
-
throw new Error(
|
|
48350
|
+
throw new Error(t_vars("errors.invalid_port", { port: portStr }));
|
|
48210
48351
|
}
|
|
48211
48352
|
connection.port = port;
|
|
48212
48353
|
connection.user = options.user || envConfig?.user || (shouldPrompt ? await promptUser.text(t("init.prompt_user")) : "");
|
|
48213
48354
|
if (!connection.user && !shouldPrompt && !options.useEnvRefs) {
|
|
48214
|
-
throw new Error("
|
|
48355
|
+
throw new Error(t("errors.require_user"));
|
|
48215
48356
|
}
|
|
48216
48357
|
connection.password = options.password || envConfig?.password || (shouldPrompt ? await promptUser.text(t("init.prompt_password")) : "");
|
|
48217
48358
|
connection.database = options.name || envConfig?.database || (shouldPrompt ? await promptUser.text(t("init.prompt_name")) : "");
|
|
48218
48359
|
if (!connection.database && !shouldPrompt && !options.useEnvRefs) {
|
|
48219
|
-
throw new Error("
|
|
48360
|
+
throw new Error(t("errors.require_name"));
|
|
48220
48361
|
}
|
|
48221
48362
|
let permission = options.permission || "query-only";
|
|
48222
48363
|
if (shouldPrompt && !options.permission) {
|
|
@@ -48227,8 +48368,8 @@ async function initCommandHandler(options) {
|
|
|
48227
48368
|
"admin"
|
|
48228
48369
|
]);
|
|
48229
48370
|
}
|
|
48230
|
-
if (!
|
|
48231
|
-
throw new Error(
|
|
48371
|
+
if (!VALID_PERMISSIONS.includes(permission)) {
|
|
48372
|
+
throw new Error(t_vars("errors.invalid_permission", { permission }));
|
|
48232
48373
|
}
|
|
48233
48374
|
configForWrite = connection;
|
|
48234
48375
|
if (options.useEnvRefs) {
|
|
@@ -48238,9 +48379,7 @@ async function initCommandHandler(options) {
|
|
|
48238
48379
|
const envPassword = options.envPassword;
|
|
48239
48380
|
const envDatabase = options.envDatabase;
|
|
48240
48381
|
if (!envHost || !envPort || !envUser || !envPassword || !envDatabase) {
|
|
48241
|
-
throw new Error(
|
|
48242
|
-
` + `Provide options: --env-host, --env-port, --env-user, --env-password, --env-database
|
|
48243
|
-
` + 'Or use interactive mode: run "bun dev init --use-env-refs" without --no-interactive');
|
|
48382
|
+
throw new Error(t("errors.env_refs_missing_options"));
|
|
48244
48383
|
}
|
|
48245
48384
|
configForWrite = {
|
|
48246
48385
|
system: connection.system,
|
|
@@ -48255,29 +48394,17 @@ async function initCommandHandler(options) {
|
|
|
48255
48394
|
connection: configForWrite,
|
|
48256
48395
|
permission
|
|
48257
48396
|
});
|
|
48258
|
-
const
|
|
48259
|
-
|
|
48260
|
-
|
|
48261
|
-
if (shouldPrompt) {
|
|
48262
|
-
const overwrite = await promptUser.confirm(t("init.config_exists_overwrite"));
|
|
48263
|
-
if (!overwrite) {
|
|
48264
|
-
console.log(t("init.cancelled"));
|
|
48265
|
-
return;
|
|
48266
|
-
}
|
|
48267
|
-
} else {
|
|
48268
|
-
throw new Error(".dbcli exists. Use --force option to overwrite.");
|
|
48269
|
-
}
|
|
48270
|
-
}
|
|
48397
|
+
const canProceed = await checkOverwrite(shouldPrompt, !!options.force);
|
|
48398
|
+
if (!canProceed)
|
|
48399
|
+
return;
|
|
48271
48400
|
if (!options.skipTest && !options.useEnvRefs) {
|
|
48272
48401
|
console.log(t("init.connection_testing"));
|
|
48273
|
-
const resolveValue = (value,
|
|
48402
|
+
const resolveValue = (value, _fieldName) => {
|
|
48274
48403
|
if (typeof value === "object" && value !== null && "$env" in value) {
|
|
48275
48404
|
const envKey = value.$env;
|
|
48276
48405
|
const envValue = process.env[envKey];
|
|
48277
48406
|
if (!envValue) {
|
|
48278
|
-
throw new Error(
|
|
48279
|
-
` + `Set ${envKey} in .env or environment variables.
|
|
48280
|
-
` + `Hint: Check .env file or run 'export ${envKey}=<value>' and retry`);
|
|
48407
|
+
throw new Error(t_vars("errors.env_var_not_defined", { envKey }));
|
|
48281
48408
|
}
|
|
48282
48409
|
return envValue;
|
|
48283
48410
|
}
|
|
@@ -48301,7 +48428,7 @@ async function initCommandHandler(options) {
|
|
|
48301
48428
|
} catch (error) {
|
|
48302
48429
|
if (error instanceof ConnectionError) {
|
|
48303
48430
|
console.error(t_vars("errors.connection_failed", { message: error.message }));
|
|
48304
|
-
console.error("
|
|
48431
|
+
console.error(t("init.connection_hints"));
|
|
48305
48432
|
error.hints.forEach((hint) => console.error(` \u2022 ${hint}`));
|
|
48306
48433
|
process.exit(1);
|
|
48307
48434
|
}
|
|
@@ -48310,7 +48437,8 @@ async function initCommandHandler(options) {
|
|
|
48310
48437
|
await adapter.disconnect();
|
|
48311
48438
|
}
|
|
48312
48439
|
} else {
|
|
48313
|
-
|
|
48440
|
+
const msgKey = options.useEnvRefs ? "init.skip_test_env_ref" : "init.skip_test";
|
|
48441
|
+
console.log(`\u23ED\uFE0F ${t(msgKey)}`);
|
|
48314
48442
|
}
|
|
48315
48443
|
await configModule.write(".dbcli", newConfig);
|
|
48316
48444
|
console.log(t("init.config_saved"));
|
|
@@ -51059,6 +51187,19 @@ var runDoctorChecks = {
|
|
|
51059
51187
|
message: `Schema cache is ${ageDays} day(s) old`
|
|
51060
51188
|
};
|
|
51061
51189
|
},
|
|
51190
|
+
checkDatabaseVersion(versionResult) {
|
|
51191
|
+
const vars = {
|
|
51192
|
+
system: versionResult.system,
|
|
51193
|
+
version: versionResult.serverVersion,
|
|
51194
|
+
minVersion: versionResult.minVersion
|
|
51195
|
+
};
|
|
51196
|
+
return {
|
|
51197
|
+
group: "Connection & Data",
|
|
51198
|
+
label: "Database version",
|
|
51199
|
+
status: versionResult.supported ? "pass" : "warn",
|
|
51200
|
+
message: versionResult.supported ? t_vars("version.doctor_pass", vars) : t_vars("version.doctor_warn", vars)
|
|
51201
|
+
};
|
|
51202
|
+
},
|
|
51062
51203
|
checkLargeTables(tables) {
|
|
51063
51204
|
const large = tables.filter((t7) => (t7.estimatedRowCount ?? 0) > 1e6);
|
|
51064
51205
|
if (large.length === 0) {
|
|
@@ -51139,6 +51280,13 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
|
|
|
51139
51280
|
status: "pass",
|
|
51140
51281
|
message: `Connected to ${config.connection.system} ${config.connection.database}@${config.connection.host}:${config.connection.port}`
|
|
51141
51282
|
});
|
|
51283
|
+
try {
|
|
51284
|
+
const rawVersion = await adapter.getServerVersion();
|
|
51285
|
+
const versionResult = checkDbVersion(rawVersion, config.connection.system);
|
|
51286
|
+
results.push(runDoctorChecks.checkDatabaseVersion(versionResult));
|
|
51287
|
+
} catch {
|
|
51288
|
+
logger.debug("Could not retrieve database version");
|
|
51289
|
+
}
|
|
51142
51290
|
try {
|
|
51143
51291
|
const tables = await adapter.listTables();
|
|
51144
51292
|
const tableColumns = new Map;
|
|
@@ -51388,7 +51536,7 @@ var completionCommand = new Command("completion").description("Generate shell co
|
|
|
51388
51536
|
var NPM_REGISTRY_URL = "https://registry.npmjs.org/@carllee1983/dbcli/latest";
|
|
51389
51537
|
var STALE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
51390
51538
|
var FETCH_TIMEOUT_MS = 3000;
|
|
51391
|
-
function
|
|
51539
|
+
function compareVersions2(a, b) {
|
|
51392
51540
|
const stripSuffix = (v) => v.replace(/-.*$/, "");
|
|
51393
51541
|
const pa = stripSuffix(a).split(".").map(Number);
|
|
51394
51542
|
const pb = stripSuffix(b).split(".").map(Number);
|
|
@@ -51440,7 +51588,7 @@ async function checkForUpdate(currentVersion, cachePath, existingCache) {
|
|
|
51440
51588
|
}
|
|
51441
51589
|
if (cache && !isStale(cache.checkedAt)) {
|
|
51442
51590
|
return {
|
|
51443
|
-
hasUpdate:
|
|
51591
|
+
hasUpdate: compareVersions2(cache.latestVersion, currentVersion) > 0,
|
|
51444
51592
|
latestVersion: cache.latestVersion
|
|
51445
51593
|
};
|
|
51446
51594
|
}
|
|
@@ -51457,7 +51605,7 @@ async function checkForUpdate(currentVersion, cachePath, existingCache) {
|
|
|
51457
51605
|
} catch {}
|
|
51458
51606
|
}
|
|
51459
51607
|
return {
|
|
51460
|
-
hasUpdate:
|
|
51608
|
+
hasUpdate: compareVersions2(latestVersion, currentVersion) > 0,
|
|
51461
51609
|
latestVersion
|
|
51462
51610
|
};
|
|
51463
51611
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@carllee1983/dbcli",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2-beta",
|
|
4
4
|
"description": "Database CLI for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -9,6 +9,27 @@
|
|
|
9
9
|
"bin": {
|
|
10
10
|
"dbcli": "./dist/cli.mjs"
|
|
11
11
|
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Carl Lee",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/CarlLee1983/dbcli.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/CarlLee1983/dbcli#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/CarlLee1983/dbcli/issues"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"database",
|
|
24
|
+
"cli",
|
|
25
|
+
"ai",
|
|
26
|
+
"agent",
|
|
27
|
+
"postgresql",
|
|
28
|
+
"mysql",
|
|
29
|
+
"mariadb",
|
|
30
|
+
"permissions",
|
|
31
|
+
"blacklist"
|
|
32
|
+
],
|
|
12
33
|
"engines": {
|
|
13
34
|
"node": ">=18.0.0",
|
|
14
35
|
"bun": ">=1.3.3"
|