@solongate/proxy 0.63.0 → 0.64.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,50 @@ 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 SectionTitle({ children, width: width2 }) {
7055
+ const label = `\u2500\u2500 ${children} `;
7056
+ return /* @__PURE__ */ jsx2(Text2, { color: theme.dim, wrap: "truncate", children: label + "\u2500".repeat(Math.max(0, width2 - label.length)) });
7057
+ }
7025
7058
  function LivePanel({ active: active2 }) {
7026
7059
  const stats = useLoader(() => api.stats.get());
7027
7060
  const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
7028
- const feed = useLoader(() => api.audit.list({ limit: 40 }));
7061
+ const feed = useLoader(() => api.audit.list({ limit: 50 }));
7062
+ const insights = useLoader(() => api.stats.securityInsights(7));
7029
7063
  usePoll(() => {
7030
7064
  stats.reload();
7031
7065
  ts.reload();
7032
7066
  feed.reload();
7033
7067
  }, 2e3, active2);
7068
+ usePoll(insights.reload, 1e4, active2);
7034
7069
  const [tick, setTick] = useState2(0);
7035
7070
  useEffect2(() => {
7036
7071
  if (!active2) return;
@@ -7045,108 +7080,113 @@ function LivePanel({ active: active2 }) {
7045
7080
  setBuffer((prev) => {
7046
7081
  const seen = new Set(prev.map((e) => e.id));
7047
7082
  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;
7083
+ return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
7049
7084
  });
7050
7085
  }, [feed.data]);
7051
- const s = stats.data;
7052
- const points = ts.data?.timeseries ?? [];
7053
7086
  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];
7087
+ const rows = process.stdout.rows ?? 30;
7088
+ const s = stats.data;
7089
+ const ins = insights.data ?? {};
7090
+ const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
7091
+ const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
7092
+ const minuteLive = minuteRaw.some((c2) => c2 > 0);
7093
+ const minuteSeries = minuteLive ? minuteRaw : hourRaw;
7094
+ const activityLabel = minuteLive ? "calls/min \xB7 last 60m" : "calls/hour \xB7 last 24h";
7095
+ const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
7096
+ const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
7061
7097
  const spin = SPIN[tick % SPIN.length];
7062
7098
  const cursor = tick % 4 < 2 ? "\u258C" : " ";
7099
+ const toolCounts = /* @__PURE__ */ new Map();
7100
+ const permCounts = /* @__PURE__ */ new Map();
7101
+ for (const e of buffer) {
7102
+ toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
7103
+ const perm = (e.permission || "?").toUpperCase();
7104
+ permCounts.set(perm, (permCounts.get(perm) ?? 0) + 1);
7105
+ }
7106
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
7107
+ const maxTool = topTools[0]?.[1] ?? 1;
7108
+ const perms = ["READ", "WRITE", "EXECUTE", "NETWORK"].map((p) => [p, permCounts.get(p) ?? 0]);
7109
+ const maxPerm = Math.max(1, ...perms.map(([, c2]) => c2));
7110
+ const dlpTop = (ins.dlpByPattern ?? []).slice(0, 2);
7111
+ const denials = buffer.filter((e) => e.decision !== "ALLOW");
7112
+ const lastDeny = denials[denials.length - 1];
7113
+ const bursts = limit > 0 ? minuteSeries.filter((c2) => c2 > limit).length : 0;
7114
+ const bigH = rows >= 34 ? 6 : 4;
7115
+ const colH = rows >= 34 ? 6 : 5;
7116
+ const streamRows = Math.max(4, rows - (2 + 1 + bigH + 1 + colH + 1 + 1));
7117
+ const tail = buffer.slice(-streamRows);
7118
+ const colW = Math.max(18, Math.floor((cols - 6) / 3));
7063
7119
  if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7064
7120
  "\u2717 ",
7065
7121
  stats.error
7066
7122
  ] });
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" }),
7123
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
7124
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7125
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE::LIVE " }),
7071
7126
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7072
- " ",
7127
+ " ",
7073
7128
  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())
