@solongate/proxy 0.81.23 → 0.81.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6814,7 +6814,7 @@ function DataView({
6814
6814
  "\u2717 ",
6815
6815
  error
6816
6816
  ] });
6817
- if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: "Loading\u2026" });
6817
+ if (loading) return /* @__PURE__ */ jsx(Text, { color: theme.warn, children: "\u27F3 loading\u2026" });
6818
6818
  if (empty) return /* @__PURE__ */ jsx(Text, { color: theme.dim, children: emptyText ?? "No data." });
6819
6819
  return /* @__PURE__ */ jsx(Fragment, { children });
6820
6820
  }
@@ -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"() {
@@ -7164,6 +7203,7 @@ function useLoader(fn, deps = []) {
7164
7203
  const [loading, setLoading] = useState(true);
7165
7204
  const [nonce, setNonce] = useState(0);
7166
7205
  const alive = useRef(true);
7206
+ const quiet = useRef(false);
7167
7207
  const fnRef = useRef(fn);
7168
7208
  fnRef.current = fn;
7169
7209
  useEffect(() => {
@@ -7173,7 +7213,8 @@ function useLoader(fn, deps = []) {
7173
7213
  };
7174
7214
  }, []);
7175
7215
  useEffect(() => {
7176
- setLoading(true);
7216
+ if (!quiet.current) setLoading(true);
7217
+ quiet.current = false;
7177
7218
  fnRef.current().then((d) => {
7178
7219
  if (!alive.current) return;
7179
7220
  setData(d);
@@ -7186,7 +7227,11 @@ function useLoader(fn, deps = []) {
7186
7227
  });
7187
7228
  }, [nonce, ...deps]);
7188
7229
  const reload = useCallback(() => setNonce((n) => n + 1), []);
7189
- return { data, error, loading, reload };
7230
+ const reloadQuiet = useCallback(() => {
7231
+ quiet.current = true;
7232
+ setNonce((n) => n + 1);
7233
+ }, []);
7234
+ return { data, error, loading, reload, reloadQuiet };
7190
7235
  }
7191
7236
  function usePoll(reload, intervalMs, enabled = true) {
7192
7237
  useEffect(() => {
@@ -7222,26 +7267,11 @@ var init_hooks = __esm({
7222
7267
  // src/tui/panels/Live.tsx
7223
7268
  import { Box as Box2, Text as Text2, useInput } from "ink";
7224
7269
  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";
7270
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
7271
+ import { homedir as homedir5 } from "os";
7272
+ import { join as join7 } from "path";
7228
7273
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7229
7274
  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
7275
  function extractTarget(detail) {
7246
7276
  try {
7247
7277
  const j = JSON.parse(detail);
@@ -7505,13 +7535,13 @@ function LivePanel({ active: active2 }) {
7505
7535
  const insights = useLoader(() => api.stats.securityInsights(7));
7506
7536
  const guard = useLoader(() => api.settings.getGuardStatus());
7507
7537
  usePoll(() => {
7508
- if (!paused()) sessions.reload();
7538
+ if (!paused()) sessions.reloadQuiet();
7509
7539
  }, 8e3, active2);
7510
7540
  usePoll(() => {
7511
- if (!paused()) insights.reload();
7541
+ if (!paused()) insights.reloadQuiet();
7512
7542
  }, 2e4, active2);
7513
7543
  usePoll(() => {
7514
- if (!paused()) guard.reload();
7544
+ if (!paused()) guard.reloadQuiet();
7515
7545
  }, 6e4, active2);
7516
7546
  useEffect2(() => {
7517
7547
  if (!detail) return;
@@ -7785,9 +7815,9 @@ function LivePanel({ active: active2 }) {
7785
7815
  else if (input === "x") toggleSignal("dlp");
7786
7816
  else if (input === "r") toggleSignal("ratelimit");
7787
7817
  else if (input === "e") {
7788
- const file = join6(homedir4(), ".solongate", "live-export.jsonl");
7818
+ const file = join7(homedir5(), ".solongate", "live-export.jsonl");
7789
7819
  try {
7790
- mkdirSync3(join6(homedir4(), ".solongate"), { recursive: true });
7820
+ mkdirSync3(join7(homedir5(), ".solongate"), { recursive: true });
7791
7821
  writeFileSync3(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7792
7822
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
7793
7823
  } catch (err2) {
@@ -8103,10 +8133,11 @@ function LivePanel({ active: active2 }) {
8103
8133
  ] })
8104
8134
  ] });
8105
8135
  }
8106
- var CONFIG, SPIN, BG, DIM_FLOOR, LOCAL_LOG, RING, hhmmss, fmtUp, FILTERS, sessStatus, STATUS_STYLE, LIVE_HELP;
8136
+ var CONFIG, SPIN, BG, DIM_FLOOR, RING, hhmmss, fmtUp, FILTERS, sessStatus, STATUS_STYLE, LIVE_HELP;
8107
8137
  var init_Live = __esm({
8108
8138
  "src/tui/panels/Live.tsx"() {
8109
8139
  "use strict";
8140
+ init_local_log();
8110
8141
  init_api_client();
8111
8142
  init_config2();
8112
8143
  init_notify();
@@ -8116,8 +8147,7 @@ var init_Live = __esm({
8116
8147
  SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
8117
8148
  BG = "#12234f";
8118
8149
  DIM_FLOOR = "#233457";
8119
- LOCAL_LOG = join6(homedir4(), ".solongate", "local-logs", "solongate-audit.jsonl");
8120
- RING = join6(process.cwd(), ".solongate", ".eval-ring.jsonl");
8150
+ RING = join7(process.cwd(), ".solongate", ".eval-ring.jsonl");
8121
8151
  hhmmss = (ts) => {
8122
8152
  const d = new Date(ts);
8123
8153
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
@@ -8200,7 +8230,6 @@ function PoliciesPanel({ focused }) {
8200
8230
  const [pi, setPi] = useState3(0);
8201
8231
  const [ri, setRi] = useState3(0);
8202
8232
  const [fi, setFi] = useState3(0);
8203
- const [vi, setVi] = useState3(0);
8204
8233
  const [rules, setRules] = useState3([]);
8205
8234
  const [mode, setMode] = useState3("denylist");
8206
8235
  const [dirty, setDirty] = useState3(false);
@@ -8209,15 +8238,6 @@ function PoliciesPanel({ focused }) {
8209
8238
  const [status, setStatus] = useState3(null);
8210
8239
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
8211
8240
  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
8241
  useEffect3(() => {
8222
8242
  if (detail.data) {
8223
8243
  setRules(detail.data.rules);
@@ -8315,32 +8335,10 @@ function PoliciesPanel({ focused }) {
8315
8335
  } else if (input === "D") {
8316
8336
  setStatus("Dry-running\u2026");
8317
8337
  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
8338
  } else if (input === "s") void save();
8322
8339
  else if (input === "x") discard();
8323
8340
  return;
8324
8341
  }
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
8342
  const rule2 = rules[ri];
8345
8343
  if (!rule2) return setView("rules");
8346
8344
  const field = FIELDS[fi];
@@ -8381,8 +8379,7 @@ function PoliciesPanel({ focused }) {
8381
8379
  p.mode.padEnd(10),
8382
8380
  " ",
8383
8381
  p.rules.length,
8384
- " rules \xB7 v",
8385
- p.version
8382
+ " rules"
8386
8383
  ] }),
8387
8384
  p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
8388
8385
  ] }, p.id)) }),
@@ -8398,7 +8395,7 @@ function PoliciesPanel({ focused }) {
8398
8395
  /* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
8399
8396
  dirtyTag
8400
8397
  ] }),
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" }),
8398
+ /* @__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
8399
  /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
8403
8400
  Table,
8404
8401
  {
@@ -8424,50 +8421,6 @@ function PoliciesPanel({ focused }) {
8424
8421
  status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
8425
8422
  ] }) });
8426
8423
  }
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
8424
  const rule = rules[ri];
8472
8425
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
8473
8426
  /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -8880,177 +8833,202 @@ var init_Dlp = __esm({
8880
8833
  }
8881
8834
  });
8882
8835
 
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
8836
  // src/tui/panels/Audit.tsx
8997
- import { Box as Box7, Text as Text7, useInput as useInput5 } from "ink";
8837
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
8998
8838
  import TextInput4 from "ink-text-input";
8999
8839
  import { useState as useState6 } from "react";
9000
- import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
8840
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
8841
+ function loadLocalRows() {
8842
+ return parseLocalLines(tailLines(LOCAL_LOG, LOCAL_MAX_BYTES)).map((j, i) => ({
8843
+ id: "l:" + j.at + ":" + i,
8844
+ at: j.at,
8845
+ tool: j.tool ?? "?",
8846
+ decision: j.decision ?? "ALLOW",
8847
+ permission: j.permission ?? "\u2014",
8848
+ trust: j.trust_level ?? "\u2014",
8849
+ agent: j.agent_name ?? null,
8850
+ session: j.session_id ?? null,
8851
+ reason: j.reason ?? null,
8852
+ rule: j.matched_rule_id ?? null,
8853
+ evalMs: j.evaluation_time_ms ?? null,
8854
+ dlp: j.dlp ? Array.isArray(j.dlp) ? j.dlp.map(String) : ["dlp"] : [],
8855
+ burst: !!j.rate_limit_burst,
8856
+ args: j.arguments ? JSON.stringify(j.arguments) : null
8857
+ })).sort((a, b) => b.at - a.at);
8858
+ }
9001
8859
  function AuditPanel({ active: active2, focused }) {
9002
8860
  const { cols, rows } = usePanelSize();
8861
+ const [source, setSource] = useState6("cloud");
8862
+ const [view, setView] = useState6("logs");
9003
8863
  const [di, setDi] = useState6(0);
9004
8864
  const [gi, setGi] = useState6(0);
9005
8865
  const [tool, setTool] = useState6("");
9006
8866
  const [agent, setAgent] = useState6("");
9007
8867
  const [search, setSearch] = useState6("");
8868
+ const [sessFilter, setSessFilter] = useState6("");
8869
+ const [page, setPage] = useState6(0);
9008
8870
  const [sel, setSel] = useState6(0);
9009
- const [view, setView] = useState6("list");
9010
8871
  const [detailScroll, setDetailScroll] = useState6(0);
9011
8872
  const [editing, setEditing] = useState6(null);
8873
+ const [si, setSi] = useState6(0);
8874
+ const [sessSearch, setSessSearch] = useState6("");
8875
+ const [sessSel, setSessSel] = useState6(0);
8876
+ const toTop = () => setSel(0);
8877
+ const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
8878
+ const tsQ = useLoader(() => source === "cloud" ? api.stats.timeseries({ period: "24h" }) : Promise.resolve(null), [source]);
8879
+ usePoll(() => {
8880
+ statsQ.reloadQuiet();
8881
+ tsQ.reloadQuiet();
8882
+ }, 15e3, active2 && source === "cloud");
9012
8883
  const query = {
9013
8884
  filter: DECISIONS[di],
9014
8885
  signal: SIGNALS[gi],
9015
8886
  tool: tool || void 0,
9016
8887
  agent_name: agent || void 0,
9017
8888
  search: search || void 0,
9018
- limit: FETCH_LIMIT
8889
+ session_id: sessFilter || void 0,
8890
+ limit: PAGE,
8891
+ offset: page * PAGE
9019
8892
  };
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))];
8893
+ const cloudQ = useLoader(
8894
+ () => source === "cloud" ? api.audit.list(query) : Promise.resolve(null),
8895
+ [source, di, gi, tool, agent, search, sessFilter, page]
8896
+ );
8897
+ usePoll(cloudQ.reloadQuiet, 6e3, active2 && source === "cloud" && view === "logs" && !editing && page === 0);
8898
+ const localQ = useLoader(() => source === "local" ? Promise.resolve(loadLocalRows()) : Promise.resolve(null), [source]);
8899
+ usePoll(localQ.reloadQuiet, 6e3, active2 && source === "local" && view === "logs" && !editing && page === 0);
8900
+ let pageRows = [];
8901
+ let total = 0;
8902
+ if (source === "cloud") {
8903
+ pageRows = (cloudQ.data?.entries ?? []).map(cloudRow).sort((a, b) => b.at - a.at);
8904
+ total = cloudQ.data?.total ?? 0;
8905
+ } else {
8906
+ const all = localQ.data ?? [];
8907
+ const q = search.trim().toLowerCase();
8908
+ const filtered = all.filter((r) => {
8909
+ if (DECISIONS[di] && r.decision !== DECISIONS[di]) return false;
8910
+ if (SIGNALS[gi] === "dlp" && r.dlp.length === 0) return false;
8911
+ if (SIGNALS[gi] === "ratelimit" && !r.burst) return false;
8912
+ if (tool && !r.tool.toLowerCase().includes(tool.toLowerCase())) return false;
8913
+ if (agent && (r.agent ?? "").toLowerCase() !== agent.toLowerCase()) return false;
8914
+ if (sessFilter && r.session !== sessFilter) return false;
8915
+ if (q && !`${r.tool} ${r.agent ?? ""} ${r.reason ?? ""} ${r.args ?? ""}`.toLowerCase().includes(q)) return false;
8916
+ return true;
8917
+ });
8918
+ total = filtered.length;
8919
+ pageRows = filtered.slice(page * PAGE, page * PAGE + PAGE);
8920
+ }
8921
+ const pages = Math.max(1, Math.ceil(total / PAGE));
8922
+ const current = pageRows[Math.min(sel, Math.max(0, pageRows.length - 1))];
8923
+ const agentsQ = useLoader(
8924
+ () => source === "cloud" ? api.agents.live({ limit: 100, includeDeactivated: true }) : Promise.resolve(null),
8925
+ [source]
8926
+ );
8927
+ usePoll(agentsQ.reloadQuiet, 8e3, active2 && source === "cloud" && view === "sessions");
8928
+ let sessions = [];
8929
+ if (source === "cloud") {
8930
+ sessions = (agentsQ.data?.agents ?? []).map((a) => ({
8931
+ id: a.session_id,
8932
+ agent: a.agent_name ?? a.session_id,
8933
+ status: a.status === "deactivated" ? "ended" : a.status,
8934
+ calls: a.total_calls,
8935
+ denies: a.denied_calls,
8936
+ dlp: a.dlp_events,
8937
+ trust: a.trust_score,
8938
+ lastAt: Date.parse(a.last_seen_at)
8939
+ }));
8940
+ } else {
8941
+ const bySess = /* @__PURE__ */ new Map();
8942
+ for (const r of localQ.data ?? []) {
8943
+ if (!r.session) continue;
8944
+ 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 };
8945
+ cur.calls++;
8946
+ if (r.decision !== "ALLOW") cur.denies++;
8947
+ if (r.dlp.length) cur.dlp++;
8948
+ cur.lastAt = Math.max(cur.lastAt, r.at);
8949
+ if (r.agent) cur.agent = r.agent;
8950
+ bySess.set(r.session, cur);
8951
+ }
8952
+ sessions = [...bySess.values()];
8953
+ }
8954
+ const sq = sessSearch.trim().toLowerCase();
8955
+ 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);
8956
+ const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
9024
8957
  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]
8958
+ () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
8959
+ [view, current?.session, source]
9027
8960
  );
9028
8961
  useInput5(
9029
8962
  (input, key) => {
9030
8963
  if (view === "detail") {
9031
- if (key.leftArrow || key.escape) setView("list");
8964
+ if (key.leftArrow || key.escape) setView("logs");
9032
8965
  else if (key.upArrow) setDetailScroll((n) => Math.max(0, n - 1));
9033
8966
  else if (key.downArrow) setDetailScroll((n) => n + 1);
9034
8967
  else if (key.pageUp) setDetailScroll((n) => Math.max(0, n - 10));
9035
8968
  else if (key.pageDown) setDetailScroll((n) => n + 10);
9036
8969
  return;
9037
8970
  }
9038
- const clearSel = () => setSel(0);
8971
+ if (input === "s") {
8972
+ setSource((s) => s === "cloud" ? "local" : "cloud");
8973
+ setPage(0);
8974
+ toTop();
8975
+ setSessSel(0);
8976
+ return;
8977
+ }
8978
+ if (input === "v") {
8979
+ setView((v) => v === "logs" ? "sessions" : "logs");
8980
+ return;
8981
+ }
8982
+ if (view === "sessions") {
8983
+ if (key.upArrow) setSessSel((n) => Math.max(0, n - 1));
8984
+ else if (key.downArrow) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 1));
8985
+ else if (key.pageUp) setSessSel((n) => Math.max(0, n - 10));
8986
+ else if (key.pageDown) setSessSel((n) => Math.min(sessionsFiltered.length - 1, n + 10));
8987
+ else if (key.return) {
8988
+ if (currentSess) {
8989
+ setSessFilter(currentSess.id);
8990
+ setPage(0);
8991
+ toTop();
8992
+ setView("logs");
8993
+ }
8994
+ } else if (input === "f") {
8995
+ setSi((n) => (n + 1) % SESS_STATUS.length);
8996
+ setSessSel(0);
8997
+ } else if (input === "/") setEditing("sess-search");
8998
+ else if (input === "c") {
8999
+ setSi(0);
9000
+ setSessSearch("");
9001
+ setSessSel(0);
9002
+ }
9003
+ return;
9004
+ }
9039
9005
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
9040
- else if (key.downArrow) setSel((n) => Math.min(entries.length - 1, n + 1));
9006
+ else if (key.downArrow) setSel((n) => Math.min(pageRows.length - 1, n + 1));
9041
9007
  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));
9043
- else if (key.return || key.rightArrow) {
9008
+ else if (key.pageDown) setSel((n) => Math.min(pageRows.length - 1, n + 10));
9009
+ else if (key.return) {
9044
9010
  if (current) {
9045
9011
  setDetailScroll(0);
9046
9012
  setView("detail");
9047
9013
  }
9014
+ } else if (key.rightArrow) {
9015
+ if (page < pages - 1) {
9016
+ setPage(page + 1);
9017
+ toTop();
9018
+ }
9019
+ } else if (key.leftArrow) {
9020
+ if (page > 0) {
9021
+ setPage(page - 1);
9022
+ toTop();
9023
+ }
9048
9024
  } else if (input === "f") {
9049
9025
  setDi((n) => (n + 1) % DECISIONS.length);
9050
- clearSel();
9026
+ setPage(0);
9027
+ toTop();
9051
9028
  } else if (input === "g") {
9052
9029
  setGi((n) => (n + 1) % SIGNALS.length);
9053
- clearSel();
9030
+ setPage(0);
9031
+ toTop();
9054
9032
  } else if (input === "t") setEditing("tool");
9055
9033
  else if (input === "n") setEditing("agent");
9056
9034
  else if (input === "/") setEditing("search");
@@ -9060,46 +9038,89 @@ function AuditPanel({ active: active2, focused }) {
9060
9038
  setTool("");
9061
9039
  setAgent("");
9062
9040
  setSearch("");
9063
- clearSel();
9041
+ setSessFilter("");
9042
+ setPage(0);
9043
+ toTop();
9064
9044
  }
9065
9045
  },
9066
9046
  { isActive: focused && !editing }
9067
9047
  );
9048
+ const points = tsQ.data?.timeseries ?? [];
9049
+ const localAll = localQ.data ?? [];
9050
+ const strip = source === "cloud" ? /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
9051
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
9052
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls \xB7 " }),
9053
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
9054
+ statsQ.data?.allowed ?? "\xB7\xB7\xB7",
9055
+ " allow"
9056
+ ] }),
9057
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
9058
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
9059
+ statsQ.data?.denied ?? "\xB7\xB7",
9060
+ " deny"
9061
+ ] }),
9062
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies \xB7 ${statsQ.data?.registered_tools ?? "\xB7"} tools \xB7 24h ` }),
9063
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: sparkline(points.map((p) => p.total), 24) }),
9064
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " deny " }),
9065
+ /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: sparkline(points.map((p) => p.denied), 24) })
9066
+ ] }) : /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
9067
+ /* @__PURE__ */ jsx6(Text6, { bold: true, children: localAll.length }),
9068
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls in local file \xB7 " }),
9069
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
9070
+ localAll.filter((r) => r.decision === "ALLOW").length,
9071
+ " allow"
9072
+ ] }),
9073
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
9074
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
9075
+ localAll.filter((r) => r.decision !== "ALLOW").length,
9076
+ " deny"
9077
+ ] }),
9078
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9079
+ " \xB7 ",
9080
+ LOCAL_LOG
9081
+ ] })
9082
+ ] });
9083
+ const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
9084
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
9085
+ /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
9086
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
9087
+ ] });
9068
9088
  if (view === "detail" && current) {
9069
- const sessionCalls = sessionQ.data?.entries ?? [];
9089
+ const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
9070
9090
  const bodyW = Math.max(20, cols - 2);
9071
9091
  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;
9092
+ const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
9093
+ const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
9074
9094
  const reasonShown = reasonLines.slice(0, 4);
9075
9095
  const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1);
9076
9096
  const maxScroll = Math.max(0, argLines.length - argBudget);
9077
9097
  const off = Math.min(detailScroll, maxScroll);
9078
9098
  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 })
9099
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9100
+ /* @__PURE__ */ jsxs6(Box6, { children: [
9101
+ /* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
9102
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
9103
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust + " " }),
9104
+ srcChip
9084
9105
  ] }),
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: [
9106
+ reasonShown.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
9107
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "reason " }),
9108
+ /* @__PURE__ */ jsx6(Text6, { children: l.slice("reason: ".length) })
9109
+ ] }) : /* @__PURE__ */ jsx6(Text6, { children: " " + l }) }, "r" + i)),
9110
+ /* @__PURE__ */ jsx6(Detail, { label: "rule", value: current.rule ?? "\u2014" }),
9111
+ /* @__PURE__ */ jsx6(Detail, { label: "agent", value: current.agent ?? "\u2014" }),
9112
+ /* @__PURE__ */ jsx6(Detail, { label: "session", value: current.session ?? "\u2014" }),
9113
+ /* @__PURE__ */ jsx6(Detail, { label: "dlp", value: current.dlp.length ? current.dlp.join(", ") : "none", color: current.dlp.length ? theme.bad : void 0 }),
9114
+ /* @__PURE__ */ jsx6(Detail, { label: "when", value: new Date(current.at).toLocaleString() + (current.evalMs != null ? ` \xB7 eval ${current.evalMs}ms` : "") }),
9115
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
9116
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
9117
+ current.session ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9118
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9098
9119
  "Session \xB7 ",
9099
9120
  sessionCalls.length,
9100
9121
  " calls"
9101
9122
  ] }),
9102
- /* @__PURE__ */ jsx7(
9123
+ /* @__PURE__ */ jsx6(
9103
9124
  Table,
9104
9125
  {
9105
9126
  columns: [
@@ -9110,59 +9131,136 @@ function AuditPanel({ active: active2, focused }) {
9110
9131
  ],
9111
9132
  rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
9112
9133
  { value: e.decision, color: decisionColor(e.decision) },
9113
- { value: truncate2(e.tool_name, 20), color: theme.accent },
9134
+ { value: truncate2(e.tool, 20), color: theme.accent },
9114
9135
  { value: truncate2(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
9115
- { value: ago(e.created_at), dim: true }
9136
+ { value: ago(e.at), dim: true }
9116
9137
  ])
9117
9138
  }
9118
9139
  )
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" })
9140
+ ] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no session id)" }),
9141
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to logs" })
9121
9142
  ] });
9122
9143
  }
9123
- const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
9124
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
9144
+ const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
9145
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9125
9146
  label,
9126
9147
  ":"
9127
9148
  ] }),
9128
- /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
9129
- /* @__PURE__ */ jsx7(Text7, { children: " " })
9149
+ /* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
9150
+ /* @__PURE__ */ jsx6(Text6, { children: " " })
9130
9151
  ] });
9131
- const headerRows = 4 + (editing ? 1 : 0);
9152
+ if (view === "sessions") {
9153
+ const headerRows2 = 5 + (editing ? 1 : 0);
9154
+ const listRows2 = Math.max(4, rows - headerRows2);
9155
+ const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
9156
+ const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
9157
+ const win = sessionsFiltered.slice(start2, start2 + listRows2);
9158
+ const loading2 = source === "cloud" ? agentsQ.loading : localQ.loading;
9159
+ return /* @__PURE__ */ jsx6(DataView, { loading: loading2, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9160
+ strip,
9161
+ /* @__PURE__ */ jsxs6(Box6, { children: [
9162
+ srcChip,
9163
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
9164
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 logs (v) " }),
9165
+ chip("status", SESS_STATUS[si] ?? "all", si !== 0),
9166
+ chip("search", sessSearch || "\xB7", !!sessSearch)
9167
+ ] }),
9168
+ /* @__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" }),
9169
+ editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
9170
+ /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
9171
+ /* @__PURE__ */ jsx6(
9172
+ TextInput4,
9173
+ {
9174
+ value: sessSearch,
9175
+ onChange: (v) => {
9176
+ setSessSearch(v);
9177
+ setSessSel(0);
9178
+ },
9179
+ onSubmit: () => setEditing(null)
9180
+ }
9181
+ )
9182
+ ] }) : null,
9183
+ /* @__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}` : ""}` }),
9184
+ /* @__PURE__ */ jsx6(
9185
+ Table,
9186
+ {
9187
+ columns: [
9188
+ { header: "", width: 2 },
9189
+ { header: "STATUS", width: 8 },
9190
+ { header: "AGENT", width: 18 },
9191
+ { header: "SESSION", width: 10 },
9192
+ { header: "CALLS", width: 6 },
9193
+ { header: "DENY", width: 5 },
9194
+ { header: "DLP", width: 4 },
9195
+ { header: "TRUST", width: 5 },
9196
+ { header: "LAST", width: 6 }
9197
+ ],
9198
+ rows: win.map((r, i) => {
9199
+ const st = STATUS_DOT[r.status];
9200
+ const isSel = start2 + i === selC && focused;
9201
+ return [
9202
+ { value: isSel ? "\u25B8" : "", color: theme.accentBright },
9203
+ { value: `${st.ch} ${r.status}`, color: st.color },
9204
+ { value: truncate2(r.agent, 18), color: theme.accent, bold: isSel },
9205
+ { value: r.id.slice(0, 8), dim: true },
9206
+ { value: String(r.calls) },
9207
+ { value: String(r.denies), color: r.denies ? theme.bad : void 0, dim: !r.denies },
9208
+ { value: String(r.dlp), color: r.dlp ? theme.bad : void 0, dim: !r.dlp },
9209
+ { value: r.trust != null ? String(r.trust) : "\u2014", dim: true },
9210
+ { value: ago(r.lastAt), dim: true }
9211
+ ];
9212
+ })
9213
+ }
9214
+ ),
9215
+ sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9216
+ "(no sessions",
9217
+ source === "local" ? " in the local file" : "",
9218
+ ")"
9219
+ ] }) : null
9220
+ ] }) });
9221
+ }
9222
+ const headerRows = 6 + (editing && editing !== "sess-search" ? 1 : 0);
9132
9223
  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);
9224
+ const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
9225
+ const maxStart = Math.max(0, pageRows.length - listRows);
9135
9226
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
9136
- const windowed = entries.slice(start, start + listRows);
9227
+ const windowed = pageRows.slice(start, start + listRows);
9137
9228
  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: [
9229
+ const loading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
9230
+ return /* @__PURE__ */ jsx6(DataView, { loading, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
9231
+ strip,
9232
+ /* @__PURE__ */ jsxs6(Box6, { children: [
9233
+ srcChip,
9234
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
9235
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 sessions (v) " }),
9140
9236
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
9141
9237
  chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
9142
9238
  chip("tool", tool || "\xB7", !!tool),
9143
9239
  chip("agent", agent || "\xB7", !!agent),
9144
- chip("search", search || "\xB7", !!search)
9240
+ chip("search", search || "\xB7", !!search),
9241
+ sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
9145
9242
  ] }),
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: [
9243
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 f dec \xB7 g sig \xB7 t tool \xB7 n agent \xB7 / search \xB7 s source \xB7 v sessions \xB7 c clear" : "press \u2192 to browse" }),
9244
+ editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
9245
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
9149
9246
  editing,
9150
9247
  ": "
9151
9248
  ] }),
9152
- /* @__PURE__ */ jsx7(
9249
+ /* @__PURE__ */ jsx6(
9153
9250
  TextInput4,
9154
9251
  {
9155
9252
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
9156
9253
  onChange: (v) => {
9157
9254
  (editing === "tool" ? setTool : editing === "agent" ? setAgent : setSearch)(v);
9158
- setSel(0);
9255
+ setPage(0);
9256
+ toTop();
9159
9257
  },
9160
9258
  onSubmit: () => setEditing(null)
9161
9259
  }
9162
9260
  )
9163
9261
  ] }) : 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(
9262
+ /* @__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` : ""}` }),
9263
+ /* @__PURE__ */ jsx6(
9166
9264
  Table,
9167
9265
  {
9168
9266
  columns: [
@@ -9176,141 +9274,75 @@ function AuditPanel({ active: active2, focused }) {
9176
9274
  ],
9177
9275
  rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
9178
9276
  }
9179
- )
9277
+ ),
9278
+ pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9279
+ "(no entries",
9280
+ source === "local" ? " in the local file" : "",
9281
+ " \u2014 c clears filters)"
9282
+ ] }) : null
9180
9283
  ] }) });
9181
9284
  }
9182
9285
  function rowFor(e, active2, reasonW) {
9183
9286
  return [
9184
9287
  { value: active2 ? "\u25B8" : "", color: theme.accentBright },
9185
9288
  { 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 }
9289
+ { value: truncate2(e.tool, 18), color: theme.accent },
9290
+ { value: truncate2(e.agent ?? "\u2014", 14), dim: true },
9291
+ { value: truncate2(e.reason ?? e.args ?? "\u2014", reasonW) },
9292
+ { value: e.dlp.length ? String(e.dlp.length) : "0", color: e.dlp.length ? theme.bad : void 0, dim: !e.dlp.length },
9293
+ { value: ago(e.at), dim: true }
9191
9294
  ];
9192
9295
  }
9193
9296
  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 })
9297
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
9298
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: label.padEnd(9) }),
9299
+ /* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
9197
9300
  ] });
9198
9301
  }
9199
- var DECISIONS, SIGNALS, FETCH_LIMIT;
9302
+ var DECISIONS, SIGNALS, SESS_STATUS, PAGE, LOCAL_MAX_BYTES, cloudRow, sessStatus2, STATUS_DOT;
9200
9303
  var init_Audit = __esm({
9201
9304
  "src/tui/panels/Audit.tsx"() {
9202
9305
  "use strict";
9203
9306
  init_api_client();
9204
9307
  init_components();
9205
9308
  init_hooks();
9309
+ init_local_log();
9206
9310
  init_theme();
9207
9311
  DECISIONS = [void 0, "DENY", "ALLOW"];
9208
9312
  SIGNALS = [void 0, "dlp", "ratelimit"];
9209
- FETCH_LIMIT = 500;
9210
- }
9211
- });
9212
-
9213
- // src/tui/panels/Agents.tsx
9214
- import { Box as Box8, Text as Text8, useInput as useInput6 } from "ink";
9215
- import { useState as useState7 } from "react";
9216
- import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
9217
- function Bar({ label, value, max = 100, width: width2 = 16, color = theme.accent }) {
9218
- 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 })
9224
- ] });
9225
- }
9226
- function AgentsPanel({ focused }) {
9227
- const live2 = useLoader(() => api.agents.live({ limit: 30, includeDeactivated: true }));
9228
- usePoll(live2.reload, 6e3, focused ? false : true);
9229
- const [sel, setSel] = useState7(0);
9230
- const agents = (live2.data?.agents ?? []).filter((a) => a.agent_id);
9231
- const selected = agents[Math.min(sel, Math.max(0, agents.length - 1))];
9232
- const detail = useLoader(() => selected?.agent_id ? api.agents.get(selected.agent_id) : Promise.resolve(null), [selected?.agent_id]);
9233
- useInput6(
9234
- (_input, key) => {
9235
- if (key.upArrow) setSel((n) => Math.max(0, n - 1));
9236
- else if (key.downArrow) setSel((n) => Math.min(agents.length - 1, n + 1));
9237
- },
9238
- { isActive: focused }
9239
- );
9240
- const d = detail.data;
9241
- const base = d?.baseline;
9242
- const radar = base?.radar;
9243
- const anomalies2 = d?.anomalies ?? [];
9244
- const tmap = d?.trust_map;
9245
- const tools = tmap?.tools ?? [];
9246
- const resources = tmap?.resources ?? [];
9247
- 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: [
9253
- a.status === "active" ? "\u25CF" : a.status === "idle" ? "\u25D0" : "\u25CB",
9254
- " "
9255
- ] }),
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}` })
9258
- ] }, a.session_id))
9259
- ] }),
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 ?? ""}` })
9264
- ] }),
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 })
9273
- ] }) : null,
9274
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
9275
- /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
9276
- "Anomalies ",
9277
- anomalies2.length ? `(${anomalies2.length})` : ""
9278
- ] }),
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) })
9283
- ] }, i))
9284
- ] }),
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 ?? ""}` })
9290
- ] }, 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))
9292
- ] })
9293
- ] }) })
9294
- ] }) });
9295
- }
9296
- var statusColor, sevColor;
9297
- var init_Agents = __esm({
9298
- "src/tui/panels/Agents.tsx"() {
9299
- "use strict";
9300
- init_api_client();
9301
- init_components();
9302
- init_hooks();
9303
- init_theme();
9304
- statusColor = (s) => s === "active" ? theme.ok : s === "idle" ? theme.warn : theme.dim;
9305
- sevColor = (s) => s === "high" ? theme.bad : s === "medium" ? theme.warn : theme.dim;
9313
+ SESS_STATUS = [void 0, "active", "idle", "ended"];
9314
+ PAGE = 500;
9315
+ LOCAL_MAX_BYTES = 16 * 1024 * 1024;
9316
+ cloudRow = (e) => ({
9317
+ id: e.id,
9318
+ at: Date.parse(e.created_at),
9319
+ tool: e.tool_name,
9320
+ decision: e.decision,
9321
+ permission: e.permission,
9322
+ trust: e.trust_level,
9323
+ agent: e.agent_name,
9324
+ session: e.session_id,
9325
+ reason: e.reason,
9326
+ rule: e.matched_rule_id,
9327
+ evalMs: e.evaluation_time_ms,
9328
+ dlp: e.dlp_matches ?? [],
9329
+ burst: !!e.rate_limit_burst,
9330
+ args: e.arguments_summary ? JSON.stringify(e.arguments_summary) : null
9331
+ });
9332
+ sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
9333
+ STATUS_DOT = {
9334
+ active: { ch: "\u25CF", color: theme.ok },
9335
+ idle: { ch: "\u25D0", color: theme.warn },
9336
+ ended: { ch: "\u25CB", color: theme.dim }
9337
+ };
9306
9338
  }
9307
9339
  });
