@solongate/proxy 0.81.49 → 0.81.51

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,14 +5,14 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/tui/index.tsx
8
- import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
9
- import { homedir as homedir7 } from "os";
10
- import { join as join7 } from "path";
8
+ import { appendFileSync, mkdirSync as mkdirSync6 } from "fs";
9
+ import { homedir as homedir8 } from "os";
10
+ import { join as join8 } from "path";
11
11
  import { render } from "ink";
12
12
 
13
13
  // src/tui/App.tsx
14
- import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
15
- import { useEffect as useEffect7, useState as useState9 } from "react";
14
+ import { Box as Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
15
+ import { useEffect as useEffect7, useState as useState8 } from "react";
16
16
 
17
17
  // src/cli-utils.ts
18
18
  var c = {
@@ -3090,9 +3090,87 @@ function Detail({ label, value, color }) {
3090
3090
  // src/tui/panels/Settings.tsx
3091
3091
  import { Box as Box7, Text as Text7, useInput as useInput6 } from "ink";
3092
3092
  import TextInput5 from "ink-text-input";
3093
- import { useState as useState7 } from "react";
3094
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3093
+ import { useEffect as useEffect6, useRef as useRef3, useState as useState7 } from "react";
3094
+
3095
+ // src/logs-server-daemon.ts
3096
+ import { spawn as spawn3 } from "child_process";
3097
+ import { mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
3098
+ import { homedir as homedir6 } from "os";
3099
+ import { dirname, join as join6 } from "path";
3100
+ import { fileURLToPath } from "url";
3101
+ var DIR = join6(homedir6(), ".solongate");
3102
+ var STATE_FILE = join6(DIR, ".logs-server.json");
3103
+ var LOG_FILE = join6(DIR, "logs-server.log");
3104
+ var LOGS_SERVER_PORT = 8788;
3105
+ function readState() {
3106
+ try {
3107
+ const s = JSON.parse(readFileSync4(STATE_FILE, "utf-8"));
3108
+ return s && typeof s === "object" ? s : {};
3109
+ } catch {
3110
+ return {};
3111
+ }
3112
+ }
3113
+ function writeState(s) {
3114
+ try {
3115
+ mkdirSync4(DIR, { recursive: true });
3116
+ writeFileSync5(STATE_FILE, JSON.stringify(s));
3117
+ } catch {
3118
+ }
3119
+ }
3120
+ function pidAlive(pid) {
3121
+ if (!pid || pid <= 0) return false;
3122
+ try {
3123
+ process.kill(pid, 0);
3124
+ return true;
3125
+ } catch (e) {
3126
+ return e.code === "EPERM";
3127
+ }
3128
+ }
3129
+ function logsServerStatus() {
3130
+ const s = readState();
3131
+ const running = pidAlive(s.pid);
3132
+ return { desired: s.desired === "on" ? "on" : "off", running, pid: running ? s.pid : void 0, port: s.port || LOGS_SERVER_PORT };
3133
+ }
3134
+ function startLogsServerDaemon() {
3135
+ const cur = logsServerStatus();
3136
+ if (cur.running) {
3137
+ writeState({ ...readState(), desired: "on" });
3138
+ return { ...cur, desired: "on" };
3139
+ }
3140
+ try {
3141
+ mkdirSync4(DIR, { recursive: true });
3142
+ const log = openSync2(LOG_FILE, "a");
3143
+ const cli = join6(dirname(fileURLToPath(import.meta.url)), "index.js");
3144
+ const p = spawn3(process.execPath, [cli, "logs-server"], {
3145
+ detached: true,
3146
+ stdio: ["ignore", log, log],
3147
+ windowsHide: true
3148
+ });
3149
+ p.on("error", () => {
3150
+ });
3151
+ p.unref();
3152
+ writeState({ desired: "on", pid: p.pid, port: LOGS_SERVER_PORT, startedAt: Date.now() });
3153
+ return { desired: "on", running: true, pid: p.pid, port: LOGS_SERVER_PORT };
3154
+ } catch {
3155
+ return { ...cur, desired: "on", running: false };
3156
+ }
3157
+ }
3158
+ function stopLogsServerDaemon() {
3159
+ const s = readState();
3160
+ if (pidAlive(s.pid)) {
3161
+ try {
3162
+ process.kill(s.pid);
3163
+ } catch {
3164
+ }
3165
+ }
3166
+ writeState({ desired: "off", port: s.port });
3167
+ return { desired: "off", running: false, port: s.port || LOGS_SERVER_PORT };
3168
+ }
3169
+
3170
+ // src/tui/panels/Settings.tsx
3171
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3095
3172
  var EVENTS = ["denials", "allowed", "all"];
3173
+ var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3096
3174
  function alertBodyFor(channel) {
3097
3175
  const c2 = channel.trim();
3098
3176
  const base = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
@@ -3100,7 +3178,13 @@ function alertBodyFor(channel) {
3100
3178
  if (c2.includes("@")) return { ...base, emails: [c2] };
3101
3179
  return { ...base, telegram: [c2] };
3102
3180
  }
3103
- function SettingsPanel({ active: active2, focused }) {
3181
+ var acctLabel = (a) => a.email || a.user || "account \u2026" + a.apiKey.slice(-4);
3182
+ function SettingsPanel({
3183
+ active: active2,
3184
+ focused,
3185
+ viewApiKey,
3186
+ onView
3187
+ }) {
3104
3188
  void active2;
3105
3189
  const { cols, rows } = usePanelSize();
3106
3190
  const [sel, setSel] = useState7(0);
@@ -3109,24 +3193,46 @@ function SettingsPanel({ active: active2, focused }) {
3109
3193
  const [confirmDel, setConfirmDel] = useState7(null);
3110
3194
  const [msg, setMsg] = useState7(null);
3111
3195
  const [busy, setBusy] = useState7(false);
3112
- const localQ = useLoader(() => api.settings.getLocalLogs());
3113
- const whQ = useLoader(() => api.settings.getWebhooks());
3114
- const alertQ = useLoader(() => api.settings.getAlerts());
3196
+ const [accounts, setAccounts] = useState7(() => listAccounts());
3197
+ const [login, setLogin] = useState7(null);
3198
+ const [tick, setTick] = useState7(0);
3199
+ const abort = useRef3(false);
3200
+ const refreshAccounts = () => setAccounts(listAccounts());
3201
+ const locked = accounts.length === 0;
3202
+ const [srv, setSrv] = useState7(() => logsServerStatus());
3203
+ useEffect6(() => {
3204
+ const t = setInterval(() => setSrv(logsServerStatus()), 3e3);
3205
+ return () => clearInterval(t);
3206
+ }, []);
3207
+ useEffect6(() => {
3208
+ if (!login || login.phase === "done" || login.phase === "error") return;
3209
+ const t = setInterval(() => setTick((n) => n + 1), 120);
3210
+ return () => clearInterval(t);
3211
+ }, [login]);
3212
+ const localQ = useLoader(() => listAccounts().length ? api.settings.getLocalLogs() : Promise.resolve(null));
3213
+ const whQ = useLoader(() => listAccounts().length ? api.settings.getWebhooks() : Promise.resolve(null));
3214
+ const alertQ = useLoader(() => listAccounts().length ? api.settings.getAlerts() : Promise.resolve(null));
3115
3215
  const local = localQ.data;
3116
3216
  const webhooks = whQ.data?.webhooks ?? [];
3117
3217
  const alerts = alertQ.data?.rules ?? [];
3118
3218
  const rowsAll = [
3119
- { kind: "ll-enabled" },
3120
- { kind: "ll-path" },
3121
- ...webhooks.map((wh) => ({ kind: "wh", wh })),
3122
- { kind: "wh-add" },
3123
- ...alerts.map((rule) => ({ kind: "alert", rule })),
3124
- { kind: "alert-add" }
3219
+ ...accounts.map((acc) => ({ kind: "acct", acc })),
3220
+ { kind: "acct-add" },
3221
+ ...locked ? [] : [
3222
+ { kind: "ll-enabled" },
3223
+ { kind: "ll-path" },
3224
+ { kind: "ll-server" },
3225
+ ...webhooks.map((wh) => ({ kind: "wh", wh })),
3226
+ { kind: "wh-add" },
3227
+ ...alerts.map((rule) => ({ kind: "alert", rule })),
3228
+ { kind: "alert-add" }
3229
+ ]
3125
3230
  ];
3126
3231
  const selClamped = Math.min(sel, rowsAll.length - 1);
3127
3232
  const cur = rowsAll[selClamped];
3128
- const keyOf = (r) => r.kind === "wh" ? "wh:" + r.wh.id : r.kind === "alert" ? "alert:" + r.rule.id : r.kind;
3233
+ const keyOf = (r) => r.kind === "acct" ? "acct:" + r.acc.apiKey : r.kind === "wh" ? "wh:" + r.wh.id : r.kind === "alert" ? "alert:" + r.rule.id : r.kind;
3129
3234
  const reloadAll = () => {
3235
+ refreshAccounts();
3130
3236
  localQ.reload();
3131
3237
  whQ.reload();
3132
3238
  alertQ.reload();
@@ -3140,8 +3246,48 @@ function SettingsPanel({ active: active2, focused }) {
3140
3246
  reload();
3141
3247
  }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" })).finally(() => setBusy(false));
3142
3248
  };
3249
+ const beginLogin = () => {
3250
+ if (login && (login.phase === "starting" || login.phase === "waiting")) return;
3251
+ abort.current = false;
3252
+ setLogin({ phase: "starting" });
3253
+ void (async () => {
3254
+ let start2;
3255
+ try {
3256
+ start2 = await startDeviceLogin(DEFAULT_API_URL);
3257
+ } catch (e) {
3258
+ setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
3259
+ return;
3260
+ }
3261
+ setLogin({ phase: "waiting", url: start2.verifyUrl });
3262
+ openBrowser(start2.verifyUrl);
3263
+ while (Date.now() < start2.expiresAt && !abort.current) {
3264
+ await new Promise((r) => setTimeout(r, start2.intervalMs));
3265
+ if (abort.current) return;
3266
+ const res = await pollDeviceLogin(DEFAULT_API_URL, start2.deviceCode);
3267
+ if (res.status === "approved" && res.apiKey) {
3268
+ saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user, email: res.email });
3269
+ setLogin({ phase: "done", msg: `\u2713 added ${res.email || res.user || res.project || "account"}` });
3270
+ setViewCredentials({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL });
3271
+ onView?.({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user, email: res.email });
3272
+ reloadAll();
3273
+ return;
3274
+ }
3275
+ if (res.status === "expired" || res.status === "not_found") {
3276
+ setLogin({ phase: "error", msg: "code expired \u2014 press n to retry" });
3277
+ return;
3278
+ }
3279
+ }
3280
+ if (!abort.current) setLogin({ phase: "error", msg: "timed out \u2014 press n to retry" });
3281
+ })();
3282
+ };
3143
3283
  const activate = (r) => {
3144
- if (r.kind === "ll-enabled") {
3284
+ if (r.kind === "acct") {
3285
+ setViewCredentials({ apiKey: r.acc.apiKey, apiUrl: r.acc.apiUrl });
3286
+ onView?.(r.acc);
3287
+ setMsg({ text: `viewing ${acctLabel(r.acc)}`, level: "ok" });
3288
+ } else if (r.kind === "acct-add") {
3289
+ beginLogin();
3290
+ } else if (r.kind === "ll-enabled") {
3145
3291
  if (!local) return;
3146
3292
  if (!local.enabled && !local.path.trim()) {
3147
3293
  setMsg({ text: "set a path first (\u2193 then enter)", level: "bad" });
@@ -3151,6 +3297,12 @@ function SettingsPanel({ active: active2, focused }) {
3151
3297
  } else if (r.kind === "ll-path") {
3152
3298
  setInput(local?.path ?? "");
3153
3299
  setEditing("path");
3300
+ } else if (r.kind === "ll-server") {
3301
+ const next = srv.running ? stopLogsServerDaemon() : startLogsServerDaemon();
3302
+ setSrv(next);
3303
+ setMsg(
3304
+ next.running ? { text: `\u2713 dashboard link running on 127.0.0.1:${next.port} \u2014 keeps running after you close the dataroom`, level: "ok" } : { text: "\u2713 dashboard link stopped (disabled until you start it again)", level: "ok" }
3305
+ );
3154
3306
  } else if (r.kind === "wh") {
3155
3307
  run(r.wh.enabled ? "webhook disabled" : "webhook enabled", () => api.settings.updateWebhook(r.wh.id, { enabled: !r.wh.enabled }), whQ.reload);
3156
3308
  } else if (r.kind === "wh-add") {
@@ -3180,6 +3332,13 @@ function SettingsPanel({ active: active2, focused }) {
3180
3332
  };
3181
3333
  useInput6(
3182
3334
  (inp, key) => {
3335
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
3336
+ if (key.escape) {
3337
+ abort.current = true;
3338
+ setLogin(null);
3339
+ }
3340
+ return;
3341
+ }
3183
3342
  setMsg(null);
3184
3343
  if (key.upArrow) {
3185
3344
  setSel((n) => Math.max(0, n - 1));
@@ -3188,6 +3347,11 @@ function SettingsPanel({ active: active2, focused }) {
3188
3347
  setSel((n) => Math.min(rowsAll.length - 1, n + 1));
3189
3348
  setConfirmDel(null);
3190
3349
  } else if (key.return || inp === " ") activate(cur);
3350
+ else if (inp === "m" && cur.kind === "acct") {
3351
+ const ok = setActiveAccount({ apiKey: cur.acc.apiKey, apiUrl: cur.acc.apiUrl });
3352
+ setMsg(ok ? { text: `\u2713 ${acctLabel(cur.acc)} is now the ACTIVE key (guard + logging)`, level: "ok" } : { text: "\u2717 could not set active", level: "bad" });
3353
+ refreshAccounts();
3354
+ } else if (inp === "n") beginLogin();
3191
3355
  else if (inp === "e" && cur.kind === "wh") {
3192
3356
  const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
3193
3357
  run(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
@@ -3214,244 +3378,171 @@ function SettingsPanel({ active: active2, focused }) {
3214
3378
  },
3215
3379
  { isActive: focused && !editing }
3216
3380
  );
3217
- const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
3218
- const error = localQ.error ?? whQ.error ?? alertQ.error;
3381
+ const spin = SPIN2[tick % SPIN2.length];
3382
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
3383
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3384
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: "Add an account" }),
3385
+ login.phase === "starting" ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3386
+ spin,
3387
+ " starting device pairing\u2026"
3388
+ ] }) : /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
3389
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
3390
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
3391
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
3392
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
3393
+ spin,
3394
+ " waiting for authorization\u2026 "
3395
+ ] }),
3396
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "esc to cancel" })
3397
+ ] })
3398
+ ] })
3399
+ ] });
3400
+ }
3401
+ const loading = !locked && (localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data);
3402
+ const error = locked ? null : localQ.error ?? whQ.error ?? alertQ.error;
3219
3403
  const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
