@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/index.js CHANGED
@@ -6844,6 +6844,45 @@ var init_components = __esm({
6844
6844
  }
6845
6845
  });
6846
6846
 
6847
+ // src/tui/local-log.ts
6848
+ import { closeSync, openSync, readSync, statSync } from "fs";
6849
+ import { homedir as homedir4 } from "os";
6850
+ import { join as join6 } from "path";
6851
+ function tailLines(file, maxBytes = 131072) {
6852
+ try {
6853
+ const size = statSync(file).size;
6854
+ const start = Math.max(0, size - maxBytes);
6855
+ const fd = openSync(file, "r");
6856
+ const buf = Buffer.alloc(size - start);
6857
+ readSync(fd, buf, 0, buf.length, start);
6858
+ closeSync(fd);
6859
+ const lines = buf.toString("utf-8").split("\n").filter(Boolean);
6860
+ if (start > 0) lines.shift();
6861
+ return lines;
6862
+ } catch {
6863
+ return [];
6864
+ }
6865
+ }
6866
+ function parseLocalLines(lines) {
6867
+ const out2 = [];
6868
+ for (const line of lines) {
6869
+ try {
6870
+ const j = JSON.parse(line);
6871
+ const at = Date.parse(j.ts ?? "");
6872
+ if (Number.isFinite(at)) out2.push({ ...j, at });
6873
+ } catch {
6874
+ }
6875
+ }
6876
+ return out2;
6877
+ }
6878
+ var LOCAL_LOG;
6879
+ var init_local_log = __esm({
6880
+ "src/tui/local-log.ts"() {
6881
+ "use strict";
6882
+ LOCAL_LOG = join6(homedir4(), ".solongate", "local-logs", "solongate-audit.jsonl");
6883
+ }
6884
+ });
6885
+
6847
6886
  // src/api-client/types.ts
6848
6887
  var init_types = __esm({
6849
6888
  "src/api-client/types.ts"() {
@@ -7222,26 +7261,11 @@ var init_hooks = __esm({
7222
7261
  // src/tui/panels/Live.tsx
7223
7262
  import { Box as Box2, Text as Text2, useInput } from "ink";
7224
7263
  import TextInput from "ink-text-input";
7225
- import { closeSync, mkdirSync as mkdirSync3, openSync, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
7226
- import { homedir as homedir4 } from "os";
7227
- import { join as join6 } from "path";
7264
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
7265
+ import { homedir as homedir5 } from "os";
7266
+ import { join as join7 } from "path";
7228
7267
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7229
7268
  import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
7230
- function tailLines(file, maxBytes = 131072) {
7231
- try {
7232
- const size = statSync(file).size;
7233
- const start = Math.max(0, size - maxBytes);
7234
- const fd = openSync(file, "r");
7235
- const buf = Buffer.alloc(size - start);
7236
- readSync(fd, buf, 0, buf.length, start);
7237
- closeSync(fd);
7238
- const lines = buf.toString("utf-8").split("\n").filter(Boolean);
7239
- if (start > 0) lines.shift();
7240
- return lines;
7241
- } catch {
7242
- return [];
7243
- }
7244
- }
7245
7269
  function extractTarget(detail) {
7246
7270
  try {
7247
7271
  const j = JSON.parse(detail);
@@ -7785,9 +7809,9 @@ function LivePanel({ active: active2 }) {
7785
7809
  else if (input === "x") toggleSignal("dlp");
7786
7810
  else if (input === "r") toggleSignal("ratelimit");
7787
7811
  else if (input === "e") {
7788
- const file = join6(homedir4(), ".solongate", "live-export.jsonl");
7812
+ const file = join7(homedir5(), ".solongate", "live-export.jsonl");
7789
7813
  try {
7790
- mkdirSync3(join6(homedir4(), ".solongate"), { recursive: true });
7814
+ mkdirSync3(join7(homedir5(), ".solongate"), { recursive: true });
7791
7815
  writeFileSync3(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7792
7816
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
7793
7817
  } catch (err2) {
@@ -8103,10 +8127,11 @@ function LivePanel({ active: active2 }) {
8103
8127
  ] })
8104
8128
  ] });
8105
8129
  }
8106
- var CONFIG, SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, hhmmss, fmtUp, FILTERS, sessStatus, STATUS_STYLE, LIVE_HELP;
8130
+ var CONFIG, SPIN, BG, DIM_FLOOR, RING, hhmmss, fmtUp, FILTERS, sessStatus, STATUS_STYLE, LIVE_HELP;
8107
8131
  var init_Live = __esm({
8108
8132
  "src/tui/panels/Live.tsx"() {
8109
8133
  "use strict";
8134
+ init_local_log();
8110
8135
  init_api_client();
8111
8136
  init_config2();
8112
8137
  init_notify();
@@ -8116,8 +8141,7 @@ var init_Live = __esm({
8116
8141
  SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8117
8142
  BG = "#12234f";
8118
8143
  DIM_FLOOR = "#233457";
8119
- LOCAL_LOG = join6(homedir4(), ".solongate", "local-logs", "solongate-audit.jsonl");
8120
- RING = join6(process.cwd(), ".solongate", ".eval-ring.jsonl");
8144
+ RING = join7(process.cwd(), ".solongate", ".eval-ring.jsonl");
8121
8145
  hhmmss = (ts) => {
8122
8146
  const d = new Date(ts);
8123
8147
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -8200,7 +8224,6 @@ function PoliciesPanel({ focused }) {
8200
8224
  const [pi, setPi] = useState3(0);
8201
8225
  const [ri, setRi] = useState3(0);
8202
8226
  const [fi, setFi] = useState3(0);
8203
- const [vi, setVi] = useState3(0);
8204
8227
  const [rules, setRules] = useState3([]);
8205
8228
  const [mode, setMode] = useState3("denylist");
8206
8229
  const [dirty, setDirty] = useState3(false);
@@ -8209,15 +8232,6 @@ function PoliciesPanel({ focused }) {
8209
8232
  const [status, setStatus] = useState3(null);
8210
8233
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
8211
8234
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
8212
- const versionsQ = useLoader(
8213
- () => view === "versions" && selected ? api.policies.versions(selected.id, { limit: 50 }) : Promise.resolve(null),
8214
- [view, selected?.id]
8215
- );
8216
- const diffVer = versionsQ.data?.versions[vi]?.version;
8217
- const diffQ = useLoader(
8218
- () => view === "versions" && selected && diffVer !== void 0 ? api.policies.get(selected.id, diffVer) : Promise.resolve(null),
8219
- [view, selected?.id, diffVer]
8220
- );
8221
8235
  useEffect3(() => {
8222
8236
  if (detail.data) {
8223
8237
  setRules(detail.data.rules);
@@ -8315,32 +8329,10 @@ function PoliciesPanel({ focused }) {
8315
8329
  } else if (input === "D") {
8316
8330
  setStatus("Dry-running\u2026");
8317
8331
  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))));
8318
- } else if (input === "v") {
8319
- setVi(0);
8320
- setView("versions");
8321
8332
  } else if (input === "s") void save();
8322
8333
  else if (input === "x") discard();
8323
8334
  return;
8324
8335
  }
8325
- if (view === "versions") {
8326
- const vers = versionsQ.data?.versions ?? [];
8327
- if (key.leftArrow) return void setView("rules");
8328
- if (key.upArrow) setVi((n) => Math.max(0, n - 1));
8329
- else if (key.downArrow) setVi((n) => Math.min(vers.length - 1, n + 1));
8330
- else if (key.return) {
8331
- const v = vers[vi];
8332
- if (v && selected) {
8333
- setStatus("Rolling back\u2026");
8334
- void api.policies.rollback(selected.id, v.version).then((r) => {
8335
- setStatus(`\u2713 rolled back to v${v.version} \u2192 new v${r.version}`);
8336
- detail.reload();
8337
- list6.reload();
8338
- setView("rules");
8339
- }).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
8340
- }
8341
- }
8342
- return;
8343
- }
8344
8336
  const rule2 = rules[ri];
8345
8337
  if (!rule2) return setView("rules");
8346
8338
  const field = FIELDS[fi];
@@ -8381,8 +8373,7 @@ function PoliciesPanel({ focused }) {
8381
8373
  p.mode.padEnd(10),
8382
8374
  " ",
8383
8375
  p.rules.length,
8384
- " rules \xB7 v",
8385
- p.version
8376
+ " rules"
8386
8377
  ] }),
8387
8378
  p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
8388
8379
  ] }, p.id)) }),
@@ -8398,7 +8389,7 @@ function PoliciesPanel({ focused }) {
8398
8389
  /* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
8399
8390
  dirtyTag
8400
8391
  ] }),
8401
- /* @__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" }),
8392
+ /* @__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" }),
8402
8393
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
8403
8394
  Table,
8404
8395
  {
@@ -8424,50 +8415,6 @@ function PoliciesPanel({ focused }) {
8424
8415
  status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
8425
8416
  ] }) });
8426
8417
  }
8427
- if (view === "versions") {
8428
- const vers = versionsQ.data?.versions ?? [];
8429
- 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: [
8430
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate2(selected?.name ?? "", 28) }),
8431
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter roll back to that version \xB7 \u2190 back" }),
8432
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
8433
- Table,
8434
- {
8435
- columns: [
8436
- { header: "", width: 2 },
8437
- { header: "VER", width: 5 },
8438
- { header: "RULES", width: 5 },
8439
- { header: "REASON", width: 34 },
8440
- { header: "WHEN", width: 16 }
8441
- ],
8442
- rows: vers.map((v, i) => [
8443
- { value: i === vi ? "\u25B8" : "", color: theme.accentBright },
8444
- { value: `v${v.version}`, bold: i === vi },
8445
- { value: String(v.rules_count), dim: true },
8446
- { value: truncate2(v.reason || "\u2014", 34) },
8447
- { value: truncate2(v.created_at, 16), dim: true }
8448
- ])
8449
- }
8450
- ) }),
8451
- /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
8452
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: `Diff v${diffVer ?? "?"} vs current draft` }),
8453
- (() => {
8454
- const old = diffQ.data?.rules ?? [];
8455
- const cur = rules;
8456
- const oldIds = new Set(old.map((r) => r.id));
8457
- const curIds = new Set(cur.map((r) => r.id));
8458
- const added = cur.filter((r) => !oldIds.has(r.id));
8459
- const removed = old.filter((r) => !curIds.has(r.id));
8460
- if (diffQ.loading && !diffQ.data) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " loading\u2026" });
8461
- if (!added.length && !removed.length) return /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " identical rule set" });
8462
- return /* @__PURE__ */ jsxs3(Fragment3, { children: [
8463
- added.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.ok, children: ` + ${r.effect} ${truncate2(r.toolPattern, 18)} ${truncate2(r.description || r.id, 30)}` }, "a" + r.id)),
8464
- removed.slice(0, 4).map((r) => /* @__PURE__ */ jsx3(Text3, { wrap: "truncate", color: theme.bad, children: ` - ${r.effect} ${truncate2(r.toolPattern, 18)} ${truncate2(r.description || r.id, 30)}` }, "r" + r.id))
8465
- ] });
8466
- })()
8467
- ] }),
8468
- status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
8469
- ] }) });
8470
- }
8471
8418
  const rule = rules[ri];