7129
+ " up ",
7130
+ fmtUp(Date.now() - startRef.current),
7131
+ " \xB7 ",
7132
+ hhmmss(Date.now()),
7133
+ " \xB7 poll 2s"
7083
7134
  ] }),
7084
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " poll 2s" })
7135
+ lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
7136
+ " \u26A0 ",
7137
+ hhmmss(lastDeny.created_at),
7138
+ " ",
7139
+ lastDeny.tool_name,
7140
+ " denied"
7141
+ ] }) : null,
7142
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " esc menu \xB7 q quit" })
7085
7143
  ] }),
7086
- /* @__PURE__ */ jsxs2(Box2, { children: [
7144
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7087
7145
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
7088
7146
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
7089
7147
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " allow " }),
7090
7148
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
7091
7149
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " deny " }),
7092
7150
  /* @__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 }),
7151
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " bursts(60m) " }),
7152
+ /* @__PURE__ */ jsx2(Text2, { color: bursts ? theme.bad : theme.dim, children: bursts }),
7153
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " limit/min " }),
7154
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: limit || "off" }),
7095
7155
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " policies " }),
7096
7156
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" })
7097
7157
  ] }),
7158
+ /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `ACTIVITY ${activityLabel} \xB7 peak ${Math.max(0, ...minuteSeries)}${limit ? ` \xB7 limit ${limit} (red = over)` : ""}` }),
7159
+ minuteSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: minuteSeries, height: bigH, width: cols - 2, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: bigH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? "loading activity\u2026" : "no activity data" }) }),
7098
7160
  /* @__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)) })
7105
- ] }),
7106
- /* @__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: [
7122
- /* @__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))
7161
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7162
+ /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "DENIALS 24h" }),
7163
+ /* @__PURE__ */ jsx2(ColumnChart, { series: denySeries, height: colH - 1, width: colW, color: theme.bad })
7133
7164
  ] }),
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
- ];
7146
- })
7147
- ] }) : null
7165
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
7166
+ /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "TOP TOOLS (buffer)" }),
7167
+ topTools.length ? topTools.slice(0, colH - 1).map(([tool, count]) => /* @__PURE__ */ jsx2(HBar, { label: truncate2(tool, 9), value: count, max: maxTool, width: Math.max(6, colW - 16), color: theme.accent }, tool)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no traffic yet" })
7168
+ ] }),
7169
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
7170
+ /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "PERMISSIONS \xB7 DLP" }),
7171
+ perms.map(([p, c2]) => /* @__PURE__ */ jsx2(HBar, { label: p, value: c2, max: maxPerm, width: Math.max(6, colW - 16), color: p === "EXECUTE" ? theme.warn : theme.accent }, p)),
7172
+ dlpTop.map((d) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.bad, children: "DLP " + truncate2(d.pattern, colW - 10) + " \xD7" + d.count }, d.pattern))
7173
+ ] })
7148
7174
  ] }),
7149
- /* @__PURE__ */ jsxs2(Box2, { children: [
7175
+ /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `TOOL STREAM \xB7 live tail \xB7 ${buffer.length} buffered` }),
7176
+ tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
7177
+ tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7178
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
7179
+ "[",
7180
+ hhmmss(e.created_at),
7181
+ "] "
7182
+ ] }),
7183
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
7184
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate2(e.tool_name, 14).padEnd(15) }),
7185
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.permission ?? "").slice(0, 4).padEnd(5) }),
7186
+ e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
7187
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
7188
+ ] }, e.id)),
7189
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
7150
7190
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "root@solongate" }),
7151
7191
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ":" }),
7152
7192
  /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: "~/live" }),
