@solongate/proxy 0.83.59 → 0.83.60

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui/index.js CHANGED
@@ -495,8 +495,8 @@ function readCodexHooksFile(path) {
495
495
  continue;
496
496
  }
497
497
  if (k === "hooks" && v && typeof v === "object") {
498
- for (const [ev, groups] of Object.entries(v)) {
499
- if (Array.isArray(groups)) out.hooks[ev] = groups;
498
+ for (const [ev, groups2] of Object.entries(v)) {
499
+ if (Array.isArray(groups2)) out.hooks[ev] = groups2;
500
500
  }
501
501
  continue;
502
502
  }
@@ -510,7 +510,7 @@ function readCodexHooksFile(path) {
510
510
  }
511
511
  function writeCodexHooksFile(path, file) {
512
512
  const hooks = {};
513
- for (const [ev, groups] of Object.entries(file.hooks)) if (groups.length) hooks[ev] = groups;
513
+ for (const [ev, groups2] of Object.entries(file.hooks)) if (groups2.length) hooks[ev] = groups2;
514
514
  const body = { ...file.rest };
515
515
  if (file.description) body["description"] = file.description;
516
516
  body["hooks"] = hooks;
@@ -557,9 +557,9 @@ function removeCodexGuard(p) {
557
557
  try {
558
558
  const file = readCodexHooksFile(p.codexHooksPath);
559
559
  let changed = false;
560
- for (const [ev, groups] of Object.entries(file.hooks)) {
561
- const kept = groups.filter((g) => !isOurCodexGroup(g));
562
- if (kept.length !== groups.length) {
560
+ for (const [ev, groups2] of Object.entries(file.hooks)) {
561
+ const kept = groups2.filter((g) => !isOurCodexGroup(g));
562
+ if (kept.length !== groups2.length) {
563
563
  file.hooks[ev] = kept;
564
564
  changed = true;
565
565
  }
@@ -691,7 +691,7 @@ function repairQuiet() {
691
691
  const p = globalPaths();
692
692
  const has = (f) => existsSync3(f);
693
693
  const guardFile = join4(p.hooksDir, "guard.mjs");
694
- const line = (label, ok, yes, no) => ({ label, ok, detail: ok ? yes : no });
694
+ const line = (label2, ok, yes, no) => ({ label: label2, ok, detail: ok ? yes : no });
695
695
  const runtime = () => {
696
696
  const r2 = hookCanStart();
697
697
  return { label: "hook runtime", ok: r2.ok, detail: r2.ok ? `node ${r2.detail}` : r2.detail };
@@ -834,13 +834,13 @@ function guardHookOutdated() {
834
834
  return false;
835
835
  }
836
836
  }
837
- function hookCommandFor(hooksDir2, script, client = "claude-code", label = "Claude Code") {
837
+ function hookCommandFor(hooksDir2, script, client = "claude-code", label2 = "Claude Code") {
838
838
  const target = join4(hooksDir2, script).replace(/\\/g, "/");
839
839
  if (process.platform === "win32") {
840
- return `& "${process.execPath.replace(/\\/g, "/")}" "${target}" ${client} "${label}"`;
840
+ return `& "${process.execPath.replace(/\\/g, "/")}" "${target}" ${client} "${label2}"`;
841
841
  }
842
842
  const launcher = join4(hooksDir2, LAUNCHER_NAME).replace(/\\/g, "/");
843
- return `/bin/sh "${launcher}" "${target}" ${client} "${label}"`;
843
+ return `/bin/sh "${launcher}" "${target}" ${client} "${label2}"`;
844
844
  }
845
845
  function writeLauncher(hooksDir2) {
846
846
  writeFileSync2(join4(hooksDir2, LAUNCHER_NAME), launcherScript(process.execPath));
@@ -948,8 +948,8 @@ import { join as join11 } from "path";
948
948
  import { render } from "ink";
949
949
 
950
950
  // src/tui/App.tsx
951
- import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
952
- import { useEffect as useEffect8, useState as useState9 } from "react";
951
+ import { Box as Box11, Text as Text11, useApp, useInput as useInput10 } from "ink";
952
+ import { useEffect as useEffect9, useState as useState11 } from "react";
953
953
 
954
954
  // src/cli-utils.ts
955
955
  var c = {
@@ -1072,14 +1072,14 @@ var ymd = (ts) => {
1072
1072
  const p = (n) => String(n).padStart(2, "0");
1073
1073
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
1074
1074
  };
1075
- function PaneTitle({ label, extra, width }) {
1075
+ function PaneTitle({ label: label2, extra, width }) {
1076
1076
  const tail = extra ? ` ${extra} ` : " ";
1077
- const used = 2 + 1 + label.length + tail.length;
1077
+ const used = 2 + 1 + label2.length + tail.length;
1078
1078
  return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
1079
1079
  /* @__PURE__ */ jsx(Text, { color: theme.dim, children: "\u2500 " }),
1080
1080
  /* @__PURE__ */ jsxs(Text, { color: theme.accentBright, bold: true, children: [
1081
1081
  "\u258E",
1082
- label
1082
+ label2
1083
1083
  ] }),
1084
1084
  /* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
1085
1085
  ] });
@@ -1133,10 +1133,10 @@ function Table({ columns, rows }) {
1133
1133
  ] });
1134
1134
  }
1135
1135
  function KeyHints({ hints }) {
1136
- return /* @__PURE__ */ jsx(Box, { children: hints.map(([key, label], i) => /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
1136
+ return /* @__PURE__ */ jsx(Box, { children: hints.map(([key, label2], i) => /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
1137
1137
  /* @__PURE__ */ jsx(Text, { color: theme.accent, children: key }),
1138
1138
  " ",
1139
- label,
1139
+ label2,
1140
1140
  i < hints.length - 1 ? " " : ""
1141
1141
  ] }, i)) });
1142
1142
  }
@@ -1471,6 +1471,10 @@ function buildUrl(base, path, query) {
1471
1471
  if (query) {
1472
1472
  for (const [k, v] of Object.entries(query)) {
1473
1473
  if (v === void 0) continue;
1474
+ if (Array.isArray(v)) {
1475
+ for (const one of v) url.searchParams.append(k, one);
1476
+ continue;
1477
+ }
1474
1478
  url.searchParams.set(k, String(v));
1475
1479
  }
1476
1480
  }
@@ -1530,6 +1534,7 @@ async function request(method, path, opts = {}) {
1530
1534
  if (res.status >= 500) throw new ApiError(res.status, "SERVER_ERROR", text || "SolonGate API had a problem (server error). Please try again in a moment.");
1531
1535
  throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
1532
1536
  }
1537
+ if (opts.raw) return text;
1533
1538
  return json;
1534
1539
  }
1535
1540
 
@@ -1809,8 +1814,100 @@ function list4() {
1809
1814
  return request("GET", "/mcp-servers");
1810
1815
  }
1811
1816
 
1817
+ // src/api-client/fleet.ts
1818
+ var fleet_exports = {};
1819
+ __export(fleet_exports, {
1820
+ activity: () => activity,
1821
+ analytics: () => analytics,
1822
+ assignGroup: () => assignGroup,
1823
+ createGroup: () => createGroup,
1824
+ deleteGroup: () => deleteGroup,
1825
+ groups: () => groups,
1826
+ invite: () => invite,
1827
+ overview: () => overview,
1828
+ renameGroup: () => renameGroup,
1829
+ report: () => report,
1830
+ reportDownload: () => reportDownload,
1831
+ reportSchedule: () => reportSchedule,
1832
+ revoke: () => revoke2,
1833
+ sendReport: () => sendReport,
1834
+ setGroupColor: () => setGroupColor,
1835
+ setGroupPolicy: () => setGroupPolicy,
1836
+ setReportSchedule: () => setReportSchedule,
1837
+ setUserPolicy: () => setUserPolicy,
1838
+ stream: () => stream,
1839
+ summary: () => summary
1840
+ });
1841
+ function overview() {
1842
+ return request("GET", "/fleet");
1843
+ }
1844
+ function summary(opts = {}) {
1845
+ const query = {};
1846
+ if (opts.range) query.range = opts.range;
1847
+ if (opts.group?.length) query.group = opts.group;
1848
+ if (opts.who?.length) query.who = opts.who;
1849
+ if (opts.agent) query.agent = opts.agent;
1850
+ return request("GET", "/fleet/summary", { query });
1851
+ }
1852
+ function groups() {
1853
+ return request("GET", "/fleet/groups");
1854
+ }
1855
+ function createGroup(name, color) {
1856
+ return request("POST", "/fleet/groups", { body: { name, color } });
1857
+ }
1858
+ function renameGroup(name, newName) {
1859
+ return request("POST", "/fleet/groups/rename", { body: { name, new_name: newName } });
1860
+ }
1861
+ function setGroupColor(name, color) {
1862
+ return request("POST", "/fleet/groups/color", { body: { name, color } });
1863
+ }
1864
+ function deleteGroup(name) {
1865
+ return request("POST", "/fleet/groups/delete", { body: { name } });
1866
+ }
1867
+ function setGroupPolicy(name, policyId) {
1868
+ return request("POST", "/fleet/groups/policy", { body: { name, policy_id: policyId } });
1869
+ }
1870
+ function setUserPolicy(ids, policyId) {
1871
+ return request("POST", "/fleet/policy", { body: { ids, policy_id: policyId } });
1872
+ }
1873
+ function assignGroup(ids, group) {
1874
+ return request("POST", "/fleet/group", { body: { ids, group } });
1875
+ }
1876
+ function invite(email) {
1877
+ return request("POST", "/fleet/invitations", { body: { email } });
1878
+ }
1879
+ function revoke2(id) {
1880
+ return request("DELETE", `/fleet/invitations/${encodeURIComponent(id)}`);
1881
+ }
1882
+ function analytics(guest) {
1883
+ return request("GET", "/fleet/analytics", { query: { guest } });
1884
+ }
1885
+ function activity(guest, limit) {
1886
+ const query = { guest };
1887
+ if (limit) query.limit = limit;
1888
+ return request("GET", "/fleet/activity", { query });
1889
+ }
1890
+ function report(span = "day") {
1891
+ return request("GET", "/fleet/report", { query: { span } });
1892
+ }
1893
+ function reportDownload(span, format = "md") {
1894
+ return request("GET", "/fleet/report/download", { query: { span, format }, raw: true });
1895
+ }
1896
+ function sendReport(span = "day") {
1897
+ return request("POST", "/fleet/report/send", { query: { span }, body: {} });
1898
+ }
1899
+ function reportSchedule() {
1900
+ return request("GET", "/settings/reports");
1901
+ }
1902
+ function setReportSchedule(schedule) {
1903
+ return request("PUT", "/settings/reports", { body: schedule });
1904
+ }
1905
+ function stream(limit = 60) {
1906
+ return request("GET", "/fleet/stream", { query: { limit } });
1907
+ }
1908
+
1812
1909
  // src/api-client/index.ts
1813
- var api = { auth: auth_exports, policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
1910
+ var api = { auth: auth_exports, policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports, fleet: fleet_exports };
1814
1911
 
1815
1912
  // src/tui/notify.ts
1816
1913
  import { spawn as spawn3 } from "child_process";
@@ -1980,10 +2077,10 @@ function ColumnChart({ series, hot, height, width, color, hotColor = theme.bad }
1980
2077
  }
1981
2078
  return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: lines });
1982
2079
  }
1983
- function HBar({ label, value, max, width, color }) {
2080
+ function HBar({ label: label2, value, max, width, color }) {
1984
2081
  const filled = max > 0 ? Math.round(value / max * width) : 0;
1985
2082
  return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
1986
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label.padEnd(10) }),
2083
+ /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: label2.padEnd(10) }),
1987
2084
  /* @__PURE__ */ jsx2(Text2, { color, children: "\u2588".repeat(Math.min(width, filled)) }),
1988
2085
  /* @__PURE__ */ jsx2(Text2, { color: DIM_FLOOR, children: "\u2591".repeat(Math.max(0, width - filled)) }),
1989
2086
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
@@ -2384,7 +2481,7 @@ function LivePanel({ active: active2 }) {
2384
2481
  const selClamped = Math.min(Math.max(0, sel), Math.max(0, visibleDesc.length - 1));
2385
2482
  const maxScroll = Math.max(0, visibleDesc.length - streamBodyRows);
2386
2483
  const clampedScroll = Math.min(Math.max(0, selClamped - Math.floor((streamBodyRows - 1) / 2)), maxScroll);
2387
- const windowed = visibleDesc.slice(clampedScroll, clampedScroll + streamBodyRows);
2484
+ const windowed2 = visibleDesc.slice(clampedScroll, clampedScroll + streamBodyRows);
2388
2485
  const selEntry = visibleDesc[selClamped];
2389
2486
  const detailEntries = detail ? (() => {
2390
2487
  const seen = /* @__PURE__ */ new Set();
@@ -2647,11 +2744,11 @@ function LivePanel({ active: active2 }) {
2647
2744
  const L = [];
2648
2745
  const push = (el) => L.push(/* @__PURE__ */ jsx2(Box2, { children: el }, L.length));
2649
2746
  const blank = () => push(/* @__PURE__ */ jsx2(Text2, { children: " " }));
2650
- const section = (label, m, note) => push(
2747
+ const section = (label2, m, note) => push(
2651
2748
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
2652
2749
  /* @__PURE__ */ jsxs2(Text2, { bold: true, color: theme.accentBright, children: [
2653
2750
  "\u258E",
2654
- label.padEnd(10)
2751
+ label2.padEnd(10)
2655
2752
  ] }),
2656
2753
  /* @__PURE__ */ jsx2(Text2, { bold: true, color: modeColor2(m), children: (m ?? "?").padEnd(8) }),
2657
2754
  note ? /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: note }) : null
@@ -2924,7 +3021,7 @@ function LivePanel({ active: active2 }) {
2924
3021
  /* @__PURE__ */ jsx2(Text2, { color: theme.ok, children: "local logs on" }),
2925
3022
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: ` \u2014 no entries yet \xB7 hooks write ${localPath?.file ?? ""}` })
2926
3023
  ] }) : null,
2927
- windowed.length === 0 ? (
3024
+ windowed2.length === 0 ? (
2928
3025
  // With a source filter on, "awaiting traffic" reads as "nothing is
2929
3026
  // happening" when the stream is merely filtered — say which it is.
2930
3027
  filter === "all" ? /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
@@ -2932,7 +3029,7 @@ function LivePanel({ active: active2 }) {
2932
3029
  " awaiting traffic\u2026"
2933
3030
  ] }) : /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: `${spin} no ${filter === "local" ? "LOC (local log)" : "CLD (cloud)"} calls in the buffer \xB7 f switches source` })
2934
3031
  ) : null,
2935
- windowed.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: clampedScroll + i === selClamped }, e.id))
3032
+ windowed2.map((e, i) => /* @__PURE__ */ jsx2(StreamLine, { e, loc: isLoc(e), selected: clampedScroll + i === selClamped }, e.id))
2936
3033
  ] }),
2937
3034
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
2938
3035
  /* @__PURE__ */ jsx2(Text2, { backgroundColor: BG, color: "white", bold: true, children: " LIVE " }),
@@ -2942,16 +3039,665 @@ function LivePanel({ active: active2 }) {
2942
3039
  ] });
2943
3040
  }
2944
3041
 
2945
- // src/tui/panels/Policies.tsx
3042
+ // src/tui/panels/FleetLive.tsx
3043
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
3044
+ import { useEffect as useEffect3, useMemo, useRef as useRef3, useState as useState3 } from "react";
3045
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3046
+ var TICK_MS = 2e3;
3047
+ var hhmmss2 = (unix) => {
3048
+ const d = new Date(unix * 1e3);
3049
+ return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
3050
+ };
3051
+ function FleetLivePanel({ focused }) {
3052
+ const { cols, rows } = useTermSize();
3053
+ const streamQ = useLoader(() => api.fleet.stream(80));
3054
+ const summaryQ = useLoader(() => api.fleet.summary({ range: "24h" }));
3055
+ const [paused, setPaused] = useState3(false);
3056
+ const [onlyDenies, setOnlyDenies] = useState3(false);
3057
+ usePoll(() => {
3058
+ if (paused) return;
3059
+ streamQ.reloadQuiet();
3060
+ }, TICK_MS);
3061
+ usePoll(() => {
3062
+ if (paused) return;
3063
+ summaryQ.reloadQuiet();
3064
+ }, 15e3);
3065
+ useInput2(
3066
+ (input) => {
3067
+ if (input === " ") setPaused((p) => !p);
3068
+ else if (input === "d") setOnlyDenies((d) => !d);
3069
+ else if (input === "r") {
3070
+ streamQ.reload();
3071
+ summaryQ.reload();
3072
+ }
3073
+ },
3074
+ { isActive: focused }
3075
+ );
3076
+ const calls = streamQ.data?.calls ?? [];
3077
+ const seenIds = useRef3(/* @__PURE__ */ new Set());
3078
+ const started = useRef3(Date.now());
3079
+ const [since, setSince] = useState3(() => ({
3080
+ calls: 0,
3081
+ denied: 0,
3082
+ byUser: /* @__PURE__ */ new Map()
3083
+ }));
3084
+ useEffect3(() => {
3085
+ if (calls.length === 0) return;
3086
+ let added = 0;
3087
+ let denied = 0;
3088
+ const byUser = new Map(since.byUser);
3089
+ for (const row of [...calls].reverse()) {
3090
+ if (seenIds.current.has(row.id)) continue;
3091
+ seenIds.current.add(row.id);
3092
+ added++;
3093
+ const isDeny = row.decision.toUpperCase() === "DENY";
3094
+ if (isDeny) denied++;
3095
+ const prev = byUser.get(row.who) ?? { calls: 0, denied: 0, last: 0 };
3096
+ byUser.set(row.who, {
3097
+ calls: prev.calls + 1,
3098
+ denied: prev.denied + (isDeny ? 1 : 0),
3099
+ last: Math.max(prev.last, row.created_at)
3100
+ });
3101
+ }
3102
+ if (added === 0) return;
3103
+ if (seenIds.current.size === added) {
3104
+ setSince({ calls: 0, denied: 0, byUser: /* @__PURE__ */ new Map() });
3105
+ return;
3106
+ }
3107
+ setSince((s) => ({ calls: s.calls + added, denied: s.denied + denied, byUser }));
3108
+ }, [calls]);
3109
+ const shown = useMemo(
3110
+ () => onlyDenies ? calls.filter((c2) => c2.decision.toUpperCase() === "DENY") : calls,
3111
+ [calls, onlyDenies]
3112
+ );
3113
+ const people = useMemo(() => {
3114
+ const list5 = [...since.byUser.entries()].map(([who, s]) => ({ who, ...s }));
3115
+ list5.sort((a, b) => b.calls - a.calls || b.last - a.last);
3116
+ return list5.slice(0, 8);
3117
+ }, [since]);
3118
+ const totals = summaryQ.data?.totals;
3119
+ const elapsed = Math.max(1, Math.round((Date.now() - started.current) / 1e3));
3120
+ const rate = since.calls > 0 ? (since.calls / (elapsed / 60)).toFixed(1) : "0.0";
3121
+ const tickerRows = Math.max(4, rows - 12 - Math.min(people.length, 8));
3122
+ const width = Math.max(40, cols - 4);
3123
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, overflow: "hidden", children: [
3124
+ /* @__PURE__ */ jsxs3(Box3, { children: [
3125
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "\u25B6 FLEET LIVE" }),
3126
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
3127
+ " " + (summaryQ.data?.developers ?? 0) + " people",
3128
+ totals ? " " + totals.calls + " calls / 24h \xB7 " + totals.denied + " denied" : ""
3129
+ ] }),
3130
+ paused ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: " paused" }) : null,
3131
+ onlyDenies ? /* @__PURE__ */ jsx3(Text3, { color: theme.bad, children: " denies only" }) : null
3132
+ ] }),
3133
+ /* @__PURE__ */ jsxs3(Box3, { children: [
3134
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "since you opened this: " + since.calls + " calls, " }),
3135
+ /* @__PURE__ */ jsx3(Text3, { color: since.denied > 0 ? theme.bad : theme.dim, children: since.denied + " denied" }),
3136
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " \xB7 " + rate + "/min" })
3137
+ ] }),
3138
+ people.length > 0 ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
3139
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "Working now" }),
3140
+ people.map((p) => /* @__PURE__ */ jsxs3(Box3, { children: [
3141
+ /* @__PURE__ */ jsx3(Text3, { children: " " + truncate(p.who, 28).padEnd(29) }),
3142
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: String(p.calls).padStart(5) + " calls " }),
3143
+ /* @__PURE__ */ jsx3(Text3, { color: p.denied > 0 ? theme.bad : theme.dim, children: String(p.denied).padStart(4) + " denied " }),
3144
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: hhmmss2(p.last) })
3145
+ ] }, p.who))
3146
+ ] }) : null,
3147
+ /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
3148
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: onlyDenies ? "Refusals" : "Calls" }),
3149
+ streamQ.error ? /* @__PURE__ */ jsx3(Text3, { color: theme.bad, children: "\u2717 " + streamQ.error }) : shown.length === 0 ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "Nothing yet. This fills as your fleet works." }) : shown.slice(0, tickerRows).map((row) => /* @__PURE__ */ jsx3(StreamRow, { row, width }, row.id))
3150
+ ] }),
3151
+ /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "space pause \xB7 d denies only \xB7 r refresh \xB7 \u2190/esc back" }) })
3152
+ ] });
3153
+ }
3154
+ function StreamRow({ row, width }) {
3155
+ const decision = (row.decision || "").toUpperCase();
3156
+ const who = truncate(row.who, 22).padEnd(23);
3157
+ const tool = truncate(row.tool || "\u2014", 26).padEnd(27);
3158
+ const rest = Math.max(10, width - 23 - 27 - 9 - 8);
3159
+ const tail = decision === "DENY" ? row.reason || "" : row.agent || "";
3160
+ return /* @__PURE__ */ jsxs3(Box3, { children: [
3161
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: hhmmss2(row.created_at) + " " }),
3162
+ /* @__PURE__ */ jsx3(Text3, { children: who }),
3163
+ /* @__PURE__ */ jsx3(Text3, { color: decisionColor(decision), children: (decision || "\u2014").padEnd(7) }),
3164
+ /* @__PURE__ */ jsx3(Text3, { children: tool }),
3165
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: truncate(tail, rest) })
3166
+ ] });
3167
+ }
3168
+
3169
+ // src/tui/panels/Fleet.tsx
2946
3170
  import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
