@solongate/proxy 0.68.0 → 0.69.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,12 +7020,12 @@ 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 { closeSync, mkdirSync as mkdirSync3, openSync, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
7024
7024
  import { homedir as homedir3 } from "os";
7025
- import { join as join5 } from "path";
7025
+ import { dirname, join as join5 } from "path";
7026
7026
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7027
7027
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7028
- function tailLines(file, maxBytes = 65536) {
7028
+ function tailLines(file, maxBytes = 131072) {
7029
7029
  try {
7030
7030
  const size = statSync(file).size;
7031
7031
  const start = Math.max(0, size - maxBytes);
@@ -7040,6 +7040,9 @@ function tailLines(file, maxBytes = 65536) {
7040
7040
  return [];
7041
7041
  }
7042
7042
  }
7043
+ function oscCopy(text) {
7044
+ process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
7045
+ }
7043
7046
  function cloudItem(e) {
7044
7047
  return {
7045
7048
  id: "c:" + e.id,
@@ -7049,7 +7052,11 @@ function cloudItem(e) {
7049
7052
  permission: (e.permission ?? "").slice(0, 4),
7050
7053
  detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
7051
7054
  dlp: !!e.dlp_matches?.length,
7052
- source: "cloud"
7055
+ source: "cloud",
7056
+ session: e.session_id ?? void 0,
7057
+ agent: e.agent_name ?? void 0,
7058
+ evalMs: e.evaluation_time_ms ?? void 0,
7059
+ rule: e.matched_rule_id ?? void 0
7053
7060
  };
7054
7061
  }
7055
7062
  function ColumnChart({ series, hot, height, width: width2, color, hotColor = theme.bad }) {
@@ -7068,7 +7075,7 @@ function ColumnChart({ series, hot, height, width: width2, color, hotColor = the
7068
7075
  else if (frac >= (r - 0.5) / height) ch = "\u2584";
7069
7076
  else if (r === 1) ch = "\u2581";
7070
7077
  else ch = " ";
7071
- const c2 = r === 1 && v === 0 ? "#233457" : hotPad?.[i] ? hotColor : color;
7078
+ const c2 = r === 1 && v === 0 ? DIM_FLOOR : hotPad?.[i] ? hotColor : color;
7072
7079
  const last = segs[segs.length - 1];
7073
7080
  if (last && last.color === c2) last.text += ch;
7074
7081
  else segs.push({ text: ch, color: c2 });
@@ -7084,7 +7091,7 @@ function HBar({ label, value, max, width: width2, color }) {
7084
7091
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7085
7092
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
7086
7093
  /* @__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)) }),
7094
+ /* @__PURE__ */ jsx2(Text2, { color: DIM_FLOOR, children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
7088
7095
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
7089
7096
  ] });
7090
7097
  }
@@ -7109,18 +7116,12 @@ function LivePanel({ active: active2 }) {
7109
7116
  const [localOn, setLocalOn] = useState2(null);
7110
7117
  const [ring, setRing] = useState2(null);
7111
7118
  const [log6, setLog] = useState2([]);
7119
+ const [filter, setFilter] = useState2("all");
7120
+ const [scroll, setScroll] = useState2(0);
7121
+ const [toast, setToast] = useState2(null);
7112
7122
  const seenRef = useRef2(/* @__PURE__ */ new Set());
7113
7123
  const lastLocalTs = useRef2(0);
7114
7124
  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
7125
  const pushLog = useCallback2((msg, level = "ok") => {
7125
7126
  setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
7126
7127
  }, []);
@@ -7136,9 +7137,8 @@ function LivePanel({ active: active2 }) {
7136
7137
  },
7137
7138
  [pushLog]
7138
7139
  );
7139
- const paused = () => frozenRef.current || Date.now() < pausedUntil.current;
7140
+ const paused = () => Date.now() < pausedUntil.current;
7140
7141
  const pollLocal = useCallback2(() => {
7141
- if (frozenRef.current) return;
7142
7142
  const lines = tailLines(LOCAL_LOG);
7143
7143
  if (!lines.length) {
7144
7144
  setLocalOn((prev) => prev === null ? false : prev);
@@ -7161,7 +7161,9 @@ function LivePanel({ active: active2 }) {
7161
7161
  dlp: !!j.dlp,
7162
7162
  source: "local",
7163
7163
  session: j.session_id,
7164
- agent: j.agent_name
7164
+ agent: j.agent_name,
7165
+ evalMs: j.evaluation_time_ms,
7166
+ rule: j.matched_rule_id
7165
7167
  });
7166
7168
  } catch {
7167
7169
  }
@@ -7170,9 +7172,7 @@ function LivePanel({ active: active2 }) {
7170
7172
  lastLocalTs.current = fresh[fresh.length - 1].at;
7171
7173
  setLocalBuf((prev) => [...prev, ...fresh].slice(-400));
7172
7174
  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
- }
7175
+ if (fresh.length < 10) pushLog(`local +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
7176
7176
  }
7177
7177
  const ringLines = tailLines(RING, 8192).slice(-30);
7178
7178
  if (ringLines.length) {
@@ -7209,9 +7209,7 @@ function LivePanel({ active: active2 }) {
7209
7209
  const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
7210
7210
  const firstLoad = seenRef.current.size === 0 && fresh.length > 1;
7211
7211
  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
- }
7212
+ if (fresh.length) setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
7215
7213
  const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
7216
7214
  if (firstLoad) pushLog(`cloud link up \xB7 api ${ms}ms \xB7 ${fresh.length} calls`);
7217
7215
  else pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : fresh.length ? "warn" : "ok");
@@ -7251,26 +7249,32 @@ function LivePanel({ active: active2 }) {
7251
7249
  }, [active2, pollFeed, pollMedium, pollSlow]);
7252
7250
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7253
7251
  const insights = useLoader(() => api.stats.securityInsights(7));
7252
+ const guard = useLoader(() => api.settings.getGuardStatus());
7254
7253
  usePoll(() => {
7255
7254
  if (!paused()) sessions.reload();
7256
7255
  }, 1e4, active2);
7257
7256
  usePoll(() => {
7258
7257
  if (!paused()) insights.reload();
7259
7258
  }, 3e4, active2);
7259
+ usePoll(() => {
7260
+ if (!paused()) guard.reload();
7261
+ }, 6e4, active2);
7260
7262
  const [tick, setTick] = useState2(0);
7261
7263
  useEffect2(() => {
7262
- if (!active2 || frozen) return;
7263
- const t = setInterval(() => setTick((n) => n + 1), 200);
7264
+ if (!active2) return;
7265
+ const t = setInterval(() => setTick((n) => n + 1), 500);
7264
7266
  return () => clearInterval(t);
7265
- }, [active2, frozen]);
7267
+ }, [active2]);
7266
7268
  const startRef = useRef2(Date.now());
7267
7269
  const cols = process.stdout.columns ?? 100;
7268
7270
  const rows = process.stdout.rows ?? 30;
7269
7271
  const ins = insights.data ?? {};
7270
7272
  const spin = SPIN[tick % SPIN.length];
7271
- const points = tsData?.timeseries ?? [];
7272
- const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
7273
7273
  const nowMs = Date.now();
7274
+ const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
7275
+ const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => e.source === filter);
7276
+ const visible = filtered.slice(-STREAM_KEEP);
7277
+ const points = tsData?.timeseries ?? [];
7274
7278
  const minuteCounts = new Array(60).fill(0);
7275
7279
  const minuteHot = new Array(60).fill(false);
7276
7280
  for (const e of mergedAll) {
@@ -7286,14 +7290,14 @@ function LivePanel({ active: active2 }) {
7286
7290
  const trafficLabel = localTraffic ? "calls/min \xB7 60m \xB7 local+cloud" : "24h \xB7 cloud";
7287
7291
  const rl = ins.layers?.rateLimit;
7288
7292
  const dl = ins.layers?.dlp;
7289
- const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
7293
+ const gh = ins.layers?.ghost;
7294
+ const minuteNow = minuteCounts[59] ?? 0;
7290
7295
  const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
7291
7296
  const maxDlpBar = dlpBars[0]?.count ?? 1;
7292
- const merged = mergedAll;
7293
- const denials = merged.filter((e) => e.decision !== "ALLOW");
7297
+ const denials = mergedAll.filter((e) => e.decision !== "ALLOW");
7294
7298
  const lastDeny = denials[denials.length - 1];
7295
7299
  const toolCounts = /* @__PURE__ */ new Map();
7296
- for (const e of merged) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
7300
+ for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
7297
7301
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7298
7302
  const latNow = lat[lat.length - 1] ?? 0;
7299
7303
  const sortedLat = [...lat].sort((a, b) => a - b);
@@ -7314,19 +7318,53 @@ function LivePanel({ active: active2 }) {
7314
7318
  }
7315
7319
  const localSessList = [...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt);
7316
7320
  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);
7321
+ const colH = rows >= 32 ? 7 : 5;
7322
+ const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
7320
7323
  const innerW = cols - 2;
7321
7324
  const leftW = Math.floor(innerW * 0.55);
7322
7325
  const rightW = innerW - leftW - 2;
7323
7326
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
7327
+ const maxScroll = Math.max(0, visible.length - streamRows);
7328
+ const clampedScroll = Math.min(scroll, maxScroll);
7329
+ const start = Math.max(0, visible.length - streamRows - clampedScroll);
7330
+ const windowed = visible.slice(start, start + streamRows);
7331
+ useInput(
7332
+ (input, key) => {
7333
+ if (input === "f") {
7334
+ setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
7335
+ 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));
7340
+ else if (input === "c") {
7341
+ const dump = visible.map(
7342
+ (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
+ ).join("\n");
7344
+ try {
7345
+ mkdirSync3(dirname(DUMP), { recursive: true });
7346
+ writeFileSync3(DUMP, dump + "\n");
7347
+ } catch {
7348
+ }
7349
+ oscCopy(dump);
7350
+ setToast({ msg: `copied ${visible.length} lines \u2192 clipboard + ${DUMP}`, until: Date.now() + 4e3 });
7351
+ }
7352
+ },
7353
+ { isActive: active2 }
7354
+ );
7324
7355
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7356
+ if (s === null && localOn === null) {
7357
+ return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7358
+ spin,
7359
+ " connecting\u2026"
7360
+ ] });
7361
+ }
7325
7362
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7326
7363
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7327
7364
  /* @__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 " })
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
7330
7368
  ] }),
7331
7369
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7332
7370
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
@@ -7336,16 +7374,20 @@ function LivePanel({ active: active2 }) {
7336
7374
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7337
7375
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
7338
7376
  /* @__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 " }),
7377
+ localOn ? /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "\u2713" }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "off" }),
7378
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 rl " }),
7341
7379
  /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
7342
7380
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
7343
7381
  /* @__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" }),
7382
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
7383
+ /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
7384
+ /* @__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` }),
7386
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
7387
+ /* @__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
7388
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7347
7389
  ] }),
7348
- /* @__PURE__ */ jsxs2(Box2, { children: [
7390
+ /* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
7349
7391
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
7350
7392
  /* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
7351
7393
  /* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
@@ -7355,39 +7397,38 @@ function LivePanel({ active: active2 }) {
7355
7397
  /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0", hotColor: "#ffb454" })
7356
7398
  ] })
7357
7399
  ] }),
7358
- /* @__PURE__ */ jsxs2(Box2, { children: [
7359
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7400
+ /* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
7401
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
7360
7402
  /* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
7361
7403
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7362
7404
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
7363
7405
  /* @__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" })
7406
+ /* @__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
7407
  ] }),
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,
7408
+ 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
7409
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7368
7410
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
7369
7411
  /* @__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
- ] })
7412
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
7374
7413
  ] }),
7375
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" }),
7376
- ring ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7415
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7377
7416
  /* @__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
7417
+ ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
7418
+ "local \u2713 ",
7419
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `eval avg ${ring.avgMs}ms` })
7420
+ ] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no local ring in cwd" })
7421
+ ] })
7381
7422
  ] }),
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 }),
7423
+ /* @__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 }),
7384
7425
  localSessList.length === 0 && sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7385
7426
  localSessList.slice(0, Math.max(1, colH - 2)).map(([id, v]) => {
7386
- const fresh = nowMs - v.lastAt < 9e4;
7427
+ const freshDot = nowMs - v.lastAt < 9e4;
7387
7428
  const isMe = ring?.session === id;
7388
7429
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7389
- /* @__PURE__ */ jsxs2(Text2, { color: fresh ? theme.ok : theme.dim, children: [
7390
- fresh ? "\u25CF" : "\u25CB",
7430
+ /* @__PURE__ */ jsxs2(Text2, { color: freshDot ? theme.ok : theme.dim, children: [
7431
+ freshDot ? "\u25CF" : "\u25CB",
7391
7432
  " "
7392
7433
  ] }),
7393
7434
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "LOC " }),
@@ -7420,12 +7461,12 @@ function LivePanel({ active: active2 }) {
7420
7461
  String(a.denied_calls).padStart(3),
7421
7462
  "d "
7422
7463
  ] }),
7423
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
7464
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `t${a.trust_score} ` + ago(a.last_seen_at) })
7424
7465
  ] }, a.session_id);
7425
7466
  })
7426
7467
  ] }),
7427
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
7428
- /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
7468
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
7469
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "system heartbeat", width: colW }),
7429
7470
  log6.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7430