8472
8419
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
8473
8420
  /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -8880,177 +8827,202 @@ var init_Dlp = __esm({
8880
8827
  }
8881
8828
  });
8882
8829
 
8883
- // src/tui/panels/Stats.tsx
8884
- import { Box as Box6, Text as Text6 } from "ink";
8885
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
8886
- function StatsPanel({ active: active2 }) {
8887
- const stats = useLoader(() => api.stats.get());
8888
- const ts = useLoader(() => api.stats.timeseries({ period: "24h" }));
8889
- const drift2 = useLoader(() => api.stats.drift(7));
8890
- usePoll(() => {
8891
- stats.reload();
8892
- ts.reload();
8893
- }, 5e3, active2);
8894
- usePoll(drift2.reload, 3e4, active2);
8895
- const s = stats.data;
8896
- const points = ts.data?.timeseries ?? [];
8897
- const driftRules = (drift2.data?.rules ?? []).slice(0, 5);
8898
- return /* @__PURE__ */ jsx6(DataView, { loading: stats.loading && !s, error: stats.error, children: s ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
8899
- /* @__PURE__ */ jsxs6(Box6, { children: [
8900
- /* @__PURE__ */ jsxs6(Text6, { bold: true, children: [
8901
- s.total_calls,
8902
- " "
8903
- ] }),
8904
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "calls " }),
8905
- /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
8906
- s.allowed,
8907
- " allowed"
8908
- ] }),
8909
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
8910
- /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
8911
- s.denied,
8912
- " denied"
8913
- ] })
8914
- ] }),
8915
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
8916
- s.active_policies,
8917
- " policies \xB7 ",
8918
- s.registered_tools,
8919
- " tools"
8920
- ] }),
8921
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
8922
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
8923
- "Last 24h ",
8924
- ts.data ? `(per ${ts.data.granularity})` : ""
8925
- ] }),
8926
- /* @__PURE__ */ jsxs6(Box6, { children: [
8927
- /* @__PURE__ */ jsx6(Text6, { children: "total " }),
8928
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 64) })
8929
- ] }),
8930
- /* @__PURE__ */ jsxs6(Box6, { children: [
8931
- /* @__PURE__ */ jsx6(Text6, { children: "allowed " }),
8932
- /* @__PURE__ */ jsx6(Text6, { color: theme.ok, children: sparkline(points.map((p) => p.allowed), 64) })
8933
- ] }),
8934
- /* @__PURE__ */ jsxs6(Box6, { children: [
8935
- /* @__PURE__ */ jsx6(Text6, { children: "denied " }),
8936
- /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 64) })
8937
- ] })
8938
- ] }),
8939
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
8940
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "Recent" }),
8941
- /* @__PURE__ */ jsx6(
8942
- Table,
8943
- {
8944
- columns: [
8945
- { header: "DECISION", width: 9 },
8946
- { header: "TOOL", width: 20 },
8947
- { header: "MS", width: 5 },
8948
- { header: "WHEN", width: 6 }
8949
- ],
8950
- rows: (s.recent_activity ?? []).slice(0, 6).map((a) => [
8951
- { value: a.decision, color: decisionColor(a.decision) },
8952
- { value: truncate2(a.tool_name, 20), color: theme.accent },
8953
- { value: String(a.evaluation_time_ms ?? "\u2014"), dim: true },
8954
- { value: ago(a.created_at), dim: true }
8955
- ])
8956
- }
8957
- )
8958
- ] }),
8959
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
8960
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
8961
- "Denial drift ",
8962
- drift2.data ? `(7d: ${drift2.data.total_current} now vs ${drift2.data.total_previous} prev)` : ""
8963
- ] }),
8964
- driftRules.length ? /* @__PURE__ */ jsx6(
8965
- Table,
8966
- {
8967
- columns: [
8968
- { header: "NOW", width: 5 },
8969
- { header: "PREV", width: 5 },
8970
- { header: "\u0394", width: 6 },
8971
- { header: "RULE", width: 22 },
8972
- { header: "REASON", width: 26 }
8973
- ],
8974
- rows: driftRules.map((r) => [
8975
- { value: String(r.current), bold: true },
8976
- { value: String(r.previous), dim: true },
8977
- { value: r.is_new ? "NEW" : r.spike ? `+${r.delta}` : String(r.delta), color: r.is_new ? theme.ok : r.spike ? theme.bad : void 0 },
8978
- { value: truncate2(r.rule_id ?? "\u2014", 22), color: theme.accent },
8979
- { value: truncate2(r.reason ?? r.last_tool ?? "\u2014", 26), dim: true }
8980
- ])
8981
- }
8982
- ) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " no denials in window" })
8983
- ] })
8984
- ] }) : null });
8985
- }
8986
- var init_Stats = __esm({
8987
- "src/tui/panels/Stats.tsx"() {
8988
- "use strict";
8989
- init_api_client();
8990
- init_components();
8991
- init_hooks();
8992
- init_theme();
8993
- }
8994
- });
8995
-
8996
8830
  // src/tui/panels/Audit.tsx
8997
- import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
8831
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
8998
8832
  import TextInput4 from "ink-text-input";
8999
8833
  import { useState as useState6 } from "react";