2947
- import TextInput3 from "ink-text-input";
2948
- import { useEffect as useEffect4, useState as useState4 } from "react";
3171
+ import TextInput2 from "ink-text-input";
3172
+ import { useMemo as useMemo2, useState as useState4 } from "react";
3173
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3174
+ var SELF = "self";
3175
+ var UNGROUPED = "~ungrouped";
3176
+ var GROUP_COLORS = ["slate", "blue", "green", "amber", "red", "violet", "cyan", "pink"];
3177
+ var shortId = (id) => id.length > 10 ? id.slice(0, 9) + "\u2026" : id;
3178
+ var policyLabel = (id, policies) => {
3179
+ if (!id) return "\u2014";
3180
+ const hit = policies.find((p) => p.id === id);
3181
+ return hit ? hit.name : shortId(id);
3182
+ };
3183
+ function FleetPanel({ focused }) {
3184
+ const { cols, rows } = usePanelSize();
3185
+ const overviewQ = useLoader(() => api.fleet.overview());
3186
+ const groupsQ = useLoader(() => api.fleet.groups());
3187
+ const summaryQ = useLoader(() => api.fleet.summary({ range: "30d" }));
3188
+ const policiesQ = useLoader(() => api.policies.list());
3189
+ const [view, setView] = useState4("roster");
3190
+ const [sel, setSel] = useState4(0);
3191
+ const [status, setStatus] = useState4(null);
3192
+ const [prompt, setPrompt] = useState4(null);
3193
+ const [draft, setDraft] = useState4("");
3194
+ const [pick, setPick] = useState4(0);
3195
+ const [confirm, setConfirm] = useState4(null);
3196
+ usePoll(() => {
3197
+ if (prompt) return;
3198
+ overviewQ.reloadQuiet();
3199
+ groupsQ.reloadQuiet();
3200
+ summaryQ.reloadQuiet();
3201
+ }, 15e3);
3202
+ const policies = policiesQ.data?.policies ?? [];
3203
+ const overview2 = overviewQ.data;
3204
+ const groups2 = groupsQ.data?.groups ?? [];
3205
+ const summary2 = summaryQ.data;
3206
+ const byUser = useMemo2(() => {
3207
+ const map = /* @__PURE__ */ new Map();
3208
+ for (const p of summary2?.people ?? []) map.set(p.role === "host" ? SELF : p.guest_user_id, p);
3209
+ return map;
3210
+ }, [summary2]);
3211
+ const list5 = useMemo2(() => {
3212
+ const accepted = (overview2?.invitations ?? []).filter((m) => m.status === "accepted");
3213
+ const pending2 = (overview2?.invitations ?? []).filter((m) => m.status === "pending");
3214
+ const self = {
3215
+ id: "",
3216
+ guest_user_id: SELF,
3217
+ email: "You (host)",
3218
+ status: "accepted",
3219
+ group: overview2?.host_group ?? "",
3220
+ policy_id: "",
3221
+ top_agent: overview2?.host_top_agent ?? "",
3222
+ variant_id: "",
3223
+ created_at: "",
3224
+ accepted_at: ""
3225
+ };
3226
+ const named = groups2.filter((g) => g.name !== UNGROUPED);
3227
+ const ungrouped = groups2.find((g) => g.name === UNGROUPED) ?? { name: UNGROUPED, members: 0, color: "slate", policy_id: "" };
3228
+ const out = [];
3229
+ const push = (group, members) => {
3230
+ out.push({ kind: "group", group });
3231
+ for (const m of members) {
3232
+ out.push({ kind: "member", group, member: m, person: byUser.get(m.guest_user_id || SELF) });
3233
+ }
3234
+ };
3235
+ const inGroup = (name) => {
3236
+ const mine = accepted.filter((m) => (m.group || "") === name);
3237
+ if ((self.group || "") === name) return [self, ...mine];
3238
+ return mine;
3239
+ };
3240
+ for (const g of named) push(g, inGroup(g.name));
3241
+ push(ungrouped, inGroup(""));
3242
+ if (pending2.length > 0) {
3243
+ const waiting = { name: "Waiting to answer", members: pending2.length, color: "amber", policy_id: "" };
3244
+ out.push({ kind: "group", group: waiting });
3245
+ for (const m of pending2) out.push({ kind: "pending", group: waiting, member: m });
3246
+ }
3247
+ return out;
3248
+ }, [overview2, groups2, byUser]);
3249
+ const current = list5[Math.min(sel, Math.max(0, list5.length - 1))];
3250
+ const say = (msg) => setStatus(msg);
3251
+ const reloadAll = () => {
3252
+ overviewQ.reload();
3253
+ groupsQ.reload();
3254
+ summaryQ.reload();
3255
+ };
3256
+ const run = async (what, fn) => {
3257
+ setStatus(what + "\u2026");
3258
+ try {
3259
+ await fn();
3260
+ setStatus("\u2713 " + what);
3261
+ reloadAll();
3262
+ } catch (e) {
3263
+ setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
3264
+ }
3265
+ };
3266
+ const openPrompt = (p, initial = "") => {
3267
+ setPrompt(p);
3268
+ setDraft(initial);
3269
+ setStatus(null);
3270
+ setPick(0);
3271
+ };
3272
+ const submitPrompt = async (value) => {
3273
+ const v = value.trim();
3274
+ const p = prompt;
3275
+ setPrompt(null);
3276
+ setDraft("");
3277
+ if (!p) return;
3278
+ if (p === "invite") {
3279
+ if (!v) return;
3280
+ await run("Invited " + v, () => api.fleet.invite(v));
3281
+ return;
3282
+ }
3283
+ if (p === "newGroup") {
3284
+ if (!v) return;
3285
+ await run("Created " + v, () => api.fleet.createGroup(v));
3286
+ return;
3287
+ }
3288
+ if (p === "rename") {
3289
+ const g = current?.group;
3290
+ if (!v || !g || g.name === UNGROUPED) return;
3291
+ await run("Renamed to " + v, () => api.fleet.renameGroup(g.name, v));
3292
+ return;
3293
+ }
3294
+ if (p === "assign") {
3295
+ const m = current?.member;
3296
+ if (!m || !m.id) return;
3297
+ await run(v ? "Filed under " + v : "Unfiled", () => api.fleet.assignGroup([m.id], v));
3298
+ return;
3299
+ }
3300
+ if (p === "emails" || p === "telegram" || p === "endTime") {
3301
+ await saveSchedule(p, v);
3302
+ return;
3303
+ }
3304
+ };
3305
+ const reportQ = useLoader(() => view === "reports" ? api.fleet.report("day") : Promise.resolve(null), [view]);
3306
+ const scheduleQ = useLoader(() => view === "reports" ? api.fleet.reportSchedule() : Promise.resolve(null), [view]);
3307
+ const schedule = scheduleQ.data?.schedule;
3308
+ const saveSchedule = async (field, value) => {
3309
+ if (!schedule) return;
3310
+ const next = {};
3311
+ if (field === "emails") next.emails = splitTargets(value);
3312
+ if (field === "telegram") next.telegram = splitTargets(value);
3313
+ if (field === "endTime") next.endMinute = minutesOf(value);
3314
+ await run("Saved schedule", async () => {
3315
+ await api.fleet.setReportSchedule(next);
3316
+ scheduleQ.reload();
3317
+ reportQ.reload();
3318
+ });
3319
+ };
3320
+ const toggleSpan = async (span) => {
3321
+ if (!schedule) return;
3322
+ await run(span + " report " + (schedule[span] ? "off" : "on"), async () => {
3323
+ await api.fleet.setReportSchedule({ [span]: !schedule[span] });
3324
+ scheduleQ.reload();
3325
+ });
3326
+ };
3327
+ useInput3(
3328
+ (input, key) => {
3329
+ if (prompt) {
3330
+ if (key.escape) {
3331
+ setPrompt(null);
3332
+ setDraft("");
3333
+ }
3334
+ return;
3335
+ }
3336
+ if (confirm) {
3337
+ if (input === "y") {
3338
+ const target = confirm;
3339
+ setConfirm(null);
3340
+ void doDelete(target);
3341
+ } else {
3342
+ setConfirm(null);
3343
+ }
3344
+ return;
3345
+ }
3346
+ if (view === "person") {
3347
+ if (key.leftArrow || key.escape) setView("roster");
3348
+ return;
3349
+ }
3350
+ if (view === "reports") {
3351
+ if (key.leftArrow || key.escape) setView("roster");
3352
+ else if (input === "d") void toggleSpan("day");
3353
+ else if (input === "w") void toggleSpan("week");
3354
+ else if (input === "m") void toggleSpan("month");
3355
+ else if (input === "e") openPrompt("emails", (schedule?.emails ?? []).join(", "));
3356
+ else if (input === "t") openPrompt("telegram", (schedule?.telegram ?? []).join(", "));
3357
+ else if (input === "h") openPrompt("endTime", timeOf(schedule?.endMinute ?? 0));
3358
+ else if (input === "s") void run("Sent", () => api.fleet.sendReport("day"));
3359
+ else if (input === "r") {
3360
+ reportQ.reload();
3361
+ scheduleQ.reload();
3362
+ }
3363
+ return;
3364
+ }
3365
+ if (key.upArrow) setSel((s) => Math.max(0, s - 1));
3366
+ else if (key.downArrow) setSel((s) => Math.min(list5.length - 1, s + 1));
3367
+ else if (key.rightArrow || key.return) {
3368
+ if (current?.kind === "member") setView("person");
3369
+ } else if (input === "R") setView("reports");
3370
+ else if (input === "i") openPrompt("invite");
3371
+ else if (input === "n") openPrompt("newGroup");
3372
+ else if (input === "r") {
3373
+ if (current?.kind === "group" && current.group.name !== UNGROUPED) openPrompt("rename", current.group.name);
3374
+ } else if (input === "g") {
3375
+ if (current?.kind === "member" && current.member?.id) openPrompt("assign", current.member.group || "");
3376
+ } else if (input === "c") void cycleColor();
3377
+ else if (input === "p") openPolicyPicker();
3378
+ else if (input === "d") askDelete();
3379
+ else if (input === "x") reloadAll();
3380
+ },
3381
+ { isActive: focused }
3382
+ );
3383
+ const cycleColor = async () => {
3384
+ const g = current?.group;
3385
+ if (!g || g.name === UNGROUPED || current?.kind !== "group") return;
3386
+ const next = GROUP_COLORS[(GROUP_COLORS.indexOf(g.color) + 1 + GROUP_COLORS.length) % GROUP_COLORS.length];
3387
+ await run("Coloured " + next, () => api.fleet.setGroupColor(g.name, next));
3388
+ };
3389
+ const openPolicyPicker = () => {
3390
+ if (!current) return;
3391
+ if (current.kind === "pending") return;
3392
+ if (policies.length === 0) {
3393
+ say("No policies to choose from yet.");
3394
+ return;
3395
+ }
3396
+ openPrompt("policy");
3397
+ };
3398
+ const choosePolicy = async (id) => {
3399
+ const target = current;
3400
+ setPrompt(null);
3401
+ if (!target) return;
3402
+ if (target.kind === "group") {
3403
+ if (target.group.name === UNGROUPED) {
3404
+ say("Uncategorized is the absence of a group, so it carries no policy.");
3405
+ return;
3406
+ }
3407
+ await run(
3408
+ id ? "Group runs " + policyLabel(id, policies) : "Group names no policy",
3409
+ () => api.fleet.setGroupPolicy(target.group.name, id)
3410
+ );
3411
+ return;
3412
+ }
3413
+ const m = target.member;
3414
+ if (!m?.id) {
3415
+ say("The host's own machine follows their group. Set the group's policy.");
3416
+ return;
3417
+ }
3418
+ await run(
3419
+ id ? "Pinned to " + policyLabel(id, policies) : "Back to their group",
3420
+ () => api.fleet.setUserPolicy([m.id], id)
3421
+ );
3422
+ };
3423
+ const askDelete = () => {
3424
+ if (!current) return;
3425
+ if (current.kind === "group") {
3426
+ if (current.group.name === UNGROUPED) return;
3427
+ setConfirm("group:" + current.group.name);
3428
+ return;
3429
+ }
3430
+ const m = current.member;
3431
+ if (!m?.id) {
3432
+ say("You cannot remove yourself from your own fleet.");
3433
+ return;
3434
+ }
3435
+ setConfirm("member:" + m.id);
3436
+ };
3437
+ const doDelete = async (target) => {
3438
+ const [kind, ...rest] = target.split(":");
3439
+ const value = rest.join(":");
3440
+ if (kind === "group") await run("Deleted " + value, () => api.fleet.deleteGroup(value));
3441
+ else await run("Revoked", () => api.fleet.revoke(value));
3442
+ };
3443
+ const loading = overviewQ.loading || groupsQ.loading;
3444
+ const error = overviewQ.error ?? groupsQ.error;
3445
+ if (overview2 && !overview2.host) {
3446
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3447
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "This account does not run a fleet." }),
3448
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Ask whoever manages your team for a seat, then come back." })
3449
+ ] });
3450
+ }
3451
+ if (view === "person") {
3452
+ return /* @__PURE__ */ jsx4(PersonView, { row: current, policies, cols });
3453
+ }
3454
+ if (view === "reports") {
3455
+ return /* @__PURE__ */ jsx4(
3456
+ ReportsView,
3457
+ {
3458
+ report: reportQ.data,
3459
+ loading: reportQ.loading,
3460
+ error: reportQ.error,
3461
+ schedule,
3462
+ next: scheduleQ.data?.next ?? 0,
3463
+ status,
3464
+ prompt,
3465
+ draft,
3466
+ onChange: setDraft,
3467
+ onSubmit: submitPrompt,
3468
+ cols
3469
+ }
3470
+ );
3471
+ }
3472
+ const visible = windowed(list5, sel, Math.max(4, rows - 6));
3473
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3474
+ /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
3475
+ (overview2?.used ?? 0) + " of " + (overview2?.seats ?? 0) + " seats",
3476
+ summary2 ? " " + summary2.totals.calls + " calls \xB7 " + summary2.totals.denied + " denied (30d)" : ""
3477
+ ] }) }),
3478
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx4(DataView, { loading, error, empty: list5.length === 0, emptyText: "Nobody on this fleet yet. Press i to invite somebody.", children: visible.map((row, i) => {
3479
+ const index = list5.indexOf(row);
3480
+ const selected = index === sel;
3481
+ if (row.kind === "group") {
3482
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
3483
+ /* @__PURE__ */ jsx4(Text4, { color: selected ? theme.accentBright : theme.accent, bold: true, children: (selected ? "\u25B8 " : " ") + label(row.group).padEnd(22) }),
3484
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: String(row.group.members).padStart(3) + " people " }),
3485
+ /* @__PURE__ */ jsx4(Text4, { color: row.group.policy_id ? theme.ok : theme.dim, children: row.group.policy_id ? policyLabel(row.group.policy_id, policies) : "no group policy" })
3486
+ ] }, "g" + index + i);
3487
+ }
3488
+ const m = row.member;
3489
+ const pinned = Boolean(m.policy_id);
3490
+ const runs = pinned ? m.policy_id : row.group.policy_id;
3491
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
3492
+ /* @__PURE__ */ jsx4(Text4, { color: selected ? theme.accentBright : void 0, children: (selected ? " \u25B8 " : " ") + truncate(m.email, 30).padEnd(31) }),
3493
+ row.kind === "pending" ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "waiting" }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
3494
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: String(row.person?.calls ?? 0).padStart(6) + " calls " }),
3495
+ /* @__PURE__ */ jsx4(Text4, { color: (row.person?.denied ?? 0) > 0 ? theme.bad : theme.dim, children: String(row.person?.denied ?? 0).padStart(4) + " deny " }),
3496
+ /* @__PURE__ */ jsx4(Text4, { color: pinned ? theme.warn : theme.dim, children: runs ? policyLabel(runs, policies) + (pinned ? " (only them)" : "") : "\u2014" })
3497
+ ] })
3498
+ ] }, "m" + index + i);
3499
+ }) }) }),
3500
+ confirm ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: confirm.startsWith("group:") ? "Delete this group? Its people become uncategorized. (y/n)" : "Revoke this person? Their machines stop being managed. (y/n)" }) }) : null,
3501
+ prompt === "policy" ? /* @__PURE__ */ jsx4(
3502
+ PolicyPicker,
3503
+ {
3504
+ policies,
3505
+ pick,
3506
+ onMove: setPick,
3507
+ onChoose: choosePolicy,
3508
+ onCancel: () => setPrompt(null),
3509
+ focused,
3510
+ target: current?.kind === "group" ? label(current.group) : current?.member?.email ?? ""
3511
+ }
3512
+ ) : prompt ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
3513
+ /* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: promptLabel(prompt) + " " }),
3514
+ /* @__PURE__ */ jsx4(TextInput2, { value: draft, onChange: setDraft, onSubmit: submitPrompt })
3515
+ ] }) : null,
3516
+ status ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null
3517
+ ] });
3518
+ }
3519
+ var label = (g) => g.name === UNGROUPED ? "Uncategorized" : g.name;
3520
+ var promptLabel = (p) => {
3521
+ switch (p) {
3522
+ case "invite":
3523
+ return "Invite (email):";
3524
+ case "newGroup":
3525
+ return "New group:";
3526
+ case "rename":
3527
+ return "Rename to:";
3528
+ case "assign":
3529
+ return "File under (blank = uncategorized):";
3530
+ case "emails":
3531
+ return "Email the report to:";
3532
+ case "telegram":
3533
+ return "Telegram chat id or @handle:";
3534
+ case "endTime":
3535
+ return "A day ends at (HH:MM):";
3536
+ default:
3537
+ return "";
3538
+ }
3539
+ };
3540
+ function windowed(list5, sel, height) {
3541
+ if (list5.length <= height) return list5;
3542
+ const start = Math.min(Math.max(0, sel - Math.floor(height / 2)), list5.length - height);
3543
+ return list5.slice(start, start + height);
3544
+ }
3545
+ var splitTargets = (v) => v.split(/[\s,;]+/).map((s) => s.trim()).filter(Boolean);
3546
+ var minutesOf = (v) => {
3547
+ const [h, m] = v.split(":");
3548
+ const total = (Number(h) || 0) * 60 + (Number(m) || 0);
3549
+ return total >= 0 && total <= 1439 ? total : 0;
3550
+ };
3551
+ var timeOf = (minute) => String(Math.floor(minute / 60)).padStart(2, "0") + ":" + String(minute % 60).padStart(2, "0");
3552
+ function PolicyPicker({
3553
+ policies,
3554
+ pick,
3555
+ onMove,
3556
+ onChoose,
3557
+ onCancel,
3558
+ focused,
3559
+ target
3560
+ }) {
3561
+ const options = [
3562
+ { id: "", name: "No policy of its own", detail: "follow the level above" },
3563
+ ...policies.map((p) => ({
3564
+ id: p.id,
3565
+ name: p.name,
3566
+ detail: (p.rules?.length ?? 0) + " rules \xB7 " + p.mode
3567
+ }))
3568
+ ];
3569
+ useInput3(
3570
+ (_input, key) => {
3571
+ if (key.upArrow) onMove(Math.max(0, pick - 1));
3572
+ else if (key.downArrow) onMove(Math.min(options.length - 1, pick + 1));
3573
+ else if (key.return) onChoose(options[pick]?.id ?? "");
3574
+ else if (key.escape || key.leftArrow) onCancel();
3575
+ },
3576
+ { isActive: focused }
3577
+ );
3578
+ const visible = windowed(options, pick, 8);
3579
+ return /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
3580
+ /* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: "Policy for " + target + " (\u2191\u2193 enter \xB7 esc cancel)" }),
3581
+ visible.map((o, i) => {
3582
+ const index = options.indexOf(o);
3583
+ const selected = index === pick;
3584
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
3585
+ /* @__PURE__ */ jsx4(Text4, { color: selected ? theme.accentBright : void 0, children: (selected ? "\u25B8 " : " ") + truncate(o.name, 30).padEnd(31) }),
3586
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: o.detail })
3587
+ ] }, o.id + i);
3588
+ })
3589
+ ] });
3590
+ }
3591
+ function PersonView({ row, policies, cols }) {
3592
+ const m = row?.member;
3593
+ const guest = m?.guest_user_id || SELF;
3594
+ const analyticsQ = useLoader(() => api.fleet.analytics(guest), [guest]);
3595
+ if (!m) return /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Nobody selected." });
3596
+ const a = analyticsQ.data ?? {};
3597
+ const totals = a["totals"] ?? {};
3598
+ const agents = a["agents"] ?? [];
3599
+ const tools = a["tools"] ?? [];
3600
+ const denies = a["deny_reasons"] ?? [];
3601
+ const runs = m.policy_id || row?.group.policy_id || "";
3602
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3603
+ /* @__PURE__ */ jsxs4(Box4, { children: [
3604
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accentBright, children: m.email }),
3605
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " " + (row?.group ? label(row.group) : "") })
3606
+ ] }),
3607
+ /* @__PURE__ */ jsxs4(Box4, { children: [
3608
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Runs " }),
3609
+ /* @__PURE__ */ jsx4(Text4, { color: m.policy_id ? theme.warn : theme.ok, children: runs ? policyLabel(runs, policies) + (m.policy_id ? " (only them)" : " (their group)") : "the project default" })
3610
+ ] }),
3611
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(DataView, { loading: analyticsQ.loading, error: analyticsQ.error, children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3612
+ /* @__PURE__ */ jsxs4(Text4, { children: [
3613
+ String(totals.calls ?? 0) + " calls ",
3614
+ /* @__PURE__ */ jsx4(Text4, { color: (totals.denied ?? 0) > 0 ? theme.bad : theme.dim, children: String(totals.denied ?? 0) + " denied" }),
3615
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " " + String(totals.sessions ?? 0) + " sessions" })
3616
+ ] }),
3617
+ denies.length > 0 ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
3618
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "What was stopped" }),
3619
+ /* @__PURE__ */ jsx4(
3620
+ Table,
3621
+ {
3622
+ columns: [{ header: "reason", width: Math.min(48, cols - 14) }, { header: "times", width: 8 }],
3623
+ rows: denies.slice(0, 6).map((d) => [{ value: d.name, color: theme.bad }, { value: String(d.calls) }])
3624
+ }
3625
+ )
3626
+ ] }) : null,
3627
+ tools.length > 0 ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
3628
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Most used tools" }),
3629
+ /* @__PURE__ */ jsx4(
3630
+ Table,
3631
+ {
3632
+ columns: [{ header: "tool", width: Math.min(48, cols - 14) }, { header: "calls", width: 8 }],
3633
+ rows: tools.slice(0, 6).map((t) => [{ value: t.name }, { value: String(t.calls) }])
3634
+ }
3635
+ )
3636
+ ] }) : null,
3637
+ agents.length > 0 ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Agents: " + agents.slice(0, 5).map((x) => x.name + " (" + x.calls + ")").join(" ") }) }) : null
3638
+ ] }) }) }),
3639
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2190 back" }) })
3640
+ ] });
3641
+ }
3642
+ function ReportsView({
3643
+ report: report2,
3644
+ loading,
3645
+ error,
3646
+ schedule,
3647
+ next,
3648
+ status,
3649
+ prompt,
3650
+ draft,
3651
+ onChange,
3652
+ onSubmit,
3653
+ cols
3654
+ }) {
3655
+ const on = (v) => v ? theme.ok : theme.dim;
3656
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3657
+ /* @__PURE__ */ jsx4(DataView, { loading, error, children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3658
+ /* @__PURE__ */ jsx4(Text4, { bold: true, children: report2?.headline ?? "" }),
3659
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: report2?.title ?? "" }),
3660
+ report2 && report2.top_denies.length > 0 ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
3661
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "What was stopped" }),
3662
+ /* @__PURE__ */ jsx4(
3663
+ Table,
3664
+ {
3665
+ columns: [{ header: "reason", width: Math.min(48, cols - 14) }, { header: "times", width: 8 }],
3666
+ rows: report2.top_denies.slice(0, 5).map((d) => [{ value: d.name, color: theme.bad }, { value: String(d.calls) }])
3667
+ }
3668
+ )
3669
+ ] }) : null
3670
+ ] }) }),
3671
+ /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
3672
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "Scheduled" }),
3673
+ /* @__PURE__ */ jsxs4(Box4, { children: [
3674
+ /* @__PURE__ */ jsx4(Text4, { color: on(schedule?.day), children: "daily " }),
3675
+ /* @__PURE__ */ jsx4(Text4, { color: on(schedule?.week), children: "weekly " }),
3676
+ /* @__PURE__ */ jsx4(Text4, { color: on(schedule?.month), children: "monthly " }),
3677
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "day ends " + timeOf(schedule?.endMinute ?? 0) + " UTC" })
3678
+ ] }),
3679
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "to " + ((schedule?.emails ?? []).concat(schedule?.telegram ?? []).join(", ") || "nobody yet") }),
3680
+ next > 0 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "next " + new Date(next * 1e3).toISOString().slice(0, 16).replace("T", " ") + " UTC" }) : null
3681
+ ] }),
3682
+ prompt ? /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
3683
+ /* @__PURE__ */ jsx4(Text4, { color: theme.accent, children: promptLabel(prompt) + " " }),
3684
+ /* @__PURE__ */ jsx4(TextInput2, { value: draft, onChange, onSubmit })
3685
+ ] }) : null,
3686
+ status ? /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) }) : null,
3687
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "d daily \xB7 w weekly \xB7 m monthly \xB7 h day ends \xB7 e email \xB7 t telegram \xB7 s send now \xB7 \u2190 back" }) })
3688
+ ] });
3689
+ }
3690
+
3691
+ // src/tui/panels/Policies.tsx
3692
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
3693
+ import TextInput4 from "ink-text-input";
3694
+ import { useEffect as useEffect5, useState as useState6 } from "react";
2949
3695
 
