@solongate/proxy 0.81.23 → 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) {
@@ -1593,7 +1612,6 @@ function PoliciesPanel({ focused }) {
1593
1612
  const [pi, setPi] = useState3(0);
1594
1613
  const [ri, setRi] = useState3(0);
1595
1614
  const [fi, setFi] = useState3(0);
1596
- const [vi, setVi] = useState3(0);
1597
1615
  const [rules, setRules] = useState3([]);
1598
1616
  const [mode, setMode] = useState3("denylist");
1599
1617
  const [dirty, setDirty] = useState3(false);
@@ -1602,15 +1620,6 @@ function PoliciesPanel({ focused }) {
1602
1620
  const [status, setStatus] = useState3(null);
1603
1621
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
1604
1622
  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
1623
  useEffect3(() => {
1615
1624
  if (detail.data) {
1616
1625
  setRules(detail.data.rules);
@@ -1708,32 +1717,10 @@ function PoliciesPanel({ focused }) {
1708
1717
  } else if (input === "D") {
1709
1718
  setStatus("Dry-running\u2026");
1710
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))));
1711
- } else if (input === "v") {
1712
- setVi(0);
1713
- setView("versions");
1714
1720
  } else if (input === "s") void save();
1715
1721
  else if (input === "x") discard();
1716
1722
  return;
1717
1723
  }
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
1724
  const rule2 = rules[ri];
1738
1725
  if (!rule2) return setView("rules");
1739
1726
  const field = FIELDS[fi];
@@ -1774,8 +1761,7 @@ function PoliciesPanel({ focused }) {
1774
1761
  p.mode.padEnd(10),
1775
1762
  " ",
1776
1763
  p.rules.length,
1777
- " rules \xB7 v",
1778
- p.version
1764
+ " rules"
1779
1765
  ] }),
1780
1766
  p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
1781
1767
  ] }, p.id)) }),
