@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 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.0-beta",
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 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) => {
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(`Invalid database system: ${system}`);
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 (shouldPrompt && !options.permission) {
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 (!["query-only", "read-write", "data-admin", "admin"].includes(permission2)) {
48181
- throw new Error(`Invalid permission level: ${permission2}`);
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 configFile2 = Bun.file(".dbcli");
48188
- const fileExists2 = await configFile2.exists();
48189
- if (fileExists2 && !options.force) {
48190
- if (shouldPrompt) {
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(`Invalid port: ${portStr}`);
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("Non-interactive mode requires --user option");
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("Non-interactive mode requires --name option");
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 (!["query-only", "read-write", "data-admin", "admin"].includes(permission)) {
48231
- throw new Error(`Invalid permission level: ${permission}`);
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(`When using --use-env-refs, environment variable names must be specified.
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 configFile = Bun.file(".dbcli");
48259
- const fileExists = await configFile.exists();
48260
- if (fileExists && !options.force) {
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, fieldName) => {
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(`Cannot test connection: environment variable ${envKey} is not defined
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("Hints:");
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
- console.log("\u23ED\uFE0F Skipping connection test (--skip-test)");
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 compareVersions(a, b) {
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: compareVersions(cache.latestVersion, currentVersion) > 0,
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: compareVersions(latestVersion, currentVersion) > 0,
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.0-beta",
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"