@solongate/proxy 0.81.22 → 0.81.24

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,7 +8,7 @@ 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";
11
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
12
12
  import { useState as useState9 } from "react";
13
13
 
14
14
  // src/cli-utils.ts
@@ -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 : {};
@@ -614,8 +649,7 @@ var CONFIG = loadConfig();
614
649
  var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
615
650
  var BG = "#12234f";
616
651
  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");
652
+ var RING = join4(process.cwd(), ".solongate", ".eval-ring.jsonl");
619
653
  var hhmmss = (ts) => {
620
654
  const d = new Date(ts);
621
655
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -625,21 +659,6 @@ var fmtUp = (ms) => {
625
659
  const p = (n) => String(n).padStart(2, "0");
626
660
  return `${p(Math.floor(s / 3600))}:${p(Math.floor(s % 3600 / 60))}:${p(s % 60)}`;
627
661
  };
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
662
  function extractTarget(detail) {
644
663
  try {
645
664
  const j = JSON.parse(detail);
@@ -1229,9 +1248,9 @@ function LivePanel({ active: active2 }) {
1229
1248
  else if (input === "x") toggleSignal("dlp");
1230
1249
  else if (input === "r") toggleSignal("ratelimit");
1231
1250
  else if (input === "e") {
1232
- const file = join3(homedir3(), ".solongate", "live-export.jsonl");
1251
+ const file = join4(homedir4(), ".solongate", "live-export.jsonl");
1233
1252
  try {
1234
- mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
1253
+ mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
1235
1254
  writeFileSync(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1236
1255
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
1237
1256
  } catch (err) {
@@ -1586,11 +1605,13 @@ function ruleSummary(r) {
1586
1605
  function PoliciesPanel({ focused }) {
1587
1606
  const list5 = useLoader(() => api.policies.list());
1588
1607
  const policies = list5.data?.policies ?? [];
1608
+ const activeQ = useLoader(() => api.policies.active());
1609
+ const activeId = activeQ.data?.policy?.id ?? null;
1610
+ const activeBy = activeQ.data?.matched_by;
1589
1611
  const [view, setView] = useState3("list");
1590
1612
  const [pi, setPi] = useState3(0);
1591
1613
  const [ri, setRi] = useState3(0);
1592
1614
  const [fi, setFi] = useState3(0);
1593
- const [vi, setVi] = useState3(0);
1594
1615
  const [rules, setRules] = useState3([]);
1595
1616
  const [mode, setMode] = useState3("denylist");
1596
1617
  const [dirty, setDirty] = useState3(false);
@@ -1599,15 +1620,6 @@ function PoliciesPanel({ focused }) {
1599
1620
  const [status, setStatus] = useState3(null);
1600
1621
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
1601
1622
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
1602
- const versionsQ = useLoader(
1603
- () => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
1604
- [view, selected?.id]
1605
- );
1606
- const diffVer = versionsQ.data?.versions[vi]?.version;
1607
- const diffQ = useLoader(
1608
- () => view === "versions" && selected && diffVer !== void 0 ? api.policies.get(selected.id, diffVer) : Promise.resolve(null),
1609
- [view, selected?.id, diffVer]
1610
- );
1611
1623
  useEffect3(() => {
1612
1624
  if (detail.data) {
1613
1625
  setRules(detail.data.rules);
@@ -1658,6 +1670,20 @@ function PoliciesPanel({ focused }) {
1658
1670
  else if (key.return || key.rightArrow) {
1659
1671
  setStatus(null);
1660
1672
  setView("rules");
1673
+ } else if (input === "a") {
1674
+ if (selected) {
1675
+ setStatus("Activating\u2026");
1676
+ void api.policies.setActive(selected.id).then(() => {
1677
+ setStatus(`\u2713 "${selected.name}" is now the ACTIVE policy (pinned) \xB7 reaches agents in ~30s`);
1678
+ activeQ.reload();
1679
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
1680
+ }
1681
+ } else if (input === "x") {
1682
+ setStatus("Deactivating\u2026");
1683
+ void api.policies.setActive(null).then(() => {
1684
+ setStatus("\u2713 active policy: NONE \u2014 no rules enforced, w/b grants are refused");
1685
+ activeQ.reload();
1686
+ }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
1661
1687
  }
1662
1688
  return;
1663
1689
  }
@@ -1691,32 +1717,10 @@ function PoliciesPanel({ focused }) {
1691
1717
  } else if (input === "D") {
1692
1718
  setStatus("Dry-running\u2026");
1693
1719
  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))));
1694
- } else if (input === "v") {
1695
- setVi(0);
1696
- setView("versions");
1697
1720
  } else if (input === "s") void save();
1698
1721
  else if (input === "x") discard();
1699
1722
  return;
1700
1723
  }
1701
- if (view === "versions") {
1702
- const vers = versionsQ.data?.versions ?? [];
1703
- if (key.leftArrow) return void setView("rules");
1704
- if (key.upArrow) setVi((n) => Math.max(0, n - 1));
1705
- else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
1706
- else if (key.return) {
1707
- const v = vers[vi];
1708
- if (v && selected) {
1709
- setStatus("Rolling back\u2026");
1710
- void api.policies.rollback(selected.id, v.version).then((r) => {
1711
- setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
1712
- detail.reload();
1713
- list5.reload();
1714
- setView("rules");
1715
- }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
1716
- }
1717
- }
1718
- return;
1719
- }
1720
1724
  const rule2 = rules[ri];
1721
1725
  if (!rule2) return setView("rules");
1722
1726
  const field = FIELDS[fi];
@@ -1738,18 +1742,30 @@ function PoliciesPanel({ focused }) {
1738
1742
  { isActive: focused && !editing }
1739
1743
  );
1740
1744
  if (view === "list") {
1745
+ const activeName = activeId ? policies.find((p) => p.id === activeId)?.name ?? activeId : null;
1741
1746
  return /* @__PURE__ */ jsx3(DataView, { loading: list5.loading && !list5.data, error: list5.error, empty: !!list5.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1742
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open" : "press \u2192 to browse" }),
1747
+ /* @__PURE__ */ jsxs3(Box3, { children: [
1748
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "active: " }),
1749
+ activeQ.loading && !activeQ.data ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2026" }) : activeName ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
1750
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.ok, bold: true, children: [
1751
+ "\u25CF ",
1752
+ truncate(activeName, 28)
1753
+ ] }),
1754
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
1755
+ ] }) : /* @__PURE__ */ jsx3(Text3, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
1756
+ ] }),
1757
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter open \xB7 a activate \xB7 x deactivate (none)" : "press \u2192 to browse" }),
1743
1758
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: policies.map((p, i) => /* @__PURE__ */ jsxs3(Text3, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
1744
1759
  (i === pi ? "\u25B8 " : " ") + truncate(p.name, 26).padEnd(27),
1745
1760
  /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
1746
1761
  p.mode.padEnd(10),
1747
1762
  " ",
1748
1763
  p.rules.length,
1749
- " rules \xB7 v",
1750
- p.version
1751
- ] })
1752
- ] }, p.id)) })
1764
+ " rules"
1765
+ ] }),
1766
+ p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
1767
+ ] }, p.id)) }),
1768
+ status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
1753
1769
  ] }) });