@@ -1791,7 +1777,7 @@ function PoliciesPanel({ focused }) {
1791
1777
  /* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
1792
1778
  dirtyTag
1793
1779
  ] }),
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" }),
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" }),
1795
1781
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
1796
1782
  Table,
1797
1783
  {
@@ -1817,50 +1803,6 @@ function PoliciesPanel({ focused }) {
1817
1803
  status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
1818
1804
  ] }) });
1819
1805
  }
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
1806
  const rule = rules[ri];
1865
1807
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
1866
1808
  /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -2222,171 +2164,229 @@ function RegexTest({ re }) {
2222
2164
  ] });
2223
2165
  }
2224
2166
 
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
2167
  // src/tui/panels/Audit.tsx
2330
- 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";
2331
2169
  import TextInput4 from "ink-text-input";
2332
2170
  import { useState as useState6 } from "react";
2333
- 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";
2334
2172
  var DECISIONS = [void 0, "DENY", "ALLOW"];
2335
2173
  var SIGNALS = [void 0, "dlp", "ratelimit"];
2336
- 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
+ };
2337
2217
  function AuditPanel({ active: active2, focused }) {
2338
2218
  const { cols, rows } = usePanelSize();
2219
+ const [source, setSource] = useState6("cloud");
2220
+ const [view, setView] = useState6("logs");
2339
2221
  const [di, setDi] = useState6(0);
2340
2222
  const [gi, setGi] = useState6(0);
2341
2223
  const [tool, setTool] = useState6("");
2342
2224
  const [agent, setAgent] = useState6("");
2343
2225
  const [search, setSearch] = useState6("");
2226
+ const [sessFilter, setSessFilter] = useState6("");
2227
+ const [page, setPage] = useState6(0);
2344
2228
  const [sel, setSel] = useState6(0);
2345
- const [view, setView] = useState6("list");
2346
2229
  const [detailScroll, setDetailScroll] = useState6(0);
2347
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");
2348
2241
  const query = {
2349
2242
  filter: DECISIONS[di],
2350
2243
  signal: SIGNALS[gi],
2351
2244
  tool: tool || void 0,
2352
2245
  agent_name: agent || void 0,
2353
2246
  search: search || void 0,
2354
- limit: FETCH_LIMIT
2247
+ session_id: sessFilter || void 0,
2248
+ limit: PAGE,
2249
+ offset: page * PAGE
2355
2250
  };
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))];
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))];
2360
2315
  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]
2316
+ () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
2317
+ [view, current?.session, source]
2363
2318
  );
2364
2319
  useInput5(
2365
2320
  (input, key) => {
2366
2321
  if (view === "detail") {
2367
- if (key.leftArrow || key.escape) setView("list");
2322
+ if (key.leftArrow || key.escape) setView("logs");
2368
2323
  else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
2369
2324
  else if (key.downArrow) setDetailScroll((n) => n + 1);
2370
2325
  else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
2371
2326
  else if (key.pageDown) setDetailScroll((n) => n + 10);
2372
2327
  return;
2373
2328
  }
2374
- 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
+ }
2375
2363
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
2376
- 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));
2377
2365
  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));
2366
+ else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
2379
2367
  else if (key.return || key.rightArrow) {
2380
2368
  if (current) {
2381
2369
  setDetailScroll(0);
2382
2370
  setView("detail");
2383
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
+ }
2384
2382
  } else if (input === "f") {
2385
2383
  setDi((n) => (n + 1) % DECISIONS.length);
2386
- clearSel();
2384
+ setPage(0);
2385
+ toTop();
2387
2386
  } else if (input === "g") {
2388
2387
  setGi((n) => (n + 1) % SIGNALS.length);
2389
- clearSel();
2388
+ setPage(0);
2389
+ toTop();
2390
2390
  } else if (input === "t") setEditing("tool");
2391
2391
  else if (input === "n") setEditing("agent");
2392
2392
  else if (input === "/") setEditing("search");
@@ -2396,46 +2396,89 @@ function AuditPanel({ active: active2, focused }) {
2396
2396
  setTool("");
2397
2397
  setAgent("");
2398
2398
  setSearch("");
2399
- clearSel();
2399
+ setSessFilter("");
2400
+ setPage(0);
2401
+ toTop();
2400
2402
  }
2401
2403
  },
2402
2404
  { isActive: focused && !editing }
2403
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
+ ] });
2404
2446
  if (view === "detail" && current) {
2405
- 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);
2406
2448
  const bodyW = Math.max(20, cols - 2);
2407
2449
  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;
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;
2410
2452
  const reasonShown = reasonLines.slice(0, 4);
2411
2453
  const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
2412
2454
  const maxScroll = Math.max(0, argLines.length - argBudget);
2413
2455
  const off = Math.min(detailScroll, maxScroll);
2414
2456
  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 })
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
2420
2463
  ] }),
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: [
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: [
2434
2477
  "Session \xB7 ",
2435
2478
  sessionCalls.length,
2436
2479
  " calls"
2437
2480
  ] }),
2438
- /* @__PURE__ */ jsx7(
2481
+ /* @__PURE__ */ jsx6(
2439
2482
  Table,
2440
2483
  {
2441
2484
  columns: [
@@ -2446,59 +2489,136 @@ function AuditPanel({ active: active2, focused }) {
2446
2489
  ],
2447
2490
  rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
2448
2491
  { value: e.decision, color: decisionColor(e.decision) },
2449
- { value: truncate(e.tool_name, 20), color: theme.accent },
2492
+ { value: truncate(e.tool, 20), color: theme.accent },
2450
2493
  { value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
2451
- { value: ago(e.created_at), dim: true }
2494
+ { value: ago(e.at), dim: true }
2452
2495
  ])
2453
2496
  }
2454
2497
  )
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" })
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" })
2457
2500
  ] });
2458
2501
  }
2459
- const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
2460
- /* @__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: [
2461
2504
  label,
2462
2505
  ":"
2463
2506
  ] }),
2464
- /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
2465
- /* @__PURE__ */ jsx7(Text7, { children: " " })
2507
+ /* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
2508
+ /* @__PURE__ */ jsx6(Text6, { children: " " })
2466
2509
  ] });
2467
- 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);
2468
2581
  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);
2582
+ const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
2583
+ const maxStart = Math.max(0, pageRows.length - listRows);
2471
2584
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
2472
- const windowed = entries.slice(start, start + listRows);
2585
+ const windowed = pageRows.slice(start, start + listRows);
2473
2586
  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: [
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) " }),
2476
2594
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
2477
2595
  chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