3220
3404
  const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
3221
3405
  const chanOf = (rule) => [...(rule.slackUrls ?? []).map((u) => "slack " + u), ...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
3222
- const listBudget = Math.max(4, rows - 8);
3406
+ const listBudget = Math.max(4, rows - 10);
3223
3407
  const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
3224
3408
  const inWindow = (r) => {
3225
3409
  const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
3226
3410
  return i >= start && i < start + listBudget;
3227
3411
  };
3228
3412
  return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3229
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter/space toggle-edit-add \xB7 e events \xB7 d delete \xB7 r refresh" : "press \u2192 to configure" }),
3413
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter view-toggle-edit \xB7 m make active \xB7 n add account \xB7 e events \xB7 d delete \xB7 r refresh" : "press \u2192 to configure" }),
3230
3414
  editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
3231
3415
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
3232
3416
  /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
3233
- ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
3234
- inWindow(rowsAll[0]) || inWindow(rowsAll[1]) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3235
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3236
- "LOCAL LOGS ",
3237
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
3238
- ] }),
3239
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3240
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[0]) }),
3241
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
3242
- onOff(!!local?.enabled),
3243
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
3244
- ] }),
3245
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3246
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark(rowsAll[1]) }),
3247
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
3248
- 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" })
3249
- ] })
3250
- ] }) : null,
3417
+ ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx7(Text7, { color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
3251
3418
  /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3252
3419
  /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3253
- "WEBHOOKS ",
3420
+ "ACCOUNTS ",
3254
3421
  /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3255
- "\u2014 POST every matching event to your endpoint (",
3256
- webhooks.length,
3257
- ")"
3422
+ "\u2014 logged in on this device (",
3423
+ accounts.length,
3424
+ ") \xB7 \u25CF viewing = shown here \xB7 ACTIVE = key the guard uses"
3258
3425
  ] })