7471
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7431
7472
  hhmmss(l.ts),
@@ -7436,37 +7477,49 @@ function LivePanel({ active: active2 }) {
7436
7477
  ] }, l.ts + ":" + i))
7437
7478
  ] })
7438
7479
  ] }),
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)),
7480
+ /* @__PURE__ */ jsx2(
7481
+ PaneTitle,
7482
+ {
7483
+ 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`,
7485
+ width: innerW
7486
+ }
7487
+ ),
7488
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
7489
+ localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7490
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
7491
+ /* @__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`" })
7492
+ ] }) : null,
7493
+ windowed.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7494
+ spin,
7495
+ " awaiting traffic\u2026"
7496
+ ] }) : 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))
7514
+ ] }),
7462
7515
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7463
7516
  /* @__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 " })
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 " })
7466
7519
  ] })
7467
7520
  ] });
7468
7521
  }
7469
- var SPIN, BG, LOCAL_LOG, RING, hhmmss, fmtUp;
7522
+ var SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, DUMP, STREAM_KEEP, hhmmss, fmtUp, FILTERS;
7470
7523
  var init_Live = __esm({
7471
7524
  "src/tui/panels/Live.tsx"() {
7472
7525
  "use strict";
@@ -7475,8 +7528,11 @@ var init_Live = __esm({
7475
7528
  init_theme();
7476
7529
  SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7477
7530
  BG = "#12234f";
7531
+ DIM_FLOOR = "#233457";
7478
7532
  LOCAL_LOG = join5(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
7479
7533
  RING = join5(process.cwd(), ".solongate", ".eval-ring.jsonl");
7534
+ DUMP = join5(homedir3(), ".solongate", "live-stream.log");
7535
+ STREAM_KEEP = 100;
7480
7536
  hhmmss = (ts) => {
7481
7537
  const d = new Date(ts);
7482
7538
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -7486,6 +7542,7 @@ var init_Live = __esm({
7486
7542
  const p = (n) => String(n).padStart(2, "0");
7487
7543
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
7488
7544
  };
7545
+ FILTERS = ["all", "local", "cloud"];
7489
7546
  }
7490
7547
  });
7491
7548
 
@@ -9147,8 +9204,8 @@ __export(global_install_exports, {
9147
9204
  runGlobalRestore: () => runGlobalRestore,
9148
9205
  unlockProtected: () => unlockProtected
9149
9206
  });
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";
9207
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
9208
+ import { resolve as resolve4, join as join6, dirname as dirname2 } from "path";
9152
9209
  import { homedir as homedir4 } from "os";
9153
9210
  import { fileURLToPath } from "url";
9154
9211
  import { createInterface } from "readline";
@@ -9260,13 +9317,13 @@ function runGlobalRestore() {
9260
9317
  unlockProtected();
9261
9318
  removeClaudeShim();
9262
9319
  if (existsSync4(p.backupPath)) {
9263
- writeFileSync3(p.settingsPath, readFileSync6(p.backupPath, "utf-8"));
9320
+ writeFileSync4(p.settingsPath, readFileSync6(p.backupPath, "utf-8"));
9264
9321
  console.log(` Restored ${p.settingsPath} from backup.`);
9265
9322
  } else if (existsSync4(p.settingsPath)) {
9266
9323
  try {
9267
9324
  const s = JSON.parse(readFileSync6(p.settingsPath, "utf-8"));
9268
9325
  delete s.hooks;
9269
- writeFileSync3(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
9326
+ writeFileSync4(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
9270
9327
  console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
9271
9328
  } catch {
9272
9329
  }
@@ -9310,8 +9367,8 @@ function writeShimBlock(file, block) {
9310
9367
  if (content.length && !content.endsWith("\n")) content += "\n";
9311
9368
  content += block + "\n";
9312
9369
  }
9313
- mkdirSync3(dirname(file), { recursive: true });
9314
- writeFileSync3(file, content);
9370
+ mkdirSync4(dirname2(file), { recursive: true });
9371
+ writeFileSync4(file, content);
9315
9372
  }
9316
9373
  function installClaudeShim(shieldPath) {
9317
9374
  const real = resolveRealClaude();
@@ -9368,22 +9425,22 @@ async function runGlobalInstall(opts = {}) {
9368
9425
  process.exit(1);
9369
9426
  }
9370
9427
  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 });
9428
+ mkdirSync4(p.hooksDir, { recursive: true });
9429
+ mkdirSync4(p.claudeDir, { recursive: true });
9373
9430
  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"));
9431
+ writeFileSync4(join6(p.hooksDir, "guard.mjs"), readGuard());
9432
+ writeFileSync4(join6(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
9433
+ writeFileSync4(join6(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
9434
+ writeFileSync4(join6(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
9378
9435
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
9379
9436
  removeClaudeShim();
9380
- writeFileSync3(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
9437
+ writeFileSync4(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
9381
9438
  console.log(` Wrote ${p.configPath}`);
9382
9439
  let existing = {};
9383
9440
  if (existsSync4(p.settingsPath)) {
9384
9441
  const raw = readFileSync6(p.settingsPath, "utf-8");
9385
9442
  if (!existsSync4(p.backupPath)) {
9386
- writeFileSync3(p.backupPath, raw);
9443
+ writeFileSync4(p.backupPath, raw);
9387
9444
  console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
9388
9445
  }
9389
9446
  try {
@@ -9404,7 +9461,7 @@ async function runGlobalInstall(opts = {}) {
9404
9461
  Stop: [{ matcher: "", hooks: [{ type: "command", command: `"${nodeBin}" "${stopAbs}" claude-code "Claude Code"` }] }]
9405
9462
  }
9406
9463
  };
9407
- writeFileSync3(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
9464
+ writeFileSync4(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
9408
9465
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
9409
9466
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
9410
9467
  lockProtected();
@@ -9418,7 +9475,7 @@ var __dirname, HOOKS_DIR, SHIM_BEGIN, SHIM_END;
9418
9475
  var init_global_install = __esm({
9419
9476
  "src/global-install.ts"() {
9420
9477
  "use strict";
9421
- __dirname = dirname(fileURLToPath(import.meta.url));
9478
+ __dirname = dirname2(fileURLToPath(import.meta.url));
9422
9479
  HOOKS_DIR = resolve4(__dirname, "..", "hooks");
9423
9480
  SHIM_BEGIN = "# >>> SolonGate shield (auto secret redaction) >>>";
9424
9481
  SHIM_END = "# <<< SolonGate shield <<<";
@@ -10068,7 +10125,7 @@ var init_logs_server = __esm({
10068
10125
 
10069
10126
  // src/inject.ts
10070
10127
  var inject_exports = {};
10071
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync4, existsSync as existsSync6, copyFileSync } from "fs";
10128
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync6, copyFileSync } from "fs";
10072
10129
  import { resolve as resolve7 } from "path";
10073
10130
  import { execSync } from "child_process";
10074
10131
  function parseInjectArgs(argv) {
@@ -10376,7 +10433,7 @@ async function main2() {
10376
10433
  log3("");
10377
10434
  log3(` Backup: ${backupPath}`);
10378
10435
  }
10379
- writeFileSync4(entryFile, result.modified);
10436
+ writeFileSync5(entryFile, result.modified);
10380
10437
  log3("");
10381
10438
  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
10439
  log3(" \u2502 SolonGate SDK injected successfully! \u2502");
@@ -10405,7 +10462,7 @@ var init_inject = __esm({
10405
10462
 
10406
10463
  // src/create.ts
10407
10464
  var create_exports = {};
10408
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
10465
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
10409
10466
  import { resolve as resolve8, join as join8 } from "path";
10410
10467
  import { execSync as execSync2 } from "child_process";
10411
10468
  function withSpinner(message, fn) {
@@ -10485,7 +10542,7 @@ EXAMPLES
10485
10542
  `);
10486
10543
  }
10487
10544
  function createProject(dir, name, _policy) {
10488
- writeFileSync5(
10545
+ writeFileSync6(
10489
10546
  join8(dir, "package.json"),
10490
10547
  JSON.stringify(
10491
10548
  {
@@ -10515,7 +10572,7 @@ function createProject(dir, name, _policy) {
10515
10572
  2
10516
10573
  ) + "\n"
10517
10574
  );
10518
- writeFileSync5(
10575
+ writeFileSync6(
10519
10576
  join8(dir, "tsconfig.json"),
10520
10577
  JSON.stringify(
10521
10578
  {
@@ -10536,8 +10593,8 @@ function createProject(dir, name, _policy) {
10536
10593
  2
10537
10594
  ) + "\n"
10538
10595
  );
10539
- mkdirSync4(join8(dir, "src"), { recursive: true });
10540
- writeFileSync5(
10596
+ mkdirSync5(join8(dir, "src"), { recursive: true });
10597
+ writeFileSync6(
10541
10598
  join8(dir, "src", "index.ts"),
10542
10599
  `#!/usr/bin/env node
10543
10600
 
@@ -10579,7 +10636,7 @@ console.log('');
10579
10636
  console.log('Press Ctrl+C to stop.');
10580
10637
  `
10581
10638
  );
10582
- writeFileSync5(
10639
+ writeFileSync6(
10583
10640
  join8(dir, ".mcp.json"),
10584
10641
  JSON.stringify(
10585
10642
  {
@@ -10597,12 +10654,12 @@ console.log('Press Ctrl+C to stop.');
10597
10654
  2
10598
10655
  ) + "\n"
10599
10656
  );
10600
- writeFileSync5(
10657
+ writeFileSync6(
10601
10658
  join8(dir, ".env"),
10602
10659
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
10603
10660
  `
10604
10661
  );
10605
- writeFileSync5(
10662
+ writeFileSync6(
10606
10663
  join8(dir, ".gitignore"),
10607
10664
  `node_modules/
10608
10665
  dist/
@@ -10622,7 +10679,7 @@ async function main3() {
10622
10679
  process.exit(1);
10623
10680
  }
10624
10681
  withSpinner(`Setting up ${opts.name}...`, () => {
10625
- mkdirSync4(dir, { recursive: true });
10682
+ mkdirSync5(dir, { recursive: true });
10626
10683
  createProject(dir, opts.name, opts.policy);
10627
10684
  });
10628
10685
  if (!opts.noInstall) {
@@ -10696,7 +10753,7 @@ var init_create = __esm({
10696
10753
 
10697
10754
  // src/pull-push.ts
10698
10755
  var pull_push_exports = {};
10699
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, existsSync as existsSync8 } from "fs";
10756
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, existsSync as existsSync8 } from "fs";
10700
10757
  import { resolve as resolve9 } from "path";
10701
10758
  function loadEnv() {
10702
10759
  if (process.env.SOLONGATE_API_KEY) return;
@@ -10891,7 +10948,7 @@ async function pull(apiKey, file, policyId) {
10891
10948
  const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
10892
10949
  const { id: _id, ...policyWithoutId } = policy;
10893
10950
  const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
10894
- writeFileSync6(file, json, "utf-8");
10951
+ writeFileSync7(file, json, "utf-8");
10895
10952
  log5("");
10896
10953
  log5(green2(" Saved to: ") + file);
10897
10954
  log5(` ${dim2("Name:")} ${policy.name}`);
package/dist/tui/index.js CHANGED
@@ -126,9 +126,9 @@ 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 { closeSync, openSync, readSync, statSync } from "fs";
129
+ import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
130
130
  import { homedir as homedir2 } from "os";
131
- import { join as join2 } from "path";
131
+ import { dirname, join as join2 } from "path";
132
132
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
133
133
 
134
134
  // src/api-client/client.ts
@@ -436,8 +436,11 @@ function usePoll(reload, intervalMs, enabled = true) {
436
436
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
437
437
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
438
438
  var BG = "#12234f";
439
+ var DIM_FLOOR = "#233457";
439
440
  var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
440
441
  var RING = join2(process.cwd(), ".solongate", ".eval-ring.jsonl");
442
+ var DUMP = join2(homedir2(), ".solongate", "live-stream.log");
443
+ var STREAM_KEEP = 100;
441
444
  var hhmmss = (ts) => {
442
445
  const d = new Date(ts);
443
446
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -447,7 +450,7 @@ var fmtUp = (ms) => {
447
450
  const p = (n) => String(n).padStart(2, "0");
448
451
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
449
452
  };
450
- function tailLines(file, maxBytes = 65536) {
453
+ function tailLines(file, maxBytes = 131072) {
451
454
  try {
452
455
  const size = statSync(file).size;
453
456
  const start = Math.max(0, size - maxBytes);
@@ -462,6 +465,9 @@ function tailLines(file, maxBytes = 65536) {
462
465
  return [];
463
466
  }
464
467
  }
468
+ function oscCopy(text) {
469
+ process.stdout.write(`\x1B]52;c;${Buffer.from(text, "utf-8").toString("base64")}\x07`);
470
+ }
465
471
  function cloudItem(e) {
466
472
  return {
467
473
  id: "c:" + e.id,
@@ -471,7 +477,11 @@ function cloudItem(e) {
471
477
  permission: (e.permission ?? "").slice(0, 4),
472
478
  detail: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " "),
473
479
  dlp: !!e.dlp_matches?.length,
474
- source: "cloud"
480
+ source: "cloud",
481
+ session: e.session_id ?? void 0,
482
+ agent: e.agent_name ?? void 0,
483
+ evalMs: e.evaluation_time_ms ?? void 0,
484
+ rule: e.matched_rule_id ?? void 0
475
485
  };
476
486
  }
477
487
  function ColumnChart({ series, hot, height, width, color, hotColor = theme.bad }) {
@@ -490,7 +500,7 @@ function ColumnChart({ series, hot, height, width, color, hotColor = theme.bad }
490
500
  else if (frac >= (r - 0.5) / height) ch = "\u2584";
491
501
  else if (r === 1) ch = "\u2581";
492
502
  else ch = " ";
493
- const c2 = r === 1 && v === 0 ? "#233457" : hotPad?.[i] ? hotColor : color;
503
+ const c2 = r === 1 && v === 0 ? DIM_FLOOR : hotPad?.[i] ? hotColor : color;
494
504
  const last = segs[segs.length - 1];
495
505
  if (last && last.color === c2) last.text += ch;
496
506
  else segs.push({ text: ch, color: c2 });
@@ -506,7 +516,7 @@ function HBar({ label, value, max, width, color }) {
506
516
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
507
517
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
508
518
  /* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width, filled)) }),
509
- /* @__PURE__ */ jsx2(Text2, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
519
+ /* @__PURE__ */ jsx2(Text2, { color: DIM_FLOOR, children: "\u2591".repeat(Math.max(0, width - filled)) }),
510
520
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
511
521
  ] });
