@solongate/proxy 0.81.56 → 0.81.58

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.
Files changed (3) hide show
  1. package/dist/index.js +146 -136
  2. package/dist/tui/index.js +146 -136
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7719,7 +7719,7 @@ function useTermSize() {
7719
7719
  function usePanelSize() {
7720
7720
  const { cols, rows } = useTermSize();
7721
7721
  const wide = cols >= 82;
7722
- const shellRows = wide ? 12 : 7;
7722
+ const shellRows = wide ? 16 : 10;
7723
7723
  return { cols: Math.max(30, cols - 22), rows: Math.max(8, rows - shellRows) };
7724
7724
  }
7725
7725
  var init_hooks = __esm({
@@ -10169,19 +10169,19 @@ function SettingsPanel({
10169
10169
  abort.current = false;
10170
10170
  setLogin({ phase: "starting" });
10171
10171
  void (async () => {
10172
- let start2;
10172
+ let start;
10173
10173
  try {
10174
- start2 = await startDeviceLogin(DEFAULT_API_URL2);
10174
+ start = await startDeviceLogin(DEFAULT_API_URL2);
10175
10175
  } catch (e) {
10176
10176
  setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
10177
10177
  return;
10178
10178
  }
10179
- setLogin({ phase: "waiting", url: start2.verifyUrl });
10180
- openBrowser(start2.verifyUrl);
10181
- while (Date.now() < start2.expiresAt && !abort.current) {
10182
- 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));
10183
10183
  if (abort.current) return;
10184
- const res = await pollDeviceLogin(DEFAULT_API_URL2, start2.deviceCode);
10184
+ const res = await pollDeviceLogin(DEFAULT_API_URL2, start.deviceCode);
10185
10185
  if (res.status === "approved" && res.apiKey) {
10186
10186
  saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL2, project: res.project, user: res.user, email: res.email });
10187
10187
  setLogin({ phase: "done", msg: `\u2713 added ${res.email || res.user || res.project || "account"}` });
@@ -10475,143 +10475,148 @@ function SettingsPanel({
10475
10475
  }
10476
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
10477
  const error = locked ? null : localQ.error ?? whQ.error ?? alertQ.error ?? guardQ.error ?? selfQ.error;
10478
- const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
10479
10478
  const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
10480
10479
  const chanOf = (rule) => [...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
10481
- const listBudget = Math.max(4, rows - 12);
10482
- const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
10483
- const inWindow = (r) => {
10484
- const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
10485
- return i >= start && i < start + listBudget;
10486
- };
10487
- return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
10488
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter act \xB7 m toggle \xB7 t test \xB7 e edit \xB7 x remove acct \xB7 d delete \xB7 n add acct \xB7 r refresh" : "press \u2192 to configure" }),
10489
- editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
10490
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
10491
- /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
10492
- ] }) : 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: " " }),
10493
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10494
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10495
- "ACCOUNTS ",
10496
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10497
- "\u2014 on this device (",
10498
- accounts.length,
10499
- ") \xB7 \u25CF viewing \xB7 ACTIVE = key the guard uses \xB7 x removes"
10500
- ] })
10501
- ] }),
10502
- accounts.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { children: [
10503
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " no accounts yet \u2014 " }),
10504
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: "n" }),
10505
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " (or enter) logs in, pairs this device and unlocks the dataroom" })
10506
- ] }) : null,
10507
- accounts.filter((acc) => inWindow({ kind: "acct", acc })).map((a) => {
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;
10508
10486
  const isView = viewApiKey ? a.apiKey === viewApiKey : accounts[0]?.apiKey === a.apiKey;
10509
10487
  const isActive = isActiveAccount(a.apiKey);
10510
10488
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10511
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "acct:" + a.apiKey && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct", acc: a }) }),
10489
+ cursor(r),
10512
10490
  /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(acctLabel(a), 30).padEnd(31) }),
10513
10491
  isView ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
10514
10492
  isActive ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
