@solongate/proxy 0.63.0 → 0.65.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
@@ -7022,15 +7022,61 @@ var init_hooks = __esm({
7022
7022
  import { Box as Box2, Text as Text2 } from "ink";
7023
7023
  import { 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);
7029
+ const lines = [];
7030
+ for (let r = height; r >= 1; r--) {
7031
+ const segs = [];
7032
+ 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;
7036
+ const last = segs[segs.length - 1];
7037
+ if (last && last.color === c2) last.text += ch;
7038
+ else segs.push({ text: ch, color: c2 });
7039
+ }
7040
+ lines.push(
7041
+ /* @__PURE__ */ jsx2(Text2, { children: segs.map((s, i) => /* @__PURE__ */ jsx2(Text2, { color: s.color, children: s.text }, i)) }, r)
7042
+ );
7043
+ }
7044
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: lines });
7045
+ }
7046
+ function HBar({ label, value, max, width: width2, color }) {
7047
+ const filled = max > 0 ? Math.round(value / max * width2) : 0;
7048
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7049
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(9) }),
7050
+ /* @__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 })
7052
+ ] });
7053
+ }
7054
+ function PaneTitle({ label, extra, width: width2 }) {
7055
+ const head = `\u2500 `;
7056
+ const tail = extra ? ` ${extra} ` : " ";
7057
+ const used = head.length + 1 + label.length + tail.length;
7058
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7059
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: head }),
7060
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
7061
+ "\u258E",
7062
+ label
7063
+ ] }),
7064
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
7065
+ ] });
7066
+ }
7025
7067
  function LivePanel({ active: active2 }) {
7026
7068
  const stats = useLoader(() => api.stats.get());
7027
7069
  const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
7028
- const feed = useLoader(() => api.audit.list({ limit: 40 }));
7070
+ const feed = useLoader(() => api.audit.list({ limit: 50 }));
7071
+ const insights = useLoader(() => api.stats.securityInsights(7));
7072
+ const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
7029
7073
  usePoll(() => {
7030
7074
  stats.reload();
7031
7075
  ts.reload();
7032
7076
  feed.reload();
7033
7077
  }, 2e3, active2);
7078
+ usePoll(sessions.reload, 5e3, active2);
7079
+ usePoll(insights.reload, 1e4, active2);
7034
7080
  const [tick, setTick] = useState2(0);
7035
7081
  useEffect2(() => {
7036
7082
  if (!active2) return;
@@ -7045,117 +7091,139 @@ function LivePanel({ active: active2 }) {
7045
7091
  setBuffer((prev) => {
7046
7092
  const seen = new Set(prev.map((e) => e.id));
7047
7093
  const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
7048
- return fresh.length ? [...prev, ...fresh].slice(-300) : prev;
7094
+ return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
7049
7095
  });
7050
7096
  }, [feed.data]);
7051
- const s = stats.data;
7052
- const points = ts.data?.timeseries ?? [];
7053
7097
  const cols = process.stdout.columns ?? 100;
7054
- const rows = process.stdout.rows ?? 32;
7055
- const feedRows = Math.max(8, Math.min(20, rows - 18));
7056
- const wide = cols >= 112;
7057
- const tail = buffer.slice(-feedRows);
7058
- const denies = buffer.filter((e) => e.decision !== "ALLOW");
7059
- const dlpHits = buffer.filter((e) => e.dlp_matches?.length);
7060
- const lastDeny = denies[denies.length - 1];
7098
+ const rows = process.stdout.rows ?? 30;
7099
+ const s = stats.data;
7100
+ 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);
7061
7110
  const spin = SPIN[tick % SPIN.length];
7062
- const cursor = tick % 4 < 2 ? "\u258C" : " ";
7111
+ 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
+ }
7117
+ 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
+ const denials = buffer.filter((e) => e.decision !== "ALLOW");
7122
+ const lastDeny = denials[denials.length - 1];
7123
+ const bursts = limit > 0 ? rateSeries.filter((c2) => c2 > limit).length : 0;
7124
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
7125
+ const sessCounts = sessions.data?.counts;
7126
+ const chartH = rows >= 36 ? 6 : 4;
7127
+ const colH = rows >= 32 ? 6 : 5;
7128
+ const streamRows = Math.max(4, rows - 6 - chartH - colH);
7129
+ const tail = buffer.slice(-streamRows);
7130
+ const colW = Math.max(20, Math.floor((cols - 6) / 3));
7131
+ const innerW = cols - 2;
7063
7132
  if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7064
7133
  "\u2717 ",
7065
7134
  stats.error
7066
7135
  ] });
7067
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
7068
- /* @__PURE__ */ jsxs2(Box2, { children: [
7069
- /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "\u25CF " }),
7070
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: "LIVE" }),
7071
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7072
- " ",
7073
- spin,
7074
- " scanning"
7075
- ] }),
7076
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7077
- " up ",
7078
- fmtUp(Date.now() - startRef.current)
7079
- ] }),
7080
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7081
- " ",
7082
- hhmmss(Date.now())
7083
- ] }),
7084
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " poll 2s" })
7136
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
7137
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7138
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7139
+ /* @__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 " })
7085
7142
  ] }),
7086
- /* @__PURE__ */ jsxs2(Box2, { children: [
7087
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
7143
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7144
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
7088
7145
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
7089
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " allow " }),
7146
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
7090
7147
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
7091
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " deny " }),
7148
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
7092
7149
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
7093
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " dlp(buf) " }),
7094
- /* @__PURE__ */ jsx2(Text2, { color: dlpHits.length ? theme.bad : theme.dim, children: dlpHits.length }),
7095
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " policies " }),
7096
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" })
7097
- ] }),
7098
- /* @__PURE__ */ jsxs2(Box2, { children: [
7099
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "act " }),
7100
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sparkline(points.map((p) => p.total), Math.min(64, cols - 30)) })
7101
- ] }),
7102
- /* @__PURE__ */ jsxs2(Box2, { children: [
7103
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "dny " }),
7104
- /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: sparkline(points.map((p) => p.denied), Math.min(64, cols - 30)) })
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 }),
7154
+ /* @__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" }),
7156
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
7157
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
7158
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
7105
7159
  ] }),
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" }) }),
7106
7162
  /* @__PURE__ */ jsxs2(Box2, { children: [
7107
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ">> " }),
7108
- lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7109
- "last deny ",
7110
- hhmmss(lastDeny.created_at),
7111
- " ",
7112
- lastDeny.tool_name,
7113
- " \xB7 ",
7114
- truncate2(lastDeny.reason ?? "policy", Math.max(20, cols - 50))
7115
- ] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no denials in buffer" })
7116
- ] }),
7117
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
7118
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 3, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
7119
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, bold: true, children: "\u2500\u2500 tool stream \u2500\u2500" }),
7120
- tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
7121
- tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", 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 })
7166
+ ] }),
7167
+ /* @__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" }),
7122
7172
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7123
- "[",
7124
- hhmmss(e.created_at),
7125
- "] "
7126
- ] }),
7127
- /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
7128
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool_name, 14).padEnd(15) }),
7129
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.permission ?? "").slice(0, 4).padEnd(5) }),
7130
- e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7131
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
7132
- ] }, e.id))
7173
+ " ",
7174
+ truncate2(h.pattern, colW - 14),
7175
+ " ",
7176
+ ago(h.at),
7177
+ " ago"
7178
+ ] })
7179
+ ] }, i))
7133
7180
  ] }),
7134
- wide ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: 34, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
7135
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, bold: true, children: "\u2500\u2500 raw \u2500\u2500" }),
7136
- tail.slice(-Math.floor(feedRows / 2)).flatMap((e) => {
7137
- const raw = e.arguments_summary ? JSON.stringify(e.arguments_summary) : `hash:${e.id.slice(0, 12)}`;
7138
- return [
7139
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", color: e.decision === "ALLOW" ? theme.dim : theme.bad, children: [
7140
- e.id.slice(0, 8),
7141
- " ",
7142
- e.tool_name.slice(0, 12)
7143
- ] }, e.id + ":h"),
7144
- /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: " " + raw.replace(/\s+/g, " ") }, e.id + ":b")
7145
- ];
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,
7184
+ sess.slice(0, colH - 1).map((a) => {
7185
+ const dot = sessionDot(a.status);
7186
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7187
+ /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
7188
+ dot.ch,
7189
+ " "
7190
+ ] }),
7191
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(a.agent_name ?? a.session_id, 12).padEnd(13) }),
7192
+ /* @__PURE__ */ jsxs2(Text2, { children: [
7193
+ String(a.total_calls).padStart(4),
7194
+ "c "
7195
+ ] }),
7196
+ /* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
7197
+ String(a.denied_calls).padStart(3),
7198
+ "d "
7199
+ ] }),
7200
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
7201
+ ] }, a.session_id);
7146
7202
  })
7147
- ] }) : null
7203
+ ] })
7148
7204
  ] }),
7149
- /* @__PURE__ */ jsxs2(Box2, { children: [
7150
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "root@solongate" }),
7151
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ":" }),
7152
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: "~/live" }),
7153
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "$ " }),
7154
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: cursor })
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,
7207
+ tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7208
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7209
+ "[",
7210
+ hhmmss(e.created_at),
7211
+ "] "
7212
+ ] }),
7213
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
7214
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool_name, 14).padEnd(15) }),
7215
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.permission ?? "").slice(0, 4).padEnd(5) }),
7216
+ e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7217
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
7218
+ ] }, e.id)),
7219
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7220
+ /* @__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"} ` }),
7222
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
7155
7223
  ] })
7156
7224
  ] });
7157
7225
  }
7158
- var SPIN, hhmmss, fmtUp;
7226
+ var SPIN, BG, hhmmss, fmtUp;
7159
7227
  var init_Live = __esm({
7160
7228
  "src/tui/panels/Live.tsx"() {
7161
7229
  "use strict";
@@ -7163,6 +7231,7 @@ var init_Live = __esm({
7163
7231
  init_hooks();
7164
7232
  init_theme();
7165
7233
  SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
7234
+ BG = "#12234f";
7166
7235
  hhmmss = (ts) => {
7167
7236
  const d = new Date(ts);
7168
7237
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -7969,6 +8038,18 @@ var init_Audit = __esm({
7969
8038
  import { Box as Box8, Text as Text8, useApp, useInput as useInput5 } from "ink";
7970
8039
  import { useState as useState7 } from "react";
7971
8040
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
8041
+ function LiveHint() {
8042
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
8043
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
8044
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
8045
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
8046
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
8047
+ "press ",
8048
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
8049
+ " to go live"
8050
+ ] }) })
8051
+ ] });
8052
+ }
7972
8053
  function Banner() {
7973
8054
  const wide = (process.stdout.columns ?? 80) >= 82;
7974
8055
  if (!wide) {
@@ -7994,17 +8075,23 @@ function App() {
7994
8075
  else if (input === "q") exit();
7995
8076
  } else {
7996
8077
  if (key.escape || key.tab) setFocus("nav");
8078
+ else if (input === "q" && SECTIONS[section].label === "Live") exit();
7997
8079
  }
7998
8080
  });
8081
+ const cols = process.stdout.columns ?? 100;
8082
+ const rows = process.stdout.rows ?? 30;
7999
8083
  const current = SECTIONS[section];
8084
+ if (current.label === "Live" && focus === "panel") {
8085
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
8086
+ }
8000
8087
  const Panel = current.Panel;
8001
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
8088
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
8002
8089
  /* @__PURE__ */ jsx8(Banner, {}),