3259
3426
  ] }),
3260
- webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3261
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
3262
- onOff(wh.enabled),
3263
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
3264
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(wh.url, cols - 16) })
3265
- ] }, wh.id)),
3266
- inWindow(rowsAll.find((r) => r.kind === "wh-add")) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3267
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
3268
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
3427
+ accounts.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { children: [
3428
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " no accounts yet \u2014 " }),
3429
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: "n" }),
3430
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " (or enter) logs in, pairs this device and unlocks the dataroom" })
3431
+ ] }) : null,
3432
+ accounts.filter((acc) => inWindow({ kind: "acct", acc })).map((a) => {
3433
+ const isView = viewApiKey ? a.apiKey === viewApiKey : accounts[0]?.apiKey === a.apiKey;
3434
+ const isActive = isActiveAccount(a.apiKey);
3435
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3436
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "acct:" + a.apiKey && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct", acc: a }) }),
3437
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(acctLabel(a), 30).padEnd(31) }),
3438
+ isView ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
3439
+ isActive ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
3440
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: a.project ? truncate(a.project, 24) : "" })
3441
+ ] }, a.apiKey);
3442
+ }),
3443
+ inWindow({ kind: "acct-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3444
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "acct-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct-add" }) }),
3445
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
3269
3446
  ] }) : null
