@solongate/proxy 0.65.0 → 0.66.1

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,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,17 +7054,17 @@ 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
7063
  function PaneTitle({ label, extra, width: width2 }) {
7055
- const head = `\u2500 `;
7056
7064
  const tail = extra ? ` ${extra} ` : " ";
7057
- const used = head.length + 1 + label.length + tail.length;
7065
+ const used = 2 + 1 + label.length + tail.length;
7058
7066
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7059
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: head }),
7067
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
7060
7068
  /* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
7061
7069
  "\u258E",
7062
7070
  label
@@ -7065,18 +7073,104 @@ function PaneTitle({ label, extra, width: width2 }) {
7065
7073
  ] });
7066
7074
  }
7067
7075
  function LivePanel({ active: active2 }) {
7068
- const stats = useLoader(() => api.stats.get());
7069
- const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
7070
- const feed = useLoader(() => api.audit.list({ limit: 50 }));
7071
- const insights = useLoader(() => api.stats.securityInsights(7));
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 pausedUntil = useRef2(0);
7083
+ const pushLog = useCallback2((msg, level = "ok") => {
7084
+ setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
7085
+ }, []);
7086
+ const onApiError = useCallback2(
7087
+ (e) => {
7088
+ const is429 = e instanceof ApiError && e.status === 429;
7089
+ if (is429 && Date.now() >= pausedUntil.current) {
7090
+ pausedUntil.current = Date.now() + 3e4;
7091
+ pushLog("rate limited by api \xB7 backing off 30s", "warn");
7092
+ } else if (!is429) {
7093
+ pushLog(`api error \xB7 ${truncate2(e instanceof Error ? e.message : String(e), 40)}`, "bad");
7094
+ }
7095
+ },
7096
+ [pushLog]
7097
+ );
7098
+ const paused = () => Date.now() < pausedUntil.current;
7099
+ const pollFeed = useCallback2(async () => {
7100
+ if (paused()) return;
7101
+ const t0 = Date.now();
7102
+ try {
7103
+ const fd = await api.audit.list({ limit: 50 });
7104
+ const ms = Date.now() - t0;
7105
+ setLat((l) => [...l, ms].slice(-240));
7106
+ const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
7107
+ const firstLoad = seenRef.current.size === 0 && fresh.length > 1;
7108
+ for (const e of fresh) seenRef.current.add(e.id);
7109
+ if (fresh.length) {
7110
+ setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
7111
+ }
7112
+ const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
7113
+ const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
7114
+ if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
7115
+ else
7116
+ pushLog(
7117
+ `api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
7118
+ denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
7119
+ );
7120
+ } catch (e) {
7121
+ onApiError(e);
7122
+ }
7123
+ }, [pushLog, onApiError]);
7124
+ const pollMedium = useCallback2(async () => {
7125
+ if (paused()) return;
7126
+ try {
7127
+ setS(await api.stats.get());
7128
+ } catch (e) {
7129
+ onApiError(e);
7130
+ }
7131
+ }, [onApiError]);
7132
+ const pollSlow = useCallback2(async () => {
7133
+ if (paused()) return;
7134
+ try {
7135
+ setTsData(await api.stats.timeseries({ period: "24h" }));
7136
+ } catch (e) {
7137
+ onApiError(e);
7138
+ }
7139
+ }, [onApiError]);
7140
+ useEffect2(() => {
7141
+ if (!active2) return;
7142
+ void pollFeed();
7143
+ void pollMedium();
7144
+ void pollSlow();
7145
+ const t1 = setInterval(() => void pollFeed(), 4e3);
7146
+ const t2 = setInterval(() => void pollMedium(), 1e4);
7147
+ const t3 = setInterval(() => void pollSlow(), 3e4);
7148
+ return () => {
7149
+ clearInterval(t1);
7150
+ clearInterval(t2);
7151
+ clearInterval(t3);
7152
+ };
7153
+ }, [active2, pollFeed, pollMedium, pollSlow]);
7072
7154
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7155
+ const insights = useLoader(() => api.stats.securityInsights(7));
7073
7156
  usePoll(() => {
7074
- stats.reload();
7075
- ts.reload();
7076
- feed.reload();
7077
- }, 2e3, active2);
7078
- usePoll(sessions.reload, 5e3, active2);
7079
- usePoll(insights.reload, 1e4, active2);
7157
+ if (!paused()) sessions.reload();
7158
+ }, 1e4, active2);
7159
+ usePoll(() => {
7160
+ if (!paused()) insights.reload();
7161
+ }, 3e4, active2);
7162
+ useEffect2(() => {
7163
+ const c2 = sessions.data?.counts;
7164
+ if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
7165
+ }, [sessions.data, pushLog]);
7166
+ useEffect2(() => {
7167
+ const ib = insights.data;
7168
+ if (ib?.layers) {
7169
+ const rl2 = ib.layers.rateLimit;
7170
+ const dl2 = ib.layers.dlp;
7171
+ pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
7172
+ }
7173
+ }, [insights.data, pushLog]);
7080
7174
  const [tick, setTick] = useState2(0);
7081
7175
  useEffect2(() => {
7082
7176
  if (!active2) return;
@@ -7084,61 +7178,43 @@ function LivePanel({ active: active2 }) {
7084
7178
  return () => clearInterval(t);
7085
7179
  }, [active2]);
7086
7180
  const startRef = useRef2(Date.now());
7087
- const [buffer, setBuffer] = useState2([]);
7088
- useEffect2(() => {
7089
- const incoming = feed.data?.entries;
7090
- if (!incoming?.length) return;
7091
- setBuffer((prev) => {
7092
- const seen = new Set(prev.map((e) => e.id));
7093
- const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
7094
- return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
7095
- });
7096
- }, [feed.data]);
7097
7181
  const cols = process.stdout.columns ?? 100;
7098
7182
  const rows = process.stdout.rows ?? 30;
7099
- const s = stats.data;
7100
7183
  const ins = insights.data ?? {};
7101
- const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
7102
- const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
7103
- const minuteLive = minuteRaw.some((c2) => c2 > 0);
7104
- const rateSeries = minuteLive ? minuteRaw : hourRaw;
7105
- const rateLabel = minuteLive ? "calls/min \xB7 60m" : "calls/hour \xB7 24h";
7106
- const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
7107
- const rlMode = ins.layers?.rateLimit?.mode ?? "?";
7108
- const dlpMode = ins.layers?.dlp?.mode ?? "?";
7109
- const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
7110
7184
  const spin = SPIN[tick % SPIN.length];
7185
+ const points = tsData?.timeseries ?? [];
7186
+ const traffic = points.map((p) => p.total);
7187
+ const trafficHot = points.map((p) => p.denied > 0);
7188
+ const rl = ins.layers?.rateLimit;
7189
+ const dl = ins.layers?.dlp;
7190
+ const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
7191
+ const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
7192
+ const maxDlpBar = dlpBars[0]?.count ?? 1;
7111
7193
  const toolCounts = /* @__PURE__ */ new Map();
