@solongate/proxy 0.68.0 → 0.70.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.
Files changed (3) hide show
  1. package/dist/index.js +348 -156
  2. package/dist/tui/index.js +318 -126
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7020,12 +7020,13 @@ var init_hooks = __esm({
7020
7020
 
7021
7021
  // src/tui/panels/Live.tsx
7022
7022
  import { Box as Box2, Text as Text2, useInput } from "ink";
7023
- import { closeSync, openSync, readSync, statSync } from "fs";
7023
+ import { spawnSync } from "child_process";
7024
+ import { closeSync, mkdirSync as mkdirSync3, openSync, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
7024
7025
  import { homedir as homedir3 } from "os";
7025
- import { join as join5 } from "path";
7026
+ import { dirname, join as join5 } from "path";
7026
7027
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7027
7028
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7028
- function tailLines(file, maxBytes = 65536) {
7029
+ function tailLines(file, maxBytes = 131072) {
7029
7030
  try {
7030
7031
  const size = statSync(file).size;
7031
7032
  const start = Math.max(0, size - maxBytes);
@@ -7040,6 +7041,22 @@ function tailLines(file, maxBytes = 65536) {
7040
7041
  return [];
7041
7042
  }
7042
7043
  }
7044
+ function copyToClipboard(text) {
7045
+ const tools = [
7046
+ ["wl-copy", []],
7047
+ ["xclip", ["-selection", "clipboard"]],
7048
+ ["xsel", ["--clipboard", "--input"]]
7049
+ ];
7050
+ for (const [cmd, args] of tools) {
7051
+ try {
7052
+ const r = spawnSync(cmd, args, { input: text, timeout: 2e3 });
7053
+ if (r.status === 0) return cmd;
7054
+ } catch {
7055
+ }
7056
+ }
7057
+ process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
7058
+ return "osc52";
7059
+ }
7043
7060
  function cloudItem(e) {
7044
7061
  return {
7045
7062
  id: "c:" + e.id,
@@ -7049,7 +7066,11 @@ function cloudItem(e) {
7049
7066
  permission: (e.permission ?? "").slice(0, 4),
7050
7067
  detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
7051
7068
  dlp: !!e.dlp_matches?.length,
7052
- source: "cloud"
7069
+ source: "cloud",
7070
+ session: e.session_id ?? void 0,
7071
+ agent: e.agent_name ?? void 0,
7072
+ evalMs: e.evaluation_time_ms ?? void 0,
7073
+ rule: e.matched_rule_id ?? void 0
7053
7074
  };
7054
7075
  }
7055
7076
  function ColumnChart({ series, hot, height, width: width2, color, hotColor = theme.bad }) {
@@ -7068,7 +7089,7 @@ function ColumnChart({ series, hot, height, width: width2, color, hotColor = the
7068
7089
  else if (frac >= (r - 0.5) / height) ch = "\u2584";
7069
7090
  else if (r === 1) ch = "\u2581";
7070
7091
  else ch = " ";
7071
- const c2 = r === 1 && v === 0 ? "#233457" : hotPad?.[i] ? hotColor : color;
7092
+ const c2 = r === 1 && v === 0 ? DIM_FLOOR : hotPad?.[i] ? hotColor : color;
7072
7093
  const last = segs[segs.length - 1];
7073
7094
  if (last && last.color === c2) last.text += ch;
7074
7095
  else segs.push({ text: ch, color: c2 });
@@ -7084,7 +7105,7 @@ function HBar({ label, value, max, width: width2, color }) {
7084
7105
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7085
7106
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
7086
7107
  /* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width2, filled)) }),
7087
- /* @__PURE__ */ jsx2(Text2, { color: "#233457", children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
7108
+ /* @__PURE__ */ jsx2(Text2, { color: DIM_FLOOR, children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
7088
7109
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
7089
7110
  ] });
7090
7111
  }
@@ -7100,6 +7121,25 @@ function PaneTitle({ label, extra, width: width2 }) {
7100
7121
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
7101
7122
  ] });
7102
7123
  }
7124
+ function StreamLine({ e }) {
7125
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7126
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7127
+ "[",
7128
+ hhmmss(e.at),
7129
+ " "
7130
+ ] }),
7131
+ /* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
7132
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
7133
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
7134
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool, 12).padEnd(13) }),
7135
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
7136
+ /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
7137
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
7138
+ e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7139
+ e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
7140
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
7141
+ ] });
7142
+ }
7103
7143
  function LivePanel({ active: active2 }) {
7104
7144
  const [s, setS] = useState2(null);
7105
7145
  const [tsData, setTsData] = useState2(null);
@@ -7109,18 +7149,17 @@ function LivePanel({ active: active2 }) {
7109
7149
  const [localOn, setLocalOn] = useState2(null);
7110
7150
  const [ring, setRing] = useState2(null);
7111
7151
  const [log6, setLog] = useState2([]);
7152
+ const [filter, setFilter] = useState2("all");
7153
+ const [scroll, setScroll] = useState2(0);
7154
+ const [toast, setToast] = useState2(null);
7155
+ const [mode, setMode] = useState2("stream");
7156
+ const [pickIdx, setPickIdx] = useState2(0);
7157
+ const [detail, setDetail] = useState2(null);
7158
+ const [detailCloud, setDetailCloud] = useState2([]);
7159
+ const [detailScroll, setDetailScroll] = useState2(0);
7112
7160
  const seenRef = useRef2(/* @__PURE__ */ new Set());
7113
7161
  const lastLocalTs = useRef2(0);
7114
7162
  const pausedUntil = useRef2(0);
7115
- const [frozen, setFrozen] = useState2(false);
7116
- const frozenRef = useRef2(false);
7117
- frozenRef.current = frozen;
7118
- useInput(
7119
- (input) => {
7120
- if (input === "p") setFrozen((f) => !f);
7121
- },
7122
- { isActive: active2 }
7123
- );
7124
7163
  const pushLog = useCallback2((msg, level = "ok") => {
7125
7164
  setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
7126
7165
  }, []);
@@ -7136,9 +7175,8 @@ function LivePanel({ active: active2 }) {
7136
7175
  },
7137
7176
  [pushLog]
7138
7177
  );
7139
- const paused = () => frozenRef.current || Date.now() < pausedUntil.current;
7178
+ const paused = () => Date.now() < pausedUntil.current;
7140
7179
  const pollLocal = useCallback2(() => {
7141
- if (frozenRef.current) return;
7142
7180
  const lines = tailLines(LOCAL_LOG);
7143
7181
  if (!lines.length) {
7144
7182
  setLocalOn((prev) => prev === null ? false : prev);
@@ -7161,7 +7199,9 @@ function LivePanel({ active: active2 }) {
7161
7199
  dlp: !!j.dlp,
7162
7200
  source: "local",
7163
7201
  session: j.session_id,
7164
- agent: j.agent_name
7202
+ agent: j.agent_name,
7203
+ evalMs: j.evaluation_time_ms,
7204
+ rule: j.matched_rule_id
7165
7205
  });
7166
7206
  } catch {
7167
7207
  }
@@ -7170,9 +7210,7 @@ function LivePanel({ active: active2 }) {
7170
7210
  lastLocalTs.current = fresh[fresh.length - 1].at;
7171
7211
  setLocalBuf((prev) => [...prev, ...fresh].slice(-400));
7172
7212
  const denies = fresh.filter((f) => f.decision !== "ALLOW").length;
7173
- if (lastLocalTs.current !== fresh[0].at || fresh.length < 10) {
7174
- pushLog(`local +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
7175
- }
7213
+ if (fresh.length < 10) pushLog(`local +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
7176
7214
  }
7177
7215
  const ringLines = tailLines(RING, 8192).slice(-30);
7178
7216
  if (ringLines.length) {
@@ -7209,12 +7247,11 @@ function LivePanel({ active: active2 }) {
7209
7247
  const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
7210
7248
  const firstLoad = seenRef.current.size === 0 && fresh.length > 1;
7211
7249
  for (const e of fresh) seenRef.current.add(e.id);
7212
- if (fresh.length) {
7213
- setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
7214
- }
7250
+ if (fresh.length) setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
7215
7251
  const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
7216
7252
  if (firstLoad) pushLog(`cloud link up \xB7 api ${ms}ms \xB7 ${fresh.length} calls`);
7217
- else pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : fresh.length ? "warn" : "ok");
7253
+ else if (fresh.length) pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
7254
+ else pushLog(`api ${ms}ms \xB7 idle`, "ok");
7218
7255
  } catch (e) {
7219
7256
  onApiError(e);
7220
7257
  }
@@ -7251,30 +7288,50 @@ function LivePanel({ active: active2 }) {
7251
7288
  }, [active2, pollFeed, pollMedium, pollSlow]);
7252
7289
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7253
7290
  const insights = useLoader(() => api.stats.securityInsights(7));
7291
+ const guard = useLoader(() => api.settings.getGuardStatus());
7254
7292
  usePoll(() => {
7255
7293
  if (!paused()) sessions.reload();
7256
7294
  }, 1e4, active2);
7257
7295
  usePoll(() => {
7258
7296
  if (!paused()) insights.reload();
7259
7297
  }, 3e4, active2);
7298
+ usePoll(() => {
7299
+ if (!paused()) guard.reload();
7300
+ }, 6e4, active2);
7301
+ useEffect2(() => {
7302
+ if (!detail) return;
7303
+ setDetailCloud([]);
7304
+ setDetailScroll(0);
7305
+ let alive = true;
7306
+ api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
7307
+ if (alive) setDetailCloud(r.entries.map(cloudItem));
7308
+ }).catch(() => {
7309
+ });
7310
+ return () => {
7311
+ alive = false;
7312
+ };
7313
+ }, [detail]);
7260
7314
  const [tick, setTick] = useState2(0);
7261
7315
  useEffect2(() => {
7262
- if (!active2 || frozen) return;
7263
- const t = setInterval(() => setTick((n) => n + 1), 200);
7316
+ if (!active2) return;
7317
+ const t = setInterval(() => setTick((n) => n + 1), 500);
7264
7318
  return () => clearInterval(t);
7265
- }, [active2, frozen]);
7319
+ }, [active2]);
7266
7320
  const startRef = useRef2(Date.now());
7267
7321
  const cols = process.stdout.columns ?? 100;
7268
7322
  const rows = process.stdout.rows ?? 30;
7269
7323
  const ins = insights.data ?? {};
7270
7324
  const spin = SPIN[tick % SPIN.length];
7271
- const points = tsData?.timeseries ?? [];
7272
- const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
7273
7325
  const nowMs = Date.now();
7326
+ const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
7327
+ const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => e.source === filter);
7328
+ const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
7329
+ const points = tsData?.timeseries ?? [];
7330
+ const nowMin = Math.floor(nowMs / 6e4);
7274
7331
  const minuteCounts = new Array(60).fill(0);
7275
7332
  const minuteHot = new Array(60).fill(false);
7276
7333
  for (const e of mergedAll) {
7277
- const idx = 59 - Math.floor((nowMs - e.at) / 6e4);
7334
+ const idx = 59 - (nowMin - Math.floor(e.at / 6e4));
7278
7335
  if (idx >= 0 && idx < 60) {
7279
7336
  minuteCounts[idx]++;
7280
7337
  if (e.decision !== "ALLOW") minuteHot[idx] = true;
@@ -7286,14 +7343,14 @@ function LivePanel({ active: active2 }) {
7286
7343
  const trafficLabel = localTraffic ? "calls/min \xB7 60m \xB7 local+cloud" : "24h \xB7 cloud";
7287
7344
  const rl = ins.layers?.rateLimit;
7288
7345
  const dl = ins.layers?.dlp;
7289
- const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
7346
+ const gh = ins.layers?.ghost;
7347
+ const minuteNow = minuteCounts[59] ?? 0;
7290
7348
  const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
7291
7349
  const maxDlpBar = dlpBars[0]?.count ?? 1;
7292
- const merged = mergedAll;
7293
- const denials = merged.filter((e) => e.decision !== "ALLOW");
7294
- const lastDeny = denials[denials.length - 1];
7350
+ const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
7351
+ const lastDeny = denialsAll[denialsAll.length - 1];
7295
7352
  const toolCounts = /* @__PURE__ */ new Map();
7296
- for (const e of merged) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
7353
+ for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
7297
7354
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7298
7355
  const latNow = lat[lat.length - 1] ?? 0;
7299
7356
  const sortedLat = [...lat].sort((a, b) => a - b);
@@ -7312,22 +7369,174 @@ function LivePanel({ active: active2 }) {
7312
7369
  if (e.agent) cur.agent = e.agent;
7313
7370
  localSess.set(e.session, cur);
7314
7371
  }
7315
- const localSessList = [...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt);
7372
+ const combinedSess = [
7373
+ ...[...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 })),
7374
+ ...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
7375
+ source: "cloud",
7376
+ id: a.session_id,
7377
+ agent: a.agent_name ?? a.session_id,
7378
+ calls: a.total_calls,
7379
+ denies: a.denied_calls,
7380
+ lastAt: Date.parse(a.last_seen_at),
7381
+ trust: a.trust_score,
7382
+ isMe: false
7383
+ }))
7384
+ ];
7316
7385
  const chartH = rows >= 36 ? 6 : 4;