2950
3696
  // src/tui/panels/DryRun.tsx
2951
- import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
2952
- import TextInput2 from "ink-text-input";
2953
- import { useEffect as useEffect3, useState as useState3 } from "react";
2954
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3697
+ import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
3698
+ import TextInput3 from "ink-text-input";
3699
+ import { useEffect as useEffect4, useState as useState5 } from "react";
3700
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2955
3701
  var DEFAULT_LIMIT = 1e3;
2956
3702
  var MAX_LIMIT = 5e3;
2957
3703
  var pendingPolicyId = null;
@@ -2962,15 +3708,15 @@ var pct = (n, d) => d ? (n / d * 100).toFixed(1) + "%" : "0%";
2962
3708
  function DryRunPanel({ focused }) {
2963
3709
  const list5 = useLoader(() => api.policies.list());
2964
3710
  const policies = list5.data?.policies ?? [];
2965
- const [pi, setPi] = useState3(0);
2966
- const [limit, setLimit] = useState3(DEFAULT_LIMIT);
2967
- const [editLogs, setEditLogs] = useState3(null);
3711
+ const [pi, setPi] = useState5(0);
3712
+ const [limit, setLimit] = useState5(DEFAULT_LIMIT);
3713
+ const [editLogs, setEditLogs] = useState5(null);
2968
3714
  const statsQ = useLoader(() => api.stats.get());
2969
3715
  const totalLogs = statsQ.data?.total_calls ?? null;
2970
- const [res, setRes] = useState3(null);
2971
- const [running, setRunning] = useState3(false);
2972
- const [err2, setErr] = useState3(null);
2973
- const [off, setOff] = useState3(0);
3716
+ const [res, setRes] = useState5(null);
3717
+ const [running, setRunning] = useState5(false);
3718
+ const [err2, setErr] = useState5(null);
3719
+ const [off, setOff] = useState5(0);
2974
3720
  const { cols, rows } = usePanelSize();
2975
3721
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
2976
3722
  function run(policyIdx = pi, lim = limit) {
@@ -2982,7 +3728,7 @@ function DryRunPanel({ focused }) {
2982
3728
  setOff(0);
2983
3729
  api.policies.backtest({ rules: p.rules ?? [], mode: p.mode, limit: lim }).then((r) => setRes(r)).catch((e) => setErr(e instanceof Error ? e.message : String(e))).finally(() => setRunning(false));
2984
3730
  }
2985
- useEffect3(() => {
3731
+ useEffect4(() => {
2986
3732
  if (policies.length === 0 || res || running || err2) return;
2987
3733
  let idx = 0;
2988
3734
  if (pendingPolicyId) {
@@ -2993,7 +3739,7 @@ function DryRunPanel({ focused }) {
2993
3739
  setPi(idx);
2994
3740
  run(idx, limit);
2995
3741
  }, [policies.length]);
2996
- useInput2((input, key) => {
3742
+ useInput4((input, key) => {
2997
3743
  if (!focused || editLogs !== null) return;
2998
3744
  if (key.upArrow) setOff((n) => Math.max(0, n - 1));
2999
3745
  else if (key.downArrow) setOff((n) => n + 1);
@@ -3015,10 +3761,10 @@ function DryRunPanel({ focused }) {
3015
3761
  }
3016
3762
  const w = Math.max(40, cols);
3017
3763
  const lines = [];
3018
- const head = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: t }, `h${lines.length}`));
3019
- const dim2 = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: t }, `d${lines.length}`));
3020
- const row = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { wrap: "truncate", children: t }, `r${lines.length}`));
3021
- const blank = () => lines.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, `b${lines.length}`));
3764
+ const head = (t) => lines.push(/* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.accentBright, children: t }, `h${lines.length}`));
3765
+ const dim2 = (t) => lines.push(/* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: t }, `d${lines.length}`));
3766
+ const row = (t) => lines.push(/* @__PURE__ */ jsx5(Text5, { wrap: "truncate", children: t }, `r${lines.length}`));
3767
+ const blank = () => lines.push(/* @__PURE__ */ jsx5(Text5, { children: " " }, `b${lines.length}`));
3022
3768
  if (res) {
3023
3769
  const s = res.summary;
3024
3770
  const denyAfter = s.would_deny;
@@ -3029,13 +3775,13 @@ function DryRunPanel({ focused }) {
3029
3775
  head("Summary");
3030
3776
  row(` evaluated ${s.evaluated} would allow ${s.would_allow} would deny ${s.would_deny}`);
3031
3777
  lines.push(
3032
- /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
3778
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3033
3779
  " newly blocked ",
3034
- /* @__PURE__ */ jsx3(Text3, { color: theme.bad, bold: true, children: String(s.newly_blocked) }),
3780
+ /* @__PURE__ */ jsx5(Text5, { color: theme.bad, bold: true, children: String(s.newly_blocked) }),
3035
3781
  " newly allowed ",
3036
- /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: String(s.newly_allowed) }),
3782
+ /* @__PURE__ */ jsx5(Text5, { color: theme.ok, bold: true, children: String(s.newly_allowed) }),
3037
3783
  " unchanged ",
3038
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: String(s.unchanged) })
3784
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: String(s.unchanged) })
3039
3785
  ] }, `sum${lines.length}`)
3040
3786
  );
3041
3787
  row(` deny rate ${before} -> ${after} (${Number(ppShift) > 0 ? "+" : ""}${ppShift}pp)`);
@@ -3070,12 +3816,12 @@ function DryRunPanel({ focused }) {
3070
3816
  const flip = `${c2.original}->${c2.predicted}`;
3071
3817
  const newlyAllowed = c2.predicted === "ALLOW";
3072
3818
  lines.push(
3073
- /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
3819
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3074
3820
  " ",
3075
- /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: truncate(c2.tool, 16).padEnd(17) }),
3076
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: truncate(c2.agent || "-", 14).padEnd(15) }),
3077
- /* @__PURE__ */ jsx3(Text3, { color: newlyAllowed ? theme.ok : theme.bad, children: flip.padEnd(14) }),
3078
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: truncate(c2.preview.replace(/\s+/g, " "), Math.max(10, w - 50)) })
3821
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: truncate(c2.tool, 16).padEnd(17) }),
3822
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: truncate(c2.agent || "-", 14).padEnd(15) }),
3823
+ /* @__PURE__ */ jsx5(Text5, { color: newlyAllowed ? theme.ok : theme.bad, children: flip.padEnd(14) }),
3824
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: truncate(c2.preview.replace(/\s+/g, " "), Math.max(10, w - 50)) })
3079
3825
  ] }, `c${lines.length}`)
3080
3826
  );
3081
3827
  }
@@ -3088,36 +3834,36 @@ function DryRunPanel({ focused }) {
3088
3834
  const win = lines.slice(start, start + budget);
3089
3835
  const above = start;
3090
3836
  const below = Math.max(0, lines.length - (start + budget));
3091
- return /* @__PURE__ */ jsx3(DataView, { loading: list5.loading && !list5.data, error: list5.error, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
3092
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Policy Dry Run" }),
3093
- /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
3094
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " policy " }),
3095
- /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + truncate(selected?.name ?? "none", 30) + " \u203A" }),
3096
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` ${policies.length ? pi + 1 : 0}/${policies.length} press p \xB7 mode ${selected?.mode ?? "-"}` })
3837
+ return /* @__PURE__ */ jsx5(DataView, { loading: list5.loading && !list5.data, error: list5.error, children: /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
3838
+ /* @__PURE__ */ jsx5(Text5, { bold: true, color: theme.accentBright, children: "Policy Dry Run" }),
3839
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3840
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " policy " }),
3841
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: "\u2039 " + truncate(selected?.name ?? "none", 30) + " \u203A" }),
3842
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: ` ${policies.length ? pi + 1 : 0}/${policies.length} press p \xB7 mode ${selected?.mode ?? "-"}` })
3097
3843
  ] }),
3098
- editLogs !== null ? /* @__PURE__ */ jsxs3(Box3, { children: [
3099
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
3100
- /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "how many recent logs to replay: " }),
3101
- /* @__PURE__ */ jsx3(TextInput2, { value: editLogs, onChange: setEditLogs, onSubmit: commitLogs }),
3102
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` (max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)})` })
3103
- ] }) : /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
3104
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " logs " }),
3105
- /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: "\u2039 " + String(limit) + " \u203A" }),
3106
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` press n to type a number max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)}${res?.sampled != null ? ` replayed ${res.sampled}` : ""}` })
3844
+ editLogs !== null ? /* @__PURE__ */ jsxs5(Box5, { children: [
3845
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " logs " }),
3846
+ /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: "how many recent logs to replay: " }),
3847
+ /* @__PURE__ */ jsx5(TextInput3, { value: editLogs, onChange: setEditLogs, onSubmit: commitLogs }),
3848
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: ` (max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)})` })
3849
+ ] }) : /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3850
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " logs " }),
3851
+ /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: "\u2039 " + String(limit) + " \u203A" }),
3852
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: ` press n to type a number max ${Math.min(MAX_LIMIT, totalLogs && totalLogs > 0 ? totalLogs : MAX_LIMIT)}${res?.sampled != null ? ` replayed ${res.sampled}` : ""}` })
3107
3853
  ] }),
3108
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n logs \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
3109
- running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
3110
- err2 ? /* @__PURE__ */ jsx3(Text3, { color: theme.bad, wrap: "truncate", children: "\u2717 " + err2 }) : null,
3111
- !running && !err2 && !res ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "no result yet, press r to run" }) : null,
3112
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: win })
3854
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n logs \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
3855
+ running ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
3856
+ err2 ? /* @__PURE__ */ jsx5(Text5, { color: theme.bad, wrap: "truncate", children: "\u2717 " + err2 }) : null,
3857
+ !running && !err2 && !res ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "no result yet, press r to run" }) : null,
3858
+ /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", children: win })
3113
3859
  ] }) });
3114
3860
  }
3115
3861
 
3116
3862
  // src/tui/nav.ts
3117
3863
  var pending = null;
3118
3864
  var subs = /* @__PURE__ */ new Set();