7112
- let dlpBufHits = 0;
7113
- for (const e of buffer) {
7114
- toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
7115
- if (e.dlp_matches?.length) dlpBufHits++;
7116
- }
7194
+ for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
7117
7195
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
7118
- const dlpBars = (ins.dlpByPattern ?? []).slice(0, 3);
7119
- const maxDlp = dlpBars[0]?.count ?? 1;
7120
- const dlpRecent = (ins.dlpHits ?? []).slice(0, 2);
7121
7196
  const denials = buffer.filter((e) => e.decision !== "ALLOW");
7122
7197
  const lastDeny = denials[denials.length - 1];
7123
- const bursts = limit > 0 ? rateSeries.filter((c2) => c2 > limit).length : 0;
7198
+ const latNow = lat[lat.length - 1] ?? 0;
7199
+ const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
7200
+ const latHotAt = Math.max(1500, latAvg * 1.8);
7201
+ const backingOff = Date.now() < pausedUntil.current;
7124
7202
  const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
7125
7203
  const sessCounts = sessions.data?.counts;
7126
7204
  const chartH = rows >= 36 ? 6 : 4;
7127
7205
  const colH = rows >= 32 ? 6 : 5;
7128
7206
  const streamRows = Math.max(4, rows - 6 - chartH - colH);
