@solongate/proxy 0.82.30 → 0.82.32

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
@@ -11,8 +11,8 @@ import { join as join9 } from "path";
11
11
  import { render } from "ink";
12
12
 
13
13
  // src/tui/App.tsx
14
- import { Box as Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
15
- import { useEffect as useEffect7, useState as useState8 } from "react";
14
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
15
+ import { useEffect as useEffect8, useState as useState9 } from "react";
16
16
 
17
17
  // src/cli-utils.ts
18
18
  var c = {
@@ -566,6 +566,7 @@ var policies_exports = {};
566
566
  __export(policies_exports, {
567
567
  active: () => active,
568
568
  addRule: () => addRule,
569
+ backtest: () => backtest,
569
570
  create: () => create,
570
571
  dryRun: () => dryRun,
571
572
  get: () => get,
@@ -615,6 +616,9 @@ function setActive(policyId) {
615
616
  function dryRun(body) {
616
617
  return request("POST", "/policies/dry-run", { body });
617
618
  }
619
+ function backtest(body) {
620
+ return request("POST", "/policies/backtest", { body });
621
+ }
618
622
 
619
623
  // src/api-client/settings.ts
620
624
  var settings_exports = {};
@@ -1899,11 +1903,167 @@ function LivePanel({ active: active2 }) {
1899
1903
  }
1900
1904
 
1901
1905
  // src/tui/panels/Policies.tsx
1902
- import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1906
+ import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
1903
1907
  import TextInput2 from "ink-text-input";
1908
+ import { useEffect as useEffect4, useState as useState4 } from "react";
1909
+
1910
+ // src/tui/panels/DryRun.tsx
1911
+ import { Box as Box3, Text as Text3, useInput as useInput2 } from "ink";
1904
1912
  import { useEffect as useEffect3, useState as useState3 } from "react";
1905
- import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1906
- var DASHBOARD_URL = "https://dashboard.solongate.com";
1913
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1914
+ var SAMPLES = [100, 250, 500, 1e3, 2500, 5e3];
1915
+ var pendingPolicyId = null;
1916
+ function requestDryRun(policyId) {
1917
+ pendingPolicyId = policyId;
1918
+ }
1919
+ var pct = (n, d) => d ? (n / d * 100).toFixed(1) + "%" : "0%";
1920
+ function DryRunPanel({ focused }) {
1921
+ const list5 = useLoader(() => api.policies.list());
1922
+ const policies = list5.data?.policies ?? [];
1923
+ const [pi, setPi] = useState3(0);
1924
+ const [si, setSi] = useState3(3);
1925
+ const [res, setRes] = useState3(null);
1926
+ const [running, setRunning] = useState3(false);
1927
+ const [err, setErr] = useState3(null);
1928
+ const [off, setOff] = useState3(0);
1929
+ const { cols, rows } = usePanelSize();
1930
+ const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
1931
+ function run(policyIdx = pi, sampleIdx = si) {
1932
+ const p = policies[policyIdx];
1933
+ if (!p) return;
1934
+ setRunning(true);
1935
+ setErr(null);
1936
+ setRes(null);
1937
+ setOff(0);
1938
+ api.policies.backtest({ rules: p.rules ?? [], mode: p.mode, limit: SAMPLES[sampleIdx] }).then((r) => setRes(r)).catch((e) => setErr(e instanceof Error ? e.message : String(e))).finally(() => setRunning(false));
1939
+ }
1940
+ useEffect3(() => {
1941
+ if (policies.length === 0 || res || running || err) return;
1942
+ let idx = 0;
1943
+ if (pendingPolicyId) {
1944
+ const found = policies.findIndex((p) => p.id === pendingPolicyId);
1945
+ if (found >= 0) idx = found;
1946
+ pendingPolicyId = null;
1947
+ }
1948
+ setPi(idx);
1949
+ run(idx, si);
1950
+ }, [policies.length]);
1951
+ useInput2((input, key) => {
1952
+ if (!focused) return;
1953
+ if (key.upArrow) setOff((n) => Math.max(0, n - 1));
1954
+ else if (key.downArrow) setOff((n) => n + 1);
1955
+ else if (input === "p") {
1956
+ const n = policies.length ? (pi + 1) % policies.length : 0;
1957
+ setPi(n);
1958
+ run(n, si);
1959
+ } else if (input === "n") {
1960
+ const n = (si + 1) % SAMPLES.length;
1961
+ setSi(n);
1962
+ run(pi, n);
1963
+ } else if (input === "r") run(pi, si);
1964
+ });
1965
+ const w = Math.max(40, cols);
1966
+ const lines = [];
1967
+ const head = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: t }, `h${lines.length}`));
1968
+ const dim = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: t }, `d${lines.length}`));
1969
+ const row = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { wrap: "truncate", children: t }, `r${lines.length}`));
1970
+ const blank = () => lines.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, `b${lines.length}`));
1971
+ if (res) {
1972
+ const s = res.summary;
1973
+ const denyAfter = s.would_deny;
1974
+ const denyBefore = s.would_deny - s.newly_blocked + s.newly_allowed;
1975
+ const before = pct(denyBefore, s.evaluated);
1976
+ const after = pct(denyAfter, s.evaluated);
1977
+ const ppShift = s.evaluated ? ((denyAfter - denyBefore) / s.evaluated * 100).toFixed(1) : "0.0";
1978
+ head("Summary");
1979
+ row(` evaluated ${s.evaluated} would allow ${s.would_allow} would deny ${s.would_deny}`);
1980
+ lines.push(
1981
+ /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
1982
+ " newly blocked ",
1983
+ /* @__PURE__ */ jsx3(Text3, { color: theme.bad, bold: true, children: String(s.newly_blocked) }),
1984
+ " newly allowed ",
1985
+ /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: String(s.newly_allowed) }),
1986
+ " unchanged ",
1987
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: String(s.unchanged) })
1988
+ ] }, `sum${lines.length}`)
1989
+ );
1990
+ row(` deny rate ${before} -> ${after} (${Number(ppShift) > 0 ? "+" : ""}${ppShift}pp)`);
1991
+ blank();
1992
+ if (res.per_rule.length) {
1993
+ head("Per-rule impact");
1994
+ dim(` ${"RULE".padEnd(38)}${"MATCHED".padEnd(9)}${"%".padEnd(8)}${"NEW-BLK".padEnd(9)}NEW-ALW`);
1995
+ for (const r of res.per_rule.slice(0, 30)) {
1996
+ row(` ${truncate(r.rule_id, 36).padEnd(38)}${String(r.matched).padEnd(9)}${pct(r.matched, s.evaluated).padEnd(8)}${String(r.newly_blocked).padEnd(9)}${r.newly_allowed}`);
1997
+ }
1998
+ blank();
1999
+ }
2000
+ if (res.per_agent.length) {
2001
+ head("Per-agent impact");
2002
+ dim(` ${"AGENT".padEnd(24)}${"EVALUATED".padEnd(11)}${"CHANGED".padEnd(9)}${"NEW-BLK".padEnd(9)}NEW-ALW`);
2003
+ for (const a of res.per_agent) {
2004
+ row(` ${truncate(a.agent || "-", 22).padEnd(24)}${String(a.evaluated).padEnd(11)}${String(a.changed).padEnd(9)}${String(a.newly_blocked).padEnd(9)}${a.newly_allowed}`);
2005
+ }
2006
+ blank();
2007
+ }
2008
+ if (res.per_tool.length) {
2009
+ head("Per-tool breakdown");
2010
+ dim(` ${"TOOL".padEnd(24)}${"EVALUATED".padEnd(11)}${"CHANGED".padEnd(9)}${"NEW-BLK".padEnd(9)}NEW-ALW`);
2011
+ for (const t of res.per_tool) {
2012
+ row(` ${truncate(t.tool || "-", 22).padEnd(24)}${String(t.evaluated).padEnd(11)}${String(t.changed).padEnd(9)}${String(t.newly_blocked).padEnd(9)}${t.newly_allowed}`);
2013
+ }
2014
+ blank();
2015
+ }
2016
+ if (res.samples.length) {
2017
+ head(`Changed calls (${res.samples.length})`);
2018
+ for (const c2 of res.samples) {
2019
+ const flip = `${c2.original}->${c2.predicted}`;
2020
+ const newlyAllowed = c2.predicted === "ALLOW";
2021
+ lines.push(
2022
+ /* @__PURE__ */ jsxs3(Text3, { wrap: "truncate", children: [
2023
+ " ",
2024
+ /* @__PURE__ */ jsx3(Text3, { color: theme.accent, children: truncate(c2.tool, 16).padEnd(17) }),
2025
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: truncate(c2.agent || "-", 14).padEnd(15) }),
2026
+ /* @__PURE__ */ jsx3(Text3, { color: newlyAllowed ? theme.ok : theme.bad, children: flip.padEnd(14) }),
2027
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: truncate(c2.preview.replace(/\s+/g, " "), Math.max(10, w - 50)) })
2028
+ ] }, `c${lines.length}`)
2029
+ );
2030
+ }
2031
+ }
2032
+ }
2033
+ const headerRows = 3;
2034
+ const budget = Math.max(3, rows - headerRows);
2035
+ const maxOff = Math.max(0, lines.length - budget);
2036
+ const start = Math.min(off, maxOff);
2037
+ const win = lines.slice(start, start + budget);
2038
+ const above = start;
2039
+ const below = Math.max(0, lines.length - (start + budget));
2040
+ const sampleLabel = `${SAMPLES[si]}${res?.sampled != null ? ` (ran ${res.sampled})` : ""}`;
2041
+ return /* @__PURE__ */ jsx3(DataView, { loading: list5.loading && !list5.data, error: list5.error, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2042
+ /* @__PURE__ */ jsxs3(Box3, { children: [
2043
+ /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Policy Dry Run" }),
2044
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2045
+ " ",
2046
+ truncate(selected?.name ?? "no policy", 26)
2047
+ ] }),
2048
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2049
+ " mode: ",
2050
+ selected?.mode ?? "-"
2051
+ ] }),
2052
+ /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2053
+ " sample: ",
2054
+ sampleLabel
2055
+ ] })
2056
+ ] }),
2057
+ /* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n sample \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
2058
+ running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
2059
+ err ? /* @__PURE__ */ jsx3(Text3, { color: theme.bad, wrap: "truncate", children: "\u2717 " + err }) : null,
2060
+ !running && !err && !res ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "no result yet \u2014 press r to run" }) : null,
2061
+ /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: win })
2062
+ ] }) });
2063
+ }
2064
+
2065
+ // src/tui/panels/Policies.tsx
2066
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1907
2067
  var constr = (r, g, side) => (r[g]?.[side] ?? []).join(", ");
