@solongate/proxy 0.81.46 → 0.81.48

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.
@@ -0,0 +1,12 @@
1
+ export interface Me {
2
+ user?: {
3
+ email?: string;
4
+ name?: string;
5
+ } | null;
6
+ project?: {
7
+ id?: string;
8
+ name?: string;
9
+ slug?: string;
10
+ } | null;
11
+ }
12
+ export declare function me(): Promise<Me>;
@@ -5,6 +5,7 @@
5
5
  export * from './client.js';
6
6
  export * from './types.js';
7
7
  export * from './device-login.js';
8
+ import * as auth from './auth.js';
8
9
  import * as policies from './policies.js';
9
10
  import * as settings from './settings.js';
10
11
  import * as stats from './stats.js';
@@ -13,6 +14,7 @@ import * as agents from './agents.js';
13
14
  import * as keys from './keys.js';
14
15
  import * as mcp from './mcp.js';
15
16
  export declare const api: {
17
+ auth: typeof auth;
16
18
  policies: typeof policies;
17
19
  settings: typeof settings;
18
20
  stats: typeof stats;
@@ -142,6 +142,15 @@ async function request(method, path, opts = {}) {
142
142
  // src/api-client/device-login.ts
143
143
  import { spawn } from "child_process";
144
144
 
145
+ // src/api-client/auth.ts
146
+ var auth_exports = {};
147
+ __export(auth_exports, {
148
+ me: () => me
149
+ });
150
+ function me() {
151
+ return request("GET", "/auth/me");
152
+ }
153
+
145
154
  // src/api-client/policies.ts
146
155
  var policies_exports = {};
147
156
  __export(policies_exports, {
@@ -364,7 +373,7 @@ function list4() {
364
373
  }
365
374
 
366
375
  // src/api-client/index.ts
367
- var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
376
+ var api = { auth: auth_exports, policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
368
377
 
369
378
  // src/cli-utils.ts
370
379
  var c = {
package/dist/index.js CHANGED
@@ -6558,7 +6558,8 @@ var init_cli_utils = __esm({
6558
6558
  var self_update_exports = {};
6559
6559
  __export(self_update_exports, {
6560
6560
  currentVersion: () => currentVersion,
6561
- maybeSelfUpdate: () => maybeSelfUpdate
6561
+ maybeSelfUpdate: () => maybeSelfUpdate,
6562
+ tuiUpdateFlow: () => tuiUpdateFlow
6562
6563
  });
6563
6564
  import { execFile, spawn } from "child_process";
6564
6565
  import { mkdirSync as mkdirSync3, openSync, readFileSync as readFileSync4, realpathSync, writeFileSync as writeFileSync3 } from "fs";
@@ -6647,6 +6648,51 @@ function spawnGlobalInstall(version) {
6647
6648
  return false;
6648
6649
  }
6649
6650
  }
6651
+ function runGlobalInstall(version) {
6652
+ return new Promise((resolve10) => {
6653
+ try {
6654
+ mkdirSync3(join4(homedir2(), ".solongate"), { recursive: true });
6655
+ execFile(
6656
+ "npm",
6657
+ ["install", "-g", `${PKG}@${version}`],
6658
+ { timeout: 3e5, windowsHide: true, shell: process.platform === "win32" },
6659
+ (err2, stdout, stderr) => {
6660
+ try {
6661
+ writeFileSync3(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err2 ? "FAILED" : "ok"}
6662
+ ${stdout}
6663
+ ${stderr}
6664
+ `, { flag: "a" });
6665
+ } catch {
6666
+ }
6667
+ resolve10(!err2);
6668
+ }
6669
+ );
6670
+ } catch {
6671
+ resolve10(false);
6672
+ }
6673
+ });
6674
+ }
6675
+ async function tuiUpdateFlow(onStatus) {
6676
+ try {
6677
+ const current = currentVersion();
6678
+ const latest = await fetchLatest();
6679
+ const state = readState();
6680
+ if (latest) writeState({ ...state, lastCheckAt: Date.now(), latestSeen: latest });
6681
+ if (!latest || !newerThan(latest, current)) return;
6682
+ const attempts = readState().attempts ?? {};
6683
+ const canRetry = Date.now() - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS;
6684
+ if (await isGlobalInstall() && canRetry) {
6685
+ writeState({ ...readState(), attempts: { [latest]: Date.now() } });
6686
+ onStatus({ kind: "updating", version: latest });
6687
+ if (await runGlobalInstall(latest)) {
6688
+ onStatus({ kind: "updated", version: latest });
6689
+ return;
6690
+ }
6691
+ }
6692
+ onStatus({ kind: "manual", version: latest });
6693
+ } catch {
6694
+ }
6695
+ }
6650
6696
  function maybeSelfUpdate(notify = (l) => process.stderr.write(l + "\n")) {
6651
6697
  void (async () => {
6652
6698
  try {
@@ -7157,6 +7203,21 @@ var init_device_login = __esm({
7157
7203
  }
7158
7204
  });
7159
7205
 
7206
+ // src/api-client/auth.ts
7207
+ var auth_exports = {};
7208
+ __export(auth_exports, {
7209
+ me: () => me
7210
+ });
7211
+ function me() {
7212
+ return request("GET", "/auth/me");
7213
+ }
7214
+ var init_auth = __esm({
7215
+ "src/api-client/auth.ts"() {
7216
+ "use strict";
7217
+ init_client();
7218
+ }
7219
+ });
7220
+
7160
7221
  // src/api-client/policies.ts
7161
7222
  var policies_exports = {};
7162
7223
  __export(policies_exports, {
@@ -7428,6 +7489,7 @@ var init_api_client = __esm({
7428
7489
  init_client();
7429
7490
  init_types();
7430
7491
  init_device_login();
7492
+ init_auth();
7431
7493
  init_policies();
7432
7494
  init_settings();
7433
7495
  init_stats();
@@ -7435,7 +7497,7 @@ var init_api_client = __esm({
7435
7497
  init_agents();
7436
7498
  init_keys();
7437
7499
  init_mcp();
7438
- api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
7500
+ api = { auth: auth_exports, policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
7439
7501
  }
7440
7502
  });
7441
7503
 
@@ -10177,7 +10239,7 @@ function AccountsPanel({
10177
10239
  if (a) {
10178
10240
  setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
10179
10241
  onView?.(a);
10180
- setMsg({ text: `viewing ${a.project || a.user || a.apiKey.slice(0, 8)}`, level: "ok" });
10242
+ setMsg({ text: `viewing ${a.project || a.user || "account \u2026" + a.apiKey.slice(-4)}`, level: "ok" });
10181
10243
  }
10182
10244
  } else if (input === "m") {
10183
10245
  const a = accounts[sel];
@@ -10226,7 +10288,7 @@ function AccountsPanel({
10226
10288
  const isActive = isActiveAccount(a.apiKey);
10227
10289
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isSel ? "#333333" : void 0, bold: isSel, children: [
10228
10290
  /* @__PURE__ */ jsx8(Text8, { color: isSel ? theme.accentBright : theme.dim, children: isSel ? "\u25B8 " : " " }),
10229
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(a.project || a.user || a.apiKey.slice(0, 12), 30).padEnd(31) }),
10291
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(a.project || a.user || "account \u2026" + a.apiKey.slice(-4), 30).padEnd(31) }),
10230
10292
  isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
10231
10293
  isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
10232
10294
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.user && a.project ? truncate2(a.user, 24) : "" })
@@ -10248,7 +10310,7 @@ var init_Accounts = __esm({
10248
10310
 
10249
10311
  // src/tui/App.tsx
10250
10312
  import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
10251
- import { useState as useState9 } from "react";
10313
+ import { useEffect as useEffect7, useState as useState9 } from "react";
10252
10314
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
10253
10315
  function LiveHint() {
10254
10316
  return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
@@ -10283,9 +10345,35 @@ function App() {
10283
10345
  const [section, setSection] = useState9(accounts.length === 0 ? ACCOUNTS_SECTION : 0);
10284
10346
  const [focus, setFocus] = useState9("nav");
10285
10347
  const [help, setHelp] = useState9(false);
10348
+ const [update2, setUpdate] = useState9({ kind: "idle" });
10349
+ useEffect7(() => {
10350
+ let alive = true;
10351
+ const run12 = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
10352
+ run12();
10353
+ const t = setInterval(run12, 30 * 6e4);
10354
+ return () => {
10355
+ alive = false;
10356
+ clearInterval(t);
10357
+ };
10358
+ }, []);
10286
10359
  const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
10287
10360
  const cur = accounts[acctIdx];
10288
- const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "not logged in";
10361
+ const acctLabel = cur ? cur.project || cur.user || `account \u2026${cur.apiKey.slice(-4)}` : "not logged in";
10362
+ useEffect7(() => {
10363
+ if (!cur || cur.project || cur.user) return;
10364
+ let alive = true;
10365
+ api.auth.me().then((r) => {
10366
+ const project = r.project?.name || void 0;
10367
+ const user = r.user?.name || r.user?.email || void 0;
10368
+ if (!alive || !project && !user) return;
10369
+ saveAccount({ ...cur, project, user });
10370
+ setAccounts(listAccounts());
10371
+ }).catch(() => {
10372
+ });
10373
+ return () => {
10374
+ alive = false;
10375
+ };
10376
+ }, [cur]);
10289
10377
  const viewAccount = (a) => {
10290
10378
  setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
10291
10379
  setAccounts(listAccounts());
@@ -10337,7 +10425,8 @@ function App() {
10337
10425
  /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
10338
10426
  /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
10339
10427
  /* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel }),
10340
- locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Accounts to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" })
10428
+ locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Accounts to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" }),
10429
+ update2.kind === "updating" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \u2014 restart (q, then solongate) to apply` }) : update2.kind === "manual" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} available \u2014 npm i -g @solongate/proxy@latest` }) : null
10341
10430
  ] }),
10342
10431
  /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
10343
10432
  /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
@@ -10379,6 +10468,8 @@ var init_App = __esm({
10379
10468
  init_Accounts();
10380
10469
  init_hooks();
10381
10470
  init_client();
10471
+ init_api_client();
10472
+ init_self_update();
10382
10473
  SECTIONS = [
10383
10474
  { label: "Live", Panel: LiveHint },
10384
10475
  { label: "Policies", Panel: PoliciesPanel },
@@ -11774,7 +11865,7 @@ ${SHIM_END}`;
11774
11865
  }
11775
11866
  }
11776
11867
  }
11777
- async function runGlobalInstall(opts = {}) {
11868
+ async function runGlobalInstall2(opts = {}) {
11778
11869
  const p = globalPaths();
11779
11870
  let apiKey = opts.apiKey || process.env["SOLONGATE_API_KEY"] || "";
11780
11871
  if (!apiKey || apiKey === "sg_live_your_key_here") {
@@ -11965,7 +12056,7 @@ async function main() {
11965
12056
  return;
11966
12057
  }
11967
12058
  console.log("");
11968
- await runGlobalInstall({ apiKey, apiUrl });
12059
+ await runGlobalInstall2({ apiKey, apiUrl });
11969
12060
  console.log("");
11970
12061
  console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
11971
12062
  console.log(" \u2502 You are logged in and protected. \u2502");
@@ -16508,9 +16599,10 @@ async function main5() {
16508
16599
  const subcommand = process.argv[2];
16509
16600
  if (IS_HUMAN_CLI) {
16510
16601
  const tuiBound = subcommand === "dataroom" || process.argv.length <= 2 && process.stdout.isTTY && process.stdin.isTTY;
16511
- const { maybeSelfUpdate: maybeSelfUpdate2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
16512
- maybeSelfUpdate2(tuiBound ? () => {
16513
- } : void 0);
16602
+ if (!tuiBound) {
16603
+ const { maybeSelfUpdate: maybeSelfUpdate2 } = await Promise.resolve().then(() => (init_self_update(), self_update_exports));
16604
+ maybeSelfUpdate2();
16605
+ }
16514
16606
  }
16515
16607
  if (process.argv.length <= 2) {
16516
16608
  if (process.stdout.isTTY && process.stdin.isTTY) {
@@ -1,4 +1,24 @@
1
1
  export declare function currentVersion(): string;
2
+ /** What the dataroom shows about the updater. */
3
+ export type UpdateStatus = {
4
+ kind: 'idle';
5
+ } | {
6
+ kind: 'updating';
7
+ version: string;
8
+ } | {
9
+ kind: 'updated';
10
+ version: string;
11
+ } | {
12
+ kind: 'manual';
13
+ version: string;
14
+ };
15
+ /**
16
+ * The dataroom's updater (Claude Code-style): check the registry EVERY time the
17
+ * TUI opens (and periodically while it stays open), install the new version in
18
+ * the background, then tell the user to restart. `onStatus` drives the in-app
19
+ * status line; nothing is ever written to the terminal directly.
20
+ */
21
+ export declare function tuiUpdateFlow(onStatus: (s: UpdateStatus) => void): Promise<void>;
2
22
  /**
3
23
  * Fire-and-forget startup hook. `notify` writes the human-facing one-liner —
4
24
  * callers pass a stderr writer so the notice never lands in piped stdout.
package/dist/tui/index.js CHANGED
@@ -5,14 +5,14 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/tui/index.tsx
8
- import { appendFileSync, mkdirSync as mkdirSync4 } from "fs";
9
- import { homedir as homedir6 } from "os";
10
- import { join as join6 } from "path";
8
+ import { appendFileSync, mkdirSync as mkdirSync5 } from "fs";
9
+ import { homedir as homedir7 } from "os";
10
+ import { join as join7 } from "path";
11
11
  import { render } from "ink";
12
12
 
13
13
  // src/tui/App.tsx
14
14
  import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
15
- import { useState as useState9 } from "react";
15
+ import { useEffect as useEffect7, useState as useState9 } from "react";
16
16
 
17
17
  // src/cli-utils.ts
18
18
  var c = {
@@ -471,6 +471,15 @@ async function pollDeviceLogin(apiUrl, deviceCode) {
471
471
  }
472
472
  }
473
473
 
474
+ // src/api-client/auth.ts
475
+ var auth_exports = {};
476
+ __export(auth_exports, {
477
+ me: () => me
478
+ });
479
+ function me() {
480
+ return request("GET", "/auth/me");
481
+ }
482
+
474
483
  // src/api-client/policies.ts
475
484
  var policies_exports = {};
476
485
  __export(policies_exports, {
@@ -693,7 +702,7 @@ function list4() {
693
702
  }
694
703
 
695
704
  // src/api-client/index.ts
696
- var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
705
+ var api = { auth: auth_exports, policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
697
706
 
698
707
  // src/tui/notify.ts
699
708
  import { spawn as spawn2 } from "child_process";
@@ -3355,7 +3364,7 @@ function AccountsPanel({
3355
3364
  if (a) {
3356
3365
  setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3357
3366
  onView?.(a);
3358
- setMsg({ text: `viewing ${a.project || a.user || a.apiKey.slice(0, 8)}`, level: "ok" });
3367
+ setMsg({ text: `viewing ${a.project || a.user || "account \u2026" + a.apiKey.slice(-4)}`, level: "ok" });
3359
3368
  }
3360
3369
  } else if (input === "m") {
3361
3370
  const a = accounts[sel];
@@ -3404,7 +3413,7 @@ function AccountsPanel({
3404
3413
  const isActive = isActiveAccount(a.apiKey);
3405
3414
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isSel ? "#333333" : void 0, bold: isSel, children: [
3406
3415
  /* @__PURE__ */ jsx8(Text8, { color: isSel ? theme.accentBright : theme.dim, children: isSel ? "\u25B8 " : " " }),
3407
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.project || a.user || a.apiKey.slice(0, 12), 30).padEnd(31) }),
3416
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.project || a.user || "account \u2026" + a.apiKey.slice(-4), 30).padEnd(31) }),
3408
3417
  isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
3409
3418
  isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
3410
3419
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.user && a.project ? truncate(a.user, 24) : "" })
@@ -3414,6 +3423,127 @@ function AccountsPanel({
3414
3423
  ] });
3415
3424
  }
3416
3425
 
3426
+ // src/self-update.ts
3427
+ import { execFile, spawn as spawn3 } from "child_process";
3428
+ import { mkdirSync as mkdirSync4, openSync as openSync2, readFileSync as readFileSync4, realpathSync, writeFileSync as writeFileSync5 } from "fs";
3429
+ import { homedir as homedir6 } from "os";
3430
+ import { dirname, join as join6 } from "path";
3431
+ import { fileURLToPath } from "url";
3432
+ var PKG = "@solongate/proxy";
3433
+ var CHECK_EVERY_MS = 30 * 60 * 1e3;
3434
+ var ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
3435
+ var STATE_FILE = join6(homedir6(), ".solongate", ".self-update.json");
3436
+ var LOG_FILE = join6(homedir6(), ".solongate", "self-update.log");
3437
+ function readState() {
3438
+ try {
3439
+ const s = JSON.parse(readFileSync4(STATE_FILE, "utf-8"));
3440
+ return s && typeof s === "object" ? s : {};
3441
+ } catch {
3442
+ return {};
3443
+ }
3444
+ }
3445
+ function writeState(s) {
3446
+ try {
3447
+ mkdirSync4(join6(homedir6(), ".solongate"), { recursive: true });
3448
+ writeFileSync5(STATE_FILE, JSON.stringify(s));
3449
+ } catch {
3450
+ }
3451
+ }
3452
+ function currentVersion() {
3453
+ try {
3454
+ const pkg = JSON.parse(readFileSync4(join6(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
3455
+ return pkg.version ?? "0.0.0";
3456
+ } catch {
3457
+ return "0.0.0";
3458
+ }
3459
+ }
3460
+ function newerThan(b, a) {
3461
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
3462
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
3463
+ for (let i = 0; i < 3; i++) {
3464
+ if ((pb[i] ?? 0) > (pa[i] ?? 0)) return true;
3465
+ if ((pb[i] ?? 0) < (pa[i] ?? 0)) return false;
3466
+ }
3467
+ return false;
3468
+ }
3469
+ async function fetchLatest() {
3470
+ try {
3471
+ const res = await fetch(`https://registry.npmjs.org/${PKG}/latest`, { signal: AbortSignal.timeout(2e3) });
3472
+ if (!res.ok) return null;
3473
+ const j = await res.json();
3474
+ return typeof j.version === "string" ? j.version : null;
3475
+ } catch {
3476
+ return null;
3477
+ }
3478
+ }
3479
+ function npmGlobalRoot() {
3480
+ return new Promise((resolve2) => {
3481
+ try {
3482
+ execFile("npm", ["root", "-g"], { timeout: 5e3, windowsHide: true, shell: process.platform === "win32" }, (err, stdout) => {
3483
+ resolve2(err ? null : stdout.trim() || null);
3484
+ });
3485
+ } catch {
3486
+ resolve2(null);
3487
+ }
3488
+ });
3489
+ }
3490
+ async function isGlobalInstall() {
3491
+ try {
3492
+ const script = realpathSync(process.argv[1] ?? "");
3493
+ if (!script) return false;
3494
+ const root = await npmGlobalRoot();
3495
+ if (!root) return false;
3496
+ return script.startsWith(realpathSync(root));
3497
+ } catch {
3498
+ return false;
3499
+ }
3500
+ }
3501
+ function runGlobalInstall(version) {
3502
+ return new Promise((resolve2) => {
3503
+ try {
3504
+ mkdirSync4(join6(homedir6(), ".solongate"), { recursive: true });
3505
+ execFile(
3506
+ "npm",
3507
+ ["install", "-g", `${PKG}@${version}`],
3508
+ { timeout: 3e5, windowsHide: true, shell: process.platform === "win32" },
3509
+ (err, stdout, stderr) => {
3510
+ try {
3511
+ writeFileSync5(LOG_FILE, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err ? "FAILED" : "ok"}
3512
+ ${stdout}
3513
+ ${stderr}
3514
+ `, { flag: "a" });
3515
+ } catch {
3516
+ }
3517
+ resolve2(!err);
3518
+ }
3519
+ );
3520
+ } catch {
3521
+ resolve2(false);
3522
+ }
3523
+ });
3524
+ }
3525
+ async function tuiUpdateFlow(onStatus) {
3526
+ try {
3527
+ const current = currentVersion();
3528
+ const latest = await fetchLatest();
3529
+ const state = readState();
3530
+ if (latest) writeState({ ...state, lastCheckAt: Date.now(), latestSeen: latest });
3531
+ if (!latest || !newerThan(latest, current)) return;
3532
+ const attempts = readState().attempts ?? {};
3533
+ const canRetry = Date.now() - (attempts[latest] ?? 0) >= ATTEMPT_EVERY_MS;
3534
+ if (await isGlobalInstall() && canRetry) {
3535
+ writeState({ ...readState(), attempts: { [latest]: Date.now() } });
3536
+ onStatus({ kind: "updating", version: latest });
3537
+ if (await runGlobalInstall(latest)) {
3538
+ onStatus({ kind: "updated", version: latest });
3539
+ return;
3540
+ }
3541
+ }
3542
+ onStatus({ kind: "manual", version: latest });
3543
+ } catch {
3544
+ }
3545
+ }
3546
+
3417
3547
  // src/tui/App.tsx
3418
3548
  import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3419
3549
  function LiveHint() {
@@ -3460,9 +3590,35 @@ function App() {
3460
3590
  const [section, setSection] = useState9(accounts.length === 0 ? ACCOUNTS_SECTION : 0);
3461
3591
  const [focus, setFocus] = useState9("nav");
3462
3592
  const [help, setHelp] = useState9(false);
3593
+ const [update2, setUpdate] = useState9({ kind: "idle" });
3594
+ useEffect7(() => {
3595
+ let alive = true;
3596
+ const run = () => void tuiUpdateFlow((s) => alive && setUpdate(s));
3597
+ run();
3598
+ const t = setInterval(run, 30 * 6e4);
3599
+ return () => {
3600
+ alive = false;
3601
+ clearInterval(t);
3602
+ };
3603
+ }, []);
3463
3604
  const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
3464
3605
  const cur = accounts[acctIdx];
3465
- const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "not logged in";
3606
+ const acctLabel = cur ? cur.project || cur.user || `account \u2026${cur.apiKey.slice(-4)}` : "not logged in";
3607
+ useEffect7(() => {
3608
+ if (!cur || cur.project || cur.user) return;
3609
+ let alive = true;
3610
+ api.auth.me().then((r) => {
3611
+ const project = r.project?.name || void 0;
3612
+ const user = r.user?.name || r.user?.email || void 0;
3613
+ if (!alive || !project && !user) return;
3614
+ saveAccount({ ...cur, project, user });
3615
+ setAccounts(listAccounts());
3616
+ }).catch(() => {
3617
+ });
3618
+ return () => {
3619
+ alive = false;
3620
+ };
3621
+ }, [cur]);
3466
3622
  const viewAccount = (a) => {
3467
3623
  setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3468
3624
  setAccounts(listAccounts());
@@ -3514,7 +3670,8 @@ function App() {
3514
3670
  /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
3515
3671
  /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
3516
3672
  /* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel }),
3517
- locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Accounts to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" })
3673
+ locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Accounts to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" }),
3674
+ update2.kind === "updating" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 updating to v${update2.version}\u2026` }) : update2.kind === "updated" ? /* @__PURE__ */ jsx9(Text9, { color: theme.ok, bold: true, children: ` \u2191 v${update2.version} installed \u2014 restart (q, then solongate) to apply` }) : update2.kind === "manual" ? /* @__PURE__ */ jsx9(Text9, { color: theme.warn, children: ` \u2191 v${update2.version} available \u2014 npm i -g @solongate/proxy@latest` }) : null
3518
3675
  ] }),
3519
3676
  /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
3520
3677
  /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
@@ -3560,11 +3717,11 @@ async function launchTui() {
3560
3717
  return;
3561
3718
  }
3562
3719
  process.stdout.write("\x1B[?1049h\x1B[H");
3563
- const debugLog = join6(homedir6(), ".solongate", "dataroom-debug.log");
3720
+ const debugLog = join7(homedir7(), ".solongate", "dataroom-debug.log");
3564
3721
  const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
3565
3722
  const toFile = (level) => (...args) => {
3566
3723
  try {
3567
- mkdirSync4(join6(homedir6(), ".solongate"), { recursive: true });
3724
+ mkdirSync5(join7(homedir7(), ".solongate"), { recursive: true });
3568
3725
  appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
3569
3726
  `);
3570
3727
  } catch {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.46",
3
+ "version": "0.81.48",
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": {