2478
2596
  chip("tool", tool || "\xB7", !!tool),
2479
2597
  chip("agent", agent || "\xB7", !!agent),
2480
- chip("search", search || "\xB7", !!search)
2598
+ chip("search", search || "\xB7", !!search),
2599
+ sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
2481
2600
  ] }),
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: [
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: [
2485
2604
  editing,
2486
2605
  ": "
2487
2606
  ] }),
2488
- /* @__PURE__ */ jsx7(
2607
+ /* @__PURE__ */ jsx6(
2489
2608
  TextInput4,
2490
2609
  {
2491
2610
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
2492
2611
  onChange: (v) => {
2493
2612
  (editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
2494
- setSel(0);
2613
+ setPage(0);
2614
+ toTop();
2495
2615
  },
2496
2616
  onSubmit: () => setEditing(null)
2497
2617
  }
2498
2618
  )
2499
2619
  ] }) : 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(
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(
2502
2622
  Table,
2503
2623
  {
2504
2624
  columns: [
@@ -2512,40 +2632,45 @@ function AuditPanel({ active: active2, focused }) {
2512
2632
  ],
2513
2633
  rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
2514
2634
  }
2515
- )
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
2516
2641
  ] }) });
2517
2642
  }
2518
2643
  function rowFor(e, active2, reasonW) {
2519
2644
  return [
2520
2645
  { value: active2 ? "\u25B8" : "", color: theme.accentBright },
2521
2646
  { 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 }
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 }
2527
2652
  ];
2528
2653
  }
2529
2654
  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 })
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 })
2533
2658
  ] });
2534
2659
  }
2535
2660
 
2536
2661
  // src/tui/panels/Agents.tsx
2537
- 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";
2538
2663
  import { useState as useState7 } from "react";
2539
- 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";
2540
2665
  var statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
2541
2666
  var sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
2542
2667
  function Bar({ label, value, max = 100, width = 16, color = theme.accent }) {
2543
2668
  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 })
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 })
2549
2674
  ] });
2550
2675
  }
2551
2676
  function AgentsPanel({ focused }) {
@@ -2570,60 +2695,60 @@ function AgentsPanel({ focused }) {
2570
2695
  const tools = tmap?.tools ?? [];
2571
2696
  const resources = tmap?.resources ?? [];
2572
2697
  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: [
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: [
2578
2703
  a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
2579
2704
  " "
2580
2705
  ] }),
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}` })
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}` })
2583
2708
  ] }, a.session_id))
2584
2709
  ] }),
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 ?? ""}` })
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 ?? ""}` })
2589
2714
  ] }),
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 })
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 })
2598
2723
  ] }) : null,
2599
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
2600
- /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2724
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
2725
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
2601
2726
  "Anomalies ",
2602
2727
  anomalies2.length ? `(${anomalies2.length})` : ""
2603
2728
  ] }),
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) })
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) })
2608
2733
  ] }, i))
2609
2734
  ] }),
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 ?? ""}` })
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 ?? ""}` })
2615
2740
  ] }, 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))
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))
2617
2742
  ] })
2618
2743
  ] }) })
2619
2744
  ] }) });
2620
2745
  }
2621
2746
 
2622
2747
  // src/tui/panels/Settings.tsx
2623
- 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";
2624
2749
  import TextInput5 from "ink-text-input";
2625
2750
  import { useState as useState8 } from "react";
2626
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2751
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2627
2752
  var EVENTS = ["denials", "allowed", "all"];
2628
2753
  function alertBodyFor(channel) {
2629
2754
  const c2 = channel.trim();
@@ -2749,7 +2874,7 @@ function SettingsPanel({ active: active2, focused }) {
2749
2874
  const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
2750
2875
  const error = localQ.error ?? whQ.error ?? alertQ.error;
2751
2876
  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" });
2877
+ const onOff = (on) => on ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "off" });
2753
2878
  const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
2754
2879
  const listBudget = Math.max(4, rows - 8);
2755
2880
  const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