9308
9340
 
9309
9341
  // src/tui/panels/Settings.tsx
9310
- import { Box as Box9, Text as Text9, useInput as useInput7 } from "ink";
9342
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
9311
9343
  import TextInput5 from "ink-text-input";
9312
- import { useState as useState8 } from "react";
9313
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
9344
+ import { useState as useState7 } from "react";
9345
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
9314
9346
  function alertBodyFor(channel) {
9315
9347
  const c2 = channel.trim();
9316
9348
  const base = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
@@ -9321,12 +9353,12 @@ function alertBodyFor(channel) {
9321
9353
  function SettingsPanel({ active: active2, focused }) {
9322
9354
  void active2;
9323
9355
  const { cols, rows } = usePanelSize();
9324
- const [sel, setSel] = useState8(0);
9325
- const [editing, setEditing] = useState8(null);
9326
- const [input, setInput] = useState8("");
9327
- const [confirmDel, setConfirmDel] = useState8(null);
9328
- const [msg, setMsg] = useState8(null);
9329
- const [busy, setBusy] = useState8(false);
9356
+ const [sel, setSel] = useState7(0);
9357
+ const [editing, setEditing] = useState7(null);
9358
+ const [input, setInput] = useState7("");
9359
+ const [confirmDel, setConfirmDel] = useState7(null);
9360
+ const [msg, setMsg] = useState7(null);
9361
+ const [busy, setBusy] = useState7(false);
9330
9362
  const localQ = useLoader(() => api.settings.getLocalLogs());
9331
9363
  const whQ = useLoader(() => api.settings.getWebhooks());
9332
9364
  const alertQ = useLoader(() => api.settings.getAlerts());
@@ -9396,7 +9428,7 @@ function SettingsPanel({ active: active2, focused }) {
9396
9428
  run12("alert rule added", () => api.settings.createAlert(alertBodyFor(v)), alertQ.reload);
9397
9429
  }
9398
9430
  };
9399
- useInput7(
9431
+ useInput6(
9400
9432
  (inp, key) => {
9401
9433
  setMsg(null);
9402
9434
  if (key.upArrow) {
@@ -9435,7 +9467,7 @@ function SettingsPanel({ active: active2, focused }) {
9435
9467
  const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
9436
9468
  const error = localQ.error ?? whQ.error ?? alertQ.error;
9437
9469
  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" });
9470
+ const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
9439
9471
  const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
9440
9472
  const listBudget = Math.max(4, rows - 8);
9441
9473
  const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
@@ -9443,68 +9475,68 @@ function SettingsPanel({ active: active2, focused }) {
9443
9475
  const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
9444
9476
  return i >= start && i < start + listBudget;
9445
9477
  };
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: [
9478
+ return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
9479
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter/space toggle-edit-add \xB7 e events \xB7 d delete \xB7 r refresh" : "press \u2192 to configure" }),
9480
+ editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
9481
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
9482
+ /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
9483
+ ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
9484
+ inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
9485
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
9454
9486
  "LOCAL LOGS ",
9455
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
9487
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
9456
9488
  ] }),
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) }),
9489
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
9490
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
9491
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
9460
9492
  onOff(!!local?.enabled),
9461
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " enter toggles" })
9493
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
9462
9494
  ] }),
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" })
9495
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
9496
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
9497
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
9498
+ local?.path ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(local.path, cols - 14) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "not set \u2014 enter to edit" })
9467
9499
  ] })
