@solongate/proxy 0.64.0 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.js +188 -104
  2. package/dist/tui/index.js +187 -103
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7020,19 +7020,27 @@ var init_hooks = __esm({
7020
7020
 
7021
7021
  // src/tui/panels/Live.tsx
7022
7022
  import { Box as Box2, Text as Text2 } from "ink";
7023
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7023
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7024
7024
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7025
- function ColumnChart({ series, height, width: width2, color, limit = 0 }) {
7026
- const tailVals = series.slice(-width2);
7027
- const data = [...new Array(Math.max(0, width2 - tailVals.length)).fill(0), ...tailVals];
7028
- const max = Math.max(1, ...data, limit);
7025
+ function ColumnChart({ series, hot, height, width: width2, color }) {
7026
+ const pad = Math.max(0, width2 - series.length);
7027
+ const data = [...new Array(pad).fill(0), ...series.slice(-width2)];
7028
+ const hotPad = hot ? [...new Array(pad).fill(false), ...hot.slice(-width2)] : void 0;
7029
+ const max = Math.max(1, ...data);
7029
7030
  const lines = [];
7030
7031
  for (let r = height; r >= 1; r--) {
7031
7032
  const segs = [];
7032
7033
  for (let i = 0; i < data.length; i++) {
7033
- const frac = data[i] / max;
7034
- const ch = frac >= r / height ? "\u2588" : frac >= (r - 0.5) / height ? "\u2584" : r === 1 && data[i] > 0 ? "\u2581" : " ";
7035
- const c2 = limit > 0 && data[i] > limit ? theme.bad : color;
7034
+ const v = data[i];
7035
+ const frac = v / max;
7036
+ let ch;
7037
+ let c2;
7038
+ if (frac >= r / height) ch = "\u2588";
7039
+ else if (frac >= (r - 0.5) / height) ch = "\u2584";
7040
+ else if (r === 1) ch = "\u2581";
7041
+ else ch = " ";
7042
+ if (r === 1 && v === 0) c2 = "#233457";
7043
+ else c2 = hotPad?.[i] ? theme.bad : color;
7036
7044
  const last = segs[segs.length - 1];
7037
7045
  if (last && last.color === c2) last.text += ch;
7038
7046
  else segs.push({ text: ch, color: c2 });
@@ -7046,26 +7054,82 @@ function ColumnChart({ series, height, width: width2, color, limit = 0 }) {
7046
7054
  function HBar({ label, value, max, width: width2, color }) {
7047
7055
  const filled = max > 0 ? Math.round(value / max * width2) : 0;
7048
7056
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7049
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(9) }),
7057
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
7050
7058
  /* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width2, filled)) }),
7051
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2591".repeat(Math.max(0, width2 - filled)) + " " + value })
7059
+ /* @__PURE__ */ jsx2(Text2, { color: "#233457", children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
7060
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
7052
7061
  ] });
7053
7062
  }
7054
- function SectionTitle({ children, width: width2 }) {
7055
- const label = `\u2500\u2500 ${children} `;
7056
- return /* @__PURE__ */ jsx2(Text2, { color: theme.dim, wrap: "truncate", children: label + "\u2500".repeat(Math.max(0, width2 - label.length)) });
7063
+ function PaneTitle({ label, extra, width: width2 }) {
7064
+ const tail = extra ? ` ${extra} ` : " ";
7065
+ const used = 2 + 1 + label.length + tail.length;
7066
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7067
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
7068
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
7069
+ "\u258E",
7070
+ label
7071
+ ] }),
7072
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
7073
+ ] });
7057
7074
  }