1908
2068
  var setConstr = (r, g, side, val) => {
1909
2069
  const arr = val.split(",").map((s) => s.trim()).filter(Boolean);
@@ -1941,19 +2101,19 @@ function PoliciesPanel({ focused }) {
1941
2101
  const activeId = activeQ.data?.policy?.id ?? null;
1942
2102
  const activeBy = activeQ.data?.matched_by;
1943
2103
  const { rows: panelRows } = usePanelSize();
1944
- const [view, setView] = useState3("list");
1945
- const [pi, setPi] = useState3(0);
1946
- const [ri, setRi] = useState3(0);
1947
- const [fi, setFi] = useState3(0);
1948
- const [rules, setRules] = useState3([]);
1949
- const [mode, setMode] = useState3("denylist");
1950
- const [dirty, setDirty] = useState3(false);
1951
- const [editing, setEditing] = useState3(false);
1952
- const [editVal, setEditVal] = useState3("");
1953
- const [status, setStatus] = useState3(null);
2104
+ const [view, setView] = useState4("list");
2105
+ const [pi, setPi] = useState4(0);
2106
+ const [ri, setRi] = useState4(0);
2107
+ const [fi, setFi] = useState4(0);
2108
+ const [rules, setRules] = useState4([]);
2109
+ const [mode, setMode] = useState4("denylist");
2110
+ const [dirty, setDirty] = useState4(false);
2111
+ const [editing, setEditing] = useState4(false);
2112
+ const [editVal, setEditVal] = useState4("");
2113
+ const [status, setStatus] = useState4(null);
1954
2114
  const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
1955
2115
  const detail = useLoader(() => selected ? api.policies.get(selected.id) : Promise.resolve(null), [selected?.id]);
1956
- useEffect3(() => {
2116
+ useEffect4(() => {
1957
2117
  if (detail.data) {
1958
2118
  setRules(detail.data.rules);
1959
2119
  setMode(detail.data.mode ?? "denylist");
@@ -1999,7 +2159,7 @@ function PoliciesPanel({ focused }) {
1999
2159
  setDirty(false);
2000
2160
  setStatus("discarded");
2001
2161
  };
2002
- useInput2(
2162
+ useInput3(
2003
2163
  (input, key) => {
2004
2164
  if (key.ctrl && input === "r" && !editing) {
2005
2165
  list5.reload();
@@ -2032,13 +2192,6 @@ function PoliciesPanel({ focused }) {
2032
2192
  return;
2033
2193
  }
2034
2194
  if (view === "rules") {
2035
- if (key.ctrl && input === "d") {
2036
- if (selected) {
2037
- openBrowser(`${DASHBOARD_URL}/playground?policy=${encodeURIComponent(selected.id)}`);
2038
- setStatus("Opened the dry-run playground in your browser.");
2039
- }
2040
- return;
2041
- }
2042
2195
  if (key.leftArrow) return setView("list"), void setStatus(null);
2043
2196
  if (key.upArrow) setRi((n) => Math.max(0, n - 1));
2044
2197
  else if (key.downArrow) setRi((n) => Math.min(rules.length - 1, n + 1));
@@ -2066,13 +2219,10 @@ function PoliciesPanel({ focused }) {
2066
2219
  setFi(0);
2067
2220
  setView("rule");
2068
2221
  } else if (input === "D") {
2069
- setStatus("Dry-running\u2026");
2070
- void api.policies.dryRun({ rules, mode }).then((res) => setStatus(
2071
- `dry-run: replayed ${res.evaluated} recent calls against ${rules.length} rules
2072
- would allow ${res.would_allow} would deny ${res.would_deny}
2073
- newly allowed ${res.newly_allowed} newly blocked ${res.newly_blocked} unchanged ${res.unchanged}` + (res.sample_newly_blocked?.length ? `
2074
- now blocked e.g.: ${res.sample_newly_blocked.slice(0, 3).map((s) => truncate(s.tool, 24)).join(", ")}` : "")
2075
- )).catch((e) => setStatus("\u2717 " + (e instanceof Error ? e.message : String(e))));
2222
+ if (selected) {
2223
+ requestDryRun(selected.id);
2224
+ setStatus('Sent to Dry Run - open the "Dry Run" section in the left nav for the full report.');
2225
+ }
2076
2226
  } else if (input === "s") void save();
2077
2227
  else if (input === "x") discard();
2078
2228
  return;
@@ -2105,35 +2255,35 @@ function PoliciesPanel({ focused }) {
2105
2255
  const lWin = policies.slice(lStart, lStart + lBudget);
2106
2256
  const lAbove = lStart;
2107
2257
  const lBelow = Math.max(0, policies.length - (lStart + lBudget));
2108
- return /* @__PURE__ */ jsx3(DataView, { loading: list5.loading && !list5.data, error: list5.error, empty: !!list5.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2109
- /* @__PURE__ */ jsxs3(Box3, { children: [
2110
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "active: " }),
2111
- activeQ.loading && !activeQ.data ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2026" }) : activeName ? /* @__PURE__ */ jsxs3(Fragment3, { children: [
2112
- /* @__PURE__ */ jsxs3(Text3, { color: theme.ok, bold: true, children: [
2258
+ return /* @__PURE__ */ jsx4(DataView, { loading: list5.loading && !list5.data, error: list5.error, empty: !!list5.data && policies.length === 0, emptyText: "No policies.", children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2259
+ /* @__PURE__ */ jsxs4(Box4, { children: [
2260
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "active: " }),
2261
+ activeQ.loading && !activeQ.data ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2026" }) : activeName ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
2262
+ /* @__PURE__ */ jsxs4(Text4, { color: theme.ok, bold: true, children: [
2113
2263
  "\u25CF ",
2114
2264
  truncate(activeName, 28)
2115
2265
  ] }),
2116
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
2117
- ] }) : /* @__PURE__ */ jsx3(Text3, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
2266
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: activeBy ? ` (${activeBy})` : "" })
2267
+ ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.bad, bold: true, children: "\u25CB NONE \u2014 nothing enforced, grants refused" })
2118
2268
  ] }),
2119
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
2120
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
2269
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 select \xB7 enter open \xB7 a activate \xB7 x deactivate \xB7 ^R refresh${lAbove ? ` \xB7 \u25B2${lAbove}` : ""}${lBelow ? ` \xB7 \u25BC${lBelow}` : ""}` : "press \u2192 to browse" }),
2270
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: lWin.map((p, wi) => {
2121
2271
  const i = lStart + wi;
2122
- return /* @__PURE__ */ jsxs3(Text3, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
2272
+ return /* @__PURE__ */ jsxs4(Text4, { color: i === pi ? theme.accentBright : void 0, bold: i === pi, children: [
2123
2273
  (i === pi ? "\u25B8 " : " ") + truncate(p.name, 26).padEnd(27),
2124
- /* @__PURE__ */ jsxs3(Text3, { color: theme.dim, children: [
2274
+ /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2125
2275
  p.mode.padEnd(10),
2126
2276
  " ",
2127
2277
  p.rules.length,
2128
2278
  " rules"
2129
2279
  ] }),
2130
- p.id === activeId ? /* @__PURE__ */ jsx3(Text3, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
2280
+ p.id === activeId ? /* @__PURE__ */ jsx4(Text4, { color: theme.ok, bold: true, children: " \u25CF ACTIVE" }) : null
2131
2281
  ] }, p.id);
2132
2282
  }) }),
2133
- status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2283
+ status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2134
2284
  ] }) });
2135
2285
  }
2136
- const dirtyTag = dirty ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
2286
+ const dirtyTag = dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null;
2137
2287
  if (view === "rules") {
2138
2288
  const rStatusLines = status ? status.split("\n").length : 0;
2139
2289
  const rHead = 4 + rStatusLines;
@@ -2143,16 +2293,16 @@ function PoliciesPanel({ focused }) {
2143
2293
  const rWin = rules.slice(rStart, rStart + rBudget);
2144
2294
  const rAbove = rStart;
2145
2295
  const rBelow = Math.max(0, rules.length - (rStart + rBudget));
2146
- return /* @__PURE__ */ jsx3(DataView, { loading: detail.loading && !detail.data, error: detail.error, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2147
- /* @__PURE__ */ jsxs3(Box3, { children: [
2148
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
2149
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: " mode: " }),
2150
- /* @__PURE__ */ jsx3(Text3, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
2151
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: ` ${rules.length} rules` }),
2296
+ return /* @__PURE__ */ jsx4(DataView, { loading: detail.loading && !detail.data, error: detail.error, children: /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2297
+ /* @__PURE__ */ jsxs4(Box4, { children: [
2298
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accentBright, children: truncate(selected?.name ?? "", 28) }),
2299
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " mode: " }),
2300
+ /* @__PURE__ */ jsx4(Text4, { color: mode === "whitelist" ? theme.warn : void 0, children: mode }),
2301
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` ${rules.length} rules` }),
2152
2302
  dirtyTag
2153
2303
  ] }),
2154
- /* @__PURE__ */ jsx3(Text3, { 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 ^D playground \xB7 s save \xB7 \u2190 back${rAbove ? ` \xB7 \u25B2${rAbove}` : ""}${rBelow ? ` \xB7 \u25BC${rBelow}` : ""}` }),
2155
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(
2304
+ /* @__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}` : ""}` }),
2305
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(
2156
2306
  Table,
2157
2307
  {
2158
2308
  columns: [
@@ -2178,24 +2328,24 @@ function PoliciesPanel({ focused }) {
2178
2328
  })
2179
2329
  }
2180
2330
  ) }),
2181
- rules.length === 0 ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "(no rules)" }) : null,
2182
- status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2331
+ rules.length === 0 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "(no rules)" }) : null,
2332
+ status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2183
2333
  ] }) });
2184
2334
  }
2185
2335
  const rule = rules[ri];
2186
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2187
- /* @__PURE__ */ jsxs3(Box3, { children: [
2188
- /* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: "Rule " }),
2189
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: rule?.id ?? "" }),
2336
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2337
+ /* @__PURE__ */ jsxs4(Box4, { children: [
2338
+ /* @__PURE__ */ jsx4(Text4, { bold: true, color: theme.accentBright, children: "Rule " }),
2339
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: rule?.id ?? "" }),
2190
2340
  dirtyTag
2191
2341
  ] }),
2192
- /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit text \xB7 space/\u2192 toggle \xB7 s save \xB7 \u2190 back" }),
2193
- /* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
2342
+ /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "\u2191\u2193 field \xB7 enter edit text \xB7 space/\u2192 toggle \xB7 s save \xB7 \u2190 back" }),
2343
+ /* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", children: FIELDS.map((f, i) => {
2194
2344
  const val = rule ? f.get(rule) : "";
2195
2345
  const active2 = i === fi;
2196
- return /* @__PURE__ */ jsxs3(Box3, { children: [
2197
- /* @__PURE__ */ jsx3(Text3, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
2198
- active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx3(
2346
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
2347
+ /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + f.label.padEnd(13) }),
2348
+ active2 && editing && f.kind === "text" ? /* @__PURE__ */ jsx4(
2199
2349
  TextInput2,
2200
2350
  {
2201
2351
  value: editVal,
@@ -2205,8 +2355,8 @@ function PoliciesPanel({ focused }) {
2205
2355
  setEditing(false);
2206
2356
  }
2207
2357
  }
2208
- ) : /* @__PURE__ */ jsx3(
2209
- Text3,
2358
+ ) : /* @__PURE__ */ jsx4(
2359
+ Text4,
2210
2360
  {
2211
2361
  color: f.kind === "effect" ? val === "ALLOW" ? theme.ok : theme.bad : void 0,
2212
2362
  dimColor: !val,
@@ -2215,14 +2365,14 @@ function PoliciesPanel({ focused }) {
2215
2365
  )
2216
2366
  ] }, f.label);
2217
2367
  }) }),
2218
- status ? /* @__PURE__ */ jsx3(Text3, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2368
+ status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: status }) : null
2219
2369
  ] });
2220
2370
  }
2221
2371
 
2222
2372
  // src/tui/panels/RateLimit.tsx
2223
- import { Box as Box4, Text as Text4, useInput as useInput3 } from "ink";
2224
- import { useEffect as useEffect4, useState as useState4 } from "react";
2225
- import { Fragment as Fragment4, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2373
+ import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
2374
+ import { useEffect as useEffect5, useState as useState5 } from "react";
2375
+ import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2226
2376
  var MODES = ["off", "detect", "block"];
2227
2377
  var FIELDS2 = ["mode", "perMinute", "perHour", "perDay"];
2228
2378
  var when = (ms) => {
@@ -2234,11 +2384,11 @@ function RateLimitPanel({ focused }) {
2234
2384
  const layersQ = useLoader(() => api.settings.getSecurityLayers());
2235
2385
  const historyQ = useLoader(() => api.settings.getRateLimitHistory());
2236
2386
  const insightsQ = useLoader(() => api.stats.securityInsights(7));
2237
- const [draft, setDraft] = useState4(null);
2238
- const [dirty, setDirty] = useState4(false);
2239
- const [sel, setSel] = useState4(0);
2240
- const [status, setStatus] = useState4(null);
2241
- useEffect4(() => {
2387
+ const [draft, setDraft] = useState5(null);
2388
+ const [dirty, setDirty] = useState5(false);
2389
+ const [sel, setSel] = useState5(0);
2390
+ const [status, setStatus] = useState5(null);
2391
+ useEffect5(() => {
2242
2392
  if (layersQ.data && !dirty) setDraft({ ...layersQ.data.layers.rateLimit });
2243
2393
  }, [layersQ.data]);
2244
2394
  usePoll(() => {
@@ -2286,7 +2436,7 @@ function RateLimitPanel({ focused }) {
2286
2436
  const fi = onField ? selC : -1;
2287
2437
  const bcur = onField ? -1 : selC - 4;
2288
2438
  const off = bcur < 0 ? 0 : Math.min(Math.max(0, bcur - Math.floor(burstBudget / 2)), maxOff);
2289
- useInput3(
2439
+ useInput4(
2290
2440
  (input, key) => {
2291
2441
  const step = key.shift ? 10 : 1;
2292
2442
  if (key.upArrow) setSel((n) => Math.max(0, n - 1));
@@ -2305,48 +2455,48 @@ function RateLimitPanel({ focused }) {
2305
2455
  },
2306
2456
  { isActive: focused }
2307
2457
  );
2308
- return /* @__PURE__ */ jsx4(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? (() => {
2458
+ return /* @__PURE__ */ jsx5(DataView, { loading: layersQ.loading && !draft, error: layersQ.error, children: draft ? (() => {
2309
2459
  const lim = draft.perMinute;
2310
2460
  const now = insightsQ.data ? insightsQ.data["activity"]?.minute?.slice(-1)[0]?.count ?? 0 : 0;
2311
2461
  const loadPct = lim > 0 ? Math.min(100, Math.round(now / lim * 100)) : 0;
2312
2462
  const last24h = (insightsQ.data?.["activity"]?.hour ?? []).reduce((s, b) => s + b.count, 0);
2313
2463
  const shown = anomalies2.slice(off, off + burstBudget);
2314
2464
  const bw = Math.max(10, cols - 30);
2315
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", height: rows, overflow: "hidden", children: [
2316
- /* @__PURE__ */ jsx4(Text4, { 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" }),
2317
- /* @__PURE__ */ jsxs4(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
2318
- /* @__PURE__ */ jsx4(Text4, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
2319
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
2465
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", height: rows, overflow: "hidden", children: [
2466
+ /* @__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" }),
2467
+ /* @__PURE__ */ jsxs5(FieldRow, { label: "Mode", active: focused && fi === 0, children: [
2468
+ /* @__PURE__ */ jsx5(Text5, { color: modeColor(draft.mode), bold: true, children: draft.mode }),
2469
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: draft.mode === "off" ? " no limit" : draft.mode === "detect" ? " flag bursts, never block" : " DENY calls over the limit" })
2320
2470
  ] }),
2321
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perMinute || "off" }) }),
2322
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perHour || "off" }) }),
2323
- /* @__PURE__ */ jsx4(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: draft.perDay || "off" }) }),
2324
- showExtras && /* @__PURE__ */ jsxs4(Fragment4, { children: [
2325
- hasLoad ? /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2326
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "load now".padEnd(13) }),
2327
- /* @__PURE__ */ jsx4(Text4, { color: loadPct >= 100 ? theme.bad : loadPct >= 80 ? theme.warn : theme.ok, children: "\u2588".repeat(Math.min(18, Math.round(now / lim * 18))) }),
2328
- /* @__PURE__ */ jsx4(Text4, { color: "#233457", children: "\u2591".repeat(Math.max(0, 18 - Math.round(now / lim * 18))) }),
2329
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` ${now}/${lim} this minute (${loadPct}%)` })
2471
+ /* @__PURE__ */ jsx5(FieldRow, { label: "Per minute", active: focused && fi === 1, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: draft.perMinute || "off" }) }),
2472
+ /* @__PURE__ */ jsx5(FieldRow, { label: "Per hour", active: focused && fi === 2, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: draft.perHour || "off" }) }),
2473
+ /* @__PURE__ */ jsx5(FieldRow, { label: "Per day", active: focused && fi === 3, children: /* @__PURE__ */ jsx5(Text5, { bold: true, children: draft.perDay || "off" }) }),
2474
+ showExtras && /* @__PURE__ */ jsxs5(Fragment4, { children: [
2475
+ hasLoad ? /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2476
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "load now".padEnd(13) }),
2477
+ /* @__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))) }),
2478
+ /* @__PURE__ */ jsx5(Text5, { color: "#233457", children: "\u2591".repeat(Math.max(0, 18 - Math.round(now / lim * 18))) }),
2479
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: ` ${now}/${lim} this minute (${loadPct}%)` })
2330
2480
  ] }) : null,
2331
- /* @__PURE__ */ jsxs4(Text4, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
2481
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
2332
2482
  "Busiest 7d",
2333
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " most calls in one window vs your limit" })
2483
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " most calls in one window vs your limit" })
2334
2484
  ] }),