9468
9500
  ] }) : null,
9469
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
9470
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
9501
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
9502
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
9471
9503
  "WEBHOOKS ",
9472
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
9504
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
9473
9505
  "\u2014 POST every matching event to your endpoint (",
9474
9506
  webhooks.length,
9475
9507
  ")"
9476
9508
  ] })
9477
9509
  ] }),
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 }) }),
9510
+ webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
9511
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
9480
9512
  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) })
9513
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
9514
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(wh.url, cols - 16) })
9483
9515
  ] }, 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)" })
9516
+ inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs7(Text7, { children: [
9517
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
9518
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
9487
9519
  ] }) : null
9488
9520
  ] }),
9489
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
9490
- /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
9521
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
9522
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
9491
9523
  "ALERTS ",
9492
- /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
9524
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
9493
9525
  "\u2014 notify on a signal spike (",
9494
9526
  alerts.length,
9495
9527
  ")"
9496
9528
  ] })
9497
9529
  ] }),
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 }) }),
9530
+ alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
9531
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
9500
9532
  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) })
9533
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
9534
+ /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
9535
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(chanOf(rule), cols - 26) })
9504
9536
  ] }, 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)" })
9537
+ inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs7(Text7, { children: [
9538
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
9539
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
9508
9540
  ] }) : null
