@solongate/proxy 0.81.37 → 0.81.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/tui/index.js CHANGED
@@ -8,8 +8,8 @@ var __export = (target, all) => {
8
8
  import { render } from "ink";
9
9
 
10
10
  // src/tui/App.tsx
11
- import { Box as Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
12
- import { useState as useState8 } from "react";
11
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
12
+ import { useState as useState9 } from "react";
13
13
 
14
14
  // src/cli-utils.ts
15
15
  var c = {
@@ -263,11 +263,48 @@ function listAccounts() {
263
263
  }
264
264
  return list5;
265
265
  }
266
+ function saveAccount(acc) {
267
+ try {
268
+ const list5 = (() => {
269
+ try {
270
+ const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
271
+ return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
272
+ } catch {
273
+ return [];
274
+ }
275
+ })();
276
+ const next = list5.filter((a) => a.apiKey !== acc.apiKey);
277
+ next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
278
+ mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
279
+ writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
280
+ } catch {
281
+ }
282
+ }
266
283
  var viewOverride = null;
267
284
  function setViewCredentials(creds) {
268
285
  viewOverride = creds;
269
286
  cached2 = null;
270
287
  }
288
+ function isActiveAccount(apiKey) {
289
+ return loginCredentialFile().apiKey === apiKey;
290
+ }
291
+ function setActiveAccount(creds) {
292
+ try {
293
+ const dir = join3(homedir3(), ".solongate");
294
+ mkdirSync(dir, { recursive: true });
295
+ const p = join3(dir, ["cloud", "guard.json"].join("-"));
296
+ let existing = {};
297
+ try {
298
+ existing = JSON.parse(readFileSync3(p, "utf-8"));
299
+ } catch {
300
+ }
301
+ writeFileSync2(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2));
302
+ cached2 = null;
303
+ return true;
304
+ } catch {
305
+ return false;
306
+ }
307
+ }
271
308
  var ApiError = class extends Error {
272
309
  status;
273
310
  code;
@@ -312,14 +349,6 @@ function dotenvApiKey() {
312
349
  return void 0;
313
350
  }
314
351
  var cached2 = null;
315
- function isAuthenticated() {
316
- try {
317
- resolveCredentials();
318
- return true;
319
- } catch {
320
- return false;
321
- }
322
- }
323
352
  function resolveCredentials(apiUrlOverride) {
324
353
  if (viewOverride && !apiUrlOverride) return viewOverride;
325
354
  if (cached2 && !apiUrlOverride) return cached2;
@@ -397,6 +426,48 @@ async function request(method, path, opts = {}) {
397
426
  return json;
398
427
  }
399
428
 
429
+ // src/api-client/device-login.ts
430
+ import { spawn } from "child_process";
431
+ function openBrowser(url) {
432
+ try {
433
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
434
+ const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
435
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
436
+ child.on("error", () => {
437
+ });
438
+ child.unref();
439
+ } catch {
440
+ }
441
+ }
442
+ async function startDeviceLogin(apiUrl = DEFAULT_API_URL) {
443
+ const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
444
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
445
+ const j = await res.json();
446
+ return {
447
+ deviceCode: j.device_code,
448
+ verifyUrl: j.verification_uri_complete || j.verification_uri || "",
449
+ intervalMs: Math.max(2, Number(j.interval) || 3) * 1e3,
450
+ expiresAt: Date.now() + (Number(j.expires_in) || 600) * 1e3
451
+ };
452
+ }
453
+ async function pollDeviceLogin(apiUrl, deviceCode) {
454
+ try {
455
+ const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
456
+ method: "POST",
457
+ headers: { "Content-Type": "application/json" },
458
+ body: JSON.stringify({ device_code: deviceCode })
459
+ });
460
+ const j = await res.json().catch(() => ({}));
461
+ if (j.status === "approved" && j.api_key) {
462
+ return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email };
463
+ }
464
+ if (j.status === "expired" || j.status === "not_found") return { status: j.status };
465
+ return { status: "pending" };
466
+ } catch {
467
+ return { status: "pending" };
468
+ }
469
+ }
470
+
400
471
  // src/api-client/policies.ts
401
472
  var policies_exports = {};