2335
- /* @__PURE__ */ jsx4(BusiestRow, { label: "minute", peak: peaks.minute, limit: draft.perMinute }),
2336
- /* @__PURE__ */ jsx4(BusiestRow, { label: "hour", peak: peaks.hour, limit: draft.perHour }),
2337
- /* @__PURE__ */ jsx4(BusiestRow, { label: "day", peak: peaks.day, limit: draft.perDay }),
2338
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, wrap: "truncate", children: `activity`.padEnd(11) + ` last 24h \xB7 ${last24h} calls \xB7 busiest hour ${peaks.hour}/hr` })
2485
+ /* @__PURE__ */ jsx5(BusiestRow, { label: "minute", peak: peaks.minute, limit: draft.perMinute }),
2486
+ /* @__PURE__ */ jsx5(BusiestRow, { label: "hour", peak: peaks.hour, limit: draft.perHour }),
2487
+ /* @__PURE__ */ jsx5(BusiestRow, { label: "day", peak: peaks.day, limit: draft.perDay }),
2488
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: `activity`.padEnd(11) + ` last 24h \xB7 ${last24h} calls \xB7 busiest hour ${peaks.hour}/hr` })
2339
2489
  ] }),
2340
- /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
2341
- /* @__PURE__ */ jsxs4(Text4, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
2490
+ /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2491
+ /* @__PURE__ */ jsxs5(Text5, { color: theme.accentBright, bold: true, wrap: "truncate", children: [
2342
2492
  `Recent bursts 7d${anomalies2.length ? ` (${anomalies2.length})` : ""}`,
2343
- maxOff > 0 ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: ` ${off + 1}-${Math.min(off + burstBudget, anomalies2.length)}/${anomalies2.length}` }) : null,
2344
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: " over the per-minute limit \xB7 Bypassed = detect (not blocked)" })
2493
+ maxOff > 0 ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: ` ${off + 1}-${Math.min(off + burstBudget, anomalies2.length)}/${anomalies2.length}` }) : null,
2494
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " over the per-minute limit \xB7 Bypassed = detect (not blocked)" })
2345
2495
  ] }),
2346
- anomalies2.length === 0 ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
2496
+ anomalies2.length === 0 ? /* @__PURE__ */ jsxs5(Text5, { color: theme.dim, children: [
2347
2497
  " ",
2348
2498
  insightsQ.loading ? "loading\u2026" : insightsQ.error ? `couldn't load bursts: ${String(insightsQ.error).slice(0, 60)}` : "no bursts in the last 7 days"
2349
- ] }) : /* @__PURE__ */ jsx4(
2499
+ ] }) : /* @__PURE__ */ jsx5(
2350
2500
  Table,
2351
2501
  {
2352
2502
  columns: [{ header: "WHEN", width: 15 }, { header: "RESULT", width: 9 }, { header: "CALLS", width: 7 }, { header: "AGENT", width: Math.max(8, bw - 31) }],
@@ -2362,13 +2512,13 @@ function RateLimitPanel({ focused }) {
2362
2512
  }
2363
2513
  )
2364
2514
  ] }),
2365
- /* @__PURE__ */ jsx4(Text4, { children: " " }),
2366
- /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2367
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: "limit now " }),
2368
- /* @__PURE__ */ jsx4(Text4, { children: lim > 0 ? `${lim}/min` : "no per-minute limit" }),
2369
- lastChange ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: ` \xB7 changed ${when(lastChange.ts)}` }) : null,
2370
- dirty ? /* @__PURE__ */ jsx4(Text4, { color: theme.warn, children: " \u25CF unsaved (s)" }) : null,
2371
- status ? /* @__PURE__ */ jsx4(Text4, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: " " + status }) : null
2515
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
2516
+ /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2517
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: "limit now " }),
2518
+ /* @__PURE__ */ jsx5(Text5, { children: lim > 0 ? `${lim}/min` : "no per-minute limit" }),
2519
+ lastChange ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: ` \xB7 changed ${when(lastChange.ts)}` }) : null,
2520
+ dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s)" }) : null,
2521
+ status ? /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, children: " " + status }) : null
2372
2522
  ] })
2373
2523
  ] });
2374
2524
  })() : null });
