@solongate/proxy 0.69.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.
package/dist/index.js CHANGED
@@ -7020,6 +7020,7 @@ 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 { spawnSync } from "child_process";
7023
7024
  import { closeSync, mkdirSync as mkdirSync3, openSync, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
7024
7025
  import { homedir as homedir3 } from "os";
7025
7026
  import { dirname, join as join5 } from "path";
@@ -7040,8 +7041,21 @@ function tailLines(file, maxBytes = 131072) {
7040
7041
  return [];
7041
7042
  }
7042
7043
  }
7043
- function oscCopy(text) {
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
+ }
7044
7057
  process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
7058
+ return "osc52";
7045
7059
  }
7046
7060
  function cloudItem(e) {
7047
7061
  return {
@@ -7107,6 +7121,25 @@ function PaneTitle({ label, extra, width: width2 }) {
7107
7121
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
7108
7122
  ] });
7109
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
+ }
7110
7143
  function LivePanel({ active: active2 }) {
7111
7144
  const [s, setS] = useState2(null);
7112
7145
  const [tsData, setTsData] = useState2(null);
@@ -7119,6 +7152,11 @@ function LivePanel({ active: active2 }) {
7119
7152
  const [filter, setFilter] = useState2("all");
7120
7153
  const [scroll, setScroll] = useState2(0);
7121
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);
7122
7160
  const seenRef = useRef2(/* @__PURE__ */ new Set());
7123
7161
  const lastLocalTs = useRef2(0);
7124
7162
  const pausedUntil = useRef2(0);
@@ -7212,7 +7250,8 @@ function LivePanel({ active: active2 }) {
7212
7250
  if (fresh.length) setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
7213
7251
  const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
7214
7252
  if (firstLoad) pushLog(`cloud link up \xB7 api ${ms}ms \xB7 ${fresh.length} calls`);
7215
- else pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : 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");
7216
7255
  } catch (e) {
7217
7256
  onApiError(e);
7218
7257
  }
@@ -7259,6 +7298,19 @@ function LivePanel({ active: active2 }) {
7259
7298
  usePoll(() => {
7260
7299
  if (!paused()) guard.reload();
7261
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]);
7262
7314
  const [tick, setTick] = useState2(0);
7263
7315
  useEffect2(() => {
7264
7316
  if (!active2) return;
@@ -7273,12 +7325,13 @@ function LivePanel({ active: active2 }) {
7273
7325
  const nowMs = Date.now();
7274
7326
  const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
7275
7327
  const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => e.source === filter);
7276
- const visible = filtered.slice(-STREAM_KEEP);
7328
+ const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
7277
7329
  const points = tsData?.timeseries ?? [];
7330
+ const nowMin = Math.floor(nowMs / 6e4);
7278
7331
  const minuteCounts = new Array(60).fill(0);
7279
7332
  const minuteHot = new Array(60).fill(false);
7280
7333
  for (const e of mergedAll) {
7281
- const idx = 59 - Math.floor((nowMs - e.at) / 6e4);
7334
+ const idx = 59 - (nowMin - Math.floor(e.at / 6e4));
7282
7335
  if (idx >= 0 && idx < 60) {
7283
7336
  minuteCounts[idx]++;
7284
7337
  if (e.decision !== "ALLOW") minuteHot[idx] = true;
@@ -7294,8 +7347,8 @@ function LivePanel({ active: active2 }) {
7294
7347
  const minuteNow = minuteCounts[59] ?? 0;
7295
7348
  const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
7296
7349
  const maxDlpBar = dlpBars[0]?.count ?? 1;
7297
- const denials = mergedAll.filter((e) => e.decision !== "ALLOW");
7298
- const lastDeny = denials[denials.length - 1];
7350
+ const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
7351
+ const lastDeny = denialsAll[denialsAll.length - 1];
7299
7352
  const toolCounts = /* @__PURE__ */ new Map();
7300
7353
  for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
7301
7354
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
@@ -7316,7 +7369,19 @@ function LivePanel({ active: active2 }) {
7316
7369
  if (e.agent) cur.agent = e.agent;
7317
7370
  localSess.set(e.session, cur);
7318
7371
  }
7319
- 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
+ ];
7320
7385
  const chartH = rows >= 36 ? 6 : 4;
7321
7386
  const colH = rows >= 32 ? 7 : 5;
7322
7387
  const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
@@ -7324,48 +7389,154 @@ function LivePanel({ active: active2 }) {
7324
7389
  const leftW = Math.floor(innerW * 0.55);
7325
7390
  const rightW = innerW - leftW - 2;
7326
7391
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
7327
- const maxScroll = Math.max(0, visible.length - streamRows);
7392
+ const pickable = combinedSess.slice(0, colH - 1);
7393
+ const maxScroll = Math.max(0, visibleDesc.length - streamRows);
7328
7394
  const clampedScroll = Math.min(scroll, maxScroll);
7329
- const start = Math.max(0, visible.length - streamRows - clampedScroll);
7330
- const windowed = visible.slice(start, start + streamRows);
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
+ })() : [];
7331
7407
  useInput(
7332
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
+ }
7333
7432
  if (input === "f") {
7334
7433
  setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
7335
7434
  setScroll(0);
7336
- } else if (key.upArrow) setScroll((n) => Math.min(maxScroll, n + 1));
7337
- else if (key.downArrow) setScroll((n) => Math.max(0, n - 1));
7338
- else if (key.pageUp) setScroll((n) => Math.min(maxScroll, n + streamRows));
7339
- else if (key.pageDown) setScroll((n) => Math.max(0, n - streamRows));
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));
7340
7442
  else if (input === "c") {
7341
- const dump = visible.map(
7443
+ const dump = visibleDesc.map(
7342
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}`
7343
7445
  ).join("\n");
7446
+ let via = "file";
7344
7447
  try {
7345
7448
  mkdirSync3(dirname(DUMP), { recursive: true });
7346
7449
  writeFileSync3(DUMP, dump + "\n");
7347
7450
  } catch {
7348
7451
  }
7349
- oscCopy(dump);
7350
- setToast({ msg: `copied ${visible.length} lines \u2192 clipboard + ${DUMP}`, until: Date.now() + 4e3 });
7452
+ via = copyToClipboard(dump);
7453
+ setToast({ msg: `copied ${visibleDesc.length} lines via ${via} + ${DUMP}`, until: Date.now() + 5e3 });
7351
7454
  }
7352
7455
  },
7353
7456
  { isActive: active2 }
7354
7457
  );
7355
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;
7356
7460
  if (s === null && localOn === null) {
7357
7461
  return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7358
7462
  spin,
7359
7463
  " connecting\u2026"
7360
7464
  ] });
7361
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
+ }
7362
7538
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7363
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7364
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
7365
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
7366
- backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " }),
7367
- toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
7368
- ] }),
7539
+ titleBar,
7369
7540
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7370
7541
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
7371
7542
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
@@ -7382,7 +7553,7 @@ function LivePanel({ active: active2 }) {
7382
7553
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
7383
7554
  /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
7384
7555
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
7385
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${localSessList.length}loc ${sessCounts?.active ?? 0}\u25B2${sessCounts?.idle ?? 0}\u25CC` }),
7556
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
7386
7557
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
7387
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" }),
7388
7559
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
@@ -7411,7 +7582,7 @@ function LivePanel({ active: active2 }) {
7411
7582
  /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
7412
7583
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
7413
7584
  ] }),
7414
- 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" }),
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" }),
7415
7586
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7416
7587
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
7417
7588
  ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
@@ -7421,48 +7592,28 @@ function LivePanel({ active: active2 }) {
7421
7592
  ] })