1754
1770
  }
1755
1771
  const dirtyTag = dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
@@ -1761,7 +1777,7 @@ function PoliciesPanel({ focused }) {
1761
1777
  /* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
1762
1778
  dirtyTag
1763
1779
  ] }),
1764
- /* @__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" }),
1780
+ /* @__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" }),
1765
1781
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
1766
1782
  Table,
1767
1783
  {
@@ -1787,50 +1803,6 @@ function PoliciesPanel({ focused }) {
1787
1803
  status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
1788
1804
  ] }) });
1789
1805
  }
1790
- if (view === "versions") {
1791
- const vers = versionsQ.data?.versions ?? [];
1792
- 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: [
1793
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
1794
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
1795
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
1796
- Table,
1797
- {
1798
- columns: [
1799
- { header: "", width: 2 },
1800
- { header: "VER", width: 5 },
1801
- { header: "RULES", width: 5 },
1802
- { header: "REASON", width: 34 },
1803
- { header: "WHEN", width: 16 }
1804
- ],
1805
- rows: vers.map((v, i) => [
1806
- { value: i === vi ? "\u25B8" : "", color: theme.accentBright },
1807
- { value: `v${v.version}`, bold: i === vi },
1808
- { value: String(v.rules_count), dim: true },
1809
- { value: truncate(v.reason || "\u2014", 34) },
1810
- { value: truncate(v.created_at, 16), dim: true }
1811
- ])
1812
- }
1813
- ) }),
1814
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
1815
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: `Diff v${diffVer ?? "?"} vs current draft` }),
1816
- (() => {
1817
- const old = diffQ.data?.rules ?? [];
1818
- const cur = rules;
1819
- const oldIds = new Set(old.map((r) => r.id));
1820
- const curIds = new Set(cur.map((r) => r.id));
1821
- const added = cur.filter((r) => !oldIds.has(r.id));
1822
- const removed = old.filter((r) => !curIds.has(r.id));
1823
- if (diffQ.loading && !diffQ.data) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " loading\u2026" });
1824
- if (!added.length && !removed.length) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " identical rule set" });
1825
- return /* @__PURE__ */ jsxs3(Fragment3, { children: [
1826
- 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)),
1827
- 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))
1828
- ] });
1829
- })()
1830
- ] }),
1831
- status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
1832
- ] }) });
1833
- }
1834
1806
  const rule = rules[ri];
1835
1807
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1836
1808
  /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -2192,171 +2164,229 @@ function RegexTest({ re }) {
2192
2164
  ] });
2193
2165
  }
2194
2166
 
2195
- // src/tui/panels/Stats.tsx
2196
- import { Box as Box6, Text as Text6 } from "ink";
2197
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2198
- function StatsPanel({ active: active2 }) {
2199
- const stats = useLoader(() => api.stats.get());
2200
- const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
2201
- const drift2 = useLoader(() => api.stats.drift(7));
2202
- usePoll(() => {
2203
- stats.reload();
2204
- ts.reload();
2205
- }, 5e3, active2);
2206
- usePoll(drift2.reload, 3e4, active2);
2207
- const s = stats.data;
2208
- const points = ts.data?.timeseries ?? [];
2209
- const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
2210
- return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2211
- /* @__PURE__ */ jsxs6(Box6, { children: [
2212
- /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
2213
- s.total_calls,
2214
- " "
2215
- ] }),
2216
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "calls " }),
2217
- /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
2218
- s.allowed,
2219
- " allowed"
2220
- ] }),
2221
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
2222
- /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
2223
- s.denied,
2224
- " denied"
2225
- ] })
2226
- ] }),
2227
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2228
- s.active_policies,
2229
- " policies \xB7 ",
2230
- s.registered_tools,
2231
- " tools"
2232
- ] }),
2233
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
2234
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2235
- "Last 24h ",
2236
- ts.data ? `(per ${ts.data.granularity})` : ""
2237
- ] }),
2238
- /* @__PURE__ */ jsxs6(Box6, { children: [
2239
- /* @__PURE__ */ jsx6(Text6, { children: "total " }),
2240
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
2241
- ] }),
2242
- /* @__PURE__ */ jsxs6(Box6, { children: [
2243
- /* @__PURE__ */ jsx6(Text6, { children: "allowed " }),
2244
- /* @__PURE__ */ jsx6(Text6, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
2245
- ] }),
2246
- /* @__PURE__ */ jsxs6(Box6, { children: [
2247
- /* @__PURE__ */ jsx6(Text6, { children: "denied " }),
2248
- /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
2249
- ] })
2250
- ] }),
2251
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
2252
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Recent" }),
2253
- /* @__PURE__ */ jsx6(
2254
- Table,
2255
- {
2256
- columns: [
2257
- { header: "DECISION", width: 9 },
2258
- { header: "TOOL", width: 20 },
2259
- { header: "MS", width: 5 },
2260
- { header: "WHEN", width: 6 }
2261
- ],
2262
- rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
2263
- { value: a.decision, color: decisionColor(a.decision) },
2264
- { value: truncate(a.tool_name, 20), color: theme.accent },
2265
- { value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
2266
- { value: ago(a.created_at), dim: true }
2267
- ])
2268
- }
2269
- )
2270
- ] }),
2271
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
2272
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2273
- "Denial drift ",
2274
- drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
2275
- ] }),
2276
- driftRules.length ? /* @__PURE__ */ jsx6(
2277
- Table,
2278
- {
2279
- columns: [
2280
- { header: "NOW", width: 5 },
2281
- { header: "PREV", width: 5 },
2282
- { header: "\u0394", width: 6 },
2283
- { header: "RULE", width: 22 },
2284
- { header: "REASON", width: 26 }
2285
- ],
2286
- rows: driftRules.map((r) => [
2287
- { value: String(r.current), bold: true },
2288
- { value: String(r.previous), dim: true },
2289
- { value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
2290
- { value: truncate(r.rule_id ?? "\u2014", 22), color: theme.accent },
2291
- { value: truncate(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
2292
- ])
2293
- }
2294
- ) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
2295
- ] })
2296
- ] }) : null });
2297
- }
2298
-
2299
2167
  // src/tui/panels/Audit.tsx