10515
10493
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: a.project ? truncate2(a.project, 20) : "" })
10516
- ] }, a.apiKey);
10517
- }),
10518
- inWindow({ kind: "acct-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10519
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "acct-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct-add" }) }),
10520
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
10521
- ] }) : null
10522
- ] }),
10523
- locked ? null : /* @__PURE__ */ jsxs7(Fragment5, { children: [
10524
- inWindow({ kind: "guard" }) || inWindow({ kind: "self" }) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10525
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10526
- "PROTECTION ",
10527
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 the guard hook enforcing every layer on this project" })
10528
- ] }),
10529
- inWindow({ kind: "guard" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10530
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "guard" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "guard" }) }),
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),
10531
10504
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
10532
10505
  guard ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
10533
10506
  /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
10534
10507
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available` }),
10535
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` : ""}` })
10536
10509
  ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading\u2026" })
10537
- ] }) : null,
10538
- inWindow({ kind: "self" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10539
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "self" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "self" }) }),
10510
+ ] });
10511
+ case "self":
10512
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10513
+ cursor(r),
10540
10514
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "self-prot".padEnd(11) }),
10541
10515
  selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2026" }),
10542
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " blocks agents from editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
10543
- ] }) : null
10544
- ] }) : null,
10545
- inWindow({ kind: "ll-enabled" }) || inWindow({ kind: "ll-path" }) || inWindow({ kind: "ll-server" }) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10546
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10547
- "LOCAL LOGS ",
10548
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on this machine" })
10549
- ] }),
10550
- inWindow({ kind: "ll-enabled" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10551
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-enabled" }) }),
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),
10552
10521
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(11) }),
10553
10522
  onOff(!!local?.enabled),
10554
10523
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
10555
- ] }) : null,
10556
- inWindow({ kind: "ll-path" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10557
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-path" }) }),
10524
+ ] });
10525
+ case "ll-path":
10526
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10527
+ cursor(r),
10558
10528
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(11) }),
10559
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" })
10560
- ] }) : null,
10561
- inWindow({ kind: "ll-server" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10562
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-server" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-server" }) }),
10530
+ ] });
10531
+ case "ll-server":
10532
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10533
+ cursor(r),
10563
10534
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "dashboard".padEnd(11) }),
10564
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" }),
10565
- /* @__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" })
10566
- ] }) : null
10567
- ] }) : null,
10568
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10569
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10570
- "WEBHOOKS ",
10571
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10572
- "\u2014 POST every matching event to your endpoint (",
10573
- webhooks.length,
10574
- ") \xB7 t tests"
10575
- ] })
10576
- ] }),
10577
- webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10578
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
10579
- onOff(wh.enabled),
10580
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
10581
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(wh.url, cols - 16) })
10582
- ] }, wh.id)),
10583
- inWindow({ kind: "wh-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10584
- /* @__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),
10585
10548
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
10586
- ] }) : null
10587
- ] }),
10588
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
10589
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
10590
- "ALERTS ",
10591
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
10592
- "\u2014 spike alerts to one email + one telegram (",
10593
- alerts.length,
10594
- ") \xB7 enter edits \xB7 t tests"
10595
- ] })
10596
- ] }),
10597
- alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10598
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
10599
- onOff(rule.enabled),
10600
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + SIGNAL_LABEL[rule.signal]).padEnd(13) }),
10601
- /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${winLabel(rule.windowSeconds)} `.padEnd(11) }),
10602
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate2(chanOf(rule), cols - 34) })
10603
- ] }, rule.id)),
10604
- inWindow({ kind: "alert-add-email" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10605
- /* @__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),
10606
10561
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add email alert (enter)" })
10607
- ] }) : null,
10608
- inWindow({ kind: "alert-add-tg" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
10609
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add-tg" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add-tg" }) }),
10562
+ ] });
10563
+ case "alert-add-tg":
10564
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10565
+ cursor(r),
10610
10566
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id from @userinfobot)" })
10611
- ] }) : null,
10612
- hasEmailAlert && hasTgAlert ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }) : null
10613
- ] })
10614
- ] })
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
+ if (lastSec) {
10585
+ lineEls.push(/* @__PURE__ */ jsx7(Text7, { children: " " }, "sp:" + sec));
10586
+ lineKey.push("");
10587
+ }
10588
+ lastSec = sec;
10589
+ lineEls.push(
10590
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
10591
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: sec }),
10592
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 " + SECTION_DESC[sec] })
10593
+ ] }, "h:" + sec)
10594
+ );
10595
+ lineKey.push("");
10596
+ }
10597
+ lineEls.push(/* @__PURE__ */ jsx7(Box7, { children: rowLine(r) }, keyOf(r)));
10598
+ lineKey.push(keyOf(r));
10599
+ });
10600
+ if (hasEmailAlert && hasTgAlert) {
10601
+ lineEls.push(
10602
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }, "both-note")
10603
+ );
10604
+ lineKey.push("");
10605
+ }
10606
+ const budget = Math.max(4, rows - 2);
10607
+ const selLine = Math.max(0, lineKey.indexOf(keyOf(cur)));
10608
+ const maxStart = Math.max(0, lineEls.length - budget);
10609
+ const startL = Math.min(Math.max(0, selLine - Math.floor(budget / 2)), maxStart);
10610
+ const win = lineEls.slice(startL, startL + budget);
10611
+ const moreAbove = startL;
10612
+ const moreBelow = Math.max(0, lineEls.length - (startL + budget));
10613
+ return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
10614
+ /* @__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" }),
10615
+ editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
10616
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
10617
+ /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
10618
+ ] }) : 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: " " }),
10619
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
10615
10620
  ] }) });
10616
10621
  }
10617
10622
  var EVENTS, SPIN2, SIGNALS2, SIGNAL_LABEL, WINDOWS, winLabel, THRESH_MIN, THRESH_MAX, nearestWindowIdx, EDITOR_FIELDS, acctLabel, alertChannel;
@@ -10757,24 +10762,29 @@ function App() {
10757
10762
  }
10758
10763
  const Panel = current.Panel;
10759
10764
  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" });
10760
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
10761
- /* @__PURE__ */ jsx8(Banner, { cols }),
10762
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
10763
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
10764
- /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
10765
- 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" }),
10766
- 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
10767
- ] }),
10768
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
10769
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
10770
- const isCur = i === effectiveSection;
10771
- const disabled = locked && s.label !== "Settings";
10772
- 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);
10773
- }) }),
10774
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
10775
- ] }, viewNonce),
10776
- /* @__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"]] }) })
10777
- ] });
10765
+ return (
10766
+ // height rows-1 (not rows): once total output reaches stdout.rows ink stops
10767
+ // diffing and clearTerminal-repaints every frame, which slides the banner.
10768
+ // overflow hidden makes it a hard guarantee even if a panel over-renders.
10769
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, paddingTop: 1, overflow: "hidden", children: [
10770
+ /* @__PURE__ */ jsx8(Banner, { cols }),
10771
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
10772
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
10773
+ /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
10774
+ 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" }),
10775
+ 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
10776
+ ] }),
10777
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
10778
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
10779
+ const isCur = i === effectiveSection;
10780
+ const disabled = locked && s.label !== "Settings";
10781
+ 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);
10782
+ }) }),
10783
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
10784
+ ] }, viewNonce),
10785
+ /* @__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"]] }) })
10786
+ ] })
10787
+ );
10778
10788
  }
10779
10789
  function HelpOverlay({ cols, rows }) {
10780
10790
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
package/dist/tui/index.js CHANGED
@@ -817,7 +817,7 @@ function useTermSize() {
817
817
  function usePanelSize() {
818
818
  const { cols, rows } = useTermSize();
819
819
  const wide = cols >= 82;
820
- const shellRows = wide ? 12 : 7;
820
+ const shellRows = wide ? 16 : 10;
821
821
  return { cols: Math.max(30, cols - 22), rows: Math.max(8, rows - shellRows) };
822
822
  }
823
823
 
@@ -3296,19 +3296,19 @@ function SettingsPanel({
3296
3296
  abort.current = false;
3297
3297
  setLogin({ phase: "starting" });
3298
3298
  void (async () => {
3299
- let start2;
3299
+ let start;
3300
3300
  try {
3301
- start2 = await startDeviceLogin(DEFAULT_API_URL);
3301
+ start = await startDeviceLogin(DEFAULT_API_URL);
3302
3302
  } catch (e) {
3303
3303
  setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
3304
3304
  return;
3305
3305
  }
3306
- setLogin({ phase: "waiting", url: start2.verifyUrl });
3307
- openBrowser(start2.verifyUrl);
3308
- while (Date.now() < start2.expiresAt && !abort.current) {
3309
- await new Promise((r) => setTimeout(r, start2.intervalMs));
3306
+ setLogin({ phase: "waiting", url: start.verifyUrl });
3307
+ openBrowser(start.verifyUrl);
3308
+ while (Date.now() < start.expiresAt && !abort.current) {
3309
+ await new Promise((r) => setTimeout(r, start.intervalMs));
3310
3310
  if (abort.current) return;
3311
- const res = await pollDeviceLogin(DEFAULT_API_URL, start2.deviceCode);
3311
+ const res = await pollDeviceLogin(DEFAULT_API_URL, start.deviceCode);
3312
3312
  if (res.status === "approved" && res.apiKey) {
3313
3313
  saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user, email: res.email });
3314
3314
  setLogin({ phase: "done", msg: `\u2713 added ${res.email || res.user || res.project || "account"}` });
@@ -3602,143 +3602,148 @@ function SettingsPanel({
3602
3602
  }
3603
3603
  const loading = !locked && (localQ.loading && !localQ.data || whQ.loading && !whQ.data || alertQ.loading && !alertQ.data || guardQ.loading && !guardQ.data || selfQ.loading && !selfQ.data);
3604
3604
  const error = locked ? null : localQ.error ?? whQ.error ?? alertQ.error ?? guardQ.error ?? selfQ.error;
3605
- const mark = (r) => keyOf(r) === keyOf(cur) && focused ? "\u25B8 " : " ";
3606
3605
  const onOff = (on) => on ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "on " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "off" });
3607
3606
  const chanOf = (rule) => [...rule.emails ?? [], ...(rule.telegram ?? []).map((t) => "tg " + t)].join(", ") || "\u2014";
3608
- const listBudget = Math.max(4, rows - 12);
3609
- const start = Math.min(Math.max(0, selClamped - Math.floor(listBudget / 2)), Math.max(0, rowsAll.length - listBudget));
3610
- const inWindow = (r) => {
3611
- const i = rowsAll.findIndex((x) => keyOf(x) === keyOf(r));
3612
- return i >= start && i < start + listBudget;
3613
- };
3614
- return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3615
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: focused ? "\u2191\u2193 move \xB7 enter act \xB7 m toggle \xB7 t test \xB7 e edit \xB7 x remove acct \xB7 d delete \xB7 n add acct \xB7 r refresh" : "press \u2192 to configure" }),
3616
- editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
3617
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
3618
- /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
3619
- ] }) : 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: " " }),
3620
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3621
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3622
- "ACCOUNTS ",
3623
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3624
- "\u2014 on this device (",
3625
- accounts.length,
3626
- ") \xB7 \u25CF viewing \xB7 ACTIVE = key the guard uses \xB7 x removes"
3627
- ] })
3628
- ] }),
3629
- accounts.length === 0 ? /* @__PURE__ */ jsxs7(Text7, { children: [
3630
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " no accounts yet \u2014 " }),
3631
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: "n" }),
3632
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " (or enter) logs in, pairs this device and unlocks the dataroom" })
3633
- ] }) : null,
3634
- accounts.filter((acc) => inWindow({ kind: "acct", acc })).map((a) => {
3607
+ const isCur = (r) => keyOf(r) === keyOf(cur) && focused;
3608
+ const cursor = (r) => /* @__PURE__ */ jsx7(Text7, { color: isCur(r) ? theme.accentBright : theme.dim, children: isCur(r) ? "\u25B8 " : " " });
3609
+ const rowLine = (r) => {
3610
+ switch (r.kind) {
3611
+ case "acct": {
3612
+ const a = r.acc;
3635
3613
  const isView = viewApiKey ? a.apiKey === viewApiKey : accounts[0]?.apiKey === a.apiKey;
3636
3614
  const isActive = isActiveAccount(a.apiKey);
3637
3615
  return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3638
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "acct:" + a.apiKey && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct", acc: a }) }),
3616
+ cursor(r),
3639
3617
  /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(acctLabel(a), 30).padEnd(31) }),
3640
3618
  isView ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
3641
3619
  isActive ? /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " " }),
3642
3620
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: a.project ? truncate(a.project, 20) : "" })
3643
- ] }, a.apiKey);
3644
- }),
3645
- inWindow({ kind: "acct-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3646
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "acct-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "acct-add" }) }),
3647
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
3648
- ] }) : null
3649
- ] }),
3650
- locked ? null : /* @__PURE__ */ jsxs7(Fragment5, { children: [
3651
- inWindow({ kind: "guard" }) || inWindow({ kind: "self" }) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3652
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3653
- "PROTECTION ",
3654
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 the guard hook enforcing every layer on this project" })
3655
- ] }),
3656
- inWindow({ kind: "guard" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3657
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "guard" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "guard" }) }),
3621
+ ] });
3622
+ }
3623
+ case "acct-add":
3624
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3625
+ cursor(r),
3626
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add account (enter \u2014 device login in the browser)" })
3627
+ ] });
3628
+ case "guard":
3629
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3630
+ cursor(r),
3658
3631
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "guard".padEnd(11) }),
3659
3632
  guard ? /* @__PURE__ */ jsxs7(Fragment5, { children: [
3660
3633
  /* @__PURE__ */ jsx7(Text7, { color: guard.up_to_date ? theme.ok : theme.warn, children: `v${guard.installed ?? "?"}` }),
3661
3634
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: guard.up_to_date ? " (latest)" : ` \u2192 v${guard.latest} available` }),
3662
3635
  /* @__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` : ""}` })
3663
3636
  ] }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "loading\u2026" })
3664
- ] }) : null,
3665
- inWindow({ kind: "self" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3666
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "self" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "self" }) }),
3637
+ ] });
3638
+ case "self":
3639
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3640
+ cursor(r),
3667
3641
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "self-prot".padEnd(11) }),
3668
3642
  selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2026" }),
3669
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " blocks agents from editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
3670
- ] }) : null
3671
- ] }) : null,
3672
- inWindow({ kind: "ll-enabled" }) || inWindow({ kind: "ll-path" }) || inWindow({ kind: "ll-server" }) ? /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3673
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3674
- "LOCAL LOGS ",
3675
- /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "\u2014 hooks mirror every decision to a file on this machine" })
3676
- ] }),
3677
- inWindow({ kind: "ll-enabled" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3678
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-enabled" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-enabled" }) }),
3643
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
3644
+ ] });
3645
+ case "ll-enabled":
3646
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3647
+ cursor(r),
3679
3648
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "enabled ".padEnd(11) }),
3680
3649
  onOff(!!local?.enabled),
3681
3650
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " enter toggles" })
3682
- ] }) : null,
3683
- inWindow({ kind: "ll-path" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3684
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-path" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-path" }) }),
3651
+ ] });
3652
+ case "ll-path":
3653
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3654
+ cursor(r),
3685
3655
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "path ".padEnd(11) }),
3686
3656
  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" })
3687
- ] }) : null,
3688
- inWindow({ kind: "ll-server" }) ? /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3689
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "ll-server" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "ll-server" }) }),
3657
+ ] });
3658
+ case "ll-server":
3659
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3660
+ cursor(r),
3690
3661
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "dashboard".padEnd(11) }),
3691
3662
  srv.running ? /* @__PURE__ */ jsx7(Text7, { color: theme.ok, children: `running 127.0.0.1:${srv.port}` }) : /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "stopped" }),