7422
7593
  ] }),
7423
7594
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
7424
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${localSessList.length} local \xB7 ${sessCounts?.active ?? 0} cloud`, width: colW }),
7425
- localSessList.length === 0 && sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7426
- localSessList.slice(0, Math.max(1, colH - 2)).map(([id, v]) => {
7427
- const freshDot = nowMs - v.lastAt < 9e4;
7428
- const isMe = ring?.session === id;
7429
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7430
- /* @__PURE__ */ jsxs2(Text2, { color: freshDot ? theme.ok : theme.dim, children: [
7431
- freshDot ? "\u25CF" : "\u25CB",
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",
7432
7603
  " "
7433
7604
  ] }),
7434
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "LOC " }),
7435
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: isMe, children: truncate2(isMe ? "this machine" : v.agent, 12).padEnd(13) }),
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) }),
7436
7607
  /* @__PURE__ */ jsxs2(Text2, { children: [
7437
- String(v.calls).padStart(4),
7608
+ String(r.calls).padStart(4),
7438
7609
  "c "
7439
7610
  ] }),
7440
- /* @__PURE__ */ jsxs2(Text2, { color: v.denies ? theme.bad : theme.dim, children: [
7441
- String(v.denies).padStart(3),
7611
+ /* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
7612
+ String(r.denies).padStart(3),
7442
7613
  "d "
7443
7614
  ] }),
7444
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(v.lastAt) })
7445
- ] }, id);
7446
- }),
7447
- sess.slice(0, Math.max(0, colH - 1 - Math.min(localSessList.length, Math.max(1, colH - 2)))).map((a) => {
7448
- const dot = sessionDot(a.status);
7449
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7450
- /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
7451
- dot.ch,
7452
- " "
7453
- ] }),
7454
- /* @__PURE__ */ jsx2(Text2, { color: "#4f6db8", children: "CLD " }),
7455
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(a.agent_name ?? a.session_id, 12).padEnd(13) }),
7456
- /* @__PURE__ */ jsxs2(Text2, { children: [
7457
- String(a.total_calls).padStart(4),
7458
- "c "
7459
- ] }),
7460
- /* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
7461
- String(a.denied_calls).padStart(3),
7462
- "d "
7463
- ] }),
7464
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `t${a.trust_score} ` + ago(a.last_seen_at) })
7465
- ] }, a.session_id);
7615
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
7616
+ ] }, r.id);
7466
7617
  })
7467
7618
  ] }),
7468
7619
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
@@ -7481,7 +7632,7 @@ function LivePanel({ active: active2 }) {
7481
7632
  PaneTitle,
7482
7633
  {
7483
7634
  label: "TOOL STREAM",
7484
- extra: `last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25B2${clampedScroll} scrolled` : " \xB7 following"} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 c copy`,
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`,
7485
7636
  width: innerW
7486
7637
  }
7487
7638
  ),
@@ -7494,28 +7645,12 @@ function LivePanel({ active: active2 }) {
7494
7645
  spin,
7495
7646
  " awaiting traffic\u2026"
7496
7647
  ] }) : null,
7497
- windowed.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7498
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7499
- "[",
7500
- hhmmss(e.at),
7501
- " "
7502
- ] }),
7503
- /* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
7504
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
7505
- /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
7506
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool, 12).padEnd(13) }),
7507
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
7508
- /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
7509
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate2(e.agent ?? "-", 11).padEnd(12) }),
7510
- e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7511
- e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate2(e.rule, 14) + " " }) : null,
7512
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
7513
- ] }, e.id))
7648
+ windowed.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e }, e.id))
7514
7649
  ] }),
7515
7650
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7516
7651
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
7517
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld buffered \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
7518
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7 \u2191\u2193 scroll \xB7 c 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 " })
7519
7654
  ] })
7520
7655
  ] });
7521
7656
  }
package/dist/tui/index.js CHANGED
@@ -126,6 +126,7 @@ function KeyHints({ hints }) {
126
126
 
127
127
  // src/tui/panels/Live.tsx
128
128
  import { Box as Box2, Text as Text2, useInput } from "ink";
129
+ import { spawnSync } from "child_process";
129
130
  import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
130
131
  import { homedir as homedir2 } from "os";
131
132
  import { dirname, join as join2 } from "path";
@@ -465,8 +466,21 @@ function tailLines(file, maxBytes = 131072) {
465
466
  return [];
466
467
  }
467
468
  }
468
- function oscCopy(text) {
469
+ function copyToClipboard(text) {
470
+ const tools = [
471
+ ["wl-copy", []],
472
+ ["xclip", ["-selection", "clipboard"]],
473
+ ["xsel", ["--clipboard", "--input"]]
474
+ ];
475
+ for (const [cmd, args] of tools) {
476
+ try {
477
+ const r = spawnSync(cmd, args, { input: text, timeout: 2e3 });
478
+ if (r.status === 0) return cmd;
479
+ } catch {
480
+ }
481
+ }
469
482
  process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
483
+ return "osc52";
470
484
  }
471
485
  function cloudItem(e) {
472
486
  return {
@@ -532,6 +546,25 @@ function PaneTitle({ label, extra, width }) {
532
546
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
533
547
  ] });
534
548
  }
549
+ function StreamLine({ e }) {
550
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
551
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
552
+ "[",
553
+ hhmmss(e.at),
554
+ " "
555
+ ] }),
556
+ /* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
557
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
558
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
559
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
560
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
561
+ /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
562
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
563
+ e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
564
+ e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
565
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
566
+ ] });
567
+ }
535
568
  var FILTERS = ["all", "local", "cloud"];
536
569
  function LivePanel({ active: active2 }) {
537
570
  const [s, setS] = useState2(null);
@@ -545,6 +578,11 @@ function LivePanel({ active: active2 }) {
545
578
  const [filter, setFilter] = useState2("all");
546
579
  const [scroll, setScroll] = useState2(0);
547
580
  const [toast, setToast] = useState2(null);
581
+ const [mode, setMode] = useState2("stream");
582
+ const [pickIdx, setPickIdx] = useState2(0);
583
+ const [detail, setDetail] = useState2(null);
584
+ const [detailCloud, setDetailCloud] = useState2([]);
585
+ const [detailScroll, setDetailScroll] = useState2(0);
548
586
  const seenRef = useRef2(/* @__PURE__ */ new Set());
549
587
  const lastLocalTs = useRef2(0);
550
588
  const pausedUntil = useRef2(0);
@@ -638,7 +676,8 @@ function LivePanel({ active: active2 }) {
638
676
  if (fresh.length) setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
639
677
  const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
640
678
  if (firstLoad) pushLog(`cloud link up \xB7 api ${ms}ms \xB7 ${fresh.length} calls`);
641
- else pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : fresh.length ? "warn" : "ok");
679
+ else if (fresh.length) pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
680
+ else pushLog(`api ${ms}ms \xB7 idle`, "ok");
642
681
  } catch (e) {
643
682
  onApiError(e);
644
683
  }
@@ -685,6 +724,19 @@ function LivePanel({ active: active2 }) {
685
724
  usePoll(() => {
686
725
  if (!paused()) guard.reload();
687
726
  }, 6e4, active2);
727
+ useEffect2(() => {
728
+ if (!detail) return;
729
+ setDetailCloud([]);
730
+ setDetailScroll(0);
731
+ let alive = true;
732
+ api.audit.list({ session_id: detail.id, limit: 100 }).then((r) => {
733
+ if (alive) setDetailCloud(r.entries.map(cloudItem));
734
+ }).catch(() => {
735
+ });
736
+ return () => {
737
+ alive = false;
738
+ };
739
+ }, [detail]);
688
740
  const [tick, setTick] = useState2(0);
689
741
  useEffect2(() => {
690
742
  if (!active2) return;
@@ -699,12 +751,13 @@ function LivePanel({ active: active2 }) {
699
751
  const nowMs = Date.now();
700
752
  const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
701
753
  const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => e.source === filter);
702
- const visible = filtered.slice(-STREAM_KEEP);
754
+ const visibleDesc = filtered.slice(-STREAM_KEEP).reverse();
703
755
  const points = tsData?.timeseries ?? [];
756
+ const nowMin = Math.floor(nowMs / 6e4);
704
757
  const minuteCounts = new Array(60).fill(0);
705
758
  const minuteHot = new Array(60).fill(false);
706
759
  for (const e of mergedAll) {
707
- const idx = 59 - Math.floor((nowMs - e.at) / 6e4);
760
+ const idx = 59 - (nowMin - Math.floor(e.at / 6e4));
708
761
  if (idx >= 0 && idx < 60) {
709
762
  minuteCounts[idx]++;
710
763
  if (e.decision !== "ALLOW") minuteHot[idx] = true;
@@ -720,8 +773,8 @@ function LivePanel({ active: active2 }) {
720
773
  const minuteNow = minuteCounts[59] ?? 0;
721
774
  const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
722
775
  const maxDlpBar = dlpBars[0]?.count ?? 1;
723
- const denials = mergedAll.filter((e) => e.decision !== "ALLOW");
724
- const lastDeny = denials[denials.length - 1];
776
+ const denialsAll = mergedAll.filter((e) => e.decision !== "ALLOW");
777
+ const lastDeny = denialsAll[denialsAll.length - 1];
725
778
  const toolCounts = /* @__PURE__ */ new Map();
726
779
  for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
727
780
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
@@ -742,7 +795,19 @@ function LivePanel({ active: active2 }) {
742
795
  if (e.agent) cur.agent = e.agent;
743
796
  localSess.set(e.session, cur);
744
797
  }
745
- const localSessList = [...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt);
798
+ const combinedSess = [
799
+ ...[...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 })),
800
+ ...sess.filter((a) => !localSess.has(a.session_id)).map((a) => ({
801
+ source: "cloud",
802
+ id: a.session_id,
803
+ agent: a.agent_name ?? a.session_id,
804
+ calls: a.total_calls,
805
+ denies: a.denied_calls,
806
+ lastAt: Date.parse(a.last_seen_at),
807
+ trust: a.trust_score,
808
+ isMe: false
809
+ }))
810
+ ];
746
811
  const chartH = rows >= 36 ? 6 : 4;
747
812
  const colH = rows >= 32 ? 7 : 5;
748
813
  const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
@@ -750,48 +815,154 @@ function LivePanel({ active: active2 }) {
750
815
  const leftW = Math.floor(innerW * 0.55);
751
816
  const rightW = innerW - leftW - 2;
752
817
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
753
- const maxScroll = Math.max(0, visible.length - streamRows);
818
+ const pickable = combinedSess.slice(0, colH - 1);
819
+ const maxScroll = Math.max(0, visibleDesc.length - streamRows);
754
820
  const clampedScroll = Math.min(scroll, maxScroll);
755
- const start = Math.max(0, visible.length - streamRows - clampedScroll);
756
- const windowed = visible.slice(start, start + streamRows);
821
+ const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamRows);
822
+ const detailEntries = detail ? (() => {
823
+ const seen = /* @__PURE__ */ new Set();
824
+ const all = [];
825
+ for (const e of [...detailCloud, ...mergedAll.filter((x) => x.session === detail.id)]) {
826
+ const key = `${e.at}:${e.tool}:${e.decision}`;
827
+ if (seen.has(key)) continue;
828
+ seen.add(key);
829
+ all.push(e);
830
+ }
831
+ return all.sort((a, b) => b.at - a.at);
832
+ })() : [];
757
833
  useInput(
758
834
  (input, key) => {
835
+ if (mode === "detail") {
836
+ const maxD = Math.max(0, detailEntries.length - (rows - 10));
837
+ if (key.leftArrow) {
838
+ setMode("stream");
839
+ setDetail(null);
840
+ } else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
841
+ else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
842
+ else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
843
+ else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
844
+ return;
845
+ }
846
+ if (mode === "pick") {
847
+ if (key.upArrow) setPickIdx((n) => Math.max(0, n - 1));
848
+ else if (key.downArrow) setPickIdx((n) => Math.min(pickable.length - 1, n + 1));
849
+ else if (key.return) {
850
+ const row = pickable[pickIdx];
851
+ if (row) {
852
+ setDetail(row);
853
+ setMode("detail");
854
+ }
855
+ } else if (input === "s" || key.leftArrow) setMode("stream");
856
+ return;
857
+ }
759
858
  if (input === "f") {
760
859
  setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
761
860
  setScroll(0);
762
- } else if (key.upArrow) setScroll((n) => Math.min(maxScroll, n + 1));
763
- else if (key.downArrow) setScroll((n) => Math.max(0, n - 1));
764
- else if (key.pageUp) setScroll((n) => Math.min(maxScroll, n + streamRows));
765
- else if (key.pageDown) setScroll((n) => Math.max(0, n - streamRows));
861
+ } else if (input === "s") {
862
+ setPickIdx(0);
863
+ setMode("pick");
864
+ } else if (key.downArrow) setScroll((n) => Math.min(maxScroll, n + 1));
865
+ else if (key.upArrow) setScroll((n) => Math.max(0, n - 1));
866
+ else if (key.pageDown) setScroll((n) => Math.min(maxScroll, n + streamRows));
867
+ else if (key.pageUp) setScroll((n) => Math.max(0, n - streamRows));
766
868
  else if (input === "c") {
767
- const dump = visible.map(
869
+ const dump = visibleDesc.map(
768
870
  (e) => `[${new Date(e.at).toISOString()}] ${e.source.toUpperCase().padEnd(5)} ${e.decision.padEnd(6)} ${e.tool.padEnd(16)} ${e.permission.padEnd(5)} ${e.evalMs != null ? String(e.evalMs).padStart(5) + "ms " : " "}${(e.agent ?? "-").padEnd(14)} ${e.session ? e.session.slice(0, 8) : "- "} ${e.detail}`
769
871
  ).join("\n");
872
+ let via = "file";
770
873
  try {
771
874
  mkdirSync(dirname(DUMP), { recursive: true });
772
875
  writeFileSync(DUMP, dump + "\n");
773
876
  } catch {
774
877
  }
775
- oscCopy(dump);
776
- setToast({ msg: `copied ${visible.length} lines \u2192 clipboard + ${DUMP}`, until: Date.now() + 4e3 });
878
+ via = copyToClipboard(dump);
879
+ setToast({ msg: `copied ${visibleDesc.length} lines via ${via} + ${DUMP}`, until: Date.now() + 5e3 });
777
880
  }
778
881
  },
779
882
  { isActive: active2 }
780
883
  );
781
884
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
885
+ void sessionDot;
782
886
  if (s === null && localOn === null) {
783
887
  return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
784
888
  spin,
785
889
  " connecting\u2026"
786
890
  ] });
787
891
  }
892
+ const titleBar = /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
893
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
894
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
895
+ 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 " }),
896
+ toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
897
+ ] });
898
+ if (mode === "detail" && detail) {
899
+ const d = detailEntries;
900
+ const allow = d.filter((e) => e.decision === "ALLOW").length;
901
+ const deny = d.length - allow;
902
+ const dlpN = d.filter((e) => e.dlp).length;
903
+ const evals = d.filter((e) => e.evalMs != null).map((e) => e.evalMs);
904
+ const evalAvg = evals.length ? Math.round(evals.reduce((a, b) => a + b, 0) / evals.length) : null;
905
+ const tCounts = /* @__PURE__ */ new Map();
906
+ const pCounts = /* @__PURE__ */ new Map();
907
+ for (const e of d) {
908
+ tCounts.set(e.tool, (tCounts.get(e.tool) ?? 0) + 1);
909
+ if (e.permission) pCounts.set(e.permission, (pCounts.get(e.permission) ?? 0) + 1);
910
+ }
911
+ const top = [...tCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
912
+ const perms = [...pCounts.entries()].sort((a, b) => b[1] - a[1]);
913
+ const first = d[d.length - 1]?.at;
914
+ const last = d[0]?.at;
915
+ const tlRows = Math.max(4, rows - 10);
916
+ const maxD = Math.max(0, d.length - tlRows);
917
+ const dScroll = Math.min(detailScroll, maxD);
918
+ const tl = d.slice(dScroll, dScroll + tlRows);
919
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
920
+ titleBar,
921
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
922
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: ` SESSION ${detail.id.slice(0, 8)} ` }),
923
+ /* @__PURE__ */ jsx2(Text2, { color: detail.source === "local" ? theme.ok : "#4f6db8", children: " " + detail.source.toUpperCase() }),
924
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: " " + truncate(detail.isMe ? detail.agent + " (this machine)" : detail.agent, 34) }),
925
+ detail.trust != null ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` trust ${detail.trust}/100` }) : null,
926
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
927
+ ] }),
928
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
929
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
930
+ /* @__PURE__ */ jsx2(Text2, { bold: true, children: d.length }),
931
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
932
+ /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: allow }),
933
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
934
+ /* @__PURE__ */ jsx2(Text2, { color: deny ? theme.bad : theme.dim, bold: deny > 0, children: deny }),
935
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
936
+ /* @__PURE__ */ jsx2(Text2, { color: dlpN ? theme.bad : theme.dim, children: dlpN }),
937
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 avg eval " }),
938
+ /* @__PURE__ */ jsx2(Text2, { children: evalAvg != null ? `${evalAvg}ms` : "\u2014" }),
939
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 first " }),
940
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: first ? `${hhmmss(first)} (${ago(first)} ago)` : "\u2014" }),
941
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 last " }),
942
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: last ? `${hhmmss(last)} (${ago(last)} ago)` : "\u2014" }),
943
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
944
+ ] }),
945
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
946
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 tools " }),
947
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: top.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014" }),
948
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perms " }),
949
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
950
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
951
+ ] }),
952
+ /* @__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 }),
953
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
954
+ tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
955
+ tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e }, e.id))
956
+ ] }),
957
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
958
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
959
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
960
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
961
+ ] })
962
+ ] });
963
+ }
788
964
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
789
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
790
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
791
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
792
- backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " }),
793
- toast && nowMs < toast.until ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: ` ${toast.msg} ` }) : null
794
- ] }),
965
+ titleBar,
795
966
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
796
967
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
797
968
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
@@ -808,7 +979,7 @@ function LivePanel({ active: active2 }) {
808
979
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
809
980
  /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
810
981
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sess " }),
811
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${localSessList.length}loc ${sessCounts?.active ?? 0}\u25B2${sessCounts?.idle ?? 0}\u25CC` }),
982
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: `${combinedSess.length} (${sessCounts?.active ?? 0} live)` }),
812
983
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
813
984
  /* @__PURE__ */ jsx2(Text2, { color: guard.data?.up_to_date ? theme.ok : theme.warn, children: guard.data ? `v${guard.data.installed ?? "?"}${guard.data.up_to_date ? "" : "\u2192v" + guard.data.latest} ${guard.data.device_count}dev` : "\xB7" }),