7058
7075
  function LivePanel({ active: active2 }) {
7059
- const stats = useLoader(() => api.stats.get());
7060
- const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
7061
- const feed = useLoader(() => api.audit.list({ limit: 50 }));
7076
+ const [s, setS] = useState2(null);
7077
+ const [tsData, setTsData] = useState2(null);
7078
+ const [lat, setLat] = useState2([]);
7079
+ const [buffer, setBuffer] = useState2([]);
7080
+ const [log6, setLog] = useState2([]);
7081
+ const seenRef = useRef2(/* @__PURE__ */ new Set());
7082
+ const pushLog = useCallback2((msg, level = "ok") => {
7083
+ setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
7084
+ }, []);
7085
+ const doPoll = useCallback2(async () => {
7086
+ const t0 = Date.now();
7087
+ try {
7088
+ const [sd, fd, td] = await Promise.all([api.stats.get(), api.audit.list({ limit: 50 }), api.stats.timeseries({ period: "24h" })]);
7089
+ const ms = Date.now() - t0;
7090
+ setS(sd);
7091
+ setTsData(td);
7092
+ setLat((l) => [...l.slice(-47), ms]);
7093
+ const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
7094
+ for (const e of fresh) seenRef.current.add(e.id);
7095
+ const firstLoad = seenRef.current.size === fresh.length && fresh.length > 1;
7096
+ if (fresh.length) {
7097
+ setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
7098
+ }
7099
+ const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
7100
+ const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
7101
+ if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
7102
+ else
7103
+ pushLog(
7104
+ `api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
7105
+ denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
7106
+ );
7107
+ } catch (e) {
7108
+ pushLog(`api error \xB7 ${truncate2(e instanceof Error ? e.message : String(e), 40)}`, "bad");
7109
+ }
7110
+ }, [pushLog]);
7111
+ useEffect2(() => {
7112
+ if (!active2) return;
7113
+ void doPoll();
7114
+ const t = setInterval(() => void doPoll(), 2e3);
7115
+ return () => clearInterval(t);
7116
+ }, [active2, doPoll]);
7117
+ const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7062
7118
  const insights = useLoader(() => api.stats.securityInsights(7));
7063
- usePoll(() => {
7064
- stats.reload();
7065
- ts.reload();
7066
- feed.reload();
7067
- }, 2e3, active2);
7119
+ usePoll(sessions.reload, 5e3, active2);
7068
7120
  usePoll(insights.reload, 1e4, active2);
7121
+ useEffect2(() => {
7122
+ const c2 = sessions.data?.counts;
7123
+ if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
7124
+ }, [sessions.data, pushLog]);
7125
+ useEffect2(() => {
7126
+ const ib = insights.data;
7127
+ if (ib?.layers) {
7128
+ const rl2 = ib.layers.rateLimit;
7129
+ const dl2 = ib.layers.dlp;
7130
+ pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
7131
+ }
7132
+ }, [insights.data, pushLog]);
7069
7133
  const [tick, setTick] = useState2(0);
7070
7134
  useEffect2(() => {
7071
7135
  if (!active2) return;
@@ -7073,107 +7137,128 @@ function LivePanel({ active: active2 }) {
7073
7137
  return () => clearInterval(t);
7074
7138
  }, [active2]);
7075
7139
  const startRef = useRef2(Date.now());
7076
- const [buffer, setBuffer] = useState2([]);
7077
- useEffect2(() => {
7078
- const incoming = feed.data?.entries;
7079
- if (!incoming?.length) return;
7080
- setBuffer((prev) => {
7081
- const seen = new Set(prev.map((e) => e.id));
7082
- const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
7083
- return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
7084
- });
7085
- }, [feed.data]);
7086
7140
  const cols = process.stdout.columns ?? 100;
7087
7141
  const rows = process.stdout.rows ?? 30;
7088
- const s = stats.data;
7089
7142
  const ins = insights.data ?? {};
7090
- const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
7091
- const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
7092
- const minuteLive = minuteRaw.some((c2) => c2 > 0);
7093
- const minuteSeries = minuteLive ? minuteRaw : hourRaw;
7094
- const activityLabel = minuteLive ? "calls/min \xB7 last 60m" : "calls/hour \xB7 last 24h";
7095
- const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
7096
- const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
7097
7143
  const spin = SPIN[tick % SPIN.length];
7098
- const cursor = tick % 4 < 2 ? "\u258C" : " ";
7144
+ const points = tsData?.timeseries ?? [];
7145
+ const traffic = points.map((p) => p.total);
7146
+ const trafficHot = points.map((p) => p.denied > 0);
7147
+ const rl = ins.layers?.rateLimit;
7148
+ const dl = ins.layers?.dlp;
7149
+ const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
7150
+ const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
7151
+ const maxDlpBar = dlpBars[0]?.count ?? 1;
7099
7152
  const toolCounts = /* @__PURE__ */ new Map();
7100
- const permCounts = /* @__PURE__ */ new Map();
7101
- for (const e of buffer) {
7102
- toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
7103
- const perm = (e.permission || "?").toUpperCase();
7104
- permCounts.set(perm, (permCounts.get(perm) ?? 0) + 1);
7105
- }
7106
- const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
7107
- const maxTool = topTools[0]?.[1] ?? 1;
7108
- const perms = ["READ", "WRITE", "EXECUTE", "NETWORK"].map((p) => [p, permCounts.get(p) ?? 0]);
7109
- const maxPerm = Math.max(1, ...perms.map(([, c2]) => c2));
7110
- const dlpTop = (ins.dlpByPattern ?? []).slice(0, 2);
7153
+ for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
7154
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7111
7155
  const denials = buffer.filter((e) => e.decision !== "ALLOW");
7112
7156
  const lastDeny = denials[denials.length - 1];
7113
- const bursts = limit > 0 ? minuteSeries.filter((c2) => c2 > limit).length : 0;
7114
- const bigH = rows >= 34 ? 6 : 4;
7115
- const colH = rows >= 34 ? 6 : 5;
7116
- const streamRows = Math.max(4, rows - (2 + 1 + bigH + 1 + colH + 1 + 1));
7157
+ const latNow = lat[lat.length - 1] ?? 0;
7158
+ const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
7159
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
7160
+ const sessCounts = sessions.data?.counts;
7161
+ const chartH = rows >= 36 ? 6 : 4;
7162
+ const colH = rows >= 32 ? 6 : 5;
7163
+ const streamRows = Math.max(4, rows - 6 - chartH - colH);
7117
7164
  const tail = buffer.slice(-streamRows);
7118
- const colW = Math.max(18, Math.floor((cols - 6) / 3));
7119
- if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7120
- "\u2717 ",
7121
- stats.error
7122
- ] });
7165
+ const innerW = cols - 2;
7166
+ const leftW = Math.floor(innerW * 0.55);
7167
+ const rightW = innerW - leftW - 2;
7168
+ const colW = Math.max(20, Math.floor((innerW - 4) / 3));
7169
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7123
7170
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7124
7171
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7125
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE::LIVE " }),
7126
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7127
- " ",
7128
- spin,
7129
- " up ",
7130
- fmtUp(Date.now() - startRef.current),
7131
- " \xB7 ",
7132
- hhmmss(Date.now()),
7133
- " \xB7 poll 2s"
7134
- ] }),
7135
- lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7136
- " \u26A0 ",
7137
- hhmmss(lastDeny.created_at),
7138
- " ",
7139
- lastDeny.tool_name,
7140
- " denied"
7141
- ] }) : null,
7142
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " esc menu \xB7 q quit" })
7172
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
7173
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
7174
+ lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.created_at)} ${lastDeny.tool_name} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
7143
7175
  ] }),
7144
7176
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7145
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
7177
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
7146
7178
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
7147
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " allow " }),
7179
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
7148
7180
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
7149
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " deny " }),
7181
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7150
7182
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
7151
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " bursts(60m) " }),
7152
- /* @__PURE__ */ jsx2(Text2, { color: bursts ? theme.bad : theme.dim, children: bursts }),
7153
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " limit/min " }),
7154
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: limit || "off" }),
7155
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " policies " }),
7156
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" })
7183
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
7184
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
7185
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
7186
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
7187
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
7188
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
7189
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
7190
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
7191
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7192
+ ] }),
7193
+ /* @__PURE__ */ jsxs2(Box2, { children: [
7194
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
7195
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
7196
+ /* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
7197
+ ] }),
7198
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
7199
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms`, width: rightW }),
7200
+ /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > 800), height: chartH, width: rightW, color: "#82AAF0" })
7201
+ ] })
7157
7202
  ] }),