3119
- function requestSection(label) {
3120
- pending = label;
3865
+ function requestSection(label2) {
3866
+ pending = label2;
3121
3867
  for (const fn of subs) fn();
3122
3868
  }
3123
3869
  function takePendingSection() {
@@ -3133,7 +3879,7 @@ function onSectionRequest(fn) {
3133
3879
  }
3134
3880
 
3135
3881
  // src/tui/panels/Policies.tsx
3136
- import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3882
+ import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3137
3883
  var CTYPES = ["none", "command", "path", "filename", "url"];
3138
3884
  var CTYPE_GROUP = {
3139
3885
  command: "commandConstraints",
@@ -3237,23 +3983,23 @@ function PoliciesPanel({ focused }) {
3237
3983
  const activeId = activeQ.data?.policy?.id ?? null;
3238
3984
  const activeBy = activeQ.data?.matched_by;
3239
3985
  const { rows: panelRows } = usePanelSize();
3240
- const [view, setView] = useState4("list");
3241
- const [pi, setPi] = useState4(0);
3242
- const [ri, setRi] = useState4(0);
3243
- const [fi, setFi] = useState4(0);
3244
- const [rules, setRules] = useState4([]);
3245
- const [mode, setMode] = useState4("denylist");
3246
- const [dirty, setDirty] = useState4(false);
3247
- const [editing, setEditing] = useState4(false);
3248
- const [editVal, setEditVal] = useState4("");
3249
- const [status, setStatus] = useState4(null);
3250
- const [pendingDel, setPendingDel] = useState4(null);
3251
- const [permCursor, setPermCursor] = useState4(0);
3252
- const [matchSel, setMatchSel] = useState4(0);
3253
- const [busyCreate, setBusyCreate] = useState4(false);
3986
+ const [view, setView] = useState6("list");
3987
+ const [pi, setPi] = useState6(0);
3988
+ const [ri, setRi] = useState6(0);
3989
+ const [fi, setFi] = useState6(0);
3990
+ const [rules, setRules] = useState6([]);
3991
+ const [mode, setMode] = useState6("denylist");
3992
+ const [dirty, setDirty] = useState6(false);
3993
+ const [editing, setEditing] = useState6(false);
3994
+ const [editVal, setEditVal] = useState6("");
3995
+ const [status, setStatus] = useState6(null);
3996
+ const [pendingDel, setPendingDel] = useState6(null);
3997
+ const [permCursor, setPermCursor] = useState6(0);
3998
+ const [matchSel, setMatchSel] = useState6(0);
3999
+ const [busyCreate, setBusyCreate] = useState6(false);
3254
4000
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
3255
4001
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
3256
- useEffect4(() => {
4002
+ useEffect5(() => {
3257
4003
  if (detail.data) {
3258
4004
  setRules(detail.data.rules);
3259
4005
  setMode(detail.data.mode ?? "denylist");
@@ -3304,7 +4050,7 @@ function PoliciesPanel({ focused }) {
3304
4050
  setDirty(false);
3305
4051
  setStatus("discarded");
3306
4052
  };
3307
- const [creating, setCreating] = useState4(null);
4053
+ const [creating, setCreating] = useState6(null);
3308
4054
  function commitNewPolicy(name) {
3309
4055
  const n = name.trim();
3310
4056
  setCreating(null);
@@ -3319,10 +4065,10 @@ function PoliciesPanel({ focused }) {
3319
4065
  setStatus("\u2717 " + (e instanceof Error ? e.message : String(e)));
3320
4066
  });
3321
4067
  }
3322
- useEffect4(() => {
4068
+ useEffect5(() => {
3323
4069
  if (busyCreate && policies.length > 0) setBusyCreate(false);
3324
4070
  }, [busyCreate, policies.length]);
3325
- useInput3(
4071
+ useInput5(
3326
4072
  (input, key) => {
3327
4073
  if (creating !== null) return;
3328
4074
  if (key.ctrl && input === "r" && !editing) {
@@ -3493,39 +4239,39 @@ function PoliciesPanel({ focused }) {
3493
4239
  const lWin = policies.slice(lStart, lStart + lBudget);
3494
4240
  const lAbove = lStart;
3495
4241
  const lBelow = Math.max(0, policies.length - (lStart + lBudget));
3496
- return /* @__PURE__ */ jsx4(DataView, { loading: list5.loading && !list5.data, error: list5.error, empty: !!list5.data && policies.length === 0 && creating === null && !busyCreate, emptyText: "No policies yet \u2014 press n to create one.", children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3497
- /* @__PURE__ */ jsxs4(Box4, { children: [
3498
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "active: " }),
3499
- activeQ.loading && !activeQ.data ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2026" }) : activeName ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
3500
- /* @__PURE__ */ jsxs4(Text4, { color: theme.ok, bold: true, children: [
4242
+ return /* @__PURE__ */ jsx6(DataView, { loading: list5.loading && !list5.data, error: list5.error, empty: !!list5.data && policies.length === 0 && creating === null && !busyCreate, emptyText: "No policies yet \u2014 press n to create one.", children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
4243
+ /* @__PURE__ */ jsxs6(Box6, { children: [
4244
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "active: " }),
4245
+ activeQ.loading && !activeQ.data ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2026" }) : activeName ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
4246
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, bold: true, children: [
3501
4247
  "\u25CF ",
3502
4248
  truncate(activeName, 28)
3503
4249
  ] }),
3504
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
3505
- ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
4250
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
4251
+ ] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
3506
4252
  ] }),
3507
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new \xB7 a activate \xB7 x deactivate \xB7 d delete \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
3508
- creating !== null ? /* @__PURE__ */ jsxs4(Box4, { children: [
3509
- /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: "new policy name: " }),
3510
- /* @__PURE__ */ jsx4(TextInput3, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
4253
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 n new \xB7 a activate \xB7 x deactivate \xB7 d delete \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
4254
+ creating !== null ? /* @__PURE__ */ jsxs6(Box6, { children: [
4255
+ /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "new policy name: " }),
4256
+ /* @__PURE__ */ jsx6(TextInput4, { value: creating, onChange: setCreating, onSubmit: commitNewPolicy })
3511
4257
  ] }) : null,
3512
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
4258
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
3513
4259
  const i = lStart + wi;
3514
- return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
4260
+ return /* @__PURE__ */ jsxs6(Text6, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
3515
4261
  (i === pi ? "\u25B8 " : " ") + truncate(p.name, 26).padEnd(27),
3516
- /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
4262
+ /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3517
4263
  p.mode.padEnd(10),
3518
4264
  " ",
3519
4265
  p.rules.length,
3520
4266
  " rules"
3521
4267
  ] }),
3522
- p.id === activeId ? /* @__PURE__ */ jsx4(Text4, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
4268
+ p.id === activeId ? /* @__PURE__ */ jsx6(Text6, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
3523
4269
  ] }, p.id);
3524
4270
  }) }),
3525
- status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
4271
+ status ? /* @__PURE__ */ jsx6(Text6, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
3526
4272
  ] }) });
3527
4273
  }
3528
- const dirtyTag = dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
4274
+ const dirtyTag = dirty ? /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
3529
4275
  if (view === "rules") {
3530
4276
  const rStatusLines = status ? status.split("\n").length : 0;
3531
4277
  const rHead = 4 + rStatusLines;
@@ -3535,16 +4281,16 @@ function PoliciesPanel({ focused }) {
3535
4281
  const rWin = rules.slice(rStart, rStart + rBudget);
3536
4282
  const rAbove = rStart;
3537
4283
  const rBelow = Math.max(0, rules.length - (rStart + rBudget));
3538
- return /* @__PURE__ */ jsx4(DataView, { loading: detail.loading && !detail.data, error: detail.error, children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3539
- /* @__PURE__ */ jsxs4(Box4, { children: [
3540
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
3541
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " mode: " }),
3542
- /* @__PURE__ */ jsx4(Text4, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
3543
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` ${rules.length} rules` }),
4284
+ return /* @__PURE__ */ jsx6(DataView, { loading: detail.loading && !detail.data, error: detail.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
4285
+ /* @__PURE__ */ jsxs6(Box6, { children: [
4286
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
4287
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " mode: " }),
4288
+ /* @__PURE__ */ jsx6(Text6, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
4289
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` ${rules.length} rules` }),
3544
4290
  dirtyTag
3545
4291
  ] }),
3546
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", 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${rAbove ? ` \xB7 \u25B2${rAbove}` : ""}${rBelow ? ` \xB7 \u25BC${rBelow}` : ""}` }),
3547
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(
4292
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", 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${rAbove ? ` \xB7 \u25B2${rAbove}` : ""}${rBelow ? ` \xB7 \u25BC${rBelow}` : ""}` }),
4293
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(
3548
4294
  Table,
3549
4295
  {
3550
4296
  columns: [
@@ -3568,33 +4314,33 @@ function PoliciesPanel({ focused }) {
3568
4314
  })
3569
4315
  }
3570
4316
  ) }),
3571
- rules.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "(no rules)" }) : null,
3572
- status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
4317
+ rules.length === 0 ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no rules)" }) : null,
4318
+ status ? /* @__PURE__ */ jsx6(Text6, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
3573
4319
  ] }) });
3574
4320
  }
3575
4321
  if (view === "match") {
3576
4322
  const r = rules[ri];
3577
4323
  const items = r ? matchItems(r) : [];
3578
4324
  const t = r ? currentCType(r) : "none";
3579
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3580
- /* @__PURE__ */ jsxs4(Box4, { children: [
3581
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accentBright, children: "Match values " }),
3582
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: r ? `${r.effect} \xB7 ${t}` : "" }),
4325
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
4326
+ /* @__PURE__ */ jsxs6(Box6, { children: [
4327
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accentBright, children: "Match values " }),
4328
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: r ? `${r.effect} \xB7 ${t}` : "" }),
3583
4329
  dirtyTag
3584
4330
  ] }),
3585
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter/e edit \xB7 a add \xB7 [ / ] wildcard L/R \xB7 d remove \xB7 s save \xB7 \u2190 back" }),
3586
- /* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
3587
- items.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "(no values \u2014 press a to add one)" }) : null,
4331
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 select \xB7 enter/e edit \xB7 a add \xB7 [ / ] wildcard L/R \xB7 d remove \xB7 s save \xB7 \u2190 back" }),
4332
+ /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, flexDirection: "column", children: [
4333
+ items.length === 0 ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no values \u2014 press a to add one)" }) : null,
3588
4334
  items.map((it, i) => {
3589
4335
  const sel = i === matchSel;
3590
4336
  if (sel && editing) {
3591
4337
  const lE = editVal.startsWith("*");
3592
4338
  const rE = editVal.length > 0 && editVal.endsWith("*");
3593
- return /* @__PURE__ */ jsxs4(Box4, { children: [
3594
- /* @__PURE__ */ jsx4(Text4, { color: theme.accentBright, children: "\u25B8 " }),
3595
- /* @__PURE__ */ jsx4(Text4, { color: lE ? theme.ok : theme.dim, children: lE ? "\u2731 " : "\xB7 " }),
3596
- /* @__PURE__ */ jsx4(
3597
- TextInput3,
4339
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
4340
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, children: "\u25B8 " }),
4341
+ /* @__PURE__ */ jsx6(Text6, { color: lE ? theme.ok : theme.dim, children: lE ? "\u2731 " : "\xB7 " }),
4342
+ /* @__PURE__ */ jsx6(
4343
+ TextInput4,
3598
4344
  {
3599
4345
  value: editVal,
3600
4346
  onChange: setEditVal,
@@ -3608,41 +4354,41 @@ function PoliciesPanel({ focused }) {
3608
4354
  }
3609
4355
  }
3610
4356
  ),
3611
- /* @__PURE__ */ jsx4(Text4, { color: rE ? theme.ok : theme.dim, children: rE ? " \u2731" : " \xB7" })
4357
+ /* @__PURE__ */ jsx6(Text6, { color: rE ? theme.ok : theme.dim, children: rE ? " \u2731" : " \xB7" })
3612
4358
  ] }, i);
3613
4359
  }
3614
4360
  const lOn = it.startsWith("*");
3615
4361
  const rOn = it.length > 0 && it.endsWith("*");
3616
- return /* @__PURE__ */ jsxs4(Box4, { children: [
3617
- /* @__PURE__ */ jsx4(Text4, { color: sel ? theme.accentBright : void 0, children: sel ? "\u25B8 " : " " }),
3618
- /* @__PURE__ */ jsx4(Text4, { color: lOn ? theme.ok : theme.dim, children: lOn ? "\u2731 " : "\xB7 " }),
3619
- /* @__PURE__ */ jsx4(Text4, { dimColor: !it, children: it || "(empty)" }),
3620
- /* @__PURE__ */ jsx4(Text4, { color: rOn ? theme.ok : theme.dim, children: rOn ? " \u2731" : " \xB7" })
4362
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
4363
+ /* @__PURE__ */ jsx6(Text6, { color: sel ? theme.accentBright : void 0, children: sel ? "\u25B8 " : " " }),
4364
+ /* @__PURE__ */ jsx6(Text6, { color: lOn ? theme.ok : theme.dim, children: lOn ? "\u2731 " : "\xB7 " }),
4365
+ /* @__PURE__ */ jsx6(Text6, { dimColor: !it, children: it || "(empty)" }),
4366
+ /* @__PURE__ */ jsx6(Text6, { color: rOn ? theme.ok : theme.dim, children: rOn ? " \u2731" : " \xB7" })
3621
4367
  ] }, i);
3622
4368
  })
3623
4369
  ] }),
3624
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " * = wildcard (any run of chars). Type it directly (rm*) or toggle with [ / ]. Green \u2731 = active \xB7 left & right shown as you type. Multiple values = OR (any one matches)." }),
3625
- status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
4370
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " * = wildcard (any run of chars). Type it directly (rm*) or toggle with [ / ]. Green \u2731 = active \xB7 left & right shown as you type. Multiple values = OR (any one matches)." }),
4371
+ status ? /* @__PURE__ */ jsx6(Text6, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
3626
4372
  ] });
3627
4373
  }
3628
4374
  const rule = rules[ri];
3629
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
3630
- /* @__PURE__ */ jsxs4(Box4, { children: [
3631
- /* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accentBright, children: "Rule " }),
3632
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: rule?.id ?? "" }),
4375
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
4376
+ /* @__PURE__ */ jsxs6(Box6, { children: [
4377
+ /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accentBright, children: "Rule " }),
4378
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: rule?.id ?? "" }),
3633
4379
  dirtyTag
3634
4380
  ] }),
3635
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit/open \xB7 space toggle \xB7 \u2190\u2192 move/pick \xB7 s save \xB7 \u2190 back" }),
3636
- /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
4381
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit/open \xB7 space toggle \xB7 \u2190\u2192 move/pick \xB7 s save \xB7 \u2190 back" }),
4382
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
3637
4383
  const active2 = i === fi;
3638
- const label = /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) });
4384
+ const label2 = /* @__PURE__ */ jsx6(Text6, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) });
3639
4385
  if (f.kind === "perms" && rule) {
3640
- return /* @__PURE__ */ jsxs4(Box4, { children: [
3641
- label,
4386
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
4387
+ label2,
3642
4388
  PERMS.map((p, pi2) => {
3643
4389
  const on = permList(rule).includes(p);
3644
4390
  const cur = active2 && pi2 === permCursor % PERMS.length;
3645
- return /* @__PURE__ */ jsxs4(Text4, { color: on ? theme.ok : theme.bad, inverse: cur, children: [
4391
+ return /* @__PURE__ */ jsxs6(Text6, { color: on ? theme.ok : theme.bad, inverse: cur, children: [
3646
4392
  p,
3647
4393
  pi2 < PERMS.length - 1 ? " " : ""
3648
4394
  ] }, p);
@@ -3652,17 +4398,17 @@ function PoliciesPanel({ focused }) {
3652
4398
  if (f.kind === "match" && rule) {
3653
4399
  const items = matchItems(rule);
3654
4400
  const preview = items.join(", ");
3655
- return /* @__PURE__ */ jsxs4(Box4, { children: [
3656
- label,
3657
- /* @__PURE__ */ jsx4(Text4, { dimColor: !preview, children: preview || "\u2014" }),
3658
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: items.length ? ` (${items.length} value${items.length > 1 ? "s" : ""} \xB7 enter: edit)` : " (enter: add values)" })
4401
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
4402
+ label2,
4403
+ /* @__PURE__ */ jsx6(Text6, { dimColor: !preview, children: preview || "\u2014" }),
4404
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: items.length ? ` (${items.length} value${items.length > 1 ? "s" : ""} \xB7 enter: edit)` : " (enter: add values)" })
3659
4405
  ] }, f.label);
3660
4406
  }
3661
4407
  const val = rule ? f.get(rule) : "";
3662
- return /* @__PURE__ */ jsxs4(Box4, { children: [
3663
- label,
3664
- active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
3665
- TextInput3,
4408
+ return /* @__PURE__ */ jsxs6(Box6, { children: [
4409
+ label2,
4410
+ active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx6(
4411
+ TextInput4,
3666
4412
  {
3667
4413
  value: editVal,
3668
4414
  onChange: setEditVal,
@@ -3671,8 +4417,8 @@ function PoliciesPanel({ focused }) {
3671
4417
  setEditing(false);
3672
4418
  }
3673
4419
  }
3674
- ) : /* @__PURE__ */ jsx4(
3675
- Text4,
4420
+ ) : /* @__PURE__ */ jsx6(
4421
+ Text6,
3676
4422
  {
3677
4423
  color: f.kind === "effect" ? val === "ALLOW" ? theme.ok : theme.bad : void 0,
3678
4424
  dimColor: !val,
@@ -3681,14 +4427,14 @@ function PoliciesPanel({ focused }) {
3681
4427
  )
3682
4428
  ] }, f.label);
3683
4429
  }) }),
3684
- status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
4430
+ status ? /* @__PURE__ */ jsx6(Text6, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
3685
4431
  ] });
3686
4432
  }
3687
4433
 
3688
4434
  // src/tui/panels/RateLimit.tsx
3689
- import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
3690
- import { useEffect as useEffect5, useState as useState5 } from "react";
3691
- import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
4435
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
4436
+ import { useEffect as useEffect6, useState as useState7 } from "react";
4437
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3692
4438
  var MODES = ["off", "detect", "block"];
3693
4439
  var FIELDS2 = ["mode", "perMinute", "perHour", "perDay"];
3694
4440
  var when = (ms) => {
@@ -3700,11 +4446,11 @@ function RateLimitPanel({ focused }) {
3700
4446
  const layersQ = useLoader(() => api.settings.getSecurityLayers());
3701
4447
  const historyQ = useLoader(() => api.settings.getRateLimitHistory());
3702
4448
  const insightsQ = useLoader(() => api.stats.securityInsights(7));
3703
- const [draft, setDraft] = useState5(null);
3704
- const [dirty, setDirty] = useState5(false);
3705
- const [sel, setSel] = useState5(0);
3706
- const [status, setStatus] = useState5(null);
3707
- useEffect5(() => {
4449
+ const [draft, setDraft] = useState7(null);
4450
+ const [dirty, setDirty] = useState7(false);
4451
+ const [sel, setSel] = useState7(0);
4452
+ const [status, setStatus] = useState7(null);
4453
+ useEffect6(() => {
3708
4454
  if (layersQ.data && !dirty) setDraft({ ...layersQ.data.layers.rateLimit });
3709
4455
  }, [layersQ.data]);
3710
4456
  usePoll(() => {
@@ -3753,7 +4499,7 @@ function RateLimitPanel({ focused }) {
3753
4499
  const fi = onField ? selC : -1;
3754
4500
  const bcur = onField ? -1 : selC - 4;
3755
4501
  const off = bcur < 0 ? 0 : Math.min(Math.max(0, bcur - Math.floor(burstBudget / 2)), maxOff);
3756
- useInput4(
4502
+ useInput6(
3757
4503
  (input, key) => {
3758
4504
  const step = key.shift ? 10 : 1;
3759
4505
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
@@ -3772,52 +4518,52 @@ function RateLimitPanel({ focused }) {
3772
4518
  },
3773
4519
  { isActive: focused }
3774
4520
  );
3775
- return /* @__PURE__ */ jsx5(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? (() => {
4521
+ return /* @__PURE__ */ jsx7(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? (() => {
3776
4522
  const lim = draft.perMinute;
3777
4523
  const now = insightsQ.data ? insightsQ.data["activity"]?.minute?.slice(-1)[0]?.count ?? 0 : 0;
3778
4524
  const loadPct = lim > 0 ? Math.min(100, Math.round(now / lim * 100)) : 0;
3779
4525
  const last24h = (insightsQ.data?.["activity"]?.hour ?? []).reduce((s, b) => s + b.count, 0);
3780
4526
  const shown = anomalies2.slice(off, off + burstBudget);
3781
4527
  const bw = Math.max(10, cols - 30);
3782
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", height: rows, overflow: "hidden", children: [
3783
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 move (fields + bursts) \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh" : "press \u2192 to edit" }),
3784
- /* @__PURE__ */ jsxs5(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
3785
- /* @__PURE__ */ jsx5(Text5, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
3786
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
4528
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", height: rows, overflow: "hidden", children: [
4529
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 move (fields + bursts) \xB7 \u2190\u2192 \xB11 \xB7 shift+\u2190\u2192 \xB110 \xB7 s save \xB7 ^R refresh" : "press \u2192 to edit" }),
4530
+ /* @__PURE__ */ jsxs7(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
4531
+ /* @__PURE__ */ jsx7(Text7, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
4532
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
3787
4533
  ] }),
3788
- /* @__PURE__ */ jsx5(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: draft.perMinute || "off" }) }),
3789
- /* @__PURE__ */ jsx5(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: draft.perHour || "off" }) }),
3790
- /* @__PURE__ */ jsx5(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: draft.perDay || "off" }) }),
3791
- showExtras && /* @__PURE__ */ jsxs5(Fragment4, { children: [
3792
- hasLoad ? /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3793
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "load now".padEnd(13) }),
3794
- /* @__PURE__ */ jsx5(Text5, { color: loadPct >= 100 ? theme.bad : loadPct >= 80 ? theme.warn : theme.ok, children: "\u2588".repeat(Math.min(18, Math.round(now / lim * 18))) }),
3795
- /* @__PURE__ */ jsx5(Text5, { color: "#233457", children: "\u2591".repeat(Math.max(0, 18 - Math.round(now / lim * 18))) }),
3796
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: ` ${now}/${lim} this minute (${loadPct}%)` })
4534
+ /* @__PURE__ */ jsx7(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx7(Text7, { bold: true, children: draft.perMinute || "off" }) }),
4535
+ /* @__PURE__ */ jsx7(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx7(Text7, { bold: true, children: draft.perHour || "off" }) }),
4536
+ /* @__PURE__ */ jsx7(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx7(Text7, { bold: true, children: draft.perDay || "off" }) }),
4537
+ showExtras && /* @__PURE__ */ jsxs7(Fragment5, { children: [
4538
+ hasLoad ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4539
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "load now".padEnd(13) }),
4540
+ /* @__PURE__ */ jsx7(Text7, { color: loadPct >= 100 ? theme.bad : loadPct >= 80 ? theme.warn : theme.ok, children: "\u2588".repeat(Math.min(18, Math.round(now / lim * 18))) }),
4541
+ /* @__PURE__ */ jsx7(Text7, { color: "#233457", children: "\u2591".repeat(Math.max(0, 18 - Math.round(now / lim * 18))) }),
4542
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` ${now}/${lim} this minute (${loadPct}%)` })
3797
4543
  ] }) : null,
3798
- /* @__PURE__ */ jsxs5(Text5, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
4544
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
3799
4545
  "Busiest 7d",
3800
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " most calls in one window vs your limit" })
4546
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " most calls in one window vs your limit" })
3801
4547
  ] }),