7129
7207
  const tail = buffer.slice(-streamRows);
7130
- const colW = Math.max(20, Math.floor((cols - 6) / 3));
7131
7208
  const innerW = cols - 2;
7132
- if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7133
- "\u2717 ",
7134
- stats.error
7135
- ] });
7209
+ const leftW = Math.floor(innerW * 0.55);
7210
+ const rightW = innerW - leftW - 2;
7211
+ const colW = Math.max(20, Math.floor((innerW - 4) / 3));
7136
7212
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7137
7213
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7138
7214
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7139
7215
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
7140
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} ` }),
7141
- 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 no recent denials " })
7216
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
7217
+ backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " \u23F8 RATE LIMITED \xB7 backing off " }) : 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 " })
7142
7218
  ] }),
7143
7219
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7144
7220
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
@@ -7147,40 +7223,48 @@ function LivePanel({ active: active2 }) {
7147
7223
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
7148
7224
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7149
7225
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
7150
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp(buf) " }),
7151
- /* @__PURE__ */ jsx2(Text2, { color: dlpBufHits ? theme.bad : theme.dim, children: dlpBufHits }),
7152
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 bursts " }),
7153
- /* @__PURE__ */ jsx2(Text2, { color: bursts ? theme.bad : theme.dim, children: bursts }),
7226
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
7227
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
7228
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
7229
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
7154
7230
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
7155
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC` : "\xB7" }),
7231
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
7156
7232
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
7157
7233
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
7158
7234
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7159
7235
  ] }),
7160
- /* @__PURE__ */ jsx2(PaneTitle, { label: "RATE LIMIT", extra: `${rateLabel} \xB7 peak ${Math.max(0, ...rateSeries)} \xB7 limit ${limit || "off"} \xB7 mode ${rlMode}${limit ? " \xB7 red = over" : ""}`, width: innerW }),
7161
- rateSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: rateSeries, height: chartH, width: innerW, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: chartH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? `${spin} loading activity\u2026` : "no activity data" }) }),
7162
7236
  /* @__PURE__ */ jsxs2(Box2, { children: [
7163
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7164
- /* @__PURE__ */ jsx2(PaneTitle, { label: "DENIALS", extra: "24h", width: colW }),
7165
- /* @__PURE__ */ jsx2(ColumnChart, { series: denySeries, height: colH - 1, width: colW, color: theme.bad })
7237
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
7238
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
7239
+ /* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
7166
7240
  ] }),
7241
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
7242
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms \xB7 red >${Math.round(latHotAt)}ms`, width: rightW }),
7243
+ /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0" })
7244
+ ] })
7245
+ ] }),
7246
+ /* @__PURE__ */ jsxs2(Box2, { children: [
7167
7247
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7168
- /* @__PURE__ */ jsx2(PaneTitle, { label: "DLP", extra: `7d \xB7 mode ${dlpMode}`, width: colW }),
7169
- dlpBars.length ? dlpBars.map((d) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(d.pattern, 9), value: d.count, max: maxDlp, width: Math.max(6, colW - 16), color: theme.bad }, d.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no dlp hits" }),
7170
- dlpRecent.map((h, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7171
- /* @__PURE__ */ jsx2(Text2, { color: h.blocked ? theme.accent : theme.bad, children: h.blocked ? "\u2298" : "\u26A0" }),
7248
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
7249
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7250
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
7251
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
7252
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
7253
+ ] }),
7254
+ 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,
7255
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7256
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
7257
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
7172
7258
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7173
- " ",
7174
- truncate2(h.pattern, colW - 14),
7175
- " ",
7176
- ago(h.at),
7177
- " ago"
7259
+ (dl?.patterns ?? []).length,
7260
+ " patterns armed"
7178
7261
  ] })
7179
- ] }, i))
7262
+ ] }),
7263
+ 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" })
7180
7264
  ] }),
