@carllee1983/dbcli 9.1.2 → 10.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2132,7 +2132,7 @@ var package_default;
2132
2132
  var init_package = __esm(() => {
2133
2133
  package_default = {
2134
2134
  name: "@carllee1983/dbcli",
2135
- version: "9.1.2",
2135
+ version: "10.0.0",
2136
2136
  description: "Database CLI for AI agents",
2137
2137
  type: "module",
2138
2138
  publishConfig: {
@@ -2173,7 +2173,7 @@ var init_package = __esm(() => {
2173
2173
  "blacklist"
2174
2174
  ],
2175
2175
  engines: {
2176
- bun: ">=1.3.3"
2176
+ bun: ">=1.4.0"
2177
2177
  },
2178
2178
  files: [
2179
2179
  "dist/",
@@ -6503,7 +6503,7 @@ var init_validation = __esm(() => {
6503
6503
  }, NamedConnectionUnion);
6504
6504
  DbcliConfigV2Schema = objectType({
6505
6505
  version: literalType(2),
6506
- default: stringType().min(1),
6506
+ default: stringType().min(1).optional(),
6507
6507
  connections: recordType(NamedConnectionSchema).refine((conns) => Object.keys(conns).length > 0, {
6508
6508
  message: "At least one connection is required"
6509
6509
  }),
@@ -6513,7 +6513,7 @@ var init_validation = __esm(() => {
6513
6513
  blacklist: BlacklistConfigSchema,
6514
6514
  audit: AuditConfigSchema,
6515
6515
  redis: RedisConfigSchema
6516
- }).refine((config) => (config.default in config.connections), {
6516
+ }).refine((config) => config.default === undefined || Object.hasOwn(config.connections, config.default), {
6517
6517
  message: "Default connection must exist in connections",
6518
6518
  path: ["default"]
6519
6519
  });
@@ -7053,14 +7053,20 @@ function findSimilarConnectionNames(requestedName, availableNames) {
7053
7053
  }).filter(({ isSimilar }) => isSimilar).sort((a, b) => a.distance - b.distance || a.name.localeCompare(b.name)).slice(0, 3).map(({ name }) => name);
7054
7054
  }
7055
7055
  function connectionNotFoundMessage(requestedName, availableNames) {
7056
+ if (requestedName === undefined) {
7057
+ return "\u5C1A\u672A\u8A2D\u5B9A\u9810\u8A2D\u9023\u7DDA\u3002\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION \u660E\u78BA\u6307\u5B9A\u9023\u7DDA\u3002" + ` \u53EF\u7528\u9023\u7DDA\uFF1A${availableNames.join(", ")}`;
7058
+ }
7056
7059
  const suggestions = findSimilarConnectionNames(requestedName, availableNames);
7057
7060
  const suggestion = suggestions.length > 0 ? ` \u4F60\u662F\u5426\u8981\u4F7F\u7528\uFF1A${suggestions.join("\u3001")}\uFF1F` : "";
7058
7061
  return `\u9023\u7DDA '${requestedName}' \u4E0D\u5B58\u5728\u3002${suggestion} \u53EF\u7528\u9023\u7DDA\uFF1A${availableNames.join(", ")}`;
7059
7062
  }
7060
7063
  function resolveConnection(config, name) {
7061
7064
  const connectionName = name ?? config.default;
7065
+ if (connectionName === undefined) {
7066
+ throw new ConnectionSelectionRequiredError(Object.keys(config.connections));
7067
+ }
7062
7068
  const conn = config.connections[connectionName];
7063
- if (!conn) {
7069
+ if (!Object.hasOwn(config.connections, connectionName) || !conn) {
7064
7070
  throw new ConfigError(connectionNotFoundMessage(connectionName, Object.keys(config.connections)));
7065
7071
  }
7066
7072
  const { permission, envFile, environment, ...connectionFields } = conn;
@@ -7078,7 +7084,7 @@ async function loadConnectionEnv(resolved, basePath) {
7078
7084
  await loadEnvFile(envPath);
7079
7085
  }
7080
7086
  }
7081
- async function readV2Config(path) {
7087
+ async function readV2ConfigDocument(path) {
7082
7088
  const storagePath = await resolveConfigStoragePath(path);
7083
7089
  const configPath = join3(storagePath, "config.json");
7084
7090
  const file = Bun.file(configPath);
@@ -7088,8 +7094,20 @@ async function readV2Config(path) {
7088
7094
  await assertAgentReadableFile(configPath);
7089
7095
  const content = await file.text();
7090
7096
  await assertConfigIntegrity(storagePath, content, { requireRecord: true });
7091
- const raw = JSON.parse(content);
7092
- return DbcliConfigV2Schema.parse(raw);
7097
+ return JSON.parse(content);
7098
+ }
7099
+ async function readV2Config(path) {
7100
+ return DbcliConfigV2Schema.parse(await readV2ConfigDocument(path));
7101
+ }
7102
+ async function clearDefaultConnection(path) {
7103
+ assertConfigMutationApproved();
7104
+ const raw = await readV2ConfigDocument(path);
7105
+ const config = DbcliConfigV2Schema.parse(raw);
7106
+ if (config.default === undefined)
7107
+ return;
7108
+ const updated = { ...raw };
7109
+ delete updated.default;
7110
+ await writeV2Config(path, updated);
7093
7111
  }
7094
7112
  async function writeV2Config(path, config) {
7095
7113
  assertConfigMutationApproved();
@@ -7114,6 +7132,7 @@ async function isV2Config(dbcliPath) {
7114
7132
  return false;
7115
7133
  }
7116
7134
  }
7135
+ var ConnectionSelectionRequiredError;
7117
7136
  var init_config_v2 = __esm(() => {
7118
7137
  init_validation();
7119
7138
  init_errors3();
@@ -7121,6 +7140,11 @@ var init_config_v2 = __esm(() => {
7121
7140
  init_config_integrity();
7122
7141
  init_env_loader2();
7123
7142
  init_config_binding();
7143
+ ConnectionSelectionRequiredError = class ConnectionSelectionRequiredError extends ConfigError {
7144
+ constructor(availableNames) {
7145
+ super(connectionNotFoundMessage(undefined, availableNames));
7146
+ }
7147
+ };
7124
7148
  });
7125
7149
 
7126
7150
  // src/agent-core/applied-limit.ts
@@ -8686,6 +8710,8 @@ var init_messages = __esm(() => {
8686
8710
  init: {
8687
8711
  description: "Initialize dbcli configuration with .env parsing and interactive prompts",
8688
8712
  welcome: "Welcome to dbcli",
8713
+ prompt_connection_name: "Connection name",
8714
+ connection_name_required: "Connection name is required. Provide --conn-name <name> (this is separate from the database --name).",
8689
8715
  select_system: "Select database system:",
8690
8716
  prompt_host: "Database host: ",
8691
8717
  prompt_port: "Database port: ",
@@ -8710,7 +8736,7 @@ var init_messages = __esm(() => {
8710
8736
  config_exists_use_force: ".dbcli exists. Use --force option to overwrite.",
8711
8737
  connection_added: "Connection '{name}' added",
8712
8738
  connection_removed: "Connection '{name}' removed",
8713
- connection_removed_switched: "Connection '{name}' removed. Default connection switched to '{newDefault}'",
8739
+ connection_removed_default_cleared: "Connection '{name}' removed. Default connection cleared. Specify --use <name> or DBCLI_CONNECTION for database commands.",
8714
8740
  connection_renamed: "Connection '{oldName}' renamed to '{newName}'",
8715
8741
  v1_migration_prompt: "Detected old config format. Create new format and import existing connection as 'default'?",
8716
8742
  config_saved_v2: "V2 configuration saved to .dbcli",
@@ -8748,6 +8774,9 @@ var init_messages = __esm(() => {
8748
8774
  description: "Switch or display the default database connection",
8749
8775
  switched: "Switched default connection to {name}",
8750
8776
  current: "Current default connection: {name}",
8777
+ current_none: "No default connection. Specify --use <name> or DBCLI_CONNECTION.",
8778
+ unset: "Default connection cleared. Specify --use <name> or DBCLI_CONNECTION for database commands.",
8779
+ unset_conflict: "--unset cannot be combined with a connection name, --list, --confirm-production, or --format json",
8751
8780
  requires_v2: "This feature requires v2 config format. Use 'dbcli init --conn-name <name>' to create one",
8752
8781
  available: "Available connections"
8753
8782
  },
@@ -9022,6 +9051,8 @@ var init_messages2 = __esm(() => {
9022
9051
  init: {
9023
9052
  description: "\u521D\u59CB\u5316 dbcli \u914D\u7F6E\uFF0C\u89E3\u6790 .env \u4E26\u9032\u884C\u4E92\u52D5\u5F0F\u63D0\u793A",
9024
9053
  welcome: "\u6B61\u8FCE\u4F7F\u7528 dbcli",
9054
+ prompt_connection_name: "\u9023\u7DDA\u540D\u7A31",
9055
+ connection_name_required: "\u9023\u7DDA\u540D\u7A31\u4E0D\u53EF\u7A7A\u767D\uFF0C\u8ACB\u63D0\u4F9B --conn-name <name>\uFF08\u8207\u8CC7\u6599\u5EAB\u540D\u7A31 --name \u4E0D\u540C\uFF09\u3002",
9025
9056
  select_system: "\u9078\u64C7\u8CC7\u6599\u5EAB\u7CFB\u7D71\uFF1A",
9026
9057
  prompt_host: "\u8CC7\u6599\u5EAB\u4E3B\u6A5F\uFF1A",
9027
9058
  prompt_port: "\u8CC7\u6599\u5EAB\u57E0\u865F\uFF1A",
@@ -9046,7 +9077,7 @@ var init_messages2 = __esm(() => {
9046
9077
  config_exists_use_force: ".dbcli \u5DF2\u5B58\u5728\u3002\u4F7F\u7528 --force \u9078\u9805\u8986\u84CB\u3002",
9047
9078
  connection_added: "\u5DF2\u65B0\u589E\u9023\u7DDA '{name}'",
9048
9079
  connection_removed: "\u5DF2\u79FB\u9664\u9023\u7DDA '{name}'",
9049
- connection_removed_switched: "\u5DF2\u79FB\u9664\u9023\u7DDA '{name}'\uFF0C\u9810\u8A2D\u9023\u7DDA\u5DF2\u5207\u63DB\u70BA '{newDefault}'",
9080
+ connection_removed_default_cleared: "\u5DF2\u79FB\u9664\u9023\u7DDA '{name}'\uFF0C\u9810\u8A2D\u9023\u7DDA\u5DF2\u6E05\u7A7A\u3002\u57F7\u884C\u8CC7\u6599\u5EAB\u6307\u4EE4\u6642\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
9050
9081
  connection_renamed: "\u5DF2\u5C07\u9023\u7DDA '{oldName}' \u91CD\u65B0\u547D\u540D\u70BA '{newName}'",
9051
9082
  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",
9052
9083
  config_saved_v2: "V2 \u8A2D\u5B9A\u5DF2\u5132\u5B58\u81F3 .dbcli",
@@ -9084,6 +9115,9 @@ var init_messages2 = __esm(() => {
9084
9115
  description: "\u5207\u63DB\u6216\u986F\u793A\u9810\u8A2D\u8CC7\u6599\u5EAB\u9023\u7DDA",
9085
9116
  switched: "\u5DF2\u5207\u63DB\u9810\u8A2D\u9023\u7DDA\u70BA {name}",
9086
9117
  current: "\u76EE\u524D\u7684\u9810\u8A2D\u9023\u7DDA\uFF1A{name}",
9118
+ current_none: "\u76EE\u524D\u6C92\u6709\u9810\u8A2D\u9023\u7DDA\u3002\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
9119
+ unset: "\u9810\u8A2D\u9023\u7DDA\u5DF2\u6E05\u7A7A\u3002\u57F7\u884C\u8CC7\u6599\u5EAB\u6307\u4EE4\u6642\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION\u3002",
9120
+ unset_conflict: "--unset \u4E0D\u53EF\u8207\u9023\u7DDA\u540D\u7A31\u3001--list\u3001--confirm-production \u6216 --format json \u540C\u6642\u4F7F\u7528",
9087
9121
  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",
9088
9122
  available: "\u53EF\u7528\u9023\u7DDA"
9089
9123
  },
@@ -9803,6 +9837,28 @@ var init_env_parser = __esm(() => {
9803
9837
  init_errors3();
9804
9838
  });
9805
9839
 
9840
+ // src/core/config-v2-mutations.ts
9841
+ function envVarNameFor(connName, field) {
9842
+ const slug = connName.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
9843
+ return `DBCLI_${slug}_${field.toUpperCase()}`;
9844
+ }
9845
+ function removeConnection(config, name) {
9846
+ if (!Object.hasOwn(config.connections, name))
9847
+ throw new Error(`\u9023\u7DDA '${name}' \u4E0D\u5B58\u5728`);
9848
+ const rest = { ...config.connections };
9849
+ delete rest[name];
9850
+ const remaining = Object.keys(rest);
9851
+ if (remaining.length === 0)
9852
+ throw new Error("\u7121\u6CD5\u522A\u9664\u6700\u5F8C\u4E00\u689D\u9023\u7DDA");
9853
+ const updated = { ...config, connections: rest };
9854
+ if (config.default === name)
9855
+ delete updated.default;
9856
+ return updated;
9857
+ }
9858
+ var init_config_v2_mutations = __esm(() => {
9859
+ init_validation();
9860
+ });
9861
+
9806
9862
  // src/utils/prompts.ts
9807
9863
  async function loadInquirer() {
9808
9864
  try {
@@ -11139,7 +11195,7 @@ class PostgreSQLAdapter {
11139
11195
  const enumQuery = `
11140
11196
  SELECT
11141
11197
  c.column_name as name,
11142
- array_agg(e.enumlabel ORDER BY e.enumsortorder) as enum_values
11198
+ array_agg(e.enumlabel::text ORDER BY e.enumsortorder) as enum_values
11143
11199
  FROM information_schema.columns c
11144
11200
  JOIN pg_catalog.pg_type AS enum_type
11145
11201
  ON enum_type.typname = c.udt_name
@@ -11160,10 +11216,10 @@ class PostgreSQLAdapter {
11160
11216
  const fkQuery = `
11161
11217
  SELECT
11162
11218
  constraint_info.conname as name,
11163
- array_agg(source_column.attname ORDER BY source_key.ordinality) as columns,
11219
+ array_agg(source_column.attname::text ORDER BY source_key.ordinality) as columns,
11164
11220
  referenced_schema.nspname as ref_schema,
11165
11221
  referenced_table.relname as ref_table,
11166
- array_agg(referenced_column.attname ORDER BY source_key.ordinality) as ref_columns
11222
+ array_agg(referenced_column.attname::text ORDER BY source_key.ordinality) as ref_columns
11167
11223
  FROM pg_catalog.pg_constraint AS constraint_info
11168
11224
  JOIN pg_catalog.pg_class AS source_table
11169
11225
  ON source_table.oid = constraint_info.conrelid
@@ -11205,7 +11261,7 @@ class PostgreSQLAdapter {
11205
11261
  const indexQuery = `
11206
11262
  SELECT
11207
11263
  i.relname as name,
11208
- array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns,
11264
+ array_agg(a.attname::text ORDER BY array_position(ix.indkey, a.attnum)) as columns,
11209
11265
  ix.indisunique as is_unique
11210
11266
  FROM pg_index ix
11211
11267
  JOIN pg_class t ON t.oid = ix.indrelid
@@ -11233,7 +11289,7 @@ class PostgreSQLAdapter {
11233
11289
  const estimateResults = estimateResult.rows;
11234
11290
  const pkQuery = `
11235
11291
  SELECT
11236
- array_agg(primary_key_column.attname ORDER BY primary_key.ordinality) as columns
11292
+ array_agg(primary_key_column.attname::text ORDER BY primary_key.ordinality) as columns
11237
11293
  FROM pg_catalog.pg_index AS index_info
11238
11294
  JOIN pg_catalog.pg_class AS source_table
11239
11295
  ON source_table.oid = index_info.indrelid
@@ -14542,7 +14598,22 @@ async function checkOverwrite(configPath, shouldPrompt, force) {
14542
14598
  }
14543
14599
  throw new Error(t("init.config_exists_use_force"));
14544
14600
  }
14601
+ async function assertV2InitImportSupported(configPath) {
14602
+ if (isGlobalConfigPath(configPath))
14603
+ return;
14604
+ const projectConfig = Bun.file(join10(configPath, "config.json"));
14605
+ if (!await projectConfig.exists())
14606
+ return;
14607
+ const raw = JSON.parse(await projectConfig.text());
14608
+ if (detectConfigVersion(raw) !== 2)
14609
+ return;
14610
+ const config = await readV2Config(configPath);
14611
+ if (Object.values(config.connections).some((connection) => connection.envFile && connection.envFile !== ".env.local")) {
14612
+ throw new Error("\u6B64\u820A\u7248\u5C08\u6848 V2 \u8A2D\u5B9A\u5305\u542B\u81EA\u8A02 envFile\u3002\u65B0\u589E\u9023\u7DDA\u6703\u642C\u79FB\u8A2D\u5B9A\uFF0C\u4F46\u76EE\u524D\u50C5\u652F\u63F4\u642C\u79FB .env.local\u3002" + "\u8ACB\u5148\u7531\u7BA1\u7406\u54E1\u5B8C\u6210\u8A2D\u5B9A\u8207\u81EA\u8A02 envFile \u7684 home storage \u9077\u79FB\uFF0C\u518D\u91CD\u8A66\u3002\u539F\u8A2D\u5B9A\u8207 env \u6A94\u6848\u672A\u8B8A\u66F4\u3002");
14613
+ }
14614
+ }
14545
14615
  async function writeV2InitConfig(configPath, connectionName, connection, permission, envFile) {
14616
+ await assertV2InitImportSupported(configPath);
14546
14617
  const globalConfig = isGlobalConfigPath(configPath);
14547
14618
  const storagePath = globalConfig ? configPath : getProjectStoragePath(configPath);
14548
14619
  const configJsonPath = join10(storagePath, "config.json");
@@ -14574,22 +14645,7 @@ async function writeV2InitConfig(configPath, connectionName, connection, permiss
14574
14645
  } else if (await projectConfigFile.exists()) {
14575
14646
  const raw = JSON.parse(await projectConfigFile.text());
14576
14647
  if (detectConfigVersion(raw) === 2) {
14577
- const v1Config = await configModule.read(configPath);
14578
- existingV2 = {
14579
- version: 2,
14580
- default: "default",
14581
- connections: {
14582
- default: {
14583
- ...v1Config.connection,
14584
- permission: v1Config.permission
14585
- }
14586
- },
14587
- schema: v1Config.schema || {},
14588
- schemas: { default: v1Config.schema || {} },
14589
- metadata: v1Config.metadata || { version: "1.0" },
14590
- blacklist: v1Config.blacklist || { tables: [], columns: {} },
14591
- audit: v1Config.audit
14592
- };
14648
+ existingV2 = await readV2Config(configPath);
14593
14649
  } else {
14594
14650
  const v1Config = await configModule.read(configPath);
14595
14651
  existingV2 = {
@@ -14996,16 +15052,10 @@ async function handleRemove(configPath, name) {
14996
15052
  if (connectionCount <= 1) {
14997
15053
  throw new Error(t("init.cannot_remove_last"));
14998
15054
  }
14999
- const remaining = Object.fromEntries(Object.entries(config.connections).filter(([connectionName]) => connectionName !== name));
15000
- const newDefault = config.default === name ? Object.keys(remaining)[0] : config.default;
15001
- const updated = {
15002
- ...config,
15003
- default: newDefault,
15004
- connections: remaining
15005
- };
15055
+ const updated = removeConnection(config, name);
15006
15056
  await writeV2Config(storagePath, updated);
15007
15057
  if (config.default === name) {
15008
- console.log(t_vars("init.connection_removed_switched", { name, newDefault }));
15058
+ console.log(t_vars("init.connection_removed_default_cleared", { name }));
15009
15059
  } else {
15010
15060
  console.log(t_vars("init.connection_removed", { name }));
15011
15061
  }
@@ -15050,13 +15100,36 @@ async function initCommandHandler(options, command) {
15050
15100
  await handleRename(configPath, options.rename);
15051
15101
  return;
15052
15102
  }
15053
- const isGlobalConfig = isGlobalConfigPath(configPath);
15054
- const isV2Init = !!(options.connName || options.envFile || isGlobalConfig);
15055
- const connectionName = options.connName || "default";
15056
- const existingConfig = await configModule.read(configPath);
15057
15103
  const isUsingEnvRefs = options.useEnvRefs;
15058
15104
  const hasAllEnvOptions = isUsingEnvRefs && options.envHost && options.envPort && options.envUser && options.envPassword && options.envDatabase;
15059
15105
  const shouldPrompt = options.interactive !== false && !hasAllEnvOptions;
15106
+ let connectionName;
15107
+ if (typeof options.connName === "string") {
15108
+ if (!options.connName.trim())
15109
+ throw new Error(t("init.connection_name_required"));
15110
+ connectionName = options.connName;
15111
+ } else {
15112
+ if (!shouldPrompt || !process.stdin.isTTY) {
15113
+ throw new Error(t("init.connection_name_required"));
15114
+ }
15115
+ for (;; ) {
15116
+ if (process.stdin.readableEnded || process.stdin.destroyed) {
15117
+ throw new Error(t("init.connection_name_required"));
15118
+ }
15119
+ connectionName = (await promptUser.text(t("init.prompt_connection_name"))).trim();
15120
+ if (connectionName)
15121
+ break;
15122
+ console.error(t("init.connection_name_required"));
15123
+ }
15124
+ }
15125
+ const isGlobalConfig = isGlobalConfigPath(configPath);
15126
+ const isV2Init = true;
15127
+ await assertV2InitImportSupported(configPath);
15128
+ const existingConfig = await configModule.read(configPath, undefined, { loadLayeredSchema: false }).catch((error) => {
15129
+ if (error instanceof ConnectionSelectionRequiredError)
15130
+ return;
15131
+ throw error;
15132
+ });
15060
15133
  let envConfig = null;
15061
15134
  try {
15062
15135
  envConfig = parseEnvDatabase(process.env);
@@ -15127,10 +15200,11 @@ async function initCommandHandler(options, command) {
15127
15200
  if (!VALID_PERMISSIONS3.includes(permission)) {
15128
15201
  throw new Error(t_vars("errors.invalid_permission", { permission }));
15129
15202
  }
15130
- const newConfig = configModule.merge(existingConfig, {
15203
+ const updates = {
15131
15204
  connection: configForWrite,
15132
15205
  permission
15133
- });
15206
+ };
15207
+ const newConfig = existingConfig ? configModule.merge(existingConfig, updates) : configModule.validate(updates);
15134
15208
  const canProceed = await checkOverwrite(configPath, shouldPrompt, !!options.force);
15135
15209
  if (!canProceed)
15136
15210
  return;
@@ -15203,10 +15277,11 @@ async function initCommandHandler(options, command) {
15203
15277
  database: { $env: envDatabase }
15204
15278
  };
15205
15279
  }
15206
- const newConfig = configModule.merge(existingConfig, {
15280
+ const updates = {
15207
15281
  connection: configForWrite,
15208
15282
  permission
15209
- });
15283
+ };
15284
+ const newConfig = existingConfig ? configModule.merge(existingConfig, updates) : configModule.validate(updates);
15210
15285
  const canProceed = await checkOverwrite(configPath, shouldPrompt, !!options.force);
15211
15286
  if (!canProceed)
15212
15287
  return;
@@ -15274,6 +15349,7 @@ var init_init = __esm(() => {
15274
15349
  init_env_parser();
15275
15350
  init_config();
15276
15351
  init_config_v2();
15352
+ init_config_v2_mutations();
15277
15353
  init_prompts();
15278
15354
  init_redaction();
15279
15355
  init_adapters();
@@ -15285,7 +15361,7 @@ var init_init = __esm(() => {
15285
15361
  init_init_sqlite();
15286
15362
  INIT_SYSTEM_CHOICES = DATABASE_SYSTEMS;
15287
15363
  VALID_PERMISSIONS3 = ["query-only", "read-write", "data-admin", "admin"];
15288
- initCommand = new Command2("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, mongodb, redis, elasticsearch)").option("--file <path>", "SQLite database file path (the database must already exist)").option("--cloud-id <id>", "Elasticsearch Cloud ID").option("--api-key <key>", "Elasticsearch API Key").option("--uri <uri>", "MongoDB connection URI (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").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) => {
15364
+ initCommand = new Command2("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, mongodb, redis, elasticsearch)").option("--file <path>", "SQLite database file path (the database must already exist)").option("--cloud-id <id>", "Elasticsearch Cloud ID").option("--api-key <key>", "Elasticsearch API Key").option("--uri <uri>", "MongoDB connection URI (mongodb://user:pass@host:27017/db?authSource=admin)").option("--auth-source <authSource>", "MongoDB auth database (default: admin when user/password are set)").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 (required in non-interactive mode)").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) => {
15289
15365
  try {
15290
15366
  await initCommandHandler(options, initCommand);
15291
15367
  } catch (error) {
@@ -31320,6 +31396,7 @@ async function updateCommand(table, options, command) {
31320
31396
  await adapter.disconnect();
31321
31397
  }
31322
31398
  }
31399
+ enforcePermissionForType("UPDATE", config.permission);
31323
31400
  let whereConditions;
31324
31401
  try {
31325
31402
  whereConditions = parseWhereClause(options.where);
@@ -42349,7 +42426,14 @@ var init_doctor = __esm(() => {
42349
42426
  });
42350
42427
  return results;
42351
42428
  }
42352
- if (!config.connections[config.default]) {
42429
+ if (config.default === undefined) {
42430
+ results.push({
42431
+ group: "Configuration",
42432
+ label: "Default connection",
42433
+ status: "pass",
42434
+ message: "\u672A\u8A2D\u5B9A\u9810\u8A2D\u9023\u7DDA\uFF1B\u8ACB\u4F7F\u7528 --use <name> \u6216\u8A2D\u5B9A DBCLI_CONNECTION \u660E\u78BA\u6307\u5B9A\u9023\u7DDA"
42435
+ });
42436
+ } else if (!config.connections[config.default]) {
42353
42437
  results.push({
42354
42438
  group: "Configuration",
42355
42439
  label: "Default connection",
@@ -42412,7 +42496,7 @@ var init_doctor = __esm(() => {
42412
42496
  const configPath = resolveConfigPath(doctorCommand);
42413
42497
  const storagePath = await resolveConfigStoragePath(configPath);
42414
42498
  const bunVersion = process.versions.bun ?? "unknown";
42415
- const requiredBun = package_default.engines?.bun?.replace(">=", "") ?? "1.3.3";
42499
+ const requiredBun = package_default.engines?.bun?.replace(">=", "") ?? "1.4.0";
42416
42500
  results.push(runDoctorChecks.checkRuntime(await collectRuntimeInfo(package_default.version)));
42417
42501
  results.push(runDoctorChecks.checkBunVersion(bunVersion, requiredBun));
42418
42502
  results.push(await runDoctorChecks.checkLatestVersion(package_default.version));
@@ -42508,12 +42592,28 @@ var init_doctor = __esm(() => {
42508
42592
  });
42509
42593
  }
42510
42594
  } catch (error) {
42511
- results.push({
42512
- group: "Configuration",
42513
- label: "Config valid",
42514
- status: "error",
42515
- message: `Config invalid: ${error.message}`
42516
- });
42595
+ if (error instanceof ConnectionSelectionRequiredError) {
42596
+ results.push({
42597
+ group: "Configuration",
42598
+ label: "Config valid",
42599
+ status: "pass",
42600
+ message: "Config valid"
42601
+ });
42602
+ results.push(...await runDoctorChecks.checkV2Config(configPath));
42603
+ results.push({
42604
+ group: "Configuration",
42605
+ label: "Connection selection",
42606
+ status: "error",
42607
+ message: error.message
42608
+ });
42609
+ } else {
42610
+ results.push({
42611
+ group: "Configuration",
42612
+ label: "Config valid",
42613
+ status: "error",
42614
+ message: `Config invalid: ${error.message}`
42615
+ });
42616
+ }
42517
42617
  }
42518
42618
  }
42519
42619
  const hasError = results.some((r) => r.status === "error");
@@ -51106,7 +51206,7 @@ var init_migrate = __esm(() => {
51106
51206
  import { join as join41 } from "path";
51107
51207
  async function switchDefault(configPath, name, config, options = {}) {
51108
51208
  const connection = config.connections[name];
51109
- if (!connection) {
51209
+ if (!Object.hasOwn(config.connections, name) || !connection) {
51110
51210
  throw new ConfigError(connectionNotFoundMessage(name, Object.keys(config.connections)));
51111
51211
  }
51112
51212
  assertProductionDefaultConfirmation(name, connection.environment, options.confirmProduction);
@@ -51151,7 +51251,7 @@ function listConnectionIdentities(config) {
51151
51251
  };
51152
51252
  });
51153
51253
  }
51154
- async function ensureV2Config(configPath) {
51254
+ async function ensureV2Config(configPath, allowV1 = true) {
51155
51255
  const storagePath = await resolveConfigStoragePath(configPath);
51156
51256
  const configFile = Bun.file(join41(storagePath, "config.json"));
51157
51257
  const legacyFile = Bun.file(configPath);
@@ -51165,6 +51265,8 @@ async function ensureV2Config(configPath) {
51165
51265
  return JSON.parse(await legacyFile.text());
51166
51266
  })();
51167
51267
  if (!raw.version || raw.version !== 2 || !raw.connections) {
51268
+ if (!allowV1)
51269
+ throw new ConfigError(t("use.requires_v2"));
51168
51270
  const v1Config = await configModule.read(configPath);
51169
51271
  return {
51170
51272
  version: 2,
@@ -51200,11 +51302,19 @@ var init_use = __esm(() => {
51200
51302
  init_config_binding();
51201
51303
  init_validation();
51202
51304
  ALLOWED_FORMATS20 = ["text", "json"];
51203
- useCommand = new Command2("use").description("Switch or display the default database connection (v2 config)").argument("[name]", "Connection name to switch to").option("--list", "List all connections").option("--format <format>", "Output format: text (default) or json", "text").option("--confirm-production <name>", "Confirm changing the persisted default to this production connection by repeating its name").action(async (name, options) => {
51305
+ useCommand = new Command2("use").description("Set, unset, or display the default database connection (v2 config)").argument("[name]", "Connection name to switch to").option("--list", "List all connections").option("--unset", "Clear the default connection and require explicit selection (v2 only)").option("--format <format>", "Output format: text (default) or json", "text").option("--confirm-production <name>", "Confirm changing the persisted default to this production connection by repeating its name").action(async (name, options) => {
51204
51306
  try {
51205
51307
  validateFormat(String(options.format), ALLOWED_FORMATS20, "use");
51308
+ if (options.unset && (name !== undefined || options.list || options.confirmProduction !== undefined || options.format === "json")) {
51309
+ throw new ConfigError(t("use.unset_conflict"));
51310
+ }
51206
51311
  const configPath = resolveConfigPath(useCommand);
51207
- const config = await ensureV2Config(configPath);
51312
+ const config = await ensureV2Config(configPath, !options.unset);
51313
+ if (options.unset) {
51314
+ await clearDefaultConnection(configPath);
51315
+ console.log(t("use.unset"));
51316
+ return;
51317
+ }
51208
51318
  if (name && !options.list && options.format === "json") {
51209
51319
  throw new ConfigError("--format json is only supported with --list");
51210
51320
  }
@@ -51214,7 +51324,7 @@ var init_use = __esm(() => {
51214
51324
  return;
51215
51325
  }
51216
51326
  if (!name) {
51217
- console.log(t_vars("use.current", { name: config.default }));
51327
+ console.log(config.default === undefined ? t("use.current_none") : t_vars("use.current", { name: config.default }));
51218
51328
  }
51219
51329
  console.log("");
51220
51330
  for (const line of listConnectionsForDisplay(config)) {
@@ -51237,15 +51347,6 @@ var init_use = __esm(() => {
51237
51347
  });
51238
51348
  });
51239
51349
 
51240
- // src/core/config-v2-mutations.ts
51241
- function envVarNameFor(connName, field) {
51242
- const slug = connName.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
51243
- return `DBCLI_${slug}_${field.toUpperCase()}`;
51244
- }
51245
- var init_config_v2_mutations = __esm(() => {
51246
- init_validation();
51247
- });
51248
-
51249
51350
  // src/core/env-file-writer.ts
51250
51351
  import { chmod as chmod2, mkdir as mkdir19, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
51251
51352
  import { dirname as dirname16 } from "path";
@@ -51315,15 +51416,11 @@ async function resolvePasswordTarget(projectPath, connectionName) {
51315
51416
  };
51316
51417
  }
51317
51418
  const config = await readV2Config(projectPath);
51318
- const targetName = connectionName ?? config.default;
51319
- const connection = config.connections[targetName];
51320
- if (!connection) {
51321
- throw new ConfigError(connectionNotFoundMessage(targetName, Object.keys(config.connections)));
51322
- }
51419
+ const { name: targetName, connection, envFile } = resolveConnection(config, connectionName);
51323
51420
  const referencedVar = envRefName(connection.password);
51324
51421
  return {
51325
51422
  connection: targetName,
51326
- envFile: connection.envFile ?? DEFAULT_ENV_FILE,
51423
+ envFile: envFile ?? DEFAULT_ENV_FILE,
51327
51424
  varName: referencedVar ?? envVarNameFor(targetName, "password"),
51328
51425
  convertedToEnvRef: referencedVar === undefined
51329
51426
  };
package/dist/cli.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  // package.json
4
4
  var package_default = {
5
5
  name: "@carllee1983/dbcli",
6
- version: "9.1.2",
6
+ version: "10.0.0",
7
7
  description: "Database CLI for AI agents",
8
8
  type: "module",
9
9
  publishConfig: {
@@ -44,7 +44,7 @@ var package_default = {
44
44
  "blacklist"
45
45
  ],
46
46
  engines: {
47
- bun: ">=1.3.3"
47
+ bun: ">=1.4.0"
48
48
  },
49
49
  files: [
50
50
  "dist/",
package/dist/core.d.ts CHANGED
@@ -1827,7 +1827,7 @@ declare const DbcliConfigSchema: z.ZodObject<{
1827
1827
  type DbcliConfig$1 = z.infer<typeof DbcliConfigSchema>;
1828
1828
  declare const DbcliConfigV2Schema: z.ZodEffects<z.ZodObject<{
1829
1829
  version: z.ZodLiteral<2>;
1830
- default: z.ZodString;
1830
+ default: z.ZodOptional<z.ZodString>;
1831
1831
  connections: z.ZodEffects<z.ZodRecord<z.ZodString, z.ZodEffects<z.ZodUnion<[
1832
1832
  z.ZodObject<{
1833
1833
  timeout: z.ZodOptional<z.ZodNumber>;
@@ -2898,7 +2898,6 @@ declare const DbcliConfigV2Schema: z.ZodEffects<z.ZodObject<{
2898
2898
  };
2899
2899
  };
2900
2900
  version: 2;
2901
- default: string;
2902
2901
  metadata: {
2903
2902
  version: string;
2904
2903
  createdAt?: string | undefined;
@@ -3047,9 +3046,9 @@ declare const DbcliConfigV2Schema: z.ZodEffects<z.ZodObject<{
3047
3046
  fields?: string[] | undefined;
3048
3047
  }[];
3049
3048
  } | undefined;
3049
+ default?: string | undefined;
3050
3050
  }, {
3051
3051
  version: 2;
3052
- default: string;
3053
3052
  connections: Record<string, unknown>;
3054
3053
  redis?: {
3055
3054
  mask?: {
@@ -3070,6 +3069,7 @@ declare const DbcliConfigV2Schema: z.ZodEffects<z.ZodObject<{
3070
3069
  max_entries?: number | undefined;
3071
3070
  } | undefined;
3072
3071
  } | undefined;
3072
+ default?: string | undefined;
3073
3073
  metadata?: {
3074
3074
  version?: string | undefined;
3075
3075
  createdAt?: string | undefined;
@@ -3092,7 +3092,6 @@ declare const DbcliConfigV2Schema: z.ZodEffects<z.ZodObject<{
3092
3092
  };
3093
3093
  };
3094
3094
  version: 2;
3095
- default: string;
3096
3095
  metadata: {
3097
3096
  version: string;
3098
3097
  createdAt?: string | undefined;
@@ -3241,9 +3240,9 @@ declare const DbcliConfigV2Schema: z.ZodEffects<z.ZodObject<{
3241
3240
  fields?: string[] | undefined;
3242
3241
  }[];
3243
3242
  } | undefined;
3243
+ default?: string | undefined;
3244
3244
  }, {
3245
3245
  version: 2;
3246
- default: string;
3247
3246
  connections: Record<string, unknown>;
3248
3247
  redis?: {
3249
3248
  mask?: {
@@ -3264,6 +3263,7 @@ declare const DbcliConfigV2Schema: z.ZodEffects<z.ZodObject<{
3264
3263
  max_entries?: number | undefined;
3265
3264
  } | undefined;
3266
3265
  } | undefined;
3266
+ default?: string | undefined;
3267
3267
  metadata?: {
3268
3268
  version?: string | undefined;
3269
3269
  createdAt?: string | undefined;
@@ -3630,9 +3630,7 @@ export declare function resolveConnection(config: DbcliConfigV2, name: string |
3630
3630
  * Load env file for a connection if specified
3631
3631
  */
3632
3632
  export declare function loadConnectionEnv(resolved: ResolvedConnection, basePath: string): Promise<void>;
3633
- /**
3634
- * Read and validate a v2 config from disk
3635
- */
3633
+ /** Read and validate a v2 config from disk. */
3636
3634
  export declare function readV2Config(path: string): Promise<DbcliConfigV2>;
3637
3635
  /**
3638
3636
  * Write a v2 config and its integrity records as one recoverable publication.
@@ -3675,7 +3673,7 @@ export interface ConnectionInput {
3675
3673
  user: string;
3676
3674
  database: string;
3677
3675
  }
3678
- /** 刪除連線(immutable)。刪預設則改派為剩餘第一條;刪最後一條則擋下(v2 需至少一條)。 */
3676
+ /** 刪除連線(immutable)。刪預設則清空預設;刪最後一條則擋下(v2 需至少一條)。 */
3679
3677
  export declare function removeConnection(config: DbcliConfigV2, name: string): DbcliConfigV2;
3680
3678
  /** 設定預設連線(immutable)。 */
3681
3679
  export declare function setDefaultConnection(config: DbcliConfigV2, name: string): DbcliConfigV2;