3802
- /* @__PURE__ */ jsx5(BusiestRow, { label: "minute", peak: peaks.minute, limit: draft.perMinute }),
3803
- /* @__PURE__ */ jsx5(BusiestRow, { label: "hour", peak: peaks.hour, limit: draft.perHour }),
3804
- /* @__PURE__ */ jsx5(BusiestRow, { label: "day", peak: peaks.day, limit: draft.perDay }),
3805
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: `activity`.padEnd(11) + ` last 24h \xB7 ${last24h} calls \xB7 busiest hour ${peaks.hour}/hr` })
4548
+ /* @__PURE__ */ jsx7(BusiestRow, { label: "minute", peak: peaks.minute, limit: draft.perMinute }),
4549
+ /* @__PURE__ */ jsx7(BusiestRow, { label: "hour", peak: peaks.hour, limit: draft.perHour }),
4550
+ /* @__PURE__ */ jsx7(BusiestRow, { label: "day", peak: peaks.day, limit: draft.perDay }),
4551
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: `activity`.padEnd(11) + ` last 24h \xB7 ${last24h} calls \xB7 busiest hour ${peaks.hour}/hr` })
3806
4552
  ] }),
3807
- /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
3808
- /* @__PURE__ */ jsxs5(Text5, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
4553
+ /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
4554
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
3809
4555
  `Recent bursts 7d${anomalies2.length ? ` (${anomalies2.length})` : ""}`,
3810
- maxOff > 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: ` ${off + 1}-${Math.min(off + burstBudget, anomalies2.length)}/${anomalies2.length}` }) : null,
3811
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " over the per-minute limit \xB7 Bypassed = detect (not blocked)" })
4556
+ maxOff > 0 ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: ` ${off + 1}-${Math.min(off + burstBudget, anomalies2.length)}/${anomalies2.length}` }) : null,
4557
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " over the per-minute limit \xB7 Bypassed = detect (not blocked)" })
3812
4558
  ] }),
3813
- anomalies2.length === 0 ? /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
4559
+ anomalies2.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3814
4560
  " ",
3815
4561
  insightsQ.loading ? "loading\u2026" : insightsQ.error ? `couldn't load bursts: ${String(insightsQ.error).slice(0, 60)}` : "no bursts in the last 7 days"
3816
4562
  ] }) : (
3817
4563
  // Bounded here as well as on the outer frame, so a list longer than
3818
4564
  // the budget is clipped inside its own box instead of shoving the
3819
4565
  // footer out of the panel and taking the scroll window with it.
3820
- /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", height: burstBudget + 1, overflow: "hidden", children: /* @__PURE__ */ jsx5(
4566
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: burstBudget + 1, overflow: "hidden", children: /* @__PURE__ */ jsx7(
3821
4567
  Table,
3822
4568
  {
3823
4569
  columns: [{ header: "WHEN", width: 15 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 31) }],
@@ -3834,54 +4580,54 @@ function RateLimitPanel({ focused }) {
3834
4580
  ) })
3835
4581
  )
3836
4582
  ] }),
3837
- spacerRows ? /* @__PURE__ */ jsx5(Text5, { children: " " }) : null,
3838
- /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3839
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "limit now " }),
3840
- /* @__PURE__ */ jsx5(Text5, { children: lim > 0 ? `${lim}/min` : "no per-minute limit" }),
3841
- lastChange ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: ` \xB7 changed ${when(lastChange.ts)}` }) : null,
3842
- dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s)" }) : null,
3843
- status ? /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: " " + status }) : null
4583
+ spacerRows ? /* @__PURE__ */ jsx7(Text7, { children: " " }) : null,
4584
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4585
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "limit now " }),
4586
+ /* @__PURE__ */ jsx7(Text7, { children: lim > 0 ? `${lim}/min` : "no per-minute limit" }),
4587
+ lastChange ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 changed ${when(lastChange.ts)}` }) : null,
4588
+ dirty ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: " \u25CF unsaved (s)" }) : null,
4589
+ status ? /* @__PURE__ */ jsx7(Text7, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: " " + status }) : null
3844
4590
  ] })
3845
4591
  ] });
3846
4592
  })() : null });
3847
4593
  }
3848
- function BusiestRow({ label, peak, limit }) {
4594
+ function BusiestRow({ label: label2, peak, limit }) {
3849
4595
  const ctx = limit > 0 ? peak > limit ? `over ${limit}` : peak === limit ? `at limit ${limit}` : `limit ${limit}` : "no limit set";
3850
4596
  const hot = limit > 0 && peak > limit;
3851
- return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3852
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: (" " + label).padEnd(11) }),
3853
- /* @__PURE__ */ jsx5(Text5, { color: hot ? theme.bad : void 0, bold: hot, children: String(peak).padStart(4) + " calls" }),
3854
- /* @__PURE__ */ jsx5(Text5, { color: hot ? theme.bad : theme.dim, children: " \xB7 " + ctx })
4597
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4598
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: (" " + label2).padEnd(11) }),
4599
+ /* @__PURE__ */ jsx7(Text7, { color: hot ? theme.bad : void 0, bold: hot, children: String(peak).padStart(4) + " calls" }),
4600
+ /* @__PURE__ */ jsx7(Text7, { color: hot ? theme.bad : theme.dim, children: " \xB7 " + ctx })
3855
4601
  ] });
3856
4602
  }
3857
- function FieldRow({ label, active: active2, children }) {
3858
- return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
3859
- /* @__PURE__ */ jsx5(Text5, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(11) }),
4603
+ function FieldRow({ label: label2, active: active2, children }) {
4604
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4605
+ /* @__PURE__ */ jsx7(Text7, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label2.padEnd(11) }),
3860
4606
  children
3861
4607
  ] });
3862
4608
  }
3863
4609
 
3864
4610
  // src/tui/panels/Dlp.tsx
3865
- import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
3866
- import TextInput4 from "ink-text-input";
3867
- import { useEffect as useEffect6, useState as useState6 } from "react";
3868
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
4611
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
4612
+ import TextInput5 from "ink-text-input";
4613
+ import { useEffect as useEffect7, useState as useState8 } from "react";
4614
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3869
4615
  var MODES2 = ["off", "detect", "block"];
3870
4616
  function DlpPanel({ focused }) {
3871
4617
  const { cols, rows } = usePanelSize();
3872
4618
  const q = useLoader(() => api.settings.getSecurityLayers());
3873
- const [dlp, setDlp] = useState6(null);
3874
- const [available, setAvailable] = useState6([]);
3875
- const [ghostOn, setGhostOn] = useState6(false);
3876
- const [ghostPats, setGhostPats] = useState6([]);
3877
- const [sel, setSel] = useState6(0);
3878
- const [dirty, setDirty] = useState6(false);
3879
- const [status, setStatus] = useState6(null);
3880
- const [adding, setAdding] = useState6(null);
3881
- const [newName, setNewName] = useState6("");
3882
- const [input, setInput] = useState6("");
3883
- const [editIdx, setEditIdx] = useState6(null);
3884
- useEffect6(() => {
4619
+ const [dlp, setDlp] = useState8(null);
4620
+ const [available, setAvailable] = useState8([]);
4621
+ const [ghostOn, setGhostOn] = useState8(false);
4622
+ const [ghostPats, setGhostPats] = useState8([]);
4623
+ const [sel, setSel] = useState8(0);
4624
+ const [dirty, setDirty] = useState8(false);
4625
+ const [status, setStatus] = useState8(null);
4626
+ const [adding, setAdding] = useState8(null);
4627
+ const [newName, setNewName] = useState8("");
4628
+ const [input, setInput] = useState8("");
4629
+ const [editIdx, setEditIdx] = useState8(null);
4630
+ useEffect7(() => {
3885
4631
  if (q.data && !dirty) {
3886
4632
  setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
3887
4633
  setAvailable(q.data.availablePatterns);
@@ -3928,7 +4674,7 @@ function DlpPanel({ focused }) {
3928
4674
  ];
3929
4675
  const selC = Math.min(sel, Math.max(0, entries.length - 1));
3930
4676
  const cur = entries[selC];
3931
- useInput5(
4677
+ useInput7(
3932
4678
  (input2, key) => {
3933
4679
  if (!dlp) return;
3934
4680
  if (key.ctrl && input2 === "r") {
@@ -3991,17 +4737,17 @@ function DlpPanel({ focused }) {
3991
4737
  if (right) core = core.replace(/\*+$/, "");
3992
4738
  return { left, core: core || s, right };
3993
4739
  };
3994
- const starL = (on) => /* @__PURE__ */ jsx6(Text6, { color: on ? theme.ok : theme.dim, children: on ? "\u2731 " : "\xB7 " });
3995
- const starR = (on) => /* @__PURE__ */ jsx6(Text6, { color: on ? theme.ok : theme.dim, children: on ? " \u2731" : " \xB7" });
4740
+ const starL = (on) => /* @__PURE__ */ jsx8(Text8, { color: on ? theme.ok : theme.dim, children: on ? "\u2731 " : "\xB7 " });
4741
+ const starR = (on) => /* @__PURE__ */ jsx8(Text8, { color: on ? theme.ok : theme.dim, children: on ? " \u2731" : " \xB7" });
3996
4742
  const lines = [];
3997
- const header = (key, label, extra) => lines.push({
3998
- node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
3999
- /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: label }),
4000
- extra ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + extra }) : null
4743
+ const header = (key, label2, extra) => lines.push({
4744
+ node: /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4745
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: label2 }),
4746
+ extra ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + extra }) : null
4001
4747
  ] }, key),
4002
4748
  entry: -1
4003
4749
  });
4004
- const note = (key, text) => lines.push({ node: /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: text }, key), entry: -1 });
4750
+ const note = (key, text) => lines.push({ node: /* @__PURE__ */ jsx8(Text8, { color: theme.dim, wrap: "truncate", children: text }, key), entry: -1 });
4005
4751
  let ei = 0;
4006
4752
  header("h:builtin", "built-in secret patterns", "space toggles on/off");
4007
4753
  available.forEach((p) => {
@@ -4009,10 +4755,10 @@ function DlpPanel({ focused }) {
4009
4755
  const isCur = focused && ei === selC;
4010
4756
  const idx = ei++;
4011
4757
  lines.push({
4012
- node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
4013
- /* @__PURE__ */ jsx6(Text6, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
4014
- /* @__PURE__ */ jsx6(Text6, { color: on ? theme.ok : theme.dim, children: on ? "\u25CF " : "\u25CB " }),
4015
- /* @__PURE__ */ jsx6(Text6, { color: on ? void 0 : theme.dim, children: p })
4758
+ node: /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
4759
+ /* @__PURE__ */ jsx8(Text8, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
4760
+ /* @__PURE__ */ jsx8(Text8, { color: on ? theme.ok : theme.dim, children: on ? "\u25CF " : "\u25CB " }),
4761
+ /* @__PURE__ */ jsx8(Text8, { color: on ? void 0 : theme.dim, children: p })
4016
4762
  ] }, "b:" + p),
4017
4763
  entry: idx
4018
4764
  });
@@ -4025,12 +4771,12 @@ function DlpPanel({ focused }) {
4025
4771
  const idx = ei++;
4026
4772
  const w = wcParts(c2.re);
4027
4773
  lines.push({
4028
- node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
4029
- /* @__PURE__ */ jsx6(Text6, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
4030
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: c2.name }),
4031
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " }),
4774
+ node: /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
4775
+ /* @__PURE__ */ jsx8(Text8, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
4776
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: c2.name }),
4777
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
4032
4778
  starL(w.left),
4033
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: truncate(w.core, Math.max(6, cols - c2.name.length - 14)) }),
4779
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: truncate(w.core, Math.max(6, cols - c2.name.length - 14)) }),
4034
4780
  starR(w.right)
4035
4781
  ] }, "c:" + c2.name + i),
4036
4782
  entry: idx
@@ -4044,12 +4790,12 @@ function DlpPanel({ focused }) {
4044
4790
  const idx = ei++;
4045
4791
  const w = wcParts(p);
4046
4792
  lines.push({
4047
- node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
4048
- /* @__PURE__ */ jsx6(Text6, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
4793
+ node: /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
4794
+ /* @__PURE__ */ jsx8(Text8, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
4049
4795
  starL(w.left),
4050
- /* @__PURE__ */ jsx6(Text6, { color: ghostOn ? theme.accent : theme.dim, children: truncate(w.core, Math.max(6, cols - 12)) }),
4796
+ /* @__PURE__ */ jsx8(Text8, { color: ghostOn ? theme.accent : theme.dim, children: truncate(w.core, Math.max(6, cols - 12)) }),
4051
4797
  starR(w.right),
4052
- !ghostOn ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (ghost off)" }) : null
4798
+ !ghostOn ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " (ghost off)" }) : null
4053
4799
  ] }, "g:" + p + i),
4054
4800
  entry: idx
4055
4801
  });
@@ -4063,19 +4809,19 @@ function DlpPanel({ focused }) {
4063
4809
  const win = lines.slice(start, start + budget);
4064
4810
  const moreAbove = start;
4065
4811
  const moreBelow = Math.max(0, lines.length - (start + budget));
4066
- return /* @__PURE__ */ jsx6(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
4067
- /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
4068
- /* @__PURE__ */ jsx6(Text6, { children: "mode: " }),
4069
- /* @__PURE__ */ jsx6(Text6, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
4070
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
4071
- dirty ? /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
4812
+ return /* @__PURE__ */ jsx8(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4813
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4814
+ /* @__PURE__ */ jsx8(Text8, { children: "mode: " }),
4815
+ /* @__PURE__ */ jsx8(Text8, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
4816
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
4817
+ dirty ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
4072
4818
  ] }),
4073
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 move \xB7 space on/off \xB7 m mode \xB7 a add \xB7 r route \xB7 e/enter edit \xB7 d remove \xB7 g ghost \xB7 s save \xB7 ^R refresh${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to edit" }),
4074
- adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
4075
- /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `${editIdx != null ? "edit" : "new"} pattern for "${newName}" (glob, * = any chars): ` : `${editIdx != null ? "edit ghost path" : "ghost path to hide"} (glob, * = any chars, e.g. */.env *.pem): ` }),
4819
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 move \xB7 space on/off \xB7 m mode \xB7 a add \xB7 r route \xB7 e/enter edit \xB7 d remove \xB7 g ghost \xB7 s save \xB7 ^R refresh${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to edit" }),
4820
+ adding ? /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", children: /* @__PURE__ */ jsxs8(Box8, { children: [
4821
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `${editIdx != null ? "edit" : "new"} pattern for "${newName}" (glob, * = any chars): ` : `${editIdx != null ? "edit ghost path" : "ghost path to hide"} (glob, * = any chars, e.g. */.env *.pem): ` }),
4076
4822
  adding !== "name" ? starL(input.startsWith("*")) : null,
4077
- /* @__PURE__ */ jsx6(
4078
- TextInput4,
4823
+ /* @__PURE__ */ jsx8(
4824
+ TextInput5,
4079
4825
  {
4080
4826
  value: input,
4081
4827
  onChange: setInput,
@@ -4117,19 +4863,19 @@ function DlpPanel({ focused }) {
4117
4863
  ),
4118
4864
  adding !== "name" ? starR(input.length > 1 && input.endsWith("*")) : null
4119
4865
  ] }) }) : null,
4120
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
4121
- status ? /* @__PURE__ */ jsx6(Text6, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
4866
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
4867
+ status ? /* @__PURE__ */ jsx8(Text8, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
4122
4868
  ] }) : null });
4123
4869
  }
4124
4870
 
4125
4871
  // src/tui/panels/Audit.tsx
4126
- import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
4127
- import TextInput5 from "ink-text-input";
4872
+ import { Box as Box9, Text as Text9, useInput as useInput8 } from "ink";
4873
+ import TextInput6 from "ink-text-input";
4128
4874
  import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
4129
4875
  import { homedir as homedir7 } from "os";
4130
4876
  import { join as join7 } from "path";
4131
- import { useState as useState7 } from "react";
4132
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
4877
+ import { useState as useState9 } from "react";
4878
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
4133
4879
  var DECISIONS = [void 0, "DENY", "ALLOW"];
4134
4880
  var SIGNALS = [void 0, "dlp", "ratelimit"];
4135
4881
  var SESS_STATUS = [void 0, "active", "idle", "ended"];
@@ -4235,24 +4981,24 @@ var STATUS_DOT = {
4235
4981
  };
4236
4982
  function AuditPanel({ active: active2, focused }) {
4237
4983
  const { cols, rows } = usePanelSize();
4238
- const [source, setSource] = useState7("cloud");
4239
- const [view, setView] = useState7("logs");
4240
- const [di, setDi] = useState7(0);
4241
- const [gi, setGi] = useState7(0);
4242
- const [tool, setTool] = useState7("");
4243
- const [agent, setAgent] = useState7("");
4244
- const [search, setSearch] = useState7("");
4245
- const [sessFilter, setSessFilter] = useState7("");
4246
- const [page, setPage] = useState7(0);
4247
- const [sel, setSel] = useState7(0);
4248
- const [detailScroll, setDetailScroll] = useState7(0);
4249
- const [editing, setEditing] = useState7(null);
4250
- const [si, setSi] = useState7(0);
4251
- const [sessSearch, setSessSearch] = useState7("");
4252
- const [sessSel, setSessSel] = useState7(0);
4253
- const [msg, setMsg] = useState7(null);
4254
- const [showHelp, setShowHelp] = useState7(false);
4255
- const [frozen, setFrozen] = useState7(false);
4984
+ const [source, setSource] = useState9("cloud");
4985
+ const [view, setView] = useState9("logs");
4986
+ const [di, setDi] = useState9(0);
4987
+ const [gi, setGi] = useState9(0);
4988
+ const [tool, setTool] = useState9("");
4989
+ const [agent, setAgent] = useState9("");
4990
+ const [search, setSearch] = useState9("");
4991
+ const [sessFilter, setSessFilter] = useState9("");
4992
+ const [page, setPage] = useState9(0);
4993
+ const [sel, setSel] = useState9(0);
4994
+ const [detailScroll, setDetailScroll] = useState9(0);
4995
+ const [editing, setEditing] = useState9(null);
4996
+ const [si, setSi] = useState9(0);
4997
+ const [sessSearch, setSessSearch] = useState9("");
4998
+ const [sessSel, setSessSel] = useState9(0);
4999
+ const [msg, setMsg] = useState9(null);
5000
+ const [showHelp, setShowHelp] = useState9(false);
5001
+ const [frozen, setFrozen] = useState9(false);
4256
5002
  const toTop = () => setSel(0);
4257
5003
  const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
4258
5004
  usePoll(statsQ.reloadQuiet, 15e3, active2 && source === "cloud" && !frozen);
@@ -4348,7 +5094,7 @@ function AuditPanel({ active: active2, focused }) {
4348
5094
  };
4349
5095
  run().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
4350
5096
  };
4351
- useInput6(
5097
+ useInput8(
4352
5098
  (input, key) => {
4353
5099
  if (input === " ") {
4354
5100
  setFrozen((f) => !f);
@@ -4464,58 +5210,58 @@ function AuditPanel({ active: active2, focused }) {
4464
5210
  { isActive: focused && !editing }
4465
5211
  );
4466
5212
  const localAll = localQ.data ?? [];
4467
- const strip = source === "cloud" ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4468
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
4469
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " calls \xB7 " }),
4470
- /* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
5213
+ const strip = source === "cloud" ? /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
5214
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
5215
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " calls \xB7 " }),
5216
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.ok, children: [
4471
5217
  statsQ.data?.allowed ?? "\xB7\xB7\xB7",
4472
5218
  " allow"
4473
5219
  ] }),
4474
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
4475
- /* @__PURE__ */ jsxs7(Text7, { color: theme.bad, children: [
5220
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 " }),
5221
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.bad, children: [
4476
5222
  statsQ.data?.denied ?? "\xB7\xB7",
4477
5223
  " deny"
4478
5224
  ] }),
4479
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies` })
4480
- ] }) : /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4481
- /* @__PURE__ */ jsx7(Text7, { bold: true, children: localAll.length }),
4482
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " calls in local file \xB7 " }),
4483
- /* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
5225
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies` })
5226
+ ] }) : /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
5227
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: localAll.length }),
5228
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " calls in local file \xB7 " }),
5229
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.ok, children: [
4484
5230
  localAll.filter((r) => r.decision === "ALLOW").length,
4485
5231
  " allow"
4486
5232
  ] }),
4487
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
4488
- /* @__PURE__ */ jsxs7(Text7, { color: theme.bad, children: [
5233
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 " }),
5234
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.bad, children: [
4489
5235
  localAll.filter((r) => r.decision !== "ALLOW").length,
4490
5236
  " deny"
4491
5237
  ] }),
4492
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
5238
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
4493
5239
  " \xB7 ",
4494
5240
  localLogFile()
4495
5241
  ] })
