@carllee1983/dbcli 1.57.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.57.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",
@@ -8943,6 +8945,12 @@ var init_messages = __esm(() => {
8943
8945
  invalid_config: "Invalid configuration: {field}",
8944
8946
  connection_failed: "Failed to connect to database: {message}",
8945
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}",
8946
8954
  table_not_found: "Table not found: {table}",
8947
8955
  unsupported_lang: "Language '{lang}' not supported, using English",
8948
8956
  invalid_json: "Invalid JSON: {message}",
@@ -8964,7 +8972,8 @@ Provide options: --env-host, --env-port, --env-user, --env-password, --env-datab
8964
8972
  Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactive`,
8965
8973
  env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
8966
8974
  Set {envKey} in .env or environment variables.
8967
- 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"
8968
8977
  },
8969
8978
  success: {
8970
8979
  inserted: "Successfully inserted {count} row(s)",
@@ -8975,19 +8984,22 @@ Hint: run 'export {envKey}=<value>' and retry`
8975
8984
  insert: {
8976
8985
  description: "Insert data into a table",
8977
8986
  prompt_data: "Enter JSON data to insert: ",
8978
- 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"
8979
8989
  },
8980
8990
  update: {
8981
8991
  description: "Update data in a table",
8982
8992
  prompt_where: "Enter WHERE clause (e.g., id=1): ",
8983
8993
  prompt_set: 'Enter JSON updates (e.g., {"status": "active"}): ',
8984
- 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"
8985
8996
  },
8986
8997
  delete: {
8987
8998
  description: "Delete data from a table",
8988
8999
  prompt_where: "Enter WHERE clause (e.g., id=1): ",
8989
9000
  confirm: "Delete rows from {table}? (y/n): ",
8990
- 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"
8991
9003
  },
8992
9004
  export: {
8993
9005
  description: "Export query results",
@@ -9241,6 +9253,12 @@ var init_messages2 = __esm(() => {
9241
9253
  invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
9242
9254
  connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
9243
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}",
9244
9262
  table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
9245
9263
  unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
9246
9264
  invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
@@ -9262,7 +9280,8 @@ var init_messages2 = __esm(() => {
9262
9280
  \u6216\u4F7F\u7528\u4E92\u52D5\u6A21\u5F0F\uFF1A\u57F7\u884C "dbcli init --use-env-refs"\uFF08\u4E0D\u52A0 --no-interactive\uFF09`,
9263
9281
  env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
9264
9282
  \u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
9265
- \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"
9266
9285
  },
9267
9286
  success: {
9268
9287
  inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
@@ -9273,19 +9292,22 @@ var init_messages2 = __esm(() => {
9273
9292
  insert: {
9274
9293
  description: "\u63D2\u5165\u8CC7\u6599\u5230\u8868\u683C",
9275
9294
  prompt_data: "\u8F38\u5165\u8981\u63D2\u5165\u7684 JSON \u8CC7\u6599\uFF1A",
9276
- 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"
9277
9297
  },
9278
9298
  update: {
9279
9299
  description: "\u66F4\u65B0\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
9280
9300
  prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
9281
9301
  prompt_set: '\u8F38\u5165 JSON \u66F4\u65B0 (\u4F8B\u5982\uFF1A{"status": "active"})\uFF1A',
9282
- 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"
9283
9304
  },
9284
9305
  delete: {
9285
9306
  description: "\u522A\u9664\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
9286
9307
  prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
9287
9308
  confirm: "\u522A\u9664 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
9288
- 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"
9289
9311
  },
9290
9312
  export: {
9291
9313
  description: "\u532F\u51FA\u67E5\u8A62\u7D50\u679C",
@@ -9430,6 +9452,60 @@ var init_shell2 = __esm(() => {
9430
9452
  };
9431
9453
  });
9432
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
+
9433
9509
  // src/i18n/message-loader.ts
9434
9510
  class MessageLoader {
9435
9511
  static instance = null;
@@ -9496,9 +9572,15 @@ var init_message_loader = __esm(() => {
9496
9572
  init_messages2();
9497
9573
  init_shell();
9498
9574
  init_shell2();
9575
+ init_ceremony();
9576
+ init_ceremony2();
9499
9577
  BUNDLED_MESSAGES = {
9500
- en: { ...messages_default, shell: shell_default },
9501
- "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
+ }
9502
9584
  };
9503
9585
  messageLoader = MessageLoader.getInstance();
9504
9586
  });
@@ -9664,7 +9746,8 @@ async function selectFallback(message, choices) {
9664
9746
  return choices[0] ?? "";
9665
9747
  }
9666
9748
  async function confirmFallback(message) {
9667
- const answer = await readLineFromStdin(`${message} (y/n): `);
9749
+ process.stderr.write(`${message} (y/n): `);
9750
+ const answer = await readLineFromStdin();
9668
9751
  return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
9669
9752
  }
9670
9753
  async function text(message, defaultValue) {
@@ -9696,7 +9779,7 @@ async function confirm(message) {
9696
9779
  const inquirer = await loadInquirer();
9697
9780
  if (!inquirer)
9698
9781
  return await confirmFallback(message);
9699
- return await inquirer.confirm({ message });
9782
+ return await inquirer.confirm({ message }, { output: process.stderr });
9700
9783
  }
9701
9784
  var promptsUnavailableReported = false, promptUser;
9702
9785
  var init_prompts = __esm(() => {
@@ -18555,33 +18638,7 @@ var init_sql_lexical = __esm(() => {
18555
18638
  DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
18556
18639
  });
18557
18640
 
18558
- // src/core/permission-guard.ts
18559
- var exports_permission_guard = {};
18560
- __export(exports_permission_guard, {
18561
- toSqlDialect: () => toSqlDialect,
18562
- stripCommentsAndStrings: () => stripCommentsAndStrings,
18563
- removeParameterMarkers: () => removeParameterMarkers,
18564
- permissionAtLeast: () => permissionAtLeast,
18565
- normalizeSQL: () => normalizeSQL,
18566
- mapKeywordToType: () => mapKeywordToType,
18567
- isDestructiveOperation: () => isDestructiveOperation,
18568
- findWriteKeyword: () => findWriteKeyword,
18569
- extractFirstKeyword: () => extractFirstKeyword,
18570
- extractAllKeywords: () => extractAllKeywords,
18571
- enforceRedisPermission: () => enforceRedisPermission,
18572
- enforcePermission: () => enforcePermission,
18573
- enforceElasticsearchPermission: () => enforceElasticsearchPermission,
18574
- determineConfidence: () => determineConfidence,
18575
- detectCompositePatterns: () => detectCompositePatterns,
18576
- containsMultipleStatements: () => containsMultipleStatements,
18577
- classifyStatement: () => classifyStatement,
18578
- classifyRedisCommand: () => classifyRedisCommand,
18579
- classifyElasticsearchRequest: () => classifyElasticsearchRequest,
18580
- checkPermission: () => checkPermission,
18581
- SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
18582
- SQL_DIALECTS: () => SQL_DIALECTS,
18583
- PermissionError: () => PermissionError
18584
- });
18641
+ // src/core/permission/sql-analysis.ts
18585
18642
  function normalizeSQL(sql) {
18586
18643
  return sql.replace(/--[^\n]*\n/g, `
18587
18644
  `).replace(/\/\*[\s\S]*?\*\//g, " ").trim().replace(/\s+/g, " ");
@@ -18828,6 +18885,29 @@ function determineConfidence(type, _keyword, _sql) {
18828
18885
  function removeParameterMarkers(sql) {
18829
18886
  return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
18830
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
+ });
18831
18911
  function classifyStatement(sql) {
18832
18912
  const normalized = normalizeSQL(sql);
18833
18913
  const stripped = stripCommentsAndStrings(normalized);
@@ -18893,15 +18973,33 @@ function checkPermission(sql, permission, dialect) {
18893
18973
  if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
18894
18974
  return {
18895
18975
  allowed: false,
18896
- reason: "SQL containing multiple statements is refused below admin permission, because only " + "the first statement determines the permission check. Run each statement separately.",
18897
- classification
18976
+ reason: t("errors.multiple_statements_refused"),
18977
+ classification,
18978
+ requiredPermission: "admin"
18898
18979
  };
18899
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) {
18900
18994
  if (permission !== "admin" && classification.escalatedFrom) {
18901
18995
  return {
18902
18996
  allowed: false,
18903
- 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}).`,
18904
- classification
18997
+ reason: t_vars("errors.escalated_write_requires_admin", {
18998
+ keyword: classification.escalatedFrom,
18999
+ permission
19000
+ }),
19001
+ classification,
19002
+ requiredPermission: "admin"
18905
19003
  };
18906
19004
  }
18907
19005
  if (permission === "admin") {
@@ -18911,220 +19009,64 @@ function checkPermission(sql, permission, dialect) {
18911
19009
  classification
18912
19010
  };
18913
19011
  }
18914
- if (permission === "data-admin") {
18915
- const allowedTypes = ["SELECT", "INSERT", "UPDATE", "DELETE", "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);
18916
19015
  if (allowedTypes.includes(classification.type)) {
18917
19016
  return {
18918
19017
  allowed: true,
18919
- reason: `${classification.type} operation allowed in data-admin mode`,
19018
+ reason: `${classification.type} operation allowed in ${permission} mode`,
18920
19019
  classification
18921
19020
  };
18922
19021
  }
19022
+ const isUnknown = permission === "query-only" && classification.type === "UNKNOWN";
18923
19023
  return {
18924
19024
  allowed: false,
18925
- reason: `${classification.type} operation requires admin permission`,
18926
- classification
18927
- };
18928
- }
18929
- if (permission === "read-write") {
18930
- const allowedTypes = ["SELECT", "INSERT", "UPDATE", "SHOW", "DESCRIBE", "EXPLAIN"];
18931
- if (allowedTypes.includes(classification.type)) {
18932
- return {
18933
- allowed: true,
18934
- reason: `${classification.type} operation allowed in read-write mode`,
18935
- classification
18936
- };
18937
- }
18938
- return {
18939
- allowed: false,
18940
- reason: `${classification.type} operation requires data-admin or admin permission`,
18941
- classification
18942
- };
18943
- }
18944
- if (permission === "query-only") {
18945
- const allowedTypes = ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"];
18946
- if (allowedTypes.includes(classification.type)) {
18947
- return {
18948
- allowed: true,
18949
- reason: `${classification.type} operation allowed in query-only mode`,
18950
- classification
18951
- };
18952
- }
18953
- const isUnknown = classification.type === "UNKNOWN";
18954
- return {
18955
- allowed: false,
18956
- 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`,
18957
- classification
19025
+ reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
19026
+ classification,
19027
+ requiredPermission: isUnknown ? "read-write" : minimumPermissionFor(classification.type)
18958
19028
  };
18959
19029
  }
18960
19030
  return {
18961
19031
  allowed: false,
18962
19032
  reason: `Unknown permission level: ${permission}`,
18963
- classification
19033
+ classification,
19034
+ requiredPermission: "admin"
18964
19035
  };
18965
19036
  }
18966
- function enforcePermission(sql, permission, dialect) {
18967
- const result = checkPermission(sql, permission, dialect);
18968
- if (!result.allowed) {
18969
- throw new PermissionError(result.reason, result.classification, permission);
18970
- }
18971
- return result.classification;
18972
- }
18973
- function classifyRedisCommand(command) {
18974
- const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
18975
- const required = REDIS_COMMAND_PERMISSION[head];
18976
- let type;
18977
- if (!required)
18978
- type = "UNKNOWN";
18979
- else if (required === "query-only")
18980
- type = "SELECT";
18981
- else if (required === "read-write")
18982
- type = "UPDATE";
18983
- else if (required === "data-admin")
18984
- type = "DELETE";
18985
- else
18986
- type = "DROP";
19037
+ function classificationForType(type) {
18987
19038
  return {
18988
- command: head,
18989
- requiredPermission: required ?? "unknown",
18990
19039
  type,
18991
- isDangerous: required === "admin" || required === "data-admin"
18992
- };
18993
- }
18994
- function permissionAtLeast(actual, required) {
18995
- return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
18996
- }
18997
- function enforceRedisPermission(command, permission) {
18998
- const classification = classifyRedisCommand(command);
18999
- const required = classification.requiredPermission;
19000
- const stmt = {
19001
- type: classification.type,
19002
- isDangerous: classification.isDangerous,
19003
- keywords: [classification.command],
19040
+ isDangerous: isDestructiveOperation(type),
19041
+ keywords: [type],
19004
19042
  isComposite: false,
19005
- confidence: required === "unknown" ? "LOW" : "HIGH"
19043
+ confidence: "HIGH"
19006
19044
  };
19007
- if (required === "unknown") {
19008
- throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
19009
- }
19010
- if (!permissionAtLeast(permission, required)) {
19011
- throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
19012
- }
19013
- return classification;
19014
19045
  }
19015
- function classifyElasticsearchRequest(request) {
19016
- const method = request.method.toUpperCase();
19017
- const path = request.apiPath.toLowerCase();
19018
- if (path.includes("_bulk")) {
19019
- return classifyElasticsearchBulk(request.body ?? "");
19020
- }
19021
- if (path.includes("_search") || path.includes("_count") || path.includes("_mapping") || path.includes("_settings") || path.includes("_alias") || method === "GET" && (path.includes("_doc") || path.includes("_source"))) {
19022
- return {
19023
- type: "SELECT",
19024
- isDangerous: false,
19025
- keywords: [method, path],
19026
- isComposite: false,
19027
- confidence: "HIGH"
19028
- };
19029
- }
19030
- if (path.includes("_update") || method === "POST" && path.includes("_doc")) {
19031
- return {
19032
- type: "UPDATE",
19033
- isDangerous: false,
19034
- keywords: [method, path],
19035
- isComposite: false,
19036
- confidence: "HIGH"
19037
- };
19038
- }
19039
- if (method === "PUT" && (path.includes("_doc") || path.includes("_create"))) {
19040
- return {
19041
- type: "INSERT",
19042
- isDangerous: false,
19043
- keywords: [method, path],
19044
- isComposite: false,
19045
- confidence: "HIGH"
19046
- };
19047
- }
19048
- if (method === "DELETE") {
19049
- return {
19050
- type: "DELETE",
19051
- isDangerous: true,
19052
- keywords: [method, path],
19053
- isComposite: false,
19054
- confidence: "HIGH"
19055
- };
19056
- }
19057
- return {
19058
- type: "DROP",
19059
- isDangerous: true,
19060
- keywords: [method, path],
19061
- isComposite: false,
19062
- confidence: "LOW"
19063
- };
19046
+ function permitsOperation(type, permission) {
19047
+ return checkPermissionForClassification(classificationForType(type), permission).allowed;
19064
19048
  }
19065
- function classifyElasticsearchBulk(body) {
19066
- const lines = body.split(`
19067
- `).filter((l) => l.trim().length > 0);
19068
- let highestType = "SELECT";
19069
- let isDangerous = false;
19070
- for (const line of lines) {
19071
- try {
19072
- const action = JSON.parse(line);
19073
- const op = Object.keys(action)[0];
19074
- if (op === "delete") {
19075
- highestType = "DELETE";
19076
- isDangerous = true;
19077
- break;
19078
- }
19079
- if (op === "update" && highestType !== "DELETE") {
19080
- highestType = "UPDATE";
19081
- }
19082
- if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
19083
- highestType = "INSERT";
19084
- }
19085
- } 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));
19086
19054
  }
19087
- return {
19088
- type: highestType,
19089
- isDangerous,
19090
- keywords: ["BULK"],
19091
- isComposite: true,
19092
- confidence: "HIGH"
19093
- };
19094
19055
  }
19095
- function enforceElasticsearchPermission(request, permission) {
19096
- const classification = classifyElasticsearchRequest(request);
19097
- const result = checkElasticsearchPermission(classification, permission);
19056
+ function enforcePermission(sql, permission, dialect) {
19057
+ const result = checkPermission(sql, permission, dialect);
19098
19058
  if (!result.allowed) {
19099
- throw new PermissionError(result.reason, classification, permission);
19059
+ throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
19100
19060
  }
19101
- return classification;
19061
+ return result.classification;
19102
19062
  }
19103
- function checkElasticsearchPermission(classification, permission) {
19104
- if (permission === "admin")
19105
- return { allowed: true, reason: "Admin" };
19106
- if (permission === "data-admin") {
19107
- const allowed = ["SELECT", "INSERT", "UPDATE", "DELETE"];
19108
- if (allowed.includes(classification.type))
19109
- return { allowed: true, reason: "Data-Admin" };
19110
- }
19111
- if (permission === "read-write") {
19112
- const allowed = ["SELECT", "INSERT", "UPDATE"];
19113
- if (allowed.includes(classification.type))
19114
- return { allowed: true, reason: "Read-Write" };
19115
- }
19116
- if (permission === "query-only") {
19117
- if (classification.type === "SELECT")
19118
- return { allowed: true, reason: "Query-Only" };
19119
- }
19120
- return {
19121
- allowed: false,
19122
- reason: `Elasticsearch ${classification.type} operation requires higher permission tier`
19123
- };
19063
+ function permissionAtLeast(actual, required) {
19064
+ return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
19124
19065
  }
19125
- 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;
19126
19067
  var init_permission_guard = __esm(() => {
19127
- init_sql_lexical();
19068
+ init_message_loader();
19069
+ init_sql_analysis();
19128
19070
  PermissionError = class PermissionError extends Error {
19129
19071
  classification;
19130
19072
  requiredPermission;
@@ -19140,90 +19082,11 @@ var init_permission_guard = __esm(() => {
19140
19082
  SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
19141
19083
  SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
19142
19084
  ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
19143
- REDIS_COMMAND_PERMISSION = {
19144
- GET: "query-only",
19145
- MGET: "query-only",
19146
- STRLEN: "query-only",
19147
- EXISTS: "query-only",
19148
- TTL: "query-only",
19149
- PTTL: "query-only",
19150
- TYPE: "query-only",
19151
- SCAN: "query-only",
19152
- HGET: "query-only",
19153
- HGETALL: "query-only",
19154
- HKEYS: "query-only",
19155
- HVALS: "query-only",
19156
- HLEN: "query-only",
19157
- HEXISTS: "query-only",
19158
- HMGET: "query-only",
19159
- LRANGE: "query-only",
19160
- LLEN: "query-only",
19161
- LINDEX: "query-only",
19162
- SMEMBERS: "query-only",
19163
- SCARD: "query-only",
19164
- SISMEMBER: "query-only",
19165
- ZRANGE: "query-only",
19166
- ZREVRANGE: "query-only",
19167
- ZRANGEBYSCORE: "query-only",
19168
- ZCARD: "query-only",
19169
- ZSCORE: "query-only",
19170
- PING: "query-only",
19171
- ECHO: "query-only",
19172
- SET: "read-write",
19173
- SETEX: "read-write",
19174
- SETNX: "read-write",
19175
- PSETEX: "read-write",
19176
- MSET: "read-write",
19177
- MSETNX: "read-write",
19178
- APPEND: "read-write",
19179
- INCR: "read-write",
19180
- INCRBY: "read-write",
19181
- DECR: "read-write",
19182
- DECRBY: "read-write",
19183
- HSET: "read-write",
19184
- HSETNX: "read-write",
19185
- HMSET: "read-write",
19186
- HINCRBY: "read-write",
19187
- LPUSH: "read-write",
19188
- RPUSH: "read-write",
19189
- LPOP: "read-write",
19190
- RPOP: "read-write",
19191
- LSET: "read-write",
19192
- LREM: "read-write",
19193
- SADD: "read-write",
19194
- SREM: "read-write",
19195
- ZADD: "read-write",
19196
- ZREM: "read-write",
19197
- XADD: "read-write",
19198
- XDEL: "data-admin",
19199
- XLEN: "query-only",
19200
- XREAD: "query-only",
19201
- XRANGE: "query-only",
19202
- XREVRANGE: "query-only",
19203
- EXPIRE: "read-write",
19204
- EXPIREAT: "read-write",
19205
- PEXPIRE: "read-write",
19206
- PERSIST: "read-write",
19207
- RENAME: "read-write",
19208
- DEL: "data-admin",
19209
- UNLINK: "data-admin",
19210
- HDEL: "data-admin",
19211
- FLUSHDB: "admin",
19212
- FLUSHALL: "admin",
19213
- CONFIG: "admin",
19214
- INFO: "admin",
19215
- CLIENT: "admin",
19216
- DEBUG: "admin",
19217
- SHUTDOWN: "admin",
19218
- KEYS: "admin",
19219
- MONITOR: "admin",
19220
- SAVE: "admin",
19221
- BGSAVE: "admin",
19222
- BGREWRITEAOF: "admin",
19223
- REPLICAOF: "admin",
19224
- SLAVEOF: "admin",
19225
- ACL: "admin"
19226
- };
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
+ ];
19227
19090
  PERMISSION_RANK = {
19228
19091
  "query-only": 1,
19229
19092
  "read-write": 2,
@@ -21914,6 +21777,7 @@ function inferColumnType(value) {
21914
21777
  }
21915
21778
  var init_query_executor = __esm(() => {
21916
21779
  init_permission_guard();
21780
+ init_sql_analysis();
21917
21781
  init_error_suggester();
21918
21782
  init_sql_tables();
21919
21783
  init_applied_limit();
@@ -22213,6 +22077,263 @@ function assertFanOutReadOnlySql(sql, dialect) {
22213
22077
  var init_query_fanout = __esm(() => {
22214
22078
  init_cli_error();
22215
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
+ };
22216
22337
  });
22217
22338
 
22218
22339
  // src/commands/query-table-schema.ts
@@ -22509,7 +22630,7 @@ async function preflightQuery(query, options, context, fieldSelection, multiConn
22509
22630
  if (multiConnection) {
22510
22631
  throw new Error("Redis queries do not support multiple connections");
22511
22632
  }
22512
- 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));
22513
22634
  enforceRedisPermission2(query, config.permission);
22514
22635
  return;
22515
22636
  }
@@ -22930,7 +23051,7 @@ var init_query = __esm(() => {
22930
23051
  init_field_projection();
22931
23052
  init_connection_selector();
22932
23053
  init_query_fanout();
22933
- init_permission_guard();
23054
+ init_elasticsearch();
22934
23055
  init_slow_query_advisory();
22935
23056
  ALLOWED_FORMATS3 = ["table", "json", "csv"];
22936
23057
  defaultQueryCommandRuntime = {
@@ -22989,7 +23110,7 @@ function registerQCommand(program2, run) {
22989
23110
  });
22990
23111
  }
22991
23112
  function registerInsertCommand(program2, run) {
22992
- 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) => {
22993
23114
  try {
22994
23115
  await run(table, options, command);
22995
23116
  } catch (error) {
@@ -23003,7 +23124,7 @@ function registerInsertCommand(program2, run) {
23003
23124
  });
23004
23125
  }
23005
23126
  function registerUpdateCommand(program2, run) {
23006
- 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) => {
23007
23128
  try {
23008
23129
  await run(table, options, command);
23009
23130
  } catch (error) {
@@ -23017,7 +23138,7 @@ function registerUpdateCommand(program2, run) {
23017
23138
  });
23018
23139
  }
23019
23140
  function registerDeleteCommand(program2, run) {
23020
- 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) => {
23021
23142
  try {
23022
23143
  await run(table, options, command);
23023
23144
  } catch (error) {
@@ -23738,7 +23859,7 @@ function deepHasScript(value) {
23738
23859
  return Object.values(obj).some(deepHasScript);
23739
23860
  }
23740
23861
  var NAME_RE2, ES_SIZE_CAP = 1000, ES_MAX_RESULT_WINDOW = 1e4, esStrategy;
23741
- var init_elasticsearch = __esm(() => {
23862
+ var init_elasticsearch2 = __esm(() => {
23742
23863
  init_types5();
23743
23864
  NAME_RE2 = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
23744
23865
  esStrategy = {
@@ -23836,7 +23957,7 @@ function applyRedisSizeGuard(command, noLimit) {
23836
23957
  return { command, warnings };
23837
23958
  }
23838
23959
  var REDIS_NAME_RE, REDIS_RANGE_CAP = 1000, RANGE_VERBS, SCAN_VERBS, REDIS_READONLY_VERBS, REDIS_HARD_REJECT, redisStrategy;
23839
- var init_redis = __esm(() => {
23960
+ var init_redis2 = __esm(() => {
23840
23961
  init_types5();
23841
23962
  REDIS_NAME_RE = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
23842
23963
  RANGE_VERBS = new Set(["LRANGE", "ZRANGE", "ZRANGEBYSCORE", "ZRANGEBYLEX"]);
@@ -24035,8 +24156,8 @@ function getStrategy(family) {
24035
24156
  }
24036
24157
  var init_strategies = __esm(() => {
24037
24158
  init_sql();
24038
- init_elasticsearch();
24039
- init_redis();
24159
+ init_elasticsearch2();
24160
+ init_redis2();
24040
24161
  init_mongodb();
24041
24162
  });
24042
24163
 
@@ -25268,8 +25389,8 @@ class DataExecutor {
25268
25389
  const timestamp = new Date().toISOString();
25269
25390
  try {
25270
25391
  this.checkBlacklist("INSERT", tableName);
25271
- enforcePermission("INSERT INTO dummy", this.permission);
25272
- 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 });
25273
25394
  } catch (error) {
25274
25395
  return this.handleMutationError("insert", timestamp, error);
25275
25396
  }
@@ -25278,8 +25399,8 @@ class DataExecutor {
25278
25399
  const timestamp = new Date().toISOString();
25279
25400
  try {
25280
25401
  this.checkBlacklist("UPDATE", tableName);
25281
- enforcePermission("UPDATE dummy", this.permission);
25282
- 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 });
25283
25404
  } catch (error) {
25284
25405
  return this.handleMutationError("update", timestamp, error);
25285
25406
  }
@@ -25288,19 +25409,8 @@ class DataExecutor {
25288
25409
  const timestamp = new Date().toISOString();
25289
25410
  try {
25290
25411
  this.checkBlacklist("DELETE", tableName);
25291
- if (this.permission !== "data-admin" && this.permission !== "admin") {
25292
- return {
25293
- status: "error",
25294
- operation: "delete",
25295
- rows_affected: 0,
25296
- timestamp,
25297
- error: "Permission denied: DELETE operation requires Data-Admin or Admin permission."
25298
- };
25299
- }
25300
- return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, {
25301
- warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
25302
- prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone."
25303
- });
25412
+ enforcePermissionForType("DELETE", this.permission);
25413
+ return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, { destructive: true });
25304
25414
  } catch (error) {
25305
25415
  return this.handleMutationError("delete", timestamp, error);
25306
25416
  }
@@ -25314,31 +25424,31 @@ class DataExecutor {
25314
25424
  checkBlacklist(operation, tableName) {
25315
25425
  this.blacklistValidator?.checkTableBlacklist(operation, tableName, []);
25316
25426
  }
25317
- async executeMutation(operation, statement, timestamp, options, confirmation) {
25427
+ async executeMutation(operation, statement, timestamp, options, { destructive }) {
25318
25428
  if (options?.dryRun) {
25319
- return this.successResult(operation, 0, timestamp, statement.sql);
25429
+ return this.outcomeResult("dry_run", operation, 0, timestamp, statement.sql);
25320
25430
  }
25321
25431
  if (!options?.force) {
25322
- if (confirmation.warning) {
25323
- console.log(`
25324
- ${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.");
25325
25434
  }
25326
- console.log(`
25327
- Generated SQL:`);
25328
- console.log(` ${statement.sql}`);
25329
- console.log(`
25330
- Parameters:`);
25331
- console.log(` ${JSON.stringify(statement.params, null, 2)}`);
25332
- if (!await promptUser.confirm(confirmation.prompt)) {
25333
- 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);
25334
25444
  }
25335
25445
  }
25336
25446
  const result = await this.adapter.execute(statement.sql, statement.params);
25337
- return this.successResult(operation, result.affectedRows, timestamp, statement.sql);
25447
+ return this.outcomeResult("success", operation, result.affectedRows, timestamp, statement.sql);
25338
25448
  }
25339
- successResult(operation, rowsAffected, timestamp, sql) {
25449
+ outcomeResult(status2, operation, rowsAffected, timestamp, sql) {
25340
25450
  return {
25341
- status: "success",
25451
+ status: status2,
25342
25452
  operation,
25343
25453
  rows_affected: rowsAffected,
25344
25454
  timestamp,
@@ -25349,13 +25459,13 @@ Parameters:`);
25349
25459
  if (error instanceof BlacklistError) {
25350
25460
  throw error;
25351
25461
  }
25352
- if (error instanceof PermissionError && operation !== "delete") {
25462
+ if (error instanceof PermissionError) {
25353
25463
  return {
25354
25464
  status: "error",
25355
25465
  operation,
25356
25466
  rows_affected: 0,
25357
25467
  timestamp,
25358
- 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 })
25359
25469
  };
25360
25470
  }
25361
25471
  const errorMessage2 = error instanceof Error ? error.message : String(error);
@@ -25413,8 +25523,152 @@ Parameters:`);
25413
25523
  }
25414
25524
  var init_data_executor = __esm(() => {
25415
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();
25416
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();
25417
25666
  init_blacklist();
25667
+ VERB_KEY = {
25668
+ insert: "ceremony.inserted",
25669
+ update: "ceremony.updated",
25670
+ delete: "ceremony.deleted"
25671
+ };
25418
25672
  });
25419
25673
 
25420
25674
  // src/core/mongo/dry-run-formatter.ts
@@ -26258,6 +26512,9 @@ async function insertCommand(table, options, command) {
26258
26512
  throw new Error("Table name required");
26259
26513
  }
26260
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
+ }
26261
26518
  let jsonInput = "";
26262
26519
  if (options.data) {
26263
26520
  jsonInput = options.data;
@@ -26298,30 +26555,40 @@ async function insertCommand(table, options, command) {
26298
26555
  return;
26299
26556
  }
26300
26557
  if (config.connection.system === "redis") {
26301
- enforcePermission("INSERT INTO dummy", config.permission);
26558
+ enforcePermissionForType("INSERT", config.permission);
26302
26559
  const blacklistManager = new BlacklistManager(config);
26303
26560
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26304
26561
  blacklistValidator.checkTableBlacklist("INSERT", table, []);
26305
26562
  blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
26563
+ const preview = `SET ${table} ... (Redis Insert)`;
26306
26564
  if (options.dryRun) {
26307
26565
  const output = {
26308
- status: "success",
26566
+ status: "dry_run",
26309
26567
  operation: "insert",
26310
26568
  rows_affected: 0,
26311
- sql: `SET ${table} ... (Redis Insert)`,
26569
+ sql: preview,
26312
26570
  timestamp: new Date().toISOString()
26313
26571
  };
26314
- console.log(JSON.stringify(output, null, 2));
26315
- await writeAuditEntry(config, "insert", options, {
26316
- success: true,
26317
- target: table,
26318
- metadata: { rows_affected: 0, dry_run: true }
26319
- });
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));
26320
26586
  return;
26321
26587
  }
26322
26588
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26323
26589
  await adapter2.connect();
26324
26590
  try {
26591
+ const startedAt = performance.now();
26325
26592
  const result = await adapter2.insert(table, data);
26326
26593
  const output = {
26327
26594
  status: "success",
@@ -26329,12 +26596,8 @@ async function insertCommand(table, options, command) {
26329
26596
  rows_affected: result.affectedRows,
26330
26597
  timestamp: new Date().toISOString()
26331
26598
  };
26332
- console.log(JSON.stringify(output, null, 2));
26333
- await writeAuditEntry(config, "insert", options, {
26334
- success: true,
26335
- target: table,
26336
- metadata: { rows_affected: result.affectedRows }
26337
- });
26599
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
26600
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26338
26601
  return;
26339
26602
  } finally {
26340
26603
  await adapter2.disconnect();
@@ -26346,7 +26609,7 @@ async function insertCommand(table, options, command) {
26346
26609
  operation: "insert",
26347
26610
  rows_affected: 0,
26348
26611
  timestamp: new Date().toISOString(),
26349
- 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")
26350
26613
  };
26351
26614
  await writeAuditEntry(config, "insert", options, {
26352
26615
  success: false,
@@ -26357,44 +26620,49 @@ async function insertCommand(table, options, command) {
26357
26620
  process.exit(1);
26358
26621
  }
26359
26622
  if (config.connection.system === "mongodb") {
26360
- enforcePermission("INSERT INTO dummy", config.permission);
26623
+ enforcePermissionForType("INSERT", config.permission);
26361
26624
  const blacklistManager = new BlacklistManager(config);
26362
26625
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26363
26626
  blacklistValidator.checkTableBlacklist("INSERT", table, []);
26364
26627
  blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
26628
+ const preview = previewInsert(table, data);
26365
26629
  if (options.dryRun) {
26366
26630
  const output = {
26367
- status: "success",
26631
+ status: "dry_run",
26368
26632
  operation: "insert",
26369
26633
  rows_affected: 0,
26370
- sql: previewInsert(table, data),
26634
+ sql: preview,
26371
26635
  timestamp: new Date().toISOString()
26372
26636
  };
26373
- console.log(JSON.stringify(output, null, 2));
26374
- await writeAuditEntry(config, "insert", options, {
26375
- success: true,
26376
- target: table,
26377
- metadata: { rows_affected: 0, dry_run: true }
26378
- });
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));
26379
26651
  return;
26380
26652
  }
26381
26653
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26382
26654
  await adapter2.connect();
26383
26655
  try {
26656
+ const startedAt = performance.now();
26384
26657
  const result = await adapter2.insert(table, data);
26385
26658
  const output = {
26386
26659
  status: "success",
26387
26660
  operation: "insert",
26388
26661
  rows_affected: result.affectedRows,
26389
- timestamp: new Date().toISOString(),
26390
- lastInsertId: result.lastInsertId
26662
+ timestamp: new Date().toISOString()
26391
26663
  };
26392
- console.log(JSON.stringify(output, null, 2));
26393
- await writeAuditEntry(config, "insert", options, {
26394
- success: true,
26395
- target: table,
26396
- metadata: { rows_affected: result.affectedRows }
26397
- });
26664
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options), { lastInsertId: result.lastInsertId });
26665
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26398
26666
  return;
26399
26667
  } finally {
26400
26668
  await adapter2.disconnect();
@@ -26408,31 +26676,20 @@ async function insertCommand(table, options, command) {
26408
26676
  const blacklistManager = new BlacklistManager(config);
26409
26677
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26410
26678
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
26679
+ const startedAt = performance.now();
26411
26680
  const result = await executor3.executeInsert(table, data, schema, {
26412
26681
  dryRun: options.dryRun,
26413
- force: options.force
26414
- });
26415
- const output = {
26416
- status: result.status,
26417
- operation: result.operation,
26418
- rows_affected: result.rows_affected,
26419
- timestamp: result.timestamp,
26420
- ...result.sql && { sql: result.sql },
26421
- ...result.error && { error: result.error }
26422
- };
26423
- console.log(JSON.stringify(output, null, 2));
26424
- await writeAuditEntry(config, "insert", options, {
26425
- success: result.status === "success",
26426
- target: table,
26427
- ...result.sql && { sql: result.sql },
26428
- metadata: {
26429
- rows_affected: result.rows_affected,
26430
- ...options.dryRun && { dry_run: true }
26431
- },
26432
- ...result.status === "error" && {
26433
- error: new Error(result.error ?? "insert failed")
26434
- }
26682
+ force: options.force,
26683
+ confirm: confirmMutationInteractively
26435
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));
26436
26693
  if (result.status === "error") {
26437
26694
  process.exit(1);
26438
26695
  }
@@ -26458,13 +26715,7 @@ async function insertCommand(table, options, command) {
26458
26715
  emitRecoveryEnvelope2(error, { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId, auditRef: auditId ?? undefined });
26459
26716
  }
26460
26717
  if (error instanceof BlacklistError) {
26461
- const output2 = {
26462
- status: "error",
26463
- operation: "insert",
26464
- rows_affected: 0,
26465
- error: error.message
26466
- };
26467
- console.log(JSON.stringify(output2, null, 2));
26718
+ printMutationFailure(error, "insert", humanOutputContext(options));
26468
26719
  process.exit(1);
26469
26720
  }
26470
26721
  if (error instanceof PermissionError) {
@@ -26477,13 +26728,7 @@ async function insertCommand(table, options, command) {
26477
26728
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
26478
26729
  process.exit(1);
26479
26730
  }
26480
- const output = {
26481
- status: "error",
26482
- operation: "insert",
26483
- rows_affected: 0,
26484
- error: error.message
26485
- };
26486
- console.log(JSON.stringify(output, null, 2));
26731
+ printMutationFailure(error, "insert", humanOutputContext(options));
26487
26732
  process.exit(1);
26488
26733
  }
26489
26734
  }
@@ -26493,6 +26738,8 @@ var init_insert = __esm(() => {
26493
26738
  init_connection_error_message();
26494
26739
  init_adapters();
26495
26740
  init_data_executor();
26741
+ init_mutation_confirm();
26742
+ init_mutation_outcome();
26496
26743
  init_config();
26497
26744
  init_permission_guard();
26498
26745
  init_blacklist_validator();
@@ -26521,6 +26768,9 @@ async function updateCommand(table, options, command) {
26521
26768
  throw new Error("Table name required");
26522
26769
  }
26523
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
+ }
26524
26774
  if (!options.plan && (!options.where || options.where.trim() === "")) {
26525
26775
  throw new Error('UPDATE requires --where clause (e.g. --where "id=1")');
26526
26776
  }
@@ -26581,29 +26831,39 @@ async function updateCommand(table, options, command) {
26581
26831
  return;
26582
26832
  }
26583
26833
  if (config.connection?.system === "redis") {
26584
- enforcePermission("UPDATE dummy", config.permission);
26834
+ enforcePermissionForType("UPDATE", config.permission);
26585
26835
  const blacklistManager = new BlacklistManager(config);
26586
26836
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26587
26837
  blacklistValidator.checkTableBlacklist("UPDATE", table, []);
26838
+ const preview = `HSET ${table} ... (Redis Update)`;
26588
26839
  if (options.dryRun) {
26589
26840
  const output = {
26590
- status: "success",
26841
+ status: "dry_run",
26591
26842
  operation: "update",
26592
26843
  rows_affected: 0,
26593
- sql: `HSET ${table} ... (Redis Update)`,
26844
+ sql: preview,
26594
26845
  timestamp: new Date().toISOString()
26595
26846
  };
26596
- console.log(JSON.stringify(output, null, 2));
26597
- await writeAuditEntry(config, "update", options, {
26598
- success: true,
26599
- target: table,
26600
- metadata: { rows_affected: 0, dry_run: true }
26601
- });
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));
26602
26861
  return;
26603
26862
  }
26604
26863
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26605
26864
  await adapter2.connect();
26606
26865
  try {
26866
+ const startedAt = performance.now();
26607
26867
  const result = await adapter2.update(table, {}, setData);
26608
26868
  const output = {
26609
26869
  status: "success",
@@ -26611,12 +26871,8 @@ async function updateCommand(table, options, command) {
26611
26871
  rows_affected: result.affectedRows,
26612
26872
  timestamp: new Date().toISOString()
26613
26873
  };
26614
- console.log(JSON.stringify(output, null, 2));
26615
- await writeAuditEntry(config, "update", options, {
26616
- success: true,
26617
- target: table,
26618
- metadata: { rows_affected: result.affectedRows }
26619
- });
26874
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
26875
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26620
26876
  return;
26621
26877
  } finally {
26622
26878
  await adapter2.disconnect();
@@ -26628,7 +26884,7 @@ async function updateCommand(table, options, command) {
26628
26884
  operation: "update",
26629
26885
  rows_affected: 0,
26630
26886
  timestamp: new Date().toISOString(),
26631
- 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")
26632
26888
  };
26633
26889
  await writeAuditEntry(config, "update", options, {
26634
26890
  success: false,
@@ -26639,7 +26895,7 @@ async function updateCommand(table, options, command) {
26639
26895
  process.exit(1);
26640
26896
  }
26641
26897
  if (config.connection?.system === "mongodb") {
26642
- enforcePermission("UPDATE dummy", config.permission);
26898
+ enforcePermissionForType("UPDATE", config.permission);
26643
26899
  const hasOperator = Object.keys(setData).some((key) => key.startsWith("$"));
26644
26900
  const updateDoc = hasOperator ? setData : { $set: setData };
26645
26901
  const writtenFields = new Set;
@@ -26665,25 +26921,35 @@ async function updateCommand(table, options, command) {
26665
26921
  } catch {
26666
26922
  filter = parseWhereClause(options.where);
26667
26923
  }
26924
+ const preview = previewUpdate(table, filter, updateDoc);
26668
26925
  if (options.dryRun) {
26669
26926
  const output = {
26670
- status: "success",
26927
+ status: "dry_run",
26671
26928
  operation: "update",
26672
26929
  rows_affected: 0,
26673
- sql: previewUpdate(table, filter, updateDoc),
26930
+ sql: preview,
26674
26931
  timestamp: new Date().toISOString()
26675
26932
  };
26676
- console.log(JSON.stringify(output, null, 2));
26677
- await writeAuditEntry(config, "update", options, {
26678
- success: true,
26679
- target: table,
26680
- metadata: { rows_affected: 0, dry_run: true }
26681
- });
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));
26682
26947
  return;
26683
26948
  }
26684
26949
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26685
26950
  await adapter2.connect();
26686
26951
  try {
26952
+ const startedAt = performance.now();
26687
26953
  const result = await adapter2.update(table, filter, updateDoc);
26688
26954
  const output = {
26689
26955
  status: "success",
@@ -26691,12 +26957,8 @@ async function updateCommand(table, options, command) {
26691
26957
  rows_affected: result.affectedRows,
26692
26958
  timestamp: new Date().toISOString()
26693
26959
  };
26694
- console.log(JSON.stringify(output, null, 2));
26695
- await writeAuditEntry(config, "update", options, {
26696
- success: true,
26697
- target: table,
26698
- metadata: { rows_affected: result.affectedRows }
26699
- });
26960
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
26961
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26700
26962
  return;
26701
26963
  } finally {
26702
26964
  await adapter2.disconnect();
@@ -26716,31 +26978,20 @@ async function updateCommand(table, options, command) {
26716
26978
  const blacklistManager = new BlacklistManager(config);
26717
26979
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26718
26980
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
26981
+ const startedAt = performance.now();
26719
26982
  const result = await executor3.executeUpdate(table, setData, whereConditions, schema, {
26720
26983
  dryRun: options.dryRun,
26721
- force: options.force
26722
- });
26723
- const output = {
26724
- status: result.status,
26725
- operation: result.operation,
26726
- rows_affected: result.rows_affected,
26727
- timestamp: result.timestamp,
26728
- ...result.sql && { sql: result.sql },
26729
- ...result.error && { error: result.error }
26730
- };
26731
- console.log(JSON.stringify(output, null, 2));
26732
- await writeAuditEntry(config, "update", options, {
26733
- success: result.status === "success",
26734
- target: table,
26735
- ...result.sql && { sql: result.sql },
26736
- metadata: {
26737
- rows_affected: result.rows_affected,
26738
- ...options.dryRun && { dry_run: true }
26739
- },
26740
- ...result.status === "error" && {
26741
- error: new Error(result.error ?? "update failed")
26742
- }
26984
+ force: options.force,
26985
+ confirm: confirmMutationInteractively
26743
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));
26744
26995
  if (result.status === "error") {
26745
26996
  process.exit(1);
26746
26997
  }
@@ -26766,13 +27017,7 @@ async function updateCommand(table, options, command) {
26766
27017
  emitRecoveryEnvelope2(error, { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId, auditRef: auditId ?? undefined });
26767
27018
  }
26768
27019
  if (error instanceof BlacklistError) {
26769
- const output2 = {
26770
- status: "error",
26771
- operation: "update",
26772
- rows_affected: 0,
26773
- error: error.message
26774
- };
26775
- console.log(JSON.stringify(output2, null, 2));
27020
+ printMutationFailure(error, "update", humanOutputContext(options));
26776
27021
  process.exit(1);
26777
27022
  }
26778
27023
  if (error instanceof PermissionError) {
@@ -26785,13 +27030,7 @@ async function updateCommand(table, options, command) {
26785
27030
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
26786
27031
  process.exit(1);
26787
27032
  }
26788
- const output = {
26789
- status: "error",
26790
- operation: "update",
26791
- rows_affected: 0,
26792
- error: error.message
26793
- };
26794
- console.log(JSON.stringify(output, null, 2));
27033
+ printMutationFailure(error, "update", humanOutputContext(options));
26795
27034
  process.exit(1);
26796
27035
  }
26797
27036
  }
@@ -26801,6 +27040,8 @@ var init_update = __esm(() => {
26801
27040
  init_connection_error_message();
26802
27041
  init_adapters();
26803
27042
  init_data_executor();
27043
+ init_mutation_confirm();
27044
+ init_mutation_outcome();
26804
27045
  init_config();
26805
27046
  init_permission_guard();
26806
27047
  init_blacklist_validator();
@@ -26829,6 +27070,9 @@ async function deleteCommand(table, options, command) {
26829
27070
  throw new Error("Table name required");
26830
27071
  }
26831
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
+ }
26832
27076
  if (!options.plan && (!options.where || options.where.trim() === "")) {
26833
27077
  throw new Error('DELETE requires --where clause (e.g. --where "id=1")');
26834
27078
  }
@@ -26875,14 +27119,8 @@ async function deleteCommand(table, options, command) {
26875
27119
  });
26876
27120
  return;
26877
27121
  }
26878
- if (config.permission !== "data-admin" && config.permission !== "admin") {
26879
- throw new PermissionError(t("delete.admin_only"), {
26880
- type: "DELETE",
26881
- isDangerous: true,
26882
- keywords: ["DELETE"],
26883
- isComposite: false,
26884
- confidence: "HIGH"
26885
- }, config.permission);
27122
+ if (!permitsOperation("DELETE", config.permission)) {
27123
+ throw new PermissionError(t("delete.admin_only"), classificationForType("DELETE"), minimumPermissionFor("DELETE"));
26886
27124
  }
26887
27125
  if (config.connection?.system === "redis") {
26888
27126
  const blacklistManager = new BlacklistManager(config);
@@ -26894,25 +27132,35 @@ async function deleteCommand(table, options, command) {
26894
27132
  } catch {
26895
27133
  filter = parseWhereClause(options.where);
26896
27134
  }
27135
+ const preview = `DEL ${table} (Redis Delete)`;
26897
27136
  if (options.dryRun) {
26898
27137
  const output = {
26899
- status: "success",
27138
+ status: "dry_run",
26900
27139
  operation: "delete",
26901
27140
  rows_affected: 0,
26902
- sql: `DEL ${table} (Redis Delete)`,
27141
+ sql: preview,
26903
27142
  timestamp: new Date().toISOString()
26904
27143
  };
26905
- console.log(JSON.stringify(output, null, 2));
26906
- await writeAuditEntry(config, "delete", options, {
26907
- success: true,
26908
- target: table,
26909
- metadata: { rows_affected: 0, dry_run: true }
26910
- });
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));
26911
27158
  return;
26912
27159
  }
26913
27160
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26914
27161
  await adapter2.connect();
26915
27162
  try {
27163
+ const startedAt = performance.now();
26916
27164
  const result = await adapter2.delete(table, filter);
26917
27165
  const output = {
26918
27166
  status: "success",
@@ -26920,12 +27168,8 @@ async function deleteCommand(table, options, command) {
26920
27168
  rows_affected: result.affectedRows,
26921
27169
  timestamp: new Date().toISOString()
26922
27170
  };
26923
- console.log(JSON.stringify(output, null, 2));
26924
- await writeAuditEntry(config, "delete", options, {
26925
- success: true,
26926
- target: table,
26927
- metadata: { rows_affected: result.affectedRows }
26928
- });
27171
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27172
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26929
27173
  return;
26930
27174
  } finally {
26931
27175
  await adapter2.disconnect();
@@ -26937,7 +27181,7 @@ async function deleteCommand(table, options, command) {
26937
27181
  operation: "delete",
26938
27182
  rows_affected: 0,
26939
27183
  timestamp: new Date().toISOString(),
26940
- 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")
26941
27185
  };
26942
27186
  await writeAuditEntry(config, "delete", options, {
26943
27187
  success: false,
@@ -26957,25 +27201,35 @@ async function deleteCommand(table, options, command) {
26957
27201
  } catch {
26958
27202
  filter = parseWhereClause(options.where);
26959
27203
  }
27204
+ const preview = previewDelete(table, filter);
26960
27205
  if (options.dryRun) {
26961
27206
  const output = {
26962
- status: "success",
27207
+ status: "dry_run",
26963
27208
  operation: "delete",
26964
27209
  rows_affected: 0,
26965
- sql: previewDelete(table, filter),
27210
+ sql: preview,
26966
27211
  timestamp: new Date().toISOString()
26967
27212
  };
26968
- console.log(JSON.stringify(output, null, 2));
26969
- await writeAuditEntry(config, "delete", options, {
26970
- success: true,
26971
- target: table,
26972
- metadata: { rows_affected: 0, dry_run: true }
26973
- });
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));
26974
27227
  return;
26975
27228
  }
26976
27229
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26977
27230
  await adapter2.connect();
26978
27231
  try {
27232
+ const startedAt = performance.now();
26979
27233
  const result = await adapter2.delete(table, filter);
26980
27234
  const output = {
26981
27235
  status: "success",
@@ -26983,12 +27237,8 @@ async function deleteCommand(table, options, command) {
26983
27237
  rows_affected: result.affectedRows,
26984
27238
  timestamp: new Date().toISOString()
26985
27239
  };
26986
- console.log(JSON.stringify(output, null, 2));
26987
- await writeAuditEntry(config, "delete", options, {
26988
- success: true,
26989
- target: table,
26990
- metadata: { rows_affected: result.affectedRows }
26991
- });
27240
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27241
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26992
27242
  return;
26993
27243
  } finally {
26994
27244
  await adapter2.disconnect();
@@ -27008,31 +27258,20 @@ async function deleteCommand(table, options, command) {
27008
27258
  const blacklistManager = new BlacklistManager(config);
27009
27259
  const blacklistValidator = new BlacklistValidator(blacklistManager);
27010
27260
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
27261
+ const startedAt = performance.now();
27011
27262
  const result = await executor3.executeDelete(table, whereConditions, schema, {
27012
27263
  dryRun: options.dryRun,
27013
- force: options.force
27014
- });
27015
- const output = {
27016
- status: result.status,
27017
- operation: result.operation,
27018
- rows_affected: result.rows_affected,
27019
- timestamp: result.timestamp,
27020
- ...result.sql && { sql: result.sql },
27021
- ...result.error && { error: result.error }
27022
- };
27023
- console.log(JSON.stringify(output, null, 2));
27024
- await writeAuditEntry(config, "delete", options, {
27025
- success: result.status === "success",
27026
- target: table,
27027
- ...result.sql && { sql: result.sql },
27028
- metadata: {
27029
- rows_affected: result.rows_affected,
27030
- ...options.dryRun && { dry_run: true }
27031
- },
27032
- ...result.status === "error" && {
27033
- error: new Error(result.error ?? "delete failed")
27034
- }
27264
+ force: options.force,
27265
+ confirm: confirmMutationInteractively
27035
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));
27036
27275
  if (result.status === "error") {
27037
27276
  process.exit(1);
27038
27277
  }
@@ -27058,17 +27297,11 @@ async function deleteCommand(table, options, command) {
27058
27297
  emitRecoveryEnvelope2(error, { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId, auditRef: auditId ?? undefined });
27059
27298
  }
27060
27299
  if (error instanceof BlacklistError) {
27061
- const output2 = {
27062
- status: "error",
27063
- operation: "delete",
27064
- rows_affected: 0,
27065
- error: error.message
27066
- };
27067
- console.log(JSON.stringify(output2, null, 2));
27300
+ printMutationFailure(error, "delete", humanOutputContext(options));
27068
27301
  process.exit(1);
27069
27302
  }
27070
27303
  if (error instanceof PermissionError) {
27071
- console.error(t_vars("errors.permission_denied", { required: "data-admin" }));
27304
+ console.error(t_vars("errors.permission_denied", { required: error.requiredPermission }));
27072
27305
  console.error(` Operation: ${error.classification.type}`);
27073
27306
  console.error(` Message: ${error.message}`);
27074
27307
  process.exit(1);
@@ -27077,13 +27310,7 @@ async function deleteCommand(table, options, command) {
27077
27310
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
27078
27311
  process.exit(1);
27079
27312
  }
27080
- const output = {
27081
- status: "error",
27082
- operation: "delete",
27083
- rows_affected: 0,
27084
- error: error.message
27085
- };
27086
- console.log(JSON.stringify(output, null, 2));
27313
+ printMutationFailure(error, "delete", humanOutputContext(options));
27087
27314
  process.exit(1);
27088
27315
  }
27089
27316
  }
@@ -27093,6 +27320,8 @@ var init_delete = __esm(() => {
27093
27320
  init_connection_error_message();
27094
27321
  init_adapters();
27095
27322
  init_data_executor();
27323
+ init_mutation_confirm();
27324
+ init_mutation_outcome();
27096
27325
  init_config();
27097
27326
  init_permission_guard();
27098
27327
  init_blacklist_validator();
@@ -27256,7 +27485,7 @@ async function exportCommand(sql, options, command) {
27256
27485
  }
27257
27486
  }
27258
27487
  async function redisExportBranch(command, options, config) {
27259
- 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));
27260
27489
  enforceRedisPermission2(command, config.permission);
27261
27490
  const redisAdapter = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? [], config.redis?.mask ?? []);
27262
27491
  await redisAdapter.connect();
@@ -44132,6 +44361,7 @@ var init_repl_engine = __esm(() => {
44132
44361
  init_meta_commands();
44133
44362
  init_history_manager();
44134
44363
  init_permission_guard();
44364
+ init_redis();
44135
44365
  init_query_result_formatter();
44136
44366
  init_message_loader();
44137
44367
  init_sql_tables();
@@ -45260,13 +45490,16 @@ class DDLExecutor {
45260
45490
  };
45261
45491
  }
45262
45492
  if (isDestructiveOperation2(operation) && !options.force) {
45263
- 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 });
45264
45497
  if (!confirmed) {
45265
45498
  return {
45266
- status: "success",
45499
+ status: "cancelled",
45267
45500
  operation: operation.kind,
45268
45501
  sql,
45269
- warnings: [...warnings, "Operation cancelled by user"],
45502
+ warnings,
45270
45503
  timestamp: timestamp2,
45271
45504
  dryRun: false
45272
45505
  };
@@ -45341,8 +45574,15 @@ class DDLExecutor {
45341
45574
  } catch {}
45342
45575
  }
45343
45576
  }
45577
+ var DDLExecutionError;
45344
45578
  var init_ddl_executor = __esm(() => {
45345
- 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
+ };
45346
45586
  });
45347
45587
 
45348
45588
  // src/commands/migrate.ts
@@ -45386,7 +45626,8 @@ async function runDDL(operation, opts, command) {
45386
45626
  const executor3 = new DDLExecutor(adapter, generator, config.permission, blacklistManager);
45387
45627
  const result = await executor3.execute(operation, {
45388
45628
  execute: opts.execute,
45389
- force: opts.force
45629
+ force: opts.force,
45630
+ confirm: confirmDdlInteractively
45390
45631
  });
45391
45632
  console.log(JSON.stringify({
45392
45633
  status: result.status,
@@ -45428,6 +45669,7 @@ var init_migrate = __esm(() => {
45428
45669
  init_adapters();
45429
45670
  init_ddl2();
45430
45671
  init_ddl_executor();
45672
+ init_mutation_confirm();
45431
45673
  init_config_path();
45432
45674
  migrateCommand = new Command("migrate").description(t("migrate.description"));
45433
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) => {