@solongate/proxy 0.81.23 → 0.81.25

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 Box10, Text as Text10, useApp, useInput as useInput8 } from "ink";
12
- import { useState as useState9 } from "react";
11
+ import { Box as Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
12
+ import { useState as useState8 } from "react";
13
13
 
14
14
  // src/cli-utils.ts
15
15
  var c = {
@@ -142,7 +142,7 @@ function DataView({
142
142
  "\u2717 ",
143
143
  error
144
144
  ] });
145
- if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: "Loading\u2026" });
145
+ if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
146
146
  if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
147
147
  return /* @__PURE__ */ jsx(Fragment, { children });
148
148
  }
@@ -169,15 +169,50 @@ function KeyHints({ hints }) {
169
169
  // src/tui/panels/Live.tsx
170
170
  import { Box as Box2, Text as Text2, useInput } from "ink";
171
171
  import TextInput from "ink-text-input";
172
- import { closeSync, mkdirSync, openSync, readSync, statSync, writeFileSync } from "fs";
173
- import { homedir as homedir3 } from "os";
174
- import { join as join3 } from "path";
172
+ import { mkdirSync, writeFileSync } from "fs";
173
+ import { homedir as homedir4 } from "os";
174
+ import { join as join4 } from "path";
175
+
176
+ // src/tui/local-log.ts
177
+ import { closeSync, openSync, readSync, statSync } from "fs";
178
+ import { homedir as homedir2 } from "os";
179
+ import { join as join2 } from "path";
180
+ var LOCAL_LOG = join2(homedir2(), ".solongate", "local-logs", "solongate-audit.jsonl");
181
+ function tailLines(file, maxBytes = 131072) {
182
+ try {
183
+ const size = statSync(file).size;
184
+ const start = Math.max(0, size - maxBytes);
185
+ const fd = openSync(file, "r");
186
+ const buf = Buffer.alloc(size - start);
187
+ readSync(fd, buf, 0, buf.length, start);
188
+ closeSync(fd);
189
+ const lines = buf.toString("utf-8").split("\n").filter(Boolean);
190
+ if (start > 0) lines.shift();
191
+ return lines;
192
+ } catch {
193
+ return [];
194
+ }
195
+ }
196
+ function parseLocalLines(lines) {
197
+ const out = [];
198
+ for (const line of lines) {
199
+ try {
200
+ const j = JSON.parse(line);
201
+ const at = Date.parse(j.ts ?? "");
202
+ if (Number.isFinite(at)) out.push({ ...j, at });
203
+ } catch {
204
+ }
205
+ }
206
+ return out;
207
+ }
208
+
209
+ // src/tui/panels/Live.tsx
175
210
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
176
211
 
177
212
  // src/api-client/client.ts
178
213
  import { readFileSync as readFileSync2, existsSync } from "fs";
179
- import { resolve, join as join2 } from "path";
180
- import { homedir as homedir2 } from "os";
214
+ import { resolve, join as join3 } from "path";
215
+ import { homedir as homedir3 } from "os";
181
216
  var DEFAULT_API_URL = "https://api.solongate.com";
182
217
  var ApiError = class extends Error {
183
218
  status;
@@ -197,7 +232,7 @@ var NotAuthenticatedError = class extends Error {
197
232
  };
198
233
  function loginCredentialFile() {
199
234
  try {
200
- const p = join2(homedir2(), ".solongate", "cloud-guard.json");
235
+ const p = join3(homedir3(), ".solongate", "cloud-guard.json");
201
236
  if (!existsSync(p)) return {};
202
237
  const c2 = JSON.parse(readFileSync2(p, "utf-8"));
203
238
  return c2 && typeof c2 === "object" ? c2 : {};
@@ -558,6 +593,7 @@ function useLoader(fn, deps = []) {
558
593
  const [loading, setLoading] = useState(true);
559
594
  const [nonce, setNonce] = useState(0);
560
595
  const alive = useRef(true);
596
+ const quiet = useRef(false);
561
597
  const fnRef = useRef(fn);
562
598
  fnRef.current = fn;
563
599
  useEffect(() => {
@@ -567,7 +603,8 @@ function useLoader(fn, deps = []) {
567
603
  };
568
604
  }, []);
569
605
  useEffect(() => {
570
- setLoading(true);
606
+ if (!quiet.current) setLoading(true);
607
+ quiet.current = false;
571
608
  fnRef.current().then((d) => {
572
609
  if (!alive.current) return;
573
610
  setData(d);
@@ -580,7 +617,11 @@ function useLoader(fn, deps = []) {
580
617
  });
581
618
  }, [nonce, ...deps]);
582
619
  const reload = useCallback(() => setNonce((n) => n + 1), []);
583
- return { data, error, loading, reload };
620
+ const reloadQuiet = useCallback(() => {
621
+ quiet.current = true;
622
+ setNonce((n) => n + 1);
623
+ }, []);
624
+ return { data, error, loading, reload, reloadQuiet };
584
625
  }
585
626
  function usePoll(reload, intervalMs, enabled = true) {
586
627
  useEffect(() => {
@@ -614,8 +655,7 @@ var CONFIG = loadConfig();
614
655
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
615
656
  var BG = "#12234f";
616
657
  var DIM_FLOOR = "#233457";
617
- var LOCAL_LOG = join3(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
618
- var RING = join3(process.cwd(), ".solongate", ".eval-ring.jsonl");
658
+ var RING = join4(process.cwd(), ".solongate", ".eval-ring.jsonl");
619
659
  var hhmmss = (ts) => {
620
660
  const d = new Date(ts);
621
661
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -625,21 +665,6 @@ var fmtUp = (ms) => {
625
665
  const p = (n) => String(n).padStart(2, "0");
626
666
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
627
667
  };
628
- function tailLines(file, maxBytes = 131072) {
629
- try {
630
- const size = statSync(file).size;
631
- const start = Math.max(0, size - maxBytes);
632
- const fd = openSync(file, "r");
633
- const buf = Buffer.alloc(size - start);
634
- readSync(fd, buf, 0, buf.length, start);
635
- closeSync(fd);
636
- const lines = buf.toString("utf-8").split("\n").filter(Boolean);
637
- if (start > 0) lines.shift();
638
- return lines;
639
- } catch {
640
- return [];
641
- }
642
- }
643
668
  function extractTarget(detail) {
644
669
  try {
645
670
  const j = JSON.parse(detail);
@@ -949,13 +974,13 @@ function LivePanel({ active: active2 }) {
949
974
  const insights = useLoader(() => api.stats.securityInsights(7));
950
975
  const guard = useLoader(() => api.settings.getGuardStatus());
951
976
  usePoll(() => {
952
- if (!paused()) sessions.reload();
977
+ if (!paused()) sessions.reloadQuiet();
953
978
  }, 8e3, active2);
954
979
  usePoll(() => {
955
- if (!paused()) insights.reload();
980
+ if (!paused()) insights.reloadQuiet();
956
981
  }, 2e4, active2);
957
982
  usePoll(() => {
958
- if (!paused()) guard.reload();
983
+ if (!paused()) guard.reloadQuiet();
959
984
  }, 6e4, active2);
960
985
  useEffect2(() => {
961
986
  if (!detail) return;
@@ -1229,9 +1254,9 @@ function LivePanel({ active: active2 }) {
1229
1254
  else if (input === "x") toggleSignal("dlp");
1230
1255
  else if (input === "r") toggleSignal("ratelimit");
1231
1256
  else if (input === "e") {
1232
- const file = join3(homedir3(), ".solongate", "live-export.jsonl");
1257
+ const file = join4(homedir4(), ".solongate", "live-export.jsonl");
1233
1258
  try {
1234
- mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
1259
+ mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
1235
1260
  writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1236
1261
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
1237
1262
  } catch (err) {
@@ -1593,7 +1618,6 @@ function PoliciesPanel({ focused }) {
1593
1618
  const [pi, setPi] = useState3(0);
1594
1619
  const [ri, setRi] = useState3(0);
1595
1620
  const [fi, setFi] = useState3(0);
1596
- const [vi, setVi] = useState3(0);
1597
1621
  const [rules, setRules] = useState3([]);
1598
1622
  const [mode, setMode] = useState3("denylist");
1599
1623
  const [dirty, setDirty] = useState3(false);
@@ -1602,15 +1626,6 @@ function PoliciesPanel({ focused }) {
1602
1626
  const [status, setStatus] = useState3(null);
1603
1627
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
1604
1628
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
1605
- const versionsQ = useLoader(
1606
- () => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
1607
- [view, selected?.id]
1608
- );
1609
- const diffVer = versionsQ.data?.versions[vi]?.version;
1610
- const diffQ = useLoader(
1611
- () => view === "versions" && selected && diffVer !== void 0 ? api.policies.get(selected.id, diffVer) : Promise.resolve(null),
1612
- [view, selected?.id, diffVer]
1613
- );
1614
1629
  useEffect3(() => {
1615
1630
  if (detail.data) {
1616
1631
  setRules(detail.data.rules);
@@ -1708,32 +1723,10 @@ function PoliciesPanel({ focused }) {
1708
1723
  } else if (input === "D") {
1709
1724
  setStatus("Dry-running\u2026");
1710
1725
  void api.policies.dryRun({ rules, mode }).then((res) => setStatus(`dry-run ${res.evaluated} calls \xB7 allow ${res.would_allow} / deny ${res.would_deny} \xB7 newly blocked ${res.newly_blocked} \xB7 newly allowed ${res.newly_allowed}`)).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
1711
- } else if (input === "v") {
1712
- setVi(0);
1713
- setView("versions");
1714
1726
  } else if (input === "s") void save();
1715
1727
  else if (input === "x") discard();
1716
1728
  return;
1717
1729
  }
1718
- if (view === "versions") {
1719
- const vers = versionsQ.data?.versions ?? [];
1720
- if (key.leftArrow) return void setView("rules");
1721
- if (key.upArrow) setVi((n) => Math.max(0, n - 1));
1722
- else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
1723
- else if (key.return) {
1724
- const v = vers[vi];
1725
- if (v && selected) {
1726
- setStatus("Rolling back\u2026");
1727
- void api.policies.rollback(selected.id, v.version).then((r) => {
1728
- setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
1729
- detail.reload();
1730
- list5.reload();
1731
- setView("rules");
1732
- }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
1733
- }
1734
- }
1735
- return;
1736
- }
1737
1730
  const rule2 = rules[ri];
1738
1731
  if (!rule2) return setView("rules");
1739
1732
  const field = FIELDS[fi];
@@ -1774,8 +1767,7 @@ function PoliciesPanel({ focused }) {
1774
1767
  p.mode.padEnd(10),
1775
1768
  " ",
1776
1769
  p.rules.length,
1777
- " rules \xB7 v",
1778
- p.version
1770
+ " rules"
1779
1771
  ] }),
1780
1772
  p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
1781
1773
  ] }, p.id)) }),
@@ -1791,7 +1783,7 @@ function PoliciesPanel({ focused }) {
1791
1783
  /* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
1792
1784
  dirtyTag
1793
1785
  ] }),
1794
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7 v versions \xB7 s save \xB7 \u2190 back" }),
1786
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 \xB7 enter edit \xB7 space on/off \xB7 e effect \xB7 n new \xB7 d del \xB7 m mode \xB7 D dry-run \xB7 s save \xB7 \u2190 back" }),
1795
1787
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
1796
1788
  Table,
1797
1789
  {
@@ -1817,50 +1809,6 @@ function PoliciesPanel({ focused }) {
1817
1809
  status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
1818
1810
  ] }) });
1819
1811
  }
1820
- if (view === "versions") {
1821
- const vers = versionsQ.data?.versions ?? [];
1822
- return /* @__PURE__ */ jsx3(DataView, { loading: versionsQ.loading && !versionsQ.data, error: versionsQ.error, empty: !!versionsQ.data && vers.length === 0, emptyText: "No versions.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1823
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
1824
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
1825
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
1826
- Table,
1827
- {
1828
- columns: [
1829
- { header: "", width: 2 },
1830
- { header: "VER", width: 5 },
1831
- { header: "RULES", width: 5 },
1832
- { header: "REASON", width: 34 },
1833
- { header: "WHEN", width: 16 }
1834
- ],
1835
- rows: vers.map((v, i) => [
1836
- { value: i === vi ? "\u25B8" : "", color: theme.accentBright },
1837
- { value: `v${v.version}`, bold: i === vi },
1838
- { value: String(v.rules_count), dim: true },
1839
- { value: truncate(v.reason || "\u2014", 34) },
1840
- { value: truncate(v.created_at, 16), dim: true }
1841
- ])
1842
- }
1843
- ) }),
1844
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1845
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: `Diff v${diffVer ?? "?"} vs current draft` }),
1846
- (() => {
1847
- const old = diffQ.data?.rules ?? [];
1848
- const cur = rules;
1849
- const oldIds = new Set(old.map((r) => r.id));
1850
- const curIds = new Set(cur.map((r) => r.id));
1851
- const added = cur.filter((r) => !oldIds.has(r.id));
1852
- const removed = old.filter((r) => !curIds.has(r.id));
1853
- if (diffQ.loading && !diffQ.data) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " loading\u2026" });
1854
- if (!added.length && !removed.length) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " identical rule set" });
1855
- return /* @__PURE__ */ jsxs3(Fragment3, { children: [
1856
- added.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.ok, children: ` + ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "a" + r.id)),
1857
- removed.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.bad, children: ` - ${r.effect} ${truncate(r.toolPattern, 18)} ${truncate(r.description || r.id, 30)}` }, "r" + r.id))
1858
- ] });
1859
- })()
1860
- ] }),
1861
- status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
1862
- ] }) });
1863
- }
1864
1812
  const rule = rules[ri];
1865
1813
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1866
1814
  /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -2222,171 +2170,229 @@ function RegexTest({ re }) {
2222
2170
  ] });
2223
2171
  }
2224
2172
 
2225
- // src/tui/panels/Stats.tsx
2226
- import { Box as Box6, Text as Text6 } from "ink";
2227
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2228
- function StatsPanel({ active: active2 }) {
2229
- const stats = useLoader(() => api.stats.get());
2230
- const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
2231
- const drift2 = useLoader(() => api.stats.drift(7));
2232
- usePoll(() => {
2233
- stats.reload();
2234
- ts.reload();
2235
- }, 5e3, active2);
2236
- usePoll(drift2.reload, 3e4, active2);
2237
- const s = stats.data;
2238
- const points = ts.data?.timeseries ?? [];
2239
- const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
2240
- return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2241
- /* @__PURE__ */ jsxs6(Box6, { children: [
2242
- /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
2243
- s.total_calls,
2244
- " "
2245
- ] }),
2246
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "calls " }),
2247
- /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
2248
- s.allowed,
2249
- " allowed"
2250
- ] }),
2251
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
2252
- /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
2253
- s.denied,
2254
- " denied"
2255
- ] })
2256
- ] }),
2257
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2258
- s.active_policies,
2259
- " policies \xB7 ",
2260
- s.registered_tools,
2261
- " tools"
2262
- ] }),
2263
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
2264
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2265
- "Last 24h ",
2266
- ts.data ? `(per ${ts.data.granularity})` : ""
2267
- ] }),
2268
- /* @__PURE__ */ jsxs6(Box6, { children: [
2269
- /* @__PURE__ */ jsx6(Text6, { children: "total " }),
2270
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
2271
- ] }),
2272
- /* @__PURE__ */ jsxs6(Box6, { children: [
2273
- /* @__PURE__ */ jsx6(Text6, { children: "allowed " }),
2274
- /* @__PURE__ */ jsx6(Text6, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
2275
- ] }),
2276
- /* @__PURE__ */ jsxs6(Box6, { children: [
2277
- /* @__PURE__ */ jsx6(Text6, { children: "denied " }),
2278
- /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
2279
- ] })
2280
- ] }),
2281
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
2282
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Recent" }),
2283
- /* @__PURE__ */ jsx6(
2284
- Table,
2285
- {
2286
- columns: [
2287
- { header: "DECISION", width: 9 },
2288
- { header: "TOOL", width: 20 },
2289
- { header: "MS", width: 5 },
2290
- { header: "WHEN", width: 6 }
2291
- ],
2292
- rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
2293
- { value: a.decision, color: decisionColor(a.decision) },
2294
- { value: truncate(a.tool_name, 20), color: theme.accent },
2295
- { value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
2296
- { value: ago(a.created_at), dim: true }
2297
- ])
2298
- }
2299
- )
2300
- ] }),
2301
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
2302
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2303
- "Denial drift ",
2304
- drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
2305
- ] }),
2306
- driftRules.length ? /* @__PURE__ */ jsx6(
2307
- Table,
2308
- {
2309
- columns: [
2310
- { header: "NOW", width: 5 },
2311
- { header: "PREV", width: 5 },
2312
- { header: "\u0394", width: 6 },
2313
- { header: "RULE", width: 22 },
2314
- { header: "REASON", width: 26 }
2315
- ],
2316
- rows: driftRules.map((r) => [
2317
- { value: String(r.current), bold: true },
2318
- { value: String(r.previous), dim: true },
2319
- { value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
2320
- { value: truncate(r.rule_id ?? "\u2014", 22), color: theme.accent },
2321
- { value: truncate(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
2322
- ])
2323
- }
2324
- ) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
2325
- ] })
2326
- ] }) : null });
2327
- }
2328
-
2329
2173
  // src/tui/panels/Audit.tsx
2330
- import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
2174
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2331
2175
  import TextInput4 from "ink-text-input";
2332
2176
  import { useState as useState6 } from "react";
2333
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2177
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2334
2178
  var DECISIONS = [void 0, "DENY", "ALLOW"];
2335
2179
  var SIGNALS = [void 0, "dlp", "ratelimit"];
2336
- var FETCH_LIMIT = 500;
2180
+ var SESS_STATUS = [void 0, "active", "idle", "ended"];
2181
+ var PAGE = 500;
2182
+ var LOCAL_MAX_BYTES = 16 * 1024 * 1024;
2183
+ var cloudRow = (e) => ({
2184
+ id: e.id,
2185
+ at: Date.parse(e.created_at),
2186
+ tool: e.tool_name,
2187
+ decision: e.decision,
2188
+ permission: e.permission,
2189
+ trust: e.trust_level,
2190
+ agent: e.agent_name,
2191
+ session: e.session_id,
2192
+ reason: e.reason,
2193
+ rule: e.matched_rule_id,
2194
+ evalMs: e.evaluation_time_ms,
2195
+ dlp: e.dlp_matches ?? [],
2196
+ burst: !!e.rate_limit_burst,
2197
+ args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
2198
+ });
2199
+ function loadLocalRows() {
2200
+ return parseLocalLines(tailLines(LOCAL_LOG, LOCAL_MAX_BYTES)).map((j, i) => ({
2201
+ id: "l:" + j.at + ":" + i,
2202
+ at: j.at,
2203
+ tool: j.tool ?? "?",
2204
+ decision: j.decision ?? "ALLOW",
2205
+ permission: j.permission ?? "\u2014",
2206
+ trust: j.trust_level ?? "\u2014",
2207
+ agent: j.agent_name ?? null,
2208
+ session: j.session_id ?? null,
2209
+ reason: j.reason ?? null,
2210
+ rule: j.matched_rule_id ?? null,
2211
+ evalMs: j.evaluation_time_ms ?? null,
2212
+ dlp: j.dlp ? Array.isArray(j.dlp) ? j.dlp.map(String) : ["dlp"] : [],
2213
+ burst: !!j.rate_limit_burst,
2214
+ args: j.arguments ? JSON.stringify(j.arguments) : null
2215
+ })).sort((a, b) => b.at - a.at);
2216
+ }
2217
+ var sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
2218
+ var STATUS_DOT = {
2219
+ active: { ch: "\u25CF", color: theme.ok },
2220
+ idle: { ch: "\u25D0", color: theme.warn },
2221
+ ended: { ch: "\u25CB", color: theme.dim }
2222
+ };
2337
2223
  function AuditPanel({ active: active2, focused }) {
2338
2224
  const { cols, rows } = usePanelSize();
2225
+ const [source, setSource] = useState6("cloud");
2226
+ const [view, setView] = useState6("logs");
2339
2227
  const [di, setDi] = useState6(0);
2340
2228
  const [gi, setGi] = useState6(0);
2341
2229
  const [tool, setTool] = useState6("");
2342
2230
  const [agent, setAgent] = useState6("");
2343
2231
  const [search, setSearch] = useState6("");
2232
+ const [sessFilter, setSessFilter] = useState6("");
2233
+ const [page, setPage] = useState6(0);
2344
2234
  const [sel, setSel] = useState6(0);
2345
- const [view, setView] = useState6("list");
2346
2235
  const [detailScroll, setDetailScroll] = useState6(0);
2347
2236
  const [editing, setEditing] = useState6(null);
2237
+ const [si, setSi] = useState6(0);
2238
+ const [sessSearch, setSessSearch] = useState6("");
2239
+ const [sessSel, setSessSel] = useState6(0);
2240
+ const toTop = () => setSel(0);
2241
+ const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
2242
+ const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
2243
+ usePoll(() => {
2244
+ statsQ.reloadQuiet();
2245
+ tsQ.reloadQuiet();
2246
+ }, 15e3, active2 && source === "cloud");
2348
2247
  const query = {
2349
2248
  filter: DECISIONS[di],
2350
2249
  signal: SIGNALS[gi],
2351
2250
  tool: tool || void 0,
2352
2251
  agent_name: agent || void 0,
2353
2252
  search: search || void 0,
2354
- limit: FETCH_LIMIT
2253
+ session_id: sessFilter || void 0,
2254
+ limit: PAGE,
2255
+ offset: page * PAGE
2355
2256
  };
2356
- const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
2357
- usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
2358
- const entries = auditQ.data?.entries ?? [];
2359
- const current = entries[Math.min(sel, Math.max(0, entries.length - 1))];
2257
+ const cloudQ = useLoader(
2258
+ () => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
2259
+ [source, di, gi, tool, agent, search, sessFilter, page]
2260
+ );
2261
+ usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
2262
+ const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
2263
+ usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
2264
+ let pageRows = [];
2265
+ let total = 0;
2266
+ if (source === "cloud") {
2267
+ pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
2268
+ total = cloudQ.data?.total ?? 0;
2269
+ } else {
2270
+ const all = localQ.data ?? [];
2271
+ const q = search.trim().toLowerCase();
2272
+ const filtered = all.filter((r) => {
2273
+ if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
2274
+ if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
2275
+ if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
2276
+ if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
2277
+ if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
2278
+ if (sessFilter && r.session !== sessFilter) return false;
2279
+ if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
2280
+ return true;
2281
+ });
2282
+ total = filtered.length;
2283
+ pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
2284
+ }
2285
+ const pages = Math.max(1, Math.ceil(total / PAGE));
2286
+ const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
2287
+ const agentsQ = useLoader(
2288
+ () => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
2289
+ [source]
2290
+ );
2291
+ usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions");
2292
+ let sessions = [];
2293
+ if (source === "cloud") {
2294
+ sessions = (agentsQ.data?.agents ?? []).map((a) => ({
2295
+ id: a.session_id,
2296
+ agent: a.agent_name ?? a.session_id,
2297
+ status: a.status === "deactivated" ? "ended" : a.status,
2298
+ calls: a.total_calls,
2299
+ denies: a.denied_calls,
2300
+ dlp: a.dlp_events,
2301
+ trust: a.trust_score,
2302
+ lastAt: Date.parse(a.last_seen_at)
2303
+ }));
2304
+ } else {
2305
+ const bySess = /* @__PURE__ */ new Map();
2306
+ for (const r of localQ.data ?? []) {
2307
+ if (!r.session) continue;
2308
+ const cur = bySess.get(r.session) ?? { id: r.session, agent: r.agent ?? "local agent", status: "ended", calls: 0, denies: 0, dlp: 0, trust: null, lastAt: 0 };
2309
+ cur.calls++;
2310
+ if (r.decision !== "ALLOW") cur.denies++;
2311
+ if (r.dlp.length) cur.dlp++;
2312
+ cur.lastAt = Math.max(cur.lastAt, r.at);
2313
+ if (r.agent) cur.agent = r.agent;
2314
+ bySess.set(r.session, cur);
2315
+ }
2316
+ sessions = [...bySess.values()];
2317
+ }
2318
+ const sq = sessSearch.trim().toLowerCase();
2319
+ const sessionsFiltered = sessions.map((s) => ({ ...s, status: source === "cloud" ? s.status : sessStatus2(s.lastAt) })).filter((s) => SESS_STATUS[si] ? s.status === SESS_STATUS[si] : true).filter((s) => !sq || `${s.agent} ${s.id}`.toLowerCase().includes(sq)).sort((a, b) => b.lastAt - a.lastAt);
2320
+ const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
2360
2321
  const sessionQ = useLoader(
2361
- () => view === "detail" && current?.session_id ? api.audit.list({ session_id: current.session_id, limit: 40 }) : Promise.resolve(null),
2362
- [view, current?.session_id]
2322
+ () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
2323
+ [view, current?.session, source]
2363
2324
  );
2364
2325
  useInput5(
2365
2326
  (input, key) => {
2366
2327
  if (view === "detail") {
2367
- if (key.leftArrow || key.escape) setView("list");
2328
+ if (key.leftArrow || key.escape) setView("logs");
2368
2329
  else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
2369
2330
  else if (key.downArrow) setDetailScroll((n) => n + 1);
2370
2331
  else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
2371
2332
  else if (key.pageDown) setDetailScroll((n) => n + 10);
2372
2333
  return;
2373
2334
  }
2374
- const clearSel = () => setSel(0);
2335
+ if (input === "s") {
2336
+ setSource((s) => s === "cloud" ? "local" : "cloud");
2337
+ setPage(0);
2338
+ toTop();
2339
+ setSessSel(0);
2340
+ return;
2341
+ }
2342
+ if (input === "v") {
2343
+ setView((v) => v === "logs" ? "sessions" : "logs");
2344
+ return;
2345
+ }
2346
+ if (view === "sessions") {
2347
+ if (key.upArrow) setSessSel((n) => Math.max(0, n - 1));
2348
+ else if (key.downArrow) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 1));
2349
+ else if (key.pageUp) setSessSel((n) => Math.max(0, n - 10));
2350
+ else if (key.pageDown) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 10));
2351
+ else if (key.return) {
2352
+ if (currentSess) {
2353
+ setSessFilter(currentSess.id);
2354
+ setPage(0);
2355
+ toTop();
2356
+ setView("logs");
2357
+ }
2358
+ } else if (input === "f") {
2359
+ setSi((n) => (n + 1) % SESS_STATUS.length);
2360
+ setSessSel(0);
2361
+ } else if (input === "/") setEditing("sess-search");
2362
+ else if (input === "c") {
2363
+ setSi(0);
2364
+ setSessSearch("");
2365
+ setSessSel(0);
2366
+ }
2367
+ return;
2368
+ }
2375
2369
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2376
- else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
2370
+ else if (key.downArrow) setSel((n) => Math.min(pageRows.length - 1, n + 1));
2377
2371
  else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
2378
- else if (key.pageDown) setSel((n) => Math.min(entries.length - 1, n + 10));
2379
- else if (key.return || key.rightArrow) {
2372
+ else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
2373
+ else if (key.return) {
2380
2374
  if (current) {
2381
2375
  setDetailScroll(0);
2382
2376
  setView("detail");
2383
2377
  }
2378
+ } else if (key.rightArrow) {
2379
+ if (page < pages - 1) {
2380
+ setPage(page + 1);
2381
+ toTop();
2382
+ }
2383
+ } else if (key.leftArrow) {
2384
+ if (page > 0) {
2385
+ setPage(page - 1);
2386
+ toTop();
2387
+ }
2384
2388
  } else if (input === "f") {
2385
2389
  setDi((n) => (n + 1) % DECISIONS.length);
2386
- clearSel();
2390
+ setPage(0);
2391
+ toTop();
2387
2392
  } else if (input === "g") {
2388
2393
  setGi((n) => (n + 1) % SIGNALS.length);
2389
- clearSel();
2394
+ setPage(0);
2395
+ toTop();
2390
2396
  } else if (input === "t") setEditing("tool");
2391
2397
  else if (input === "n") setEditing("agent");
2392
2398
  else if (input === "/") setEditing("search");
@@ -2396,46 +2402,89 @@ function AuditPanel({ active: active2, focused }) {
2396
2402
  setTool("");
2397
2403
  setAgent("");
2398
2404
  setSearch("");
2399
- clearSel();
2405
+ setSessFilter("");
2406
+ setPage(0);
2407
+ toTop();
2400
2408
  }
2401
2409
  },
2402
2410
  { isActive: focused && !editing }
2403
2411
  );
2412
+ const points = tsQ.data?.timeseries ?? [];
2413
+ const localAll = localQ.data ?? [];
2414
+ const strip = source === "cloud" ? /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2415
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
2416
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls \xB7 " }),
2417
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
2418
+ statsQ.data?.allowed ?? "\xB7\xB7\xB7",
2419
+ " allow"
2420
+ ] }),
2421
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
2422
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
2423
+ statsQ.data?.denied ?? "\xB7\xB7",
2424
+ " deny"
2425
+ ] }),
2426
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies \xB7 ${statsQ.data?.registered_tools ?? "\xB7"} tools \xB7 24h ` }),
2427
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 24) }),
2428
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " deny " }),
2429
+ /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 24) })
2430
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2431
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: localAll.length }),
2432
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls in local file \xB7 " }),
2433
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
2434
+ localAll.filter((r) => r.decision === "ALLOW").length,
2435
+ " allow"
2436
+ ] }),
2437
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
2438
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
2439
+ localAll.filter((r) => r.decision !== "ALLOW").length,
2440
+ " deny"
2441
+ ] }),
2442
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2443
+ " \xB7 ",
2444
+ LOCAL_LOG
2445
+ ] })
2446
+ ] });
2447
+ const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
2448
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
2449
+ /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
2450
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
2451
+ ] });
2404
2452
  if (view === "detail" && current) {
2405
- const sessionCalls = sessionQ.data?.entries ?? [];
2453
+ const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
2406
2454
  const bodyW = Math.max(20, cols - 2);
2407
2455
  const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
2408
- const argLines = current.arguments_summary ? wrapLines(prettyJson(JSON.stringify(current.arguments_summary)), bodyW) : ["(no arguments recorded)"];
2409
- const sessionRows = current.session_id ? Math.min(4, sessionCalls.length) + 2 : 1;
2456
+ const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
2457
+ const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
2410
2458
  const reasonShown = reasonLines.slice(0, 4);
2411
2459
  const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
2412
2460
  const maxScroll = Math.max(0, argLines.length - argBudget);
2413
2461
  const off = Math.min(detailScroll, maxScroll);
2414
2462
  const argWin = argLines.slice(off, off + argBudget);
2415
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2416
- /* @__PURE__ */ jsxs7(Box7, { children: [
2417
- /* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
2418
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
2419
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
2463
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2464
+ /* @__PURE__ */ jsxs6(Box6, { children: [
2465
+ /* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
2466
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
2467
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust + " " }),
2468
+ srcChip
2420
2469
  ] }),
2421
- reasonShown.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
2422
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "reason " }),
2423
- /* @__PURE__ */ jsx7(Text7, { children: l.slice("reason: ".length) })
2424
- ] }) : /* @__PURE__ */ jsx7(Text7, { children: " " + l }) }, "r" + i)),
2425
- /* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
2426
- /* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
2427
- /* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
2428
- /* @__PURE__ */ jsx7(Detail, { label: "dlp", value: current.dlp_matches?.length ? current.dlp_matches.join(", ") : "none", color: current.dlp_matches?.length ? theme.bad : void 0 }),
2429
- /* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() + (current.evaluation_time_ms != null ? ` \xB7 eval ${current.evaluation_time_ms}ms` : "") }),
2430
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
2431
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
2432
- current.session_id ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2433
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2470
+ reasonShown.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2471
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "reason " }),
2472
+ /* @__PURE__ */ jsx6(Text6, { children: l.slice("reason: ".length) })
2473
+ ] }) : /* @__PURE__ */ jsx6(Text6, { children: " " + l }) }, "r" + i)),
2474
+ /* @__PURE__ */ jsx6(Detail, { label: "rule", value: current.rule ?? "\u2014" }),
2475
+ /* @__PURE__ */ jsx6(Detail, { label: "agent", value: current.agent ?? "\u2014" }),
2476
+ /* @__PURE__ */ jsx6(Detail, { label: "session", value: current.session ?? "\u2014" }),
2477
+ /* @__PURE__ */ jsx6(Detail, { label: "dlp", value: current.dlp.length ? current.dlp.join(", ") : "none", color: current.dlp.length ? theme.bad : void 0 }),
2478
+ /* @__PURE__ */ jsx6(Detail, { label: "when", value: new Date(current.at).toLocaleString() + (current.evalMs != null ? ` \xB7 eval ${current.evalMs}ms` : "") }),
2479
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
2480
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
2481
+ current.session ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2482
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2434
2483
  "Session \xB7 ",