402
473
  __export(policies_exports, {
@@ -622,24 +693,24 @@ function list4() {
622
693
  var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
623
694
 
624
695
  // src/tui/notify.ts
625
- import { spawn } from "child_process";
696
+ import { spawn as spawn2 } from "child_process";
626
697
  function desktopNotify(title, msg) {
627
698
  try {
628
699
  if (process.platform === "linux") {
629
- const p = spawn("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
700
+ const p = spawn2("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
630
701
  p.on("error", () => {
631
702
  });
632
703
  p.unref();
633
704
  } else if (process.platform === "win32") {
634
705
  const q = (s) => s.replace(/'/g, "''");
635
706
  const ps = `Add-Type -AssemblyName System.Windows.Forms;Add-Type -AssemblyName System.Drawing;$n=New-Object System.Windows.Forms.NotifyIcon;$n.Icon=[System.Drawing.SystemIcons]::Information;$n.Visible=$true;$n.ShowBalloonTip(6000,'${q(title)}','${q(msg)}',[System.Windows.Forms.ToolTipIcon]::Warning);Start-Sleep -Milliseconds 6500;$n.Dispose()`;
636
- const p = spawn("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "ignore", detached: true, windowsHide: true });
707
+ const p = spawn2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "ignore", detached: true, windowsHide: true });
637
708
  p.on("error", () => {
638
709
  });
639
710
  p.unref();
640
711
  } else if (process.platform === "darwin") {
641
712
  const esc = (s) => s.replace(/"/g, '\\"');
642
- const p = spawn("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
713
+ const p = spawn2("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
643
714
  p.on("error", () => {
644
715
  });
645
716
  p.unref();
@@ -3083,16 +3154,147 @@ function SettingsPanel({ active: active2, focused }) {
3083
3154
  ] }) });
3084
3155
  }
3085
3156
 
3086
- // src/tui/App.tsx
3157
+ // src/tui/panels/Accounts.tsx
3158
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
3159
+ import { useEffect as useEffect6, useRef as useRef3, useState as useState8 } from "react";
3087
3160
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3088
- function LiveHint() {
3161
+ var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
3162
+ function AccountsPanel({
3163
+ focused,
3164
+ viewApiKey,
3165
+ onView
3166
+ }) {
3167
+ const [accounts, setAccounts] = useState8(() => listAccounts());
3168
+ const [sel, setSel] = useState8(0);
3169
+ const [msg, setMsg] = useState8(null);
3170
+ const [login, setLogin] = useState8(null);
3171
+ const [tick, setTick] = useState8(0);
3172
+ const abort = useRef3(false);
3173
+ const refresh = () => setAccounts(listAccounts());
3174
+ useEffect6(() => {
3175
+ if (!login || login.phase === "done" || login.phase === "error") return;
3176
+ const t = setInterval(() => setTick((n) => n + 1), 120);
3177
+ return () => clearInterval(t);
3178
+ }, [login]);
3179
+ const beginLogin = () => {
3180
+ if (login && (login.phase === "starting" || login.phase === "waiting")) return;
3181
+ abort.current = false;
3182
+ setLogin({ phase: "starting" });
3183
+ void (async () => {
3184
+ let start;
3185
+ try {
3186
+ start = await startDeviceLogin(DEFAULT_API_URL);
3187
+ } catch (e) {
3188
+ setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
3189
+ return;
3190
+ }
3191
+ setLogin({ phase: "waiting", url: start.verifyUrl });
3192
+ openBrowser(start.verifyUrl);
3193
+ while (Date.now() < start.expiresAt && !abort.current) {
3194
+ await new Promise((r) => setTimeout(r, start.intervalMs));
3195
+ if (abort.current) return;
3196
+ const res = await pollDeviceLogin(DEFAULT_API_URL, start.deviceCode);
3197
+ if (res.status === "approved" && res.apiKey) {
3198
+ saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user });
3199
+ setLogin({ phase: "done", msg: `\u2713 added ${res.project || res.user || "account"}` });
3200
+ refresh();
3201
+ setViewCredentials({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL });
3202
+ onView?.({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user });
3203
+ return;
3204
+ }
3205
+ if (res.status === "expired" || res.status === "not_found") {
3206
+ setLogin({ phase: "error", msg: "code expired \u2014 press n to retry" });
3207
+ return;
3208
+ }
3209
+ }
3210
+ if (!abort.current) setLogin({ phase: "error", msg: "timed out \u2014 press n to retry" });
3211
+ })();
3212
+ };
3213
+ useInput7(
3214
+ (input, key) => {
3215
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
3216
+ if (key.escape) {
3217
+ abort.current = true;
3218
+ setLogin(null);
3219
+ }
3220
+ return;
3221
+ }
3222
+ if (key.upArrow) setSel((n) => Math.max(0, n - 1));
3223
+ else if (key.downArrow) setSel((n) => Math.min(accounts.length - 1, n + 1));
3224
+ else if (key.return) {
3225
+ const a = accounts[sel];
3226
+ if (a) {
3227
+ setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3228
+ onView?.(a);
3229
+ setMsg({ text: `viewing ${a.project || a.user || a.apiKey.slice(0, 8)}`, level: "ok" });
3230
+ }
3231
+ } else if (input === "m") {
3232
+ const a = accounts[sel];
3233
+ if (a) {
3234
+ const ok = setActiveAccount({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3235
+ setMsg(ok ? { text: `\u2713 ${a.project || a.user || "account"} is now the ACTIVE key (guard + logging)`, level: "ok" } : { text: "\u2717 could not set active", level: "bad" });
3236
+ refresh();
3237
+ }
3238
+ } else if (input === "n") beginLogin();
3239
+ else if (input === "r") refresh();
3240
+ },
3241
+ { isActive: focused }
3242
+ );
3243
+ const spin = SPIN2[tick % SPIN2.length];
3244
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
3245
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3246
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "Add an account" }),
3247
+ login.phase === "starting" ? /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
3248
+ spin,
3249
+ " starting device pairing\u2026"
3250
+ ] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
3251
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
3252
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
3253
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
3254
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.warn, children: [
3255
+ spin,
3256
+ " waiting for authorization\u2026 "
3257
+ ] }),
3258
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "esc to cancel" })
3259
+ ] })
3260
+ ] })
3261
+ ] });
3262
+ }
3089
3263
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3090
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
3091
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
3092
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
3093
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
3264
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: focused ? "\u2191\u2193 select \xB7 enter view \xB7 m make active \xB7 n add account \xB7 r refresh" : "press \u2192 to manage accounts" }),
3265
+ msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, 70) }) : login?.msg ? /* @__PURE__ */ jsx8(Text8, { color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
3266
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: accounts.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3267
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "No accounts on this device yet." }),
3268
+ /* @__PURE__ */ jsxs8(Text8, { children: [
3269
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "n" }),
3270
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " to log in \u2014 pairs this device and starts protecting your Claude Code sessions." })
3271
+ ] })
3272
+ ] }) : accounts.map((a, i) => {
3273
+ const isSel = i === sel && focused;
3274
+ const isView = viewApiKey ? a.apiKey === viewApiKey : i === 0;
3275
+ const isActive = isActiveAccount(a.apiKey);
3276
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isSel ? "#333333" : void 0, bold: isSel, children: [
3277
+ /* @__PURE__ */ jsx8(Text8, { color: isSel ? theme.accentBright : theme.dim, children: isSel ? "\u25B8 " : " " }),
3278
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.project || a.user || a.apiKey.slice(0, 12), 30).padEnd(31) }),
3279
+ isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
3280
+ isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
3281
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.user && a.project ? truncate(a.user, 24) : "" })
3282
+ ] }, a.apiKey);
3283
+ }) }),
3284
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u25CF viewing = the account the dataroom is showing \xB7 ACTIVE = the key the guard hooks + logging use" }) })
3285
+ ] });
3286
+ }
3287
+
3288
+ // src/tui/App.tsx
3289
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
3290
+ function LiveHint() {
3291
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3292
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
3293
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
3294
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
3295
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
3094
3296
  "press ",
3095
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
3297
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
3096
3298
  " to go live"
3097
3299
  ] }) })
