@solongate/proxy 0.81.18 → 0.81.20

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/tui/index.js CHANGED
@@ -8,8 +8,8 @@ var __export = (target, all) => {
8
8
  import { render } from "ink";
9
9
 
10
10
  // src/tui/App.tsx
11
- import { Box as Box9, Text as Text9, useApp, useInput as useInput7 } from "ink";
12
- import { useState as useState8 } from "react";
11
+ import { Box as Box10, Text as Text10, useApp, useInput as useInput8 } from "ink";
12
+ import { useState as useState9 } from "react";
13
13
 
14
14
  // src/cli-utils.ts
15
15
  var c = {
@@ -96,6 +96,25 @@ function sparkline(values, width) {
96
96
  if (max === 0) return BLOCKS[0].repeat(v.length);
97
97
  return v.map((n) => BLOCKS[Math.min(BLOCKS.length - 1, Math.round(n / max * (BLOCKS.length - 1)))]).join("");
98
98
  }
99
+ function prettyJson(s) {
100
+ try {
101
+ return JSON.stringify(JSON.parse(s), null, 2);
102
+ } catch {
103
+ return s;
104
+ }
105
+ }
106
+ function wrapLines(s, width) {
107
+ const w = Math.max(8, width);
108
+ const out = [];
109
+ for (const raw of (s ?? "").split("\n")) {
110
+ if (raw.length <= w) {
111
+ out.push(raw);
112
+ continue;
113
+ }
114
+ for (let i = 0; i < raw.length; i += w) out.push(raw.slice(i, i + w));
115
+ }
116
+ return out;
117
+ }
99
118
  function truncate(s, n) {
100
119
  if (!s) return "";
101
120
  return s.length <= n ? s : s.slice(0, Math.max(0, n - 1)) + "\u2026";
@@ -150,9 +169,9 @@ function KeyHints({ hints }) {
150
169
  // src/tui/panels/Live.tsx
151
170
  import { Box as Box2, Text as Text2, useInput } from "ink";
152
171
  import TextInput from "ink-text-input";
153
- import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync as writeFileSync2 } from "fs";
172
+ import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
154
173
  import { homedir as homedir3 } from "os";
155
- import { join as join4 } from "path";
174
+ import { join as join3 } from "path";
156
175
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
157
176
 
158
177
  // src/api-client/client.ts
@@ -353,12 +372,16 @@ __export(settings_exports, {
353
372
  deleteWebhook: () => deleteWebhook,
354
373
  getAlerts: () => getAlerts,
355
374
  getGuardStatus: () => getGuardStatus,
375
+ getLocalLogs: () => getLocalLogs,
356
376
  getRateLimitHistory: () => getRateLimitHistory,
357
377
  getSecurityLayers: () => getSecurityLayers,
358
378
  getSelfProtection: () => getSelfProtection,
359
379
  getWebhooks: () => getWebhooks,
380
+ setAlertEnabled: () => setAlertEnabled,
381
+ setLocalLogs: () => setLocalLogs,
360
382
  setSecurityLayers: () => setSecurityLayers,
361
- setSelfProtection: () => setSelfProtection
383
+ setSelfProtection: () => setSelfProtection,
384
+ updateWebhook: () => updateWebhook
362
385
  });
363
386
  function getSecurityLayers() {
364
387
  return request("GET", "/settings/security-layers");
@@ -381,6 +404,12 @@ function getSelfProtection() {
381
404
  function setSelfProtection(enabled) {
382
405
  return request("PUT", "/settings/self-protection", { body: { enabled } });
383
406
  }
407
+ function getLocalLogs() {
408
+ return request("GET", "/settings/local-logs");
409
+ }
410
+ function setLocalLogs(cfg) {
411
+ return request("PUT", "/settings/local-logs", { body: cfg });
412
+ }
384
413
  function getAlerts() {
385
414
  return request("GET", "/settings/denial-alerts");
386
415
  }
@@ -390,6 +419,9 @@ function createAlert(body) {
390
419
  function deleteAlert(id) {
391
420
  return request("DELETE", "/settings/denial-alerts", { query: { id } });
392
421
  }
422
+ function setAlertEnabled(id, enabled) {
423
+ return request("PATCH", "/settings/denial-alerts", { query: { id }, body: { enabled } });
424
+ }
393
425
  function getWebhooks() {
394
426
  return request("GET", "/settings/denial-webhook");
395
427
  }
@@ -399,6 +431,9 @@ function createWebhook(body) {
399
431
  function deleteWebhook(id) {
400
432
  return request("DELETE", "/settings/denial-webhook", { query: { id } });
401
433
  }
434
+ function updateWebhook(id, patch) {
435
+ return request("PATCH", "/settings/denial-webhook", { body: { id, ...patch } });
436
+ }
402
437
 
403
438
  // src/api-client/stats.ts
404
439
  var stats_exports = {};
@@ -489,138 +524,28 @@ function list4() {
489
524
  var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
490
525
 
491
526
  // src/tui/notify.ts
492
- import { spawn, execFileSync } from "child_process";
493
- import { writeFileSync, existsSync as existsSync2 } from "fs";
494
- import { join as join3 } from "path";
495
- import { tmpdir } from "os";
496
- var focusToken = null;
497
- function captureFocusTarget() {
527
+ import { spawn } from "child_process";
528
+ function desktopNotify(title, msg) {
498
529
  try {
499
530
  if (process.platform === "linux") {
500
- focusToken = (process.env.WINDOWID || "").trim() || null;
501
- if (!focusToken) {
502
- try {
503
- focusToken = execFileSync("xdotool", ["getactivewindow"], { encoding: "utf-8", timeout: 1500, stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
504
- } catch {
505
- focusToken = null;
506
- }
507
- }
531
+ const p = spawn("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
532
+ p.on("error", () => {
533
+ });
534
+ p.unref();
508
535
  } else if (process.platform === "win32") {
509
- const ps = `Add-Type -Name W -Namespace N -MemberDefinition '[DllImport("user32.dll")]public static extern System.IntPtr GetForegroundWindow();' | Out-Null; [N.W]::GetForegroundWindow().ToInt64()`;
510
- try {
511
- focusToken = execFileSync("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { encoding: "utf-8", timeout: 4e3, stdio: ["ignore", "pipe", "ignore"] }).trim() || null;
512
- } catch {
513
- focusToken = null;
514
- }
536
+ const q = (s) => s.replace(/'/g, "''");
537
+ const ps = `Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.Visible=$true;$n.ShowBalloonTip(6000,'${q(title)}','${q(msg)}',[System.Windows.Forms.ToolTipIcon]::Warning);Start-Sleep -Milliseconds 6500;$n.Dispose()`;
538
+ const p = spawn("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "ignore", detached: true, windowsHide: true });
539
+ p.on("error", () => {
540
+ });
541
+ p.unref();
542
+ } else if (process.platform === "darwin") {
543
+ const esc = (s) => s.replace(/"/g, '\\"');
544
+ const p = spawn("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
545
+ p.on("error", () => {
546
+ });
547
+ p.unref();
515
548
  }
516
- } catch {
517
- focusToken = null;
518
- }
519
- }
520
- function desktopNotify(title, msg) {
521
- try {
522
- if (process.platform === "linux") linuxNotify(title, msg);
523
- else if (process.platform === "win32") winNotify(title, msg);
524
- else if (process.platform === "darwin") macNotify(title, msg);
525
- } catch {
526
- }
527
- }
528
- function linuxNotify(title, msg) {
529
- let p;
530
- try {
531
- p = spawn("notify-send", ["--wait", "--action=default=Open", "-a", "SolonGate", title, msg], { stdio: ["ignore", "pipe", "ignore"] });
532
- } catch {
533
- return;
534
- }
535
- p.on("error", () => {
536
- });
537
- p.stdout?.on("data", (d) => {
538
- if (String(d).trim() === "default") raiseLinuxWindow();
539
- });
540
- p.unref();
541
- }
542
- function raiseLinuxWindow() {
543
- try {
544
- process.stdout.write("\x1B[5t");
545
- } catch {
546
- }
547
- if (!focusToken) return;
548
- try {
549
- const x = spawn("xdotool", ["windowactivate", focusToken], { stdio: "ignore" });
550
- x.on("error", () => {
551
- try {
552
- const hex = "0x" + Number(focusToken).toString(16);
553
- const w = spawn("wmctrl", ["-ia", hex], { stdio: "ignore" });
554
- w.on("error", () => {
555
- });
556
- w.unref();
557
- } catch {
558
- }
559
- });
560
- x.unref();
561
- } catch {
562
- }
563
- }
564
- var winScriptPath = null;
565
- function ensureWinScript() {
566
- if (winScriptPath && existsSync2(winScriptPath)) return winScriptPath;
567
- const p = join3(tmpdir(), "solongate-toast.ps1");
568
- const script = [
569
- "param([string]$Title,[string]$Msg,[string]$Hwnd)",
570
- "Add-Type -AssemblyName System.Windows.Forms",
571
- "Add-Type -AssemblyName System.Drawing",
572
- `Add-Type -Name Win -Namespace Sg -MemberDefinition '[DllImport("user32.dll")]public static extern bool SetForegroundWindow(System.IntPtr h);[DllImport("user32.dll")]public static extern bool ShowWindow(System.IntPtr h,int c);'`,
573
- "$ni=New-Object System.Windows.Forms.NotifyIcon",
574
- "$ni.Icon=[System.Drawing.SystemIcons]::Information",
575
- "$ni.Visible=$true",
576
- "$script:clicked=$false",
577
- "$ni.add_BalloonTipClicked({$script:clicked=$true})",
578
- "$ni.ShowBalloonTip(8000,$Title,$Msg,[System.Windows.Forms.ToolTipIcon]::Warning)",
579
- "$sw=[System.Diagnostics.Stopwatch]::StartNew()",
580
- "while($sw.Elapsed.TotalSeconds -lt 9 -and -not $script:clicked){[System.Windows.Forms.Application]::DoEvents();Start-Sleep -Milliseconds 100}",
581
- "if($script:clicked -and $Hwnd){try{$h=[System.IntPtr][int64]$Hwnd;[Sg.Win]::ShowWindow($h,9)|Out-Null;[Sg.Win]::SetForegroundWindow($h)|Out-Null}catch{}}",
582
- "$ni.Visible=$false;$ni.Dispose()"
583
- ].join("\n");
584
- try {
585
- writeFileSync(p, script);
586
- winScriptPath = p;
587
- return p;
588
- } catch {
589
- return null;
590
- }
591
- }
592
- function winNotify(title, msg) {
593
- const script = ensureWinScript();
594
- if (!script) return;
595
- try {
596
- const p = spawn("powershell", ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", script, "-Title", title, "-Msg", msg, "-Hwnd", focusToken || ""], { stdio: "ignore", detached: true, windowsHide: true });
597
- p.on("error", () => {
598
- });
599
- p.unref();
600
- } catch {
601
- }
602
- }
603
- function macBundleId() {
604
- const prog = (process.env.TERM_PROGRAM || "").toLowerCase();
605
- if (prog.includes("iterm")) return "com.googlecode.iterm2";
606
- if (prog.includes("apple_terminal")) return "com.apple.Terminal";
607
- if (prog.includes("vscode")) return "com.microsoft.VSCode";
608
- return "com.apple.Terminal";
609
- }
610
- function macNotify(title, msg) {
611
- try {
612
- const p = spawn("terminal-notifier", ["-title", title, "-message", msg, "-activate", macBundleId(), "-sender", macBundleId()], { stdio: "ignore", detached: true });
613
- p.on("error", () => {
614
- try {
615
- const esc = (s) => s.replace(/"/g, '\\"');
616
- const a = spawn("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
617
- a.on("error", () => {
618
- });
619
- a.unref();
620
- } catch {
621
- }
622
- });
623
- p.unref();
624
549
  } catch {
625
550
  }
626
551
  }
@@ -664,6 +589,24 @@ function usePoll(reload, intervalMs, enabled = true) {
664
589
  return () => clearInterval(t);
665
590
  }, [reload, intervalMs, enabled]);
666
591
  }
592
+ function useTermSize() {
593
+ const read = () => ({ cols: process.stdout.columns ?? 100, rows: process.stdout.rows ?? 30 });
594
+ const [size, setSize] = useState(read);
595
+ useEffect(() => {
596
+ const onResize = () => setSize(read());
597
+ process.stdout.on("resize", onResize);
598
+ return () => {
599
+ process.stdout.off("resize", onResize);
600
+ };
601
+ }, []);
602
+ return size;
603
+ }
604
+ function usePanelSize() {
605
+ const { cols, rows } = useTermSize();
606
+ const wide = cols >= 82;
607
+ const shellRows = wide ? 12 : 7;
608
+ return { cols: Math.max(30, cols - 22), rows: Math.max(8, rows - shellRows) };
609
+ }
667
610
 
668
611
  // src/tui/panels/Live.tsx
669
612
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
@@ -671,8 +614,8 @@ var CONFIG = loadConfig();
671
614
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
672
615
  var BG = "#12234f";
673
616
  var DIM_FLOOR = "#233457";
674
- var LOCAL_LOG = join4(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
675
- var RING = join4(process.cwd(), ".solongate", ".eval-ring.jsonl");
617
+ var LOCAL_LOG = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
618
+ var RING = join3(process.cwd(), ".solongate", ".eval-ring.jsonl");
676
619
  var hhmmss = (ts) => {
677
620
  const d = new Date(ts);
678
621
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -824,6 +767,9 @@ function LivePanel({ active: active2 }) {
824
767
  const [detail, setDetail] = useState2(null);
825
768
  const [detailCloud, setDetailCloud] = useState2([]);
826
769
  const [detailScroll, setDetailScroll] = useState2(0);
770
+ const [inspect, setInspect] = useState2(null);
771
+ const [inspectScroll, setInspectScroll] = useState2(0);
772
+ const inspectFromRef = useRef2("stream");
827
773
  const seenRef = useRef2(/* @__PURE__ */ new Set());
828
774
  const lastLocalTs = useRef2(0);
829
775
  const pausedUntil = useRef2(0);
@@ -1029,8 +975,7 @@ function LivePanel({ active: active2 }) {
1029
975
  else if (e.burst) fireAlert("sec:" + e.id, "Rate-limit burst", `BURST ${e.tool}${who}`, "warn");
1030
976
  }
1031
977
  }, [tick, active2, fireAlert]);
1032
- const cols = process.stdout.columns ?? 100;
1033
- const rows = process.stdout.rows ?? 30;
978
+ const { cols, rows } = useTermSize();
1034
979
  const ins = insights.data ?? {};
1035
980
  const spin = SPIN[tick % SPIN.length];
1036
981
  const nowMs = Date.now();
@@ -1176,8 +1121,18 @@ function LivePanel({ active: active2 }) {
1176
1121
  setEditingSearch(true);
1177
1122
  return;
1178
1123
  }
1124
+ if (mode === "inspect") {
1125
+ if (key.leftArrow) {
1126
+ setMode(inspectFromRef.current);
1127
+ setInspect(null);
1128
+ } else if (key.upArrow) setInspectScroll((n) => Math.max(0, n - 1));
1129
+ else if (key.downArrow) setInspectScroll((n) => n + 1);
1130
+ else if (key.pageUp) setInspectScroll((n) => Math.max(0, n - 10));
1131
+ else if (key.pageDown) setInspectScroll((n) => n + 10);
1132
+ return;
1133
+ }
1179
1134
  if (mode === "detail") {
1180
- const maxD = Math.max(0, detailEntries.length - (rows - 10));
1135
+ const maxD = Math.max(0, detailEntries.length - 1);
1181
1136
  if (key.leftArrow) {
1182
1137
  setMode("stream");
1183
1138
  setDetail(null);
@@ -1185,6 +1140,15 @@ function LivePanel({ active: active2 }) {
1185
1140
  else if (key.downArrow) setDetailScroll((n) => Math.min(maxD, n + 1));
1186
1141
  else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
1187
1142
  else if (key.pageDown) setDetailScroll((n) => Math.min(maxD, n + 10));
1143
+ else if (key.return) {
1144
+ const e = detailEntries[Math.min(detailScroll, maxD)];
1145
+ if (e) {
1146
+ inspectFromRef.current = "detail";
1147
+ setInspect(e);
1148
+ setInspectScroll(0);
1149
+ setMode("inspect");
1150
+ }
1151
+ }
1188
1152
  return;
1189
1153
  }
1190
1154
  if (mode === "pick") {
@@ -1206,6 +1170,13 @@ function LivePanel({ active: active2 }) {
1206
1170
  if (input === "f") {
1207
1171
  setFilter((f) => FILTERS[(FILTERS.indexOf(f) + 1) % FILTERS.length]);
1208
1172
  setSel(0);
1173
+ } else if (key.return) {
1174
+ if (selEntry) {
1175
+ inspectFromRef.current = "stream";
1176
+ setInspect(selEntry);
1177
+ setInspectScroll(0);
1178
+ setMode("inspect");
1179
+ }
1209
1180
  } else if (input === "s") {
1210
1181
  setPickIdx(0);
1211
1182
  setMode("pick");
@@ -1215,10 +1186,10 @@ function LivePanel({ active: active2 }) {
1215
1186
  else if (input === "x") toggleSignal("dlp");
1216
1187
  else if (input === "r") toggleSignal("ratelimit");
1217
1188
  else if (input === "e") {
1218
- const file = join4(homedir3(), ".solongate", "live-export.jsonl");
1189
+ const file = join3(homedir3(), ".solongate", "live-export.jsonl");
1219
1190
  try {
1220
- mkdirSync(join4(homedir3(), ".solongate"), { recursive: true });
1221
- writeFileSync2(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1191
+ mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
1192
+ writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1222
1193
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
1223
1194
  } catch (err) {
1224
1195
  setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
@@ -1268,6 +1239,52 @@ function LivePanel({ active: active2 }) {
1268
1239
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: ` ${spin} up ${fmtUp(nowMs - startRef.current)} \xB7 ${hhmmss(nowMs)} \xB7 api ${latNow}ms ` }),
1269
1240
  frozen ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : backingOff ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d2a12", color: "#ffb454", bold: true, children: " RATE LIMITED \xB7 backing off " }) : lastDeny ? /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#3d1220", color: "#ff6b6b", bold: true, children: ` \u26A0 ${hhmmss(lastDeny.at)} ${lastDeny.tool} DENIED ` }) : /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#4f6db8", children: " \u2713 clean " })
1270
1241
  ] });
1242
+ if (mode === "inspect" && inspect) {
1243
+ const e = inspect;
1244
+ const bodyW = Math.max(20, cols - 4);
1245
+ const lines = wrapLines(prettyJson(e.detail || "(no arguments / reason recorded)"), bodyW);
1246
+ const bodyRows = Math.max(4, rows - 7);
1247
+ const maxScroll2 = Math.max(0, lines.length - bodyRows);
1248
+ const off = Math.min(inspectScroll, maxScroll2);
1249
+ const win = lines.slice(off, off + bodyRows);
1250
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
1251
+ titleBar,
1252
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1253
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#AAC8FA", bold: true, children: " ENTRY " }),
1254
+ /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
1255
+ /* @__PURE__ */ jsx2(Text2, { color: theme.accent, bold: true, children: " " + e.tool }),
1256
+ /* @__PURE__ */ jsx2(Text2, { color: isLoc(e) ? theme.ok : "#4f6db8", children: " " + (isLoc(e) ? "LOC" : "CLD") }),
1257
+ e.dlp ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: " DLP!" }) : null,
1258
+ e.burst ? /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: " BURST" }) : null,
1259
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2190 back" })
1260
+ ] }),
1261
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1262
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 when " }),
1263
+ /* @__PURE__ */ jsx2(Text2, { children: new Date(e.at).toLocaleString() }),
1264
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 perm " }),
1265
+ /* @__PURE__ */ jsx2(Text2, { children: e.permission || "\u2014" }),
1266
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 eval " }),
1267
+ /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
1268
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 agent " }),
1269
+ /* @__PURE__ */ jsx2(Text2, { children: e.agent ?? "\u2014" }),
1270
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
1271
+ ] }),
1272
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1273
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2502 session " }),
1274
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.session ?? "\u2014" }),
1275
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502 rule " }),
1276
+ /* @__PURE__ */ jsx2(Text2, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
1277
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
1278
+ ] }),
1279
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "FULL CONTENT", extra: `${lines.length} lines${maxScroll2 ? ` \xB7 \u25BC${maxScroll2 - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
1280
+ /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx2(Text2, { wrap: "truncate", children: l || " " }, off + i)) }),
1281
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1282
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " ENTRY " }),
1283
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${e.id} ` }),
1284
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 space copy mode \xB7 \u2190 back \xB7 esc menu " })
1285
+ ] })
1286
+ ] });
1287
+ }
1271
1288
  if (mode === "detail" && detail) {
1272
1289
  const d = detailEntries;
1273
1290
  const allow = d.filter((e) => e.decision === "ALLOW").length;
@@ -1287,9 +1304,10 @@ function LivePanel({ active: active2 }) {
1287
1304
  const last = d[0]?.at;
1288
1305
  const tlRows = Math.max(4, rows - 10);
1289
1306
  const tlBody = Math.max(3, tlRows - 1);
1290
- const maxD = Math.max(0, d.length - tlBody);
1291
- const dScroll = Math.min(detailScroll, maxD);
1292
- const tl = d.slice(dScroll, dScroll + tlBody);
1307
+ const dSel = Math.min(detailScroll, Math.max(0, d.length - 1));
1308
+ const maxStart = Math.max(0, d.length - tlBody);
1309
+ const start = Math.min(Math.max(0, dSel - Math.floor((tlBody - 1) / 2)), maxStart);
1310
+ const tl = d.slice(start, start + tlBody);
1293
1311
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
1294
1312
  titleBar,
1295
1313
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
@@ -1327,16 +1345,16 @@ function LivePanel({ active: active2 }) {
1327
1345
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: perms.map(([p, c2]) => `${p}\xD7${c2}`).join(" ") || "\u2014" }),
1328
1346
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2502" })
1329
1347
  ] }),
1330
- /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${d.length} calls \xB7 newest first${dScroll ? ` \xB7 \u25BC${dScroll} older` : ""} \xB7 \u2191\u2193 scroll \xB7 / search \xB7 \u2190 back`, width: innerW }),
1348
+ /* @__PURE__ */ jsx2(PaneTitle, { label: "TIMELINE", extra: `${dSel + 1}/${d.length} \xB7 newest first \xB7 \u2191\u2193 select \xB7 enter full entry \xB7 / search \xB7 \u2190 back`, width: innerW }),
1331
1349
  /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", height: tlRows, overflow: "hidden", children: [
1332
1350
  searchRow,
1333
1351
  tl.length === 0 ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: detailCloud.length === 0 ? spin + " loading history\u2026" : "no entries" }) : null,
1334
- tl.map((e) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e) }, e.id))
1352
+ tl.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: start + i === dSel }, e.id))
1335
1353
  ] }),