814
985
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
@@ -837,7 +1008,7 @@ function LivePanel({ active: active2 }) {
837
1008
  /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
838
1009
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
839
1010
  ] }),
840
- dlpBars.length ? dlpBars.map((d) => /* @__PURE__ */ jsx2(HBar, { label: truncate(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" }),
1011
+ dlpBars.length ? dlpBars.map((d2) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d2.pattern, 10), value: d2.count, max: maxDlpBar, width: Math.max(6, colW - 16), color: theme.bad }, d2.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " no dlp hits in last 7 days" }),
841
1012
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
842
1013
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
843
1014
  ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
@@ -847,48 +1018,28 @@ function LivePanel({ active: active2 }) {
847
1018
  ] })
848
1019
  ] }),
849
1020
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
850
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${localSessList.length} local \xB7 ${sessCounts?.active ?? 0} cloud`, width: colW }),
851
- localSessList.length === 0 && sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
852
- localSessList.slice(0, Math.max(1, colH - 2)).map(([id, v]) => {
853
- const freshDot = nowMs - v.lastAt < 9e4;
854
- const isMe = ring?.session === id;
855
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
856
- /* @__PURE__ */ jsxs2(Text2, { color: freshDot ? theme.ok : theme.dim, children: [
857
- freshDot ? "\u25CF" : "\u25CB",
1021
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: mode === "pick" ? "\u2191\u2193 pick \xB7 enter open" : `${combinedSess.length} \xB7 s = inspect`, width: colW }),
1022
+ pickable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
1023
+ pickable.map((r, i) => {
1024
+ const freshDot = nowMs - r.lastAt < 9e4;
1025
+ const sel = mode === "pick" && i === pickIdx;
1026
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: sel ? "#1c2f63" : void 0, bold: sel || r.isMe, children: [
1027
+ /* @__PURE__ */ jsxs2(Text2, { color: sel ? theme.accentBright : freshDot ? theme.ok : theme.dim, children: [
1028
+ sel ? "\u25B8" : freshDot ? "\u25CF" : "\u25CB",
858
1029
  " "
859
1030
  ] }),
860
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "LOC " }),
861
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, bold: isMe, children: truncate(isMe ? "this machine" : v.agent, 12).padEnd(13) }),
1031
+ /* @__PURE__ */ jsx2(Text2, { color: r.source === "local" ? theme.ok : "#4f6db8", children: r.source === "local" ? "LOC " : "CLD " }),
1032
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: truncate(r.isMe ? "this machine" : r.agent, 12).padEnd(13) }),
862
1033
  /* @__PURE__ */ jsxs2(Text2, { children: [
863
- String(v.calls).padStart(4),
1034
+ String(r.calls).padStart(4),
864
1035
  "c "
865
1036
  ] }),
866
- /* @__PURE__ */ jsxs2(Text2, { color: v.denies ? theme.bad : theme.dim, children: [
867
- String(v.denies).padStart(3),
1037
+ /* @__PURE__ */ jsxs2(Text2, { color: r.denies ? theme.bad : theme.dim, children: [
1038
+ String(r.denies).padStart(3),
868
1039
  "d "
869
1040
  ] }),
870
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(v.lastAt) })
871
- ] }, id);
872
- }),
873
- sess.slice(0, Math.max(0, colH - 1 - Math.min(localSessList.length, Math.max(1, colH - 2)))).map((a) => {
874
- const dot = sessionDot(a.status);
875
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
876
- /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
877
- dot.ch,
878
- " "
879
- ] }),
880
- /* @__PURE__ */ jsx2(Text2, { color: "#4f6db8", children: "CLD " }),
881
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(a.agent_name ?? a.session_id, 12).padEnd(13) }),
882
- /* @__PURE__ */ jsxs2(Text2, { children: [
883
- String(a.total_calls).padStart(4),
884
- "c "
885
- ] }),
886
- /* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
887
- String(a.denied_calls).padStart(3),
888
- "d "
889
- ] }),
890
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `t${a.trust_score} ` + ago(a.last_seen_at) })
891
- ] }, a.session_id);
1041
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (r.trust != null ? `t${r.trust} ` : "") + ago(r.lastAt) })
1042
+ ] }, r.id);
892
1043
  })
893
1044
  ] }),
894
1045
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
@@ -907,7 +1058,7 @@ function LivePanel({ active: active2 }) {
907
1058
  PaneTitle,
908
1059
  {
909
1060
  label: "TOOL STREAM",
910
- extra: `last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25B2${clampedScroll} scrolled` : " \xB7 following"} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 c copy`,
1061
+ extra: `newest first \xB7 last ${STREAM_KEEP} \xB7 filter ${filter}${clampedScroll > 0 ? ` \xB7 \u25BC${clampedScroll} older` : ""} \xB7 f filter \xB7 \u2191\u2193 scroll \xB7 c copy \xB7 s sessions`,
911
1062
  width: innerW
912
1063
  }
913
1064
  ),
@@ -920,28 +1071,12 @@ function LivePanel({ active: active2 }) {
920
1071
  spin,
921
1072
  " awaiting traffic\u2026"
922
1073
  ] }) : null,
923
- windowed.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
924
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
925
- "[",
926
- hhmmss(e.at),
927
- " "
928
- ] }),
929
- /* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
930
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
931
- /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
932
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
933
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
934
- /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
935
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
936
- e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
937
- e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
938
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
939
- ] }, e.id))
1074
+ windowed.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e }, e.id))
940
1075
  ] }),
941
1076
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
942
1077
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
943
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld buffered \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
944
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7 \u2191\u2193 scroll \xB7 c copy \xB7 esc menu \xB7 q quit " })
1078
+ /* @__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"} ` }),
1079
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " f filter \xB7 s sessions \xB7 c copy \xB7 esc menu \xB7 q quit " })
945
1080
  ] })
946
1081
  ] });
947
1082
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.69.0",
3
+ "version": "0.70.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": {