3270
3447
  ] }),
3271
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3272
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3273
- "ALERTS ",
3274
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3275
- "\u2014 notify on a signal spike (",
3276
- alerts.length,
3277
- ")"
3448
+ locked ? null : /* @__PURE__ */ jsxs7(Fragment5, { children: [
3449
+ inWindow({ kind: "ll-enabled" }) || inWindow({ kind: "ll-path" }) || inWindow({ kind: "ll-server" }) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3450
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3451
+ "LOCAL LOGS ",
3452
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
3453
+ ] }),
3454
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3455
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-enabled" }) }),
3456
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
3457
+ onOff(!!local?.enabled),
3458
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
3459
+ ] }),
3460
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3461
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-path" }) }),
3462
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
3463
+ 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" })
3464
+ ] }),
3465
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3466
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-server" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-server" }) }),
3467
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "dashboard".padEnd(10) }),
3468
+ srv.running ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: `running 127.0.0.1:${srv.port}` }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "stopped" }),
3469
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: srv.running ? " serves local logs to the dashboard \xB7 survives closing the dataroom \xB7 enter stops" : " enter starts the local-logs link for the dashboard" })
3278
3470
  ] })
3471
+ ] }) : null,
3472
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3473
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3474
+ "WEBHOOKS ",
3475
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3476
+ "\u2014 POST every matching event to your endpoint (",
3477
+ webhooks.length,
3478
+ ")"
3479
+ ] })
3480
+ ] }),
3481
+ webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3482
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
3483
+ onOff(wh.enabled),
3484
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
3485
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(wh.url, cols - 16) })
3486
+ ] }, wh.id)),
3487
+ inWindow({ kind: "wh-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3488
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
3489
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
3490
+ ] }) : null
3279
3491
  ] }),
