@carllee1983/dbcli 1.23.1 → 1.28.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/dist/cli.mjs CHANGED
@@ -25565,7 +25565,7 @@ class AuditLockManager {
25565
25565
  var LOCK_RETRY_BUDGET_MS = 200, LOCK_BACKOFF_START_MS = 5, LOCK_BACKOFF_MAX_MS = 50, STALE_LOCK_MULTIPLIER = 10;
25566
25566
  var init_lock = () => {};
25567
25567
 
25568
- // src/core/audit/rotation.ts
25568
+ // src/utils/jsonl-rotation.ts
25569
25569
  import { rename } from "fs/promises";
25570
25570
  function shouldRotate(stats, thresholds, nextLineByteLength) {
25571
25571
  const bytesAfter = stats.currentSizeBytes + nextLineByteLength;
@@ -25577,7 +25577,12 @@ async function rotate(currentPath, previousPath) {
25577
25577
  await rename(currentPath, previousPath);
25578
25578
  } catch {}
25579
25579
  }
25580
- var init_rotation = () => {};
25580
+ var init_jsonl_rotation = () => {};
25581
+
25582
+ // src/core/audit/rotation.ts
25583
+ var init_rotation = __esm(() => {
25584
+ init_jsonl_rotation();
25585
+ });
25581
25586
 
25582
25587
  // src/core/audit/logger.ts
25583
25588
  import { appendFile, mkdir as mkdir3, readFile, stat } from "fs/promises";
@@ -81217,7 +81222,7 @@ var {
81217
81222
  // package.json
81218
81223
  var package_default = {
81219
81224
  name: "@carllee1983/dbcli",
81220
- version: "1.23.1",
81225
+ version: "1.28.0",
81221
81226
  description: "Database CLI for AI agents",
81222
81227
  type: "module",
81223
81228
  publishConfig: {
@@ -81226,6 +81231,13 @@ var package_default = {
81226
81231
  bin: {
81227
81232
  dbcli: "./dist/cli.mjs"
81228
81233
  },
81234
+ exports: {
81235
+ ".": "./dist/cli.mjs",
81236
+ "./core": {
81237
+ types: "./dist/core.d.ts",
81238
+ import: "./dist/core.mjs"
81239
+ }
81240
+ },
81229
81241
  license: "MIT",
81230
81242
  author: "Carl Lee",
81231
81243
  repository: {
@@ -81296,9 +81308,11 @@ var package_default = {
81296
81308
  "@inquirer/prompts": "^8.4.3",
81297
81309
  "@testing-library/react": "^16.3.2",
81298
81310
  "@types/bun": "latest",
81311
+ "@types/pg": "^8.20.0",
81299
81312
  "@types/react": "^19.2.14",
81300
81313
  "@types/react-dom": "^19.2.3",
81301
81314
  autoprefixer: "^10.5.0",
81315
+ "dts-bundle-generator": "^9.5.1",
81302
81316
  eslint: "^10.4.0",
81303
81317
  "happy-dom": "^20.9.0",
81304
81318
  postcss: "^8.5.14",
@@ -87648,6 +87662,7 @@ function resolveSkillSource(lang) {
87648
87662
  var SUPPORTED_PLATFORMS = [
87649
87663
  "claude",
87650
87664
  "gemini",
87665
+ "antigravity",
87651
87666
  "copilot",
87652
87667
  "cursor",
87653
87668
  "codex",
@@ -87715,6 +87730,8 @@ function getInstallPath(platform) {
87715
87730
  return path4.join(home, ".claude", "skills", "dbcli", "SKILL.md");
87716
87731
  case "gemini":
87717
87732
  return path4.join(home, ".gemini", "skills", "dbcli", "SKILL.md");
87733
+ case "antigravity":
87734
+ return path4.join(home, ".gemini", "antigravity-cli", "skills", "dbcli", "SKILL.md");
87718
87735
  case "codex":
87719
87736
  return path4.join(home, ".codex", "skills", "dbcli", "SKILL.md");
87720
87737
  case "copilot":
@@ -87755,7 +87772,7 @@ async function ensureDir(dirPath) {
87755
87772
  }
87756
87773
  }
87757
87774
  function registerSkillCommand(program2) {
87758
- const skill = program2.command("skill").description(t("skill.description")).option("--install <platform>", "Install to platform directory (claude, gemini, copilot, cursor, codex, windsurf)").option("--output <path>", "Write skill to file instead of stdout").addOption(new Option("--lang <lang>", "Source language for SKILL content").choices(["en", "zh-TW"]).default("en")).action(async (options) => {
87775
+ const skill = program2.command("skill").description(t("skill.description")).option("--install <platform>", "Install to platform directory (claude, gemini, antigravity, copilot, cursor, codex, windsurf)").option("--output <path>", "Write skill to file instead of stdout").addOption(new Option("--lang <lang>", "Source language for SKILL content").choices(["en", "zh-TW"]).default("en")).action(async (options) => {
87759
87776
  try {
87760
87777
  await skillCommand(program2, options);
87761
87778
  } catch (error) {
@@ -90355,11 +90372,454 @@ function makeSavedQueryLoader2() {
90355
90372
  };
90356
90373
  }
90357
90374
 
90375
+ // src/commands/snapshot.ts
90376
+ init_adapters();
90377
+ init_config();
90378
+ import { join as join26 } from "path";
90379
+ init_validation();
90380
+ init_blacklist_validator();
90381
+ init_engine_hints();
90382
+
90383
+ // src/core/result-snapshot/fingerprint.ts
90384
+ import { createHash as createHash2 } from "crypto";
90385
+ function sha256(input) {
90386
+ return createHash2("sha256").update(input).digest("hex");
90387
+ }
90388
+ function isNumeric(values) {
90389
+ return values.length > 0 && values.every((v) => typeof v === "number");
90390
+ }
90391
+ function buildColumn(name2, type, values) {
90392
+ const nonNull = values.filter((v) => v !== null && v !== undefined);
90393
+ const nullCount = values.length - nonNull.length;
90394
+ const asStrings = nonNull.map((v) => String(v));
90395
+ const distinctCount = new Set(asStrings).size;
90396
+ const checksum = sha256(JSON.stringify([...asStrings].sort()));
90397
+ const col = { name: name2, type, nullCount, distinctCount, checksum };
90398
+ if (isNumeric(nonNull)) {
90399
+ col.min = Math.min(...nonNull);
90400
+ col.max = Math.max(...nonNull);
90401
+ col.sum = nonNull.reduce((a, b) => a + b, 0);
90402
+ } else if (nonNull.length > 0) {
90403
+ const sorted = [...asStrings].sort();
90404
+ col.min = sorted[0];
90405
+ col.max = sorted[sorted.length - 1];
90406
+ }
90407
+ return col;
90408
+ }
90409
+ function buildFingerprint(result, opts) {
90410
+ const columns = result.columnNames.map((name2, i) => buildColumn(name2, result.columnTypes?.[i] ?? "unknown", result.rows.map((r) => r[name2])));
90411
+ for (const name2 of opts.redactedColumns ?? []) {
90412
+ if (!columns.some((c2) => c2.name === name2)) {
90413
+ columns.push({
90414
+ name: name2,
90415
+ type: "redacted",
90416
+ nullCount: 0,
90417
+ distinctCount: 0,
90418
+ checksum: "",
90419
+ redacted: true
90420
+ });
90421
+ }
90422
+ }
90423
+ const rowStrings = result.rows.map((r) => JSON.stringify(result.columnNames.map((n) => r[n]))).sort();
90424
+ const snap = {
90425
+ schemaVersion: 1,
90426
+ query: opts.query ?? "",
90427
+ engine: opts.engine ?? "postgresql",
90428
+ createdAt: opts.createdAt ?? new Date().toISOString(),
90429
+ rowCount: result.rowCount,
90430
+ resultChecksum: sha256(JSON.stringify(rowStrings)),
90431
+ columns
90432
+ };
90433
+ if (opts.includeRows)
90434
+ snap.rows = result.rows;
90435
+ return snap;
90436
+ }
90437
+ function compareAgainst(current, baseline, tolerance) {
90438
+ const checks = [];
90439
+ if (tolerance === 0) {
90440
+ checks.push({
90441
+ name: "resultChecksum",
90442
+ expected: baseline.resultChecksum,
90443
+ actual: current.resultChecksum,
90444
+ pass: current.resultChecksum === baseline.resultChecksum
90445
+ });
90446
+ return checks;
90447
+ }
90448
+ const within = (a, b) => b === 0 ? a === 0 : Math.abs(a - b) / Math.abs(b) <= tolerance;
90449
+ checks.push({
90450
+ name: "rowCount",
90451
+ expected: `${baseline.rowCount} \xB1${tolerance * 100}%`,
90452
+ actual: String(current.rowCount),
90453
+ pass: within(current.rowCount, baseline.rowCount)
90454
+ });
90455
+ for (const base of baseline.columns) {
90456
+ if (base.sum === undefined)
90457
+ continue;
90458
+ const cur = current.columns.find((c2) => c2.name === base.name);
90459
+ const curSum = cur?.sum;
90460
+ checks.push({
90461
+ name: `sum(${base.name})`,
90462
+ expected: `${base.sum} \xB1${tolerance * 100}%`,
90463
+ actual: String(curSum),
90464
+ pass: curSum !== undefined && within(curSum, base.sum)
90465
+ });
90466
+ }
90467
+ return checks;
90468
+ }
90469
+
90470
+ // src/core/result-snapshot/types.ts
90471
+ class SnapshotVersionError extends Error {
90472
+ code = "SNAPSHOT_VERSION_MISMATCH";
90473
+ constructor(message) {
90474
+ super(message);
90475
+ this.name = "SnapshotVersionError";
90476
+ }
90477
+ }
90478
+
90479
+ // src/core/result-snapshot/serializer.ts
90480
+ async function writeSnapshot(path6, snap) {
90481
+ await Bun.write(path6, JSON.stringify(snap, null, 2));
90482
+ }
90483
+ async function readSnapshot(path6) {
90484
+ const file = Bun.file(path6);
90485
+ if (!await file.exists()) {
90486
+ const err = new Error(`Snapshot file not found: ${path6}`);
90487
+ err.code = "SNAPSHOT_NOT_FOUND";
90488
+ throw err;
90489
+ }
90490
+ const parsed = JSON.parse(await file.text());
90491
+ if (parsed.schemaVersion !== 1) {
90492
+ throw new SnapshotVersionError(`Unsupported snapshot schemaVersion ${parsed.schemaVersion} (expected 1)`);
90493
+ }
90494
+ return parsed;
90495
+ }
90496
+
90497
+ // src/commands/snapshot.ts
90498
+ init_saved_queries();
90499
+ init_integration_helper();
90500
+ var ALLOWED_FORMATS12 = ["json", "table"];
90501
+ var SQL_SYSTEMS5 = ["postgresql", "mysql", "mariadb"];
90502
+ function requireSqlConnection9(connection) {
90503
+ if (!SQL_SYSTEMS5.includes(connection.system)) {
90504
+ throw new Error(`snapshot currently supports SQL engines only, got: ${connection.system}`);
90505
+ }
90506
+ return connection;
90507
+ }
90508
+ function pad(n) {
90509
+ return String(n).padStart(2, "0");
90510
+ }
90511
+ function defaultSnapshotPath() {
90512
+ const d = new Date;
90513
+ const stamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
90514
+ return join26(".dbcli", "snapshots", `snap-${stamp}.json`);
90515
+ }
90516
+ var snapshotCommand = new Command().name("snapshot").description("Capture a result fingerprint (rowCount + per-column aggregates) for later comparison").argument("<query>", "SQL string or @saved-query reference").option("--out <path>", "Write snapshot to this path (default: .dbcli/snapshots/snap-<timestamp>.json)").option("--rows", "Also include full (blacklist-masked) rows in the snapshot", false).option("--stdout", "Print snapshot JSON to stdout instead of writing a file", false).option("--format <format>", "Output format for --stdout: json (default) or table", "json").option("--no-limit", "Disable the automatic query-only LIMIT").action(async (query, options, command) => {
90517
+ try {
90518
+ validateFormat(options.format, ALLOWED_FORMATS12, "snapshot");
90519
+ const configPath = resolveConfigPath(command, options);
90520
+ const config = await configModule.read(configPath);
90521
+ if (!config.connection) {
90522
+ console.error("Database not configured. Run: dbcli init");
90523
+ process.exit(1);
90524
+ }
90525
+ let sql = query;
90526
+ if (query.startsWith("@")) {
90527
+ const engine = mapSystemToEngine(config.connection.system);
90528
+ const dirs = resolveSnippetDirs(process.cwd());
90529
+ const snippets = await loadSnippets(dirs);
90530
+ sql = resolveByName(snippets, query.slice(1), engine).query.sqlBody;
90531
+ }
90532
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection9(config.connection));
90533
+ await adapter.connect();
90534
+ try {
90535
+ const blacklistManager = new BlacklistManager(config);
90536
+ const blacklistValidator = new BlacklistValidator(blacklistManager);
90537
+ const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, options);
90538
+ const result = await executor3.execute(sql, { autoLimit: options.limit !== false });
90539
+ const table = extractTableName(sql);
90540
+ const redactedColumns = table ? blacklistManager.getBlacklistedColumns(table) : [];
90541
+ const snap = buildFingerprint(result, {
90542
+ includeRows: options.rows === true,
90543
+ redactedColumns,
90544
+ query: sql,
90545
+ engine: config.connection.system
90546
+ });
90547
+ if (options.stdout === true) {
90548
+ console.log(JSON.stringify(snap, null, 2));
90549
+ } else {
90550
+ const outPath = options.out ?? defaultSnapshotPath();
90551
+ await writeSnapshot(outPath, snap);
90552
+ console.error(`Snapshot saved to ${outPath} (${snap.rowCount} rows, ${snap.columns.length} columns)`);
90553
+ }
90554
+ await writeAuditEntry(config, "snapshot", options, {
90555
+ success: true,
90556
+ sql
90557
+ });
90558
+ } finally {
90559
+ await adapter.disconnect();
90560
+ }
90561
+ } catch (error) {
90562
+ if (error instanceof Error) {
90563
+ console.error(error.message);
90564
+ if (error instanceof ConnectionError)
90565
+ error.hints.forEach((h) => console.error(` Hint: ${h}`));
90566
+ }
90567
+ process.exit(1);
90568
+ }
90569
+ });
90570
+
90571
+ // src/commands/assert.ts
90572
+ init_adapters();
90573
+ init_config();
90574
+ init_validation();
90575
+ init_blacklist_validator();
90576
+ init_saved_queries();
90577
+
90578
+ // src/core/assert/grammar.ts
90579
+ class AssertExpressionError extends Error {
90580
+ code = "ASSERT_BAD_EXPRESSION";
90581
+ constructor(input) {
90582
+ super(`Cannot parse --expect "${input}". Examples: "rows > 0", "value == 5000", ` + `"col:email not null", "col:id unique", "col:amount between 0 and 100", "col:age >= 18".`);
90583
+ this.name = "AssertExpressionError";
90584
+ }
90585
+ }
90586
+ var OP = /(>=|<=|==|!=|>|<)/;
90587
+ function parseScalar2(raw) {
90588
+ const s = raw.trim();
90589
+ if (/^".*"$/.test(s) || /^'.*'$/.test(s))
90590
+ return s.slice(1, -1);
90591
+ const n = Number(s);
90592
+ return Number.isNaN(n) ? s : n;
90593
+ }
90594
+ function parseExpect(input) {
90595
+ const s = input.trim();
90596
+ const rows = s.match(new RegExp(`^rows\\s*${OP.source}\\s*(\\d+)$`));
90597
+ if (rows)
90598
+ return { kind: "rows", op: rows[1], value: parseInt(rows[2], 10) };
90599
+ const value = s.match(new RegExp(`^value\\s*${OP.source}\\s*(.+)$`));
90600
+ if (value)
90601
+ return { kind: "value", op: value[1], value: parseScalar2(value[2]) };
90602
+ const col = s.match(/^col:(\w+)\s+(.+)$/);
90603
+ if (col) {
90604
+ const column = col[1];
90605
+ const rest = col[2].trim();
90606
+ if (/^not\s+null$/i.test(rest))
90607
+ return { kind: "col", column, pred: { type: "notNull" } };
90608
+ if (/^unique$/i.test(rest))
90609
+ return { kind: "col", column, pred: { type: "unique" } };
90610
+ const between = rest.match(/^between\s+(-?\d+(?:\.\d+)?)\s+and\s+(-?\d+(?:\.\d+)?)$/i);
90611
+ if (between) {
90612
+ return {
90613
+ kind: "col",
90614
+ column,
90615
+ pred: { type: "between", low: Number(between[1]), high: Number(between[2]) }
90616
+ };
90617
+ }
90618
+ const cmp = rest.match(new RegExp(`^${OP.source}\\s*(.+)$`));
90619
+ if (cmp)
90620
+ return {
90621
+ kind: "col",
90622
+ column,
90623
+ pred: { type: "cmp", op: cmp[1], value: parseScalar2(cmp[2]) }
90624
+ };
90625
+ }
90626
+ throw new AssertExpressionError(input);
90627
+ }
90628
+
90629
+ // src/core/assert/evaluator.ts
90630
+ class AssertShapeError extends Error {
90631
+ code = "ASSERT_SHAPE_MISMATCH";
90632
+ constructor(message) {
90633
+ super(message);
90634
+ this.name = "AssertShapeError";
90635
+ }
90636
+ }
90637
+ function compare(a, op, b) {
90638
+ switch (op) {
90639
+ case ">":
90640
+ return a > b;
90641
+ case ">=":
90642
+ return a >= b;
90643
+ case "<":
90644
+ return a < b;
90645
+ case "<=":
90646
+ return a <= b;
90647
+ case "==":
90648
+ return a === b;
90649
+ case "!=":
90650
+ return a !== b;
90651
+ }
90652
+ }
90653
+ function firstScalar(result) {
90654
+ if (result.columnNames.length !== 1) {
90655
+ throw new AssertShapeError(`value assertion needs a single-column result, got ${result.columnNames.length} columns. Project to one column.`);
90656
+ }
90657
+ if (result.rows.length === 0)
90658
+ return null;
90659
+ const v = result.rows[0][result.columnNames[0]];
90660
+ return v === null || v === undefined ? null : v;
90661
+ }
90662
+ function evaluateExpect(node, result) {
90663
+ if (node.kind === "rows") {
90664
+ const actual = result.rowCount;
90665
+ return {
90666
+ name: "rows",
90667
+ expected: `rows ${node.op} ${node.value}`,
90668
+ actual: String(actual),
90669
+ pass: compare(actual, node.op, node.value)
90670
+ };
90671
+ }
90672
+ if (node.kind === "value") {
90673
+ const actual = firstScalar(result);
90674
+ return {
90675
+ name: "value",
90676
+ expected: `value ${node.op} ${node.value}`,
90677
+ actual: String(actual),
90678
+ pass: actual !== null && compare(actual, node.op, node.value)
90679
+ };
90680
+ }
90681
+ const { column, pred } = node;
90682
+ const values = result.rows.map((r) => r[column]);
90683
+ const nonNull = values.filter((v) => v !== null && v !== undefined);
90684
+ if (pred.type === "notNull") {
90685
+ const nullCount = values.length - nonNull.length;
90686
+ return {
90687
+ name: `col:${column} not null`,
90688
+ expected: "0 nulls",
90689
+ actual: `${nullCount} nulls`,
90690
+ pass: nullCount === 0
90691
+ };
90692
+ }
90693
+ if (pred.type === "unique") {
90694
+ const distinct = new Set(nonNull.map((v) => String(v))).size;
90695
+ return {
90696
+ name: `col:${column} unique`,
90697
+ expected: `${nonNull.length} distinct`,
90698
+ actual: `${distinct} distinct`,
90699
+ pass: distinct === nonNull.length
90700
+ };
90701
+ }
90702
+ if (pred.type === "between") {
90703
+ const bad2 = nonNull.filter((v) => typeof v !== "number" || v < pred.low || v > pred.high);
90704
+ return {
90705
+ name: `col:${column} between ${pred.low} and ${pred.high}`,
90706
+ expected: "0 out of range",
90707
+ actual: `${bad2.length} out of range`,
90708
+ pass: bad2.length === 0
90709
+ };
90710
+ }
90711
+ const bad = nonNull.filter((v) => !compare(v, pred.op, pred.value));
90712
+ return {
90713
+ name: `col:${column} ${pred.op} ${pred.value}`,
90714
+ expected: "0 violations",
90715
+ actual: `${bad.length} violations`,
90716
+ pass: bad.length === 0
90717
+ };
90718
+ }
90719
+ function compareVs(a, b, mode) {
90720
+ if (mode === "rows") {
90721
+ return {
90722
+ name: "vs:rows",
90723
+ expected: String(b.rowCount),
90724
+ actual: String(a.rowCount),
90725
+ pass: a.rowCount === b.rowCount
90726
+ };
90727
+ }
90728
+ const av = firstScalar(a);
90729
+ const bv = firstScalar(b);
90730
+ return {
90731
+ name: "vs:value",
90732
+ expected: String(bv),
90733
+ actual: String(av),
90734
+ pass: av !== null && bv !== null && av === bv
90735
+ };
90736
+ }
90737
+
90738
+ // src/commands/assert.ts
90739
+ init_integration_helper();
90740
+ var ALLOWED_FORMATS13 = ["json", "table"];
90741
+ var SQL_SYSTEMS6 = ["postgresql", "mysql", "mariadb"];
90742
+ function requireSqlConnection10(connection) {
90743
+ if (!SQL_SYSTEMS6.includes(connection.system)) {
90744
+ throw new Error(`assert currently supports SQL engines only, got: ${connection.system}`);
90745
+ }
90746
+ return connection;
90747
+ }
90748
+ var assertCommand = new Command().name("assert").description("Assert an invariant on a query result (exit 1 on failure unless --no-fail)").argument("<query>", "SQL string or @saved-query reference").option("--expect <condition>", 'e.g. "rows > 0", "value == 5000", "col:email not null"').option("--vs <query>", "Second SQL/@saved query for reconciliation").option("--compare <mode>", "For --vs: rows | value (default value)", "value").option("--against <path>", "Compare current result fingerprint against a saved snapshot").option("--tolerance <pct>", "For --against: allowed relative drift, e.g. 0.01 (default 0)", (v) => parseFloat(v), 0).option("--no-fail", "Always exit 0; report pass/fail in output only").option("--format <format>", "Output format: json (default) or table", "json").action(async (query, options, command) => {
90749
+ try {
90750
+ validateFormat(options.format, ALLOWED_FORMATS13, "assert");
90751
+ if (!options.expect && !options.vs && !options.against) {
90752
+ console.error("Specify one of --expect, --vs, or --against");
90753
+ process.exit(1);
90754
+ }
90755
+ const configPath = resolveConfigPath(command, options);
90756
+ const config = await configModule.read(configPath);
90757
+ if (!config.connection) {
90758
+ console.error("Database not configured. Run: dbcli init");
90759
+ process.exit(1);
90760
+ }
90761
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection10(config.connection));
90762
+ await adapter.connect();
90763
+ let verdict;
90764
+ try {
90765
+ const blacklistManager = new BlacklistManager(config);
90766
+ const blacklistValidator = new BlacklistValidator(blacklistManager);
90767
+ const executor3 = new QueryExecutor(adapter, config.permission, blacklistValidator, config, options);
90768
+ const engine = mapSystemToEngine(config.connection.system);
90769
+ const dirs = resolveSnippetDirs(process.cwd());
90770
+ const snippets = await loadSnippets(dirs);
90771
+ const resolveSql = (q) => q.startsWith("@") ? resolveByName(snippets, q.slice(1), engine).query.sqlBody : q;
90772
+ const result = await executor3.execute(resolveSql(query), { autoLimit: true });
90773
+ const checks = [];
90774
+ if (options.expect)
90775
+ checks.push(evaluateExpect(parseExpect(options.expect), result));
90776
+ if (options.vs) {
90777
+ const other = await executor3.execute(resolveSql(options.vs), {
90778
+ autoLimit: true
90779
+ });
90780
+ checks.push(compareVs(result, other, options.compare ?? "value"));
90781
+ }
90782
+ if (options.against) {
90783
+ const baseline = await readSnapshot(options.against);
90784
+ const current = buildFingerprint(result, {
90785
+ query: resolveSql(query),
90786
+ engine: config.connection.system
90787
+ });
90788
+ checks.push(...compareAgainst(current, baseline, options.tolerance));
90789
+ }
90790
+ verdict = { pass: checks.every((c2) => c2.pass), checks };
90791
+ await writeAuditEntry(config, "assert", options, {
90792
+ success: verdict.pass,
90793
+ sql: query
90794
+ });
90795
+ } finally {
90796
+ await adapter.disconnect();
90797
+ }
90798
+ if (options.format === "json") {
90799
+ console.log(JSON.stringify(verdict, null, 2));
90800
+ } else {
90801
+ for (const c2 of verdict.checks) {
90802
+ console.log(`${c2.pass ? "PASS" : "FAIL"} ${c2.name} expected=${c2.expected} actual=${c2.actual}`);
90803
+ }
90804
+ console.log(`
90805
+ Verdict: ${verdict.pass ? "PASS" : "FAIL"}`);
90806
+ }
90807
+ process.exit(verdict.pass || options.fail === false ? 0 : 1);
90808
+ } catch (error) {
90809
+ if (error instanceof Error) {
90810
+ console.error(error.message);
90811
+ if (error instanceof ConnectionError)
90812
+ error.hints.forEach((h) => console.error(` Hint: ${h}`));
90813
+ }
90814
+ process.exit(1);
90815
+ }
90816
+ });
90817
+
90358
90818
  // src/commands/recovery.ts
90359
90819
  init_message_loader();
90360
90820
  init_validation();
90361
90821
  init_recovery();
90362
- var ALLOWED_FORMATS12 = ["json", "markdown"];
90822
+ var ALLOWED_FORMATS14 = ["json", "markdown"];
90363
90823
  function parseCode(value) {
90364
90824
  const normalized = value.trim();
90365
90825
  if (!RECOVERY_CODES.includes(normalized)) {
@@ -90395,7 +90855,7 @@ var recoveryCommand = new Command().name("recovery").description(t("recovery.des
90395
90855
  const forAgent = options.forAgent === true;
90396
90856
  const format = forAgent ? "json" : options.format;
90397
90857
  const brief = forAgent || options.brief === true;
90398
- validateFormat(format, ALLOWED_FORMATS12, "recovery");
90858
+ validateFormat(format, ALLOWED_FORMATS14, "recovery");
90399
90859
  if (options.list === true) {
90400
90860
  if (format === "markdown") {
90401
90861
  console.log(renderCodeList());
@@ -90572,7 +91032,7 @@ function looksLikeSavedEnvelope(x) {
90572
91032
  init_next_step();
90573
91033
  init_next_step_schema();
90574
91034
  init_validation();
90575
- var ALLOWED_FORMATS13 = ["json", "markdown"];
91035
+ var ALLOWED_FORMATS15 = ["json", "markdown"];
90576
91036
  var ALLOWED_TIERS = ["readonly-cmd", "write-cmd"];
90577
91037
 
90578
91038
  class RecoverCliError extends Error {
@@ -90724,7 +91184,7 @@ var recoverCommand = new Command().name("recover").description("Inspect or apply
90724
91184
  }
90725
91185
  const explicitFormat = options.format;
90726
91186
  const format = explicitFormat ?? (options.apply === true || options.next === true ? "json" : "markdown");
90727
- validateFormat(format, ALLOWED_FORMATS13, "recover");
91187
+ validateFormat(format, ALLOWED_FORMATS15, "recover");
90728
91188
  let allowWrite = "none";
90729
91189
  if (options.next !== true) {
90730
91190
  const allowWriteRaw = options.allowWrite;
@@ -90791,14 +91251,14 @@ var recoverCommand = new Command().name("recover").description("Inspect or apply
90791
91251
 
90792
91252
  // src/commands/audit.ts
90793
91253
  import { rm as rm3, stat as stat7 } from "fs/promises";
90794
- import { join as join26 } from "path";
91254
+ import { join as join27 } from "path";
90795
91255
  init_message_loader();
90796
91256
  init_validation();
90797
91257
  init_config();
90798
91258
  init_config_binding();
90799
91259
  init_reader();
90800
91260
  init_integration_helper();
90801
- var ALLOWED_FORMATS14 = ["table", "json"];
91261
+ var ALLOWED_FORMATS16 = ["table", "json"];
90802
91262
  var DEFAULT_TAIL_N = 10;
90803
91263
  var MAX_TAIL_N = 1e4;
90804
91264
  var SHORT_ID_LEN = 8;
@@ -90807,8 +91267,8 @@ var PREFIX_MIN = 4;
90807
91267
  async function resolveAuditPaths(configPath, config) {
90808
91268
  const storagePath = await resolveConfigStoragePath(configPath);
90809
91269
  const connName = config.effectiveConnectionName || getGlobalConnectionName() || "default";
90810
- const auditDir = join26(storagePath, ".dbcli", "audit");
90811
- const auditFile = join26(auditDir, `${connName}.jsonl`);
91270
+ const auditDir = join27(storagePath, ".dbcli", "audit");
91271
+ const auditFile = join27(auditDir, `${connName}.jsonl`);
90812
91272
  return { auditDir, connectionName: connName, auditFile };
90813
91273
  }
90814
91274
  function isAuditDisabled(config) {
@@ -90992,13 +91452,13 @@ async function statAuditFile(file) {
90992
91452
  }
90993
91453
  }
90994
91454
  var auditCommand = new Command("audit").description(t("audit.description"));
90995
- 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) => {
91455
+ 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_FORMATS16.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) => {
90996
91456
  const forAgent = options.forAgent === true;
90997
91457
  const format = forAgent ? "json" : options.format;
90998
91458
  const briefSource = command.getOptionValueSource("brief");
90999
91459
  const briefExplicit = briefSource !== undefined && briefSource !== "default";
91000
91460
  const brief = briefExplicit ? options.brief === true : forAgent;
91001
- validateFormat(format, ALLOWED_FORMATS14, "audit tail");
91461
+ validateFormat(format, ALLOWED_FORMATS16, "audit tail");
91002
91462
  const n = parseTailN(options.n);
91003
91463
  const configPath = resolveConfigPath(command, options);
91004
91464
  const config = await configModule.read(configPath);
@@ -91041,7 +91501,7 @@ auditCommand.command("tail").description(t("audit.tail.description")).option("--
91041
91501
  console.log(renderTailTable(tail));
91042
91502
  }
91043
91503
  });
91044
- auditCommand.command("show [id]").description(t("audit.show.description")).option("--all", "Search across all connections", false).option("--recovery-ref <ref>", "Look up by entry.recovery_ref (exact match)").option("--format <format>", `Output format: ${ALLOWED_FORMATS14.join(" | ")} (default: table)`, "table").option("--brief", "Trim metadata + redacted_query", false).option("--no-brief", "Disable brief mode (override --for-agent default)").option("--for-agent", "Shortcut for --format json --brief", false).action(async (id, options, command) => {
91504
+ auditCommand.command("show [id]").description(t("audit.show.description")).option("--all", "Search across all connections", false).option("--recovery-ref <ref>", "Look up by entry.recovery_ref (exact match)").option("--format <format>", `Output format: ${ALLOWED_FORMATS16.join(" | ")} (default: table)`, "table").option("--brief", "Trim metadata + redacted_query", false).option("--no-brief", "Disable brief mode (override --for-agent default)").option("--for-agent", "Shortcut for --format json --brief", false).action(async (id, options, command) => {
91045
91505
  if (id && options.recoveryRef) {
91046
91506
  console.error(t("audit.show_mutex_violation"));
91047
91507
  process.exit(1);
@@ -91055,7 +91515,7 @@ auditCommand.command("show [id]").description(t("audit.show.description")).optio
91055
91515
  const briefSource = command.getOptionValueSource("brief");
91056
91516
  const briefExplicit = briefSource !== undefined && briefSource !== "default";
91057
91517
  const brief = briefExplicit ? options.brief === true : forAgent;
91058
- validateFormat(format, ALLOWED_FORMATS14, "audit show");
91518
+ validateFormat(format, ALLOWED_FORMATS16, "audit show");
91059
91519
  const configPath = resolveConfigPath(command, options);
91060
91520
  const config = await configModule.read(configPath);
91061
91521
  if (isAuditDisabled(config))
@@ -91197,13 +91657,13 @@ auditCommand.command("clear").description(t("audit.clear.description")).option("
91197
91657
  `);
91198
91658
  process.exit(0);
91199
91659
  });
91200
- 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) => {
91660
+ auditCommand.command("health").description(t("audit.health.description")).option("--format <format>", `Output format: ${ALLOWED_FORMATS16.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) => {
91201
91661
  const forAgent = options.forAgent === true;
91202
91662
  const format = forAgent ? "json" : options.format;
91203
91663
  const briefSource = command.getOptionValueSource("brief");
91204
91664
  const briefExplicit = briefSource !== undefined && briefSource !== "default";
91205
91665
  const brief = briefExplicit ? options.brief === true : forAgent;
91206
- validateFormat(format, ALLOWED_FORMATS14, "audit health");
91666
+ validateFormat(format, ALLOWED_FORMATS16, "audit health");
91207
91667
  const configPath = resolveConfigPath(command, options);
91208
91668
  const config = await configModule.read(configPath);
91209
91669
  const logger = await getAuditLogger(config, configPath);
@@ -91235,15 +91695,15 @@ init_config_binding();
91235
91695
  init_schema_path();
91236
91696
  init_config();
91237
91697
  init_integration_helper();
91238
- import { join as join27 } from "path";
91698
+ import { join as join28 } from "path";
91239
91699
  import { resolveSrv as resolveSrv2 } from "dns/promises";
91240
- function requireSqlConnection9(connection) {
91700
+ function requireSqlConnection11(connection) {
91241
91701
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
91242
91702
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
91243
91703
  }
91244
91704
  return connection;
91245
91705
  }
91246
- var ALLOWED_FORMATS15 = ["text", "json"];
91706
+ var ALLOWED_FORMATS17 = ["text", "json"];
91247
91707
  var SENSITIVE_PATTERNS = [
91248
91708
  "password",
91249
91709
  "passwd",
@@ -91315,7 +91775,7 @@ var runDoctorChecks = {
91315
91775
  }
91316
91776
  },
91317
91777
  async checkConfigExists(configPath, existsFn) {
91318
- const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(join27(configPath, "config.json")).exists();
91778
+ const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(join28(configPath, "config.json")).exists();
91319
91779
  return {
91320
91780
  group: "Configuration",
91321
91781
  label: "Config exists",
@@ -91472,7 +91932,7 @@ var runDoctorChecks = {
91472
91932
  async checkV2Config(configPath) {
91473
91933
  const results = [];
91474
91934
  const storagePath = await resolveConfigStoragePath(configPath);
91475
- const configFile = Bun.file(join27(storagePath, "config.json"));
91935
+ const configFile = Bun.file(join28(storagePath, "config.json"));
91476
91936
  if (!await configFile.exists())
91477
91937
  return results;
91478
91938
  let raw;
@@ -91512,7 +91972,7 @@ var runDoctorChecks = {
91512
91972
  }
91513
91973
  for (const [name2, conn] of Object.entries(config.connections)) {
91514
91974
  if (conn.envFile) {
91515
- const envPath = join27(storagePath, conn.envFile);
91975
+ const envPath = join28(storagePath, conn.envFile);
91516
91976
  const exists = await Bun.file(envPath).exists();
91517
91977
  results.push({
91518
91978
  group: "Configuration",
@@ -91657,7 +92117,7 @@ async function collectElasticsearchDoctorResults(config) {
91657
92117
  return results;
91658
92118
  }
91659
92119
  var doctorCommand = new Command("doctor").description("Run diagnostic checks on dbcli configuration, environment, and connection").option("--format <type>", "Output format: text, json", "text").action(async (options) => {
91660
- validateFormat(options.format, ALLOWED_FORMATS15, "doctor");
92120
+ validateFormat(options.format, ALLOWED_FORMATS17, "doctor");
91661
92121
  const logger = getLogger();
91662
92122
  const results = [];
91663
92123
  const configPath = resolveConfigPath(doctorCommand);
@@ -91703,7 +92163,7 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
91703
92163
  blacklistedColumns
91704
92164
  }));
91705
92165
  } else {
91706
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection9(config.connection));
92166
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
91707
92167
  await adapter.connect();
91708
92168
  results.push({
91709
92169
  group: "Connection & Data",
@@ -91731,7 +92191,7 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
91731
92191
  }
91732
92192
  try {
91733
92193
  const schemaConnName = await getSchemaIsolationConnectionName(configPath);
91734
- const indexPath = join27(resolveSchemaPath(storagePath, schemaConnName), "index.json");
92194
+ const indexPath = join28(resolveSchemaPath(storagePath, schemaConnName), "index.json");
91735
92195
  const indexFile = Bun.file(indexPath);
91736
92196
  let indexParsed = null;
91737
92197
  if (await indexFile.exists()) {
@@ -91785,7 +92245,7 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
91785
92245
 
91786
92246
  // src/commands/completion.ts
91787
92247
  init_colors();
91788
- import { join as join28 } from "path";
92248
+ import { join as join29 } from "path";
91789
92249
  import { homedir as homedir3 } from "os";
91790
92250
  function extractCommands(program2) {
91791
92251
  return program2.commands.map((cmd) => ({
@@ -91887,11 +92347,11 @@ function getInstallPath2(shell) {
91887
92347
  const home = homedir3();
91888
92348
  switch (shell) {
91889
92349
  case "bash":
91890
- return join28(home, ".bashrc");
92350
+ return join29(home, ".bashrc");
91891
92351
  case "zsh":
91892
- return join28(home, ".zshrc");
92352
+ return join29(home, ".zshrc");
91893
92353
  case "fish":
91894
- return join28(home, ".config", "fish", "completions", "dbcli.fish");
92354
+ return join29(home, ".config", "fish", "completions", "dbcli.fish");
91895
92355
  default:
91896
92356
  throw new Error(`Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
91897
92357
  }
@@ -91911,7 +92371,7 @@ var MARKER_END = "# <<< dbcli completion <<<";
91911
92371
  async function installCompletion(shell, script) {
91912
92372
  const targetPath = getInstallPath2(shell);
91913
92373
  if (shell === "fish") {
91914
- const dir = join28(homedir3(), ".config", "fish", "completions");
92374
+ const dir = join29(homedir3(), ".config", "fish", "completions");
91915
92375
  await Bun.$`mkdir -p ${dir}`.quiet();
91916
92376
  await Bun.file(targetPath).write(script);
91917
92377
  console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
@@ -92144,7 +92604,7 @@ ${t("upgrade.failed")}`));
92144
92604
  init_config();
92145
92605
  init_adapters();
92146
92606
  import { createInterface as createInterface3 } from "readline";
92147
- import { join as join29 } from "path";
92607
+ import { join as join30 } from "path";
92148
92608
  import { homedir as homedir4 } from "os";
92149
92609
 
92150
92610
  // src/core/repl/types.ts
@@ -93066,13 +93526,13 @@ async function runEsShell(configPath) {
93066
93526
  }
93067
93527
 
93068
93528
  // src/commands/shell.ts
93069
- function requireSqlConnection10(connection) {
93529
+ function requireSqlConnection12(connection) {
93070
93530
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
93071
93531
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
93072
93532
  }
93073
93533
  return connection;
93074
93534
  }
93075
- var HISTORY_PATH = join29(homedir4(), ".dbcli_history");
93535
+ var HISTORY_PATH = join30(homedir4(), ".dbcli_history");
93076
93536
  var MONGO_COMPLETION_EAGER_THRESHOLD = 20;
93077
93537
  async function populateMongoColumns(mongoAdapter, collectionNames, threshold = MONGO_COMPLETION_EAGER_THRESHOLD) {
93078
93538
  const columnsByTable = {};
@@ -93109,7 +93569,7 @@ async function runShell(options, configPath) {
93109
93569
  const connectionOpts = config.connection;
93110
93570
  const mongoInner = isMongoDB ? AdapterFactory.createMongoDBAdapter(connectionOpts) : null;
93111
93571
  const redisInner = isRedis ? AdapterFactory.createRedisAdapter(connectionOpts, config.blacklist?.tables ?? [], config.redis?.mask ?? []) : null;
93112
- const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(requireSqlConnection10(connectionOpts));
93572
+ const adapter = isMongoDB ? new MongoShellAdapter(mongoInner) : isRedis ? new RedisShellAdapter(redisInner) : AdapterFactory.createSqlAdapter(requireSqlConnection12(connectionOpts));
93113
93573
  try {
93114
93574
  await adapter.connect();
93115
93575
  } catch (error) {
@@ -93836,7 +94296,7 @@ class DDLExecutor {
93836
94296
  }
93837
94297
  }
93838
94298
  // src/commands/migrate.ts
93839
- function requireSqlConnection11(connection) {
94299
+ function requireSqlConnection13(connection) {
93840
94300
  if (!["postgresql", "mysql", "mariadb"].includes(connection.system)) {
93841
94301
  throw new Error(`This command requires a SQL connection, got: ${connection.system}`);
93842
94302
  }
@@ -93860,7 +94320,7 @@ async function runDDL(operation, opts) {
93860
94320
  console.error("\u6B64\u547D\u4EE4\u76EE\u524D\u4E0D\u652F\u63F4 Elasticsearch\uFF1B\u5982\u9700\u5EFA\u7ACB\u7D22\u5F15\u6216\u8ABF\u6574 mapping\uFF0C\u8ACB\u6539\u7528\u5916\u90E8\u5DE5\u5177");
93861
94321
  process.exit(1);
93862
94322
  }
93863
- const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection11(config.connection));
94323
+ const adapter = AdapterFactory.createSqlAdapter(requireSqlConnection13(config.connection));
93864
94324
  const isDryRun = !opts.execute;
93865
94325
  if (!isDryRun) {
93866
94326
  await adapter.connect();
@@ -94071,7 +94531,7 @@ init_config();
94071
94531
  init_errors();
94072
94532
  init_message_loader();
94073
94533
  init_config_binding();
94074
- import { join as join30 } from "path";
94534
+ import { join as join31 } from "path";
94075
94535
  async function switchDefault(configPath, name2, config) {
94076
94536
  if (!config.connections[name2]) {
94077
94537
  const available = Object.keys(config.connections).join(", ");
@@ -94094,7 +94554,7 @@ function listConnectionsForDisplay(config) {
94094
94554
  }
94095
94555
  async function ensureV2Config(configPath) {
94096
94556
  const storagePath = await resolveConfigStoragePath(configPath);
94097
- const configFile = Bun.file(join30(storagePath, "config.json"));
94557
+ const configFile = Bun.file(join31(storagePath, "config.json"));
94098
94558
  const legacyFile = Bun.file(configPath);
94099
94559
  if (!await configFile.exists() && !await legacyFile.exists()) {
94100
94560
  throw new ConfigError(t("init.config_not_found"));
@@ -94156,14 +94616,1214 @@ var useCommand = new Command("use").description("Switch or display the default d
94156
94616
  }
94157
94617
  });
94158
94618
 
94619
+ // src/commands/proxy.ts
94620
+ init_config();
94621
+ init_validation();
94622
+ import { join as join32 } from "path";
94623
+
94624
+ // src/proxy/relay.ts
94625
+ class TcpRelay {
94626
+ clientBytes = 0;
94627
+ serverBytes = 0;
94628
+ opts;
94629
+ constructor(opts) {
94630
+ this.opts = opts;
94631
+ }
94632
+ fromClient(bytes) {
94633
+ this.opts.writeToUpstream(bytes);
94634
+ this.clientBytes += bytes.length;
94635
+ this.feed("client_to_server", bytes);
94636
+ }
94637
+ fromUpstream(bytes) {
94638
+ this.opts.writeToClient(bytes);
94639
+ this.serverBytes += bytes.length;
94640
+ this.feed("server_to_client", bytes);
94641
+ }
94642
+ feed(direction, bytes) {
94643
+ try {
94644
+ this.opts.analyzer.onData(direction, bytes);
94645
+ } catch (err) {
94646
+ this.opts.onSignal({
94647
+ kind: "parse_error",
94648
+ message: err instanceof Error ? err.message : String(err)
94649
+ });
94650
+ }
94651
+ }
94652
+ }
94653
+
94654
+ // src/proxy/events.ts
94655
+ init_jsonl_rotation();
94656
+ import { appendFile as appendFile2, mkdir as mkdir10, readFile as readFile6, stat as stat8 } from "fs/promises";
94657
+ import { dirname as dirname11 } from "path";
94658
+
94659
+ // src/proxy/sql-metadata.ts
94660
+ var KNOWN_KEYWORDS = [
94661
+ "SELECT",
94662
+ "INSERT",
94663
+ "UPDATE",
94664
+ "DELETE",
94665
+ "CREATE",
94666
+ "ALTER",
94667
+ "DROP",
94668
+ "TRUNCATE",
94669
+ "BEGIN",
94670
+ "COMMIT",
94671
+ "ROLLBACK",
94672
+ "SET",
94673
+ "SHOW",
94674
+ "USE"
94675
+ ];
94676
+ var KNOWN = new Set(KNOWN_KEYWORDS);
94677
+ function detectStatement(sql) {
94678
+ const m = sql.trim().match(/^([a-zA-Z]+)/);
94679
+ if (!m || !m[1])
94680
+ return "OTHER";
94681
+ const kw = m[1].toUpperCase();
94682
+ return KNOWN.has(kw) ? kw : "OTHER";
94683
+ }
94684
+ var TABLE_RE = /\b(?:FROM|JOIN|INTO|UPDATE)\s+["'`]?([A-Za-z_]\w*)["'`]?(?:\.["'`]?([A-Za-z_]\w*)["'`]?)?/gi;
94685
+ function extractTables(sql) {
94686
+ const seen = new Set;
94687
+ for (const m of sql.matchAll(TABLE_RE)) {
94688
+ const name2 = m[2] ?? m[1];
94689
+ if (name2)
94690
+ seen.add(name2);
94691
+ }
94692
+ return [...seen];
94693
+ }
94694
+ function redactLiterals(sql) {
94695
+ const noStrings = sql.replace(/'(?:[^']|'')*'/g, "?");
94696
+ return noStrings.replace(/\b\d+(?:\.\d+)?\b/g, "?");
94697
+ }
94698
+
94699
+ // src/proxy/events.ts
94700
+ var PROXY_EVENT_VERSION = 1;
94701
+ function hasSql(e) {
94702
+ return e.type === "query_observed" || e.type === "query_completed" || e.type === "query_errored";
94703
+ }
94704
+ function applyRedaction(event, mode) {
94705
+ if (mode === "none" || !hasSql(event))
94706
+ return event;
94707
+ return { ...event, sql: redactLiterals(event.sql) };
94708
+ }
94709
+ var DEFAULT_ROTATION = {
94710
+ maxBytes: 50 * 1024 * 1024,
94711
+ maxEntries: 200000
94712
+ };
94713
+
94714
+ class EventWriter {
94715
+ path;
94716
+ previousPath;
94717
+ redact;
94718
+ maxBytes;
94719
+ maxEntries;
94720
+ dirEnsured = false;
94721
+ initialized = false;
94722
+ currentSizeBytes = 0;
94723
+ currentEntryCount = 0;
94724
+ writeChain = Promise.resolve();
94725
+ constructor(opts) {
94726
+ this.path = opts.path;
94727
+ this.previousPath = `${opts.path}.1`;
94728
+ this.redact = opts.redact;
94729
+ this.maxBytes = opts.rotation?.maxBytes ?? DEFAULT_ROTATION.maxBytes;
94730
+ this.maxEntries = opts.rotation?.maxEntries ?? DEFAULT_ROTATION.maxEntries;
94731
+ }
94732
+ write(event) {
94733
+ const op = this.writeChain.then(() => this.writeInternal(event));
94734
+ this.writeChain = op.then(() => {
94735
+ return;
94736
+ }, () => {
94737
+ return;
94738
+ });
94739
+ return op;
94740
+ }
94741
+ async writeInternal(event) {
94742
+ if (!this.dirEnsured) {
94743
+ await mkdir10(dirname11(this.path), { recursive: true });
94744
+ this.dirEnsured = true;
94745
+ }
94746
+ if (!this.initialized) {
94747
+ await this.syncCountersFromDisk();
94748
+ this.initialized = true;
94749
+ }
94750
+ const redacted = applyRedaction(event, this.redact);
94751
+ const line = JSON.stringify(redacted) + `
94752
+ `;
94753
+ const lineBytes = Buffer.byteLength(line, "utf8");
94754
+ if (shouldRotate({ currentSizeBytes: this.currentSizeBytes, currentEntryCount: this.currentEntryCount }, { maxBytes: this.maxBytes, maxEntries: this.maxEntries }, lineBytes)) {
94755
+ await rotate(this.path, this.previousPath);
94756
+ this.currentSizeBytes = 0;
94757
+ this.currentEntryCount = 0;
94758
+ }
94759
+ await appendFile2(this.path, line, { encoding: "utf8" });
94760
+ this.currentSizeBytes += lineBytes;
94761
+ this.currentEntryCount += 1;
94762
+ }
94763
+ async syncCountersFromDisk() {
94764
+ try {
94765
+ const s = await stat8(this.path);
94766
+ this.currentSizeBytes = s.size;
94767
+ const raw = await readFile6(this.path, "utf8");
94768
+ this.currentEntryCount = raw.split(`
94769
+ `).filter(Boolean).length;
94770
+ } catch {
94771
+ this.currentSizeBytes = 0;
94772
+ this.currentEntryCount = 0;
94773
+ }
94774
+ }
94775
+ }
94776
+
94777
+ // src/proxy/session.ts
94778
+ class ProxySession {
94779
+ o;
94780
+ active = null;
94781
+ queryCounter = 0;
94782
+ startedAt = 0;
94783
+ clientBytesAtBoundary = 0;
94784
+ pending = Promise.resolve();
94785
+ constructor(opts) {
94786
+ this.o = opts;
94787
+ }
94788
+ enqueue(event) {
94789
+ this.pending = this.pending.then(() => this.o.writeEvent(event)).catch((err) => {
94790
+ this.o.warn(`event write failed: ${err instanceof Error ? err.message : String(err)}`);
94791
+ });
94792
+ }
94793
+ async start() {
94794
+ this.startedAt = this.o.now();
94795
+ this.enqueue({
94796
+ version: PROXY_EVENT_VERSION,
94797
+ type: "session_started",
94798
+ timestamp: new Date().toISOString(),
94799
+ engine: this.o.engine,
94800
+ sessionId: this.o.sessionId,
94801
+ client: this.o.client,
94802
+ target: this.o.target
94803
+ });
94804
+ await this.flush();
94805
+ }
94806
+ onSignal(signal) {
94807
+ switch (signal.kind) {
94808
+ case "query":
94809
+ this.beginQuery(signal.sql, signal.tags ?? []);
94810
+ break;
94811
+ case "query_end":
94812
+ this.completeQuery(signal.rowCount ?? null);
94813
+ break;
94814
+ case "error":
94815
+ this.errorQuery(signal.code, signal.message);
94816
+ break;
94817
+ case "tag":
94818
+ if (this.active && !this.active.tags.includes(signal.tag)) {
94819
+ this.active.tags.push(signal.tag);
94820
+ }
94821
+ break;
94822
+ case "parse_error":
94823
+ this.enqueue({
94824
+ version: PROXY_EVENT_VERSION,
94825
+ type: "parse_error",
94826
+ timestamp: new Date().toISOString(),
94827
+ engine: this.o.engine,
94828
+ sessionId: this.o.sessionId,
94829
+ client: this.o.client,
94830
+ target: this.o.target,
94831
+ message: signal.message,
94832
+ tags: []
94833
+ });
94834
+ break;
94835
+ }
94836
+ }
94837
+ beginQuery(sql, tags) {
94838
+ const bytes = this.o.getBytes();
94839
+ this.queryCounter += 1;
94840
+ this.active = {
94841
+ queryId: `qry_${this.o.sessionId}_${this.queryCounter}`,
94842
+ sql,
94843
+ startedAt: this.o.now(),
94844
+ clientBytesAtStart: this.clientBytesAtBoundary,
94845
+ serverBytesAtStart: bytes.serverBytes,
94846
+ tags: [...tags]
94847
+ };
94848
+ this.enqueue({
94849
+ version: PROXY_EVENT_VERSION,
94850
+ type: "query_observed",
94851
+ timestamp: new Date().toISOString(),
94852
+ engine: this.o.engine,
94853
+ sessionId: this.o.sessionId,
94854
+ queryId: this.active.queryId,
94855
+ client: this.o.client,
94856
+ target: this.o.target,
94857
+ sql,
94858
+ statement: detectStatement(sql),
94859
+ tables: extractTables(sql),
94860
+ tags: [...this.active.tags]
94861
+ });
94862
+ }
94863
+ completeQuery(rowCount) {
94864
+ const q3 = this.active;
94865
+ if (!q3)
94866
+ return;
94867
+ const bytes = this.o.getBytes();
94868
+ const durationMs = this.o.now() - q3.startedAt;
94869
+ const requestBytes = bytes.clientBytes - q3.clientBytesAtStart;
94870
+ const responseBytes = bytes.serverBytes - q3.serverBytesAtStart;
94871
+ const slow = durationMs >= this.o.slowMs;
94872
+ this.enqueue({
94873
+ version: PROXY_EVENT_VERSION,
94874
+ type: "query_completed",
94875
+ timestamp: new Date().toISOString(),
94876
+ engine: this.o.engine,
94877
+ sessionId: this.o.sessionId,
94878
+ queryId: q3.queryId,
94879
+ client: this.o.client,
94880
+ target: this.o.target,
94881
+ sql: q3.sql,
94882
+ statement: detectStatement(q3.sql),
94883
+ tables: extractTables(q3.sql),
94884
+ durationMs,
94885
+ requestBytes,
94886
+ responseBytes,
94887
+ rowCount,
94888
+ slow,
94889
+ error: null,
94890
+ tags: [...q3.tags]
94891
+ });
94892
+ if (slow) {
94893
+ this.o.warn(`slow query (${durationMs}ms): ${q3.sql.slice(0, 80)}`);
94894
+ }
94895
+ this.clientBytesAtBoundary = bytes.clientBytes;
94896
+ this.active = null;
94897
+ }
94898
+ errorQuery(code, message) {
94899
+ const q3 = this.active;
94900
+ const bytes = this.o.getBytes();
94901
+ const startedAt = q3?.startedAt ?? this.o.now();
94902
+ this.enqueue({
94903
+ version: PROXY_EVENT_VERSION,
94904
+ type: "query_errored",
94905
+ timestamp: new Date().toISOString(),
94906
+ engine: this.o.engine,
94907
+ sessionId: this.o.sessionId,
94908
+ queryId: q3?.queryId ?? `qry_${this.o.sessionId}_err_${++this.queryCounter}`,
94909
+ client: this.o.client,
94910
+ target: this.o.target,
94911
+ sql: q3?.sql ?? "",
94912
+ statement: detectStatement(q3?.sql ?? ""),
94913
+ tables: extractTables(q3?.sql ?? ""),
94914
+ durationMs: this.o.now() - startedAt,
94915
+ requestBytes: q3 ? bytes.clientBytes - q3.clientBytesAtStart : 0,
94916
+ responseBytes: q3 ? bytes.serverBytes - q3.serverBytesAtStart : 0,
94917
+ rowCount: null,
94918
+ error: { code, message },
94919
+ tags: q3 ? [...q3.tags] : []
94920
+ });
94921
+ this.clientBytesAtBoundary = bytes.clientBytes;
94922
+ this.active = null;
94923
+ }
94924
+ async end(reason) {
94925
+ const bytes = this.o.getBytes();
94926
+ this.enqueue({
94927
+ version: PROXY_EVENT_VERSION,
94928
+ type: "session_ended",
94929
+ timestamp: new Date().toISOString(),
94930
+ engine: this.o.engine,
94931
+ sessionId: this.o.sessionId,
94932
+ client: this.o.client,
94933
+ target: this.o.target,
94934
+ durationMs: this.o.now() - this.startedAt,
94935
+ requestBytes: bytes.clientBytes,
94936
+ responseBytes: bytes.serverBytes,
94937
+ reason
94938
+ });
94939
+ await this.flush();
94940
+ }
94941
+ async flush() {
94942
+ await this.pending;
94943
+ }
94944
+ }
94945
+
94946
+ // src/proxy/analyzers/types.ts
94947
+ var UTF8 = new TextDecoder;
94948
+
94949
+ class FrameBuffer {
94950
+ buf = new Uint8Array(0);
94951
+ get length() {
94952
+ return this.buf.length;
94953
+ }
94954
+ push(chunk) {
94955
+ if (this.buf.length === 0) {
94956
+ this.buf = chunk.slice();
94957
+ return;
94958
+ }
94959
+ const next = new Uint8Array(this.buf.length + chunk.length);
94960
+ next.set(this.buf, 0);
94961
+ next.set(chunk, this.buf.length);
94962
+ this.buf = next;
94963
+ }
94964
+ peek(n) {
94965
+ return this.buf.subarray(0, Math.min(n, this.buf.length));
94966
+ }
94967
+ byteAt(offset) {
94968
+ return this.buf[offset];
94969
+ }
94970
+ consume(n) {
94971
+ this.buf = this.buf.subarray(Math.min(n, this.buf.length));
94972
+ }
94973
+ readUInt24LE(offset) {
94974
+ const b0 = this.buf[offset] ?? 0;
94975
+ const b1 = this.buf[offset + 1] ?? 0;
94976
+ const b2 = this.buf[offset + 2] ?? 0;
94977
+ return b0 | b1 << 8 | b2 << 16;
94978
+ }
94979
+ readUInt16LE(offset) {
94980
+ const b0 = this.buf[offset] ?? 0;
94981
+ const b1 = this.buf[offset + 1] ?? 0;
94982
+ return b0 | b1 << 8;
94983
+ }
94984
+ readUInt32BE(offset) {
94985
+ const b0 = this.buf[offset] ?? 0;
94986
+ const b1 = this.buf[offset + 1] ?? 0;
94987
+ const b2 = this.buf[offset + 2] ?? 0;
94988
+ const b3 = this.buf[offset + 3] ?? 0;
94989
+ return (b0 << 24 >>> 0) + (b1 << 16) + (b2 << 8) + b3;
94990
+ }
94991
+ text(start, end) {
94992
+ return UTF8.decode(this.buf.subarray(start, end));
94993
+ }
94994
+ }
94995
+
94996
+ // src/proxy/analyzers/mysql.ts
94997
+ var COM_QUERY = 3;
94998
+ var COM_STMT_PREPARE = 22;
94999
+ var COM_STMT_EXECUTE = 23;
95000
+ var HEADER = 4;
95001
+ function createMysqlAnalyzer(deps) {
95002
+ const clientBuf = new FrameBuffer;
95003
+ const serverBuf = new FrameBuffer;
95004
+ let awaitingResponse = false;
95005
+ function handleClientPacket(payloadStart, payloadLen) {
95006
+ const seqId = clientBuf.byteAt(payloadStart - 1);
95007
+ if (seqId !== 0)
95008
+ return;
95009
+ const cmd = clientBuf.byteAt(payloadStart);
95010
+ if (cmd === undefined)
95011
+ return;
95012
+ if (cmd === COM_QUERY) {
95013
+ const sql = clientBuf.text(payloadStart + 1, payloadStart + payloadLen);
95014
+ deps.emit({ kind: "query", sql });
95015
+ awaitingResponse = true;
95016
+ } else if (cmd === COM_STMT_PREPARE) {
95017
+ const sql = clientBuf.text(payloadStart + 1, payloadStart + payloadLen);
95018
+ deps.emit({ kind: "query", sql, tags: ["prepared_statement"] });
95019
+ awaitingResponse = true;
95020
+ } else if (cmd === COM_STMT_EXECUTE) {
95021
+ deps.emit({ kind: "tag", tag: "prepared_statement" });
95022
+ awaitingResponse = true;
95023
+ }
95024
+ }
95025
+ function handleServerPacket(payloadStart, payloadLen) {
95026
+ if (!awaitingResponse)
95027
+ return;
95028
+ const first = serverBuf.byteAt(payloadStart);
95029
+ if (first === undefined)
95030
+ return;
95031
+ if (first === 255) {
95032
+ const code = serverBuf.readUInt16LE(payloadStart + 1);
95033
+ let msgStart = payloadStart + 3;
95034
+ if (serverBuf.byteAt(msgStart) === 35) {
95035
+ msgStart += 6;
95036
+ }
95037
+ const message = serverBuf.text(msgStart, payloadStart + payloadLen);
95038
+ deps.emit({ kind: "error", code: String(code), message });
95039
+ awaitingResponse = false;
95040
+ } else if (first === 0) {
95041
+ deps.emit({ kind: "query_end", rowCount: null });
95042
+ awaitingResponse = false;
95043
+ } else {
95044
+ deps.emit({ kind: "tag", tag: "parse_partial" });
95045
+ deps.emit({ kind: "query_end", rowCount: null });
95046
+ awaitingResponse = false;
95047
+ }
95048
+ }
95049
+ function drain(buf, onPacket) {
95050
+ while (buf.length >= HEADER) {
95051
+ const payloadLen = buf.readUInt24LE(0);
95052
+ if (buf.length < HEADER + payloadLen)
95053
+ break;
95054
+ onPacket(HEADER, payloadLen);
95055
+ buf.consume(HEADER + payloadLen);
95056
+ }
95057
+ }
95058
+ return {
95059
+ onData(direction, chunk) {
95060
+ try {
95061
+ if (direction === "client_to_server") {
95062
+ clientBuf.push(chunk);
95063
+ drain(clientBuf, handleClientPacket);
95064
+ } else {
95065
+ serverBuf.push(chunk);
95066
+ drain(serverBuf, handleServerPacket);
95067
+ }
95068
+ } catch (err) {
95069
+ deps.emit({
95070
+ kind: "parse_error",
95071
+ message: err instanceof Error ? err.message : String(err)
95072
+ });
95073
+ }
95074
+ }
95075
+ };
95076
+ }
95077
+
95078
+ // src/proxy/analyzers/postgresql.ts
95079
+ function rowCountFromTag(tag) {
95080
+ const parts = tag.trim().split(/\s+/);
95081
+ const last = parts[parts.length - 1];
95082
+ if (last === undefined)
95083
+ return null;
95084
+ const n = Number(last);
95085
+ return Number.isInteger(n) ? n : null;
95086
+ }
95087
+ var MAX_STARTUP_LEN = 1e4;
95088
+ function createPostgresAnalyzer(deps) {
95089
+ const clientBuf = new FrameBuffer;
95090
+ const serverBuf = new FrameBuffer;
95091
+ let startupSeen = false;
95092
+ let awaitingResponse = false;
95093
+ function cStringEnd(buf, start, end) {
95094
+ let i = start;
95095
+ while (i < end && buf.byteAt(i) !== 0)
95096
+ i++;
95097
+ return i;
95098
+ }
95099
+ function readCString(buf, start, end) {
95100
+ return buf.text(start, cStringEnd(buf, start, end));
95101
+ }
95102
+ function handleClientMessage(type, bodyStart, bodyEnd) {
95103
+ const t2 = String.fromCharCode(type);
95104
+ if (t2 === "Q") {
95105
+ const sql = readCString(clientBuf, bodyStart, bodyEnd);
95106
+ deps.emit({ kind: "query", sql });
95107
+ awaitingResponse = true;
95108
+ } else if (t2 === "P" || t2 === "B" || t2 === "E" || t2 === "S" || t2 === "D") {
95109
+ deps.emit({ kind: "tag", tag: "extended_protocol" });
95110
+ if (t2 === "P") {
95111
+ const nameEnd = cStringEnd(clientBuf, bodyStart, bodyEnd) + 1;
95112
+ const sql = readCString(clientBuf, nameEnd, bodyEnd);
95113
+ if (sql)
95114
+ deps.emit({ kind: "query", sql, tags: ["extended_protocol"] });
95115
+ awaitingResponse = true;
95116
+ }
95117
+ }
95118
+ }
95119
+ function handleServerMessage(type, bodyStart, bodyEnd) {
95120
+ if (!awaitingResponse)
95121
+ return;
95122
+ const t2 = String.fromCharCode(type);
95123
+ if (t2 === "E") {
95124
+ let code = null;
95125
+ let message = "";
95126
+ let i = bodyStart;
95127
+ while (i < bodyEnd) {
95128
+ const fieldType = serverBuf.byteAt(i);
95129
+ if (fieldType === undefined || fieldType === 0)
95130
+ break;
95131
+ i += 1;
95132
+ const valEnd = cStringEnd(serverBuf, i, bodyEnd);
95133
+ const value = serverBuf.text(i, valEnd);
95134
+ i = valEnd + 1;
95135
+ if (fieldType === 67)
95136
+ code = value;
95137
+ else if (fieldType === 77)
95138
+ message = value;
95139
+ }
95140
+ deps.emit({ kind: "error", code, message });
95141
+ awaitingResponse = false;
95142
+ } else if (t2 === "C") {
95143
+ const tag = readCString(serverBuf, bodyStart, bodyEnd);
95144
+ deps.emit({ kind: "query_end", rowCount: rowCountFromTag(tag) });
95145
+ awaitingResponse = false;
95146
+ }
95147
+ }
95148
+ function drain(buf, isClient, onMessage) {
95149
+ if (isClient && !startupSeen) {
95150
+ if (buf.length < 4)
95151
+ return;
95152
+ const len = buf.readUInt32BE(0);
95153
+ if (len >= 4 && len <= buf.length && len <= MAX_STARTUP_LEN) {
95154
+ startupSeen = true;
95155
+ buf.consume(len);
95156
+ } else {
95157
+ startupSeen = true;
95158
+ }
95159
+ }
95160
+ while (buf.length >= 5) {
95161
+ const type = buf.byteAt(0);
95162
+ const len = buf.readUInt32BE(1);
95163
+ const total = 1 + len;
95164
+ if (buf.length < total)
95165
+ break;
95166
+ onMessage(type, 5, total);
95167
+ buf.consume(total);
95168
+ }
95169
+ }
95170
+ return {
95171
+ onData(direction, chunk) {
95172
+ try {
95173
+ if (direction === "client_to_server") {
95174
+ clientBuf.push(chunk);
95175
+ drain(clientBuf, true, handleClientMessage);
95176
+ } else {
95177
+ serverBuf.push(chunk);
95178
+ drain(serverBuf, false, handleServerMessage);
95179
+ }
95180
+ } catch (err) {
95181
+ deps.emit({
95182
+ kind: "parse_error",
95183
+ message: err instanceof Error ? err.message : String(err)
95184
+ });
95185
+ }
95186
+ }
95187
+ };
95188
+ }
95189
+
95190
+ // src/proxy/server.ts
95191
+ function makeAnalyzer(engine, deps) {
95192
+ return engine === "postgresql" ? createPostgresAnalyzer(deps) : createMysqlAnalyzer(deps);
95193
+ }
95194
+
95195
+ class ProxyServer {
95196
+ o;
95197
+ writer;
95198
+ listener = null;
95199
+ sessionCounter = 0;
95200
+ writeFailed = false;
95201
+ constructor(opts) {
95202
+ this.o = opts;
95203
+ this.writer = new EventWriter({ path: opts.eventsPath, redact: opts.redact });
95204
+ }
95205
+ get port() {
95206
+ return this.listener?.port ?? null;
95207
+ }
95208
+ async start() {
95209
+ await this.writer.write({
95210
+ version: PROXY_EVENT_VERSION,
95211
+ type: "proxy_started",
95212
+ timestamp: new Date().toISOString(),
95213
+ engine: this.o.engine,
95214
+ sessionId: "pxy_root",
95215
+ listen: `${this.o.listen.host}:${this.o.listen.port}`,
95216
+ target: `${this.o.target.host}:${this.o.target.port}`
95217
+ });
95218
+ this.listener = Bun.listen({
95219
+ hostname: this.o.listen.host,
95220
+ port: this.o.listen.port,
95221
+ socket: {
95222
+ open: (client) => {
95223
+ this.handleConnection(client);
95224
+ },
95225
+ data(client, chunk) {
95226
+ const ctx = client.data;
95227
+ ctx?.onData?.(chunk);
95228
+ },
95229
+ close(client) {
95230
+ const ctx = client.data;
95231
+ ctx?.onClose?.();
95232
+ },
95233
+ error(client) {
95234
+ const ctx = client.data;
95235
+ ctx?.onClose?.();
95236
+ }
95237
+ }
95238
+ });
95239
+ }
95240
+ stop() {
95241
+ this.listener?.stop();
95242
+ this.listener = null;
95243
+ }
95244
+ async handleConnection(client) {
95245
+ if (this.writeFailed) {
95246
+ client.end();
95247
+ return;
95248
+ }
95249
+ this.sessionCounter += 1;
95250
+ const sessionId = `pxy_${this.sessionCounter}`;
95251
+ const clientAddr = client.remoteAddress ?? "unknown";
95252
+ const target = `${this.o.target.host}:${this.o.target.port}`;
95253
+ const earlyBuffer = [];
95254
+ let relay = null;
95255
+ client.data = {
95256
+ onData: (chunk) => {
95257
+ if (relay) {
95258
+ relay.fromClient(chunk);
95259
+ } else {
95260
+ earlyBuffer.push(chunk);
95261
+ }
95262
+ },
95263
+ onClose: () => {
95264
+ client.end();
95265
+ }
95266
+ };
95267
+ const session = new ProxySession({
95268
+ sessionId,
95269
+ engine: this.o.engine,
95270
+ client: clientAddr,
95271
+ target,
95272
+ slowMs: this.o.slowMs,
95273
+ now: () => performance.now(),
95274
+ getBytes: () => ({
95275
+ clientBytes: relay?.clientBytes ?? 0,
95276
+ serverBytes: relay?.serverBytes ?? 0
95277
+ }),
95278
+ writeEvent: (e) => this.writer.write(e).catch((err) => {
95279
+ this.writeFailed = true;
95280
+ this.o.warn(`event write failed: ${err instanceof Error ? err.message : String(err)}`);
95281
+ }),
95282
+ warn: this.o.warn
95283
+ });
95284
+ let upstream;
95285
+ try {
95286
+ const analyzer = makeAnalyzer(this.o.engine, { emit: (s) => session.onSignal(s) });
95287
+ upstream = await Bun.connect({
95288
+ hostname: this.o.target.host,
95289
+ port: this.o.target.port,
95290
+ socket: {
95291
+ data(_s, chunk) {
95292
+ relay?.fromUpstream(chunk);
95293
+ },
95294
+ close() {
95295
+ session.end("upstream_closed").then(() => client.end());
95296
+ },
95297
+ error() {
95298
+ session.end("error").then(() => client.end());
95299
+ }
95300
+ }
95301
+ });
95302
+ relay = new TcpRelay({
95303
+ writeToClient: (b) => {
95304
+ client.write(b);
95305
+ },
95306
+ writeToUpstream: (b) => {
95307
+ upstream.write(b);
95308
+ },
95309
+ analyzer,
95310
+ onSignal: (s) => session.onSignal(s)
95311
+ });
95312
+ const liveUpstream = upstream;
95313
+ client.data = {
95314
+ onData: (chunk) => relay?.fromClient(chunk),
95315
+ onClose: () => void session.end("client_closed").then(() => liveUpstream.end())
95316
+ };
95317
+ for (const chunk of earlyBuffer) {
95318
+ relay.fromClient(chunk);
95319
+ }
95320
+ earlyBuffer.length = 0;
95321
+ } catch (err) {
95322
+ this.o.warn(`upstream connect failed: ${err instanceof Error ? err.message : String(err)}`);
95323
+ await session.start();
95324
+ await session.end("error");
95325
+ client.end();
95326
+ return;
95327
+ }
95328
+ try {
95329
+ await session.start();
95330
+ } catch (err) {
95331
+ this.o.warn(`session start failed: ${err instanceof Error ? err.message : String(err)}`);
95332
+ client.end();
95333
+ upstream.end();
95334
+ return;
95335
+ }
95336
+ }
95337
+ }
95338
+
95339
+ // src/proxy/event-reader.ts
95340
+ import { readFile as readFile7 } from "fs/promises";
95341
+ async function readEvents(path6, opts) {
95342
+ const candidates = opts.includeRotated ? [path6, `${path6}.1`] : [path6];
95343
+ const files = [];
95344
+ const events = [];
95345
+ let malformedLines = 0;
95346
+ for (const file of candidates) {
95347
+ let raw;
95348
+ try {
95349
+ raw = await readFile7(file, "utf8");
95350
+ } catch (err) {
95351
+ if (err.code !== "ENOENT")
95352
+ throw err;
95353
+ continue;
95354
+ }
95355
+ files.push(file);
95356
+ for (const rawLine of raw.split(`
95357
+ `)) {
95358
+ const trimmed = rawLine.trim();
95359
+ if (!trimmed)
95360
+ continue;
95361
+ try {
95362
+ events.push(JSON.parse(trimmed));
95363
+ } catch {
95364
+ malformedLines += 1;
95365
+ }
95366
+ }
95367
+ }
95368
+ events.sort((a, b) => a.timestamp < b.timestamp ? -1 : a.timestamp > b.timestamp ? 1 : 0);
95369
+ return { events, malformedLines, files };
95370
+ }
95371
+
95372
+ // src/proxy/analyze.ts
95373
+ var isCompleted = (e) => e.type === "query_completed";
95374
+ var isErrored = (e) => e.type === "query_errored";
95375
+ function percentile(values, p) {
95376
+ if (values.length === 0)
95377
+ return 0;
95378
+ const sorted = [...values].sort((a, b) => a - b);
95379
+ const rank = Math.ceil(p / 100 * sorted.length);
95380
+ const idx = Math.min(Math.max(rank, 1), sorted.length) - 1;
95381
+ return sorted[idx];
95382
+ }
95383
+ function fingerprintSql(sql) {
95384
+ return redactLiterals(sql).replace(/\s+/g, " ").trim();
95385
+ }
95386
+ function shellEscapeDq(s) {
95387
+ return s.replace(/\\/g, "\\\\").replace(/\$/g, "\\$").replace(/`/g, "\\`").replace(/"/g, "\\\"");
95388
+ }
95389
+ function buildByFingerprint(events, slowMs, top) {
95390
+ const errorByFp = new Map;
95391
+ for (const e of events.filter(isErrored)) {
95392
+ const fp = fingerprintSql(e.sql);
95393
+ errorByFp.set(fp, (errorByFp.get(fp) ?? 0) + 1);
95394
+ }
95395
+ const groups = new Map;
95396
+ for (const e of events.filter(isCompleted)) {
95397
+ const fp = fingerprintSql(e.sql);
95398
+ let g = groups.get(fp);
95399
+ if (!g) {
95400
+ g = {
95401
+ fingerprint: fp,
95402
+ statement: e.statement,
95403
+ tables: e.tables,
95404
+ durations: [],
95405
+ reqBytes: 0,
95406
+ respBytes: 0,
95407
+ rows: [],
95408
+ slowCount: 0,
95409
+ exampleSql: e.sql,
95410
+ exampleQueryId: e.queryId,
95411
+ exampleDuration: e.durationMs
95412
+ };
95413
+ groups.set(fp, g);
95414
+ }
95415
+ g.durations.push(e.durationMs);
95416
+ g.reqBytes += e.requestBytes;
95417
+ g.respBytes += e.responseBytes;
95418
+ if (e.rowCount !== null)
95419
+ g.rows.push(e.rowCount);
95420
+ if (e.durationMs >= slowMs)
95421
+ g.slowCount += 1;
95422
+ if (e.durationMs > g.exampleDuration) {
95423
+ g.exampleDuration = e.durationMs;
95424
+ g.exampleSql = e.sql;
95425
+ g.exampleQueryId = e.queryId;
95426
+ }
95427
+ }
95428
+ const stats = [...groups.values()].map((g) => {
95429
+ const count = g.durations.length;
95430
+ const total = g.durations.reduce((sum, d) => sum + d, 0);
95431
+ return {
95432
+ fingerprint: g.fingerprint,
95433
+ statement: g.statement,
95434
+ tables: g.tables,
95435
+ count,
95436
+ durationMs: {
95437
+ total,
95438
+ avg: count ? Math.round(total / count) : 0,
95439
+ p95: percentile(g.durations, 95),
95440
+ max: count ? Math.max(...g.durations) : 0
95441
+ },
95442
+ rowsAvg: g.rows.length ? Math.round(g.rows.reduce((sum, r) => sum + r, 0) / g.rows.length) : 0,
95443
+ bytesAvg: {
95444
+ request: count ? Math.round(g.reqBytes / count) : 0,
95445
+ response: count ? Math.round(g.respBytes / count) : 0
95446
+ },
95447
+ errorCount: errorByFp.get(g.fingerprint) ?? 0,
95448
+ slowCount: g.slowCount,
95449
+ redacted: redactLiterals(g.exampleSql) === g.exampleSql,
95450
+ exampleSql: g.exampleSql,
95451
+ exampleQueryId: g.exampleQueryId
95452
+ };
95453
+ });
95454
+ stats.sort((a, b) => b.durationMs.total - a.durationMs.total);
95455
+ return stats.map((s, i) => {
95456
+ if (i < top && s.statement === "SELECT") {
95457
+ const sql = shellEscapeDq(s.exampleSql);
95458
+ return {
95459
+ ...s,
95460
+ suggestedCommands: [`dbcli explain "${sql}"`, `dbcli guide missing-index-for "${sql}"`]
95461
+ };
95462
+ }
95463
+ return s;
95464
+ });
95465
+ }
95466
+ function buildSlowest(events, top) {
95467
+ return events.filter(isCompleted).sort((a, b) => b.durationMs - a.durationMs).slice(0, top).map((e) => ({
95468
+ queryId: e.queryId,
95469
+ durationMs: e.durationMs,
95470
+ sql: e.sql,
95471
+ statement: e.statement,
95472
+ tables: e.tables,
95473
+ timestamp: e.timestamp,
95474
+ sessionId: e.sessionId
95475
+ }));
95476
+ }
95477
+ function buildErrors(events) {
95478
+ const groups = new Map;
95479
+ for (const e of events.filter(isErrored)) {
95480
+ const key = `${e.error.code ?? ""} ${e.error.message}`;
95481
+ let g = groups.get(key);
95482
+ if (!g) {
95483
+ g = {
95484
+ code: e.error.code,
95485
+ message: e.error.message,
95486
+ count: 0,
95487
+ fingerprint: fingerprintSql(e.sql),
95488
+ exampleSql: e.sql
95489
+ };
95490
+ groups.set(key, g);
95491
+ }
95492
+ g.count += 1;
95493
+ }
95494
+ return [...groups.values()].sort((a, b) => b.count - a.count);
95495
+ }
95496
+ function buildHotTables(events) {
95497
+ const map = new Map;
95498
+ for (const e of events.filter(isCompleted)) {
95499
+ for (const t2 of e.tables) {
95500
+ let g = map.get(t2);
95501
+ if (!g) {
95502
+ g = { queryCount: 0, totalDurationMs: 0 };
95503
+ map.set(t2, g);
95504
+ }
95505
+ g.queryCount += 1;
95506
+ g.totalDurationMs += e.durationMs;
95507
+ }
95508
+ }
95509
+ return [...map.entries()].map(([table, g]) => ({ table, queryCount: g.queryCount, totalDurationMs: g.totalDurationMs })).sort((a, b) => b.queryCount - a.queryCount);
95510
+ }
95511
+ function buildRepetition(events, threshold) {
95512
+ const groups = new Map;
95513
+ for (const e of events.filter(isCompleted)) {
95514
+ const fp = fingerprintSql(e.sql);
95515
+ const key = `${e.sessionId} ${fp}`;
95516
+ const ts = Date.parse(e.timestamp);
95517
+ let g = groups.get(key);
95518
+ if (!g) {
95519
+ g = {
95520
+ fingerprint: fp,
95521
+ sessionId: e.sessionId,
95522
+ tables: e.tables,
95523
+ count: 0,
95524
+ totalDurationMs: 0,
95525
+ minTs: ts,
95526
+ maxTs: ts
95527
+ };
95528
+ groups.set(key, g);
95529
+ }
95530
+ g.count += 1;
95531
+ g.totalDurationMs += e.durationMs;
95532
+ if (ts < g.minTs)
95533
+ g.minTs = ts;
95534
+ if (ts > g.maxTs)
95535
+ g.maxTs = ts;
95536
+ }
95537
+ return [...groups.values()].filter((g) => g.count >= threshold).map((g) => ({
95538
+ fingerprint: g.fingerprint,
95539
+ sessionId: g.sessionId,
95540
+ count: g.count,
95541
+ spanMs: g.maxTs - g.minTs,
95542
+ totalDurationMs: g.totalDurationMs,
95543
+ tables: g.tables
95544
+ })).sort((a, b) => b.count - a.count);
95545
+ }
95546
+ function analyzeEvents(events, opts) {
95547
+ const timestamps = events.map((e) => e.timestamp).filter(Boolean).sort();
95548
+ const from = timestamps[0] ?? null;
95549
+ const to = timestamps[timestamps.length - 1] ?? null;
95550
+ return {
95551
+ version: 1,
95552
+ tool: "proxy-analyze",
95553
+ engine: events[0]?.engine ?? null,
95554
+ source: {
95555
+ files: opts.sourceFiles,
95556
+ eventsRead: events.length,
95557
+ malformedLines: opts.malformedLines,
95558
+ timeSpan: {
95559
+ from,
95560
+ to,
95561
+ durationMs: from && to ? Date.parse(to) - Date.parse(from) : 0
95562
+ }
95563
+ },
95564
+ summary: buildSummary(events, opts.slowMs),
95565
+ byFingerprint: buildByFingerprint(events, opts.slowMs, opts.top),
95566
+ slowest: buildSlowest(events, opts.top),
95567
+ errors: buildErrors(events),
95568
+ hotTables: buildHotTables(events),
95569
+ repetition: buildRepetition(events, opts.nPlusOne)
95570
+ };
95571
+ }
95572
+ function buildSummary(events, slowMs) {
95573
+ const completed = events.filter(isCompleted);
95574
+ const errored = events.filter(isErrored);
95575
+ const durations = completed.map((e) => e.durationMs);
95576
+ const queries = completed.length;
95577
+ const errors3 = errored.length;
95578
+ const denom = queries + errors3;
95579
+ return {
95580
+ sessions: new Set(events.filter((e) => e.type === "session_started").map((e) => e.sessionId)).size,
95581
+ queries,
95582
+ errors: errors3,
95583
+ errorRate: denom === 0 ? 0 : errors3 / denom,
95584
+ parseErrors: events.filter((e) => e.type === "parse_error").length,
95585
+ slowCount: completed.filter((e) => e.durationMs >= slowMs).length,
95586
+ latencyMs: {
95587
+ p50: percentile(durations, 50),
95588
+ p95: percentile(durations, 95),
95589
+ p99: percentile(durations, 99),
95590
+ max: durations.length ? Math.max(...durations) : 0
95591
+ },
95592
+ bytes: {
95593
+ request: completed.reduce((sum, e) => sum + e.requestBytes, 0),
95594
+ response: completed.reduce((sum, e) => sum + e.responseBytes, 0)
95595
+ }
95596
+ };
95597
+ }
95598
+
95599
+ // src/proxy/analyze-render.ts
95600
+ function renderAnalysisText(report, top) {
95601
+ if (report.summary.queries === 0 && report.summary.errors === 0) {
95602
+ return "no events to analyze";
95603
+ }
95604
+ const s = report.summary;
95605
+ const L2 = [];
95606
+ L2.push("SUMMARY");
95607
+ L2.push(` engine: ${report.engine ?? "unknown"} sessions: ${s.sessions} ` + `queries: ${s.queries} errors: ${s.errors} (${(s.errorRate * 100).toFixed(2)}%)`);
95608
+ L2.push(` latency ms: p50=${s.latencyMs.p50} p95=${s.latencyMs.p95} ` + `p99=${s.latencyMs.p99} max=${s.latencyMs.max} slow=${s.slowCount}`);
95609
+ L2.push(` bytes: req=${s.bytes.request} resp=${s.bytes.response}`);
95610
+ L2.push("", "TOP QUERIES BY TOTAL TIME");
95611
+ for (const f of report.byFingerprint.slice(0, top)) {
95612
+ L2.push(` [${f.count}x total=${f.durationMs.total}ms avg=${f.durationMs.avg} ` + `p95=${f.durationMs.p95}] ${f.fingerprint}`);
95613
+ }
95614
+ L2.push("", "SLOWEST SINGLE QUERIES");
95615
+ for (const q3 of report.slowest.slice(0, top)) {
95616
+ L2.push(` ${q3.durationMs}ms ${q3.sql}`);
95617
+ }
95618
+ L2.push("", "HOT TABLES");
95619
+ for (const t2 of report.hotTables.slice(0, top)) {
95620
+ L2.push(` ${t2.queryCount}x ${t2.totalDurationMs}ms ${t2.table}`);
95621
+ }
95622
+ L2.push("", "ERRORS");
95623
+ if (report.errors.length === 0)
95624
+ L2.push(" (none)");
95625
+ for (const e of report.errors.slice(0, top)) {
95626
+ L2.push(` ${e.count}x [${e.code ?? "?"}] ${e.message}`);
95627
+ }
95628
+ L2.push("", "N+1 SUSPECTS");
95629
+ if (report.repetition.length === 0)
95630
+ L2.push(" (none)");
95631
+ for (const r of report.repetition.slice(0, top)) {
95632
+ L2.push(` ${r.count}x in session ${r.sessionId} (${r.spanMs}ms) ${r.fingerprint}`);
95633
+ }
95634
+ const cmds = [...new Set(report.byFingerprint.flatMap((f) => f.suggestedCommands ?? []))];
95635
+ if (cmds.length) {
95636
+ L2.push("", "SUGGESTED COMMANDS");
95637
+ for (const c2 of cmds)
95638
+ L2.push(` ${c2}`);
95639
+ }
95640
+ return L2.join(`
95641
+ `);
95642
+ }
95643
+
95644
+ // src/commands/proxy.ts
95645
+ var SUPPORTED = ["mysql", "mariadb", "postgresql"];
95646
+ var ALLOWED_FORMATS18 = ["text", "json"];
95647
+ var ALLOWED_REDACT = ["none", "literals"];
95648
+ function parseHostPort(value) {
95649
+ const idx = value.lastIndexOf(":");
95650
+ if (idx <= 0 || idx === value.length - 1) {
95651
+ throw new Error(`Invalid address "${value}". Expected host:port`);
95652
+ }
95653
+ const host = value.slice(0, idx);
95654
+ const port = Number(value.slice(idx + 1));
95655
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
95656
+ throw new Error(`Invalid port in "${value}". Expected host:port with a numeric port`);
95657
+ }
95658
+ return { host, port };
95659
+ }
95660
+ function resolveProxyConfig(input) {
95661
+ if (!input.listen) {
95662
+ throw new Error("--listen <host:port> is required");
95663
+ }
95664
+ const listen = parseHostPort(input.listen);
95665
+ let engine;
95666
+ if (input.subcommandEngine) {
95667
+ engine = input.subcommandEngine;
95668
+ } else {
95669
+ const sys = input.connection?.system;
95670
+ if (!sys || !SUPPORTED.includes(sys)) {
95671
+ throw new Error(`proxy supports mysql, mariadb, postgresql (got: ${sys ?? "none"})`);
95672
+ }
95673
+ engine = sys;
95674
+ }
95675
+ let target;
95676
+ if (input.target) {
95677
+ target = parseHostPort(input.target);
95678
+ } else if (input.connection) {
95679
+ target = { host: input.connection.host, port: input.connection.port };
95680
+ } else {
95681
+ throw new Error("--target <host:port> is required when config does not provide host/port");
95682
+ }
95683
+ return { engine, listen, target };
95684
+ }
95685
+ async function runProxy(subcommandEngine, options, command) {
95686
+ try {
95687
+ validateFormat(options.format ?? "text", ALLOWED_FORMATS18, "proxy");
95688
+ const redact = options.redact ?? "none";
95689
+ if (!ALLOWED_REDACT.includes(redact)) {
95690
+ throw new Error(`Invalid --redact "${redact}". Allowed: none, literals`);
95691
+ }
95692
+ const configPath = resolveConfigPath(command, options);
95693
+ let connection = null;
95694
+ try {
95695
+ const config = await configModule.read(configPath);
95696
+ if (config.connection) {
95697
+ connection = {
95698
+ system: config.connection.system,
95699
+ host: config.connection.host,
95700
+ port: config.connection.port
95701
+ };
95702
+ }
95703
+ } catch {}
95704
+ const resolved = resolveProxyConfig({
95705
+ subcommandEngine,
95706
+ listen: options.listen,
95707
+ target: options.target,
95708
+ connection
95709
+ });
95710
+ const eventsPath = options.events ?? join32(".dbcli", "proxy", "events.jsonl");
95711
+ const slowMs = Number(options.slowMs ?? 1000);
95712
+ if (!Number.isFinite(slowMs) || slowMs < 0) {
95713
+ throw new Error(`Invalid --slow-ms "${options.slowMs}". Expected a non-negative number`);
95714
+ }
95715
+ const server = new ProxyServer({
95716
+ engine: resolved.engine,
95717
+ listen: resolved.listen,
95718
+ target: resolved.target,
95719
+ eventsPath,
95720
+ slowMs,
95721
+ redact,
95722
+ warn: (m) => process.stderr.write(`[proxy] ${m}
95723
+ `)
95724
+ });
95725
+ await server.start();
95726
+ if (options.format === "json") {
95727
+ process.stdout.write(JSON.stringify({
95728
+ status: "listening",
95729
+ engine: resolved.engine,
95730
+ listen: `${resolved.listen.host}:${resolved.listen.port}`,
95731
+ target: `${resolved.target.host}:${resolved.target.port}`,
95732
+ events: eventsPath,
95733
+ redact
95734
+ }) + `
95735
+ `);
95736
+ } else {
95737
+ process.stdout.write(`dbcli proxy (${resolved.engine}) listening on ${resolved.listen.host}:${resolved.listen.port}` + ` -> ${resolved.target.host}:${resolved.target.port}
95738
+ ` + `events: ${eventsPath} | slow-ms: ${slowMs} | redact: ${redact}
95739
+ ` + `Press Ctrl+C to stop.
95740
+ `);
95741
+ }
95742
+ await new Promise((resolve5) => {
95743
+ const shutdown = () => {
95744
+ process.removeListener("SIGINT", shutdown);
95745
+ process.removeListener("SIGTERM", shutdown);
95746
+ server.stop();
95747
+ resolve5();
95748
+ };
95749
+ process.on("SIGINT", shutdown);
95750
+ process.on("SIGTERM", shutdown);
95751
+ });
95752
+ } catch (error) {
95753
+ if (error instanceof Error)
95754
+ console.error(error.message);
95755
+ process.exit(1);
95756
+ }
95757
+ }
95758
+ function addCommonOptions(cmd) {
95759
+ return cmd.option("--listen <host:port>", "Local proxy listen address (required)").option("--target <host:port>", "Upstream DB target (optional when config provides host/port)").option("--events <path>", "Event JSONL path", join32(".dbcli", "proxy", "events.jsonl")).option("--slow-ms <number>", "Threshold (ms); queries at/above it get slow:true in the event + a terminal warning", "1000").option("--redact <mode>", "SQL redaction: none | literals", "none").option("--format <format>", "Runtime status output: text | json", "text");
95760
+ }
95761
+ var proxyCommand = new Command().name("proxy").description("Local development observability proxy for MySQL/MariaDB/PostgreSQL (observe-only)");
95762
+ proxyCommand.enablePositionalOptions();
95763
+ for (const engine of SUPPORTED) {
95764
+ addCommonOptions(proxyCommand.command(engine).description(`Proxy a ${engine} connection`)).action(async (options, command) => {
95765
+ await runProxy(engine, options, command);
95766
+ });
95767
+ }
95768
+ var ANALYZE_FORMATS = ["json", "text"];
95769
+ function parseNonNegInt(value, flag, fallback) {
95770
+ if (value === undefined)
95771
+ return fallback;
95772
+ const n = Number(value);
95773
+ if (!Number.isInteger(n) || n < 0) {
95774
+ throw new Error(`Invalid --${flag} "${value}". Expected a non-negative integer`);
95775
+ }
95776
+ return n;
95777
+ }
95778
+ async function runAnalyze(options) {
95779
+ try {
95780
+ const format = options.format ?? "json";
95781
+ validateFormat(format, ANALYZE_FORMATS, "proxy analyze");
95782
+ const top = parseNonNegInt(options.top, "top", 20);
95783
+ const slowMs = parseNonNegInt(options.slowMs, "slow-ms", 1000);
95784
+ const nPlusOne = parseNonNegInt(options.nPlusOne, "n-plus-one", 10);
95785
+ const eventsPath = options.events ?? join32(".dbcli", "proxy", "events.jsonl");
95786
+ const { events, malformedLines, files } = await readEvents(eventsPath, {
95787
+ includeRotated: options.includeRotated !== false
95788
+ });
95789
+ if (files.length === 0) {
95790
+ throw new Error(`no events found at ${eventsPath}; run 'dbcli proxy <engine>' first`);
95791
+ }
95792
+ const report = analyzeEvents(events, {
95793
+ slowMs,
95794
+ top,
95795
+ nPlusOne,
95796
+ sourceFiles: files,
95797
+ malformedLines
95798
+ });
95799
+ if (format === "text") {
95800
+ process.stdout.write(renderAnalysisText(report, top) + `
95801
+ `);
95802
+ } else {
95803
+ process.stdout.write(JSON.stringify(report, null, 2) + `
95804
+ `);
95805
+ }
95806
+ } catch (error) {
95807
+ if (error instanceof Error)
95808
+ console.error(error.message);
95809
+ process.exit(1);
95810
+ }
95811
+ }
95812
+ proxyCommand.command("analyze").description("Analyze a proxy event log offline (no DB connection)").option("--events <path>", "Event JSONL path", join32(".dbcli", "proxy", "events.jsonl")).option("--format <format>", "Output format: json | text", "json").option("--top <number>", "Rows shown in text + suggestedCommands depth", "20").option("--slow-ms <number>", "Slow-query threshold (ms) for slowCount", "1000").option("--n-plus-one <number>", "Min repeats per (session,fingerprint) to flag N+1", "10").option("--no-include-rotated", "Do not merge the rotated <events>.1 segment").action(async (options) => {
95813
+ await runAnalyze(options);
95814
+ });
95815
+ addCommonOptions(proxyCommand).action(async (options, command) => {
95816
+ await runProxy(null, options, command);
95817
+ });
95818
+
94159
95819
  // src/cli.ts
94160
95820
  init_config();
94161
- import { join as join31 } from "path";
95821
+ import { join as join33 } from "path";
94162
95822
  var _bgVersionCheckResult;
94163
95823
  function shouldSkipBackgroundChecks() {
94164
95824
  return process.env.DBCLI_NO_UPDATE_CHECK === "1" || process.env.DBCLI_NO_UPDATE_CHECK === "true" || false;
94165
95825
  }
94166
- var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").option("--use <connection>", "Use a specific named connection (v2 config)");
95826
+ var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").option("--use <connection>", "Use a specific named connection (v2 config)").enablePositionalOptions();
94167
95827
  program2.hook("preAction", (thisCommand, actionCommand) => {
94168
95828
  const opts = thisCommand.opts();
94169
95829
  const useConnection = opts.use;
@@ -94187,7 +95847,7 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
94187
95847
  try {
94188
95848
  let cache = null;
94189
95849
  try {
94190
- const cacheFile = Bun.file(join31(configPath, "version-check.json"));
95850
+ const cacheFile = Bun.file(join33(configPath, "version-check.json"));
94191
95851
  if (await cacheFile.exists()) {
94192
95852
  cache = await cacheFile.json();
94193
95853
  }
@@ -94307,6 +95967,9 @@ program2.addCommand(migrateCommand);
94307
95967
  program2.addCommand(useCommand);
94308
95968
  program2.addCommand(queriesCommand);
94309
95969
  program2.addCommand(explainCommand);
95970
+ program2.addCommand(snapshotCommand);
95971
+ program2.addCommand(assertCommand);
95972
+ program2.addCommand(proxyCommand);
94310
95973
  if (!process.argv.slice(2).length) {
94311
95974
  program2.outputHelp();
94312
95975
  }