@solongate/proxy 0.81.48 → 0.81.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui/index.js CHANGED
@@ -11,8 +11,8 @@ import { join as join7 } 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 = {
@@ -462,7 +462,7 @@ async function pollDeviceLogin(apiUrl, deviceCode) {
462
462
  });
463
463
  const j = await res.json().catch(() => ({}));
464
464
  if (j.status === "approved" && j.api_key) {
465
- return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email };
465
+ return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email, email: j.user?.email };
466
466
  }
467
467
  if (j.status === "expired" || j.status === "not_found") return { status: j.status };
468
468
  return { status: "pending" };
@@ -3090,9 +3090,10 @@ 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
+ import { Fragment as Fragment5, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
3095
3095
  var EVENTS = ["denials", "allowed", "all"];
3096
+ var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3096
3097
  function alertBodyFor(channel) {
3097
3098
  const c2 = channel.trim();
3098
3099
  const base = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
@@ -3100,7 +3101,13 @@ function alertBodyFor(channel) {
3100
3101
  if (c2.includes("@")) return { ...base, emails: [c2] };
3101
3102
  return { ...base, telegram: [c2] };
3102
3103
  }
3103
- function SettingsPanel({ active: active2, focused }) {
3104
+ var acctLabel = (a) => a.email || a.user || "account \u2026" + a.apiKey.slice(-4);
3105
+ function SettingsPanel({
3106
+ active: active2,
3107
+ focused,
3108
+ viewApiKey,
3109
+ onView
3110
+ }) {
3104
3111
  void active2;
3105
3112
  const { cols, rows } = usePanelSize();
3106
3113
  const [sel, setSel] = useState7(0);
@@ -3109,24 +3116,40 @@ function SettingsPanel({ active: active2, focused }) {
3109
3116
  const [confirmDel, setConfirmDel] = useState7(null);
3110
3117
  const [msg, setMsg] = useState7(null);
3111
3118
  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());
3119
+ const [accounts, setAccounts] = useState7(() => listAccounts());
3120
+ const [login, setLogin] = useState7(null);
3121
+ const [tick, setTick] = useState7(0);
3122
+ const abort = useRef3(false);
3123
+ const refreshAccounts = () => setAccounts(listAccounts());
3124
+ const locked = accounts.length === 0;
3125
+ useEffect6(() => {
3126
+ if (!login || login.phase === "done" || login.phase === "error") return;
3127
+ const t = setInterval(() => setTick((n) => n + 1), 120);
3128
+ return () => clearInterval(t);
3129
+ }, [login]);
3130
+ const localQ = useLoader(() => listAccounts().length ? api.settings.getLocalLogs() : Promise.resolve(null));
3131
+ const whQ = useLoader(() => listAccounts().length ? api.settings.getWebhooks() : Promise.resolve(null));
3132
+ const alertQ = useLoader(() => listAccounts().length ? api.settings.getAlerts() : Promise.resolve(null));
3115
3133
  const local = localQ.data;
3116
3134
  const webhooks = whQ.data?.webhooks ?? [];
3117
3135
  const alerts = alertQ.data?.rules ?? [];
3118
3136
  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" }
3137
+ ...accounts.map((acc) => ({ kind: "acct", acc })),
3138
+ { kind: "acct-add" },
3139
+ ...locked ? [] : [
3140
+ { kind: "ll-enabled" },
3141
+ { kind: "ll-path" },
3142
+ ...webhooks.map((wh) => ({ kind: "wh", wh })),
3143
+ { kind: "wh-add" },
3144
+ ...alerts.map((rule) => ({ kind: "alert", rule })),
3145
+ { kind: "alert-add" }
3146
+ ]
3125
3147
  ];
3126
3148
  const selClamped = Math.min(sel, rowsAll.length - 1);
3127
3149
  const cur = rowsAll[selClamped];
3128
- const keyOf = (r) => r.kind === "wh" ? "wh:" + r.wh.id : r.kind === "alert" ? "alert:" + r.rule.id : r.kind;
3150
+ 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
3151
  const reloadAll = () => {
3152
+ refreshAccounts();
3130
3153
  localQ.reload();
3131
3154
  whQ.reload();
3132
3155
  alertQ.reload();
@@ -3140,8 +3163,48 @@ function SettingsPanel({ active: active2, focused }) {
3140
3163
  reload();
3141
3164
  }).catch((e) => setMsg({ text: "\u2717 " + (e instanceof Error ? e.message : String(e)), level: "bad" })).finally(() => setBusy(false));
3142
3165
  };
3166
+ const beginLogin = () => {
3167
+ if (login && (login.phase === "starting" || login.phase === "waiting")) return;
3168
+ abort.current = false;
3169
+ setLogin({ phase: "starting" });
3170
+ void (async () => {
3171
+ let start2;
3172
+ try {
3173
+ start2 = await startDeviceLogin(DEFAULT_API_URL);
3174
+ } catch (e) {
3175
+ setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
3176
+ return;
3177
+ }
3178
+ setLogin({ phase: "waiting", url: start2.verifyUrl });
3179
+ openBrowser(start2.verifyUrl);
3180
+ while (Date.now() < start2.expiresAt && !abort.current) {
3181
+ await new Promise((r) => setTimeout(r, start2.intervalMs));
3182
+ if (abort.current) return;
3183
+ const res = await pollDeviceLogin(DEFAULT_API_URL, start2.deviceCode);
3184
+ if (res.status === "approved" && res.apiKey) {
3185
+ saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user, email: res.email });
3186
+ setLogin({ phase: "done", msg: `\u2713 added ${res.email || res.user || res.project || "account"}` });
3187
+ setViewCredentials({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL });
3188
+ onView?.({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user, email: res.email });
3189
+ reloadAll();
3190
+ return;
3191
+ }
3192
+ if (res.status === "expired" || res.status === "not_found") {
3193
+ setLogin({ phase: "error", msg: "code expired \u2014 press n to retry" });
3194
+ return;
3195
+ }
3196
+ }
3197
+ if (!abort.current) setLogin({ phase: "error", msg: "timed out \u2014 press n to retry" });
3198
+ })();
3199
+ };
3143
3200
  const activate = (r) => {
3144
- if (r.kind === "ll-enabled") {
3201
+ if (r.kind === "acct") {
3202
+ setViewCredentials({ apiKey: r.acc.apiKey, apiUrl: r.acc.apiUrl });
3203
+ onView?.(r.acc);
3204
+ setMsg({ text: `viewing ${acctLabel(r.acc)}`, level: "ok" });
3205
+ } else if (r.kind === "acct-add") {
3206
+ beginLogin();
3207
+ } else if (r.kind === "ll-enabled") {
3145
3208
  if (!local) return;
3146
3209
  if (!local.enabled && !local.path.trim()) {
3147
3210
  setMsg({ text: "set a path first (\u2193 then enter)", level: "bad" });
@@ -3180,6 +3243,13 @@ function SettingsPanel({ active: active2, focused }) {
3180
3243
  };
3181
3244
  useInput6(
3182
3245
  (inp, key) => {
3246
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
3247
+ if (key.escape) {
3248
+ abort.current = true;
3249
+ setLogin(null);
3250
+ }
3251
+ return;
3252
+ }
3183
3253
  setMsg(null);
3184
3254
  if (key.upArrow) {
3185
3255
  setSel((n) => Math.max(0, n - 1));
@@ -3188,6 +3258,11 @@ function SettingsPanel({ active: active2, focused }) {
3188
3258
  setSel((n) => Math.min(rowsAll.length - 1, n + 1));
3189
3259
  setConfirmDel(null);
3190
3260
  } else if (key.return || inp === " ") activate(cur);
3261
+ else if (inp === "m" && cur.kind === "acct") {
3262
+ const ok = setActiveAccount({ apiKey: cur.acc.apiKey, apiUrl: cur.acc.apiUrl });
3263
+ setMsg(ok ? { text: `\u2713 ${acctLabel(cur.acc)} is now the ACTIVE key (guard + logging)`, level: "ok" } : { text: "\u2717 could not set active", level: "bad" });
3264
+ refreshAccounts();
3265
+ } else if (inp === "n") beginLogin();
3191
3266
  else if (inp === "e" && cur.kind === "wh") {
3192
3267
  const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
3193
3268
  run(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
@@ -3214,215 +3289,136 @@ function SettingsPanel({ active: active2, focused }) {
3214
3289
  },
3215
3290
  { isActive: focused && !editing }
3216
3291
  );
3217
- const loading = localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data;
3218
- const error = localQ.error ?? whQ.error ?? alertQ.error;
3292
+ const spin = SPIN2[tick % SPIN2.length];
3293
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
3294
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3295
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: "Add an account" }),
3296
+ login.phase === "starting" ? /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3297
+ spin,
3298
+ " starting device pairing\u2026"
3299
+ ] }) : /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginTop: 1, children: [
3300
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
3301
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
3302
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
3303
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.warn, children: [
3304
+ spin,
3305
+ " waiting for authorization\u2026 "
3306
+ ] }),
3307
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "esc to cancel" })
3308
+ ] })
3309
+ ] })
3310
+ ] });
3311
+ }
3312
+ const loading = !locked && (localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data);
3313
+ const error = locked ? null : localQ.error ?? whQ.error ?? alertQ.error;
3219
3314
  const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