@@ -7969,6 +8009,18 @@ var init_Audit = __esm({
7969
8009
  import { Box as Box8, Text as Text8, useApp, useInput as useInput5 } from "ink";
7970
8010
  import { useState as useState7 } from "react";
7971
8011
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
8012
+ function LiveHint() {
8013
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
8014
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
8015
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
8016
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
8017
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
8018
+ "press ",
8019
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
8020
+ " to go live"
8021
+ ] }) })
8022
+ ] });
8023
+ }
7972
8024
  function Banner() {
7973
8025
  const wide = (process.stdout.columns ?? 80) >= 82;
7974
8026
  if (!wide) {
@@ -7994,17 +8046,23 @@ function App() {
7994
8046
  else if (input === "q") exit();
7995
8047
  } else {
7996
8048
  if (key.escape || key.tab) setFocus("nav");
8049
+ else if (input === "q" && SECTIONS[section].label === "Live") exit();
7997
8050
  }
7998
8051
  });
8052
+ const cols = process.stdout.columns ?? 100;
8053
+ const rows = process.stdout.rows ?? 30;
7999
8054
  const current = SECTIONS[section];
8055
+ if (current.label === "Live" && focus === "panel") {
8056
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
8057
+ }
8000
8058
  const Panel = current.Panel;
8001
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
8059
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
8002
8060
  /* @__PURE__ */ jsx8(Banner, {}),