9000
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8834
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
8835
+ function loadLocalRows() {
8836
+ return parseLocalLines(tailLines(LOCAL_LOG, LOCAL_MAX_BYTES)).map((j, i) => ({
8837
+ id: "l:" + j.at + ":" + i,
8838
+ at: j.at,
8839
+ tool: j.tool ?? "?",
8840
+ decision: j.decision ?? "ALLOW",
8841
+ permission: j.permission ?? "\u2014",
8842
+ trust: j.trust_level ?? "\u2014",
8843
+ agent: j.agent_name ?? null,
8844
+ session: j.session_id ?? null,
8845
+ reason: j.reason ?? null,
8846
+ rule: j.matched_rule_id ?? null,
8847
+ evalMs: j.evaluation_time_ms ?? null,
8848
+ dlp: j.dlp ? Array.isArray(j.dlp) ? j.dlp.map(String) : ["dlp"] : [],
8849
+ burst: !!j.rate_limit_burst,
8850
+ args: j.arguments ? JSON.stringify(j.arguments) : null
8851
+ })).sort((a, b) => b.at - a.at);
8852
+ }
9001
8853
  function AuditPanel({ active: active2, focused }) {
9002
8854
  const { cols, rows } = usePanelSize();
8855
+ const [source, setSource] = useState6("cloud");
8856
+ const [view, setView] = useState6("logs");
9003
8857
  const [di, setDi] = useState6(0);
9004
8858
  const [gi, setGi] = useState6(0);
9005
8859
  const [tool, setTool] = useState6("");
9006
8860
  const [agent, setAgent] = useState6("");
9007
8861
  const [search, setSearch] = useState6("");
8862
+ const [sessFilter, setSessFilter] = useState6("");
8863
+ const [page, setPage] = useState6(0);
9008
8864
  const [sel, setSel] = useState6(0);
9009
- const [view, setView] = useState6("list");
9010
8865
  const [detailScroll, setDetailScroll] = useState6(0);
9011
8866
  const [editing, setEditing] = useState6(null);
8867
+ const [si, setSi] = useState6(0);
8868
+ const [sessSearch, setSessSearch] = useState6("");
8869
+ const [sessSel, setSessSel] = useState6(0);
8870
+ const toTop = () => setSel(0);
8871
+ const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
8872
+ const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
8873
+ usePoll(() => {
8874
+ statsQ.reload();
8875
+ tsQ.reload();
8876
+ }, 15e3, active2 && source === "cloud");
9012
8877
  const query = {
9013
8878
  filter: DECISIONS[di],
9014
8879
  signal: SIGNALS[gi],
9015
8880
  tool: tool || void 0,
9016
8881
  agent_name: agent || void 0,
9017
8882
  search: search || void 0,
9018
- limit: FETCH_LIMIT
8883
+ session_id: sessFilter || void 0,
8884
+ limit: PAGE,
8885
+ offset: page * PAGE
9019
8886
  };
9020
- const auditQ = useLoader(() => api.audit.list(query), [di, gi, tool, agent, search]);
9021
- usePoll(auditQ.reload, 6e3, active2 && view === "list" && !editing);
9022
- const entries = auditQ.data?.entries ?? [];
9023
- const current = entries[Math.min(sel, Math.max(0, entries.length - 1))];
8887
+ const cloudQ = useLoader(
8888
+ () => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
8889
+ [source, di, gi, tool, agent, search, sessFilter, page]
8890
+ );
8891
+ usePoll(cloudQ.reload, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
8892
+ const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
8893
+ usePoll(localQ.reload, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
8894
+ let pageRows = [];
8895
+ let total = 0;
8896
+ if (source === "cloud") {
8897
+ pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
8898
+ total = cloudQ.data?.total ?? 0;
8899
+ } else {
8900
+ const all = localQ.data ?? [];
8901
+ const q = search.trim().toLowerCase();
8902
+ const filtered = all.filter((r) => {
8903
+ if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
8904
+ if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
8905
+ if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
8906
+ if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
8907
+ if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
8908
+ if (sessFilter && r.session !== sessFilter) return false;
8909
+ if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
8910
+ return true;
8911
+ });
8912
+ total = filtered.length;
8913
+ pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
8914
+ }
8915
+ const pages = Math.max(1, Math.ceil(total / PAGE));
8916
+ const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
8917
+ const agentsQ = useLoader(
8918
+ () => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
8919
+ [source]
8920
+ );
8921
+ usePoll(agentsQ.reload, 8e3, active2 && source === "cloud" && view === "sessions");
8922
+ let sessions = [];
8923
+ if (source === "cloud") {
8924
+ sessions = (agentsQ.data?.agents ?? []).map((a) => ({
8925
+ id: a.session_id,
8926
+ agent: a.agent_name ?? a.session_id,
8927
+ status: a.status === "deactivated" ? "ended" : a.status,
8928
+ calls: a.total_calls,
8929
+ denies: a.denied_calls,
8930
+ dlp: a.dlp_events,
8931
+ trust: a.trust_score,
8932
+ lastAt: Date.parse(a.last_seen_at)
8933
+ }));
8934
+ } else {
8935
+ const bySess = /* @__PURE__ */ new Map();
8936
+ for (const r of localQ.data ?? []) {
8937
+ if (!r.session) continue;
8938
+ 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 };
8939
+ cur.calls++;
8940
+ if (r.decision !== "ALLOW") cur.denies++;
8941
+ if (r.dlp.length) cur.dlp++;
8942
+ cur.lastAt = Math.max(cur.lastAt, r.at);
8943
+ if (r.agent) cur.agent = r.agent;
8944
+ bySess.set(r.session, cur);
8945
+ }
8946
+ sessions = [...bySess.values()];
8947
+ }
8948
+ const sq = sessSearch.trim().toLowerCase();
8949
+ 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);
8950
+ const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
9024
8951
  const sessionQ = useLoader(
9025
- () => view === "detail" && current?.session_id ? api.audit.list({ session_id: current.session_id, limit: 40 }) : Promise.resolve(null),
9026
- [view, current?.session_id]
8952
+ () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
8953
+ [view, current?.session, source]
9027
8954
  );
9028
8955
  useInput5(
9029
8956
  (input, key) => {
9030
8957
  if (view === "detail") {
9031
- if (key.leftArrow || key.escape) setView("list");
8958
+ if (key.leftArrow || key.escape) setView("logs");
9032
8959
  else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
9033
8960
  else if (key.downArrow) setDetailScroll((n) => n + 1);
9034
8961
  else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
9035
8962
  else if (key.pageDown) setDetailScroll((n) => n + 10);
9036
8963
  return;
9037
8964
  }
9038
- const clearSel = () => setSel(0);
8965
+ if (input === "s") {
8966
+ setSource((s) => s === "cloud" ? "local" : "cloud");
8967
+ setPage(0);
8968
+ toTop();
8969
+ setSessSel(0);
8970
+ return;
8971
+ }
8972
+ if (input === "v") {
8973
+ setView((v) => v === "logs" ? "sessions" : "logs");
8974
+ return;
8975
+ }
8976
+ if (view === "sessions") {
8977
+ if (key.upArrow) setSessSel((n) => Math.max(0, n - 1));
8978
+ else if (key.downArrow) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 1));
8979
+ else if (key.pageUp) setSessSel((n) => Math.max(0, n - 10));
8980
+ else if (key.pageDown) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 10));
8981
+ else if (key.return || key.rightArrow) {
8982
+ if (currentSess) {
8983
+ setSessFilter(currentSess.id);
8984
+ setPage(0);
8985
+ toTop();
8986
+ setView("logs");
8987
+ }
8988
+ } else if (input === "f") {
8989
+ setSi((n) => (n + 1) % SESS_STATUS.length);
8990
+ setSessSel(0);
8991
+ } else if (input === "/") setEditing("sess-search");
8992
+ else if (input === "c") {
8993
+ setSi(0);
8994
+ setSessSearch("");
8995
+ setSessSel(0);
8996
+ }
8997
+ return;
8998
+ }
9039
8999
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
9040
- else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
9000
+ else if (key.downArrow) setSel((n) => Math.min(pageRows.length - 1, n + 1));
9041
9001
  else if (key.pageUp) setSel((n) => Math.max(0, n - 10));
9042
- else if (key.pageDown) setSel((n) => Math.min(entries.length - 1, n + 10));
9002
+ else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
9043
9003
  else if (key.return || key.rightArrow) {
9044
9004
  if (current) {
9045
9005
  setDetailScroll(0);
9046
9006
  setView("detail");
9047
9007
  }
9008
+ } else if (input === "]") {
9009
+ if (page < pages - 1) {
9010
+ setPage(page + 1);
9011
+ toTop();
9012
+ }
9013
+ } else if (input === "[") {
9014
+ if (page > 0) {
9015
+ setPage(page - 1);
9016
+ toTop();
9017
+ }
9048
9018
  } else if (input === "f") {
9049
9019
  setDi((n) => (n + 1) % DECISIONS.length);
9050
- clearSel();
9020
+ setPage(0);
9021
+ toTop();
9051
9022
  } else if (input === "g") {
9052
9023
  setGi((n) => (n + 1) % SIGNALS.length);
9053
- clearSel();
9024
+ setPage(0);
9025
+ toTop();
9054
9026
  } else if (input === "t") setEditing("tool");
9055
9027
  else if (input === "n") setEditing("agent");
9056
9028
  else if (input === "/") setEditing("search");
@@ -9060,46 +9032,89 @@ function AuditPanel({ active: active2, focused }) {
9060
9032
  setTool("");
9061
9033
  setAgent("");
9062
9034
  setSearch("");
9063
- clearSel();
9035
+ setSessFilter("");
9036
+ setPage(0);
9037
+ toTop();
9064
9038
  }
9065
9039
  },
9066
9040
  { isActive: focused && !editing }
9067
9041
  );