7158
- /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `ACTIVITY ${activityLabel} \xB7 peak ${Math.max(0, ...minuteSeries)}${limit ? ` \xB7 limit ${limit} (red = over)` : ""}` }),
7159
- minuteSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: minuteSeries, height: bigH, width: cols - 2, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: bigH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? "loading activity\u2026" : "no activity data" }) }),
7160
7203
  /* @__PURE__ */ jsxs2(Box2, { children: [
7161
7204
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7162
- /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "DENIALS 24h" }),
7163
- /* @__PURE__ */ jsx2(ColumnChart, { series: denySeries, height: colH - 1, width: colW, color: theme.bad })
7205
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
7206
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7207
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
7208
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
7209
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
7210
+ ] }),
7211
+ 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,
7212
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7213
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
7214
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
7215
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7216
+ (dl?.patterns ?? []).length,
7217
+ " patterns armed"
7218
+ ] })
7219
+ ] }),
7220
+ 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: "hits 7d none \u2014 clean" })
7164
7221
  ] }),
7165
7222
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7166
- /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "TOP TOOLS (buffer)" }),
7167
- topTools.length ? topTools.slice(0, colH - 1).map(([tool, count]) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(tool, 9), value: count, max: maxTool, width: Math.max(6, colW - 16), color: theme.accent }, tool)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no traffic yet" })
7223
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
7224
+ sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7225
+ sess.slice(0, colH - 1).map((a) => {
7226
+ const dot = sessionDot(a.status);
7227
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7228
+ /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
7229
+ dot.ch,
7230
+ " "
7231
+ ] }),
7232
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(a.agent_name ?? a.session_id, 12).padEnd(13) }),
7233
+ /* @__PURE__ */ jsxs2(Text2, { children: [
7234
+ String(a.total_calls).padStart(4),
7235
+ "c "
7236
+ ] }),
7237
+ /* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
7238
+ String(a.denied_calls).padStart(3),
7239
+ "d "
7240
+ ] }),
7241
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
7242
+ ] }, a.session_id);
7243
+ })
7168
7244
  ] }),
7169
7245
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
7170
- /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "PERMISSIONS \xB7 DLP" }),
7171
- perms.map(([p, c2]) => /* @__PURE__ */ jsx2(HBar, { label: p, value: c2, max: maxPerm, width: Math.max(6, colW - 16), color: p === "EXECUTE" ? theme.warn : theme.accent }, p)),
7172
- dlpTop.map((d) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.bad, children: "DLP " + truncate2(d.pattern, colW - 10) + " \xD7" + d.count }, d.pattern))
7246
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
7247
+ log6.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7248
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7249
+ hhmmss(l.ts),
7250
+ " "
7251
+ ] }),
7252
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
7253
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
7254
+ ] }, l.ts + ":" + i))
7173
7255
  ] })