3692
- /* @__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" })
3693
- ] }) : null
3694
- ] }) : null,
3695
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3696
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3697
- "WEBHOOKS ",
3698
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3699
- "\u2014 POST every matching event to your endpoint (",
3700
- webhooks.length,
3701
- ") \xB7 t tests"
3702
- ] })
3703
- ] }),
3704
- webhooks.filter((wh) => inWindow({ kind: "wh", wh })).map((wh) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3705
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "wh:" + wh.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh", wh }) }),
3706
- onOff(wh.enabled),
3707
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + wh.events).padEnd(9) }),
3708
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(wh.url, cols - 16) })
3709
- ] }, wh.id)),
3710
- inWindow({ kind: "wh-add" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3711
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "wh-add" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "wh-add" }) }),
3663
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: srv.running ? " survives closing the dataroom \xB7 enter stops" : " enter starts the dashboard local-logs link" })
3664
+ ] });
3665
+ case "wh":
3666
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3667
+ cursor(r),
3668
+ onOff(r.wh.enabled),
3669
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + r.wh.events).padEnd(9) }),
3670
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(r.wh.url, cols - 16) })
3671
+ ] });
3672
+ case "wh-add":
3673
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3674
+ cursor(r),
3712
3675
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add webhook (enter)" })
3713
- ] }) : null
3714
- ] }),
3715
- /* @__PURE__ */ jsxs7(Box7, { marginTop: 1, flexDirection: "column", children: [
3716
- /* @__PURE__ */ jsxs7(Text7, { bold: true, color: theme.accentBright, children: [
3717
- "ALERTS ",
3718
- /* @__PURE__ */ jsxs7(Text7, { color: theme.dim, children: [
3719
- "\u2014 spike alerts to one email + one telegram (",
3720
- alerts.length,
3721
- ") \xB7 enter edits \xB7 t tests"
3722
- ] })
3723
- ] }),
3724
- alerts.filter((rule) => inWindow({ kind: "alert", rule })).map((rule) => /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3725
- /* @__PURE__ */ jsx7(Text7, { color: keyOf(cur) === "alert:" + rule.id && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert", rule }) }),
3726
- onOff(rule.enabled),
3727
- /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + SIGNAL_LABEL[rule.signal]).padEnd(13) }),
3728
- /* @__PURE__ */ jsx7(Text7, { children: `\u2265${rule.threshold}/${winLabel(rule.windowSeconds)} `.padEnd(11) }),
3729
- /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(rule), cols - 34) })
3730
- ] }, rule.id)),
3731
- inWindow({ kind: "alert-add-email" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3732
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add-email" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add-email" }) }),
3676
+ ] });
3677
+ case "alert":
3678
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3679
+ cursor(r),
3680
+ onOff(r.rule.enabled),
3681
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: (" " + SIGNAL_LABEL[r.rule.signal]).padEnd(13) }),
3682
+ /* @__PURE__ */ jsx7(Text7, { children: `\u2265${r.rule.threshold}/${winLabel(r.rule.windowSeconds)} `.padEnd(11) }),
3683
+ /* @__PURE__ */ jsx7(Text7, { color: theme.accent, children: truncate(chanOf(r.rule), cols - 34) })
3684
+ ] });
3685
+ case "alert-add-email":
3686
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3687
+ cursor(r),
3733
3688
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add email alert (enter)" })
3734
- ] }) : null,
3735
- inWindow({ kind: "alert-add-tg" }) ? /* @__PURE__ */ jsxs7(Text7, { children: [
3736
- /* @__PURE__ */ jsx7(Text7, { color: cur.kind === "alert-add-tg" && focused ? theme.accentBright : theme.dim, children: mark({ kind: "alert-add-tg" }) }),
3689
+ ] });
3690
+ case "alert-add-tg":
3691
+ return /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3692
+ cursor(r),
3737
3693
  /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: "+ add telegram alert (enter \u2014 numeric chat id from @userinfobot)" })