9509
9541
  ] })
9510
9542
  ] }) });
@@ -9522,17 +9554,17 @@ var init_Settings = __esm({
9522
9554
  });
9523
9555
 
9524
9556
  // src/tui/App.tsx
9525
- import { Box as Box10, Text as Text10, useApp, useInput as useInput8 } from "ink";
9526
- import { useState as useState9 } from "react";
9527
- import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
9557
+ import { Box as Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
9558
+ import { useState as useState8 } from "react";
9559
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
9528
9560
  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: [
9561
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9562
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
9563
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
9564
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
9565
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
9534
9566
  "press ",
9535
- /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: "enter" }),
9567
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
9536
9568
  " to go live"
9537
9569
  ] }) })
9538
9570
  ] });
@@ -9540,22 +9572,22 @@ function LiveHint() {
9540
9572
  function Banner({ cols }) {
9541
9573
  const wide = cols >= 82;
9542
9574
  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" })
9575
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
9576
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
9577
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
9546
9578
  ] });
9547
9579
  }
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" })
9580
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9581
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
9582
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
9551
9583
  ] });
9552
9584
  }
9553
9585
  function App() {
9554
9586
  const { exit } = useApp();
9555
- const [section, setSection] = useState9(0);
9556
- const [focus, setFocus] = useState9("nav");
9557
- const [help, setHelp] = useState9(false);
9558
- useInput8((input, key) => {
9587
+ const [section, setSection] = useState8(0);
9588
+ const [focus, setFocus] = useState8("nav");
9589
+ const [help, setHelp] = useState8(false);
9590
+ useInput7((input, key) => {
9559
9591
  if (help) {
9560
9592
  setHelp(false);
9561
9593
  return;
@@ -9576,31 +9608,31 @@ function App() {
9576
9608
  });
9577
9609
  const { cols, rows } = useTermSize();
9578
9610
  const current = SECTIONS[section];
9579
- if (help) return /* @__PURE__ */ jsx10(HelpOverlay, { cols, rows });
9611
+ if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
9580
9612
  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 }) });
9613
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
9582
9614
  }