1336
1354
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1337
1355
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " SESSION " }),
1338
1356
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` ${detail.id} ` }),
1339
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 scroll \xB7 \u2190 back \xB7 esc menu " })
1357
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 enter full entry \xB7 \u2190 back \xB7 esc menu " })
1340
1358
  ] })
1341
1359
  ] });
1342
1360
  }
@@ -1449,7 +1467,7 @@ function LivePanel({ active: active2 }) {
1449
1467
  PaneTitle,
1450
1468
  {
1451
1469
  label: "TOOL STREAM",
1452
- extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""} \xB7 \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r ${filter} \xB7 s`,
1470
+ extra: `${selClamped + 1}/${visibleDesc.length}${signal !== "none" ? ` \xB7 ${signal}` : ""}${q ? " \xB7 search" : ""} \xB7 \u2191\u2193 select \xB7 enter full \xB7 w allow \xB7 b block \xB7 d/x/r ${filter} \xB7 s`,
1453
1471
  width: innerW
1454
1472
  }
1455
1473
  ),
@@ -1457,7 +1475,7 @@ function LivePanel({ active: active2 }) {
1457
1475
  searchRow,
1458
1476
  localOn === false ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1459
1477
  /* @__PURE__ */ jsx2(Text2, { color: theme.warn, children: "local logs off" }),
1460
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dashboard \u2192 Settings \u2192 Local logs \xB7 hooks write ~/.solongate/local-logs \xB7 view: `solongate logs-server`" })
1478
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " \u2014 enable: dataroom \u2192 Settings (local logs) or dashboard \u2192 Settings \xB7 hooks write ~/.solongate/local-logs" })
1461
1479
  ] }) : null,
1462
1480
  windowed.length === 0 ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
1463
1481
  spin,
@@ -1468,7 +1486,7 @@ function LivePanel({ active: active2 }) {
1468
1486
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1469
1487
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", bold: true, children: " LIVE " }),
1470
1488
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: "#0b1530", color: "#4f6db8", children: ` local ${localOn ? "on" : "off"} \xB7 ${localBuf.length} loc/${cloudBuf.length} cld \xB7 top ${topTools.map(([t, c2]) => `${t}\xD7${c2}`).join(" ") || "\u2014"} ` }),
1471
- /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 w allow \xB7 b block \xB7 d/x/r \xB7 / search \xB7 f \xB7 s \xB7 space copy \xB7 esc \xB7 q " })
1489
+ /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "#82AAF0", children: " \u2191\u2193 select \xB7 enter full \xB7 w allow \xB7 b block \xB7 d/x/r \xB7 / search \xB7 f \xB7 s \xB7 space copy \xB7 esc \xB7 q " })
1472
1490
  ] })
1473
1491
  ] });
1474
1492
  }
@@ -2225,10 +2243,12 @@ function StatsPanel({ active: active2 }) {
2225
2243
  import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
2226
2244
  import TextInput4 from "ink-text-input";
2227
2245
  import { useState as useState6 } from "react";
2228
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2246
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2229
2247
  var DECISIONS = [void 0, "DENY", "ALLOW"];
2230
2248
  var SIGNALS = [void 0, "dlp", "ratelimit"];
2249
+ var FETCH_LIMIT = 500;
2231
2250
  function AuditPanel({ active: active2, focused }) {
2251
+ const { cols, rows } = usePanelSize();
2232
2252
  const [di, setDi] = useState6(0);
2233
2253
  const [gi, setGi] = useState6(0);
2234
2254
  const [tool, setTool] = useState6("");
@@ -2236,6 +2256,7 @@ function AuditPanel({ active: active2, focused }) {
2236
2256
  const [search, setSearch] = useState6("");
2237
2257
  const [sel, setSel] = useState6(0);
2238
2258
  const [view, setView] = useState6("list");
2259
+ const [detailScroll, setDetailScroll] = useState6(0);
2239
2260
  const [editing, setEditing] = useState6(null);
2240
2261
  const query = {
2241
2262
  filter: DECISIONS[di],
@@ -2243,7 +2264,7 @@ function AuditPanel({ active: active2, focused }) {
2243
2264
  tool: tool || void 0,
2244
2265
  agent_name: agent || void 0,
2245
2266
  search: search || void 0,
2246
- limit: 100
2267
+ limit: FETCH_LIMIT
2247
2268
  };
2248
2269
  const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
2249
2270
  usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
@@ -2257,15 +2278,29 @@ function AuditPanel({ active: active2, focused }) {
2257
2278
  (input, key) => {
2258
2279
  if (view === "detail") {
2259
2280
  if (key.leftArrow || key.escape) setView("list");
2281
+ else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
2282
+ else if (key.downArrow) setDetailScroll((n) => n + 1);
2283
+ else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
2284
+ else if (key.pageDown) setDetailScroll((n) => n + 10);
2260
2285
  return;
2261
2286
  }
2287
+ const clearSel = () => setSel(0);
2262
2288
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2263
2289
  else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
2290
+ else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
2291
+ else if (key.pageDown) setSel((n) => Math.min(entries.length - 1, n + 10));
2264
2292
  else if (key.return || key.rightArrow) {
2265
- if (current) setView("detail");
2266
- } else if (input === "f") setDi((n) => (n + 1) % DECISIONS.length);
2267
- else if (input === "g") setGi((n) => (n + 1) % SIGNALS.length);
2268
- else if (input === "t") setEditing("tool");
2293
+ if (current) {
2294
+ setDetailScroll(0);
2295
+ setView("detail");
2296
+ }
2297
+ } else if (input === "f") {
2298
+ setDi((n) => (n + 1) % DECISIONS.length);
2299
+ clearSel();
2300
+ } else if (input === "g") {
2301
+ setGi((n) => (n + 1) % SIGNALS.length);
2302
+ clearSel();
2303
+ } else if (input === "t") setEditing("tool");
2269
2304
  else if (input === "n") setEditing("agent");
2270
2305
  else if (input === "/") setEditing("search");
2271
2306
  else if (input === "c") {
@@ -2274,49 +2309,64 @@ function AuditPanel({ active: active2, focused }) {
2274
2309
  setTool("");
2275
2310
  setAgent("");
2276
2311
  setSearch("");
2312
+ clearSel();
2277
2313
  }
2278
2314
  },
2279
2315
  { isActive: focused && !editing }
2280
2316
  );
2281
2317
  if (view === "detail" && current) {
2282
2318
  const sessionCalls = sessionQ.data?.entries ?? [];
2319
+ const bodyW = Math.max(20, cols - 2);
2320
+ const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
2321
+ const argLines = current.arguments_summary ? wrapLines(prettyJson(JSON.stringify(current.arguments_summary)), bodyW) : ["(no arguments recorded)"];
2322
+ const sessionRows = current.session_id ? Math.min(4, sessionCalls.length) + 2 : 1;
2323
+ const reasonShown = reasonLines.slice(0, 4);
2324
+ const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
2325
+ const maxScroll = Math.max(0, argLines.length - argBudget);
2326
+ const off = Math.min(detailScroll, maxScroll);
2327
+ const argWin = argLines.slice(off, off + argBudget);
2283
2328
  return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2284
2329
  /* @__PURE__ */ jsxs7(Box7, { children: [
2285
2330
  /* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
2286
2331
  /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
2287
2332
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
2288
2333
  ] }),
2289
- /* @__PURE__ */ jsx7(Detail, { label: "reason", value: current.reason ?? "\u2014" }),
2334
+ reasonShown.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
2335
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "reason " }),
2336
+ /* @__PURE__ */ jsx7(Text7, { children: l.slice("reason: ".length) })
2337
+ ] }) : /* @__PURE__ */ jsx7(Text7, { children: " " + l }) }, "r" + i)),
2290
2338
  /* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
2291
2339
  /* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
2292
2340
  /* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
2293
2341
  /* @__PURE__ */ jsx7(Detail, { label: "dlp", value: current.dlp_matches?.length ? current.dlp_matches.join(", ") : "none", color: current.dlp_matches?.length ? theme.bad : void 0 }),
2294
- /* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() }),
2295
- current.arguments_summary ? /* @__PURE__ */ jsx7(Detail, { label: "args", value: truncate(JSON.stringify(current.arguments_summary), 60) }) : null,
2296
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2342
+ /* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() + (current.evaluation_time_ms != null ? ` \xB7 eval ${current.evaluation_time_ms}ms` : "") }),
2343
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
2344
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
2345
+ current.session_id ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2297
2346
  /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2298
- "Session ",
2299
- current.session_id ? `\xB7 ${sessionCalls.length} calls` : "(no session id)"
2347
+ "Session \xB7 ",
2348
+ sessionCalls.length,
2349
+ " calls"
2300
2350
  ] }),
2301
- current.session_id ? /* @__PURE__ */ jsx7(
2351
+ /* @__PURE__ */ jsx7(
2302
2352
  Table,
2303
2353
  {
2304
2354
  columns: [
2305
2355
  { header: "DECISION", width: 9 },
2306
2356
  { header: "TOOL", width: 20 },
2307
- { header: "REASON", width: 26 },
2357
+ { header: "REASON", width: Math.max(16, cols - 48) },
2308
2358
  { header: "WHEN", width: 6 }
2309
2359
  ],
2310
- rows: sessionCalls.slice(0, 12).map((e) => [
2360
+ rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
2311
2361
  { value: e.decision, color: decisionColor(e.decision) },
2312
2362
  { value: truncate(e.tool_name, 20), color: theme.accent },
2313
- { value: truncate(e.reason ?? "\u2014", 26) },
2363
+ { value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
2314
2364
  { value: ago(e.created_at), dim: true }
2315
2365
  ])
2316
2366
  }
2317
- ) : null
2318
- ] }),
2319
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2190 back to list" })
2367
+ )
2368
+ ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "(no session id)" }),
2369
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to list" })
2320
2370
  ] });
2321
2371
  }
2322
2372
  const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
@@ -2327,6 +2377,13 @@ function AuditPanel({ active: active2, focused }) {
2327
2377
  /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
2328
2378
  /* @__PURE__ */ jsx7(Text7, { children: " " })
2329
2379
  ] });
2380
+ const headerRows = 4 + (editing ? 1 : 0);
2381
+ const listRows = Math.max(4, rows - headerRows);
2382
+ const selClamped = Math.min(sel, Math.max(0, entries.length - 1));
2383
+ const maxStart = Math.max(0, entries.length - listRows);
2384
+ const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
2385
+ const windowed = entries.slice(start, start + listRows);
2386
+ const reasonW = Math.min(60, Math.max(12, cols - 67));
2330
2387
  return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2331
2388
  /* @__PURE__ */ jsxs7(Box7, { children: [
2332
2389
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
@@ -2335,8 +2392,8 @@ function AuditPanel({ active: active2, focused }) {
2335
2392
  chip("agent", agent || "\xB7", !!agent),
2336
2393
  chip("search", search || "\xB7", !!search)
2337
2394
  ] }),
2338
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 \xB7 enter detail \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 c clear" : "press \u2192 to browse" }),
2339
- editing ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
2395
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 c clear" : "press \u2192 to browse" }),
2396
+ editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
2340
2397
  /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
2341
2398
  editing,
2342
2399
  ": "
@@ -2345,16 +2402,15 @@ function AuditPanel({ active: active2, focused }) {
2345
2402
  TextInput4,
2346
2403
  {
2347
2404
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
2348
- onChange: editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch,
2405
+ onChange: (v) => {
2406
+ (editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
2407
+ setSel(0);
2408
+ },
2349
2409
  onSubmit: () => setEditing(null)
2350
2410
  }
2351
2411
  )
2352
2412
  ] }) : null,
2353
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2354
- auditQ.data?.total ?? 0,
2355
- " matched \xB7 showing ",
2356
- Math.min(entries.length, 12)
2357
- ] }) }),
2413
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: `${auditQ.data?.total ?? 0} matched \xB7 ${entries.length} loaded \xB7 ${selClamped + 1}/${entries.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < entries.length ? ` \xB7 \u25BC${entries.length - start - listRows} older` : ""}` }),
2358
2414
  /* @__PURE__ */ jsx7(
2359
2415
  Table,
2360
2416
  {
@@ -2363,22 +2419,22 @@ function AuditPanel({ active: active2, focused }) {
2363
2419
  { header: "DECISION", width: 9 },
2364
2420
  { header: "TOOL", width: 18 },
2365
2421
  { header: "AGENT", width: 14 },
2366
- { header: "REASON", width: 24 },
2422
+ { header: "REASON", width: reasonW },
2367
2423
  { header: "DLP", width: 4 },
2368
2424
  { header: "WHEN", width: 6 }
2369
2425
  ],
2370
- rows: entries.slice(0, 12).map((e, i) => rowFor(e, i === sel && focused))
2426
+ rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
2371
2427
  }
2372
2428
  )
2373
2429
  ] }) });
2374
2430
  }
2375
- function rowFor(e, active2) {
2431
+ function rowFor(e, active2, reasonW) {
2376
2432
  return [
2377
2433
  { value: active2 ? "\u25B8" : "", color: theme.accentBright },
2378
2434
  { value: e.decision, color: decisionColor(e.decision) },
2379
2435
  { value: truncate(e.tool_name, 18), color: theme.accent },
2380
2436
  { value: truncate(e.agent_name ?? "\u2014", 14), dim: true },
2381
- { value: truncate(e.reason ?? "\u2014", 24) },
2437
+ { value: truncate(e.reason ?? "\u2014", reasonW) },
2382
2438
  { value: e.dlp_matches?.length ? String(e.dlp_matches.length) : "0", color: e.dlp_matches?.length ? theme.bad : void 0, dim: !e.dlp_matches?.length },
2383
2439
  { value: ago(e.created_at), dim: true }
2384
2440
  ];
@@ -2386,14 +2442,14 @@ function rowFor(e, active2) {
2386
2442
  function Detail({ label, value, color }) {
2387
2443
  return /* @__PURE__ */ jsxs7(Box7, { children: [
2388
2444
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
2389
- /* @__PURE__ */ jsx7(Text7, { color, children: value })
2445
+ /* @__PURE__ */ jsx7(Text7, { color, wrap: "truncate", children: value })
2390
2446
  ] });
2391
2447
  }
2392
2448
 
2393
2449
  // src/tui/panels/Agents.tsx
2394
2450
  import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
2395
2451
  import { useState as useState7 } from "react";
2396
- import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2452
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2397
2453
  var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
2398
2454
  var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
2399
2455
  function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
@@ -2439,7 +2495,7 @@ function AgentsPanel({ focused }) {
2439
2495
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
2440
2496
  ] }, a.session_id))
2441
2497
  ] }),
2442
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
2498
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
2443
2499
  /* @__PURE__ */ jsxs8(Box8, { children: [
2444
2500
  /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
2445
2501
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
@@ -2476,16 +2532,221 @@ function AgentsPanel({ focused }) {
2476
2532
  ] }) });
2477
2533
  }
2478
2534
 
2479
- // src/tui/App.tsx
2535
+ // src/tui/panels/Settings.tsx
2536
+ import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
2537
+ import TextInput5 from "ink-text-input";
2538
+ import { useState as useState8 } from "react";
2480
2539
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2540
+ var EVENTS = ["denials", "allowed", "all"];
2541
+ function alertBodyFor(channel) {
2542
+ const c2 = channel.trim();
2543
+ const base = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
2544
+ if (/^https?:\/\//i.test(c2)) return { ...base, slackUrl: c2 };
2545
+ if (c2.includes("@")) return { ...base, emails: [c2] };
2546
+ return { ...base, telegram: [c2] };
2547
+ }
2548
+ function SettingsPanel({ active: active2, focused }) {
2549
+ void active2;
2550
+ const { cols, rows } = usePanelSize();
2551
+ const [sel, setSel] = useState8(0);
2552
+ const [editing, setEditing] = useState8(null);
2553
+ const [input, setInput] = useState8("");
2554
+ const [confirmDel, setConfirmDel] = useState8(null);
2555
+ const [msg, setMsg] = useState8(null);
2556
+ const [busy, setBusy] = useState8(false);
2557
+ const localQ = useLoader(() => api.settings.getLocalLogs());
2558
+ const whQ = useLoader(() => api.settings.getWebhooks());
2559
+ const alertQ = useLoader(() => api.settings.getAlerts());
2560
+ const local = localQ.data;
2561
+ const webhooks = whQ.data?.webhooks ?? [];
2562
+ const alerts = alertQ.data?.rules ?? [];
2563
+ const rowsAll = [
2564
+ { kind: "ll-enabled" },
2565
+ { kind: "ll-path" },
2566
+ ...webhooks.map((wh) => ({ kind: "wh", wh })),
2567
+ { kind: "wh-add" },
2568
+ ...alerts.map((rule) => ({ kind: "alert", rule })),
2569
+ { kind: "alert-add" }
2570
+ ];
2571
+ const selClamped = Math.min(sel, rowsAll.length - 1);
2572
+ const cur = rowsAll[selClamped];
2573
+ const keyOf = (r) => r.kind === "wh" ? "wh:" + r.wh.id : r.kind === "alert" ? "alert:" + r.rule.id : r.kind;
2574
+ const reloadAll = () => {
2575
+ localQ.reload();
2576
+ whQ.reload();
2577
+ alertQ.reload();
2578
+ };
2579
+ const run = (label, fn, reload) => {
2580
+ if (busy) return;
2581
+ setBusy(true);
2582
+ setMsg({ text: label + "\u2026", level: "ok" });
2583
+ fn().then(() => {
2584
+ setMsg({ text: "\u2713 " + label, level: "ok" });
2585
+ reload();
2586
+ }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" })).finally(() => setBusy(false));
2587
+ };
2588
+ const activate = (r) => {
2589
+ if (r.kind === "ll-enabled") {
2590
+ if (!local) return;
2591
+ if (!local.enabled && !local.path.trim()) {
2592
+ setMsg({ text: "set a path first (\u2193 then enter)", level: "bad" });
2593
+ return;
2594
+ }
2595
+ run(local.enabled ? "local logs disabled" : "local logs enabled", () => api.settings.setLocalLogs({ enabled: !local.enabled, path: local.path }), localQ.reload);
2596
+ } else if (r.kind === "ll-path") {
2597
+ setInput(local?.path ?? "");
2598
+ setEditing("path");
2599
+ } else if (r.kind === "wh") {
2600
+ run(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
2601
+ } else if (r.kind === "wh-add") {
2602
+ setInput("");
2603
+ setEditing("wh-url");
2604
+ } else if (r.kind === "alert") {
2605
+ run(r.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(r.rule.id, !r.rule.enabled), alertQ.reload);
2606
+ } else {
2607
+ setInput("");
2608
+ setEditing("alert-channel");
2609
+ }
2610
+ };
2611
+ const submitInput = () => {
2612
+ const v = input.trim();
2613
+ const which = editing;
2614
+ setEditing(null);
2615
+ if (which === "path") {
2616
+ const enabled = (local?.enabled ?? false) && v.length > 0;
2617
+ run(v ? `path saved${enabled ? "" : " (press enter on enabled to turn on)"}` : "path cleared (local logs off)", () => api.settings.setLocalLogs({ enabled, path: v }), localQ.reload);
2618
+ } else if (which === "wh-url") {
2619
+ if (!v) return;
2620
+ run("webhook added", () => api.settings.createWebhook({ url: v, events: "denials" }), whQ.reload);
2621
+ } else if (which === "alert-channel") {
2622
+ if (!v) return;
2623
+ run("alert rule added", () => api.settings.createAlert(alertBodyFor(v)), alertQ.reload);
2624
+ }
2625
+ };
2626
+ useInput7(
2627
+ (inp, key) => {
2628
+ setMsg(null);
2629
+ if (key.upArrow) {
2630
+ setSel((n) => Math.max(0, n - 1));
2631
+ setConfirmDel(null);
2632
+ } else if (key.downArrow) {
2633
+ setSel((n) => Math.min(rowsAll.length - 1, n + 1));
2634
+ setConfirmDel(null);
2635
+ } else if (key.return || inp === " ") activate(cur);
2636
+ else if (inp === "e" && cur.kind === "wh") {
2637
+ const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
2638
+ run(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
2639
+ } else if (inp === "e" && cur.kind === "ll-path") activate(cur);
2640
+ else if (inp === "a") {
2641
+ if (cur.kind === "wh" || cur.kind === "wh-add") {
2642
+ setInput("");
2643
+ setEditing("wh-url");
2644
+ } else if (cur.kind === "alert" || cur.kind === "alert-add") {
2645
+ setInput("");
2646
+ setEditing("alert-channel");
2647
+ }
2648
+ } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
2649
+ const k = keyOf(cur);
2650
+ if (confirmDel !== k) {
2651
+ setConfirmDel(k);
2652
+ setMsg({ text: "press d again to delete", level: "bad" });
2653
+ return;
2654
+ }
2655
+ setConfirmDel(null);
2656
+ if (cur.kind === "wh") run("webhook deleted", () => api.settings.deleteWebhook(cur.wh.id), whQ.reload);
2657
+ else run("alert deleted", () => api.settings.deleteAlert(cur.rule.id), alertQ.reload);
2658
+ } else if (inp === "r") reloadAll();
2659
+ },
2660
+ { isActive: focused && !editing }
2661
+ );
2662
+ const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
2663
+ const error = localQ.error ?? whQ.error ?? alertQ.error;
2664
+ const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
2665
+ const onOff = (on) => on ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "off" });
2666
+ const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
2667
+ const listBudget = Math.max(4, rows - 8);
2668
+ const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
2669
+ const inWindow = (r) => {
2670
+ const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
2671
+ return i >= start && i < start + listBudget;
2672
+ };
2673
+ return /* @__PURE__ */ jsx9(DataView, { loading, error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2674
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter/space toggle-edit-add \xB7 e events \xB7 d delete \xB7 r refresh" : "press \u2192 to configure" }),
2675
+ editing ? /* @__PURE__ */ jsxs9(Box9, { children: [
2676
+ /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
2677
+ /* @__PURE__ */ jsx9(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
2678
+ ] }) : msg ? /* @__PURE__ */ jsx9(Text9, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx9(Text9, { children: " " }),
2679
+ inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2680
+ /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2681
+ "LOCAL LOGS ",
2682
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
2683
+ ] }),
2684
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2685
+ /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
2686
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "enabled ".padEnd(10) }),
2687
+ onOff(!!local?.enabled),
2688
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
2689
+ ] }),
2690
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2691
+ /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
2692
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "path ".padEnd(10) }),
2693
+ local?.path ? /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "not set \u2014 enter to edit" })
2694
+ ] })
2695
+ ] }) : null,
2696
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2697
+ /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2698
+ "WEBHOOKS ",
2699
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2700
+ "\u2014 POST every matching event to your endpoint (",
2701
+ webhooks.length,
2702
+ ")"
2703
+ ] })
2704
+ ] }),
2705
+ webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2706
+ /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
2707
+ onOff(wh.enabled),
2708
+ /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
2709
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(wh.url, cols - 16) })
2710
+ ] }, wh.id)),
2711
+ inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs9(Text9, { children: [
2712
+ /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
2713
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add webhook (enter)" })
2714
+ ] }) : null
2715
+ ] }),
2716
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2717
+ /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2718
+ "ALERTS ",
2719
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2720
+ "\u2014 notify on a signal spike (",
2721
+ alerts.length,
2722
+ ")"
2723
+ ] })
2724
+ ] }),
2725
+ alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2726
+ /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
2727
+ onOff(rule.enabled),
2728
+ /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
2729
+ /* @__PURE__ */ jsx9(Text9, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
2730
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
2731
+ ] }, rule.id)),
2732
+ inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs9(Text9, { children: [
2733
+ /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
2734
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
2735
+ ] }) : null
2736
+ ] })
2737
+ ] }) });
2738
+ }
2739
+
2740
+ // src/tui/App.tsx
2741
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2481
2742
  function LiveHint() {
2482
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2483
- /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2484
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2485
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2486
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
2743
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2744
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2745
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2746
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2747
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { children: [
2487
2748
  "press ",
2488
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
2749
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
2489
2750
  " to go live"
2490
2751
  ] }) })
2491
2752
  ] });
@@ -2497,28 +2758,29 @@ var SECTIONS = [
2497
2758
  { label: "Rate Limit", Panel: RateLimitPanel },
2498
2759
  { label: "DLP", Panel: DlpPanel },
2499
2760
  { label: "Stats", Panel: StatsPanel },
2500
- { label: "Audit", Panel: AuditPanel }
2761
+ { label: "Audit", Panel: AuditPanel },
2762
+ { label: "Settings", Panel: SettingsPanel }
2501
2763
  ];
2502
2764
  var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8FA"];
2503
- function Banner() {
2504
- const wide = (process.stdout.columns ?? 80) >= 82;
2765
+ function Banner({ cols }) {
2766
+ const wide = cols >= 82;
2505
2767
  if (!wide) {
2506
- return /* @__PURE__ */ jsxs9(Box9, { children: [
2507
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2508
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
2768
+ return /* @__PURE__ */ jsxs10(Box10, { children: [
2769
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2770
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \u2014 security control center" })
2509
2771
  ] });
2510
2772
  }
2511
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2512
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2513
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2773
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2774
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2775
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2514
2776
  ] });
2515
2777
  }
2516
2778
  function App() {
2517
2779
  const { exit } = useApp();
2518
- const [section, setSection] = useState8(0);
2519
- const [focus, setFocus] = useState8("nav");
2520
- const [help, setHelp] = useState8(false);
2521
- useInput7((input, key) => {
2780
+ const [section, setSection] = useState9(0);
2781
+ const [focus, setFocus] = useState9("nav");
2782
+ const [help, setHelp] = useState9(false);
2783
+ useInput8((input, key) => {
2522
2784
  if (help) {
2523
2785
  setHelp(false);
2524
2786
  return;
@@ -2537,45 +2799,46 @@ function App() {
2537
2799
  else if (input === "q" && SECTIONS[section].label === "Live") exit();
2538
2800
  }
2539
2801
  });
2540
- const cols = process.stdout.columns ?? 100;
2541
- const rows = process.stdout.rows ?? 30;
2802
+ const { cols, rows } = useTermSize();
2542
2803
  const current = SECTIONS[section];
2543
- if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
2804
+ if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
2544
2805
  if (current.label === "Live" && focus === "panel") {
2545
- return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }) });
2806
+ return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx10(LivePanel, { active: true, focused: true }) });
2546
2807
  }
2547
2808
  const Panel = current.Panel;
2548
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
2549
- /* @__PURE__ */ jsx9(Banner, {}),
2550
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
2551
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
2552
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
2809
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
2810
+ /* @__PURE__ */ jsx10(Banner, { cols }),
2811
+ /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexGrow: 1, children: [
2812
+ /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx10(Text10, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
2813
+ /* @__PURE__ */ jsx10(Box10, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx10(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
2553
2814
  ] }),
2554
- /* @__PURE__ */ jsx9(Box9, { children: focus === "nav" ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
2815
+ /* @__PURE__ */ jsx10(Box10, { children: focus === "nav" ? /* @__PURE__ */ jsx10(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx10(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
2555
2816
  ] });
2556
2817
  }
2557
2818
  var HELP = [
2558
2819
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
2559
- ["Live", [["\u2191\u2193", "select a stream row"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
2820
+ ["Live", [["\u2191\u2193", "select a stream row"], ["enter", "full entry content"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
2560
2821
  ["Policies", [["\u2191\u2193", "browse / select"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["v", "versions / rollback"], ["s", "save"], ["x", "discard"]]],
2561
- ["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]]
2822
+ ["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]],
2823
+ ["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry detail"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
2824
+ ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
2562
2825
  ];
2563
2826
  function HelpOverlay({ cols, rows }) {
2564
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
2565
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
2566
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
2567
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
2568
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
2569
- /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
2570
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
2827
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
2828
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
2829
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginBottom: 1, children: [
2830
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accent, children: group }),
2831
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs10(Text10, { children: [
2832
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
2833
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: desc })
2571
2834
  ] }, k))
2572
2835
  ] }, group)) }),
2573
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
2836
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
2574
2837
  ] });
2575
2838
  }
2576
2839
 
2577
2840
  // src/tui/index.tsx
2578
- import { jsx as jsx10 } from "react/jsx-runtime";
2841
+ import { jsx as jsx11 } from "react/jsx-runtime";
2579
2842
  async function launchTui() {
2580
2843
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
2581
2844
  process.stderr.write(
@@ -2587,10 +2850,9 @@ async function launchTui() {
2587
2850
  process.stderr.write("Not logged in. Run `solongate login` first.\n");
2588
2851
  return;
2589
2852
  }
2590
- captureFocusTarget();
2591
2853
  process.stdout.write("\x1B[?1049h\x1B[H");
2592
2854
  try {
2593
- const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
2855
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
2594
2856
  await waitUntilExit();
2595
2857
  } finally {
2596
2858
  process.stdout.write("\x1B[?1049l");