3280
- alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3281
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
3282
- onOff(rule.enabled),
3283
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
3284
- /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
3285
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
3286
- ] }, rule.id)),
3287
- inWindow(rowsAll[rowsAll.length - 1]) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3288
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
3289
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
3290
- ] }) : null
3492
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3493
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3494
+ "ALERTS ",
3495
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3496
+ "\u2014 notify on a signal spike (",
3497
+ alerts.length,
3498
+ ")"
3499
+ ] })
3500
+ ] }),
3501
+ alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3502
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
3503
+ onOff(rule.enabled),
3504
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
3505
+ /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
3506
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
3507
+ ] }, rule.id)),
3508
+ inWindow({ kind: "alert-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3509
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
3510
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
3511
+ ] }) : null
3512
+ ] })
3291
3513
  ] })
3292
3514
  ] }) });
3293
3515
  }
3294
3516
 
3295
- // src/tui/panels/Accounts.tsx
3296
- import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
3297
- import { useEffect as useEffect6, useRef as useRef3, useState as useState8 } from "react";
3298
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3299
- var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3300
- function AccountsPanel({
3301
- focused,
3302
- viewApiKey,
3303
- onView
3304
- }) {
3305
- const [accounts, setAccounts] = useState8(() => listAccounts());
3306
- const [sel, setSel] = useState8(0);
3307
- const [msg, setMsg] = useState8(null);
3308
- const [login, setLogin] = useState8(null);
3309
- const [tick, setTick] = useState8(0);
3310
- const abort = useRef3(false);
3311
- const refresh = () => setAccounts(listAccounts());
3312
- useEffect6(() => {
3313
- if (!login || login.phase === "done" || login.phase === "error") return;
3314
- const t = setInterval(() => setTick((n) => n + 1), 120);
3315
- return () => clearInterval(t);
3316
- }, [login]);
3317
- const beginLogin = () => {
3318
- if (login && (login.phase === "starting" || login.phase === "waiting")) return;
3319
- abort.current = false;
3320
- setLogin({ phase: "starting" });
3321
- void (async () => {
3322
- let start;
3323
- try {
3324
- start = await startDeviceLogin(DEFAULT_API_URL);
3325
- } catch (e) {
3326
- setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
3327
- return;
3328
- }
3329
- setLogin({ phase: "waiting", url: start.verifyUrl });
3330
- openBrowser(start.verifyUrl);
3331
- while (Date.now() < start.expiresAt && !abort.current) {
3332
- await new Promise((r) => setTimeout(r, start.intervalMs));
3333
- if (abort.current) return;
3334
- const res = await pollDeviceLogin(DEFAULT_API_URL, start.deviceCode);
3335
- if (res.status === "approved" && res.apiKey) {
3336
- saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user, email: res.email });
3337
- setLogin({ phase: "done", msg: `\u2713 added ${res.email || res.user || res.project || "account"}` });
3338
- refresh();
3339
- setViewCredentials({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL });
3340
- onView?.({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user, email: res.email });
3341
- return;
3342
- }
3343
- if (res.status === "expired" || res.status === "not_found") {
3344
- setLogin({ phase: "error", msg: "code expired \u2014 press n to retry" });
3345
- return;
3346
- }
3347
- }
3348
- if (!abort.current) setLogin({ phase: "error", msg: "timed out \u2014 press n to retry" });
3349
- })();
3350
- };
3351
- useInput7(
3352
- (input, key) => {
3353
- if (login && (login.phase === "starting" || login.phase === "waiting")) {
3354
- if (key.escape) {
3355
- abort.current = true;
3356
- setLogin(null);
3357
- }
3358
- return;
3359
- }
3360
- if (key.upArrow) setSel((n) => Math.max(0, n - 1));
3361
- else if (key.downArrow) setSel((n) => Math.min(accounts.length - 1, n + 1));
3362
- else if (key.return) {
3363
- const a = accounts[sel];
3364
- if (a) {
3365
- setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3366
- onView?.(a);
3367
- setMsg({ text: `viewing ${a.email || a.user || a.project || "account \u2026" + a.apiKey.slice(-4)}`, level: "ok" });
3368
- }
3369
- } else if (input === "m") {
3370
- const a = accounts[sel];
3371
- if (a) {
3372
- const ok = setActiveAccount({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3373
- setMsg(ok ? { text: `\u2713 ${a.email || a.user || a.project || "account"} is now the ACTIVE key (guard + logging)`, level: "ok" } : { text: "\u2717 could not set active", level: "bad" });
3374
- refresh();
3375
- }
3376
- } else if (input === "n") beginLogin();
3377
- else if (input === "r") refresh();
3378
- },
3379
- { isActive: focused }
3380
- );
3381
- const spin = SPIN2[tick % SPIN2.length];
3382
- if (login && (login.phase === "starting" || login.phase === "waiting")) {
3383
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3384
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "Add an account" }),
3385
- login.phase === "starting" ? /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
3386
- spin,
3387
- " starting device pairing\u2026"
3388
- ] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
3389
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
3390
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
3391
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
3392
- /* @__PURE__ */ jsxs8(Text8, { color: theme.warn, children: [
3393
- spin,
3394
- " waiting for authorization\u2026 "
3395
- ] }),
3396
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "esc to cancel" })
3397
- ] })
3398
- ] })
3399
- ] });
3400
- }
3401
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3402
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter view \xB7 m make active \xB7 n add account \xB7 r refresh" : "press \u2192 to manage accounts" }),
3403
- msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, 70) }) : login?.msg ? /* @__PURE__ */ jsx8(Text8, { color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
3404
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: accounts.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3405
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "No accounts on this device yet." }),
3406
- /* @__PURE__ */ jsxs8(Text8, { children: [
3407
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "n" }),
3408
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " to log in \u2014 pairs this device and starts protecting your Claude Code sessions." })
3409
- ] })
3410
- ] }) : accounts.map((a, i) => {
3411
- const isSel = i === sel && focused;
3412
- const isView = viewApiKey ? a.apiKey === viewApiKey : i === 0;
3413
- const isActive = isActiveAccount(a.apiKey);
3414
- return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isSel ? "#333333" : void 0, bold: isSel, children: [
3415
- /* @__PURE__ */ jsx8(Text8, { color: isSel ? theme.accentBright : theme.dim, children: isSel ? "\u25B8 " : " " }),
3416
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.email || a.user || "account \u2026" + a.apiKey.slice(-4), 30).padEnd(31) }),
3417
- isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
3418
- isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
3419
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.project ? truncate(a.project, 24) : "" })
3420
- ] }, a.apiKey);
3421
- }) }),
3422
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u25CF viewing = the account the dataroom is showing \xB7 ACTIVE = the key the guard hooks + logging use" }) })
3423
- ] });
3424
- }
3425
-
3426
3517
  // src/self-update.ts
