@carllee1983/dbcli 1.25.0 → 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/CHANGELOG.md +25 -1
- package/README.md +23 -0
- package/README.zh-TW.md +23 -0
- package/assets/SKILL.md +1 -0
- package/assets/SKILL.zh-TW.md +1 -0
- package/assets/reference.md +44 -0
- package/dist/cli.mjs +1221 -6
- package/dist/core.d.ts +2797 -0
- package/dist/core.mjs +9753 -0
- package/package.json +10 -1
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/
|
|
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
|
|
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.
|
|
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",
|
|
@@ -94602,14 +94616,1214 @@ var useCommand = new Command("use").description("Switch or display the default d
|
|
|
94602
94616
|
}
|
|
94603
94617
|
});
|
|
94604
94618
|
|
|
94605
|
-
// src/
|
|
94619
|
+
// src/commands/proxy.ts
|
|
94606
94620
|
init_config();
|
|
94621
|
+
init_validation();
|
|
94607
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
|
+
|
|
95819
|
+
// src/cli.ts
|
|
95820
|
+
init_config();
|
|
95821
|
+
import { join as join33 } from "path";
|
|
94608
95822
|
var _bgVersionCheckResult;
|
|
94609
95823
|
function shouldSkipBackgroundChecks() {
|
|
94610
95824
|
return process.env.DBCLI_NO_UPDATE_CHECK === "1" || process.env.DBCLI_NO_UPDATE_CHECK === "true" || false;
|
|
94611
95825
|
}
|
|
94612
|
-
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();
|
|
94613
95827
|
program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
94614
95828
|
const opts = thisCommand.opts();
|
|
94615
95829
|
const useConnection = opts.use;
|
|
@@ -94633,7 +95847,7 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
94633
95847
|
try {
|
|
94634
95848
|
let cache = null;
|
|
94635
95849
|
try {
|
|
94636
|
-
const cacheFile = Bun.file(
|
|
95850
|
+
const cacheFile = Bun.file(join33(configPath, "version-check.json"));
|
|
94637
95851
|
if (await cacheFile.exists()) {
|
|
94638
95852
|
cache = await cacheFile.json();
|
|
94639
95853
|
}
|
|
@@ -94755,6 +95969,7 @@ program2.addCommand(queriesCommand);
|
|
|
94755
95969
|
program2.addCommand(explainCommand);
|
|
94756
95970
|
program2.addCommand(snapshotCommand);
|
|
94757
95971
|
program2.addCommand(assertCommand);
|
|
95972
|
+
program2.addCommand(proxyCommand);
|
|
94758
95973
|
if (!process.argv.slice(2).length) {
|
|
94759
95974
|
program2.outputHelp();
|
|
94760
95975
|
}
|