7317
- const colH = rows >= 32 ? 6 : 5;
7318
- const streamRows = Math.max(4, rows - 6 - chartH - colH - (localOn === false ? 1 : 0));
7319
- const tail = merged.slice(-streamRows);
7386
+ const colH = rows >= 32 ? 7 : 5;
7387
+ const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
7320
7388
  const innerW = cols - 2;
7321
7389
  const leftW = Math.floor(innerW * 0.55);
7322
7390
  const rightW = innerW - leftW - 2;
7323
7391
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
7392
+ const pickable = combinedSess.slice(0, colH - 1);
7393
+ const maxScroll = Math.max(0, visibleDesc.length - streamRows);
7394
+ const clampedScroll = Math.min(scroll, maxScroll);
7395
+ const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
7396
+ const detailEntries = detail ? (() => {
7397
+ const seen = /* @__PURE__ */ new Set();
7398
+ const all = [];
7399
+ for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
7400
+ const key = `${e.at}:${e.tool}:${e.decision}`;
7401
+ if (seen.has(key)) continue;
7402
+ seen.add(key);
7403
+ all.push(e);
7404
+ }
7405
+ return all.sort((a, b) => b.at - a.at);
7406
+ })() : [];
7407
+ useInput(
7408
+ (input, key) => {
7409
+ if (mode === "detail") {
7410
+ const maxD = Math.max(0, detailEntries.length - (rows - 10));
7411
+ if (key.leftArrow) {
7412
+ setMode("stream");
7413
+ setDetail(null);
7414
+ } else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
7415
+ else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
7416
+ else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
7417
+ else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
7418
+ return;
7419
+ }
7420
+ if (mode === "pick") {
7421
+ if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
7422
+ else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
7423
+ else if (key.return) {
7424
+ const row = pickable[pickIdx];
7425
+ if (row) {
7426
+ setDetail(row);
7427
+ setMode("detail");
7428
+ }
7429
+ } else if (input === "s" || key.leftArrow) setMode("stream");
7430
+ return;
7431
+ }
7432
+ if (input === "f") {
7433
+ setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
7434
+ setScroll(0);
7435
+ } else if (input === "s") {
7436
+ setPickIdx(0);
7437
+ setMode("pick");
7438
+ } else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
7439
+ else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
7440
+ else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
7441
+ else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
7442
+ else if (input === "c") {
7443
+ const dump = visibleDesc.map(
7444
+ (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}`
7445
+ ).join("\n");
7446
+ let via = "file";
7447
+ try {
7448
+ mkdirSync3(dirname(DUMP), { recursive: true });
7449
+ writeFileSync3(DUMP, dump + "\n");
7450
+ } catch {
7451
+ }
7452
+ via = copyToClipboard(dump);
7453
+ setToast({ msg: `copied ${visibleDesc.length} lines via ${via} + ${DUMP}`, until: Date.now() + 5e3 });
7454
+ }
7455
+ },
7456
+ { isActive: active2 }
7457
+ );
7324
7458
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7459
+ void sessionDot;
7460
+ if (s === null && localOn === null) {
7461
+ return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7462
+ spin,
7463
+ " connecting\u2026"
7464
+ ] });
7465
+ }
7466
+ const titleBar = /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7467
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
7468
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
7469
+ 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 " }),
7470
+ toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
7471
+ ] });
7472
+ if (mode === "detail" && detail) {
7473
+ const d = detailEntries;
7474
+ const allow = d.filter((e) => e.decision === "ALLOW").length;
7475
+ const deny = d.length - allow;
7476
+ const dlpN = d.filter((e) => e.dlp).length;
7477
+ const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
7478
+ const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
7479
+ const tCounts = /* @__PURE__ */ new Map();
7480
+ const pCounts = /* @__PURE__ */ new Map();
7481
+ for (const e of d) {
7482
+ tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
7483
+ if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
7484
+ }
7485
+ const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
7486
+ const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
7487
+ const first = d[d.length - 1]?.at;
7488
+ const last = d[0]?.at;
7489
+ const tlRows = Math.max(4, rows - 10);
7490
+ const maxD = Math.max(0, d.length - tlRows);
7491
+ const dScroll = Math.min(detailScroll, maxD);
7492
+ const tl = d.slice(dScroll, dScroll + tlRows);
7493
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7494
+ titleBar,
7495
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7496
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
7497
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
7498
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate2(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
7499
+ detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
7500
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
7501
+ ] }),
7502
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7503
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
7504
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
7505
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
7506
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
7507
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7508
+ /* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
7509
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
7510
+ /* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
7511
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
7512
+ /* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
7513
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
7514
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
7515
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
7516
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
7517
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7518
+ ] }),
7519
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7520
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
7521
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
7522
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
7523
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
7524
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7525
+ ] }),
7526
+ /* @__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 }),
7527
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
7528
+ tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
7529
+ tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e }, e.id))
7530
+ ] }),
7531
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7532
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
7533
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
7534
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
7535
+ ] })
7536
+ ] });
7537
+ }
7325
7538
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7326
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7327
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
7328
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
7329
- frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " PAUSED \xB7 copy freely \xB7 p resume " }) : 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 " })
7330
- ] }),
7539
+ titleBar,
7331
7540
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7332
7541
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
7333
7542
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
@@ -7336,16 +7545,20 @@ function LivePanel({ active: active2 }) {
7336
7545
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7337
7546
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
7338
7547
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 local " }),
7339
- localOn ? /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "\u2713 streaming" }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "off" }),
7340
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
7548
+ localOn ? /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "\u2713" }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "off" }),
7549
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 rl " }),
7341
7550
  /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
7342
7551
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
7343
7552
  /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
7344
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
7345
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
7553
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
7554
+ /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
7555
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
7556
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
7557
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
7558
+ /* @__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" }),
7346
7559
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7347
7560
  ] }),
7348
- /* @__PURE__ */ jsxs2(Box2, { children: [
7561
+ /* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
7349
7562
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
7350
7563
  /* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
7351
7564
  /* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
@@ -7355,77 +7568,56 @@ function LivePanel({ active: active2 }) {
7355
7568
  /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0", hotColor: "#ffb454" })
7356
7569
  ] })
7357
7570
  ] }),
7358
- /* @__PURE__ */ jsxs2(Box2, { children: [
7359
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7571
+ /* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
7572
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
7360
7573
  /* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
7361
7574
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7362
7575
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
7363
7576
  /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
7364
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
7577
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute}m ${rl.perHour || "\u2014"}h ${rl.perDay || "\u2014"}d` : "no limits set" })
7365
7578
  ] }),