2435
2484
  sessionCalls.length,
2436
2485
  " calls"
2437
2486
  ] }),
2438
- /* @__PURE__ */ jsx7(
2487
+ /* @__PURE__ */ jsx6(
2439
2488
  Table,
2440
2489
  {
2441
2490
  columns: [
@@ -2446,59 +2495,136 @@ function AuditPanel({ active: active2, focused }) {
2446
2495
  ],
2447
2496
  rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
2448
2497
  { value: e.decision, color: decisionColor(e.decision) },
2449
- { value: truncate(e.tool_name, 20), color: theme.accent },
2498
+ { value: truncate(e.tool, 20), color: theme.accent },
2450
2499
  { value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
2451
- { value: ago(e.created_at), dim: true }
2500
+ { value: ago(e.at), dim: true }
2452
2501
  ])
2453
2502
  }
2454
2503
  )
2455
- ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "(no session id)" }),
2456
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to list" })
2504
+ ] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no session id)" }),
2505
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to logs" })
2457
2506
  ] });
2458
2507
  }
2459
- const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
2460
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2508
+ const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
2509
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2461
2510
  label,
2462
2511
  ":"
2463
2512
  ] }),
2464
- /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
2465
- /* @__PURE__ */ jsx7(Text7, { children: " " })
2513
+ /* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
2514
+ /* @__PURE__ */ jsx6(Text6, { children: " " })
2466
2515
  ] });