3427
- import { execFile, spawn as spawn3 } from "child_process";
3428
- import { mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync4, realpathSync, writeFileSync as writeFileSync5 } from "fs";
3429
- import { homedir as homedir6 } from "os";
3430
- import { dirname, join as join6 } from "path";
3431
- import { fileURLToPath } from "url";
3518
+ import { execFile, spawn as spawn4 } from "child_process";
3519
+ import { mkdirSync as mkdirSync5, openSync as openSync3, readFileSync as readFileSync5, realpathSync, writeFileSync as writeFileSync6 } from "fs";
3520
+ import { homedir as homedir7 } from "os";
3521
+ import { dirname as dirname2, join as join7 } from "path";
3522
+ import { fileURLToPath as fileURLToPath2 } from "url";
3432
3523
  var PKG = "@solongate/proxy";
3433
3524
  var CHECK_EVERY_MS = 30 * 60 * 1e3;
3434
3525
  var ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
3435
- var STATE_FILE = join6(homedir6(), ".solongate", ".self-update.json");
3436
- var LOG_FILE = join6(homedir6(), ".solongate", "self-update.log");
3437
- function readState() {
3526
+ var STATE_FILE2 = join7(homedir7(), ".solongate", ".self-update.json");
3527
+ var LOG_FILE2 = join7(homedir7(), ".solongate", "self-update.log");
3528
+ function readState2() {
3438
3529
  try {
3439
- const s = JSON.parse(readFileSync4(STATE_FILE, "utf-8"));
3530
+ const s = JSON.parse(readFileSync5(STATE_FILE2, "utf-8"));
3440
3531
  return s && typeof s === "object" ? s : {};
3441
3532
  } catch {
3442
3533
  return {};
3443
3534
  }
3444
3535
  }
3445
- function writeState(s) {
3536
+ function writeState2(s) {
3446
3537
  try {
3447
- mkdirSync4(join6(homedir6(), ".solongate"), { recursive: true });
3448
- writeFileSync5(STATE_FILE, JSON.stringify(s));
3538
+ mkdirSync5(join7(homedir7(), ".solongate"), { recursive: true });
3539
+ writeFileSync6(STATE_FILE2, JSON.stringify(s));
3449
3540
  } catch {
3450
3541
  }
3451
3542
  }
3452
3543
  function currentVersion() {
3453
3544
  try {
3454
- const pkg = JSON.parse(readFileSync4(join6(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
3545
+ const pkg = JSON.parse(readFileSync5(join7(dirname2(fileURLToPath2(import.meta.url)), "..", "package.json"), "utf-8"));
3455
3546
  return pkg.version ?? "0.0.0";
3456
3547
  } catch {
3457
3548
  return "0.0.0";
@@ -3501,14 +3592,14 @@ async function isGlobalInstall() {
3501
3592
  function runGlobalInstall(version) {
3502
3593
  return new Promise((resolve2) => {
3503
3594
  try {
3504
- mkdirSync4(join6(homedir6(), ".solongate"), { recursive: true });
3595
+ mkdirSync5(join7(homedir7(), ".solongate"), { recursive: true });
3505
3596
  execFile(
3506
3597
  "npm",
3507
3598
  ["install", "-g", `${PKG}@${version}`],
3508
3599
  { timeout: 3e5, windowsHide: true, shell: process.platform === "win32" },
3509
3600
  (err, stdout, stderr) => {
3510
3601
  try {
3511
- writeFileSync5(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err ? "FAILED" : "ok"}
3602
+ writeFileSync6(LOG_FILE2, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err ? "FAILED" : "ok"}
3512
3603
  ${stdout}
3513
3604
  ${stderr}
3514
3605
  `, { flag: "a" });
@@ -3526,13 +3617,13 @@ async function tuiUpdateFlow(onStatus) {
3526
3617
  try {
3527
3618
  const current = currentVersion();
3528
3619
  const latest = await fetchLatest();
3529
- const state = readState();
3530
- if (latest) writeState({ ...state, lastCheckAt: Date.now(), latestSeen: latest });
3620
+ const state = readState2();
3621
+ if (latest) writeState2({ ...state, lastCheckAt: Date.now(), latestSeen: latest });
3531
3622
  if (!latest || !newerThan(latest, current)) return;
3532
- const attempts = readState().attempts ?? {};
3623
+ const attempts = readState2().attempts ?? {};
3533
3624
  const canRetry = Date.now() - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS;
3534
3625
  if (await isGlobalInstall() && canRetry) {
3535
- writeState({ ...readState(), attempts: { [latest]: Date.now() } });
3626
+ writeState2({ ...readState2(), attempts: { [latest]: Date.now() } });
3536
3627
  onStatus({ kind: "updating", version: latest });
3537
3628
  if (await runGlobalInstall(latest)) {
3538
3629
  onStatus({ kind: "updated", version: latest });
@@ -3545,15 +3636,15 @@ async function tuiUpdateFlow(onStatus) {
3545
3636
  }
3546
3637
 
3547
3638
  // src/tui/App.tsx
3548
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3639
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3549
3640
  function LiveHint() {
3550
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3551
- /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
3552
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
3553
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
3554
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
3641
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3642
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
3643
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
3644
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
3645
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
3555
3646
  "press ",
3556
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
3647
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
3557
3648
  " to go live"
3558
3649
  ] }) })
3559
3650
  ] });
@@ -3564,33 +3655,32 @@ var SECTIONS = [
3564
3655
  { label: "Rate Limit", Panel: RateLimitPanel },
3565
3656
  { label: "DLP", Panel: DlpPanel },
3566
3657
  { label: "Audit", Panel: AuditPanel },
3567
- { label: "Settings", Panel: SettingsPanel },
3568
- { label: "Accounts", Panel: AccountsPanel }
3658
+ { label: "Settings", Panel: SettingsPanel }
3569
3659
  ];
3570
3660
  var BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
3571
3661
  function Banner({ cols }) {
3572
3662
  const wide = cols >= 82;
3573
3663
  if (!wide) {
3574
- return /* @__PURE__ */ jsxs9(Box9, { children: [
3575
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
3576
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
3664
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
3665
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
3666
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
3577
3667
  ] });
3578
3668
  }
3579
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3580
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
3581
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
3669
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3670
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
3671
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
3582
3672
  ] });
3583
3673
  }
3584
- var ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
3674
+ var SETTINGS_SECTION = SECTIONS.findIndex((s) => s.label === "Settings");
3585
3675
  function App() {
3586
3676
  const { exit } = useApp();
3587
- const [accounts, setAccounts] = useState9(() => listAccounts());
3588
- const [viewKey, setViewKey] = useState9(() => accounts[0]?.apiKey);
3589
- const [viewNonce, setViewNonce] = useState9(0);
3590
- const [section, setSection] = useState9(accounts.length === 0 ? ACCOUNTS_SECTION : 0);
3591
- const [focus, setFocus] = useState9("nav");
3592
- const [help, setHelp] = useState9(false);
3593
- const [update2, setUpdate] = useState9({ kind: "idle" });
3677
+ const [accounts, setAccounts] = useState8(() => listAccounts());
3678
+ const [viewKey, setViewKey] = useState8(() => accounts[0]?.apiKey);
3679
+ const [viewNonce, setViewNonce] = useState8(0);
3680
+ const [section, setSection] = useState8(accounts.length === 0 ? SETTINGS_SECTION : 0);
3681
+ const [focus, setFocus] = useState8("nav");
3682
+ const [help, setHelp] = useState8(false);
3683
+ const [update2, setUpdate] = useState8({ kind: "idle" });
3594
3684
  useEffect7(() => {
3595
3685
  let alive = true;
3596
3686
  const run = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
@@ -3603,7 +3693,7 @@ function App() {
3603
3693
  }, []);
3604
3694
  const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
3605
3695
  const cur = accounts[acctIdx];
3606
- const acctLabel = cur ? cur.email || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}` : "not logged in";
3696
+ const acctLabel2 = cur ? cur.email || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}` : "not logged in";
3607
3697
  useEffect7(() => {
3608
3698
  if (!cur || cur.email) return;
3609
3699
  let alive = true;
@@ -3631,8 +3721,8 @@ function App() {
3631
3721
  viewAccount(accounts[(acctIdx + 1) % accounts.length]);
3632
3722
  };
3633
3723
  const locked = accounts.length === 0;
3634
- const effectiveSection = locked ? ACCOUNTS_SECTION : section;
3635
- useInput8((input, key) => {
3724
+ const effectiveSection = locked ? SETTINGS_SECTION : section;
3725
+ useInput7((input, key) => {
3636
3726
  if (help) {
3637
3727
  setHelp(false);
3638
3728
  return;
@@ -3660,29 +3750,29 @@ function App() {
3660
3750
  });
3661
3751
  const { cols, rows } = useTermSize();
3662
3752
  const current = SECTIONS[effectiveSection];
3663
- if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
3753
+ if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
3664
3754
  if (current.label === "Live" && focus === "panel") {
3665
- return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
3755
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
3666
3756
  }
3667
3757
  const Panel = current.Panel;
3668
- const panelBody = current.label === "Accounts" ? /* @__PURE__ */ jsx9(AccountsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount }) : /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
3669
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3670
- /* @__PURE__ */ jsx9(Banner, { cols }),
3671
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
3672
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
3673
- /* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel }),
3674
- locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Accounts to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" }),
3675
- 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` }) : update2.kind === "manual" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} available \u2014 npm i -g @solongate/proxy@latest` }) : null
3758
+ 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" });
3759
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3760
+ /* @__PURE__ */ jsx8(Banner, { cols }),
3761
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
3762
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
3763
+ /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
3764
+ 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" }),
3765
+ 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` }) : update2.kind === "manual" ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: ` \u2191 v${update2.version} available \u2014 npm i -g @solongate/proxy@latest` }) : null
3676
3766
  ] }),
3677
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
3678
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
3767
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
3768
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
3679
3769
  const isCur = i === effectiveSection;
3680
- const disabled = locked && s.label !== "Accounts";
3681
- 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);
3770
+ const disabled = locked && s.label !== "Settings";
3771
+ 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);
3682
3772
  }) }),