8003
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
8090
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
8004
8091
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
8005
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minHeight: 16, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: true, focused: focus === "panel" }) })
8092
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
8006
8093
  ] }),
8007
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "move"], ["\u2190\u2192/space/s", "edit"], ["esc", "back to menu"]] }) })
8094
+ /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
8008
8095
  ] });
8009
8096
  }
8010
8097
  var SECTIONS, BANNER_HEX;
@@ -8021,7 +8108,7 @@ var init_App = __esm({
8021
8108
  init_Stats();
8022
8109
  init_Audit();
8023
8110
  SECTIONS = [
8024
- { label: "Live", Panel: LivePanel },
8111
+ { label: "Live", Panel: LiveHint },
8025
8112
  { label: "Policies", Panel: PoliciesPanel },
8026
8113
  { label: "Rate Limit", Panel: RateLimitPanel },
8027
8114
  { label: "DLP", Panel: DlpPanel },
@@ -8050,8 +8137,13 @@ async function launchTui() {
8050
8137
  process.stderr.write("Not logged in. Run `solongate login` first.\n");
8051
8138
  return;
8052
8139
  }
8053
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
8054
- await waitUntilExit();
8140
+ process.stdout.write("\x1B[?1049h\x1B[H");
8141
+ try {
8142
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
8143
+ await waitUntilExit();
8144
+ } finally {
8145
+ process.stdout.write("\x1B[?1049l");
8146
+ }
8055
8147
  }
8056
8148
  var init_tui = __esm({
8057
8149
  "src/tui/index.tsx"() {
package/dist/tui/index.js CHANGED
@@ -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,15 +442,61 @@ 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
  };
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);
449
+ const lines = [];
450
+ for (let r = height; r >= 1; r--) {
451
+ const segs = [];
452
+ 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;
456
+ const last = segs[segs.length - 1];
457
+ if (last && last.color === c2) last.text += ch;
458
+ else segs.push({ text: ch, color: c2 });
459
+ }
460
+ lines.push(
461
+ /* @__PURE__ */ jsx2(Text2, { children: segs.map((s, i) => /* @__PURE__ */ jsx2(Text2, { color: s.color, children: s.text }, i)) }, r)
462
+ );
463
+ }
464
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: lines });
465
+ }
466
+ function HBar({ label, value, max, width, color }) {
467
+ const filled = max > 0 ? Math.round(value / max * width) : 0;
468
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
469
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(9) }),
470
+ /* @__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 })
472
+ ] });
473
+ }
474
+ function PaneTitle({ label, extra, width }) {
475
+ const head = `\u2500 `;
476
+ const tail = extra ? ` ${extra} ` : " ";
477
+ const used = head.length + 1 + label.length + tail.length;
478
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
479
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: head }),
480
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
481
+ "\u258E",
482
+ label
483
+ ] }),
484
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
485
+ ] });
486
+ }
444
487
  function LivePanel({ active: active2 }) {
445
488
  const stats = useLoader(() => api.stats.get());
446
489
  const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
447
- const feed = useLoader(() => api.audit.list({ limit: 40 }));
490
+ const feed = useLoader(() => api.audit.list({ limit: 50 }));
491
+ const insights = useLoader(() => api.stats.securityInsights(7));
492
+ const sessions = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
448
493
  usePoll(() => {
449
494
  stats.reload();
450
495
  ts.reload();
451
496
  feed.reload();
452
497
  }, 2e3, active2);
498
+ usePoll(sessions.reload, 5e3, active2);
499
+ usePoll(insights.reload, 1e4, active2);
453
500
  const [tick, setTick] = useState2(0);
454
501
  useEffect2(() => {
455
502
  if (!active2) return;
@@ -464,113 +511,135 @@ function LivePanel({ active: active2 }) {
464
511
  setBuffer((prev) => {
465
512
  const seen = new Set(prev.map((e) => e.id));
466
513
  const fresh = incoming.filter((e) => !seen.has(e.id)).sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
467
- return fresh.length ? [...prev, ...fresh].slice(-300) : prev;
514
+ return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
468
515
  });
469
516
  }, [feed.data]);
470
- const s = stats.data;
471
- const points = ts.data?.timeseries ?? [];
472
517
  const cols = process.stdout.columns ?? 100;
473
- const rows = process.stdout.rows ?? 32;
474
- const feedRows = Math.max(8, Math.min(20, rows - 18));
475
- const wide = cols >= 112;
476
- const tail = buffer.slice(-feedRows);
477
- const denies = buffer.filter((e) => e.decision !== "ALLOW");
478
- const dlpHits = buffer.filter((e) => e.dlp_matches?.length);
479
- const lastDeny = denies[denies.length - 1];
518
+ const rows = process.stdout.rows ?? 30;
519
+ const s = stats.data;
520
+ 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);
480
530
  const spin = SPIN[tick % SPIN.length];
481
- const cursor = tick % 4 < 2 ? "\u258C" : " ";
531
+ 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
+ }
537
+ 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
+ const denials = buffer.filter((e) => e.decision !== "ALLOW");
542
+ const lastDeny = denials[denials.length - 1];
543
+ const bursts = limit > 0 ? rateSeries.filter((c2) => c2 > limit).length : 0;
544
+ const sess = (sessions.data?.agents ?? []).slice().sort((a, b) => Date.parse(b.last_seen_at) - Date.parse(a.last_seen_at));
545
+ const sessCounts = sessions.data?.counts;
546
+ const chartH = rows >= 36 ? 6 : 4;
547
+ const colH = rows >= 32 ? 6 : 5;
548
+ const streamRows = Math.max(4, rows - 6 - chartH - colH);
549
+ const tail = buffer.slice(-streamRows);
550
+ const colW = Math.max(20, Math.floor((cols - 6) / 3));
551
+ const innerW = cols - 2;
482
552
  if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
483
553
  "\u2717 ",
484
554
  stats.error
485
555
  ] });
486
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
487
- /* @__PURE__ */ jsxs2(Box2, { children: [
488
- /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "\u25CF " }),
489
- /* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accentBright, children: "LIVE" }),
490
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
491
- " ",
492
- spin,
493
- " scanning"
494
- ] }),
495
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
496
- " up ",
497
- fmtUp(Date.now() - startRef.current)
498
- ] }),
499
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
500
- " ",
501
- hhmmss(Date.now())
502
- ] }),
503
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " poll 2s" })
556
+ const sessionDot = (st) => st === "active" ? { ch: "\u25CF", color: theme.ok } : st === "idle" ? { ch: "\u25D0", color: theme.warn } : { ch: "\u25CB", color: theme.dim };
557
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
558
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
559
+ /* @__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 " })
504
562
  ] }),
505
- /* @__PURE__ */ jsxs2(Box2, { children: [
506
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
563
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
564
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 calls " }),
507
565
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
508
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " allow " }),
566
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 allow " }),
509
567
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
510
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " deny " }),
568
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 deny " }),
511
569
  /* @__PURE__ */ jsx2(Text2, { color: theme.bad, bold: true, children: s ? s.denied : "\xB7\xB7" }),