3220
3315
  const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
3221
3316
  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);
3317
+ const listBudget = Math.max(4, rows - 10);
3223
3318
  const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
3224
3319
  const inWindow = (r) => {
3225
3320
  const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
3226
3321
  return i >= start && i < start + listBudget;
3227
3322
  };
3228
3323
  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" }),
3324
+ /* @__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
3325
  editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
3231
3326
  /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : "channel (slack url / email / telegram id): " }),
3232
3327
  /* @__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,
3328
+ ] }) : 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
3329
  /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3252
3330
  /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3253
- "WEBHOOKS ",
3331
+ "ACCOUNTS ",
3254
3332
  /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3255
- "\u2014 POST every matching event to your endpoint (",
3256
- webhooks.length,
3257
- ")"
3333
+ "\u2014 logged in on this device (",
3334
+ accounts.length,
3335
+ ") \xB7 \u25CF viewing = shown here \xB7 ACTIVE = key the guard uses"
3258
3336
  ] })
3259
3337
  ] }),
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)" })
3338
+ accounts.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { children: [
3339
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " no accounts yet \u2014 " }),
3340
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: "n" }),
3341
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " (or enter) logs in, pairs this device and unlocks the dataroom" })
3342
+ ] }) : null,
3343
+ accounts.filter((acc) => inWindow({ kind: "acct", acc })).map((a) => {
3344
+ const isView = viewApiKey ? a.apiKey === viewApiKey : accounts[0]?.apiKey === a.apiKey;
3345
+ const isActive = isActiveAccount(a.apiKey);
3346
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3347
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "acct:" + a.apiKey && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct", acc: a }) }),
3348
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(acctLabel(a), 30).padEnd(31) }),
3349
+ isView ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
3350
+ isActive ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
3351
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: a.project ? truncate(a.project, 24) : "" })
3352
+ ] }, a.apiKey);
3353
+ }),
3354
+ inWindow({ kind: "acct-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3355
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "acct-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct-add" }) }),
3356
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
3269
3357
  ] }) : null
3270
3358
  ] }),
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
- ")"
3359
+ locked ? null : /* @__PURE__ */ jsxs7(Fragment5, { children: [
3360
+ inWindow({ kind: "ll-enabled" }) || inWindow({ kind: "ll-path" }) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3361
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3362
+ "LOCAL LOGS ",
3363
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
3364
+ ] }),
3365
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3366
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-enabled" }) }),
3367
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
3368
+ onOff(!!local?.enabled),
3369
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
3370
+ ] }),
3371
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3372
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-path" }) }),
3373
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
3374
+ 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" })
3278
3375
  ] })
