@solongate/proxy 0.81.70 → 0.81.72

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.
@@ -26,6 +26,14 @@ export declare function isActiveAccount(apiKey: string): boolean;
26
26
  * account. Best-effort; returns false on failure.
27
27
  */
28
28
  export declare function setActiveAccount(creds: Credentials): boolean;
29
+ /**
30
+ * Clear the ACTIVE login credential — a local sign-out. Removes the key/url from
31
+ * the active-key file so `listAccounts` no longer re-seeds it (the source of the
32
+ * "ghost account …xxxx" that lingers after removing the account you're logged in
33
+ * as). The guard hooks then have NO key until the next `solongate login`, so this
34
+ * only runs when the user removes their LAST account. Best-effort; false on error.
35
+ */
36
+ export declare function clearActiveCredential(): boolean;
29
37
  /**
30
38
  * Thrown for any non-2xx response. `code`/`message` come from the API's
31
39
  * `{ error: { code, message } }` envelope when present; `status` is the HTTP
package/dist/index.js CHANGED
@@ -6946,11 +6946,12 @@ function PaneTitle({ label, extra, width: width2 }) {
6946
6946
  /* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width2 - used)) })
6947
6947
  ] });
6948
6948
  }
6949
- function StreamLine({ e, loc, selected }) {
6949
+ function StreamLine({ e, loc, selected, date }) {
6950
6950
  return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
6951
6951
  /* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
6952
6952
  /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
6953
6953
  "[",
6954
+ date ? ymd(e.at) + " " : "",
6954
6955
  hhmmss(e.at),
6955
6956
  " "
6956
6957
  ] }),
@@ -7001,7 +7002,7 @@ function KeyHints({ hints }) {
7001
7002
  i < hints.length - 1 ? " " : ""
7002
7003
  ] }, i)) });
7003
7004
  }
7004
- var hhmmss;
7005
+ var hhmmss, ymd;
7005
7006
  var init_components = __esm({
7006
7007
  "src/tui/components.tsx"() {
7007
7008
  "use strict";
@@ -7010,6 +7011,12 @@ var init_components = __esm({
7010
7011
  const d = new Date(ts);
7011
7012
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
7012
7013
  };
7014
+ ymd = (ts) => {
7015
+ const d = new Date(ts);
7016
+ if (Number.isNaN(d.getTime())) return "----------";
7017
+ const p = (n) => String(n).padStart(2, "0");
7018
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
7019
+ };
7013
7020
  }
7014
7021
  });
7015
7022
 
@@ -7168,6 +7175,28 @@ function setActiveAccount(creds) {
7168
7175
  return false;
7169
7176
  }
7170
7177
  }
7178
+ function clearActiveCredential() {
7179
+ try {
7180
+ const p = join8(homedir6(), ".solongate", ["cloud", "guard.json"].join("-"));
7181
+ if (!existsSync3(p)) {
7182
+ cached2 = null;
7183
+ return true;
7184
+ }
7185
+ let existing = {};
7186
+ try {
7187
+ existing = JSON.parse(readFileSync8(p, "utf-8"));
7188
+ } catch {
7189
+ }
7190
+ delete existing.apiKey;
7191
+ delete existing.apiUrl;
7192
+ writeFileSync6(p, JSON.stringify(existing, null, 2));
7193
+ cached2 = null;
7194
+ viewOverride = null;
7195
+ return true;
7196
+ } catch {
7197
+ return false;
7198
+ }
7199
+ }
7171
7200
  function loginCredentialFile() {
7172
7201
  try {
7173
7202
  const p = join8(homedir6(), ".solongate", "cloud-guard.json");
@@ -9951,7 +9980,7 @@ function AuditPanel({ active: active2, focused }) {
9951
9980
  )
9952
9981
  ] }) : null,
9953
9982
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
9954
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused }, e.id)) }),
9983
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
9955
9984
  pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
9956
9985
  "(no entries",
9957
9986
  source === "local" ? " in the local file" : "",
@@ -10315,7 +10344,8 @@ function SettingsPanel({
10315
10344
  active: active2,
10316
10345
  focused,
10317
10346
  viewApiKey,
10318
- onView
10347
+ onView,
10348
+ onAccountsChanged
10319
10349
  }) {
10320
10350
  void active2;
10321
10351
  const { cols, rows } = usePanelSize();
@@ -10589,16 +10619,31 @@ function SettingsPanel({
10589
10619
  else activate(cur);
10590
10620
  }
10591
10621
  } else if (inp === "x" && cur.kind === "acct") {
10592
- const k = "acct:" + cur.acc.apiKey;
10622
+ const target = cur.acc;
10623
+ const k = "acct:" + target.apiKey;
10624
+ const wasActive = isActiveAccount(target.apiKey);
10625
+ const others = accounts.filter((a) => a.apiKey !== target.apiKey);
10593
10626
  if (confirmDel !== k) {
10594
10627
  setConfirmDel(k);
10595
- setMsg({ text: `press x again to remove ${acctLabel(cur.acc)} from this device (the cloud account is untouched)`, level: "bad" });
10628
+ const note = wasActive ? others.length ? ` \u2014 the ACTIVE guard key; ${acctLabel(others[0])} takes over` : " \u2014 your ONLY account & the active guard key; this signs the device out (guard has no key until you log in again)" : " (the cloud account is untouched)";
10629
+ setMsg({ text: `press x again to remove ${acctLabel(target)} from this device${note}`, level: "bad" });
10596
10630
  return;
10597
10631
  }
10598
10632
  setConfirmDel(null);
10599
- removeAccount(cur.acc.apiKey);
10600
- setMsg({ text: `\u2713 removed ${acctLabel(cur.acc)} from this device`, level: "ok" });
10633
+ removeAccount(target.apiKey);
10634
+ let extra = " from this device";
10635
+ if (wasActive) {
10636
+ if (others.length) {
10637
+ setActiveAccount({ apiKey: others[0].apiKey, apiUrl: others[0].apiUrl });
10638
+ extra = ` \xB7 ${acctLabel(others[0])} is now the active key`;
10639
+ } else {
10640
+ clearActiveCredential();
10641
+ extra = " \xB7 signed out of this device";
10642
+ }
10643
+ }
10644
+ setMsg({ text: `\u2713 removed ${acctLabel(target)}${extra}`, level: "ok" });
10601
10645
  refreshAccounts();
10646
+ onAccountsChanged?.();
10602
10647
  } else if (inp === "t" && cur.kind === "wh") {
10603
10648
  run12("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
10604
10649
  if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
@@ -10953,6 +10998,14 @@ function App() {
10953
10998
  if (accounts.length < 2) return;
10954
10999
  viewAccount(accounts[(acctIdx + 1) % accounts.length]);
10955
11000
  };
11001
+ const syncAccounts = () => {
11002
+ const list6 = listAccounts();
11003
+ setAccounts(list6);
11004
+ const next = list6.find((a) => a.apiKey === viewKey) ?? list6[0];
11005
+ setViewCredentials(next ? { apiKey: next.apiKey, apiUrl: next.apiUrl } : null);
11006
+ setViewKey(next?.apiKey);
11007
+ setViewNonce((n) => n + 1);
11008
+ };
10956
11009
  const locked = accounts.length === 0;
10957
11010
  const effectiveSection = locked ? SETTINGS_SECTION : section;
10958
11011
  useInput7((input, key) => {
@@ -10988,7 +11041,7 @@ function App() {
10988
11041
  return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
10989
11042
  }
10990
11043
  const Panel = current.Panel;
10991
- 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" });
11044
+ const panelBody = current.label === "Settings" ? /* @__PURE__ */ jsx8(SettingsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount, onAccountsChanged: syncAccounts }) : /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
10992
11045
  return (
10993
11046
  // height rows-1 (not rows): once total output reaches stdout.rows ink stops
10994
11047
  // diffing and clearTerminal-repaints every frame, which slides the banner.
@@ -1,6 +1,8 @@
1
1
  import type { ReactNode } from 'react';
2
2
  /** HH:MM:SS for a timestamp (ms or ISO); `--:--:--` when unparseable. */
3
3
  export declare const hhmmss: (ts: string | number) => string;
4
+ /** YYYY-MM-DD for a timestamp; `----------` when unparseable. */
5
+ export declare const ymd: (ts: string | number) => string;
4
6
  /** Section divider title used across the Live/Audit consoles. */
5
7
  export declare function PaneTitle({ label, extra, width }: {
6
8
  label: string;
@@ -25,10 +27,11 @@ export interface StreamRow {
25
27
  evalMs?: number | null;
26
28
  rule?: string | null;
27
29
  }
28
- export declare function StreamLine({ e, loc, selected }: {
30
+ export declare function StreamLine({ e, loc, selected, date }: {
29
31
  e: StreamRow;
30
32
  loc: boolean;
31
33
  selected?: boolean;
34
+ date?: boolean;
32
35
  }): JSX.Element;
33
36
  export declare function Panel({ title, subtitle, children }: {
34
37
  title: string;
package/dist/tui/index.js CHANGED
@@ -129,6 +129,12 @@ var hhmmss = (ts) => {
129
129
  const d = new Date(ts);
130
130
  return Number.isNaN(d.getTime()) ? "--:--:--" : d.toTimeString().slice(0, 8);
131
131
  };
132
+ var ymd = (ts) => {
133
+ const d = new Date(ts);
134
+ if (Number.isNaN(d.getTime())) return "----------";
135
+ const p = (n) => String(n).padStart(2, "0");
136
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
137
+ };
132
138
  function PaneTitle({ label, extra, width }) {
133
139
  const tail = extra ? ` ${extra} ` : " ";
134
140
  const used = 2 + 1 + label.length + tail.length;
@@ -141,11 +147,12 @@ function PaneTitle({ label, extra, width }) {
141
147
  /* @__PURE__ */ jsx(Text, { color: theme.dim, children: tail + "\u2500".repeat(Math.max(0, width - used)) })
142
148
  ] });
143
149
  }
144
- function StreamLine({ e, loc, selected }) {
150
+ function StreamLine({ e, loc, selected, date }) {
145
151
  return /* @__PURE__ */ jsxs(Text, { wrap: "truncate", backgroundColor: selected ? "#1c2f63" : void 0, children: [
146
152
  /* @__PURE__ */ jsx(Text, { color: selected ? theme.accentBright : theme.dim, children: selected ? "\u25B8" : " " }),
147
153
  /* @__PURE__ */ jsxs(Text, { color: theme.dim, children: [
148
154
  "[",
155
+ date ? ymd(e.at) + " " : "",
149
156
  hhmmss(e.at),
150
157
  " "
151
158
  ] }),
@@ -359,6 +366,28 @@ function setActiveAccount(creds) {
359
366
  return false;
360
367
  }
361
368
  }
369
+ function clearActiveCredential() {
370
+ try {
371
+ const p = join3(homedir3(), ".solongate", ["cloud", "guard.json"].join("-"));
372
+ if (!existsSync(p)) {
373
+ cached2 = null;
374
+ return true;
375
+ }
376
+ let existing = {};
377
+ try {
378
+ existing = JSON.parse(readFileSync3(p, "utf-8"));
379
+ } catch {
380
+ }
381
+ delete existing.apiKey;
382
+ delete existing.apiUrl;
383
+ writeFileSync2(p, JSON.stringify(existing, null, 2));
384
+ cached2 = null;
385
+ viewOverride = null;
386
+ return true;
387
+ } catch {
388
+ return false;
389
+ }
390
+ }
362
391
  var ApiError = class extends Error {
363
392
  status;
364
393
  code;
@@ -3071,7 +3100,7 @@ function AuditPanel({ active: active2, focused }) {
3071
3100
  )
3072
3101
  ] }) : null,
3073
3102
  /* @__PURE__ */ jsx6(Text6, { color: theme.dim, wrap: "truncate", children: `${total} matched \xB7 page ${Math.min(page + 1, pages)}/${pages} \xB7 ${PAGE}/page \xB7 ${pageRows.length ? selClamped + 1 : 0}/${pageRows.length}${start ? ` \xB7 \u25B2${start} newer` : ""}${start + listRows < pageRows.length ? ` \xB7 \u25BC${pageRows.length - start - listRows} older` : ""}` }),
3074
- /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused }, e.id)) }),
3103
+ /* @__PURE__ */ jsx6(Box6, { flexDirection: "column", overflow: "hidden", children: windowed.map((e, i) => /* @__PURE__ */ jsx6(StreamLine, { e: toStream(e), loc: source === "local", selected: start + i === selClamped && focused, date: true }, e.id)) }),
3075
3104
  pageRows.length === 0 ? /* @__PURE__ */ jsxs6(Text6, { color: theme.dim, children: [
3076
3105
  "(no entries",
3077
3106
  source === "local" ? " in the local file" : "",
@@ -3215,7 +3244,8 @@ function SettingsPanel({
3215
3244
  active: active2,
3216
3245
  focused,
3217
3246
  viewApiKey,
3218
- onView
3247
+ onView,
3248
+ onAccountsChanged
3219
3249
  }) {
3220
3250
  void active2;
3221
3251
  const { cols, rows } = usePanelSize();
@@ -3489,16 +3519,31 @@ function SettingsPanel({
3489
3519
  else activate(cur);
3490
3520
  }
3491
3521
  } else if (inp === "x" && cur.kind === "acct") {
3492
- const k = "acct:" + cur.acc.apiKey;
3522
+ const target = cur.acc;
3523
+ const k = "acct:" + target.apiKey;
3524
+ const wasActive = isActiveAccount(target.apiKey);
3525
+ const others = accounts.filter((a) => a.apiKey !== target.apiKey);
3493
3526
  if (confirmDel !== k) {
3494
3527
  setConfirmDel(k);
3495
- setMsg({ text: `press x again to remove ${acctLabel(cur.acc)} from this device (the cloud account is untouched)`, level: "bad" });
3528
+ const note = wasActive ? others.length ? ` \u2014 the ACTIVE guard key; ${acctLabel(others[0])} takes over` : " \u2014 your ONLY account & the active guard key; this signs the device out (guard has no key until you log in again)" : " (the cloud account is untouched)";
3529
+ setMsg({ text: `press x again to remove ${acctLabel(target)} from this device${note}`, level: "bad" });
3496
3530
  return;
3497
3531
  }
3498
3532
  setConfirmDel(null);
3499
- removeAccount(cur.acc.apiKey);
3500
- setMsg({ text: `\u2713 removed ${acctLabel(cur.acc)} from this device`, level: "ok" });
3533
+ removeAccount(target.apiKey);
3534
+ let extra = " from this device";
3535
+ if (wasActive) {
3536
+ if (others.length) {
3537
+ setActiveAccount({ apiKey: others[0].apiKey, apiUrl: others[0].apiUrl });
3538
+ extra = ` \xB7 ${acctLabel(others[0])} is now the active key`;
3539
+ } else {
3540
+ clearActiveCredential();
3541
+ extra = " \xB7 signed out of this device";
3542
+ }
3543
+ }
3544
+ setMsg({ text: `\u2713 removed ${acctLabel(target)}${extra}`, level: "ok" });
3501
3545
  refreshAccounts();
3546
+ onAccountsChanged?.();
3502
3547
  } else if (inp === "t" && cur.kind === "wh") {
3503
3548
  run("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
3504
3549
  if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
@@ -3958,6 +4003,14 @@ function App() {
3958
4003
  if (accounts.length < 2) return;
3959
4004
  viewAccount(accounts[(acctIdx + 1) % accounts.length]);
3960
4005
  };
4006
+ const syncAccounts = () => {
4007
+ const list5 = listAccounts();
4008
+ setAccounts(list5);
4009
+ const next = list5.find((a) => a.apiKey === viewKey) ?? list5[0];
4010
+ setViewCredentials(next ? { apiKey: next.apiKey, apiUrl: next.apiUrl } : null);
4011
+ setViewKey(next?.apiKey);
4012
+ setViewNonce((n) => n + 1);
4013
+ };
3961
4014
  const locked = accounts.length === 0;
3962
4015
  const effectiveSection = locked ? SETTINGS_SECTION : section;
3963
4016
  useInput7((input, key) => {
@@ -3993,7 +4046,7 @@ function App() {
3993
4046
  return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
3994
4047
  }
3995
4048
  const Panel = current.Panel;
3996
- 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" });
4049
+ const panelBody = current.label === "Settings" ? /* @__PURE__ */ jsx8(SettingsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount, onAccountsChanged: syncAccounts }) : /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
3997
4050
  return (
3998
4051
  // height rows-1 (not rows): once total output reaches stdout.rows ink stops
3999
4052
  // diffing and clearTerminal-repaints every frame, which slides the banner.
@@ -1,7 +1,10 @@
1
1
  import { type SavedAccount } from '../../api-client/client.js';
2
- export declare function SettingsPanel({ active, focused, viewApiKey, onView, }: {
2
+ export declare function SettingsPanel({ active, focused, viewApiKey, onView, onAccountsChanged, }: {
3
3
  active: boolean;
4
4
  focused: boolean;
5
5
  viewApiKey?: string;
6
6
  onView?: (acc: SavedAccount) => void;
7
+ /** Fired after this panel mutates the on-disk account set so the App shell can
8
+ * re-derive its header / view / locked state from disk. */
9
+ onAccountsChanged?: () => void;
7
10
  }): JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.70",
3
+ "version": "0.81.72",
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": {