7181
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
7182
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} active` : "", width: colW }),
7183
- sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no sessions" }) : null,
7265
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7266
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
7267
+ sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
7184
7268
  sess.slice(0, colH - 1).map((a) => {
7185
7269
  const dot = sessionDot(a.status);
7186
7270
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
@@ -7200,10 +7284,24 @@ function LivePanel({ active: active2 }) {
7200
7284
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
7201
7285
  ] }, a.session_id);
7202
7286
  })
7287
+ ] }),
7288
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
7289
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
7290
+ log6.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7291
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7292
+ hhmmss(l.ts),
7293
+ " "
7294
+ ] }),
7295
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
7296
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
7297
+ ] }, l.ts + ":" + i))
7203
7298
  ] })
7204
7299
  ] }),
7205
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `live tail \xB7 ${buffer.length} buffered`, width: innerW }),
7206
- tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
7300
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
7301
+ tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7302
+ spin,
7303
+ " awaiting traffic\u2026"
7304
+ ] }) : null,
7207
7305
  tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7208
7306
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7209
7307
  "[",
@@ -7218,7 +7316,7 @@ function LivePanel({ active: active2 }) {
7218
7316
  ] }, e.id)),
7219
7317
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7220
7318
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
7221
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
7319
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
7222
7320
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
7223
7321
  ] })
7224
7322
  ] });
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";
@@ -442,17 +442,25 @@ var fmtUp = (ms) => {
442
442
  const p = (n) => String(n).padStart(2, "0");
443
443
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
444
444
  };
445
- function ColumnChart({ series, height, width, color, limit = 0 }) {
446
- const tailVals = series.slice(-width);
447
- const data = [...new Array(Math.max(0, width - tailVals.length)).fill(0), ...tailVals];
448
- 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);
449
450
  const lines = [];
450
451
  for (let r = height; r >= 1; r--) {
451
452
  const segs = [];
452
453
  for (let i = 0; i < data.length; i++) {
453
- const frac = data[i] / max;
454
- const ch = frac >= r / height ? "\u2588" : frac >= (r - 0.5) / height ? "\u2584" : r === 1 && data[i] > 0 ? "\u2581" : " ";
455
- 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;
456
464
  const last = segs[segs.length - 1];
457
465
  if (last && last.color === c2) last.text += ch;
458
466
  else segs.push({ text: ch, color: c2 });
@@ -466,17 +474,17 @@ function ColumnChart({ series, height, width, color, limit = 0 }) {
466
474
  function HBar({ label, value, max, width, color }) {
467
475
  const filled = max > 0 ? Math.round(value / max * width) : 0;
468
476
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
469
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(9) }),
477
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
470
478
  /* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width, filled)) }),
471
- /* @__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 })
472
481
  ] });
473
482
  }
474
483
  function PaneTitle({ label, extra, width }) {
475
- const head = `\u2500 `;
476
484
  const tail = extra ? ` ${extra} ` : " ";
477
- const used = head.length + 1 + label.length + tail.length;
485
+ const used = 2 + 1 + label.length + tail.length;
478
486
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
479
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: head }),
487
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
480
488
  /* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
481
489
  "\u258E",
482
490
  label
@@ -485,18 +493,104 @@ function PaneTitle({ label, extra, width }) {
485
493
  ] });
486
494
  }
487
495
  function LivePanel({ active: active2 }) {
488
- const stats = useLoader(() => api.stats.get());
489
- const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
490
- const feed = useLoader(() => api.audit.list({ limit: 50 }));
491
- const insights = useLoader(() => api.stats.securityInsights(7));
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 pausedUntil = useRef2(0);
503
+ const pushLog = useCallback2((msg, level = "ok") => {
504
+ setLog((prev) => [...prev.slice(-59), { ts: Date.now(), msg, level }]);
505
+ }, []);
506
+ const onApiError = useCallback2(
507
+ (e) => {
508
+ const is429 = e instanceof ApiError && e.status === 429;
509
+ if (is429 && Date.now() >= pausedUntil.current) {
510
+ pausedUntil.current = Date.now() + 3e4;
511
+ pushLog("rate limited by api \xB7 backing off 30s", "warn");
512
+ } else if (!is429) {
513
+ pushLog(`api error \xB7 ${truncate(e instanceof Error ? e.message : String(e), 40)}`, "bad");
514
+ }
515
+ },
516
+ [pushLog]
517
+ );
518
+ const paused = () => Date.now() < pausedUntil.current;
519
+ const pollFeed = useCallback2(async () => {
520
+ if (paused()) return;
521
+ const t0 = Date.now();
522
+ try {
523
+ const fd = await api.audit.list({ limit: 50 });
524
+ const ms = Date.now() - t0;
525
+ setLat((l) => [...l, ms].slice(-240));
526
+ const fresh = fd.entries.filter((e) => !seenRef.current.has(e.id));
527
+ const firstLoad = seenRef.current.size === 0 && fresh.length > 1;
528
+ for (const e of fresh) seenRef.current.add(e.id);
529
+ if (fresh.length) {
530
+ setBuffer((prev) => [...prev, ...fresh.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at))].slice(-400));
531
+ }
532
+ const denies = fresh.filter((e) => e.decision !== "ALLOW").length;
533
+ const dlp = fresh.filter((e) => e.dlp_matches?.length).length;
534
+ if (firstLoad) pushLog(`link up \xB7 api ${ms}ms \xB7 loaded ${fresh.length} calls`);
535
+ else
536
+ pushLog(
537
+ `api ${ms}ms \xB7 +${fresh.length} calls${denies ? ` \xB7 ${denies} DENIED` : ""}${dlp ? ` \xB7 ${dlp} DLP` : ""}`,
538
+ denies || dlp ? "bad" : fresh.length ? "warn" : "ok"
539
+ );
540
+ } catch (e) {
541
+ onApiError(e);
542
+ }
543
+ }, [pushLog, onApiError]);
544
+ const pollMedium = useCallback2(async () => {
545
+ if (paused()) return;
546
+ try {
547
+ setS(await api.stats.get());
548
+ } catch (e) {
549
+ onApiError(e);
550
+ }
551
+ }, [onApiError]);
552
+ const pollSlow = useCallback2(async () => {
553
+ if (paused()) return;
554
+ try {
555
+ setTsData(await api.stats.timeseries({ period: "24h" }));
556
+ } catch (e) {
557
+ onApiError(e);
558
+ }
559
+ }, [onApiError]);
560
+ useEffect2(() => {
561
+ if (!active2) return;
562
+ void pollFeed();
563
+ void pollMedium();
564
+ void pollSlow();
565
+ const t1 = setInterval(() => void pollFeed(), 4e3);
566
+ const t2 = setInterval(() => void pollMedium(), 1e4);
567
+ const t3 = setInterval(() => void pollSlow(), 3e4);
568
+ return () => {
569
+ clearInterval(t1);
570
+ clearInterval(t2);
571
+ clearInterval(t3);
572
+ };
573
+ }, [active2, pollFeed, pollMedium, pollSlow]);
492
574
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
575
+ const insights = useLoader(() => api.stats.securityInsights(7));
493
576
  usePoll(() => {
494
- stats.reload();
495
- ts.reload();
496
- feed.reload();
497
- }, 2e3, active2);
498
- usePoll(sessions.reload, 5e3, active2);
499
- usePoll(insights.reload, 1e4, active2);
577
+ if (!paused()) sessions.reload();
578
+ }, 1e4, active2);
579
+ usePoll(() => {
580
+ if (!paused()) insights.reload();
581
+ }, 3e4, active2);
582
+ useEffect2(() => {
583
+ const c2 = sessions.data?.counts;
584
+ if (c2) pushLog(`sessions ${c2.active}\u25B2 ${c2.idle}\u25CC ${c2.deactivated}\u25CB`);
585
+ }, [sessions.data, pushLog]);
586
+ useEffect2(() => {
587
+ const ib = insights.data;
588
+ if (ib?.layers) {
589
+ const rl2 = ib.layers.rateLimit;
590
+ const dl2 = ib.layers.dlp;
591
+ pushLog(`layers \xB7 ratelimit ${rl2?.mode ?? "?"} ${rl2?.perMinute || "\u2014"}/m \xB7 dlp ${dl2?.mode ?? "?"} ${(dl2?.patterns ?? []).length} armed`);
592
+ }
593
+ }, [insights.data, pushLog]);
500
594
  const [tick, setTick] = useState2(0);
501
595
  useEffect2(() => {
502
596
  if (!active2) return;
@@ -504,61 +598,43 @@ function LivePanel({ active: active2 }) {
504
598
  return () => clearInterval(t);
505
599
  }, [active2]);
506
600
  const startRef = useRef2(Date.now());
507
- const [buffer, setBuffer] = useState2([]);
508
- useEffect2(() => {
509
- const incoming = feed.data?.entries;
510
- if (!incoming?.length) return;
511
- setBuffer((prev) => {
512
- const seen = new Set(prev.map((e) => e.id));
513
- const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
514
- return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
515
- });
516
- }, [feed.data]);
517
601
  const cols = process.stdout.columns ?? 100;
518
602
  const rows = process.stdout.rows ?? 30;
519
- const s = stats.data;
520
603
  const ins = insights.data ?? {};
521
- const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
522
- const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
523
- const minuteLive = minuteRaw.some((c2) => c2 > 0);
524
- const rateSeries = minuteLive ? minuteRaw : hourRaw;
525
- const rateLabel = minuteLive ? "calls/min \xB7 60m" : "calls/hour \xB7 24h";
526
- const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
527
- const rlMode = ins.layers?.rateLimit?.mode ?? "?";
528
- const dlpMode = ins.layers?.dlp?.mode ?? "?";
529
- const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
530
604
  const spin = SPIN[tick % SPIN.length];
605
+ const points = tsData?.timeseries ?? [];
606
+ const traffic = points.map((p) => p.total);
607
+ const trafficHot = points.map((p) => p.denied > 0);
608
+ const rl = ins.layers?.rateLimit;
609
+ const dl = ins.layers?.dlp;
610
+ const minuteNow = (ins.activity?.minute ?? []).slice(-1)[0]?.count ?? 0;
611
+ const dlpBars = (ins.dlpByPattern ?? []).slice(0, 2);
612
+ const maxDlpBar = dlpBars[0]?.count ?? 1;
531
613
  const toolCounts = /* @__PURE__ */ new Map();
532
- let dlpBufHits = 0;
533
- for (const e of buffer) {
534
- toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
535
- if (e.dlp_matches?.length) dlpBufHits++;
536
- }
614
+ for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
537
615
  const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 3);
538
- const dlpBars = (ins.dlpByPattern ?? []).slice(0, 3);
539
- const maxDlp = dlpBars[0]?.count ?? 1;
540
- const dlpRecent = (ins.dlpHits ?? []).slice(0, 2);
541
616
  const denials = buffer.filter((e) => e.decision !== "ALLOW");
542
617
  const lastDeny = denials[denials.length - 1];
543
- const bursts = limit > 0 ? rateSeries.filter((c2) => c2 > limit).length : 0;
618
+ const latNow = lat[lat.length - 1] ?? 0;
619
+ const latAvg = lat.length ? Math.round(lat.reduce((a, b) => a + b, 0) / lat.length) : 0;
620
+ const latHotAt = Math.max(1500, latAvg * 1.8);
621
+ const backingOff = Date.now() < pausedUntil.current;
544
622
  const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
545
623
  const sessCounts = sessions.data?.counts;
546
624
  const chartH = rows >= 36 ? 6 : 4;
547
625
  const colH = rows >= 32 ? 6 : 5;
548
626
  const streamRows = Math.max(4, rows - 6 - chartH - colH);
549
627
  const tail = buffer.slice(-streamRows);
550
- const colW = Math.max(20, Math.floor((cols - 6) / 3));
551
628
  const innerW = cols - 2;
552
- if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
553
- "\u2717 ",
554
- stats.error
555
- ] });
629
+ const leftW = Math.floor(innerW * 0.55);
630
+ const rightW = innerW - leftW - 2;
631
+ const colW = Math.max(20, Math.floor((innerW - 4) / 3));
556
632
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
557
633
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
558
634
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
559
635
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " \u26E8 SOLONGATE LIVE " }),
560
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} ` }),
561
- 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 no recent denials " })
636
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(Date.now() - startRef.current)} \xB7 ${hhmmss(Date.now())} \xB7 api ${latNow}ms ` }),
637
+ backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " \u23F8 RATE LIMITED \xB7 backing off " }) : 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
638
  ] }),
563
639
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
564
640
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
@@ -567,40 +643,48 @@ function LivePanel({ active: active2 }) {
567
643
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
568
644
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
569
645
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
570
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp(buf) " }),
571
- /* @__PURE__ */ jsx2(Text2, { color: dlpBufHits ? theme.bad : theme.dim, children: dlpBufHits }),
572
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 bursts " }),
573
- /* @__PURE__ */ jsx2(Text2, { color: bursts ? theme.bad : theme.dim, children: bursts }),
646
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 ratelimit " }),
647
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: rl?.mode ?? "?" }),
648
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 dlp " }),
649
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: dl?.mode ?? "?" }),
574
650
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 sessions " }),
575
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC` : "\xB7" }),
651
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
576
652
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
577
653
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
578
654
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
579
655
  ] }),