4496
5242
  ] });
4497
- const copyBanner = frozen ? /* @__PURE__ */ jsx7(Text7, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, wrap: "truncate", children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : null;
4498
- const srcChip = /* @__PURE__ */ jsxs7(Text7, { children: [
4499
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "src:" }),
4500
- /* @__PURE__ */ jsx7(Text7, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
4501
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " (s) " })
5243
+ const copyBanner = frozen ? /* @__PURE__ */ jsx9(Text9, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, wrap: "truncate", children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : null;
5244
+ const srcChip = /* @__PURE__ */ jsxs9(Text9, { children: [
5245
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "src:" }),
5246
+ /* @__PURE__ */ jsx9(Text9, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
5247
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " (s) " })
4502
5248
  ] });
4503
5249
  if (showHelp) {
4504
- const colOf = (groups) => /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", width: "50%", paddingRight: 2, children: groups.map(([group, keys]) => /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
4505
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accent, children: group }),
4506
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4507
- /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: (" " + k).padEnd(19) }),
4508
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: desc })
5250
+ const colOf = (groups2) => /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: "50%", paddingRight: 2, children: groups2.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
5251
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
5252
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
5253
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(19) }),
5254
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
4509
5255
  ] }, k))
4510
5256
  ] }, group)) });
4511
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
5257
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4512
5258
  copyBanner,
4513
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
5259
+ /* @__PURE__ */ jsxs9(Text9, { bold: true, color: theme.accentBright, children: [
4514
5260
  "AUDIT \u2014 all keys",
4515
5261
  " ",
4516
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: frozen ? "\xB7 screen frozen \u2014 space resumes, other keys are locked" : "\xB7 press any key to close \xB7 space = copy mode (works here too)" })
5262
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: frozen ? "\xB7 screen frozen \u2014 space resumes, other keys are locked" : "\xB7 press any key to close \xB7 space = copy mode (works here too)" })
4517
5263
  ] }),
4518
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
5264
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, children: [
4519
5265
  colOf(AUDIT_HELP.slice(0, 1)),
4520
5266
  colOf(AUDIT_HELP.slice(1))
4521
5267
  ] })
@@ -4534,53 +5280,53 @@ function AuditPanel({ active: active2, focused }) {
4534
5280
  const maxScroll = Math.max(0, contentLines.length - bodyRows);
4535
5281
  const off = Math.min(detailScroll, maxScroll);
4536
5282
  const win = contentLines.slice(off, off + bodyRows);
4537
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
5283
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4538
5284
  copyBanner,
4539
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4540
- /* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
4541
- /* @__PURE__ */ jsx7(Text7, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
4542
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: " " + e.tool }),
4543
- /* @__PURE__ */ jsx7(Text7, { color: loc ? theme.ok : "white", children: " " + (loc ? "LOC" : "CLD") }),
4544
- e.dlp.length ? /* @__PURE__ */ jsx7(Text7, { color: theme.bad, children: " DLP!" }) : null,
4545
- e.burst ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: " BURST" }) : null,
4546
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2190 back" })
5285
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
5286
+ /* @__PURE__ */ jsx9(Text9, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
5287
+ /* @__PURE__ */ jsx9(Text9, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
5288
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, bold: true, children: " " + e.tool }),
5289
+ /* @__PURE__ */ jsx9(Text9, { color: loc ? theme.ok : "white", children: " " + (loc ? "LOC" : "CLD") }),
5290
+ e.dlp.length ? /* @__PURE__ */ jsx9(Text9, { color: theme.bad, children: " DLP!" }) : null,
5291
+ e.burst ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: " BURST" }) : null,
5292
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2190 back" })
4547
5293
  ] }),
4548
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4549
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2502 when " }),
4550
- /* @__PURE__ */ jsx7(Text7, { children: new Date(e.at).toLocaleString() }),
4551
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 perm " }),
4552
- /* @__PURE__ */ jsx7(Text7, { children: e.permission || "\u2014" }),
4553
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 trust " }),
4554
- /* @__PURE__ */ jsx7(Text7, { children: e.trust || "\u2014" }),
4555
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 eval " }),
4556
- /* @__PURE__ */ jsx7(Text7, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
4557
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 agent " }),
4558
- /* @__PURE__ */ jsx7(Text7, { children: e.agent ?? "\u2014" }),
4559
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
5294
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
5295
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2502 when " }),
5296
+ /* @__PURE__ */ jsx9(Text9, { children: new Date(e.at).toLocaleString() }),
5297
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2502 perm " }),
5298
+ /* @__PURE__ */ jsx9(Text9, { children: e.permission || "\u2014" }),
5299
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2502 trust " }),
5300
+ /* @__PURE__ */ jsx9(Text9, { children: e.trust || "\u2014" }),
5301
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2502 eval " }),
5302
+ /* @__PURE__ */ jsx9(Text9, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
5303
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2502 agent " }),
5304
+ /* @__PURE__ */ jsx9(Text9, { children: e.agent ?? "\u2014" }),
5305
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2502" })
4560
5306
  ] }),
4561
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4562
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2502 session " }),
4563
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: e.session ?? "\u2014" }),
4564
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 rule " }),
4565
- /* @__PURE__ */ jsx7(Text7, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
4566
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
5307
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
5308
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "\u2502 session " }),
5309
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: e.session ?? "\u2014" }),
5310
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2502 rule " }),
5311
+ /* @__PURE__ */ jsx9(Text9, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
5312
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2502" })
4567
5313
  ] }),