8003
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
8061
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
8004
8062
  /* @__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" }) })
8063
+ /* @__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
8064
  ] }),
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"]] }) })
8065
+ /* @__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
8066
  ] });
8009
8067
  }
8010
8068
  var SECTIONS, BANNER_HEX;
@@ -8021,7 +8079,7 @@ var init_App = __esm({
8021
8079
  init_Stats();
8022
8080
  init_Audit();
8023
8081
  SECTIONS = [
8024
- { label: "Live", Panel: LivePanel },
8082
+ { label: "Live", Panel: LiveHint },
8025
8083
  { label: "Policies", Panel: PoliciesPanel },
8026
8084
  { label: "Rate Limit", Panel: RateLimitPanel },
8027
8085
  { label: "DLP", Panel: DlpPanel },
@@ -8050,8 +8108,13 @@ async function launchTui() {
8050
8108
  process.stderr.write("Not logged in. Run `solongate login` first.\n");
8051
8109
  return;
8052
8110
  }
8053
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
8054
- await waitUntilExit();
8111
+ process.stdout.write("\x1B[?1049h\x1B[H");
8112
+ try {
8113
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
8114
+ await waitUntilExit();
8115
+ } finally {
8116
+ process.stdout.write("\x1B[?1049l");
8117
+ }
8055
8118
  }
8056
8119
  var init_tui = __esm({
8057
8120
  "src/tui/index.tsx"() {
package/dist/tui/index.js CHANGED
@@ -441,15 +441,50 @@ var fmtUp = (ms) => {
441
441
  const p = (n) => String(n).padStart(2, "0");
442
442
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
443
443
  };
444
+ function ColumnChart({ series, height, width, color, limit = 0 }) {
445
+ const tailVals = series.slice(-width);
446
+ const data = [...new Array(Math.max(0, width - tailVals.length)).fill(0), ...tailVals];
447
+ const max = Math.max(1, ...data, limit);
448
+ const lines = [];
449
+ for (let r = height; r >= 1; r--) {
450
+ const segs = [];
451
+ for (let i = 0; i < data.length; i++) {
452
+ const frac = data[i] / max;
453
+ const ch = frac >= r / height ? "\u2588" : frac >= (r - 0.5) / height ? "\u2584" : r === 1 && data[i] > 0 ? "\u2581" : " ";
454
+ const c2 = limit > 0 && data[i] > limit ? theme.bad : color;
455
+ const last = segs[segs.length - 1];
456
+ if (last && last.color === c2) last.text += ch;
457
+ else segs.push({ text: ch, color: c2 });
458
+ }
459
+ lines.push(
460
+ /* @__PURE__ */ jsx2(Text2, { children: segs.map((s, i) => /* @__PURE__ */ jsx2(Text2, { color: s.color, children: s.text }, i)) }, r)
461
+ );
462
+ }
463
+ return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: lines });
464
+ }
465
+ function HBar({ label, value, max, width, color }) {
466
+ const filled = max > 0 ? Math.round(value / max * width) : 0;
467
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
468
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(9) }),
469
+ /* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width, filled)) }),
470
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2591".repeat(Math.max(0, width - filled)) + " " + value })
471
+ ] });
472
+ }
473
+ function SectionTitle({ children, width }) {
474
+ const label = `\u2500\u2500 ${children} `;
475
+ return /* @__PURE__ */ jsx2(Text2, { color: theme.dim, wrap: "truncate", children: label + "\u2500".repeat(Math.max(0, width - label.length)) });
476
+ }
444
477
  function LivePanel({ active: active2 }) {
445
478
  const stats = useLoader(() => api.stats.get());
446
479
  const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
447
- const feed = useLoader(() => api.audit.list({ limit: 40 }));
480
+ const feed = useLoader(() => api.audit.list({ limit: 50 }));
481
+ const insights = useLoader(() => api.stats.securityInsights(7));
448
482
  usePoll(() => {
449
483
  stats.reload();
450
484
  ts.reload();
451
485
  feed.reload();
452
486
  }, 2e3, active2);
487
+ usePoll(insights.reload, 1e4, active2);
453
488
  const [tick, setTick] = useState2(0);
454
489
  useEffect2(() => {
455
490
  if (!active2) return;
@@ -464,108 +499,113 @@ function LivePanel({ active: active2 }) {
464
499
  setBuffer((prev) => {
465
500
  const seen = new Set(prev.map((e) => e.id));
466
501
  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;
502
+ return fresh.length ? [...prev, ...fresh].slice(-400) : prev;
468
503
  });
469
504
  }, [feed.data]);
470
- const s = stats.data;
471
- const points = ts.data?.timeseries ?? [];
472
505
  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];
506
+ const rows = process.stdout.rows ?? 30;
507
+ const s = stats.data;
508
+ const ins = insights.data ?? {};
509
+ const minuteRaw = (ins.activity?.minute ?? []).map((b) => b.count);
510
+ const hourRaw = (ins.activity?.hour ?? []).map((b) => b.count);
511
+ const minuteLive = minuteRaw.some((c2) => c2 > 0);
512
+ const minuteSeries = minuteLive ? minuteRaw : hourRaw;
513
+ const activityLabel = minuteLive ? "calls/min \xB7 last 60m" : "calls/hour \xB7 last 24h";
514
+ const limit = minuteLive ? ins.layers?.rateLimit?.perMinute ?? 0 : 0;
515
+ const denySeries = (ts.data?.timeseries ?? []).map((p) => p.denied);
480
516
  const spin = SPIN[tick % SPIN.length];
481
517
  const cursor = tick % 4 < 2 ? "\u258C" : " ";
518
+ const toolCounts = /* @__PURE__ */ new Map();
519
+ const permCounts = /* @__PURE__ */ new Map();
520
+ for (const e of buffer) {
521
+ toolCounts.set(e.tool_name, (toolCounts.get(e.tool_name) ?? 0) + 1);
522
+ const perm = (e.permission || "?").toUpperCase();
523
+ permCounts.set(perm, (permCounts.get(perm) ?? 0) + 1);
524
+ }
525
+ const topTools = [...toolCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 5);
526
+ const maxTool = topTools[0]?.[1] ?? 1;
527
+ const perms = ["READ", "WRITE", "EXECUTE", "NETWORK"].map((p) => [p, permCounts.get(p) ?? 0]);
528
+ const maxPerm = Math.max(1, ...perms.map(([, c2]) => c2));
529
+ const dlpTop = (ins.dlpByPattern ?? []).slice(0, 2);
530
+ const denials = buffer.filter((e) => e.decision !== "ALLOW");
531
+ const lastDeny = denials[denials.length - 1];
532
+ const bursts = limit > 0 ? minuteSeries.filter((c2) => c2 > limit).length : 0;
533
+ const bigH = rows >= 34 ? 6 : 4;
534
+ const colH = rows >= 34 ? 6 : 5;
535
+ const streamRows = Math.max(4, rows - (2 + 1 + bigH + 1 + colH + 1 + 1));
536
+ const tail = buffer.slice(-streamRows);
537
+ const colW = Math.max(18, Math.floor((cols - 6) / 3));
482
538
  if (stats.error) return /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
483
539
  "\u2717 ",
484
540
  stats.error
485
541
  ] });
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" }),
542
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
543
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
544
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#1432A0", color: "#AAC8FA", bold: true, children: " SOLONGATE::LIVE " }),
490
545
  /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