580
- /* @__PURE__ */ jsx2(PaneTitle, { label: "RATE LIMIT", extra: `${rateLabel} \xB7 peak ${Math.max(0, ...rateSeries)} \xB7 limit ${limit || "off"} \xB7 mode ${rlMode}${limit ? " \xB7 red = over" : ""}`, width: innerW }),
581
- rateSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: rateSeries, height: chartH, width: innerW, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: chartH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? `${spin} loading activity\u2026` : "no activity data" }) }),
582
656
  /* @__PURE__ */ jsxs2(Box2, { children: [
583
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
584
- /* @__PURE__ */ jsx2(PaneTitle, { label: "DENIALS", extra: "24h", width: colW }),
585
- /* @__PURE__ */ jsx2(ColumnChart, { series: denySeries, height: colH - 1, width: colW, color: theme.bad })
657
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: leftW, marginRight: 2, children: [
658
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TRAFFIC", extra: `24h \xB7 peak ${Math.max(0, ...traffic)} \xB7 red = had denials`, width: leftW }),
659
+ /* @__PURE__ */ jsx2(ColumnChart, { series: traffic, hot: trafficHot, height: chartH, width: leftW, color: theme.accent })
586
660
  ] }),
661
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: rightW, children: [
662
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "API LATENCY", extra: `live \xB7 now ${latNow}ms \xB7 avg ${latAvg}ms \xB7 red >${Math.round(latHotAt)}ms`, width: rightW }),
663
+ /* @__PURE__ */ jsx2(ColumnChart, { series: lat, hot: lat.map((v) => v > latHotAt), height: chartH, width: rightW, color: "#82AAF0" })
664
+ ] })
665
+ ] }),
666
+ /* @__PURE__ */ jsxs2(Box2, { children: [
587
667
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
588
- /* @__PURE__ */ jsx2(PaneTitle, { label: "DLP", extra: `7d \xB7 mode ${dlpMode}`, width: colW }),
589
- dlpBars.length ? dlpBars.map((d) => /* @__PURE__ */ jsx2(HBar, { label: truncate(d.pattern, 9), value: d.count, max: maxDlp, width: Math.max(6, colW - 16), color: theme.bad }, d.pattern)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no dlp hits" }),
590
- dlpRecent.map((h, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
591
- /* @__PURE__ */ jsx2(Text2, { color: h.blocked ? theme.accent : theme.bad, children: h.blocked ? "\u2298" : "\u26A0" }),
668
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "LAYERS", width: colW }),
669
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
670
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "RATELIMIT ".padEnd(10) }),
671
+ /* @__PURE__ */ jsx2(Text2, { color: rl?.mode === "block" ? theme.ok : rl?.mode === "detect" ? theme.warn : theme.dim, children: (rl?.mode ?? "?").padEnd(7) }),
672
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: rl?.perMinute ? `${minuteNow}/${rl.perMinute} per min` : "no limit set" })
673
+ ] }),
674
+ 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,
675
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
676
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "DLP".padEnd(10) }),
677
+ /* @__PURE__ */ jsx2(Text2, { color: dl?.mode === "block" ? theme.ok : dl?.mode === "detect" ? theme.warn : theme.dim, children: (dl?.mode ?? "?").padEnd(7) }),
592
678
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
593
- " ",
594
- truncate(h.pattern, colW - 14),
595
- " ",
596
- ago(h.at),
597
- " ago"
679
+ (dl?.patterns ?? []).length,
680
+ " patterns armed"
598
681
  ] })