512
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " dlp(buf) " }),
513
- /* @__PURE__ */ jsx2(Text2, { color: dlpHits.length ? theme.bad : theme.dim, children: dlpHits.length }),
514
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " policies " }),
515
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" })
516
- ] }),
517
- /* @__PURE__ */ jsxs2(Box2, { children: [
518
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "act " }),
519
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: sparkline(points.map((p) => p.total), Math.min(64, cols - 30)) })
520
- ] }),
521
- /* @__PURE__ */ jsxs2(Box2, { children: [
522
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "dny " }),
523
- /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: sparkline(points.map((p) => p.denied), Math.min(64, cols - 30)) })
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 }),
574
+ /* @__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" }),
576
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 policies " }),
577
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" }),
578
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
524
579
  ] }),
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" }) }),
525
582
  /* @__PURE__ */ jsxs2(Box2, { children: [
526
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ">> " }),
527
- lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
528
- "last deny ",
529
- hhmmss(lastDeny.created_at),
530
- " ",
531
- lastDeny.tool_name,
532
- " \xB7 ",
533
- truncate(lastDeny.reason ?? "policy", Math.max(20, cols - 50))
534
- ] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no denials in buffer" })
535
- ] }),
536
- /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
537
- /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 3, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
538
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, bold: true, children: "\u2500\u2500 tool stream \u2500\u2500" }),
539
- tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
540
- tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", 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 })
586
+ ] }),
587
+ /* @__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" }),
541
592
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
542
- "[",
543
- hhmmss(e.created_at),
544
- "] "
545
- ] }),
546
- /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
547
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool_name, 14).padEnd(15) }),
548
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.permission ?? "").slice(0, 4).padEnd(5) }),
549
- e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
550
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
551
- ] }, e.id))
593
+ " ",
594
+ truncate(h.pattern, colW - 14),
595
+ " ",
596
+ ago(h.at),
597
+ " ago"
598
+ ] })
599
+ ] }, i))
552
600
  ] }),
553
- wide ? /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: 34, borderStyle: "single", borderColor: "gray", paddingX: 1, children: [
554
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, bold: true, children: "\u2500\u2500 raw \u2500\u2500" }),
555
- tail.slice(-Math.floor(feedRows / 2)).flatMap((e) => {
556
- const raw = e.arguments_summary ? JSON.stringify(e.arguments_summary) : `hash:${e.id.slice(0, 12)}`;
557
- return [
558
- /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", color: e.decision === "ALLOW" ? theme.dim : theme.bad, children: [
559
- e.id.slice(0, 8),
560
- " ",
561
- e.tool_name.slice(0, 12)
562
- ] }, e.id + ":h"),
563
- /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.dim, children: " " + raw.replace(/\s+/g, " ") }, e.id + ":b")
564
- ];
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,
604
+ sess.slice(0, colH - 1).map((a) => {
605
+ const dot = sessionDot(a.status);
606
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
607
+ /* @__PURE__ */ jsxs2(Text2, { color: dot.color, children: [
608
+ dot.ch,
609
+ " "
610
+ ] }),
611
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(a.agent_name ?? a.session_id, 12).padEnd(13) }),
612
+ /* @__PURE__ */ jsxs2(Text2, { children: [
613
+ String(a.total_calls).padStart(4),
614
+ "c "
615
+ ] }),
616
+ /* @__PURE__ */ jsxs2(Text2, { color: a.denied_calls ? theme.bad : theme.dim, children: [
617
+ String(a.denied_calls).padStart(3),
618
+ "d "
619
+ ] }),
620
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ago(a.last_seen_at) })
621
+ ] }, a.session_id);
565
622
  })