491
- " ",
546
+ " ",
492
547
  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())
548
+ " up ",
549
+ fmtUp(Date.now() - startRef.current),
550
+ " \xB7 ",
551
+ hhmmss(Date.now()),
552
+ " \xB7 poll 2s"
502
553
  ] }),
503
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " poll 2s" })
554
+ lastDeny ? /* @__PURE__ */ jsxs2(Text2, { color: theme.bad, children: [
555
+ " \u26A0 ",
556
+ hhmmss(lastDeny.created_at),
557
+ " ",
558
+ lastDeny.tool_name,
559
+ " denied"
560
+ ] }) : null,
561
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " esc menu \xB7 q quit" })
504
562
  ] }),
505
- /* @__PURE__ */ jsxs2(Box2, { children: [
563
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
506
564
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "calls " }),
507
565
  /* @__PURE__ */ jsx2(Text2, { bold: true, children: s ? s.total_calls : "\xB7\xB7\xB7\xB7" }),
508
566
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " allow " }),
509
567
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: s ? s.allowed : "\xB7\xB7\xB7" }),
510
568
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " 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 }),
570
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " bursts(60m) " }),
571
+ /* @__PURE__ */ jsx2(Text2, { color: bursts ? theme.bad : theme.dim, children: bursts }),
572
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " limit/min " }),
573
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: limit || "off" }),
514
574
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " policies " }),
515
575
  /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: s ? s.active_policies : "\xB7" })
516
576
  ] }),
577
+ /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `ACTIVITY ${activityLabel} \xB7 peak ${Math.max(0, ...minuteSeries)}${limit ? ` \xB7 limit ${limit} (red = over)` : ""}` }),
578
+ minuteSeries.length ? /* @__PURE__ */ jsx2(ColumnChart, { series: minuteSeries, height: bigH, width: cols - 2, color: theme.accent, limit }) : /* @__PURE__ */ jsx2(Box2, { height: bigH, children: /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: insights.loading ? "loading activity\u2026" : "no activity data" }) }),
517
579
  /* @__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)) })
524
- ] }),
525
- /* @__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: [
541
- /* @__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))
580
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
581
+ /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "DENIALS 24h" }),
582
+ /* @__PURE__ */ jsx2(ColumnChart, { series: denySeries, height: colH - 1, width: colW, color: theme.bad })
583
+ ] }),
584
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, marginRight: 2, children: [
585
+ /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "TOP TOOLS (buffer)" }),
586
+ topTools.length ? topTools.slice(0, colH - 1).map(([tool, count]) => /* @__PURE__ */ jsx2(HBar, { label: truncate(tool, 9), value: count, max: maxTool, width: Math.max(6, colW - 16), color: theme.accent }, tool)) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "no traffic yet" })
552
587
  ] }),
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
- ];
565
- })
566
- ] }) : null
588
+ /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", width: colW, children: [
589
+ /* @__PURE__ */ jsx2(SectionTitle, { width: colW, children: "PERMISSIONS \xB7 DLP" }),
590
+ perms.map(([p, c2]) => /* @__PURE__ */ jsx2(HBar, { label: p, value: c2, max: maxPerm, width: Math.max(6, colW - 16), color: p === "EXECUTE" ? theme.warn : theme.accent }, p)),
591
+ dlpTop.map((d) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", color: theme.bad, children: "DLP " + truncate(d.pattern, colW - 10) + " \xD7" + d.count }, d.pattern))
592
+ ] })
567
593
  ] }),