4568
- /* @__PURE__ */ jsx7(PaneTitle, { label: "FULL CONTENT", extra: `${contentLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
4569
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
4570
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4571
- /* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
4572
- /* @__PURE__ */ jsx7(Text7, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
4573
- /* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
5314
+ /* @__PURE__ */ jsx9(PaneTitle, { label: "FULL CONTENT", extra: `${contentLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""} \xB7 space copy \xB7 \u2190 back`, width: innerW }),
5315
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx9(Text9, { wrap: "truncate", children: l || " " }, off + i)) }),
5316
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
5317
+ /* @__PURE__ */ jsx9(Text9, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
5318
+ /* @__PURE__ */ jsx9(Text9, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
5319
+ /* @__PURE__ */ jsx9(Text9, { backgroundColor: BG2, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
4574
5320
  ] })
4575
5321
  ] });
4576
5322
  }
4577
- const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
4578
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
4579
- label,
5323
+ const chip = (label2, val, on) => /* @__PURE__ */ jsxs9(Text9, { children: [
5324
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
5325
+ label2,
4580
5326
  ":"
4581
5327
  ] }),
4582
- /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
4583
- /* @__PURE__ */ jsx7(Text7, { children: " " })
5328
+ /* @__PURE__ */ jsx9(Text9, { color: on ? theme.accentBright : theme.dim, children: val }),
5329
+ /* @__PURE__ */ jsx9(Text9, { children: " " })
4584
5330
  ] });
4585
5331
  if (view === "sessions") {
4586
5332
  const headerRows2 = 5 + (editing ? 1 : 0);
@@ -4588,21 +5334,21 @@ function AuditPanel({ active: active2, focused }) {
4588
5334
  const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
4589
5335
  const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
4590
5336
  const win = sessionsFiltered.slice(start2, start2 + listRows2);
4591
- return /* @__PURE__ */ jsx7(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
5337
+ return /* @__PURE__ */ jsx9(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4592
5338
  strip,
4593
5339
  copyBanner,
4594
- /* @__PURE__ */ jsxs7(Box7, { children: [
5340
+ /* @__PURE__ */ jsxs9(Box9, { children: [
4595
5341
  srcChip,
4596
- /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
4597
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 logs (v) " }),
5342
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
5343
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 logs (v) " }),
4598
5344
  chip("status", SESS_STATUS[si] ?? "all", si !== 0),
4599
5345
  chip("search", sessSearch || "\xB7", !!sessSearch)
4600
5346
  ] }),
4601
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 v logs \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
4602
- editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
4603
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
4604
- /* @__PURE__ */ jsx7(
4605
- TextInput5,
5347
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter \u2192 session logs \xB7 v logs \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
5348
+ editing === "sess-search" ? /* @__PURE__ */ jsxs9(Box9, { children: [
5349
+ /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: "search: " }),
5350
+ /* @__PURE__ */ jsx9(
5351
+ TextInput6,
4606
5352
  {
4607
5353
  value: sessSearch,
4608
5354
  onChange: (v) => {
@@ -4613,8 +5359,8 @@ function AuditPanel({ active: active2, focused }) {
4613
5359
  }
4614
5360
  )
4615
5361
  ] }) : null,
4616
- /* @__PURE__ */ jsx7(Text7, { 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}` : ""}` }),
4617
- /* @__PURE__ */ jsx7(
5362
+ /* @__PURE__ */ jsx9(Text9, { 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}` : ""}` }),
5363
+ /* @__PURE__ */ jsx9(
4618
5364
  Table,
4619
5365
  {
4620
5366
  columns: [
@@ -4645,7 +5391,7 @@ function AuditPanel({ active: active2, focused }) {
4645
5391
  })
4646
5392
  }
4647
5393
  ),
4648
- sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
5394
+ sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
4649
5395
  "(no sessions",
4650
5396
  source === "local" ? " in the local file" : "",
4651
5397
  ")"
@@ -4657,14 +5403,14 @@ function AuditPanel({ active: active2, focused }) {
4657
5403
  const selClamped = Math.min(sel, Math.max(0, pageRows.length - 1));
4658
5404
  const maxStart = Math.max(0, pageRows.length - listRows);
4659
5405
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
4660
- const windowed = pageRows.slice(start, start + listRows);
4661
- return /* @__PURE__ */ jsx7(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
5406
+ const windowed2 = pageRows.slice(start, start + listRows);
5407
+ return /* @__PURE__ */ jsx9(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4662
5408
  strip,
4663
5409
  copyBanner,
4664
- /* @__PURE__ */ jsxs7(Box7, { children: [
5410
+ /* @__PURE__ */ jsxs9(Box9, { children: [
4665
5411
  srcChip,
4666
- /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "LOGS" }),
4667
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 sessions (v) " }),
5412
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "LOGS" }),
5413
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 sessions (v) " }),
4668
5414
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
4669
5415
  chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
4670
5416
  chip("tool", tool || "\xB7", !!tool),
@@ -4672,15 +5418,15 @@ function AuditPanel({ active: active2, focused }) {
4672
5418
  chip("search", search || "\xB7", !!search),
4673
5419
  sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
4674
5420
  ] }),
4675
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 v sessions \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
4676
- msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : null,
4677
- editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
4678
- /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
5421
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, wrap: "truncate", children: focused ? "\u2191\u2193 select \xB7 enter full entry \xB7 \u2190\u2192 page \xB7 v sessions \xB7 ^R refresh \xB7 ? all keys" : "press \u2192 to browse" }),
5422
+ msg ? /* @__PURE__ */ jsx9(Text9, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : null,
5423
+ editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs9(Box9, { children: [
5424
+ /* @__PURE__ */ jsxs9(Text9, { color: theme.warn, children: [
4679
5425
  editing,
4680
5426
  ": "
4681
5427
  ] }),
4682
- /* @__PURE__ */ jsx7(
4683
- TextInput5,
5428
+ /* @__PURE__ */ jsx9(
5429
+ TextInput6,
4684
5430
  {
4685
5431
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
4686
5432
  onChange: (v) => {
@@ -4692,9 +5438,9 @@ function AuditPanel({ active: active2, focused }) {
4692
5438
  }
4693
5439
  )
4694
5440
  ] }) : null,
4695
- /* @__PURE__ */ jsx7(Text7, { 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` : ""}` }),
4696
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx7(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
4697
- pageRows.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
5441
+ /* @__PURE__ */ jsx9(Text9, { 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` : ""}` }),
5442
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", overflow: "hidden", children: windowed2.map((e, i) => /* @__PURE__ */ jsx9(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
5443
+ pageRows.length === 0 ? /* @__PURE__ */ jsxs9(Text9, { color: theme.dim, children: [
4698
5444
  "(no entries",
4699
5445
  source === "local" ? " in the local file" : "",
4700
5446
  " \u2014 c clears filters)"
@@ -4703,9 +5449,9 @@ function AuditPanel({ active: active2, focused }) {
4703
5449
  }
4704
5450
 
4705
5451
  // src/tui/panels/Settings.tsx
4706
- import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
4707
- import TextInput6 from "ink-text-input";
4708
- import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
5452
+ import { Box as Box10, Text as Text10, useInput as useInput9 } from "ink";
5453
+ import TextInput7 from "ink-text-input";
5454
+ import { useEffect as useEffect8, useRef as useRef4, useState as useState10 } from "react";
4709
5455
  init_global_install();
4710
5456
 
4711
5457
  // src/commands/doctor.ts
@@ -5099,7 +5845,7 @@ async function tuiUpdateFlow(onStatus) {
5099
5845
  }
5100
5846
 
5101
5847
  // src/tui/panels/Settings.tsx
5102
- import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
5848
+ import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
5103
5849
  var EVENTS = ["denials", "allowed", "all"];
5104
5850
  var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
5105
5851
  var SIGNALS2 = ["any", "deny", "dlp", "ratelimit"];
@@ -5125,19 +5871,19 @@ function SettingsPanel({
5125
5871
  }) {
5126
5872
  void active2;
5127
5873
  const { cols, rows } = usePanelSize();
5128
- const [sel, setSel] = useState8(0);
5129
- const [editing, setEditing] = useState8(null);
5130
- const [input, setInput] = useState8("");
5131
- const [confirmDel, setConfirmDel] = useState8(null);
5132
- const [msg, setMsg] = useState8(null);
5133
- const [busy, setBusy] = useState8(false);
5134
- const [editor, setEditor] = useState8(null);
5135
- const [latest, setLatest] = useState8(null);
5136
- const [diagBusy, setDiagBusy] = useState8(null);
5137
- const [autoUp, setAutoUp] = useState8(() => autoUpdateEnabled());
5138
- const [updBusy, setUpdBusy] = useState8(false);
5139
- const [llSetting, setLlSetting] = useState8(null);
5140
- useEffect7(() => {
5874
+ const [sel, setSel] = useState10(0);
5875
+ const [editing, setEditing] = useState10(null);
5876
+ const [input, setInput] = useState10("");
5877
+ const [confirmDel, setConfirmDel] = useState10(null);
5878
+ const [msg, setMsg] = useState10(null);
5879
+ const [busy, setBusy] = useState10(false);
5880
+ const [editor, setEditor] = useState10(null);
5881
+ const [latest, setLatest] = useState10(null);
5882
+ const [diagBusy, setDiagBusy] = useState10(null);
5883
+ const [autoUp, setAutoUp] = useState10(() => autoUpdateEnabled());
5884
+ const [updBusy, setUpdBusy] = useState10(false);
5885
+ const [llSetting, setLlSetting] = useState10(null);
5886
+ useEffect8(() => {
5141
5887
  const read = () => {
5142
5888
  try {
5143
5889
  setLlSetting(localLogsSetting());
@@ -5148,8 +5894,8 @@ function SettingsPanel({
5148
5894
  const t = setInterval(read, 5e3);
5149
5895
  return () => clearInterval(t);
5150
5896
  }, []);
5151
- const [canInstall, setCanInstall] = useState8(null);
5152
- useEffect7(() => {
5897
+ const [canInstall, setCanInstall] = useState10(null);
5898
+ useEffect8(() => {
5153
5899
  let live2 = true;
5154
5900
  globalInstallCheck().then((r) => {
5155
5901
  if (live2) setCanInstall(r.writable);
@@ -5159,8 +5905,8 @@ function SettingsPanel({
5159
5905
  live2 = false;
5160
5906
  };
5161
5907
  }, []);
5162
- const [diag, setDiag] = useState8(null);
5163
- const [diagStep, setDiagStep] = useState8(0);
5908
+ const [diag, setDiag] = useState10(null);
5909
+ const [diagStep, setDiagStep] = useState10(0);
5164
5910
  const DIAG_STEPS = {
5165
5911
  doctor: ["login", "active policy", "guard hook", "agent hooks", "local logs"],
5166
5912
  repair: ["inspecting protection files", "rewriting hook files", "registering with agents", "re-locking"]
@@ -5170,14 +5916,14 @@ function SettingsPanel({
5170
5916
  const left = min - (Date.now() - started);
5171
5917
  if (left > 0) await new Promise((res) => setTimeout(res, left));
5172
5918
  };
5173
- useEffect7(() => {
5919
+ useEffect8(() => {
5174
5920
  if (!diagBusy) return;
5175
5921
  const total = DIAG_STEPS[diagBusy].length;
5176
5922
  const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
5177
5923
  return () => clearInterval(step);
5178
5924
  }, [diagBusy]);
5179
5925
  const ver = currentVersion();
5180
- useEffect7(() => {
5926
+ useEffect8(() => {
5181
5927
  let live2 = true;
5182
5928
  latestVersion().then((v) => {
5183
5929
  if (live2) setLatest(v);
@@ -5187,20 +5933,20 @@ function SettingsPanel({
5187
5933
  live2 = false;
5188
5934
  };
5189
5935
  }, []);
5190
- const [accounts, setAccounts] = useState8(() => listAccounts());
5191
- const [login, setLogin] = useState8(null);
5192
- const [tick, setTick] = useState8(0);
5193
- const abort = useRef3(false);
5936
+ const [accounts, setAccounts] = useState10(() => listAccounts());
5937
+ const [login, setLogin] = useState10(null);
5938
+ const [tick, setTick] = useState10(0);
5939
+ const abort = useRef4(false);
5194
5940
  const refreshAccounts = () => setAccounts(listAccounts());
5195
- const [guardHere, setGuardHere] = useState8(() => isGuardInstalled());
5196
- const [guardVer, setGuardVer] = useState8(() => installedGuardVersion());
5197
- const [guardOld, setGuardOld] = useState8(() => guardHookOutdated());
5941
+ const [guardHere, setGuardHere] = useState10(() => isGuardInstalled());
5942
+ const [guardVer, setGuardVer] = useState10(() => installedGuardVersion());
5943
+ const [guardOld, setGuardOld] = useState10(() => guardHookOutdated());
5198
5944
  const codexNeedsTrust = () => {
5199
5945
  if (!codexDetected()) return false;
5200
5946
  const cx = codexHooksStatus();
5201
5947
  return cx.registered && !cx.trusted;
5202
5948
  };
5203
- const [codexTrust, setCodexTrust] = useState8(codexNeedsTrust);
5949
+ const [codexTrust, setCodexTrust] = useState10(codexNeedsTrust);
5204
5950
  const refreshGuard = () => {
5205
5951
  setGuardHere(isGuardInstalled());
5206
5952
  setGuardVer(installedGuardVersion());
@@ -5208,12 +5954,12 @@ function SettingsPanel({
5208
5954
  setCodexTrust(codexNeedsTrust());
5209
5955
  };
5210
5956
  const locked = accounts.length === 0;
5211
- const [srv, setSrv] = useState8(() => logsServerStatus());
5212
- useEffect7(() => {
5957
+ const [srv, setSrv] = useState10(() => logsServerStatus());
5958
+ useEffect8(() => {
5213
5959
  const t = setInterval(() => setSrv(logsServerStatus()), 3e3);
5214
5960
  return () => clearInterval(t);
5215
5961
  }, []);
5216
- useEffect7(() => {
5962
+ useEffect8(() => {
5217
5963
  const loggingIn = login && login.phase !== "done" && login.phase !== "error";
5218
5964
  if (!loggingIn && !diagBusy && !updBusy) return;
5219
5965
  const t = setInterval(() => setTick((n) => n + 1), 120);
@@ -5226,8 +5972,8 @@ function SettingsPanel({
5226
5972
  const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
5227
5973
  const local = localQ.data;
5228
5974
  const selfProt = selfQ.data;
5229
- const autoInstalledRef = useRef3(false);
5230
- useEffect7(() => {
5975
+ const autoInstalledRef = useRef4(false);
5976
+ useEffect8(() => {
5231
5977
  if (autoInstalledRef.current) return;
5232
5978
  if (guardHere && guardOld) {
5233
5979
  autoInstalledRef.current = true;
@@ -5239,7 +5985,7 @@ function SettingsPanel({
5239
5985
  }
5240
5986
  }
5241
5987
  }, [guardHere, guardOld]);
5242
- const [hidden, setHidden] = useState8(/* @__PURE__ */ new Set());
5988
+ const [hidden, setHidden] = useState10(/* @__PURE__ */ new Set());
5243
5989
  const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
5244
5990
  const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
5245
5991
  const hasEmailAlert = alerts.some((r) => alertChannel(r) === "email");
@@ -5275,12 +6021,12 @@ function SettingsPanel({
5275
6021
  guardQ.reload();
5276
6022
  selfQ.reload();
5277
6023
  };
5278
- const run = (label, fn, reload) => {
6024
+ const run = (label2, fn, reload) => {
5279
6025
  if (busy) return;
5280
6026
  setBusy(true);
5281
- setMsg({ text: label + "\u2026", level: "ok" });
6027
+ setMsg({ text: label2 + "\u2026", level: "ok" });
5282
6028
  fn().then(() => {
5283
- setMsg({ text: "\u2713 " + label, level: "ok" });
6029
+ setMsg({ text: "\u2713 " + label2, level: "ok" });
5284
6030
  reload();
5285
6031
  }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" })).finally(() => setBusy(false));
5286
6032
  };
@@ -5522,7 +6268,7 @@ function SettingsPanel({
5522
6268
  setEditor({ ...editor, target: v, field: 1 });
5523
6269
  }
5524
6270
  };
5525
- useInput7(
6271
+ useInput9(
5526
6272
  (inp, key) => {
5527
6273
  if (login && (login.phase === "starting" || login.phase === "waiting")) {
5528
6274
  if (key.escape) {
@@ -5657,58 +6403,58 @@ function SettingsPanel({
5657
6403
  );
5658
6404
  const spin = SPIN2[tick % SPIN2.length];
5659
6405
  if (login && (login.phase === "starting" || login.phase === "waiting")) {
5660
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
5661
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "Add an account" }),
5662
- login.phase === "starting" ? /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
6406
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
6407
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: "Add an account" }),
6408
+ login.phase === "starting" ? /* @__PURE__ */ jsxs10(Text10, { color: theme.dim, children: [
5663
6409
  spin,
5664
6410
  " starting device pairing\u2026"
5665
- ] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
5666
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
5667
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
5668
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
5669
- /* @__PURE__ */ jsxs8(Text8, { color: theme.warn, children: [
6411
+ ] }) : /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginTop: 1, children: [
6412
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
6413
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
6414
+ /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
6415
+ /* @__PURE__ */ jsxs10(Text10, { color: theme.warn, children: [
5670
6416
  spin,
5671
6417
  " waiting for authorization\u2026 "
5672
6418
  ] }),
5673
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "esc to cancel" })
6419
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "esc to cancel" })
5674
6420
  ] })
5675
6421
  ] })
5676
6422
  ] });
5677
6423
  }
5678
6424
  if (editor) {
5679
- const fieldRow = (idx, label, value, hint) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
5680
- /* @__PURE__ */ jsx8(Text8, { color: editor.field === idx ? theme.accentBright : theme.dim, children: editor.field === idx ? "\u25B8 " : " " }),
5681
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(11) }),
6425
+ const fieldRow = (idx, label2, value, hint) => /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
6426
+ /* @__PURE__ */ jsx10(Text10, { color: editor.field === idx ? theme.accentBright : theme.dim, children: editor.field === idx ? "\u25B8 " : " " }),
6427
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: label2.padEnd(11) }),
5682
6428
  value,
5683
- editor.field === idx && hint ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + hint }) : null
6429
+ editor.field === idx && hint ? /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " " + hint }) : null
5684
6430
  ] });
5685
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
5686
- /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
6431
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
6432
+ /* @__PURE__ */ jsxs10(Text10, { bold: true, color: theme.accentBright, children: [
5687
6433
  editor.id ? "Edit" : "New",
5688
6434
  " ",
5689
6435
  editor.channel,
5690
6436
  " alert"
5691
6437
  ] }),
5692
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
5693
- editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
5694
- /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
5695
- /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
5696
- ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
5697
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
5698
- fieldRow(0, "target", editor.target ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(editor.target, cols - 16) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "empty \u2014 press e" }), "press e to edit"),
5699
- fieldRow(1, "signal", /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }), "\u2190\u2192 change"),
5700
- fieldRow(2, "threshold", /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: `\u2265 ${editor.threshold}` }), "\u2190\u2192 \xB15"),
5701
- fieldRow(3, "window", /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: winLabel(editor.windowSeconds) }), "\u2190\u2192 change"),
5702
- editor.id ? fieldRow(4, "status", editor.enabled ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "on" }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "off" }), "\u2190\u2192 turn on/off") : null
6438
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
6439
+ editing === "alert-target" ? /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
6440
+ /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
6441
+ /* @__PURE__ */ jsx10(TextInput7, { value: input, onChange: setInput, onSubmit: submitInput })
6442
+ ] }) : msg ? /* @__PURE__ */ jsx10(Text10, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx10(Text10, { children: " " }),
6443
+ /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
6444
+ fieldRow(0, "target", editor.target ? /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: truncate(editor.target, cols - 16) }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "empty \u2014 press e" }), "press e to edit"),
6445
+ fieldRow(1, "signal", /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }), "\u2190\u2192 change"),
6446
+ fieldRow(2, "threshold", /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: `\u2265 ${editor.threshold}` }), "\u2190\u2192 \xB15"),
6447
+ fieldRow(3, "window", /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: winLabel(editor.windowSeconds) }), "\u2190\u2192 change"),
6448
+ editor.id ? fieldRow(4, "status", editor.enabled ? /* @__PURE__ */ jsx10(Text10, { color: theme.ok, children: "on" }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "off" }), "\u2190\u2192 turn on/off") : null
5703
6449
  ] }),
5704
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: editor.id && !editor.enabled ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "disabled \u2014 this alert will NOT fire until you set status back to on" }) : /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
6450
+ /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: editor.id && !editor.enabled ? /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: "disabled \u2014 this alert will NOT fire until you set status back to on" }) : /* @__PURE__ */ jsxs10(Text10, { color: theme.dim, children: [
5705
6451
  "fires when ",
5706
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }),
6452
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }),
5707
6453
  " reach ",
5708
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: editor.threshold }),
6454
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: editor.threshold }),
5709
6455
  " within",
5710
6456
  " ",
5711
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: winLabel(editor.windowSeconds) })
6457
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: winLabel(editor.windowSeconds) })
5712
6458
  ] }) })
5713
6459
  ] });
5714
6460
  }
@@ -5717,140 +6463,140 @@ function SettingsPanel({
5717
6463
  const authError = rawError && /invalid api key|authentication|401|unauthor|not logged in/i.test(rawError) ? rawError : null;
5718
6464
  const noKey = !!authError && /not logged in/i.test(authError);
5719
6465
  const error = authError ? null : rawError;
5720
- const onOff = (on) => on ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "off" });
6466
+ const onOff = (on) => on ? /* @__PURE__ */ jsx10(Text10, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "off" });
5721
6467
  const chanOf = (rule) => [...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
5722
6468
  const isCur = (r) => keyOf(r) === keyOf(cur) && focused;
5723
- const cursor = (r) => /* @__PURE__ */ jsx8(Text8, { color: isCur(r) ? theme.accentBright : theme.dim, children: isCur(r) ? "\u25B8 " : " " });
6469
+ const cursor = (r) => /* @__PURE__ */ jsx10(Text10, { color: isCur(r) ? theme.accentBright : theme.dim, children: isCur(r) ? "\u25B8 " : " " });
5724
6470
  const rowLine = (r) => {
5725
6471
  switch (r.kind) {
5726
6472
  case "acct": {
5727
6473
  const a = r.acc;
5728
6474
  const isView = viewApiKey ? a.apiKey === viewApiKey : accounts[0]?.apiKey === a.apiKey;
5729
6475
  const isActive = isActiveAccount(a.apiKey);
5730
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6476
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5731
6477
  cursor(r),
5732
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(acctLabel(a), 30).padEnd(31) }),
5733
- isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
5734
- isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
5735
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.project ? truncate(a.project, 20) : "" })
6478
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: truncate(acctLabel(a), 30).padEnd(31) }),
6479
+ isView ? /* @__PURE__ */ jsx10(Text10, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " " }),
6480
+ isActive ? /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " " }),
6481
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: a.project ? truncate(a.project, 20) : "" })
5736
6482
  ] });
5737
6483
  }
5738
6484
  case "acct-add":
5739
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6485
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5740
6486
  cursor(r),
5741
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
6487
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
5742
6488
  ] });
5743
6489
  case "guard":
5744
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6490
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5745
6491
  cursor(r),
5746
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "guard".padEnd(11) }),
5747
- !guardHere ? /* @__PURE__ */ jsxs8(Fragment5, { children: [
5748
- /* @__PURE__ */ jsx8(Text8, { color: theme.bad, bold: true, children: "removed" }),
5749
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \xB7 " }),
5750
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: "enter to install" })
5751
- ] }) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
5752
- /* @__PURE__ */ jsx8(Text8, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
5753
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
5754
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \xB7 d remove" }),
5755
- codexTrust ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: " \xB7 codex: run /hooks once to trust" }) : null
6492
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "guard".padEnd(11) }),
6493
+ !guardHere ? /* @__PURE__ */ jsxs10(Fragment6, { children: [
6494
+ /* @__PURE__ */ jsx10(Text10, { color: theme.bad, bold: true, children: "removed" }),
6495
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \xB7 " }),
6496
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: "enter to install" })
6497
+ ] }) : /* @__PURE__ */ jsxs10(Fragment6, { children: [
6498
+ /* @__PURE__ */ jsx10(Text10, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
6499
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
6500
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \xB7 d remove" }),
6501
+ codexTrust ? /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: " \xB7 codex: run /hooks once to trust" }) : null
5756
6502
  ] })
5757
6503
  ] });
5758
6504
  case "self":
5759
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6505
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5760
6506
  cursor(r),
5761
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "self-prot".padEnd(11) }),
5762
- selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2026" }),
5763
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
6507
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "self-prot".padEnd(11) }),
6508
+ selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "\u2026" }),
6509
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
5764
6510
  ] });
5765
6511
  case "doctor":
5766
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6512
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5767
6513
  cursor(r),
5768
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "doctor".padEnd(11) }),
5769
- diagBusy === "doctor" ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: `${spin} running health check\u2026` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "health check: login, policy, guard, hooks, local logs \xB7 enter runs it" })
6514
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "doctor".padEnd(11) }),
6515
+ diagBusy === "doctor" ? /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: `${spin} running health check\u2026` }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "health check: login, policy, guard, hooks, local logs \xB7 enter runs it" })
5770
6516
  ] });
5771
6517
  case "repair":
5772
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6518
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5773
6519
  cursor(r),
5774
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "repair".padEnd(11) }),
5775
- diagBusy === "repair" ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: `${spin} restoring protection\u2026` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "restore every protection file after tampering or deletion \xB7 enter runs it" })
6520
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "repair".padEnd(11) }),
6521
+ diagBusy === "repair" ? /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: `${spin} restoring protection\u2026` }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "restore every protection file after tampering or deletion \xB7 enter runs it" })
5776
6522
  ] });
5777
6523
  case "cli-update": {
5778
6524
  const behind = latest ? newerThan(latest, ver) : false;
5779
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6525
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5780
6526
  cursor(r),
5781
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "version".padEnd(11) }),
5782
- /* @__PURE__ */ jsx8(Text8, { color: behind ? theme.warn : theme.ok, children: `v${ver}` }),
5783
- updBusy ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind && canInstall === false ? (
6527
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "version".padEnd(11) }),
6528
+ /* @__PURE__ */ jsx10(Text10, { color: behind ? theme.warn : theme.ok, children: `v${ver}` }),
6529
+ updBusy ? /* @__PURE__ */ jsx10(Text10, { color: theme.accentBright, children: ` ${spin} updating\u2026` }) : behind && canInstall === false ? (
5784
6530
  // Said before they press enter, not after a minute of npm noise.
5785
- /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` v${latest} on npm \xB7 needs sudo here \xB7 enter shows how` })
5786
- ) : behind ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` v${latest} on npm \xB7 enter updates now` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: latest ? " latest \xB7 enter checks again" : " enter checks npm and updates" })
6531
+ /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: ` v${latest} on npm \xB7 needs sudo here \xB7 enter shows how` })
6532
+ ) : behind ? /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: ` v${latest} on npm \xB7 enter updates now` }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: latest ? " latest \xB7 enter checks again" : " enter checks npm and updates" })
5787
6533
  ] });
5788
6534
  }
5789
6535
  case "auto-update":
5790
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6536
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5791
6537
  cursor(r),
5792
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "auto".padEnd(11) }),
6538
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "auto".padEnd(11) }),
5793
6539
  onOff(autoUp),
5794
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : canInstall === false ? " cannot work here: npm needs sudo, so updates stay manual \xB7 enter toggles" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
6540
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: autoUpdateForcedByEnv() ? " set by SOLONGATE_AUTO_UPDATE" : canInstall === false ? " cannot work here: npm needs sudo, so updates stay manual \xB7 enter toggles" : autoUp ? " installs new versions in the background \xB7 enter toggles" : " off: nothing installs on its own (npm -g may need sudo) \xB7 enter toggles" })
5795
6541
  ] });
5796
6542
  case "ll-enabled":
5797
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6543
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5798
6544
  cursor(r),
5799
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "enabled ".padEnd(11) }),
6545
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "enabled ".padEnd(11) }),
5800
6546
  onOff(!!local?.enabled),
5801
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " enter toggles" })
6547
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " enter toggles" })
5802
6548
  ] });
5803
6549
  case "ll-path":
5804
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6550
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5805
6551
  cursor(r),
5806
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "path ".padEnd(11) }),
6552
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "path ".padEnd(11) }),
5807
6553
  local?.path ? (
5808
6554
  // The folder is a PROJECT setting shared by every device on it, so
5809
6555
  // it can name a path that only exists on another OS. The hooks then
5810
6556
  // quietly fall back and this row was the last place still claiming
5811
6557
  // the configured path was in use.
5812
- llSetting && local.enabled && !llSetting.usableHere ? /* @__PURE__ */ jsxs8(Fragment5, { children: [
5813
- /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: truncate(local.path, Math.max(12, Math.floor((cols - 20) / 2))) }),
5814
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` not valid here \u2192 ${truncate(llSetting.file, Math.max(12, Math.floor((cols - 20) / 2)))}` })
5815
- ] }) : /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(local.path, cols - 14) })
5816
- ) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
6558
+ llSetting && local.enabled && !llSetting.usableHere ? /* @__PURE__ */ jsxs10(Fragment6, { children: [
6559
+ /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: truncate(local.path, Math.max(12, Math.floor((cols - 20) / 2))) }),
6560
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: ` not valid here \u2192 ${truncate(llSetting.file, Math.max(12, Math.floor((cols - 20) / 2)))}` })
6561
+ ] }) : /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: truncate(local.path, cols - 14) })
6562
+ ) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "not set \u2014 enter to edit" })
5817
6563
  ] });
5818
6564
  case "ll-server":
5819
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6565
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5820
6566
  cursor(r),
5821
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "dashboard".padEnd(11) }),
5822
- srv.running ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: `running 127.0.0.1:${srv.port}` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "stopped" }),
5823
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: srv.running ? " survives closing the dataroom \xB7 enter stops" : " enter starts the dashboard local-logs link" })
6567
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "dashboard".padEnd(11) }),
6568
+ srv.running ? /* @__PURE__ */ jsx10(Text10, { color: theme.ok, children: `running 127.0.0.1:${srv.port}` }) : /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "stopped" }),
6569
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: srv.running ? " survives closing the dataroom \xB7 enter stops" : " enter starts the dashboard local-logs link" })
5824
6570
  ] });
5825
6571
  case "wh":
5826
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6572
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5827
6573
  cursor(r),
5828
6574
  onOff(r.wh.enabled),
5829
- /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + r.wh.events).padEnd(9) }),
5830
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(r.wh.url, cols - 16) })
6575
+ /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: (" " + r.wh.events).padEnd(9) }),
6576
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: truncate(r.wh.url, cols - 16) })
5831
6577
  ] });
5832
6578
  case "wh-add":
5833
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6579
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5834
6580
  cursor(r),
5835
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add webhook (enter)" })
6581
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "+ add webhook (enter)" })
5836
6582
  ] });
5837
6583
  case "alert":
5838
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6584
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5839
6585
  cursor(r),
5840
6586
  onOff(r.rule.enabled),
5841
- /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + SIGNAL_LABEL[r.rule.signal]).padEnd(13) }),
5842
- /* @__PURE__ */ jsx8(Text8, { children: `\u2265${r.rule.threshold}/${winLabel(r.rule.windowSeconds)} `.padEnd(11) }),
5843
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(chanOf(r.rule), cols - 34) })
6587
+ /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: (" " + SIGNAL_LABEL[r.rule.signal]).padEnd(13) }),
6588
+ /* @__PURE__ */ jsx10(Text10, { children: `\u2265${r.rule.threshold}/${winLabel(r.rule.windowSeconds)} `.padEnd(11) }),
6589
+ /* @__PURE__ */ jsx10(Text10, { color: theme.accent, children: truncate(chanOf(r.rule), cols - 34) })
5844
6590
  ] });
5845
6591
  case "alert-add-email":
5846
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6592
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5847
6593
  cursor(r),
5848
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add email alert (enter)" })
6594
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "+ add email alert (enter)" })
5849
6595
  ] });
5850
6596
  case "alert-add-tg":
5851
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
6597
+ return /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
5852
6598
  cursor(r),
5853
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id from @userinfobot)" })
6599
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id from @userinfobot)" })
5854
6600
  ] });
5855
6601
  }
5856
6602
  };
@@ -5870,31 +6616,31 @@ function SettingsPanel({
5870
6616
  const sec = sectionOf(r);
5871
6617
  if (sec !== lastSec) {
5872
6618
  if (lastSec) {
5873
- lineEls.push(/* @__PURE__ */ jsx8(Text8, { children: " " }, "sp:" + sec));
6619
+ lineEls.push(/* @__PURE__ */ jsx10(Text10, { children: " " }, "sp:" + sec));
5874
6620
  lineKey.push("");
5875
6621
  }
5876
6622
  lastSec = sec;
5877
6623
  lineEls.push(
5878
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
5879
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: sec }),
5880
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 " + SECTION_DESC[sec] })
6624
+ /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", children: [
6625
+ /* @__PURE__ */ jsx10(Text10, { bold: true, color: theme.accentBright, children: sec }),
6626
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " \u2014 " + SECTION_DESC[sec] })
5881
6627
  ] }, "h:" + sec)
5882
6628
  );
5883
6629
  lineKey.push("");
5884
6630
  if ((sec === "WEBHOOKS" || sec === "ALERTS") && local?.enabled) {
5885
6631
  lineEls.push(
5886
- /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: theme.warn, children: ` \u26A0 local logs on \u2014 real denials stay on this machine, so ${sec.toLowerCase()} do NOT fire${sec === "WEBHOOKS" ? " (t test still works)" : ""}` }, "warn:" + sec)
6632
+ /* @__PURE__ */ jsx10(Text10, { wrap: "truncate", color: theme.warn, children: ` \u26A0 local logs on \u2014 real denials stay on this machine, so ${sec.toLowerCase()} do NOT fire${sec === "WEBHOOKS" ? " (t test still works)" : ""}` }, "warn:" + sec)
5887
6633
  );
5888
6634
  lineKey.push("");
5889
6635
  }
5890
6636
  }
5891
- lineEls.push(/* @__PURE__ */ jsx8(Box8, { children: rowLine(r) }, keyOf(r)));
6637
+ lineEls.push(/* @__PURE__ */ jsx10(Box10, { children: rowLine(r) }, keyOf(r)));
5892
6638
  lineKey.push(keyOf(r));
5893
6639
  if ((r.kind === "doctor" || r.kind === "repair") && diagBusy === r.kind) {
5894
- DIAG_STEPS[r.kind].forEach((label, i) => {
6640
+ DIAG_STEPS[r.kind].forEach((label2, i) => {
5895
6641
  const done = i < diagStep;
5896
6642
  lineEls.push(
5897
- /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: done ? theme.ok : i === diagStep ? theme.accentBright : theme.dim, children: ` ${done ? "\u2713" : i === diagStep ? spin : " "} ${label}` }, `step:${r.kind}:${i}`)
6643
+ /* @__PURE__ */ jsx10(Text10, { wrap: "truncate", color: done ? theme.ok : i === diagStep ? theme.accentBright : theme.dim, children: ` ${done ? "\u2713" : i === diagStep ? spin : " "} ${label2}` }, `step:${r.kind}:${i}`)
5898
6644
  );
5899
6645
  lineKey.push("");
5900
6646
  });
@@ -5902,8 +6648,8 @@ function SettingsPanel({
5902
6648
  if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair" || r.kind === "cli-update" && diag.of === "cli-update")) {
5903
6649
  diag.lines.forEach((l, i) => {
5904
6650
  lineEls.push(
5905
- /* @__PURE__ */ jsx8(
5906
- Text8,
6651
+ /* @__PURE__ */ jsx10(
6652
+ Text10,
5907
6653
  {
5908
6654
  wrap: "truncate",
5909
6655
  color: l.level === "ok" ? theme.ok : l.level === "warn" ? theme.warn : l.level === "bad" ? theme.bad : theme.dim,
@@ -5918,16 +6664,16 @@ function SettingsPanel({
5918
6664
  });
5919
6665
  if (hasEmailAlert && hasTgAlert) {
5920
6666
  lineEls.push(
5921
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }, "both-note")
6667
+ /* @__PURE__ */ jsx10(Text10, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }, "both-note")
5922
6668
  );
5923
6669
  lineKey.push("");
5924
6670
  }
5925
- lineEls.push(/* @__PURE__ */ jsx8(Text8, { children: " " }, "ver-sp"));
6671
+ lineEls.push(/* @__PURE__ */ jsx10(Text10, { children: " " }, "ver-sp"));
5926
6672
  lineKey.push("");
5927
6673
  lineEls.push(
5928
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", color: theme.dim, children: [
6674
+ /* @__PURE__ */ jsxs10(Text10, { wrap: "truncate", color: theme.dim, children: [
5929
6675
  `solongate v${ver}`,
5930
- latest ? newerThan(latest, ver) ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \xB7 v${latest} on npm \u2014 ${autoUp ? "auto-updating" : "UPDATES above"}` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: " \xB7 latest" }) : null
6676
+ latest ? newerThan(latest, ver) ? /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: ` \xB7 v${latest} on npm \u2014 ${autoUp ? "auto-updating" : "UPDATES above"}` }) : /* @__PURE__ */ jsx10(Text10, { color: theme.ok, children: " \xB7 latest" }) : null
5931
6677
  ] }, "ver")