@@ -2757,83 +2882,83 @@ function SettingsPanel({ active: active2, focused }) {
2757
2882
  const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
2758
2883
  return i >= start && i < start + listBudget;
2759
2884
  };
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: [
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: [
2768
2893
  "LOCAL LOGS ",
2769
- /* @__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" })
2770
2895
  ] }),
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) }),
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) }),
2774
2899
  onOff(!!local?.enabled),
2775
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
2900
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " enter toggles" })
2776
2901
  ] }),
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" })
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" })
2781
2906
  ] })
2782
2907
  ] }) : null,
2783
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2784
- /* @__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: [
2785
2910
  "WEBHOOKS ",
2786
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2911
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2787
2912
  "\u2014 POST every matching event to your endpoint (",
2788
2913
  webhooks.length,
2789
2914
  ")"
2790
2915
  ] })
2791
2916
  ] }),
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 }) }),
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 }) }),
2794
2919
  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) })
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) })
2797
2922
  ] }, 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)" })
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)" })
2801
2926
  ] }) : null
2802
2927
  ] }),
2803
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2804
- /* @__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: [
2805
2930
  "ALERTS ",
2806
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
2931
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
2807
2932
  "\u2014 notify on a signal spike (",
2808
2933
  alerts.length,
2809
2934
  ")"
2810
2935
  ] })
2811
2936
  ] }),
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 }) }),
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 }) }),
2814
2939
  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) })
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) })
2818
2943
  ] }, 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)" })
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)" })
2822
2947
  ] }) : null
2823
2948
  ] })
2824
2949
  ] }) });
2825
2950
  }
2826
2951
 
2827
2952
  // src/tui/App.tsx
2828
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
2953
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2829
2954
  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: [
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: [
2835
2960
  "press ",
2836
- /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
2961
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
2837
2962
  " to go live"
2838
2963
  ] }) })
2839
2964
  ] });
@@ -2844,7 +2969,6 @@ var SECTIONS = [
2844
2969
  { label: "Policies", Panel: PoliciesPanel },
2845
2970
  { label: "Rate Limit", Panel: RateLimitPanel },
2846
2971
  { label: "DLP", Panel: DlpPanel },
2847
- { label: "Stats", Panel: StatsPanel },
2848
2972
  { label: "Audit", Panel: AuditPanel },
2849
2973
  { label: "Settings", Panel: SettingsPanel }
2850
2974
  ];
@@ -2852,14 +2976,14 @@ var BANNER_HEX = ["#1432A0", "#2850BE", "#3C6ED7", "#5A8CE6", "#82AAF0", "#AAC8F
2852
2976
  function Banner({ cols }) {
2853
2977
  const wide = cols >= 82;
2854
2978
  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" })
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" })
2858
2982
  ] });
2859
2983
  }
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" })
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" })
2863
2987
  ] });
2864
2988
  }
2865
2989
  function App() {
@@ -2888,44 +3012,44 @@ function App() {
2888
3012
  });
2889
3013
  const { cols, rows } = useTermSize();
2890
3014
  const current = SECTIONS[section];
2891
- if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
3015
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
2892
3016
  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 }) });
3017
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }) });
2894
3018
  }
2895
3019
  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" }) })
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" }) })
2901
3025
  ] }),
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"]] }) })
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"]] }) })
2903
3027
  ] });
2904
3028
  }
2905
3029
  var HELP = [
2906
3030
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
2907
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)"]]],
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"]]],
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"]]],
2909
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"]]],
2910
- ["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"]]],
2911
3035
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
2912
3036
  ];
2913
3037
  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 })
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 })
2921
3045
  ] }, k))
2922
3046
  ] }, group)) }),
2923
- /* @__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" })
2924
3048
  ] });
2925
3049
  }
2926
3050
 
2927
3051
  // src/tui/index.tsx
2928
- import { jsx as jsx11 } from "react/jsx-runtime";
3052
+ import { jsx as jsx10 } from "react/jsx-runtime";
2929
3053
  async function launchTui() {
2930
3054
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
2931
3055
  process.stderr.write(
@@ -2939,7 +3063,7 @@ async function launchTui() {
2939
3063
  }
2940
3064
  process.stdout.write("\x1B[?1049h\x1B[H");
2941
3065
  try {
2942
- const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
3066
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
2943
3067
  await waitUntilExit();
2944
3068
  } finally {
2945
3069
  process.stdout.write("\x1B[?1049l");