9042
+ const points = tsQ.data?.timeseries ?? [];
9043
+ const localAll = localQ.data ?? [];
9044
+ const strip = source === "cloud" ? /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
9045
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
9046
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls \xB7 " }),
9047
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
9048
+ statsQ.data?.allowed ?? "\xB7\xB7\xB7",
9049
+ " allow"
9050
+ ] }),
9051
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
9052
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
9053
+ statsQ.data?.denied ?? "\xB7\xB7",
9054
+ " deny"
9055
+ ] }),
9056
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies \xB7 ${statsQ.data?.registered_tools ?? "\xB7"} tools \xB7 24h ` }),
9057
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 24) }),
9058
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " deny " }),
9059
+ /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 24) })
9060
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
9061
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: localAll.length }),
9062
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls in local file \xB7 " }),
9063
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
9064
+ localAll.filter((r) => r.decision === "ALLOW").length,
9065
+ " allow"
9066
+ ] }),
9067
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
9068
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
9069
+ localAll.filter((r) => r.decision !== "ALLOW").length,
9070
+ " deny"
9071
+ ] }),
9072
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9073
+ " \xB7 ",
9074
+ LOCAL_LOG
9075
+ ] })
9076
+ ] });
9077
+ const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
9078
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
9079
+ /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
9080
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
9081
+ ] });
9068
9082
  if (view === "detail" && current) {
9069
- const sessionCalls = sessionQ.data?.entries ?? [];
9083
+ const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
9070
9084
  const bodyW = Math.max(20, cols - 2);
9071
9085
  const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
9072
- const argLines = current.arguments_summary ? wrapLines(prettyJson(JSON.stringify(current.arguments_summary)), bodyW) : ["(no arguments recorded)"];
9073
- const sessionRows = current.session_id ? Math.min(4, sessionCalls.length) + 2 : 1;
9086
+ const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
9087
+ const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
9074
9088
  const reasonShown = reasonLines.slice(0, 4);
9075
9089
  const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
9076
9090
  const maxScroll = Math.max(0, argLines.length - argBudget);
9077
9091
  const off = Math.min(detailScroll, maxScroll);
9078
9092
  const argWin = argLines.slice(off, off + argBudget);
9079
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
9080
- /* @__PURE__ */ jsxs7(Box7, { children: [
9081
- /* @__PURE__ */ jsx7(Text7, { color: decisionColor(current.decision), bold: true, children: current.decision }),
9082
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: " " + current.tool_name }),
9083
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust_level })
9093
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9094
+ /* @__PURE__ */ jsxs6(Box6, { children: [
9095
+ /* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
9096
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
9097
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust + " " }),
9098
+ srcChip
9084
9099
  ] }),
9085
- reasonShown.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
9086
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "reason " }),
9087
- /* @__PURE__ */ jsx7(Text7, { children: l.slice("reason: ".length) })
9088
- ] }) : /* @__PURE__ */ jsx7(Text7, { children: " " + l }) }, "r" + i)),
9089
- /* @__PURE__ */ jsx7(Detail, { label: "rule", value: current.matched_rule_id ?? "\u2014" }),
9090
- /* @__PURE__ */ jsx7(Detail, { label: "agent", value: current.agent_name ?? "\u2014" }),
9091
- /* @__PURE__ */ jsx7(Detail, { label: "session", value: current.session_id ?? "\u2014" }),
9092
- /* @__PURE__ */ jsx7(Detail, { label: "dlp", value: current.dlp_matches?.length ? current.dlp_matches.join(", ") : "none", color: current.dlp_matches?.length ? theme.bad : void 0 }),
9093
- /* @__PURE__ */ jsx7(Detail, { label: "when", value: new Date(current.created_at).toLocaleString() + (current.evaluation_time_ms != null ? ` \xB7 eval ${current.evaluation_time_ms}ms` : "") }),
9094
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
9095
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
9096
- current.session_id ? /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
9097
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
9100
+ reasonShown.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
9101
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "reason " }),
9102
+ /* @__PURE__ */ jsx6(Text6, { children: l.slice("reason: ".length) })
9103
+ ] }) : /* @__PURE__ */ jsx6(Text6, { children: " " + l }) }, "r" + i)),
9104
+ /* @__PURE__ */ jsx6(Detail, { label: "rule", value: current.rule ?? "\u2014" }),
9105
+ /* @__PURE__ */ jsx6(Detail, { label: "agent", value: current.agent ?? "\u2014" }),
9106
+ /* @__PURE__ */ jsx6(Detail, { label: "session", value: current.session ?? "\u2014" }),
9107
+ /* @__PURE__ */ jsx6(Detail, { label: "dlp", value: current.dlp.length ? current.dlp.join(", ") : "none", color: current.dlp.length ? theme.bad : void 0 }),
9108
+ /* @__PURE__ */ jsx6(Detail, { label: "when", value: new Date(current.at).toLocaleString() + (current.evalMs != null ? ` \xB7 eval ${current.evalMs}ms` : "") }),
9109
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
9110
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
9111
+ current.session ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9112
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9098
9113
  "Session \xB7 ",
9099
9114
  sessionCalls.length,
9100
9115
  " calls"
9101
9116
  ] }),
9102
- /* @__PURE__ */ jsx7(
9117
+ /* @__PURE__ */ jsx6(
9103
9118
  Table,
9104
9119
  {
9105
9120
  columns: [
@@ -9110,59 +9125,136 @@ function AuditPanel({ active: active2, focused }) {
9110
9125
  ],
9111
9126
  rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
9112
9127
  { value: e.decision, color: decisionColor(e.decision) },
9113
- { value: truncate2(e.tool_name, 20), color: theme.accent },
9128
+ { value: truncate2(e.tool, 20), color: theme.accent },
9114
9129
  { value: truncate2(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
9115
- { value: ago(e.created_at), dim: true }
9130
+ { value: ago(e.at), dim: true }
9116
9131
  ])
9117
9132
  }
9118
9133
  )
9119
- ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "(no session id)" }),
9120
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to list" })
9134
+ ] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no session id)" }),
9135
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to logs" })
9121
9136
  ] });
9122
9137
  }
9123
- const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
9124
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
9138
+ const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
9139
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9125
9140
  label,
9126
9141
  ":"
9127
9142
  ] }),
9128
- /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
9129
- /* @__PURE__ */ jsx7(Text7, { children: " " })
9143
+ /* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
9144
+ /* @__PURE__ */ jsx6(Text6, { children: " " })
9130
9145
  ] });
9131
- const headerRows = 4 + (editing ? 1 : 0);
9146
+ if (view === "sessions") {
9147
+ const headerRows2 = 5 + (editing ? 1 : 0);
9148
+ const listRows2 = Math.max(4, rows - headerRows2);
9149
+ const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
9150
+ const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
9151
+ const win = sessionsFiltered.slice(start2, start2 + listRows2);
9152
+ const loading2 = source === "cloud" ? agentsQ.loading && !agentsQ.data : localQ.loading && !localQ.data;
9153
+ return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9154
+ strip,
9155
+ /* @__PURE__ */ jsxs6(Box6, { children: [
9156
+ srcChip,
9157
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
9158
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 logs (v) " }),
9159
+ chip("status", SESS_STATUS[si] ?? "all", si !== 0),
9160
+ chip("search", sessSearch || "\xB7", !!sessSearch)
9161
+ ] }),
9162
+ /* @__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" }),
9163
+ editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
9164
+ /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
9165
+ /* @__PURE__ */ jsx6(
9166
+ TextInput4,
9167
+ {
9168
+ value: sessSearch,
9169
+ onChange: (v) => {
9170
+ setSessSearch(v);
9171
+ setSessSel(0);
9172
+ },
9173
+ onSubmit: () => setEditing(null)
9174
+ }
9175
+ )
9176
+ ] }) : null,
9177
+ /* @__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}` : ""}` }),
9178
+ /* @__PURE__ */ jsx6(
9179
+ Table,
9180
+ {
9181
+ columns: [
9182
+ { header: "", width: 2 },
9183
+ { header: "STATUS", width: 8 },
9184
+ { header: "AGENT", width: 18 },
9185
+ { header: "SESSION", width: 10 },
9186
+ { header: "CALLS", width: 6 },
9187
+ { header: "DENY", width: 5 },
9188
+ { header: "DLP", width: 4 },
9189
+ { header: "TRUST", width: 5 },
9190
+ { header: "LAST", width: 6 }
9191
+ ],
9192
+ rows: win.map((r, i) => {
9193
+ const st = STATUS_DOT[r.status];
9194
+ const isSel = start2 + i === selC && focused;
9195
+ return [
9196
+ { value: isSel ? "\u25B8" : "", color: theme.accentBright },
9197
+ { value: `${st.ch} ${r.status}`, color: st.color },
9198
+ { value: truncate2(r.agent, 18), color: theme.accent, bold: isSel },
9199
+ { value: r.id.slice(0, 8), dim: true },
9200
+ { value: String(r.calls) },
9201
+ { value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
9202
+ { value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
9203
+ { value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
9204
+ { value: ago(r.lastAt), dim: true }
9205
+ ];
9206
+ })
9207
+ }
9208
+ ),
9209
+ sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9210
+ "(no sessions",
9211
+ source === "local" ? " in the local file" : "",
9212
+ ")"
9213
+ ] }) : null
9214
+ ] }) });
9215
+ }
9216
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
9132
9217
  const listRows = Math.max(4, rows - headerRows);
9133
- const selClamped = Math.min(sel, Math.max(0, entries.length - 1));
9134
- const maxStart = Math.max(0, entries.length - listRows);
9218
+ const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
9219
+ const maxStart = Math.max(0, pageRows.length - listRows);
9135
9220
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
9136
- const windowed = entries.slice(start, start + listRows);
9221
+ const windowed = pageRows.slice(start, start + listRows);
9137
9222
  const reasonW = Math.min(60, Math.max(12, cols - 67));
9138
- return /* @__PURE__ */ jsx7(DataView, { loading: auditQ.loading && !auditQ.data, error: auditQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
9139
- /* @__PURE__ */ jsxs7(Box7, { children: [
9223
+ const loading = source === "cloud" ? cloudQ.loading && !cloudQ.data : localQ.loading && !localQ.data;
9224
+ return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9225
+ strip,
9226
+ /* @__PURE__ */ jsxs6(Box6, { children: [
9227
+ srcChip,
9228
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
9229
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 sessions (v) " }),
9140
9230
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
9141
9231
  chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
9142
9232
  chip("tool", tool || "\xB7", !!tool),
9143
9233
  chip("agent", agent || "\xB7", !!agent),
9144
- chip("search", search || "\xB7", !!search)
9234
+ chip("search", search || "\xB7", !!search),
9235
+ sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
9145
9236
  ] }),
9146
- /* @__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" }),
9147
- editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
9148
- /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
9237
+ /* @__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" }),
9238
+ editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
9239
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
9149
9240
  editing,
9150
9241
  ": "
9151
9242
  ] }),
9152
- /* @__PURE__ */ jsx7(
9243
+ /* @__PURE__ */ jsx6(
9153
9244
  TextInput4,
9154
9245
  {
9155
9246
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
9156
9247
  onChange: (v) => {
9157
9248
  (editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
9158
- setSel(0);
9249
+ setPage(0);
9250
+ toTop();
9159
9251
  },
9160
9252
  onSubmit: () => setEditing(null)
9161
9253
  }
9162
9254
  )
9163
9255
  ] }) : null,
9164
- /* @__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` : ""}` }),
9165
- /* @__PURE__ */ jsx7(
9256
+ /* @__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` : ""}` }),
9257
+ /* @__PURE__ */ jsx6(
9166
9258
  Table,
9167
9259
  {
9168
9260
  columns: [
@@ -9176,51 +9268,81 @@ function AuditPanel({ active: active2, focused }) {
9176
9268
  ],
9177
9269
  rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
9178
9270
  }
9179
- )
9271
+ ),
9272
+ pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9273
+ "(no entries",
9274
+ source === "local" ? " in the local file" : "",
9275
+ " \u2014 c clears filters)"
9276
+ ] }) : null
9180
9277
  ] }) });
9181
9278
  }
9182
9279
  function rowFor(e, active2, reasonW) {
9183
9280
  return [
9184
9281
  { value: active2 ? "\u25B8" : "", color: theme.accentBright },
9185
9282
  { value: e.decision, color: decisionColor(e.decision) },
9186
- { value: truncate2(e.tool_name, 18), color: theme.accent },
9187
- { value: truncate2(e.agent_name ?? "\u2014", 14), dim: true },
9188
- { value: truncate2(e.reason ?? "\u2014", reasonW) },
9189
- { 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 },
9190
- { value: ago(e.created_at), dim: true }
9283
+ { value: truncate2(e.tool, 18), color: theme.accent },
9284
+ { value: truncate2(e.agent ?? "\u2014", 14), dim: true },
9285
+ { value: truncate2(e.reason ?? e.args ?? "\u2014", reasonW) },
9286
+ { value: e.dlp.length ? String(e.dlp.length) : "0", color: e.dlp.length ? theme.bad : void 0, dim: !e.dlp.length },
9287
+ { value: ago(e.at), dim: true }
9191
9288
  ];
9192
9289
  }
9193
9290
  function Detail({ label, value, color }) {
9194
- return /* @__PURE__ */ jsxs7(Box7, { children: [
9195
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
9196
- /* @__PURE__ */ jsx7(Text7, { color, wrap: "truncate", children: value })
9291
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
9292
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: label.padEnd(9) }),
9293
+ /* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
9197
9294
  ] });
9198
9295
  }
9199
- var DECISIONS, SIGNALS, FETCH_LIMIT;
9296
+ var DECISIONS, SIGNALS, SESS_STATUS, PAGE, LOCAL_MAX_BYTES, cloudRow, sessStatus2, STATUS_DOT;
9200
9297
  var init_Audit = __esm({
9201
9298
  "src/tui/panels/Audit.tsx"() {
9202
9299
  "use strict";
9203
9300
  init_api_client();
9204
9301
  init_components();
9205
9302
  init_hooks();
9303
+ init_local_log();
9206
9304
  init_theme();
9207
9305
  DECISIONS = [void 0, "DENY", "ALLOW"];
9208
9306
  SIGNALS = [void 0, "dlp", "ratelimit"];
9209
- FETCH_LIMIT = 500;
9307
+ SESS_STATUS = [void 0, "active", "idle", "ended"];
9308
+ PAGE = 500;
9309
+ LOCAL_MAX_BYTES = 16 * 1024 * 1024;
9310
+ cloudRow = (e) => ({
9311
+ id: e.id,
9312
+ at: Date.parse(e.created_at),
9313
+ tool: e.tool_name,
9314
+ decision: e.decision,
9315
+ permission: e.permission,
9316
+ trust: e.trust_level,
9317
+ agent: e.agent_name,
9318
+ session: e.session_id,
9319
+ reason: e.reason,
9320
+ rule: e.matched_rule_id,
9321
+ evalMs: e.evaluation_time_ms,
9322
+ dlp: e.dlp_matches ?? [],
9323
+ burst: !!e.rate_limit_burst,
9324
+ args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
9325
+ });
9326
+ sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
9327
+ STATUS_DOT = {
9328
+ active: { ch: "\u25CF", color: theme.ok },
9329
+ idle: { ch: "\u25D0", color: theme.warn },
9330
+ ended: { ch: "\u25CB", color: theme.dim }
9331
+ };
9210
9332
  }
9211
9333
  });
9212
9334
 
9213
9335
  // src/tui/panels/Agents.tsx
9214
- import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
9336
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
9215
9337
  import { useState as useState7 } from "react";
9216
- import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
9338
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
9217
9339
  function Bar({ label, value, max = 100, width: width2 = 16, color = theme.accent }) {
9218
9340
  const filled = max > 0 ? Math.round(Math.min(value, max) / max * width2) : 0;
9219
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9220
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(9) }),
9221
- /* @__PURE__ */ jsx8(Text8, { color, children: "\u2588".repeat(filled) }),
9222
- /* @__PURE__ */ jsx8(Text8, { color: "#233457", children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
9223
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + value })
9341
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
9342
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(9) }),
9343
+ /* @__PURE__ */ jsx7(Text7, { color, children: "\u2588".repeat(filled) }),
9344
+ /* @__PURE__ */ jsx7(Text7, { color: "#233457", children: "\u2591".repeat(Math.max(0, width2 - filled)) }),
9345
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + value })
9224
9346
  ] });
9225
9347
  }
9226
9348
  function AgentsPanel({ focused }) {
@@ -9245,50 +9367,50 @@ function AgentsPanel({ focused }) {
9245
9367
  const tools = tmap?.tools ?? [];
9246
9368
  const resources = tmap?.resources ?? [];
9247
9369
  const listW = 30;
9248
- 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: [
9249
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
9250
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
9251
- agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
9252
- /* @__PURE__ */ jsxs8(Text8, { color: statusColor(a.status), children: [
9370
+ 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: [
9371
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", width: listW, marginRight: 2, overflow: "hidden", children: [
9372
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 select agent" : "press \u2192 to browse" }),
9373
+ agents.slice(0, 16).map((a, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", backgroundColor: focused && i === sel ? "#1c2f63" : void 0, bold: i === sel, children: [
9374
+ /* @__PURE__ */ jsxs7(Text7, { color: statusColor(a.status), children: [
9253
9375
  a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
9254
9376
  " "
9255
9377
  ] }),
9256
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
9257
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `t${a.trust_score}` })
9378
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(a.agent_name ?? a.agent_id ?? "?", 16).padEnd(17) }),
9379
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `t${a.trust_score}` })
9258
9380
  ] }, a.session_id))
9259
9381
  ] }),
9260
- /* @__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: [
9261
- /* @__PURE__ */ jsxs8(Box8, { children: [
9262
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: truncate2(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
9263
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
9382
+ /* @__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: [
9383
+ /* @__PURE__ */ jsxs7(Box7, { children: [
9384
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: truncate2(selected.agent_name ?? selected.agent_id ?? "?", 22) }),
9385
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` trust ${base?.trustScore ?? "\u2014"}/100 \xB7 ${base?.character ?? ""}` })
9264
9386
  ] }),
9265
- base?.characterBlurb ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate2(String(base.characterBlurb), 70) }) : null,
9266
- radar ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
9267
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
9268
- /* @__PURE__ */ jsx8(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
9269
- /* @__PURE__ */ jsx8(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
9270
- /* @__PURE__ */ jsx8(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
9271
- /* @__PURE__ */ jsx8(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
9272
- /* @__PURE__ */ jsx8(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
9387
+ base?.characterBlurb ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate2(String(base.characterBlurb), 70) }) : null,
9388
+ radar ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
9389
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Behaviour radar (0\u2013100)" }),
9390
+ /* @__PURE__ */ jsx7(Bar, { label: "read", value: Math.round((radar.read ?? 0) * 100) }),
9391
+ /* @__PURE__ */ jsx7(Bar, { label: "write", value: Math.round((radar.write ?? 0) * 100), color: theme.warn }),
9392
+ /* @__PURE__ */ jsx7(Bar, { label: "execute", value: Math.round((radar.execute ?? 0) * 100), color: theme.warn }),
9393
+ /* @__PURE__ */ jsx7(Bar, { label: "network", value: Math.round((radar.network ?? 0) * 100) }),
9394
+ /* @__PURE__ */ jsx7(Bar, { label: "complian.", value: Math.round((radar.compliance ?? 0) * 100), color: theme.ok })
9273
9395
  ] }) : null,
9274
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
9275
- /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
9396
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
9397
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
9276
9398
  "Anomalies ",
9277
9399
  anomalies2.length ? `(${anomalies2.length})` : ""
9278
9400
  ] }),
9279
- anomalies2.length === 0 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9280
- /* @__PURE__ */ jsx8(Text8, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
9281
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: String(an.kind).padEnd(12) }),
9282
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate2(String(an.description ?? ""), 44) })
9401
+ anomalies2.length === 0 ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " none" }) : anomalies2.slice(0, 4).map((an, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
9402
+ /* @__PURE__ */ jsx7(Text7, { color: sevColor(String(an.severity)), children: ` ${String(an.severity).toUpperCase().slice(0, 4).padEnd(5)}` }),
9403
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: String(an.kind).padEnd(12) }),
9404
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: truncate2(String(an.description ?? ""), 44) })
9283
9405
  ] }, i))
9284
9406
  ] }),
9285
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
9286
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Trust map \xB7 top tools" }),
9287
- tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9288
- /* @__PURE__ */ jsx8(Text8, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate2(String(t.name), 16).padEnd(17)}` }),
9289
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
9407
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
9408
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Trust map \xB7 top tools" }),
9409
+ tools.slice(0, 4).map((t, i) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
9410
+ /* @__PURE__ */ jsx7(Text7, { color: t.anomalous ? theme.bad : theme.accent, children: ` ${truncate2(String(t.name), 16).padEnd(17)}` }),
9411
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: `${t.count}\xD7 ${t.permission ?? ""}` })
9290
9412
  ] }, i)),