5932
6678
  );
5933
6679
  lineKey.push("");
@@ -5938,36 +6684,50 @@ function SettingsPanel({
5938
6684
  const win = lineEls.slice(startL, startL + budget);
5939
6685
  const moreAbove = startL;
5940
6686
  const moreBelow = Math.max(0, lineEls.length - (startL + budget));
5941
- return /* @__PURE__ */ jsx8(DataView, { loading, error, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
5942
- /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: theme.dim, children: focused ? `\u2191\u2193 move \xB7 enter act \xB7 m toggle \xB7 t test \xB7 e edit \xB7 x remove \xB7 d delete \xB7 n add${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to configure" }),
5943
- authError ? /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: theme.bad, children: truncate(
6687
+ return /* @__PURE__ */ jsx10(DataView, { loading, error, children: /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
6688
+ /* @__PURE__ */ jsx10(Text10, { wrap: "truncate", color: theme.dim, children: focused ? `\u2191\u2193 move \xB7 enter act \xB7 m toggle \xB7 t test \xB7 e edit \xB7 x remove \xB7 d delete \xB7 n add${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to configure" }),
6689
+ authError ? /* @__PURE__ */ jsx10(Text10, { wrap: "truncate", color: theme.bad, children: truncate(
5944
6690
  noKey ? "\u2717 this device is not paired yet \u2014 press + add account below to sign in and pair it" : "\u2717 this device's key is invalid or was revoked \u2014 press x on the account below to remove it, then + add account to pair again",
5945
6691
  cols
5946
6692
  ) }) : null,
5947
- editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
5948
- /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
5949
- /* @__PURE__ */ jsx8(TextInput6, { value: input, onChange: setInput, onSubmit: submitInput })
5950
- ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
5951
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
6693
+ editing ? /* @__PURE__ */ jsxs10(Box10, { children: [
6694
+ /* @__PURE__ */ jsx10(Text10, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
6695
+ /* @__PURE__ */ jsx10(TextInput7, { value: input, onChange: setInput, onSubmit: submitInput })
6696
+ ] }) : msg ? /* @__PURE__ */ jsx10(Text10, { wrap: "truncate", color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx10(Text10, { wrap: "truncate", color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx10(Text10, { children: " " }),
6697
+ /* @__PURE__ */ jsx10(Box10, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
5952
6698
  ] }) });
5953
6699
  }
5954
6700
 
5955
6701
  // src/tui/App.tsx
5956
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
6702
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
5957
6703
  function LiveHint() {
5958
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
5959
- /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
5960
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
5961
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
5962
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
6704
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
6705
+ /* @__PURE__ */ jsx11(Text11, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE \u2014 THIS MACHINE" }),
6706
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
6707
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
6708
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { children: [
6709
+ "press ",
6710
+ /* @__PURE__ */ jsx11(Text11, { color: theme.accent, children: "enter" }),
6711
+ " to go live"
6712
+ ] }) })
6713
+ ] });
6714
+ }
6715
+ function FleetLiveHint() {
6716
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
6717
+ /* @__PURE__ */ jsx11(Text11, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE \u2014 THE WHOLE FLEET" }),
6718
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: "Every machine under your policy, as it happens: whose call it was," }),
6719
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: "what was refused, and who is working right now." }),
6720
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, children: /* @__PURE__ */ jsxs11(Text11, { children: [
5963
6721
  "press ",
5964
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
6722
+ /* @__PURE__ */ jsx11(Text11, { color: theme.accent, children: "enter" }),
5965
6723
  " to go live"
5966
6724
  ] }) })
5967
6725
  ] });
5968
6726
  }
5969
6727
  var SECTIONS = [
5970
- { label: "Live", Panel: LiveHint },
6728
+ { label: "Solo Live", Panel: LiveHint },
6729
+ { label: "Fleet Live", Panel: FleetLiveHint },
6730
+ { label: "Fleet", Panel: FleetPanel },
5971
6731
  { label: "Policies", Panel: PoliciesPanel },
5972
6732
  { label: "Rate Limit", Panel: RateLimitPanel },
5973
6733
  { label: "DLP", Panel: DlpPanel },
@@ -5975,33 +6735,34 @@ var SECTIONS = [
5975
6735
  { label: "Audit", Panel: AuditPanel },
5976
6736
  { label: "Settings", Panel: SettingsPanel }
5977
6737
  ];
6738
+ var TAKEOVER = /* @__PURE__ */ new Set(["Solo Live", "Fleet Live"]);
5978
6739
  var BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
5979
6740
  function Banner({ cols }) {
5980
6741
  const wide = cols >= 82;
5981
6742
  if (!wide) {
5982
- return /* @__PURE__ */ jsxs9(Box9, { children: [
5983
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
5984
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
6743
+ return /* @__PURE__ */ jsxs11(Box11, { children: [
6744
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.accentBright, children: "SolonGate" }),
6745
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: " \u2014 security control center" })
5985
6746
  ] });
5986
6747
  }
5987
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
5988
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
5989
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
6748
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
6749
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx11(Text11, { bold: true, color: BANNER_HEX[i], children: line }, i)),
6750
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
5990
6751
  ] });
5991
6752
  }
5992
6753
  var SETTINGS_SECTION = SECTIONS.findIndex((s) => s.label === "Settings");
5993
6754
  function App() {
5994
6755
  const { exit } = useApp();
5995
- const [accounts, setAccounts] = useState9(() => listAccounts());
5996
- const [viewKey, setViewKey] = useState9(() => accounts[0]?.apiKey);
5997
- const [viewNonce, setViewNonce] = useState9(0);
5998
- const [section, setSection] = useState9(accounts.length === 0 ? SETTINGS_SECTION : 0);
5999
- const [focus, setFocus] = useState9("nav");
6000
- useEffect8(
6756
+ const [accounts, setAccounts] = useState11(() => listAccounts());
6757
+ const [viewKey, setViewKey] = useState11(() => accounts[0]?.apiKey);
6758
+ const [viewNonce, setViewNonce] = useState11(0);
6759
+ const [section, setSection] = useState11(accounts.length === 0 ? SETTINGS_SECTION : 0);
6760
+ const [focus, setFocus] = useState11("nav");
6761
+ useEffect9(
6001
6762
  () => onSectionRequest(() => {
6002
- const label = takePendingSection();
6003
- if (!label) return;
6004
- const idx = SECTIONS.findIndex((s) => s.label === label);
6763
+ const label2 = takePendingSection();
6764
+ if (!label2) return;
6765
+ const idx = SECTIONS.findIndex((s) => s.label === label2);
6005
6766
  if (idx >= 0) {
6006
6767
  setSection(idx);
6007
6768
  setFocus("panel");
@@ -6009,9 +6770,9 @@ function App() {
6009
6770
  }),
6010
6771
  []
6011
6772
  );
6012
- const [help, setHelp] = useState9(false);
6013
- const [update2, setUpdate] = useState9({ kind: "idle" });
6014
- useEffect8(() => {
6773
+ const [help, setHelp] = useState11(false);
6774
+ const [update2, setUpdate] = useState11({ kind: "idle" });
6775
+ useEffect9(() => {
6015
6776
  let alive = true;
6016
6777
  const run = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
6017
6778
  run();
@@ -6025,7 +6786,7 @@ function App() {
6025
6786
  const cur = accounts[acctIdx];
6026
6787
  const rawEmail = cur?.email ?? "";
6027
6788
  const acctLabel2 = !cur ? "not logged in" : rawEmail || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}`;
6028
- useEffect8(() => {
6789
+ useEffect9(() => {
6029
6790
  if (!cur || cur.email) return;
6030
6791
  let alive = true;
6031
6792
  api.auth.me().then((r) => {
@@ -6059,7 +6820,7 @@ function App() {
6059
6820
  setViewKey(next?.apiKey);
6060
6821
  setViewNonce((n) => n + 1);
6061
6822
  };
6062
- useEffect8(() => {
6823
+ useEffect9(() => {
6063
6824
  try {
6064
6825
  ensureLocalLogOwner(enforcingKey());
6065
6826
  } catch {
@@ -6067,7 +6828,7 @@ function App() {
6067
6828
  }, [accounts.length]);
6068
6829
  const locked = accounts.length === 0;
6069
6830
  const effectiveSection = locked ? SETTINGS_SECTION : section;
6070
- useInput8((input, key) => {
6831
+ useInput10((input, key) => {
6071
6832
  if (help) {
6072
6833
  setHelp(false);
6073
6834
  return;
@@ -6095,39 +6856,41 @@ function App() {
6095
6856
  });
6096
6857
  const { cols, rows } = useTermSize();
6097
6858
  const current = SECTIONS[effectiveSection];
6098
- if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
6099
- if (current.label === "Live" && focus === "panel") {
6100
- return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
6859
+ if (help) return /* @__PURE__ */ jsx11(HelpOverlay, { cols, rows });
6860
+ if (TAKEOVER.has(current.label) && focus === "panel") {
6861
+ return /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: current.label === "Fleet Live" ? /* @__PURE__ */ jsx11(FleetLivePanel, { active: true, focused: true }, viewNonce) : /* @__PURE__ */ jsx11(LivePanel, { active: true, focused: true }, viewNonce) });
6101
6862
  }
6102
6863
  const Panel = current.Panel;
6103
- const panelBody = current.label === "Settings" ? /* @__PURE__ */ jsx9(SettingsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount, onAccountsChanged: syncAccounts }) : /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
6864
+ const panelBody = current.label === "Settings" ? /* @__PURE__ */ jsx11(SettingsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount, onAccountsChanged: syncAccounts }) : /* @__PURE__ */ jsx11(Panel, { active: focus === "panel" || !TAKEOVER.has(current.label), focused: focus === "panel" });
6104
6865
  return (
6105
6866
  // height rows-1 (not rows): once total output reaches stdout.rows ink stops
6106
6867
  // diffing and clearTerminal-repaints every frame, which slides the banner.
6107
6868
  // overflow hidden makes it a hard guarantee even if a panel over-renders.
6108
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, paddingTop: 1, overflow: "hidden", children: [
6109
- /* @__PURE__ */ jsx9(Banner, { cols }),
6110
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
6111
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
6112
- /* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
6113
- locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Settings to add another" }),
6114
- update2.kind === "updating" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \xB7 restart (q, then solongate) to apply` }) : update2.kind === "available" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} out \xB7 Settings \u2192 UPDATES` }) : update2.kind === "needs-admin" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} needs admin rights \xB7 Settings \u2192 UPDATES` }) : null
6869
+ /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, paddingTop: 1, overflow: "hidden", children: [
6870
+ /* @__PURE__ */ jsx11(Banner, { cols }),
6871
+ /* @__PURE__ */ jsxs11(Text11, { wrap: "truncate", children: [
6872
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: "account: " }),
6873
+ /* @__PURE__ */ jsx11(Text11, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
6874
+ locked ? /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: " \xB7 Settings to add another" }),
6875
+ update2.kind === "updating" ? /* @__PURE__ */ jsx11(Text11, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx11(Text11, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \xB7 restart (q, then solongate) to apply` }) : update2.kind === "available" ? /* @__PURE__ */ jsx11(Text11, { color: theme.warn, children: ` \u2191 v${update2.version} out \xB7 Settings \u2192 UPDATES` }) : update2.kind === "needs-admin" ? /* @__PURE__ */ jsx11(Text11, { color: theme.warn, children: ` \u2191 v${update2.version} needs admin rights \xB7 Settings \u2192 UPDATES` }) : null
6115
6876
  ] }),
6116
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
6117
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
6877
+ /* @__PURE__ */ jsxs11(Box11, { marginTop: 1, flexGrow: 1, children: [
6878
+ /* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
6118
6879
  const isCur = i === effectiveSection;
6119
6880
  const disabled = locked && s.label !== "Settings";
6120
- return /* @__PURE__ */ jsx9(Text9, { color: isCur ? theme.accentBright : disabled ? theme.dim : void 0, bold: isCur, dimColor: disabled, children: (isCur ? "\u25B8 " : disabled ? "\u2298 " : " ") + s.label }, s.label);
6881
+ return /* @__PURE__ */ jsx11(Text11, { color: isCur ? theme.accentBright : disabled ? theme.dim : void 0, bold: isCur, dimColor: disabled, children: (isCur ? "\u25B8 " : disabled ? "\u2298 " : " ") + s.label }, s.label);
6121
6882
  }) }),
6122
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
6883
+ /* @__PURE__ */ jsx11(Box11, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
6123
6884
  ] }, viewNonce),
6124
- /* @__PURE__ */ jsx9(Box9, { children: locked ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2192/enter", "open Settings"], ["n", "log in"], ["q", "quit"]] }) : focus === "nav" ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
6885
+ /* @__PURE__ */ jsx11(Box11, { children: locked ? /* @__PURE__ */ jsx11(KeyHints, { hints: [["\u2192/enter", "open Settings"], ["n", "log in"], ["q", "quit"]] }) : focus === "nav" ? /* @__PURE__ */ jsx11(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx11(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
6125
6886
  ] })
6126
6887
  );
6127
6888
  }
6128
6889
  var HELP = [
6129
6890
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
6130
- ["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)"]]],
6891
+ ["Solo 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)"]]],
6892
+ ["Fleet Live", [["space", "pause the ticker"], ["d", "refusals only"], ["r", "refresh"], ["", "counters are since you opened it, so a still number means nothing is happening"]]],
6893
+ ["Fleet", [["\u2191\u2193", "move through the groups and the people in them"], ["\u2192 / enter", "open one person"], ["i", "invite somebody"], ["n", "new group"], ["r", "rename the group"], ["c", "cycle its colour"], ["g", "file this person under a group"], ["p", "policy \u2014 on a group it applies to all of them, on a person it is an exception"], ["d", "delete the group / revoke the person"], ["R", "reports: yesterday, the schedule, and sending one now"]]],
6131
6894
  ["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"]]],
6132
6895
  ["Rate limit", [["\u2191\u2193", "field"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["s", "save"]]],
6133
6896
  ["DLP", [["\u2191\u2193", "move"], ["space", "toggle a built-in pattern on/off"], ["m", "cycle mode"], ["a", "add custom pattern (name \u2192 glob, * = any chars)"], ["r", "add a ghost route (glob path to hide)"], ["d", "remove custom / route"], ["g", "ghost on/off"], ["s", "save \xB7 x discard"]]],
@@ -6136,21 +6899,21 @@ var HELP = [
6136
6899
  ["Accounts", [["a", "switch account (view another account logged in on this device)"], ["", "the header shows which account you are viewing; guard/logging keep the active key"]]]
6137
6900
  ];
6138
6901
  function HelpOverlay({ cols, rows }) {
6139
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
6140
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
6141
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
6142
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
6143
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
6144
- /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
6145
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
6902
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
6903
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
6904
+ /* @__PURE__ */ jsx11(Box11, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", marginBottom: 1, children: [
6905
+ /* @__PURE__ */ jsx11(Text11, { bold: true, color: theme.accent, children: group }),
6906
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs11(Text11, { children: [
6907
+ /* @__PURE__ */ jsx11(Text11, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
6908
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: desc })
6146
6909
  ] }, k))
6147
6910
  ] }, group)) }),
6148
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
6911
+ /* @__PURE__ */ jsx11(Text11, { color: theme.dim, children: "press any key to close" })
6149
6912
  ] });
6150
6913
  }
6151
6914
 
6152
6915
  // src/tui/index.tsx
6153
- import { jsx as jsx10 } from "react/jsx-runtime";
6916
+ import { jsx as jsx12 } from "react/jsx-runtime";
6154
6917
  async function launchTui() {
6155
6918
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
6156
6919
  process.stderr.write(
@@ -6175,7 +6938,7 @@ async function launchTui() {
6175
6938
  console.info = toFile("info");
6176
6939
  console.debug = toFile("debug");
6177
6940
  try {
6178
- const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}), { patchConsole: false });
6941
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx12(App, {}), { patchConsole: false });
6179
6942
  await waitUntilExit();
6180
6943
  } finally {
6181
6944
  Object.assign(console, saved);