@solongate/proxy 0.81.69 → 0.81.71

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
@@ -5,9 +5,9 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/tui/index.tsx
8
- import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
9
- import { homedir as homedir8 } from "os";
10
- import { join as join8 } from "path";
8
+ import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
9
+ import { homedir as homedir9 } from "os";
10
+ import { join as join9 } from "path";
11
11
  import { render } from "ink";
12
12
 
13
13
  // src/tui/App.tsx
@@ -125,6 +125,43 @@ function ago(ts) {
125
125
 
126
126
  // src/tui/components.tsx
127
127
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
128
+ var hhmmss = (ts) => {
129
+ const d = new Date(ts);
130
+ return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
131
+ };
132
+ function PaneTitle({ label, extra, width }) {
133
+ const tail = extra ? ` ${extra} ` : " ";
134
+ const used = 2 + 1 + label.length + tail.length;
135
+ return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", children: [
136
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: "\u2500 " }),
137
+ /* @__PURE__ */ jsxs(Text, { color: theme.accentBright, bold: true, children: [
138
+ "\u258E",
139
+ label
140
+ ] }),
141
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
142
+ ] });
143
+ }
144
+ function StreamLine({ e, loc, selected }) {
145
+ return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
146
+ /* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
147
+ /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
148
+ "[",
149
+ hhmmss(e.at),
150
+ " "
151
+ ] }),
152
+ /* @__PURE__ */ jsx(Text, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
153
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: "] " }),
154
+ /* @__PURE__ */ jsx(Text, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
155
+ /* @__PURE__ */ jsx(Text, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
156
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: (e.permission ?? "").padEnd(5) }),
157
+ /* @__PURE__ */ jsx(Text, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
158
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
159
+ /* @__PURE__ */ jsx(Text, { color: e.dlp ? theme.bad : theme.dim, bold: e.dlp, children: ("dlp:" + (e.dlp ? "yes" : "no")).padEnd(8) }),
160
+ /* @__PURE__ */ jsx(Text, { color: e.burst ? theme.warn : theme.dim, bold: e.burst, children: ("rl:" + (e.burst ? "yes" : "no")).padEnd(7) }),
161
+ e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx(Text, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
162
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: e.detail })
163
+ ] });
164
+ }
128
165
  function DataView({
129
166
  loading,
130
167
  error,
@@ -322,6 +359,28 @@ function setActiveAccount(creds) {
322
359
  return false;
323
360
  }
324
361
  }
362
+ function clearActiveCredential() {
363
+ try {
364
+ const p = join3(homedir3(), ".solongate", ["cloud", "guard.json"].join("-"));
365
+ if (!existsSync(p)) {
366
+ cached2 = null;
367
+ return true;
368
+ }
369
+ let existing = {};
370
+ try {
371
+ existing = JSON.parse(readFileSync3(p, "utf-8"));
372
+ } catch {
373
+ }
374
+ delete existing.apiKey;
375
+ delete existing.apiUrl;
376
+ writeFileSync2(p, JSON.stringify(existing, null, 2));
377
+ cached2 = null;
378
+ viewOverride = null;
379
+ return true;
380
+ } catch {
381
+ return false;
382
+ }
383
+ }
325
384
  var ApiError = class extends Error {
326
385
  status;
327
386
  code;
@@ -824,10 +883,6 @@ var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826"
824
883
  var BG = "#12234f";
825
884
  var DIM_FLOOR = "#233457";
826
885
  var RING = join4(process.cwd(), ".solongate", ".eval-ring.jsonl");
827
- var hhmmss = (ts) => {
828
- const d = new Date(ts);
829
- return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
830
- };
831
886
  var fmtUp = (ms) => {
832
887
  const s = Math.floor(ms / 1e3);
833
888
  const p = (n) => String(n).padStart(2, "0");
@@ -899,39 +954,6 @@ function HBar({ label, value, max, width, color }) {
899
954
  /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: " " + value })
900
955
  ] });
901
956
  }