@@ -2376,39 +2526,39 @@ function RateLimitPanel({ focused }) {
2376
2526
  function BusiestRow({ label, peak, limit }) {
2377
2527
  const ctx = limit > 0 ? peak > limit ? `over ${limit}` : peak === limit ? `at limit ${limit}` : `limit ${limit}` : "no limit set";
2378
2528
  const hot = limit > 0 && peak > limit;
2379
- return /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2380
- /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: (" " + label).padEnd(11) }),
2381
- /* @__PURE__ */ jsx4(Text4, { color: hot ? theme.bad : void 0, bold: hot, children: String(peak).padStart(4) + " calls" }),
2382
- /* @__PURE__ */ jsx4(Text4, { color: hot ? theme.bad : theme.dim, children: " \xB7 " + ctx })
2529
+ return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2530
+ /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: (" " + label).padEnd(11) }),
2531
+ /* @__PURE__ */ jsx5(Text5, { color: hot ? theme.bad : void 0, bold: hot, children: String(peak).padStart(4) + " calls" }),
2532
+ /* @__PURE__ */ jsx5(Text5, { color: hot ? theme.bad : theme.dim, children: " \xB7 " + ctx })
2383
2533
  ] });
2384
2534
  }
2385
2535
  function FieldRow({ label, active: active2, children }) {
2386
- return /* @__PURE__ */ jsxs4(Text4, { wrap: "truncate", children: [
2387
- /* @__PURE__ */ jsx4(Text4, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(11) }),
2536
+ return /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2537
+ /* @__PURE__ */ jsx5(Text5, { color: active2 ? theme.accentBright : void 0, children: (active2 ? "\u25B8 " : " ") + label.padEnd(11) }),
2388
2538
  children
2389
2539
  ] });
2390
2540
  }
2391
2541
 
2392
2542
  // src/tui/panels/Dlp.tsx
2393
- import { Box as Box5, Text as Text5, useInput as useInput4 } from "ink";
2543
+ import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2394
2544
  import TextInput3 from "ink-text-input";
2395
- import { useEffect as useEffect5, useState as useState5 } from "react";
2396
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2545
+ import { useEffect as useEffect6, useState as useState6 } from "react";
2546
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2397
2547
  var MODES2 = ["off", "detect", "block"];
2398
2548
  function DlpPanel({ focused }) {
2399
2549
  const { cols, rows } = usePanelSize();
2400
2550
  const q = useLoader(() => api.settings.getSecurityLayers());
2401
- const [dlp, setDlp] = useState5(null);
2402
- const [available, setAvailable] = useState5([]);
2403
- const [ghostOn, setGhostOn] = useState5(false);
2404
- const [ghostPats, setGhostPats] = useState5([]);
2405
- const [sel, setSel] = useState5(0);
2406
- const [dirty, setDirty] = useState5(false);
2407
- const [status, setStatus] = useState5(null);
2408
- const [adding, setAdding] = useState5(null);
2409
- const [newName, setNewName] = useState5("");
2410
- const [input, setInput] = useState5("");
2411
- useEffect5(() => {
2551
+ const [dlp, setDlp] = useState6(null);
2552
+ const [available, setAvailable] = useState6([]);
2553
+ const [ghostOn, setGhostOn] = useState6(false);
2554
+ const [ghostPats, setGhostPats] = useState6([]);
2555
+ const [sel, setSel] = useState6(0);
2556
+ const [dirty, setDirty] = useState6(false);
2557
+ const [status, setStatus] = useState6(null);
2558
+ const [adding, setAdding] = useState6(null);
2559
+ const [newName, setNewName] = useState6("");
2560
+ const [input, setInput] = useState6("");
2561
+ useEffect6(() => {
2412
2562
  if (q.data && !dirty) {
2413
2563
  setDlp({ ...q.data.layers.dlp, patterns: [...q.data.layers.dlp.patterns], custom: [...q.data.layers.dlp.custom] });
2414
2564
  setAvailable(q.data.availablePatterns);
@@ -2455,7 +2605,7 @@ function DlpPanel({ focused }) {
2455
2605
  ];
2456
2606
  const selC = Math.min(sel, Math.max(0, entries.length - 1));
2457
2607
  const cur = entries[selC];
2458
- useInput4(
2608
+ useInput5(
2459
2609
  (input2, key) => {
2460
2610
  if (!dlp) return;
2461
2611
  if (key.ctrl && input2 === "r") {
@@ -2500,13 +2650,13 @@ function DlpPanel({ focused }) {
2500
2650
  const enabled = new Set(dlp?.patterns ?? []);
2501
2651
  const lines = [];
2502
2652
  const header = (key, label, extra) => lines.push({
2503
- node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2504
- /* @__PURE__ */ jsx5(Text5, { color: theme.accentBright, bold: true, children: label }),
2505
- extra ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + extra }) : null
2653
+ node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2654
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: label }),
2655
+ extra ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + extra }) : null
2506
2656
  ] }, key),
2507
2657
  entry: -1
2508
2658
  });
2509
- const note = (key, text) => lines.push({ node: /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: text }, key), entry: -1 });
2659
+ const note = (key, text) => lines.push({ node: /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: text }, key), entry: -1 });
2510
2660
  let ei = 0;
2511
2661
  header("h:builtin", "built-in secret patterns", "space toggles on/off");
2512
2662
  available.forEach((p) => {
@@ -2514,10 +2664,10 @@ function DlpPanel({ focused }) {
2514
2664
  const isCur = focused && ei === selC;
2515
2665
  const idx = ei++;
2516
2666
  lines.push({
2517
- node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2518
- /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2519
- /* @__PURE__ */ jsx5(Text5, { color: on ? theme.ok : theme.dim, children: on ? "\u25CF " : "\u25CB " }),
2520
- /* @__PURE__ */ jsx5(Text5, { color: on ? void 0 : theme.dim, children: p })
2667
+ node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2668
+ /* @__PURE__ */ jsx6(Text6, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2669
+ /* @__PURE__ */ jsx6(Text6, { color: on ? theme.ok : theme.dim, children: on ? "\u25CF " : "\u25CB " }),
2670
+ /* @__PURE__ */ jsx6(Text6, { color: on ? void 0 : theme.dim, children: p })
2521
2671
  ] }, "b:" + p),
2522
2672
  entry: idx
2523
2673
  });
@@ -2529,10 +2679,10 @@ function DlpPanel({ focused }) {
2529
2679
  const isCur = focused && ei === selC;
2530
2680
  const idx = ei++;
2531
2681
  lines.push({
2532
- node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2533
- /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2534
- /* @__PURE__ */ jsx5(Text5, { color: theme.accent, children: c2.name }),
2535
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " " + truncate(c2.re, Math.max(10, cols - c2.name.length - 8)) })
2682
+ node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2683
+ /* @__PURE__ */ jsx6(Text6, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2684
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: c2.name }),
2685
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + truncate(c2.re, Math.max(10, cols - c2.name.length - 8)) })
2536
2686
  ] }, "c:" + c2.name + i),
2537
2687
  entry: idx
2538
2688
  });
@@ -2544,10 +2694,10 @@ function DlpPanel({ focused }) {
2544
2694
  const isCur = focused && ei === selC;
2545
2695
  const idx = ei++;
2546
2696
  lines.push({
2547
- node: /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2548
- /* @__PURE__ */ jsx5(Text5, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2549
- /* @__PURE__ */ jsx5(Text5, { color: ghostOn ? theme.accent : theme.dim, children: truncate(p, Math.max(10, cols - 6)) }),
2550
- !ghostOn ? /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: " (ghost off)" }) : null
2697
+ node: /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", backgroundColor: isCur ? "#1c2f63" : void 0, bold: isCur, children: [
2698
+ /* @__PURE__ */ jsx6(Text6, { color: isCur ? theme.accentBright : theme.dim, children: isCur ? "\u25B8 " : " " }),
2699
+ /* @__PURE__ */ jsx6(Text6, { color: ghostOn ? theme.accent : theme.dim, children: truncate(p, Math.max(10, cols - 6)) }),
2700
+ !ghostOn ? /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (ghost off)" }) : null
2551
2701
  ] }, "g:" + p + i),
2552
2702
  entry: idx
2553
2703
  });
@@ -2561,17 +2711,17 @@ function DlpPanel({ focused }) {
2561
2711
  const win = lines.slice(start, start + budget);
2562
2712
  const moreAbove = start;
2563
2713
  const moreBelow = Math.max(0, lines.length - (start + budget));
2564
- return /* @__PURE__ */ jsx5(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
2565
- /* @__PURE__ */ jsxs5(Text5, { wrap: "truncate", children: [
2566
- /* @__PURE__ */ jsx5(Text5, { children: "mode: " }),
2567
- /* @__PURE__ */ jsx5(Text5, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
2568
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
2569
- dirty ? /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
2714
+ return /* @__PURE__ */ jsx6(DataView, { loading: q.loading && !dlp, error: q.error, children: dlp ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2715
+ /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2716
+ /* @__PURE__ */ jsx6(Text6, { children: "mode: " }),
2717
+ /* @__PURE__ */ jsx6(Text6, { color: modeColor(dlp.mode), bold: true, children: dlp.mode }),
2718
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: dlp.mode === "off" ? " (scanning disabled)" : dlp.mode === "detect" ? " (flag dlp:yes, redact output)" : " (deny + redact secrets)" }),
2719
+ dirty ? /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: " \u25CF unsaved (s save \xB7 x discard)" }) : null
2570
2720
  ] }),
2571
- /* @__PURE__ */ jsx5(Text5, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 move \xB7 space on/off \xB7 m mode \xB7 a add pattern \xB7 r add route \xB7 d remove \xB7 g ghost \xB7 s save \xB7 ^R refresh${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to edit" }),
2572
- adding ? /* @__PURE__ */ jsx5(Box5, { flexDirection: "column", children: /* @__PURE__ */ jsxs5(Box5, { children: [
2573
- /* @__PURE__ */ jsx5(Text5, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `pattern for "${newName}" (glob, * = any chars, e.g. sk-* AKIA*): ` : "ghost path to hide (glob, * = any chars, e.g. */.env *.pem): " }),
2574
- /* @__PURE__ */ jsx5(
2721
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 move \xB7 space on/off \xB7 m mode \xB7 a add pattern \xB7 r add route \xB7 d remove \xB7 g ghost \xB7 s save \xB7 ^R refresh${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to edit" }),
2722
+ adding ? /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", children: /* @__PURE__ */ jsxs6(Box6, { children: [
2723
+ /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: adding === "name" ? "new pattern name: " : adding === "re" ? `pattern for "${newName}" (glob, * = any chars, e.g. sk-* AKIA*): ` : "ghost path to hide (glob, * = any chars, e.g. */.env *.pem): " }),
2724
+ /* @__PURE__ */ jsx6(
2575
2725
  TextInput3,
2576
2726
  {
2577
2727
  value: input,
@@ -2598,19 +2748,19 @@ function DlpPanel({ focused }) {
2598
2748
  }
2599
2749
  )
2600
2750
  ] }) }) : null,
2601
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
2602
- status ? /* @__PURE__ */ jsx5(Text5, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
2751
+ /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", height: budget, overflow: "hidden", children: win.map((l) => l.node) }),
2752
+ status ? /* @__PURE__ */ jsx6(Text6, { color: status.startsWith("\u2717") ? theme.bad : theme.ok, wrap: "truncate", children: status }) : null
2603
2753
  ] }) : null });
2604
2754
  }
2605
2755
 
2606
2756
  // src/tui/panels/Audit.tsx
2607
- import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2757
+ import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
2608
2758
  import TextInput4 from "ink-text-input";
2609
2759
  import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2610
2760
  import { homedir as homedir5 } from "os";
2611
2761
  import { join as join5 } from "path";
2612
- import { useState as useState6 } from "react";
2613
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2762
+ import { useState as useState7 } from "react";
2763
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2614
2764
  var DECISIONS = [void 0, "DENY", "ALLOW"];
2615
2765
  var SIGNALS = [void 0, "dlp", "ratelimit"];
2616
2766
  var SESS_STATUS = [void 0, "active", "idle", "ended"];
@@ -2718,25 +2868,25 @@ var STATUS_DOT = {
2718
2868
  };
2719
2869
  function AuditPanel({ active: active2, focused }) {
2720
2870
  const { cols, rows } = usePanelSize();
2721
- const [source, setSource] = useState6("cloud");
2722
- const [view, setView] = useState6("logs");
2723
- const [di, setDi] = useState6(0);
2724
- const [gi, setGi] = useState6(0);
2725
- const [tool, setTool] = useState6("");
2726
- const [agent, setAgent] = useState6("");
2727
- const [search, setSearch] = useState6("");
2728
- const [sessFilter, setSessFilter] = useState6("");
2729
- const [page, setPage] = useState6(0);
2730
- const [sel, setSel] = useState6(0);
2731
- const [detailScroll, setDetailScroll] = useState6(0);
2732
- const [editing, setEditing] = useState6(null);
2733
- const [si, setSi] = useState6(0);
2734
- const [sessSearch, setSessSearch] = useState6("");
2735
- const [sessSel, setSessSel] = useState6(0);
2736
- const [confirm, setConfirm] = useState6(null);
2737
- const [msg, setMsg] = useState6(null);
2738
- const [showHelp, setShowHelp] = useState6(false);
2739
- const [frozen, setFrozen] = useState6(false);
2871
+ const [source, setSource] = useState7("cloud");
2872
+ const [view, setView] = useState7("logs");
2873
+ const [di, setDi] = useState7(0);
2874
+ const [gi, setGi] = useState7(0);
2875
+ const [tool, setTool] = useState7("");
2876
+ const [agent, setAgent] = useState7("");
2877
+ const [search, setSearch] = useState7("");
2878
+ const [sessFilter, setSessFilter] = useState7("");
2879
+ const [page, setPage] = useState7(0);
2880
+ const [sel, setSel] = useState7(0);
2881
+ const [detailScroll, setDetailScroll] = useState7(0);
2882
+ const [editing, setEditing] = useState7(null);
2883
+ const [si, setSi] = useState7(0);
2884
+ const [sessSearch, setSessSearch] = useState7("");
2885
+ const [sessSel, setSessSel] = useState7(0);
2886
+ const [confirm, setConfirm] = useState7(null);
2887
+ const [msg, setMsg] = useState7(null);
2888
+ const [showHelp, setShowHelp] = useState7(false);
2889
+ const [frozen, setFrozen] = useState7(false);
2740
2890
  const toTop = () => setSel(0);
2741
2891
  const statsQ = useLoader(() => source === "cloud" ? api.stats.get() : Promise.resolve(null), [source]);
2742
2892
  usePoll(statsQ.reloadQuiet, 15e3, active2 && source === "cloud" && !frozen);
@@ -2855,7 +3005,7 @@ function AuditPanel({ active: active2, focused }) {
2855
3005
  };
2856
3006
  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" }));
2857
3007
  };
2858
- useInput5(
3008
+ useInput6(
2859
3009
  (input, key) => {
2860
3010
  if (input === " ") {
2861
3011
  setFrozen((f) => !f);
@@ -2989,58 +3139,58 @@ function AuditPanel({ active: active2, focused }) {
2989
3139
  { isActive: focused && !editing }
2990
3140
  );
2991
3141
  const localAll = localQ.data ?? [];
2992
- const strip = source === "cloud" ? /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2993
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
2994
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls \xB7 " }),
2995
- /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
3142
+ const strip = source === "cloud" ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3143
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: statsQ.data ? statsQ.data.total_calls : "\xB7\xB7\xB7\xB7" }),
3144
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " calls \xB7 " }),
3145
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
2996
3146
  statsQ.data?.allowed ?? "\xB7\xB7\xB7",
2997
3147
  " allow"
2998
3148
  ] }),
2999
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
3000
- /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
3149
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
3150
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.bad, children: [
3001
3151
  statsQ.data?.denied ?? "\xB7\xB7",
3002
3152
  " deny"
3003
3153
  ] }),
3004
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies` })
3005
- ] }) : /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
3006
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: localAll.length }),
3007
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " calls in local file \xB7 " }),
3008
- /* @__PURE__ */ jsxs6(Text6, { color: theme.ok, children: [
3154
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${statsQ.data?.active_policies ?? "\xB7"} policies` })
3155
+ ] }) : /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3156
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: localAll.length }),
3157
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " calls in local file \xB7 " }),
3158
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.ok, children: [
3009
3159
  localAll.filter((r) => r.decision === "ALLOW").length,
3010
3160
  " allow"
3011
3161
  ] }),
3012
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 " }),
3013
- /* @__PURE__ */ jsxs6(Text6, { color: theme.bad, children: [
3162
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
3163
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.bad, children: [
3014
3164
  localAll.filter((r) => r.decision !== "ALLOW").length,
3015
3165
  " deny"
3016
3166
  ] }),
3017
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3167
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3018
3168
  " \xB7 ",
3019
3169
  LOCAL_LOG
3020
3170
  ] })
3021
3171
  ] });
3022
- const copyBanner = frozen ? /* @__PURE__ */ jsx6(Text6, { backgroundColor: "#123d1f", color: "#7bd88f", bold: true, wrap: "truncate", children: " \u23F5 COPY MODE \u2014 screen frozen, select & copy freely \xB7 space resume " }) : null;
3023
- const srcChip = /* @__PURE__ */ jsxs6(Text6, { children: [
3024
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "src:" }),
3025
- /* @__PURE__ */ jsx6(Text6, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
3026
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " (s) " })
3172
+ 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;
3173
+ const srcChip = /* @__PURE__ */ jsxs7(Text7, { children: [
3174
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "src:" }),
3175
+ /* @__PURE__ */ jsx7(Text7, { color: source === "local" ? theme.ok : "#4f6db8", bold: true, children: source }),
3176
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " (s) " })
3027
3177
  ] });
3028
3178
  if (showHelp) {
3029
- const colOf = (groups) => /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", width: "50%", paddingRight: 2, children: groups.map(([group, keys]) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", marginBottom: 1, children: [
3030
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: theme.accent, children: group }),
3031
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
3032
- /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, children: (" " + k).padEnd(19) }),
3033
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: desc })
3179
+ 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: [
3180
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accent, children: group }),
3181
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3182
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: (" " + k).padEnd(19) }),
3183
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: desc })
3034
3184
  ] }, k))
3035
3185
  ] }, group)) });
3036
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
3186
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3037
3187
  copyBanner,
3038
- /* @__PURE__ */ jsxs6(Text6, { bold: true, color: theme.accentBright, children: [
3188
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3039
3189
  "AUDIT \u2014 all keys",
3040
3190
  " ",
3041
- /* @__PURE__ */ jsx6(Text6, { 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)" })
3191
+ /* @__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)" })
3042
3192
  ] }),
3043
- /* @__PURE__ */ jsxs6(Box6, { marginTop: 1, children: [
3193
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
3044
3194
  colOf(AUDIT_HELP.slice(0, 1)),
3045
3195
  colOf(AUDIT_HELP.slice(1))
3046
3196
  ] })
@@ -3059,53 +3209,53 @@ function AuditPanel({ active: active2, focused }) {
3059
3209
  const maxScroll = Math.max(0, contentLines.length - bodyRows);
3060
3210
  const off = Math.min(detailScroll, maxScroll);
3061
3211
  const win = contentLines.slice(off, off + bodyRows);
3062
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
3212
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3063
3213
  copyBanner,
3064
- /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
3065
- /* @__PURE__ */ jsx6(Text6, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
3066
- /* @__PURE__ */ jsx6(Text6, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
3067
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: " " + e.tool }),
3068
- /* @__PURE__ */ jsx6(Text6, { color: loc ? theme.ok : "white", children: " " + (loc ? "LOC" : "CLD") }),
3069
- e.dlp.length ? /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: " DLP!" }) : null,
3070
- e.burst ? /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: " BURST" }) : null,
3071
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2190 back" })
3214
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3215
+ /* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
3216
+ /* @__PURE__ */ jsx7(Text7, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
3217
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, bold: true, children: " " + e.tool }),
3218
+ /* @__PURE__ */ jsx7(Text7, { color: loc ? theme.ok : "white", children: " " + (loc ? "LOC" : "CLD") }),
3219
+ e.dlp.length ? /* @__PURE__ */ jsx7(Text7, { color: theme.bad, children: " DLP!" }) : null,
3220
+ e.burst ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: " BURST" }) : null,
3221
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2190 back" })
3072
3222
  ] }),
3073
- /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
3074
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2502 when " }),
3075
- /* @__PURE__ */ jsx6(Text6, { children: new Date(e.at).toLocaleString() }),
3076
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 perm " }),
3077
- /* @__PURE__ */ jsx6(Text6, { children: e.permission || "\u2014" }),
3078
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 trust " }),
3079
- /* @__PURE__ */ jsx6(Text6, { children: e.trust || "\u2014" }),
3080
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 eval " }),
3081
- /* @__PURE__ */ jsx6(Text6, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
3082
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 agent " }),
3083
- /* @__PURE__ */ jsx6(Text6, { children: e.agent ?? "\u2014" }),
3084
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502" })
3223
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3224
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2502 when " }),
3225
+ /* @__PURE__ */ jsx7(Text7, { children: new Date(e.at).toLocaleString() }),
3226
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 perm " }),
3227
+ /* @__PURE__ */ jsx7(Text7, { children: e.permission || "\u2014" }),
3228
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 trust " }),
3229
+ /* @__PURE__ */ jsx7(Text7, { children: e.trust || "\u2014" }),
3230
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 eval " }),
3231
+ /* @__PURE__ */ jsx7(Text7, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
3232
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 agent " }),
3233
+ /* @__PURE__ */ jsx7(Text7, { children: e.agent ?? "\u2014" }),
3234
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
3085
3235
  ] }),
3086
- /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
3087
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2502 session " }),
3088
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: e.session ?? "\u2014" }),
3089
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 rule " }),
3090
- /* @__PURE__ */ jsx6(Text6, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
3091
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502" })
3236
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3237
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2502 session " }),
3238
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: e.session ?? "\u2014" }),
3239
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502 rule " }),
3240
+ /* @__PURE__ */ jsx7(Text7, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
3241
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2502" })
3092
3242
  ] }),
3093
- /* @__PURE__ */ jsx6(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 }),
3094
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
3095
- /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
3096
- /* @__PURE__ */ jsx6(Text6, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
3097
- /* @__PURE__ */ jsx6(Text6, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
3098
- /* @__PURE__ */ jsx6(Text6, { backgroundColor: BG2, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
3243
+ /* @__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 }),
3244
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", children: l || " " }, off + i)) }),
3245
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3246
+ /* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
3247
+ /* @__PURE__ */ jsx7(Text7, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
3248
+ /* @__PURE__ */ jsx7(Text7, { backgroundColor: BG2, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
3099
3249
  ] })
3100
3250
  ] });
3101
3251
  }