2300
- import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
2168
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2301
2169
  import TextInput4 from "ink-text-input";
2302
2170
  import { useState as useState6 } from "react";
2303
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2171
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2304
2172
  var DECISIONS = [void 0, "DENY", "ALLOW"];
2305
2173
  var SIGNALS = [void 0, "dlp", "ratelimit"];
2306
- var FETCH_LIMIT = 500;
2174
+ var SESS_STATUS = [void 0, "active", "idle", "ended"];
2175
+ var PAGE = 500;
2176
+ var LOCAL_MAX_BYTES = 16 * 1024 * 1024;
2177
+ var cloudRow = (e) => ({
2178
+ id: e.id,
2179
+ at: Date.parse(e.created_at),
2180
+ tool: e.tool_name,
2181
+ decision: e.decision,
2182
+ permission: e.permission,
2183
+ trust: e.trust_level,
2184
+ agent: e.agent_name,
2185
+ session: e.session_id,
2186
+ reason: e.reason,
2187
+ rule: e.matched_rule_id,
2188
+ evalMs: e.evaluation_time_ms,
2189
+ dlp: e.dlp_matches ?? [],
2190
+ burst: !!e.rate_limit_burst,
2191
+ args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
2192
+ });
2193
+ function loadLocalRows() {
2194
+ return parseLocalLines(tailLines(LOCAL_LOG, LOCAL_MAX_BYTES)).map((j, i) => ({
2195
+ id: "l:" + j.at + ":" + i,
2196
+ at: j.at,
2197
+ tool: j.tool ?? "?",
2198
+ decision: j.decision ?? "ALLOW",
2199
+ permission: j.permission ?? "\u2014",
2200
+ trust: j.trust_level ?? "\u2014",
2201
+ agent: j.agent_name ?? null,
2202
+ session: j.session_id ?? null,
2203
+ reason: j.reason ?? null,
2204
+ rule: j.matched_rule_id ?? null,
2205
+ evalMs: j.evaluation_time_ms ?? null,
2206
+ dlp: j.dlp ? Array.isArray(j.dlp) ? j.dlp.map(String) : ["dlp"] : [],
2207
+ burst: !!j.rate_limit_burst,
2208
+ args: j.arguments ? JSON.stringify(j.arguments) : null
2209
+ })).sort((a, b) => b.at - a.at);
2210
+ }
2211
+ var sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
2212
+ var STATUS_DOT = {
2213
+ active: { ch: "\u25CF", color: theme.ok },
2214
+ idle: { ch: "\u25D0", color: theme.warn },
2215
+ ended: { ch: "\u25CB", color: theme.dim }
2216
+ };
2307
2217
  function AuditPanel({ active: active2, focused }) {
2308
2218
  const { cols, rows } = usePanelSize();
2219
+ const [source, setSource] = useState6("cloud");
2220
+ const [view, setView] = useState6("logs");
2309
2221
  const [di, setDi] = useState6(0);
2310
2222
  const [gi, setGi] = useState6(0);
2311
2223
  const [tool, setTool] = useState6("");
2312
2224
  const [agent, setAgent] = useState6("");
2313
2225
  const [search, setSearch] = useState6("");
2226
+ const [sessFilter, setSessFilter] = useState6("");
2227
+ const [page, setPage] = useState6(0);
2314
2228
  const [sel, setSel] = useState6(0);
2315
- const [view, setView] = useState6("list");
2316
2229
  const [detailScroll, setDetailScroll] = useState6(0);
2317
2230
  const [editing, setEditing] = useState6(null);
2231
+ const [si, setSi] = useState6(0);
2232
+ const [sessSearch, setSessSearch] = useState6("");
2233
+ const [sessSel, setSessSel] = useState6(0);
2234
+ const toTop = () => setSel(0);
2235
+ const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
2236
+ const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
2237
+ usePoll(() => {
2238
+ statsQ.reload();
2239
+ tsQ.reload();
2240
+ }, 15e3, active2 && source === "cloud");
2318
2241
  const query = {
2319
2242
  filter: DECISIONS[di],
2320
2243
  signal: SIGNALS[gi],
2321
2244
  tool: tool || void 0,
2322
2245
  agent_name: agent || void 0,
2323
2246
  search: search || void 0,
2324
- limit: FETCH_LIMIT
2247
+ session_id: sessFilter || void 0,
2248
+ limit: PAGE,
2249
+ offset: page * PAGE
2325
2250
  };
2326
- const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
2327
- usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
2328
- const entries = auditQ.data?.entries ?? [];
2329
- const current = entries[Math.min(sel, Math.max(0, entries.length - 1))];
2251
+ const cloudQ = useLoader(
2252
+ () => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
2253
+ [source, di, gi, tool, agent, search, sessFilter, page]
2254
+ );
2255
+ usePoll(cloudQ.reload, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
2256
+ const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
2257
+ usePoll(localQ.reload, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
2258
+ let pageRows = [];
2259
+ let total = 0;
2260
+ if (source === "cloud") {
2261
+ pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
2262
+ total = cloudQ.data?.total ?? 0;
2263
+ } else {
2264
+ const all = localQ.data ?? [];
2265
+ const q = search.trim().toLowerCase();
2266
+ const filtered = all.filter((r) => {
2267
+ if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
2268
+ if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
2269
+ if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
2270
+ if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
2271
+ if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
2272
+ if (sessFilter && r.session !== sessFilter) return false;
2273
+ if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
2274
+ return true;
2275
+ });
2276
+ total = filtered.length;
2277
+ pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
2278
+ }
2279
+ const pages = Math.max(1, Math.ceil(total / PAGE));
2280
+ const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
2281
+ const agentsQ = useLoader(
2282
+ () => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
2283
+ [source]
2284
+ );
2285
+ usePoll(agentsQ.reload, 8e3, active2 && source === "cloud" && view === "sessions");
2286
+ let sessions = [];
2287
+ if (source === "cloud") {
2288
+ sessions = (agentsQ.data?.agents ?? []).map((a) => ({
2289
+ id: a.session_id,
2290
+ agent: a.agent_name ?? a.session_id,
2291
+ status: a.status === "deactivated" ? "ended" : a.status,
2292
+ calls: a.total_calls,
2293
+ denies: a.denied_calls,
2294
+ dlp: a.dlp_events,
2295
+ trust: a.trust_score,
2296
+ lastAt: Date.parse(a.last_seen_at)
2297
+ }));
2298
+ } else {
2299
+ const bySess = /* @__PURE__ */ new Map();
2300
+ for (const r of localQ.data ?? []) {
2301
+ if (!r.session) continue;
2302
+ 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 };
2303
+ cur.calls++;
2304
+ if (r.decision !== "ALLOW") cur.denies++;
2305
+ if (r.dlp.length) cur.dlp++;
2306
+ cur.lastAt = Math.max(cur.lastAt, r.at);
2307
+ if (r.agent) cur.agent = r.agent;
2308
+ bySess.set(r.session, cur);
2309
+ }
2310
+ sessions = [...bySess.values()];
2311
+ }
2312
+ const sq = sessSearch.trim().toLowerCase();
2313
+ 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);
2314
+ const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
2330
2315
  const sessionQ = useLoader(
2331
- () => view === "detail" && current?.session_id ? api.audit.list({ session_id: current.session_id, limit: 40 }) : Promise.resolve(null),
2332
- [view, current?.session_id]
2316
+ () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
2317
+ [view, current?.session, source]
2333
2318
  );
2334
2319
  useInput5(
2335
2320
  (input, key) => {
2336
2321
  if (view === "detail") {
2337
- if (key.leftArrow || key.escape) setView("list");
2322
+ if (key.leftArrow || key.escape) setView("logs");
2338
2323
  else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
2339
2324
  else if (key.downArrow) setDetailScroll((n) => n + 1);
2340
2325
  else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
2341
2326
  else if (key.pageDown) setDetailScroll((n) => n + 10);
2342
2327
  return;
2343
2328
  }
2344
- const clearSel = () => setSel(0);
2329
+ if (input === "s") {
2330
+ setSource((s) => s === "cloud" ? "local" : "cloud");
2331
+ setPage(0);
2332
+ toTop();
2333
+ setSessSel(0);
2334
+ return;
2335
+ }
2336
+ if (input === "v") {
2337
+ setView((v) => v === "logs" ? "sessions" : "logs");
2338
+ return;
2339
+ }
2340
+ if (view === "sessions") {
2341
+ if (key.upArrow) setSessSel((n) => Math.max(0, n - 1));
2342
+ else if (key.downArrow) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 1));
2343
+ else if (key.pageUp) setSessSel((n) => Math.max(0, n - 10));
2344
+ else if (key.pageDown) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 10));
2345
+ else if (key.return || key.rightArrow) {
2346
+ if (currentSess) {
2347
+ setSessFilter(currentSess.id);
2348
+ setPage(0);
2349
+ toTop();
2350
+ setView("logs");
2351
+ }
2352
+ } else if (input === "f") {
2353
+ setSi((n) => (n + 1) % SESS_STATUS.length);
2354
+ setSessSel(0);
2355
+ } else if (input === "/") setEditing("sess-search");
2356
+ else if (input === "c") {
2357
+ setSi(0);
2358
+ setSessSearch("");
2359
+ setSessSel(0);
2360
+ }
2361
+ return;
2362
+ }
2345
2363
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2346
- else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
2364
+ else if (key.downArrow) setSel((n) => Math.min(pageRows.length - 1, n + 1));
2347
2365
  else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
2348
- else if (key.pageDown) setSel((n) => Math.min(entries.length - 1, n + 10));
2366
+ else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
2349
2367
  else if (key.return || key.rightArrow) {
2350
2368
  if (current) {
2351
2369
  setDetailScroll(0);
2352
2370
  setView("detail");
2353
2371
  }
2372
+ } else if (input === "]") {
2373
+ if (page < pages - 1) {
2374
+ setPage(page + 1);
2375
+ toTop();
2376
+ }
2377
+ } else if (input === "[") {
2378
+ if (page > 0) {
2379
+ setPage(page - 1);
2380
+ toTop();
2381
+ }
2354
2382
  } else if (input === "f") {
2355
2383
  setDi((n) => (n + 1) % DECISIONS.length);
2356
- clearSel();
2384
+ setPage(0);
2385
+ toTop();
2357
2386
  } else if (input === "g") {
2358
2387
  setGi((n) => (n + 1) % SIGNALS.length);
2359
- clearSel();
2388
+ setPage(0);
2389
+ toTop();
2360
2390
  } else if (input === "t") setEditing("tool");
2361
2391
  else if (input === "n") setEditing("agent");
2362
2392
  else if (input === "/") setEditing("search");
@@ -2366,46 +2396,89 @@ function AuditPanel({ active: active2, focused }) {
2366
2396
  setTool("");
2367
2397
  setAgent("");
2368
2398
  setSearch("");
2369
- clearSel();
2399
+ setSessFilter("");
2400
+ setPage(0);
2401
+ toTop();
2370
2402
  }
2371
2403
  },
2372
2404
  { isActive: focused && !editing }
2373
2405
  );
