@solongate/proxy 0.81.55 → 0.81.57

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/index.js CHANGED
@@ -7086,6 +7086,20 @@ function saveAccount(acc) {
7086
7086
  } catch {
7087
7087
  }
7088
7088
  }
7089
+ function removeAccount(apiKey) {
7090
+ try {
7091
+ const list6 = (() => {
7092
+ try {
7093
+ const raw = JSON.parse(readFileSync8(accountsFile(), "utf-8"));
7094
+ return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
7095
+ } catch {
7096
+ return [];
7097
+ }
7098
+ })();
7099
+ writeFileSync6(accountsFile(), JSON.stringify(list6.filter((a) => a.apiKey !== apiKey), null, 2));
7100
+ } catch {
7101
+ }
7102
+ }
7089
7103
  function setViewCredentials(creds) {
7090
7104
  viewOverride = creds;
7091
7105
  cached2 = null;
@@ -7394,11 +7408,13 @@ __export(settings_exports, {
7394
7408
  getSecurityLayers: () => getSecurityLayers,
7395
7409
  getSelfProtection: () => getSelfProtection,
7396
7410
  getWebhooks: () => getWebhooks,
7411
+ sendTestAlert: () => sendTestAlert,
7397
7412
  sendTestWebhook: () => sendTestWebhook,
7398
7413
  setAlertEnabled: () => setAlertEnabled,
7399
7414
  setLocalLogs: () => setLocalLogs,
7400
7415
  setSecurityLayers: () => setSecurityLayers,
7401
7416
  setSelfProtection: () => setSelfProtection,
7417
+ updateAlert: () => updateAlert,
7402
7418
  updateWebhook: () => updateWebhook
7403
7419
  });
7404
7420
  function getSecurityLayers() {
@@ -7440,6 +7456,12 @@ function deleteAlert(id) {
7440
7456
  function setAlertEnabled(id, enabled) {
7441
7457
  return request("PATCH", "/settings/denial-alerts", { query: { id }, body: { enabled } });
7442
7458
  }
7459
+ function updateAlert(id, patch) {
7460
+ return request("PATCH", "/settings/denial-alerts", { query: { id }, body: patch });
7461
+ }
7462
+ function sendTestAlert(id) {
7463
+ return request("POST", "/settings/denial-alerts/send-test", { body: { id } });
7464
+ }
7443
7465
  function getWebhooks() {
7444
7466
  return request("GET", "/settings/denial-webhook");
7445
7467
  }
@@ -10076,6 +10098,7 @@ function SettingsPanel({
10076
10098
  const [confirmDel, setConfirmDel] = useState7(null);
10077
10099
  const [msg, setMsg] = useState7(null);
10078
10100
  const [busy, setBusy] = useState7(false);
10101
+ const [editor, setEditor] = useState7(null);
10079
10102
  const [accounts, setAccounts] = useState7(() => listAccounts());
10080
10103
  const [login, setLogin] = useState7(null);
10081
10104
  const [tick, setTick] = useState7(0);
@@ -10095,22 +10118,30 @@ function SettingsPanel({
10095
10118
  const localQ = useLoader(() => listAccounts().length ? api.settings.getLocalLogs() : Promise.resolve(null));
10096
10119
  const whQ = useLoader(() => listAccounts().length ? api.settings.getWebhooks() : Promise.resolve(null));
10097
10120
  const alertQ = useLoader(() => listAccounts().length ? api.settings.getAlerts() : Promise.resolve(null));
10121
+ const guardQ = useLoader(() => listAccounts().length ? api.settings.getGuardStatus() : Promise.resolve(null));
10122
+ const selfQ = useLoader(() => listAccounts().length ? api.settings.getSelfProtection() : Promise.resolve(null));
10098
10123
  const local = localQ.data;
10124
+ const guard = guardQ.data;
10125
+ const selfProt = selfQ.data;
10099
10126
  const [hidden, setHidden] = useState7(/* @__PURE__ */ new Set());
10100
10127
  const webhooks = (whQ.data?.webhooks ?? []).filter((w) => !hidden.has("wh:" + w.id));
10101
10128
  const alerts = (alertQ.data?.rules ?? []).filter((r) => !hidden.has("alert:" + r.id));
10129
+ const hasEmailAlert = alerts.some((r) => alertChannel(r) === "email");
10130
+ const hasTgAlert = alerts.some((r) => alertChannel(r) === "telegram");
10102
10131
  const rowsAll = [
10103
10132
  ...accounts.map((acc) => ({ kind: "acct", acc })),
10104
10133
  { kind: "acct-add" },
10105
10134
  ...locked ? [] : [
10135
+ { kind: "guard" },
10136
+ { kind: "self" },
10106
10137
  { kind: "ll-enabled" },
10107
10138
  { kind: "ll-path" },
10108
10139
  { kind: "ll-server" },
10109
10140
  ...webhooks.map((wh) => ({ kind: "wh", wh })),
10110
10141
  { kind: "wh-add" },
10111
10142
  ...alerts.map((rule) => ({ kind: "alert", rule })),
10112
- { kind: "alert-add-email" },
10113
- { kind: "alert-add-tg" }
10143
+ ...hasEmailAlert ? [] : [{ kind: "alert-add-email" }],
10144
+ ...hasTgAlert ? [] : [{ kind: "alert-add-tg" }]
10114
10145
  ]
10115
10146
  ];
10116
10147
  const selClamped = Math.min(sel, rowsAll.length - 1);
@@ -10121,6 +10152,8 @@ function SettingsPanel({
10121
10152
  localQ.reload();
10122
10153
  whQ.reload();
10123
10154
  alertQ.reload();
10155
+ guardQ.reload();
10156
+ selfQ.reload();
10124
10157
  };
10125
10158
  const run12 = (label, fn, reload) => {
10126
10159
  if (busy) return;
@@ -10136,19 +10169,19 @@ function SettingsPanel({
10136
10169
  abort.current = false;
10137
10170
  setLogin({ phase: "starting" });
10138
10171
  void (async () => {
10139
- let start2;
10172
+ let start;
10140
10173
  try {
10141
- start2 = await startDeviceLogin(DEFAULT_API_URL2);
10174
+ start = await startDeviceLogin(DEFAULT_API_URL2);
10142
10175
  } catch (e) {
10143
10176
  setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
10144
10177
  return;
10145
10178
  }
10146
- setLogin({ phase: "waiting", url: start2.verifyUrl });
10147
- openBrowser(start2.verifyUrl);
10148
- while (Date.now() < start2.expiresAt && !abort.current) {
10149
- await new Promise((r) => setTimeout(r, start2.intervalMs));
10179
+ setLogin({ phase: "waiting", url: start.verifyUrl });
10180
+ openBrowser(start.verifyUrl);
10181
+ while (Date.now() < start.expiresAt && !abort.current) {
10182
+ await new Promise((r) => setTimeout(r, start.intervalMs));
10150
10183
  if (abort.current) return;
10151
- const res = await pollDeviceLogin(DEFAULT_API_URL2, start2.deviceCode);
10184
+ const res = await pollDeviceLogin(DEFAULT_API_URL2, start.deviceCode);
10152
10185
  if (res.status === "approved" && res.apiKey) {
10153
10186
  saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL2, project: res.project, user: res.user, email: res.email });
10154
10187
  setLogin({ phase: "done", msg: `\u2713 added ${res.email || res.user || res.project || "account"}` });
@@ -10165,6 +10198,57 @@ function SettingsPanel({
10165
10198
  if (!abort.current) setLogin({ phase: "error", msg: "timed out \u2014 press n to retry" });
10166
10199
  })();
10167
10200
  };
10201
+ const openAlertEditor = (channel, rule) => {
10202
+ setEditor({
10203
+ id: rule?.id,
10204
+ channel,
10205
+ target: (channel === "email" ? rule?.emails?.[0] : rule?.telegram?.[0]) ?? "",
10206
+ signal: rule?.signal ?? "any",
10207
+ threshold: rule?.threshold ?? THRESH_MIN,
10208
+ windowSeconds: rule?.windowSeconds ?? 300,
10209
+ field: rule ? 1 : 0
10210
+ // new rule starts on the target; existing on signal
10211
+ });
10212
+ if (!rule) {
10213
+ setInput("");
10214
+ setEditing("alert-target");
10215
+ }
10216
+ };
10217
+ const normTarget = (channel, raw) => {
10218
+ const v = raw.trim();
10219
+ if (channel === "email") {
10220
+ const email = v.replace(/^mailto:/i, "").replace(/^["'<]+|["'>]+$/g, "").replace(/[.,;:]+$/, "").replace(/\s+/g, "");
10221
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return { value: email, error: "that does not look like an e-mail address" };
10222
+ return { value: email };
10223
+ }
10224
+ const tg = v.replace(/^@/, "").replace(/^(chat[\s_-]*)?id[:=\s]*/i, "").replace(/\s+/g, "");
10225
+ if (!/^-?\d{5,}$/.test(tg)) return { value: tg, error: "a telegram chat id is a number \u2014 get yours from @userinfobot" };
10226
+ return { value: tg };
10227
+ };
10228
+ const saveEditor = () => {
10229
+ if (!editor) return;
10230
+ const t = normTarget(editor.channel, editor.target);
10231
+ if (t.error) {
10232
+ setMsg({ text: "\u2717 " + t.error, level: "bad" });
10233
+ return;
10234
+ }
10235
+ const channels = editor.channel === "email" ? { emails: [t.value], telegram: [] } : { telegram: [t.value], emails: [] };
10236
+ const body = { signal: editor.signal, threshold: editor.threshold, windowSeconds: editor.windowSeconds, ...channels };
10237
+ const id = editor.id;
10238
+ setEditor(null);
10239
+ if (id) run12("alert updated", () => api.settings.updateAlert(id, body), alertQ.reload);
10240
+ else run12(`${editor.channel} alert added`, () => api.settings.createAlert({ name: "dataroom alert", ...body }), alertQ.reload);
10241
+ };
10242
+ const testAlert = (rule) => {
10243
+ run12(
10244
+ "alert test sent \u2014 check your " + (alertChannel(rule) ?? "channel"),
10245
+ () => api.settings.sendTestAlert(rule.id).then((r) => {
10246
+ if (!r.delivered.telegram && !r.delivered.email) throw new Error("no channel on this rule");
10247
+ }),
10248
+ () => {
10249
+ }
10250
+ );
10251
+ };
10168
10252
  const activate = (r) => {
10169
10253
  if (r.kind === "acct") {
10170
10254
  setViewCredentials({ apiKey: r.acc.apiKey, apiUrl: r.acc.apiUrl });
@@ -10172,6 +10256,9 @@ function SettingsPanel({
10172
10256
  setMsg({ text: `viewing ${acctLabel(r.acc)}`, level: "ok" });
10173
10257
  } else if (r.kind === "acct-add") {
10174
10258
  beginLogin();
10259
+ } else if (r.kind === "self") {
10260
+ if (!selfProt) return;
10261
+ run12(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
10175
10262
  } else if (r.kind === "ll-enabled") {
10176
10263
  if (!local) return;
10177
10264
  if (!local.enabled && !local.path.trim()) {
@@ -10194,13 +10281,12 @@ function SettingsPanel({
10194
10281
  setInput("");
10195
10282
  setEditing("wh-url");
10196
10283
  } else if (r.kind === "alert") {
10197
- run12(r.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(r.rule.id, !r.rule.enabled), alertQ.reload);
10284
+ const ch = alertChannel(r.rule);
10285
+ if (ch) openAlertEditor(ch, r.rule);
10198
10286
  } else if (r.kind === "alert-add-email") {
10199
- setInput("");
10200
- setEditing("alert-email");
10201
- } else {
10202
- setInput("");
10203
- setEditing("alert-tg");
10287
+ openAlertEditor("email");
10288
+ } else if (r.kind === "alert-add-tg") {
10289
+ openAlertEditor("telegram");
10204
10290
  }
10205
10291
  };
10206
10292
  const submitInput = () => {
@@ -10218,22 +10304,8 @@ function SettingsPanel({
10218
10304
  return;
10219
10305
  }
10220
10306
  run12("webhook added", () => api.settings.createWebhook({ url, events: "denials" }), whQ.reload);
10221
- } else if (which === "alert-email") {
10222
- if (!v) return;
10223
- const email = v.replace(/^mailto:/i, "").replace(/^["'<]+|["'>]+$/g, "").replace(/[.,;:]+$/, "").replace(/\s+/g, "").trim();
10224
- if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
10225
- setMsg({ text: "\u2717 that does not look like an e-mail address", level: "bad" });
10226
- return;
10227
- }
10228
- run12("email alert added", () => api.settings.createAlert({ ...ALERT_BASE, emails: [email] }), alertQ.reload);
10229
- } else if (which === "alert-tg") {
10230
- if (!v) return;
10231
- const tg = v.replace(/^@/, "").replace(/^(chat[\s_-]*)?id[:=\s]*/i, "").replace(/\s+/g, "").trim();
10232
- if (!/^-?\d{5,}$/.test(tg)) {
10233
- setMsg({ text: "\u2717 a telegram chat id is a number \u2014 get yours from @userinfobot", level: "bad" });
10234
- return;
10235
- }
10236
- run12("telegram alert added", () => api.settings.createAlert({ ...ALERT_BASE, telegram: [tg] }), alertQ.reload);
10307
+ } else if (which === "alert-target" && editor) {
10308
+ setEditor({ ...editor, target: v, field: 1 });
10237
10309
  }
10238
10310
  };
10239
10311
  useInput6(
@@ -10245,6 +10317,35 @@ function SettingsPanel({
10245
10317
  }
10246
10318
  return;
10247
10319
  }
10320
+ if (editor) {
10321
+ if (key.escape) {
10322
+ setEditor(null);
10323
+ return;
10324
+ }
10325
+ if (inp === "e" || key.return && editor.field === 0) {
10326
+ setInput(editor.target);
10327
+ setEditing("alert-target");
10328
+ return;
10329
+ }
10330
+ if (inp === "t" && editor.id) {
10331
+ const rule = alerts.find((a) => a.id === editor.id);
10332
+ if (rule) testAlert(rule);
10333
+ return;
10334
+ }
10335
+ if (key.return) {
10336
+ saveEditor();
10337
+ return;
10338
+ }
10339
+ if (key.upArrow) setEditor({ ...editor, field: (editor.field + EDITOR_FIELDS - 1) % EDITOR_FIELDS });
10340
+ else if (key.downArrow) setEditor({ ...editor, field: (editor.field + 1) % EDITOR_FIELDS });
10341
+ else if (key.leftArrow || key.rightArrow) {
10342
+ const d = key.rightArrow ? 1 : -1;
10343
+ if (editor.field === 1) setEditor({ ...editor, signal: SIGNALS2[(SIGNALS2.indexOf(editor.signal) + d + SIGNALS2.length) % SIGNALS2.length] });
10344
+ else if (editor.field === 2) setEditor({ ...editor, threshold: Math.max(THRESH_MIN, Math.min(THRESH_MAX, editor.threshold + d * 5)) });
10345
+ else if (editor.field === 3) setEditor({ ...editor, windowSeconds: WINDOWS[Math.max(0, Math.min(WINDOWS.length - 1, nearestWindowIdx(editor.windowSeconds) + d))] });
10346
+ }
10347
+ return;
10348
+ }
10248
10349
  setMsg(null);
10249
10350
  if (key.upArrow) {
10250
10351
  setSel((n) => Math.max(0, n - 1));
@@ -10258,28 +10359,41 @@ function SettingsPanel({
10258
10359
  const ok = setActiveAccount({ apiKey: cur.acc.apiKey, apiUrl: cur.acc.apiUrl });
10259
10360
  setMsg(ok ? { text: `\u2713 ${acctLabel(cur.acc)} is now the ACTIVE key (guard + logging)`, level: "ok" } : { text: "\u2717 could not set active", level: "bad" });
10260
10361
  refreshAccounts();
10261
- } else if (cur.kind === "wh" || cur.kind === "alert" || cur.kind === "ll-enabled" || cur.kind === "ll-server") activate(cur);
10362
+ } else if (cur.kind === "wh" || cur.kind === "alert" || cur.kind === "self" || cur.kind === "ll-enabled" || cur.kind === "ll-server") {
10363
+ if (cur.kind === "alert") run12(cur.rule.enabled ? "alert disabled" : "alert enabled", () => api.settings.setAlertEnabled(cur.rule.id, !cur.rule.enabled), alertQ.reload);
10364
+ else activate(cur);
10365
+ }
10366
+ } else if (inp === "x" && cur.kind === "acct") {
10367
+ const k = "acct:" + cur.acc.apiKey;
10368
+ if (confirmDel !== k) {
10369
+ setConfirmDel(k);
10370
+ setMsg({ text: `press x again to remove ${acctLabel(cur.acc)} from this device (the cloud account is untouched)`, level: "bad" });
10371
+ return;
10372
+ }
10373
+ setConfirmDel(null);
10374
+ removeAccount(cur.acc.apiKey);
10375
+ setMsg({ text: `\u2713 removed ${acctLabel(cur.acc)} from this device`, level: "ok" });
10376
+ refreshAccounts();
10262
10377
  } else if (inp === "t" && cur.kind === "wh") {
10263
- run12(
10264
- "test event sent \u2014 check your endpoint",
10265
- () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
10266
- if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx response)");
10267
- }),
10268
- whQ.reload
10269
- );
10270
- } else if (inp === "n") beginLogin();
10378
+ run12("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
10379
+ if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
10380
+ }), whQ.reload);
10381
+ } else if (inp === "t" && cur.kind === "alert") {
10382
+ testAlert(cur.rule);
10383
+ } else if (inp === "e" && cur.kind === "ll-path") activate(cur);
10271
10384
  else if (inp === "e" && cur.kind === "wh") {
10272
10385
  const next = EVENTS[(EVENTS.indexOf(cur.wh.events) + 1) % EVENTS.length];
10273
10386
  run12(`webhook events \u2192 ${next}`, () => api.settings.updateWebhook(cur.wh.id, { events: next }), whQ.reload);
10274
- } else if (inp === "e" && cur.kind === "ll-path") activate(cur);
10387
+ } else if (inp === "e" && cur.kind === "alert") {
10388
+ const ch = alertChannel(cur.rule);
10389
+ if (ch) openAlertEditor(ch, cur.rule);
10390
+ } else if (inp === "n") beginLogin();
10275
10391
  else if (inp === "a") {
10276
10392
  if (cur.kind === "wh" || cur.kind === "wh-add") {
10277
10393
  setInput("");
10278
10394
  setEditing("wh-url");
10279
- } else if (cur.kind === "alert" || cur.kind === "alert-add-email" || cur.kind === "alert-add-tg") {
10280
- setInput("");
10281
- setEditing("alert-email");
10282
- }
10395
+ } else if (cur.kind === "alert-add-email") openAlertEditor("email");
10396
+ else if (cur.kind === "alert-add-tg") openAlertEditor("telegram");
10283
10397
  } else if (inp === "d" && (cur.kind === "wh" || cur.kind === "alert")) {
10284
10398
  const k = keyOf(cur);
10285
10399
  if (confirmDel !== k) {
@@ -10319,126 +10433,189 @@ function SettingsPanel({
10319
10433
  ] })
10320
10434
  ] });
10321
10435
  }
10322
- const loading = !locked && (localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data);
10323
- const error = locked ? null : localQ.error ?? whQ.error ?? alertQ.error;
10324
- const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
10325
- const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
10326
- const chanOf = (rule) => [...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
10327
- const listBudget = Math.max(4, rows - 10);
10328
- const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
10329
- const inWindow = (r) => {
10330
- const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
10331
- return i >= start && i < start + listBudget;
10332
- };
10333
- return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
10334
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter/m act \xB7 t test webhook \xB7 n add account \xB7 e events \xB7 d delete \xB7 r refresh" : "press \u2192 to configure" }),
10335
- editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
10336
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : editing === "wh-url" ? "webhook url: " : editing === "alert-email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
10337
- /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
10338
- ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx7(Text7, { color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
10339
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10436
+ if (editor) {
10437
+ const fieldRow = (idx, label, value, hint) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10438
+ /* @__PURE__ */ jsx7(Text7, { color: editor.field === idx ? theme.accentBright : theme.dim, children: editor.field === idx ? "\u25B8 " : " " }),
10439
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: label.padEnd(11) }),
10440
+ value,
10441
+ editor.field === idx && hint ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " + hint }) : null
10442
+ ] });
10443
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
10340
10444
  /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10341
- "ACCOUNTS ",
10342
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10343
- "\u2014 logged in on this device (",
10344
- accounts.length,
10345
- ") \xB7 \u25CF viewing = shown here \xB7 ACTIVE = key the guard uses"
10346
- ] })
10445
+ editor.id ? "Edit" : "New",
10446
+ " ",
10447
+ editor.channel,
10448
+ " alert"
10347
10449
  ] }),
10348
- accounts.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { children: [
10349
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " no accounts yet \u2014 " }),
10350
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: "n" }),
10351
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " (or enter) logs in, pairs this device and unlocks the dataroom" })
10352
- ] }) : null,
10353
- accounts.filter((acc) => inWindow({ kind: "acct", acc })).map((a) => {
10450
+ /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10451
+ "\u2191\u2193 field \xB7 \u2190\u2192 change \xB7 e edit target \xB7 enter save",
10452
+ editor.id ? " \xB7 t test" : "",
10453
+ " \xB7 esc cancel"
10454
+ ] }),
10455
+ editing === "alert-target" ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, children: [
10456
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editor.channel === "email" ? "e-mail address: " : "telegram chat id (from @userinfobot): " }),
10457
+ /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
10458
+ ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
10459
+ /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10460
+ fieldRow(0, "target", editor.target ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(editor.target, cols - 16) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "empty \u2014 press e" }), "press e to edit"),
10461
+ fieldRow(1, "signal", /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }), "\u2190\u2192 change"),
10462
+ fieldRow(2, "threshold", /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: `\u2265 ${editor.threshold}` }), "\u2190\u2192 \xB15"),
10463
+ fieldRow(3, "window", /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: winLabel(editor.windowSeconds) }), "\u2190\u2192 change")
10464
+ ] }),
10465
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10466
+ "fires when ",
10467
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: SIGNAL_LABEL[editor.signal] }),
10468
+ " reach ",
10469
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: editor.threshold }),
10470
+ " within",
10471
+ " ",
10472
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: winLabel(editor.windowSeconds) })
10473
+ ] }) })
10474
+ ] });
10475
+ }
10476
+ const loading = !locked && (localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data || guardQ.loading && !guardQ.data || selfQ.loading && !selfQ.data);
10477
+ const error = locked ? null : localQ.error ?? whQ.error ?? alertQ.error ?? guardQ.error ?? selfQ.error;
10478
+ const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
10479
+ const chanOf = (rule) => [...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
10480
+ const isCur = (r) => keyOf(r) === keyOf(cur) && focused;
10481
+ const cursor = (r) => /* @__PURE__ */ jsx7(Text7, { color: isCur(r) ? theme.accentBright : theme.dim, children: isCur(r) ? "\u25B8 " : " " });
10482
+ const rowLine = (r) => {
10483
+ switch (r.kind) {
10484
+ case "acct": {
10485
+ const a = r.acc;
10354
10486
  const isView = viewApiKey ? a.apiKey === viewApiKey : accounts[0]?.apiKey === a.apiKey;
10355
10487
  const isActive = isActiveAccount(a.apiKey);
10356
10488
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10357
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "acct:" + a.apiKey && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct", acc: a }) }),
10489
+ cursor(r),
10358
10490
  /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(acctLabel(a), 30).padEnd(31) }),
10359
10491
  isView ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
10360
10492
  isActive ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
10361
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: a.project ? truncate2(a.project, 24) : "" })
10362
- ] }, a.apiKey);
10363
- }),
10364
- inWindow({ kind: "acct-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10365
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "acct-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct-add" }) }),
10366
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
10367
- ] }) : null
10368
- ] }),
10369
- locked ? null : /* @__PURE__ */ jsxs7(Fragment5, { children: [
10370
- inWindow({ kind: "ll-enabled" }) || inWindow({ kind: "ll-path" }) || inWindow({ kind: "ll-server" }) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10371
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10372
- "LOCAL LOGS ",
10373
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on the agent machine" })
10374
- ] }),
10375
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10376
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-enabled" }) }),
10377
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(10) }),
10493
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: a.project ? truncate2(a.project, 20) : "" })
10494
+ ] });
10495
+ }
10496
+ case "acct-add":
10497
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10498
+ cursor(r),
10499
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
10500
+ ] });
10501
+ case "guard":
10502
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10503
+ cursor(r),
10504
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
10505
+ guard ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
10506
+ /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
10507
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available` }),
10508
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: ` \xB7 ${guard.device_count} device${guard.device_count === 1 ? "" : "s"}${guard.outdated_count ? ` \xB7 ${guard.outdated_count} outdated` : ""}` })
10509
+ ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading\u2026" })
10510
+ ] });
10511
+ case "self":
10512
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10513
+ cursor(r),
10514
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "self-prot".padEnd(11) }),
10515
+ selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2026" }),
10516
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
10517
+ ] });
10518
+ case "ll-enabled":
10519
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10520
+ cursor(r),
10521
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(11) }),
10378
10522
  onOff(!!local?.enabled),
10379
10523
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
10380
- ] }),
10381
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10382
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-path" }) }),
10383
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(10) }),
10524
+ ] });
10525
+ case "ll-path":
10526
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10527
+ cursor(r),
10528
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(11) }),
10384
10529
  local?.path ? /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(local.path, cols - 14) }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "not set \u2014 enter to edit" })
10385
- ] }),
10386
- /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10387
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-server" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-server" }) }),
10388
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "dashboard".padEnd(10) }),
10530
+ ] });
10531
+ case "ll-server":
10532
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10533
+ cursor(r),
10534
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "dashboard".padEnd(11) }),
10389
10535
  srv.running ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: `running 127.0.0.1:${srv.port}` }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "stopped" }),
10390
- /* @__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" })
10391
- ] })
10392
- ] }) : null,
10393
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10394
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10395
- "WEBHOOKS ",
10396
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10397
- "\u2014 POST every matching event to your endpoint (",
10398
- webhooks.length,
10399
- ")"
10400
- ] })
10401
- ] }),
10402
- webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10403
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
10404
- onOff(wh.enabled),
10405
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
10406
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(wh.url, cols - 16) })
10407
- ] }, wh.id)),
10408
- inWindow({ kind: "wh-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10409
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
10536
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: srv.running ? " survives closing the dataroom \xB7 enter stops" : " enter starts the dashboard local-logs link" })
10537
+ ] });
10538
+ case "wh":
10539
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10540
+ cursor(r),
10541
+ onOff(r.wh.enabled),
10542
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + r.wh.events).padEnd(9) }),
10543
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(r.wh.url, cols - 16) })
10544
+ ] });
10545
+ case "wh-add":
10546
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10547
+ cursor(r),
10410
10548
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
10411
- ] }) : null
10412
- ] }),
10413
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10414
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10415
- "ALERTS ",
10416
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10417
- "\u2014 notify on a signal spike (",
10418
- alerts.length,
10419
- ")"
10420
- ] })
10421
- ] }),
10422
- alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10423
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
10424
- onOff(rule.enabled),
10425
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + rule.signal).padEnd(11) }),
10426
- /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${rule.windowSeconds}s ` }),
10427
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(chanOf(rule), cols - 26) })
10428
- ] }, rule.id)),
10429
- inWindow({ kind: "alert-add-email" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10430
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add-email" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add-email" }) }),
10549
+ ] });
10550
+ case "alert":
10551
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10552
+ cursor(r),
10553
+ onOff(r.rule.enabled),
10554
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + SIGNAL_LABEL[r.rule.signal]).padEnd(13) }),
10555
+ /* @__PURE__ */ jsx7(Text7, { children: `\u2265${r.rule.threshold}/${winLabel(r.rule.windowSeconds)} `.padEnd(11) }),
10556
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(chanOf(r.rule), cols - 34) })
10557
+ ] });
10558
+ case "alert-add-email":
10559
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10560
+ cursor(r),
10431
10561
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add email alert (enter)" })
10432
- ] }) : null,
10433
- inWindow({ kind: "alert-add-tg" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10434
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add-tg" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add-tg" }) }),
10435
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id, get it from @userinfobot)" })
10436
- ] }) : null
10437
- ] })
10438
- ] })
10562
+ ] });
10563
+ case "alert-add-tg":
10564
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10565
+ cursor(r),
10566
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id from @userinfobot)" })
10567
+ ] });
10568
+ }
10569
+ };
10570
+ const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
10571
+ const SECTION_DESC = {
10572
+ ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
10573
+ PROTECTION: "guard hook version + self-protection toggle",
10574
+ "LOCAL LOGS": "mirror every decision to a file + dashboard link",
10575
+ WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
10576
+ ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 t tests`
10577
+ };
10578
+ const lineEls = [];
10579
+ const lineKey = [];
10580
+ let lastSec = "";
10581
+ rowsAll.forEach((r) => {
10582
+ const sec = sectionOf(r);
10583
+ if (sec !== lastSec) {
10584
+ lastSec = sec;
10585
+ lineEls.push(
10586
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10587
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: sec }),
10588
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 " + SECTION_DESC[sec] })
10589
+ ] }, "h:" + sec)
10590
+ );
10591
+ lineKey.push("");
10592
+ }
10593
+ lineEls.push(/* @__PURE__ */ jsx7(Box7, { children: rowLine(r) }, keyOf(r)));
10594
+ lineKey.push(keyOf(r));
10595
+ });
10596
+ if (hasEmailAlert && hasTgAlert) {
10597
+ lineEls.push(
10598
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }, "both-note")
10599
+ );
10600
+ lineKey.push("");
10601
+ }
10602
+ const budget = Math.max(4, rows - 2);
10603
+ const selLine = Math.max(0, lineKey.indexOf(keyOf(cur)));
10604
+ const maxStart = Math.max(0, lineEls.length - budget);
10605
+ const startL = Math.min(Math.max(0, selLine - Math.floor(budget / 2)), maxStart);
10606
+ const win = lineEls.slice(startL, startL + budget);
10607
+ const moreAbove = startL;
10608
+ const moreBelow = Math.max(0, lineEls.length - (startL + budget));
10609
+ return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
10610
+ /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", color: theme.dim, children: focused ? `\u2191\u2193 move \xB7 enter act \xB7 m toggle \xB7 t test \xB7 e edit \xB7 x remove \xB7 d delete \xB7 n add${moreAbove ? ` \xB7 \u25B2${moreAbove}` : ""}${moreBelow ? ` \xB7 \u25BC${moreBelow}` : ""}` : "press \u2192 to configure" }),
10611
+ editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
10612
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
10613
+ /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
10614
+ ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
10615
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
10439
10616
  ] }) });
10440
10617
  }
10441
- var EVENTS, SPIN2, ALERT_BASE, acctLabel;
10618
+ var EVENTS, SPIN2, SIGNALS2, SIGNAL_LABEL, WINDOWS, winLabel, THRESH_MIN, THRESH_MAX, nearestWindowIdx, EDITOR_FIELDS, acctLabel, alertChannel;
10442
10619
  var init_Settings = __esm({
10443
10620
  "src/tui/panels/Settings.tsx"() {
10444
10621
  "use strict";
@@ -10451,8 +10628,20 @@ var init_Settings = __esm({
10451
10628
  init_theme();
10452
10629
  EVENTS = ["denials", "allowed", "all"];
10453
10630
  SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
10454
- ALERT_BASE = { name: "dataroom alert", threshold: 5, windowSeconds: 60, signal: "any" };
10631
+ SIGNALS2 = ["any", "deny", "dlp", "ratelimit"];
10632
+ SIGNAL_LABEL = { any: "any signal", deny: "denials", dlp: "dlp secrets", ratelimit: "rate-limit" };
10633
+ WINDOWS = [30, 60, 300, 3600, 86400];
10634
+ winLabel = (s) => s < 60 ? `${s}s` : s < 3600 ? `${s / 60}m` : s < 86400 ? `${s / 3600}h` : `${s / 86400}d`;
10635
+ THRESH_MIN = 5;
10636
+ THRESH_MAX = 100;
10637
+ nearestWindowIdx = (s) => {
10638
+ let best = 0;
10639
+ for (let i = 1; i < WINDOWS.length; i++) if (Math.abs(WINDOWS[i] - s) < Math.abs(WINDOWS[best] - s)) best = i;
10640
+ return best;
10641
+ };
10642
+ EDITOR_FIELDS = 4;
10455
10643
  acctLabel = (a) => a.email || a.user || "account \u2026" + a.apiKey.slice(-4);
10644
+ alertChannel = (r) => r.emails && r.emails.length ? "email" : r.telegram && r.telegram.length ? "telegram" : null;
10456
10645
  }
10457
10646
  });
10458
10647
 
@@ -10569,24 +10758,29 @@ function App() {
10569
10758
  }
10570
10759
  const Panel = current.Panel;
10571
10760
  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" });
10572
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
10573
- /* @__PURE__ */ jsx8(Banner, { cols }),
10574
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
10575
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
10576
- /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
10577
- 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" }),
10578
- 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
10579
- ] }),
10580
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
10581
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
10582
- const isCur = i === effectiveSection;
10583
- const disabled = locked && s.label !== "Settings";
10584
- 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);
10585
- }) }),
10586
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
10587
- ] }, viewNonce),
10588
- /* @__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"]] }) })
10589
- ] });
10761
+ return (
10762
+ // height rows-1 (not rows): once total output reaches stdout.rows ink stops
10763
+ // diffing and clearTerminal-repaints every frame, which slides the banner.
10764
+ // overflow hidden makes it a hard guarantee even if a panel over-renders.
10765
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, paddingTop: 1, overflow: "hidden", children: [
10766
+ /* @__PURE__ */ jsx8(Banner, { cols }),
10767
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
10768
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
10769
+ /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
10770
+ 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" }),
10771
+ 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
10772
+ ] }),
10773
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
10774
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
10775
+ const isCur = i === effectiveSection;
10776
+ const disabled = locked && s.label !== "Settings";
10777
+ 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);
10778
+ }) }),
10779
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
10780
+ ] }, viewNonce),
10781
+ /* @__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"]] }) })
10782
+ ] })
10783
+ );
10590
10784
  }
10591
10785
  function HelpOverlay({ cols, rows }) {
10592
10786
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [