@carllee1983/dbcli 1.57.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: "2.0.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,94 @@ 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
+ gate_summary: "This will {operation} rows in {table}.",
9477
+ gate_summary_untargeted: "This will run a {operation} statement.",
9478
+ gate_statement: "Statement:",
9479
+ gate_cancelled: "Cancelled. Nothing was run.",
9480
+ gate_full_table_warning: "\u26A0\uFE0F This affects EVERY row in {table} and cannot be undone.",
9481
+ gate_ddl_warning: "\u26A0\uFE0F This destroys {table} and cannot be undone.",
9482
+ gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
9483
+ gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
9484
+ gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
9485
+ gate_typed_prompt: "Type {phrase} to run it",
9486
+ gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
9487
+ gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
9488
+ gate_remedy_no_where: "Add a WHERE clause \u2014 WHERE 1=1 if you really do mean every row \u2014 or a LIMIT, then run it again.",
9489
+ gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
9490
+ gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
9491
+ gate_remedy_multiple_statements: "Run one statement per command.",
9492
+ gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
9493
+ recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
9494
+ recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
9495
+ blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
9496
+ };
9497
+ });
9498
+
9499
+ // resources/lang/zh-TW/ceremony.json
9500
+ var ceremony_default2;
9501
+ var init_ceremony2 = __esm(() => {
9502
+ ceremony_default2 = {
9503
+ updated: "\u5DF2\u66F4\u65B0 {table} \u7684 {count} \u5217",
9504
+ deleted: "\u5DF2\u5F9E {table} \u522A\u9664 {count} \u5217",
9505
+ inserted: "\u5DF2\u5BEB\u5165 {count} \u5217\u5230 {table}",
9506
+ matched_nothing: "{table} \u6C92\u6709\u4EFB\u4F55\u5217\u7B26\u5408\u689D\u4EF6\uFF0C\u8CC7\u6599\u672A\u8B8A\u52D5",
9507
+ cancelled: "\u5DF2\u53D6\u6D88\uFF0C{table} \u672A\u8B8A\u52D5",
9508
+ dry_run: "\u50C5\u9810\u89BD\uFF0C{table} \u672A\u8B8A\u52D5",
9509
+ elapsed: "\u8017\u6642 {seconds} \u79D2",
9510
+ failed: "\u5931\u6557\uFF1A{reason}",
9511
+ unknown_error: "\u672A\u77E5\u7684\u932F\u8AA4",
9512
+ confirm_sql: "\u7522\u751F\u7684 SQL\uFF1A",
9513
+ confirm_command: "\u5373\u5C07\u57F7\u884C\u7684\u6307\u4EE4\uFF1A",
9514
+ confirm_params: "\u53C3\u6578\uFF1A",
9515
+ confirm_destructive_warning: "\u26A0\uFE0F \u8B66\u544A\uFF1ADELETE \u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
9516
+ confirm_destructive_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B DELETE \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
9517
+ 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",
9518
+ confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
9519
+ confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
9520
+ gate_summary: "\u9019\u53E5\u6703\u5C0D {table} \u57F7\u884C {operation}\u3002",
9521
+ gate_summary_untargeted: "\u9019\u53E5\u6703\u57F7\u884C {operation}\u3002",
9522
+ gate_statement: "\u8A9E\u53E5\uFF1A",
9523
+ gate_cancelled: "\u5DF2\u53D6\u6D88\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
9524
+ gate_full_table_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u5F71\u97FF {table} \u7684\u6BCF\u4E00\u5217\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
9525
+ gate_ddl_warning: "\u26A0\uFE0F \u9019\u53E5\u6703\u6467\u6BC0 {table}\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
9526
+ gate_ddl_warning_untargeted: "\u26A0\uFE0F \u9019\u662F\u7834\u58DE\u6027\u7684 schema \u8B8A\u66F4\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\u3002",
9527
+ gate_unparseable_warning: "\u26A0\uFE0F dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u56E0\u6B64\u4E00\u5F8B\u8996\u70BA\u6703\u5F71\u97FF\u5168\u90E8\u8CC7\u6599\u3002",
9528
+ gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
9529
+ gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
9530
+ gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
9531
+ gate_refused: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u53E5\u6C92\u6709\u9650\u5B9A\u8981\u52D5\u54EA\u4E9B\u5217\uFF0C\u800C\u73FE\u5728\u6C92\u6709\u4EBA\u80FD\u78BA\u8A8D\uFF08reason={reason}\uFF09\u3002",
9532
+ gate_remedy_no_where: "\u88DC\u4E0A WHERE\uFF08\u771F\u7684\u8981\u5168\u8868\u5C31\u5BEB WHERE 1=1\uFF09\u6216 LIMIT\uFF0C\u7136\u5F8C\u91CD\u8DD1\u3002",
9533
+ gate_remedy_ddl_destruction: "\u6C92\u6709\u4EFB\u4F55\u65D7\u6A19\u53EF\u4EE5\u8DF3\u904E\u9019\u4E00\u95DC\u3002\u8ACB\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\uFF0C\u6216\u6539\u8D70\u7D93\u904E\u5BE9\u67E5\u7684 migration\u3002",
9534
+ gate_remedy_unparseable: "dbcli \u7121\u6CD5\u89E3\u6790\u9019\u53E5\uFF0C\u4E5F\u4E0D\u6703\u7528\u731C\u7684\u3002\u8ACB\u7C21\u5316\u8A9E\u53E5\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
9535
+ gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
9536
+ gate_remedy_non_unique_where: "\u6539\u7528\u4E3B\u9375\u6216\u552F\u4E00\u7D22\u5F15\u6B04\u4F4D\u7576\u689D\u4EF6\uFF0C\u6216\u5728\u4E92\u52D5\u7D42\u7AEF\u4E0B\u57F7\u884C\u3002",
9537
+ 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",
9538
+ recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
9539
+ blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
9540
+ };
9541
+ });
9542
+
9433
9543
  // src/i18n/message-loader.ts