2406
+ const points = tsQ.data?.timeseries ?? [];
2407
+ const localAll = localQ.data ?? [];
2408
+ const strip = source === "cloud" ? /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2409
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
2410
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls \xB7 " }),
2411
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
2412
+ statsQ.data?.allowed ?? "\xB7\xB7\xB7",
2413
+ " allow"
2414
+ ] }),
2415
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
2416
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
2417
+ statsQ.data?.denied ?? "\xB7\xB7",
2418
+ " deny"
2419
+ ] }),
2420
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies \xB7 ${statsQ.data?.registered_tools ?? "\xB7"} tools \xB7 24h ` }),
2421
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 24) }),
2422
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " deny " }),
2423
+ /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 24) })
2424
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2425
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: localAll.length }),
2426
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls in local file \xB7 " }),
2427
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
2428
+ localAll.filter((r) => r.decision === "ALLOW").length,
2429
+ " allow"
2430
+ ] }),
2431
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
2432
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
2433
+ localAll.filter((r) => r.decision !== "ALLOW").length,
2434
+ " deny"
2435
+ ] }),
2436
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2437
+ " \xB7 ",
2438
+ LOCAL_LOG
2439
+ ] })
2440
+ ] });
2441
+ const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
2442
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
2443
+ /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
2444
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
2445
+ ] });
2374
2446
  if (view === "detail" && current) {
2375
- const sessionCalls = sessionQ.data?.entries ?? [];
2447
+ const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
2376
2448
  const bodyW = Math.max(20, cols - 2);
2377
2449
  const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
2378
- const argLines = current.arguments_summary ? wrapLines(prettyJson(JSON.stringify(current.arguments_summary)), bodyW) : ["(no arguments recorded)"];
2379
- const sessionRows = current.session_id ? Math.min(4, sessionCalls.length) + 2 : 1;
2450
+ const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
2451
+ const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
2380
2452
  const reasonShown = reasonLines.slice(0, 4);
2381
2453
  const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
2382
2454
  const maxScroll = Math.max(0, argLines.length - argBudget);
2383
2455
  const off = Math.min(detailScroll, maxScroll);
2384
2456
  const argWin = argLines.slice(off, off + argBudget);
2385
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2386
- /* @__PURE__ */ jsxs7(Box7, { children: [
2387
- /* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
2388
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
2389
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
2457
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2458
+ /* @__PURE__ */ jsxs6(Box6, { children: [
2459
+ /* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
2460
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
2461
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust + " " }),
2462
+ srcChip
2390
2463
  ] }),
2391
- reasonShown.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
2392
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "reason " }),
2393
- /* @__PURE__ */ jsx7(Text7, { children: l.slice("reason: ".length) })
2394
- ] }) : /* @__PURE__ */ jsx7(Text7, { children: " " + l }) }, "r" + i)),
2395
- /* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
2396
- /* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
2397
- /* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
2398
- /* @__PURE__ */ jsx7(Detail, { label: "dlp", value: current.dlp_matches?.length ? current.dlp_matches.join(", ") : "none", color: current.dlp_matches?.length ? theme.bad : void 0 }),
2399
- /* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() + (current.evaluation_time_ms != null ? ` \xB7 eval ${current.evaluation_time_ms}ms` : "") }),
2400
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
2401
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
2402
- current.session_id ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2403
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2464
+ reasonShown.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2465
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "reason " }),
2466
+ /* @__PURE__ */ jsx6(Text6, { children: l.slice("reason: ".length) })
2467
+ ] }) : /* @__PURE__ */ jsx6(Text6, { children: " " + l }) }, "r" + i)),
2468
+ /* @__PURE__ */ jsx6(Detail, { label: "rule", value: current.rule ?? "\u2014" }),
2469
+ /* @__PURE__ */ jsx6(Detail, { label: "agent", value: current.agent ?? "\u2014" }),
2470
+ /* @__PURE__ */ jsx6(Detail, { label: "session", value: current.session ?? "\u2014" }),
2471
+ /* @__PURE__ */ jsx6(Detail, { label: "dlp", value: current.dlp.length ? current.dlp.join(", ") : "none", color: current.dlp.length ? theme.bad : void 0 }),
2472
+ /* @__PURE__ */ jsx6(Detail, { label: "when", value: new Date(current.at).toLocaleString() + (current.evalMs != null ? ` \xB7 eval ${current.evalMs}ms` : "") }),
2473
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
2474
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
2475
+ current.session ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2476
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2404
2477
  "Session \xB7 ",
2405
2478
  sessionCalls.length,
2406
2479
  " calls"
2407
2480
  ] }),
2408
- /* @__PURE__ */ jsx7(
2481
+ /* @__PURE__ */ jsx6(
2409
2482
  Table,
2410
2483
  {
2411
2484
  columns: [
@@ -2416,59 +2489,136 @@ function AuditPanel({ active: active2, focused }) {
2416
2489
  ],
2417
2490
  rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
2418
2491
  { value: e.decision, color: decisionColor(e.decision) },
2419
- { value: truncate(e.tool_name, 20), color: theme.accent },
2492
+ { value: truncate(e.tool, 20), color: theme.accent },
2420
2493
  { value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
2421
- { value: ago(e.created_at), dim: true }
2494
+ { value: ago(e.at), dim: true }
2422
2495
  ])
2423
2496
  }
2424
2497
  )
2425
- ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "(no session id)" }),
2426
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to list" })
2498
+ ] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no session id)" }),
2499
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to logs" })
2427
2500
  ] });
2428
2501
  }
2429
- const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
2430
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2502
+ const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
2503
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2431
2504
  label,
2432
2505
  ":"
2433
2506
  ] }),
2434
- /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
2435
- /* @__PURE__ */ jsx7(Text7, { children: " " })
2507
+ /* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
2508
+ /* @__PURE__ */ jsx6(Text6, { children: " " })
2436
2509
  ] });
2437
- const headerRows = 4 + (editing ? 1 : 0);
2510
+ if (view === "sessions") {
2511
+ const headerRows2 = 5 + (editing ? 1 : 0);
2512
+ const listRows2 = Math.max(4, rows - headerRows2);
2513
+ const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
2514
+ const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
2515
+ const win = sessionsFiltered.slice(start2, start2 + listRows2);
2516
+ const loading2 = source === "cloud" ? agentsQ.loading && !agentsQ.data : localQ.loading && !localQ.data;
2517
+ return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2518
+ strip,
2519
+ /* @__PURE__ */ jsxs6(Box6, { children: [
2520
+ srcChip,
2521
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
2522
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 logs (v) " }),
2523
+ chip("status", SESS_STATUS[si] ?? "all", si !== 0),
2524
+ chip("search", sessSearch || "\xB7", !!sessSearch)
2525
+ ] }),
2526
+ /* @__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" }),
2527
+ editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
2528
+ /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
2529
+ /* @__PURE__ */ jsx6(
2530
+ TextInput4,
2531
+ {
2532
+ value: sessSearch,
2533
+ onChange: (v) => {
2534
+ setSessSearch(v);
2535
+ setSessSel(0);
2536
+ },
2537
+ onSubmit: () => setEditing(null)
2538
+ }
2539
+ )
2540
+ ] }) : null,
2541
+ /* @__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}` : ""}` }),
2542
+ /* @__PURE__ */ jsx6(
2543
+ Table,
2544
+ {
2545
+ columns: [
2546
+ { header: "", width: 2 },
2547
+ { header: "STATUS", width: 8 },
2548
+ { header: "AGENT", width: 18 },
2549
+ { header: "SESSION", width: 10 },
2550
+ { header: "CALLS", width: 6 },
2551
+ { header: "DENY", width: 5 },
2552
+ { header: "DLP", width: 4 },
2553
+ { header: "TRUST", width: 5 },
2554
+ { header: "LAST", width: 6 }
2555
+ ],
2556
+ rows: win.map((r, i) => {
2557
+ const st = STATUS_DOT[r.status];
2558
+ const isSel = start2 + i === selC && focused;
2559
+ return [
2560
+ { value: isSel ? "\u25B8" : "", color: theme.accentBright },
2561
+ { value: `${st.ch} ${r.status}`, color: st.color },
2562
+ { value: truncate(r.agent, 18), color: theme.accent, bold: isSel },
2563
+ { value: r.id.slice(0, 8), dim: true },
2564
+ { value: String(r.calls) },
2565
+ { value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
2566
+ { value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
2567
+ { value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
2568
+ { value: ago(r.lastAt), dim: true }
2569
+ ];
2570
+ })
2571
+ }
2572
+ ),
2573
+ sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2574
+ "(no sessions",
2575
+ source === "local" ? " in the local file" : "",
2576
+ ")"
2577
+ ] }) : null
2578
+ ] }) });
2579
+ }
2580
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
2438
2581
  const listRows = Math.max(4, rows - headerRows);
2439
- const selClamped = Math.min(sel, Math.max(0, entries.length - 1));
2440
- const maxStart = Math.max(0, entries.length - listRows);
2582
+ const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
2583
+ const maxStart = Math.max(0, pageRows.length - listRows);
2441
2584
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
2442
- const windowed = entries.slice(start, start + listRows);
2585
+ const windowed = pageRows.slice(start, start + listRows);
2443
2586
  const reasonW = Math.min(60, Math.max(12, cols - 67));
2444
- return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
2445
- /* @__PURE__ */ jsxs7(Box7, { children: [
2587
+ const loading = source === "cloud" ? cloudQ.loading && !cloudQ.data : localQ.loading && !localQ.data;
2588
+ return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2589
+ strip,
2590
+ /* @__PURE__ */ jsxs6(Box6, { children: [
2591
+ srcChip,
2592
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
2593
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 sessions (v) " }),
2446
2594
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
2447
2595
  chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
2448
2596
  chip("tool", tool || "\xB7", !!tool),
2449
2597
  chip("agent", agent || "\xB7", !!agent),
2450
- chip("search", search || "\xB7", !!search)
2598
+ chip("search", search || "\xB7", !!search),
2599
+ sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
2451
2600
  ] }),
2452
- /* @__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" }),
2453
- editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
2454
- /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
2601
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 [ ] 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" }),
2602
+ editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
2603
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
2455
2604
  editing,
2456
2605
  ": "
2457
2606
  ] }),
2458
- /* @__PURE__ */ jsx7(
2607
+ /* @__PURE__ */ jsx6(
2459
2608
  TextInput4,
2460
2609
  {
2461
2610
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
2462
2611
  onChange: (v) => {
2463
2612
  (editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
2464
- setSel(0);
2613
+ setPage(0);
2614
+ toTop();
2465
2615
  },
2466
2616
  onSubmit: () => setEditing(null)
2467
2617
  }
2468
2618
  )
2469
2619
  ] }) : null,
2470
- /* @__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` : ""}` }),
2471
- /* @__PURE__ */ jsx7(
2620
+ /* @__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` : ""}` }),
2621
+ /* @__PURE__ */ jsx6(
2472
2622
  Table,
2473
2623
  {
2474
2624
  columns: [
@@ -2482,40 +2632,45 @@ function AuditPanel({ active: active2, focused }) {
2482
2632
  ],
2483
2633
  rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
2484
2634
  }
2485
- )
2635
+ ),
2636
+ pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2637
+ "(no entries",
2638
+ source === "local" ? " in the local file" : "",
2639
+ " \u2014 c clears filters)"
2640
+ ] }) : null
2486
2641
  ] }) });
2487
2642
  }
2488
2643
  function rowFor(e, active2, reasonW) {
2489
2644
  return [
2490
2645
  { value: active2 ? "\u25B8" : "", color: theme.accentBright },
2491
2646
  { value: e.decision, color: decisionColor(e.decision) },
2492
- { value: truncate(e.tool_name, 18), color: theme.accent },
2493
- { value: truncate(e.agent_name ?? "\u2014", 14), dim: true },
2494
- { value: truncate(e.reason ?? "\u2014", reasonW) },
2495
- { 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 },
2496
- { value: ago(e.created_at), dim: true }
2647
+ { value: truncate(e.tool, 18), color: theme.accent },
2648
+ { value: truncate(e.agent ?? "\u2014", 14), dim: true },
2649
+ { value: truncate(e.reason ?? e.args ?? "\u2014", reasonW) },
2650
+ { value: e.dlp.length ? String(e.dlp.length) : "0", color: e.dlp.length ? theme.bad : void 0, dim: !e.dlp.length },
2651
+ { value: ago(e.at), dim: true }
2497
2652
  ];
2498
2653
  }
2499
2654
  function Detail({ label, value, color }) {
2500
- return /* @__PURE__ */ jsxs7(Box7, { children: [
2501
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
2502
- /* @__PURE__ */ jsx7(Text7, { color, wrap: "truncate", children: value })
2655
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
2656
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: label.padEnd(9) }),
2657
+ /* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
2503
2658
  ] });
2504
2659
  }
2505
2660
 
2506
2661
  // src/tui/panels/Agents.tsx
2507
- import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
2662
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
2508
2663
  import { useState as useState7 } from "react";
2509
- import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2664
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2510
2665
  var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
2511
2666
  var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
2512
2667
  function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
2513
2668
  const filled = max > 0 ? Math.round(Math.min(value, max) / max * width) : 0;
2514
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2515
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(9) }),
2516
- /* @__PURE__ */ jsx8(Text8, { color, children: "\u2588".repeat(filled) }),
2517
- /* @__PURE__ */ jsx8(Text8, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
2518
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + value })
2669
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
2670
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
2671
+ /* @__PURE__ */ jsx7(Text7, { color, children: "\u2588".repeat(filled) }),
2672
+ /* @__PURE__ */ jsx7(Text7, { color: "#233457", children: "\u2591".repeat(Math.max(0, width - filled)) }),
2673
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + value })
2519
2674
  ] });
2520
2675
  }
2521
2676
  function AgentsPanel({ focused }) {
@@ -2540,60 +2695,60 @@ function AgentsPanel({ focused }) {
2540
2695
  const tools = tmap?.tools ?? [];
2541
2696
  const resources = tmap?.resources ?? [];
2542
2697
  const listW = 30;
2543
- 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: [
2544
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
2545
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
2546
- agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
2547
- /* @__PURE__ */ jsxs8(Text8, { color: statusColor(a.status), children: [
2698
+ return /* @__PURE__ */ jsx7(DataView, { loading: live2.loading && !live2.data, error: live2.error, empty: !!live2.data && agents.length === 0, emptyText: "No identified agents yet.", children: /* @__PURE__ */ jsxs7(Box7, { children: [
2699
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
2700
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
2701
+ agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
2702
+ /* @__PURE__ */ jsxs7(Text7, { color: statusColor(a.status), children: [
2548
2703
  a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
2549
2704
  " "
2550
2705
  ] }),
2551
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
2552
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
2706
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
2707
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `t${a.trust_score}` })
2553
2708
  ] }, a.session_id))
2554
2709
  ] }),
2555
- /* @__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: [
2556
- /* @__PURE__ */ jsxs8(Box8, { children: [
2557
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
2558
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
2710
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: !selected ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "select an agent" }) : detail.loading && !d ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading profile\u2026" }) : !d ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "no profile (agent has no calls yet)" }) : /* @__PURE__ */ jsxs7(Fragment5, { children: [
2711
+ /* @__PURE__ */ jsxs7(Box7, { children: [
2712
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: truncate(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
2713
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
2559
2714
  ] }),
2560
- base?.characterBlurb ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
2561
- radar ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2562
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
2563
- /* @__PURE__ */ jsx8(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
2564
- /* @__PURE__ */ jsx8(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
2565
- /* @__PURE__ */ jsx8(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
2566
- /* @__PURE__ */ jsx8(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
2567
- /* @__PURE__ */ jsx8(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
2715
+ base?.characterBlurb ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate(String(base.characterBlurb), 70) }) : null,
2716
+ radar ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2717
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
2718
+ /* @__PURE__ */ jsx7(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
2719
+ /* @__PURE__ */ jsx7(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
2720
+ /* @__PURE__ */ jsx7(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
2721
+ /* @__PURE__ */ jsx7(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
2722
+ /* @__PURE__ */ jsx7(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
2568
2723
  ] }) : null,
2569
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2570
- /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2724
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2725
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2571
2726
  "Anomalies ",
2572
2727
  anomalies2.length ? `(${anomalies2.length})` : ""
2573
2728
  ] }),
2574
- anomalies2.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2575
- /* @__PURE__ */ jsx8(Text8, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
2576
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: String(an.kind).padEnd(12) }),
2577
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
2729
+ anomalies2.length === 0 ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
2730
+ /* @__PURE__ */ jsx7(Text7, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
2731
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: String(an.kind).padEnd(12) }),
2732
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate(String(an.description ?? ""), 44) })
2578
2733
  ] }, i))
2579
2734
  ] }),
2580
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2581
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Trust map \xB7 top tools" }),
2582
- tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2583
- /* @__PURE__ */ jsx8(Text8, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
2584
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
2735
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2736
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Trust map \xB7 top tools" }),
2737
+ tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
2738
+ /* @__PURE__ */ jsx7(Text7, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate(String(t.name), 16).padEnd(17)}` }),
2739
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
2585
2740
  ] }, i)),
2586
- 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))
2741
+ resources.slice(0, 3).map((r, i) => /* @__PURE__ */ jsx7(Text7, { 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))
2587
2742
  ] })
2588
2743
  ] }) })
2589
2744
  ] }) });
2590
2745
  }
2591
2746
 
2592
2747
  // src/tui/panels/Settings.tsx
2593
- import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
2748
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
2594
2749
  import TextInput5 from "ink-text-input";
2595
2750
  import { useState as useState8 } from "react";
2596
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2751
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2597
2752
  var EVENTS = ["denials", "allowed", "all"];
2598
2753
  function alertBodyFor(channel) {
2599
2754
  const c2 = channel.trim();
@@ -2719,7 +2874,7 @@ function SettingsPanel({ active: active2, focused }) {
2719
2874
  const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
2720
2875
  const error = localQ.error ?? whQ.error ?? alertQ.error;
2721
2876
  const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
2722
- const onOff = (on) => on ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "off" });
2877
+ const onOff = (on) => on ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "off" });
2723
2878
  const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
2724
2879
  const listBudget = Math.max(4, rows - 8);
2725
2880
  const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
@@ -2727,83 +2882,83 @@ function SettingsPanel({ active: active2, focused }) {
2727
2882
  const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
2728
2883
  return i >= start && i < start + listBudget;
2729
2884
  };