9291
- 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}"} ${truncate2(String(r.value), 40)} ${r.count}\xD7` }, "r" + i))
9413
+ 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}"} ${truncate2(String(r.value), 40)} ${r.count}\xD7` }, "r" + i))
9292
9414
  ] })
9293
9415
  ] }) })
9294
9416
  ] }) });
@@ -9307,10 +9429,10 @@ var init_Agents = __esm({
9307
9429
  });
9308
9430
 
9309
9431
  // src/tui/panels/Settings.tsx
9310
- import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
9432
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
9311
9433
  import TextInput5 from "ink-text-input";
9312
9434
  import { useState as useState8 } from "react";
9313
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
9435
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
9314
9436
  function alertBodyFor(channel) {
9315
9437
  const c2 = channel.trim();
9316
9438
  const base = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
@@ -9435,7 +9557,7 @@ function SettingsPanel({ active: active2, focused }) {
9435
9557
  const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
9436
9558
  const error = localQ.error ?? whQ.error ?? alertQ.error;
9437
9559
  const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
9438
- const onOff = (on) => on ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "off" });
9560
+ const onOff = (on) => on ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "off" });
9439
9561
  const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
9440
9562
  const listBudget = Math.max(4, rows - 8);
9441
9563
  const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
@@ -9443,68 +9565,68 @@ function SettingsPanel({ active: active2, focused }) {
9443
9565
  const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
9444
9566
  return i >= start && i < start + listBudget;
9445
9567
  };
