@solongate/proxy 0.81.70 → 0.81.71

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
@@ -7168,6 +7168,28 @@ function setActiveAccount(creds) {
7168
7168
  return false;
7169
7169
  }
7170
7170
  }
7171
+ function clearActiveCredential() {
7172
+ try {
7173
+ const p = join8(homedir6(), ".solongate", ["cloud", "guard.json"].join("-"));
7174
+ if (!existsSync3(p)) {
7175
+ cached2 = null;
7176
+ return true;
7177
+ }
7178
+ let existing = {};
7179
+ try {
7180
+ existing = JSON.parse(readFileSync8(p, "utf-8"));
7181
+ } catch {
7182
+ }
7183
+ delete existing.apiKey;
7184
+ delete existing.apiUrl;
7185
+ writeFileSync6(p, JSON.stringify(existing, null, 2));
7186
+ cached2 = null;
7187
+ viewOverride = null;
7188
+ return true;
7189
+ } catch {
7190
+ return false;
7191
+ }
7192
+ }
7171
7193
  function loginCredentialFile() {
7172
7194
  try {
7173
7195
  const p = join8(homedir6(), ".solongate", "cloud-guard.json");
@@ -10315,7 +10337,8 @@ function SettingsPanel({
10315
10337
  active: active2,
10316
10338
  focused,
10317
10339
  viewApiKey,
10318
- onView
10340
+ onView,
10341
+ onAccountsChanged
10319
10342
  }) {
10320
10343
  void active2;
10321
10344
  const { cols, rows } = usePanelSize();
@@ -10589,16 +10612,31 @@ function SettingsPanel({
10589
10612
  else activate(cur);
10590
10613
  }
10591
10614
  } else if (inp === "x" && cur.kind === "acct") {
10592
- const k = "acct:" + cur.acc.apiKey;
10615
+ const target = cur.acc;
10616
+ const k = "acct:" + target.apiKey;
10617
+ const wasActive = isActiveAccount(target.apiKey);
10618
+ const others = accounts.filter((a) => a.apiKey !== target.apiKey);
10593
10619
  if (confirmDel !== k) {
10594
10620
  setConfirmDel(k);
10595
- setMsg({ text: `press x again to remove ${acctLabel(cur.acc)} from this device (the cloud account is untouched)`, level: "bad" });
10621
+ 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)";
10622
+ setMsg({ text: `press x again to remove ${acctLabel(target)} from this device${note}`, level: "bad" });
10596
10623
  return;
10597
10624
  }
10598
10625
  setConfirmDel(null);
10599
- removeAccount(cur.acc.apiKey);
10600
- setMsg({ text: `\u2713 removed ${acctLabel(cur.acc)} from this device`, level: "ok" });
10626
+ removeAccount(target.apiKey);
10627
+ let extra = " from this device";
10628
+ if (wasActive) {
10629
+ if (others.length) {
10630
+ setActiveAccount({ apiKey: others[0].apiKey, apiUrl: others[0].apiUrl });
10631
+ extra = ` \xB7 ${acctLabel(others[0])} is now the active key`;
10632
+ } else {
10633
+ clearActiveCredential();
10634
+ extra = " \xB7 signed out of this device";
10635
+ }
10636
+ }
10637
+ setMsg({ text: `\u2713 removed ${acctLabel(target)}${extra}`, level: "ok" });
10601
10638
  refreshAccounts();
10639
+ onAccountsChanged?.();
10602
10640
  } else if (inp === "t" && cur.kind === "wh") {
10603
10641
  run12("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
10604
10642
  if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
@@ -10953,6 +10991,14 @@ function App() {
10953
10991
  if (accounts.length < 2) return;
10954
10992
  viewAccount(accounts[(acctIdx + 1) % accounts.length]);
10955
10993
  };
10994
+ const syncAccounts = () => {
10995
+ const list6 = listAccounts();
10996
+ setAccounts(list6);
10997
+ const next = list6.find((a) => a.apiKey === viewKey) ?? list6[0];
10998
+ setViewCredentials(next ? { apiKey: next.apiKey, apiUrl: next.apiUrl } : null);
10999
+ setViewKey(next?.apiKey);
11000
+ setViewNonce((n) => n + 1);
11001
+ };
10956
11002
  const locked = accounts.length === 0;
10957
11003
  const effectiveSection = locked ? SETTINGS_SECTION : section;
10958
11004
  useInput7((input, key) => {
@@ -10988,7 +11034,7 @@ function App() {
10988
11034
  return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
10989
11035
  }
10990
11036
  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" });
11037
+ 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
11038
  return (
10993
11039
  // height rows-1 (not rows): once total output reaches stdout.rows ink stops
10994
11040
  // diffing and clearTerminal-repaints every frame, which slides the banner.
package/dist/tui/index.js CHANGED
@@ -359,6 +359,28 @@ function setActiveAccount(creds) {
359
359
  return false;
360
360
  }
361
361
  }
362
+ function clearActiveCredential() {
363
+ try {
364
+ const p = join3(homedir3(), ".solongate", ["cloud", "guard.json"].join("-"));
365
+ if (!existsSync(p)) {
366
+ cached2 = null;
367
+ return true;
368
+ }
369
+ let existing = {};
370
+ try {
371
+ existing = JSON.parse(readFileSync3(p, "utf-8"));
372
+ } catch {
373
+ }
374
+ delete existing.apiKey;
375
+ delete existing.apiUrl;
376
+ writeFileSync2(p, JSON.stringify(existing, null, 2));
377
+ cached2 = null;
378
+ viewOverride = null;
379
+ return true;
380
+ } catch {
381
+ return false;
382
+ }
383
+ }
362
384
  var ApiError = class extends Error {
363
385
  status;
364
386
  code;
@@ -3215,7 +3237,8 @@ function SettingsPanel({
3215
3237
  active: active2,
3216
3238
  focused,
3217
3239
  viewApiKey,
3218
- onView
3240
+ onView,
3241
+ onAccountsChanged
3219
3242
  }) {
3220
3243
  void active2;
3221
3244
  const { cols, rows } = usePanelSize();
@@ -3489,16 +3512,31 @@ function SettingsPanel({
3489
3512
  else activate(cur);
3490
3513
  }
3491
3514
  } else if (inp === "x" && cur.kind === "acct") {
3492
- const k = "acct:" + cur.acc.apiKey;
3515
+ const target = cur.acc;
3516
+ const k = "acct:" + target.apiKey;
3517
+ const wasActive = isActiveAccount(target.apiKey);
3518
+ const others = accounts.filter((a) => a.apiKey !== target.apiKey);
3493
3519
  if (confirmDel !== k) {
3494
3520
  setConfirmDel(k);
3495
- setMsg({ text: `press x again to remove ${acctLabel(cur.acc)} from this device (the cloud account is untouched)`, level: "bad" });
3521
+ 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)";
3522
+ setMsg({ text: `press x again to remove ${acctLabel(target)} from this device${note}`, level: "bad" });
3496
3523
  return;
3497
3524
  }
3498
3525
  setConfirmDel(null);
3499
- removeAccount(cur.acc.apiKey);
3500
- setMsg({ text: `\u2713 removed ${acctLabel(cur.acc)} from this device`, level: "ok" });
3526
+ removeAccount(target.apiKey);
3527
+ let extra = " from this device";
3528
+ if (wasActive) {
3529
+ if (others.length) {
3530
+ setActiveAccount({ apiKey: others[0].apiKey, apiUrl: others[0].apiUrl });
3531
+ extra = ` \xB7 ${acctLabel(others[0])} is now the active key`;
3532
+ } else {
3533
+ clearActiveCredential();
3534
+ extra = " \xB7 signed out of this device";
3535
+ }
3536
+ }
3537
+ setMsg({ text: `\u2713 removed ${acctLabel(target)}${extra}`, level: "ok" });
3501
3538
  refreshAccounts();
3539
+ onAccountsChanged?.();
3502
3540
  } else if (inp === "t" && cur.kind === "wh") {
3503
3541
  run("webhook test sent \u2014 check your endpoint", () => api.settings.sendTestWebhook(cur.wh.id).then((r) => {
3504
3542
  if (!r.delivered) throw new Error("endpoint rejected the test (non-2xx)");
@@ -3958,6 +3996,14 @@ function App() {
3958
3996
  if (accounts.length < 2) return;
3959
3997
  viewAccount(accounts[(acctIdx + 1) % accounts.length]);
3960
3998
  };
3999
+ const syncAccounts = () => {
4000
+ const list5 = listAccounts();
4001
+ setAccounts(list5);
4002
+ const next = list5.find((a) => a.apiKey === viewKey) ?? list5[0];
4003
+ setViewCredentials(next ? { apiKey: next.apiKey, apiUrl: next.apiUrl } : null);
4004
+ setViewKey(next?.apiKey);
4005
+ setViewNonce((n) => n + 1);
4006
+ };
3961
4007
  const locked = accounts.length === 0;
3962
4008
  const effectiveSection = locked ? SETTINGS_SECTION : section;
3963
4009
  useInput7((input, key) => {
@@ -3993,7 +4039,7 @@ function App() {
3993
4039
  return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows - 1, overflow: "hidden", children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, viewNonce) });
3994
4040
  }
3995
4041
  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" });
4042
+ 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
4043
  return (
3998
4044
  // height rows-1 (not rows): once total output reaches stdout.rows ink stops
3999
4045
  // 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.71",
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": {