2730
- return /* @__PURE__ */ jsx9(DataView, { loading, error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2731
- /* @__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" }),
2732
- editing ? /* @__PURE__ */ jsxs9(Box9, { children: [
2733
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
2734
- /* @__PURE__ */ jsx9(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
2735
- ] }) : msg ? /* @__PURE__ */ jsx9(Text9, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx9(Text9, { children: " " }),
2736
- inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2737
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2885
+ return /* @__PURE__ */ jsx8(DataView, { loading, error, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
2886
+ /* @__PURE__ */ jsx8(Text8, { 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" }),
2887
+ editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
2888
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
2889
+ /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
2890
+ ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
2891
+ inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2892
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
2738
2893
  "LOCAL LOGS ",
2739
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
2894
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
2740
2895
  ] }),
2741
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2742
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
2743
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "enabled ".padEnd(10) }),
2896
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2897
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
2898
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "enabled ".padEnd(10) }),
2744
2899
  onOff(!!local?.enabled),
2745
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
2900
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " enter toggles" })
2746
2901
  ] }),
2747
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2748
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
2749
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "path ".padEnd(10) }),
2750
- 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" })
2902
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2903
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
2904
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "path ".padEnd(10) }),
2905
+ local?.path ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
2751
2906
  ] })
2752
2907
  ] }) : null,