9446
- return /* @__PURE__ */ jsx9(DataView, { loading, error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
9447
- /* @__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" }),
9448
- editing ? /* @__PURE__ */ jsxs9(Box9, { children: [
9449
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
9450
- /* @__PURE__ */ jsx9(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
9451
- ] }) : msg ? /* @__PURE__ */ jsx9(Text9, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : /* @__PURE__ */ jsx9(Text9, { children: " " }),
9452
- inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
9453
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
9568
+ return /* @__PURE__ */ jsx8(DataView, { loading, error, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9569
+ /* @__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" }),
9570
+ editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
9571
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
9572
+ /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
9573
+ ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
9574
+ inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
9575
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
9454
9576
  "LOCAL LOGS ",
9455
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
9577
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
9456
9578
  ] }),
9457
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
9458
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
9459
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "enabled ".padEnd(10) }),
9579
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9580
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
9581
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "enabled ".padEnd(10) }),
9460
9582
  onOff(!!local?.enabled),
9461
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
9583
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " enter toggles" })
9462
9584
  ] }),
9463
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
9464
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
9465
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "path ".padEnd(10) }),
9466
- local?.path ? /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate2(local.path, cols - 14) }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "not set \u2014 enter to edit" })
9585
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9586
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
9587
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "path ".padEnd(10) }),
9588
+ local?.path ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(local.path, cols - 14) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
9467
9589
  ] })
9468
9590
  ] }) : null,
9469
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
9470
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
9591
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
9592
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
9471
9593
  "WEBHOOKS ",
9472
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
9594
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
9473
9595
  "\u2014 POST every matching event to your endpoint (",
9474
9596
  webhooks.length,
9475
9597
  ")"
9476
9598
  ] })
9477
9599
  ] }),
9478
- webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
9479
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
9600
+ webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9601
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
9480
9602
  onOff(wh.enabled),
9481
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
9482
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate2(wh.url, cols - 16) })
9603
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
9604
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(wh.url, cols - 16) })
9483
9605
  ] }, wh.id)),
9484
- inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs9(Text9, { children: [
9485
- /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
9486
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add webhook (enter)" })
9606
+ inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs8(Text8, { children: [
9607
+ /* @__PURE__ */ jsx8(Text8, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
9608
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add webhook (enter)" })
9487
9609
  ] }) : null
9488
9610
  ] }),
9489
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
9490
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
9611
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
9612
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
9491
9613
  "ALERTS ",
9492
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
9614
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
9493
9615
  "\u2014 notify on a signal spike (",
9494
9616
  alerts.length,
9495
9617
  ")"
9496
9618
  ] })
9497
9619
  ] }),
9498
- alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
9499
- /* @__PURE__ */ jsx9(Text9, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
9620
+ alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9621
+ /* @__PURE__ */ jsx8(Text8, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
9500
9622
  onOff(rule.enabled),
9501
- /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
9502
- /* @__PURE__ */ jsx9(Text9, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
9503
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: truncate2(chanOf(rule), cols - 26) })
9623
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
9624
+ /* @__PURE__ */ jsx8(Text8, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
9625
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(chanOf(rule), cols - 26) })
9504
9626
  ] }, rule.id)),
9505
- inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs9(Text9, { children: [
9506
- /* @__PURE__ */ jsx9(Text9, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
9507
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
9627
+ inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs8(Text8, { children: [
9628
+ /* @__PURE__ */ jsx8(Text8, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
9629
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
9508
9630
  ] }) : null
9509
9631
  ] })
9510
9632
  ] }) });
@@ -9522,17 +9644,17 @@ var init_Settings = __esm({
9522
9644
  });
9523
9645
 
9524
9646
  // src/tui/App.tsx
9525
- import { Box as Box10, Text as Text10, useApp, useInput as useInput8 } from "ink";
9647
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
9526
9648
  import { useState as useState9 } from "react";
9527
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
9649
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
9528
9650
  function LiveHint() {
9529
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
9530
- /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
9531
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
9532
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
9533
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs10(Text10, { children: [
9651
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
9652
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
9653
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
9654
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
9655
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
9534
9656
  "press ",
9535
- /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
9657
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
9536
9658
  " to go live"
9537
9659
  ] }) })