3738
- ] }) : null,
3739
- hasEmailAlert && hasTgAlert ? /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }) : null
3740
- ] })
3741
- ] })
3694
+ ] });
3695
+ }
3696
+ };
3697
+ 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";
3698
+ const SECTION_DESC = {
3699
+ ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
3700
+ PROTECTION: "guard hook version + self-protection toggle",
3701
+ "LOCAL LOGS": "mirror every decision to a file + dashboard link",
3702
+ WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
3703
+ ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 t tests`
3704
+ };
3705
+ const lineEls = [];
3706
+ const lineKey = [];
3707
+ let lastSec = "";
3708
+ rowsAll.forEach((r) => {
3709
+ const sec = sectionOf(r);
3710
+ if (sec !== lastSec) {
3711
+ if (lastSec) {
3712
+ lineEls.push(/* @__PURE__ */ jsx7(Text7, { children: " " }, "sp:" + sec));
3713
+ lineKey.push("");
3714
+ }
3715
+ lastSec = sec;
3716
+ lineEls.push(
3717
+ /* @__PURE__ */ jsxs7(Text7, { wrap: "truncate", children: [
3718
+ /* @__PURE__ */ jsx7(Text7, { bold: true, color: theme.accentBright, children: sec }),
3719
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " \u2014 " + SECTION_DESC[sec] })
3720
+ ] }, "h:" + sec)
3721
+ );
3722
+ lineKey.push("");
3723
+ }
3724
+ lineEls.push(/* @__PURE__ */ jsx7(Box7, { children: rowLine(r) }, keyOf(r)));
3725
+ lineKey.push(keyOf(r));
3726
+ });
3727
+ if (hasEmailAlert && hasTgAlert) {
3728
+ lineEls.push(
3729
+ /* @__PURE__ */ jsx7(Text7, { color: theme.dim, children: " both channels in use \u2014 remove one (d) to change it" }, "both-note")
3730
+ );
3731
+ lineKey.push("");
3732
+ }
3733
+ const budget = Math.max(4, rows - 2);
3734
+ const selLine = Math.max(0, lineKey.indexOf(keyOf(cur)));
3735
+ const maxStart = Math.max(0, lineEls.length - budget);
3736
+ const startL = Math.min(Math.max(0, selLine - Math.floor(budget / 2)), maxStart);
3737
+ const win = lineEls.slice(startL, startL + budget);
3738
+ const moreAbove = startL;
3739
+ const moreBelow = Math.max(0, lineEls.length - (startL + budget));
3740
+ return /* @__PURE__ */ jsx7(DataView, { loading, error, children: /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
3741
+ /* @__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" }),
3742
+ editing ? /* @__PURE__ */ jsxs7(Box7, { children: [
3743
+ /* @__PURE__ */ jsx7(Text7, { color: theme.warn, children: editing === "path" ? "local log path: " : "webhook url: " }),
3744
+ /* @__PURE__ */ jsx7(TextInput5, { value: input, onChange: setInput, onSubmit: submitInput })
3745
+ ] }) : msg ? /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, cols) }) : login?.msg ? /* @__PURE__ */ jsx7(Text7, { wrap: "truncate", color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx7(Text7, { children: " " }),
3746
+ /* @__PURE__ */ jsx7(Box7, { flexDirection: "column", height: budget, overflow: "hidden", children: win })
3742
3747
  ] }) });
3743
3748
  }
3744
3749
 
@@ -3984,24 +3989,29 @@ function App() {
3984
3989
  }
3985
3990
  const Panel = current.Panel;
3986
3991
  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" });
3987
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3988
- /* @__PURE__ */ jsx8(Banner, { cols }),
3989
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
3990
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
3991
- /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
3992
- 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" }),
3993
- 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
3994
- ] }),
3995
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
3996
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
3997
- const isCur = i === effectiveSection;
3998
- const disabled = locked && s.label !== "Settings";
3999
- 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);
4000
- }) }),
4001
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
4002
- ] }, viewNonce),
4003
- /* @__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"]] }) })
4004
- ] });
3992
+ return (
3993
+ // height rows-1 (not rows): once total output reaches stdout.rows ink stops
3994
+ // diffing and clearTerminal-repaints every frame, which slides the banner.
3995
+ // overflow hidden makes it a hard guarantee even if a panel over-renders.
3996
+ /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows - 1, paddingX: 1, paddingTop: 1, overflow: "hidden", children: [
3997
+ /* @__PURE__ */ jsx8(Banner, { cols }),
3998
+ /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
3999
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
4000
+ /* @__PURE__ */ jsx8(Text8, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel2 }),
4001
+ 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" }),
4002
+ 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
4003
+ ] }),
4004
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
4005
+ /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
4006
+ const isCur = i === effectiveSection;
4007
+ const disabled = locked && s.label !== "Settings";
4008
+ 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);
4009
+ }) }),
4010
+ /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, overflow: "hidden", children: panelBody })
4011
+ ] }, viewNonce),
4012
+ /* @__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"]] }) })
4013
+ ] })
4014
+ );
4005
4015
  }
4006
4016
  var HELP = [
4007
4017
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.56",
3
+ "version": "0.81.58",
4
4
  "description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
5
5
  "type": "module",
6
6
  "bin": {