3098
3300
  ] });
@@ -3103,39 +3305,46 @@ var SECTIONS = [
3103
3305
  { label: "Rate Limit", Panel: RateLimitPanel },
3104
3306
  { label: "DLP", Panel: DlpPanel },
3105
3307
  { label: "Audit", Panel: AuditPanel },
3106
- { label: "Settings", Panel: SettingsPanel }
3308
+ { label: "Settings", Panel: SettingsPanel },
3309
+ { label: "Accounts", Panel: AccountsPanel }
3107
3310
  ];
3108
3311
  var BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
3109
3312
  function Banner({ cols }) {
3110
3313
  const wide = cols >= 82;
3111
3314
  if (!wide) {
3112
- return /* @__PURE__ */ jsxs8(Box8, { children: [
3113
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
3114
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
3315
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
3316
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
3317
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
3115
3318
  ] });
3116
3319
  }
3117
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3118
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
3119
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
3320
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
3321
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
3322
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
3120
3323
  ] });
3121
3324
  }
3325
+ var ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
3122
3326
  function App() {
3123
3327
  const { exit } = useApp();
3124
- const [section, setSection] = useState8(0);
3125
- const [focus, setFocus] = useState8("nav");
3126
- const [help, setHelp] = useState8(false);
3127
- const [accounts] = useState8(() => listAccounts());
3128
- const [acctIdx, setAcctIdx] = useState8(0);
3328
+ const [accounts, setAccounts] = useState9(() => listAccounts());
3329
+ const [viewKey, setViewKey] = useState9(() => accounts[0]?.apiKey);
3330
+ const [viewNonce, setViewNonce] = useState9(0);
3331
+ const [section, setSection] = useState9(accounts.length === 0 ? ACCOUNTS_SECTION : 0);
3332
+ const [focus, setFocus] = useState9("nav");
3333
+ const [help, setHelp] = useState9(false);
3334
+ const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
3129
3335
  const cur = accounts[acctIdx];
3130
- const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "\u2014";
3336
+ const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "not logged in";
3337
+ const viewAccount = (a) => {
3338
+ setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3339
+ setAccounts(listAccounts());
3340
+ setViewKey(a.apiKey);
3341
+ setViewNonce((n) => n + 1);
3342
+ };
3131
3343
  const switchAccount = () => {
3132
3344
  if (accounts.length < 2) return;
3133
- const next = (acctIdx + 1) % accounts.length;
3134
- const a = accounts[next];
3135
- setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
3136
- setAcctIdx(next);
3345
+ viewAccount(accounts[(acctIdx + 1) % accounts.length]);
3137
3346
  };
3138
- useInput7((input, key) => {
3347
+ useInput8((input, key) => {
3139
3348
  if (help) {
3140
3349
  setHelp(false);
3141
3350
  return;
@@ -3157,23 +3366,24 @@ function App() {
3157
3366
  });
3158
3367
  const { cols, rows } = useTermSize();
3159
3368
  const current = SECTIONS[section];
3160
- if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
3369
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
3161
3370
  if (current.label === "Live" && focus === "panel") {
3162
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, acctIdx) });
3371
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
3163
3372
  }
3164
3373
  const Panel = current.Panel;
3165
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3166
- /* @__PURE__ */ jsx8(Banner, { cols }),
3167
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
3168
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
3169
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: acctLabel }),
3170
- accounts.length > 1 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch)` }) : null
3374
+ const panelBody = current.label === "Accounts" ? /* @__PURE__ */ jsx9(AccountsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount }) : /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
3375
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3376
+ /* @__PURE__ */ jsx9(Banner, { cols }),
3377
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
3378
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
3379
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: acctLabel }),
3380
+ accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : accounts.length === 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" }) : null
3171
3381
  ] }),
3172
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
3173
- /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx8(Text8, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
3174
- /* @__PURE__ */ jsx8(Box8, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: /* @__PURE__ */ jsx8(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" }) })
3175
- ] }, acctIdx),
3176
- /* @__PURE__ */ jsx8(Box8, { children: 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"]] }) })
3382
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
3383
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => /* @__PURE__ */ jsx9(Text9, { color: i === section ? theme.accentBright : void 0, bold: i === section, children: (i === section ? "\u25B8 " : " ") + s.label }, s.label)) }),
3384
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
3385
+ ] }, viewNonce),
3386
+ /* @__PURE__ */ jsx9(Box9, { children: focus === "nav" ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
3177
3387
  ] });
3178
3388
  }
3179
3389
  var HELP = [
@@ -3186,21 +3396,21 @@ var HELP = [
3186
3396
  ["Accounts", [["a", "switch account (view another account logged in on this device)"], ["", "the header shows which account you are viewing; guard/logging keep the active key"]]]
3187
3397
  ];
3188
3398
  function HelpOverlay({ cols, rows }) {
3189
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3190
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3191
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
3192
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
3193
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
3194
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3195
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
3399
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3400
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3401
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
3402
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
3403
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
3404
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3405
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
3196
3406
  ] }, k))
3197
3407
  ] }, group)) }),
3198
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
3408
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
3199
3409
  ] });
3200
3410
  }
3201
3411
 
3202
3412
  // src/tui/index.tsx
3203
- import { jsx as jsx9 } from "react/jsx-runtime";
3413
+ import { jsx as jsx10 } from "react/jsx-runtime";
3204
3414
  async function launchTui() {
3205
3415
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
3206
3416
  process.stderr.write(
@@ -3208,13 +3418,9 @@ async function launchTui() {
3208
3418
  );
3209
3419
  return;
3210
3420
  }
3211
- if (!isAuthenticated()) {
3212
- process.stderr.write("Not logged in. Run `solongate login` first.\n");
3213
- return;
3214
- }
3215
3421
  process.stdout.write("\x1B[?1049h\x1B[H");
3216
3422
  try {
3217
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
3423
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
3218
3424
  await waitUntilExit();
3219
3425
  } finally {
3220
3426
  process.stdout.write("\x1B[?1049l");
@@ -0,0 +1,7 @@
1
+ import { type SavedAccount } from '../../api-client/client.js';
2
+ export declare function AccountsPanel({ focused, viewApiKey, onView, }: {
3
+ active: boolean;
4
+ focused: boolean;
5
+ viewApiKey?: string;
6
+ onView?: (acc: SavedAccount) => void;
7
+ }): JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solongate/proxy",
3
- "version": "0.81.37",
3
+ "version": "0.81.38",
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": {