3102
- const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
3103
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3252
+ const chip = (label, val, on) => /* @__PURE__ */ jsxs7(Text7, { children: [
3253
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3104
3254
  label,
3105
3255
  ":"
3106
3256
  ] }),
3107
- /* @__PURE__ */ jsx6(Text6, { color: on ? theme.accentBright : theme.dim, children: val }),
3108
- /* @__PURE__ */ jsx6(Text6, { children: " " })
3257
+ /* @__PURE__ */ jsx7(Text7, { color: on ? theme.accentBright : theme.dim, children: val }),
3258
+ /* @__PURE__ */ jsx7(Text7, { children: " " })
3109
3259
  ] });
3110
3260
  if (view === "sessions") {
3111
3261
  const headerRows2 = 5 + (editing ? 1 : 0);
@@ -3113,20 +3263,20 @@ function AuditPanel({ active: active2, focused }) {
3113
3263
  const selC = Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1));
3114
3264
  const start2 = Math.min(Math.max(0, selC - Math.floor((listRows2 - 1) / 2)), Math.max(0, sessionsFiltered.length - listRows2));
3115
3265
  const win = sessionsFiltered.slice(start2, start2 + listRows2);
3116
- return /* @__PURE__ */ jsx6(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
3266
+ return /* @__PURE__ */ jsx7(DataView, { loading: sessLoading && !frozen, error: source === "cloud" ? agentsQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3117
3267
  strip,
3118
3268
  copyBanner,
3119
- /* @__PURE__ */ jsxs6(Box6, { children: [
3269
+ /* @__PURE__ */ jsxs7(Box7, { children: [
3120
3270
  srcChip,
3121
- /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
3122
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 logs (v) " }),
3271
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "SESSIONS" }),
3272
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 logs (v) " }),
3123
3273
  chip("status", SESS_STATUS[si] ?? "all", si !== 0),
3124
3274
  chip("search", sessSearch || "\xB7", !!sessSearch)
3125
3275
  ] }),
3126
- /* @__PURE__ */ jsx6(Text6, { 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" }),
3127
- editing === "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
3128
- /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: "search: " }),
3129
- /* @__PURE__ */ jsx6(
3276
+ /* @__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" }),
3277
+ editing === "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
3278
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "search: " }),
3279
+ /* @__PURE__ */ jsx7(
3130
3280
  TextInput4,
3131
3281
  {
3132
3282
  value: sessSearch,
@@ -3138,8 +3288,8 @@ function AuditPanel({ active: active2, focused }) {
3138
3288
  }
3139
3289
  )
3140
3290
  ] }) : null,
3141
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${sessionsFiltered.length} sessions \xB7 ${selC + 1}/${sessionsFiltered.length}${start2 ? ` \xB7 \u25B2${start2}` : ""}${start2 + listRows2 < sessionsFiltered.length ? ` \xB7 \u25BC${sessionsFiltered.length - start2 - listRows2}` : ""}` }),
3142
- /* @__PURE__ */ jsx6(
3291
+ /* @__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}` : ""}` }),
3292
+ /* @__PURE__ */ jsx7(
3143
3293
  Table,
3144
3294
  {
3145
3295
  columns: [
@@ -3170,7 +3320,7 @@ function AuditPanel({ active: active2, focused }) {
3170
3320
  })
3171
3321
  }
3172
3322
  ),
3173
- sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3323
+ sessionsFiltered.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3174
3324
  "(no sessions",
3175
3325
  source === "local" ? " in the local file" : "",
3176
3326
  ")"
@@ -3183,13 +3333,13 @@ function AuditPanel({ active: active2, focused }) {
3183
3333
  const maxStart = Math.max(0, pageRows.length - listRows);
3184
3334
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
3185
3335
  const windowed = pageRows.slice(start, start + listRows);
3186
- return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
3336
+ return /* @__PURE__ */ jsx7(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3187
3337
  strip,
3188
3338
  copyBanner,
3189
- /* @__PURE__ */ jsxs6(Box6, { children: [
3339
+ /* @__PURE__ */ jsxs7(Box7, { children: [
3190
3340
  srcChip,
3191
- /* @__PURE__ */ jsx6(Text6, { color: theme.accentBright, bold: true, children: "LOGS" }),
3192
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \xB7 sessions (v) " }),
3341
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, children: "LOGS" }),
3342
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 sessions (v) " }),
3193
3343
  chip("dec", DECISIONS[di] ?? "all", di !== 0),
3194
3344
  chip("sig", SIGNALS[gi] ?? "all", gi !== 0),
3195
3345
  chip("tool", tool || "\xB7", !!tool),
@@ -3197,14 +3347,14 @@ function AuditPanel({ active: active2, focused }) {
3197
3347
  chip("search", search || "\xB7", !!search),
3198
3348
  sessFilter ? chip("sess", sessFilter.slice(0, 8), true) : null
3199
3349
  ] }),
3200
- /* @__PURE__ */ jsx6(Text6, { 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" }),
3201
- msg ? /* @__PURE__ */ jsx6(Text6, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : null,
3202
- editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs6(Box6, { children: [
3203
- /* @__PURE__ */ jsxs6(Text6, { color: theme.warn, children: [
3350
+ /* @__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" }),
3351
+ msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : null,
3352
+ editing && editing !== "sess-search" ? /* @__PURE__ */ jsxs7(Box7, { children: [
3353
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
3204
3354
  editing,
3205
3355
  ": "
3206
3356
  ] }),
3207
- /* @__PURE__ */ jsx6(
3357
+ /* @__PURE__ */ jsx7(
3208
3358
  TextInput4,
3209
3359
  {
3210
3360
  value: editing === "tool" ? tool : editing === "agent" ? agent : search,
@@ -3217,9 +3367,9 @@ function AuditPanel({ active: active2, focused }) {
3217
3367
  }
3218
3368
  )
3219
3369
  ] }) : null,
3220
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
3221
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
3222
- pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3370
+ /* @__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` : ""}` }),
3371
+ /* @__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)) }),
3372
+ pageRows.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3223
3373
  "(no entries",
3224
3374
  source === "local" ? " in the local file" : "",
3225
3375
  " \u2014 c clears filters)"
@@ -3228,9 +3378,9 @@ function AuditPanel({ active: active2, focused }) {
3228
3378
  }
3229
3379
 
3230
3380
  // src/tui/panels/Settings.tsx
3231
- import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
3381
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
3232
3382
  import TextInput5 from "ink-text-input";
3233
- import { useEffect as useEffect6, useRef as useRef3, useState as useState7 } from "react";
3383
+ import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
3234
3384
 
3235
3385
  // src/global-install.ts
3236
3386
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync2, mkdirSync as mkdirSync4, rmSync } from "fs";
@@ -3714,7 +3864,7 @@ async function tuiUpdateFlow(onStatus) {
3714
3864
  }
3715
3865
 
3716
3866
  // src/tui/panels/Settings.tsx
3717
- import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3867
+ import { Fragment as Fragment5, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3718
3868
  var EVENTS = ["denials", "allowed", "all"];
3719
3869
  var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3720
3870
  var SIGNALS2 = ["any", "deny", "dlp", "ratelimit"];
@@ -3740,16 +3890,16 @@ function SettingsPanel({
3740
3890
  }) {
3741
3891
  void active2;
3742
3892
  const { cols, rows } = usePanelSize();
3743
- const [sel, setSel] = useState7(0);
3744
- const [editing, setEditing] = useState7(null);
3745
- const [input, setInput] = useState7("");
3746
- const [confirmDel, setConfirmDel] = useState7(null);
3747
- const [msg, setMsg] = useState7(null);
3748
- const [busy, setBusy] = useState7(false);
3749
- const [editor, setEditor] = useState7(null);
3750
- const [latest, setLatest] = useState7(null);
3893
+ const [sel, setSel] = useState8(0);
3894
+ const [editing, setEditing] = useState8(null);
3895
+ const [input, setInput] = useState8("");
3896
+ const [confirmDel, setConfirmDel] = useState8(null);
3897
+ const [msg, setMsg] = useState8(null);
3898
+ const [busy, setBusy] = useState8(false);
3899
+ const [editor, setEditor] = useState8(null);
3900
+ const [latest, setLatest] = useState8(null);
3751
3901
  const ver = currentVersion();
3752
- useEffect6(() => {
3902
+ useEffect7(() => {
3753
3903
  let live2 = true;
3754
3904
  latestVersion().then((v) => {
3755
3905
  if (live2) setLatest(v);
@@ -3759,26 +3909,26 @@ function SettingsPanel({
3759
3909
  live2 = false;
3760
3910
  };
3761
3911
  }, []);
3762
- const [accounts, setAccounts] = useState7(() => listAccounts());
3763
- const [login, setLogin] = useState7(null);
3764
- const [tick, setTick] = useState7(0);
3912
+ const [accounts, setAccounts] = useState8(() => listAccounts());
3913
+ const [login, setLogin] = useState8(null);
3914
+ const [tick, setTick] = useState8(0);
3765
3915
  const abort = useRef3(false);
3766
3916
  const refreshAccounts = () => setAccounts(listAccounts());
3767
- const [guardHere, setGuardHere] = useState7(() => isGuardInstalled());
3768
- const [guardVer, setGuardVer] = useState7(() => installedGuardVersion());
3769
- const [guardOld, setGuardOld] = useState7(() => guardHookOutdated());
3917
+ const [guardHere, setGuardHere] = useState8(() => isGuardInstalled());
3918
+ const [guardVer, setGuardVer] = useState8(() => installedGuardVersion());
3919
+ const [guardOld, setGuardOld] = useState8(() => guardHookOutdated());
3770
3920
  const refreshGuard = () => {
3771
3921
  setGuardHere(isGuardInstalled());
3772
3922
  setGuardVer(installedGuardVersion());
3773
3923
  setGuardOld(guardHookOutdated());
3774
3924
  };
3775
3925
  const locked = accounts.length === 0;
3776
- const [srv, setSrv] = useState7(() => logsServerStatus());
3777
- useEffect6(() => {
3926
+ const [srv, setSrv] = useState8(() => logsServerStatus());
3927
+ useEffect7(() => {
3778
3928
  const t = setInterval(() => setSrv(logsServerStatus()), 3e3);
3779
3929
  return () => clearInterval(t);
3780
3930
  }, []);
3781
- useEffect6(() => {
3931
+ useEffect7(() => {
3782
3932
  if (!login || login.phase === "done" || login.phase === "error") return;
3783
3933
  const t = setInterval(() => setTick((n) => n + 1), 120);
3784
3934
  return () => clearInterval(t);
@@ -3791,7 +3941,7 @@ function SettingsPanel({
3791
3941
  const local = localQ.data;
3792
3942
  const selfProt = selfQ.data;
3793
3943
  const autoInstalledRef = useRef3(false);
3794
- useEffect6(() => {
3944
+ useEffect7(() => {
3795
3945
  if (autoInstalledRef.current) return;
3796
3946
  if (guardHere && guardOld) {
3797
3947
  autoInstalledRef.current = true;
@@ -3803,7 +3953,7 @@ function SettingsPanel({
3803
3953
  }
3804
3954
  }
3805
3955
  }, [guardHere, guardOld]);
3806
- const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
3956
+ const [hidden, setHidden] = useState8(/* @__PURE__ */ new Set());
3807
3957
  const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
3808
3958
  const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
3809
3959
  const hasEmailAlert = alerts.some((r) => alertChannel(r) === "email");
@@ -3984,7 +4134,7 @@ function SettingsPanel({
3984
4134
  setEditor({ ...editor, target: v, field: 1 });
3985
4135
  }
3986
4136
  };
3987
- useInput6(
4137
+ useInput7(
3988
4138
  (inp, key) => {
3989
4139
  if (login && (login.phase === "starting" || login.phase === "waiting")) {
3990
4140
  if (key.escape) {
@@ -4117,156 +4267,156 @@ function SettingsPanel({
4117
4267
  );
4118
4268
  const spin = SPIN2[tick % SPIN2.length];
4119
4269
  if (login && (login.phase === "starting" || login.phase === "waiting")) {
4120
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
4121
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: "Add an account" }),
4122
- login.phase === "starting" ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
4270
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4271
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "Add an account" }),
4272
+ login.phase === "starting" ? /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
4123
4273
  spin,
4124
4274
  " starting device pairing\u2026"
4125
- ] }) : /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
4126
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
4127
- /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
4128
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
4129
- /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
4275
+ ] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
4276
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
4277
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
4278
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
4279
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.warn, children: [
4130
4280
  spin,
4131
4281
  " waiting for authorization\u2026 "
4132
4282
  ] }),
4133
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "esc to cancel" })
4283
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "esc to cancel" })
4134
4284
  ] })
4135
4285
  ] })
4136
4286
  ] });
4137
4287
  }
4138
4288
  if (editor) {
4139
- const fieldRow = (idx, label, value, hint) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4140
- /* @__PURE__ */ jsx7(Text7, { color: editor.field === idx ? theme.accentBright : theme.dim, children: editor.field === idx ? "\u25B8 " : " " }),
4141
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(11) }),
4289
+ const fieldRow = (idx, label, value, hint) => /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4290
+ /* @__PURE__ */ jsx8(Text8, { color: editor.field === idx ? theme.accentBright : theme.dim, children: editor.field === idx ? "\u25B8 " : " " }),
4291
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: label.padEnd(11) }),
4142
4292
  value,
4143
- editor.field === idx && hint ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + hint }) : null
4293
+ editor.field === idx && hint ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " + hint }) : null
4144
4294
  ] });
4145
- return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
4146
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
4295
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4296
+ /* @__PURE__ */ jsxs8(Text8, { bold: true, color: theme.accentBright, children: [
4147
4297
  editor.id ? "Edit" : "New",
4148
4298
  " ",
4149
4299
  editor.channel,
4150
4300
  " alert"
4151
4301
  ] }),
4152
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
4153
- editing === "alert-target" ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
4154
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
4155
- /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4156
- ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
4157
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
4158
- fieldRow(0, "target", editor.target ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(editor.target, cols - 16) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "empty \u2014 press e" }), "press e to edit"),
4159
- fieldRow(1, "signal", /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }), "\u2190\u2192 change"),
4160
- fieldRow(2, "threshold", /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: `\u2265 ${editor.threshold}` }), "\u2190\u2192 \xB15"),
4161
- fieldRow(3, "window", /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: winLabel(editor.windowSeconds) }), "\u2190\u2192 change"),
4162
- editor.id ? fieldRow(4, "status", editor.enabled ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on" }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" }), "\u2190\u2192 turn on/off") : null
4302
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save \xB7 esc cancel" }),
4303
+ editing === "alert-target" ? /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
4304
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
4305
+ /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4306
+ ] }) : msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
4307
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexDirection: "column", children: [
4308
+ 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"),
4309
+ fieldRow(1, "signal", /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }), "\u2190\u2192 change"),
4310
+ fieldRow(2, "threshold", /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: `\u2265 ${editor.threshold}` }), "\u2190\u2192 \xB15"),
4311
+ fieldRow(3, "window", /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: winLabel(editor.windowSeconds) }), "\u2190\u2192 change"),
4312
+ 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
4163
4313
  ] }),
4164
- /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: editor.id && !editor.enabled ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "disabled \u2014 this alert will NOT fire until you set status back to on" }) : /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
4314
+ /* @__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: [
4165
4315
  "fires when ",
4166
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }),
4316
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }),
4167
4317
  " reach ",
4168
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: editor.threshold }),
4318
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: editor.threshold }),
4169
4319
  " within",
4170
4320
  " ",
4171
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: winLabel(editor.windowSeconds) })
4321
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: winLabel(editor.windowSeconds) })
4172
4322
  ] }) })
4173
4323
  ] });
4174
4324
  }
4175
4325
  const loading = !locked && (localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data || guardQ.loading && !guardQ.data || selfQ.loading && !selfQ.data);
4176
4326
  const error = locked ? null : localQ.error ?? whQ.error ?? alertQ.error ?? guardQ.error ?? selfQ.error;
4177
- const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
4327
+ const onOff = (on) => on ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "off" });
4178
4328
  const chanOf = (rule) => [...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
4179
4329
  const isCur = (r) => keyOf(r) === keyOf(cur) && focused;
4180
- const cursor = (r) => /* @__PURE__ */ jsx7(Text7, { color: isCur(r) ? theme.accentBright : theme.dim, children: isCur(r) ? "\u25B8 " : " " });
4330
+ const cursor = (r) => /* @__PURE__ */ jsx8(Text8, { color: isCur(r) ? theme.accentBright : theme.dim, children: isCur(r) ? "\u25B8 " : " " });
4181
4331
  const rowLine = (r) => {
4182
4332
  switch (r.kind) {
4183
4333
  case "acct": {
4184
4334
  const a = r.acc;
4185
4335
  const isView = viewApiKey ? a.apiKey === viewApiKey : accounts[0]?.apiKey === a.apiKey;
4186
4336
  const isActive = isActiveAccount(a.apiKey);
4187
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4337
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4188
4338
  cursor(r),
4189
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(acctLabel(a), 30).padEnd(31) }),
4190
- isView ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
4191
- isActive ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
4192
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: a.project ? truncate(a.project, 20) : "" })
4339
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(acctLabel(a), 30).padEnd(31) }),
4340
+ isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
4341
+ isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
4342
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.project ? truncate(a.project, 20) : "" })
4193
4343
  ] });
4194
4344
  }
4195
4345
  case "acct-add":
4196
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4346
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4197
4347
  cursor(r),
4198
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
4348
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
4199
4349
  ] });
4200
4350
  case "guard":
4201
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4351
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4202
4352
  cursor(r),
4203
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
4204
- !guardHere ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
4205
- /* @__PURE__ */ jsx7(Text7, { color: theme.bad, bold: true, children: "removed" }),
4206
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 " }),
4207
- /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, children: "enter to install" })
4208
- ] }) : /* @__PURE__ */ jsxs7(Fragment5, { children: [
4209
- /* @__PURE__ */ jsx7(Text7, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
4210
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
4211
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \xB7 d remove" })
4353
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "guard".padEnd(11) }),
4354
+ !guardHere ? /* @__PURE__ */ jsxs8(Fragment5, { children: [
4355
+ /* @__PURE__ */ jsx8(Text8, { color: theme.bad, bold: true, children: "removed" }),
4356
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \xB7 " }),
4357
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: "enter to install" })
4358
+ ] }) : /* @__PURE__ */ jsxs8(Fragment5, { children: [
4359
+ /* @__PURE__ */ jsx8(Text8, { color: guardOld ? theme.warn : theme.ok, children: `v${guardVer ?? "?"}` }),
4360
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: guardOld ? " \xB7 update available \xB7 enter update" : " (latest) \xB7 enter reinstall" }),
4361
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \xB7 d remove" })
4212
4362
  ] })
4213
4363
  ] });
4214
4364
  case "self":
4215
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4365
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4216
4366
  cursor(r),
4217
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "self-prot".padEnd(11) }),
4218
- selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2026" }),
4219
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
4367
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "self-prot".padEnd(11) }),
4368
+ selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2026" }),
4369
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
4220
4370
  ] });
4221
4371
  case "ll-enabled":
4222
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4372
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4223
4373
  cursor(r),
4224
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(11) }),
4374
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "enabled ".padEnd(11) }),
4225
4375
  onOff(!!local?.enabled),
4226
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
4376
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " enter toggles" })
4227
4377
  ] });
4228
4378
  case "ll-path":
4229
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4379
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4230
4380
  cursor(r),
4231
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(11) }),
4232
- local?.path ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "not set \u2014 enter to edit" })
4381
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "path ".padEnd(11) }),
4382
+ local?.path ? /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(local.path, cols - 14) }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "not set \u2014 enter to edit" })
4233
4383
  ] });
4234
4384
  case "ll-server":
4235
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4385
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4236
4386
  cursor(r),
4237
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "dashboard".padEnd(11) }),
4238
- srv.running ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: `running 127.0.0.1:${srv.port}` }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "stopped" }),
4239
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: srv.running ? " survives closing the dataroom \xB7 enter stops" : " enter starts the dashboard local-logs link" })
4387
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "dashboard".padEnd(11) }),
4388
+ srv.running ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: `running 127.0.0.1:${srv.port}` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "stopped" }),
4389
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: srv.running ? " survives closing the dataroom \xB7 enter stops" : " enter starts the dashboard local-logs link" })
4240
4390
  ] });
4241
4391
  case "wh":
4242
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4392
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4243
4393
  cursor(r),
4244
4394
  onOff(r.wh.enabled),
4245
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + r.wh.events).padEnd(9) }),
4246
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(r.wh.url, cols - 16) })
4395
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + r.wh.events).padEnd(9) }),
4396
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(r.wh.url, cols - 16) })
4247
4397
  ] });
4248
4398
  case "wh-add":
4249
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4399
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4250
4400
  cursor(r),
4251
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
4401
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add webhook (enter)" })
4252
4402
  ] });
4253
4403
  case "alert":
4254
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4404
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4255
4405
  cursor(r),
4256
4406
  onOff(r.rule.enabled),
4257
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + SIGNAL_LABEL[r.rule.signal]).padEnd(13) }),
4258
- /* @__PURE__ */ jsx7(Text7, { children: `\u2265${r.rule.threshold}/${winLabel(r.rule.windowSeconds)} `.padEnd(11) }),
4259
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(r.rule), cols - 34) })
4407
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: (" " + SIGNAL_LABEL[r.rule.signal]).padEnd(13) }),
4408
+ /* @__PURE__ */ jsx8(Text8, { children: `\u2265${r.rule.threshold}/${winLabel(r.rule.windowSeconds)} `.padEnd(11) }),
4409
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(chanOf(r.rule), cols - 34) })
4260
4410
  ] });
4261
4411
  case "alert-add-email":
4262
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4412
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4263
4413
  cursor(r),
4264
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add email alert (enter)" })
4414
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add email alert (enter)" })
4265
4415
  ] });
4266
4416
  case "alert-add-tg":
4267
- return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4417
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4268
4418
  cursor(r),
4269
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id from @userinfobot)" })
4419
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id from @userinfobot)" })
4270
4420
  ] });
4271
4421
  }
4272
4422
  };
@@ -4285,39 +4435,39 @@ function SettingsPanel({
4285
4435
  const sec = sectionOf(r);
4286
4436
  if (sec !== lastSec) {
4287
4437
  if (lastSec) {
4288
- lineEls.push(/* @__PURE__ */ jsx7(Text7, { children: " " }, "sp:" + sec));
4438
+ lineEls.push(/* @__PURE__ */ jsx8(Text8, { children: " " }, "sp:" + sec));
4289
4439
  lineKey.push("");
4290
4440
  }
4291
4441
  lastSec = sec;
4292
4442
  lineEls.push(
4293
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
4294
- /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: sec }),
4295
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 " + SECTION_DESC[sec] })
4443
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4444
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: sec }),
4445
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 " + SECTION_DESC[sec] })
4296
4446
  ] }, "h:" + sec)
4297
4447
  );
4298
4448
  lineKey.push("");
4299
4449
  if ((sec === "WEBHOOKS" || sec === "ALERTS") && local?.enabled) {
4300
4450
  lineEls.push(
4301
- /* @__PURE__ */ jsx7(Text7, { 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)
4451
+ /* @__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)
4302
4452
  );
4303
4453
  lineKey.push("");
4304
4454
  }
4305
4455
  }
4306
- lineEls.push(/* @__PURE__ */ jsx7(Box7, { children: rowLine(r) }, keyOf(r)));
4456
+ lineEls.push(/* @__PURE__ */ jsx8(Box8, { children: rowLine(r) }, keyOf(r)));
4307
4457
  lineKey.push(keyOf(r));
4308
4458
  });
4309
4459
  if (hasEmailAlert && hasTgAlert) {
4310
4460
  lineEls.push(
4311
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }, "both-note")
4461
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }, "both-note")
4312
4462
  );
4313
4463
  lineKey.push("");
4314
4464
  }
4315
- lineEls.push(/* @__PURE__ */ jsx7(Text7, { children: " " }, "ver-sp"));
4465
+ lineEls.push(/* @__PURE__ */ jsx8(Text8, { children: " " }, "ver-sp"));
4316
4466
  lineKey.push("");
4317
4467
  lineEls.push(
4318
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", color: theme.dim, children: [
4468
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", color: theme.dim, children: [
4319
4469
  `solongate v${ver}`,
4320
- latest ? newerThan(latest, ver) ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: ` \xB7 v${latest} on npm \u2014 auto-updating` }) : /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: " \xB7 latest" }) : null
4470
+ latest ? newerThan(latest, ver) ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \xB7 v${latest} on npm \u2014 auto-updating` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: " \xB7 latest" }) : null
4321
4471
  ] }, "ver")
