@carllee1983/dbcli 2.0.0 → 2.1.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.
- package/.cursor/rules/dbcli.mdc +8 -1
- package/.cursor/skills/dbcli/reference.md +107 -5
- package/.github/skills/dbcli/SKILL.md +8 -1
- package/.github/skills/dbcli/reference.md +107 -5
- package/CHANGELOG.md +46 -0
- package/assets/SKILL.md +8 -1
- package/assets/SKILL.zh-TW.md +7 -1
- package/assets/reference.md +107 -5
- package/dist/cli-runtime.mjs +553 -39
- package/dist/cli.mjs +2 -1
- package/dist/core.mjs +34 -2
- package/package.json +2 -1
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +8 -1
- package/plugins/dbcli-agent/skills/dbcli/reference.md +107 -5
- package/skills/dbcli/SKILL.md +8 -1
- package/skills/dbcli/reference.md +107 -5
package/dist/cli-runtime.mjs
CHANGED
|
@@ -51,7 +51,7 @@ var package_default;
|
|
|
51
51
|
var init_package = __esm(() => {
|
|
52
52
|
package_default = {
|
|
53
53
|
name: "@carllee1983/dbcli",
|
|
54
|
-
version: "2.
|
|
54
|
+
version: "2.1.0",
|
|
55
55
|
description: "Database CLI for AI agents",
|
|
56
56
|
type: "module",
|
|
57
57
|
publishConfig: {
|
|
@@ -134,6 +134,7 @@ var init_package = __esm(() => {
|
|
|
134
134
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
135
135
|
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
136
136
|
typecheck: "tsc --noEmit --pretty false",
|
|
137
|
+
"typecheck:tests": "tsc --noEmit --pretty false -p tsconfig.tests.json",
|
|
137
138
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
138
139
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
139
140
|
"format:check": "prettier --check .",
|
|
@@ -8903,6 +8904,15 @@ var init_messages = __esm(() => {
|
|
|
8903
8904
|
health: {
|
|
8904
8905
|
description: "Show AuditLogger health snapshot (size, entries, lock state, last write)"
|
|
8905
8906
|
},
|
|
8907
|
+
write_gate: {
|
|
8908
|
+
description: "Summarize tier-two write-gate decisions (ADR 0010's measurement)",
|
|
8909
|
+
header: "Write-gate summary \u2014 connection: {conn}",
|
|
8910
|
+
window: "Audit entries scanned: {scanned}, {from} \u2192 {to}",
|
|
8911
|
+
decisions: "Tier-two evaluations: {count}, {from} \u2192 {to}",
|
|
8912
|
+
tier_one: "Tier one, counted apart: {count} ({outcomes})",
|
|
8913
|
+
no_decisions: "Tier two was never reached in this window. Per ADR 0010 that falsifies the criterion, not the gate: the shapes it looks for are not the shapes people write.",
|
|
8914
|
+
no_entries: "The audit log is empty, so this says nothing about the gate either way. Run some writes, or check `dbcli audit health`."
|
|
8915
|
+
},
|
|
8906
8916
|
disabled_hint: "Audit is disabled (audit.enabled = false in .dbcli). Use 'dbcli audit health' for details.",
|
|
8907
8917
|
no_entries: "No audit entries.",
|
|
8908
8918
|
show_no_match: "No audit entry matches '{prefix}'.",
|
|
@@ -9211,6 +9221,15 @@ var init_messages2 = __esm(() => {
|
|
|
9211
9221
|
health: {
|
|
9212
9222
|
description: "\u986F\u793A AuditLogger \u5065\u5EB7\u72C0\u614B\uFF08\u6A94\u6848\u5927\u5C0F\u3001\u7B46\u6578\u3001lock \u72C0\u614B\u3001\u6700\u5F8C\u5BEB\u5165\u7D50\u679C\uFF09"
|
|
9213
9223
|
},
|
|
9224
|
+
write_gate: {
|
|
9225
|
+
description: "\u7D71\u8A08\u7B2C\u4E8C\u7D1A\u5BEB\u5165\u9598\u9580\u7684\u5224\u5B9A\uFF08ADR 0010 \u62BC\u6CE8\u7684\u90A3\u500B\u91CF\u6E2C\uFF09",
|
|
9226
|
+
header: "\u5BEB\u5165\u9598\u9580\u6458\u8981 \u2014 \u9023\u7DDA\uFF1A{conn}",
|
|
9227
|
+
window: "\u6383\u904E\u7684\u7A3D\u6838\u7D00\u9304\uFF1A{scanned} \u7B46\uFF0C{from} \u2192 {to}",
|
|
9228
|
+
decisions: "\u7B2C\u4E8C\u7D1A\u5224\u5B9A\uFF1A{count} \u6B21\uFF0C{from} \u2192 {to}",
|
|
9229
|
+
tier_one: "\u7B2C\u4E00\u7D1A\uFF0C\u5206\u958B\u8A08\u7B97\uFF1A{count} \u6B21\uFF08{outcomes}\uFF09",
|
|
9230
|
+
no_decisions: "\u9019\u6BB5\u5340\u9593\u5167\u7B2C\u4E8C\u7D1A\u4E00\u6B21\u90FD\u6C92\u88AB\u89F8\u53CA\u3002\u4F9D ADR 0010\uFF0C\u9019\u63A8\u7FFB\u7684\u662F\u5224\u6E96\u800C\u4E0D\u662F\u9598\u9580\uFF1A\u5B83\u5728\u627E\u7684\u5F62\u72C0\u4E0D\u662F\u5927\u5BB6\u5BE6\u969B\u6703\u5BEB\u7684\u5F62\u72C0\u3002",
|
|
9231
|
+
no_entries: "\u7A3D\u6838\u7D00\u9304\u662F\u7A7A\u7684\uFF0C\u6240\u4EE5\u9019\u5C0D\u9598\u9580\u6C92\u6709\u4EFB\u4F55\u7D50\u8AD6\u3002\u5148\u8DD1\u4E00\u4E9B\u5BEB\u5165\uFF0C\u6216\u7528 `dbcli audit health` \u6AA2\u67E5\u3002"
|
|
9232
|
+
},
|
|
9214
9233
|
disabled_hint: "Audit \u5DF2\u505C\u7528\uFF08.dbcli \u4E2D audit.enabled = false\uFF09\u3002\u57F7\u884C `dbcli audit health` \u67E5\u770B\u8A73\u60C5\u3002",
|
|
9215
9234
|
no_entries: "\u6C92\u6709 audit \u7D00\u9304\u3002",
|
|
9216
9235
|
show_no_match: "\u627E\u4E0D\u5230\u7B26\u5408 '{prefix}' \u7684 audit \u7D00\u9304\u3002",
|
|
@@ -9422,7 +9441,8 @@ var init_shell = __esm(() => {
|
|
|
9422
9441
|
multiline_cancelled: "Multiline input cancelled.",
|
|
9423
9442
|
timing_display: "Time: {ms}ms",
|
|
9424
9443
|
rows_display: "{count} row(s)",
|
|
9425
|
-
unknown_command: "Unknown command: '{command}'. Type '.help' for available commands."
|
|
9444
|
+
unknown_command: "Unknown command: '{command}'. Type '.help' for available commands.",
|
|
9445
|
+
subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels."
|
|
9426
9446
|
};
|
|
9427
9447
|
});
|
|
9428
9448
|
|
|
@@ -9448,7 +9468,8 @@ var init_shell2 = __esm(() => {
|
|
|
9448
9468
|
multiline_cancelled: "\u5DF2\u53D6\u6D88\u591A\u884C\u8F38\u5165\u3002",
|
|
9449
9469
|
timing_display: "\u8017\u6642\uFF1A{ms}ms",
|
|
9450
9470
|
rows_display: "{count} \u7B46\u8CC7\u6599",
|
|
9451
|
-
unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002"
|
|
9471
|
+
unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002",
|
|
9472
|
+
subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002"
|
|
9452
9473
|
};
|
|
9453
9474
|
});
|
|
9454
9475
|
|
|
@@ -9482,14 +9503,20 @@ var init_ceremony = __esm(() => {
|
|
|
9482
9503
|
gate_ddl_warning_untargeted: "\u26A0\uFE0F This is a destructive schema change and cannot be undone.",
|
|
9483
9504
|
gate_unparseable_warning: "\u26A0\uFE0F dbcli could not parse this statement, so it is treated as affecting everything.",
|
|
9484
9505
|
gate_multiple_statements_warning: "\u26A0\uFE0F This is more than one statement, and dbcli will not judge them one at a time.",
|
|
9506
|
+
gate_multi_table_warning: "This write joins another table. How many rows of the target it touches depends on the data, not on the statement, so it cannot be checked before it runs.",
|
|
9507
|
+
gate_nested_write_warning: "\u26A0\uFE0F This statement carries a second write inside it, and dbcli will not read the tier of that write off this one.",
|
|
9485
9508
|
gate_typed_prompt: "Type {phrase} to run it",
|
|
9486
9509
|
gate_typed_mismatch: "That did not match {phrase}. Nothing was run.",
|
|
9487
9510
|
gate_refused: "Refused: this statement is not limited to specific rows and nobody is here to confirm it (reason={reason}).",
|
|
9511
|
+
gate_refused_in_shell: "Refused: a dbcli subcommand started from the shell has no way to ask you, even though you are right here (reason={reason}).",
|
|
9488
9512
|
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
9513
|
gate_remedy_ddl_destruction: "No flag skips this. Run it from an interactive terminal, or apply the schema change through a reviewed migration.",
|
|
9490
9514
|
gate_remedy_unparseable: "dbcli could not parse this statement and will not guess. Simplify it, or run it from an interactive terminal.",
|
|
9491
9515
|
gate_remedy_multiple_statements: "Run one statement per command.",
|
|
9516
|
+
gate_remedy_multi_table: "Rewrite it as a single-table statement \u2014 move the other table into a subquery in the WHERE \u2014 or run it where someone can confirm it.",
|
|
9517
|
+
gate_remedy_nested_write: "Run the nested write as its own statement, or run this one where someone can confirm it.",
|
|
9492
9518
|
gate_remedy_non_unique_where: "Match on a primary key or a uniquely indexed column, or run it from an interactive terminal.",
|
|
9519
|
+
gate_remedy_in_shell: "Type the statement at the dbcli> prompt instead \u2014 there the shell asks you to type the target table name.",
|
|
9493
9520
|
recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
|
|
9494
9521
|
recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
|
|
9495
9522
|
blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
|
|
@@ -9526,14 +9553,20 @@ var init_ceremony2 = __esm(() => {
|
|
|
9526
9553
|
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
9554
|
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
9555
|
gate_multiple_statements_warning: "\u26A0\uFE0F \u9019\u88E1\u4E0D\u53EA\u4E00\u53E5\u8A9E\u53E5\uFF0Cdbcli \u4E0D\u6703\u9010\u53E5\u5224\u65B7\u3002",
|
|
9556
|
+
gate_multi_table_warning: "\u9019\u500B\u5BEB\u5165\u63A5\u4E86\u53E6\u4E00\u5F35\u8868\u3002\u5B83\u6703\u52D5\u5230\u76EE\u6A19\u8868\u5E7E\u5217\u53D6\u6C7A\u65BC\u8CC7\u6599\u800C\u4E0D\u662F\u8A9E\u53E5\u672C\u8EAB\uFF0C\u57F7\u884C\u524D\u7121\u6CD5\u6AA2\u67E5\u3002",
|
|
9557
|
+
gate_nested_write_warning: "\u26A0\uFE0F \u9019\u53E5\u88E1\u9762\u9084\u593E\u8457\u53E6\u4E00\u500B\u5BEB\u5165\uFF0Cdbcli \u4E0D\u6703\u62FF\u9019\u53E5\u7684\u5C64\u7D1A\u4EE3\u66FF\u90A3\u500B\u5BEB\u5165\u7684\u5C64\u7D1A\u3002",
|
|
9529
9558
|
gate_typed_prompt: "\u8F38\u5165 {phrase} \u624D\u6703\u57F7\u884C",
|
|
9530
9559
|
gate_typed_mismatch: "\u8F38\u5165\u7684\u5167\u5BB9\u8207 {phrase} \u4E0D\u7B26\uFF0C\u6C92\u6709\u57F7\u884C\u4EFB\u4F55\u8A9E\u53E5\u3002",
|
|
9531
9560
|
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",
|
|
9561
|
+
gate_refused_in_shell: "\u5DF2\u62D2\u7D55\uFF1A\u5F9E shell \u555F\u52D5\u7684 dbcli \u5B50\u6307\u4EE4\u6C92\u6709\u8FA6\u6CD5\u5411\u4F60\u63D0\u554F\uFF0C\u5373\u4F7F\u4F60\u4EBA\u5C31\u5728\u9019\u88E1\uFF08reason={reason}\uFF09\u3002",
|
|
9532
9562
|
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
9563
|
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
9564
|
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
9565
|
gate_remedy_multiple_statements: "\u4E00\u6B21\u53EA\u57F7\u884C\u4E00\u53E5\u8A9E\u53E5\u3002",
|
|
9566
|
+
gate_remedy_multi_table: "\u6539\u5BEB\u6210\u55AE\u8868\u8A9E\u53E5\u2014\u2014\u628A\u53E6\u4E00\u5F35\u8868\u79FB\u9032 WHERE \u7684\u5B50\u67E5\u8A62\u88E1\u2014\u2014\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
|
|
9567
|
+
gate_remedy_nested_write: "\u628A\u88E1\u9762\u90A3\u500B\u5BEB\u5165\u62C6\u6210\u7368\u7ACB\u7684\u4E00\u53E5\u4F86\u57F7\u884C\uFF0C\u6216\u6539\u5728\u6709\u4EBA\u53EF\u4EE5\u78BA\u8A8D\u7684\u5730\u65B9\u57F7\u884C\u3002",
|
|
9536
9568
|
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",
|
|
9569
|
+
gate_remedy_in_shell: "\u8ACB\u76F4\u63A5\u5728 dbcli> \u63D0\u793A\u7B26\u6253\u9019\u53E5\u8A9E\u53E5\u2014\u2014\u5728\u90A3\u88E1 dbcli \u6703\u8ACB\u4F60\u6253\u51FA\u76EE\u6A19\u8CC7\u6599\u8868\u7684\u540D\u7A31\u4F86\u78BA\u8A8D\u3002",
|
|
9537
9570
|
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
9571
|
recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
|
|
9539
9572
|
blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
|
|
@@ -18007,7 +18040,7 @@ async function writeAuditEntry(config, commandName, options, outcome) {
|
|
|
18007
18040
|
const logger = await getAuditLogger(config, options.config || ".dbcli", connectionName);
|
|
18008
18041
|
const engine = config.connection?.system || "postgresql";
|
|
18009
18042
|
const target = outcome.target || getOperationTarget(engine, commandName, options, outcome.sql);
|
|
18010
|
-
let tier = getEngineCapability(engine, commandName).tier;
|
|
18043
|
+
let tier = outcome.sideEffectTier ?? getEngineCapability(engine, commandName).tier;
|
|
18011
18044
|
if (options.dryRun || options.plan) {
|
|
18012
18045
|
tier = "dry-run";
|
|
18013
18046
|
}
|
|
@@ -18940,6 +18973,7 @@ __export(exports_permission_guard, {
|
|
|
18940
18973
|
checkPermissionForClassification: () => checkPermissionForClassification,
|
|
18941
18974
|
checkPermission: () => checkPermission,
|
|
18942
18975
|
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18976
|
+
SQL_LOCK_CLAUSE: () => SQL_LOCK_CLAUSE,
|
|
18943
18977
|
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18944
18978
|
PermissionError: () => PermissionError
|
|
18945
18979
|
});
|
|
@@ -22358,7 +22392,8 @@ async function enforceTierTwo(request, attended) {
|
|
|
22358
22392
|
const { verdict } = request;
|
|
22359
22393
|
const reason = verdict.reason ?? "no_where";
|
|
22360
22394
|
if (!attended) {
|
|
22361
|
-
|
|
22395
|
+
const inShell = process.env.DBCLI_SHELL_SUBCOMMAND === "1";
|
|
22396
|
+
throw new WriteGateRefusal(inShell ? `${t_vars("ceremony.gate_refused_in_shell", { reason })} ${t("ceremony.gate_remedy_in_shell")}` : `${t_vars("ceremony.gate_refused", { reason })} ${t(REMEDY_KEY[reason])}`, reason, verdict.table);
|
|
22362
22397
|
}
|
|
22363
22398
|
writeSummary(request);
|
|
22364
22399
|
const warning = verdict.table === undefined && reason === "ddl_destruction" ? t("ceremony.gate_ddl_warning_untargeted") : t_vars(WARNING_KEY[reason], { table: verdict.table ?? "" });
|
|
@@ -22366,7 +22401,13 @@ async function enforceTierTwo(request, attended) {
|
|
|
22366
22401
|
${warning}
|
|
22367
22402
|
${t(REMEDY_KEY[reason])}
|
|
22368
22403
|
`);
|
|
22369
|
-
const
|
|
22404
|
+
const ask = request.ask ?? promptUser.text;
|
|
22405
|
+
const typed = await ask(t_vars("ceremony.gate_typed_prompt", { phrase: verdict.confirmationPhrase }));
|
|
22406
|
+
if (typed === null) {
|
|
22407
|
+
process.stderr.write(`${t("ceremony.gate_cancelled")}
|
|
22408
|
+
`);
|
|
22409
|
+
return false;
|
|
22410
|
+
}
|
|
22370
22411
|
const matched = typed.trim().toLowerCase() === verdict.confirmationPhrase.toLowerCase();
|
|
22371
22412
|
if (!matched) {
|
|
22372
22413
|
process.stderr.write(`${t_vars("ceremony.gate_typed_mismatch", { phrase: verdict.confirmationPhrase })}
|
|
@@ -22404,14 +22445,18 @@ var init_write_gate_prompt = __esm(() => {
|
|
|
22404
22445
|
ddl_destruction: "ceremony.gate_remedy_ddl_destruction",
|
|
22405
22446
|
unparseable: "ceremony.gate_remedy_unparseable",
|
|
22406
22447
|
non_unique_where: "ceremony.gate_remedy_non_unique_where",
|
|
22407
|
-
multiple_statements: "ceremony.gate_remedy_multiple_statements"
|
|
22448
|
+
multiple_statements: "ceremony.gate_remedy_multiple_statements",
|
|
22449
|
+
multi_table: "ceremony.gate_remedy_multi_table",
|
|
22450
|
+
nested_write: "ceremony.gate_remedy_nested_write"
|
|
22408
22451
|
};
|
|
22409
22452
|
WARNING_KEY = {
|
|
22410
22453
|
no_where: "ceremony.gate_full_table_warning",
|
|
22411
22454
|
ddl_destruction: "ceremony.gate_ddl_warning",
|
|
22412
22455
|
unparseable: "ceremony.gate_unparseable_warning",
|
|
22413
22456
|
non_unique_where: "ceremony.gate_full_table_warning",
|
|
22414
|
-
multiple_statements: "ceremony.gate_multiple_statements_warning"
|
|
22457
|
+
multiple_statements: "ceremony.gate_multiple_statements_warning",
|
|
22458
|
+
multi_table: "ceremony.gate_multi_table_warning",
|
|
22459
|
+
nested_write: "ceremony.gate_nested_write_warning"
|
|
22415
22460
|
};
|
|
22416
22461
|
});
|
|
22417
22462
|
|
|
@@ -22446,6 +22491,12 @@ async function classifySqlWriteGate(sql, options = {}) {
|
|
|
22446
22491
|
if (containsMultipleStatements(sql, dialect)) {
|
|
22447
22492
|
return verdict("two", "multiple_statements", undefined, type);
|
|
22448
22493
|
}
|
|
22494
|
+
const nested = nestedWrite(sql, dialect);
|
|
22495
|
+
if (nested)
|
|
22496
|
+
return verdict("two", "nested_write", undefined, nested);
|
|
22497
|
+
const merge = classifyMerge(sql, dialect);
|
|
22498
|
+
if (merge)
|
|
22499
|
+
return merge;
|
|
22449
22500
|
if (type === "DROP" || type === "TRUNCATE") {
|
|
22450
22501
|
const matched = DDL_TARGET.exec(sql)?.[1];
|
|
22451
22502
|
const table = matched ? matched.replace(/[`"[\]]/g, "") : undefined;
|
|
@@ -22464,20 +22515,139 @@ async function classifyQualification(sql, dialect, type) {
|
|
|
22464
22515
|
return unparseable(sql, dialect, type);
|
|
22465
22516
|
}
|
|
22466
22517
|
const statement = Array.isArray(ast) ? ast[0] : ast;
|
|
22467
|
-
if (!statement)
|
|
22518
|
+
if (!statement || statement.type !== "update" && statement.type !== "delete") {
|
|
22468
22519
|
return unparseable(sql, dialect, type);
|
|
22469
|
-
|
|
22470
|
-
|
|
22520
|
+
}
|
|
22521
|
+
const scope = [...statement.table ?? [], ...statement.from ?? []];
|
|
22522
|
+
const sources = new Set(scope.map((ref, index) => refIdentity(ref) ?? `#unnamed-${index}`));
|
|
22523
|
+
if (sources.size > 1) {
|
|
22524
|
+
return verdict("two", "multi_table", undefined, type);
|
|
22525
|
+
}
|
|
22526
|
+
const target = normaliseRef(statement.table?.[0] ?? scope[0]);
|
|
22527
|
+
const table = tableName(target);
|
|
22528
|
+
if (statement.limit != null)
|
|
22529
|
+
return verdict("one", undefined, table, type);
|
|
22530
|
+
if (statement.where != null && narrowsTarget(statement.where, refNames(target))) {
|
|
22471
22531
|
return verdict("one", undefined, table, type);
|
|
22472
22532
|
}
|
|
22473
22533
|
return verdict("two", "no_where", table, type);
|
|
22474
22534
|
}
|
|
22535
|
+
function normaliseRef(ref) {
|
|
22536
|
+
if (ref?.table?.toUpperCase() !== "ONLY")
|
|
22537
|
+
return ref;
|
|
22538
|
+
if (typeof ref.as !== "string" || ref.as.length === 0)
|
|
22539
|
+
return ref;
|
|
22540
|
+
return {
|
|
22541
|
+
db: ref.db,
|
|
22542
|
+
on: ref.on,
|
|
22543
|
+
as: null,
|
|
22544
|
+
table: typeof ref.as === "string" ? ref.as : undefined
|
|
22545
|
+
};
|
|
22546
|
+
}
|
|
22547
|
+
function refIdentity(reference) {
|
|
22548
|
+
const ref = normaliseRef(reference);
|
|
22549
|
+
if (typeof ref?.table !== "string")
|
|
22550
|
+
return;
|
|
22551
|
+
return (ref.db ? `${ref.db}.${ref.table}` : ref.table).toLowerCase();
|
|
22552
|
+
}
|
|
22553
|
+
function refNames(reference) {
|
|
22554
|
+
const ref = normaliseRef(reference);
|
|
22555
|
+
const name = typeof ref?.as === "string" && ref.as.length > 0 ? ref.as : ref?.table;
|
|
22556
|
+
return typeof name === "string" ? [name.toLowerCase()] : [];
|
|
22557
|
+
}
|
|
22558
|
+
function narrowsTarget(where, targetNames) {
|
|
22559
|
+
const names = new Set(targetNames);
|
|
22560
|
+
const { owners, correlated, skippedSubquery } = scanCondition(where);
|
|
22561
|
+
if (owners.length === 0 && correlated.length === 0)
|
|
22562
|
+
return !skippedSubquery;
|
|
22563
|
+
return owners.some((owner) => owner === null || names.has(owner)) || correlated.some((owner) => names.has(owner));
|
|
22564
|
+
}
|
|
22565
|
+
function mergeScans(scans) {
|
|
22566
|
+
return {
|
|
22567
|
+
owners: scans.flatMap((scan) => scan.owners),
|
|
22568
|
+
correlated: scans.flatMap((scan) => scan.correlated),
|
|
22569
|
+
skippedSubquery: scans.some((scan) => scan.skippedSubquery)
|
|
22570
|
+
};
|
|
22571
|
+
}
|
|
22572
|
+
function scanCondition(node, bound) {
|
|
22573
|
+
if (node === null || typeof node !== "object")
|
|
22574
|
+
return EMPTY_SCAN;
|
|
22575
|
+
const record = node;
|
|
22576
|
+
const inner = record.ast ?? (record.type === "select" ? record : undefined);
|
|
22577
|
+
const nested = inner !== undefined;
|
|
22578
|
+
if (record.type === "column_ref") {
|
|
22579
|
+
const owner = record.table;
|
|
22580
|
+
const name = owner == null ? null : String(owner).toLowerCase();
|
|
22581
|
+
if (bound === undefined)
|
|
22582
|
+
return { owners: [name], correlated: [], skippedSubquery: false };
|
|
22583
|
+
if (name === null || bound.has(name))
|
|
22584
|
+
return EMPTY_SCAN;
|
|
22585
|
+
return { owners: [], correlated: [name], skippedSubquery: false };
|
|
22586
|
+
}
|
|
22587
|
+
const scope = nested ? new Set([...bound ?? [], ...boundNames(inner)]) : bound;
|
|
22588
|
+
const scans = Object.values(record).map((value) => Array.isArray(value) ? mergeScans(value.map((item) => scanCondition(item, scope))) : scanCondition(value, scope));
|
|
22589
|
+
const merged = mergeScans(scans);
|
|
22590
|
+
return nested ? { ...merged, skippedSubquery: true } : merged;
|
|
22591
|
+
}
|
|
22592
|
+
function boundNames(select2) {
|
|
22593
|
+
const from = select2?.from ?? [];
|
|
22594
|
+
return Array.isArray(from) ? from.flatMap((ref) => refNames(ref)) : [];
|
|
22595
|
+
}
|
|
22596
|
+
function stripParenthesised(text2) {
|
|
22597
|
+
let previous = text2;
|
|
22598
|
+
for (;; ) {
|
|
22599
|
+
const next = previous.replace(/\([^()]*\)/g, " ");
|
|
22600
|
+
if (next === previous)
|
|
22601
|
+
return next;
|
|
22602
|
+
previous = next;
|
|
22603
|
+
}
|
|
22604
|
+
}
|
|
22475
22605
|
function unparseable(sql, dialect, type) {
|
|
22476
22606
|
const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {});
|
|
22607
|
+
const statementLevel = stripParenthesised(executable);
|
|
22608
|
+
const singleTable = SINGLE_TARGET.test(executable) && !SECOND_SOURCE_ANYWHERE.test(executable) && !SECOND_SOURCE_AT_STATEMENT_LEVEL.some((pattern) => pattern.test(statementLevel));
|
|
22609
|
+
if (!singleTable)
|
|
22610
|
+
return verdict("two", "unparseable", undefined, type);
|
|
22477
22611
|
if (/\bWHERE\b|\bLIMIT\b/i.test(executable))
|
|
22478
22612
|
return verdict("one", undefined, undefined, type);
|
|
22479
22613
|
return verdict("two", "unparseable", undefined, type);
|
|
22480
22614
|
}
|
|
22615
|
+
function classifyMerge(sql, dialect) {
|
|
22616
|
+
const executable = stripParenthesised(stripCommentsAndStrings(sql, dialect ? { dialect } : {}));
|
|
22617
|
+
if (!MERGE_STATEMENT.test(executable))
|
|
22618
|
+
return;
|
|
22619
|
+
const matched = MERGE_TARGET.exec(executable)?.[1];
|
|
22620
|
+
const table = matched === undefined || matched.endsWith(".") || /^USING$/i.test(matched) ? undefined : matched;
|
|
22621
|
+
const destructive = /\bTHEN\s+DELETE\b/i.test(executable) ? "DELETE" : /\bTHEN\s+UPDATE\b/i.test(executable) ? "UPDATE" : undefined;
|
|
22622
|
+
if (!destructive)
|
|
22623
|
+
return verdict("one", undefined, table, "INSERT");
|
|
22624
|
+
return verdict("two", "multi_table", table, destructive);
|
|
22625
|
+
}
|
|
22626
|
+
function nestedWrite(sql, dialect) {
|
|
22627
|
+
const executable = stripCommentsAndStrings(sql, dialect ? { dialect } : {}).replace(SQL_LOCK_CLAUSE, " ").replace(REFERENTIAL_ACTION, " ");
|
|
22628
|
+
const match = /\b(INSERT|UPDATE|DELETE)\b|\bMERGE\s+INTO\b/i.exec(insideParentheses(executable));
|
|
22629
|
+
if (match === null)
|
|
22630
|
+
return;
|
|
22631
|
+
return keywordToType(match[1] ?? "MERGE");
|
|
22632
|
+
}
|
|
22633
|
+
function insideParentheses(text2) {
|
|
22634
|
+
let depth = 0;
|
|
22635
|
+
let result = "";
|
|
22636
|
+
for (const char of text2) {
|
|
22637
|
+
if (char === "(") {
|
|
22638
|
+
depth += 1;
|
|
22639
|
+
result += " ";
|
|
22640
|
+
continue;
|
|
22641
|
+
}
|
|
22642
|
+
if (char === ")") {
|
|
22643
|
+
depth = Math.max(0, depth - 1);
|
|
22644
|
+
result += " ";
|
|
22645
|
+
continue;
|
|
22646
|
+
}
|
|
22647
|
+
result += depth > 0 ? char : " ";
|
|
22648
|
+
}
|
|
22649
|
+
return result;
|
|
22650
|
+
}
|
|
22481
22651
|
function classifyStructuredWriteGate(request) {
|
|
22482
22652
|
const conditions = new Set(Object.keys(request.where).map((key) => key.toLowerCase()));
|
|
22483
22653
|
const identifies = uniqueSelectors(request.schema).some((selector) => selector.length > 0 && selector.every((column) => conditions.has(column.toLowerCase())));
|
|
@@ -22511,7 +22681,7 @@ function verdict(tier, reason, table, operation) {
|
|
|
22511
22681
|
confirmationPhrase: table ?? WRITE_GATE_FALLBACK_PHRASE
|
|
22512
22682
|
};
|
|
22513
22683
|
}
|
|
22514
|
-
var WRITE_GATE_FALLBACK_PHRASE = "CONFIRM", PARSER_DIALECTS, WRITE_TYPES, DDL_TARGET;
|
|
22684
|
+
var WRITE_GATE_FALLBACK_PHRASE = "CONFIRM", PARSER_DIALECTS, WRITE_TYPES, DDL_TARGET, EMPTY_SCAN, SINGLE_TARGET, SECOND_SOURCE_ANYWHERE, SECOND_SOURCE_AT_STATEMENT_LEVEL, MERGE_STATEMENT, MERGE_TARGET, REFERENTIAL_ACTION;
|
|
22515
22685
|
var init_write_gate = __esm(() => {
|
|
22516
22686
|
init_permission_guard();
|
|
22517
22687
|
init_sql_analysis();
|
|
@@ -22530,6 +22700,13 @@ var init_write_gate = __esm(() => {
|
|
|
22530
22700
|
"CREATE"
|
|
22531
22701
|
]);
|
|
22532
22702
|
DDL_TARGET = /\b(?:DROP\s+TABLE|TRUNCATE(?:\s+TABLE)?)\s+(?:IF\s+EXISTS\s+)?([`"[\]\w$.]+)\s*;?\s*$/i;
|
|
22703
|
+
EMPTY_SCAN = { owners: [], correlated: [], skippedSubquery: false };
|
|
22704
|
+
SINGLE_TARGET = /^\s*(?:UPDATE\s+(?:ONLY\s+)?[\w`"[\]$.]+\s+SET\b|DELETE\s+FROM\s+(?:ONLY\s+)?[\w`"[\]$.]+(?!\s*,)(?:\s|$))/i;
|
|
22705
|
+
SECOND_SOURCE_ANYWHERE = /\b(?:SELECT|TABLE|VALUES|JOIN|USING)\b/i;
|
|
22706
|
+
SECOND_SOURCE_AT_STATEMENT_LEVEL = [/\bWITH\b/i, /\bUPDATE\b[\s\S]*?\bFROM\b/i];
|
|
22707
|
+
MERGE_STATEMENT = /\bMERGE\s+INTO\b/i;
|
|
22708
|
+
MERGE_TARGET = /\bMERGE\s+INTO\s+(?:ONLY\s+)?([\w$.]+)/i;
|
|
22709
|
+
REFERENTIAL_ACTION = /\bON\s+(?:DELETE|UPDATE)\b/gi;
|
|
22533
22710
|
});
|
|
22534
22711
|
|
|
22535
22712
|
// src/commands/write-gate-guard.ts
|
|
@@ -22541,6 +22718,7 @@ __export(exports_write_gate_guard, {
|
|
|
22541
22718
|
async function recordGateDecision(request) {
|
|
22542
22719
|
await writeAuditEntry(request.config, request.command, request.options, {
|
|
22543
22720
|
success: request.outcome === "allowed",
|
|
22721
|
+
sideEffectTier: "db-write",
|
|
22544
22722
|
...request.sql && { sql: request.sql },
|
|
22545
22723
|
...request.verdict.table && { target: request.verdict.table },
|
|
22546
22724
|
metadata: {
|
|
@@ -25491,9 +25669,12 @@ async function qCommand(name, options, command) {
|
|
|
25491
25669
|
console.error(`\u26A0 ${w2}`);
|
|
25492
25670
|
if (prepared.warnings.length > 0)
|
|
25493
25671
|
console.error("");
|
|
25672
|
+
const family = engineFamily(engine);
|
|
25673
|
+
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
25674
|
+
const classification = family === "sql" ? enforcePermission(prepared.rewrittenSql, config.permission, sqlDialect) : undefined;
|
|
25494
25675
|
if (options.dryRun) {
|
|
25495
25676
|
console.log(formatDryRun({
|
|
25496
|
-
family
|
|
25677
|
+
family,
|
|
25497
25678
|
driverSql: prepared.driver.sql,
|
|
25498
25679
|
values: prepared.driver.values,
|
|
25499
25680
|
execHints: prepared.execHints,
|
|
@@ -25514,8 +25695,6 @@ async function qCommand(name, options, command) {
|
|
|
25514
25695
|
}
|
|
25515
25696
|
const blacklistManager = new BlacklistManager(config);
|
|
25516
25697
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
25517
|
-
const family = engineFamily(engine);
|
|
25518
|
-
const sqlDialect = SQL_DIALECTS.find((dialect) => dialect === connectionSystem);
|
|
25519
25698
|
const targets = family === "sql" ? extractTableReferences(prepared.rewrittenSql, {
|
|
25520
25699
|
...sqlDialect ? { dialect: sqlDialect } : {}
|
|
25521
25700
|
}) : family === "es" ? [prepared.execHints?.index ?? ""] : [];
|
|
@@ -25523,8 +25702,8 @@ async function qCommand(name, options, command) {
|
|
|
25523
25702
|
targetNameForAudit = targetName || name;
|
|
25524
25703
|
if (family === "es") {
|
|
25525
25704
|
blacklistValidator.checkIndexBlacklist("SELECT", targetName);
|
|
25526
|
-
} else if (
|
|
25527
|
-
blacklistValidator.checkTablesBlacklist(
|
|
25705
|
+
} else if (classification && targets.length > 0) {
|
|
25706
|
+
blacklistValidator.checkTablesBlacklist(classification.type, targets);
|
|
25528
25707
|
}
|
|
25529
25708
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
25530
25709
|
await adapter.connect();
|
|
@@ -36541,6 +36720,81 @@ Verification artifact: ${verificationArtifactPath}`);
|
|
|
36541
36720
|
});
|
|
36542
36721
|
});
|
|
36543
36722
|
|
|
36723
|
+
// src/core/audit/write-gate-summary.ts
|
|
36724
|
+
function metadataOf(entry) {
|
|
36725
|
+
const metadata = entry.metadata;
|
|
36726
|
+
return metadata && typeof metadata === "object" ? metadata : {};
|
|
36727
|
+
}
|
|
36728
|
+
function readString(source, key) {
|
|
36729
|
+
const value = source[key];
|
|
36730
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
36731
|
+
}
|
|
36732
|
+
|
|
36733
|
+
class Span {
|
|
36734
|
+
from;
|
|
36735
|
+
to;
|
|
36736
|
+
add(ts) {
|
|
36737
|
+
if (this.from === undefined || ts < this.from)
|
|
36738
|
+
this.from = ts;
|
|
36739
|
+
if (this.to === undefined || ts > this.to)
|
|
36740
|
+
this.to = ts;
|
|
36741
|
+
}
|
|
36742
|
+
value() {
|
|
36743
|
+
return this.from === undefined || this.to === undefined ? null : { from: this.from, to: this.to };
|
|
36744
|
+
}
|
|
36745
|
+
}
|
|
36746
|
+
function summarizeWriteGate(entries) {
|
|
36747
|
+
const outcomes = Object.fromEntries(GATE_OUTCOMES.map((o) => [o, 0]));
|
|
36748
|
+
const reasons = Object.fromEntries(GATE_REASONS.map((r) => [r, 0]));
|
|
36749
|
+
const tierOneOutcomes = {};
|
|
36750
|
+
const window = new Span;
|
|
36751
|
+
const range = new Span;
|
|
36752
|
+
let total = 0;
|
|
36753
|
+
let tierOneTotal = 0;
|
|
36754
|
+
const tally = (into, key) => {
|
|
36755
|
+
into[key] = (into[key] ?? 0) + 1;
|
|
36756
|
+
};
|
|
36757
|
+
for (const entry of entries) {
|
|
36758
|
+
window.add(entry.ts);
|
|
36759
|
+
const metadata = metadataOf(entry);
|
|
36760
|
+
const tier = readString(metadata, "write_gate_tier");
|
|
36761
|
+
if (!tier)
|
|
36762
|
+
continue;
|
|
36763
|
+
const outcome = readString(metadata, "write_gate_outcome") ?? "unknown";
|
|
36764
|
+
if (tier !== "two") {
|
|
36765
|
+
tierOneTotal += 1;
|
|
36766
|
+
tally(tierOneOutcomes, outcome);
|
|
36767
|
+
continue;
|
|
36768
|
+
}
|
|
36769
|
+
total += 1;
|
|
36770
|
+
range.add(entry.ts);
|
|
36771
|
+
tally(outcomes, outcome);
|
|
36772
|
+
tally(reasons, readString(metadata, "write_gate_reason") ?? "unknown");
|
|
36773
|
+
}
|
|
36774
|
+
return {
|
|
36775
|
+
scanned: entries.length,
|
|
36776
|
+
window: window.value(),
|
|
36777
|
+
total,
|
|
36778
|
+
outcomes,
|
|
36779
|
+
reasons,
|
|
36780
|
+
tierOne: { total: tierOneTotal, outcomes: tierOneOutcomes },
|
|
36781
|
+
range: range.value()
|
|
36782
|
+
};
|
|
36783
|
+
}
|
|
36784
|
+
var GATE_OUTCOMES, GATE_REASONS;
|
|
36785
|
+
var init_write_gate_summary = __esm(() => {
|
|
36786
|
+
GATE_OUTCOMES = ["allowed", "declined", "refused"];
|
|
36787
|
+
GATE_REASONS = [
|
|
36788
|
+
"no_where",
|
|
36789
|
+
"ddl_destruction",
|
|
36790
|
+
"unparseable",
|
|
36791
|
+
"multiple_statements",
|
|
36792
|
+
"non_unique_where",
|
|
36793
|
+
"multi_table",
|
|
36794
|
+
"nested_write"
|
|
36795
|
+
];
|
|
36796
|
+
});
|
|
36797
|
+
|
|
36544
36798
|
// src/commands/audit.ts
|
|
36545
36799
|
var exports_audit = {};
|
|
36546
36800
|
__export(exports_audit, {
|
|
@@ -36726,6 +36980,53 @@ function readLineFromStdinWithStderrPrompt(prompt) {
|
|
|
36726
36980
|
process.stdin.resume();
|
|
36727
36981
|
});
|
|
36728
36982
|
}
|
|
36983
|
+
function formatOutcomeCounts(outcomes) {
|
|
36984
|
+
const seen = Object.entries(outcomes).filter(([, count]) => count > 0);
|
|
36985
|
+
if (seen.length === 0)
|
|
36986
|
+
return MISSING_PLACEHOLDER;
|
|
36987
|
+
return seen.map(([name, count]) => `${name} ${count}`).join(", ");
|
|
36988
|
+
}
|
|
36989
|
+
function renderWriteGateSummary(summary, connections) {
|
|
36990
|
+
const lines = [];
|
|
36991
|
+
lines.push(t_vars("audit.write_gate.header", {
|
|
36992
|
+
conn: connections.length > 0 ? connections.join(", ") : MISSING_PLACEHOLDER
|
|
36993
|
+
}));
|
|
36994
|
+
if (summary.window) {
|
|
36995
|
+
lines.push(t_vars("audit.write_gate.window", {
|
|
36996
|
+
scanned: String(summary.scanned),
|
|
36997
|
+
from: summary.window.from,
|
|
36998
|
+
to: summary.window.to
|
|
36999
|
+
}));
|
|
37000
|
+
}
|
|
37001
|
+
if (summary.total === 0) {
|
|
37002
|
+
lines.push("");
|
|
37003
|
+
lines.push(summary.scanned === 0 ? t("audit.write_gate.no_entries") : t("audit.write_gate.no_decisions"));
|
|
37004
|
+
if (summary.tierOne.total > 0) {
|
|
37005
|
+
lines.push(t_vars("audit.write_gate.tier_one", {
|
|
37006
|
+
count: String(summary.tierOne.total),
|
|
37007
|
+
outcomes: formatOutcomeCounts(summary.tierOne.outcomes)
|
|
37008
|
+
}));
|
|
37009
|
+
}
|
|
37010
|
+
return lines.join(`
|
|
37011
|
+
`);
|
|
37012
|
+
}
|
|
37013
|
+
lines.push(t_vars("audit.write_gate.decisions", {
|
|
37014
|
+
count: String(summary.total),
|
|
37015
|
+
from: summary.range?.from ?? MISSING_PLACEHOLDER,
|
|
37016
|
+
to: summary.range?.to ?? MISSING_PLACEHOLDER
|
|
37017
|
+
}));
|
|
37018
|
+
lines.push("");
|
|
37019
|
+
lines.push(renderTable(Object.entries(summary.outcomes).map(([name, count]) => [name, String(count)]), ["outcome", "count"]));
|
|
37020
|
+
lines.push("");
|
|
37021
|
+
lines.push(renderTable(Object.entries(summary.reasons).map(([name, count]) => [name, String(count)]), ["reason", "count"]));
|
|
37022
|
+
lines.push("");
|
|
37023
|
+
lines.push(t_vars("audit.write_gate.tier_one", {
|
|
37024
|
+
count: String(summary.tierOne.total),
|
|
37025
|
+
outcomes: formatOutcomeCounts(summary.tierOne.outcomes)
|
|
37026
|
+
}));
|
|
37027
|
+
return lines.join(`
|
|
37028
|
+
`);
|
|
37029
|
+
}
|
|
36729
37030
|
async function statAuditFile(file) {
|
|
36730
37031
|
try {
|
|
36731
37032
|
const s = await stat7(file);
|
|
@@ -36745,6 +37046,7 @@ var init_audit = __esm(() => {
|
|
|
36745
37046
|
init_config_binding();
|
|
36746
37047
|
init_reader2();
|
|
36747
37048
|
init_integration_helper();
|
|
37049
|
+
init_write_gate_summary();
|
|
36748
37050
|
ALLOWED_FORMATS14 = ["table", "json"];
|
|
36749
37051
|
auditCommand = new Command("audit").description(t("audit.description"));
|
|
36750
37052
|
auditCommand.command("tail").description(t("audit.tail.description")).option("--n <number>", `Number of recent entries to show (1..${MAX_TAIL_N})`, String(DEFAULT_TAIL_N)).option("--all", "Merge entries across all connections", false).option("--format <format>", `Output format: ${ALLOWED_FORMATS14.join(" | ")} (default: table)`, "table").option("--brief", "Trim each entry to ts/command/target/success", false).option("--no-brief", "Disable brief mode (override --for-agent default)").option("--for-agent", "Shortcut for --format json --brief", false).action(async (options, command) => {
|
|
@@ -36952,6 +37254,33 @@ var init_audit = __esm(() => {
|
|
|
36952
37254
|
`);
|
|
36953
37255
|
process.exit(0);
|
|
36954
37256
|
});
|
|
37257
|
+
auditCommand.command("write-gate").description(t("audit.write_gate.description")).option("--all", "Summarize across all connections", false).option("--format <format>", `Output format: ${ALLOWED_FORMATS14.join(" | ")} (default: table)`, "table").option("--for-agent", "Shortcut for --format json", false).action(async (options, command) => {
|
|
37258
|
+
const format = options.forAgent === true ? "json" : options.format;
|
|
37259
|
+
validateFormat(format, ALLOWED_FORMATS14, "audit write-gate");
|
|
37260
|
+
const configPath = resolveConfigPath(command, options);
|
|
37261
|
+
const config = await configModule.read(configPath);
|
|
37262
|
+
if (isAuditDisabled(config))
|
|
37263
|
+
emitDisabledAndExit0();
|
|
37264
|
+
const { auditDir, auditFile, connectionName } = await resolveAuditPaths(configPath, config);
|
|
37265
|
+
let entries = [];
|
|
37266
|
+
let connections = [connectionName];
|
|
37267
|
+
if (options.all === true) {
|
|
37268
|
+
const discovered = await discoverConnections(auditDir);
|
|
37269
|
+
connections = discovered.map((c) => c.connection);
|
|
37270
|
+
for (const c of discovered) {
|
|
37271
|
+
for (const file of c.files)
|
|
37272
|
+
entries.push(...await readEntries(file));
|
|
37273
|
+
}
|
|
37274
|
+
} else {
|
|
37275
|
+
entries = await readEntries(auditFile, { include_rotated: true });
|
|
37276
|
+
}
|
|
37277
|
+
const summary = summarizeWriteGate(entries);
|
|
37278
|
+
if (format === "json") {
|
|
37279
|
+
console.log(JSON.stringify({ connections, summary }, null, 2));
|
|
37280
|
+
return;
|
|
37281
|
+
}
|
|
37282
|
+
console.log(renderWriteGateSummary(summary, connections));
|
|
37283
|
+
});
|
|
36955
37284
|
auditCommand.command("health").description(t("audit.health.description")).option("--format <format>", `Output format: ${ALLOWED_FORMATS14.join(" | ")} (default: table)`, "table").option("--brief", "Trim to enabled / lastWrite / rotationUsage", false).option("--no-brief", "Disable brief mode (override --for-agent default)").option("--for-agent", "Shortcut for --format json --brief", false).action(async (options, command) => {
|
|
36956
37285
|
const forAgent = options.forAgent === true;
|
|
36957
37286
|
const format = forAgent ? "json" : options.format;
|
|
@@ -44242,6 +44571,9 @@ function classifyInput(raw) {
|
|
|
44242
44571
|
return { type: "meta", raw, normalized: normalized2 };
|
|
44243
44572
|
}
|
|
44244
44573
|
}
|
|
44574
|
+
if (normalized2.startsWith(COMMAND_PREFIX)) {
|
|
44575
|
+
return { type: "command", raw, normalized: normalized2 };
|
|
44576
|
+
}
|
|
44245
44577
|
const firstWord = (normalized2.split(/\s+/)[0] ?? "").toUpperCase();
|
|
44246
44578
|
if (sqlKeywordSet.has(firstWord)) {
|
|
44247
44579
|
return { type: "sql", raw, normalized: normalized2 };
|
|
@@ -44251,7 +44583,7 @@ function classifyInput(raw) {
|
|
|
44251
44583
|
}
|
|
44252
44584
|
return { type: "command", raw, normalized: normalized2 };
|
|
44253
44585
|
}
|
|
44254
|
-
var META_PREFIX = ".", SQL_TERMINATOR = ";", sqlKeywordSet, metaCommandNames;
|
|
44586
|
+
var META_PREFIX = ".", COMMAND_PREFIX = "\\", SQL_TERMINATOR = ";", sqlKeywordSet, metaCommandNames;
|
|
44255
44587
|
var init_input_classifier = __esm(() => {
|
|
44256
44588
|
init_types11();
|
|
44257
44589
|
sqlKeywordSet = new Set(SQL_KEYWORDS_FOR_DETECTION);
|
|
@@ -44275,6 +44607,10 @@ class MultilineBuffer {
|
|
|
44275
44607
|
isActive() {
|
|
44276
44608
|
return this.lines.length > 0;
|
|
44277
44609
|
}
|
|
44610
|
+
isInsideLiteral() {
|
|
44611
|
+
return insideLiteral(this.lines.join(`
|
|
44612
|
+
`));
|
|
44613
|
+
}
|
|
44278
44614
|
getPartial() {
|
|
44279
44615
|
return this.lines.join(`
|
|
44280
44616
|
`);
|
|
@@ -44284,9 +44620,17 @@ class MultilineBuffer {
|
|
|
44284
44620
|
}
|
|
44285
44621
|
}
|
|
44286
44622
|
function hasUnquotedSemicolon(sql) {
|
|
44623
|
+
return scan(sql).semicolon;
|
|
44624
|
+
}
|
|
44625
|
+
function insideLiteral(sql) {
|
|
44626
|
+
const { inSingleQuote, inDoubleQuote } = scan(sql);
|
|
44627
|
+
return inSingleQuote || inDoubleQuote;
|
|
44628
|
+
}
|
|
44629
|
+
function scan(sql) {
|
|
44287
44630
|
let inSingleQuote = false;
|
|
44288
44631
|
let inDoubleQuote = false;
|
|
44289
44632
|
let escaped = false;
|
|
44633
|
+
let semicolon = false;
|
|
44290
44634
|
for (let i = 0;i < sql.length; i++) {
|
|
44291
44635
|
const ch = sql[i];
|
|
44292
44636
|
if (escaped) {
|
|
@@ -44306,10 +44650,10 @@ function hasUnquotedSemicolon(sql) {
|
|
|
44306
44650
|
continue;
|
|
44307
44651
|
}
|
|
44308
44652
|
if (ch === ";" && !inSingleQuote && !inDoubleQuote) {
|
|
44309
|
-
|
|
44653
|
+
semicolon = true;
|
|
44310
44654
|
}
|
|
44311
44655
|
}
|
|
44312
|
-
return
|
|
44656
|
+
return { semicolon, inSingleQuote, inDoubleQuote };
|
|
44313
44657
|
}
|
|
44314
44658
|
|
|
44315
44659
|
// src/core/repl/meta-commands.ts
|
|
@@ -44353,7 +44697,8 @@ function helpText() {
|
|
|
44353
44697
|
"",
|
|
44354
44698
|
import_picocolors2.default.bold("Usage:"),
|
|
44355
44699
|
` SQL statements Execute directly (end with ${import_picocolors2.default.cyan(";")})`,
|
|
44356
|
-
` dbcli commands Run without ${import_picocolors2.default.dim("dbcli")} prefix (e.g. ${import_picocolors2.default.cyan("schema users")})`,
|
|
44700
|
+
` dbcli commands Run without the ${import_picocolors2.default.dim("dbcli")} prefix (e.g. ${import_picocolors2.default.cyan("schema users")})`,
|
|
44701
|
+
` ${import_picocolors2.default.cyan("\\<command>")} Force the dbcli subcommand \u2014 required for ${import_picocolors2.default.dim("delete")}, ${import_picocolors2.default.dim("update")}, ${import_picocolors2.default.dim("insert")} and other SQL keywords (e.g. ${import_picocolors2.default.cyan("\\delete users --where id=1")})`,
|
|
44357
44702
|
` Ctrl+D Exit`
|
|
44358
44703
|
];
|
|
44359
44704
|
return lines.join(`
|
|
@@ -44457,24 +44802,26 @@ function splitRespectingQuotes(input) {
|
|
|
44457
44802
|
let current = "";
|
|
44458
44803
|
let inDoubleQuote = false;
|
|
44459
44804
|
let inSingleQuote = false;
|
|
44805
|
+
let quoted = false;
|
|
44460
44806
|
for (let i = 0;i < input.length; i++) {
|
|
44461
44807
|
const ch = input[i];
|
|
44462
44808
|
if (ch === '"' && !inSingleQuote) {
|
|
44463
44809
|
inDoubleQuote = !inDoubleQuote;
|
|
44464
|
-
|
|
44810
|
+
quoted = true;
|
|
44465
44811
|
} else if (ch === "'" && !inDoubleQuote) {
|
|
44466
44812
|
inSingleQuote = !inSingleQuote;
|
|
44467
|
-
|
|
44813
|
+
quoted = true;
|
|
44468
44814
|
} else if (ch === " " && !inDoubleQuote && !inSingleQuote) {
|
|
44469
|
-
if (current.length > 0) {
|
|
44815
|
+
if (current.length > 0 || quoted) {
|
|
44470
44816
|
tokens.push(current);
|
|
44471
44817
|
current = "";
|
|
44818
|
+
quoted = false;
|
|
44472
44819
|
}
|
|
44473
44820
|
} else {
|
|
44474
44821
|
current += ch;
|
|
44475
44822
|
}
|
|
44476
44823
|
}
|
|
44477
|
-
if (current.length > 0) {
|
|
44824
|
+
if (current.length > 0 || quoted) {
|
|
44478
44825
|
tokens.push(current);
|
|
44479
44826
|
}
|
|
44480
44827
|
return tokens;
|
|
@@ -44540,14 +44887,16 @@ var init_history_manager = () => {};
|
|
|
44540
44887
|
class ReplEngine {
|
|
44541
44888
|
adapter;
|
|
44542
44889
|
context;
|
|
44890
|
+
writeGate;
|
|
44543
44891
|
state;
|
|
44544
44892
|
buffer;
|
|
44545
44893
|
history;
|
|
44546
44894
|
formatter;
|
|
44547
44895
|
config;
|
|
44548
|
-
constructor(adapter, context, historyPath, config = null) {
|
|
44896
|
+
constructor(adapter, context, historyPath, config = null, writeGate = null) {
|
|
44549
44897
|
this.adapter = adapter;
|
|
44550
44898
|
this.context = context;
|
|
44899
|
+
this.writeGate = writeGate;
|
|
44551
44900
|
this.state = { format: "table", timing: false, connected: true, noLimit: false };
|
|
44552
44901
|
this.buffer = new MultilineBuffer;
|
|
44553
44902
|
this.history = new HistoryManager(historyPath);
|
|
@@ -44560,6 +44909,9 @@ class ReplEngine {
|
|
|
44560
44909
|
isMultiline() {
|
|
44561
44910
|
return this.buffer.isActive();
|
|
44562
44911
|
}
|
|
44912
|
+
cancelMultiline() {
|
|
44913
|
+
this.buffer.reset();
|
|
44914
|
+
}
|
|
44563
44915
|
getHistory() {
|
|
44564
44916
|
return this.history.getAll();
|
|
44565
44917
|
}
|
|
@@ -44568,6 +44920,13 @@ class ReplEngine {
|
|
|
44568
44920
|
}
|
|
44569
44921
|
async processInput(line) {
|
|
44570
44922
|
if (this.buffer.isActive()) {
|
|
44923
|
+
const classified2 = classifyInput(line);
|
|
44924
|
+
if (classified2.type === "meta" && !this.buffer.isInsideLiteral()) {
|
|
44925
|
+
const result = this.handleMeta(classified2.normalized);
|
|
44926
|
+
if (result.action === "quit" || result.action === "clear")
|
|
44927
|
+
this.buffer.reset();
|
|
44928
|
+
return result;
|
|
44929
|
+
}
|
|
44571
44930
|
const bufResult = this.buffer.append(line);
|
|
44572
44931
|
if (bufResult.complete) {
|
|
44573
44932
|
this.history.add(bufResult.sql);
|
|
@@ -44604,14 +44963,27 @@ class ReplEngine {
|
|
|
44604
44963
|
this.history.add(bufResult.sql);
|
|
44605
44964
|
return this.executeSql(bufResult.sql);
|
|
44606
44965
|
}
|
|
44607
|
-
|
|
44966
|
+
const hint = this.subcommandHint(input);
|
|
44967
|
+
return hint ? { action: "multiline", output: hint } : { action: "multiline" };
|
|
44968
|
+
}
|
|
44969
|
+
subcommandHint(input) {
|
|
44970
|
+
const words = input.trim().split(/\s+/);
|
|
44971
|
+
const firstWord = (words[0] ?? "").toLowerCase();
|
|
44972
|
+
if (!this.context.commandNames.includes(firstWord))
|
|
44973
|
+
return;
|
|
44974
|
+
if (!/\s--[a-z]/.test(input))
|
|
44975
|
+
return;
|
|
44976
|
+
if (STATEMENT_CONTINUATION.has((words[1] ?? "").toUpperCase()))
|
|
44977
|
+
return;
|
|
44978
|
+
return import_picocolors3.default.dim(t_vars("shell.subcommand_prefix_hint", { command: `${COMMAND_PREFIX}${firstWord}` }));
|
|
44608
44979
|
}
|
|
44609
44980
|
async handleCommand(input) {
|
|
44610
|
-
const
|
|
44981
|
+
const invocation = input.startsWith(COMMAND_PREFIX) ? input.slice(COMMAND_PREFIX.length) : input;
|
|
44982
|
+
const parsed = parseCommandLine(invocation);
|
|
44611
44983
|
if (!isKnownCommand(parsed.command, this.context.commandNames)) {
|
|
44612
44984
|
if (this.context.system === "redis") {
|
|
44613
44985
|
this.history.add(input);
|
|
44614
|
-
return this.executeSql(
|
|
44986
|
+
return this.executeSql(invocation);
|
|
44615
44987
|
}
|
|
44616
44988
|
return {
|
|
44617
44989
|
action: "continue",
|
|
@@ -44621,11 +44993,11 @@ class ReplEngine {
|
|
|
44621
44993
|
this.history.add(input);
|
|
44622
44994
|
try {
|
|
44623
44995
|
const cliPath = new URL("../../cli.ts", import.meta.url).pathname;
|
|
44624
|
-
const argv = [
|
|
44996
|
+
const argv = ["--config", this.context.configPath, parsed.command, ...parsed.args];
|
|
44625
44997
|
const proc = Bun.spawn(["bun", "run", cliPath, ...argv], {
|
|
44626
44998
|
stdout: "pipe",
|
|
44627
44999
|
stderr: "pipe",
|
|
44628
|
-
env: { ...process.env }
|
|
45000
|
+
env: { ...process.env, DBCLI_SHELL_SUBCOMMAND: "1" }
|
|
44629
45001
|
});
|
|
44630
45002
|
const [stdoutBuf, stderrBuf] = await Promise.all([
|
|
44631
45003
|
new Response(proc.stdout).text(),
|
|
@@ -44681,6 +45053,11 @@ class ReplEngine {
|
|
|
44681
45053
|
};
|
|
44682
45054
|
}
|
|
44683
45055
|
}
|
|
45056
|
+
if (this.writeGate && !await this.writeGate(sql))
|
|
45057
|
+
return { action: "continue" };
|
|
45058
|
+
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
45059
|
+
}
|
|
45060
|
+
async runStatement(sql, blacklistValidator, referencedTables) {
|
|
44684
45061
|
const startTime = Date.now();
|
|
44685
45062
|
try {
|
|
44686
45063
|
const result = await this.adapter.execute(sql, undefined, {
|
|
@@ -44716,7 +45093,7 @@ class ReplEngine {
|
|
|
44716
45093
|
await this.adapter.connect();
|
|
44717
45094
|
this.state = { ...this.state, connected: true };
|
|
44718
45095
|
console.error(import_picocolors3.default.green(t("shell.error_reconnect_success")));
|
|
44719
|
-
return this.
|
|
45096
|
+
return this.runStatement(sql, blacklistValidator, referencedTables);
|
|
44720
45097
|
} catch (reconnectError) {
|
|
44721
45098
|
return {
|
|
44722
45099
|
action: "continue",
|
|
@@ -44747,7 +45124,7 @@ class ReplEngine {
|
|
|
44747
45124
|
return isTransportFailure(error);
|
|
44748
45125
|
}
|
|
44749
45126
|
}
|
|
44750
|
-
var import_picocolors3;
|
|
45127
|
+
var import_picocolors3, STATEMENT_CONTINUATION;
|
|
44751
45128
|
var init_repl_engine = __esm(() => {
|
|
44752
45129
|
init_connection_error_message();
|
|
44753
45130
|
init_input_classifier();
|
|
@@ -44761,6 +45138,15 @@ var init_repl_engine = __esm(() => {
|
|
|
44761
45138
|
init_blacklist_validator();
|
|
44762
45139
|
init_blacklist();
|
|
44763
45140
|
import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
45141
|
+
STATEMENT_CONTINUATION = new Set([
|
|
45142
|
+
"FROM",
|
|
45143
|
+
"INTO",
|
|
45144
|
+
"SET",
|
|
45145
|
+
"TABLE",
|
|
45146
|
+
"INDEX",
|
|
45147
|
+
"VIEW",
|
|
45148
|
+
"DATABASE"
|
|
45149
|
+
]);
|
|
44764
45150
|
});
|
|
44765
45151
|
|
|
44766
45152
|
// src/core/repl/completer.ts
|
|
@@ -44803,6 +45189,11 @@ function createCompleter(ctx) {
|
|
|
44803
45189
|
}
|
|
44804
45190
|
const words = trimmed.split(/\s+/);
|
|
44805
45191
|
if (words.length === 1) {
|
|
45192
|
+
if (lastWord.startsWith(COMMAND_PREFIX)) {
|
|
45193
|
+
const bare = lastWord.slice(COMMAND_PREFIX.length);
|
|
45194
|
+
const [hits] = matchWithSuffix([...ctx.commandNames], bare);
|
|
45195
|
+
return [hits.map((hit) => `${COMMAND_PREFIX}${hit}`), lastWord];
|
|
45196
|
+
}
|
|
44806
45197
|
const cmdHits = matchWithSuffix([...ctx.commandNames], lastWord);
|
|
44807
45198
|
const sqlHits = matchWithSuffix([...SQL_KEYWORDS_FOR_COMPLETION], lastWord);
|
|
44808
45199
|
return [[...cmdHits[0], ...sqlHits[0]], lastWord];
|
|
@@ -44856,6 +45247,7 @@ function matchWithSuffix(candidates, prefix) {
|
|
|
44856
45247
|
var REDIS_COMMANDS, TABLE_POSITION_KEYWORDS, COMMANDS_TAKING_TABLE_ARG;
|
|
44857
45248
|
var init_completer = __esm(() => {
|
|
44858
45249
|
init_types11();
|
|
45250
|
+
init_input_classifier();
|
|
44859
45251
|
init_command_metadata();
|
|
44860
45252
|
REDIS_COMMANDS = Object.keys(REDIS_COMMAND_TABLE);
|
|
44861
45253
|
TABLE_POSITION_KEYWORDS = new Set([
|
|
@@ -45153,6 +45545,94 @@ var init_command_registry = __esm(() => {
|
|
|
45153
45545
|
REPL_DENYLIST = new Set(["shell"]);
|
|
45154
45546
|
});
|
|
45155
45547
|
|
|
45548
|
+
// src/commands/shell-prompt-asker.ts
|
|
45549
|
+
var exports_shell_prompt_asker = {};
|
|
45550
|
+
__export(exports_shell_prompt_asker, {
|
|
45551
|
+
createPromptAsker: () => createPromptAsker
|
|
45552
|
+
});
|
|
45553
|
+
function createPromptAsker(getInterface) {
|
|
45554
|
+
let pending = null;
|
|
45555
|
+
return {
|
|
45556
|
+
async ask(question) {
|
|
45557
|
+
const rl = getInterface();
|
|
45558
|
+
if (!rl)
|
|
45559
|
+
throw new Error("Write gate asked for confirmation before the shell started");
|
|
45560
|
+
return new Promise((resolve10) => {
|
|
45561
|
+
const controller = new AbortController;
|
|
45562
|
+
const settle = (answer) => {
|
|
45563
|
+
pending = null;
|
|
45564
|
+
rl.pause();
|
|
45565
|
+
resolve10(answer);
|
|
45566
|
+
};
|
|
45567
|
+
pending = {
|
|
45568
|
+
abort: () => {
|
|
45569
|
+
controller.abort();
|
|
45570
|
+
settle(null);
|
|
45571
|
+
}
|
|
45572
|
+
};
|
|
45573
|
+
try {
|
|
45574
|
+
rl.question(`${question}: `, { signal: controller.signal }, settle);
|
|
45575
|
+
} catch (error) {
|
|
45576
|
+
pending = null;
|
|
45577
|
+
throw error;
|
|
45578
|
+
}
|
|
45579
|
+
});
|
|
45580
|
+
},
|
|
45581
|
+
cancel() {
|
|
45582
|
+
if (!pending)
|
|
45583
|
+
return false;
|
|
45584
|
+
pending.abort();
|
|
45585
|
+
return true;
|
|
45586
|
+
}
|
|
45587
|
+
};
|
|
45588
|
+
}
|
|
45589
|
+
|
|
45590
|
+
// src/commands/shell-write-gate.ts
|
|
45591
|
+
var exports_shell_write_gate = {};
|
|
45592
|
+
__export(exports_shell_write_gate, {
|
|
45593
|
+
createShellWriteGate: () => createShellWriteGate
|
|
45594
|
+
});
|
|
45595
|
+
function createShellWriteGate(options) {
|
|
45596
|
+
const record4 = options.record ?? ((decision) => recordGateDecision({
|
|
45597
|
+
config: options.config,
|
|
45598
|
+
command: "shell",
|
|
45599
|
+
options: { config: options.configPath },
|
|
45600
|
+
verdict: decision.verdict,
|
|
45601
|
+
outcome: decision.outcome,
|
|
45602
|
+
tier: decision.tier,
|
|
45603
|
+
sql: decision.sql
|
|
45604
|
+
}));
|
|
45605
|
+
return async (sql) => {
|
|
45606
|
+
const verdict2 = await classifySqlWriteGate(sql, { dialect: options.dialect });
|
|
45607
|
+
if (verdict2.tier !== "two")
|
|
45608
|
+
return true;
|
|
45609
|
+
const decide5 = (outcome) => record4({ verdict: verdict2, outcome, tier: "two", sql });
|
|
45610
|
+
try {
|
|
45611
|
+
const proceed = await enforceWriteGate({
|
|
45612
|
+
verdict: verdict2,
|
|
45613
|
+
statement: sql,
|
|
45614
|
+
operation: verdict2.operation ?? "WRITE",
|
|
45615
|
+
human: { isTTY: process.stdin.isTTY === true },
|
|
45616
|
+
...options.ask && { ask: options.ask }
|
|
45617
|
+
});
|
|
45618
|
+
await decide5(proceed ? "allowed" : "declined");
|
|
45619
|
+
return proceed;
|
|
45620
|
+
} catch (error) {
|
|
45621
|
+
if (!(error instanceof WriteGateRefusal))
|
|
45622
|
+
throw error;
|
|
45623
|
+
await decide5("refused");
|
|
45624
|
+
process.stderr.write(`${error.message}
|
|
45625
|
+
`);
|
|
45626
|
+
return false;
|
|
45627
|
+
}
|
|
45628
|
+
};
|
|
45629
|
+
}
|
|
45630
|
+
var init_shell_write_gate = __esm(() => {
|
|
45631
|
+
init_write_gate();
|
|
45632
|
+
init_write_gate_prompt();
|
|
45633
|
+
init_write_gate_guard();
|
|
45634
|
+
});
|
|
45635
|
+
|
|
45156
45636
|
// src/commands/shell.ts
|
|
45157
45637
|
var exports_shell = {};
|
|
45158
45638
|
__export(exports_shell, {
|
|
@@ -45259,7 +45739,18 @@ async function runShell(options, configPath) {
|
|
|
45259
45739
|
columnsByTable,
|
|
45260
45740
|
commandNames
|
|
45261
45741
|
};
|
|
45262
|
-
|
|
45742
|
+
let replInterface = null;
|
|
45743
|
+
const { createPromptAsker: createPromptAsker2 } = await Promise.resolve().then(() => exports_shell_prompt_asker);
|
|
45744
|
+
const asker = createPromptAsker2(() => replInterface);
|
|
45745
|
+
const { toSqlDialect: toSqlDialect2 } = await Promise.resolve().then(() => (init_permission_guard(), exports_permission_guard));
|
|
45746
|
+
const dialect = toSqlDialect2(config.connection.system);
|
|
45747
|
+
const writeGate = dialect ? (await Promise.resolve().then(() => (init_shell_write_gate(), exports_shell_write_gate))).createShellWriteGate({
|
|
45748
|
+
config,
|
|
45749
|
+
configPath,
|
|
45750
|
+
dialect,
|
|
45751
|
+
ask: asker.ask
|
|
45752
|
+
}) : null;
|
|
45753
|
+
const engine = new ReplEngine(adapter, context, HISTORY_PATH, config, writeGate);
|
|
45263
45754
|
const complete = createCompleter(context);
|
|
45264
45755
|
console.error(import_picocolors5.default.bold(t_vars("shell.welcome", {
|
|
45265
45756
|
system: config.connection.system,
|
|
@@ -45293,10 +45784,22 @@ async function runShell(options, configPath) {
|
|
|
45293
45784
|
completer: (line) => complete(line),
|
|
45294
45785
|
terminal: process.stdin.isTTY ?? false
|
|
45295
45786
|
});
|
|
45787
|
+
replInterface = rl;
|
|
45296
45788
|
const continuationPrompt = import_picocolors5.default.dim(t("shell.continuation_prompt") + "> ");
|
|
45297
45789
|
rl.prompt();
|
|
45298
|
-
|
|
45299
|
-
|
|
45790
|
+
const handleLine = async (line) => {
|
|
45791
|
+
rl.pause();
|
|
45792
|
+
let result;
|
|
45793
|
+
try {
|
|
45794
|
+
result = await engine.processInput(line);
|
|
45795
|
+
} catch (error) {
|
|
45796
|
+
console.error(import_picocolors5.default.red(t_vars("shell.error_sql_failed", { message: error.message })));
|
|
45797
|
+
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45798
|
+
rl.prompt();
|
|
45799
|
+
return;
|
|
45800
|
+
} finally {
|
|
45801
|
+
rl.resume();
|
|
45802
|
+
}
|
|
45300
45803
|
switch (result.action) {
|
|
45301
45804
|
case "quit":
|
|
45302
45805
|
if (result.output)
|
|
@@ -45307,16 +45810,21 @@ async function runShell(options, configPath) {
|
|
|
45307
45810
|
console.clear();
|
|
45308
45811
|
break;
|
|
45309
45812
|
case "multiline":
|
|
45310
|
-
|
|
45813
|
+
if (result.output)
|
|
45814
|
+
console.error(result.output);
|
|
45311
45815
|
break;
|
|
45312
45816
|
case "continue":
|
|
45313
45817
|
if (result.output) {
|
|
45314
45818
|
console.log(result.output);
|
|
45315
45819
|
}
|
|
45316
|
-
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45317
45820
|
break;
|
|
45318
45821
|
}
|
|
45822
|
+
rl.setPrompt(engine.isMultiline() ? continuationPrompt : import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45319
45823
|
rl.prompt();
|
|
45824
|
+
};
|
|
45825
|
+
let pending = Promise.resolve();
|
|
45826
|
+
rl.on("line", (line) => {
|
|
45827
|
+
pending = pending.then(() => handleLine(line));
|
|
45320
45828
|
});
|
|
45321
45829
|
rl.on("close", async () => {
|
|
45322
45830
|
console.error(import_picocolors5.default.dim(t("shell.goodbye")));
|
|
@@ -45325,7 +45833,10 @@ async function runShell(options, configPath) {
|
|
|
45325
45833
|
process.exit(0);
|
|
45326
45834
|
});
|
|
45327
45835
|
rl.on("SIGINT", () => {
|
|
45836
|
+
if (asker.cancel())
|
|
45837
|
+
return;
|
|
45328
45838
|
if (engine.isMultiline()) {
|
|
45839
|
+
engine.cancelMultiline();
|
|
45329
45840
|
console.error(import_picocolors5.default.dim(t("shell.multiline_cancelled")));
|
|
45330
45841
|
rl.setPrompt(import_picocolors5.default.cyan(t("shell.prompt") + "> "));
|
|
45331
45842
|
}
|
|
@@ -45341,7 +45852,10 @@ async function runBatchSession(engine, input) {
|
|
|
45341
45852
|
continue;
|
|
45342
45853
|
const result = await engine.processInput(line);
|
|
45343
45854
|
if (result.output) {
|
|
45344
|
-
|
|
45855
|
+
if (result.action === "multiline")
|
|
45856
|
+
console.error(result.output);
|
|
45857
|
+
else
|
|
45858
|
+
console.log(result.output);
|
|
45345
45859
|
}
|
|
45346
45860
|
if (result.action === "quit") {
|
|
45347
45861
|
return;
|