@carllee1983/dbcli 1.1.0 → 1.2.1

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/dist/cli.mjs CHANGED
@@ -6105,7 +6105,9 @@ var exports_validation = {};
6105
6105
  __export(exports_validation, {
6106
6106
  validateFormat: () => validateFormat,
6107
6107
  PermissionSchema: () => PermissionSchema,
6108
+ NamedConnectionSchema: () => NamedConnectionSchema,
6108
6109
  MetadataSchema: () => MetadataSchema,
6110
+ DbcliConfigV2Schema: () => DbcliConfigV2Schema,
6109
6111
  DbcliConfigSchema: () => DbcliConfigSchema,
6110
6112
  ConnectionConfigSchema: () => ConnectionConfigSchema,
6111
6113
  BlacklistConfigSchema: () => BlacklistConfigSchema
@@ -6116,7 +6118,7 @@ function validateFormat(value, allowedFormats, commandName) {
6116
6118
  throw new Error(`Invalid format "${value}" for ${commandName}. Allowed: ${allowed}`);
6117
6119
  }
6118
6120
  }
6119
- var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, ConnectionConfigSchema, PermissionSchema, MetadataSchema, BlacklistConfigSchema, DbcliConfigSchema;
6121
+ var EnvRefSchema, StringOrEnvRef, NumberOrEnvRef, ConnectionConfigSchema, PermissionSchema, MetadataSchema, BlacklistConfigSchema, DbcliConfigSchema, NamedConnectionSchema, DbcliConfigV2Schema;
6120
6122
  var init_validation = __esm(() => {
6121
6123
  init_zod();
6122
6124
  EnvRefSchema = exports_external.object({
@@ -6154,6 +6156,18 @@ var init_validation = __esm(() => {
6154
6156
  metadata: MetadataSchema,
6155
6157
  blacklist: BlacklistConfigSchema
6156
6158
  });
6159
+ NamedConnectionSchema = ConnectionConfigSchema.extend({
6160
+ permission: PermissionSchema,
6161
+ envFile: exports_external.string().optional()
6162
+ });
6163
+ DbcliConfigV2Schema = exports_external.object({
6164
+ version: exports_external.literal(2),
6165
+ default: exports_external.string().min(1),
6166
+ connections: exports_external.record(NamedConnectionSchema).refine((conns) => Object.keys(conns).length > 0, { message: "At least one connection is required" }),
6167
+ schema: exports_external.record(exports_external.any()).optional().default({}),
6168
+ metadata: MetadataSchema,
6169
+ blacklist: BlacklistConfigSchema
6170
+ }).refine((config) => (config.default in config.connections), { message: "Default connection must exist in connections", path: ["default"] });
6157
6171
  });
6158
6172
 
6159
6173
  // node_modules/@inquirer/core/dist/esm/lib/key.mjs
@@ -42998,7 +43012,7 @@ var require_named_placeholders = __commonJS((exports, module) => {
42998
43012
  }
42999
43013
  return s;
43000
43014
  }
43001
- function join2(tree) {
43015
+ function join3(tree) {
43002
43016
  if (tree.length === 1) {
43003
43017
  return tree;
43004
43018
  }
@@ -43024,7 +43038,7 @@ var require_named_placeholders = __commonJS((exports, module) => {
43024
43038
  if (cache && (tree = cache.get(query))) {
43025
43039
  return toArrayParams(tree, paramsObj);
43026
43040
  }
43027
- tree = join2(parse(query));
43041
+ tree = join3(parse(query));
43028
43042
  if (cache) {
43029
43043
  cache.set(query, tree);
43030
43044
  }
@@ -46850,7 +46864,7 @@ var {
46850
46864
  // package.json
46851
46865
  var package_default = {
46852
46866
  name: "@carllee1983/dbcli",
46853
- version: "1.1.0",
46867
+ version: "1.2.1",
46854
46868
  description: "Database CLI for AI agents",
46855
46869
  type: "module",
46856
46870
  publishConfig: {
@@ -46952,7 +46966,27 @@ var messages_default = {
46952
46966
  skip_test_env_ref: "Skipping connection test in env-ref mode",
46953
46967
  skip_test: "Skipping connection test (--skip-test)",
46954
46968
  connection_hints: "Hints:",
46955
- config_exists_use_force: ".dbcli exists. Use --force option to overwrite."
46969
+ config_exists_use_force: ".dbcli exists. Use --force option to overwrite.",
46970
+ connection_added: "Connection '{{name}}' added",
46971
+ connection_removed: "Connection '{{name}}' removed",
46972
+ connection_removed_switched: "Connection '{{name}}' removed. Default connection switched to '{{newDefault}}'",
46973
+ connection_renamed: "Connection '{{oldName}}' renamed to '{{newName}}'",
46974
+ v1_migration_prompt: "Detected old config format. Create new format and import existing connection as 'default'?",
46975
+ config_saved_v2: "V2 configuration saved to .dbcli",
46976
+ config_not_found: "Configuration file not found",
46977
+ requires_v2_remove: "Removing a connection requires V2 config format",
46978
+ requires_v2_rename: "Renaming a connection requires V2 config format",
46979
+ connection_not_found: "Connection '{{name}}' does not exist",
46980
+ cannot_remove_last: "Cannot remove the last connection",
46981
+ connection_already_exists: "Connection '{{name}}' already exists",
46982
+ rename_invalid_format: "Usage: --rename <old-name>:<new-name>"
46983
+ },
46984
+ use: {
46985
+ description: "Switch or display the default database connection",
46986
+ switched: "Switched default connection to {{name}}",
46987
+ current: "Current default connection: {{name}}",
46988
+ requires_v2: "This feature requires v2 config format. Use 'dbcli init --conn-name <name>' to create one",
46989
+ available: "Available connections"
46956
46990
  },
46957
46991
  schema: {
46958
46992
  description: "Retrieve table structure or list all tables",
@@ -47104,7 +47138,27 @@ var messages_default2 = {
47104
47138
  skip_test_env_ref: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08\u74B0\u5883\u8B8A\u6578\u53C3\u7167\u6A21\u5F0F\uFF09",
47105
47139
  skip_test: "\u8DF3\u904E\u9023\u7DDA\u6E2C\u8A66\uFF08--skip-test\uFF09",
47106
47140
  connection_hints: "\u63D0\u793A\uFF1A",
47107
- config_exists_use_force: ".dbcli \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u9078\u9805\u8986\u84CB\u3002"
47141
+ config_exists_use_force: ".dbcli \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u9078\u9805\u8986\u84CB\u3002",
47142
+ connection_added: "\u5DF2\u65B0\u589E\u9023\u7DDA '{{name}}'",
47143
+ connection_removed: "\u5DF2\u79FB\u9664\u9023\u7DDA '{{name}}'",
47144
+ connection_removed_switched: "\u5DF2\u79FB\u9664\u9023\u7DDA '{{name}}'\uFF0C\u9810\u8A2D\u9023\u7DDA\u5DF2\u5207\u63DB\u70BA '{{newDefault}}'",
47145
+ connection_renamed: "\u5DF2\u5C07\u9023\u7DDA '{{oldName}}' \u91CD\u65B0\u547D\u540D\u70BA '{{newName}}'",
47146
+ v1_migration_prompt: "\u5075\u6E2C\u5230\u820A\u683C\u5F0F\u8A2D\u5B9A\uFF0C\u5C07\u5EFA\u7ACB\u65B0\u683C\u5F0F\u4E26\u5C07\u73FE\u6709\u9023\u7DDA\u532F\u5165\u70BA 'default'\uFF0C\u662F\u5426\u7E7C\u7E8C\uFF1F",
47147
+ config_saved_v2: "V2 \u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 .dbcli",
47148
+ config_not_found: "\u627E\u4E0D\u5230\u8A2D\u5B9A\u6A94",
47149
+ requires_v2_remove: "\u79FB\u9664\u9023\u7DDA\u9700\u8981 V2 \u683C\u5F0F\u8A2D\u5B9A",
47150
+ requires_v2_rename: "\u91CD\u65B0\u547D\u540D\u9023\u7DDA\u9700\u8981 V2 \u683C\u5F0F\u8A2D\u5B9A",
47151
+ connection_not_found: "\u9023\u7DDA '{{name}}' \u4E0D\u5B58\u5728",
47152
+ cannot_remove_last: "\u7121\u6CD5\u79FB\u9664\u6700\u5F8C\u4E00\u500B\u9023\u7DDA",
47153
+ connection_already_exists: "\u9023\u7DDA '{{name}}' \u5DF2\u5B58\u5728",
47154
+ rename_invalid_format: "\u7528\u6CD5\uFF1A--rename <\u820A\u540D\u7A31>:<\u65B0\u540D\u7A31>"
47155
+ },
47156
+ use: {
47157
+ description: "\u5207\u63DB\u6216\u986F\u793A\u9810\u8A2D\u8CC7\u6599\u5EAB\u9023\u7DDA",
47158
+ switched: "\u5DF2\u5207\u63DB\u9810\u8A2D\u9023\u7DDA\u70BA {{name}}",
47159
+ current: "\u76EE\u524D\u9810\u8A2D\u9023\u7DDA\uFF1A{{name}}",
47160
+ requires_v2: "\u6B64\u529F\u80FD\u9700\u8981\u65B0\u683C\u5F0F\u8A2D\u5B9A\u3002\u8ACB\u4F7F\u7528 dbcli init --conn-name <\u540D\u7A31> \u5EFA\u7ACB\u591A\u9023\u7DDA\u8A2D\u5B9A",
47161
+ available: "\u53EF\u7528\u9023\u7DDA"
47108
47162
  },
47109
47163
  schema: {
47110
47164
  description: "\u6AA2\u7D22\u8868\u683C\u7D50\u69CB\u6216\u5217\u51FA\u6240\u6709\u8868\u683C",
@@ -47389,6 +47443,9 @@ function getLogger() {
47389
47443
  return globalLogger;
47390
47444
  }
47391
47445
 
47446
+ // src/commands/init.ts
47447
+ import { join as join3 } from "path";
47448
+
47392
47449
  // src/utils/errors.ts
47393
47450
  class EnvParseError extends Error {
47394
47451
  constructor(message) {
@@ -47490,7 +47547,97 @@ function parseEnvDatabase(env) {
47490
47547
 
47491
47548
  // src/core/config.ts
47492
47549
  init_validation();
47550
+
47551
+ // src/core/config-v2.ts
47552
+ init_validation();
47553
+
47554
+ // src/core/env-loader.ts
47555
+ function parseEnvContent(content) {
47556
+ const entries = [];
47557
+ for (const line of content.split(`
47558
+ `)) {
47559
+ const trimmed = line.trim();
47560
+ if (!trimmed || trimmed.startsWith("#"))
47561
+ continue;
47562
+ const eqIndex = trimmed.indexOf("=");
47563
+ if (eqIndex === -1)
47564
+ continue;
47565
+ const key = trimmed.slice(0, eqIndex).trim();
47566
+ let value = trimmed.slice(eqIndex + 1);
47567
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
47568
+ value = value.slice(1, -1);
47569
+ }
47570
+ entries.push([key, value]);
47571
+ }
47572
+ return entries;
47573
+ }
47574
+ async function loadEnvFile(filePath) {
47575
+ const file = Bun.file(filePath);
47576
+ const exists = await file.exists();
47577
+ if (!exists) {
47578
+ throw new ConfigError(`\u627E\u4E0D\u5230 env \u6A94\u6848\uFF1A${filePath}`);
47579
+ }
47580
+ const content = await file.text();
47581
+ const entries = parseEnvContent(content);
47582
+ for (const [key, value] of entries) {
47583
+ if (process.env[key] === undefined) {
47584
+ process.env[key] = value;
47585
+ }
47586
+ }
47587
+ }
47588
+
47589
+ // src/core/config-v2.ts
47493
47590
  import { join } from "path";
47591
+ function detectConfigVersion(raw) {
47592
+ if (typeof raw === "object" && raw !== null && "version" in raw && raw.version === 2 && "connections" in raw) {
47593
+ return 2;
47594
+ }
47595
+ return 1;
47596
+ }
47597
+ function resolveConnection(config, name) {
47598
+ const connectionName = name ?? config.default;
47599
+ const conn = config.connections[connectionName];
47600
+ if (!conn) {
47601
+ const available = Object.keys(config.connections).join(", ");
47602
+ throw new ConfigError(`\u9023\u7DDA '${connectionName}' \u4E0D\u5B58\u5728\u3002\u53EF\u7528\u9023\u7DDA\uFF1A${available}`);
47603
+ }
47604
+ const { permission, envFile, ...connectionFields } = conn;
47605
+ return {
47606
+ name: connectionName,
47607
+ connection: connectionFields,
47608
+ permission,
47609
+ envFile
47610
+ };
47611
+ }
47612
+ async function loadConnectionEnv(resolved, basePath) {
47613
+ if (resolved.envFile) {
47614
+ const envPath = join(basePath, "..", resolved.envFile);
47615
+ await loadEnvFile(envPath);
47616
+ }
47617
+ }
47618
+ async function readV2Config(path) {
47619
+ const configPath = join(path, "config.json");
47620
+ const file = Bun.file(configPath);
47621
+ if (!await file.exists()) {
47622
+ throw new ConfigError(`\u627E\u4E0D\u5230 V2 \u8A2D\u5B9A\u6A94\uFF1A${configPath}`);
47623
+ }
47624
+ const content = await file.text();
47625
+ const raw = JSON.parse(content);
47626
+ return DbcliConfigV2Schema.parse(raw);
47627
+ }
47628
+ async function writeV2Config(path, config) {
47629
+ DbcliConfigV2Schema.parse(config);
47630
+ const configPath = join(path, "config.json");
47631
+ const json = JSON.stringify(config, null, 2);
47632
+ await Bun.write(configPath, json);
47633
+ }
47634
+
47635
+ // src/core/config.ts
47636
+ import { join as join2 } from "path";
47637
+ var _globalConnectionName;
47638
+ function setGlobalConnectionName(name) {
47639
+ _globalConnectionName = name;
47640
+ }
47494
47641
  var DEFAULT_CONFIG = {
47495
47642
  connection: {
47496
47643
  system: "postgresql",
@@ -47547,7 +47694,8 @@ function parseEnvPassword(content) {
47547
47694
  return match ? match[1].trim() : null;
47548
47695
  }
47549
47696
  var configModule = {
47550
- async read(path) {
47697
+ async read(path, connectionName) {
47698
+ const effectiveConnectionName = connectionName ?? _globalConnectionName;
47551
47699
  try {
47552
47700
  let isDirectory = false;
47553
47701
  try {
@@ -47557,14 +47705,40 @@ var configModule = {
47557
47705
  isDirectory = false;
47558
47706
  }
47559
47707
  if (isDirectory) {
47560
- const configPath = join(path, "config.json");
47708
+ const configPath = join2(path, "config.json");
47561
47709
  const configFile = Bun.file(configPath);
47562
47710
  const configExists = await configFile.exists();
47563
47711
  if (configExists) {
47564
47712
  const content = await configFile.text();
47565
47713
  const config = JSON.parse(content);
47714
+ if (detectConfigVersion(config) === 2) {
47715
+ const v2Config = DbcliConfigV2Schema.parse(config);
47716
+ const resolved = resolveConnection(v2Config, effectiveConnectionName);
47717
+ await loadConnectionEnv(resolved, path);
47718
+ const envLocalPath = join2(path, ".env.local");
47719
+ const envLocalFile = Bun.file(envLocalPath);
47720
+ let legacyPassword = null;
47721
+ if (await envLocalFile.exists()) {
47722
+ const envContent = await envLocalFile.text();
47723
+ legacyPassword = parseEnvPassword(envContent);
47724
+ if (legacyPassword && !process.env.DBCLI_PASSWORD) {
47725
+ process.env.DBCLI_PASSWORD = legacyPassword;
47726
+ }
47727
+ }
47728
+ const resolvedConnection = resolveEnvReferences(resolved.connection, process.env, undefined, false);
47729
+ if (!resolvedConnection.password && legacyPassword) {
47730
+ resolvedConnection.password = legacyPassword;
47731
+ }
47732
+ return DbcliConfigSchema.parse({
47733
+ connection: resolvedConnection,
47734
+ permission: resolved.permission,
47735
+ schema: v2Config.schema,
47736
+ metadata: v2Config.metadata,
47737
+ blacklist: v2Config.blacklist
47738
+ });
47739
+ }
47566
47740
  const resolvedConfig = resolveEnvReferences(config, process.env, undefined, false);
47567
- const envPath = join(path, ".env.local");
47741
+ const envPath = join2(path, ".env.local");
47568
47742
  const envFile = Bun.file(envPath);
47569
47743
  if (await envFile.exists()) {
47570
47744
  const envContent = await envFile.text();
@@ -47586,6 +47760,8 @@ var configModule = {
47586
47760
  }
47587
47761
  return { ...DEFAULT_CONFIG };
47588
47762
  } catch (error) {
47763
+ if (error instanceof ConfigError)
47764
+ throw error;
47589
47765
  if (error instanceof Error && error.message.includes("JSON")) {
47590
47766
  throw new ConfigError(`Failed to parse .dbcli file: ${error.message}`);
47591
47767
  }
@@ -47629,7 +47805,7 @@ var configModule = {
47629
47805
  if (isDirectory || path.endsWith(".dbcli")) {
47630
47806
  const hasEnvReferences = isEnvReference(config.connection.password);
47631
47807
  if (hasEnvReferences) {
47632
- const configPath = join(path, "config.json");
47808
+ const configPath = join2(path, "config.json");
47633
47809
  const configJson = JSON.stringify(config, null, 2);
47634
47810
  await Bun.file(configPath).write(configJson);
47635
47811
  } else {
@@ -47642,11 +47818,11 @@ var configModule = {
47642
47818
  }
47643
47819
  };
47644
47820
  delete configWithoutPassword.connection.password;
47645
- const configPath = join(path, "config.json");
47821
+ const configPath = join2(path, "config.json");
47646
47822
  const configJson = JSON.stringify(configWithoutPassword, null, 2);
47647
47823
  await Bun.file(configPath).write(configJson);
47648
47824
  if (password) {
47649
- const envPath = join(path, ".env.local");
47825
+ const envPath = join2(path, ".env.local");
47650
47826
  const envContent = `# Database Credentials - DO NOT commit to git
47651
47827
 
47652
47828
  DBCLI_PASSWORD=${password}
@@ -48440,10 +48616,10 @@ class AdapterFactory {
48440
48616
  }
48441
48617
  // src/commands/init.ts
48442
48618
  var VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
48443
- async function checkOverwrite(shouldPrompt, force) {
48444
- const configFile = Bun.file(".dbcli");
48445
- const fileExists = await configFile.exists();
48446
- if (!fileExists || force)
48619
+ async function checkOverwrite(configPath, shouldPrompt, force) {
48620
+ const fileExists = await Bun.file(configPath).exists();
48621
+ const dirConfigExists = await Bun.file(join3(configPath, "config.json")).exists();
48622
+ if (!fileExists && !dirConfigExists || force)
48447
48623
  return true;
48448
48624
  if (shouldPrompt) {
48449
48625
  const overwrite = await promptUser.confirm(t("init.config_exists_overwrite"));
@@ -48455,7 +48631,140 @@ async function checkOverwrite(shouldPrompt, force) {
48455
48631
  }
48456
48632
  throw new Error(t("init.config_exists_use_force"));
48457
48633
  }
48458
- 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) => {
48634
+ async function handleRemove(configPath, name) {
48635
+ const configFile = Bun.file(join3(configPath, "config.json"));
48636
+ if (!await configFile.exists()) {
48637
+ throw new Error(t("init.config_not_found"));
48638
+ }
48639
+ const raw = JSON.parse(await configFile.text());
48640
+ if (detectConfigVersion(raw) !== 2) {
48641
+ throw new Error(t("init.requires_v2_remove"));
48642
+ }
48643
+ const config = await readV2Config(configPath);
48644
+ if (!config.connections[name]) {
48645
+ throw new Error(t_vars("init.connection_not_found", { name }));
48646
+ }
48647
+ const connectionCount = Object.keys(config.connections).length;
48648
+ if (connectionCount <= 1) {
48649
+ throw new Error(t("init.cannot_remove_last"));
48650
+ }
48651
+ const { [name]: _removed, ...remaining } = config.connections;
48652
+ const newDefault = config.default === name ? Object.keys(remaining)[0] : config.default;
48653
+ const updated = {
48654
+ ...config,
48655
+ default: newDefault,
48656
+ connections: remaining
48657
+ };
48658
+ await writeV2Config(configPath, updated);
48659
+ if (config.default === name) {
48660
+ console.log(t_vars("init.connection_removed_switched", { name, newDefault }));
48661
+ } else {
48662
+ console.log(t_vars("init.connection_removed", { name }));
48663
+ }
48664
+ }
48665
+ async function handleRename(configPath, renameArg) {
48666
+ const [oldName, newName] = renameArg.split(":");
48667
+ if (!oldName || !newName) {
48668
+ throw new Error(t("init.rename_invalid_format"));
48669
+ }
48670
+ const configFile = Bun.file(join3(configPath, "config.json"));
48671
+ if (!await configFile.exists()) {
48672
+ throw new Error(t("init.config_not_found"));
48673
+ }
48674
+ const raw = JSON.parse(await configFile.text());
48675
+ if (detectConfigVersion(raw) !== 2) {
48676
+ throw new Error(t("init.requires_v2_rename"));
48677
+ }
48678
+ const config = await readV2Config(configPath);
48679
+ if (!config.connections[oldName]) {
48680
+ throw new Error(t_vars("init.connection_not_found", { name: oldName }));
48681
+ }
48682
+ if (config.connections[newName]) {
48683
+ throw new Error(t_vars("init.connection_already_exists", { name: newName }));
48684
+ }
48685
+ const entries = Object.entries(config.connections).map(([key2, value]) => [key2 === oldName ? newName : key2, value]);
48686
+ const updated = {
48687
+ ...config,
48688
+ default: config.default === oldName ? newName : config.default,
48689
+ connections: Object.fromEntries(entries)
48690
+ };
48691
+ await writeV2Config(configPath, updated);
48692
+ console.log(t_vars("init.connection_renamed", { oldName, newName }));
48693
+ }
48694
+ async function writeV2InitConfig(configPath, connectionName, connection, permission, envFile) {
48695
+ const configJsonPath = join3(configPath, "config.json");
48696
+ const configFile = Bun.file(configJsonPath);
48697
+ let existingV2 = null;
48698
+ if (await configFile.exists()) {
48699
+ const raw = JSON.parse(await configFile.text());
48700
+ if (detectConfigVersion(raw) === 2) {
48701
+ existingV2 = await readV2Config(configPath);
48702
+ } else {
48703
+ const v1Config = await configModule.read(configPath);
48704
+ existingV2 = {
48705
+ version: 2,
48706
+ default: "default",
48707
+ connections: {
48708
+ default: {
48709
+ ...v1Config.connection,
48710
+ permission: v1Config.permission
48711
+ }
48712
+ },
48713
+ schema: v1Config.schema || {},
48714
+ metadata: v1Config.metadata || { version: "1.0" },
48715
+ blacklist: v1Config.blacklist || { tables: [], columns: {} }
48716
+ };
48717
+ }
48718
+ } else {
48719
+ const legacyFile = Bun.file(configPath);
48720
+ if (await legacyFile.exists()) {
48721
+ const raw = JSON.parse(await legacyFile.text());
48722
+ if (detectConfigVersion(raw) !== 2) {
48723
+ const v1Config = await configModule.read(configPath);
48724
+ existingV2 = {
48725
+ version: 2,
48726
+ default: "default",
48727
+ connections: {
48728
+ default: {
48729
+ ...v1Config.connection,
48730
+ permission: v1Config.permission
48731
+ }
48732
+ },
48733
+ schema: v1Config.schema || {},
48734
+ metadata: v1Config.metadata || { version: "1.0" },
48735
+ blacklist: v1Config.blacklist || { tables: [], columns: {} }
48736
+ };
48737
+ }
48738
+ }
48739
+ }
48740
+ const connEntry = {
48741
+ ...connection,
48742
+ permission
48743
+ };
48744
+ if (envFile) {
48745
+ connEntry.envFile = envFile;
48746
+ }
48747
+ const v2Config = existingV2 ? {
48748
+ ...existingV2,
48749
+ connections: {
48750
+ ...existingV2.connections,
48751
+ [connectionName]: connEntry
48752
+ }
48753
+ } : {
48754
+ version: 2,
48755
+ default: connectionName,
48756
+ connections: {
48757
+ [connectionName]: connEntry
48758
+ },
48759
+ schema: {},
48760
+ metadata: { version: "1.0", createdAt: new Date().toISOString() },
48761
+ blacklist: { tables: [], columns: {} }
48762
+ };
48763
+ await Bun.$`mkdir -p ${configPath}`;
48764
+ await writeV2Config(configPath, v2Config);
48765
+ console.log(t("init.config_saved"));
48766
+ }
48767
+ 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").option("--conn-name <name>", "Connection name (creates v2 multi-connection config)").option("--env-file <path>", "Path to env file for this connection").option("--remove <name>", "Remove a named connection").option("--rename <names>", "Rename a connection (format: old:new)").action(async (options) => {
48459
48768
  try {
48460
48769
  await initCommandHandler(options);
48461
48770
  } catch (error) {
@@ -48468,7 +48777,18 @@ var initCommand = new Command("init").description("Initialize dbcli configuratio
48468
48777
  }
48469
48778
  });
48470
48779
  async function initCommandHandler(options) {
48471
- const existingConfig = await configModule.read(".dbcli");
48780
+ const configPath = initCommand.parent?.opts().config ?? ".dbcli";
48781
+ if (options.remove) {
48782
+ await handleRemove(configPath, options.remove);
48783
+ return;
48784
+ }
48785
+ if (options.rename) {
48786
+ await handleRename(configPath, options.rename);
48787
+ return;
48788
+ }
48789
+ const isV2Init = !!(options.connName || options.envFile);
48790
+ const connectionName = options.connName || "default";
48791
+ const existingConfig = await configModule.read(configPath);
48472
48792
  const isUsingEnvRefs = options.useEnvRefs;
48473
48793
  const hasAllEnvOptions = isUsingEnvRefs && options.envHost && options.envPort && options.envUser && options.envPassword && options.envDatabase;
48474
48794
  const shouldPrompt = !options.noInteractive && !hasAllEnvOptions;
@@ -48526,11 +48846,15 @@ async function initCommandHandler(options) {
48526
48846
  connection: configForWrite,
48527
48847
  permission: permission2
48528
48848
  });
48529
- const canProceed2 = await checkOverwrite(shouldPrompt, !!options.force);
48849
+ const canProceed2 = await checkOverwrite(configPath, shouldPrompt, !!options.force);
48530
48850
  if (!canProceed2)
48531
48851
  return;
48532
48852
  console.log(`\u23ED\uFE0F ${t("init.skip_test_env_ref")}`);
48533
- await configModule.write(".dbcli", newConfig2);
48853
+ if (isV2Init) {
48854
+ await writeV2InitConfig(configPath, connectionName, configForWrite, permission2, options.envFile);
48855
+ return;
48856
+ }
48857
+ await configModule.write(configPath, newConfig2);
48534
48858
  console.log(t("init.config_saved"));
48535
48859
  return;
48536
48860
  }
@@ -48585,7 +48909,7 @@ async function initCommandHandler(options) {
48585
48909
  connection: configForWrite,
48586
48910
  permission
48587
48911
  });
48588
- const canProceed = await checkOverwrite(shouldPrompt, !!options.force);
48912
+ const canProceed = await checkOverwrite(configPath, shouldPrompt, !!options.force);
48589
48913
  if (!canProceed)
48590
48914
  return;
48591
48915
  if (!options.skipTest && !options.useEnvRefs) {
@@ -48631,7 +48955,11 @@ async function initCommandHandler(options) {
48631
48955
  const msgKey = options.useEnvRefs ? "init.skip_test_env_ref" : "init.skip_test";
48632
48956
  console.log(`\u23ED\uFE0F ${t(msgKey)}`);
48633
48957
  }
48634
- await configModule.write(".dbcli", newConfig);
48958
+ if (isV2Init) {
48959
+ await writeV2InitConfig(configPath, connectionName, configForWrite, permission, options.envFile);
48960
+ return;
48961
+ }
48962
+ await configModule.write(configPath, newConfig);
48635
48963
  console.log(t("init.config_saved"));
48636
48964
  }
48637
48965
 
@@ -51287,7 +51615,7 @@ var statusCommand = new Command("status").description("Show current configuratio
51287
51615
 
51288
51616
  // src/commands/doctor.ts
51289
51617
  init_validation();
51290
- import { join as join3 } from "path";
51618
+ import { join as join5 } from "path";
51291
51619
  var ALLOWED_FORMATS7 = ["text", "json"];
51292
51620
  var SENSITIVE_PATTERNS = [
51293
51621
  "password",
@@ -51349,7 +51677,7 @@ var runDoctorChecks = {
51349
51677
  }
51350
51678
  },
51351
51679
  async checkConfigExists(configPath, existsFn) {
51352
- const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(join3(configPath, "config.json")).exists();
51680
+ const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(join5(configPath, "config.json")).exists();
51353
51681
  return {
51354
51682
  group: "Configuration",
51355
51683
  label: "Config exists",
@@ -51441,6 +51769,60 @@ var runDoctorChecks = {
51441
51769
  message: `Large tables: ${list}`
51442
51770
  };
51443
51771
  },
51772
+ async checkV2Config(configPath) {
51773
+ const results = [];
51774
+ const configFile = Bun.file(join5(configPath, "config.json"));
51775
+ if (!await configFile.exists())
51776
+ return results;
51777
+ let raw;
51778
+ try {
51779
+ raw = JSON.parse(await configFile.text());
51780
+ } catch {
51781
+ return results;
51782
+ }
51783
+ if (detectConfigVersion(raw) !== 2)
51784
+ return results;
51785
+ let config;
51786
+ try {
51787
+ config = DbcliConfigV2Schema.parse(raw);
51788
+ } catch {
51789
+ results.push({
51790
+ group: "Configuration",
51791
+ label: "V2 config validation",
51792
+ status: "error",
51793
+ message: "V2 \u8A2D\u5B9A\u6A94\u683C\u5F0F\u7121\u6548"
51794
+ });
51795
+ return results;
51796
+ }
51797
+ if (!config.connections[config.default]) {
51798
+ results.push({
51799
+ group: "Configuration",
51800
+ label: "Default connection",
51801
+ status: "error",
51802
+ message: `\u9810\u8A2D\u9023\u7DDA '${config.default}' \u4E0D\u5B58\u5728\u65BC connections \u4E2D`
51803
+ });
51804
+ } else {
51805
+ results.push({
51806
+ group: "Configuration",
51807
+ label: "Default connection",
51808
+ status: "pass",
51809
+ message: `\u9810\u8A2D\u9023\u7DDA '${config.default}' \u6709\u6548`
51810
+ });
51811
+ }
51812
+ for (const [name, conn] of Object.entries(config.connections)) {
51813
+ if (conn.envFile) {
51814
+ const envPath = join5(configPath, "..", conn.envFile);
51815
+ const exists = await Bun.file(envPath).exists();
51816
+ results.push({
51817
+ group: "Configuration",
51818
+ label: `Env file (${name})`,
51819
+ status: exists ? "pass" : "error",
51820
+ message: exists ? `${conn.envFile} \u5B58\u5728` : `\u9023\u7DDA '${name}' \u7684 env \u6A94\u6848 ${conn.envFile} \u4E0D\u5B58\u5728`
51821
+ });
51822
+ }
51823
+ }
51824
+ return results;
51825
+ },
51444
51826
  formatTextOutput(results, version) {
51445
51827
  const lines2 = [`dbcli doctor v${version}`, ""];
51446
51828
  const groups = ["Environment", "Configuration", "Connection & Data"];
@@ -51489,6 +51871,8 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
51489
51871
  status: "pass",
51490
51872
  message: `Permission: ${config.permission}`
51491
51873
  });
51874
+ const v2Results = await runDoctorChecks.checkV2Config(configPath);
51875
+ results.push(...v2Results);
51492
51876
  const blacklistedColumns = new Map;
51493
51877
  if (config.blacklist?.columns) {
51494
51878
  for (const [table, cols] of Object.entries(config.blacklist.columns)) {
@@ -51523,7 +51907,7 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
51523
51907
  logger.debug("Could not list tables for blacklist/large table check");
51524
51908
  }
51525
51909
  try {
51526
- const indexPath = join3(configPath, "schemas", "index.json");
51910
+ const indexPath = join5(configPath, "schemas", "index.json");
51527
51911
  const indexFile = Bun.file(indexPath);
51528
51912
  if (await indexFile.exists()) {
51529
51913
  const indexContent = await indexFile.text();
@@ -51565,7 +51949,7 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
51565
51949
  });
51566
51950
 
51567
51951
  // src/commands/completion.ts
51568
- import { join as join4 } from "path";
51952
+ import { join as join6 } from "path";
51569
51953
  import { homedir as homedir2 } from "os";
51570
51954
  function extractCommands(program2) {
51571
51955
  return program2.commands.map((cmd) => ({
@@ -51670,11 +52054,11 @@ function getInstallPath2(shell) {
51670
52054
  const home = homedir2();
51671
52055
  switch (shell) {
51672
52056
  case "bash":
51673
- return join4(home, ".bashrc");
52057
+ return join6(home, ".bashrc");
51674
52058
  case "zsh":
51675
- return join4(home, ".zshrc");
52059
+ return join6(home, ".zshrc");
51676
52060
  case "fish":
51677
- return join4(home, ".config", "fish", "completions", "dbcli.fish");
52061
+ return join6(home, ".config", "fish", "completions", "dbcli.fish");
51678
52062
  default:
51679
52063
  throw new Error(`Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
51680
52064
  }
@@ -51694,7 +52078,7 @@ var MARKER_END = "# <<< dbcli completion <<<";
51694
52078
  async function installCompletion(shell, script) {
51695
52079
  const targetPath = getInstallPath2(shell);
51696
52080
  if (shell === "fish") {
51697
- const dir = join4(homedir2(), ".config", "fish", "completions");
52081
+ const dir = join6(homedir2(), ".config", "fish", "completions");
51698
52082
  await Bun.$`mkdir -p ${dir}`.quiet();
51699
52083
  await Bun.file(targetPath).write(script);
51700
52084
  console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
@@ -51897,7 +52281,7 @@ var upgradeCommand = new Command("upgrade").description("Check for updates and u
51897
52281
 
51898
52282
  // src/commands/shell.ts
51899
52283
  import { createInterface as createInterface2 } from "readline";
51900
- import { join as join5 } from "path";
52284
+ import { join as join7 } from "path";
51901
52285
  import { homedir as homedir3 } from "os";
51902
52286
 
51903
52287
  // src/core/repl/types.ts
@@ -52585,7 +52969,7 @@ function matchWithSuffix(candidates, prefix) {
52585
52969
 
52586
52970
  // src/commands/shell.ts
52587
52971
  var import_picocolors4 = __toESM(require_picocolors(), 1);
52588
- var HISTORY_PATH = join5(homedir3(), ".dbcli_history");
52972
+ var HISTORY_PATH = join7(homedir3(), ".dbcli_history");
52589
52973
  var shellCommand = new Command("shell").description("Interactive database shell with auto-completion and syntax highlighting").option("--sql", "SQL-only mode (skip dbcli command parsing)").action(async (options) => {
52590
52974
  const globalOpts = shellCommand.optsWithGlobals?.() ?? {};
52591
52975
  const configPath = globalOpts.config ?? ".dbcli";
@@ -53483,12 +53867,73 @@ addExecOpts(migrateCommand.command("drop-enum <name>").description(t("migrate.dr
53483
53867
  }
53484
53868
  });
53485
53869
 
53870
+ // src/commands/use.ts
53871
+ import { join as join8 } from "path";
53872
+ async function switchDefault(configPath, name, config) {
53873
+ if (!config.connections[name]) {
53874
+ const available = Object.keys(config.connections).join(", ");
53875
+ throw new ConfigError(`${t_vars("init.connection_not_found", { name })}. ${t("use.available")}: ${available}`);
53876
+ }
53877
+ const updated = {
53878
+ ...config,
53879
+ default: name
53880
+ };
53881
+ await writeV2Config(configPath, updated);
53882
+ }
53883
+ function listConnectionsForDisplay(config) {
53884
+ return Object.entries(config.connections).map(([name, conn]) => {
53885
+ const marker = name === config.default ? "*" : " ";
53886
+ const host = typeof conn.host === "object" ? `\${${conn.host.$env}}` : conn.host;
53887
+ const port = typeof conn.port === "object" ? `\${${conn.port.$env}}` : conn.port;
53888
+ const db = typeof conn.database === "object" ? `\${${conn.database.$env}}` : conn.database;
53889
+ return `${marker} ${name.padEnd(12)} ${conn.system.padEnd(12)} ${host}:${port}/${db}`;
53890
+ });
53891
+ }
53892
+ async function ensureV2Config(configPath) {
53893
+ const configFile = Bun.file(join8(configPath, "config.json"));
53894
+ if (!await configFile.exists()) {
53895
+ throw new ConfigError(t("init.config_not_found"));
53896
+ }
53897
+ const raw = JSON.parse(await configFile.text());
53898
+ if (!raw.version || raw.version !== 2 || !raw.connections) {
53899
+ throw new ConfigError(t("use.requires_v2"));
53900
+ }
53901
+ return readV2Config(configPath);
53902
+ }
53903
+ var useCommand = new Command("use").description("Switch or display the default database connection (v2 config)").argument("[name]", "Connection name to switch to").option("--list", "List all connections").action(async (name, options) => {
53904
+ try {
53905
+ const configPath = useCommand.parent?.opts().config ?? ".dbcli";
53906
+ const config = await ensureV2Config(configPath);
53907
+ if (options.list || !name) {
53908
+ if (!name) {
53909
+ console.log(t_vars("use.current", { name: config.default }));
53910
+ }
53911
+ console.log("");
53912
+ for (const line of listConnectionsForDisplay(config)) {
53913
+ console.log(line);
53914
+ }
53915
+ return;
53916
+ }
53917
+ await switchDefault(configPath, name, config);
53918
+ console.log(t_vars("use.switched", { name }));
53919
+ } catch (error) {
53920
+ if (error instanceof Error) {
53921
+ console.error(error.message);
53922
+ } else {
53923
+ console.error(String(error));
53924
+ }
53925
+ process.exit(1);
53926
+ }
53927
+ });
53928
+
53486
53929
  // src/cli.ts
53487
- import { join as join6 } from "path";
53930
+ import { join as join9 } from "path";
53488
53931
  var _bgVersionCheckResult;
53489
- var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli");
53932
+ var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").option("--use <connection>", "Use a specific named connection (v2 config)");
53490
53933
  program2.hook("preAction", (thisCommand, actionCommand) => {
53491
53934
  const opts = thisCommand.opts();
53935
+ const useConnection = opts.use;
53936
+ setGlobalConnectionName(useConnection);
53492
53937
  if (opts.color === false) {
53493
53938
  process.env.NO_COLOR = "1";
53494
53939
  }
@@ -53508,7 +53953,7 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
53508
53953
  try {
53509
53954
  let cache = null;
53510
53955
  try {
53511
- const cacheFile = Bun.file(join6(configPath, "version-check.json"));
53956
+ const cacheFile = Bun.file(join9(configPath, "version-check.json"));
53512
53957
  if (await cacheFile.exists()) {
53513
53958
  cache = await cacheFile.json();
53514
53959
  }
@@ -53589,6 +54034,7 @@ program2.addCommand(completionCommand);
53589
54034
  program2.addCommand(upgradeCommand);
53590
54035
  program2.addCommand(shellCommand);
53591
54036
  program2.addCommand(migrateCommand);
54037
+ program2.addCommand(useCommand);
53592
54038
  if (!process.argv.slice(2).length) {
53593
54039
  program2.outputHelp();
53594
54040
  }