7366
- rl?.perMinute ? /* @__PURE__ */ jsx2(HBar, { label: "load", value: minuteNow, max: rl.perMinute, width: Math.max(6, colW - 16), color: minuteNow > rl.perMinute ? theme.bad : theme.accent }) : null,
7579
+ rl?.perMinute ? /* @__PURE__ */ jsx2(HBar, { label: "load/min", value: minuteNow, max: rl.perMinute, width: Math.max(6, colW - 16), color: minuteNow > rl.perMinute ? theme.bad : theme.accent }) : null,
7367
7580
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7368
7581
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
7369
7582
  /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
7370
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7371
- (dl?.patterns ?? []).length,
7372
- " patterns armed"
7373
- ] })
7583
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
7374
7584
  ] }),
7375
- dlpBars.length ? dlpBars.map((d) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(d.pattern, 10), value: d.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " no dlp hits in last 7 days" }),
7376
- ring ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7585
+ 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" }),
7586
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7377
7587
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
7378
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "local \u2713" }),
7379
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` eval avg ${ring.avgMs}ms \xB7 ${ring.count} recent` })
7380
- ] }) : null
7588
+ ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
7589
+ "local \u2713 ",
7590
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `eval avg ${ring.avgMs}ms` })
7591
+ ] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no local ring in cwd" })
7592
+ ] })
7381
7593
  ] }),