2467
- const headerRows = 4 + (editing ? 1 : 0);
2516
+ if (view === "sessions") {
2517
+ const headerRows2 = 5 + (editing ? 1 : 0);
2518
+ const listRows2 = Math.max(4, rows - headerRows2);
2519
+ const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
2520
+ const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
2521
+ const win = sessionsFiltered.slice(start2, start2 + listRows2);
2522
+ const loading2 = source === "cloud" ? agentsQ.loading : localQ.loading;
2523
+ return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2524
+ strip,
2525
+ /* @__PURE__ */ jsxs6(Box6, { children: [
2526
+ srcChip,
2527
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
2528
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 logs (v) " }),
2529
+ chip("status", SESS_STATUS[si] ?? "all", si !== 0),
2530
+ chip("search", sessSearch || "\xB7", !!sessSearch)
2531
+ ] }),
2532
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 f status \xB7 / search \xB7 s source \xB7 v logs \xB7 c clear" : "press \u2192 to browse" }),
2533
+ editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
2534
+ /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
2535
+ /* @__PURE__ */ jsx6(
2536
+ TextInput4,
2537
+ {
2538
+ value: sessSearch,
2539
+ onChange: (v) => {
2540
+ setSessSearch(v);
2541
+ setSessSel(0);
2542
+ },
2543
+ onSubmit: () => setEditing(null)
2544
+ }
2545
+ )
2546
+ ] }) : null,
2547
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${sessionsFiltered.length} sessions \xB7 ${selC + 1}/${sessionsFiltered.length}${start2 ? ` \xB7 \u25B2${start2}` : ""}${start2 + listRows2 < sessionsFiltered.length ? ` \xB7 \u25BC${sessionsFiltered.length - start2 - listRows2}` : ""}` }),
2548
+ /* @__PURE__ */ jsx6(
2549
+ Table,
2550
+ {
2551
+ columns: [
2552
+ { header: "", width: 2 },
2553
+ { header: "STATUS", width: 8 },
2554
+ { header: "AGENT", width: 18 },
2555
+ { header: "SESSION", width: 10 },
2556
+ { header: "CALLS", width: 6 },
2557
+ { header: "DENY", width: 5 },
2558
+ { header: "DLP", width: 4 },
2559
+ { header: "TRUST", width: 5 },
2560
+ { header: "LAST", width: 6 }
2561
+ ],
2562
+ rows: win.map((r, i) => {
2563
+ const st = STATUS_DOT[r.status];
2564
+ const isSel = start2 + i === selC && focused;
2565
+ return [
2566
+ { value: isSel ? "\u25B8" : "", color: theme.accentBright },
2567
+ { value: `${st.ch} ${r.status}`, color: st.color },
2568
+ { value: truncate(r.agent, 18), color: theme.accent, bold: isSel },
2569
+ { value: r.id.slice(0, 8), dim: true },
2570
+ { value: String(r.calls) },
2571
+ { value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
2572
+ { value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
2573
+ { value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
2574
+ { value: ago(r.lastAt), dim: true }
2575
+ ];
2576
+ })
2577
+ }
2578
+ ),
2579
+ sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2580
+ "(no sessions",
2581
+ source === "local" ? " in the local file" : "",
2582
+ ")"
2583
+ ] }) : null
2584
+ ] }) });
2585
+ }
2586
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
2468
2587
  const listRows = Math.max(4, rows - headerRows);
2469
- const selClamped = Math.min(sel, Math.max(0, entries.length - 1));
2470
- const maxStart = Math.max(0, entries.length - listRows);
2588
+ const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
2589
+ const maxStart = Math.max(0, pageRows.length - listRows);
2471
2590
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
2472
- const windowed = entries.slice(start, start + listRows);
2591
+ const windowed = pageRows.slice(start, start + listRows);
2473
2592
  const reasonW = Math.min(60, Math.max(12, cols - 67));
2474
- return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2475
- /* @__PURE__ */ jsxs7(Box7, { children: [
2593
+ const loading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
2594
+ return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2595
+ strip,
2596
+ /* @__PURE__ */ jsxs6(Box6, { children: [
2597
+ srcChip,
2598
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
2599
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 sessions (v) " }),
2476
2600
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
2477
2601
  chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
2478
2602
  chip("tool", tool || "\xB7", !!tool),
2479
2603
  chip("agent", agent || "\xB7", !!agent),
2480
- chip("search", search || "\xB7", !!search)
2604
+ chip("search", search || "\xB7", !!search),
2605
+ sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
2481
2606
  ] }),
2482
- /* @__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" }),
2483
- editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
2484
- /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
2607
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 s source \xB7 v sessions \xB7 c clear" : "press \u2192 to browse" }),
2608
+ editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
2609
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
2485
2610
  editing,
2486
2611
  ": "
2487
2612
  ] }),
2488
- /* @__PURE__ */ jsx7(
2613
+ /* @__PURE__ */ jsx6(
2489
2614
  TextInput4,
2490
2615
  {
2491
2616
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
2492
2617
  onChange: (v) => {
2493
2618
  (editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
2494
- setSel(0);
2619
+ setPage(0);
2620
+ toTop();
2495
2621
  },
2496
2622
  onSubmit: () => setEditing(null)
2497
2623
  }
2498
2624
  )
2499
2625
  ] }) : null,
2500
- /* @__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` : ""}` }),
2501
- /* @__PURE__ */ jsx7(
2626
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
2627
+ /* @__PURE__ */ jsx6(
2502
2628
  Table,
2503
2629
  {
2504
2630
  columns: [
@@ -2512,118 +2638,37 @@ function AuditPanel({ active: active2, focused }) {
2512
2638
  ],
2513
2639
  rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
2514
2640
  }
2515
- )
2641
+ ),
2642
+ pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2643
+ "(no entries",
2644
+ source === "local" ? " in the local file" : "",
2645
+ " \u2014 c clears filters)"
2646
+ ] }) : null
2516
2647
  ] }) });
2517
2648
  }
2518
2649
  function rowFor(e, active2, reasonW) {
2519
2650
  return [
2520
2651
  { value: active2 ? "\u25B8" : "", color: theme.accentBright },
2521
2652
  { value: e.decision, color: decisionColor(e.decision) },
2522
- { value: truncate(e.tool_name, 18), color: theme.accent },
2523
- { value: truncate(e.agent_name ?? "\u2014", 14), dim: true },
2524
- { value: truncate(e.reason ?? "\u2014", reasonW) },
2525
- { 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 },
2526
- { value: ago(e.created_at), dim: true }
2653
+ { value: truncate(e.tool, 18), color: theme.accent },
2654
+ { value: truncate(e.agent ?? "\u2014", 14), dim: true },
2655
+ { value: truncate(e.reason ?? e.args ?? "\u2014", reasonW) },
2656
+ { value: e.dlp.length ? String(e.dlp.length) : "0", color: e.dlp.length ? theme.bad : void 0, dim: !e.dlp.length },
2657
+ { value: ago(e.at), dim: true }
2527
2658
  ];
2528
2659
  }
2529
2660
  function Detail({ label, value, color }) {
2530
- return /* @__PURE__ */ jsxs7(Box7, { children: [
2531
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
2532
- /* @__PURE__ */ jsx7(Text7, { color, wrap: "truncate", children: value })
2661
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
2662
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: label.padEnd(9) }),
2663
+ /* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
2533
2664
  ] });
2534
2665
  }
2535
2666
 
2536
- // src/tui/panels/Agents.tsx
2537
- import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
2538
- import { useState as useState7 } from "react";
2539
- import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2540
- var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
2541
- var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
2542
- function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
2543
- const filled = max > 0 ? Math.round(Math.min(value, max) / max * width) : 0;
2544
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2545
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(9) }),
2546
- /* @__PURE__ */ jsx8(Text8, { color, children: "\u2588".repeat(filled) }),
2547
- /* @__PURE__ */ jsx8(Text8, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
2548
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + value })
2549
- ] });
2550
- }
2551
- function AgentsPanel({ focused }) {
2552
- const live2 = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
2553
- usePoll(live2.reload, 6e3, focused ? false : true);
2554
- const [sel, setSel] = useState7(0);
2555
- const agents = (live2.data?.agents ?? []).filter((a) => a.agent_id);
2556
- const selected = agents[Math.min(sel, Math.max(0, agents.length - 1))];
2557
- const detail = useLoader(() => selected?.agent_id ? api.agents.get(selected.agent_id) : Promise.resolve(null), [selected?.agent_id]);
2558
- useInput6(
2559
- (_input, key) => {
2560
- if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2561
- else if (key.downArrow) setSel((n) => Math.min(agents.length - 1, n + 1));
2562
- },
2563
- { isActive: focused }
2564
- );
2565
- const d = detail.data;
2566
- const base = d?.baseline;
2567
- const radar = base?.radar;
2568
- const anomalies2 = d?.anomalies ?? [];
2569
- const tmap = d?.trust_map;
2570
- const tools = tmap?.tools ?? [];
2571
- const resources = tmap?.resources ?? [];
2572
- const listW = 30;
2573
- return /* @__PURE__ */ jsx8(DataView, { loading: live2.loading && !live2.data, error: live2.error, empty: !!live2.data && agents.length === 0, emptyText: "No identified agents yet.", children: /* @__PURE__ */ jsxs8(Box8, { children: [
2574
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
2575
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
2576
- agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
2577
- /* @__PURE__ */ jsxs8(Text8, { color: statusColor(a.status), children: [
2578
- a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
2579
- " "
2580
- ] }),
2581
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
2582
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
2583
- ] }, a.session_id))
2584
- ] }),
2585
- /* @__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: [
2586
- /* @__PURE__ */ jsxs8(Box8, { children: [
2587
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
2588
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
2589
- ] }),
2590
- base?.characterBlurb ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
2591
- radar ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2592
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
2593
- /* @__PURE__ */ jsx8(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
2594
- /* @__PURE__ */ jsx8(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
2595
- /* @__PURE__ */ jsx8(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
2596
- /* @__PURE__ */ jsx8(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
2597
- /* @__PURE__ */ jsx8(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
2598
- ] }) : null,
2599
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2600
- /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2601
- "Anomalies ",
2602
- anomalies2.length ? `(${anomalies2.length})` : ""
2603
- ] }),
2604
- anomalies2.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2605
- /* @__PURE__ */ jsx8(Text8, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
2606
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: String(an.kind).padEnd(12) }),
2607
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
2608
- ] }, i))
2609
- ] }),
2610
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2611
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Trust map \xB7 top tools" }),
2612
- tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2613
- /* @__PURE__ */ jsx8(Text8, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
2614
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
2615
- ] }, i)),
2616
- resources.slice(0, 3).map((r, i) => /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: r.anomalous ? theme.bad : theme.dim, children: ` ${r.type === "domain" ? "\u{1F310}" : "\u{1F4C1}"} ${truncate(String(r.value), 40)} ${r.count}\xD7` }, "r" + i))
2617
- ] })
2618
- ] }) })
2619
- ] }) });
2620
- }
2621
-
2622
2667
  // src/tui/panels/Settings.tsx
2623
- import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
2668
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
2624
2669
  import TextInput5 from "ink-text-input";
2625
- import { useState as useState8 } from "react";
2626
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2670
+ import { useState as useState7 } from "react";
2671
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2627
2672
  var EVENTS = ["denials", "allowed", "all"];
2628
2673
  function alertBodyFor(channel) {
2629
2674
  const c2 = channel.trim();
@@ -2635,12 +2680,12 @@ function alertBodyFor(channel) {
2635
2680
  function SettingsPanel({ active: active2, focused }) {
2636
2681
  void active2;
2637
2682
  const { cols, rows } = usePanelSize();
2638
- const [sel, setSel] = useState8(0);
2639
- const [editing, setEditing] = useState8(null);
2640
- const [input, setInput] = useState8("");
2641
- const [confirmDel, setConfirmDel] = useState8(null);
2642
- const [msg, setMsg] = useState8(null);
2643
- const [busy, setBusy] = useState8(false);
2683
+ const [sel, setSel] = useState7(0);
2684
+ const [editing, setEditing] = useState7(null);
2685
+ const [input, setInput] = useState7("");
2686
+ const [confirmDel, setConfirmDel] = useState7(null);
2687
+ const [msg, setMsg] = useState7(null);
2688
+ const [busy, setBusy] = useState7(false);
2644
2689
  const localQ = useLoader(() => api.settings.getLocalLogs());
2645
2690
  const whQ = useLoader(() => api.settings.getWebhooks());
2646
2691
  const alertQ = useLoader(() => api.settings.getAlerts());
@@ -2710,7 +2755,7 @@ function SettingsPanel({ active: active2, focused }) {
2710
2755
  run("alert rule added", () => api.settings.createAlert(alertBodyFor(v)), alertQ.reload);
2711
2756
  }
2712
2757
  };
2713
- useInput7(
2758
+ useInput6(
2714
2759
  (inp, key) => {
2715
2760
  setMsg(null);
2716
2761
  if (key.upArrow) {
@@ -2749,7 +2794,7 @@ function SettingsPanel({ active: active2, focused }) {
2749
2794
  const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
2750
2795
  const error = localQ.error ?? whQ.error ?? alertQ.error;
2751
2796
  const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
2752
- const onOff = (on) => on ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "off" });
2797
+ const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
2753
2798
  const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
2754
2799
  const listBudget = Math.max(4, rows - 8);
2755
2800
  const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
@@ -2757,94 +2802,92 @@ function SettingsPanel({ active: active2, focused }) {
2757
2802
  const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
2758
2803
  return i >= start && i < start + listBudget;
2759
2804
  };
2760
- return /* @__PURE__ */ jsx9(DataView, { loading, error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2761
- /* @__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" }),
2762
- editing ? /* @__PURE__ */ jsxs9(Box9, { children: [
2763
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
2764
- /* @__PURE__ */ jsx9(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
2765
- ] }) : msg ? /* @__PURE__ */ jsx9(Text9, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx9(Text9, { children: " " }),
2766
- inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2767
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2805
+ return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2806
+ /* @__PURE__ */ jsx7(Text7, { 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" }),
2807
+ editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
2808
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
2809
+ /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
2810
+ ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
2811
+ inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2812
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
2768
2813
  "LOCAL LOGS ",
2769
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
2814
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
2770
2815
  ] }),
2771
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2772
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
2773
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "enabled ".padEnd(10) }),
2816
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
2817
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
2818
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
2774
2819
  onOff(!!local?.enabled),
2775
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
2820
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
2776
2821
  ] }),
2777
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2778
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
2779
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "path ".padEnd(10) }),
2780
- 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" })
2822
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
2823
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
2824
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
2825
+ local?.path ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "not set \u2014 enter to edit" })
2781
2826
  ] })
2782
2827
  ] }) : null,
2783
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2784
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2828
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2829
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
2785
2830
  "WEBHOOKS ",
2786
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2831
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2787
2832
  "\u2014 POST every matching event to your endpoint (",
2788
2833
  webhooks.length,
2789
2834
  ")"
2790
2835
  ] })
2791
2836
  ] }),
2792
- webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2793
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
2837
+ webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
2838
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
2794
2839
  onOff(wh.enabled),
2795
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
2796
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(wh.url, cols - 16) })
2840
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
2841
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(wh.url, cols - 16) })
2797
2842
  ] }, wh.id)),
2798
- inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs9(Text9, { children: [
2799
- /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
2800
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add webhook (enter)" })
2843
+ inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs7(Text7, { children: [
2844
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
2845
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
2801
2846
  ] }) : null
2802
2847
  ] }),
2803
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2804
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2848
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2849
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
2805
2850
  "ALERTS ",
2806
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2851
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2807
2852
  "\u2014 notify on a signal spike (",
2808
2853
  alerts.length,
2809
2854
  ")"
2810
2855
  ] })
2811
2856
  ] }),
2812
- alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2813
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
2857
+ alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
2858
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
2814
2859
  onOff(rule.enabled),
2815
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
2816
- /* @__PURE__ */ jsx9(Text9, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
2817
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
2860
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
2861
+ /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
2862
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
2818
2863
  ] }, rule.id)),
2819
- inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs9(Text9, { children: [
2820
- /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
2821
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
2864
+ inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs7(Text7, { children: [
2865
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
2866
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
2822
2867
  ] }) : null
2823
2868
  ] })
2824
2869
  ] }) });
2825
2870
  }
2826
2871
 
2827
2872
  // src/tui/App.tsx
2828
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2873
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2829
2874
  function LiveHint() {
2830
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2831
- /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2832
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2833
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2834
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { children: [
2875
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2876
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2877
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2878
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2879
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
2835
2880
  "press ",
2836
- /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
2881
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
2837
2882
  " to go live"
2838
2883
  ] }) })
2839
2884
  ] });
2840
2885
  }
2841
2886
  var SECTIONS = [
2842
2887
  { label: "Live", Panel: LiveHint },
2843
- { label: "Agents", Panel: AgentsPanel },
2844
2888
  { label: "Policies", Panel: PoliciesPanel },
2845
2889
  { label: "Rate Limit", Panel: RateLimitPanel },
2846
2890
  { label: "DLP", Panel: DlpPanel },
2847
- { label: "Stats", Panel: StatsPanel },
2848
2891
  { label: "Audit", Panel: AuditPanel },
2849
2892
  { label: "Settings", Panel: SettingsPanel }
2850
2893
  ];
@@ -2852,22 +2895,22 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
2852
2895
  function Banner({ cols }) {
2853
2896
  const wide = cols >= 82;
2854
2897
  if (!wide) {
2855
- return /* @__PURE__ */ jsxs10(Box10, { children: [
2856
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2857
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \u2014 security control center" })
2898
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
2899
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2900
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
2858
2901
  ] });
2859
2902
  }
2860
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2861
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2862
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2903
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2904
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2905
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2863
2906
  ] });
2864
2907
  }
2865
2908
  function App() {
2866
2909
  const { exit } = useApp();
2867
- const [section, setSection] = useState9(0);
2868
- const [focus, setFocus] = useState9("nav");
2869
- const [help, setHelp] = useState9(false);
2870
- useInput8((input, key) => {
2910
+ const [section, setSection] = useState8(0);
2911
+ const [focus, setFocus] = useState8("nav");
2912
+ const [help, setHelp] = useState8(false);
2913
+ useInput7((input, key) => {
2871
2914
  if (help) {
2872
2915
  setHelp(false);
2873
2916
  return;
@@ -2888,44 +2931,44 @@ function App() {
2888
2931
  });
2889
2932
  const { cols, rows } = useTermSize();
2890
2933
  const current = SECTIONS[section];
2891
- if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
2934
+ if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
2892
2935
  if (current.label === "Live" && focus === "panel") {
2893
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx10(LivePanel, { active: true, focused: true }) });
2936
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
2894
2937
  }
2895
2938
  const Panel = current.Panel;
2896
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
2897
- /* @__PURE__ */ jsx10(Banner, { cols }),
2898
- /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexGrow: 1, children: [
2899
- /* @__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)) }),
2900
- /* @__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" }) })
2939
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
2940
+ /* @__PURE__ */ jsx8(Banner, { cols }),
2941
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
2942
+ /* @__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)) }),
2943
+ /* @__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" }) })
2901
2944
  ] }),
2902
- /* @__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"]] }) })
2945
+ /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
2903
2946
  ] });
2904
2947
  }
2905
2948
  var HELP = [
2906
2949
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
2907
2950
  ["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)"]]],
2908
- ["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["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"]]],
2951
+ ["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["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"], ["s", "save"], ["x", "discard"]]],
2909
2952
  ["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"]]],
2910
- ["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry detail"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
2953
+ ["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
2911
2954
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
2912
2955
  ];
2913
2956
  function HelpOverlay({ cols, rows }) {
2914
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
2915
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
2916
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginBottom: 1, children: [
2917
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accent, children: group }),
2918
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs10(Text10, { children: [
2919
- /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
2920
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: desc })
2957
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
2958
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
2959
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
2960
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
2961
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
2962
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
2963
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
2921
2964
  ] }, k))
2922
2965
  ] }, group)) }),
2923
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
2966
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
2924
2967
  ] });
2925
2968
  }
2926
2969
 
2927
2970
  // src/tui/index.tsx
2928
- import { jsx as jsx11 } from "react/jsx-runtime";
2971
+ import { jsx as jsx9 } from "react/jsx-runtime";
2929
2972
  async function launchTui() {
2930
2973
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
2931
2974
  process.stderr.write(
@@ -2939,7 +2982,7 @@ async function launchTui() {
2939
2982
  }
2940
2983
  process.stdout.write("\x1B[?1049h\x1B[H");
2941
2984
  try {
2942
- const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
2985
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
2943
2986
  await waitUntilExit();
2944
2987
  } finally {
2945
2988
  process.stdout.write("\x1B[?1049l");