3376
+ ] }) : null,
3377
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3378
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3379
+ "WEBHOOKS ",
3380
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3381
+ "\u2014 POST every matching event to your endpoint (",
3382
+ webhooks.length,
3383
+ ")"
3384
+ ] })
3385
+ ] }),
3386
+ webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3387
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
3388
+ onOff(wh.enabled),
3389
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
3390
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(wh.url, cols - 16) })
3391
+ ] }, wh.id)),
3392
+ inWindow({ kind: "wh-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3393
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
3394
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
3395
+ ] }) : null
3279
3396
  ] }),
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
3397
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3398
+ /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3399
+ "ALERTS ",
3400
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3401
+ "\u2014 notify on a signal spike (",
3402
+ alerts.length,
3403
+ ")"
3404
+ ] })
3405
+ ] }),
3406
+ alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3407
+ /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
3408
+ onOff(rule.enabled),
3409
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
3410
+ /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
3411
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(rule), cols - 26) })
3412
+ ] }, rule.id)),
3413
+ inWindow({ kind: "alert-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3414
+ /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add" }) }),
3415
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add alert (enter \u2014 one channel: slack url, email or telegram chat id)" })
3416
+ ] }) : null
3417
+ ] })
3291
3418
  ] })
3292
3419
  ] }) });