7174
7256
  ] }),
7175
- /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `TOOL STREAM \xB7 live tail \xB7 ${buffer.length} buffered` }),
7176
- tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
7257
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
7258
+ tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7259
+ spin,
7260
+ " awaiting traffic\u2026"
7261
+ ] }) : null,
7177
7262
  tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7178
7263
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7179
7264
  "[",
@@ -7187,15 +7272,13 @@ function LivePanel({ active: active2 }) {
7187
7272
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
7188
7273
  ] }, e.id)),
7189
7274
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7190
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "root@solongate" }),
7191
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ":" }),
7192
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: "~/live" }),
7193
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "$ " }),
7194
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: cursor })
7275
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
7276
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
7277
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
7195
7278
  ] })
7196
7279
  ] });
7197
7280
  }
7198
- var SPIN, hhmmss, fmtUp;
7281
+ var SPIN, BG, hhmmss, fmtUp;
7199
7282
  var init_Live = __esm({
7200
7283
  "src/tui/panels/Live.tsx"() {
7201
7284
  "use strict";
@@ -7203,6 +7286,7 @@ var init_Live = __esm({
7203
7286
  init_hooks();
7204
7287
  init_theme();
7205
7288
  SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7289
+ BG = "#12234f";
7206
7290
  hhmmss = (ts) => {
7207
7291
  const d = new Date(ts);
7208
7292
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
package/dist/tui/index.js CHANGED
@@ -126,7 +126,7 @@ function KeyHints({ hints }) {
126
126
 
127
127
  // src/tui/panels/Live.tsx
128
128
  import { Box as Box2, Text as Text2 } from "ink";
129
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
129
+ import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
130
130
 
131
131
  // src/api-client/client.ts
132
132
  import { readFileSync, existsSync } from "fs";
@@ -432,6 +432,7 @@ function usePoll(reload, intervalMs, enabled = true) {
432
432
  // src/tui/panels/Live.tsx
433
433
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
434
434
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
435
+ var BG = "#12234f";
435
436
  var hhmmss = (ts) => {
436
437
  const d = new Date(ts);
437
438
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -441,17 +442,25 @@ var fmtUp = (ms) => {
441
442
  const p = (n) => String(n).padStart(2, "0");
442
443
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
443
444
  };
444
- function ColumnChart({ series, height, width, color, limit = 0 }) {
445
- const tailVals = series.slice(-width);
446
- const data = [...new Array(Math.max(0, width - tailVals.length)).fill(0), ...tailVals];
447
- const max = Math.max(1, ...data, limit);
445
+ function ColumnChart({ series, hot, height, width, color }) {
446
+ const pad = Math.max(0, width - series.length);
447
+ const data = [...new Array(pad).fill(0), ...series.slice(-width)];
448
+ const hotPad = hot ? [...new Array(pad).fill(false), ...hot.slice(-width)] : void 0;
449
+ const max = Math.max(1, ...data);
448
450
  const lines = [];
449
451
  for (let r = height; r >= 1; r--) {
450
452
  const segs = [];
451
453
  for (let i = 0; i < data.length; i++) {
452
- const frac = data[i] / max;
453
- const ch = frac >= r / height ? "\u2588" : frac >= (r - 0.5) / height ? "\u2584" : r === 1 && data[i] > 0 ? "\u2581" : " ";
454
- const c2 = limit > 0 && data[i] > limit ? theme.bad : color;
454
+ const v = data[i];
455
+ const frac = v / max;
456
+ let ch;
457
+ let c2;
458
+ if (frac >= r / height) ch = "\u2588";
459
+ else if (frac >= (r - 0.5) / height) ch = "\u2584";
460
+ else if (r === 1) ch = "\u2581";
461
+ else ch = " ";
462
+ if (r === 1 && v === 0) c2 = "#233457";
463
+ else c2 = hotPad?.[i] ? theme.bad : color;
455
464
  const last = segs[segs.length - 1];
456
465
  if (last && last.color === c2) last.text += ch;
457
466
  else segs.push({ text: ch, color: c2 });
@@ -465,26 +474,82 @@ function ColumnChart({ series, height, width, color, limit = 0 }) {
465
474
  function HBar({ label, value, max, width, color }) {
466
475
  const filled = max > 0 ? Math.round(value / max * width) : 0;
467
476
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
468
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(9) }),
477
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
469
478
  /* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width, filled)) }),
470
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2591".repeat(Math.max(0, width - filled)) + " " + value })
479
+ /* @__PURE__ */ jsx2(Text2, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
480
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
471
481
  ] });
472
482
  }
473
- function SectionTitle({ children, width }) {
474
- const label = `\u2500\u2500 ${children} `;
475
- return /* @__PURE__ */ jsx2(Text2, { color: theme.dim, wrap: "truncate", children: label + "\u2500".repeat(Math.max(0, width - label.length)) });
483
+ function PaneTitle({ label, extra, width }) {
484
+ const tail = extra ? ` ${extra} ` : " ";
485
+ const used = 2 + 1 + label.length + tail.length;
486
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
487
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
488
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
489
+ "\u258E",
490
+ label
491
+ ] }),
492
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
493
+ ] });
476
494
  }
477
495
  function LivePanel({ active: active2 }) {
478
- const stats = useLoader(() => api.stats.get());
479
- const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
480
- const feed = useLoader(() => api.audit.list({ limit: 50 }));
496
+ const [s, setS] = useState2(null);
497
+ const [tsData, setTsData] = useState2(null);
498
+ const [lat, setLat] = useState2([]);
499
+ const [buffer, setBuffer] = useState2([]);
500
+ const [log, setLog] = useState2([]);
501
+ const seenRef = useRef2(/* @__PURE__ */ new Set());
502
+ const pushLog = useCallback2((msg, level = "ok") => {
503
+ setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
504
+ }, []);
505
+ const doPoll = useCallback2(async () => {
506
+ const t0 = Date.now();
507
+ try {
508
+ const [sd, fd, td] = await Promise.all([api.stats.get(), api.audit.list({ limit: 50 }), api.stats.timeseries({ period: "24h" })]);
509
+ const ms = Date.now() - t0;
510
+ setS(sd);
511
+ setTsData(td);
512
+ setLat((l) => [...l.slice(-47), ms]);
513
+ const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
514
+ for (const e of fresh) seenRef.current.add(e.id);
515
+ const firstLoad = seenRef.current.size === fresh.length && fresh.length > 1;
516
+ if (fresh.length) {
517
+ setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
518
+ }
519
+ const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
520
+ const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
521
+ if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
522
+ else
523
+ pushLog(
524
+ `api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
525
+ denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
526
+ );
527
+ } catch (e) {
528
+ pushLog(`api error \xB7 ${truncate(e instanceof Error ? e.message : String(e), 40)}`, "bad");
529
+ }
530
+ }, [pushLog]);
531
+ useEffect2(() => {
532
+ if (!active2) return;
533
+ void doPoll();
534
+ const t = setInterval(() => void doPoll(), 2e3);
535
+ return () => clearInterval(t);
536
+ }, [active2, doPoll]);
537
+ const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
481
538
  const insights = useLoader(() => api.stats.securityInsights(7));
482
- usePoll(() => {
483
- stats.reload();
484
- ts.reload();
485
- feed.reload();
486
- }, 2e3, active2);
539
+ usePoll(sessions.reload, 5e3, active2);
487
540
  usePoll(insights.reload, 1e4, active2);
541
+ useEffect2(() => {
542
+ const c2 = sessions.data?.counts;
543
+ if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
544
+ }, [sessions.data, pushLog]);
545
+ useEffect2(() => {
546
+ const ib = insights.data;
547
+ if (ib?.layers) {
548
+ const rl2 = ib.layers.rateLimit;
549
+ const dl2 = ib.layers.dlp;
550
+ pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
551
+ }
552
+ }, [insights.data, pushLog]);
488
553
  const [tick, setTick] = useState2(0);
489
554
  useEffect2(() => {
490
555
  if (!active2) return;
@@ -492,107 +557,128 @@ function LivePanel({ active: active2 }) {
492
557
  return () => clearInterval(t);
493
558
  }, [active2]);
494
559
  const startRef = useRef2(Date.now());
495
- const [buffer, setBuffer] = useState2([]);
496
- useEffect2(() => {
497
- const incoming = feed.data?.entries;
498
- if (!incoming?.length) return;
499
- setBuffer((prev) => {
500
- const seen = new Set(prev.map((e) => e.id));
501
- const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
502
- return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
503
- });
504
- }, [feed.data]);
505
560
  const cols = process.stdout.columns ?? 100;
506
561
  const rows = process.stdout.rows ?? 30;
507
- const s = stats.data;
508
562
  const ins = insights.data ?? {};
509
- const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
510
- const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
511
- const minuteLive = minuteRaw.some((c2) => c2 > 0);
512
- const minuteSeries = minuteLive ? minuteRaw : hourRaw;
513
- const activityLabel = minuteLive ? "calls/min \xB7 last 60m" : "calls/hour \xB7 last 24h";
514
- const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
515
- const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
516
563
  const spin = SPIN[tick % SPIN.length];
517
- const cursor = tick % 4 < 2 ? "\u258C" : " ";
564
+ const points = tsData?.timeseries ?? [];
565
+ const traffic = points.map((p) => p.total);
566
+ const trafficHot = points.map((p) => p.denied > 0);
567
+ const rl = ins.layers?.rateLimit;
568
+ const dl = ins.layers?.dlp;
569
+ const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
570
+ const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
571
+ const maxDlpBar = dlpBars[0]?.count ?? 1;
518
572
  const toolCounts = /* @__PURE__ */ new Map();
519
- const permCounts = /* @__PURE__ */ new Map();
520
- for (const e of buffer) {
521
- toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
522
- const perm = (e.permission || "?").toUpperCase();
523
- permCounts.set(perm, (permCounts.get(perm) ?? 0) + 1);
524
- }
525
- const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
526
- const maxTool = topTools[0]?.[1] ?? 1;
527
- const perms = ["READ", "WRITE", "EXECUTE", "NETWORK"].map((p) => [p, permCounts.get(p) ?? 0]);
528
- const maxPerm = Math.max(1, ...perms.map(([, c2]) => c2));
529
- const dlpTop = (ins.dlpByPattern ?? []).slice(0, 2);
573
+ for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
574
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
530
575
  const denials = buffer.filter((e) => e.decision !== "ALLOW");
531
576
  const lastDeny = denials[denials.length - 1];
532
- const bursts = limit > 0 ? minuteSeries.filter((c2) => c2 > limit).length : 0;
533
- const bigH = rows >= 34 ? 6 : 4;
534
- const colH = rows >= 34 ? 6 : 5;
535
- const streamRows = Math.max(4, rows - (2 + 1 + bigH + 1 + colH + 1 + 1));
577
+ const latNow = lat[lat.length - 1] ?? 0;
578
+ const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
579
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
580
+ const sessCounts = sessions.data?.counts;
581
+ const chartH = rows >= 36 ? 6 : 4;
582
+ const colH = rows >= 32 ? 6 : 5;
583
+ const streamRows = Math.max(4, rows - 6 - chartH - colH);
536
584
  const tail = buffer.slice(-streamRows);
537
- const colW = Math.max(18, Math.floor((cols - 6) / 3));
538
- if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
539
- "\u2717 ",
540
- stats.error
541
- ] });
585
+ const innerW = cols - 2;
586
+ const leftW = Math.floor(innerW * 0.55);
587
+ const rightW = innerW - leftW - 2;
588
+ const colW = Math.max(20, Math.floor((innerW - 4) / 3));
589
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
542
590
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
543
591
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
544
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE::LIVE " }),
545
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
546
- " ",
547
- spin,
548
- " up ",
549
- fmtUp(Date.now() - startRef.current),
550
- " \xB7 ",
551
- hhmmss(Date.now()),
552
- " \xB7 poll 2s"
553
- ] }),
554
- lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
555
- " \u26A0 ",
556
- hhmmss(lastDeny.created_at),
557
- " ",
558
- lastDeny.tool_name,
559
- " denied"
560
- ] }) : null,
561
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " esc menu \xB7 q quit" })
592
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
593
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
594
+ lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.created_at)} ${lastDeny.tool_name} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
562
595
  ] }),
563
596
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
564
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
597
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
565
598
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
566
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " allow " }),
599
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
567
600
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
568
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " deny " }),
601
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
569
602
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
570
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " bursts(60m) " }),
571
- /* @__PURE__ */ jsx2(Text2, { color: bursts ? theme.bad : theme.dim, children: bursts }),
572
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " limit/min " }),
573
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: limit || "off" }),
574
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " policies " }),
575
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" })
603
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
604
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
605
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
606
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
607
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
608
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
609
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
610
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
611
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
612
+ ] }),
613
+ /* @__PURE__ */ jsxs2(Box2, { children: [
614
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
615
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
616
+ /* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
617
+ ] }),
618
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
619
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms`, width: rightW }),
620
+ /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > 800), height: chartH, width: rightW, color: "#82AAF0" })
621
+ ] })
576
622
  ] }),