9538
9660
  ] });
@@ -9540,14 +9662,14 @@ function LiveHint() {
9540
9662
  function Banner({ cols }) {
9541
9663
  const wide = cols >= 82;
9542
9664
  if (!wide) {
9543
- return /* @__PURE__ */ jsxs10(Box10, { children: [
9544
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate" }),
9545
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \u2014 security control center" })
9665
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
9666
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
9667
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
9546
9668
  ] });
9547
9669
  }
9548
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
9549
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx10(Text10, { bold: true, color: BANNER_HEX[i], children: line }, i)),
9550
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
9670
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
9671
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
9672
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
9551
9673
  ] });
9552
9674
  }
9553
9675
  function App() {
@@ -9576,31 +9698,31 @@ function App() {
9576
9698
  });
9577
9699
  const { cols, rows } = useTermSize();
9578
9700
  const current = SECTIONS[section];
9579
- if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
9701
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
9580
9702
  if (current.label === "Live" && focus === "panel") {
9581
- return /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx10(LivePanel, { active: true, focused: true }) });
9703
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }) });
9582
9704
  }
9583
9705
  const Panel = current.Panel;
9584
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
9585
- /* @__PURE__ */ jsx10(Banner, { cols }),
9586
- /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexGrow: 1, children: [
9587
- /* @__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)) }),
9588
- /* @__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" }) })
9706
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
9707
+ /* @__PURE__ */ jsx9(Banner, { cols }),
9708
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
9709
+ /* @__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)) }),
9710
+ /* @__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" }) })
9589
9711
  ] }),
9590
- /* @__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"]] }) })
9712
+ /* @__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"]] }) })
9591
9713
  ] });
9592
9714
  }
9593
9715
  function HelpOverlay({ cols, rows }) {
9594
- return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
9595
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
9596
- /* @__PURE__ */ jsx10(Box10, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginBottom: 1, children: [
9597
- /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accent, children: group }),
9598
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs10(Text10, { children: [
9599
- /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
9600
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: desc })
9716
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
9717
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
9718
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
9719
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
9720
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
9721
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
9722
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
9601
9723
  ] }, k))
9602
9724
  ] }, group)) }),
9603
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
9725
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
9604
9726
  ] });
9605
9727
  }
9606
9728
  var SECTIONS, BANNER_HEX, HELP;
@@ -9614,7 +9736,6 @@ var init_App = __esm({
9614
9736
  init_Policies();
9615
9737
  init_RateLimit();
9616
9738
  init_Dlp();
9617
- init_Stats();
9618
9739
  init_Audit();
9619
9740
  init_Agents();
9620
9741
  init_Settings();
@@ -9625,7 +9746,6 @@ var init_App = __esm({
9625
9746
  { label: "Policies", Panel: PoliciesPanel },
9626
9747
  { label: "Rate Limit", Panel: RateLimitPanel },
9627
9748
  { label: "DLP", Panel: DlpPanel },
9628
- { label: "Stats", Panel: StatsPanel },
9629
9749
  { label: "Audit", Panel: AuditPanel },
9630
9750
  { label: "Settings", Panel: SettingsPanel }
9631
9751
  ];
@@ -9633,9 +9753,9 @@ var init_App = __esm({
9633
9753
  HELP = [
9634
9754
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
9635
9755
  ["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)"]]],
9636
- ["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"]]],
9756
+ ["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"]]],
9637
9757
  ["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"]]],
9638
- ["Audit", [["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry detail"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
9758
+ ["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"]]],
9639
9759
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
9640
9760
  ];
9641
9761
  }
@@ -9647,7 +9767,7 @@ __export(tui_exports, {
9647
9767
  launchTui: () => launchTui
9648
9768
  });
9649
9769
  import { render } from "ink";