3293
3420
  }
3294
3421
 
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 });
3337
- setLogin({ phase: "done", msg: `\u2713 added ${res.project || res.user || "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 });
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.project || a.user || "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.project || a.user || "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.project || 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.user && a.project ? truncate(a.user, 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
3422
  // src/self-update.ts
3427
3423
  import { execFile, spawn as spawn3 } from "child_process";
3428
3424
  import { mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync4, realpathSync, writeFileSync as writeFileSync5 } from "fs";
@@ -3545,15 +3541,15 @@ async function tuiUpdateFlow(onStatus) {
3545
3541
  }
3546
3542
 
3547
3543
  // src/tui/App.tsx
3548
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3544
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3549
3545
  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: [
3546
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3547
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
3548
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
3549
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
3550
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
3555
3551
  "press ",
3556
- /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
3552
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
3557
3553
  " to go live"
3558
3554
  ] }) })
3559
3555
  ] });
@@ -3564,33 +3560,32 @@ var SECTIONS = [
3564
3560
  { label: "Rate Limit", Panel: RateLimitPanel },
3565
3561
  { label: "DLP", Panel: DlpPanel },
3566
3562
  { label: "Audit", Panel: AuditPanel },
3567
- { label: "Settings", Panel: SettingsPanel },
3568
- { label: "Accounts", Panel: AccountsPanel }
3563
+ { label: "Settings", Panel: SettingsPanel }
3569
3564
  ];
3570
3565
  var BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
3571
3566
  function Banner({ cols }) {
3572
3567
  const wide = cols >= 82;
3573
3568
  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" })
3569
+ return /* @__PURE__ */ jsxs8(Box8, { children: [
3570
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
3571
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
3577
3572
  ] });
3578
3573
  }
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" })
3574
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3575
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
3576
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
3582
3577
  ] });
3583
3578
  }
3584
- var ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
3579
+ var SETTINGS_SECTION = SECTIONS.findIndex((s) => s.label === "Settings");
3585
3580
  function App() {
3586
3581
  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" });
3582
+ const [accounts, setAccounts] = useState8(() => listAccounts());
3583
+ const [viewKey, setViewKey] = useState8(() => accounts[0]?.apiKey);
3584
+ const [viewNonce, setViewNonce] = useState8(0);
3585
+ const [section, setSection] = useState8(accounts.length === 0 ? SETTINGS_SECTION : 0);
3586
+ const [focus, setFocus] = useState8("nav");
3587
+ const [help, setHelp] = useState8(false);
3588
+ const [update2, setUpdate] = useState8({ kind: "idle" });
3594
3589
  useEffect7(() => {
3595
3590
  let alive = true;
3596
3591
  const run = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
@@ -3603,15 +3598,16 @@ function App() {
3603
3598
  }, []);
3604
3599
  const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
3605
3600
  const cur = accounts[acctIdx];
3606
- const acctLabel = cur ? cur.project || cur.user || `account \u2026${cur.apiKey.slice(-4)}` : "not logged in";
3601
+ const acctLabel2 = cur ? cur.email || cur.user || cur.project || `account \u2026${cur.apiKey.slice(-4)}` : "not logged in";
3607
3602
  useEffect7(() => {
3608
- if (!cur || cur.project || cur.user) return;
3603
+ if (!cur || cur.email) return;
3609
3604
  let alive = true;
3610
3605
  api.auth.me().then((r) => {
3611
- const project = r.project?.name || void 0;
3612
- const user = r.user?.name || r.user?.email || void 0;
3613
- if (!alive || !project && !user) return;
3614
- saveAccount({ ...cur, project, user });
3606
+ const project = r.project?.name || cur.project;
3607
+ const user = r.user?.name || cur.user;
3608
+ const email = r.user?.email || void 0;
3609
+ if (!alive || !project && !user && !email) return;
3610
+ saveAccount({ ...cur, project, user, email });
3615
3611
  setAccounts(listAccounts());
3616
3612
  }).catch(() => {
3617
3613
  });
@@ -3630,8 +3626,8 @@ function App() {
3630
3626
  viewAccount(accounts[(acctIdx + 1) % accounts.length]);
3631
3627
  };
3632
3628
  const locked = accounts.length === 0;
3633
- const effectiveSection = locked ? ACCOUNTS_SECTION : section;
3634
- useInput8((input, key) => {
3629
+ const effectiveSection = locked ? SETTINGS_SECTION : section;
3630
+ useInput7((input, key) => {
3635
3631
  if (help) {
3636
3632
  setHelp(false);
3637
3633
  return;
@@ -3659,29 +3655,29 @@ function App() {
3659
3655
  });
3660
3656
  const { cols, rows } = useTermSize();
3661
3657
  const current = SECTIONS[effectiveSection];
3662
- if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
3658
+ if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
3663
3659
  if (current.label === "Live" && focus === "panel") {
3664
- return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
3660
+ return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
3665
3661
  }
3666
3662
  const Panel = current.Panel;
3667
- 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" });
3668
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3669
- /* @__PURE__ */ jsx9(Banner, { cols }),
3670
- /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
3671
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
3672
- /* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel }),
3673
- 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" }),
3674
- 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
3663
+ 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" });
3664
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3665
+ /* @__PURE__ */ jsx8(Banner, { cols }),
3666
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
3667
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
3668
+ /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
3669
+ 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" }),
3670
+ 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
3675
3671
  ] }),
3676
- /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
3677
- /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
3672
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
3673
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
3678
3674
  const isCur = i === effectiveSection;
3679
- const disabled = locked && s.label !== "Accounts";
3680
- 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);
3675
+ const disabled = locked && s.label !== "Settings";
3676
+ 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);
3681
3677
  }) }),
3682
- /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
3678
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
3683
3679
  ] }, viewNonce),
3684
- /* @__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"]] }) })
3680
+ /* @__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"]] }) })
3685
3681
  ] });
3686
3682
  }
3687
3683
  var HELP = [
@@ -3694,21 +3690,21 @@ var HELP = [
3694
3690
  ["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"]]]
3695
3691
  ];
3696
3692
  function HelpOverlay({ cols, rows }) {
3697
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3698
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3699
- /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
3700
- /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
3701
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
3702
- /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3703
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
3693
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3694
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3695
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
3696
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
3697
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
3698
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3699
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
3704
3700
  ] }, k))
3705
3701
  ] }, group)) }),
3706
- /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
3702
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
3707
3703
  ] });
3708
3704
  }
3709
3705
 
3710
3706
  // src/tui/index.tsx
3711
- import { jsx as jsx10 } from "react/jsx-runtime";
3707
+ import { jsx as jsx9 } from "react/jsx-runtime";
3712
3708
  async function launchTui() {
3713
3709
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
3714
3710
  process.stderr.write(
@@ -3733,7 +3729,7 @@ async function launchTui() {
3733
3729
  console.info = toFile("info");
3734
3730
  console.debug = toFile("debug");
3735
3731
  try {
3736
- const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}), { patchConsole: false });
3732
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}), { patchConsole: false });
3737
3733
  await waitUntilExit();
3738
3734
  } finally {
3739
3735
  Object.assign(console, saved);