@solongate/proxy 0.69.0 → 0.71.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/commands/index.js +19 -9
- package/dist/index.js +246 -91
- package/dist/tui/index.js +246 -91
- package/package.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -84,17 +84,27 @@ async function request(method, path, opts = {}) {
|
|
|
84
84
|
headers["Content-Type"] = "application/json";
|
|
85
85
|
bodyInit = JSON.stringify(opts.body);
|
|
86
86
|
}
|
|
87
|
+
const attempt = () => fetch(url, {
|
|
88
|
+
method,
|
|
89
|
+
headers,
|
|
90
|
+
body: bodyInit,
|
|
91
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
92
|
+
});
|
|
87
93
|
let res;
|
|
88
94
|
try {
|
|
89
|
-
res = await
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
95
|
+
res = await attempt();
|
|
96
|
+
} catch (firstErr) {
|
|
97
|
+
if (method !== "GET") {
|
|
98
|
+
const msg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
99
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
100
|
+
}
|
|
101
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
102
|
+
try {
|
|
103
|
+
res = await attempt();
|
|
104
|
+
} catch (err2) {
|
|
105
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
106
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
107
|
+
}
|
|
98
108
|
}
|
|
99
109
|
const text = await res.text().catch(() => "");
|
|
100
110
|
let json = void 0;
|
package/dist/index.js
CHANGED
|
@@ -6633,17 +6633,27 @@ async function request(method, path, opts = {}) {
|
|
|
6633
6633
|
headers["Content-Type"] = "application/json";
|
|
6634
6634
|
bodyInit = JSON.stringify(opts.body);
|
|
6635
6635
|
}
|
|
6636
|
+
const attempt = () => fetch(url, {
|
|
6637
|
+
method,
|
|
6638
|
+
headers,
|
|
6639
|
+
body: bodyInit,
|
|
6640
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
6641
|
+
});
|
|
6636
6642
|
let res;
|
|
6637
6643
|
try {
|
|
6638
|
-
res = await
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
}
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6644
|
+
res = await attempt();
|
|
6645
|
+
} catch (firstErr) {
|
|
6646
|
+
if (method !== "GET") {
|
|
6647
|
+
const msg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
6648
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
6649
|
+
}
|
|
6650
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
6651
|
+
try {
|
|
6652
|
+
res = await attempt();
|
|
6653
|
+
} catch (err2) {
|
|
6654
|
+
const msg = err2 instanceof Error ? err2.message : String(err2);
|
|
6655
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
6656
|
+
}
|
|
6647
6657
|
}
|
|
6648
6658
|
const text = await res.text().catch(() => "");
|
|
6649
6659
|
let json = void 0;
|
|
@@ -7020,6 +7030,7 @@ var init_hooks = __esm({
|
|
|
7020
7030
|
|
|
7021
7031
|
// src/tui/panels/Live.tsx
|
|
7022
7032
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
7033
|
+
import { spawnSync } from "child_process";
|
|
7023
7034
|
import { closeSync, mkdirSync as mkdirSync3, openSync, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
|
|
7024
7035
|
import { homedir as homedir3 } from "os";
|
|
7025
7036
|
import { dirname, join as join5 } from "path";
|
|
@@ -7040,8 +7051,21 @@ function tailLines(file, maxBytes = 131072) {
|
|
|
7040
7051
|
return [];
|
|
7041
7052
|
}
|
|
7042
7053
|
}
|
|
7043
|
-
function
|
|
7054
|
+
function copyToClipboard(text) {
|
|
7055
|
+
const tools = [
|
|
7056
|
+
["wl-copy", []],
|
|
7057
|
+
["xclip", ["-selection", "clipboard"]],
|
|
7058
|
+
["xsel", ["--clipboard", "--input"]]
|
|
7059
|
+
];
|
|
7060
|
+
for (const [cmd, args] of tools) {
|
|
7061
|
+
try {
|
|
7062
|
+
const r = spawnSync(cmd, args, { input: text, timeout: 2e3 });
|
|
7063
|
+
if (r.status === 0) return cmd;
|
|
7064
|
+
} catch {
|
|
7065
|
+
}
|
|
7066
|
+
}
|
|
7044
7067
|
process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
|
|
7068
|
+
return null;
|
|
7045
7069
|
}
|
|
7046
7070
|
function cloudItem(e) {
|
|
7047
7071
|
return {
|
|
@@ -7107,6 +7131,25 @@ function PaneTitle({ label, extra, width: width2 }) {
|
|
|
7107
7131
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
|
|
7108
7132
|
] });
|
|
7109
7133
|
}
|
|
7134
|
+
function StreamLine({ e, loc }) {
|
|
7135
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7136
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7137
|
+
"[",
|
|
7138
|
+
hhmmss(e.at),
|
|
7139
|
+
" "
|
|
7140
|
+
] }),
|
|
7141
|
+
/* @__PURE__ */ jsx2(Text2, { color: loc ? theme.ok : "#4f6db8", children: loc ? "LOC" : "CLD" }),
|
|
7142
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
|
|
7143
|
+
/* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
7144
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool, 12).padEnd(13) }),
|
|
7145
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
|
|
7146
|
+
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
7147
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
|
|
7148
|
+
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
|
|
7149
|
+
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
|
|
7150
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
|
|
7151
|
+
] });
|
|
7152
|
+
}
|
|
7110
7153
|
function LivePanel({ active: active2 }) {
|
|
7111
7154
|
const [s, setS] = useState2(null);
|
|
7112
7155
|
const [tsData, setTsData] = useState2(null);
|
|
@@ -7119,6 +7162,11 @@ function LivePanel({ active: active2 }) {
|
|
|
7119
7162
|
const [filter, setFilter] = useState2("all");
|
|
7120
7163
|
const [scroll, setScroll] = useState2(0);
|
|
7121
7164
|
const [toast, setToast] = useState2(null);
|
|
7165
|
+
const [mode, setMode] = useState2("stream");
|
|
7166
|
+
const [pickIdx, setPickIdx] = useState2(0);
|
|
7167
|
+
const [detail, setDetail] = useState2(null);
|
|
7168
|
+
const [detailCloud, setDetailCloud] = useState2([]);
|
|
7169
|
+
const [detailScroll, setDetailScroll] = useState2(0);
|
|
7122
7170
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
7123
7171
|
const lastLocalTs = useRef2(0);
|
|
7124
7172
|
const pausedUntil = useRef2(0);
|
|
@@ -7212,7 +7260,8 @@ function LivePanel({ active: active2 }) {
|
|
|
7212
7260
|
if (fresh.length) setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
|
|
7213
7261
|
const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
|
|
7214
7262
|
if (firstLoad) pushLog(`cloud link up \xB7 api ${ms}ms \xB7 ${fresh.length} calls`);
|
|
7215
|
-
else pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" :
|
|
7263
|
+
else if (fresh.length) pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
|
|
7264
|
+
else pushLog(`api ${ms}ms \xB7 idle`, "ok");
|
|
7216
7265
|
} catch (e) {
|
|
7217
7266
|
onApiError(e);
|
|
7218
7267
|
}
|
|
@@ -7259,6 +7308,19 @@ function LivePanel({ active: active2 }) {
|
|
|
7259
7308
|
usePoll(() => {
|
|
7260
7309
|
if (!paused()) guard.reload();
|
|
7261
7310
|
}, 6e4, active2);
|
|
7311
|
+
useEffect2(() => {
|
|
7312
|
+
if (!detail) return;
|
|
7313
|
+
setDetailCloud([]);
|
|
7314
|
+
setDetailScroll(0);
|
|
7315
|
+
let alive = true;
|
|
7316
|
+
api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
|
|
7317
|
+
if (alive) setDetailCloud(r.entries.map(cloudItem));
|
|
7318
|
+
}).catch(() => {
|
|
7319
|
+
});
|
|
7320
|
+
return () => {
|
|
7321
|
+
alive = false;
|
|
7322
|
+
};
|
|
7323
|
+
}, [detail]);
|
|
7262
7324
|
const [tick, setTick] = useState2(0);
|
|
7263
7325
|
useEffect2(() => {
|
|
7264
7326
|
if (!active2) return;
|
|
@@ -7271,14 +7333,19 @@ function LivePanel({ active: active2 }) {
|
|
|
7271
7333
|
const ins = insights.data ?? {};
|
|
7272
7334
|
const spin = SPIN[tick % SPIN.length];
|
|
7273
7335
|
const nowMs = Date.now();
|
|
7274
|
-
const
|
|
7275
|
-
|
|
7276
|
-
|
|
7336
|
+
const localKeys = /* @__PURE__ */ new Set();
|
|
7337
|
+
for (const e of localBuf) {
|
|
7338
|
+
const m = Math.round(e.at / 6e4);
|
|
7339
|
+
for (const d of [-1, 0, 1]) localKeys.add(`${e.tool}|${e.decision}|${m + d}`);
|
|
7340
|
+
}
|
|
7341
|
+
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
7342
|
+
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
7277
7343
|
const points = tsData?.timeseries ?? [];
|
|
7344
|
+
const nowMin = Math.floor(nowMs / 6e4);
|
|
7278
7345
|
const minuteCounts = new Array(60).fill(0);
|
|
7279
7346
|
const minuteHot = new Array(60).fill(false);
|
|
7280
7347
|
for (const e of mergedAll) {
|
|
7281
|
-
const idx = 59 - Math.floor(
|
|
7348
|
+
const idx = 59 - (nowMin - Math.floor(e.at / 6e4));
|
|
7282
7349
|
if (idx >= 0 && idx < 60) {
|
|
7283
7350
|
minuteCounts[idx]++;
|
|
7284
7351
|
if (e.decision !== "ALLOW") minuteHot[idx] = true;
|
|
@@ -7294,8 +7361,8 @@ function LivePanel({ active: active2 }) {
|
|
|
7294
7361
|
const minuteNow = minuteCounts[59] ?? 0;
|
|
7295
7362
|
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
7296
7363
|
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
7297
|
-
const
|
|
7298
|
-
const lastDeny =
|
|
7364
|
+
const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
|
|
7365
|
+
const lastDeny = denialsAll[denialsAll.length - 1];
|
|
7299
7366
|
const toolCounts = /* @__PURE__ */ new Map();
|
|
7300
7367
|
for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
|
|
7301
7368
|
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
@@ -7316,7 +7383,24 @@ function LivePanel({ active: active2 }) {
|
|
|
7316
7383
|
if (e.agent) cur.agent = e.agent;
|
|
7317
7384
|
localSess.set(e.session, cur);
|
|
7318
7385
|
}
|
|
7319
|
-
const
|
|
7386
|
+
const combinedSess = [
|
|
7387
|
+
...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, lastAt: v.lastAt, isMe: ring?.session === id })),
|
|
7388
|
+
...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
|
|
7389
|
+
source: "cloud",
|
|
7390
|
+
id: a.session_id,
|
|
7391
|
+
agent: a.agent_name ?? a.session_id,
|
|
7392
|
+
calls: a.total_calls,
|
|
7393
|
+
denies: a.denied_calls,
|
|
7394
|
+
lastAt: Date.parse(a.last_seen_at),
|
|
7395
|
+
trust: a.trust_score,
|
|
7396
|
+
isMe: false
|
|
7397
|
+
}))
|
|
7398
|
+
];
|
|
7399
|
+
const localSessIds = new Set(localSess.keys());
|
|
7400
|
+
if (ring?.session) localSessIds.add(ring.session);
|
|
7401
|
+
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
7402
|
+
const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
|
|
7403
|
+
const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
|
|
7320
7404
|
const chartH = rows >= 36 ? 6 : 4;
|
|
7321
7405
|
const colH = rows >= 32 ? 7 : 5;
|
|
7322
7406
|
const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
|
|
@@ -7324,21 +7408,58 @@ function LivePanel({ active: active2 }) {
|
|
|
7324
7408
|
const leftW = Math.floor(innerW * 0.55);
|
|
7325
7409
|
const rightW = innerW - leftW - 2;
|
|
7326
7410
|
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
7327
|
-
const
|
|
7411
|
+
const pickable = combinedSess.slice(0, colH - 1);
|
|
7412
|
+
const maxScroll = Math.max(0, visibleDesc.length - streamRows);
|
|
7328
7413
|
const clampedScroll = Math.min(scroll, maxScroll);
|
|
7329
|
-
const
|
|
7330
|
-
const
|
|
7414
|
+
const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
|
|
7415
|
+
const detailEntries = detail ? (() => {
|
|
7416
|
+
const seen = /* @__PURE__ */ new Set();
|
|
7417
|
+
const all = [];
|
|
7418
|
+
for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
|
|
7419
|
+
const key = `${e.at}:${e.tool}:${e.decision}`;
|
|
7420
|
+
if (seen.has(key)) continue;
|
|
7421
|
+
seen.add(key);
|
|
7422
|
+
all.push(e);
|
|
7423
|
+
}
|
|
7424
|
+
return all.sort((a, b) => b.at - a.at);
|
|
7425
|
+
})() : [];
|
|
7331
7426
|
useInput(
|
|
7332
7427
|
(input, key) => {
|
|
7428
|
+
if (mode === "detail") {
|
|
7429
|
+
const maxD = Math.max(0, detailEntries.length - (rows - 10));
|
|
7430
|
+
if (key.leftArrow) {
|
|
7431
|
+
setMode("stream");
|
|
7432
|
+
setDetail(null);
|
|
7433
|
+
} else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
7434
|
+
else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
|
|
7435
|
+
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
7436
|
+
else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
|
|
7437
|
+
return;
|
|
7438
|
+
}
|
|
7439
|
+
if (mode === "pick") {
|
|
7440
|
+
if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
|
|
7441
|
+
else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
|
|
7442
|
+
else if (key.return) {
|
|
7443
|
+
const row = pickable[pickIdx];
|
|
7444
|
+
if (row) {
|
|
7445
|
+
setDetail(row);
|
|
7446
|
+
setMode("detail");
|
|
7447
|
+
}
|
|
7448
|
+
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
7449
|
+
return;
|
|
7450
|
+
}
|
|
7333
7451
|
if (input === "f") {
|
|
7334
7452
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
7335
7453
|
setScroll(0);
|
|
7336
|
-
} else if (
|
|
7337
|
-
|
|
7338
|
-
|
|
7339
|
-
else if (key.
|
|
7454
|
+
} else if (input === "s") {
|
|
7455
|
+
setPickIdx(0);
|
|
7456
|
+
setMode("pick");
|
|
7457
|
+
} else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
|
|
7458
|
+
else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
|
|
7459
|
+
else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
|
|
7460
|
+
else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
|
|
7340
7461
|
else if (input === "c") {
|
|
7341
|
-
const dump =
|
|
7462
|
+
const dump = visibleDesc.map(
|
|
7342
7463
|
(e) => `[${new Date(e.at).toISOString()}] ${e.source.toUpperCase().padEnd(5)} ${e.decision.padEnd(6)} ${e.tool.padEnd(16)} ${e.permission.padEnd(5)} ${e.evalMs != null ? String(e.evalMs).padStart(5) + "ms " : " "}${(e.agent ?? "-").padEnd(14)} ${e.session ? e.session.slice(0, 8) : "- "} ${e.detail}`
|
|
7343
7464
|
).join("\n");
|
|
7344
7465
|
try {
|
|
@@ -7346,26 +7467,96 @@ function LivePanel({ active: active2 }) {
|
|
|
7346
7467
|
writeFileSync3(DUMP, dump + "\n");
|
|
7347
7468
|
} catch {
|
|
7348
7469
|
}
|
|
7349
|
-
|
|
7350
|
-
setToast(
|
|
7470
|
+
const via = copyToClipboard(dump);
|
|
7471
|
+
setToast(
|
|
7472
|
+
via ? { msg: `\u2713 ${visibleDesc.length} lines in clipboard (${via})`, until: Date.now() + 4e3 } : { msg: "\u2717 no clipboard tool \u2014 install wl-clipboard or xclip", until: Date.now() + 6e3 }
|
|
7473
|
+
);
|
|
7351
7474
|
}
|
|
7352
7475
|
},
|
|
7353
7476
|
{ isActive: active2 }
|
|
7354
7477
|
);
|
|
7355
7478
|
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
7479
|
+
void sessionDot;
|
|
7356
7480
|
if (s === null && localOn === null) {
|
|
7357
7481
|
return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7358
7482
|
spin,
|
|
7359
7483
|
" connecting\u2026"
|
|
7360
7484
|
] });
|
|
7361
7485
|
}
|
|
7486
|
+
const titleBar = /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7487
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
|
|
7488
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
7489
|
+
backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " }),
|
|
7490
|
+
toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
|
|
7491
|
+
] });
|
|
7492
|
+
if (mode === "detail" && detail) {
|
|
7493
|
+
const d = detailEntries;
|
|
7494
|
+
const allow = d.filter((e) => e.decision === "ALLOW").length;
|
|
7495
|
+
const deny = d.length - allow;
|
|
7496
|
+
const dlpN = d.filter((e) => e.dlp).length;
|
|
7497
|
+
const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
|
|
7498
|
+
const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
|
|
7499
|
+
const tCounts = /* @__PURE__ */ new Map();
|
|
7500
|
+
const pCounts = /* @__PURE__ */ new Map();
|
|
7501
|
+
for (const e of d) {
|
|
7502
|
+
tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
|
|
7503
|
+
if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
|
|
7504
|
+
}
|
|
7505
|
+
const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
7506
|
+
const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
7507
|
+
const first = d[d.length - 1]?.at;
|
|
7508
|
+
const last = d[0]?.at;
|
|
7509
|
+
const tlRows = Math.max(4, rows - 10);
|
|
7510
|
+
const maxD = Math.max(0, d.length - tlRows);
|
|
7511
|
+
const dScroll = Math.min(detailScroll, maxD);
|
|
7512
|
+
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
7513
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
7514
|
+
titleBar,
|
|
7515
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7516
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
7517
|
+
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
7518
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
7519
|
+
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
7520
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
7521
|
+
] }),
|
|
7522
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7523
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
7524
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
|
|
7525
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
|
|
7526
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
|
|
7527
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
7528
|
+
/* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
|
|
7529
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
7530
|
+
/* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
|
|
7531
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
|
|
7532
|
+
/* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
|
|
7533
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
|
|
7534
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
|
|
7535
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
|
|
7536
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
|
|
7537
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7538
|
+
] }),
|
|
7539
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7540
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
|
|
7541
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
7542
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
|
|
7543
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
7544
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7545
|
+
] }),
|
|
7546
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
|
|
7547
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
7548
|
+
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
7549
|
+
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
7550
|
+
] }),
|
|
7551
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7552
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
|
|
7553
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
|
|
7554
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
|
|
7555
|
+
] })
|
|
7556
|
+
] });
|
|
7557
|
+
}
|
|
7362
7558
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
7363
|
-
|
|
7364
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
|
|
7365
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
7366
|
-
backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " }),
|
|
7367
|
-
toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
|
|
7368
|
-
] }),
|
|
7559
|
+
titleBar,
|
|
7369
7560
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7370
7561
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
7371
7562
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
|
|
@@ -7382,7 +7573,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7382
7573
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
|
|
7383
7574
|
/* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
|
|
7384
7575
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
|
|
7385
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${
|
|
7576
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
|
|
7386
7577
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
|
|
7387
7578
|
/* @__PURE__ */ jsx2(Text2, { color: guard.data?.up_to_date ? theme.ok : theme.warn, children: guard.data ? `v${guard.data.installed ?? "?"}${guard.data.up_to_date ? "" : "\u2192v" + guard.data.latest} ${guard.data.device_count}dev` : "\xB7" }),
|
|
7388
7579
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
@@ -7411,7 +7602,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7411
7602
|
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
7412
7603
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
|
|
7413
7604
|
] }),
|
|
7414
|
-
dlpBars.length ? dlpBars.map((
|
|
7605
|
+
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " no dlp hits in last 7 days" }),
|
|
7415
7606
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7416
7607
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
|
|
7417
7608
|
ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
|
|
@@ -7421,48 +7612,28 @@ function LivePanel({ active: active2 }) {
|
|
|
7421
7612
|
] })
|
|
7422
7613
|
] }),
|
|
7423
7614
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
7424
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${
|
|
7425
|
-
|
|
7426
|
-
|
|
7427
|
-
const freshDot = nowMs -
|
|
7428
|
-
const
|
|
7429
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7430
|
-
/* @__PURE__ */ jsxs2(Text2, { color: freshDot ? theme.ok : theme.dim, children: [
|
|
7431
|
-
freshDot ? "\u25CF" : "\u25CB",
|
|
7432
|
-
" "
|
|
7433
|
-
] }),
|
|
7434
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "LOC " }),
|
|
7435
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: isMe, children: truncate2(isMe ? "this machine" : v.agent, 12).padEnd(13) }),
|
|
7436
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
7437
|
-
String(v.calls).padStart(4),
|
|
7438
|
-
"c "
|
|
7439
|
-
] }),
|
|
7440
|
-
/* @__PURE__ */ jsxs2(Text2, { color: v.denies ? theme.bad : theme.dim, children: [
|
|
7441
|
-
String(v.denies).padStart(3),
|
|
7442
|
-
"d "
|
|
7443
|
-
] }),
|
|
7444
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(v.lastAt) })
|
|
7445
|
-
] }, id);
|
|
7446
|
-
}),
|
|
7447
|
-
sess.slice(0, Math.max(0, colH - 1 - Math.min(localSessList.length, Math.max(1, colH - 2)))).map((a) => {
|
|
7448
|
-
const dot = sessionDot(a.status);
|
|
7449
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7450
|
-
/* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
|
|
7451
|
-
dot.ch,
|
|
7615
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
7616
|
+
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
7617
|
+
pickable.map((r, i) => {
|
|
7618
|
+
const freshDot = nowMs - r.lastAt < 9e4;
|
|
7619
|
+
const sel = mode === "pick" && i === pickIdx;
|
|
7620
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
7621
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
|
|
7622
|
+
sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
|
|
7452
7623
|
" "
|
|
7453
7624
|
] }),
|
|
7454
|
-
/* @__PURE__ */ jsx2(Text2, { color: "#4f6db8", children: "CLD " }),
|
|
7455
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.
|
|
7625
|
+
/* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
|
|
7626
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate2(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
|
|
7456
7627
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
7457
|
-
String(
|
|
7628
|
+
String(r.calls).padStart(4),
|
|
7458
7629
|
"c "
|
|
7459
7630
|
] }),
|
|
7460
|
-
/* @__PURE__ */ jsxs2(Text2, { color:
|
|
7461
|
-
String(
|
|
7631
|
+
/* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
|
|
7632
|
+
String(r.denies).padStart(3),
|
|
7462
7633
|
"d "
|
|
7463
7634
|
] }),
|
|
7464
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `t${
|
|
7465
|
-
] },
|
|
7635
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
|
|
7636
|
+
] }, r.id);
|
|
7466
7637
|
})
|
|
7467
7638
|
] }),
|
|
7468
7639
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
|
|
@@ -7481,7 +7652,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7481
7652
|
PaneTitle,
|
|
7482
7653
|
{
|
|
7483
7654
|
label: "TOOL STREAM",
|
|
7484
|
-
extra: `last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \
|
|
7655
|
+
extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 c copy \xB7 s sessions`,
|
|
7485
7656
|
width: innerW
|
|
7486
7657
|
}
|
|
7487
7658
|
),
|
|
@@ -7494,28 +7665,12 @@ function LivePanel({ active: active2 }) {
|
|
|
7494
7665
|
spin,
|
|
7495
7666
|
" awaiting traffic\u2026"
|
|
7496
7667
|
] }) : null,
|
|
7497
|
-
windowed.map((e) => /* @__PURE__ */
|
|
7498
|
-
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7499
|
-
"[",
|
|
7500
|
-
hhmmss(e.at),
|
|
7501
|
-
" "
|
|
7502
|
-
] }),
|
|
7503
|
-
/* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
|
|
7504
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
|
|
7505
|
-
/* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
7506
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool, 12).padEnd(13) }),
|
|
7507
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
|
|
7508
|
-
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
7509
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
|
|
7510
|
-
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
|
|
7511
|
-
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
|
|
7512
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
|
|
7513
|
-
] }, e.id))
|
|
7668
|
+
windowed.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
7514
7669
|
] }),
|
|
7515
7670
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7516
7671
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
7517
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld
|
|
7518
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7
|
|
7672
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
|
|
7673
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7 s sessions \xB7 c copy \xB7 esc menu \xB7 q quit " })
|
|
7519
7674
|
] })
|
|
7520
7675
|
] });
|
|
7521
7676
|
}
|
package/dist/tui/index.js
CHANGED
|
@@ -126,6 +126,7 @@ function KeyHints({ hints }) {
|
|
|
126
126
|
|
|
127
127
|
// src/tui/panels/Live.tsx
|
|
128
128
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
129
|
+
import { spawnSync } from "child_process";
|
|
129
130
|
import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
|
|
130
131
|
import { homedir as homedir2 } from "os";
|
|
131
132
|
import { dirname, join as join2 } from "path";
|
|
@@ -219,17 +220,27 @@ async function request(method, path, opts = {}) {
|
|
|
219
220
|
headers["Content-Type"] = "application/json";
|
|
220
221
|
bodyInit = JSON.stringify(opts.body);
|
|
221
222
|
}
|
|
223
|
+
const attempt = () => fetch(url, {
|
|
224
|
+
method,
|
|
225
|
+
headers,
|
|
226
|
+
body: bodyInit,
|
|
227
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
|
|
228
|
+
});
|
|
222
229
|
let res;
|
|
223
230
|
try {
|
|
224
|
-
res = await
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
231
|
+
res = await attempt();
|
|
232
|
+
} catch (firstErr) {
|
|
233
|
+
if (method !== "GET") {
|
|
234
|
+
const msg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
235
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
236
|
+
}
|
|
237
|
+
await new Promise((r) => setTimeout(r, 800));
|
|
238
|
+
try {
|
|
239
|
+
res = await attempt();
|
|
240
|
+
} catch (err) {
|
|
241
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
242
|
+
throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
|
|
243
|
+
}
|
|
233
244
|
}
|
|
234
245
|
const text = await res.text().catch(() => "");
|
|
235
246
|
let json = void 0;
|
|
@@ -465,8 +476,21 @@ function tailLines(file, maxBytes = 131072) {
|
|
|
465
476
|
return [];
|
|
466
477
|
}
|
|
467
478
|
}
|
|
468
|
-
function
|
|
479
|
+
function copyToClipboard(text) {
|
|
480
|
+
const tools = [
|
|
481
|
+
["wl-copy", []],
|
|
482
|
+
["xclip", ["-selection", "clipboard"]],
|
|
483
|
+
["xsel", ["--clipboard", "--input"]]
|
|
484
|
+
];
|
|
485
|
+
for (const [cmd, args] of tools) {
|
|
486
|
+
try {
|
|
487
|
+
const r = spawnSync(cmd, args, { input: text, timeout: 2e3 });
|
|
488
|
+
if (r.status === 0) return cmd;
|
|
489
|
+
} catch {
|
|
490
|
+
}
|
|
491
|
+
}
|
|
469
492
|
process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
|
|
493
|
+
return null;
|
|
470
494
|
}
|
|
471
495
|
function cloudItem(e) {
|
|
472
496
|
return {
|
|
@@ -532,6 +556,25 @@ function PaneTitle({ label, extra, width }) {
|
|
|
532
556
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
|
|
533
557
|
] });
|
|
534
558
|
}
|
|
559
|
+
function StreamLine({ e, loc }) {
|
|
560
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
561
|
+
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
562
|
+
"[",
|
|
563
|
+
hhmmss(e.at),
|
|
564
|
+
" "
|
|
565
|
+
] }),
|
|
566
|
+
/* @__PURE__ */ jsx2(Text2, { color: loc ? theme.ok : "#4f6db8", children: loc ? "LOC" : "CLD" }),
|
|
567
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
|
|
568
|
+
/* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
569
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
|
|
570
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
|
|
571
|
+
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
572
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
|
|
573
|
+
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
|
|
574
|
+
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
|
|
575
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
|
|
576
|
+
] });
|
|
577
|
+
}
|
|
535
578
|
var FILTERS = ["all", "local", "cloud"];
|
|
536
579
|
function LivePanel({ active: active2 }) {
|
|
537
580
|
const [s, setS] = useState2(null);
|
|
@@ -545,6 +588,11 @@ function LivePanel({ active: active2 }) {
|
|
|
545
588
|
const [filter, setFilter] = useState2("all");
|
|
546
589
|
const [scroll, setScroll] = useState2(0);
|
|
547
590
|
const [toast, setToast] = useState2(null);
|
|
591
|
+
const [mode, setMode] = useState2("stream");
|
|
592
|
+
const [pickIdx, setPickIdx] = useState2(0);
|
|
593
|
+
const [detail, setDetail] = useState2(null);
|
|
594
|
+
const [detailCloud, setDetailCloud] = useState2([]);
|
|
595
|
+
const [detailScroll, setDetailScroll] = useState2(0);
|
|
548
596
|
const seenRef = useRef2(/* @__PURE__ */ new Set());
|
|
549
597
|
const lastLocalTs = useRef2(0);
|
|
550
598
|
const pausedUntil = useRef2(0);
|
|
@@ -638,7 +686,8 @@ function LivePanel({ active: active2 }) {
|
|
|
638
686
|
if (fresh.length) setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
|
|
639
687
|
const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
|
|
640
688
|
if (firstLoad) pushLog(`cloud link up \xB7 api ${ms}ms \xB7 ${fresh.length} calls`);
|
|
641
|
-
else pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" :
|
|
689
|
+
else if (fresh.length) pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
|
|
690
|
+
else pushLog(`api ${ms}ms \xB7 idle`, "ok");
|
|
642
691
|
} catch (e) {
|
|
643
692
|
onApiError(e);
|
|
644
693
|
}
|
|
@@ -685,6 +734,19 @@ function LivePanel({ active: active2 }) {
|
|
|
685
734
|
usePoll(() => {
|
|
686
735
|
if (!paused()) guard.reload();
|
|
687
736
|
}, 6e4, active2);
|
|
737
|
+
useEffect2(() => {
|
|
738
|
+
if (!detail) return;
|
|
739
|
+
setDetailCloud([]);
|
|
740
|
+
setDetailScroll(0);
|
|
741
|
+
let alive = true;
|
|
742
|
+
api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
|
|
743
|
+
if (alive) setDetailCloud(r.entries.map(cloudItem));
|
|
744
|
+
}).catch(() => {
|
|
745
|
+
});
|
|
746
|
+
return () => {
|
|
747
|
+
alive = false;
|
|
748
|
+
};
|
|
749
|
+
}, [detail]);
|
|
688
750
|
const [tick, setTick] = useState2(0);
|
|
689
751
|
useEffect2(() => {
|
|
690
752
|
if (!active2) return;
|
|
@@ -697,14 +759,19 @@ function LivePanel({ active: active2 }) {
|
|
|
697
759
|
const ins = insights.data ?? {};
|
|
698
760
|
const spin = SPIN[tick % SPIN.length];
|
|
699
761
|
const nowMs = Date.now();
|
|
700
|
-
const
|
|
701
|
-
|
|
702
|
-
|
|
762
|
+
const localKeys = /* @__PURE__ */ new Set();
|
|
763
|
+
for (const e of localBuf) {
|
|
764
|
+
const m = Math.round(e.at / 6e4);
|
|
765
|
+
for (const d of [-1, 0, 1]) localKeys.add(`${e.tool}|${e.decision}|${m + d}`);
|
|
766
|
+
}
|
|
767
|
+
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
768
|
+
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
703
769
|
const points = tsData?.timeseries ?? [];
|
|
770
|
+
const nowMin = Math.floor(nowMs / 6e4);
|
|
704
771
|
const minuteCounts = new Array(60).fill(0);
|
|
705
772
|
const minuteHot = new Array(60).fill(false);
|
|
706
773
|
for (const e of mergedAll) {
|
|
707
|
-
const idx = 59 - Math.floor(
|
|
774
|
+
const idx = 59 - (nowMin - Math.floor(e.at / 6e4));
|
|
708
775
|
if (idx >= 0 && idx < 60) {
|
|
709
776
|
minuteCounts[idx]++;
|
|
710
777
|
if (e.decision !== "ALLOW") minuteHot[idx] = true;
|
|
@@ -720,8 +787,8 @@ function LivePanel({ active: active2 }) {
|
|
|
720
787
|
const minuteNow = minuteCounts[59] ?? 0;
|
|
721
788
|
const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
|
|
722
789
|
const maxDlpBar = dlpBars[0]?.count ?? 1;
|
|
723
|
-
const
|
|
724
|
-
const lastDeny =
|
|
790
|
+
const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
|
|
791
|
+
const lastDeny = denialsAll[denialsAll.length - 1];
|
|
725
792
|
const toolCounts = /* @__PURE__ */ new Map();
|
|
726
793
|
for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
|
|
727
794
|
const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
@@ -742,7 +809,24 @@ function LivePanel({ active: active2 }) {
|
|
|
742
809
|
if (e.agent) cur.agent = e.agent;
|
|
743
810
|
localSess.set(e.session, cur);
|
|
744
811
|
}
|
|
745
|
-
const
|
|
812
|
+
const combinedSess = [
|
|
813
|
+
...[...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt).map(([id, v]) => ({ source: "local", id, agent: v.agent, calls: v.calls, denies: v.denies, lastAt: v.lastAt, isMe: ring?.session === id })),
|
|
814
|
+
...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
|
|
815
|
+
source: "cloud",
|
|
816
|
+
id: a.session_id,
|
|
817
|
+
agent: a.agent_name ?? a.session_id,
|
|
818
|
+
calls: a.total_calls,
|
|
819
|
+
denies: a.denied_calls,
|
|
820
|
+
lastAt: Date.parse(a.last_seen_at),
|
|
821
|
+
trust: a.trust_score,
|
|
822
|
+
isMe: false
|
|
823
|
+
}))
|
|
824
|
+
];
|
|
825
|
+
const localSessIds = new Set(localSess.keys());
|
|
826
|
+
if (ring?.session) localSessIds.add(ring.session);
|
|
827
|
+
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
828
|
+
const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e));
|
|
829
|
+
const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
|
|
746
830
|
const chartH = rows >= 36 ? 6 : 4;
|
|
747
831
|
const colH = rows >= 32 ? 7 : 5;
|
|
748
832
|
const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
|
|
@@ -750,21 +834,58 @@ function LivePanel({ active: active2 }) {
|
|
|
750
834
|
const leftW = Math.floor(innerW * 0.55);
|
|
751
835
|
const rightW = innerW - leftW - 2;
|
|
752
836
|
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
753
|
-
const
|
|
837
|
+
const pickable = combinedSess.slice(0, colH - 1);
|
|
838
|
+
const maxScroll = Math.max(0, visibleDesc.length - streamRows);
|
|
754
839
|
const clampedScroll = Math.min(scroll, maxScroll);
|
|
755
|
-
const
|
|
756
|
-
const
|
|
840
|
+
const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
|
|
841
|
+
const detailEntries = detail ? (() => {
|
|
842
|
+
const seen = /* @__PURE__ */ new Set();
|
|
843
|
+
const all = [];
|
|
844
|
+
for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
|
|
845
|
+
const key = `${e.at}:${e.tool}:${e.decision}`;
|
|
846
|
+
if (seen.has(key)) continue;
|
|
847
|
+
seen.add(key);
|
|
848
|
+
all.push(e);
|
|
849
|
+
}
|
|
850
|
+
return all.sort((a, b) => b.at - a.at);
|
|
851
|
+
})() : [];
|
|
757
852
|
useInput(
|
|
758
853
|
(input, key) => {
|
|
854
|
+
if (mode === "detail") {
|
|
855
|
+
const maxD = Math.max(0, detailEntries.length - (rows - 10));
|
|
856
|
+
if (key.leftArrow) {
|
|
857
|
+
setMode("stream");
|
|
858
|
+
setDetail(null);
|
|
859
|
+
} else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
|
|
860
|
+
else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
|
|
861
|
+
else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
|
|
862
|
+
else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (mode === "pick") {
|
|
866
|
+
if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
|
|
867
|
+
else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
|
|
868
|
+
else if (key.return) {
|
|
869
|
+
const row = pickable[pickIdx];
|
|
870
|
+
if (row) {
|
|
871
|
+
setDetail(row);
|
|
872
|
+
setMode("detail");
|
|
873
|
+
}
|
|
874
|
+
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
759
877
|
if (input === "f") {
|
|
760
878
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
761
879
|
setScroll(0);
|
|
762
|
-
} else if (
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
else if (key.
|
|
880
|
+
} else if (input === "s") {
|
|
881
|
+
setPickIdx(0);
|
|
882
|
+
setMode("pick");
|
|
883
|
+
} else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
|
|
884
|
+
else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
|
|
885
|
+
else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
|
|
886
|
+
else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
|
|
766
887
|
else if (input === "c") {
|
|
767
|
-
const dump =
|
|
888
|
+
const dump = visibleDesc.map(
|
|
768
889
|
(e) => `[${new Date(e.at).toISOString()}] ${e.source.toUpperCase().padEnd(5)} ${e.decision.padEnd(6)} ${e.tool.padEnd(16)} ${e.permission.padEnd(5)} ${e.evalMs != null ? String(e.evalMs).padStart(5) + "ms " : " "}${(e.agent ?? "-").padEnd(14)} ${e.session ? e.session.slice(0, 8) : "- "} ${e.detail}`
|
|
769
890
|
).join("\n");
|
|
770
891
|
try {
|
|
@@ -772,26 +893,96 @@ function LivePanel({ active: active2 }) {
|
|
|
772
893
|
writeFileSync(DUMP, dump + "\n");
|
|
773
894
|
} catch {
|
|
774
895
|
}
|
|
775
|
-
|
|
776
|
-
setToast(
|
|
896
|
+
const via = copyToClipboard(dump);
|
|
897
|
+
setToast(
|
|
898
|
+
via ? { msg: `\u2713 ${visibleDesc.length} lines in clipboard (${via})`, until: Date.now() + 4e3 } : { msg: "\u2717 no clipboard tool \u2014 install wl-clipboard or xclip", until: Date.now() + 6e3 }
|
|
899
|
+
);
|
|
777
900
|
}
|
|
778
901
|
},
|
|
779
902
|
{ isActive: active2 }
|
|
780
903
|
);
|
|
781
904
|
const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
|
|
905
|
+
void sessionDot;
|
|
782
906
|
if (s === null && localOn === null) {
|
|
783
907
|
return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
784
908
|
spin,
|
|
785
909
|
" connecting\u2026"
|
|
786
910
|
] });
|
|
787
911
|
}
|
|
912
|
+
const titleBar = /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
913
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
|
|
914
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
915
|
+
backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " }),
|
|
916
|
+
toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
|
|
917
|
+
] });
|
|
918
|
+
if (mode === "detail" && detail) {
|
|
919
|
+
const d = detailEntries;
|
|
920
|
+
const allow = d.filter((e) => e.decision === "ALLOW").length;
|
|
921
|
+
const deny = d.length - allow;
|
|
922
|
+
const dlpN = d.filter((e) => e.dlp).length;
|
|
923
|
+
const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
|
|
924
|
+
const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
|
|
925
|
+
const tCounts = /* @__PURE__ */ new Map();
|
|
926
|
+
const pCounts = /* @__PURE__ */ new Map();
|
|
927
|
+
for (const e of d) {
|
|
928
|
+
tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
|
|
929
|
+
if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
|
|
930
|
+
}
|
|
931
|
+
const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
|
|
932
|
+
const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
933
|
+
const first = d[d.length - 1]?.at;
|
|
934
|
+
const last = d[0]?.at;
|
|
935
|
+
const tlRows = Math.max(4, rows - 10);
|
|
936
|
+
const maxD = Math.max(0, d.length - tlRows);
|
|
937
|
+
const dScroll = Math.min(detailScroll, maxD);
|
|
938
|
+
const tl = d.slice(dScroll, dScroll + tlRows);
|
|
939
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
940
|
+
titleBar,
|
|
941
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
942
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
|
|
943
|
+
/* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
|
|
944
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
|
|
945
|
+
detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
|
|
946
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
|
|
947
|
+
] }),
|
|
948
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
949
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
950
|
+
/* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
|
|
951
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
|
|
952
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
|
|
953
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
|
|
954
|
+
/* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
|
|
955
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
|
|
956
|
+
/* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
|
|
957
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
|
|
958
|
+
/* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
|
|
959
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
|
|
960
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
|
|
961
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
|
|
962
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
|
|
963
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
964
|
+
] }),
|
|
965
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
966
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
|
|
967
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
968
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
|
|
969
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
|
|
970
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
971
|
+
] }),
|
|
972
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 \u2190 back`, width: innerW }),
|
|
973
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
|
|
974
|
+
tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
|
|
975
|
+
tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
976
|
+
] }),
|
|
977
|
+
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
978
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
|
|
979
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
|
|
980
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
|
|
981
|
+
] })
|
|
982
|
+
] });
|
|
983
|
+
}
|
|
788
984
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
789
|
-
|
|
790
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
|
|
791
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
|
|
792
|
-
backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " }),
|
|
793
|
-
toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
|
|
794
|
-
] }),
|
|
985
|
+
titleBar,
|
|
795
986
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
796
987
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
|
|
797
988
|
/* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
|
|
@@ -808,7 +999,7 @@ function LivePanel({ active: active2 }) {
|
|
|
808
999
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
|
|
809
1000
|
/* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
|
|
810
1001
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
|
|
811
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${
|
|
1002
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
|
|
812
1003
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
|
|
813
1004
|
/* @__PURE__ */ jsx2(Text2, { color: guard.data?.up_to_date ? theme.ok : theme.warn, children: guard.data ? `v${guard.data.installed ?? "?"}${guard.data.up_to_date ? "" : "\u2192v" + guard.data.latest} ${guard.data.device_count}dev` : "\xB7" }),
|
|
814
1005
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
@@ -837,7 +1028,7 @@ function LivePanel({ active: active2 }) {
|
|
|
837
1028
|
/* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
|
|
838
1029
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
|
|
839
1030
|
] }),
|
|
840
|
-
dlpBars.length ? dlpBars.map((
|
|
1031
|
+
dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " no dlp hits in last 7 days" }),
|
|
841
1032
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
842
1033
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
|
|
843
1034
|
ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
|
|
@@ -847,48 +1038,28 @@ function LivePanel({ active: active2 }) {
|
|
|
847
1038
|
] })
|
|
848
1039
|
] }),
|
|
849
1040
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
|
|
850
|
-
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
const freshDot = nowMs -
|
|
854
|
-
const
|
|
855
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
856
|
-
/* @__PURE__ */ jsxs2(Text2, { color: freshDot ? theme.ok : theme.dim, children: [
|
|
857
|
-
freshDot ? "\u25CF" : "\u25CB",
|
|
1041
|
+
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
1042
|
+
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
1043
|
+
pickable.map((r, i) => {
|
|
1044
|
+
const freshDot = nowMs - r.lastAt < 9e4;
|
|
1045
|
+
const sel = mode === "pick" && i === pickIdx;
|
|
1046
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
|
|
1047
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
|
|
1048
|
+
sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
|
|
858
1049
|
" "
|
|
859
1050
|
] }),
|
|
860
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "LOC " }),
|
|
861
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright,
|
|
1051
|
+
/* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
|
|
1052
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
|
|
862
1053
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
863
|
-
String(
|
|
1054
|
+
String(r.calls).padStart(4),
|
|
864
1055
|
"c "
|
|
865
1056
|
] }),
|
|
866
|
-
/* @__PURE__ */ jsxs2(Text2, { color:
|
|
867
|
-
String(
|
|
1057
|
+
/* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
|
|
1058
|
+
String(r.denies).padStart(3),
|
|
868
1059
|
"d "
|
|
869
1060
|
] }),
|
|
870
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(
|
|
871
|
-
] }, id);
|
|
872
|
-
}),
|
|
873
|
-
sess.slice(0, Math.max(0, colH - 1 - Math.min(localSessList.length, Math.max(1, colH - 2)))).map((a) => {
|
|
874
|
-
const dot = sessionDot(a.status);
|
|
875
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
876
|
-
/* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
|
|
877
|
-
dot.ch,
|
|
878
|
-
" "
|
|
879
|
-
] }),
|
|
880
|
-
/* @__PURE__ */ jsx2(Text2, { color: "#4f6db8", children: "CLD " }),
|
|
881
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(a.agent_name ?? a.session_id, 12).padEnd(13) }),
|
|
882
|
-
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
883
|
-
String(a.total_calls).padStart(4),
|
|
884
|
-
"c "
|
|
885
|
-
] }),
|
|
886
|
-
/* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
|
|
887
|
-
String(a.denied_calls).padStart(3),
|
|
888
|
-
"d "
|
|
889
|
-
] }),
|
|
890
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `t${a.trust_score} ` + ago(a.last_seen_at) })
|
|
891
|
-
] }, a.session_id);
|
|
1061
|
+
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
|
|
1062
|
+
] }, r.id);
|
|
892
1063
|
})
|
|
893
1064
|
] }),
|
|
894
1065
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
|
|
@@ -907,7 +1078,7 @@ function LivePanel({ active: active2 }) {
|
|
|
907
1078
|
PaneTitle,
|
|
908
1079
|
{
|
|
909
1080
|
label: "TOOL STREAM",
|
|
910
|
-
extra: `last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \
|
|
1081
|
+
extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 c copy \xB7 s sessions`,
|
|
911
1082
|
width: innerW
|
|
912
1083
|
}
|
|
913
1084
|
),
|
|
@@ -920,28 +1091,12 @@ function LivePanel({ active: active2 }) {
|
|
|
920
1091
|
spin,
|
|
921
1092
|
" awaiting traffic\u2026"
|
|
922
1093
|
] }) : null,
|
|
923
|
-
windowed.map((e) => /* @__PURE__ */
|
|
924
|
-
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
925
|
-
"[",
|
|
926
|
-
hhmmss(e.at),
|
|
927
|
-
" "
|
|
928
|
-
] }),
|
|
929
|
-
/* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
|
|
930
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
|
|
931
|
-
/* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
|
|
932
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
|
|
933
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
|
|
934
|
-
/* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
|
|
935
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
|
|
936
|
-
e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
|
|
937
|
-
e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
|
|
938
|
-
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
|
|
939
|
-
] }, e.id))
|
|
1094
|
+
windowed.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
940
1095
|
] }),
|
|
941
1096
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
942
1097
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
943
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld
|
|
944
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7
|
|
1098
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
|
|
1099
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7 s sessions \xB7 c copy \xB7 esc menu \xB7 q quit " })
|
|
945
1100
|
] })
|
|
946
1101
|
] });
|
|
947
1102
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|