566
- ] }) : null
623
+ ] })
567
624
  ] }),
568
- /* @__PURE__ */ jsxs2(Box2, { children: [
569
- /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "root@solongate" }),
570
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ":" }),
571
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: "~/live" }),
572
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "$ " }),
573
- /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: cursor })
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,
627
+ tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
628
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
629
+ "[",
630
+ hhmmss(e.created_at),
631
+ "] "
632
+ ] }),
633
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
634
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool_name, 14).padEnd(15) }),
635
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.permission ?? "").slice(0, 4).padEnd(5) }),
636
+ e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
637
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
638
+ ] }, e.id)),
639
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
640
+ /* @__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"} ` }),
642
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " esc menu \xB7 q quit " })
574
643
  ] })
575
644
  ] });
576
645
  }
@@ -1318,8 +1387,20 @@ function Detail({ label, value, color }) {
1318
1387
 
1319
1388
  // src/tui/App.tsx
1320
1389
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1390
+ function LiveHint() {
1391
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1392
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
1393
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
1394
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
1395
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
1396
+ "press ",
1397
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
1398
+ " to go live"
1399
+ ] }) })
1400
+ ] });
1401
+ }
1321
1402
  var SECTIONS = [
1322
- { label: "Live", Panel: LivePanel },
1403
+ { label: "Live", Panel: LiveHint },
1323
1404
  { label: "Policies", Panel: PoliciesPanel },
1324
1405
  { label: "Rate Limit", Panel: RateLimitPanel },
1325
1406
  { label: "DLP", Panel: DlpPanel },
@@ -1352,17 +1433,23 @@ function App() {
1352
1433
  else if (input === "q") exit();
1353
1434
  } else {
1354
1435
  if (key.escape || key.tab) setFocus("nav");
1436
+ else if (input === "q" && SECTIONS[section].label === "Live") exit();
1355
1437
  }
1356
1438
  });
1439
+ const cols = process.stdout.columns ?? 100;
1440
+ const rows = process.stdout.rows ?? 30;
1357
1441
  const current = SECTIONS[section];
1442
+ if (current.label === "Live" && focus === "panel") {
1443
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
1444
+ }
1358
1445
  const Panel = current.Panel;
1359
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1446
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
1360
1447
  /* @__PURE__ */ jsx8(Banner, {}),
1361
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
1448
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
1362
1449
  /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
1363
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minHeight: 16, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: true, focused: focus === "panel" }) })
1450
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
1364
1451
  ] }),
1365
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "move"], ["\u2190\u2192/space/s", "edit"], ["esc", "back to menu"]] }) })
1452
+ /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
1366
1453
  ] });
1367
1454
  }
1368
1455
 
@@ -1379,8 +1466,13 @@ async function launchTui() {
1379
1466
  process.stderr.write("Not logged in. Run `solongate login` first.\n");
1380
1467
  return;
1381
1468
  }
1382
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
1383
- await waitUntilExit();
1469
+ process.stdout.write("\x1B[?1049h\x1B[H");
1470
+ try {
1471
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
1472
+ await waitUntilExit();
1473
+ } finally {
1474
+ process.stdout.write("\x1B[?1049l");
1475
+ }
1384
1476
  }
1385
1477
  export {
1386
1478
  launchTui
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.63.0",
3
+ "version": "0.65.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": {