@solongate/proxy 0.65.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.
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,63 @@ 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 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]);
7072
7117
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7073
- usePoll(() => {
7074
- stats.reload();
7075
- ts.reload();
7076
- feed.reload();
7077
- }, 2e3, active2);
7118
+ const insights = useLoader(() => api.stats.securityInsights(7));
7078
7119
  usePoll(sessions.reload, 5e3, active2);
7079
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]);
7080
7133
  const [tick, setTick] = useState2(0);
7081
7134
  useEffect2(() => {
7082
7135
  if (!active2) return;
@@ -7084,61 +7137,41 @@ function LivePanel({ active: active2 }) {
7084
7137
  return () => clearInterval(t);
7085
7138
  }, [active2]);
7086
7139
  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
7140
  const cols = process.stdout.columns ?? 100;
7098
7141
  const rows = process.stdout.rows ?? 30;
7099
- const s = stats.data;
7100
7142
  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
7143
  const spin = SPIN[tick % SPIN.length];
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;
7111
7152
  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
- }
7153
+ for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
7117
7154
  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
7155
  const denials = buffer.filter((e) => e.decision !== "ALLOW");
7122
7156
  const lastDeny = denials[denials.length - 1];
7123
- const bursts = limit > 0 ? rateSeries.filter((c2) => c2 > limit).length : 0;
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;
7124
7159
  const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
7125
7160
  const sessCounts = sessions.data?.counts;
7126
7161
  const chartH = rows >= 36 ? 6 : 4;
7127
7162
  const colH = rows >= 32 ? 6 : 5;
7128
7163
  const streamRows = Math.max(4, rows - 6 - chartH - colH);
7129
7164
  const tail = buffer.slice(-streamRows);
7130
- const colW = Math.max(20, Math.floor((cols - 6) / 3));
7131
7165
  const innerW = cols - 2;
7132
- if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7133
- "\u2717 ",
7134
- stats.error
7135
- ] });
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));
7136
7169
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7137
7170
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7138
7171
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7139
7172
  /* @__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 " })
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 " })
7142
7175
  ] }),
7143
7176
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7144
7177
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
@@ -7147,40 +7180,48 @@ function LivePanel({ active: active2 }) {
7147
7180
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
7148
7181
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7149
7182
  /* @__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 }),
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 ?? "?" }),
7154
7187
  /* @__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" }),
7188
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
7156
7189
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
7157
7190
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
7158
7191
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7159
7192
  ] }),
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
7193
  /* @__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 })
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 })
7166
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
+ ] })
7202
+ ] }),
7203
+ /* @__PURE__ */ jsxs2(Box2, { children: [
7167
7204
  /* @__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" }),
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) }),
7172
7215
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7173
- " ",
7174
- truncate2(h.pattern, colW - 14),
7175
- " ",
7176
- ago(h.at),
7177
- " ago"
7216
+ (dl?.patterns ?? []).length,
7217
+ " patterns armed"
7178
7218
  ] })
7179
- ] }, i))
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" })
7180
7221
  ] }),
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,
7222
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
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,
7184
7225
  sess.slice(0, colH - 1).map((a) => {
7185
7226
  const dot = sessionDot(a.status);
7186
7227
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
@@ -7200,10 +7241,24 @@ function LivePanel({ active: active2 }) {
7200
7241
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
7201
7242
  ] }, a.session_id);
7202
7243
  })
7244
+ ] }),
7245
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
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))
7203
7255
  ] })
7204
7256
  ] }),
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,
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,
7207
7262
  tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7208
7263
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7209
7264
  "[",
@@ -7218,7 +7273,7 @@ function LivePanel({ active: active2 }) {
7218
7273
  ] }, e.id)),
7219
7274
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7220
7275
  /* @__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"} ` }),
7276
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
7222
7277
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
7223
7278
  ] })
7224
7279
  ] });
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,63 @@ 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 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]);
492
537
  const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
493
- usePoll(() => {
494
- stats.reload();
495
- ts.reload();
496
- feed.reload();
497
- }, 2e3, active2);
538
+ const insights = useLoader(() => api.stats.securityInsights(7));
498
539
  usePoll(sessions.reload, 5e3, active2);
499
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]);
500
553
  const [tick, setTick] = useState2(0);
501
554
  useEffect2(() => {
502
555
  if (!active2) return;
@@ -504,61 +557,41 @@ function LivePanel({ active: active2 }) {
504
557
  return () => clearInterval(t);
505
558
  }, [active2]);
506
559
  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
560
  const cols = process.stdout.columns ?? 100;
518
561
  const rows = process.stdout.rows ?? 30;
519
- const s = stats.data;
520
562
  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
563
  const spin = SPIN[tick % SPIN.length];
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;
531
572
  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
- }
573
+ for (const e of buffer) toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
537
574
  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
575
  const denials = buffer.filter((e) => e.decision !== "ALLOW");
542
576
  const lastDeny = denials[denials.length - 1];
543
- const bursts = limit > 0 ? rateSeries.filter((c2) => c2 > limit).length : 0;
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;
544
579
  const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
545
580
  const sessCounts = sessions.data?.counts;
546
581
  const chartH = rows >= 36 ? 6 : 4;
547
582
  const colH = rows >= 32 ? 6 : 5;
548
583
  const streamRows = Math.max(4, rows - 6 - chartH - colH);
549
584
  const tail = buffer.slice(-streamRows);
550
- const colW = Math.max(20, Math.floor((cols - 6) / 3));
551
585
  const innerW = cols - 2;
552
- if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
553
- "\u2717 ",
554
- stats.error
555
- ] });
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));
556
589
  const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
557
590
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
558
591
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
559
592
  /* @__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 " })
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
597
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
@@ -567,40 +600,48 @@ function LivePanel({ active: active2 }) {
567
600
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
568
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: " \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 }),
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 ?? "?" }),
574
607
  /* @__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" }),
608
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sessCounts ? `${sessCounts.active}\u25B2 ${sessCounts.idle}\u25CC ${sessCounts.deactivated}\u25CB` : "\xB7" }),
576
609
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
577
610
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
578
611
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
579
612
  ] }),
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
613
  /* @__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 })
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 })
586
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
+ ] })
622
+ ] }),
623
+ /* @__PURE__ */ jsxs2(Box2, { children: [
587
624
  /* @__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" }),
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) }),
592
635
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
593
- " ",
594
- truncate(h.pattern, colW - 14),
595
- " ",
596
- ago(h.at),
597
- " ago"
636
+ (dl?.patterns ?? []).length,
637
+ " patterns armed"
598
638
  ] })
599
- ] }, i))
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" })
600
641
  ] }),
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,
642
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
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,
604
645
  sess.slice(0, colH - 1).map((a) => {
605
646
  const dot = sessionDot(a.status);
606
647
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
@@ -620,10 +661,24 @@ function LivePanel({ active: active2 }) {
620
661
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
621
662
  ] }, a.session_id);
622
663
  })
664
+ ] }),
665
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
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))
623
675
  ] })
624
676
  ] }),
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,
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,
627
682
  tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
628
683
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
629
684
  "[",
@@ -638,7 +693,7 @@ function LivePanel({ active: active2 }) {
638
693
  ] }, e.id)),
639
694
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
640
695
  /* @__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"} ` }),
696
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} \xB7 buf ${buffer.length} ` }),
642
697
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
643
698
  ] })
644
699
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.65.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": {