2753
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2754
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2908
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2909
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
2755
2910
  "WEBHOOKS ",
2756
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2911
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2757
2912
  "\u2014 POST every matching event to your endpoint (",
2758
2913
  webhooks.length,
2759
2914
  ")"
2760
2915
  ] })
2761
2916
  ] }),
2762
- webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2763
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
2917
+ webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2918
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
2764
2919
  onOff(wh.enabled),
2765
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
2766
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(wh.url, cols - 16) })
2920
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
2921
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(wh.url, cols - 16) })
2767
2922
  ] }, wh.id)),
2768
- inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs9(Text9, { children: [
2769
- /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
2770
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add webhook (enter)" })
2923
+ inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs8(Text8, { children: [
2924
+ /* @__PURE__ */ jsx8(Text8, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
2925
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add webhook (enter)" })
2771
2926
  ] }) : null
2772
2927
  ] }),
2773
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2774
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
2928
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2929
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
2775
2930
  "ALERTS ",
2776
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2931
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2777
2932
  "\u2014 notify on a signal spike (",
2778
2933
  alerts.length,
2779
2934
  ")"
2780
2935
  ] })
2781
2936
  ] }),
2782
- alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
2783
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
2937
+ alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
2938
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
2784
2939
  onOff(rule.enabled),
2785
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
2786
- /* @__PURE__ */ jsx9(Text9, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
2787
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
2940
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
2941
+ /* @__PURE__ */ jsx8(Text8, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
2942
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
2788
2943
  ] }, rule.id)),
2789
- inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs9(Text9, { children: [
2790
- /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
2791
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
2944
+ inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs8(Text8, { children: [
2945
+ /* @__PURE__ */ jsx8(Text8, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
2946
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
2792
2947
  ] }) : null
2793
2948
  ] })
2794
2949
  ] }) });
2795
2950
  }
2796
2951
 
2797
2952
  // src/tui/App.tsx
2798
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2953
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2799
2954
  function LiveHint() {
2800
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2801
- /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2802
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2803
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2804
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { children: [
2955
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2956
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
2957
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
2958
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
2959
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
2805
2960
  "press ",
2806
- /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
2961
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
2807
2962
  " to go live"
2808
2963
  ] }) })
2809
2964
  ] });
@@ -2814,7 +2969,6 @@ var SECTIONS = [
2814
2969
  { label: "Policies", Panel: PoliciesPanel },
2815
2970
  { label: "Rate Limit", Panel: RateLimitPanel },
2816
2971
  { label: "DLP", Panel: DlpPanel },
2817
- { label: "Stats", Panel: StatsPanel },
2818
2972
  { label: "Audit", Panel: AuditPanel },
2819
2973
  { label: "Settings", Panel: SettingsPanel }
2820
2974
  ];
@@ -2822,14 +2976,14 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
2822
2976
  function Banner({ cols }) {
2823
2977
  const wide = cols >= 82;
2824
2978
  if (!wide) {
2825
- return /* @__PURE__ */ jsxs10(Box10, { children: [
2826
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2827
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \u2014 security control center" })
2979
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
2980
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
2981
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
2828
2982
  ] });
2829
2983
  }
2830
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
2831
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2832
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2984
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2985
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
2986
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
2833
2987
  ] });
2834
2988
  }
2835
2989
  function App() {
@@ -2858,44 +3012,44 @@ function App() {
2858
3012
  });
2859
3013
  const { cols, rows } = useTermSize();
2860
3014
  const current = SECTIONS[section];
2861
- if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
3015
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
2862
3016
  if (current.label === "Live" && focus === "panel") {
2863
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx10(LivePanel, { active: true, focused: true }) });
3017
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }) });
2864
3018
  }
2865
3019
  const Panel = current.Panel;
2866
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
2867
- /* @__PURE__ */ jsx10(Banner, { cols }),
2868
- /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexGrow: 1, children: [
2869
- /* @__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)) }),
2870
- /* @__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" }) })
3020
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3021
+ /* @__PURE__ */ jsx9(Banner, { cols }),
3022
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
3023
+ /* @__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)) }),
3024
+ /* @__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" }) })
2871
3025
  ] }),
2872
- /* @__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"]] }) })
3026
+ /* @__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"]] }) })
2873
3027
  ] });
2874
3028
  }
2875
3029
  var HELP = [
2876
3030
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
2877
3031
  ["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)"]]],
2878
- ["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"]]],
3032
+ ["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"]]],
2879
3033
  ["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"]]],
2880
- ["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry detail"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
3034
+ ["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["[ / ]", "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"]]],
2881
3035
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
2882
3036
  ];
2883
3037
  function HelpOverlay({ cols, rows }) {
2884
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
2885
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
2886
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginBottom: 1, children: [
2887
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accent, children: group }),
2888
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs10(Text10, { children: [
2889
- /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
2890
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: desc })
3038
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3039
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3040
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
3041
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
3042
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
3043
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3044
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
2891
3045
  ] }, k))
2892
3046
  ] }, group)) }),
2893
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
3047
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
2894
3048
  ] });
2895
3049
  }
2896
3050
 
2897
3051
  // src/tui/index.tsx
2898
- import { jsx as jsx11 } from "react/jsx-runtime";
3052
+ import { jsx as jsx10 } from "react/jsx-runtime";
2899
3053
  async function launchTui() {
2900
3054
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
2901
3055
  process.stderr.write(
@@ -2909,7 +3063,7 @@ async function launchTui() {
2909
3063
  }
2910
3064
  process.stdout.write("\x1B[?1049h\x1B[H");
2911
3065
  try {
2912
- const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
3066
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
2913
3067
  await waitUntilExit();
2914
3068
  } finally {
2915
3069
  process.stdout.write("\x1B[?1049l");