568
- /* @__PURE__ */ jsxs2(Box2, { children: [
594
+ /* @__PURE__ */ jsx2(SectionTitle, { width: cols - 2, children: `TOOL STREAM \xB7 live tail \xB7 ${buffer.length} buffered` }),
595
+ tail.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "awaiting traffic\u2026" }) : null,
596
+ tail.map((e) => /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
597
+ /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
598
+ "[",
599
+ hhmmss(e.created_at),
600
+ "] "
601
+ ] }),
602
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
603
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool_name, 14).padEnd(15) }),
604
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.permission ?? "").slice(0, 4).padEnd(5) }),
605
+ e.dlp_matches?.length ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: "DLP! " }) : null,
606
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: (e.arguments_summary ? JSON.stringify(e.arguments_summary) : e.reason ?? "").replace(/\s+/g, " ") })
607
+ ] }, e.id)),
608
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
569
609
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "root@solongate" }),
570
610
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ":" }),
571
611
  /* @__PURE__ */ jsx2(Text2, { color: theme.accentBright, children: "~/live" }),
@@ -1318,8 +1358,20 @@ function Detail({ label, value, color }) {
1318
1358
 
1319
1359
  // src/tui/App.tsx
1320
1360
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1361
+ function LiveHint() {
1362
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1363
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
1364
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
1365
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
1366
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
1367
+ "press ",
1368
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
1369
+ " to go live"
1370
+ ] }) })
1371
+ ] });
1372
+ }
1321
1373
  var SECTIONS = [
1322
- { label: "Live", Panel: LivePanel },
1374
+ { label: "Live", Panel: LiveHint },
1323
1375
  { label: "Policies", Panel: PoliciesPanel },
1324
1376
  { label: "Rate Limit", Panel: RateLimitPanel },
1325
1377
  { label: "DLP", Panel: DlpPanel },
@@ -1352,17 +1404,23 @@ function App() {
1352
1404
  else if (input === "q") exit();
1353
1405
  } else {
1354
1406
  if (key.escape || key.tab) setFocus("nav");
1407
+ else if (input === "q" && SECTIONS[section].label === "Live") exit();
1355
1408
  }
1356
1409
  });
1410
+ const cols = process.stdout.columns ?? 100;
1411
+ const rows = process.stdout.rows ?? 30;
1357
1412
  const current = SECTIONS[section];
1413
+ if (current.label === "Live" && focus === "panel") {
1414
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
1415
+ }
1358
1416
  const Panel = current.Panel;
1359
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, paddingTop: 1, children: [
1417
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
1360
1418
  /* @__PURE__ */ jsx8(Banner, {}),
1361
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
1419
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
1362
1420
  /* @__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" }) })
1421
+ /* @__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
1422
  ] }),
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"]] }) })
1423
+ /* @__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
1424
  ] });
1367
1425
  }
1368
1426
 
@@ -1379,8 +1437,13 @@ async function launchTui() {
1379
1437
  process.stderr.write("Not logged in. Run `solongate login` first.\n");
1380
1438
  return;
1381
1439
  }
1382
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
1383
- await waitUntilExit();
1440
+ process.stdout.write("\x1B[?1049h\x1B[H");
1441
+ try {
1442
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
1443
+ await waitUntilExit();
1444
+ } finally {
1445
+ process.stdout.write("\x1B[?1049l");
1446
+ }
1384
1447
  }
1385
1448
  export {
1386
1449
  launchTui
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.63.0",
3
+ "version": "0.64.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": {