9434
9544
  class MessageLoader {
9435
9545
  static instance = null;
@@ -9496,9 +9606,15 @@ var init_message_loader = __esm(() => {
9496
9606
  init_messages2();
9497
9607
  init_shell();
9498
9608
  init_shell2();
9609
+ init_ceremony();
9610
+ init_ceremony2();
9499
9611
  BUNDLED_MESSAGES = {
9500
- en: { ...messages_default, shell: shell_default },
9501
- "zh-TW": { ...messages_default2, shell: shell_default2 }
9612
+ en: { ...messages_default, shell: shell_default, ceremony: ceremony_default },
9613
+ "zh-TW": {
9614
+ ...messages_default2,
9615
+ shell: shell_default2,
9616
+ ceremony: ceremony_default2
9617
+ }
9502
9618
  };
9503
9619
  messageLoader = MessageLoader.getInstance();
9504
9620
  });
@@ -9648,7 +9764,8 @@ async function readLineFromStdin(prompt = "") {
9648
9764
  }
9649
9765
  async function textFallback(message, defaultValue) {
9650
9766
  const displayMessage = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
9651
- const answer = await readLineFromStdin(displayMessage);
9767
+ process.stderr.write(displayMessage);
9768
+ const answer = await readLineFromStdin();
9652
9769
  return answer.trim() || defaultValue || "";
9653
9770
  }
9654
9771
  async function selectFallback(message, choices) {
@@ -9664,7 +9781,8 @@ async function selectFallback(message, choices) {
9664
9781
  return choices[0] ?? "";
9665
9782
  }
9666
9783
  async function confirmFallback(message) {
9667
- const answer = await readLineFromStdin(`${message} (y/n): `);
9784
+ process.stderr.write(`${message} (y/n): `);
9785
+ const answer = await readLineFromStdin();
9668
9786
  return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
9669
9787
  }
9670
9788
  async function text(message, defaultValue) {
@@ -9673,7 +9791,7 @@ async function text(message, defaultValue) {
9673
9791
  const inquirer = await loadInquirer();
9674
9792
  if (!inquirer)
9675
9793
  return textFallback(message, defaultValue);
9676
- return await inquirer.input({ message, default: defaultValue });
9794
+ return await inquirer.input({ message, default: defaultValue }, { output: process.stderr });
9677
9795
  }
9678
9796
  async function select(message, choices) {
9679
9797
  if (!process.stdin.isTTY)
@@ -9696,7 +9814,7 @@ async function confirm(message) {
9696
9814
  const inquirer = await loadInquirer();
9697
9815
  if (!inquirer)
9698
9816
  return await confirmFallback(message);
9699
- return await inquirer.confirm({ message });
9817
+ return await inquirer.confirm({ message }, { output: process.stderr });
9700
9818
  }
9701
9819
  var promptsUnavailableReported = false, promptUser;
9702
9820
  var init_prompts = __esm(() => {
@@ -18555,33 +18673,7 @@ var init_sql_lexical = __esm(() => {
18555
18673
  DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
18556
18674
  });
18557
18675
 
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
- });
18676
+ // src/core/permission/sql-analysis.ts
18585
18677
  function normalizeSQL(sql) {
18586
18678
  return sql.replace(/--[^\n]*\n/g, `
18587
18679
  `).replace(/\/\*[\s\S]*?\*\//g, " ").trim().replace(/\s+/g, " ");
@@ -18828,6 +18920,29 @@ function determineConfidence(type, _keyword, _sql) {
18828
18920
  function removeParameterMarkers(sql) {
18829
18921
  return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
18830
18922
  }
18923
+ var init_sql_analysis = __esm(() => {
18924
+ init_sql_lexical();
18925
+ });
18926
+
18927
+ // src/core/permission-guard.ts
18928
+ var exports_permission_guard = {};
18929
+ __export(exports_permission_guard, {
18930
+ toSqlDialect: () => toSqlDialect,
18931
+ permitsOperation: () => permitsOperation,
18932
+ permissionAtLeast: () => permissionAtLeast,
18933
+ minimumPermissionFor: () => minimumPermissionFor,
18934
+ findWriteKeyword: () => findWriteKeyword,
18935
+ enforcePermissionForType: () => enforcePermissionForType,
18936
+ enforcePermission: () => enforcePermission,
18937
+ containsMultipleStatements: () => containsMultipleStatements,
18938
+ classifyStatement: () => classifyStatement,
18939
+ classificationForType: () => classificationForType,
18940
+ checkPermissionForClassification: () => checkPermissionForClassification,
18941
+ checkPermission: () => checkPermission,
18942
+ SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
18943
+ SQL_DIALECTS: () => SQL_DIALECTS,
18944
+ PermissionError: () => PermissionError
18945
+ });
18831
18946
  function classifyStatement(sql) {
18832
18947
  const normalized = normalizeSQL(sql);
18833
18948
  const stripped = stripCommentsAndStrings(normalized);
@@ -18893,15 +19008,33 @@ function checkPermission(sql, permission, dialect) {
18893
19008
  if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
18894
19009
  return {
18895
19010
  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
19011
+ reason: t("errors.multiple_statements_refused"),
19012
+ classification,
19013
+ requiredPermission: "admin"
18898
19014
  };
18899
19015
  }
19016
+ return checkPermissionForClassification(classification, permission);
19017
+ }
19018
+ function minimumPermissionFor(type) {
19019
+ return TIER_GRANTS.find((tier) => tier.types.includes(type))?.permission ?? "admin";
19020
+ }
19021
+ function refusalReason(type, permission) {
19022
+ return t_vars("errors.permission_requires_level", {
19023
+ type,
19024
+ minimum: minimumPermissionFor(type),
19025
+ permission
19026
+ });
19027
+ }
19028
+ function checkPermissionForClassification(classification, permission) {
18900
19029
  if (permission !== "admin" && classification.escalatedFrom) {
18901
19030
  return {
18902
19031
  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
19032
+ reason: t_vars("errors.escalated_write_requires_admin", {
19033
+ keyword: classification.escalatedFrom,
19034
+ permission
19035
+ }),
19036
+ classification,
19037
+ requiredPermission: "admin"
18905
19038
  };
18906
19039
  }
18907
19040
  if (permission === "admin") {
@@ -18911,220 +19044,64 @@ function checkPermission(sql, permission, dialect) {
18911
19044
  classification
18912
19045
  };
18913
19046
  }
18914
- if (permission === "data-admin") {
18915
- const allowedTypes = ["SELECT", "INSERT", "UPDATE", "DELETE", "SHOW", "DESCRIBE", "EXPLAIN"];
19047
+ const tierIndex = TIER_GRANTS.findIndex((tier) => tier.permission === permission);
19048
+ if (tierIndex !== -1) {
19049
+ const allowedTypes = TIER_GRANTS.slice(0, tierIndex + 1).flatMap((tier) => tier.types);
18916
19050
  if (allowedTypes.includes(classification.type)) {
18917
19051
  return {
18918
19052
  allowed: true,
18919
- reason: `${classification.type} operation allowed in data-admin mode`,
19053
+ reason: `${classification.type} operation allowed in ${permission} mode`,
18920
19054
  classification
18921
19055
  };
18922
19056
  }
19057
+ const isUnknown = permission === "query-only" && classification.type === "UNKNOWN";
18923
19058
  return {
18924
19059
  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
19060
+ reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
19061
+ classification,
19062
+ requiredPermission: isUnknown ? "read-write" : minimumPermissionFor(classification.type)
18958
19063
  };
18959
19064
  }
18960
19065
  return {
18961
19066
  allowed: false,
18962
19067
  reason: `Unknown permission level: ${permission}`,
18963
- classification
19068
+ classification,
19069
+ requiredPermission: "admin"
18964
19070
  };
18965
19071
  }
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";
19072
+ function classificationForType(type) {
18987
19073
  return {
18988
- command: head,
18989
- requiredPermission: required ?? "unknown",
18990
19074
  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],
19075
+ isDangerous: isDestructiveOperation(type),
19076
+ keywords: [type],
19004
19077
  isComposite: false,
19005
- confidence: required === "unknown" ? "LOW" : "HIGH"
19078
+ confidence: "HIGH"
19006
19079
  };
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
19080
  }
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
- };
19081
+ function permitsOperation(type, permission) {
19082
+ return checkPermissionForClassification(classificationForType(type), permission).allowed;
19064
19083
  }
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 {}
19084
+ function enforcePermissionForType(type, permission) {
19085
+ const classification = classificationForType(type);
19086
+ const result = checkPermissionForClassification(classification, permission);
19087
+ if (!result.allowed) {
19088
+ throw new PermissionError(result.reason, classification, minimumPermissionFor(type));
19086
19089
  }
19087
- return {
19088
- type: highestType,
19089
- isDangerous,
19090
- keywords: ["BULK"],
19091
- isComposite: true,
19092
- confidence: "HIGH"
19093
- };
19094
19090
  }
19095
- function enforceElasticsearchPermission(request, permission) {
19096
- const classification = classifyElasticsearchRequest(request);
19097
- const result = checkElasticsearchPermission(classification, permission);
19091
+ function enforcePermission(sql, permission, dialect) {
19092
+ const result = checkPermission(sql, permission, dialect);
19098
19093
  if (!result.allowed) {
19099
- throw new PermissionError(result.reason, classification, permission);
19094
+ throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
19100
19095
  }
19101
- return classification;
19096
+ return result.classification;
19102
19097
  }
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
- };
19098
+ function permissionAtLeast(actual, required) {
19099
+ return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
19124
19100
  }
19125
- var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, REDIS_COMMAND_PERMISSION, PERMISSION_RANK;
19101
+ var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, TIER_GRANTS, PERMISSION_RANK;
19126
19102
  var init_permission_guard = __esm(() => {
19127
- init_sql_lexical();
19103
+ init_message_loader();
19104
+ init_sql_analysis();
19128
19105
  PermissionError = class PermissionError extends Error {
19129
19106
  classification;
19130
19107
  requiredPermission;
@@ -19140,90 +19117,11 @@ var init_permission_guard = __esm(() => {
19140
19117
  SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
19141
19118
  SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
19142
19119
  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
- };
19120
+ TIER_GRANTS = [
19121
+ { permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
19122
+ { permission: "read-write", types: ["INSERT", "UPDATE"] },
19123
+ { permission: "data-admin", types: ["DELETE"] }
19124
+ ];
19227
19125
  PERMISSION_RANK = {
19228
19126
  "query-only": 1,
19229
19127
  "read-write": 2,
@@ -21914,6 +21812,7 @@ function inferColumnType(value) {
21914
21812
  }
21915
21813
  var init_query_executor = __esm(() => {
21916
21814
  init_permission_guard();
21815
+ init_sql_analysis();
21917
21816
  init_error_suggester();
21918
21817
  init_sql_tables();
21919
21818
  init_applied_limit();
@@ -22213,6 +22112,617 @@ function assertFanOutReadOnlySql(sql, dialect) {
22213
22112
  var init_query_fanout = __esm(() => {
22214
22113
  init_cli_error();
22215
22114
  init_permission_guard();
22115
+ init_sql_analysis();
22116
+ });
22117
+
22118
+ // src/core/permission/elasticsearch.ts
22119
+ function classifyElasticsearchRequest(request) {
22120
+ const method = request.method.toUpperCase();
22121
+ const path2 = request.apiPath.toLowerCase();
22122
+ if (path2.includes("_bulk")) {
22123
+ return classifyElasticsearchBulk(request.body ?? "");
22124
+ }
22125
+ const readMethod = method === "GET" || method === "HEAD";
22126
+ const searchPath = path2.includes("_search") || path2.includes("_count");
22127
+ if (searchPath && (readMethod || method === "POST") || readMethod && (path2.includes("_mapping") || path2.includes("_settings") || path2.includes("_alias") || path2.includes("_doc") || path2.includes("_source"))) {
22128
+ return {
22129
+ type: "SELECT",
22130
+ isDangerous: false,
22131
+ keywords: [method, path2],
22132
+ isComposite: false,
22133
+ confidence: "HIGH"
22134
+ };
22135
+ }
22136
+ if (path2.includes("_update") || method === "POST" && path2.includes("_doc")) {
22137
+ return {
22138
+ type: "UPDATE",
22139
+ isDangerous: false,
22140
+ keywords: [method, path2],
22141
+ isComposite: false,
22142
+ confidence: "HIGH"
22143
+ };
22144
+ }
22145
+ if (method === "PUT" && (path2.includes("_doc") || path2.includes("_create"))) {
22146
+ return {
22147
+ type: "INSERT",
22148
+ isDangerous: false,
22149
+ keywords: [method, path2],
22150
+ isComposite: false,
22151
+ confidence: "HIGH"
22152
+ };
22153
+ }
22154
+ if (method === "DELETE") {
22155
+ if (ES_DOCUMENT_PATH.test(path2)) {
22156
+ return {
22157
+ type: "DELETE",
22158
+ isDangerous: true,
22159
+ keywords: [method, path2],
22160
+ isComposite: false,
22161
+ confidence: "HIGH"
22162
+ };
22163
+ }
22164
+ return {
22165
+ type: "DROP",
22166
+ isDangerous: true,
22167
+ keywords: [method, path2],
22168
+ isComposite: false,
22169
+ confidence: "HIGH"
22170
+ };
22171
+ }
22172
+ return {
22173
+ type: "DROP",
22174
+ isDangerous: true,
22175
+ keywords: [method, path2],
22176
+ isComposite: false,
22177
+ confidence: "LOW"
22178
+ };
22179
+ }
22180
+ function classifyElasticsearchBulk(body) {
22181
+ const lines = body.split(`
22182
+ `).filter((l) => l.trim().length > 0);
22183
+ let highestType = "SELECT";
22184
+ let isDangerous = false;
22185
+ for (const line of lines) {
22186
+ try {
22187
+ const action = JSON.parse(line);
22188
+ const op = Object.keys(action)[0];
22189
+ if (op === "delete") {
22190
+ highestType = "DELETE";
22191
+ isDangerous = true;
22192
+ break;
22193
+ }
22194
+ if (op === "update" && highestType !== "DELETE") {
22195
+ highestType = "UPDATE";
22196
+ }
22197
+ if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
22198
+ highestType = "INSERT";
22199
+ }
22200
+ } catch {}
22201
+ }
22202
+ return {
22203
+ type: highestType,
22204
+ isDangerous,
22205
+ keywords: ["BULK"],
22206
+ isComposite: true,
22207
+ confidence: "HIGH"
22208
+ };
22209
+ }
22210
+ function enforceElasticsearchPermission(request, permission) {
22211
+ const classification = classifyElasticsearchRequest(request);
22212
+ const result = checkElasticsearchPermission(classification, permission);
22213
+ if (!result.allowed) {
22214
+ throw new PermissionError(result.reason, classification, result.requiredPermission ?? "admin");
22215
+ }
22216
+ return classification;
22217
+ }
22218
+ function checkElasticsearchPermission(classification, permission) {
22219
+ if (permitsOperation(classification.type, permission)) {
22220
+ return { allowed: true, reason: `${classification.type} allowed in ${permission} mode` };
22221
+ }
22222
+ const minimum = minimumPermissionFor(classification.type);
22223
+ return {
22224
+ allowed: false,
22225
+ reason: t_vars("errors.elasticsearch_requires_level", {
22226
+ type: classification.type,
22227
+ minimum,
22228
+ permission
22229
+ }),
22230
+ requiredPermission: minimum
22231
+ };
22232
+ }
22233
+ var ES_DOCUMENT_PATH;
22234
+ var init_elasticsearch = __esm(() => {
22235
+ init_permission_guard();
22236
+ init_message_loader();
22237
+ ES_DOCUMENT_PATH = /\/(?:_doc|_source)\/[^/?]+/;
22238
+ });
22239
+
22240
+ // src/commands/mutation-outcome.ts
22241
+ var exports_mutation_outcome = {};
22242
+ __export(exports_mutation_outcome, {
22243
+ shouldRenderForHuman: () => shouldRenderForHuman,
22244
+ renderMutationOutcome: () => renderMutationOutcome,
22245
+ printMutationOutcome: () => printMutationOutcome,
22246
+ printMutationFailure: () => printMutationFailure,
22247
+ humanOutputContext: () => humanOutputContext,
22248
+ cancelledOutcome: () => cancelledOutcome
22249
+ });
22250
+ function shouldRenderForHuman(context) {
22251
+ if (context.format === "json")
22252
+ return false;
22253
+ return context.isTTY;
22254
+ }
22255
+ function humanOutputContext(options) {
22256
+ return { isTTY: process.stdout.isTTY === true, ...options.format && { format: options.format } };
22257
+ }
22258
+ function renderMutationOutcome(result, table, elapsedMs) {
22259
+ if (result.status === "error") {
22260
+ return [
22261
+ t_vars("ceremony.failed", { reason: result.error ?? t("ceremony.unknown_error") }),
22262
+ t("ceremony.recovery_retry")
22263
+ ];
22264
+ }
22265
+ if (result.status === "cancelled") {
22266
+ return [t_vars("ceremony.cancelled", { table })];
22267
+ }
22268
+ if (result.status === "dry_run") {
22269
+ return [t_vars("ceremony.dry_run", { table })];
22270
+ }
22271
+ const elapsed = t_vars("ceremony.elapsed", { seconds: (elapsedMs / 1000).toFixed(2) });
22272
+ if (result.rows_affected === 0) {
22273
+ return [t_vars("ceremony.matched_nothing", { table }), elapsed];
22274
+ }
22275
+ return [
22276
+ t_vars(VERB_KEY[result.operation], { count: result.rows_affected, table }),
22277
+ elapsed,
22278
+ t("ceremony.recovery_none")
22279
+ ];
22280
+ }
22281
+ function printMutationOutcome(result, table, elapsedMs, context, envelopeExtras = {}) {
22282
+ if (shouldRenderForHuman(context)) {
22283
+ const writeLine = result.status === "error" ? console.error : console.log;
22284
+ for (const line of renderMutationOutcome(result, table, elapsedMs))
22285
+ writeLine(line);
22286
+ return;
22287
+ }
22288
+ console.log(JSON.stringify({ ...mutationEnvelope(result), ...envelopeExtras }, null, 2));
22289
+ }
22290
+ function printMutationFailure(error, operation, context) {
22291
+ if (shouldRenderForHuman(context)) {
22292
+ console.error(t_vars("ceremony.failed", { reason: error.message }));
22293
+ if (error instanceof BlacklistError) {
22294
+ console.error(t("ceremony.blacklist_hint"));
22295
+ }
22296
+ return;
22297
+ }
22298
+ console.log(JSON.stringify({ status: "error", operation, rows_affected: 0, error: error.message }, null, 2));
22299
+ }
22300
+ function cancelledOutcome(operation, preview) {
22301
+ return {
22302
+ status: "cancelled",
22303
+ operation,
22304
+ rows_affected: 0,
22305
+ sql: preview,
22306
+ timestamp: new Date().toISOString()
22307
+ };
22308
+ }
22309
+ function mutationEnvelope(result) {
22310
+ return {
22311
+ status: result.status,
22312
+ operation: result.operation,
22313
+ rows_affected: result.rows_affected,
22314
+ ...result.timestamp && { timestamp: result.timestamp },
22315
+ ...result.sql && { sql: result.sql },
22316
+ ...result.error && { error: result.error }
22317
+ };
22318
+ }
22319
+ var VERB_KEY;
22320
+ var init_mutation_outcome = __esm(() => {
22321
+ init_message_loader();
22322
+ init_blacklist();
22323
+ VERB_KEY = {
22324
+ insert: "ceremony.inserted",
22325
+ update: "ceremony.updated",
22326
+ delete: "ceremony.deleted"
22327
+ };
22328
+ });
22329
+
22330
+ // src/commands/write-gate-prompt.ts
22331
+ var exports_write_gate_prompt = {};
22332
+ __export(exports_write_gate_prompt, {
22333
+ enforceWriteGate: () => enforceWriteGate,
22334
+ WriteGateRefusal: () => WriteGateRefusal
22335
+ });
22336
+ async function enforceWriteGate(request) {
22337
+ const { verdict, human } = request;
22338
+ if (verdict.tier === "none")
22339
+ return true;
22340
+ const attended = isAttended(human);
22341
+ if (verdict.tier === "two")
22342
+ return await enforceTierTwo(request, attended);
22343
+ if (request.yes === true)
22344
+ return true;
22345
+ if (!attended)
22346
+ return true;
22347
+ writeSummary(request);
22348
+ const proceed = await promptUser.confirm(t("ceremony.confirm_prompt"));
22349
+ if (!proceed)
22350
+ process.stderr.write(`${t("ceremony.gate_cancelled")}
22351
+ `);
22352
+ return proceed;
22353
+ }
22354
+ function isAttended(human) {
22355
+ return shouldRenderForHuman(human) && process.stdin.isTTY === true;
22356
+ }
22357
+ async function enforceTierTwo(request, attended) {
22358
+ const { verdict } = request;
22359
+ const reason = verdict.reason ?? "no_where";
22360
+ if (!attended) {
22361
+ throw new WriteGateRefusal(`${t_vars("ceremony.gate_refused", { reason })} ${t(REMEDY_KEY[reason])}`, reason, verdict.table);
22362
+ }
22363
+ writeSummary(request);
22364
+ const warning = verdict.table === undefined && reason === "ddl_destruction" ? t("ceremony.gate_ddl_warning_untargeted") : t_vars(WARNING_KEY[reason], { table: verdict.table ?? "" });
22365
+ process.stderr.write(`
22366
+ ${warning}
22367
+ ${t(REMEDY_KEY[reason])}
22368
+ `);
22369
+ const typed = await promptUser.text(t_vars("ceremony.gate_typed_prompt", { phrase: verdict.confirmationPhrase }));
22370
+ const matched = typed.trim().toLowerCase() === verdict.confirmationPhrase.toLowerCase();
22371
+ if (!matched) {
22372
+ process.stderr.write(`${t_vars("ceremony.gate_typed_mismatch", { phrase: verdict.confirmationPhrase })}
22373
+ `);
22374
+ }
22375
+ return matched;
22376
+ }
22377
+ function writeSummary(request) {
22378
+ const { verdict, operation, statement } = request;
22379
+ const summary = verdict.table ? t_vars("ceremony.gate_summary", { operation, table: verdict.table }) : t_vars("ceremony.gate_summary_untargeted", { operation });
22380
+ process.stderr.write(`
22381
+ ${summary}
22382
+ ${t("ceremony.gate_statement")}
22383
+ ${statement}
22384
+ `);
22385
+ }
22386
+ var WriteGateRefusal, REMEDY_KEY, WARNING_KEY;
22387
+ var init_write_gate_prompt = __esm(() => {
22388
+ init_message_loader();
22389
+ init_prompts();
22390
+ init_mutation_outcome();
22391
+ WriteGateRefusal = class WriteGateRefusal extends Error {
22392
+ code = "WRITE_GATE_REFUSED";
22393
+ reason;
22394
+ table;
22395
+ constructor(message, reason, table) {
22396
+ super(message);
22397
+ this.name = "WriteGateRefusal";
22398
+ this.reason = reason;
22399
+ this.table = table;
22400
+ }
22401
+ };
22402
+ REMEDY_KEY = {
22403
+ no_where: "ceremony.gate_remedy_no_where",
22404
+ ddl_destruction: "ceremony.gate_remedy_ddl_destruction",
22405
+ unparseable: "ceremony.gate_remedy_unparseable",
22406
+ non_unique_where: "ceremony.gate_remedy_non_unique_where",
22407
+ multiple_statements: "ceremony.gate_remedy_multiple_statements"
22408
+ };
22409
+ WARNING_KEY = {
22410
+ no_where: "ceremony.gate_full_table_warning",
22411
+ ddl_destruction: "ceremony.gate_ddl_warning",
22412
+ unparseable: "ceremony.gate_unparseable_warning",
22413
+ non_unique_where: "ceremony.gate_full_table_warning",
22414
+ multiple_statements: "ceremony.gate_multiple_statements_warning"
22415
+ };
22416
+ });
22417
+
22418
+ // src/core/sql-parser.ts
22419
+ var exports_sql_parser = {};
22420
+ __export(exports_sql_parser, {
22421
+ sqlParser: () => sqlParser
22422
+ });
22423
+ function sqlParser() {
22424
+ if (!cached2) {
22425
+ const { Parser } = __require("node-sql-parser");
22426
+ cached2 = new Parser;
22427
+ }
22428
+ return cached2;
22429
+ }
22430
+ var cached2 = null;
22431
+
22432
+ // src/commands/write-gate.ts
22433
+ var exports_write_gate = {};
22434
+ __export(exports_write_gate, {
22435
+ classifyStructuredWriteGate: () => classifyStructuredWriteGate,
22436
+ classifySqlWriteGate: () => classifySqlWriteGate,
22437
+ WRITE_GATE_FALLBACK_PHRASE: () => WRITE_GATE_FALLBACK_PHRASE
22438
+ });
22439
+ async function classifySqlWriteGate(sql, options = {}) {
22440
+ const { dialect } = options;
22441
+ const classification = classifyStatement(sql);
22442
+ const hiddenWrite = findWriteKeyword(sql, dialect ? [dialect] : undefined);
22443
+ const type = WRITE_TYPES.has(classification.type) ? classification.type : hiddenWrite ? keywordToType(hiddenWrite) : undefined;
22444
+ if (type === undefined)
22445
+ return { tier: "none", confirmationPhrase: WRITE_GATE_FALLBACK_PHRASE };
22446
+ if (containsMultipleStatements(sql, dialect)) {
22447
+ return verdict("two", "multiple_statements", undefined, type);
22448
+ }
22449
+ if (type === "DROP" || type === "TRUNCATE") {
22450
+ const matched = DDL_TARGET.exec(sql)?.[1];
22451
+ const table = matched ? matched.replace(/[`"[\]]/g, "") : undefined;
22452
+ return verdict("two", "ddl_destruction", table, type);
22453
+ }
22454
+ if (type !== "UPDATE" && type !== "DELETE")
22455
+ return verdict("one", undefined, undefined, type);
22456
+ return await classifyQualification(sql, dialect, type);
22457
+ }
22458
+ async function classifyQualification(sql, dialect, type) {
22459
+ const { sqlParser: sqlParser2 } = await Promise.resolve().then(() => exports_sql_parser);
22460
+ let ast;
22461
+ try {
22462
+ ast = sqlParser2().astify(sql, { database: PARSER_DIALECTS[dialect ?? "mysql"] });
22463
+ } catch {
22464
+ return unparseable(sql, dialect, type);
22465
+ }
22466
+ const statement = Array.isArray(ast) ? ast[0] : ast;
22467
+ if (!statement)
22468
+ return unparseable(sql, dialect, type);
22469
+ const table = tableName(statement.from?.[0] ?? statement.table?.[0]);
22470
+ if (statement.where != null || statement.limit != null) {
22471
+ return verdict("one", undefined, table, type);
22472
+ }
22473
+ return verdict("two", "no_where", table, type);
22474
+ }
22475
+ function unparseable(sql, dialect, type) {
22476
+ const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {});
22477
+ if (/\bWHERE\b|\bLIMIT\b/i.test(executable))
22478
+ return verdict("one", undefined, undefined, type);
22479
+ return verdict("two", "unparseable", undefined, type);
22480
+ }
22481
+ function classifyStructuredWriteGate(request) {
22482
+ const conditions = new Set(Object.keys(request.where).map((key) => key.toLowerCase()));
22483
+ const identifies = uniqueSelectors(request.schema).some((selector) => selector.length > 0 && selector.every((column) => conditions.has(column.toLowerCase())));
22484
+ if (identifies)
22485
+ return verdict("one", undefined, request.table);
22486
+ return verdict("two", "non_unique_where", request.table);
22487
+ }
22488
+ function uniqueSelectors(schema) {
22489
+ const declared = schema.primaryKey;
22490
+ const primaryKey = Array.isArray(declared) ? declared : typeof declared === "string" ? [declared] : (schema.columns ?? []).filter((column) => column.primaryKey).map((column) => column.name);
22491
+ const unique = (schema.indexes ?? []).filter((index) => index.unique).map((index) => index.columns);
22492
+ return [primaryKey, ...unique];
22493
+ }
22494
+ function tableName(ref) {
22495
+ if (!ref?.table)
22496
+ return;
22497
+ return ref.db ? `${ref.db}.${ref.table}` : ref.table;
22498
+ }
22499
+ function keywordToType(keyword) {
22500
+ const upper = keyword.toUpperCase();
22501
+ if (WRITE_TYPES.has(upper))
22502
+ return upper;
22503
+ return "INSERT";
22504
+ }
22505
+ function verdict(tier, reason, table, operation) {
22506
+ return {
22507
+ tier,
22508
+ ...reason && { reason },
22509
+ ...table && { table },
22510
+ ...operation && { operation },
22511
+ confirmationPhrase: table ?? WRITE_GATE_FALLBACK_PHRASE
22512
+ };
22513
+ }
22514
+ var WRITE_GATE_FALLBACK_PHRASE = "CONFIRM", PARSER_DIALECTS, WRITE_TYPES, DDL_TARGET;
22515
+ var init_write_gate = __esm(() => {
22516
+ init_permission_guard();
22517
+ init_sql_analysis();
22518
+ PARSER_DIALECTS = {
22519
+ mysql: "MySQL",
22520
+ mariadb: "MariaDB",
22521
+ postgresql: "Postgresql"
22522
+ };
22523
+ WRITE_TYPES = new Set([
22524
+ "INSERT",
22525
+ "UPDATE",
22526
+ "DELETE",
22527
+ "TRUNCATE",
22528
+ "DROP",
22529
+ "ALTER",
22530
+ "CREATE"
22531
+ ]);
22532
+ DDL_TARGET = /\b(?:DROP\s+TABLE|TRUNCATE(?:\s+TABLE)?)\s+(?:IF\s+EXISTS\s+)?([`"[\]\w$.]+)\s*;?\s*$/i;
22533
+ });
22534
+
22535
+ // src/commands/write-gate-guard.ts
22536
+ var exports_write_gate_guard = {};
22537
+ __export(exports_write_gate_guard, {
22538
+ recordGateDecision: () => recordGateDecision,
22539
+ guardStructuredWrite: () => guardStructuredWrite
22540
+ });
22541
+ async function recordGateDecision(request) {
22542
+ await writeAuditEntry(request.config, request.command, request.options, {
22543
+ success: request.outcome === "allowed",
22544
+ ...request.sql && { sql: request.sql },
22545
+ ...request.verdict.table && { target: request.verdict.table },
22546
+ metadata: {
22547
+ write_gate_tier: request.tier,
22548
+ write_gate_outcome: request.outcome,
22549
+ write_gate_reason: request.verdict.reason ?? "no_where"
22550
+ }
22551
+ });
22552
+ }
22553
+ async function guardStructuredWrite(request) {
22554
+ const verdict2 = classifyStructuredWriteGate({
22555
+ table: request.table,
22556
+ where: request.where,
22557
+ schema: request.schema
22558
+ });
22559
+ if (verdict2.tier !== "two")
22560
+ return true;
22561
+ const operation = request.operation.toUpperCase();
22562
+ const preview = `${operation} ${request.table} WHERE ${Object.keys(request.where).join(", ")}`;
22563
+ const record = (outcome) => recordGateDecision({
22564
+ config: request.config,
22565
+ command: request.operation,
22566
+ options: request.options,
22567
+ verdict: verdict2,
22568
+ outcome,
22569
+ tier: "two",
22570
+ sql: preview
22571
+ });
22572
+ try {
22573
+ const proceed = await enforceWriteGate({
22574
+ verdict: verdict2,
22575
+ statement: preview,
22576
+ operation,
22577
+ human: humanOutputContext(request.options)
22578
+ });
22579
+ await record(proceed ? "allowed" : "declined");
22580
+ return proceed;
22581
+ } catch (error) {
22582
+ if (error instanceof WriteGateRefusal)
22583
+ await record("refused");
22584
+ throw error;
22585
+ }
22586
+ }
22587
+ var init_write_gate_guard = __esm(() => {
22588
+ init_integration_helper();
22589
+ init_mutation_outcome();
22590
+ init_write_gate();
22591
+ init_write_gate_prompt();
22592
+ });
22593
+
22594
+ // src/core/permission/redis.ts
22595
+ var exports_redis = {};
22596
+ __export(exports_redis, {
22597
+ enforceRedisPermission: () => enforceRedisPermission,
22598
+ classifyRedisCommand: () => classifyRedisCommand
22599
+ });
22600
+ function classifyRedisCommand(command) {
22601
+ const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
22602
+ const required = REDIS_COMMAND_PERMISSION[head];
22603
+ let type;
22604
+ if (!required)
22605
+ type = "UNKNOWN";
22606
+ else if (required === "query-only")
22607
+ type = "SELECT";
22608
+ else if (required === "read-write")
22609
+ type = "UPDATE";
22610
+ else if (required === "data-admin")
22611
+ type = "DELETE";
22612
+ else
22613
+ type = "DROP";
22614
+ return {
22615
+ command: head,
22616
+ requiredPermission: required ?? "unknown",
22617
+ type,
22618
+ isDangerous: required === "admin" || required === "data-admin"
22619
+ };
22620
+ }
22621
+ function enforceRedisPermission(command, permission) {
22622
+ const classification = classifyRedisCommand(command);
22623
+ const required = classification.requiredPermission;
22624
+ const stmt = {
22625
+ type: classification.type,
22626
+ isDangerous: classification.isDangerous,
22627
+ keywords: [classification.command],
22628
+ isComposite: false,
22629
+ confidence: required === "unknown" ? "LOW" : "HIGH"
22630
+ };
22631
+ if (required === "unknown") {
22632
+ throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
22633
+ }
22634
+ if (!permissionAtLeast(permission, required)) {
22635
+ throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
22636
+ }
22637
+ return classification;
22638
+ }
22639
+ var REDIS_COMMAND_PERMISSION;
22640
+ var init_redis = __esm(() => {
22641
+ init_permission_guard();
22642
+ REDIS_COMMAND_PERMISSION = {
22643
+ GET: "query-only",
22644
+ MGET: "query-only",
22645
+ STRLEN: "query-only",
22646
+ EXISTS: "query-only",
22647
+ TTL: "query-only",
22648
+ PTTL: "query-only",
22649
+ TYPE: "query-only",
22650
+ SCAN: "query-only",
22651
+ HGET: "query-only",
22652
+ HGETALL: "query-only",
22653
+ HKEYS: "query-only",
22654
+ HVALS: "query-only",
22655
+ HLEN: "query-only",
22656
+ HEXISTS: "query-only",
22657
+ HMGET: "query-only",
22658
+ LRANGE: "query-only",
22659
+ LLEN: "query-only",
22660
+ LINDEX: "query-only",
22661
+ SMEMBERS: "query-only",
22662
+ SCARD: "query-only",
22663
+ SISMEMBER: "query-only",
22664
+ ZRANGE: "query-only",
22665
+ ZREVRANGE: "query-only",
22666
+ ZRANGEBYSCORE: "query-only",
22667
+ ZCARD: "query-only",
22668
+ ZSCORE: "query-only",
22669
+ PING: "query-only",
22670
+ ECHO: "query-only",
22671
+ SET: "read-write",
22672
+ SETEX: "read-write",
22673
+ SETNX: "read-write",
22674
+ PSETEX: "read-write",
22675
+ MSET: "read-write",
22676
+ MSETNX: "read-write",
22677
+ APPEND: "read-write",
22678
+ INCR: "read-write",
22679
+ INCRBY: "read-write",
22680
+ DECR: "read-write",
22681
+ DECRBY: "read-write",
22682
+ HSET: "read-write",
22683
+ HSETNX: "read-write",
22684
+ HMSET: "read-write",
22685
+ HINCRBY: "read-write",
22686
+ LPUSH: "read-write",
22687
+ RPUSH: "read-write",
22688
+ LPOP: "read-write",
22689
+ RPOP: "read-write",
22690
+ LSET: "read-write",
22691
+ LREM: "read-write",
22692
+ SADD: "read-write",
22693
+ SREM: "read-write",
22694
+ ZADD: "read-write",
22695
+ ZREM: "read-write",
22696
+ XADD: "read-write",
22697
+ XDEL: "data-admin",
22698
+ XLEN: "query-only",
22699
+ XREAD: "query-only",
22700
+ XRANGE: "query-only",
22701
+ XREVRANGE: "query-only",
22702
+ EXPIRE: "read-write",
22703
+ EXPIREAT: "read-write",
22704
+ PEXPIRE: "read-write",
22705
+ PERSIST: "read-write",
22706
+ RENAME: "read-write",
22707
+ DEL: "data-admin",
22708
+ UNLINK: "data-admin",
22709
+ HDEL: "data-admin",
22710
+ FLUSHDB: "admin",
22711
+ FLUSHALL: "admin",
22712
+ CONFIG: "admin",
22713
+ INFO: "admin",
22714
+ CLIENT: "admin",
22715
+ DEBUG: "admin",
22716
+ SHUTDOWN: "admin",
22717
+ KEYS: "admin",
22718
+ MONITOR: "admin",
22719
+ SAVE: "admin",
22720
+ BGSAVE: "admin",
22721
+ BGREWRITEAOF: "admin",
22722
+ REPLICAOF: "admin",
22723
+ SLAVEOF: "admin",
22724
+ ACL: "admin"
22725
+ };
22216
22726
  });
22217
22727
 
22218
22728
  // src/commands/query-table-schema.ts
@@ -22407,10 +22917,24 @@ async function executeQueryCommand(sql, options, command, runtimeOverrides = {})
22407
22917
  connectionName = connectionNames[0];
22408
22918
  config = await runtime.loadConfig(configPath, connectionName);
22409
22919
  const context = { config, configPath, connectionName };
22920
+ if (!await runtime.enforceGate(resolvedSql, options, context))
22921
+ return;
22410
22922
  await runtime.preflight(resolvedSql, options, context, fieldSelection, false);
22411
22923
  const execution = await runtime.executeConnection(resolvedSql, options, context, fieldSelection);
22412
22924
  await runtime.presentResult(resolvedSql, options, execution, tableCellLimit);
22413
22925
  } catch (error) {
22926
+ const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
22927
+ if (error instanceof WriteGateRefusal2) {
22928
+ if (options.format === "json") {
22929
+ console.log(JSON.stringify({
22930
+ status: "refused",
22931
+ code: error.code,
22932
+ reason: error.reason,
22933
+ ...error.table && { table: error.table }
22934
+ }, null, 2));
22935
+ }
22936
+ throw error;
22937
+ }
22414
22938
  let auditId = null;
22415
22939
  let envelopeId;
22416
22940
  if (options.recovery === true && !multiConnectionRequest) {
@@ -22497,6 +23021,48 @@ function scopeMultiConnectionNotices(execution) {
22497
23021
  notices: []
22498
23022
  };
22499
23023
  }
23024
+ async function enforceQueryWriteGate(query, options, context) {
23025
+ const { config } = context;
23026
+ const { toSqlDialect: toSqlDialect2, enforcePermission: enforcePermission2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
23027
+ const dialect = toSqlDialect2(config.connection?.system);
23028
+ if (!dialect)
23029
+ return true;
23030
+ const { classifySqlWriteGate: classifySqlWriteGate2 } = await Promise.resolve().then(() => (init_write_gate(), exports_write_gate));
23031
+ const verdict2 = await classifySqlWriteGate2(query, { dialect });
23032
+ if (verdict2.tier === "none")
23033
+ return true;
23034
+ enforcePermission2(query, config.permission, dialect);
23035
+ const { enforceWriteGate: enforceWriteGate2, WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
23036
+ const { humanOutputContext: humanOutputContext2 } = await Promise.resolve().then(() => (init_mutation_outcome(), exports_mutation_outcome));
23037
+ const { recordGateDecision: recordGateDecision2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
23038
+ const record = (outcome) => recordGateDecision2({
23039
+ config,
23040
+ command: "query",
23041
+ options: { ...options, config: context.configPath, connectionName: context.connectionName },
23042
+ verdict: verdict2,
23043
+ outcome,
23044
+ tier: verdict2.tier === "two" ? "two" : "one",
23045
+ sql: query
23046
+ });
23047
+ try {
23048
+ const proceed = await enforceWriteGate2({
23049
+ verdict: verdict2,
23050
+ statement: query,
23051
+ operation: verdict2.operation ?? "WRITE",
23052
+ human: humanOutputContext2(options),
23053
+ ...options.yes !== undefined && { yes: options.yes }
23054
+ });
23055
+ if (verdict2.tier === "two")
23056
+ await record(proceed ? "allowed" : "declined");
23057
+ else if (!proceed)
23058
+ await record("declined");
23059
+ return proceed;
23060
+ } catch (error) {
23061
+ if (error instanceof WriteGateRefusal2)
23062
+ await record("refused");
23063
+ throw error;
23064
+ }
23065
+ }
22500
23066
  async function preflightQuery(query, options, context, fieldSelection, multiConnection) {
22501
23067
  const { config } = context;
22502
23068
  if (!config.connection)
@@ -22509,7 +23075,7 @@ async function preflightQuery(query, options, context, fieldSelection, multiConn
22509
23075
  if (multiConnection) {
22510
23076
  throw new Error("Redis queries do not support multiple connections");
22511
23077
  }
22512
- const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
23078
+ const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
22513
23079
  enforceRedisPermission2(query, config.permission);
22514
23080
  return;
22515
23081
  }
@@ -22930,7 +23496,7 @@ var init_query = __esm(() => {
22930
23496
  init_field_projection();
22931
23497
  init_connection_selector();
22932
23498
  init_query_fanout();
22933
- init_permission_guard();
23499
+ init_elasticsearch();
22934
23500
  init_slow_query_advisory();
22935
23501
  ALLOWED_FORMATS3 = ["table", "json", "csv"];
22936
23502
  defaultQueryCommandRuntime = {
@@ -22940,6 +23506,7 @@ var init_query = __esm(() => {
22940
23506
  executeConnection: executeConnectionQuery,
22941
23507
  presentResult: presentSingleResult,
22942
23508
  writeAudit: writeAuditEntry,
23509
+ enforceGate: enforceQueryWriteGate,
22943
23510
  randomUUID: () => crypto3.randomUUID(),
22944
23511
  emitRecovery: async (error, context, options) => {
22945
23512
  const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
@@ -22964,7 +23531,7 @@ __export(exports_inline_registrars, {
22964
23531
  registerDeleteCommand: () => registerDeleteCommand
22965
23532
  });
22966
23533
  function registerQueryCommand(program2, run) {
22967
- program2.command("query [sql]").description(t("query.description")).option("-f, --query-file <path>", "Read query text from a UTF-8 file, or - for stdin").option("--format <type>", "Output format: table, json, csv, html", "table").option("--ui", "Show interactive dashboard in browser", false).option("--limit <number>", "Limit result rows (overrides auto-limit)", parsePositiveInteger).option("--no-limit", "Disable auto-limit in query-only mode").option("--collection <name>", "MongoDB collection name; Elasticsearch index name").option("--index <name>", "Elasticsearch index name (alias for --collection)").option("--fields <list>", "Include fields, or exclude them with --fields=-field_a,-field_b").option("--truncate <number>", "Limit serialized table cells to N Unicode characters", parsePositiveInteger).option("--no-truncate", "Disable the default table-cell truncation").option("--slow-ms <number>", "Emit a passive performance hint at or above this execution time; 0 disables it", parseSlowMs, 1000).addOption(createConnectionSelectorOption()).option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (sql, options, command) => {
23534
+ program2.command("query [sql]").description(t("query.description")).option("-f, --query-file <path>", "Read query text from a UTF-8 file, or - for stdin").option("--format <type>", "Output format: table, json, csv, html", "table").option("--ui", "Show interactive dashboard in browser", false).option("--limit <number>", "Limit result rows (overrides auto-limit)", parsePositiveInteger).option("--no-limit", "Disable auto-limit in query-only mode").option("--collection <name>", "MongoDB collection name; Elasticsearch index name").option("--index <name>", "Elasticsearch index name (alias for --collection)").option("--fields <list>", "Include fields, or exclude them with --fields=-field_a,-field_b").option("--truncate <number>", "Limit serialized table cells to N Unicode characters", parsePositiveInteger).option("--no-truncate", "Disable the default table-cell truncation").option("--slow-ms <number>", "Emit a passive performance hint at or above this execution time; 0 disables it", parseSlowMs, 1000).addOption(createConnectionSelectorOption()).option("--yes", "Skip the confirmation shown before an ordinary write; statements that are not limited to specific rows ignore it", false).option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (sql, options, command) => {
22968
23535
  const rawArgs = command.parent?.rawArgs ?? [];
22969
23536
  const rawTruncate = findLongOptionValue(rawArgs, "--truncate");
22970
23537
  const rootUse = command.parent?.opts().use;
@@ -22989,7 +23556,7 @@ function registerQCommand(program2, run) {
22989
23556
  });
22990
23557
  }
22991
23558
  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) => {
23559
+ 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
23560
  try {
22994
23561
  await run(table, options, command);
22995
23562
  } catch (error) {
@@ -23003,7 +23570,7 @@ function registerInsertCommand(program2, run) {
23003
23570
  });
23004
23571
  }
23005
23572
  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) => {
23573
+ 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
23574
  try {
23008
23575
  await run(table, options, command);
23009
23576
  } catch (error) {
@@ -23017,7 +23584,7 @@ function registerUpdateCommand(program2, run) {
23017
23584
  });
23018
23585
  }
23019
23586
  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) => {
23587
+ 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
23588
  try {
23022
23589
  await run(table, options, command);
23023
23590
  } catch (error) {
@@ -23280,8 +23847,8 @@ function applyBlacklistRules(facts, blacklist, factors) {
23280
23847
  }
23281
23848
  function findBlacklistedColumnsForTable(blacklist, table) {
23282
23849
  const columns = blacklist.columns ?? {};
23283
- for (const [tableName, columnNames] of Object.entries(columns)) {
23284
- if (tableName.toLowerCase() === table.toLowerCase()) {
23850
+ for (const [tableName2, columnNames] of Object.entries(columns)) {
23851
+ if (tableName2.toLowerCase() === table.toLowerCase()) {
23285
23852
  return new Set(columnNames);
23286
23853
  }
23287
23854
  }
@@ -23738,7 +24305,7 @@ function deepHasScript(value) {
23738
24305
  return Object.values(obj).some(deepHasScript);
23739
24306
  }
23740
24307
  var NAME_RE2, ES_SIZE_CAP = 1000, ES_MAX_RESULT_WINDOW = 1e4, esStrategy;
23741
- var init_elasticsearch = __esm(() => {
24308
+ var init_elasticsearch2 = __esm(() => {
23742
24309
  init_types5();
23743
24310
  NAME_RE2 = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
23744
24311
  esStrategy = {
@@ -23836,7 +24403,7 @@ function applyRedisSizeGuard(command, noLimit) {
23836
24403
  return { command, warnings };
23837
24404
  }
23838
24405
  var REDIS_NAME_RE, REDIS_RANGE_CAP = 1000, RANGE_VERBS, SCAN_VERBS, REDIS_READONLY_VERBS, REDIS_HARD_REJECT, redisStrategy;
23839
- var init_redis = __esm(() => {
24406
+ var init_redis2 = __esm(() => {
23840
24407
  init_types5();
23841
24408
  REDIS_NAME_RE = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
23842
24409
  RANGE_VERBS = new Set(["LRANGE", "ZRANGE", "ZRANGEBYSCORE", "ZRANGEBYLEX"]);
@@ -24035,8 +24602,8 @@ function getStrategy(family) {
24035
24602
  }
24036
24603
  var init_strategies = __esm(() => {
24037
24604
  init_sql();
24038
- init_elasticsearch();
24039
- init_redis();
24605
+ init_elasticsearch2();
24606
+ init_redis2();
24040
24607
  init_mongodb();
24041
24608
  });
24042
24609
 
@@ -25245,12 +25812,12 @@ class DataExecutor {
25245
25812
  this.dbSystem = dbSystem;
25246
25813
  this.blacklistValidator = blacklistValidator;
25247
25814
  }
25248
- buildInsertSql(tableName, data, schema) {
25815
+ buildInsertSql(tableName2, data, schema) {
25249
25816
  const columnNames = schema.columns.map((col) => col.name);
25250
25817
  const dataKeys = Object.keys(data);
25251
25818
  for (const key of dataKeys) {
25252
25819
  if (!columnNames.includes(key)) {
25253
- throw new Error(`Column "${key}" not found in table "${tableName}". Valid columns: ${columnNames.join(", ")}`);
25820
+ throw new Error(`Column "${key}" not found in table "${tableName2}". Valid columns: ${columnNames.join(", ")}`);
25254
25821
  }
25255
25822
  }
25256
25823
  const columns = dataKeys;
@@ -25258,49 +25825,38 @@ class DataExecutor {
25258
25825
  const systemType = this.getSystemType();
25259
25826
  const placeholders = systemType === "postgresql" ? dataKeys.map((_, index) => `$${index + 1}`).join(", ") : dataKeys.map(() => "?").join(", ");
25260
25827
  const quote = this.getQuoteChar();
25261
- const sql = `INSERT INTO ${quote}${tableName}${quote} (${columns.map((col) => `${quote}${col}${quote}`).join(", ")}) VALUES (${placeholders})`;
25828
+ const sql = `INSERT INTO ${quote}${tableName2}${quote} (${columns.map((col) => `${quote}${col}${quote}`).join(", ")}) VALUES (${placeholders})`;
25262
25829
  return {
25263
25830
  sql,
25264
25831
  params: values
25265
25832
  };
25266
25833
  }
25267
- async executeInsert(tableName, data, schema, options) {
25834
+ async executeInsert(tableName2, data, schema, options) {
25268
25835
  const timestamp = new Date().toISOString();
25269
25836
  try {
25270
- 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?" });
25837
+ this.checkBlacklist("INSERT", tableName2);
25838
+ enforcePermissionForType("INSERT", this.permission);
25839
+ return await this.executeMutation("insert", this.buildInsertSql(tableName2, data, schema), timestamp, options, { destructive: false });
25273
25840
  } catch (error) {
25274
25841
  return this.handleMutationError("insert", timestamp, error);
25275
25842
  }
25276
25843
  }
25277
- async executeUpdate(tableName, data, where, schema, options) {
25844
+ async executeUpdate(tableName2, data, where, schema, options) {
25278
25845
  const timestamp = new Date().toISOString();
25279
25846
  try {
25280
- 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?" });
25847
+ this.checkBlacklist("UPDATE", tableName2);
25848
+ enforcePermissionForType("UPDATE", this.permission);
25849
+ return await this.executeMutation("update", this.buildUpdateSql(tableName2, data, where, schema), timestamp, options, { destructive: false });
25283
25850
  } catch (error) {
25284
25851
  return this.handleMutationError("update", timestamp, error);
25285
25852
  }
25286
25853
  }
25287
- async executeDelete(tableName, where, schema, options) {
25854
+ async executeDelete(tableName2, where, schema, options) {
25288
25855
  const timestamp = new Date().toISOString();
25289
25856
  try {
25290
- 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
- });
25857
+ this.checkBlacklist("DELETE", tableName2);
25858
+ enforcePermissionForType("DELETE", this.permission);
25859
+ return await this.executeMutation("delete", this.buildDeleteSql(tableName2, where, schema), timestamp, options, { destructive: true });
25304
25860
  } catch (error) {
25305
25861
  return this.handleMutationError("delete", timestamp, error);
25306
25862
  }
@@ -25311,34 +25867,34 @@ class DataExecutor {
25311
25867
  getQuoteChar() {
25312
25868
  return this.dbSystem === "mysql" ? "`" : '"';
25313
25869
  }
25314
- checkBlacklist(operation, tableName) {
25315
- this.blacklistValidator?.checkTableBlacklist(operation, tableName, []);
25870
+ checkBlacklist(operation, tableName2) {
25871
+ this.blacklistValidator?.checkTableBlacklist(operation, tableName2, []);
25316
25872
  }
25317
- async executeMutation(operation, statement, timestamp, options, confirmation) {
25873
+ async executeMutation(operation, statement, timestamp, options, { destructive }) {
25318
25874
  if (options?.dryRun) {
25319
- return this.successResult(operation, 0, timestamp, statement.sql);
25875
+ return this.outcomeResult("dry_run", operation, 0, timestamp, statement.sql);
25320
25876
  }
25321
25877
  if (!options?.force) {
25322
- if (confirmation.warning) {
25323
- console.log(`
25324
- ${confirmation.warning}`);
25878
+ if (!options?.confirm) {
25879
+ 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
25880
  }
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);
25881
+ const proceed = await options.confirm({
25882
+ operation,
25883
+ engine: "sql",
25884
+ sql: statement.sql,
25885
+ params: statement.params,
25886
+ destructive
25887
+ });
25888
+ if (!proceed) {
25889
+ return this.outcomeResult("cancelled", operation, 0, timestamp, statement.sql);
25334
25890
  }
25335
25891
  }
25336
25892
  const result = await this.adapter.execute(statement.sql, statement.params);
25337
- return this.successResult(operation, result.affectedRows, timestamp, statement.sql);
25893
+ return this.outcomeResult("success", operation, result.affectedRows, timestamp, statement.sql);
25338
25894
  }
25339
- successResult(operation, rowsAffected, timestamp, sql) {
25895
+ outcomeResult(status2, operation, rowsAffected, timestamp, sql) {
25340
25896
  return {
25341
- status: "success",
25897
+ status: status2,
25342
25898
  operation,
25343
25899
  rows_affected: rowsAffected,
25344
25900
  timestamp,
@@ -25349,13 +25905,13 @@ Parameters:`);
25349
25905
  if (error instanceof BlacklistError) {
25350
25906
  throw error;
25351
25907
  }
25352
- if (error instanceof PermissionError && operation !== "delete") {
25908
+ if (error instanceof PermissionError) {
25353
25909
  return {
25354
25910
  status: "error",
25355
25911
  operation,
25356
25912
  rows_affected: 0,
25357
25913
  timestamp,
25358
- error: `Permission denied: Query-only mode only allows SELECT. Use Read-Write or Admin mode to execute ${operation.toUpperCase()}.`
25914
+ error: t_vars("errors.permission_denied_reason", { reason: error.message })
25359
25915
  };
25360
25916
  }
25361
25917
  const errorMessage2 = error instanceof Error ? error.message : String(error);
@@ -25367,13 +25923,13 @@ Parameters:`);
25367
25923
  error: `${operation.toUpperCase()} failed: ${errorMessage2}`
25368
25924
  };
25369
25925
  }
25370
- buildUpdateSql(tableName, data, where, schema) {
25926
+ buildUpdateSql(tableName2, data, where, schema) {
25371
25927
  const dataKeys = Object.keys(data);
25372
25928
  const whereKeys = Object.keys(where);
25373
25929
  const columnNames = schema.columns.map((col) => col.name);
25374
25930
  for (const key of [...dataKeys, ...whereKeys]) {
25375
25931
  if (!columnNames.includes(key)) {
25376
- throw new Error(`Column "${key}" not found in table "${tableName}"`);
25932
+ throw new Error(`Column "${key}" not found in table "${tableName2}"`);
25377
25933
  }
25378
25934
  }
25379
25935
  const systemType = this.getSystemType();
@@ -25387,16 +25943,16 @@ Parameters:`);
25387
25943
  const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
25388
25944
  return `${quote}${key}${quote} = ${placeholder}`;
25389
25945
  }).join(" AND ");
25390
- const sql = `UPDATE ${quote}${tableName}${quote} SET ${setClause} WHERE ${whereClause}`;
25946
+ const sql = `UPDATE ${quote}${tableName2}${quote} SET ${setClause} WHERE ${whereClause}`;
25391
25947
  const params = [...dataKeys.map((key) => data[key]), ...whereKeys.map((key) => where[key])];
25392
25948
  return { sql, params };
25393
25949
  }
25394
- buildDeleteSql(tableName, where, schema) {
25950
+ buildDeleteSql(tableName2, where, schema) {
25395
25951
  const whereKeys = Object.keys(where);
25396
25952
  const columnNames = schema.columns.map((col) => col.name);
25397
25953
  for (const key of whereKeys) {
25398
25954
  if (!columnNames.includes(key)) {
25399
- throw new Error(`Column "${key}" not found in table "${tableName}"`);
25955
+ throw new Error(`Column "${key}" not found in table "${tableName2}"`);
25400
25956
  }
25401
25957
  }
25402
25958
  const systemType = this.getSystemType();
@@ -25406,17 +25962,80 @@ Parameters:`);
25406
25962
  const placeholder = systemType === "postgresql" ? `$${paramIndex++}` : "?";
25407
25963
  return `${quote}${key}${quote} = ${placeholder}`;
25408
25964
  }).join(" AND ");
25409
- const sql = `DELETE FROM ${quote}${tableName}${quote} WHERE ${whereClause}`;
25965
+ const sql = `DELETE FROM ${quote}${tableName2}${quote} WHERE ${whereClause}`;
25410
25966
  const params = whereKeys.map((key) => where[key]);
25411
25967
  return { sql, params };
25412
25968
  }
25413
25969
  }
25414
25970
  var init_data_executor = __esm(() => {
25415
25971
  init_permission_guard();
25416
- init_prompts();
25972
+ init_message_loader();
25417
25973
  init_blacklist();
25418
25974
  });
25419
25975
 
25976
+ // src/commands/mutation-confirm.ts
25977
+ async function confirmDirectMutation(request) {
25978
+ if (request.force === true)
25979
+ return true;
25980
+ return confirmMutationInteractively({
25981
+ operation: request.operation,
25982
+ engine: request.engine,
25983
+ sql: request.preview,
25984
+ params: [],
25985
+ destructive: request.destructive
25986
+ });
25987
+ }
25988
+ var confirmMutationInteractively = async (request) => {
25989
+ if (request.destructive) {
25990
+ process.stderr.write(`
25991
+ ${t("ceremony.confirm_destructive_warning")}
25992
+ `);
25993
+ }
25994
+ const label = request.engine === "sql" ? "ceremony.confirm_sql" : "ceremony.confirm_command";
25995
+ process.stderr.write(`
25996
+ ${t(label)}
25997
+ ${request.sql}
25998
+ `);
25999
+ if (request.params.length > 0) {
26000
+ process.stderr.write(`
26001
+ ${t("ceremony.confirm_params")}
26002
+ ${JSON.stringify(request.params, null, 2)}
26003
+ `);
26004
+ }
26005
+ return promptUser.confirm(t(request.destructive ? "ceremony.confirm_destructive_prompt" : "ceremony.confirm_prompt"));
26006
+ }, confirmDdlInteractively = async (request) => {
26007
+ process.stderr.write(`
26008
+ ${t("ceremony.confirm_destructive_warning_ddl")}
26009
+ `);
26010
+ process.stderr.write(`
26011
+ ${t("ceremony.confirm_sql")}
26012
+ ${request.sql}
26013
+ `);
26014
+ return promptUser.confirm(t_vars("ceremony.confirm_ddl_prompt", { operation: request.operation }));
26015
+ };
26016
+ var init_mutation_confirm = __esm(() => {
26017
+ init_message_loader();
26018
+ init_prompts();
26019
+ });
26020
+
26021
+ // src/commands/mutation-audit.ts
26022
+ function auditOutcomeForMutation(result, table, recoveryRef) {
26023
+ return {
26024
+ success: result.status === "success" || result.status === "dry_run",
26025
+ target: table,
26026
+ ...recoveryRef && { recovery_ref: recoveryRef },
26027
+ ...result.sql && { sql: result.sql },
26028
+ metadata: {
26029
+ rows_affected: result.rows_affected,
26030
+ outcome: result.status,
26031
+ ...result.status === "dry_run" && { dry_run: true }
26032
+ },
26033
+ ...result.status === "error" && {
26034
+ error: new Error(result.error ?? `${result.operation} failed`)
26035
+ }
26036
+ };
26037
+ }
26038
+
25420
26039
  // src/core/mongo/dry-run-formatter.ts
25421
26040
  function pretty(value) {
25422
26041
  return JSON.stringify(value, null, INDENT);
@@ -26258,6 +26877,9 @@ async function insertCommand(table, options, command) {
26258
26877
  throw new Error("Table name required");
26259
26878
  }
26260
26879
  table = table.trim();
26880
+ if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
26881
+ throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
26882
+ }
26261
26883
  let jsonInput = "";
26262
26884
  if (options.data) {
26263
26885
  jsonInput = options.data;
@@ -26298,30 +26920,40 @@ async function insertCommand(table, options, command) {
26298
26920
  return;
26299
26921
  }
26300
26922
  if (config.connection.system === "redis") {
26301
- enforcePermission("INSERT INTO dummy", config.permission);
26923
+ enforcePermissionForType("INSERT", config.permission);
26302
26924
  const blacklistManager = new BlacklistManager(config);
26303
26925
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26304
26926
  blacklistValidator.checkTableBlacklist("INSERT", table, []);
26305
26927
  blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
26928
+ const preview = `SET ${table} ... (Redis Insert)`;
26306
26929
  if (options.dryRun) {
26307
26930
  const output = {
26308
- status: "success",
26931
+ status: "dry_run",
26309
26932
  operation: "insert",
26310
26933
  rows_affected: 0,
26311
- sql: `SET ${table} ... (Redis Insert)`,
26934
+ sql: preview,
26312
26935
  timestamp: new Date().toISOString()
26313
26936
  };
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
- });
26937
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26938
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26939
+ return;
26940
+ }
26941
+ if (!await confirmDirectMutation({
26942
+ operation: "insert",
26943
+ engine: "redis",
26944
+ preview,
26945
+ destructive: false,
26946
+ force: options.force
26947
+ })) {
26948
+ const output = cancelledOutcome("insert", preview);
26949
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
26950
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26320
26951
  return;
26321
26952
  }
26322
26953
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26323
26954
  await adapter2.connect();
26324
26955
  try {
26956
+ const startedAt = performance.now();
26325
26957
  const result = await adapter2.insert(table, data);
26326
26958
  const output = {
26327
26959
  status: "success",
@@ -26329,12 +26961,8 @@ async function insertCommand(table, options, command) {
26329
26961
  rows_affected: result.affectedRows,
26330
26962
  timestamp: new Date().toISOString()
26331
26963
  };
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
- });
26964
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
26965
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26338
26966
  return;
26339
26967
  } finally {
26340
26968
  await adapter2.disconnect();
@@ -26346,7 +26974,7 @@ async function insertCommand(table, options, command) {
26346
26974
  operation: "insert",
26347
26975
  rows_affected: 0,
26348
26976
  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"
26977
+ error: t("insert.elasticsearch_unsupported")
26350
26978
  };
26351
26979
  await writeAuditEntry(config, "insert", options, {
26352
26980
  success: false,
@@ -26357,44 +26985,49 @@ async function insertCommand(table, options, command) {
26357
26985
  process.exit(1);
26358
26986
  }
26359
26987
  if (config.connection.system === "mongodb") {
26360
- enforcePermission("INSERT INTO dummy", config.permission);
26988
+ enforcePermissionForType("INSERT", config.permission);
26361
26989
  const blacklistManager = new BlacklistManager(config);
26362
26990
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26363
26991
  blacklistValidator.checkTableBlacklist("INSERT", table, []);
26364
26992
  blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
26993
+ const preview = previewInsert(table, data);
26365
26994
  if (options.dryRun) {
26366
26995
  const output = {
26367
- status: "success",
26996
+ status: "dry_run",
26368
26997
  operation: "insert",
26369
26998
  rows_affected: 0,
26370
- sql: previewInsert(table, data),
26999
+ sql: preview,
26371
27000
  timestamp: new Date().toISOString()
26372
27001
  };
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
- });
27002
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27003
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
27004
+ return;
27005
+ }
27006
+ if (!await confirmDirectMutation({
27007
+ operation: "insert",
27008
+ engine: "mongodb",
27009
+ preview,
27010
+ destructive: false,
27011
+ force: options.force
27012
+ })) {
27013
+ const output = cancelledOutcome("insert", preview);
27014
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27015
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26379
27016
  return;
26380
27017
  }
26381
27018
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26382
27019
  await adapter2.connect();
26383
27020
  try {
27021
+ const startedAt = performance.now();
26384
27022
  const result = await adapter2.insert(table, data);
26385
27023
  const output = {
26386
27024
  status: "success",
26387
27025
  operation: "insert",
26388
27026
  rows_affected: result.affectedRows,
26389
- timestamp: new Date().toISOString(),
26390
- lastInsertId: result.lastInsertId
27027
+ timestamp: new Date().toISOString()
26391
27028
  };
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
- });
27029
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options), { lastInsertId: result.lastInsertId });
27030
+ await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
26398
27031
  return;
26399
27032
  } finally {
26400
27033
  await adapter2.disconnect();
@@ -26408,31 +27041,20 @@ async function insertCommand(table, options, command) {
26408
27041
  const blacklistManager = new BlacklistManager(config);
26409
27042
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26410
27043
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
27044
+ const startedAt = performance.now();
26411
27045
  const result = await executor3.executeInsert(table, data, schema, {
26412
27046
  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
- }
27047
+ force: options.force,
27048
+ confirm: confirmMutationInteractively
26435
27049
  });
27050
+ const elapsedMs = performance.now() - startedAt;
27051
+ const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto5.randomUUID() : undefined;
27052
+ const auditId = await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
27053
+ if (recoveryEnvelopeId !== undefined) {
27054
+ const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
27055
+ emitRecoveryEnvelope2(new Error(result.error ?? "INSERT failed"), { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
27056
+ }
27057
+ printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
26436
27058
  if (result.status === "error") {
26437
27059
  process.exit(1);
26438
27060
  }
@@ -26458,13 +27080,7 @@ async function insertCommand(table, options, command) {
26458
27080
  emitRecoveryEnvelope2(error, { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId, auditRef: auditId ?? undefined });
26459
27081
  }
26460
27082
  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));
27083
+ printMutationFailure(error, "insert", humanOutputContext(options));
26468
27084
  process.exit(1);
26469
27085
  }
26470
27086
  if (error instanceof PermissionError) {
@@ -26477,13 +27093,7 @@ async function insertCommand(table, options, command) {
26477
27093
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
26478
27094
  process.exit(1);
26479
27095
  }
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));
27096
+ printMutationFailure(error, "insert", humanOutputContext(options));
26487
27097
  process.exit(1);
26488
27098
  }
26489
27099
  }
@@ -26493,6 +27103,8 @@ var init_insert = __esm(() => {
26493
27103
  init_connection_error_message();
26494
27104
  init_adapters();
26495
27105
  init_data_executor();
27106
+ init_mutation_confirm();
27107
+ init_mutation_outcome();
26496
27108
  init_config();
26497
27109
  init_permission_guard();
26498
27110
  init_blacklist_validator();
@@ -26521,6 +27133,9 @@ async function updateCommand(table, options, command) {
26521
27133
  throw new Error("Table name required");
26522
27134
  }
26523
27135
  table = table.trim();
27136
+ if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
27137
+ throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
27138
+ }
26524
27139
  if (!options.plan && (!options.where || options.where.trim() === "")) {
26525
27140
  throw new Error('UPDATE requires --where clause (e.g. --where "id=1")');
26526
27141
  }
@@ -26581,29 +27196,39 @@ async function updateCommand(table, options, command) {
26581
27196
  return;
26582
27197
  }
26583
27198
  if (config.connection?.system === "redis") {
26584
- enforcePermission("UPDATE dummy", config.permission);
27199
+ enforcePermissionForType("UPDATE", config.permission);
26585
27200
  const blacklistManager = new BlacklistManager(config);
26586
27201
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26587
27202
  blacklistValidator.checkTableBlacklist("UPDATE", table, []);
27203
+ const preview = `HSET ${table} ... (Redis Update)`;
26588
27204
  if (options.dryRun) {
26589
27205
  const output = {
26590
- status: "success",
27206
+ status: "dry_run",
26591
27207
  operation: "update",
26592
27208
  rows_affected: 0,
26593
- sql: `HSET ${table} ... (Redis Update)`,
27209
+ sql: preview,
26594
27210
  timestamp: new Date().toISOString()
26595
27211
  };
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
- });
27212
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27213
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
27214
+ return;
27215
+ }
27216
+ if (!await confirmDirectMutation({
27217
+ operation: "update",
27218
+ engine: "redis",
27219
+ preview,
27220
+ destructive: false,
27221
+ force: options.force
27222
+ })) {
27223
+ const output = cancelledOutcome("update", preview);
27224
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27225
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26602
27226
  return;
26603
27227
  }
26604
27228
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26605
27229
  await adapter2.connect();
26606
27230
  try {
27231
+ const startedAt = performance.now();
26607
27232
  const result = await adapter2.update(table, {}, setData);
26608
27233
  const output = {
26609
27234
  status: "success",
@@ -26611,12 +27236,8 @@ async function updateCommand(table, options, command) {
26611
27236
  rows_affected: result.affectedRows,
26612
27237
  timestamp: new Date().toISOString()
26613
27238
  };
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
- });
27239
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27240
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26620
27241
  return;
26621
27242
  } finally {
26622
27243
  await adapter2.disconnect();
@@ -26628,7 +27249,7 @@ async function updateCommand(table, options, command) {
26628
27249
  operation: "update",
26629
27250
  rows_affected: 0,
26630
27251
  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"
27252
+ error: t("update.elasticsearch_unsupported")
26632
27253
  };
26633
27254
  await writeAuditEntry(config, "update", options, {
26634
27255
  success: false,
@@ -26639,7 +27260,7 @@ async function updateCommand(table, options, command) {
26639
27260
  process.exit(1);
26640
27261
  }
26641
27262
  if (config.connection?.system === "mongodb") {
26642
- enforcePermission("UPDATE dummy", config.permission);
27263
+ enforcePermissionForType("UPDATE", config.permission);
26643
27264
  const hasOperator = Object.keys(setData).some((key) => key.startsWith("$"));
26644
27265
  const updateDoc = hasOperator ? setData : { $set: setData };
26645
27266
  const writtenFields = new Set;
@@ -26665,25 +27286,35 @@ async function updateCommand(table, options, command) {
26665
27286
  } catch {
26666
27287
  filter = parseWhereClause(options.where);
26667
27288
  }
27289
+ const preview = previewUpdate(table, filter, updateDoc);
26668
27290
  if (options.dryRun) {
26669
27291
  const output = {
26670
- status: "success",
27292
+ status: "dry_run",
26671
27293
  operation: "update",
26672
27294
  rows_affected: 0,
26673
- sql: previewUpdate(table, filter, updateDoc),
27295
+ sql: preview,
26674
27296
  timestamp: new Date().toISOString()
26675
27297
  };
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
- });
27298
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27299
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
27300
+ return;
27301
+ }
27302
+ if (!await confirmDirectMutation({
27303
+ operation: "update",
27304
+ engine: "mongodb",
27305
+ preview,
27306
+ destructive: false,
27307
+ force: options.force
27308
+ })) {
27309
+ const output = cancelledOutcome("update", preview);
27310
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27311
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26682
27312
  return;
26683
27313
  }
26684
27314
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26685
27315
  await adapter2.connect();
26686
27316
  try {
27317
+ const startedAt = performance.now();
26687
27318
  const result = await adapter2.update(table, filter, updateDoc);
26688
27319
  const output = {
26689
27320
  status: "success",
@@ -26691,12 +27322,8 @@ async function updateCommand(table, options, command) {
26691
27322
  rows_affected: result.affectedRows,
26692
27323
  timestamp: new Date().toISOString()
26693
27324
  };
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
- });
27325
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27326
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
26700
27327
  return;
26701
27328
  } finally {
26702
27329
  await adapter2.disconnect();
@@ -26712,35 +27339,38 @@ async function updateCommand(table, options, command) {
26712
27339
  await adapter.connect();
26713
27340
  try {
26714
27341
  const schema = await adapter.getTableSchema(table);
27342
+ const { guardStructuredWrite: guardStructuredWrite2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
27343
+ if (options.dryRun !== true && !await guardStructuredWrite2({
27344
+ operation: "update",
27345
+ table,
27346
+ where: whereConditions,
27347
+ schema,
27348
+ config,
27349
+ options
27350
+ })) {
27351
+ const cancelled = cancelledOutcome("update", `UPDATE ${table}`);
27352
+ printMutationOutcome(cancelled, table, 0, humanOutputContext(options));
27353
+ await writeAuditEntry(config, "update", options, auditOutcomeForMutation(cancelled, table));
27354
+ return;
27355
+ }
26715
27356
  const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
26716
27357
  const blacklistManager = new BlacklistManager(config);
26717
27358
  const blacklistValidator = new BlacklistValidator(blacklistManager);
26718
27359
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
27360
+ const startedAt = performance.now();
26719
27361
  const result = await executor3.executeUpdate(table, setData, whereConditions, schema, {
26720
27362
  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
- }
27363
+ force: options.force,
27364
+ confirm: confirmMutationInteractively
26743
27365
  });
27366
+ const elapsedMs = performance.now() - startedAt;
27367
+ const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto6.randomUUID() : undefined;
27368
+ const auditId = await writeAuditEntry(config, "update", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
27369
+ if (recoveryEnvelopeId !== undefined) {
27370
+ const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
27371
+ emitRecoveryEnvelope2(new Error(result.error ?? "UPDATE failed"), { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
27372
+ }
27373
+ printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
26744
27374
  if (result.status === "error") {
26745
27375
  process.exit(1);
26746
27376
  }
@@ -26748,6 +27378,11 @@ async function updateCommand(table, options, command) {
26748
27378
  await adapter.disconnect();
26749
27379
  }
26750
27380
  } catch (error) {
27381
+ const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
27382
+ if (error instanceof WriteGateRefusal2) {
27383
+ printMutationFailure(error, "update", humanOutputContext(options));
27384
+ process.exit(1);
27385
+ }
26751
27386
  let auditId = null;
26752
27387
  let envelopeId;
26753
27388
  if (options.recovery === true) {
@@ -26766,13 +27401,7 @@ async function updateCommand(table, options, command) {
26766
27401
  emitRecoveryEnvelope2(error, { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId, auditRef: auditId ?? undefined });
26767
27402
  }
26768
27403
  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));
27404
+ printMutationFailure(error, "update", humanOutputContext(options));
26776
27405
  process.exit(1);
26777
27406
  }
26778
27407
  if (error instanceof PermissionError) {
@@ -26785,13 +27414,7 @@ async function updateCommand(table, options, command) {
26785
27414
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
26786
27415
  process.exit(1);
26787
27416
  }
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));
27417
+ printMutationFailure(error, "update", humanOutputContext(options));
26795
27418
  process.exit(1);
26796
27419
  }
26797
27420
  }
@@ -26801,6 +27424,8 @@ var init_update = __esm(() => {
26801
27424
  init_connection_error_message();
26802
27425
  init_adapters();
26803
27426
  init_data_executor();
27427
+ init_mutation_confirm();
27428
+ init_mutation_outcome();
26804
27429
  init_config();
26805
27430
  init_permission_guard();
26806
27431
  init_blacklist_validator();
@@ -26829,6 +27454,9 @@ async function deleteCommand(table, options, command) {
26829
27454
  throw new Error("Table name required");
26830
27455
  }
26831
27456
  table = table.trim();
27457
+ if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
27458
+ throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
27459
+ }
26832
27460
  if (!options.plan && (!options.where || options.where.trim() === "")) {
26833
27461
  throw new Error('DELETE requires --where clause (e.g. --where "id=1")');
26834
27462
  }
@@ -26875,14 +27503,8 @@ async function deleteCommand(table, options, command) {
26875
27503
  });
26876
27504
  return;
26877
27505
  }
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);
27506
+ if (!permitsOperation("DELETE", config.permission)) {
27507
+ throw new PermissionError(t("delete.admin_only"), classificationForType("DELETE"), minimumPermissionFor("DELETE"));
26886
27508
  }
26887
27509
  if (config.connection?.system === "redis") {
26888
27510
  const blacklistManager = new BlacklistManager(config);
@@ -26894,25 +27516,35 @@ async function deleteCommand(table, options, command) {
26894
27516
  } catch {
26895
27517
  filter = parseWhereClause(options.where);
26896
27518
  }
27519
+ const preview = `DEL ${table} (Redis Delete)`;
26897
27520
  if (options.dryRun) {
26898
27521
  const output = {
26899
- status: "success",
27522
+ status: "dry_run",
26900
27523
  operation: "delete",
26901
27524
  rows_affected: 0,
26902
- sql: `DEL ${table} (Redis Delete)`,
27525
+ sql: preview,
26903
27526
  timestamp: new Date().toISOString()
26904
27527
  };
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
- });
27528
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27529
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
27530
+ return;
27531
+ }
27532
+ if (!await confirmDirectMutation({
27533
+ operation: "delete",
27534
+ engine: "redis",
27535
+ preview,
27536
+ destructive: true,
27537
+ force: options.force
27538
+ })) {
27539
+ const output = cancelledOutcome("delete", preview);
27540
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27541
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26911
27542
  return;
26912
27543
  }
26913
27544
  const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
26914
27545
  await adapter2.connect();
26915
27546
  try {
27547
+ const startedAt = performance.now();
26916
27548
  const result = await adapter2.delete(table, filter);
26917
27549
  const output = {
26918
27550
  status: "success",
@@ -26920,12 +27552,8 @@ async function deleteCommand(table, options, command) {
26920
27552
  rows_affected: result.affectedRows,
26921
27553
  timestamp: new Date().toISOString()
26922
27554
  };
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
- });
27555
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27556
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26929
27557
  return;
26930
27558
  } finally {
26931
27559
  await adapter2.disconnect();
@@ -26937,7 +27565,7 @@ async function deleteCommand(table, options, command) {
26937
27565
  operation: "delete",
26938
27566
  rows_affected: 0,
26939
27567
  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"
27568
+ error: t("delete.elasticsearch_unsupported")
26941
27569
  };
26942
27570
  await writeAuditEntry(config, "delete", options, {
26943
27571
  success: false,
@@ -26957,25 +27585,35 @@ async function deleteCommand(table, options, command) {
26957
27585
  } catch {
26958
27586
  filter = parseWhereClause(options.where);
26959
27587
  }
27588
+ const preview = previewDelete(table, filter);
26960
27589
  if (options.dryRun) {
26961
27590
  const output = {
26962
- status: "success",
27591
+ status: "dry_run",
26963
27592
  operation: "delete",
26964
27593
  rows_affected: 0,
26965
- sql: previewDelete(table, filter),
27594
+ sql: preview,
26966
27595
  timestamp: new Date().toISOString()
26967
27596
  };
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
- });
27597
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27598
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
27599
+ return;
27600
+ }
27601
+ if (!await confirmDirectMutation({
27602
+ operation: "delete",
27603
+ engine: "mongodb",
27604
+ preview,
27605
+ destructive: true,
27606
+ force: options.force
27607
+ })) {
27608
+ const output = cancelledOutcome("delete", preview);
27609
+ printMutationOutcome(output, table, 0, humanOutputContext(options));
27610
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26974
27611
  return;
26975
27612
  }
26976
27613
  const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
26977
27614
  await adapter2.connect();
26978
27615
  try {
27616
+ const startedAt = performance.now();
26979
27617
  const result = await adapter2.delete(table, filter);
26980
27618
  const output = {
26981
27619
  status: "success",
@@ -26983,12 +27621,8 @@ async function deleteCommand(table, options, command) {
26983
27621
  rows_affected: result.affectedRows,
26984
27622
  timestamp: new Date().toISOString()
26985
27623
  };
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
- });
27624
+ printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
27625
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
26992
27626
  return;
26993
27627
  } finally {
26994
27628
  await adapter2.disconnect();
@@ -27004,35 +27638,38 @@ async function deleteCommand(table, options, command) {
27004
27638
  await adapter.connect();
27005
27639
  try {
27006
27640
  const schema = await adapter.getTableSchema(table);
27641
+ const { guardStructuredWrite: guardStructuredWrite2 } = await Promise.resolve().then(() => (init_write_gate_guard(), exports_write_gate_guard));
27642
+ if (options.dryRun !== true && !await guardStructuredWrite2({
27643
+ operation: "delete",
27644
+ table,
27645
+ where: whereConditions,
27646
+ schema,
27647
+ config,
27648
+ options
27649
+ })) {
27650
+ const cancelled = cancelledOutcome("delete", `DELETE FROM ${table}`);
27651
+ printMutationOutcome(cancelled, table, 0, humanOutputContext(options));
27652
+ await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(cancelled, table));
27653
+ return;
27654
+ }
27007
27655
  const dbSystem = config.connection.system === "postgresql" ? "postgresql" : "mysql";
27008
27656
  const blacklistManager = new BlacklistManager(config);
27009
27657
  const blacklistValidator = new BlacklistValidator(blacklistManager);
27010
27658
  const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
27659
+ const startedAt = performance.now();
27011
27660
  const result = await executor3.executeDelete(table, whereConditions, schema, {
27012
27661
  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
- }
27662
+ force: options.force,
27663
+ confirm: confirmMutationInteractively
27035
27664
  });
27665
+ const elapsedMs = performance.now() - startedAt;
27666
+ const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto7.randomUUID() : undefined;
27667
+ const auditId = await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
27668
+ if (recoveryEnvelopeId !== undefined) {
27669
+ const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
27670
+ emitRecoveryEnvelope2(new Error(result.error ?? "DELETE failed"), { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
27671
+ }
27672
+ printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
27036
27673
  if (result.status === "error") {
27037
27674
  process.exit(1);
27038
27675
  }
@@ -27040,6 +27677,11 @@ async function deleteCommand(table, options, command) {
27040
27677
  await adapter.disconnect();
27041
27678
  }
27042
27679
  } catch (error) {
27680
+ const { WriteGateRefusal: WriteGateRefusal2 } = await Promise.resolve().then(() => (init_write_gate_prompt(), exports_write_gate_prompt));
27681
+ if (error instanceof WriteGateRefusal2) {
27682
+ printMutationFailure(error, "delete", humanOutputContext(options));
27683
+ process.exit(1);
27684
+ }
27043
27685
  let auditId = null;
27044
27686
  let envelopeId;
27045
27687
  if (options.recovery === true) {
@@ -27058,17 +27700,11 @@ async function deleteCommand(table, options, command) {
27058
27700
  emitRecoveryEnvelope2(error, { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId, auditRef: auditId ?? undefined });
27059
27701
  }
27060
27702
  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));
27703
+ printMutationFailure(error, "delete", humanOutputContext(options));
27068
27704
  process.exit(1);
27069
27705
  }
27070
27706
  if (error instanceof PermissionError) {
27071
- console.error(t_vars("errors.permission_denied", { required: "data-admin" }));
27707
+ console.error(t_vars("errors.permission_denied", { required: error.requiredPermission }));
27072
27708
  console.error(` Operation: ${error.classification.type}`);
27073
27709
  console.error(` Message: ${error.message}`);
27074
27710
  process.exit(1);
@@ -27077,13 +27713,7 @@ async function deleteCommand(table, options, command) {
27077
27713
  printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
27078
27714
  process.exit(1);
27079
27715
  }
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));
27716
+ printMutationFailure(error, "delete", humanOutputContext(options));
27087
27717
  process.exit(1);
27088
27718
  }
27089
27719
  }
@@ -27093,6 +27723,8 @@ var init_delete = __esm(() => {
27093
27723
  init_connection_error_message();
27094
27724
  init_adapters();
27095
27725
  init_data_executor();
27726
+ init_mutation_confirm();
27727
+ init_mutation_outcome();
27096
27728
  init_config();
27097
27729
  init_permission_guard();
27098
27730
  init_blacklist_validator();
@@ -27256,7 +27888,7 @@ async function exportCommand(sql, options, command) {
27256
27888
  }
27257
27889
  }
27258
27890
  async function redisExportBranch(command, options, config) {
27259
- const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
27891
+ const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
27260
27892
  enforceRedisPermission2(command, config.permission);
27261
27893
  const redisAdapter = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? [], config.redis?.mask ?? []);
27262
27894
  await redisAdapter.connect();
@@ -27548,16 +28180,6 @@ var init_export = __esm(() => {
27548
28180
  SQL_PATTERN = /^\s*(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER|SHOW|DESCRIBE)\b/i;
27549
28181
  });
27550
28182
 
27551
- // src/core/sql-parser.ts
27552
- function sqlParser() {
27553
- if (!cached2) {
27554
- const { Parser } = __require("node-sql-parser");
27555
- cached2 = new Parser;
27556
- }
27557
- return cached2;
27558
- }
27559
- var cached2 = null;
27560
-
27561
28183
  // src/core/explain/read-only.ts
27562
28184
  function containsAnalyzeUnsafeNode(node) {
27563
28185
  if (!node || typeof node !== "object")
@@ -28662,12 +29284,12 @@ function compactVisibleSchema(config) {
28662
29284
  const blacklistManager = new BlacklistManager(config);
28663
29285
  const compactSchema = {};
28664
29286
  const rawSchema = config.schema ?? {};
28665
- for (const [tableName, tableSchema] of Object.entries(rawSchema)) {
28666
- if (blacklistManager.isTableBlacklisted(tableName))
29287
+ for (const [tableName2, tableSchema] of Object.entries(rawSchema)) {
29288
+ if (blacklistManager.isTableBlacklisted(tableName2))
28667
29289
  continue;
28668
29290
  const columns = [];
28669
29291
  for (const col of tableSchema.columns || []) {
28670
- if (blacklistManager.isColumnBlacklisted(tableName, col.name))
29292
+ if (blacklistManager.isColumnBlacklisted(tableName2, col.name))
28671
29293
  continue;
28672
29294
  const compactCol = {
28673
29295
  name: col.name,
@@ -28687,7 +29309,7 @@ function compactVisibleSchema(config) {
28687
29309
  columns.push(compactCol);
28688
29310
  }
28689
29311
  const compactTable = {
28690
- name: tableSchema.name || tableName,
29312
+ name: tableSchema.name || tableName2,
28691
29313
  columns
28692
29314
  };
28693
29315
  const rowCount = tableSchema.estimatedRowCount ?? tableSchema.rowCount;
@@ -28703,7 +29325,7 @@ function compactVisibleSchema(config) {
28703
29325
  refColumns: fk.refColumns
28704
29326
  }));
28705
29327
  }
28706
- compactSchema[tableName] = compactTable;
29328
+ compactSchema[tableName2] = compactTable;
28707
29329
  }
28708
29330
  return compactSchema;
28709
29331
  }
@@ -28811,10 +29433,10 @@ function serializeXml(payload) {
28811
29433
  }
28812
29434
  if (Object.keys(payload.schema).length > 0) {
28813
29435
  parts.push(" <tables>");
28814
- for (const [tableName, table] of Object.entries(payload.schema)) {
29436
+ for (const [tableName2, table] of Object.entries(payload.schema)) {
28815
29437
  const rowsAttr = table.rowCount !== undefined ? ` rows="${table.rowCount}"` : "";
28816
29438
  const pkAttr = table.primaryKey ? ` primary_key="${table.primaryKey.join(",")}"` : "";
28817
- parts.push(` <table name="${escapeXml(tableName)}"${rowsAttr}${pkAttr}>`);
29439
+ parts.push(` <table name="${escapeXml(tableName2)}"${rowsAttr}${pkAttr}>`);
28818
29440
  for (const col of table.columns) {
28819
29441
  const pk = col.primaryKey ? ' primary_key="true"' : "";
28820
29442
  const nulls = col.nullable ? ' nullable="true"' : ' nullable="false"';
@@ -28925,9 +29547,9 @@ function serializeMarkdown(payload) {
28925
29547
  parts.push(`*No table schemas cached or active.*`);
28926
29548
  parts.push(``);
28927
29549
  } else {
28928
- for (const [tableName, table] of Object.entries(payload.schema)) {
29550
+ for (const [tableName2, table] of Object.entries(payload.schema)) {
28929
29551
  const rows = table.rowCount !== undefined ? ` (~${table.rowCount.toLocaleString()} rows)` : "";
28930
- parts.push(`### \`${tableName}\`${rows}`);
29552
+ parts.push(`### \`${tableName2}\`${rows}`);
28931
29553
  parts.push(``);
28932
29554
  parts.push(`| Column | Type | Nullable | Primary Key | Default | References |`);
28933
29555
  parts.push(`| :--- | :--- | :---: | :---: | :--- | :--- |`);
@@ -30060,37 +30682,37 @@ async function blacklistList(configPath, format = "text") {
30060
30682
  console.error(`\u26A0 blacklist.columns["${w2.collection}"]: '${w2.raw}' is ignored on mongo connections (${w2.reason}).`);
30061
30683
  }
30062
30684
  }
30063
- async function blacklistTableAdd(tableName, configPath) {
30064
- if (!isValidTableName(tableName)) {
30065
- throw new Error(t_vars("errors.invalid_table_name", { table: tableName }));
30685
+ async function blacklistTableAdd(tableName2, configPath) {
30686
+ if (!isValidTableName(tableName2)) {
30687
+ throw new Error(t_vars("errors.invalid_table_name", { table: tableName2 }));
30066
30688
  }
30067
30689
  const config = await configModule.read(configPath);
30068
30690
  const blacklist = getOrInitBlacklist(config);
30069
- if (blacklist.tables.includes(tableName)) {
30070
- throw new Error(t_vars("errors.table_already_blacklisted", { table: tableName }));
30691
+ if (blacklist.tables.includes(tableName2)) {
30692
+ throw new Error(t_vars("errors.table_already_blacklisted", { table: tableName2 }));
30071
30693
  }
30072
30694
  const newBlacklist = {
30073
30695
  ...blacklist,
30074
- tables: [...blacklist.tables, tableName]
30696
+ tables: [...blacklist.tables, tableName2]
30075
30697
  };
30076
30698
  await configModule.write(configPath, { ...config, blacklist: newBlacklist });
30077
- console.log(t_vars("blacklist.table_added", { table: tableName }));
30699
+ console.log(t_vars("blacklist.table_added", { table: tableName2 }));
30078
30700
  }
30079
- async function blacklistTableRemove(tableName, configPath) {
30080
- if (!isValidTableName(tableName)) {
30081
- throw new Error(t_vars("errors.invalid_table_name", { table: tableName }));
30701
+ async function blacklistTableRemove(tableName2, configPath) {
30702
+ if (!isValidTableName(tableName2)) {
30703
+ throw new Error(t_vars("errors.invalid_table_name", { table: tableName2 }));
30082
30704
  }
30083
30705
  const config = await configModule.read(configPath);
30084
30706
  const blacklist = getOrInitBlacklist(config);
30085
- if (!blacklist.tables.includes(tableName)) {
30086
- throw new Error(t_vars("errors.table_not_in_blacklist", { table: tableName }));
30707
+ if (!blacklist.tables.includes(tableName2)) {
30708
+ throw new Error(t_vars("errors.table_not_in_blacklist", { table: tableName2 }));
30087
30709
  }
30088
30710
  const newBlacklist = {
30089
30711
  ...blacklist,
30090
- tables: blacklist.tables.filter((t2) => t2 !== tableName)
30712
+ tables: blacklist.tables.filter((t2) => t2 !== tableName2)
30091
30713
  };
30092
30714
  await configModule.write(configPath, { ...config, blacklist: newBlacklist });
30093
- console.log(t_vars("blacklist.table_removed", { table: tableName }));
30715
+ console.log(t_vars("blacklist.table_removed", { table: tableName2 }));
30094
30716
  }
30095
30717
  async function blacklistColumnAdd(identifier, configPath) {
30096
30718
  const parsed = parseColumnIdentifier(identifier);
@@ -30160,17 +30782,17 @@ var init_blacklist2 = __esm(() => {
30160
30782
  }
30161
30783
  });
30162
30784
  tableCmd = blacklistCommand.command("table").description(t("blacklist.tables_label"));
30163
- tableCmd.command("add <table>").description("Add table to blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName, options) => {
30785
+ tableCmd.command("add <table>").description("Add table to blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName2, options) => {
30164
30786
  try {
30165
- await blacklistTableAdd(tableName, options.config || DEFAULT_CONFIG_PATH);
30787
+ await blacklistTableAdd(tableName2, options.config || DEFAULT_CONFIG_PATH);
30166
30788
  } catch (error) {
30167
30789
  console.error(error.message);
30168
30790
  process.exit(1);
30169
30791
  }
30170
30792
  });
30171
- tableCmd.command("remove <table>").description("Remove table from blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName, options) => {
30793
+ tableCmd.command("remove <table>").description("Remove table from blacklist").option("--config <path>", "Path to .dbcli config file", DEFAULT_CONFIG_PATH).action(async (tableName2, options) => {
30172
30794
  try {
30173
- await blacklistTableRemove(tableName, options.config || DEFAULT_CONFIG_PATH);
30795
+ await blacklistTableRemove(tableName2, options.config || DEFAULT_CONFIG_PATH);
30174
30796
  } catch (error) {
30175
30797
  console.error(error.message);
30176
30798
  process.exit(1);
@@ -30804,7 +31426,7 @@ function consumeCreateTable(node, statement, context) {
30804
31426
  return;
30805
31427
  }
30806
31428
  const identity = resolveTableIdentifier(parsedIdentifier);
30807
- const tableName = qualifiedTableName(identity);
31429
+ const tableName2 = qualifiedTableName(identity);
30808
31430
  const unsupportedTableField = firstMeaningfulUnsupportedField(node, [
30809
31431
  "type",
30810
31432
  "keyword",
@@ -30814,14 +31436,14 @@ function consumeCreateTable(node, statement, context) {
30814
31436
  ]);
30815
31437
  if (unsupportedTableField) {
30816
31438
  context.unparsed.push({
30817
- location: tableName,
31439
+ location: tableName2,
30818
31440
  reason: `blocked: unsupported CREATE TABLE ${unsupportedTableField}`
30819
31441
  });
30820
31442
  return;
30821
31443
  }
30822
31444
  if (!Array.isArray(node.create_definitions)) {
30823
31445
  context.unparsed.push({
30824
- location: tableName,
31446
+ location: tableName2,
30825
31447
  reason: "blocked: unsupported CREATE TABLE without column definitions"
30826
31448
  });
30827
31449
  return;
@@ -30836,7 +31458,7 @@ function consumeCreateTable(node, statement, context) {
30836
31458
  const definitions = [];
30837
31459
  for (const value of node.create_definitions) {
30838
31460
  if (!isAst(value)) {
30839
- pushUnsupportedDefinition(tableName, "malformed", context.unparsed);
31461
+ pushUnsupportedDefinition(tableName2, "malformed", context.unparsed);
30840
31462
  continue;
30841
31463
  }
30842
31464
  definitions.push(value);
@@ -30862,14 +31484,14 @@ function consumeCreateTable(node, statement, context) {
30862
31484
  consumeTablePrimaryKey(definition, table, context.unparsed);
30863
31485
  continue;
30864
31486
  }
30865
- pushUnsupportedDefinition(tableName, stringValue2(definition.constraint_type) ?? stringValue2(definition.resource) ?? "malformed", context.unparsed);
31487
+ pushUnsupportedDefinition(tableName2, stringValue2(definition.constraint_type) ?? stringValue2(definition.resource) ?? "malformed", context.unparsed);
30866
31488
  }
30867
31489
  const key = tableIdentityKey(identity);
30868
31490
  if (context.tablesByIdentity.has(key)) {
30869
- context.duplicateTableIdentity ??= tableName;
31491
+ context.duplicateTableIdentity ??= tableName2;
30870
31492
  context.unparsed.push({
30871
- location: tableName,
30872
- reason: `blocked: duplicate table identity '${tableName}'`
31493
+ location: tableName2,
31494
+ reason: `blocked: duplicate table identity '${tableName2}'`
30873
31495
  });
30874
31496
  return;
30875
31497
  }
@@ -31012,12 +31634,12 @@ function consumeCreateIndex(node, statement, context) {
31012
31634
  return;
31013
31635
  }
31014
31636
  const identity = resolveTableIdentifier(parsedIdentifier);
31015
- const tableName = qualifiedTableName(identity);
31637
+ const tableName2 = qualifiedTableName(identity);
31016
31638
  const target = context.tablesByIdentity.get(tableIdentityKey(identity));
31017
31639
  if (!target) {
31018
31640
  context.unparsed.push({
31019
31641
  location: indexName ?? "index",
31020
- reason: `blocked: CREATE INDEX targets unknown table '${tableName}'`
31642
+ reason: `blocked: CREATE INDEX targets unknown table '${tableName2}'`
31021
31643
  });
31022
31644
  return;
31023
31645
  }
@@ -31050,7 +31672,7 @@ function consumeCreateIndex(node, statement, context) {
31050
31672
  }
31051
31673
  if (columns.value.length === 0) {
31052
31674
  context.unparsed.push({
31053
- location: indexName ?? `${tableName} index`,
31675
+ location: indexName ?? `${tableName2} index`,
31054
31676
  reason: "blocked: malformed CREATE INDEX without columns"
31055
31677
  });
31056
31678
  return;
@@ -31498,9 +32120,9 @@ function firstMeaningfulUnsupportedField(value, allowed) {
31498
32120
  }
31499
32121
  return null;
31500
32122
  }
31501
- function pushUnsupportedDefinition(tableName, definition, unparsed) {
32123
+ function pushUnsupportedDefinition(tableName2, definition, unparsed) {
31502
32124
  unparsed.push({
31503
- location: `${tableName} (${definition})`,
32125
+ location: `${tableName2} (${definition})`,
31504
32126
  reason: `blocked: unsupported table definition '${definition}'`
31505
32127
  });
31506
32128
  }
@@ -31626,7 +32248,7 @@ function collectBlocks(text3, unparsed) {
31626
32248
  function parseModel2(block, unparsed) {
31627
32249
  const fields = [];
31628
32250
  const pendingIndexes = [];
31629
- let tableName = block.name;
32251
+ let tableName2 = block.name;
31630
32252
  let hasTableMap = false;
31631
32253
  for (const sourceLine of block.lines) {
31632
32254
  const line = stripLineComment(sourceLine.text).trim();
@@ -31645,7 +32267,7 @@ function parseModel2(block, unparsed) {
31645
32267
  addBlocked2(unparsed, block.name, `malformed or duplicate '@@map' attribute`);
31646
32268
  continue;
31647
32269
  }
31648
- tableName = mapped;
32270
+ tableName2 = mapped;
31649
32271
  hasTableMap = true;
31650
32272
  continue;
31651
32273
  }
@@ -31671,12 +32293,12 @@ function parseModel2(block, unparsed) {
31671
32293
  }
31672
32294
  return {
31673
32295
  name: block.name,
31674
- tableName,
32296
+ tableName: tableName2,
31675
32297
  fields,
31676
32298
  pendingIndexes,
31677
32299
  pendingRelations: [],
31678
32300
  columnNames: new Map,
31679
- table: { identity: { table: tableName }, columns: [], indexes: [], foreignKeys: [] }
32301
+ table: { identity: { table: tableName2 }, columns: [], indexes: [], foreignKeys: [] }
31680
32302
  };
31681
32303
  }
31682
32304
  function parseField2(line, modelName, lineNumber, unparsed) {
@@ -32735,31 +33357,31 @@ function compareSnapshots(before, after) {
32735
33357
  const removedColumns = [];
32736
33358
  const modifiedColumns = [];
32737
33359
  const indexChanges = [];
32738
- for (const tableName of addedTables) {
32739
- const added = after.tables[tableName];
33360
+ for (const tableName2 of addedTables) {
33361
+ const added = after.tables[tableName2];
32740
33362
  if (!added)
32741
33363
  continue;
32742
33364
  for (const col of added.columns) {
32743
33365
  addedColumns.push({
32744
- table: tableName,
33366
+ table: tableName2,
32745
33367
  column: col.name,
32746
33368
  type: col.type,
32747
33369
  nullable: col.nullable
32748
33370
  });
32749
33371
  }
32750
33372
  }
32751
- for (const tableName of removedTables) {
32752
- const removed = before.tables[tableName];
33373
+ for (const tableName2 of removedTables) {
33374
+ const removed = before.tables[tableName2];
32753
33375
  if (!removed)
32754
33376
  continue;
32755
33377
  for (const col of removed.columns) {
32756
- removedColumns.push({ table: tableName, column: col.name, type: col.type });
33378
+ removedColumns.push({ table: tableName2, column: col.name, type: col.type });
32757
33379
  }
32758
33380
  }
32759
33381
  const commonTables = Array.from(afterTables).filter((t2) => beforeTables.has(t2));
32760
- for (const tableName of commonTables) {
32761
- const beforeTable = before.tables[tableName];
32762
- const afterTable = after.tables[tableName];
33382
+ for (const tableName2 of commonTables) {
33383
+ const beforeTable = before.tables[tableName2];
33384
+ const afterTable = after.tables[tableName2];
32763
33385
  if (!beforeTable || !afterTable)
32764
33386
  continue;
32765
33387
  const beforeColMap = new Map(beforeTable.columns.map((c) => [c.name, c]));
@@ -32767,7 +33389,7 @@ function compareSnapshots(before, after) {
32767
33389
  for (const [name, col] of afterColMap) {
32768
33390
  if (!beforeColMap.has(name)) {
32769
33391
  addedColumns.push({
32770
- table: tableName,
33392
+ table: tableName2,
32771
33393
  column: name,
32772
33394
  type: col.type,
32773
33395
  nullable: col.nullable
@@ -32776,7 +33398,7 @@ function compareSnapshots(before, after) {
32776
33398
  }
32777
33399
  for (const [name, col] of beforeColMap) {
32778
33400
  if (!afterColMap.has(name)) {
32779
- removedColumns.push({ table: tableName, column: name, type: col.type });
33401
+ removedColumns.push({ table: tableName2, column: name, type: col.type });
32780
33402
  }
32781
33403
  }
32782
33404
  for (const [name, afterCol] of afterColMap) {
@@ -32785,7 +33407,7 @@ function compareSnapshots(before, after) {
32785
33407
  continue;
32786
33408
  if (beforeCol.type.toLowerCase() !== afterCol.type.toLowerCase() || beforeCol.nullable !== afterCol.nullable) {
32787
33409
  modifiedColumns.push({
32788
- table: tableName,
33410
+ table: tableName2,
32789
33411
  column: name,
32790
33412
  before: { type: beforeCol.type, nullable: beforeCol.nullable },
32791
33413
  after: { type: afterCol.type, nullable: afterCol.nullable }
@@ -32796,12 +33418,12 @@ function compareSnapshots(before, after) {
32796
33418
  const afterIndexes = new Map((afterTable.indexes || []).map((i) => [i.name, i]));
32797
33419
  for (const name of afterIndexes.keys()) {
32798
33420
  if (!beforeIndexes.has(name)) {
32799
- indexChanges.push({ table: tableName, name, change: "added" });
33421
+ indexChanges.push({ table: tableName2, name, change: "added" });
32800
33422
  }
32801
33423
  }
32802
33424
  for (const name of beforeIndexes.keys()) {
32803
33425
  if (!afterIndexes.has(name)) {
32804
- indexChanges.push({ table: tableName, name, change: "removed" });
33426
+ indexChanges.push({ table: tableName2, name, change: "removed" });
32805
33427
  }
32806
33428
  }
32807
33429
  }
@@ -38394,10 +39016,10 @@ async function loadSchemaContext(dbcliPath, connectionName) {
38394
39016
  const loader3 = new SchemaLayeredLoader(dbcliPath, { connectionName });
38395
39017
  const { cache, index } = await loader3.initialize();
38396
39018
  const schema = {};
38397
- for (const tableName of Object.keys(index?.tables ?? {})) {
38398
- const table = await cache.getTableSchema(tableName);
39019
+ for (const tableName2 of Object.keys(index?.tables ?? {})) {
39020
+ const table = await cache.getTableSchema(tableName2);
38399
39021
  if (table) {
38400
- schema[tableName] = table;
39022
+ schema[tableName2] = table;
38401
39023
  }
38402
39024
  }
38403
39025
  return buildSchemaContext(schema);
@@ -39833,9 +40455,9 @@ var init_select_star = __esm(() => {
39833
40455
  span: findingSpan(ctx.sql, "select *"),
39834
40456
  schemaVerified: false
39835
40457
  };
39836
- const tableName = singlePhysicalTable(ctx.ast);
39837
- if (ctx.schema.available && tableName && isSoleUnqualifiedStar(ctx.ast)) {
39838
- const table = ctx.schema.getTable(tableName);
40458
+ const tableName2 = singlePhysicalTable(ctx.ast);
40459
+ if (ctx.schema.available && tableName2 && isSoleUnqualifiedStar(ctx.ast)) {
40460
+ const table = ctx.schema.getTable(tableName2);
39839
40461
  if (table && table.columns.length > 0) {
39840
40462
  if (wildcardIndexes.length === 1) {
39841
40463
  const wildcardIndex = wildcardIndexes[0];
@@ -40744,17 +41366,17 @@ function canonicalizeEvidenceReceiptCommand(command, operation = "assert") {
40744
41366
  function sha2563(value) {
40745
41367
  return `sha256:${createHash5("sha256").update(value).digest("hex")}`;
40746
41368
  }
40747
- function verdictFingerprint(verdict) {
40748
- if (!record3(verdict) || typeof verdict.pass !== "boolean" || !Array.isArray(verdict.checks) || verdict.checks.length > 1000) {
41369
+ function verdictFingerprint(verdict2) {
41370
+ if (!record3(verdict2) || typeof verdict2.pass !== "boolean" || !Array.isArray(verdict2.checks) || verdict2.checks.length > 1000) {
40749
41371
  throw new EvidenceReceiptValidationError("verdict must contain bounded pass bits");
40750
41372
  }
40751
- const bits = verdict.checks.map((check) => {
41373
+ const bits = verdict2.checks.map((check) => {
40752
41374
  if (!record3(check) || Object.keys(check).some((key) => key !== "pass") || typeof check.pass !== "boolean") {
40753
41375
  throw new EvidenceReceiptValidationError("verdict checks must contain only pass bits");
40754
41376
  }
40755
41377
  return check.pass;
40756
41378
  });
40757
- return sha2563(JSON.stringify({ pass: verdict.pass, checks: bits }));
41379
+ return sha2563(JSON.stringify({ pass: verdict2.pass, checks: bits }));
40758
41380
  }
40759
41381
  function verificationOutcomeFingerprint(status2, artifactPersisted) {
40760
41382
  if (!isVerificationStatus(status2)) {
@@ -41052,7 +41674,7 @@ var init_assert = __esm(() => {
41052
41674
  const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
41053
41675
  await adapter.connect();
41054
41676
  let auditRef = null;
41055
- let verdict;
41677
+ let verdict2;
41056
41678
  try {
41057
41679
  const blacklistManager = new BlacklistManager(config);
41058
41680
  const blacklistValidator = new BlacklistValidator(blacklistManager);
@@ -41079,9 +41701,9 @@ var init_assert = __esm(() => {
41079
41701
  });
41080
41702
  checks.push(...compareAgainst(current, baseline, options.tolerance));
41081
41703
  }
41082
- verdict = { pass: checks.every((c) => c.pass), checks };
41704
+ verdict2 = { pass: checks.every((c) => c.pass), checks };
41083
41705
  auditRef = await writeAuditEntry(config, "assert", options, {
41084
- success: verdict.pass,
41706
+ success: verdict2.pass,
41085
41707
  sql: query
41086
41708
  });
41087
41709
  } finally {
@@ -41091,7 +41713,7 @@ var init_assert = __esm(() => {
41091
41713
  if (options.writeVerificationArtifact === true && verificationSubject) {
41092
41714
  try {
41093
41715
  const artifact2 = buildAssertVerificationArtifact({
41094
- verdict,
41716
+ verdict: verdict2,
41095
41717
  subject: verificationSubject,
41096
41718
  summary: options.verificationSummary,
41097
41719
  argv: process.argv,
@@ -41110,7 +41732,7 @@ var init_assert = __esm(() => {
41110
41732
  context: await buildEvidenceReceiptContext(config, process.cwd()),
41111
41733
  auditRef,
41112
41734
  verificationArtifactRef: verificationArtifactPath ? basename7(verificationArtifactPath) : null,
41113
- verdict
41735
+ verdict: verdict2
41114
41736
  });
41115
41737
  evidenceReceiptPath = await writeEvidenceReceipt(process.cwd(), options.evidenceReceipt, receipt);
41116
41738
  } catch {
@@ -41120,23 +41742,23 @@ var init_assert = __esm(() => {
41120
41742
  }
41121
41743
  if (options.format === "json") {
41122
41744
  console.log(JSON.stringify({
41123
- ...verdict,
41745
+ ...verdict2,
41124
41746
  ...verificationArtifactPath ? { verificationArtifactPath } : {},
41125
41747
  ...evidenceReceiptPath ? { evidenceReceiptPath } : {}
41126
41748
  }, null, 2));
41127
41749
  } else {
41128
- for (const c of verdict.checks) {
41750
+ for (const c of verdict2.checks) {
41129
41751
  console.log(`${c.pass ? "PASS" : "FAIL"} ${c.name} expected=${c.expected} actual=${c.actual}`);
41130
41752
  }
41131
41753
  console.log(`
41132
- Verdict: ${verdict.pass ? "PASS" : "FAIL"}`);
41754
+ Verdict: ${verdict2.pass ? "PASS" : "FAIL"}`);
41133
41755
  if (verificationArtifactPath) {
41134
41756
  console.log(`Verification artifact: ${verificationArtifactPath}`);
41135
41757
  }
41136
41758
  if (evidenceReceiptPath)
41137
41759
  console.log(`Evidence receipt: ${evidenceReceiptPath}`);
41138
41760
  }
41139
- process.exit(verdict.pass || options.fail === false ? 0 : 1);
41761
+ process.exit(verdict2.pass || options.fail === false ? 0 : 1);
41140
41762
  } catch (error) {
41141
41763
  if (error instanceof Error) {
41142
41764
  console.error(error.message);
@@ -44132,6 +44754,7 @@ var init_repl_engine = __esm(() => {
44132
44754
  init_meta_commands();
44133
44755
  init_history_manager();
44134
44756
  init_permission_guard();
44757
+ init_redis();
44135
44758
  init_query_result_formatter();
44136
44759
  init_message_loader();
44137
44760
  init_sql_tables();
@@ -44169,9 +44792,9 @@ function createCompleter(ctx) {
44169
44792
  const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
44170
44793
  const tableHits = matchWithSuffix(allTableNames, lastWord);
44171
44794
  const colHits = isColumnPosition(upperLine) ? (() => {
44172
- const tableName = extractTableFromLine(trimmed, allTableNames);
44173
- if (tableName && ctx.columnsByTable[tableName]) {
44174
- return matchWithSuffix([...ctx.columnsByTable[tableName]], lastWord);
44795
+ const tableName2 = extractTableFromLine(trimmed, allTableNames);
44796
+ if (tableName2 && ctx.columnsByTable[tableName2]) {
44797
+ return matchWithSuffix([...ctx.columnsByTable[tableName2]], lastWord);
44175
44798
  }
44176
44799
  return matchWithSuffix(uniqueColumns, lastWord);
44177
44800
  })() : [[], lastWord];
@@ -45260,13 +45883,16 @@ class DDLExecutor {
45260
45883
  };
45261
45884
  }
45262
45885
  if (isDestructiveOperation2(operation) && !options.force) {
45263
- const confirmed = await promptUser.confirm(`This is a destructive operation (${operation.kind}). Proceed?`);
45886
+ if (!options.confirm) {
45887
+ 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.");
45888
+ }
45889
+ const confirmed = await options.confirm({ operation: operation.kind, sql });
45264
45890
  if (!confirmed) {
45265
45891
  return {
45266
- status: "success",
45892
+ status: "cancelled",
45267
45893
  operation: operation.kind,
45268
45894
  sql,
45269
- warnings: [...warnings, "Operation cancelled by user"],
45895
+ warnings,
45270
45896
  timestamp: timestamp2,
45271
45897
  dryRun: false
45272
45898
  };
@@ -45341,8 +45967,15 @@ class DDLExecutor {
45341
45967
  } catch {}
45342
45968
  }
45343
45969
  }
45970
+ var DDLExecutionError;
45344
45971
  var init_ddl_executor = __esm(() => {
45345
- init_prompts();
45972
+ DDLExecutionError = class DDLExecutionError extends Error {
45973
+ constructor(message) {
45974
+ super(message);
45975
+ this.name = "DDLExecutionError";
45976
+ Object.setPrototypeOf(this, DDLExecutionError.prototype);
45977
+ }
45978
+ };
45346
45979
  });
45347
45980
 
45348
45981
  // src/commands/migrate.ts
@@ -45386,7 +46019,8 @@ async function runDDL(operation, opts, command) {
45386
46019
  const executor3 = new DDLExecutor(adapter, generator, config.permission, blacklistManager);
45387
46020
  const result = await executor3.execute(operation, {
45388
46021
  execute: opts.execute,
45389
- force: opts.force
46022
+ force: opts.force,
46023
+ confirm: confirmDdlInteractively
45390
46024
  });
45391
46025
  console.log(JSON.stringify({
45392
46026
  status: result.status,
@@ -45428,6 +46062,7 @@ var init_migrate = __esm(() => {
45428
46062
  init_adapters();
45429
46063
  init_ddl2();
45430
46064
  init_ddl_executor();
46065
+ init_mutation_confirm();
45431
46066
  init_config_path();
45432
46067
  migrateCommand = new Command("migrate").description(t("migrate.description"));
45433
46068
  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) => {