4322
4472
  );
4323
4473
  lineKey.push("");
@@ -4328,26 +4478,26 @@ function SettingsPanel({
4328
4478
  const win = lineEls.slice(startL, startL + budget);
4329
4479
  const moreAbove = startL;
4330
4480
  const moreBelow = Math.max(0, lineEls.length - (startL + budget));
4331
- return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
4332
- /* @__PURE__ */ jsx7(Text7, { 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" }),
4333
- editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
4334
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
4335
- /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4336
- ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
4337
- /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
4481
+ return /* @__PURE__ */ jsx8(DataView, { loading, error, children: /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4482
+ /* @__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" }),
4483
+ editing ? /* @__PURE__ */ jsxs8(Box8, { children: [
4484
+ /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
4485
+ /* @__PURE__ */ jsx8(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
4486
+ ] }) : 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: " " }),
4487
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
4338
4488
  ] }) });
4339
4489
  }
4340
4490
 
4341
4491
  // src/tui/App.tsx
4342
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
4492
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
4343
4493
  function LiveHint() {
4344
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4345
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
4346
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
4347
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
4348
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
4494
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4495
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
4496
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
4497
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
4498
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
4349
4499
  "press ",
4350
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
4500
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
4351
4501
  " to go live"
4352
4502
  ] }) })