902
- function PaneTitle({ label, extra, width }) {
903
- const tail = extra ? ` ${extra} ` : " ";
904
- const used = 2 + 1 + label.length + tail.length;
905
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
906
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "\u2500 " }),
907
- /* @__PURE__ */ jsxs2(Text2, { color: theme.accentBright, bold: true, children: [
908
- "\u258E",
909
- label
910
- ] }),
911
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
912
- ] });
913
- }
914
- function StreamLine({ e, loc, selected }) {
915
- return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
916
- /* @__PURE__ */ jsx2(Text2, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
917
- /* @__PURE__ */ jsxs2(Text2, { color: theme.dim, children: [
918
- "[",
919
- hhmmss(e.at),
920
- " "
921
- ] }),
922
- /* @__PURE__ */ jsx2(Text2, { color: loc ? theme.ok : "white", children: loc ? "LOC" : "CLD" }),
923
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: "] " }),
924
- /* @__PURE__ */ jsx2(Text2, { color: decisionColor(e.decision), bold: e.decision !== "ALLOW", children: e.decision.padEnd(6) }),
925
- /* @__PURE__ */ jsx2(Text2, { color: theme.accent, children: truncate(e.tool, 12).padEnd(13) }),
926
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.permission.padEnd(5) }),
927
- /* @__PURE__ */ jsx2(Text2, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : theme.dim, children: (e.evalMs != null ? `${e.evalMs}ms` : "\u2014").padEnd(7) }),
928
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: truncate(e.agent ?? "-", 11).padEnd(12) }),
929
- /* @__PURE__ */ jsx2(Text2, { color: e.dlp ? theme.bad : theme.dim, bold: e.dlp, children: ("dlp:" + (e.dlp ? "yes" : "no")).padEnd(8) }),
930
- /* @__PURE__ */ jsx2(Text2, { color: e.burst ? theme.warn : theme.dim, bold: e.burst, children: ("rl:" + (e.burst ? "yes" : "no")).padEnd(7) }),
931
- e.rule && e.decision !== "ALLOW" ? /* @__PURE__ */ jsx2(Text2, { color: theme.bad, children: truncate(e.rule, 14) + " " }) : null,
932
- /* @__PURE__ */ jsx2(Text2, { color: theme.dim, children: e.detail })
933
- ] });
934
- }
935
957
  var FILTERS = ["all", "local", "cloud"];
936
958
  var sessStatus = (lastAt, now) => now - lastAt < 6e4 ? "active" : now - lastAt < 3e5 ? "idle" : "ended";