7382
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7383
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${localSessList.length} local \xB7 ${sessCounts?.active ?? 0} cloud live`, width: colW }),
7384
- localSessList.length === 0 && sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7385
- localSessList.slice(0, Math.max(1, colH - 2)).map(([id, v]) => {
7386
- const fresh = nowMs - v.lastAt < 9e4;
7387
- const isMe = ring?.session === id;
7388
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7389
- /* @__PURE__ */ jsxs2(Text2, { color: fresh ? theme.ok : theme.dim, children: [
7390
- fresh ? "\u25CF" : "\u25CB",
7391
- " "
7392
- ] }),
7393
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "LOC " }),
7394
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: isMe, children: truncate2(isMe ? "this machine" : v.agent, 12).padEnd(13) }),
7395
- /* @__PURE__ */ jsxs2(Text2, { children: [
7396
- String(v.calls).padStart(4),
7397
- "c "
7398
- ] }),
7399
- /* @__PURE__ */ jsxs2(Text2, { color: v.denies ? theme.bad : theme.dim, children: [
7400
- String(v.denies).padStart(3),
7401
- "d "
7402
- ] }),
7403
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(v.lastAt) })
7404
- ] }, id);
7405
- }),
7406
- sess.slice(0, Math.max(0, colH - 1 - Math.min(localSessList.length, Math.max(1, colH - 2)))).map((a) => {
7407
- const dot = sessionDot(a.status);
7408
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7409
- /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
7410
- dot.ch,
7594
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
7595
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
7596
+ pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7597
+ pickable.map((r, i) => {
7598
+ const freshDot = nowMs - r.lastAt < 9e4;
7599
+ const sel = mode === "pick" && i === pickIdx;
7600
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
7601
+ /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
7602
+ sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
7411
7603
  " "
7412
7604
  ] }),
7413
- /* @__PURE__ */ jsx2(Text2, { color: "#4f6db8", children: "CLD " }),
7414
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(a.agent_name ?? a.session_id, 12).padEnd(13) }),
7605
+ /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
7606
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate2(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
7415
7607
  /* @__PURE__ */ jsxs2(Text2, { children: [
7416
- String(a.total_calls).padStart(4),
7608
+ String(r.calls).padStart(4),
7417
7609
  "c "
7418
7610
  ] }),
7419
- /* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
7420
- String(a.denied_calls).padStart(3),
7611
+ /* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
7612
+ String(r.denies).padStart(3),
7421
7613
  "d "
7422
7614
  ] }),
7423
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
7424
- ] }, a.session_id);
7615
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
7616
+ ] }, r.id);
7425
7617
  })
7426
7618
  ] }),
7427
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
7428
- /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
7619
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
7620
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "system heartbeat", width: colW }),
7429
7621
  log6.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7430
7622
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7431
7623
  hhmmss(l.ts),
@@ -7436,37 +7628,33 @@ function LivePanel({ active: active2 }) {
7436
7628
  ] }, l.ts + ":" + i))
7437
7629
  ] })
7438
7630
  ] }),
7439
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `local+cloud tail \xB7 ${merged.length} buffered`, width: innerW }),
7440
- localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7441
- /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
7442
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable in dashboard \u2192 Settings \u2192 Local logs; hooks then write ~/.solongate/local-logs (view them there with `solongate logs-server`)" })
7443
- ] }) : null,
7444
- tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7445
- spin,
7446
- " awaiting traffic\u2026"
7447
- ] }) : null,
7448
- tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7449
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7450
- "[",
7451
- hhmmss(e.at),
7452
- " "
7453
- ] }),
7454
- /* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
7455
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
7456
- /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
7457
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool, 14).padEnd(15) }),
7458
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
7459
- e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7460
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
7461
- ] }, e.id)),
7631
+ /* @__PURE__ */ jsx2(
7632
+ PaneTitle,
7633
+ {
7634
+ label: "TOOL STREAM",
7635
+ 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`,
7636
+ width: innerW
7637
+ }
7638
+ ),
7639
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
7640
+ localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7641
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
7642
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
7643
+ ] }) : null,
7644
+ windowed.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7645
+ spin,
7646
+ " awaiting traffic\u2026"
7647
+ ] }) : null,
7648
+ windowed.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e }, e.id))
7649
+ ] }),
7462
7650
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7463
7651
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
7464
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${merged.length} ` }),
7465
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " p pause/copy \xB7 esc menu \xB7 q quit " })
7652
+ /* @__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"} ` }),
7653
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7 s sessions \xB7 c copy \xB7 esc menu \xB7 q quit " })
7466
7654
  ] })