4353
4503
  ] });
@@ -4357,6 +4507,7 @@ var SECTIONS = [
4357
4507
  { label: "Policies", Panel: PoliciesPanel },
4358
4508
  { label: "Rate Limit", Panel: RateLimitPanel },
4359
4509
  { label: "DLP", Panel: DlpPanel },
4510
+ { label: "Dry Run", Panel: DryRunPanel },
4360
4511
  { label: "Audit", Panel: AuditPanel },
4361
4512
  { label: "Settings", Panel: SettingsPanel }
4362
4513
  ];
@@ -4364,27 +4515,27 @@ var BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
4364
4515
  function Banner({ cols }) {
4365
4516
  const wide = cols >= 82;
4366
4517
  if (!wide) {
4367
- return /* @__PURE__ */ jsxs8(Box8, { children: [
4368
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
4369
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
4518
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
4519
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
4520
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
4370
4521
  ] });
4371
4522
  }
4372
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
4373
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
4374
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
4523
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
4524
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
4525
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
4375
4526
  ] });
4376
4527
  }
4377
4528
  var SETTINGS_SECTION = SECTIONS.findIndex((s) => s.label === "Settings");
4378
4529
  function App() {
4379
4530
  const { exit } = useApp();
4380
- const [accounts, setAccounts] = useState8(() => listAccounts());
4381
- const [viewKey, setViewKey] = useState8(() => accounts[0]?.apiKey);
4382
- const [viewNonce, setViewNonce] = useState8(0);
4383
- const [section, setSection] = useState8(accounts.length === 0 ? SETTINGS_SECTION : 0);
4384
- const [focus, setFocus] = useState8("nav");
4385
- const [help, setHelp] = useState8(false);
4386
- const [update2, setUpdate] = useState8({ kind: "idle" });
4387
- useEffect7(() => {
4531
+ const [accounts, setAccounts] = useState9(() => listAccounts());
4532
+ const [viewKey, setViewKey] = useState9(() => accounts[0]?.apiKey);
4533
+ const [viewNonce, setViewNonce] = useState9(0);
4534
+ const [section, setSection] = useState9(accounts.length === 0 ? SETTINGS_SECTION : 0);
4535
+ const [focus, setFocus] = useState9("nav");
4536
+ const [help, setHelp] = useState9(false);
4537
+ const [update2, setUpdate] = useState9({ kind: "idle" });
4538
+ useEffect8(() => {
4388
4539
  let alive = true;
4389
4540
  const run = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
4390
4541
  run();
@@ -4397,7 +4548,7 @@ function App() {
4397
4548
  const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
4398
4549
  const cur = accounts[acctIdx];
4399
4550
  const acctLabel2 = cur ? cur.email || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}` : "not logged in";
4400
- useEffect7(() => {
4551
+ useEffect8(() => {
4401
4552
  if (!cur || cur.email) return;
4402
4553
  let alive = true;
4403
4554
  api.auth.me().then((r) => {
@@ -4433,7 +4584,7 @@ function App() {
4433
4584
  };
4434
4585
  const locked = accounts.length === 0;
4435
4586
  const effectiveSection = locked ? SETTINGS_SECTION : section;
4436
- useInput7((input, key) => {
4587
+ useInput8((input, key) => {
4437
4588
  if (help) {
4438
4589
  setHelp(false);
4439
4590
  return;
@@ -4461,33 +4612,33 @@ function App() {
4461
4612
  });
4462
4613
  const { cols, rows } = useTermSize();
4463
4614
  const current = SECTIONS[effectiveSection];
4464
- if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
4615
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
4465
4616
  if (current.label === "Live" && focus === "panel") {
4466
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
4617
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
4467
4618
  }
4468
4619
  const Panel = current.Panel;
4469
- const panelBody = current.label === "Settings" ? /* @__PURE__ */ jsx8(SettingsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount, onAccountsChanged: syncAccounts }) : /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
4620
+ 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" });
4470
4621
  return (
4471
4622
  // height rows-1 (not rows): once total output reaches stdout.rows ink stops
4472
4623
  // diffing and clearTerminal-repaints every frame, which slides the banner.
4473
4624
  // overflow hidden makes it a hard guarantee even if a panel over-renders.
4474
- /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, paddingTop: 1, overflow: "hidden", children: [
4475
- /* @__PURE__ */ jsx8(Banner, { cols }),
4476
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
4477
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
4478
- /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
4479
- locked ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \xB7 log in from Settings to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Settings to manage)` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \xB7 Settings to add another" }),
4480
- update2.kind === "updating" ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \u2014 restart (q, then solongate) to apply` }) : null
4625
+ /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, paddingTop: 1, overflow: "hidden", children: [
4626
+ /* @__PURE__ */ jsx9(Banner, { cols }),
4627
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
4628
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
4629
+ /* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
4630
+ 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" }),
4631
+ 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 \u2014 restart (q, then solongate) to apply` }) : null
4481
4632
  ] }),
4482
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
4483
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
4633
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
4634
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", flexShrink: 0, width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
4484
4635
  const isCur = i === effectiveSection;
4485
4636
  const disabled = locked && s.label !== "Settings";
4486
- return /* @__PURE__ */ jsx8(Text8, { color: isCur ? theme.accentBright : disabled ? theme.dim : void 0, bold: isCur, dimColor: disabled, children: (isCur ? "\u25B8 " : disabled ? "\u2298 " : " ") + s.label }, s.label);
4637
+ 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);
4487
4638
  }) }),
4488
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
4639
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, minWidth: 0, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
4489
4640
  ] }, viewNonce),
4490
- /* @__PURE__ */ jsx8(Box8, { children: locked ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2192/enter", "open Settings"], ["n", "log in"], ["q", "quit"]] }) : focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
4641
+ /* @__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"]] }) })
4491
4642
  ] })
4492
4643
  );
4493
4644
  }
@@ -4502,21 +4653,21 @@ var HELP = [
4502
4653
  ["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"]]]
4503
4654
  ];
4504
4655
  function HelpOverlay({ cols, rows }) {
4505
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
4506
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
4507
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
4508
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
4509
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
4510
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
4511
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
4656
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
4657
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
4658
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
4659
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
4660
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
4661
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
4662
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
4512
4663
  ] }, k))
4513
4664
  ] }, group)) }),
4514
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
4665
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
4515
4666
  ] });
4516
4667
  }
4517
4668
 
4518
4669
  // src/tui/index.tsx
4519
- import { jsx as jsx9 } from "react/jsx-runtime";
4670
+ import { jsx as jsx10 } from "react/jsx-runtime";
4520
4671
  async function launchTui() {
4521
4672
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
4522
4673
  process.stderr.write(
@@ -4541,7 +4692,7 @@ async function launchTui() {
4541
4692
  console.info = toFile("info");
4542
4693
  console.debug = toFile("debug");
4543
4694
  try {
4544
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}), { patchConsole: false });
4695
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}), { patchConsole: false });
4545
4696
  await waitUntilExit();
4546
4697
  } finally {
4547
4698
  Object.assign(console, saved);