@solongate/proxy 0.77.1 → 0.78.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/api-client/audit.d.ts +8 -0
- package/dist/api-client/policies.d.ts +1 -0
- package/dist/commands/index.js +4 -0
- package/dist/index.js +147 -39
- package/dist/tui/index.js +142 -34
- package/package.json +1 -1
|
@@ -20,3 +20,11 @@ export declare function whitelist(id: string, scope?: 'exact' | 'tool'): Promise
|
|
|
20
20
|
policy_version?: number;
|
|
21
21
|
message?: string;
|
|
22
22
|
}>;
|
|
23
|
+
export declare function block(id: string, scope?: 'exact' | 'tool'): Promise<{
|
|
24
|
+
ok: true;
|
|
25
|
+
deduped: boolean;
|
|
26
|
+
scope: string;
|
|
27
|
+
policy_id: string;
|
|
28
|
+
policy_version?: number;
|
|
29
|
+
message?: string;
|
|
30
|
+
}>;
|
package/dist/commands/index.js
CHANGED
|
@@ -233,6 +233,7 @@ function securityInsights(days) {
|
|
|
233
233
|
// src/api-client/audit.ts
|
|
234
234
|
var audit_exports = {};
|
|
235
235
|
__export(audit_exports, {
|
|
236
|
+
block: () => block,
|
|
236
237
|
list: () => list2,
|
|
237
238
|
whitelist: () => whitelist
|
|
238
239
|
});
|
|
@@ -242,6 +243,9 @@ function list2(query = {}) {
|
|
|
242
243
|
function whitelist(id, scope = "exact") {
|
|
243
244
|
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
244
245
|
}
|
|
246
|
+
function block(id, scope = "exact") {
|
|
247
|
+
return request("POST", `/audit-logs/${encodeURIComponent(id)}/block`, { body: { scope } });
|
|
248
|
+
}
|
|
245
249
|
|
|
246
250
|
// src/api-client/agents.ts
|
|
247
251
|
var agents_exports = {};
|
package/dist/index.js
CHANGED
|
@@ -6924,6 +6924,7 @@ var init_stats = __esm({
|
|
|
6924
6924
|
// src/api-client/audit.ts
|
|
6925
6925
|
var audit_exports = {};
|
|
6926
6926
|
__export(audit_exports, {
|
|
6927
|
+
block: () => block,
|
|
6927
6928
|
list: () => list2,
|
|
6928
6929
|
whitelist: () => whitelist
|
|
6929
6930
|
});
|
|
@@ -6933,6 +6934,9 @@ function list2(query = {}) {
|
|
|
6933
6934
|
function whitelist(id, scope = "exact") {
|
|
6934
6935
|
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
6935
6936
|
}
|
|
6937
|
+
function block(id, scope = "exact") {
|
|
6938
|
+
return request("POST", `/audit-logs/${encodeURIComponent(id)}/block`, { body: { scope } });
|
|
6939
|
+
}
|
|
6936
6940
|
var init_audit = __esm({
|
|
6937
6941
|
"src/api-client/audit.ts"() {
|
|
6938
6942
|
"use strict";
|
|
@@ -7052,6 +7056,19 @@ function tailLines(file, maxBytes = 131072) {
|
|
|
7052
7056
|
return [];
|
|
7053
7057
|
}
|
|
7054
7058
|
}
|
|
7059
|
+
function extractTarget(detail) {
|
|
7060
|
+
try {
|
|
7061
|
+
const j = JSON.parse(detail);
|
|
7062
|
+
const cmd = String(j.command ?? j.cmd ?? "").trim();
|
|
7063
|
+
if (cmd) return { kind: "command", value: cmd };
|
|
7064
|
+
const fp = String(j.file_path ?? j.path ?? "").trim();
|
|
7065
|
+
if (fp) return { kind: "path", value: fp.split(/[\\/]/).pop() || fp };
|
|
7066
|
+
const url = String(j.url ?? "").trim();
|
|
7067
|
+
if (url) return { kind: "url", value: url };
|
|
7068
|
+
} catch {
|
|
7069
|
+
}
|
|
7070
|
+
return null;
|
|
7071
|
+
}
|
|
7055
7072
|
function cloudItem(e) {
|
|
7056
7073
|
return {
|
|
7057
7074
|
id: "c:" + e.id,
|
|
@@ -7061,6 +7078,7 @@ function cloudItem(e) {
|
|
|
7061
7078
|
permission: (e.permission ?? "").slice(0, 4),
|
|
7062
7079
|
detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
|
|
7063
7080
|
dlp: !!e.dlp_matches?.length,
|
|
7081
|
+
burst: !!e.rate_limit_burst,
|
|
7064
7082
|
source: "cloud",
|
|
7065
7083
|
session: e.session_id ?? void 0,
|
|
7066
7084
|
agent: e.agent_name ?? void 0,
|
|
@@ -7116,8 +7134,9 @@ function PaneTitle({ label, extra, width: width2 }) {
|
|
|
7116
7134
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
|
|
7117
7135
|
] });
|
|
7118
7136
|
}
|
|
7119
|
-
function StreamLine({ e, loc }) {
|
|
7120
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7137
|
+
function StreamLine({ e, loc, selected }) {
|
|
7138
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
|
|
7139
|
+
/* @__PURE__ */ jsx2(Text2, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
|
|
7121
7140
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
7122
7141
|
"[",
|
|
7123
7142
|
hhmmss(e.at),
|
|
@@ -7144,9 +7163,14 @@ function LivePanel({ active: active2 }) {
|
|
|
7144
7163
|
const [ring, setRing] = useState2(null);
|
|
7145
7164
|
const [log6, setLog] = useState2([]);
|
|
7146
7165
|
const [filter, setFilter] = useState2("all");
|
|
7166
|
+
const [signal, setSignal] = useState2("none");
|
|
7147
7167
|
const [search, setSearch] = useState2("");
|
|
7148
7168
|
const [editingSearch, setEditingSearch] = useState2(false);
|
|
7149
|
-
const [
|
|
7169
|
+
const [sel, setSel] = useState2(0);
|
|
7170
|
+
const [actionMsg, setActionMsg] = useState2(null);
|
|
7171
|
+
const notifiedRef = useRef2(/* @__PURE__ */ new Set());
|
|
7172
|
+
const notifyReady = useRef2(false);
|
|
7173
|
+
const activePolicyRef = useRef2(null);
|
|
7150
7174
|
const [mode, setMode] = useState2("stream");
|
|
7151
7175
|
const [pickIdx, setPickIdx] = useState2(0);
|
|
7152
7176
|
const [detail, setDetail] = useState2(null);
|
|
@@ -7156,6 +7180,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7156
7180
|
const lastLocalTs = useRef2(0);
|
|
7157
7181
|
const pausedUntil = useRef2(0);
|
|
7158
7182
|
const sessRef = useRef2([]);
|
|
7183
|
+
const mergedRef = useRef2([]);
|
|
7159
7184
|
const prevStatus = useRef2(/* @__PURE__ */ new Map());
|
|
7160
7185
|
const [alerts, setAlerts] = useState2([]);
|
|
7161
7186
|
const [frozen, setFrozen] = useState2(false);
|
|
@@ -7204,6 +7229,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7204
7229
|
permission: (j.permission ?? "").slice(0, 4),
|
|
7205
7230
|
detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
|
|
7206
7231
|
dlp: !!j.dlp,
|
|
7232
|
+
burst: !!j.rate_limit_burst,
|
|
7207
7233
|
source: "local",
|
|
7208
7234
|
session: j.session_id,
|
|
7209
7235
|
agent: j.agent_name,
|
|
@@ -7318,30 +7344,52 @@ function LivePanel({ active: active2 }) {
|
|
|
7318
7344
|
return () => clearInterval(t);
|
|
7319
7345
|
}, [active2, frozen]);
|
|
7320
7346
|
const startRef = useRef2(Date.now());
|
|
7347
|
+
const fireAlert = useCallback2(
|
|
7348
|
+
(id, title, msg, level = "warn") => {
|
|
7349
|
+
pushLog(`${level === "bad" ? "\u26D4" : "\u23F8"} ${msg}`, level);
|
|
7350
|
+
setAlerts((a) => [...a.filter((x) => x.id !== id), { id, msg, level, until: Date.now() + 15e3 }].slice(-4));
|
|
7351
|
+
try {
|
|
7352
|
+
process.stdout.write("\x07");
|
|
7353
|
+
} catch {
|
|
7354
|
+
}
|
|
7355
|
+
try {
|
|
7356
|
+
const child = spawn("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
|
|
7357
|
+
child.on("error", () => {
|
|
7358
|
+
});
|
|
7359
|
+
child.unref();
|
|
7360
|
+
} catch {
|
|
7361
|
+
}
|
|
7362
|
+
},
|
|
7363
|
+
[pushLog]
|
|
7364
|
+
);
|
|
7321
7365
|
useEffect2(() => {
|
|
7322
7366
|
if (!active2) return;
|
|
7323
7367
|
for (const r of sessRef.current) {
|
|
7324
7368
|
const prev = prevStatus.current.get(r.id);
|
|
7325
7369
|
if (prev && prev === "active" && r.status === "idle") {
|
|
7326
7370
|
const name = r.isMe ? `${r.agent} (this machine)` : r.agent;
|
|
7327
|
-
|
|
7328
|
-
pushLog(`\u23F8 ${msg}`, "warn");
|
|
7329
|
-
setAlerts((a) => [...a.filter((x) => x.id !== r.id), { id: r.id, msg, until: Date.now() + 15e3 }].slice(-4));
|
|
7330
|
-
try {
|
|
7331
|
-
process.stdout.write("\x07");
|
|
7332
|
-
} catch {
|
|
7333
|
-
}
|
|
7334
|
-
try {
|
|
7335
|
-
const child = spawn("notify-send", ["-a", "SolonGate", "Agent idle", msg], { stdio: "ignore", detached: true });
|
|
7336
|
-
child.on("error", () => {
|
|
7337
|
-
});
|
|
7338
|
-
child.unref();
|
|
7339
|
-
} catch {
|
|
7340
|
-
}
|
|
7371
|
+
fireAlert("idle:" + r.id, "Agent idle", `${name} went IDLE, no tool call for 60s`, "warn");
|
|
7341
7372
|
}
|
|
7342
7373
|
prevStatus.current.set(r.id, r.status);
|
|
7343
7374
|
}
|
|
7344
|
-
}, [tick, active2,
|
|
7375
|
+
}, [tick, active2, fireAlert]);
|
|
7376
|
+
useEffect2(() => {
|
|
7377
|
+
if (!active2) return;
|
|
7378
|
+
const notable = mergedRef.current.filter((e) => e.decision !== "ALLOW" || e.dlp || e.burst);
|
|
7379
|
+
if (!notifyReady.current) {
|
|
7380
|
+
for (const e of notable) notifiedRef.current.add(e.id);
|
|
7381
|
+
notifyReady.current = true;
|
|
7382
|
+
return;
|
|
7383
|
+
}
|
|
7384
|
+
for (const e of notable) {
|
|
7385
|
+
if (notifiedRef.current.has(e.id)) continue;
|
|
7386
|
+
notifiedRef.current.add(e.id);
|
|
7387
|
+
const who = e.agent ? ` \xB7 ${truncate2(e.agent, 20)}` : "";
|
|
7388
|
+
if (e.dlp) fireAlert("sec:" + e.id, "DLP hit", `SECRET in ${e.tool}${who}: ${truncate2(e.detail, 60)}`, "bad");
|
|
7389
|
+
else if (e.decision !== "ALLOW") fireAlert("sec:" + e.id, "Call denied", `DENY ${e.tool}${who}: ${truncate2(e.rule ?? e.detail, 60)}`, "bad");
|
|
7390
|
+
else if (e.burst) fireAlert("sec:" + e.id, "Rate-limit burst", `BURST ${e.tool}${who}`, "warn");
|
|
7391
|
+
}
|
|
7392
|
+
}, [tick, active2, fireAlert]);
|
|
7345
7393
|
const cols = process.stdout.columns ?? 100;
|
|
7346
7394
|
const rows = process.stdout.rows ?? 30;
|
|
7347
7395
|
const ins = insights.data ?? {};
|
|
@@ -7354,6 +7402,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7354
7402
|
}
|
|
7355
7403
|
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
7356
7404
|
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
7405
|
+
mergedRef.current = mergedAll;
|
|
7357
7406
|
const nowSlot = Math.floor(nowMs / 1e4);
|
|
7358
7407
|
const traffic = new Array(60).fill(0);
|
|
7359
7408
|
const trafficHot = new Array(60).fill(false);
|
|
@@ -7413,7 +7462,8 @@ function LivePanel({ active: active2 }) {
|
|
|
7413
7462
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
7414
7463
|
const q = search.trim().toLowerCase();
|
|
7415
7464
|
const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
|
|
7416
|
-
const
|
|
7465
|
+
const signalOk = (e) => signal === "none" ? true : signal === "deny" ? e.decision !== "ALLOW" : signal === "dlp" ? e.dlp : e.burst;
|
|
7466
|
+
const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches).filter(signalOk);
|
|
7417
7467
|
const visibleDesc = filtered.slice().reverse();
|
|
7418
7468
|
const chartH = rows >= 36 ? 6 : 4;
|
|
7419
7469
|
const colH = rows >= 32 ? 7 : 5;
|
|
@@ -7424,9 +7474,11 @@ function LivePanel({ active: active2 }) {
|
|
|
7424
7474
|
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
7425
7475
|
const pickable = combinedSess.slice(0, colH - 1);
|
|
7426
7476
|
const streamBodyRows = Math.max(3, streamRows - 1);
|
|
7477
|
+
const selClamped = Math.min(Math.max(0, sel), Math.max(0, visibleDesc.length - 1));
|
|
7427
7478
|
const maxScroll = Math.max(0, visibleDesc.length - streamBodyRows);
|
|
7428
|
-
const clampedScroll = Math.min(
|
|
7479
|
+
const clampedScroll = Math.min(Math.max(0, selClamped - Math.floor((streamBodyRows - 1) / 2)), maxScroll);
|
|
7429
7480
|
const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamBodyRows);
|
|
7481
|
+
const selEntry = visibleDesc[selClamped];
|
|
7430
7482
|
const detailEntries = detail ? (() => {
|
|
7431
7483
|
const seen = /* @__PURE__ */ new Set();
|
|
7432
7484
|
const all = [];
|
|
@@ -7438,6 +7490,42 @@ function LivePanel({ active: active2 }) {
|
|
|
7438
7490
|
}
|
|
7439
7491
|
return all.sort((a, b) => b.at - a.at);
|
|
7440
7492
|
})() : [];
|
|
7493
|
+
const doAction = async (act) => {
|
|
7494
|
+
const e = selEntry;
|
|
7495
|
+
if (!e) return;
|
|
7496
|
+
const ok = act === "whitelist" ? e.decision !== "ALLOW" : e.decision === "ALLOW";
|
|
7497
|
+
if (!ok) {
|
|
7498
|
+
setActionMsg({ text: act === "whitelist" ? "select a DENY to whitelist (w)" : "select an ALLOW to block (b)", level: "bad", until: Date.now() + 4e3 });
|
|
7499
|
+
return;
|
|
7500
|
+
}
|
|
7501
|
+
setActionMsg({ text: act === "whitelist" ? "whitelisting\u2026" : "blocking\u2026", level: "ok", until: Date.now() + 4e3 });
|
|
7502
|
+
try {
|
|
7503
|
+
let res;
|
|
7504
|
+
if (e.source === "cloud") {
|
|
7505
|
+
const realId = e.id.slice(2);
|
|
7506
|
+
res = act === "whitelist" ? await api.audit.whitelist(realId) : await api.audit.block(realId);
|
|
7507
|
+
} else {
|
|
7508
|
+
let pid = activePolicyRef.current;
|
|
7509
|
+
if (!pid) {
|
|
7510
|
+
const a = await api.policies.active();
|
|
7511
|
+
pid = a.policy?.id ?? null;
|
|
7512
|
+
activePolicyRef.current = pid;
|
|
7513
|
+
}
|
|
7514
|
+
if (!pid) throw new Error("no active policy resolves for this machine");
|
|
7515
|
+
const t = extractTarget(e.detail);
|
|
7516
|
+
res = await api.policies.addRule(pid, { toolPattern: e.tool, kind: t?.kind ?? "tool", value: t?.value, effect: act === "whitelist" ? "ALLOW" : "DENY" });
|
|
7517
|
+
}
|
|
7518
|
+
const verb = act === "whitelist" ? "ALLOW" : "DENY";
|
|
7519
|
+
setActionMsg({
|
|
7520
|
+
text: res.deduped ? `${verb} rule already present` : `${verb} rule added \u2192 ${res.policy_id ?? "?"}${res.policy_version ? " v" + res.policy_version : ""} \xB7 reaches agents in ~30s`,
|
|
7521
|
+
level: "ok",
|
|
7522
|
+
until: Date.now() + 7e3
|
|
7523
|
+
});
|
|
7524
|
+
pushLog(`${act === "whitelist" ? "\u2713 whitelisted" : "\u26D4 blocked"} ${e.tool}`, act === "whitelist" ? "ok" : "warn");
|
|
7525
|
+
} catch (err2) {
|
|
7526
|
+
setActionMsg({ text: "\u2717 " + (err2 instanceof Error ? err2.message : String(err2)), level: "bad", until: Date.now() + 7e3 });
|
|
7527
|
+
}
|
|
7528
|
+
};
|
|
7441
7529
|
useInput(
|
|
7442
7530
|
(input, key) => {
|
|
7443
7531
|
if (input === " ") {
|
|
@@ -7472,16 +7560,25 @@ function LivePanel({ active: active2 }) {
|
|
|
7472
7560
|
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
7473
7561
|
return;
|
|
7474
7562
|
}
|
|
7563
|
+
const toggleSignal = (s2) => {
|
|
7564
|
+
setSignal((cur) => cur === s2 ? "none" : s2);
|
|
7565
|
+
setSel(0);
|
|
7566
|
+
};
|
|
7475
7567
|
if (input === "f") {
|
|
7476
7568
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
7477
|
-
|
|
7569
|
+
setSel(0);
|
|
7478
7570
|
} else if (input === "s") {
|
|
7479
7571
|
setPickIdx(0);
|
|
7480
7572
|
setMode("pick");
|
|
7481
|
-
} else if (
|
|
7482
|
-
else if (
|
|
7483
|
-
else if (
|
|
7484
|
-
else if (
|
|
7573
|
+
} else if (input === "w") void doAction("whitelist");
|
|
7574
|
+
else if (input === "b") void doAction("block");
|
|
7575
|
+
else if (input === "d") toggleSignal("deny");
|
|
7576
|
+
else if (input === "x") toggleSignal("dlp");
|
|
7577
|
+
else if (input === "r") toggleSignal("ratelimit");
|
|
7578
|
+
else if (key.downArrow) setSel((n) => Math.min(visibleDesc.length - 1, n + 1));
|
|
7579
|
+
else if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
7580
|
+
else if (key.pageDown) setSel((n) => Math.min(visibleDesc.length - 1, n + streamBodyRows));
|
|
7581
|
+
else if (key.pageUp) setSel((n) => Math.max(0, n - streamBodyRows));
|
|
7485
7582
|
},
|
|
7486
7583
|
{ isActive: active2 && !editingSearch }
|
|
7487
7584
|
);
|
|
@@ -7498,7 +7595,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7498
7595
|
return;
|
|
7499
7596
|
}
|
|
7500
7597
|
setSearch(v);
|
|
7501
|
-
|
|
7598
|
+
setSel(0);
|
|
7502
7599
|
setDetailScroll(0);
|
|
7503
7600
|
},
|
|
7504
7601
|
onSubmit: () => setEditingSearch(false)
|
|
@@ -7618,7 +7715,18 @@ function LivePanel({ active: active2 }) {
|
|
|
7618
7715
|
/* @__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" }),
|
|
7619
7716
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
7620
7717
|
] }),
|
|
7621
|
-
alerts.filter((a) => nowMs < a.until).slice(-1).map((a) => /* @__PURE__ */ jsx2(
|
|
7718
|
+
alerts.filter((a) => nowMs < a.until).slice(-1).map((a) => /* @__PURE__ */ jsx2(
|
|
7719
|
+
Text2,
|
|
7720
|
+
{
|
|
7721
|
+
wrap: "truncate",
|
|
7722
|
+
backgroundColor: a.level === "bad" ? "#3d1220" : "#3d2a12",
|
|
7723
|
+
color: a.level === "bad" ? "#ff6b6b" : "#ffb454",
|
|
7724
|
+
bold: true,
|
|
7725
|
+
children: ` ${a.level === "bad" ? "\u26D4 ALERT" : "\u23F8 IDLE"} \xB7 ${truncate2(a.msg, innerW - 14)} `
|
|
7726
|
+
},
|
|
7727
|
+
a.id
|
|
7728
|
+
)),
|
|
7729
|
+
actionMsg && nowMs < actionMsg.until ? /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", backgroundColor: actionMsg.level === "bad" ? "#3d1220" : "#123d1f", color: actionMsg.level === "bad" ? "#ff6b6b" : "#7bd88f", bold: true, children: ` ${truncate2(actionMsg.text, innerW - 4)} ` }) : null,
|
|
7622
7730
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
7623
7731
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
7624
7732
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -7656,11 +7764,11 @@ function LivePanel({ active: active2 }) {
|
|
|
7656
7764
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
7657
7765
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
7658
7766
|
pickable.map((r, i) => {
|
|
7659
|
-
const
|
|
7767
|
+
const sel2 = mode === "pick" && i === pickIdx;
|
|
7660
7768
|
const st = STATUS_STYLE[r.status];
|
|
7661
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor:
|
|
7662
|
-
/* @__PURE__ */ jsxs2(Text2, { color:
|
|
7663
|
-
|
|
7769
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel2 ? "#1c2f63" : void 0, bold: sel2 || r.isMe, children: [
|
|
7770
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel2 ? theme.accentBright : st.color, children: [
|
|
7771
|
+
sel2 ? "\u25B8" : st.dot,
|
|
7664
7772
|
" "
|
|
7665
7773
|
] }),
|
|
7666
7774
|
/* @__PURE__ */ jsx2(Text2, { color: st.color, children: st.label.padEnd(7) }),
|
|
@@ -7693,7 +7801,7 @@ function LivePanel({ active: active2 }) {
|
|
|
7693
7801
|
PaneTitle,
|
|
7694
7802
|
{
|
|
7695
7803
|
label: "TOOL STREAM",
|
|
7696
|
-
extra: `${visibleDesc.length}
|
|
7804
|
+
extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""} \xB7 \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r ${filter} \xB7 s`,
|
|
7697
7805
|
width: innerW
|
|
7698
7806
|
}
|
|
7699
7807
|
),
|
|
@@ -7707,12 +7815,12 @@ function LivePanel({ active: active2 }) {
|
|
|
7707
7815
|
spin,
|
|
7708
7816
|
" awaiting traffic\u2026"
|
|
7709
7817
|
] }) : null,
|
|
7710
|
-
windowed.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
7818
|
+
windowed.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: clampedScroll + i === selClamped }, e.id))
|
|
7711
7819
|
] }),
|
|
7712
7820
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
7713
7821
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
7714
7822
|
/* @__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"} ` }),
|
|
7715
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: "
|
|
7823
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r \xB7 / search \xB7 f \xB7 s \xB7 space copy \xB7 esc \xB7 q " })
|
|
7716
7824
|
] })
|
|
7717
7825
|
] });
|
|
7718
7826
|
}
|
|
@@ -9560,13 +9668,13 @@ function shimTargets() {
|
|
|
9560
9668
|
}
|
|
9561
9669
|
return [".bashrc", ".zshrc", ".profile"].map((f) => join6(homedir4(), f)).filter((f) => existsSync4(f));
|
|
9562
9670
|
}
|
|
9563
|
-
function writeShimBlock(file,
|
|
9671
|
+
function writeShimBlock(file, block2) {
|
|
9564
9672
|
const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
|
|
9565
9673
|
let content = existsSync4(file) ? readFileSync6(file, "utf-8") : "";
|
|
9566
9674
|
content = content.replace(re, "");
|
|
9567
|
-
if (
|
|
9675
|
+
if (block2) {
|
|
9568
9676
|
if (content.length && !content.endsWith("\n")) content += "\n";
|
|
9569
|
-
content +=
|
|
9677
|
+
content += block2 + "\n";
|
|
9570
9678
|
}
|
|
9571
9679
|
mkdirSync3(dirname(file), { recursive: true });
|
|
9572
9680
|
writeFileSync3(file, content);
|
|
@@ -9580,7 +9688,7 @@ function installClaudeShim(shieldPath) {
|
|
|
9580
9688
|
const node = process.execPath.replace(/\\/g, "/");
|
|
9581
9689
|
const shield = shieldPath.replace(/\\/g, "/");
|
|
9582
9690
|
const win = process.platform === "win32";
|
|
9583
|
-
const
|
|
9691
|
+
const block2 = win ? `${SHIM_BEGIN}
|
|
9584
9692
|
function claude { & "${node}" "${shield}" -- "${real}" @args }
|
|
9585
9693
|
${SHIM_END}` : `${SHIM_BEGIN}
|
|
9586
9694
|
claude() { "${node}" "${shield}" -- "${real}" "$@"; }
|
|
@@ -9592,7 +9700,7 @@ ${SHIM_END}`;
|
|
|
9592
9700
|
}
|
|
9593
9701
|
for (const file of targets) {
|
|
9594
9702
|
try {
|
|
9595
|
-
writeShimBlock(file,
|
|
9703
|
+
writeShimBlock(file, block2);
|
|
9596
9704
|
console.log(` Auto-shield on: \`claude\` now masks secrets (${file})`);
|
|
9597
9705
|
} catch (e) {
|
|
9598
9706
|
console.log(` (Could not enable auto-shield in ${file}: ${e.message})`);
|
package/dist/tui/index.js
CHANGED
|
@@ -370,6 +370,7 @@ function securityInsights(days) {
|
|
|
370
370
|
// src/api-client/audit.ts
|
|
371
371
|
var audit_exports = {};
|
|
372
372
|
__export(audit_exports, {
|
|
373
|
+
block: () => block,
|
|
373
374
|
list: () => list2,
|
|
374
375
|
whitelist: () => whitelist
|
|
375
376
|
});
|
|
@@ -379,6 +380,9 @@ function list2(query = {}) {
|
|
|
379
380
|
function whitelist(id, scope = "exact") {
|
|
380
381
|
return request("POST", `/audit-logs/${encodeURIComponent(id)}/whitelist`, { body: { scope } });
|
|
381
382
|
}
|
|
383
|
+
function block(id, scope = "exact") {
|
|
384
|
+
return request("POST", `/audit-logs/${encodeURIComponent(id)}/block`, { body: { scope } });
|
|
385
|
+
}
|
|
382
386
|
|
|
383
387
|
// src/api-client/agents.ts
|
|
384
388
|
var agents_exports = {};
|
|
@@ -475,6 +479,19 @@ function tailLines(file, maxBytes = 131072) {
|
|
|
475
479
|
return [];
|
|
476
480
|
}
|
|
477
481
|
}
|
|
482
|
+
function extractTarget(detail) {
|
|
483
|
+
try {
|
|
484
|
+
const j = JSON.parse(detail);
|
|
485
|
+
const cmd = String(j.command ?? j.cmd ?? "").trim();
|
|
486
|
+
if (cmd) return { kind: "command", value: cmd };
|
|
487
|
+
const fp = String(j.file_path ?? j.path ?? "").trim();
|
|
488
|
+
if (fp) return { kind: "path", value: fp.split(/[\\/]/).pop() || fp };
|
|
489
|
+
const url = String(j.url ?? "").trim();
|
|
490
|
+
if (url) return { kind: "url", value: url };
|
|
491
|
+
} catch {
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
478
495
|
function cloudItem(e) {
|
|
479
496
|
return {
|
|
480
497
|
id: "c:" + e.id,
|
|
@@ -484,6 +501,7 @@ function cloudItem(e) {
|
|
|
484
501
|
permission: (e.permission ?? "").slice(0, 4),
|
|
485
502
|
detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
|
|
486
503
|
dlp: !!e.dlp_matches?.length,
|
|
504
|
+
burst: !!e.rate_limit_burst,
|
|
487
505
|
source: "cloud",
|
|
488
506
|
session: e.session_id ?? void 0,
|
|
489
507
|
agent: e.agent_name ?? void 0,
|
|
@@ -539,8 +557,9 @@ function PaneTitle({ label, extra, width }) {
|
|
|
539
557
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
|
|
540
558
|
] });
|
|
541
559
|
}
|
|
542
|
-
function StreamLine({ e, loc }) {
|
|
543
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
560
|
+
function StreamLine({ e, loc, selected }) {
|
|
561
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
|
|
562
|
+
/* @__PURE__ */ jsx2(Text2, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
|
|
544
563
|
/* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
|
|
545
564
|
"[",
|
|
546
565
|
hhmmss(e.at),
|
|
@@ -574,9 +593,14 @@ function LivePanel({ active: active2 }) {
|
|
|
574
593
|
const [ring, setRing] = useState2(null);
|
|
575
594
|
const [log, setLog] = useState2([]);
|
|
576
595
|
const [filter, setFilter] = useState2("all");
|
|
596
|
+
const [signal, setSignal] = useState2("none");
|
|
577
597
|
const [search, setSearch] = useState2("");
|
|
578
598
|
const [editingSearch, setEditingSearch] = useState2(false);
|
|
579
|
-
const [
|
|
599
|
+
const [sel, setSel] = useState2(0);
|
|
600
|
+
const [actionMsg, setActionMsg] = useState2(null);
|
|
601
|
+
const notifiedRef = useRef2(/* @__PURE__ */ new Set());
|
|
602
|
+
const notifyReady = useRef2(false);
|
|
603
|
+
const activePolicyRef = useRef2(null);
|
|
580
604
|
const [mode, setMode] = useState2("stream");
|
|
581
605
|
const [pickIdx, setPickIdx] = useState2(0);
|
|
582
606
|
const [detail, setDetail] = useState2(null);
|
|
@@ -586,6 +610,7 @@ function LivePanel({ active: active2 }) {
|
|
|
586
610
|
const lastLocalTs = useRef2(0);
|
|
587
611
|
const pausedUntil = useRef2(0);
|
|
588
612
|
const sessRef = useRef2([]);
|
|
613
|
+
const mergedRef = useRef2([]);
|
|
589
614
|
const prevStatus = useRef2(/* @__PURE__ */ new Map());
|
|
590
615
|
const [alerts, setAlerts] = useState2([]);
|
|
591
616
|
const [frozen, setFrozen] = useState2(false);
|
|
@@ -634,6 +659,7 @@ function LivePanel({ active: active2 }) {
|
|
|
634
659
|
permission: (j.permission ?? "").slice(0, 4),
|
|
635
660
|
detail: (j.arguments ? JSON.stringify(j.arguments) : j.reason ?? "").replace(/\s+/g, " "),
|
|
636
661
|
dlp: !!j.dlp,
|
|
662
|
+
burst: !!j.rate_limit_burst,
|
|
637
663
|
source: "local",
|
|
638
664
|
session: j.session_id,
|
|
639
665
|
agent: j.agent_name,
|
|
@@ -748,30 +774,52 @@ function LivePanel({ active: active2 }) {
|
|
|
748
774
|
return () => clearInterval(t);
|
|
749
775
|
}, [active2, frozen]);
|
|
750
776
|
const startRef = useRef2(Date.now());
|
|
777
|
+
const fireAlert = useCallback2(
|
|
778
|
+
(id, title, msg, level = "warn") => {
|
|
779
|
+
pushLog(`${level === "bad" ? "\u26D4" : "\u23F8"} ${msg}`, level);
|
|
780
|
+
setAlerts((a) => [...a.filter((x) => x.id !== id), { id, msg, level, until: Date.now() + 15e3 }].slice(-4));
|
|
781
|
+
try {
|
|
782
|
+
process.stdout.write("\x07");
|
|
783
|
+
} catch {
|
|
784
|
+
}
|
|
785
|
+
try {
|
|
786
|
+
const child = spawn("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
|
|
787
|
+
child.on("error", () => {
|
|
788
|
+
});
|
|
789
|
+
child.unref();
|
|
790
|
+
} catch {
|
|
791
|
+
}
|
|
792
|
+
},
|
|
793
|
+
[pushLog]
|
|
794
|
+
);
|
|
751
795
|
useEffect2(() => {
|
|
752
796
|
if (!active2) return;
|
|
753
797
|
for (const r of sessRef.current) {
|
|
754
798
|
const prev = prevStatus.current.get(r.id);
|
|
755
799
|
if (prev && prev === "active" && r.status === "idle") {
|
|
756
800
|
const name = r.isMe ? `${r.agent} (this machine)` : r.agent;
|
|
757
|
-
|
|
758
|
-
pushLog(`\u23F8 ${msg}`, "warn");
|
|
759
|
-
setAlerts((a) => [...a.filter((x) => x.id !== r.id), { id: r.id, msg, until: Date.now() + 15e3 }].slice(-4));
|
|
760
|
-
try {
|
|
761
|
-
process.stdout.write("\x07");
|
|
762
|
-
} catch {
|
|
763
|
-
}
|
|
764
|
-
try {
|
|
765
|
-
const child = spawn("notify-send", ["-a", "SolonGate", "Agent idle", msg], { stdio: "ignore", detached: true });
|
|
766
|
-
child.on("error", () => {
|
|
767
|
-
});
|
|
768
|
-
child.unref();
|
|
769
|
-
} catch {
|
|
770
|
-
}
|
|
801
|
+
fireAlert("idle:" + r.id, "Agent idle", `${name} went IDLE, no tool call for 60s`, "warn");
|
|
771
802
|
}
|
|
772
803
|
prevStatus.current.set(r.id, r.status);
|
|
773
804
|
}
|
|
774
|
-
}, [tick, active2,
|
|
805
|
+
}, [tick, active2, fireAlert]);
|
|
806
|
+
useEffect2(() => {
|
|
807
|
+
if (!active2) return;
|
|
808
|
+
const notable = mergedRef.current.filter((e) => e.decision !== "ALLOW" || e.dlp || e.burst);
|
|
809
|
+
if (!notifyReady.current) {
|
|
810
|
+
for (const e of notable) notifiedRef.current.add(e.id);
|
|
811
|
+
notifyReady.current = true;
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
for (const e of notable) {
|
|
815
|
+
if (notifiedRef.current.has(e.id)) continue;
|
|
816
|
+
notifiedRef.current.add(e.id);
|
|
817
|
+
const who = e.agent ? ` \xB7 ${truncate(e.agent, 20)}` : "";
|
|
818
|
+
if (e.dlp) fireAlert("sec:" + e.id, "DLP hit", `SECRET in ${e.tool}${who}: ${truncate(e.detail, 60)}`, "bad");
|
|
819
|
+
else if (e.decision !== "ALLOW") fireAlert("sec:" + e.id, "Call denied", `DENY ${e.tool}${who}: ${truncate(e.rule ?? e.detail, 60)}`, "bad");
|
|
820
|
+
else if (e.burst) fireAlert("sec:" + e.id, "Rate-limit burst", `BURST ${e.tool}${who}`, "warn");
|
|
821
|
+
}
|
|
822
|
+
}, [tick, active2, fireAlert]);
|
|
775
823
|
const cols = process.stdout.columns ?? 100;
|
|
776
824
|
const rows = process.stdout.rows ?? 30;
|
|
777
825
|
const ins = insights.data ?? {};
|
|
@@ -784,6 +832,7 @@ function LivePanel({ active: active2 }) {
|
|
|
784
832
|
}
|
|
785
833
|
const cloudDeduped = cloudBuf.filter((e) => !localKeys.has(`${e.tool}|${e.decision}|${Math.round(e.at / 6e4)}`));
|
|
786
834
|
const mergedAll = [...cloudDeduped, ...localBuf].sort((a, b) => a.at - b.at);
|
|
835
|
+
mergedRef.current = mergedAll;
|
|
787
836
|
const nowSlot = Math.floor(nowMs / 1e4);
|
|
788
837
|
const traffic = new Array(60).fill(0);
|
|
789
838
|
const trafficHot = new Array(60).fill(false);
|
|
@@ -843,7 +892,8 @@ function LivePanel({ active: active2 }) {
|
|
|
843
892
|
const isLoc = (e) => e.source === "local" || !!e.session && localSessIds.has(e.session);
|
|
844
893
|
const q = search.trim().toLowerCase();
|
|
845
894
|
const matches = (e) => !q || `${e.tool} ${e.agent ?? ""} ${e.decision} ${e.permission} ${e.rule ?? ""} ${e.detail}`.toLowerCase().includes(q);
|
|
846
|
-
const
|
|
895
|
+
const signalOk = (e) => signal === "none" ? true : signal === "deny" ? e.decision !== "ALLOW" : signal === "dlp" ? e.dlp : e.burst;
|
|
896
|
+
const filtered = (filter === "all" ? mergedAll : mergedAll.filter((e) => filter === "local" ? isLoc(e) : !isLoc(e))).filter(matches).filter(signalOk);
|
|
847
897
|
const visibleDesc = filtered.slice().reverse();
|
|
848
898
|
const chartH = rows >= 36 ? 6 : 4;
|
|
849
899
|
const colH = rows >= 32 ? 7 : 5;
|
|
@@ -854,9 +904,11 @@ function LivePanel({ active: active2 }) {
|
|
|
854
904
|
const colW = Math.max(20, Math.floor((innerW - 4) / 3));
|
|
855
905
|
const pickable = combinedSess.slice(0, colH - 1);
|
|
856
906
|
const streamBodyRows = Math.max(3, streamRows - 1);
|
|
907
|
+
const selClamped = Math.min(Math.max(0, sel), Math.max(0, visibleDesc.length - 1));
|
|
857
908
|
const maxScroll = Math.max(0, visibleDesc.length - streamBodyRows);
|
|
858
|
-
const clampedScroll = Math.min(
|
|
909
|
+
const clampedScroll = Math.min(Math.max(0, selClamped - Math.floor((streamBodyRows - 1) / 2)), maxScroll);
|
|
859
910
|
const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamBodyRows);
|
|
911
|
+
const selEntry = visibleDesc[selClamped];
|
|
860
912
|
const detailEntries = detail ? (() => {
|
|
861
913
|
const seen = /* @__PURE__ */ new Set();
|
|
862
914
|
const all = [];
|
|
@@ -868,6 +920,42 @@ function LivePanel({ active: active2 }) {
|
|
|
868
920
|
}
|
|
869
921
|
return all.sort((a, b) => b.at - a.at);
|
|
870
922
|
})() : [];
|
|
923
|
+
const doAction = async (act) => {
|
|
924
|
+
const e = selEntry;
|
|
925
|
+
if (!e) return;
|
|
926
|
+
const ok = act === "whitelist" ? e.decision !== "ALLOW" : e.decision === "ALLOW";
|
|
927
|
+
if (!ok) {
|
|
928
|
+
setActionMsg({ text: act === "whitelist" ? "select a DENY to whitelist (w)" : "select an ALLOW to block (b)", level: "bad", until: Date.now() + 4e3 });
|
|
929
|
+
return;
|
|
930
|
+
}
|
|
931
|
+
setActionMsg({ text: act === "whitelist" ? "whitelisting\u2026" : "blocking\u2026", level: "ok", until: Date.now() + 4e3 });
|
|
932
|
+
try {
|
|
933
|
+
let res;
|
|
934
|
+
if (e.source === "cloud") {
|
|
935
|
+
const realId = e.id.slice(2);
|
|
936
|
+
res = act === "whitelist" ? await api.audit.whitelist(realId) : await api.audit.block(realId);
|
|
937
|
+
} else {
|
|
938
|
+
let pid = activePolicyRef.current;
|
|
939
|
+
if (!pid) {
|
|
940
|
+
const a = await api.policies.active();
|
|
941
|
+
pid = a.policy?.id ?? null;
|
|
942
|
+
activePolicyRef.current = pid;
|
|
943
|
+
}
|
|
944
|
+
if (!pid) throw new Error("no active policy resolves for this machine");
|
|
945
|
+
const t = extractTarget(e.detail);
|
|
946
|
+
res = await api.policies.addRule(pid, { toolPattern: e.tool, kind: t?.kind ?? "tool", value: t?.value, effect: act === "whitelist" ? "ALLOW" : "DENY" });
|
|
947
|
+
}
|
|
948
|
+
const verb = act === "whitelist" ? "ALLOW" : "DENY";
|
|
949
|
+
setActionMsg({
|
|
950
|
+
text: res.deduped ? `${verb} rule already present` : `${verb} rule added \u2192 ${res.policy_id ?? "?"}${res.policy_version ? " v" + res.policy_version : ""} \xB7 reaches agents in ~30s`,
|
|
951
|
+
level: "ok",
|
|
952
|
+
until: Date.now() + 7e3
|
|
953
|
+
});
|
|
954
|
+
pushLog(`${act === "whitelist" ? "\u2713 whitelisted" : "\u26D4 blocked"} ${e.tool}`, act === "whitelist" ? "ok" : "warn");
|
|
955
|
+
} catch (err) {
|
|
956
|
+
setActionMsg({ text: "\u2717 " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 7e3 });
|
|
957
|
+
}
|
|
958
|
+
};
|
|
871
959
|
useInput(
|
|
872
960
|
(input, key) => {
|
|
873
961
|
if (input === " ") {
|
|
@@ -902,16 +990,25 @@ function LivePanel({ active: active2 }) {
|
|
|
902
990
|
} else if (input === "s" || key.leftArrow) setMode("stream");
|
|
903
991
|
return;
|
|
904
992
|
}
|
|
993
|
+
const toggleSignal = (s2) => {
|
|
994
|
+
setSignal((cur) => cur === s2 ? "none" : s2);
|
|
995
|
+
setSel(0);
|
|
996
|
+
};
|
|
905
997
|
if (input === "f") {
|
|
906
998
|
setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
|
|
907
|
-
|
|
999
|
+
setSel(0);
|
|
908
1000
|
} else if (input === "s") {
|
|
909
1001
|
setPickIdx(0);
|
|
910
1002
|
setMode("pick");
|
|
911
|
-
} else if (
|
|
912
|
-
else if (
|
|
913
|
-
else if (
|
|
914
|
-
else if (
|
|
1003
|
+
} else if (input === "w") void doAction("whitelist");
|
|
1004
|
+
else if (input === "b") void doAction("block");
|
|
1005
|
+
else if (input === "d") toggleSignal("deny");
|
|
1006
|
+
else if (input === "x") toggleSignal("dlp");
|
|
1007
|
+
else if (input === "r") toggleSignal("ratelimit");
|
|
1008
|
+
else if (key.downArrow) setSel((n) => Math.min(visibleDesc.length - 1, n + 1));
|
|
1009
|
+
else if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
1010
|
+
else if (key.pageDown) setSel((n) => Math.min(visibleDesc.length - 1, n + streamBodyRows));
|
|
1011
|
+
else if (key.pageUp) setSel((n) => Math.max(0, n - streamBodyRows));
|
|
915
1012
|
},
|
|
916
1013
|
{ isActive: active2 && !editingSearch }
|
|
917
1014
|
);
|
|
@@ -928,7 +1025,7 @@ function LivePanel({ active: active2 }) {
|
|
|
928
1025
|
return;
|
|
929
1026
|
}
|
|
930
1027
|
setSearch(v);
|
|
931
|
-
|
|
1028
|
+
setSel(0);
|
|
932
1029
|
setDetailScroll(0);
|
|
933
1030
|
},
|
|
934
1031
|
onSubmit: () => setEditingSearch(false)
|
|
@@ -1048,7 +1145,18 @@ function LivePanel({ active: active2 }) {
|
|
|
1048
1145
|
/* @__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" }),
|
|
1049
1146
|
/* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
|
|
1050
1147
|
] }),
|
|
1051
|
-
alerts.filter((a) => nowMs < a.until).slice(-1).map((a) => /* @__PURE__ */ jsx2(
|
|
1148
|
+
alerts.filter((a) => nowMs < a.until).slice(-1).map((a) => /* @__PURE__ */ jsx2(
|
|
1149
|
+
Text2,
|
|
1150
|
+
{
|
|
1151
|
+
wrap: "truncate",
|
|
1152
|
+
backgroundColor: a.level === "bad" ? "#3d1220" : "#3d2a12",
|
|
1153
|
+
color: a.level === "bad" ? "#ff6b6b" : "#ffb454",
|
|
1154
|
+
bold: true,
|
|
1155
|
+
children: ` ${a.level === "bad" ? "\u26D4 ALERT" : "\u23F8 IDLE"} \xB7 ${truncate(a.msg, innerW - 14)} `
|
|
1156
|
+
},
|
|
1157
|
+
a.id
|
|
1158
|
+
)),
|
|
1159
|
+
actionMsg && nowMs < actionMsg.until ? /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", backgroundColor: actionMsg.level === "bad" ? "#3d1220" : "#123d1f", color: actionMsg.level === "bad" ? "#ff6b6b" : "#7bd88f", bold: true, children: ` ${truncate(actionMsg.text, innerW - 4)} ` }) : null,
|
|
1052
1160
|
/* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
|
|
1053
1161
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
|
|
1054
1162
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
|
|
@@ -1086,11 +1194,11 @@ function LivePanel({ active: active2 }) {
|
|
|
1086
1194
|
/* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
|
|
1087
1195
|
pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
|
|
1088
1196
|
pickable.map((r, i) => {
|
|
1089
|
-
const
|
|
1197
|
+
const sel2 = mode === "pick" && i === pickIdx;
|
|
1090
1198
|
const st = STATUS_STYLE[r.status];
|
|
1091
|
-
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor:
|
|
1092
|
-
/* @__PURE__ */ jsxs2(Text2, { color:
|
|
1093
|
-
|
|
1199
|
+
return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel2 ? "#1c2f63" : void 0, bold: sel2 || r.isMe, children: [
|
|
1200
|
+
/* @__PURE__ */ jsxs2(Text2, { color: sel2 ? theme.accentBright : st.color, children: [
|
|
1201
|
+
sel2 ? "\u25B8" : st.dot,
|
|
1094
1202
|
" "
|
|
1095
1203
|
] }),
|
|
1096
1204
|
/* @__PURE__ */ jsx2(Text2, { color: st.color, children: st.label.padEnd(7) }),
|
|
@@ -1123,7 +1231,7 @@ function LivePanel({ active: active2 }) {
|
|
|
1123
1231
|
PaneTitle,
|
|
1124
1232
|
{
|
|
1125
1233
|
label: "TOOL STREAM",
|
|
1126
|
-
extra: `${visibleDesc.length}
|
|
1234
|
+
extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""} \xB7 \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r ${filter} \xB7 s`,
|
|
1127
1235
|
width: innerW
|
|
1128
1236
|
}
|
|
1129
1237
|
),
|
|
@@ -1137,12 +1245,12 @@ function LivePanel({ active: active2 }) {
|
|
|
1137
1245
|
spin,
|
|
1138
1246
|
" awaiting traffic\u2026"
|
|
1139
1247
|
] }) : null,
|
|
1140
|
-
windowed.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
|
|
1248
|
+
windowed.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: clampedScroll + i === selClamped }, e.id))
|
|
1141
1249
|
] }),
|
|
1142
1250
|
/* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
|
|
1143
1251
|
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
|
|
1144
1252
|
/* @__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"} ` }),
|
|
1145
|
-
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: "
|
|
1253
|
+
/* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r \xB7 / search \xB7 f \xB7 s \xB7 space copy \xB7 esc \xB7 q " })
|
|
1146
1254
|
] })
|
|
1147
1255
|
] });
|
|
1148
1256
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.78.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": {
|