937
959
  var STATUS_STYLE = {
@@ -2472,12 +2494,25 @@ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2472
2494
  import { homedir as homedir5 } from "os";
2473
2495
  import { join as join5 } from "path";
2474
2496
  import { useState as useState6 } from "react";
2475
- import { Fragment as Fragment4, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2497
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2476
2498
  var DECISIONS = [void 0, "DENY", "ALLOW"];
2477
2499
  var SIGNALS = [void 0, "dlp", "ratelimit"];
2478
2500
  var SESS_STATUS = [void 0, "active", "idle", "ended"];
2479
2501
  var PAGE = 500;
2480
2502
  var LOCAL_MAX_BYTES = 16 * 1024 * 1024;
2503
+ var BG2 = "#12234f";
2504
+ var toStream = (e) => ({
2505
+ at: e.at,
2506
+ tool: e.tool,
2507
+ decision: e.decision,
2508
+ permission: (e.permission ?? "").slice(0, 4),
2509
+ detail: (e.args ?? e.reason ?? "").replace(/\s+/g, " "),
2510
+ dlp: e.dlp.length > 0,
2511
+ burst: e.burst,
2512
+ agent: e.agent,
2513
+ evalMs: e.evalMs,
2514
+ rule: e.rule
2515
+ });
2481
2516
  var cloudRow = (e) => {
2482
2517
  const rs = reasonSignals(e.reason);
2483
2518
  return {
@@ -2557,7 +2592,7 @@ var AUDIT_HELP = [
2557
2592
  ["esc", "back to the menu"]
2558
2593
  ]
2559
2594
  ],
2560
- ["Entry detail", [["\u2191\u2193 / PgUp PgDn", "scroll the arguments"], ["\u2190 / esc", "back to the list"]]]
2595
+ ["Entry (full content)", [["\u2191\u2193 / PgUp PgDn", "scroll the reason + arguments"], ["space", "copy mode (freeze, then select)"], ["\u2190 / esc", "back to the list"]]]
2561
2596
  ];
2562
2597
  var sessStatus2 = (lastAt) => Date.now() - lastAt < 6e4 ? "active" : Date.now() - lastAt < 3e5 ? "idle" : "ended";
2563
2598
  var STATUS_DOT = {
@@ -2662,10 +2697,6 @@ function AuditPanel({ active: active2, focused }) {
2662
2697
  const sq = sessSearch.trim().toLowerCase();
2663
2698
  const sessionsFiltered = sessions.map((s) => ({ ...s, status: source === "cloud" ? s.status : sessStatus2(s.lastAt) })).filter((s) => SESS_STATUS[si] ? s.status === SESS_STATUS[si] : true).filter((s) => !sq || `${s.agent} ${s.id}`.toLowerCase().includes(sq)).sort((a, b) => b.lastAt - a.lastAt);
2664
2699
  const currentSess = sessionsFiltered[Math.min(sessSel, Math.max(0, sessionsFiltered.length - 1))];
2665
- const sessionQ = useLoader(
2666
- () => view === "detail" && current?.session && source === "cloud" ? api.audit.list({ session_id: current.session, limit: 40 }) : Promise.resolve(null),
2667
- [view, current?.session, source]
2668
- );
2669
2700
  const logsLoading = (source === "cloud" ? cloudQ.loading : localQ.loading) && !editing;
2670
2701
  const sessLoading = source === "cloud" ? agentsQ.loading : localQ.loading;
2671
2702
  const doDelete = (kind) => {
@@ -2891,61 +2922,56 @@ function AuditPanel({ active: active2, focused }) {
2891
2922
  ] });
2892
2923
  }
2893
2924
  if (view === "detail" && current) {
2894
- const sessionCalls = source === "cloud" ? (sessionQ.data?.entries ?? []).map(cloudRow) : localAll.filter((r) => r.session && r.session === current.session).slice(0, 40);
2895
- const bodyW = Math.max(20, cols - 2);
2896
- const reasonLines = wrapLines("reason: " + (current.reason ?? "\u2014"), bodyW);
2897
- const argLines = current.args ? wrapLines(prettyJson(current.args), bodyW) : ["(no arguments recorded)"];
2898
- const sessionRows = current.session ? Math.min(4, sessionCalls.length) + 2 : 1;
2899
- const reasonShown = reasonLines.slice(0, 4);
2900
- const argBudget = Math.max(3, rows - 2 - 5 - reasonShown.length - 1 - sessionRows - 1 - (frozen ? 1 : 0));
2901
- const maxScroll = Math.max(0, argLines.length - argBudget);
2925
+ const e = current;
2926
+ const loc = source === "local";
2927
+ const bodyW = Math.max(20, cols - 4);
2928
+ const innerW = cols - 2;
2929
+ const contentLines = [];
2930
+ if (e.reason && e.decision !== "ALLOW") contentLines.push(...wrapLines("reason: " + e.reason, bodyW), "");
2931
+ const argsText = e.args ? prettyJson(e.args) : e.reason && e.decision === "ALLOW" ? e.reason : "(no arguments recorded)";
2932
+ contentLines.push(...wrapLines(argsText, bodyW));
2933
+ const bodyRows = Math.max(4, rows - 7 - (frozen ? 1 : 0));
2934
+ const maxScroll = Math.max(0, contentLines.length - bodyRows);
2902
2935
  const off = Math.min(detailScroll, maxScroll);
2903
- const argWin = argLines.slice(off, off + argBudget);
2936
+ const win = contentLines.slice(off, off + bodyRows);
2904
2937
  return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2905
2938
  copyBanner,
2906
- /* @__PURE__ */ jsxs6(Box6, { children: [
2907
- /* @__PURE__ */ jsx6(Text6, { color: decisionColor(current.decision), bold: true, children: current.decision }),
2908
- /* @__PURE__ */ jsx6(Text6, { color: theme.accent, children: " " + current.tool }),
2909
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " " + current.permission + " \xB7 " + current.trust + " " }),
2910
- srcChip
2939
+ /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2940
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
2941
+ /* @__PURE__ */ jsx6(Text6, { color: decisionColor(e.decision), bold: true, children: " " + e.decision }),
2942
+ /* @__PURE__ */ jsx6(Text6, { color: theme.accent, bold: true, children: " " + e.tool }),
2943
+ /* @__PURE__ */ jsx6(Text6, { color: loc ? theme.ok : "white", children: " " + (loc ? "LOC" : "CLD") }),
2944
+ e.dlp.length ? /* @__PURE__ */ jsx6(Text6, { color: theme.bad, children: " DLP!" }) : null,
2945
+ e.burst ? /* @__PURE__ */ jsx6(Text6, { color: theme.warn, children: " BURST" }) : null,
2946
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2190 back" })
2911
2947
  ] }),
2912
- reasonShown.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: i === 0 ? /* @__PURE__ */ jsxs6(Fragment4, { children: [
2913
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "reason " }),
2914
- /* @__PURE__ */ jsx6(Text6, { children: l.slice("reason: ".length) })
2915
- ] }) : /* @__PURE__ */ jsx6(Text6, { children: " " + l }) }, "r" + i)),
2916
- /* @__PURE__ */ jsx6(Detail, { label: "rule", value: current.rule ?? "\u2014" }),
2917
- /* @__PURE__ */ jsx6(Detail, { label: "agent", value: current.agent ?? "\u2014" }),
2918
- /* @__PURE__ */ jsx6(Detail, { label: "session", value: current.session ?? "\u2014" }),
2919
- /* @__PURE__ */ jsx6(Detail, { label: "dlp", value: current.dlp.length ? current.dlp.join(", ") : "none", color: current.dlp.length ? theme.bad : void 0 }),
2920
- /* @__PURE__ */ jsx6(Detail, { label: "ratelimit", value: current.burst ? "BURST \u2014 over the limit for this agent" : "no", color: current.burst ? theme.bad : void 0 }),
2921
- /* @__PURE__ */ jsx6(Detail, { label: "when", value: new Date(current.at).toLocaleString() + (current.evalMs != null ? ` \xB7 eval ${current.evalMs}ms` : "") }),
2922
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: `arguments \u2014 full \xB7 ${argLines.length} lines${maxScroll ? ` \xB7 \u25BC${maxScroll - off} more \xB7 \u2191\u2193 scroll` : ""}` }),
2923
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: argBudget, overflow: "hidden", children: argWin.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
2924
- current.session ? /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
2925
- /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
2926
- "Session \xB7 ",
2927
- sessionCalls.length,
2928
- " calls"
2929
- ] }),
2930
- /* @__PURE__ */ jsx6(
2931
- Table,
2932
- {
2933
- columns: [
2934
- { header: "DECISION", width: 9 },
2935
- { header: "TOOL", width: 20 },
2936
- { header: "REASON", width: Math.max(16, cols - 48) },
2937
- { header: "WHEN", width: 6 }
2938
- ],
2939
- rows: sessionCalls.slice(0, Math.max(1, sessionRows - 2)).map((e) => [
2940
- { value: e.decision, color: decisionColor(e.decision) },
2941
- { value: truncate(e.tool, 20), color: theme.accent },
2942
- { value: truncate(e.reason ?? "\u2014", Math.max(16, cols - 48)) },
2943
- { value: ago(e.at), dim: true }
2944
- ])
2945
- }
2946
- )
2947
- ] }) : /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "(no session id)" }),
2948
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2191\u2193 scroll arguments \xB7 \u2190/esc back to logs" })
2948
+ /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2949
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2502 when " }),
2950
+ /* @__PURE__ */ jsx6(Text6, { children: new Date(e.at).toLocaleString() }),
2951
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 perm " }),
2952
+ /* @__PURE__ */ jsx6(Text6, { children: e.permission || "\u2014" }),
2953
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 trust " }),
2954
+ /* @__PURE__ */ jsx6(Text6, { children: e.trust || "\u2014" }),
2955
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 eval " }),
2956
+ /* @__PURE__ */ jsx6(Text6, { color: e.evalMs != null && e.evalMs > 500 ? theme.warn : void 0, children: e.evalMs != null ? `${e.evalMs}ms` : "\u2014" }),
2957
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 agent " }),
2958
+ /* @__PURE__ */ jsx6(Text6, { children: e.agent ?? "\u2014" }),
2959
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502" })
2960
+ ] }),
2961
+ /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2962
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: "\u2502 session " }),
2963
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: e.session ?? "\u2014" }),
2964
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502 rule " }),
2965
+ /* @__PURE__ */ jsx6(Text6, { color: e.rule && e.decision !== "ALLOW" ? theme.bad : theme.dim, children: e.rule ?? "\u2014" }),
2966
+ /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: " \u2502" })
2967
+ ] }),
2968
+ /* @__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 }),
2969
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", height: bodyRows, overflow: "hidden", children: win.map((l, i) => /* @__PURE__ */ jsx6(Text6, { wrap: "truncate", children: l || " " }, off + i)) }),
2970
+ /* @__PURE__ */ jsxs6(Text6, { wrap: "truncate", children: [
2971
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: BG2, color: "white", bold: true, children: " ENTRY " }),
2972
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: "#0b1530", color: "white", children: ` ${e.id} ` }),
2973
+ /* @__PURE__ */ jsx6(Text6, { backgroundColor: BG2, color: "white", children: " \u2191\u2193 scroll \xB7 space copy \xB7 ? all keys \xB7 \u2190 back \xB7 esc menu " })
2974
+ ] })
2949
2975
  ] });