512
522
  }
@@ -522,6 +532,7 @@ function PaneTitle({ label, extra, width }) {
522
532
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
523
533
  ] });
524
534
  }
535
+ var FILTERS = ["all", "local", "cloud"];
525
536
  function LivePanel({ active: active2 }) {
526
537
  const [s, setS] = useState2(null);
527
538
  const [tsData, setTsData] = useState2(null);
@@ -531,18 +542,12 @@ function LivePanel({ active: active2 }) {
531
542
  const [localOn, setLocalOn] = useState2(null);
532
543
  const [ring, setRing] = useState2(null);
533
544
  const [log, setLog] = useState2([]);
545
+ const [filter, setFilter] = useState2("all");
546
+ const [scroll, setScroll] = useState2(0);
547
+ const [toast, setToast] = useState2(null);
534
548
  const seenRef = useRef2(/* @__PURE__ */ new Set());
535
549
  const lastLocalTs = useRef2(0);
536
550
  const pausedUntil = useRef2(0);
537
- const [frozen, setFrozen] = useState2(false);
538
- const frozenRef = useRef2(false);
539
- frozenRef.current = frozen;
540
- useInput(
541
- (input) => {
542
- if (input === "p") setFrozen((f) => !f);
543
- },
544
- { isActive: active2 }
545
- );
546
551
  const pushLog = useCallback2((msg, level = "ok") => {
547
552
  setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
548
553
  }, []);
@@ -558,9 +563,8 @@ function LivePanel({ active: active2 }) {
558
563
  },
559
564
  [pushLog]
560
565
  );
561
- const paused = () => frozenRef.current || Date.now() < pausedUntil.current;
566
+ const paused = () => Date.now() < pausedUntil.current;
562
567
  const pollLocal = useCallback2(() => {
563
- if (frozenRef.current) return;
564
568
  const lines = tailLines(LOCAL_LOG);
565
569
  if (!lines.length) {
566
570
  setLocalOn((prev) => prev === null ? false : prev);
@@ -583,7 +587,9 @@ function LivePanel({ active: active2 }) {
583
587
  dlp: !!j.dlp,
584
588
  source: "local",
585
589
  session: j.session_id,
586
- agent: j.agent_name
590
+ agent: j.agent_name,
591
+ evalMs: j.evaluation_time_ms,
592
+ rule: j.matched_rule_id
587
593
  });
588
594
  } catch {
589
595
  }
@@ -592,9 +598,7 @@ function LivePanel({ active: active2 }) {
592
598
  lastLocalTs.current = fresh[fresh.length - 1].at;
593
599
  setLocalBuf((prev) => [...prev, ...fresh].slice(-400));
594
600
  const denies = fresh.filter((f) => f.decision !== "ALLOW").length;
595
- if (lastLocalTs.current !== fresh[0].at || fresh.length < 10) {
596
- pushLog(`local +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
597
- }
601
+ if (fresh.length < 10) pushLog(`local +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : "warn");
598
602
  }
599
603
  const ringLines = tailLines(RING, 8192).slice(-30);
600
604
  if (ringLines.length) {
@@ -631,9 +635,7 @@ function LivePanel({ active: active2 }) {
631
635
  const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
632
636
  const firstLoad = seenRef.current.size === 0 && fresh.length > 1;
633
637
  for (const e of fresh) seenRef.current.add(e.id);
634
- if (fresh.length) {
635
- setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
636
- }
638
+ if (fresh.length) setCloudBuf((prev) => [...prev, ...fresh.map(cloudItem).sort((a, b) => a.at - b.at)].slice(-400));
637
639
  const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
638
640
  if (firstLoad) pushLog(`cloud link up \xB7 api ${ms}ms \xB7 ${fresh.length} calls`);
639
641
  else pushLog(`api ${ms}ms \xB7 +${fresh.length} cloud${denies ? ` \xB7 ${denies} DENIED` : ""}`, denies ? "bad" : fresh.length ? "warn" : "ok");
@@ -673,26 +675,32 @@ function LivePanel({ active: active2 }) {
673
675
  }, [active2, pollFeed, pollMedium, pollSlow]);
674
676
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
675
677
  const insights = useLoader(() => api.stats.securityInsights(7));
678
+ const guard = useLoader(() => api.settings.getGuardStatus());
676
679
  usePoll(() => {
677
680
  if (!paused()) sessions.reload();
678
681
  }, 1e4, active2);
679
682
  usePoll(() => {
680
683
  if (!paused()) insights.reload();
681
684
  }, 3e4, active2);
685
+ usePoll(() => {
686
+ if (!paused()) guard.reload();
687
+ }, 6e4, active2);
682
688
  const [tick, setTick] = useState2(0);
683
689
  useEffect2(() => {
684
- if (!active2 || frozen) return;
685
- const t = setInterval(() => setTick((n) => n + 1), 200);
690
+ if (!active2) return;
691
+ const t = setInterval(() => setTick((n) => n + 1), 500);
686
692
  return () => clearInterval(t);
687
- }, [active2, frozen]);
693
+ }, [active2]);
688
694
  const startRef = useRef2(Date.now());
689
695
  const cols = process.stdout.columns ?? 100;
690
696
  const rows = process.stdout.rows ?? 30;
691
697
  const ins = insights.data ?? {};
692
698
  const spin = SPIN[tick % SPIN.length];
693
- const points = tsData?.timeseries ?? [];
694
- const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
695
699
  const nowMs = Date.now();
700
+ const mergedAll = [...cloudBuf, ...localBuf].sort((a, b) => a.at - b.at);
701
+ const filtered = filter === "all" ? mergedAll : mergedAll.filter((e) => e.source === filter);
702
+ const visible = filtered.slice(-STREAM_KEEP);
703
+ const points = tsData?.timeseries ?? [];
696
704
  const minuteCounts = new Array(60).fill(0);
697
705
  const minuteHot = new Array(60).fill(false);
698
706
  for (const e of mergedAll) {
@@ -708,14 +716,14 @@ function LivePanel({ active: active2 }) {
708
716
  const trafficLabel = localTraffic ? "calls/min \xB7 60m \xB7 local+cloud" : "24h \xB7 cloud";
709
717
  const rl = ins.layers?.rateLimit;
710
718
  const dl = ins.layers?.dlp;
711
- const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
719
+ const gh = ins.layers?.ghost;
720
+ const minuteNow = minuteCounts[59] ?? 0;
712
721
  const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
713
722
  const maxDlpBar = dlpBars[0]?.count ?? 1;
714
- const merged = mergedAll;
715
- const denials = merged.filter((e) => e.decision !== "ALLOW");
723
+ const denials = mergedAll.filter((e) => e.decision !== "ALLOW");
716
724
  const lastDeny = denials[denials.length - 1];
717
725
  const toolCounts = /* @__PURE__ */ new Map();
718
- for (const e of merged) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
726
+ for (const e of mergedAll) toolCounts.set(e.tool, (toolCounts.get(e.tool) ?? 0) + 1);
719
727
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
720
728
  const latNow = lat[lat.length - 1] ?? 0;
721
729
  const sortedLat = [...lat].sort((a, b) => a - b);
@@ -736,19 +744,53 @@ function LivePanel({ active: active2 }) {
736
744
  }
737
745
  const localSessList = [...localSess.entries()].sort((a, b) => b[1].lastAt - a[1].lastAt);
738
746
  const chartH = rows >= 36 ? 6 : 4;
739
- const colH = rows >= 32 ? 6 : 5;
740
- const streamRows = Math.max(4, rows - 6 - chartH - colH - (localOn === false ? 1 : 0));
741
- const tail = merged.slice(-streamRows);
747
+ const colH = rows >= 32 ? 7 : 5;
748
+ const streamRows = Math.max(4, rows - 5 - (1 + chartH) - colH);
742
749
  const innerW = cols - 2;
743
750
  const leftW = Math.floor(innerW * 0.55);
744
751
  const rightW = innerW - leftW - 2;
745
752
  const colW = Math.max(20, Math.floor((innerW - 4) / 3));
753
+ const maxScroll = Math.max(0, visible.length - streamRows);
754
+ const clampedScroll = Math.min(scroll, maxScroll);
755
+ const start = Math.max(0, visible.length - streamRows - clampedScroll);
756
+ const windowed = visible.slice(start, start + streamRows);
757
+ useInput(
758
+ (input, key) => {
759
+ if (input === "f") {
760
+ setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
761
+ 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));
766
+ else if (input === "c") {
767
+ const dump = visible.map(
768
+ (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
+ ).join("\n");
770
+ try {
771
+ mkdirSync(dirname(DUMP), { recursive: true });
772
+ writeFileSync(DUMP, dump + "\n");
773
+ } catch {
774
+ }
775
+ oscCopy(dump);
776
+ setToast({ msg: `copied ${visible.length} lines \u2192 clipboard + ${DUMP}`, until: Date.now() + 4e3 });
777
+ }
778
+ },
779
+ { isActive: active2 }
780
+ );
746
781
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25CC", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
782
+ if (s === null && localOn === null) {
783
+ return /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
784
+ spin,
785
+ " connecting\u2026"
786
+ ] });
787
+ }
747
788
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
748
789
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
749
790
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE LIVE " }),
750
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
751
- 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 " })
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
752
794
  ] }),