9583
9615
  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" }) })
9616
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
9617
+ /* @__PURE__ */ jsx8(Banner, { cols }),
9618
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
9619
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
9620
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
9589
9621
  ] }),
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"]] }) })
9622
+ /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
9591
9623
  ] });
9592
9624
  }
9593
9625
  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 })
9626
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
9627
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
9628
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
9629
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
9630
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
9631
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
9632
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
9601
9633
  ] }, k))
9602
9634
  ] }, group)) }),
9603
- /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "press any key to close" })
9635
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
9604
9636
  ] });
9605
9637
  }
9606
9638
  var SECTIONS, BANNER_HEX, HELP;
@@ -9614,18 +9646,14 @@ var init_App = __esm({
9614
9646
  init_Policies();
9615
9647
  init_RateLimit();
9616
9648
  init_Dlp();
9617
- init_Stats();
9618
9649
  init_Audit();
9619
- init_Agents();
9620
9650
  init_Settings();
9621
9651
  init_hooks();
9622
9652
  SECTIONS = [
9623
9653
  { label: "Live", Panel: LiveHint },
9624
- { label: "Agents", Panel: AgentsPanel },
9625
9654
  { label: "Policies", Panel: PoliciesPanel },
9626
9655
  { label: "Rate Limit", Panel: RateLimitPanel },
9627
9656
  { label: "DLP", Panel: DlpPanel },
9628
- { label: "Stats", Panel: StatsPanel },
9629
9657
  { label: "Audit", Panel: AuditPanel },
9630
9658
  { label: "Settings", Panel: SettingsPanel }
9631
9659
  ];
@@ -9633,9 +9661,9 @@ var init_App = __esm({
9633
9661
  HELP = [
9634
9662
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
9635
9663
  ["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"]]],
9664
+ ["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
9665
  ["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"]]],
9666
+ ["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["c", "clear filters"]]],
9639
9667
  ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
9640
9668
  ];
9641
9669
  }
@@ -9647,7 +9675,7 @@ __export(tui_exports, {
9647
9675
  launchTui: () => launchTui
9648
9676
  });
9649
9677
  import { render } from "ink";
9650
- import { jsx as jsx11 } from "react/jsx-runtime";
9678
+ import { jsx as jsx9 } from "react/jsx-runtime";
9651
9679
  async function launchTui() {
9652
9680
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
9653
9681
  process.stderr.write(
@@ -9661,7 +9689,7 @@ async function launchTui() {
9661
9689
  }
9662
9690
  process.stdout.write("\x1B[?1049h\x1B[H");
9663
9691
  try {
9664
- const { waitUntilExit } = render(/* @__PURE__ */ jsx11(App, {}));
9692
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
9665
9693
  await waitUntilExit();
9666
9694
  } finally {
9667
9695
  process.stdout.write("\x1B[?1049l");
@@ -10312,7 +10340,7 @@ async function runAgents(argv) {
10312
10340
  table(
10313
10341
  ["STATUS", "AGENT", "CALLS", "DENY", "DLP", "TRUST", "CHARACTER"],
10314
10342
  res.agents.map((a) => [
10315
- statusColor2(a.status),
10343
+ statusColor(a.status),
10316
10344
  cyan(truncate3(a.agent_name ?? a.agent_id ?? a.session_id, 20)),
10317
10345
  String(a.total_calls),
10318
10346
  a.denied_calls ? red(String(a.denied_calls)) : dim("0"),
@@ -10331,7 +10359,7 @@ async function runAgent(argv) {
10331
10359
  const a = await api.agents.get(id);
10332
10360
  if (json) return printJson(a), 0;
10333
10361
  err("");
10334
- err(` ${bold(a.agent_id)} status: ${statusColor2(String(a.status))}`);
10362
+ err(` ${bold(a.agent_id)} status: ${statusColor(String(a.status))}`);
10335
10363
  const base = a.baseline;
10336
10364
  if (base) {
10337
10365
  err(` ${dim(base.character ?? "")} trust ${bold(String(base.trustScore ?? "\u2014"))}/100 deny-rate ${(Number(base.denyRate ?? 0) * 100).toFixed(0)}%`);
@@ -10351,21 +10379,21 @@ async function runAgent(argv) {
10351
10379
  }
10352
10380
  return 0;
10353
10381
  }
10354
- var statusColor2;
10382
+ var statusColor;
10355
10383
  var init_agents2 = __esm({
10356
10384
  "src/commands/agents.ts"() {
10357
10385
  "use strict";
10358
10386
  init_api_client();
10359
10387
  init_args();
10360
10388
  init_format();
10361
- statusColor2 = (s) => s === "active" ? green(s) : s === "idle" ? yellow(s) : dim(s);
10389
+ statusColor = (s) => s === "active" ? green(s) : s === "idle" ? yellow(s) : dim(s);
10362
10390
  }
10363
10391
  });
10364
10392
 
10365
10393
  // src/commands/doctor.ts
10366
10394
  import { existsSync as existsSync4, statSync as statSync2 } from "fs";
10367
- import { homedir as homedir5 } from "os";
10368
- import { join as join7 } from "path";
10395
+ import { homedir as homedir6 } from "os";
10396
+ import { join as join8 } from "path";
10369
10397
  async function run6(argv) {
10370
10398
  const { flags } = parse(argv);
10371
10399
  const json = flagBool(flags, "json");
@@ -10425,14 +10453,14 @@ var init_doctor = __esm({
10425
10453
  init_api_client();
10426
10454
  init_format();
10427
10455
  init_args();
10428
- LOCAL_LOG2 = join7(homedir5(), ".solongate", "local-logs", "solongate-audit.jsonl");
10456
+ LOCAL_LOG2 = join8(homedir6(), ".solongate", "local-logs", "solongate-audit.jsonl");
10429
10457
  }
10430
10458
  });
10431
10459
 
10432
10460
  // src/commands/watch.ts
10433
10461
  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";
10462
+ import { homedir as homedir7 } from "os";
10463
+ import { join as join9 } from "path";
10436
10464
  function tailLocal(file, maxBytes = 131072) {
10437
10465
  try {
10438
10466
  const size = statSync3(file).size;
@@ -10539,7 +10567,7 @@ var init_watch = __esm({
10539
10567
  init_cli_utils();
10540
10568
  init_args();
10541
10569
  init_format();
10542
- LOCAL_LOG3 = join8(homedir6(), ".solongate", "local-logs", "solongate-audit.jsonl");
10570
+ LOCAL_LOG3 = join9(homedir7(), ".solongate", "local-logs", "solongate-audit.jsonl");
10543
10571
  trunc = (s, n) => s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
10544
10572
  time = (ms) => new Date(ms).toTimeString().slice(0, 8);
10545
10573
  }
@@ -10842,8 +10870,8 @@ __export(global_install_exports, {
10842
10870
  unlockProtected: () => unlockProtected
10843
10871
  });
10844
10872
  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";
10873
+ import { resolve as resolve4, join as join10, dirname } from "path";
10874
+ import { homedir as homedir8 } from "os";
10847
10875
  import { fileURLToPath } from "url";
10848
10876
  import { createInterface } from "readline";
10849
10877
  import { execFileSync as execFileSync2 } from "child_process";
@@ -10906,10 +10934,10 @@ function unlockFile(file) {
10906
10934
  function protectedTargets() {
10907
10935
  const p = globalPaths();
10908
10936
  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"),
10937
+ join10(p.hooksDir, "guard.mjs"),
10938
+ join10(p.hooksDir, "audit.mjs"),
10939
+ join10(p.hooksDir, "stop.mjs"),
10940
+ join10(p.hooksDir, "shield.mjs"),
10913
10941
  p.configPath,
10914
10942
  p.settingsPath
10915
10943
  ];
@@ -10921,25 +10949,25 @@ function unlockProtected() {
10921
10949
  for (const f of protectedTargets()) unlockFile(f);
10922
10950
  }
10923
10951
  function globalPaths() {
10924
- const home = homedir7();
10925
- const sgDir = join9(home, ".solongate");
10926
- const hooksDir = join9(sgDir, "hooks");
10927
- const claudeDir = join9(home, ".claude");
10952
+ const home = homedir8();
10953
+ const sgDir = join10(home, ".solongate");
10954
+ const hooksDir = join10(sgDir, "hooks");
10955
+ const claudeDir = join10(home, ".claude");
10928
10956
  return {
10929
10957
  home,
10930
10958
  sgDir,
10931
10959
  hooksDir,
10932
10960
  claudeDir,
10933
- settingsPath: join9(claudeDir, "settings.json"),
10934
- backupPath: join9(claudeDir, "settings.solongate.bak"),
10935
- configPath: join9(sgDir, "cloud-guard.json")
10961
+ settingsPath: join10(claudeDir, "settings.json"),
10962
+ backupPath: join10(claudeDir, "settings.solongate.bak"),
10963
+ configPath: join10(sgDir, "cloud-guard.json")
10936
10964
  };
10937
10965
  }
10938
10966
  function readHook(filename) {
10939
- return readFileSync7(join9(HOOKS_DIR, filename), "utf-8");
10967
+ return readFileSync7(join10(HOOKS_DIR, filename), "utf-8");
10940
10968
  }
10941
10969
  function readGuard() {
10942
- const bundled = join9(HOOKS_DIR, "guard.bundled.mjs");
10970
+ const bundled = join10(HOOKS_DIR, "guard.bundled.mjs");
10943
10971
  return existsSync6(bundled) ? readFileSync7(bundled, "utf-8") : readHook("guard.mjs");
10944
10972
  }
10945
10973
  function ask(question) {
@@ -10994,7 +11022,7 @@ function shimTargets() {
10994
11022
  return [];
10995
11023
  }
10996
11024
  }
10997
- return [".bashrc", ".zshrc", ".profile"].map((f) => join9(homedir7(), f)).filter((f) => existsSync6(f));
11025
+ return [".bashrc", ".zshrc", ".profile"].map((f) => join10(homedir8(), f)).filter((f) => existsSync6(f));
10998
11026
  }
10999
11027
  function writeShimBlock(file, block2) {
11000
11028
  const re = new RegExp(escapeRe(SHIM_BEGIN) + "[\\s\\S]*?" + escapeRe(SHIM_END) + "\\r?\\n?", "g");
@@ -11059,12 +11087,12 @@ async function runGlobalInstall(opts = {}) {
11059
11087
  mkdirSync4(p.hooksDir, { recursive: true });
11060
11088
  mkdirSync4(p.claudeDir, { recursive: true });
11061
11089
  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"));
11090
+ writeFileSync4(join10(p.hooksDir, "guard.mjs"), readGuard());
11091
+ writeFileSync4(join10(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11092
+ writeFileSync4(join10(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11093
+ writeFileSync4(join10(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11066
11094
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
11067
- installClaudeShim(join9(p.hooksDir, "shield.mjs"));
11095
+ installClaudeShim(join10(p.hooksDir, "shield.mjs"));
11068
11096
  writeFileSync4(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11069
11097
  console.log(` Wrote ${p.configPath}`);
11070
11098
  let existing = {};
@@ -11080,9 +11108,9 @@ async function runGlobalInstall(opts = {}) {
11080
11108
  existing = {};
11081
11109
  }
11082
11110
  }
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, "/");
11111
+ const guardAbs = join10(p.hooksDir, "guard.mjs").replace(/\\/g, "/");
11112
+ const auditAbs = join10(p.hooksDir, "audit.mjs").replace(/\\/g, "/");
11113
+ const stopAbs = join10(p.hooksDir, "stop.mjs").replace(/\\/g, "/");
11086
11114
  const nodeBin = process.execPath.replace(/\\/g, "/");
11087
11115
  const call = process.platform === "win32" ? "& " : "";
11088
11116
  const hookCmd = (script) => `${call}"${nodeBin}" "${script}" claude-code "Claude Code"`;
@@ -11276,9 +11304,9 @@ import { spawn as spawn3 } from "child_process";
11276
11304
  import { URL as URL2 } from "url";
11277
11305
  import { readFileSync as readFileSync8, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11278
11306
  import { resolve as resolve5 } from "path";
11279
- import { homedir as homedir8 } from "os";
11307
+ import { homedir as homedir9 } from "os";
11280
11308
  function findCacheFile() {
11281
- const dir = resolve5(homedir8(), ".solongate");
11309
+ const dir = resolve5(homedir9(), ".solongate");
11282
11310
  const envSel = process.env.SOLONGATE_AGENT_ID;
11283
11311
  if (envSel) {
11284
11312
  const f = resolve5(dir, ".policy-cache-" + envSel.replace(/[^a-zA-Z0-9_-]/g, "_") + ".json");
@@ -11593,8 +11621,8 @@ __export(logs_server_exports, {
11593
11621
  });
11594
11622
  import { createServer as createServer2 } from "http";
11595
11623
  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";
11624
+ import { resolve as resolve6, join as join11, isAbsolute } from "path";
11625
+ import { homedir as homedir10 } from "os";
11598
11626
  import { readdirSync as readdirSync2 } from "fs";
11599
11627
  function allowedOrigins() {
11600
11628
  const base = [
@@ -11611,15 +11639,15 @@ function resolveLocalLogDir(rawPath) {
11611
11639
  const dir = String(rawPath || "").trim().replace(/[\\/]+$/, "");
11612
11640
  if (!dir) return null;
11613
11641
  if (isAbsolute(dir)) return dir;
11614
- return resolve6(homedir9(), ".solongate", "local-logs");
11642
+ return resolve6(homedir10(), ".solongate", "local-logs");
11615
11643
  }
11616
11644
  async function findLogDir() {
11617
- const base = resolve6(homedir9(), ".solongate");
11645
+ const base = resolve6(homedir10(), ".solongate");
11618
11646
  try {
11619
11647
  const files = readdirSync2(base).filter((f) => f.startsWith(".policy-cache-") && f.endsWith(".json"));
11620
11648
  for (const f of files) {
11621
11649
  try {
11622
- const c2 = JSON.parse(readFileSync9(join10(base, f), "utf-8"));
11650
+ const c2 = JSON.parse(readFileSync9(join11(base, f), "utf-8"));
11623
11651
  const p = c2?.security?.localLogs?.path;
11624
11652
  if (typeof p === "string" && p.trim()) return { dir: resolveLocalLogDir(p), configured: p };
11625
11653
  } catch {
@@ -11628,7 +11656,7 @@ async function findLogDir() {
11628
11656
  } catch {
11629
11657
  }
11630
11658
  try {
11631
- const cfgRaw = readFileSync9(join10(base, "cloud-guard.json"), "utf-8");
11659
+ const cfgRaw = readFileSync9(join11(base, "cloud-guard.json"), "utf-8");
11632
11660
  const { apiKey, apiUrl } = JSON.parse(cfgRaw);
11633
11661
  if (apiKey) {
11634
11662
  const url = `${apiUrl || "https://api.solongate.com"}/api/v1/policies/active`;
@@ -11657,7 +11685,7 @@ function setCors(req, res) {
11657
11685
  }
11658
11686
  function fileInfo(dir) {
11659
11687
  if (!dir) return { file: null, exists: false, size: 0, mtimeMs: 0 };
11660
- const file = join10(dir, LOG_FILENAME);
11688
+ const file = join11(dir, LOG_FILENAME);
11661
11689
  try {
11662
11690
  const st = statSync5(file);
11663
11691
  return { file, exists: true, size: st.size, mtimeMs: st.mtimeMs };
@@ -12108,7 +12136,7 @@ var init_inject = __esm({
12108
12136
  // src/create.ts
12109
12137
  var create_exports = {};
12110
12138
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
12111
- import { resolve as resolve8, join as join11 } from "path";
12139
+ import { resolve as resolve8, join as join12 } from "path";
12112
12140
  import { execSync as execSync2 } from "child_process";
12113
12141
  function withSpinner(message, fn) {
12114
12142
  const frames = ["\u28FE", "\u28FD", "\u28FB", "\u28BF", "\u287F", "\u28DF", "\u28EF", "\u28F7"];
@@ -12188,7 +12216,7 @@ EXAMPLES
12188
12216
  }
12189
12217
  function createProject(dir, name, _policy) {
12190
12218
  writeFileSync6(
12191
- join11(dir, "package.json"),
12219
+ join12(dir, "package.json"),
12192
12220
  JSON.stringify(
12193
12221
  {
12194
12222
  name,
@@ -12218,7 +12246,7 @@ function createProject(dir, name, _policy) {
12218
12246
  ) + "\n"
12219
12247
  );
12220
12248
  writeFileSync6(
12221
- join11(dir, "tsconfig.json"),
12249
+ join12(dir, "tsconfig.json"),
12222
12250
  JSON.stringify(
12223
12251
  {
12224
12252
  compilerOptions: {
@@ -12238,9 +12266,9 @@ function createProject(dir, name, _policy) {
12238
12266
  2
12239
12267
  ) + "\n"
12240
12268
  );
12241
- mkdirSync5(join11(dir, "src"), { recursive: true });
12269
+ mkdirSync5(join12(dir, "src"), { recursive: true });
12242
12270
  writeFileSync6(
12243
- join11(dir, "src", "index.ts"),
12271
+ join12(dir, "src", "index.ts"),
12244
12272
  `#!/usr/bin/env node
12245
12273
 
12246
12274
  console.log = (...args: unknown[]) => {
@@ -12282,7 +12310,7 @@ console.log('Press Ctrl+C to stop.');
12282
12310
  `
12283
12311
  );
12284
12312
  writeFileSync6(
12285
- join11(dir, ".mcp.json"),
12313
+ join12(dir, ".mcp.json"),
12286
12314
  JSON.stringify(
12287
12315
  {
12288
12316
  mcpServers: {
@@ -12300,12 +12328,12 @@ console.log('Press Ctrl+C to stop.');
12300
12328
  ) + "\n"
12301
12329
  );
12302
12330
  writeFileSync6(
12303
- join11(dir, ".env"),
12331
+ join12(dir, ".env"),
12304
12332
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
12305
12333
  `
12306
12334
  );
12307
12335
  writeFileSync6(
12308
- join11(dir, ".gitignore"),
12336
+ join12(dir, ".gitignore"),
12309
12337
  `node_modules/
12310
12338
  dist/
12311
12339
  *.solongate-backup