2950
2976
  }
2951
2977
  const chip = (label, val, on) => /* @__PURE__ */ jsxs6(Text6, { children: [
@@ -3032,7 +3058,6 @@ function AuditPanel({ active: active2, focused }) {
3032
3058
  const maxStart = Math.max(0, pageRows.length - listRows);
3033
3059
  const start = Math.min(Math.max(0, selClamped - Math.floor((listRows - 1) / 2)), maxStart);
3034
3060
  const windowed = pageRows.slice(start, start + listRows);
3035
- const reasonW = Math.min(58, Math.max(12, cols - 71));
3036
3061
  return /* @__PURE__ */ jsx6(DataView, { loading: logsLoading && !frozen, error: source === "cloud" ? cloudQ.error : localQ.error, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
3037
3062
  strip,
3038
3063
  copyBanner,
@@ -3068,21 +3093,7 @@ function AuditPanel({ active: active2, focused }) {
3068
3093
  )
3069
3094
  ] }) : null,
3070
3095
  /* @__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` : ""}` }),
3071
- /* @__PURE__ */ jsx6(
3072
- Table,
3073
- {
3074
- columns: [
3075
- { header: "", width: 2 },
3076
- { header: "DECISION", width: 9 },
3077
- { header: "TOOL", width: 18 },
3078
- { header: "AGENT", width: 14 },
3079
- { header: "REASON", width: reasonW },
3080
- { header: "SIGNAL", width: 8 },
3081
- { header: "WHEN", width: 6 }
3082
- ],
3083
- rows: windowed.map((e, i) => rowFor(e, start + i === selClamped && focused, reasonW))
3084
- }
3085
- ),
3096
+ /* @__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 }, e.id)) }),
3086
3097
  pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3087
3098
  "(no entries",
3088
3099
  source === "local" ? " in the local file" : "",
@@ -3090,43 +3101,58 @@ function AuditPanel({ active: active2, focused }) {
3090
3101
  ] }) : null
3091
3102
  ] }) });
3092
3103
  }
3093
- function rowFor(e, active2, reasonW) {
3094
- const sig = [e.dlp.length ? `DLP${e.dlp.length > 1 ? e.dlp.length : ""}` : "", e.burst ? "RL" : ""].filter(Boolean).join(" ");
3095
- return [
3096
- { value: active2 ? "\u25B8" : "", color: theme.accentBright },
3097
- { value: e.decision, color: decisionColor(e.decision) },
3098
- { value: truncate(e.tool, 18), color: theme.accent },
3099
- { value: truncate(e.agent ?? "\u2014", 14), dim: true },
3100
- { value: truncate(e.reason ?? e.args ?? "\u2014", reasonW) },
3101
- { value: sig || "\xB7", color: sig ? theme.bad : void 0, dim: !sig },
3102
- { value: ago(e.at), dim: true }
3103
- ];
3104
- }
3105
- function Detail({ label, value, color }) {
3106
- return /* @__PURE__ */ jsxs6(Box6, { children: [
3107
- /* @__PURE__ */ jsx6(Text6, { color: theme.dim, children: label.padEnd(9) }),
3108
- /* @__PURE__ */ jsx6(Text6, { color, wrap: "truncate", children: value })
3109
- ] });
3110
- }
3111
3104
 
3112
3105
  // src/tui/panels/Settings.tsx
3113
3106
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
3114
3107
  import TextInput5 from "ink-text-input";
3115
3108
  import { useEffect as useEffect6, useRef as useRef3, useState as useState7 } from "react";
3116
3109
 
3117
- // src/logs-server-daemon.ts
3118
- import { spawn as spawn3 } from "child_process";
3119
- import { mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
3110
+ // src/global-install.ts
3111
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync2, mkdirSync as mkdirSync4, rmSync } from "fs";
3112
+ import { resolve as resolve2, join as join6, dirname } from "path";
3120
3113
  import { homedir as homedir6 } from "os";
3121
- import { dirname, join as join6 } from "path";
3122
3114
  import { fileURLToPath } from "url";
3123
- var DIR = join6(homedir6(), ".solongate");
3124
- var STATE_FILE = join6(DIR, ".logs-server.json");
3125
- var LOG_FILE = join6(DIR, "logs-server.log");
3115
+ import { createInterface } from "readline";
3116
+ import { execFileSync } from "child_process";
3117
+ var __dirname = dirname(fileURLToPath(import.meta.url));
3118
+ var HOOKS_DIR = resolve2(__dirname, "..", "hooks");
3119
+ function globalPaths() {
3120
+ const home = homedir6();
3121
+ const sgDir = join6(home, ".solongate");
3122
+ const hooksDir = join6(sgDir, "hooks");
3123
+ const claudeDir = join6(home, ".claude");
3124
+ return {
3125
+ home,
3126
+ sgDir,
3127
+ hooksDir,
3128
+ claudeDir,
3129
+ settingsPath: join6(claudeDir, "settings.json"),
3130
+ backupPath: join6(claudeDir, "settings.solongate.bak"),
3131
+ configPath: join6(sgDir, "cloud-guard.json")
3132
+ };
3133
+ }
3134
+ function clearGuardUpdateCheck() {
3135
+ try {
3136
+ rmSync(join6(globalPaths().sgDir, ".hook-update-check"), { force: true });
3137
+ return true;
3138
+ } catch {
3139
+ return false;
3140
+ }
3141
+ }
3142
+
3143
+ // src/logs-server-daemon.ts
3144
+ import { spawn as spawn3 } from "child_process";
3145
+ import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
3146
+ import { homedir as homedir7 } from "os";
3147
+ import { dirname as dirname2, join as join7 } from "path";
3148
+ import { fileURLToPath as fileURLToPath2 } from "url";
3149
+ var DIR = join7(homedir7(), ".solongate");
3150
+ var STATE_FILE = join7(DIR, ".logs-server.json");
3151
+ var LOG_FILE = join7(DIR, "logs-server.log");
3126
3152
  var LOGS_SERVER_PORT = 8788;
3127
3153
  function readState() {
3128
3154
  try {
3129
- const s = JSON.parse(readFileSync4(STATE_FILE, "utf-8"));
3155
+ const s = JSON.parse(readFileSync5(STATE_FILE, "utf-8"));
3130
3156
  return s && typeof s === "object" ? s : {};
3131
3157
  } catch {
3132
3158
  return {};
@@ -3134,8 +3160,8 @@ function readState() {
3134
3160
  }
3135
3161
  function writeState(s) {
3136
3162
  try {
3137
- mkdirSync4(DIR, { recursive: true });
3138
- writeFileSync5(STATE_FILE, JSON.stringify(s));
3163
+ mkdirSync5(DIR, { recursive: true });
3164
+ writeFileSync6(STATE_FILE, JSON.stringify(s));
3139
3165
  } catch {
3140
3166
  }
3141
3167
  }
@@ -3160,9 +3186,9 @@ function startLogsServerDaemon() {
3160
3186
  return { ...cur, desired: "on" };
3161
3187
  }
3162
3188
  try {
3163
- mkdirSync4(DIR, { recursive: true });
3189
+ mkdirSync5(DIR, { recursive: true });
3164
3190
  const log = openSync2(LOG_FILE, "a");
3165
- const cli = join6(dirname(fileURLToPath(import.meta.url)), "index.js");
3191
+ const cli = join7(dirname2(fileURLToPath2(import.meta.url)), "index.js");
3166
3192
  const p = spawn3(process.execPath, [cli, "logs-server"], {
3167
3193
  detached: true,
3168
3194
  stdio: ["ignore", log, log],
@@ -3190,7 +3216,7 @@ function stopLogsServerDaemon() {
3190
3216
  }
3191
3217
 
3192
3218
  // src/tui/panels/Settings.tsx
3193
- import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3219
+ import { Fragment as Fragment4, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3194
3220
  var EVENTS = ["denials", "allowed", "all"];
3195
3221
  var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3196
3222
  var SIGNALS2 = ["any", "deny", "dlp", "ratelimit"];
@@ -3211,7 +3237,8 @@ function SettingsPanel({
3211
3237
  active: active2,
3212
3238
  focused,
3213
3239
  viewApiKey,
3214
- onView
3240
+ onView,
3241
+ onAccountsChanged
3215
3242
  }) {
3216
3243
  void active2;
3217
3244
  const { cols, rows } = usePanelSize();
@@ -3370,6 +3397,16 @@ function SettingsPanel({
3370
3397
  setMsg({ text: `viewing ${acctLabel(r.acc)}`, level: "ok" });
3371
3398
  } else if (r.kind === "acct-add") {
3372
3399
  beginLogin();
3400
+ } else if (r.kind === "guard") {
3401
+ if (!guard) return;
3402
+ if (guard.up_to_date) {
3403
+ setMsg({ text: `guard already on the latest hook (v${guard.latest})`, level: "ok" });
3404
+ return;
3405
+ }
3406
+ const armed = clearGuardUpdateCheck();
3407
+ setMsg(
3408
+ armed ? { text: `\u2713 update armed. now make your agent RUN any command (e.g. "echo ok"), not just chat; the guard installs v${guard.latest} on the next executed command`, level: "ok" } : { text: "\u2717 could not arm the update (check ~/.solongate permissions)", level: "bad" }
3409
+ );
3373
3410
  } else if (r.kind === "self") {
3374
3411
  if (!selfProt) return;
3375
3412
  run(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
@@ -3475,16 +3512,31 @@ function SettingsPanel({
3475
3512
  else activate(cur);
3476
3513
  }
3477
3514
  } else if (inp === "x" && cur.kind === "acct") {
3478
- const k = "acct:" + cur.acc.apiKey;
3515
+ const target = cur.acc;
3516
+ const k = "acct:" + target.apiKey;
3517
+ const wasActive = isActiveAccount(target.apiKey);
3518
+ const others = accounts.filter((a) => a.apiKey !== target.apiKey);
3479
3519
  if (confirmDel !== k) {
3480
3520
  setConfirmDel(k);
3481
- setMsg({ text: `press x again to remove ${acctLabel(cur.acc)} from this device (the cloud account is untouched)`, level: "bad" });
3521
+ const note = wasActive ? others.length ? ` \u2014 the ACTIVE guard key; ${acctLabel(others[0])} takes over` : " \u2014 your ONLY account & the active guard key; this signs the device out (guard has no key until you log in again)" : " (the cloud account is untouched)";
3522
+ setMsg({ text: `press x again to remove ${acctLabel(target)} from this device${note}`, level: "bad" });
3482
3523
  return;
3483
3524
  }
3484
3525
  setConfirmDel(null);
3485
- removeAccount(cur.acc.apiKey);
3486
- setMsg({ text: `\u2713 removed ${acctLabel(cur.acc)} from this device`, level: "ok" });
3526
+ removeAccount(target.apiKey);
3527
+ let extra = " from this device";
3528
+ if (wasActive) {
3529
+ if (others.length) {
3530
+ setActiveAccount({ apiKey: others[0].apiKey, apiUrl: others[0].apiUrl });
3531
+ extra = ` \xB7 ${acctLabel(others[0])} is now the active key`;
3532
+ } else {
3533
+ clearActiveCredential();
3534
+ extra = " \xB7 signed out of this device";
3535
+ }
3536
+ }
3537
+ setMsg({ text: `\u2713 removed ${acctLabel(target)}${extra}`, level: "ok" });
3487
3538
  refreshAccounts();
3539
+ onAccountsChanged?.();
3488
3540
  } else if (inp === "t" && cur.kind === "wh") {
3489
3541
  run("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
3490
3542
  if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
@@ -3608,9 +3660,9 @@ function SettingsPanel({
3608
3660
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3609
3661
  cursor(r),
3610
3662
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
3611
- guard ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
3663
+ guard ? /* @__PURE__ */ jsxs7(Fragment4, { children: [
3612
3664
  /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
3613
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available` }),
3665
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available \xB7 enter to update` }),
3614
3666
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""}` })
3615
3667
  ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading\u2026" })
3616
3668
  ] });
@@ -3676,7 +3728,7 @@ function SettingsPanel({
3676
3728
  const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
3677
3729
  const SECTION_DESC = {
3678
3730
  ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
3679
- PROTECTION: "guard hook version + self-protection toggle",
3731
+ PROTECTION: "guard hook version (enter to update) + self-protection toggle",
3680
3732
  "LOCAL LOGS": "mirror every decision to a file + dashboard link",
3681
3733
  WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
3682
3734
  ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
@@ -3734,18 +3786,18 @@ function SettingsPanel({
3734
3786
 
3735
3787
  // src/self-update.ts
3736
3788
  import { execFile, spawn as spawn4 } from "child_process";
3737
- import { mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync5, realpathSync, writeFileSync as writeFileSync6 } from "fs";
3738
- import { homedir as homedir7 } from "os";
3739
- import { dirname as dirname2, join as join7 } from "path";
3740
- import { fileURLToPath as fileURLToPath2 } from "url";
3789
+ import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync6, realpathSync, writeFileSync as writeFileSync7 } from "fs";
3790
+ import { homedir as homedir8 } from "os";
3791
+ import { dirname as dirname3, join as join8 } from "path";
3792
+ import { fileURLToPath as fileURLToPath3 } from "url";
3741
3793
  var PKG = "@solongate/proxy";
3742
3794
  var CHECK_EVERY_MS = 30 * 60 * 1e3;
3743
3795
  var ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
3744
- var STATE_FILE2 = join7(homedir7(), ".solongate", ".self-update.json");
3745
- var LOG_FILE2 = join7(homedir7(), ".solongate", "self-update.log");
3796
+ var STATE_FILE2 = join8(homedir8(), ".solongate", ".self-update.json");
3797
+ var LOG_FILE2 = join8(homedir8(), ".solongate", "self-update.log");
3746
3798
  function readState2() {
3747
3799
  try {
3748
- const s = JSON.parse(readFileSync5(STATE_FILE2, "utf-8"));
3800
+ const s = JSON.parse(readFileSync6(STATE_FILE2, "utf-8"));
3749
3801
  return s && typeof s === "object" ? s : {};
3750
3802
  } catch {
3751
3803
  return {};
@@ -3753,14 +3805,14 @@ function readState2() {
3753
3805
  }
3754
3806
  function writeState2(s) {
3755
3807
  try {
3756
- mkdirSync5(join7(homedir7(), ".solongate"), { recursive: true });
3757
- writeFileSync6(STATE_FILE2, JSON.stringify(s));
3808
+ mkdirSync6(join8(homedir8(), ".solongate"), { recursive: true });
3809
+ writeFileSync7(STATE_FILE2, JSON.stringify(s));
3758
3810
  } catch {
3759
3811
  }
3760
3812
  }
3761
3813
  function currentVersion() {
3762
3814
  try {
3763
- const pkg = JSON.parse(readFileSync5(join7(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf-8"));
3815
+ const pkg = JSON.parse(readFileSync6(join8(dirname3(fileURLToPath3(import.meta.url)), "..", "package.json"), "utf-8"));
3764
3816
  return pkg.version ?? "0.0.0";
3765
3817
  } catch {
3766
3818
  return "0.0.0";
@@ -3792,13 +3844,13 @@ async function fetchLatest() {
3792
3844
  }
3793
3845
  }
3794
3846
  function npmGlobalRoot() {
3795
- return new Promise((resolve2) => {
3847
+ return new Promise((resolve3) => {
3796
3848
  try {
3797
3849
  execFile("npm", ["root", "-g"], { timeout: 5e3, windowsHide: true, shell: process.platform === "win32" }, (err, stdout) => {
3798
- resolve2(err ? null : stdout.trim() || null);
3850
+ resolve3(err ? null : stdout.trim() || null);
3799
3851
  });
3800
3852
  } catch {
3801
- resolve2(null);
3853
+ resolve3(null);
3802
3854
  }
3803
3855
  });
3804
3856
  }
@@ -3814,26 +3866,26 @@ async function isGlobalInstall() {
3814
3866
  }
3815
3867
  }
3816
3868
  function runGlobalInstall(version) {
3817
- return new Promise((resolve2) => {
3869
+ return new Promise((resolve3) => {
3818
3870
  try {
3819
- mkdirSync5(join7(homedir7(), ".solongate"), { recursive: true });
3871
+ mkdirSync6(join8(homedir8(), ".solongate"), { recursive: true });
3820
3872
  execFile(
3821
3873
  "npm",
3822
3874
  ["install", "-g", `${PKG}@${version}`],
3823
3875
  { timeout: 3e5, windowsHide: true, shell: process.platform === "win32" },
3824
3876
  (err, stdout, stderr) => {
3825
3877
  try {
3826
- writeFileSync6(LOG_FILE2, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err ? "FAILED" : "ok"}
3878
+ writeFileSync7(LOG_FILE2, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err ? "FAILED" : "ok"}
3827
3879
  ${stdout}
3828
3880
  ${stderr}
3829
3881
  `, { flag: "a" });
3830
3882
  } catch {
3831
3883
  }
3832
- resolve2(!err);
3884
+ resolve3(!err);
3833
3885
  }
3834
3886
  );
3835
3887
  } catch {
3836
- resolve2(false);
3888
+ resolve3(false);
3837
3889
  }
3838
3890
  });
3839
3891
  }
@@ -3944,6 +3996,14 @@ function App() {
3944
3996
  if (accounts.length < 2) return;
3945
3997
  viewAccount(accounts[(acctIdx + 1) % accounts.length]);
3946
3998
  };
3999
+ const syncAccounts = () => {
4000
+ const list5 = listAccounts();
4001
+ setAccounts(list5);
4002
+ const next = list5.find((a) => a.apiKey === viewKey) ?? list5[0];
4003
+ setViewCredentials(next ? { apiKey: next.apiKey, apiUrl: next.apiUrl } : null);
4004
+ setViewKey(next?.apiKey);
4005
+ setViewNonce((n) => n + 1);
4006
+ };
3947
4007
  const locked = accounts.length === 0;
3948
4008
  const effectiveSection = locked ? SETTINGS_SECTION : section;
3949
4009
  useInput7((input, key) => {
@@ -3979,7 +4039,7 @@ function App() {
3979
4039
  return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
3980
4040
  }
3981
4041
  const Panel = current.Panel;
3982
- const panelBody = current.label === "Settings" ? /* @__PURE__ */ jsx8(SettingsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount }) : /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
4042
+ 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" });
3983
4043
  return (
3984
4044
  // height rows-1 (not rows): once total output reaches stdout.rows ink stops
3985
4045
  // diffing and clearTerminal-repaints every frame, which slides the banner.
@@ -4037,11 +4097,11 @@ async function launchTui() {
4037
4097
  return;
4038
4098
  }
4039
4099
  process.stdout.write("\x1B[?1049h\x1B[H");
4040
- const debugLog = join8(homedir8(), ".solongate", "dataroom-debug.log");
4100
+ const debugLog = join9(homedir9(), ".solongate", "dataroom-debug.log");
4041
4101
  const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
4042
4102
  const toFile = (level) => (...args) => {
4043
4103
  try {
4044
- mkdirSync6(join8(homedir8(), ".solongate"), { recursive: true });
4104
+ mkdirSync7(join9(homedir9(), ".solongate"), { recursive: true });
4045
4105
  appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
4046
4106
  `);
4047
4107
  } catch {