753
795
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
754
796
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
@@ -758,16 +800,20 @@ function LivePanel({ active: active2 }) {
758
800
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
759
801
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
760
802
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 local " }),
761
- localOn ? /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "\u2713 streaming" }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "off" }),
762
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
803
+ localOn ? /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "\u2713" }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "off" }),
804
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 rl " }),
763
805
  /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
764
806
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
765
807
  /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
766
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
767
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
808
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ghost " }),
809
+ /* @__PURE__ */ jsx2(Text2, { color: gh?.mode === "on" ? theme.ok : theme.dim, children: gh?.mode ?? "?" }),
810
+ /* @__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` }),
812
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 hooks " }),
813
+ /* @__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" }),
768
814
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
769
815
  ] }),
770
- /* @__PURE__ */ jsxs2(Box2, { children: [
816
+ /* @__PURE__ */ jsxs2(Box2, { height: 1 + chartH, children: [
771
817
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
772
818
  /* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `${trafficLabel} \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = denials`, width: leftW }),
773
819
  /* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
@@ -777,39 +823,38 @@ function LivePanel({ active: active2 }) {
777
823
  /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0", hotColor: "#ffb454" })
778
824
  ] })
779
825
  ] }),
780
- /* @__PURE__ */ jsxs2(Box2, { children: [
781
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
826
+ /* @__PURE__ */ jsxs2(Box2, { height: colH, children: [
827
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, overflow: "hidden", children: [
782
828
  /* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
783
829
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
784
830
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
785
831
  /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
786
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
832
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute}m ${rl.perHour || "\u2014"}h ${rl.perDay || "\u2014"}d` : "no limits set" })
787
833
  ] }),
788
- 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,
834
+ 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,
789
835
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
790
836
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
791
837
  /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
792
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
793
- (dl?.patterns ?? []).length,
794
- " patterns armed"
795
- ] })
838
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${(dl?.patterns ?? []).length} builtin \xB7 ${(dl?.custom ?? []).length} custom` })
796
839
  ] }),
797
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" }),
798
- ring ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
841
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
799
842
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "GUARD".padEnd(10) }),
800
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "local \u2713" }),
801
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` eval avg ${ring.avgMs}ms \xB7 ${ring.count} recent` })
802
- ] }) : null
843
+ ring ? /* @__PURE__ */ jsxs2(Text2, { color: theme.ok, children: [
844
+ "local \u2713 ",
845
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `eval avg ${ring.avgMs}ms` })
846
+ ] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no local ring in cwd" })
847
+ ] })
803
848
  ] }),
804
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
805
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: `${localSessList.length} local \xB7 ${sessCounts?.active ?? 0} cloud live`, width: colW }),
849
+ /* @__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 }),
806
851
  localSessList.length === 0 && sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
807
852
  localSessList.slice(0, Math.max(1, colH - 2)).map(([id, v]) => {
808
- const fresh = nowMs - v.lastAt < 9e4;
853
+ const freshDot = nowMs - v.lastAt < 9e4;
809
854
  const isMe = ring?.session === id;
810
855
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
811
- /* @__PURE__ */ jsxs2(Text2, { color: fresh ? theme.ok : theme.dim, children: [
812
- fresh ? "\u25CF" : "\u25CB",
856
+ /* @__PURE__ */ jsxs2(Text2, { color: freshDot ? theme.ok : theme.dim, children: [
857
+ freshDot ? "\u25CF" : "\u25CB",
813
858
  " "
814
859
  ] }),
815
860
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "LOC " }),
@@ -842,12 +887,12 @@ function LivePanel({ active: active2 }) {
842
887
  String(a.denied_calls).padStart(3),
843
888
  "d "
844
889
  ] }),
845
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
890
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `t${a.trust_score} ` + ago(a.last_seen_at) })
846
891
  ] }, a.session_id);
