@carllee1983/dbcli 1.56.0 → 1.58.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.
@@ -51,7 +51,7 @@ var package_default;
51
51
  var init_package = __esm(() => {
52
52
  package_default = {
53
53
  name: "@carllee1983/dbcli",
54
- version: "1.56.0",
54
+ version: "1.58.0",
55
55
  description: "Database CLI for AI agents",
56
56
  type: "module",
57
57
  publishConfig: {
@@ -125,12 +125,14 @@ var init_package = __esm(() => {
125
125
  "test:unit": "bun test tests/unit tests/core",
126
126
  "test:integration": "bun test tests/integration",
127
127
  "test:gherkin": "bun test tests/gherkin",
128
- "test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration/adapters; docker compose -f docker-compose.test.yml down",
128
+ "test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration; docker compose -f docker-compose.test.yml down",
129
+ "services:check": "bun run scripts/check-test-services.ts",
129
130
  "docs:check": "bun run scripts/check-user-docs.ts",
130
131
  "contract:check": "bun run scripts/check-cli-contract.ts",
131
132
  "skill:check": "bun run scripts/check-skill-parity.ts",
132
133
  "platform:check": "bun run scripts/check-platform-parity.ts",
133
134
  "agent-core:check": "bun run scripts/check-agent-core-purity.ts",
135
+ "core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
134
136
  typecheck: "tsc --noEmit --pretty false",
135
137
  "test:perf": "bun test ./tests/perf/*.bench.ts",
136
138
  lint: "eslint src tests scripts --ext .ts --max-warnings=0",
@@ -6232,7 +6234,11 @@ function resolveEnvReferences(config, env, parentKey) {
6232
6234
  }
6233
6235
  function parseEnvPassword(content) {
6234
6236
  const match = content.match(/^DBCLI_PASSWORD=(.+)$/m);
6235
- return match?.[1] != null ? match[1].trim() : null;
6237
+ if (match?.[1] == null)
6238
+ return null;
6239
+ const raw = match[1].trim();
6240
+ const quoted = raw.startsWith('"') && raw.endsWith('"') || raw.startsWith("'") && raw.endsWith("'");
6241
+ return quoted && raw.length >= 2 ? raw.slice(1, -1) : raw;
6236
6242
  }
6237
6243
  var _globalConnectionName, DEFAULT_CONFIG, configModule;
6238
6244
  var init_config = __esm(() => {
@@ -8939,6 +8945,12 @@ var init_messages = __esm(() => {
8939
8945
  invalid_config: "Invalid configuration: {field}",
8940
8946
  connection_failed: "Failed to connect to database: {message}",
8941
8947
  permission_denied: "Permission denied (required: {required})",
8948
+ permission_requires_level: "{type} operation requires {minimum} permission or higher (current level: {permission})",
8949
+ elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
8950
+ escalated_write_requires_admin: "This statement opens as a read but contains an executable {keyword}. A write hidden inside a read statement requires admin permission (current level: {permission}).",
8951
+ multiple_statements_refused: "SQL containing multiple statements is refused below admin permission, because only the first statement determines the permission check. Run each statement separately.",
8952
+ unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires read-write+ for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
8953
+ permission_denied_reason: "Permission denied: {reason}",
8942
8954
  table_not_found: "Table not found: {table}",
8943
8955
  unsupported_lang: "Language '{lang}' not supported, using English",
8944
8956
  invalid_json: "Invalid JSON: {message}",
@@ -8960,7 +8972,8 @@ Provide options: --env-host, --env-port, --env-user, --env-password, --env-datab
8960
8972
  Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactive`,
8961
8973
  env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
8962
8974
  Set {envKey} in .env or environment variables.
8963
- Hint: run 'export {envKey}=<value>' and retry`
8975
+ Hint: run 'export {envKey}=<value>' and retry`,
8976
+ invalid_output_format: "--format {format} is not supported here \u2014 use text or json"
8964
8977
  },
8965
8978
  success: {
8966
8979
  inserted: "Successfully inserted {count} row(s)",
@@ -8971,19 +8984,22 @@ Hint: run 'export {envKey}=<value>' and retry`
8971
8984
  insert: {
8972
8985
  description: "Insert data into a table",
8973
8986
  prompt_data: "Enter JSON data to insert: ",
8974
- confirm: "Insert {count} row(s) into {table}? (y/n): "
8987
+ confirm: "Insert {count} row(s) into {table}? (y/n): ",
8988
+ elasticsearch_unsupported: "Elasticsearch does not support the insert command; for now, use an external tool (e.g. curl or Kibana DevTools) to write documents"
8975
8989
  },
8976
8990
  update: {
8977
8991
  description: "Update data in a table",
8978
8992
  prompt_where: "Enter WHERE clause (e.g., id=1): ",
8979
8993
  prompt_set: 'Enter JSON updates (e.g., {"status": "active"}): ',
8980
- confirm: "Update rows in {table}? (y/n): "
8994
+ confirm: "Update rows in {table}? (y/n): ",
8995
+ elasticsearch_unsupported: "Elasticsearch does not support the update command; for now, use an external tool (e.g. curl or Kibana DevTools) to update documents"
8981
8996
  },
8982
8997
  delete: {
8983
8998
  description: "Delete data from a table",
8984
8999
  prompt_where: "Enter WHERE clause (e.g., id=1): ",
8985
9000
  confirm: "Delete rows from {table}? (y/n): ",
8986
- admin_only: "Delete command requires data-admin or admin permission"
9001
+ admin_only: "Delete command requires data-admin or admin permission",
9002
+ elasticsearch_unsupported: "Elasticsearch does not support the delete command; for now, use an external tool (e.g. curl or Kibana DevTools) to delete documents"
8987
9003
  },
8988
9004
  export: {
8989
9005
  description: "Export query results",
@@ -9058,6 +9074,20 @@ Reference: {referencePath}`,
9058
9074
  add_enum_description: "Create an enum type (PostgreSQL only)",
9059
9075
  alter_enum_description: "Add a value to an enum type",
9060
9076
  drop_enum_description: "Drop an enum type"
9077
+ },
9078
+ password: {
9079
+ description: "Change the password of a single connection, leaving every other setting untouched",
9080
+ arg_connection: "Connection name (defaults to the current default connection)",
9081
+ opt_stdin: "Read the new password from stdin (for rotation scripts)",
9082
+ opt_password: "New password given inline (visible in shell history and process list)",
9083
+ opt_skip_test: "Skip verifying the new password against the database before saving",
9084
+ prompt: "New password for connection '{name}'",
9085
+ source_conflict: "--password and --stdin cannot be used together; pick one source.",
9086
+ needs_source: "No terminal available for a masked prompt. Pass the value with --stdin or --password.",
9087
+ empty: "The new password is empty; nothing was changed.",
9088
+ updated: "Password for connection '{name}' updated \u2192 {envFile} ({varName})",
9089
+ converted: 'The literal password in config.json was replaced by a {{ "$env": "{varName}" }} reference; later rotations only touch the env file.',
9090
+ skipped_test: "Verification skipped (--skip-test): the new password was not checked against the database."
9061
9091
  }
9062
9092
  };
9063
9093
  });
@@ -9223,6 +9253,12 @@ var init_messages2 = __esm(() => {
9223
9253
  invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
9224
9254
  connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
9225
9255
  permission_denied: "\u6B0A\u9650\u88AB\u62D2\uFF08\u9700\u8981\uFF1A{required}\uFF09",
9256
+ permission_requires_level: "{type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
9257
+ elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
9258
+ escalated_write_requires_admin: "\u6B64\u8A9E\u53E5\u8868\u9762\u4E0A\u662F\u8B80\u53D6\uFF0C\u4F46\u5BE6\u969B\u5305\u542B\u53EF\u57F7\u884C\u7684 {keyword}\u3002\u8B80\u53D6\u8A9E\u53E5\u4E2D\u96B1\u85CF\u5BEB\u5165\u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09\u3002",
9259
+ multiple_statements_refused: "\u4F4E\u65BC admin \u6B0A\u9650\u6642\u62D2\u7D55\u57F7\u884C\u5305\u542B\u591A\u500B\u8A9E\u53E5\u7684 SQL\uFF0C\u56E0\u70BA\u6B0A\u9650\u6AA2\u67E5\u53EA\u6703\u4F9D\u64DA\u7B2C\u4E00\u500B\u8A9E\u53E5\u5224\u65B7\u3002\u8ACB\u5C07\u5404\u8A9E\u53E5\u5206\u958B\u57F7\u884C\u3002",
9260
+ unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u81F3\u5C11\u9700\u8981 read-write \u4EE5\u4E0A\u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
9261
+ permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
9226
9262
  table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
9227
9263
  unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
9228
9264
  invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
@@ -9244,7 +9280,8 @@ var init_messages2 = __esm(() => {
9244
9280
  \u6216\u4F7F\u7528\u4E92\u52D5\u6A21\u5F0F\uFF1A\u57F7\u884C "dbcli init --use-env-refs"\uFF08\u4E0D\u52A0 --no-interactive\uFF09`,
9245
9281
  env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
9246
9282
  \u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
9247
- \u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`
9283
+ \u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
9284
+ invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json"
9248
9285
  },
9249
9286
  success: {
9250
9287
  inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
@@ -9255,19 +9292,22 @@ var init_messages2 = __esm(() => {
9255
9292
  insert: {
9256
9293
  description: "\u63D2\u5165\u8CC7\u6599\u5230\u8868\u683C",
9257
9294
  prompt_data: "\u8F38\u5165\u8981\u63D2\u5165\u7684 JSON \u8CC7\u6599\uFF1A",
9258
- confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A"
9295
+ confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A",
9296
+ elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 insert \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u5BEB\u5165"
9259
9297
  },
9260
9298
  update: {
9261
9299
  description: "\u66F4\u65B0\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
9262
9300
  prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
9263
9301
  prompt_set: '\u8F38\u5165 JSON \u66F4\u65B0 (\u4F8B\u5982\uFF1A{"status": "active"})\uFF1A',
9264
- confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A"
9302
+ confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
9303
+ elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 update \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u66F4\u65B0"
9265
9304
  },
9266
9305
  delete: {
9267
9306
  description: "\u522A\u9664\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
9268
9307
  prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
9269
9308
  confirm: "\u522A\u9664 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
9270
- admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650"
9309
+ admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650",
9310
+ elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 delete \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u522A\u9664"
9271
9311
  },
9272
9312
  export: {
9273
9313
  description: "\u532F\u51FA\u67E5\u8A62\u7D50\u679C",
@@ -9342,6 +9382,20 @@ var init_messages2 = __esm(() => {
9342
9382
  add_enum_description: "\u5EFA\u7ACB\u5217\u8209\u578B\u5225\uFF08\u50C5 PostgreSQL\uFF09",
9343
9383
  alter_enum_description: "\u65B0\u589E\u5217\u8209\u503C",
9344
9384
  drop_enum_description: "\u522A\u9664\u5217\u8209\u578B\u5225"
9385
+ },
9386
+ password: {
9387
+ description: "\u53EA\u8B8A\u66F4\u55AE\u4E00\u9023\u7DDA\u7684\u5BC6\u78BC\uFF0C\u5176\u9918\u8A2D\u5B9A\u539F\u5C01\u4E0D\u52D5",
9388
+ arg_connection: "\u9023\u7DDA\u540D\u7A31\uFF08\u7701\u7565\u6642\u6539\u9810\u8A2D\u9023\u7DDA\uFF09",
9389
+ opt_stdin: "\u5F9E stdin \u8B80\u53D6\u65B0\u5BC6\u78BC\uFF08\u4F9B\u8F2A\u66FF\u8173\u672C\u4F7F\u7528\uFF09",
9390
+ opt_password: "\u76F4\u63A5\u5E36\u5165\u65B0\u5BC6\u78BC\uFF08\u6703\u7559\u5728 shell \u6B77\u53F2\u8207\u884C\u7A0B\u5217\u8868\uFF09",
9391
+ opt_skip_test: "\u5B58\u6A94\u524D\u4E0D\u5148\u7528\u65B0\u5BC6\u78BC\u9023\u7DDA\u9A57\u8B49",
9392
+ prompt: "\u9023\u7DDA '{name}' \u7684\u65B0\u5BC6\u78BC",
9393
+ source_conflict: "--password \u8207 --stdin \u53EA\u80FD\u64C7\u4E00\u3002",
9394
+ needs_source: "\u6C92\u6709\u7D42\u7AEF\u6A5F\u53EF\u505A\u906E\u853D\u8F38\u5165\uFF0C\u8ACB\u6539\u7528 --stdin \u6216 --password \u50B3\u5165\u3002",
9395
+ empty: "\u65B0\u5BC6\u78BC\u662F\u7A7A\u7684\uFF0C\u672A\u505A\u4EFB\u4F55\u8B8A\u66F4\u3002",
9396
+ updated: "\u5DF2\u66F4\u65B0\u9023\u7DDA '{name}' \u7684\u5BC6\u78BC \u2192 {envFile}\uFF08{varName}\uFF09",
9397
+ converted: 'config.json \u88E1\u7684\u660E\u6587\u5BC6\u78BC\u5DF2\u6539\u6210 {{ "$env": "{varName}" }} \u53C3\u7167\uFF0C\u4E4B\u5F8C\u8F2A\u66FF\u53EA\u6703\u52D5 env \u6A94\u3002',
9398
+ skipped_test: "\u5DF2\u8DF3\u904E\u9A57\u8B49\uFF08--skip-test\uFF09\uFF1A\u65B0\u5BC6\u78BC\u672A\u7D93\u8CC7\u6599\u5EAB\u78BA\u8A8D\u3002"
9345
9399
  }
9346
9400
  };
9347
9401
  });
@@ -9398,6 +9452,60 @@ var init_shell2 = __esm(() => {
9398
9452
  };
9399
9453
  });
9400
9454
 
9455
+ // resources/lang/en/ceremony.json
9456
+ var ceremony_default;
9457
+ var init_ceremony = __esm(() => {
9458
+ ceremony_default = {
9459
+ updated: "Updated {count} row(s) in {table}",
9460
+ deleted: "Deleted {count} row(s) from {table}",
9461
+ inserted: "Inserted {count} row(s) into {table}",
9462
+ matched_nothing: "No rows matched in {table} \u2014 nothing changed",
9463
+ cancelled: "Cancelled. {table} was not changed",
9464
+ dry_run: "Preview only. {table} was not changed",
9465
+ elapsed: "Took {seconds}s",
9466
+ failed: "Failed: {reason}",
9467
+ unknown_error: "unknown error",
9468
+ confirm_sql: "Generated SQL:",
9469
+ confirm_command: "Command to run:",
9470
+ confirm_params: "Parameters:",
9471
+ confirm_destructive_warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
9472
+ confirm_destructive_prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone.",
9473
+ confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
9474
+ confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
9475
+ confirm_prompt: "Proceed with this operation?",
9476
+ recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
9477
+ recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
9478
+ blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
9479
+ };
9480
+ });
9481
+
9482
+ // resources/lang/zh-TW/ceremony.json
9483
+ var ceremony_default2;
9484
+ var init_ceremony2 = __esm(() => {
9485
+ ceremony_default2 = {
9486
+ updated: "\u5DF2\u66F4\u65B0 {table} \u7684 {count} \u5217",
9487
+ deleted: "\u5DF2\u5F9E {table} \u522A\u9664 {count} \u5217",
9488
+ inserted: "\u5DF2\u5BEB\u5165 {count} \u5217\u5230 {table}",
9489
+ matched_nothing: "{table} \u6C92\u6709\u4EFB\u4F55\u5217\u7B26\u5408\u689D\u4EF6\uFF0C\u8CC7\u6599\u672A\u8B8A\u52D5",
9490
+ cancelled: "\u5DF2\u53D6\u6D88\uFF0C{table} \u672A\u8B8A\u52D5",
9491
+ dry_run: "\u50C5\u9810\u89BD\uFF0C{table} \u672A\u8B8A\u52D5",
9492
+ elapsed: "\u8017\u6642 {seconds} \u79D2",
9493
+ failed: "\u5931\u6557\uFF1A{reason}",
9494
+ unknown_error: "\u672A\u77E5\u7684\u932F\u8AA4",
9495
+ confirm_sql: "\u7522\u751F\u7684 SQL\uFF1A",
9496
+ confirm_command: "\u5373\u5C07\u57F7\u884C\u7684\u6307\u4EE4\uFF1A",
9497
+ confirm_params: "\u53C3\u6578\uFF1A",
9498
+ confirm_destructive_warning: "\u26A0\uFE0F \u8B66\u544A\uFF1ADELETE \u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
9499
+ confirm_destructive_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B DELETE \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
9500
+ confirm_destructive_warning_ddl: "\u26A0\uFE0F \u8B66\u544A\uFF1A\u9019\u500B schema \u8B8A\u66F4\u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
9501
+ confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
9502
+ confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
9503
+ recovery_none: "\u6B64\u64CD\u4F5C\u7121\u6CD5\u81EA\u52D5\u9084\u539F\uFF0C\u8981\u53D6\u56DE\u5148\u524D\u7684\u8CC7\u6599\u8ACB\u5F9E\u5099\u4EFD\u56DE\u5FA9\u3002",
9504
+ recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
9505
+ blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
9506
+ };
9507
+ });
9508
+
9401
9509
  // src/i18n/message-loader.ts
9402
9510
  class MessageLoader {
9403
9511
  static instance = null;
@@ -9464,9 +9572,15 @@ var init_message_loader = __esm(() => {
9464
9572
  init_messages2();
9465
9573
  init_shell();
9466
9574
  init_shell2();
9575
+ init_ceremony();
9576
+ init_ceremony2();
9467
9577
  BUNDLED_MESSAGES = {
9468
- en: { ...messages_default, shell: shell_default },
9469
- "zh-TW": { ...messages_default2, shell: shell_default2 }
9578
+ en: { ...messages_default, shell: shell_default, ceremony: ceremony_default },
9579
+ "zh-TW": {
9580
+ ...messages_default2,
9581
+ shell: shell_default2,
9582
+ ceremony: ceremony_default2
9583
+ }
9470
9584
  };
9471
9585
  messageLoader = MessageLoader.getInstance();
9472
9586
  });
@@ -9632,7 +9746,8 @@ async function selectFallback(message, choices) {
9632
9746
  return choices[0] ?? "";
9633
9747
  }
9634
9748
  async function confirmFallback(message) {
9635
- const answer = await readLineFromStdin(`${message} (y/n): `);
9749
+ process.stderr.write(`${message} (y/n): `);
9750
+ const answer = await readLineFromStdin();
9636
9751
  return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
9637
9752
  }
9638
9753
  async function text(message, defaultValue) {
@@ -9651,20 +9766,28 @@ async function select(message, choices) {
9651
9766
  return await selectFallback(message, choices);
9652
9767
  return await inquirer.select({ message, choices });
9653
9768
  }
9769
+ async function secret(message) {
9770
+ const inquirer = await loadInquirer();
9771
+ if (!inquirer) {
9772
+ throw new Error("Masked input is unavailable; pass the value with --stdin or --password instead.");
9773
+ }
9774
+ return await inquirer.password({ message, mask: "*" });
9775
+ }
9654
9776
  async function confirm(message) {
9655
9777
  if (!process.stdin.isTTY)
9656
9778
  return await confirmFallback(message);
9657
9779
  const inquirer = await loadInquirer();
9658
9780
  if (!inquirer)
9659
9781
  return await confirmFallback(message);
9660
- return await inquirer.confirm({ message });
9782
+ return await inquirer.confirm({ message }, { output: process.stderr });
9661
9783
  }
9662
9784
  var promptsUnavailableReported = false, promptUser;
9663
9785
  var init_prompts = __esm(() => {
9664
9786
  promptUser = {
9665
9787
  text,
9666
9788
  select,
9667
- confirm
9789
+ confirm,
9790
+ secret
9668
9791
  };
9669
9792
  });
9670
9793
 
@@ -18515,33 +18638,7 @@ var init_sql_lexical = __esm(() => {
18515
18638
  DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
18516
18639
  });
18517
18640
 
18518
- // src/core/permission-guard.ts
18519
- var exports_permission_guard = {};
18520
- __export(exports_permission_guard, {
18521
- toSqlDialect: () => toSqlDialect,
18522
- stripCommentsAndStrings: () => stripCommentsAndStrings,
18523
- removeParameterMarkers: () => removeParameterMarkers,
18524
- permissionAtLeast: () => permissionAtLeast,
18525
- normalizeSQL: () => normalizeSQL,
18526
- mapKeywordToType: () => mapKeywordToType,
18527
- isDestructiveOperation: () => isDestructiveOperation,
18528
- findWriteKeyword: () => findWriteKeyword,
18529
- extractFirstKeyword: () => extractFirstKeyword,
18530
- extractAllKeywords: () => extractAllKeywords,
18531
- enforceRedisPermission: () => enforceRedisPermission,
18532
- enforcePermission: () => enforcePermission,
18533
- enforceElasticsearchPermission: () => enforceElasticsearchPermission,
18534
- determineConfidence: () => determineConfidence,
18535
- detectCompositePatterns: () => detectCompositePatterns,
18536
- containsMultipleStatements: () => containsMultipleStatements,
18537
- classifyStatement: () => classifyStatement,
18538
- classifyRedisCommand: () => classifyRedisCommand,
18539
- classifyElasticsearchRequest: () => classifyElasticsearchRequest,
18540
- checkPermission: () => checkPermission,
18541
- SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
18542
- SQL_DIALECTS: () => SQL_DIALECTS,
18543
- PermissionError: () => PermissionError
18544
- });
18641
+ // src/core/permission/sql-analysis.ts
18545
18642
  function normalizeSQL(sql) {
18546
18643
  return sql.replace(/--[^\n]*\n/g, `
18547
18644
  `).replace(/\/\*[\s\S]*?\*\//g, " ").trim().replace(/\s+/g, " ");
@@ -18788,6 +18885,29 @@ function determineConfidence(type, _keyword, _sql) {
18788
18885
  function removeParameterMarkers(sql) {
18789
18886
  return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
18790
18887
  }
18888
+ var init_sql_analysis = __esm(() => {
18889
+ init_sql_lexical();
18890
+ });
18891
+
18892
+ // src/core/permission-guard.ts
18893
+ var exports_permission_guard = {};
18894
+ __export(exports_permission_guard, {
18895
+ toSqlDialect: () => toSqlDialect,
18896
+ permitsOperation: () => permitsOperation,
18897
+ permissionAtLeast: () => permissionAtLeast,
18898
+ minimumPermissionFor: () => minimumPermissionFor,
18899
+ findWriteKeyword: () => findWriteKeyword,
18900
+ enforcePermissionForType: () => enforcePermissionForType,
18901
+ enforcePermission: () => enforcePermission,
18902
+ containsMultipleStatements: () => containsMultipleStatements,
18903
+ classifyStatement: () => classifyStatement,
18904
+ classificationForType: () => classificationForType,
18905
+ checkPermissionForClassification: () => checkPermissionForClassification,
18906
+ checkPermission: () => checkPermission,
18907
+ SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
18908
+ SQL_DIALECTS: () => SQL_DIALECTS,
18909
+ PermissionError: () => PermissionError
18910
+ });
18791
18911
  function classifyStatement(sql) {
18792
18912
  const normalized = normalizeSQL(sql);
18793
18913
  const stripped = stripCommentsAndStrings(normalized);
@@ -18853,15 +18973,33 @@ function checkPermission(sql, permission, dialect) {
18853
18973
  if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
18854
18974
  return {
18855
18975
  allowed: false,
18856
- reason: "SQL containing multiple statements is refused below admin permission, because only " + "the first statement determines the permission check. Run each statement separately.",
18857
- classification
18976
+ reason: t("errors.multiple_statements_refused"),
18977
+ classification,
18978
+ requiredPermission: "admin"
18858
18979
  };
18859
18980
  }
18981
+ return checkPermissionForClassification(classification, permission);
18982
+ }
18983
+ function minimumPermissionFor(type) {
18984
+ return TIER_GRANTS.find((tier) => tier.types.includes(type))?.permission ?? "admin";
18985
+ }
18986
+ function refusalReason(type, permission) {
18987
+ return t_vars("errors.permission_requires_level", {
18988
+ type,
18989
+ minimum: minimumPermissionFor(type),
18990
+ permission
18991
+ });
18992
+ }
18993
+ function checkPermissionForClassification(classification, permission) {
18860
18994
  if (permission !== "admin" && classification.escalatedFrom) {
18861
18995
  return {
18862
18996
  allowed: false,
18863
- reason: `This statement opens as a read but contains an executable ` + `${classification.escalatedFrom}. A write hidden inside a read statement ` + `requires admin permission (current level: ${permission}).`,
18864
- classification
18997
+ reason: t_vars("errors.escalated_write_requires_admin", {
18998
+ keyword: classification.escalatedFrom,
18999
+ permission
19000
+ }),
19001
+ classification,
19002
+ requiredPermission: "admin"
18865
19003
  };
18866
19004
  }
18867
19005
  if (permission === "admin") {
@@ -18871,220 +19009,64 @@ function checkPermission(sql, permission, dialect) {
18871
19009
  classification
18872
19010
  };
18873
19011
  }
18874
- if (permission === "data-admin") {
18875
- const allowedTypes = ["SELECT", "INSERT", "UPDATE", "DELETE", "SHOW", "DESCRIBE", "EXPLAIN"];
18876
- if (allowedTypes.includes(classification.type)) {
18877
- return {
18878
- allowed: true,
18879
- reason: `${classification.type} operation allowed in data-admin mode`,
18880
- classification
18881
- };
18882
- }
18883
- return {
18884
- allowed: false,
18885
- reason: `${classification.type} operation requires admin permission`,
18886
- classification
18887
- };
18888
- }
18889
- if (permission === "read-write") {
18890
- const allowedTypes = ["SELECT", "INSERT", "UPDATE", "SHOW", "DESCRIBE", "EXPLAIN"];
18891
- if (allowedTypes.includes(classification.type)) {
18892
- return {
18893
- allowed: true,
18894
- reason: `${classification.type} operation allowed in read-write mode`,
18895
- classification
18896
- };
18897
- }
18898
- return {
18899
- allowed: false,
18900
- reason: `${classification.type} operation requires data-admin or admin permission`,
18901
- classification
18902
- };
18903
- }
18904
- if (permission === "query-only") {
18905
- const allowedTypes = ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"];
19012
+ const tierIndex = TIER_GRANTS.findIndex((tier) => tier.permission === permission);
19013
+ if (tierIndex !== -1) {
19014
+ const allowedTypes = TIER_GRANTS.slice(0, tierIndex + 1).flatMap((tier) => tier.types);
18906
19015
  if (allowedTypes.includes(classification.type)) {
18907
19016
  return {
18908
19017
  allowed: true,
18909
- reason: `${classification.type} operation allowed in query-only mode`,
19018
+ reason: `${classification.type} operation allowed in ${permission} mode`,
18910
19019
  classification
18911
19020
  };
18912
19021
  }
18913
- const isUnknown = classification.type === "UNKNOWN";
19022
+ const isUnknown = permission === "query-only" && classification.type === "UNKNOWN";
18914
19023
  return {
18915
19024
  allowed: false,
18916
- reason: isUnknown ? `Unrecognised SQL statement (current level: query-only). Security policy requires read-write+ for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.` : `${classification.type} operation requires read-write or admin permission`,
18917
- classification
19025
+ reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
19026
+ classification,
19027
+ requiredPermission: isUnknown ? "read-write" : minimumPermissionFor(classification.type)
18918
19028
  };
18919
19029
  }
18920
19030
  return {
18921
19031
  allowed: false,
18922
19032
  reason: `Unknown permission level: ${permission}`,
18923
- classification
19033
+ classification,
19034
+ requiredPermission: "admin"
18924
19035
  };
18925
19036
  }
18926
- function enforcePermission(sql, permission, dialect) {
18927
- const result = checkPermission(sql, permission, dialect);
18928
- if (!result.allowed) {
18929
- throw new PermissionError(result.reason, result.classification, permission);
18930
- }
18931
- return result.classification;
18932
- }
18933
- function classifyRedisCommand(command) {
18934
- const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
18935
- const required = REDIS_COMMAND_PERMISSION[head];
18936
- let type;
18937
- if (!required)
18938
- type = "UNKNOWN";
18939
- else if (required === "query-only")
18940
- type = "SELECT";
18941
- else if (required === "read-write")
18942
- type = "UPDATE";
18943
- else if (required === "data-admin")
18944
- type = "DELETE";
18945
- else
18946
- type = "DROP";
19037
+ function classificationForType(type) {
18947
19038
  return {
18948
- command: head,
18949
- requiredPermission: required ?? "unknown",
18950
19039
  type,
18951
- isDangerous: required === "admin" || required === "data-admin"
18952
- };
18953
- }
18954
- function permissionAtLeast(actual, required) {
18955
- return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
18956
- }
18957
- function enforceRedisPermission(command, permission) {
18958
- const classification = classifyRedisCommand(command);
18959
- const required = classification.requiredPermission;
18960
- const stmt = {
18961
- type: classification.type,
18962
- isDangerous: classification.isDangerous,
18963
- keywords: [classification.command],
19040
+ isDangerous: isDestructiveOperation(type),
19041
+ keywords: [type],
18964
19042
  isComposite: false,
18965
- confidence: required === "unknown" ? "LOW" : "HIGH"
19043
+ confidence: "HIGH"
18966
19044
  };
18967
- if (required === "unknown") {
18968
- throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
18969
- }
18970
- if (!permissionAtLeast(permission, required)) {
18971
- throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
18972
- }
18973
- return classification;
18974
19045
  }
18975
- function classifyElasticsearchRequest(request) {
18976
- const method = request.method.toUpperCase();
18977
- const path = request.apiPath.toLowerCase();
18978
- if (path.includes("_bulk")) {
18979
- return classifyElasticsearchBulk(request.body ?? "");
18980
- }
18981
- if (path.includes("_search") || path.includes("_count") || path.includes("_mapping") || path.includes("_settings") || path.includes("_alias") || method === "GET" && (path.includes("_doc") || path.includes("_source"))) {
18982
- return {
18983
- type: "SELECT",
18984
- isDangerous: false,
18985
- keywords: [method, path],
18986
- isComposite: false,
18987
- confidence: "HIGH"
18988
- };
18989
- }
18990
- if (path.includes("_update") || method === "POST" && path.includes("_doc")) {
18991
- return {
18992
- type: "UPDATE",
18993
- isDangerous: false,
18994
- keywords: [method, path],
18995
- isComposite: false,
18996
- confidence: "HIGH"
18997
- };
18998
- }
18999
- if (method === "PUT" && (path.includes("_doc") || path.includes("_create"))) {
19000
- return {
19001
- type: "INSERT",
19002
- isDangerous: false,
19003
- keywords: [method, path],
19004
- isComposite: false,
19005
- confidence: "HIGH"
19006
- };
19007
- }
19008
- if (method === "DELETE") {
19009
- return {
19010
- type: "DELETE",
19011
- isDangerous: true,
19012
- keywords: [method, path],
19013
- isComposite: false,
19014
- confidence: "HIGH"
19015
- };
19016
- }
19017
- return {
19018
- type: "DROP",
19019
- isDangerous: true,
19020
- keywords: [method, path],
19021
- isComposite: false,
19022
- confidence: "LOW"
19023
- };
19046
+ function permitsOperation(type, permission) {
19047
+ return checkPermissionForClassification(classificationForType(type), permission).allowed;
19024
19048
  }
19025
- function classifyElasticsearchBulk(body) {
19026
- const lines = body.split(`
19027
- `).filter((l) => l.trim().length > 0);
19028
- let highestType = "SELECT";
19029
- let isDangerous = false;
19030
- for (const line of lines) {
19031
- try {
19032
- const action = JSON.parse(line);
19033
- const op = Object.keys(action)[0];
19034
- if (op === "delete") {
19035
- highestType = "DELETE";
19036
- isDangerous = true;
19037
- break;
19038
- }
19039
- if (op === "update" && highestType !== "DELETE") {
19040
- highestType = "UPDATE";
19041
- }
19042
- if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
19043
- highestType = "INSERT";
19044
- }
19045
- } catch {}
19049
+ function enforcePermissionForType(type, permission) {
19050
+ const classification = classificationForType(type);
19051
+ const result = checkPermissionForClassification(classification, permission);
19052
+ if (!result.allowed) {
19053
+ throw new PermissionError(result.reason, classification, minimumPermissionFor(type));
19046
19054
  }
19047
- return {
19048
- type: highestType,
19049
- isDangerous,
19050
- keywords: ["BULK"],
19051
- isComposite: true,
19052
- confidence: "HIGH"
19053
- };
19054
19055
  }
19055
- function enforceElasticsearchPermission(request, permission) {
19056
- const classification = classifyElasticsearchRequest(request);
19057
- const result = checkElasticsearchPermission(classification, permission);
19056
+ function enforcePermission(sql, permission, dialect) {
19057
+ const result = checkPermission(sql, permission, dialect);
19058
19058
  if (!result.allowed) {
19059
- throw new PermissionError(result.reason, classification, permission);
19059
+ throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
19060
19060
  }
19061
- return classification;
19061
+ return result.classification;
19062
19062
  }
19063
- function checkElasticsearchPermission(classification, permission) {
19064
- if (permission === "admin")
19065
- return { allowed: true, reason: "Admin" };
19066
- if (permission === "data-admin") {
19067
- const allowed = ["SELECT", "INSERT", "UPDATE", "DELETE"];
19068
- if (allowed.includes(classification.type))
19069
- return { allowed: true, reason: "Data-Admin" };
19070
- }
19071
- if (permission === "read-write") {
19072
- const allowed = ["SELECT", "INSERT", "UPDATE"];
19073
- if (allowed.includes(classification.type))
19074
- return { allowed: true, reason: "Read-Write" };
19075
- }
19076
- if (permission === "query-only") {
19077
- if (classification.type === "SELECT")
19078
- return { allowed: true, reason: "Query-Only" };
19079
- }
19080
- return {
19081
- allowed: false,
19082
- reason: `Elasticsearch ${classification.type} operation requires higher permission tier`
19083
- };
19063
+ function permissionAtLeast(actual, required) {
19064
+ return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
19084
19065
  }
19085
- var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, REDIS_COMMAND_PERMISSION, PERMISSION_RANK;
19066
+ var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, TIER_GRANTS, PERMISSION_RANK;
19086
19067
  var init_permission_guard = __esm(() => {
19087
- init_sql_lexical();
19068
+ init_message_loader();
19069
+ init_sql_analysis();
19088
19070
  PermissionError = class PermissionError extends Error {
19089
19071
  classification;
19090
19072
  requiredPermission;
@@ -19100,90 +19082,11 @@ var init_permission_guard = __esm(() => {
19100
19082
  SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
19101
19083
  SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
19102
19084
  ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
19103
- REDIS_COMMAND_PERMISSION = {
19104
- GET: "query-only",
19105
- MGET: "query-only",
19106
- STRLEN: "query-only",
19107
- EXISTS: "query-only",
19108
- TTL: "query-only",
19109
- PTTL: "query-only",
19110
- TYPE: "query-only",
19111
- SCAN: "query-only",
19112
- HGET: "query-only",
19113
- HGETALL: "query-only",
19114
- HKEYS: "query-only",
19115
- HVALS: "query-only",
19116
- HLEN: "query-only",
19117
- HEXISTS: "query-only",
19118
- HMGET: "query-only",
19119
- LRANGE: "query-only",
19120
- LLEN: "query-only",
19121
- LINDEX: "query-only",
19122
- SMEMBERS: "query-only",
19123
- SCARD: "query-only",
19124
- SISMEMBER: "query-only",
19125
- ZRANGE: "query-only",
19126
- ZREVRANGE: "query-only",
19127
- ZRANGEBYSCORE: "query-only",
19128
- ZCARD: "query-only",
19129
- ZSCORE: "query-only",
19130
- PING: "query-only",
19131
- ECHO: "query-only",
19132
- SET: "read-write",
19133
- SETEX: "read-write",
19134
- SETNX: "read-write",
19135
- PSETEX: "read-write",
19136
- MSET: "read-write",
19137
- MSETNX: "read-write",
19138
- APPEND: "read-write",
19139
- INCR: "read-write",
19140
- INCRBY: "read-write",
19141
- DECR: "read-write",
19142
- DECRBY: "read-write",
19143
- HSET: "read-write",
19144
- HSETNX: "read-write",
19145
- HMSET: "read-write",
19146
- HINCRBY: "read-write",
19147
- LPUSH: "read-write",
19148
- RPUSH: "read-write",
19149
- LPOP: "read-write",
19150
- RPOP: "read-write",
19151
- LSET: "read-write",
19152
- LREM: "read-write",
19153
- SADD: "read-write",
19154
- SREM: "read-write",
19155
- ZADD: "read-write",
19156
- ZREM: "read-write",
19157
- XADD: "read-write",
19158
- XDEL: "data-admin",
19159
- XLEN: "query-only",
19160
- XREAD: "query-only",
19161
- XRANGE: "query-only",
19162
- XREVRANGE: "query-only",
19163
- EXPIRE: "read-write",
19164
- EXPIREAT: "read-write",
19165
- PEXPIRE: "read-write",
19166
- PERSIST: "read-write",
19167
- RENAME: "read-write",
19168
- DEL: "data-admin",
19169
- UNLINK: "data-admin",
19170
- HDEL: "data-admin",
19171
- FLUSHDB: "admin",
19172
- FLUSHALL: "admin",
19173
- CONFIG: "admin",
19174
- INFO: "admin",
19175
- CLIENT: "admin",
19176
- DEBUG: "admin",
19177
- SHUTDOWN: "admin",
19178
- KEYS: "admin",
19179
- MONITOR: "admin",
19180
- SAVE: "admin",
19181
- BGSAVE: "admin",
19182
- BGREWRITEAOF: "admin",
19183
- REPLICAOF: "admin",
19184
- SLAVEOF: "admin",
19185
- ACL: "admin"
19186
- };
19085
+ TIER_GRANTS = [
19086
+ { permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
19087
+ { permission: "read-write", types: ["INSERT", "UPDATE"] },
19088
+ { permission: "data-admin", types: ["DELETE"] }
19089
+ ];
19187
19090
  PERMISSION_RANK = {
19188
19091
  "query-only": 1,
19189
19092
  "read-write": 2,
@@ -21874,6 +21777,7 @@ function inferColumnType(value) {
21874
21777
  }
21875
21778
  var init_query_executor = __esm(() => {
21876
21779
  init_permission_guard();
21780
+ init_sql_analysis();
21877
21781
  init_error_suggester();
21878
21782
  init_sql_tables();
21879
21783
  init_applied_limit();
@@ -22173,6 +22077,263 @@ function assertFanOutReadOnlySql(sql, dialect) {
22173
22077
  var init_query_fanout = __esm(() => {
22174
22078
  init_cli_error();
22175
22079
  init_permission_guard();
22080
+ init_sql_analysis();
22081
+ });
22082
+
22083
+ // src/core/permission/elasticsearch.ts
22084
+ function classifyElasticsearchRequest(request) {
22085
+ const method = request.method.toUpperCase();
22086
+ const path2 = request.apiPath.toLowerCase();
22087
+ if (path2.includes("_bulk")) {
22088
+ return classifyElasticsearchBulk(request.body ?? "");
22089
+ }
22090
+ const readMethod = method === "GET" || method === "HEAD";
22091
+ const searchPath = path2.includes("_search") || path2.includes("_count");
22092
+ if (searchPath && (readMethod || method === "POST") || readMethod && (path2.includes("_mapping") || path2.includes("_settings") || path2.includes("_alias") || path2.includes("_doc") || path2.includes("_source"))) {
22093
+ return {
22094
+ type: "SELECT",
22095
+ isDangerous: false,
22096
+ keywords: [method, path2],
22097
+ isComposite: false,
22098
+ confidence: "HIGH"
22099
+ };
22100
+ }
22101
+ if (path2.includes("_update") || method === "POST" && path2.includes("_doc")) {
22102
+ return {
22103
+ type: "UPDATE",
22104
+ isDangerous: false,
22105
+ keywords: [method, path2],
22106
+ isComposite: false,
22107
+ confidence: "HIGH"
22108
+ };
22109
+ }
22110
+ if (method === "PUT" && (path2.includes("_doc") || path2.includes("_create"))) {
22111
+ return {
22112
+ type: "INSERT",
22113
+ isDangerous: false,
22114
+ keywords: [method, path2],
22115
+ isComposite: false,
22116
+ confidence: "HIGH"
22117
+ };
22118
+ }
22119
+ if (method === "DELETE") {
22120
+ if (ES_DOCUMENT_PATH.test(path2)) {
22121
+ return {
22122
+ type: "DELETE",
22123
+ isDangerous: true,
22124
+ keywords: [method, path2],
22125
+ isComposite: false,
22126
+ confidence: "HIGH"
22127
+ };
22128
+ }
22129
+ return {
22130
+ type: "DROP",
22131
+ isDangerous: true,
22132
+ keywords: [method, path2],
22133
+ isComposite: false,
22134
+ confidence: "HIGH"
22135
+ };
22136
+ }
22137
+ return {
22138
+ type: "DROP",
22139
+ isDangerous: true,
22140
+ keywords: [method, path2],
22141
+ isComposite: false,
22142
+ confidence: "LOW"
22143
+ };
22144
+ }
22145
+ function classifyElasticsearchBulk(body) {
22146
+ const lines = body.split(`
22147
+ `).filter((l) => l.trim().length > 0);
22148
+ let highestType = "SELECT";
22149
+ let isDangerous = false;
22150
+ for (const line of lines) {
22151
+ try {
22152
+ const action = JSON.parse(line);
22153
+ const op = Object.keys(action)[0];
22154
+ if (op === "delete") {
22155
+ highestType = "DELETE";
22156
+ isDangerous = true;
22157
+ break;
22158
+ }
22159
+ if (op === "update" && highestType !== "DELETE") {
22160
+ highestType = "UPDATE";
22161
+ }
22162
+ if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
22163
+ highestType = "INSERT";
22164
+ }
22165
+ } catch {}
22166
+ }
22167
+ return {
22168
+ type: highestType,
22169
+ isDangerous,
22170
+ keywords: ["BULK"],
22171
+ isComposite: true,
22172
+ confidence: "HIGH"
22173
+ };
22174
+ }
22175
+ function enforceElasticsearchPermission(request, permission) {
22176
+ const classification = classifyElasticsearchRequest(request);
22177
+ const result = checkElasticsearchPermission(classification, permission);
22178
+ if (!result.allowed) {
22179
+ throw new PermissionError(result.reason, classification, result.requiredPermission ?? "admin");
22180
+ }
22181
+ return classification;
22182
+ }
22183
+ function checkElasticsearchPermission(classification, permission) {
22184
+ if (permitsOperation(classification.type, permission)) {
22185
+ return { allowed: true, reason: `${classification.type} allowed in ${permission} mode` };
22186
+ }
22187
+ const minimum = minimumPermissionFor(classification.type);
22188
+ return {
22189
+ allowed: false,
22190
+ reason: t_vars("errors.elasticsearch_requires_level", {
22191
+ type: classification.type,
22192
+ minimum,
22193
+ permission
22194
+ }),
22195
+ requiredPermission: minimum
22196
+ };
22197
+ }
22198
+ var ES_DOCUMENT_PATH;
22199
+ var init_elasticsearch = __esm(() => {
22200
+ init_permission_guard();
22201
+ init_message_loader();
22202
+ ES_DOCUMENT_PATH = /\/(?:_doc|_source)\/[^/?]+/;
22203
+ });
22204
+
22205
+ // src/core/permission/redis.ts
22206
+ var exports_redis = {};
22207
+ __export(exports_redis, {
22208
+ enforceRedisPermission: () => enforceRedisPermission,
22209
+ classifyRedisCommand: () => classifyRedisCommand
22210
+ });
22211
+ function classifyRedisCommand(command) {
22212
+ const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
22213
+ const required = REDIS_COMMAND_PERMISSION[head];
22214
+ let type;
22215
+ if (!required)
22216
+ type = "UNKNOWN";
22217
+ else if (required === "query-only")
22218
+ type = "SELECT";
22219
+ else if (required === "read-write")
22220
+ type = "UPDATE";
22221
+ else if (required === "data-admin")
22222
+ type = "DELETE";
22223
+ else
22224
+ type = "DROP";
22225
+ return {
22226
+ command: head,
22227
+ requiredPermission: required ?? "unknown",
22228
+ type,
22229
+ isDangerous: required === "admin" || required === "data-admin"
22230
+ };
22231
+ }
22232
+ function enforceRedisPermission(command, permission) {
22233
+ const classification = classifyRedisCommand(command);
22234
+ const required = classification.requiredPermission;
22235
+ const stmt = {
22236
+ type: classification.type,
22237
+ isDangerous: classification.isDangerous,
22238
+ keywords: [classification.command],
22239
+ isComposite: false,
22240
+ confidence: required === "unknown" ? "LOW" : "HIGH"
22241
+ };
22242
+ if (required === "unknown") {
22243
+ throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
22244
+ }
22245
+ if (!permissionAtLeast(permission, required)) {
22246
+ throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
22247
+ }
22248
+ return classification;
22249
+ }
22250
+ var REDIS_COMMAND_PERMISSION;
22251
+ var init_redis = __esm(() => {
22252
+ init_permission_guard();
22253
+ REDIS_COMMAND_PERMISSION = {
22254
+ GET: "query-only",
22255
+ MGET: "query-only",
22256
+ STRLEN: "query-only",
22257
+ EXISTS: "query-only",
22258
+ TTL: "query-only",
22259
+ PTTL: "query-only",
22260
+ TYPE: "query-only",
22261
+ SCAN: "query-only",
22262
+ HGET: "query-only",
22263
+ HGETALL: "query-only",
22264
+ HKEYS: "query-only",
22265
+ HVALS: "query-only",
22266
+ HLEN: "query-only",
22267
+ HEXISTS: "query-only",
22268
+ HMGET: "query-only",
22269
+ LRANGE: "query-only",
22270
+ LLEN: "query-only",
22271
+ LINDEX: "query-only",
22272
+ SMEMBERS: "query-only",
22273
+ SCARD: "query-only",
22274
+ SISMEMBER: "query-only",
22275
+ ZRANGE: "query-only",
22276
+ ZREVRANGE: "query-only",
22277
+ ZRANGEBYSCORE: "query-only",
22278
+ ZCARD: "query-only",
22279
+ ZSCORE: "query-only",
22280
+ PING: "query-only",
22281
+ ECHO: "query-only",
22282
+ SET: "read-write",
22283
+ SETEX: "read-write",
22284
+ SETNX: "read-write",
22285
+ PSETEX: "read-write",
22286
+ MSET: "read-write",
22287
+ MSETNX: "read-write",
22288
+ APPEND: "read-write",
22289
+ INCR: "read-write",
22290
+ INCRBY: "read-write",
22291
+ DECR: "read-write",
22292
+ DECRBY: "read-write",
22293
+ HSET: "read-write",
22294
+ HSETNX: "read-write",
22295
+ HMSET: "read-write",
22296
+ HINCRBY: "read-write",
22297
+ LPUSH: "read-write",
22298
+ RPUSH: "read-write",
22299
+ LPOP: "read-write",
22300
+ RPOP: "read-write",
22301
+ LSET: "read-write",
22302
+ LREM: "read-write",
22303
+ SADD: "read-write",
22304
+ SREM: "read-write",
22305
+ ZADD: "read-write",
22306
+ ZREM: "read-write",
22307
+ XADD: "read-write",
22308
+ XDEL: "data-admin",
22309
+ XLEN: "query-only",
22310
+ XREAD: "query-only",
22311
+ XRANGE: "query-only",
22312
+ XREVRANGE: "query-only",
22313
+ EXPIRE: "read-write",
22314
+ EXPIREAT: "read-write",
22315
+ PEXPIRE: "read-write",
22316
+ PERSIST: "read-write",
22317
+ RENAME: "read-write",
22318
+ DEL: "data-admin",
22319
+ UNLINK: "data-admin",
22320
+ HDEL: "data-admin",
22321
+ FLUSHDB: "admin",
22322
+ FLUSHALL: "admin",
22323
+ CONFIG: "admin",
22324
+ INFO: "admin",
22325
+ CLIENT: "admin",
22326
+ DEBUG: "admin",
22327
+ SHUTDOWN: "admin",
22328
+ KEYS: "admin",
22329
+ MONITOR: "admin",
22330
+ SAVE: "admin",
22331
+ BGSAVE: "admin",
22332
+ BGREWRITEAOF: "admin",
22333
+ REPLICAOF: "admin",
22334
+ SLAVEOF: "admin",
22335
+ ACL: "admin"
22336
+ };
22176
22337
  });
22177
22338
 
22178
22339
  // src/commands/query-table-schema.ts
@@ -22469,7 +22630,7 @@ async function preflightQuery(query, options, context, fieldSelection, multiConn
22469
22630
  if (multiConnection) {
22470
22631
  throw new Error("Redis queries do not support multiple connections");
22471
22632
  }
22472
- const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
22633
+ const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
22473
22634
  enforceRedisPermission2(query, config.permission);
22474
22635
  return;
22475
22636
  }
@@ -22890,7 +23051,7 @@ var init_query = __esm(() => {
22890
23051
  init_field_projection();
22891
23052
  init_connection_selector();
22892
23053
  init_query_fanout();
22893
- init_permission_guard();
23054
+ init_elasticsearch();
22894
23055
  init_slow_query_advisory();
22895
23056
  ALLOWED_FORMATS3 = ["table", "json", "csv"];
22896
23057
  defaultQueryCommandRuntime = {
@@ -22949,7 +23110,7 @@ function registerQCommand(program2, run) {
22949
23110
  });
22950
23111
  }
22951
23112
  function registerInsertCommand(program2, run) {
22952
- program2.command("insert <table>").description(t("insert.description")).option("--data <json>", "JSON object to insert").option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format for --plan: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
23113
+ program2.command("insert <table>").description(t("insert.description")).option("--data <json>", "JSON object to insert").option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
22953
23114
  try {
22954
23115
  await run(table, options, command);
22955
23116
  } catch (error) {
@@ -22963,7 +23124,7 @@ function registerInsertCommand(program2, run) {
22963
23124
  });
22964
23125
  }
22965
23126
  function registerUpdateCommand(program2, run) {
22966
- program2.command("update <table>").description(t("update.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--set <json>", `JSON with fields to update (required, e.g. '{"name":"Bob"}')`).option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format for --plan: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
23127
+ program2.command("update <table>").description(t("update.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--set <json>", `JSON with fields to update (required, e.g. '{"name":"Bob"}')`).option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
22967
23128
  try {
22968
23129
  await run(table, options, command);
22969
23130
  } catch (error) {
@@ -22977,7 +23138,7 @@ function registerUpdateCommand(program2, run) {
22977
23138
  });
22978
23139
  }
22979
23140
  function registerDeleteCommand(program2, run) {
22980
- program2.command("delete <table>").description(t("delete.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format for --plan: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
23141
+ program2.command("delete <table>").description(t("delete.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
22981
23142
  try {
22982
23143
  await run(table, options, command);
22983
23144
  } catch (error) {
@@ -23698,7 +23859,7 @@ function deepHasScript(value) {
23698
23859
  return Object.values(obj).some(deepHasScript);
23699
23860
  }
23700
23861
  var NAME_RE2, ES_SIZE_CAP = 1000, ES_MAX_RESULT_WINDOW = 1e4, esStrategy;
23701
- var init_elasticsearch = __esm(() => {
23862
+ var init_elasticsearch2 = __esm(() => {
23702
23863
  init_types5();
23703
23864
  NAME_RE2 = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
23704
23865
  esStrategy = {
@@ -23796,7 +23957,7 @@ function applyRedisSizeGuard(command, noLimit) {
23796
23957
  return { command, warnings };
23797
23958
  }
23798
23959
  var REDIS_NAME_RE, REDIS_RANGE_CAP = 1000, RANGE_VERBS, SCAN_VERBS, REDIS_READONLY_VERBS, REDIS_HARD_REJECT, redisStrategy;
23799
- var init_redis = __esm(() => {
23960
+ var init_redis2 = __esm(() => {
23800
23961
  init_types5();
23801
23962
  REDIS_NAME_RE = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
23802
23963
  RANGE_VERBS = new Set(["LRANGE", "ZRANGE", "ZRANGEBYSCORE", "ZRANGEBYLEX"]);
@@ -23995,8 +24156,8 @@ function getStrategy(family) {
23995
24156
  }
23996
24157
  var init_strategies = __esm(() => {
23997
24158
  init_sql();
23998
- init_elasticsearch();
23999
- init_redis();
24159
+ init_elasticsearch2();
24160
+ init_redis2();
24000
24161
  init_mongodb();
24001
24162
  });
24002
24163
 
@@ -25228,8 +25389,8 @@ class DataExecutor {
25228
25389
  const timestamp = new Date().toISOString();
25229
25390
  try {
25230
25391
  this.checkBlacklist("INSERT", tableName);
25231
- enforcePermission("INSERT INTO dummy", this.permission);
25232
- return await this.executeMutation("insert", this.buildInsertSql(tableName, data, schema), timestamp, options, { prompt: "Proceed with this operation?" });
25392
+ enforcePermissionForType("INSERT", this.permission);
25393
+ return await this.executeMutation("insert", this.buildInsertSql(tableName, data, schema), timestamp, options, { destructive: false });
25233
25394
  } catch (error) {
25234
25395
  return this.handleMutationError("insert", timestamp, error);
25235
25396
  }
@@ -25238,8 +25399,8 @@ class DataExecutor {
25238
25399
  const timestamp = new Date().toISOString();
25239
25400
  try {
25240
25401
  this.checkBlacklist("UPDATE", tableName);
25241
- enforcePermission("UPDATE dummy", this.permission);
25242
- return await this.executeMutation("update", this.buildUpdateSql(tableName, data, where, schema), timestamp, options, { prompt: "Proceed with this operation?" });
25402
+ enforcePermissionForType("UPDATE", this.permission);
25403
+ return await this.executeMutation("update", this.buildUpdateSql(tableName, data, where, schema), timestamp, options, { destructive: false });
25243
25404
  } catch (error) {
25244
25405
  return this.handleMutationError("update", timestamp, error);
25245
25406
  }
@@ -25248,19 +25409,8 @@ class DataExecutor {
25248
25409
  const timestamp = new Date().toISOString();
25249
25410
  try {
25250
25411
  this.checkBlacklist("DELETE", tableName);
25251
- if (this.permission !== "data-admin" && this.permission !== "admin") {
25252
- return {
25253
- status: "error",
25254
- operation: "delete",
25255
- rows_affected: 0,
25256
- timestamp,
25257
- error: "Permission denied: DELETE operation requires Data-Admin or Admin permission."
25258
- };
25259
- }
25260
- return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, {
25261
- warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
25262
- prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone."
25263
- });
25412
+ enforcePermissionForType("DELETE", this.permission);
25413
+ return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, { destructive: true });
25264
25414
  } catch (error) {
25265
25415
  return this.handleMutationError("delete", timestamp, error);
25266
25416
  }
@@ -25274,31 +25424,31 @@ class DataExecutor {
25274
25424
  checkBlacklist(operation, tableName) {
25275
25425
  this.blacklistValidator?.checkTableBlacklist(operation, tableName, []);
25276
25426
  }
25277
- async executeMutation(operation, statement, timestamp, options, confirmation) {
25427
+ async executeMutation(operation, statement, timestamp, options, { destructive }) {
25278
25428
  if (options?.dryRun) {
25279
- return this.successResult(operation, 0, timestamp, statement.sql);
25429
+ return this.outcomeResult("dry_run", operation, 0, timestamp, statement.sql);
25280
25430
  }
25281
25431
  if (!options?.force) {
25282
- if (confirmation.warning) {
25283
- console.log(`
25284
- ${confirmation.warning}`);
25432
+ if (!options?.confirm) {
25433
+ throw new Error(`Refusing to ${operation} without confirmation: no confirmation handler was supplied. ` + "Pass options.confirm to ask the user, or options.force to proceed unattended.");
25285
25434
  }
25286
- console.log(`
25287
- Generated SQL:`);
25288
- console.log(` ${statement.sql}`);
25289
- console.log(`
25290
- Parameters:`);
25291
- console.log(` ${JSON.stringify(statement.params, null, 2)}`);
25292
- if (!await promptUser.confirm(confirmation.prompt)) {
25293
- return this.successResult(operation, 0, timestamp, statement.sql);
25435
+ const proceed = await options.confirm({
25436
+ operation,
25437
+ engine: "sql",
25438
+ sql: statement.sql,
25439
+ params: statement.params,
25440
+ destructive
25441
+ });
25442
+ if (!proceed) {
25443
+ return this.outcomeResult("cancelled", operation, 0, timestamp, statement.sql);
25294
25444
  }
25295
25445
  }
25296
25446
  const result = await this.adapter.execute(statement.sql, statement.params);
25297
- return this.successResult(operation, result.affectedRows, timestamp, statement.sql);
25447
+ return this.outcomeResult("success", operation, result.affectedRows, timestamp, statement.sql);
25298
25448
  }
25299
- successResult(operation, rowsAffected, timestamp, sql) {
25449
+ outcomeResult(status2, operation, rowsAffected, timestamp, sql) {
25300
25450
  return {
25301
- status: "success",
25451
+ status: status2,
25302
25452
  operation,
25303
25453
  rows_affected: rowsAffected,
25304
25454
  timestamp,
@@ -25309,13 +25459,13 @@ Parameters:`);
25309
25459
  if (error instanceof BlacklistError) {
25310
25460
  throw error;
25311
25461
  }
25312
- if (error instanceof PermissionError && operation !== "delete") {
25462
+ if (error instanceof PermissionError) {
25313
25463
  return {
25314
25464
  status: "error",
25315
25465
  operation,
25316
25466
  rows_affected: 0,
25317
25467
  timestamp,
25318
- error: `Permission denied: Query-only mode only allows SELECT. Use Read-Write or Admin mode to execute ${operation.toUpperCase()}.`
25468
+ error: t_vars("errors.permission_denied_reason", { reason: error.message })
25319
25469
  };
25320
25470
  }
25321
25471
  const errorMessage2 = error instanceof Error ? error.message : String(error);
@@ -25373,8 +25523,152 @@ Parameters:`);
25373
25523
  }
25374
25524
  var init_data_executor = __esm(() => {
25375
25525
  init_permission_guard();
25526
+ init_message_loader();
25527
+ init_blacklist();
25528
+ });
25529
+
25530
+ // src/commands/mutation-confirm.ts
25531
+ async function confirmDirectMutation(request) {
25532
+ if (request.force === true)
25533
+ return true;
25534
+ return confirmMutationInteractively({
25535
+ operation: request.operation,
25536
+ engine: request.engine,
25537
+ sql: request.preview,
25538
+ params: [],
25539
+ destructive: request.destructive
25540
+ });
25541
+ }
25542
+ var confirmMutationInteractively = async (request) => {
25543
+ if (request.destructive) {
25544
+ process.stderr.write(`
25545
+ ${t("ceremony.confirm_destructive_warning")}
25546
+ `);
25547
+ }
25548
+ const label = request.engine === "sql" ? "ceremony.confirm_sql" : "ceremony.confirm_command";
25549
+ process.stderr.write(`
25550
+ ${t(label)}
25551
+ ${request.sql}
25552
+ `);
25553
+ if (request.params.length > 0) {
25554
+ process.stderr.write(`
25555
+ ${t("ceremony.confirm_params")}
25556
+ ${JSON.stringify(request.params, null, 2)}
25557
+ `);
25558
+ }
25559
+ return promptUser.confirm(t(request.destructive ? "ceremony.confirm_destructive_prompt" : "ceremony.confirm_prompt"));
25560
+ }, confirmDdlInteractively = async (request) => {
25561
+ process.stderr.write(`
25562
+ ${t("ceremony.confirm_destructive_warning_ddl")}
25563
+ `);
25564
+ process.stderr.write(`
25565
+ ${t("ceremony.confirm_sql")}
25566
+ ${request.sql}
25567
+ `);
25568
+ return promptUser.confirm(t_vars("ceremony.confirm_ddl_prompt", { operation: request.operation }));
25569
+ };
25570
+ var init_mutation_confirm = __esm(() => {
25571
+ init_message_loader();
25376
25572
  init_prompts();
25573
+ });
25574
+
25575
+ // src/commands/mutation-audit.ts
25576
+ function auditOutcomeForMutation(result, table, recoveryRef) {
25577
+ return {
25578
+ success: result.status === "success" || result.status === "dry_run",
25579
+ target: table,
25580
+ ...recoveryRef && { recovery_ref: recoveryRef },
25581
+ ...result.sql && { sql: result.sql },
25582
+ metadata: {
25583
+ rows_affected: result.rows_affected,
25584
+ outcome: result.status,
25585
+ ...result.status === "dry_run" && { dry_run: true }
25586
+ },
25587
+ ...result.status === "error" && {
25588
+ error: new Error(result.error ?? `${result.operation} failed`)
25589
+ }
25590
+ };
25591
+ }
25592
+
25593
+ // src/commands/mutation-outcome.ts
25594
+ function shouldRenderForHuman(context) {
25595
+ if (context.format === "json")
25596
+ return false;
25597
+ return context.isTTY;
25598
+ }
25599
+ function humanOutputContext(options) {
25600
+ return { isTTY: process.stdout.isTTY === true, ...options.format && { format: options.format } };
25601
+ }
25602
+ function renderMutationOutcome(result, table, elapsedMs) {
25603
+ if (result.status === "error") {
25604
+ return [
25605
+ t_vars("ceremony.failed", { reason: result.error ?? t("ceremony.unknown_error") }),
25606
+ t("ceremony.recovery_retry")
25607
+ ];
25608
+ }
25609
+ if (result.status === "cancelled") {
25610
+ return [t_vars("ceremony.cancelled", { table })];
25611
+ }
25612
+ if (result.status === "dry_run") {
25613
+ return [t_vars("ceremony.dry_run", { table })];
25614
+ }
25615
+ const elapsed = t_vars("ceremony.elapsed", { seconds: (elapsedMs / 1000).toFixed(2) });
25616
+ if (result.rows_affected === 0) {
25617
+ return [t_vars("ceremony.matched_nothing", { table }), elapsed];
25618
+ }
25619
+ return [
25620
+ t_vars(VERB_KEY[result.operation], { count: result.rows_affected, table }),
25621
+ elapsed,
25622
+ t("ceremony.recovery_none")
25623
+ ];
25624
+ }
25625
+ function printMutationOutcome(result, table, elapsedMs, context, envelopeExtras = {}) {
25626
+ if (shouldRenderForHuman(context)) {
25627
+ const writeLine = result.status === "error" ? console.error : console.log;
25628
+ for (const line of renderMutationOutcome(result, table, elapsedMs))
25629
+ writeLine(line);
25630
+ return;
25631
+ }
25632
+ console.log(JSON.stringify({ ...mutationEnvelope(result), ...envelopeExtras }, null, 2));
25633
+ }
25634
+ function printMutationFailure(error, operation, context) {
25635
+ if (shouldRenderForHuman(context)) {
25636
+ console.error(t_vars("ceremony.failed", { reason: error.message }));
25637
+ if (error instanceof BlacklistError) {
25638
+ console.error(t("ceremony.blacklist_hint"));
25639
+ }
25640
+ return;
25641
+ }
25642
+ console.log(JSON.stringify({ status: "error", operation, rows_affected: 0, error: error.message }, null, 2));
25643
+ }
25644
+ function cancelledOutcome(operation, preview) {
25645
+ return {
25646
+ status: "cancelled",
25647
+ operation,
25648
+ rows_affected: 0,
25649
+ sql: preview,
25650
+ timestamp: new Date().toISOString()
25651
+ };
25652
+ }
25653
+ function mutationEnvelope(result) {
25654
+ return {
25655
+ status: result.status,
25656
+ operation: result.operation,
25657
+ rows_affected: result.rows_affected,
25658
+ ...result.timestamp && { timestamp: result.timestamp },
25659
+ ...result.sql && { sql: result.sql },
25660
+ ...result.error && { error: result.error }
25661
+ };
25662
+ }
25663
+ var VERB_KEY;
25664
+ var init_mutation_outcome = __esm(() => {
25665
+ init_message_loader();
25377
25666
  init_blacklist();
25667
+ VERB_KEY = {
25668
+ insert: "ceremony.inserted",
25669
+ update: "ceremony.updated",
25670
+ delete: "ceremony.deleted"
25671
+ };
25378
25672
  });
25379
25673
 
25380
25674
  // src/core/mongo/dry-run-formatter.ts
@@ -26218,6 +26512,9 @@ async function insertCommand(table, options, command) {
26218
26512
  throw new Error("Table name required");
26219
26513
  }
26220
26514
  table = table.trim();
26515
+ if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
26516
+ throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
26517
+ }
26221
26518
  let jsonInput = "";
26222
26519
  if (options.data) {
26223
26520
  jsonInput = options.data;
@@ -26258,30 +26555,40 @@ async function insertCommand(table, options, command) {
26258
26555
  return;
26259
26556
  }
26260
26557
  if (config.connection.system === "redis") {
26261
- enforcePermission("INSERT INTO dummy", config.permission);
26558
+ enforcePermissionForType("INSERT", config.permission);
26262
26559
  const blacklistManager = new BlacklistManager(config);
26263
26560
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26264
26561
  blacklistValidator.checkTableBlacklist("INSERT", table, []);
26265
26562
  blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
26563
+ const preview = `SET ${table} ... (Redis Insert)`;
26266
26564
  if (options.dryRun) {
26267
26565
  const output = {
26268
- status: "success",
26566
+ status: "dry_run",
26269
26567
  operation: "insert",
26270
26568
  rows_affected: 0,
26271
- sql: `SET ${table} ... (Redis Insert)`,
26569
+ sql: preview,
26272
26570
  timestamp: new Date().toISOString()
26273
26571
  };
26274
- console.log(JSON.stringify(output, null, 2));
26275
- await writeAuditEntry(config, "insert", options, {
26276
- success: true,
26277
- target: table,
26278
- metadata: { rows_affected: 0, dry_run: true }
26279
- });
26572
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26573
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26574
+ return;
26575
+ }
26576
+ if (!await confirmDirectMutation({
26577
+ operation: "insert",
26578
+ engine: "redis",
26579
+ preview,
26580
+ destructive: false,
26581
+ force: options.force
26582
+ })) {
26583
+ const output = cancelledOutcome("insert", preview);
26584
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26585
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26280
26586
  return;
26281
26587
  }
26282
26588
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26283
26589
  await adapter2.connect();
26284
26590
  try {
26591
+ const startedAt = performance.now();
26285
26592
  const result = await adapter2.insert(table, data);
26286
26593
  const output = {
26287
26594
  status: "success",
@@ -26289,12 +26596,8 @@ async function insertCommand(table, options, command) {
26289
26596
  rows_affected: result.affectedRows,
26290
26597
  timestamp: new Date().toISOString()
26291
26598
  };
26292
- console.log(JSON.stringify(output, null, 2));
26293
- await writeAuditEntry(config, "insert", options, {
26294
- success: true,
26295
- target: table,
26296
- metadata: { rows_affected: result.affectedRows }
26297
- });
26599
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
26600
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26298
26601
  return;
26299
26602
  } finally {
26300
26603
  await adapter2.disconnect();
@@ -26306,7 +26609,7 @@ async function insertCommand(table, options, command) {
26306
26609
  operation: "insert",
26307
26610
  rows_affected: 0,
26308
26611
  timestamp: new Date().toISOString(),
26309
- error: "Elasticsearch \u4E0D\u652F\u63F4 insert \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u5BEB\u5165"
26612
+ error: t("insert.elasticsearch_unsupported")
26310
26613
  };
26311
26614
  await writeAuditEntry(config, "insert", options, {
26312
26615
  success: false,
@@ -26317,44 +26620,49 @@ async function insertCommand(table, options, command) {
26317
26620
  process.exit(1);
26318
26621
  }
26319
26622
  if (config.connection.system === "mongodb") {
26320
- enforcePermission("INSERT INTO dummy", config.permission);
26623
+ enforcePermissionForType("INSERT", config.permission);
26321
26624
  const blacklistManager = new BlacklistManager(config);
26322
26625
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26323
26626
  blacklistValidator.checkTableBlacklist("INSERT", table, []);
26324
26627
  blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
26628
+ const preview = previewInsert(table, data);
26325
26629
  if (options.dryRun) {
26326
26630
  const output = {
26327
- status: "success",
26631
+ status: "dry_run",
26328
26632
  operation: "insert",
26329
26633
  rows_affected: 0,
26330
- sql: previewInsert(table, data),
26634
+ sql: preview,
26331
26635
  timestamp: new Date().toISOString()
26332
26636
  };
26333
- console.log(JSON.stringify(output, null, 2));
26334
- await writeAuditEntry(config, "insert", options, {
26335
- success: true,
26336
- target: table,
26337
- metadata: { rows_affected: 0, dry_run: true }
26338
- });
26637
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26638
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26639
+ return;
26640
+ }
26641
+ if (!await confirmDirectMutation({
26642
+ operation: "insert",
26643
+ engine: "mongodb",
26644
+ preview,
26645
+ destructive: false,
26646
+ force: options.force
26647
+ })) {
26648
+ const output = cancelledOutcome("insert", preview);
26649
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26650
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26339
26651
  return;
26340
26652
  }
26341
26653
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26342
26654
  await adapter2.connect();
26343
26655
  try {
26656
+ const startedAt = performance.now();
26344
26657
  const result = await adapter2.insert(table, data);
26345
26658
  const output = {
26346
26659
  status: "success",
26347
26660
  operation: "insert",
26348
26661
  rows_affected: result.affectedRows,
26349
- timestamp: new Date().toISOString(),
26350
- lastInsertId: result.lastInsertId
26662
+ timestamp: new Date().toISOString()
26351
26663
  };
26352
- console.log(JSON.stringify(output, null, 2));
26353
- await writeAuditEntry(config, "insert", options, {
26354
- success: true,
26355
- target: table,
26356
- metadata: { rows_affected: result.affectedRows }
26357
- });
26664
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options), { lastInsertId: result.lastInsertId });
26665
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26358
26666
  return;
26359
26667
  } finally {
26360
26668
  await adapter2.disconnect();
@@ -26368,31 +26676,20 @@ async function insertCommand(table, options, command) {
26368
26676
  const blacklistManager = new BlacklistManager(config);
26369
26677
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26370
26678
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
26679
+ const startedAt = performance.now();
26371
26680
  const result = await executor3.executeInsert(table, data, schema, {
26372
26681
  dryRun: options.dryRun,
26373
- force: options.force
26374
- });
26375
- const output = {
26376
- status: result.status,
26377
- operation: result.operation,
26378
- rows_affected: result.rows_affected,
26379
- timestamp: result.timestamp,
26380
- ...result.sql && { sql: result.sql },
26381
- ...result.error && { error: result.error }
26382
- };
26383
- console.log(JSON.stringify(output, null, 2));
26384
- await writeAuditEntry(config, "insert", options, {
26385
- success: result.status === "success",
26386
- target: table,
26387
- ...result.sql && { sql: result.sql },
26388
- metadata: {
26389
- rows_affected: result.rows_affected,
26390
- ...options.dryRun && { dry_run: true }
26391
- },
26392
- ...result.status === "error" && {
26393
- error: new Error(result.error ?? "insert failed")
26394
- }
26682
+ force: options.force,
26683
+ confirm: confirmMutationInteractively
26395
26684
  });
26685
+ const elapsedMs = performance.now() - startedAt;
26686
+ const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto5.randomUUID() : undefined;
26687
+ const auditId = await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
26688
+ if (recoveryEnvelopeId !== undefined) {
26689
+ const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
26690
+ emitRecoveryEnvelope2(new Error(result.error ?? "INSERT failed"), { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
26691
+ }
26692
+ printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
26396
26693
  if (result.status === "error") {
26397
26694
  process.exit(1);
26398
26695
  }
@@ -26418,13 +26715,7 @@ async function insertCommand(table, options, command) {
26418
26715
  emitRecoveryEnvelope2(error, { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId, auditRef: auditId ?? undefined });
26419
26716
  }
26420
26717
  if (error instanceof BlacklistError) {
26421
- const output2 = {
26422
- status: "error",
26423
- operation: "insert",
26424
- rows_affected: 0,
26425
- error: error.message
26426
- };
26427
- console.log(JSON.stringify(output2, null, 2));
26718
+ printMutationFailure(error, "insert", humanOutputContext(options));
26428
26719
  process.exit(1);
26429
26720
  }
26430
26721
  if (error instanceof PermissionError) {
@@ -26437,13 +26728,7 @@ async function insertCommand(table, options, command) {
26437
26728
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
26438
26729
  process.exit(1);
26439
26730
  }
26440
- const output = {
26441
- status: "error",
26442
- operation: "insert",
26443
- rows_affected: 0,
26444
- error: error.message
26445
- };
26446
- console.log(JSON.stringify(output, null, 2));
26731
+ printMutationFailure(error, "insert", humanOutputContext(options));
26447
26732
  process.exit(1);
26448
26733
  }
26449
26734
  }
@@ -26453,6 +26738,8 @@ var init_insert = __esm(() => {
26453
26738
  init_connection_error_message();
26454
26739
  init_adapters();
26455
26740
  init_data_executor();
26741
+ init_mutation_confirm();
26742
+ init_mutation_outcome();
26456
26743
  init_config();
26457
26744
  init_permission_guard();
26458
26745
  init_blacklist_validator();
@@ -26481,6 +26768,9 @@ async function updateCommand(table, options, command) {
26481
26768
  throw new Error("Table name required");
26482
26769
  }
26483
26770
  table = table.trim();
26771
+ if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
26772
+ throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
26773
+ }
26484
26774
  if (!options.plan && (!options.where || options.where.trim() === "")) {
26485
26775
  throw new Error('UPDATE requires --where clause (e.g. --where "id=1")');
26486
26776
  }
@@ -26541,29 +26831,39 @@ async function updateCommand(table, options, command) {
26541
26831
  return;
26542
26832
  }
26543
26833
  if (config.connection?.system === "redis") {
26544
- enforcePermission("UPDATE dummy", config.permission);
26834
+ enforcePermissionForType("UPDATE", config.permission);
26545
26835
  const blacklistManager = new BlacklistManager(config);
26546
26836
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26547
26837
  blacklistValidator.checkTableBlacklist("UPDATE", table, []);
26838
+ const preview = `HSET ${table} ... (Redis Update)`;
26548
26839
  if (options.dryRun) {
26549
26840
  const output = {
26550
- status: "success",
26841
+ status: "dry_run",
26551
26842
  operation: "update",
26552
26843
  rows_affected: 0,
26553
- sql: `HSET ${table} ... (Redis Update)`,
26844
+ sql: preview,
26554
26845
  timestamp: new Date().toISOString()
26555
26846
  };
26556
- console.log(JSON.stringify(output, null, 2));
26557
- await writeAuditEntry(config, "update", options, {
26558
- success: true,
26559
- target: table,
26560
- metadata: { rows_affected: 0, dry_run: true }
26561
- });
26847
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26848
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26849
+ return;
26850
+ }
26851
+ if (!await confirmDirectMutation({
26852
+ operation: "update",
26853
+ engine: "redis",
26854
+ preview,
26855
+ destructive: false,
26856
+ force: options.force
26857
+ })) {
26858
+ const output = cancelledOutcome("update", preview);
26859
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26860
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26562
26861
  return;
26563
26862
  }
26564
26863
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26565
26864
  await adapter2.connect();
26566
26865
  try {
26866
+ const startedAt = performance.now();
26567
26867
  const result = await adapter2.update(table, {}, setData);
26568
26868
  const output = {
26569
26869
  status: "success",
@@ -26571,12 +26871,8 @@ async function updateCommand(table, options, command) {
26571
26871
  rows_affected: result.affectedRows,
26572
26872
  timestamp: new Date().toISOString()
26573
26873
  };
26574
- console.log(JSON.stringify(output, null, 2));
26575
- await writeAuditEntry(config, "update", options, {
26576
- success: true,
26577
- target: table,
26578
- metadata: { rows_affected: result.affectedRows }
26579
- });
26874
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
26875
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26580
26876
  return;
26581
26877
  } finally {
26582
26878
  await adapter2.disconnect();
@@ -26588,7 +26884,7 @@ async function updateCommand(table, options, command) {
26588
26884
  operation: "update",
26589
26885
  rows_affected: 0,
26590
26886
  timestamp: new Date().toISOString(),
26591
- error: "Elasticsearch \u4E0D\u652F\u63F4 update \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u66F4\u65B0"
26887
+ error: t("update.elasticsearch_unsupported")
26592
26888
  };
26593
26889
  await writeAuditEntry(config, "update", options, {
26594
26890
  success: false,
@@ -26599,7 +26895,7 @@ async function updateCommand(table, options, command) {
26599
26895
  process.exit(1);
26600
26896
  }
26601
26897
  if (config.connection?.system === "mongodb") {
26602
- enforcePermission("UPDATE dummy", config.permission);
26898
+ enforcePermissionForType("UPDATE", config.permission);
26603
26899
  const hasOperator = Object.keys(setData).some((key) => key.startsWith("$"));
26604
26900
  const updateDoc = hasOperator ? setData : { $set: setData };
26605
26901
  const writtenFields = new Set;
@@ -26625,25 +26921,35 @@ async function updateCommand(table, options, command) {
26625
26921
  } catch {
26626
26922
  filter = parseWhereClause(options.where);
26627
26923
  }
26924
+ const preview = previewUpdate(table, filter, updateDoc);
26628
26925
  if (options.dryRun) {
26629
26926
  const output = {
26630
- status: "success",
26927
+ status: "dry_run",
26631
26928
  operation: "update",
26632
26929
  rows_affected: 0,
26633
- sql: previewUpdate(table, filter, updateDoc),
26930
+ sql: preview,
26634
26931
  timestamp: new Date().toISOString()
26635
26932
  };
26636
- console.log(JSON.stringify(output, null, 2));
26637
- await writeAuditEntry(config, "update", options, {
26638
- success: true,
26639
- target: table,
26640
- metadata: { rows_affected: 0, dry_run: true }
26641
- });
26933
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26934
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26935
+ return;
26936
+ }
26937
+ if (!await confirmDirectMutation({
26938
+ operation: "update",
26939
+ engine: "mongodb",
26940
+ preview,
26941
+ destructive: false,
26942
+ force: options.force
26943
+ })) {
26944
+ const output = cancelledOutcome("update", preview);
26945
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26946
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26642
26947
  return;
26643
26948
  }
26644
26949
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26645
26950
  await adapter2.connect();
26646
26951
  try {
26952
+ const startedAt = performance.now();
26647
26953
  const result = await adapter2.update(table, filter, updateDoc);
26648
26954
  const output = {
26649
26955
  status: "success",
@@ -26651,12 +26957,8 @@ async function updateCommand(table, options, command) {
26651
26957
  rows_affected: result.affectedRows,
26652
26958
  timestamp: new Date().toISOString()
26653
26959
  };
26654
- console.log(JSON.stringify(output, null, 2));
26655
- await writeAuditEntry(config, "update", options, {
26656
- success: true,
26657
- target: table,
26658
- metadata: { rows_affected: result.affectedRows }
26659
- });
26960
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
26961
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26660
26962
  return;
26661
26963
  } finally {
26662
26964
  await adapter2.disconnect();
@@ -26676,31 +26978,20 @@ async function updateCommand(table, options, command) {
26676
26978
  const blacklistManager = new BlacklistManager(config);
26677
26979
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26678
26980
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
26981
+ const startedAt = performance.now();
26679
26982
  const result = await executor3.executeUpdate(table, setData, whereConditions, schema, {
26680
26983
  dryRun: options.dryRun,
26681
- force: options.force
26682
- });
26683
- const output = {
26684
- status: result.status,
26685
- operation: result.operation,
26686
- rows_affected: result.rows_affected,
26687
- timestamp: result.timestamp,
26688
- ...result.sql && { sql: result.sql },
26689
- ...result.error && { error: result.error }
26690
- };
26691
- console.log(JSON.stringify(output, null, 2));
26692
- await writeAuditEntry(config, "update", options, {
26693
- success: result.status === "success",
26694
- target: table,
26695
- ...result.sql && { sql: result.sql },
26696
- metadata: {
26697
- rows_affected: result.rows_affected,
26698
- ...options.dryRun && { dry_run: true }
26699
- },
26700
- ...result.status === "error" && {
26701
- error: new Error(result.error ?? "update failed")
26702
- }
26984
+ force: options.force,
26985
+ confirm: confirmMutationInteractively
26703
26986
  });
26987
+ const elapsedMs = performance.now() - startedAt;
26988
+ const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto6.randomUUID() : undefined;
26989
+ const auditId = await writeAuditEntry(config, "update", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
26990
+ if (recoveryEnvelopeId !== undefined) {
26991
+ const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
26992
+ emitRecoveryEnvelope2(new Error(result.error ?? "UPDATE failed"), { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
26993
+ }
26994
+ printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
26704
26995
  if (result.status === "error") {
26705
26996
  process.exit(1);
26706
26997
  }
@@ -26726,13 +27017,7 @@ async function updateCommand(table, options, command) {
26726
27017
  emitRecoveryEnvelope2(error, { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId, auditRef: auditId ?? undefined });
26727
27018
  }
26728
27019
  if (error instanceof BlacklistError) {
26729
- const output2 = {
26730
- status: "error",
26731
- operation: "update",
26732
- rows_affected: 0,
26733
- error: error.message
26734
- };
26735
- console.log(JSON.stringify(output2, null, 2));
27020
+ printMutationFailure(error, "update", humanOutputContext(options));
26736
27021
  process.exit(1);
26737
27022
  }
26738
27023
  if (error instanceof PermissionError) {
@@ -26745,13 +27030,7 @@ async function updateCommand(table, options, command) {
26745
27030
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
26746
27031
  process.exit(1);
26747
27032
  }
26748
- const output = {
26749
- status: "error",
26750
- operation: "update",
26751
- rows_affected: 0,
26752
- error: error.message
26753
- };
26754
- console.log(JSON.stringify(output, null, 2));
27033
+ printMutationFailure(error, "update", humanOutputContext(options));
26755
27034
  process.exit(1);
26756
27035
  }
26757
27036
  }
@@ -26761,6 +27040,8 @@ var init_update = __esm(() => {
26761
27040
  init_connection_error_message();
26762
27041
  init_adapters();
26763
27042
  init_data_executor();
27043
+ init_mutation_confirm();
27044
+ init_mutation_outcome();
26764
27045
  init_config();
26765
27046
  init_permission_guard();
26766
27047
  init_blacklist_validator();
@@ -26789,6 +27070,9 @@ async function deleteCommand(table, options, command) {
26789
27070
  throw new Error("Table name required");
26790
27071
  }
26791
27072
  table = table.trim();
27073
+ if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
27074
+ throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
27075
+ }
26792
27076
  if (!options.plan && (!options.where || options.where.trim() === "")) {
26793
27077
  throw new Error('DELETE requires --where clause (e.g. --where "id=1")');
26794
27078
  }
@@ -26835,14 +27119,8 @@ async function deleteCommand(table, options, command) {
26835
27119
  });
26836
27120
  return;
26837
27121
  }
26838
- if (config.permission !== "data-admin" && config.permission !== "admin") {
26839
- throw new PermissionError(t("delete.admin_only"), {
26840
- type: "DELETE",
26841
- isDangerous: true,
26842
- keywords: ["DELETE"],
26843
- isComposite: false,
26844
- confidence: "HIGH"
26845
- }, config.permission);
27122
+ if (!permitsOperation("DELETE", config.permission)) {
27123
+ throw new PermissionError(t("delete.admin_only"), classificationForType("DELETE"), minimumPermissionFor("DELETE"));
26846
27124
  }
26847
27125
  if (config.connection?.system === "redis") {
26848
27126
  const blacklistManager = new BlacklistManager(config);
@@ -26854,25 +27132,35 @@ async function deleteCommand(table, options, command) {
26854
27132
  } catch {
26855
27133
  filter = parseWhereClause(options.where);
26856
27134
  }
27135
+ const preview = `DEL ${table} (Redis Delete)`;
26857
27136
  if (options.dryRun) {
26858
27137
  const output = {
26859
- status: "success",
27138
+ status: "dry_run",
26860
27139
  operation: "delete",
26861
27140
  rows_affected: 0,
26862
- sql: `DEL ${table} (Redis Delete)`,
27141
+ sql: preview,
26863
27142
  timestamp: new Date().toISOString()
26864
27143
  };
26865
- console.log(JSON.stringify(output, null, 2));
26866
- await writeAuditEntry(config, "delete", options, {
26867
- success: true,
26868
- target: table,
26869
- metadata: { rows_affected: 0, dry_run: true }
26870
- });
27144
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27145
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
27146
+ return;
27147
+ }
27148
+ if (!await confirmDirectMutation({
27149
+ operation: "delete",
27150
+ engine: "redis",
27151
+ preview,
27152
+ destructive: true,
27153
+ force: options.force
27154
+ })) {
27155
+ const output = cancelledOutcome("delete", preview);
27156
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27157
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26871
27158
  return;
26872
27159
  }
26873
27160
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26874
27161
  await adapter2.connect();
26875
27162
  try {
27163
+ const startedAt = performance.now();
26876
27164
  const result = await adapter2.delete(table, filter);
26877
27165
  const output = {
26878
27166
  status: "success",
@@ -26880,12 +27168,8 @@ async function deleteCommand(table, options, command) {
26880
27168
  rows_affected: result.affectedRows,
26881
27169
  timestamp: new Date().toISOString()
26882
27170
  };
26883
- console.log(JSON.stringify(output, null, 2));
26884
- await writeAuditEntry(config, "delete", options, {
26885
- success: true,
26886
- target: table,
26887
- metadata: { rows_affected: result.affectedRows }
26888
- });
27171
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27172
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26889
27173
  return;
26890
27174
  } finally {
26891
27175
  await adapter2.disconnect();
@@ -26897,7 +27181,7 @@ async function deleteCommand(table, options, command) {
26897
27181
  operation: "delete",
26898
27182
  rows_affected: 0,
26899
27183
  timestamp: new Date().toISOString(),
26900
- error: "Elasticsearch \u4E0D\u652F\u63F4 delete \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u522A\u9664"
27184
+ error: t("delete.elasticsearch_unsupported")
26901
27185
  };
26902
27186
  await writeAuditEntry(config, "delete", options, {
26903
27187
  success: false,
@@ -26917,25 +27201,35 @@ async function deleteCommand(table, options, command) {
26917
27201
  } catch {
26918
27202
  filter = parseWhereClause(options.where);
26919
27203
  }
27204
+ const preview = previewDelete(table, filter);
26920
27205
  if (options.dryRun) {
26921
27206
  const output = {
26922
- status: "success",
27207
+ status: "dry_run",
26923
27208
  operation: "delete",
26924
27209
  rows_affected: 0,
26925
- sql: previewDelete(table, filter),
27210
+ sql: preview,
26926
27211
  timestamp: new Date().toISOString()
26927
27212
  };
26928
- console.log(JSON.stringify(output, null, 2));
26929
- await writeAuditEntry(config, "delete", options, {
26930
- success: true,
26931
- target: table,
26932
- metadata: { rows_affected: 0, dry_run: true }
26933
- });
27213
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27214
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
27215
+ return;
27216
+ }
27217
+ if (!await confirmDirectMutation({
27218
+ operation: "delete",
27219
+ engine: "mongodb",
27220
+ preview,
27221
+ destructive: true,
27222
+ force: options.force
27223
+ })) {
27224
+ const output = cancelledOutcome("delete", preview);
27225
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27226
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26934
27227
  return;
26935
27228
  }
26936
27229
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26937
27230
  await adapter2.connect();
26938
27231
  try {
27232
+ const startedAt = performance.now();
26939
27233
  const result = await adapter2.delete(table, filter);
26940
27234
  const output = {
26941
27235
  status: "success",
@@ -26943,12 +27237,8 @@ async function deleteCommand(table, options, command) {
26943
27237
  rows_affected: result.affectedRows,
26944
27238
  timestamp: new Date().toISOString()
26945
27239
  };
26946
- console.log(JSON.stringify(output, null, 2));
26947
- await writeAuditEntry(config, "delete", options, {
26948
- success: true,
26949
- target: table,
26950
- metadata: { rows_affected: result.affectedRows }
26951
- });
27240
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27241
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26952
27242
  return;
26953
27243
  } finally {
26954
27244
  await adapter2.disconnect();
@@ -26968,31 +27258,20 @@ async function deleteCommand(table, options, command) {
26968
27258
  const blacklistManager = new BlacklistManager(config);
26969
27259
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26970
27260
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
27261
+ const startedAt = performance.now();
26971
27262
  const result = await executor3.executeDelete(table, whereConditions, schema, {
26972
27263
  dryRun: options.dryRun,
26973
- force: options.force
26974
- });
26975
- const output = {
26976
- status: result.status,
26977
- operation: result.operation,
26978
- rows_affected: result.rows_affected,
26979
- timestamp: result.timestamp,
26980
- ...result.sql && { sql: result.sql },
26981
- ...result.error && { error: result.error }
26982
- };
26983
- console.log(JSON.stringify(output, null, 2));
26984
- await writeAuditEntry(config, "delete", options, {
26985
- success: result.status === "success",
26986
- target: table,
26987
- ...result.sql && { sql: result.sql },
26988
- metadata: {
26989
- rows_affected: result.rows_affected,
26990
- ...options.dryRun && { dry_run: true }
26991
- },
26992
- ...result.status === "error" && {
26993
- error: new Error(result.error ?? "delete failed")
26994
- }
27264
+ force: options.force,
27265
+ confirm: confirmMutationInteractively
26995
27266
  });
27267
+ const elapsedMs = performance.now() - startedAt;
27268
+ const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto7.randomUUID() : undefined;
27269
+ const auditId = await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
27270
+ if (recoveryEnvelopeId !== undefined) {
27271
+ const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
27272
+ emitRecoveryEnvelope2(new Error(result.error ?? "DELETE failed"), { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
27273
+ }
27274
+ printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
26996
27275
  if (result.status === "error") {
26997
27276
  process.exit(1);
26998
27277
  }
@@ -27018,17 +27297,11 @@ async function deleteCommand(table, options, command) {
27018
27297
  emitRecoveryEnvelope2(error, { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId, auditRef: auditId ?? undefined });
27019
27298
  }
27020
27299
  if (error instanceof BlacklistError) {
27021
- const output2 = {
27022
- status: "error",
27023
- operation: "delete",
27024
- rows_affected: 0,
27025
- error: error.message
27026
- };
27027
- console.log(JSON.stringify(output2, null, 2));
27300
+ printMutationFailure(error, "delete", humanOutputContext(options));
27028
27301
  process.exit(1);
27029
27302
  }
27030
27303
  if (error instanceof PermissionError) {
27031
- console.error(t_vars("errors.permission_denied", { required: "data-admin" }));
27304
+ console.error(t_vars("errors.permission_denied", { required: error.requiredPermission }));
27032
27305
  console.error(` Operation: ${error.classification.type}`);
27033
27306
  console.error(` Message: ${error.message}`);
27034
27307
  process.exit(1);
@@ -27037,13 +27310,7 @@ async function deleteCommand(table, options, command) {
27037
27310
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
27038
27311
  process.exit(1);
27039
27312
  }
27040
- const output = {
27041
- status: "error",
27042
- operation: "delete",
27043
- rows_affected: 0,
27044
- error: error.message
27045
- };
27046
- console.log(JSON.stringify(output, null, 2));
27313
+ printMutationFailure(error, "delete", humanOutputContext(options));
27047
27314
  process.exit(1);
27048
27315
  }
27049
27316
  }
@@ -27053,6 +27320,8 @@ var init_delete = __esm(() => {
27053
27320
  init_connection_error_message();
27054
27321
  init_adapters();
27055
27322
  init_data_executor();
27323
+ init_mutation_confirm();
27324
+ init_mutation_outcome();
27056
27325
  init_config();
27057
27326
  init_permission_guard();
27058
27327
  init_blacklist_validator();
@@ -27216,7 +27485,7 @@ async function exportCommand(sql, options, command) {
27216
27485
  }
27217
27486
  }
27218
27487
  async function redisExportBranch(command, options, config) {
27219
- const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
27488
+ const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
27220
27489
  enforceRedisPermission2(command, config.permission);
27221
27490
  const redisAdapter = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? [], config.redis?.mask ?? []);
27222
27491
  await redisAdapter.connect();
@@ -44092,6 +44361,7 @@ var init_repl_engine = __esm(() => {
44092
44361
  init_meta_commands();
44093
44362
  init_history_manager();
44094
44363
  init_permission_guard();
44364
+ init_redis();
44095
44365
  init_query_result_formatter();
44096
44366
  init_message_loader();
44097
44367
  init_sql_tables();
@@ -45220,13 +45490,16 @@ class DDLExecutor {
45220
45490
  };
45221
45491
  }
45222
45492
  if (isDestructiveOperation2(operation) && !options.force) {
45223
- const confirmed = await promptUser.confirm(`This is a destructive operation (${operation.kind}). Proceed?`);
45493
+ if (!options.confirm) {
45494
+ throw new DDLExecutionError(`Refusing to run ${operation.kind} without confirmation: no confirmation handler was ` + "supplied. Pass options.confirm to ask the user, or options.force to proceed unattended.");
45495
+ }
45496
+ const confirmed = await options.confirm({ operation: operation.kind, sql });
45224
45497
  if (!confirmed) {
45225
45498
  return {
45226
- status: "success",
45499
+ status: "cancelled",
45227
45500
  operation: operation.kind,
45228
45501
  sql,
45229
- warnings: [...warnings, "Operation cancelled by user"],
45502
+ warnings,
45230
45503
  timestamp: timestamp2,
45231
45504
  dryRun: false
45232
45505
  };
@@ -45301,8 +45574,15 @@ class DDLExecutor {
45301
45574
  } catch {}
45302
45575
  }
45303
45576
  }
45577
+ var DDLExecutionError;
45304
45578
  var init_ddl_executor = __esm(() => {
45305
- init_prompts();
45579
+ DDLExecutionError = class DDLExecutionError extends Error {
45580
+ constructor(message) {
45581
+ super(message);
45582
+ this.name = "DDLExecutionError";
45583
+ Object.setPrototypeOf(this, DDLExecutionError.prototype);
45584
+ }
45585
+ };
45306
45586
  });
45307
45587
 
45308
45588
  // src/commands/migrate.ts
@@ -45346,7 +45626,8 @@ async function runDDL(operation, opts, command) {
45346
45626
  const executor3 = new DDLExecutor(adapter, generator, config.permission, blacklistManager);
45347
45627
  const result = await executor3.execute(operation, {
45348
45628
  execute: opts.execute,
45349
- force: opts.force
45629
+ force: opts.force,
45630
+ confirm: confirmDdlInteractively
45350
45631
  });
45351
45632
  console.log(JSON.stringify({
45352
45633
  status: result.status,
@@ -45388,6 +45669,7 @@ var init_migrate = __esm(() => {
45388
45669
  init_adapters();
45389
45670
  init_ddl2();
45390
45671
  init_ddl_executor();
45672
+ init_mutation_confirm();
45391
45673
  init_config_path();
45392
45674
  migrateCommand = new Command("migrate").description(t("migrate.description"));
45393
45675
  addExecOpts(migrateCommand.command("create <table>").description(t("migrate.create_description")).option("--column <spec...>", 'Column definitions (e.g., "id:serial:pk" "name:varchar(50):not-null")')).action(async (table, opts, command) => {
@@ -45685,6 +45967,258 @@ var init_use = __esm(() => {
45685
45967
  });
45686
45968
  });
45687
45969
 
45970
+ // src/core/config-v2-mutations.ts
45971
+ function envVarNameFor(connName, field) {
45972
+ const slug = connName.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
45973
+ return `DBCLI_${slug}_${field.toUpperCase()}`;
45974
+ }
45975
+ var init_config_v2_mutations = () => {};
45976
+
45977
+ // src/core/env-file-writer.ts
45978
+ import { chmod as chmod2, mkdir as mkdir18, readFile as readFile9, writeFile as writeFile9 } from "fs/promises";
45979
+ import { dirname as dirname16 } from "path";
45980
+ function escapeForRegExp(value) {
45981
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
45982
+ }
45983
+ async function upsertEnvVar(envPath, varName, value) {
45984
+ let content = "";
45985
+ try {
45986
+ content = await readFile9(envPath, "utf8");
45987
+ } catch (cause) {
45988
+ const code = cause.code;
45989
+ if (code !== "ENOENT")
45990
+ throw cause;
45991
+ await mkdir18(dirname16(envPath), { recursive: true });
45992
+ }
45993
+ const line = `${varName}="${value}"`;
45994
+ const existing = new RegExp(`^${escapeForRegExp(varName)}=.*$`, "m");
45995
+ if (existing.test(content)) {
45996
+ content = content.replace(existing, () => line);
45997
+ } else {
45998
+ content = content.length && !content.endsWith(`
45999
+ `) ? `${content}
46000
+ ${line}
46001
+ ` : `${content}${line}
46002
+ `;
46003
+ }
46004
+ await writeFile9(envPath, content, { mode: SECRET_FILE_MODE });
46005
+ await chmod2(envPath, SECRET_FILE_MODE);
46006
+ }
46007
+ var SECRET_FILE_MODE = 384;
46008
+ var init_env_file_writer = () => {};
46009
+
46010
+ // src/core/connection-credential.ts
46011
+ import { join as join39 } from "path";
46012
+ async function readRawConfig(storagePath) {
46013
+ const configPath = join39(storagePath, "config.json");
46014
+ const file = Bun.file(configPath);
46015
+ if (!await file.exists()) {
46016
+ throw new ConfigError(`\u627E\u4E0D\u5230\u8A2D\u5B9A\u6A94\uFF1A${configPath}`);
46017
+ }
46018
+ return JSON.parse(await file.text());
46019
+ }
46020
+ function envRefName(value) {
46021
+ if (typeof value === "object" && value !== null && "$env" in value) {
46022
+ return String(value.$env);
46023
+ }
46024
+ return;
46025
+ }
46026
+ async function resolvePasswordTarget(projectPath, connectionName) {
46027
+ const storagePath = await resolveConfigStoragePath(projectPath);
46028
+ const raw = await readRawConfig(storagePath);
46029
+ if (detectConfigVersion(raw) !== 2) {
46030
+ if (connectionName && connectionName !== V1_CONNECTION_NAME) {
46031
+ throw new ConfigError(`\u9019\u662F v1 \u8A2D\u5B9A\uFF0C\u53EA\u6709\u4E00\u689D\u9023\u7DDA\uFF0C\u7121\u6CD5\u6307\u5B9A\u9023\u7DDA\u540D\u7A31 '${connectionName}'\u3002` + `\u9700\u8981\u591A\u9023\u7DDA\u8ACB\u5148\u7528 dbcli init --conn-name <name> \u5347\u7D1A\u5230 v2\u3002`);
46032
+ }
46033
+ const connection2 = raw.connection ?? {};
46034
+ const customRef = envRefName(connection2.password);
46035
+ if (customRef !== undefined && customRef !== V1_VAR_NAME) {
46036
+ throw new ConfigError(`\u9019\u500B v1 \u8A2D\u5B9A\u7684\u5BC6\u78BC\u4F86\u81EA\u74B0\u5883\u8B8A\u6578 '${customRef}'\uFF0Cdbcli \u7121\u6CD5\u4EE3\u70BA\u8F2A\u66FF\u3002` + `\u8ACB\u76F4\u63A5\u66F4\u65B0\u8A72\u74B0\u5883\u8B8A\u6578\uFF0C\u6216\u7528 dbcli init --conn-name <name> \u5347\u7D1A\u5230 v2 \u5F8C\u518D\u8F2A\u66FF\u3002`);
46037
+ }
46038
+ return {
46039
+ connection: V1_CONNECTION_NAME,
46040
+ envFile: DEFAULT_ENV_FILE,
46041
+ varName: V1_VAR_NAME,
46042
+ convertedToEnvRef: false
46043
+ };
46044
+ }
46045
+ const config = await readV2Config(projectPath);
46046
+ const targetName = connectionName ?? config.default;
46047
+ const connection = config.connections[targetName];
46048
+ if (!connection) {
46049
+ throw new ConfigError(connectionNotFoundMessage(targetName, Object.keys(config.connections)));
46050
+ }
46051
+ const referencedVar = envRefName(connection.password);
46052
+ return {
46053
+ connection: targetName,
46054
+ envFile: connection.envFile ?? DEFAULT_ENV_FILE,
46055
+ varName: referencedVar ?? envVarNameFor(targetName, "password"),
46056
+ convertedToEnvRef: referencedVar === undefined
46057
+ };
46058
+ }
46059
+ function assertSingleLine(value) {
46060
+ if (value.includes(`
46061
+ `) || value.includes("\r")) {
46062
+ throw new ConfigError("\u5BC6\u78BC\u4E0D\u53EF\u5305\u542B\u63DB\u884C\u5B57\u5143");
46063
+ }
46064
+ }
46065
+ async function stripV1LiteralPassword(storagePath, raw) {
46066
+ const connection = raw.connection ?? {};
46067
+ if (typeof connection.password !== "string")
46068
+ return;
46069
+ const stripped = { ...raw, connection: { ...connection } };
46070
+ delete stripped.connection.password;
46071
+ await writeConfigWithIntegrity(storagePath, JSON.stringify(stripped, null, 2));
46072
+ }
46073
+ async function setConnectionPassword(projectPath, connectionName, password) {
46074
+ assertConfigMutationApproved();
46075
+ assertSingleLine(password);
46076
+ const target = await resolvePasswordTarget(projectPath, connectionName);
46077
+ const storagePath = await resolveConfigStoragePath(projectPath);
46078
+ const raw = await readRawConfig(storagePath);
46079
+ await upsertEnvVar(join39(storagePath, target.envFile), target.varName, password);
46080
+ if (detectConfigVersion(raw) !== 2) {
46081
+ await stripV1LiteralPassword(storagePath, raw);
46082
+ return target;
46083
+ }
46084
+ const config = await readV2Config(projectPath);
46085
+ const connection = config.connections[target.connection];
46086
+ const needsEnvFile = connection.envFile === undefined;
46087
+ if (target.convertedToEnvRef || needsEnvFile) {
46088
+ await writeV2Config(projectPath, {
46089
+ ...config,
46090
+ connections: {
46091
+ ...config.connections,
46092
+ [target.connection]: {
46093
+ ...connection,
46094
+ envFile: target.envFile,
46095
+ ...target.convertedToEnvRef && { password: { $env: target.varName } }
46096
+ }
46097
+ }
46098
+ });
46099
+ }
46100
+ return target;
46101
+ }
46102
+ var DEFAULT_ENV_FILE = ".env.local", V1_VAR_NAME = "DBCLI_PASSWORD", V1_CONNECTION_NAME = "default";
46103
+ var init_connection_credential = __esm(() => {
46104
+ init_config_binding();
46105
+ init_config_v2();
46106
+ init_config_integrity();
46107
+ init_config_mutation_guard();
46108
+ init_config_v2_mutations();
46109
+ init_errors3();
46110
+ init_env_file_writer();
46111
+ });
46112
+
46113
+ // src/commands/credential.ts
46114
+ var exports_credential = {};
46115
+ __export(exports_credential, {
46116
+ resolveNewPassword: () => resolveNewPassword,
46117
+ passwordCommand: () => passwordCommand
46118
+ });
46119
+ async function readAllStdin() {
46120
+ return await Bun.stdin.text();
46121
+ }
46122
+ async function resolveNewPassword(options, io) {
46123
+ if (options.password !== undefined && options.stdin) {
46124
+ throw new ConfigError(t("password.source_conflict"));
46125
+ }
46126
+ let value;
46127
+ if (options.password !== undefined) {
46128
+ value = options.password;
46129
+ } else if (options.stdin) {
46130
+ value = (await io.readStdin()).replace(/\r?\n$/, "");
46131
+ } else if (io.isTTY) {
46132
+ value = await io.prompt();
46133
+ } else {
46134
+ throw new ConfigError(t("password.needs_source"));
46135
+ }
46136
+ if (value.length === 0) {
46137
+ throw new ConfigError(t("password.empty"));
46138
+ }
46139
+ return value;
46140
+ }
46141
+ async function verifyNewPassword(configPath, target, connectionName, password) {
46142
+ const storagePath = await resolveConfigStoragePath(configPath);
46143
+ const previous = process.env[target.varName];
46144
+ process.env[target.varName] = password;
46145
+ try {
46146
+ const config = await configModule.read(storagePath, connectionName);
46147
+ const adapter = AdapterFactory.createAdapter({
46148
+ ...config.connection,
46149
+ password
46150
+ });
46151
+ try {
46152
+ await adapter.connect();
46153
+ await adapter.testConnection();
46154
+ } finally {
46155
+ await adapter.disconnect().catch(() => {
46156
+ return;
46157
+ });
46158
+ }
46159
+ } finally {
46160
+ if (previous === undefined)
46161
+ delete process.env[target.varName];
46162
+ else
46163
+ process.env[target.varName] = previous;
46164
+ }
46165
+ }
46166
+ var passwordCommand;
46167
+ var init_credential = __esm(() => {
46168
+ init_esm();
46169
+ init_adapters();
46170
+ init_config();
46171
+ init_config_mutation_guard();
46172
+ init_config_binding();
46173
+ init_connection_credential();
46174
+ init_config_path();
46175
+ init_prompts();
46176
+ init_errors3();
46177
+ init_message_loader();
46178
+ passwordCommand = new Command("password").description(t("password.description")).argument("[connection]", t("password.arg_connection")).option("--stdin", t("password.opt_stdin")).option("--password <value>", t("password.opt_password")).option("--skip-test", t("password.opt_skip_test")).option("--format <format>", "Output format (text, json)", "text").action(async (connection, options) => {
46179
+ try {
46180
+ assertConfigMutationApproved();
46181
+ const configPath = resolveConfigPath(passwordCommand);
46182
+ const target = await resolvePasswordTarget(configPath, connection);
46183
+ const value = await resolveNewPassword({
46184
+ password: typeof options.password === "string" ? options.password : undefined,
46185
+ stdin: options.stdin === true
46186
+ }, {
46187
+ isTTY: Boolean(process.stdin.isTTY),
46188
+ readStdin: readAllStdin,
46189
+ prompt: () => promptUser.secret(t_vars("password.prompt", { name: target.connection }))
46190
+ });
46191
+ if (options.skipTest !== true) {
46192
+ await verifyNewPassword(configPath, target, connection, value);
46193
+ }
46194
+ const written = await setConnectionPassword(configPath, connection, value);
46195
+ if (options.format === "json") {
46196
+ console.log(JSON.stringify({ success: true, ...written }, null, 2));
46197
+ } else {
46198
+ console.log(t_vars("password.updated", {
46199
+ name: written.connection,
46200
+ envFile: written.envFile,
46201
+ varName: written.varName
46202
+ }));
46203
+ if (written.convertedToEnvRef) {
46204
+ console.log(t_vars("password.converted", { varName: written.varName }));
46205
+ }
46206
+ if (options.skipTest === true) {
46207
+ console.log(t("password.skipped_test"));
46208
+ }
46209
+ }
46210
+ } catch (error) {
46211
+ const message = error instanceof Error ? error.message : String(error);
46212
+ if (options.format === "json") {
46213
+ console.error(JSON.stringify({ success: false, error: message }, null, 2));
46214
+ } else {
46215
+ console.error(t_vars("errors.message", { message }));
46216
+ }
46217
+ process.exit(1);
46218
+ }
46219
+ });
46220
+ });
46221
+
45688
46222
  // src/proxy/relay.ts
45689
46223
  class TcpRelay {
45690
46224
  clientBytes = 0;
@@ -45759,8 +46293,8 @@ var init_sql_metadata = __esm(() => {
45759
46293
  });
45760
46294
 
45761
46295
  // src/proxy/events.ts
45762
- import { appendFile as appendFile2, mkdir as mkdir18, readFile as readFile9, stat as stat9 } from "fs/promises";
45763
- import { dirname as dirname16 } from "path";
46296
+ import { appendFile as appendFile2, mkdir as mkdir19, readFile as readFile10, stat as stat9 } from "fs/promises";
46297
+ import { dirname as dirname17 } from "path";
45764
46298
  function hasSql(e) {
45765
46299
  return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
45766
46300
  }
@@ -45799,7 +46333,7 @@ class EventWriter {
45799
46333
  }
45800
46334
  async writeInternal(event) {
45801
46335
  if (!this.dirEnsured) {
45802
- await mkdir18(dirname16(this.path), { recursive: true });
46336
+ await mkdir19(dirname17(this.path), { recursive: true });
45803
46337
  this.dirEnsured = true;
45804
46338
  }
45805
46339
  if (!this.initialized) {
@@ -45823,7 +46357,7 @@ class EventWriter {
45823
46357
  try {
45824
46358
  const s = await stat9(this.path);
45825
46359
  this.currentSizeBytes = s.size;
45826
- const raw = await readFile9(this.path, "utf8");
46360
+ const raw = await readFile10(this.path, "utf8");
45827
46361
  this.currentEntryCount = raw.split(`
45828
46362
  `).filter(Boolean).length;
45829
46363
  } catch {
@@ -46420,7 +46954,7 @@ var init_server = __esm(() => {
46420
46954
  });
46421
46955
 
46422
46956
  // src/proxy/event-reader.ts
46423
- import { readFile as readFile10 } from "fs/promises";
46957
+ import { readFile as readFile11 } from "fs/promises";
46424
46958
  async function readEvents(path4, opts) {
46425
46959
  const candidates = opts.includeRotated ? [path4, `${path4}.1`] : [path4];
46426
46960
  const files = [];
@@ -46429,7 +46963,7 @@ async function readEvents(path4, opts) {
46429
46963
  for (const file of candidates) {
46430
46964
  let raw;
46431
46965
  try {
46432
- raw = await readFile10(file, "utf8");
46966
+ raw = await readFile11(file, "utf8");
46433
46967
  } catch (err) {
46434
46968
  if (err.code !== "ENOENT")
46435
46969
  throw err;
@@ -46860,7 +47394,7 @@ __export(exports_proxy, {
46860
47394
  proxyCommand: () => proxyCommand,
46861
47395
  parseHostPort: () => parseHostPort
46862
47396
  });
46863
- import { join as join39 } from "path";
47397
+ import { join as join40 } from "path";
46864
47398
  function parseHostPort(value) {
46865
47399
  const idx = value.lastIndexOf(":");
46866
47400
  if (idx <= 0 || idx === value.length - 1) {
@@ -46923,7 +47457,7 @@ async function runProxy(subcommandEngine, options, command) {
46923
47457
  target: options.target,
46924
47458
  connection
46925
47459
  });
46926
- const eventsPath = options.events ?? join39(".dbcli", "proxy", "events.jsonl");
47460
+ const eventsPath = options.events ?? join40(".dbcli", "proxy", "events.jsonl");
46927
47461
  const slowMs = Number(options.slowMs ?? 1000);
46928
47462
  if (!Number.isFinite(slowMs) || slowMs < 0) {
46929
47463
  throw new Error(`Invalid --slow-ms "${options.slowMs}". Expected a non-negative number`);
@@ -46972,7 +47506,7 @@ async function runProxy(subcommandEngine, options, command) {
46972
47506
  }
46973
47507
  }
46974
47508
  function addCommonOptions(cmd) {
46975
- return cmd.option("--listen <host:port>", "Local proxy listen address (required)").option("--target <host:port>", "Upstream DB target (optional when config provides host/port)").option("--events <path>", "Event JSONL path", join39(".dbcli", "proxy", "events.jsonl")).option("--slow-ms <number>", "Threshold (ms); queries at/above it get slow:true in the event + a terminal warning", "1000").option("--redact <mode>", "SQL redaction: none | literals", "none").option("--format <format>", "Runtime status output: text | json", "text");
47509
+ return cmd.option("--listen <host:port>", "Local proxy listen address (required)").option("--target <host:port>", "Upstream DB target (optional when config provides host/port)").option("--events <path>", "Event JSONL path", join40(".dbcli", "proxy", "events.jsonl")).option("--slow-ms <number>", "Threshold (ms); queries at/above it get slow:true in the event + a terminal warning", "1000").option("--redact <mode>", "SQL redaction: none | literals", "none").option("--format <format>", "Runtime status output: text | json", "text");
46976
47510
  }
46977
47511
  function parseNonNegInt(value, flag, fallback) {
46978
47512
  if (value === undefined)
@@ -46990,7 +47524,7 @@ async function runAnalyze(options) {
46990
47524
  const top = parseNonNegInt(options.top, "top", 20);
46991
47525
  const slowMs = parseNonNegInt(options.slowMs, "slow-ms", 1000);
46992
47526
  const nPlusOne = parseNonNegInt(options.nPlusOne, "n-plus-one", 10);
46993
- const eventsPath = options.events ?? join39(".dbcli", "proxy", "events.jsonl");
47527
+ const eventsPath = options.events ?? join40(".dbcli", "proxy", "events.jsonl");
46994
47528
  const { events, malformedLines, files } = await readEvents(eventsPath, {
46995
47529
  includeRotated: options.includeRotated !== false
46996
47530
  });
@@ -47043,7 +47577,7 @@ var init_proxy = __esm(() => {
47043
47577
  });
47044
47578
  }
47045
47579
  ANALYZE_FORMATS = ["json", "text", "markdown"];
47046
- proxyCommand.command("analyze").description("Analyze a proxy event log offline (no DB connection)").option("--events <path>", "Event JSONL path", join39(".dbcli", "proxy", "events.jsonl")).option("--format <format>", "Output format: json | text | markdown (QueryLens)", "json").option("--top <number>", "Rows shown in text + suggestedCommands depth", "20").option("--slow-ms <number>", "Slow-query threshold (ms) for slowCount", "1000").option("--n-plus-one <number>", "Min repeats per (session,fingerprint) to flag N+1", "10").option("--no-include-rotated", "Do not merge the rotated <events>.1 segment").action(async (options) => {
47580
+ proxyCommand.command("analyze").description("Analyze a proxy event log offline (no DB connection)").option("--events <path>", "Event JSONL path", join40(".dbcli", "proxy", "events.jsonl")).option("--format <format>", "Output format: json | text | markdown (QueryLens)", "json").option("--top <number>", "Rows shown in text + suggestedCommands depth", "20").option("--slow-ms <number>", "Slow-query threshold (ms) for slowCount", "1000").option("--n-plus-one <number>", "Min repeats per (session,fingerprint) to flag N+1", "10").option("--no-include-rotated", "Do not merge the rotated <events>.1 segment").action(async (options) => {
47047
47581
  await runAnalyze(options);
47048
47582
  });
47049
47583
  addCommonOptions(proxyCommand).action(async (options, command) => {
@@ -47377,9 +47911,9 @@ var init_semantic2 = __esm(() => {
47377
47911
  });
47378
47912
 
47379
47913
  // src/core/design/index.ts
47380
- import { join as join40 } from "path";
47914
+ import { join as join41 } from "path";
47381
47915
  function defaultDesignFile(workspaceRoot) {
47382
- return join40(workspaceRoot, DEFAULT_FILE3);
47916
+ return join41(workspaceRoot, DEFAULT_FILE3);
47383
47917
  }
47384
47918
  function parseDesignSpec(raw, filePath = DEFAULT_FILE3) {
47385
47919
  const parsed = specSchema.safeParse(raw);
@@ -48210,8 +48744,8 @@ var exports_backfill = {};
48210
48744
  __export(exports_backfill, {
48211
48745
  backfillCommand: () => backfillCommand
48212
48746
  });
48213
- import { dirname as dirname17, resolve as resolve10 } from "path";
48214
- import { mkdir as mkdir19 } from "fs/promises";
48747
+ import { dirname as dirname18, resolve as resolve10 } from "path";
48748
+ import { mkdir as mkdir20 } from "fs/promises";
48215
48749
  function identityFor(config, name) {
48216
48750
  const connection = config.connections[name];
48217
48751
  if (!connection) {
@@ -48261,7 +48795,7 @@ var init_backfill = __esm(() => {
48261
48795
  return;
48262
48796
  }
48263
48797
  const out = resolve10(options.out ?? `.dbcli/backfills/${artifact2.source.sha256.slice(0, 12)}.json`);
48264
- await mkdir19(dirname17(out), { recursive: true });
48798
+ await mkdir20(dirname18(out), { recursive: true });
48265
48799
  await Bun.write(out, JSON.stringify(artifact2, null, 2) + `
48266
48800
  `);
48267
48801
  console.log(JSON.stringify({ path: out, artifact: artifact2 }, null, 2));
@@ -48270,8 +48804,8 @@ var init_backfill = __esm(() => {
48270
48804
 
48271
48805
  // src/core/evidence-pack/index.ts
48272
48806
  import { createHash as createHash8, randomUUID as randomUUID6 } from "crypto";
48273
- import { link as link3, lstat as lstat5, mkdir as mkdir20, realpath as realpath2, unlink as unlink8, writeFile as writeFile9 } from "fs/promises";
48274
- import { basename as basename9, dirname as dirname18, relative as relative5, resolve as resolve11, sep as sep7 } from "path";
48807
+ import { link as link3, lstat as lstat5, mkdir as mkdir21, realpath as realpath2, unlink as unlink8, writeFile as writeFile10 } from "fs/promises";
48808
+ import { basename as basename9, dirname as dirname19, relative as relative5, resolve as resolve11, sep as sep7 } from "path";
48275
48809
  function isRecord4(value) {
48276
48810
  return typeof value === "object" && value !== null && !Array.isArray(value);
48277
48811
  }
@@ -48522,7 +49056,7 @@ async function realExistingAncestor(path4) {
48522
49056
  } catch (error) {
48523
49057
  if (error.code !== "ENOENT")
48524
49058
  throw error;
48525
- const parent = dirname18(candidate);
49059
+ const parent = dirname19(candidate);
48526
49060
  if (parent === candidate)
48527
49061
  throw error;
48528
49062
  candidate = parent;
@@ -48534,12 +49068,12 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
48534
49068
  const requestedTarget = resolve11(workspace, outputPath);
48535
49069
  if (!isInside(workspace, requestedTarget))
48536
49070
  throw new EvidencePackValidationError("output path must stay inside the workspace");
48537
- const parent = dirname18(requestedTarget);
49071
+ const parent = dirname19(requestedTarget);
48538
49072
  const existingAncestor2 = await realExistingAncestor(parent);
48539
49073
  if (!isInside(workspace, existingAncestor2) && existingAncestor2 !== workspace) {
48540
49074
  throw new EvidencePackValidationError("output path resolves outside the workspace");
48541
49075
  }
48542
- await mkdir20(parent, { recursive: true });
49076
+ await mkdir21(parent, { recursive: true });
48543
49077
  const realParent = await realpath2(parent);
48544
49078
  if (!isInside(workspace, realParent) && realParent !== workspace) {
48545
49079
  throw new EvidencePackValidationError("output path resolves outside the workspace");
@@ -48556,7 +49090,7 @@ async function writeEvidencePack(workspaceRoot, outputPath, pack) {
48556
49090
  }
48557
49091
  const temp = resolve11(realParent, `.${basename9(target)}.${process.pid}.${randomUUID6()}.tmp`);
48558
49092
  try {
48559
- await writeFile9(temp, `${JSON.stringify(pack, null, 2)}
49093
+ await writeFile10(temp, `${JSON.stringify(pack, null, 2)}
48560
49094
  `, "utf8");
48561
49095
  try {
48562
49096
  await link3(temp, target);
@@ -48633,7 +49167,7 @@ var exports_evidence = {};
48633
49167
  __export(exports_evidence, {
48634
49168
  evidenceCommand: () => evidenceCommand
48635
49169
  });
48636
- import { join as join41, relative as relative6, resolve as resolve12, sep as sep8 } from "path";
49170
+ import { join as join42, relative as relative6, resolve as resolve12, sep as sep8 } from "path";
48637
49171
  import { realpath as realpath3 } from "fs/promises";
48638
49172
  import { createHash as createHash9 } from "crypto";
48639
49173
  function safeMessage(error) {
@@ -48709,7 +49243,7 @@ async function auditEntries(configPath, config) {
48709
49243
  throw new EvidencePackValidationError("audit evidence is disabled");
48710
49244
  const storagePath = await resolveConfigStoragePath(configPath);
48711
49245
  const connectionName = config.effectiveConnectionName || getGlobalConnectionName() || "default";
48712
- const file = join41(storagePath, ".dbcli", "audit", `${connectionName}.jsonl`);
49246
+ const file = join42(storagePath, ".dbcli", "audit", `${connectionName}.jsonl`);
48713
49247
  return { connectionName, entries: await readEntries(file, { include_rotated: true }) };
48714
49248
  }
48715
49249
  function auditReference(entry, connectionName) {
@@ -50073,8 +50607,8 @@ __export(exports_impact, {
50073
50607
  impactCommand: () => impactCommand
50074
50608
  });
50075
50609
  import { randomUUID as randomUUID7 } from "crypto";
50076
- import { link as link4, lstat as lstat8, mkdir as mkdir21, realpath as realpath5, unlink as unlink9, writeFile as writeFile10 } from "fs/promises";
50077
- import { basename as basename10, dirname as dirname19, relative as relative8, resolve as resolve14, sep as sep10 } from "path";
50610
+ import { link as link4, lstat as lstat8, mkdir as mkdir22, realpath as realpath5, unlink as unlink9, writeFile as writeFile11 } from "fs/promises";
50611
+ import { basename as basename10, dirname as dirname20, relative as relative8, resolve as resolve14, sep as sep10 } from "path";
50078
50612
  function collectOption3(value, previous) {
50079
50613
  return [...previous, value];
50080
50614
  }
@@ -50278,7 +50812,7 @@ async function realExistingAncestor2(path4) {
50278
50812
  } catch (error) {
50279
50813
  if (error.code !== "ENOENT")
50280
50814
  throw error;
50281
- const parent = dirname19(candidate);
50815
+ const parent = dirname20(candidate);
50282
50816
  if (parent === candidate)
50283
50817
  throw error;
50284
50818
  candidate = parent;
@@ -50290,11 +50824,11 @@ async function resolveOutput(workspaceRoot, outputPath) {
50290
50824
  const target = resolve14(workspace, outputPath);
50291
50825
  if (!isInside2(workspace, target))
50292
50826
  throw new Error("output path must stay inside the workspace");
50293
- const parent = dirname19(target);
50827
+ const parent = dirname20(target);
50294
50828
  const existingAncestor2 = await realExistingAncestor2(parent);
50295
50829
  if (!isInside2(workspace, existingAncestor2) && existingAncestor2 !== workspace)
50296
50830
  throw new Error("output path must stay inside the workspace");
50297
- await mkdir21(parent, { recursive: true });
50831
+ await mkdir22(parent, { recursive: true });
50298
50832
  const realParent = await realpath5(parent);
50299
50833
  if (!isInside2(workspace, realParent) && realParent !== workspace)
50300
50834
  throw new Error("output path must stay inside the workspace");
@@ -50302,7 +50836,7 @@ async function resolveOutput(workspaceRoot, outputPath) {
50302
50836
  }
50303
50837
  async function assertOutputParent(workspaceRoot, path4) {
50304
50838
  const workspace = await realpath5(workspaceRoot);
50305
- const parent = await realpath5(dirname19(path4));
50839
+ const parent = await realpath5(dirname20(path4));
50306
50840
  if (!isInside2(workspace, parent) && parent !== workspace) {
50307
50841
  throw new Error("output path must stay inside the workspace");
50308
50842
  }
@@ -50316,9 +50850,9 @@ async function writeOutput(workspaceRoot, path4, content) {
50316
50850
  if (error.code !== "ENOENT")
50317
50851
  throw error;
50318
50852
  }
50319
- const temp = resolve14(dirname19(path4), `.${basename10(path4)}.${process.pid}.${randomUUID7()}.tmp`);
50853
+ const temp = resolve14(dirname20(path4), `.${basename10(path4)}.${process.pid}.${randomUUID7()}.tmp`);
50320
50854
  try {
50321
- await writeFile10(temp, content, "utf8");
50855
+ await writeFile11(temp, content, "utf8");
50322
50856
  await assertOutputParent(workspaceRoot, path4);
50323
50857
  await link4(temp, path4);
50324
50858
  } catch (error) {
@@ -50457,6 +50991,7 @@ function buildProgram() {
50457
50991
  program2.addCommand(shellCommand);
50458
50992
  program2.addCommand(migrateCommand);
50459
50993
  program2.addCommand(useCommand);
50994
+ program2.addCommand(passwordCommand);
50460
50995
  program2.addCommand(queriesCommand);
50461
50996
  program2.addCommand(explainCommand);
50462
50997
  program2.addCommand(lintCommand);
@@ -50510,6 +51045,7 @@ var init_program = __esm(() => {
50510
51045
  init_shell3();
50511
51046
  init_migrate();
50512
51047
  init_use();
51048
+ init_credential();
50513
51049
  init_proxy();
50514
51050
  init_semantic2();
50515
51051
  init_design2();
@@ -50533,9 +51069,9 @@ __export(exports_completion, {
50533
51069
  detectShell: () => detectShell,
50534
51070
  completionCommand: () => completionCommand
50535
51071
  });
50536
- import { join as join42 } from "path";
51072
+ import { join as join43 } from "path";
50537
51073
  import { homedir as homedir4 } from "os";
50538
- import { mkdir as mkdir22 } from "fs/promises";
51074
+ import { mkdir as mkdir23 } from "fs/promises";
50539
51075
  function resolveHome() {
50540
51076
  return process.env.HOME ?? homedir4();
50541
51077
  }
@@ -50785,11 +51321,11 @@ function getInstallPath2(shell) {
50785
51321
  const home = resolveHome();
50786
51322
  switch (shell) {
50787
51323
  case "bash":
50788
- return join42(home, ".bashrc");
51324
+ return join43(home, ".bashrc");
50789
51325
  case "zsh":
50790
- return join42(home, ".zshrc");
51326
+ return join43(home, ".zshrc");
50791
51327
  case "fish":
50792
- return join42(home, ".config", "fish", "completions", "dbcli.fish");
51328
+ return join43(home, ".config", "fish", "completions", "dbcli.fish");
50793
51329
  default:
50794
51330
  throw new Error(`Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
50795
51331
  }
@@ -50807,8 +51343,8 @@ function detectShell() {
50807
51343
  async function installCompletion(shell, script) {
50808
51344
  const targetPath = getInstallPath2(shell);
50809
51345
  if (shell === "fish") {
50810
- const dir = join42(resolveHome(), ".config", "fish", "completions");
50811
- await mkdir22(dir, { recursive: true });
51346
+ const dir = join43(resolveHome(), ".config", "fish", "completions");
51347
+ await mkdir23(dir, { recursive: true });
50812
51348
  await Bun.file(targetPath).write(script);
50813
51349
  console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
50814
51350
  return;
@@ -51147,6 +51683,12 @@ var COMMAND_LOADERS = {
51147
51683
  program2.addCommand(migrateCommand2);
51148
51684
  };
51149
51685
  },
51686
+ password: async () => {
51687
+ const { passwordCommand: passwordCommand2 } = await Promise.resolve().then(() => (init_credential(), exports_credential));
51688
+ return (program2) => {
51689
+ program2.addCommand(passwordCommand2);
51690
+ };
51691
+ },
51150
51692
  use: async () => {
51151
51693
  const { useCommand: useCommand2 } = await Promise.resolve().then(() => (init_use(), exports_use));
51152
51694
  return (program2) => {
@@ -51287,7 +51829,7 @@ async function buildProgramFor(argv) {
51287
51829
 
51288
51830
  // src/cli-runtime.ts
51289
51831
  init_cli_error();
51290
- import { join as join43 } from "path";
51832
+ import { join as join44 } from "path";
51291
51833
  import { writeSync as writeSync2 } from "fs";
51292
51834
  import { format } from "util";
51293
51835
  function installSynchronousRedirectedStdout() {
@@ -51425,7 +51967,7 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
51425
51967
  try {
51426
51968
  let cache = null;
51427
51969
  try {
51428
- const cacheFile = Bun.file(join43(configPath, "version-check.json"));
51970
+ const cacheFile = Bun.file(join44(configPath, "version-check.json"));
51429
51971
  if (await cacheFile.exists()) {
51430
51972
  cache = await cacheFile.json();
51431
51973
  }