599
- ] }, i))
682
+ ] }),
683
+ 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" })
600
684
  ] }),
601
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
602
- /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} active` : "", width: colW }),
603
- sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no sessions" }) : null,
685
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
686
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "SESSIONS", extra: sessCounts ? `${sessCounts.active} live` : "", width: colW }),
687
+ sess.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "scanning\u2026" }) : null,
604
688
  sess.slice(0, colH - 1).map((a) => {
605
689
  const dot = sessionDot(a.status);
606
690
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
@@ -620,10 +704,24 @@ function LivePanel({ active: active2 }) {
620
704
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
621
705
  ] }, a.session_id);
622
706
  })
707
+ ] }),
708
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
709
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "EVENT LOG", extra: "streaming", width: colW }),
710
+ log.slice(-(colH - 1)).map((l, i) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
711
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
712
+ hhmmss(l.ts),
713
+ " "
714
+ ] }),
715
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : l.level === "warn" ? theme.warn : "#4f8f6b", children: "\u25B8 " }),
716
+ /* @__PURE__ */ jsx2(Text2, { color: l.level === "bad" ? theme.bad : theme.dim, children: l.msg })
717
+ ] }, l.ts + ":" + i))
623
718
  ] })
624
719
  ] }),
625
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `live tail \xB7 ${buffer.length} buffered`, width: innerW }),
626
- tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
720
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TOOL STREAM", extra: `tail -f \xB7 ${buffer.length} buffered`, width: innerW }),
721
+ tail.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
722
+ spin,
723
+ " awaiting traffic\u2026"
724
+ ] }) : null,
627
725
  tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
628
726
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
629
727
  "[",
@@ -638,7 +736,7 @@ function LivePanel({ active: active2 }) {
638
736
  ] }, e.id)),
639
737
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
640
738
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " \u25B2 LIVE " }),
641
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
739
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
642
740
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
643
741
  ] })
644
742
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.65.0",
3
+ "version": "0.66.1",
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": {