577
- /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `ACTIVITY ${activityLabel} \xB7 peak ${Math.max(0, ...minuteSeries)}${limit ? ` \xB7 limit ${limit} (red = over)` : ""}` }),
578
- minuteSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: minuteSeries, height: bigH, width: cols - 2, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: bigH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? "loading activity\u2026" : "no activity data" }) }),
579
623
  /* @__PURE__ */ jsxs2(Box2, { children: [
580
624
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
581
- /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "DENIALS 24h" }),
582
- /* @__PURE__ */ jsx2(ColumnChart, { series: denySeries, height: colH - 1, width: colW, color: theme.bad })
625
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
626
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
627
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
628
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
629
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
630
+ ] }),
631
+ 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,
632
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
633
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
634
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
635
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
636
+ (dl?.patterns ?? []).length,
637
+ " patterns armed"
638
+ ] })
639
+ ] }),
640
+ 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: "hits 7d none \u2014 clean" })
583
641
  ] }),
584
642
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
585
- /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "TOP TOOLS (buffer)" }),
586
- topTools.length ? topTools.slice(0, colH - 1).map(([tool, count]) => /* @__PURE__ */ jsx2(HBar, { label: truncate(tool, 9), value: count, max: maxTool, width: Math.max(6, colW - 16), color: theme.accent }, tool)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no traffic yet" })
643
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
644
+ sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
645
+ sess.slice(0, colH - 1).map((a) => {
646
+ const dot = sessionDot(a.status);
647
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
648
+ /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
649
+ dot.ch,
650
+ " "
651
+ ] }),
652
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(a.agent_name ?? a.session_id, 12).padEnd(13) }),
653
+ /* @__PURE__ */ jsxs2(Text2, { children: [
654
+ String(a.total_calls).padStart(4),
655
+ "c "
656
+ ] }),
657
+ /* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
658
+ String(a.denied_calls).padStart(3),
659
+ "d "
660
+ ] }),
661
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
662
+ ] }, a.session_id);
663
+ })
587
664
  ] }),
588
665
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
589
- /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "PERMISSIONS \xB7 DLP" }),
590
- perms.map(([p, c2]) => /* @__PURE__ */ jsx2(HBar, { label: p, value: c2, max: maxPerm, width: Math.max(6, colW - 16), color: p === "EXECUTE" ? theme.warn : theme.accent }, p)),
591
- dlpTop.map((d) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.bad, children: "DLP " + truncate(d.pattern, colW - 10) + " \xD7" + d.count }, d.pattern))
666
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
667
+ log.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
668
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
669
+ hhmmss(l.ts),
670
+ " "
671
+ ] }),
672
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
673
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
674
+ ] }, l.ts + ":" + i))
592
675
  ] })
593
676
  ] }),
594
- /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `TOOL STREAM \xB7 live tail \xB7 ${buffer.length} buffered` }),
595
- tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
677
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
678
+ tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
679
+ spin,
680
+ " awaiting traffic\u2026"
681
+ ] }) : null,
596
682
  tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
597
683
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
598
684
  "[",
@@ -606,11 +692,9 @@ function LivePanel({ active: active2 }) {
606
692
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
607
693
  ] }, e.id)),
608
694
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
609
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "root@solongate" }),
610
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ":" }),
611
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: "~/live" }),
612
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "$ " }),
613
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: cursor })
695
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
696
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
697
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
614
698
  ] })
615
699
  ] });
616
700
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.64.0",
3
+ "version": "0.66.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": {