9650
- import { jsx as jsx11 } from "react/jsx-runtime";
9770
+ import { jsx as jsx10 } from "react/jsx-runtime";
9651
9771
  async function launchTui() {
9652
9772
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
9653
9773
  process.stderr.write(
@@ -9661,7 +9781,7 @@ async function launchTui() {
9661
9781
  }
9662
9782
  process.stdout.write("\x1B[?1049h\x1B[H");
9663
9783
  try {
9664
- const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
9784
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
9665
9785
  await waitUntilExit();
9666
9786
  } finally {
9667
9787
  process.stdout.write("\x1B[?1049l");
@@ -10364,8 +10484,8 @@ var init_agents2 = __esm({
10364
10484
 
10365
10485
  // src/commands/doctor.ts
10366
10486
  import { existsSync as existsSync4, statSync as statSync2 } from "fs";
10367
- import { homedir as homedir5 } from "os";
10368
- import { join as join7 } from "path";
10487
+ import { homedir as homedir6 } from "os";
10488
+ import { join as join8 } from "path";
10369
10489
  async function run6(argv) {
10370
10490
  const { flags } = parse(argv);
10371
10491
  const json = flagBool(flags, "json");
@@ -10425,14 +10545,14 @@ var init_doctor = __esm({
10425
10545
  init_api_client();
10426
10546
  init_format();
10427
10547
  init_args();
10428
- LOCAL_LOG2 = join7(homedir5(), ".solongate", "local-logs", "solongate-audit.jsonl");
10548
+ LOCAL_LOG2 = join8(homedir6(), ".solongate", "local-logs", "solongate-audit.jsonl");
10429
10549
  }
10430
10550
  });
10431
10551
 
10432
10552
  // src/commands/watch.ts
10433
10553
  import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readSync as readSync2, statSync as statSync3 } from "fs";
10434
- import { homedir as homedir6 } from "os";
10435
- import { join as join8 } from "path";
10554
+ import { homedir as homedir7 } from "os";
10555
+ import { join as join9 } from "path";
10436
10556
  function tailLocal(file, maxBytes = 131072) {
10437
10557
  try {
10438
10558
  const size = statSync3(file).size;
@@ -10539,7 +10659,7 @@ var init_watch = __esm({
10539
10659
  init_cli_utils();
10540
10660
  init_args();
10541
10661
  init_format();
10542
- LOCAL_LOG3 = join8(homedir6(), ".solongate", "local-logs", "solongate-audit.jsonl");
10662
+ LOCAL_LOG3 = join9(homedir7(), ".solongate", "local-logs", "solongate-audit.jsonl");
10543
10663
  trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
10544
10664
  time = (ms) => new Date(ms).toTimeString().slice(0, 8);
10545
10665
  }
@@ -10842,8 +10962,8 @@ __export(global_install_exports, {
10842
10962
  unlockProtected: () => unlockProtected
10843
10963
  });
10844
10964
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync4, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
10845
- import { resolve as resolve4, join as join9, dirname } from "path";
10846
- import { homedir as homedir7 } from "os";
10965
+ import { resolve as resolve4, join as join10, dirname } from "path";
10966
+ import { homedir as homedir8 } from "os";
10847
10967
  import { fileURLToPath } from "url";
10848
10968
  import { createInterface } from "readline";
10849
10969
  import { execFileSync as execFileSync2 } from "child_process";
@@ -10906,10 +11026,10 @@ function unlockFile(file) {
10906
11026
  function protectedTargets() {
10907
11027
  const p = globalPaths();
10908
11028
  return [
10909
- join9(p.hooksDir, "guard.mjs"),
10910
- join9(p.hooksDir, "audit.mjs"),
10911
- join9(p.hooksDir, "stop.mjs"),
10912
- join9(p.hooksDir, "shield.mjs"),
11029
+ join10(p.hooksDir, "guard.mjs"),
11030
+ join10(p.hooksDir, "audit.mjs"),
11031
+ join10(p.hooksDir, "stop.mjs"),
11032
+ join10(p.hooksDir, "shield.mjs"),
10913
11033
  p.configPath,
10914
11034
  p.settingsPath
10915
11035
  ];
@@ -10921,25 +11041,25 @@ function unlockProtected() {
10921
11041
  for (const f of protectedTargets()) unlockFile(f);
10922
11042
  }
10923
11043
  function globalPaths() {
10924
- const home = homedir7();
10925
- const sgDir = join9(home, ".solongate");
10926
- const hooksDir = join9(sgDir, "hooks");
10927
- const claudeDir = join9(home, ".claude");
11044
+ const home = homedir8();
11045
+ const sgDir = join10(home, ".solongate");
11046
+ const hooksDir = join10(sgDir, "hooks");
11047
+ const claudeDir = join10(home, ".claude");
10928
11048
  return {
10929
11049
  home,
10930
11050
  sgDir,
10931
11051
  hooksDir,
10932
11052
  claudeDir,
10933
- settingsPath: join9(claudeDir, "settings.json"),
10934
- backupPath: join9(claudeDir, "settings.solongate.bak"),
10935
- configPath: join9(sgDir, "cloud-guard.json")
11053
+ settingsPath: join10(claudeDir, "settings.json"),
11054
+ backupPath: join10(claudeDir, "settings.solongate.bak"),
11055
+ configPath: join10(sgDir, "cloud-guard.json")
10936
11056
  };
10937
11057
  }
10938
11058
  function readHook(filename) {
10939
- return readFileSync7(join9(HOOKS_DIR, filename), "utf-8");
11059
+ return readFileSync7(join10(HOOKS_DIR, filename), "utf-8");
10940
11060
  }
10941
11061
  function readGuard() {
10942
- const bundled = join9(HOOKS_DIR, "guard.bundled.mjs");
11062
+ const bundled = join10(HOOKS_DIR, "guard.bundled.mjs");
10943
11063
  return existsSync6(bundled) ? readFileSync7(bundled, "utf-8") : readHook("guard.mjs");
10944
11064
  }
10945
11065
  function ask(question) {
@@ -10994,7 +11114,7 @@ function shimTargets() {
10994
11114
  return [];
10995
11115
  }
10996
11116
  }
10997
- return [".bashrc", ".zshrc", ".profile"].map((f) => join9(homedir7(), f)).filter((f) => existsSync6(f));
11117
+ return [".bashrc", ".zshrc", ".profile"].map((f) => join10(homedir8(), f)).filter((f) => existsSync6(f));
10998
11118
  }
10999
11119
  function writeShimBlock(file, block2) {
11000
11120
  const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
@@ -11059,12 +11179,12 @@ async function runGlobalInstall(opts = {}) {
11059
11179
  mkdirSync4(p.hooksDir, { recursive: true });
11060
11180
  mkdirSync4(p.claudeDir, { recursive: true });
11061
11181
  unlockProtected();
11062
- writeFileSync4(join9(p.hooksDir, "guard.mjs"), readGuard());
11063
- writeFileSync4(join9(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11064
- writeFileSync4(join9(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11065
- writeFileSync4(join9(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11182
+ writeFileSync4(join10(p.hooksDir, "guard.mjs"), readGuard());
11183
+ writeFileSync4(join10(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11184
+ writeFileSync4(join10(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11185
+ writeFileSync4(join10(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11066
11186
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
11067
- installClaudeShim(join9(p.hooksDir, "shield.mjs"));
11187
+ installClaudeShim(join10(p.hooksDir, "shield.mjs"));
11068
11188
  writeFileSync4(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11069
11189
  console.log(` Wrote ${p.configPath}`);
11070
11190
  let existing = {};
@@ -11080,9 +11200,9 @@ async function runGlobalInstall(opts = {}) {
11080
11200
  existing = {};
11081
11201
  }
11082
11202
  }
11083
- const guardAbs = join9(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
11084
- const auditAbs = join9(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
11085
- const stopAbs = join9(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
11203
+ const guardAbs = join10(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
11204
+ const auditAbs = join10(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
11205
+ const stopAbs = join10(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
11086
11206
  const nodeBin = process.execPath.replace(/\\/g, "/");
11087
11207
  const call = process.platform === "win32" ? "& " : "";
11088
11208
  const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
@@ -11276,9 +11396,9 @@ import { spawn as spawn3 } from "child_process";
11276
11396
  import { URL as URL2 } from "url";
11277
11397
  import { readFileSync as readFileSync8, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11278
11398
  import { resolve as resolve5 } from "path";
11279
- import { homedir as homedir8 } from "os";
11399
+ import { homedir as homedir9 } from "os";
11280
11400
  function findCacheFile() {
11281
- const dir = resolve5(homedir8(), ".solongate");
11401
+ const dir = resolve5(homedir9(), ".solongate");
11282
11402
  const envSel = process.env.SOLONGATE_AGENT_ID;
11283
11403
  if (envSel) {
11284
11404
  const f = resolve5(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
@@ -11593,8 +11713,8 @@ __export(logs_server_exports, {
11593
11713
  });
11594
11714
  import { createServer as createServer2 } from "http";
11595
11715
  import { readFileSync as readFileSync9, statSync as statSync5 } from "fs";
11596
- import { resolve as resolve6, join as join10, isAbsolute } from "path";
11597
- import { homedir as homedir9 } from "os";
11716
+ import { resolve as resolve6, join as join11, isAbsolute } from "path";
11717
+ import { homedir as homedir10 } from "os";
11598
11718
  import { readdirSync as readdirSync2 } from "fs";
11599
11719
  function allowedOrigins() {
11600
11720
  const base = [
@@ -11611,15 +11731,15 @@ function resolveLocalLogDir(rawPath) {
11611
11731
  const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
11612
11732
  if (!dir) return null;
11613
11733
  if (isAbsolute(dir)) return dir;
11614
- return resolve6(homedir9(), ".solongate", "local-logs");
11734
+ return resolve6(homedir10(), ".solongate", "local-logs");
11615
11735
  }
11616
11736
  async function findLogDir() {
11617
- const base = resolve6(homedir9(), ".solongate");
11737
+ const base = resolve6(homedir10(), ".solongate");
11618
11738
  try {
11619
11739
  const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
11620
11740
  for (const f of files) {
11621
11741
  try {
11622
- const c2 = JSON.parse(readFileSync9(join10(base, f), "utf-8"));
11742
+ const c2 = JSON.parse(readFileSync9(join11(base, f), "utf-8"));
11623
11743
  const p = c2?.security?.localLogs?.path;
11624
11744
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
11625
11745
  } catch {
@@ -11628,7 +11748,7 @@ async function findLogDir() {
11628
11748
  } catch {
11629
11749
  }
11630
11750
  try {
11631
- const cfgRaw = readFileSync9(join10(base, "cloud-guard.json"), "utf-8");
11751
+ const cfgRaw = readFileSync9(join11(base, "cloud-guard.json"), "utf-8");
11632
11752
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
11633
11753
  if (apiKey) {
11634
11754
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -11657,7 +11777,7 @@ function setCors(req, res) {
11657
11777
  }
11658
11778
  function fileInfo(dir) {
11659
11779
  if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
11660
- const file = join10(dir, LOG_FILENAME);
11780
+ const file = join11(dir, LOG_FILENAME);
11661
11781
  try {
11662
11782
  const st = statSync5(file);
11663
11783
  return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
@@ -12108,7 +12228,7 @@ var init_inject = __esm({
12108
12228
  // src/create.ts
12109
12229
  var create_exports = {};
12110
12230
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
12111
- import { resolve as resolve8, join as join11 } from "path";
12231
+ import { resolve as resolve8, join as join12 } from "path";
12112
12232
  import { execSync as execSync2 } from "child_process";
12113
12233
  function withSpinner(message, fn) {
12114
12234
  const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
@@ -12188,7 +12308,7 @@ EXAMPLES
12188
12308
  }
12189
12309
  function createProject(dir, name, _policy) {
12190
12310
  writeFileSync6(
12191
- join11(dir, "package.json"),
12311
+ join12(dir, "package.json"),
12192
12312
  JSON.stringify(
12193
12313
  {
12194
12314
  name,
@@ -12218,7 +12338,7 @@ function createProject(dir, name, _policy) {
12218
12338
  ) + "\n"
12219
12339
  );
12220
12340
  writeFileSync6(
12221
- join11(dir, "tsconfig.json"),
12341
+ join12(dir, "tsconfig.json"),
12222
12342
  JSON.stringify(
12223
12343
  {
12224
12344
  compilerOptions: {
@@ -12238,9 +12358,9 @@ function createProject(dir, name, _policy) {
12238
12358
  2
12239
12359
  ) + "\n"
12240
12360
  );
12241
- mkdirSync5(join11(dir, "src"), { recursive: true });
12361
+ mkdirSync5(join12(dir, "src"), { recursive: true });
12242
12362
  writeFileSync6(
12243
- join11(dir, "src", "index.ts"),
12363
+ join12(dir, "src", "index.ts"),
12244
12364
  `#!/usr/bin/env node
12245
12365
 
12246
12366
  console.log = (...args: unknown[]) => {
@@ -12282,7 +12402,7 @@ console.log('Press Ctrl+C to stop.');
12282
12402
  `
12283
12403
  );
12284
12404
  writeFileSync6(
12285
- join11(dir, ".mcp.json"),
12405
+ join12(dir, ".mcp.json"),
12286
12406
  JSON.stringify(
12287
12407
  {
12288
12408
  mcpServers: {
@@ -12300,12 +12420,12 @@ console.log('Press Ctrl+C to stop.');
12300
12420
  ) + "\n"
12301
12421
  );
12302
12422
  writeFileSync6(
12303
- join11(dir, ".env"),
12423
+ join12(dir, ".env"),
12304
12424
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
12305
12425
  `
12306
12426
  );
12307
12427
  writeFileSync6(
12308
- join11(dir, ".gitignore"),
12428
+ join12(dir, ".gitignore"),
12309
12429
  `node_modules/
12310
12430
  dist/
12311
12431
  *.solongate-backup