847
892
  })
848
893
  ] }),
849
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
850
- /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
894
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, overflow: "hidden", children: [
895
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "system heartbeat", width: colW }),
851
896
  log.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
852
897
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
853
898
  hhmmss(l.ts),
@@ -858,33 +903,45 @@ function LivePanel({ active: active2 }) {
858
903
  ] }, l.ts + ":" + i))
859
904
  ] })
860
905
  ] }),
861
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `local+cloud tail \xB7 ${merged.length} buffered`, width: innerW }),
862
- localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
863
- /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
864
- /* @__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`)" })
865
- ] }) : null,
866
- tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
867
- spin,
868
- " awaiting traffic\u2026"
869
- ] }) : null,
870
- tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
871
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
872
- "[",
873
- hhmmss(e.at),
874
- " "
875
- ] }),
876
- /* @__PURE__ */ jsx2(Text2, { color: e.source === "local" ? theme.ok : "#4f6db8", children: e.source === "local" ? "LOC" : "CLD" }),
877
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
878
- /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
879
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool, 14).padEnd(15) }),
880
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
881
- e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
882
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
883
- ] }, e.id)),
906
+ /* @__PURE__ */ jsx2(
907
+ PaneTitle,
908
+ {
909
+ 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`,
911
+ width: innerW
912
+ }
913
+ ),
914
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: streamRows, overflow: "hidden", children: [
915
+ localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
916
+ /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
917
+ /* @__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`" })
918
+ ] }) : null,
919
+ windowed.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
920
+ spin,
921
+ " awaiting traffic\u2026"
922
+ ] }) : 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))
940
+ ] }),
884
941
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
885
942
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
886
- /* @__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} ` }),
887
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " p pause/copy \xB7 esc menu \xB7 q quit " })
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 " })
888
945
  ] })
889
946
  ] });
890
947
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.68.0",
3
+ "version": "0.69.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": {