3683
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
3773
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
3684
3774
  ] }, viewNonce),
3685
- /* @__PURE__ */ jsx9(Box9, { children: locked ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2192/enter", "open Accounts"], ["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"]] }) })
3775
+ /* @__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"]] }) })
3686
3776
  ] });
3687
3777
  }
3688
3778
  var HELP = [
@@ -3695,21 +3785,21 @@ var HELP = [
3695
3785
  ["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"]]]
3696
3786
  ];
3697
3787
  function HelpOverlay({ cols, rows }) {
3698
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3699
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3700
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
3701
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
3702
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
3703
- /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3704
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
3788
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3789
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3790
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
3791
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
3792
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
3793
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3794
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
3705
3795
  ] }, k))
3706
3796
  ] }, group)) }),
3707
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
3797
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
3708
3798
  ] });
3709
3799
  }
3710
3800
 
3711
3801
  // src/tui/index.tsx
3712
- import { jsx as jsx10 } from "react/jsx-runtime";
3802
+ import { jsx as jsx9 } from "react/jsx-runtime";
3713
3803
  async function launchTui() {
3714
3804
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
3715
3805
  process.stderr.write(
@@ -3718,11 +3808,11 @@ async function launchTui() {
3718
3808
  return;
3719
3809
  }
3720
3810
  process.stdout.write("\x1B[?1049h\x1B[H");
3721
- const debugLog = join7(homedir7(), ".solongate", "dataroom-debug.log");
3811
+ const debugLog = join8(homedir8(), ".solongate", "dataroom-debug.log");
3722
3812
  const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
3723
3813
  const toFile = (level) => (...args) => {
3724
3814
  try {
3725
- mkdirSync5(join7(homedir7(), ".solongate"), { recursive: true });
3815
+ mkdirSync6(join8(homedir8(), ".solongate"), { recursive: true });
3726
3816
  appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
3727
3817
  `);
3728
3818
  } catch {
@@ -3734,7 +3824,7 @@ async function launchTui() {
3734
3824
  console.info = toFile("info");
3735
3825
  console.debug = toFile("debug");
3736
3826
  try {
3737
- const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}), { patchConsole: false });
3827
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}), { patchConsole: false });
3738
3828
  await waitUntilExit();
3739
3829
  } finally {
3740
3830
  Object.assign(console, saved);