7467
7655
  ] });
7468
7656
  }
7469
- var SPIN, BG, LOCAL_LOG, RING, hhmmss, fmtUp;
7657
+ var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, DUMP, STREAM_KEEP, hhmmss, fmtUp, FILTERS;
7470
7658
  var init_Live = __esm({
7471
7659
  "src/tui/panels/Live.tsx"() {
7472
7660
  "use strict";
@@ -7475,8 +7663,11 @@ var init_Live = __esm({
7475
7663
  init_theme();
7476
7664
  SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7477
7665
  BG = "#12234f";
7666
+ DIM_FLOOR = "#233457";
7478
7667
  LOCAL_LOG = join5(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
7479
7668
  RING = join5(process.cwd(), ".solongate", ".eval-ring.jsonl");
7669
+ DUMP = join5(homedir3(), ".solongate", "live-stream.log");
7670
+ STREAM_KEEP = 100;
7480
7671
  hhmmss = (ts) => {
7481
7672
  const d = new Date(ts);
7482
7673
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -7486,6 +7677,7 @@ var init_Live = __esm({
7486
7677
  const p = (n) => String(n).padStart(2, "0");
7487
7678
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
7488
7679
  };
7680
+ FILTERS = ["all", "local", "cloud"];
7489
7681
  }
7490
7682
  });
7491
7683
 
@@ -9147,8 +9339,8 @@ __export(global_install_exports, {
9147
9339
  runGlobalRestore: () => runGlobalRestore,
9148
9340
  unlockProtected: () => unlockProtected
9149
9341
  });
9150
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3 } from "fs";
9151
- import { resolve as resolve4, join as join6, dirname } from "path";
9342
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
9343
+ import { resolve as resolve4, join as join6, dirname as dirname2 } from "path";
9152
9344
  import { homedir as homedir4 } from "os";
9153
9345
  import { fileURLToPath } from "url";
9154
9346
  import { createInterface } from "readline";
@@ -9260,13 +9452,13 @@ function runGlobalRestore() {
9260
9452
  unlockProtected();
9261
9453
  removeClaudeShim();
9262
9454
  if (existsSync4(p.backupPath)) {
9263
- writeFileSync3(p.settingsPath, readFileSync6(p.backupPath, "utf-8"));
9455
+ writeFileSync4(p.settingsPath, readFileSync6(p.backupPath, "utf-8"));
9264
9456
  console.log(` Restored ${p.settingsPath} from backup.`);
9265
9457
  } else if (existsSync4(p.settingsPath)) {
9266
9458
  try {
9267
9459
  const s = JSON.parse(readFileSync6(p.settingsPath, "utf-8"));
9268
9460
  delete s.hooks;
9269
- writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
9461
+ writeFileSync4(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
9270
9462
  console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
9271
9463
  } catch {
9272
9464
  }
@@ -9310,8 +9502,8 @@ function writeShimBlock(file, block) {
9310
9502
  if (content.length && !content.endsWith("\n")) content += "\n";
9311
9503
  content += block + "\n";
9312
9504
  }
9313
- mkdirSync3(dirname(file), { recursive: true });
9314
- writeFileSync3(file, content);
9505
+ mkdirSync4(dirname2(file), { recursive: true });
9506
+ writeFileSync4(file, content);
9315
9507
  }
9316
9508
  function installClaudeShim(shieldPath) {
9317
9509
  const real = resolveRealClaude();
@@ -9368,22 +9560,22 @@ async function runGlobalInstall(opts = {}) {
9368
9560
  process.exit(1);
9369
9561
  }
9370
9562
  const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
9371
- mkdirSync3(p.hooksDir, { recursive: true });
9372
- mkdirSync3(p.claudeDir, { recursive: true });
9563
+ mkdirSync4(p.hooksDir, { recursive: true });
9564
+ mkdirSync4(p.claudeDir, { recursive: true });
9373
9565
  unlockProtected();
9374
- writeFileSync3(join6(p.hooksDir, "guard.mjs"), readGuard());
9375
- writeFileSync3(join6(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
9376
- writeFileSync3(join6(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
9377
- writeFileSync3(join6(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
9566
+ writeFileSync4(join6(p.hooksDir, "guard.mjs"), readGuard());
9567
+ writeFileSync4(join6(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
9568
+ writeFileSync4(join6(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
9569
+ writeFileSync4(join6(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
9378
9570
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
9379
9571
  removeClaudeShim();
9380
- writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
9572
+ writeFileSync4(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
9381
9573
  console.log(` Wrote ${p.configPath}`);
9382
9574
  let existing = {};
9383
9575
  if (existsSync4(p.settingsPath)) {
9384
9576
  const raw = readFileSync6(p.settingsPath, "utf-8");
9385
9577
  if (!existsSync4(p.backupPath)) {
9386
- writeFileSync3(p.backupPath, raw);
9578
+ writeFileSync4(p.backupPath, raw);
9387
9579
  console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
9388
9580
  }
9389
9581
  try {
@@ -9404,7 +9596,7 @@ async function runGlobalInstall(opts = {}) {
9404
9596
  Stop: [{ matcher: "", hooks: [{ type: "command", command: `"${nodeBin}" "${stopAbs}" claude-code "Claude Code"` }] }]
9405
9597
  }
9406
9598
  };
9407
- writeFileSync3(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
9599
+ writeFileSync4(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
9408
9600
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
9409
9601
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
9410
9602
  lockProtected();
@@ -9418,7 +9610,7 @@ var __dirname, HOOKS_DIR, SHIM_BEGIN, SHIM_END;
9418
9610
  var init_global_install = __esm({
9419
9611
  "src/global-install.ts"() {
9420
9612
  "use strict";
9421
- __dirname = dirname(fileURLToPath(import.meta.url));
9613
+ __dirname = dirname2(fileURLToPath(import.meta.url));
9422
9614
  HOOKS_DIR = resolve4(__dirname, "..", "hooks");
9423
9615
  SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
9424
9616
  SHIM_END = "# <<< SolonGate shield <<<";
@@ -10068,7 +10260,7 @@ var init_logs_server = __esm({
10068
10260
 
10069
10261
  // src/inject.ts
10070
10262
  var inject_exports = {};
10071
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, existsSync as existsSync6, copyFileSync } from "fs";
10263
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync6, copyFileSync } from "fs";
10072
10264
  import { resolve as resolve7 } from "path";
10073
10265
  import { execSync } from "child_process";
10074
10266
  function parseInjectArgs(argv) {
@@ -10376,7 +10568,7 @@ async function main2() {
10376
10568
  log3("");
10377
10569
  log3(` Backup: ${backupPath}`);
10378
10570
  }
10379
- writeFileSync4(entryFile, result.modified);
10571
+ writeFileSync5(entryFile, result.modified);
10380
10572
  log3("");
10381
10573
  log3(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
10382
10574
  log3(" \u2502 SolonGate SDK injected successfully! \u2502");
@@ -10405,7 +10597,7 @@ var init_inject = __esm({
10405
10597
 
10406
10598
  // src/create.ts
10407
10599
  var create_exports = {};
10408
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
10600
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
10409
10601
  import { resolve as resolve8, join as join8 } from "path";
10410
10602
  import { execSync as execSync2 } from "child_process";
10411
10603
  function withSpinner(message, fn) {
@@ -10485,7 +10677,7 @@ EXAMPLES
10485
10677
  `);
10486
10678
  }
10487
10679
  function createProject(dir, name, _policy) {
10488
- writeFileSync5(
10680
+ writeFileSync6(
10489
10681
  join8(dir, "package.json"),
10490
10682
  JSON.stringify(
10491
10683
  {
@@ -10515,7 +10707,7 @@ function createProject(dir, name, _policy) {
10515
10707
  2
10516
10708
  ) + "\n"
10517
10709
  );
10518
- writeFileSync5(
10710
+ writeFileSync6(
10519
10711
  join8(dir, "tsconfig.json"),
10520
10712
  JSON.stringify(
10521
10713
  {
@@ -10536,8 +10728,8 @@ function createProject(dir, name, _policy) {
10536
10728
  2
10537
10729
  ) + "\n"
10538
10730
  );
10539
- mkdirSync4(join8(dir, "src"), { recursive: true });
10540
- writeFileSync5(
10731
+ mkdirSync5(join8(dir, "src"), { recursive: true });
10732
+ writeFileSync6(
10541
10733
  join8(dir, "src", "index.ts"),
10542
10734
  `#!/usr/bin/env node
10543
10735
 
@@ -10579,7 +10771,7 @@ console.log('');
10579
10771
  console.log('Press Ctrl+C to stop.');
10580
10772
  `
10581
10773
  );
10582
- writeFileSync5(
10774
+ writeFileSync6(
10583
10775
  join8(dir, ".mcp.json"),
10584
10776
  JSON.stringify(
10585
10777
  {
@@ -10597,12 +10789,12 @@ console.log('Press Ctrl+C to stop.');
10597
10789
  2
10598
10790
  ) + "\n"
10599
10791
  );
10600
- writeFileSync5(
10792
+ writeFileSync6(
10601
10793
  join8(dir, ".env"),
10602
10794
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
10603
10795
  `
10604
10796
  );
10605
- writeFileSync5(
10797
+ writeFileSync6(
10606
10798
  join8(dir, ".gitignore"),
10607
10799
  `node_modules/
10608
10800
  dist/
@@ -10622,7 +10814,7 @@ async function main3() {
10622
10814
  process.exit(1);
10623
10815
  }
10624
10816
  withSpinner(`Setting up ${opts.name}...`, () => {
10625
- mkdirSync4(dir, { recursive: true });
10817
+ mkdirSync5(dir, { recursive: true });
10626
10818
  createProject(dir, opts.name, opts.policy);
10627
10819
  });
10628
10820
  if (!opts.noInstall) {
@@ -10696,7 +10888,7 @@ var init_create = __esm({
10696
10888
 
10697
10889
  // src/pull-push.ts
10698
10890
  var pull_push_exports = {};
10699
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
10891
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync8 } from "fs";
10700
10892
  import { resolve as resolve9 } from "path";
10701
10893
  function loadEnv() {
10702
10894
  if (process.env.SOLONGATE_API_KEY) return;
@@ -10891,7 +11083,7 @@ async function pull(apiKey, file, policyId) {
10891
11083
  const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
10892
11084
  const { id: _id, ...policyWithoutId } = policy;
10893
11085
  const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
10894
- writeFileSync6(file, json, "utf-8");
11086
+ writeFileSync7(file, json, "utf-8");
10895
11087
  log5("");
10896
11088
  log5(green2(" Saved to: ") + file);
10897
11089
  log5(` ${dim2("Name:")} ${policy.name}`);