@solongate/proxy 0.81.36 → 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/login.js CHANGED
@@ -263,6 +263,29 @@ async function runGlobalInstall(opts = {}) {
263
263
  }
264
264
  }
265
265
 
266
+ // src/api-client/client.ts
267
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
268
+ import { resolve as resolve2, join as join2 } from "path";
269
+ import { homedir as homedir2 } from "os";
270
+ var accountsFile = () => join2(homedir2(), ".solongate", "accounts.json");
271
+ function saveAccount(acc) {
272
+ try {
273
+ const list = (() => {
274
+ try {
275
+ const raw = JSON.parse(readFileSync2(accountsFile(), "utf-8"));
276
+ return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
277
+ } catch {
278
+ return [];
279
+ }
280
+ })();
281
+ const next = list.filter((a) => a.apiKey !== acc.apiKey);
282
+ next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
283
+ mkdirSync2(join2(homedir2(), ".solongate"), { recursive: true });
284
+ writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
285
+ } catch {
286
+ }
287
+ }
288
+
266
289
  // src/login.ts
267
290
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
268
291
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -360,6 +383,7 @@ async function main() {
360
383
  if (data?.project?.name) {
361
384
  console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
362
385
  }
386
+ saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email });
363
387
  break;
364
388
  }
365
389
  if (data?.status === "expired" || data?.status === "not_found") {
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 = {
@@ -160,7 +160,7 @@ function KeyHints({ hints }) {
160
160
  // src/tui/panels/Live.tsx
161
161
  import { Box as Box2, Text as Text2, useInput } from "ink";
162
162
  import TextInput from "ink-text-input";
163
- import { mkdirSync, writeFileSync as writeFileSync2 } from "fs";
163
+ import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
164
164
  import { homedir as homedir4 } from "os";
165
165
  import { join as join4 } from "path";
166
166
 
@@ -245,10 +245,66 @@ function parseLocalLines(lines) {
245
245
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
246
246
 
247
247
  // src/api-client/client.ts
248
- import { readFileSync as readFileSync3, existsSync } from "fs";
248
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync } from "fs";
249
249
  import { resolve, join as join3 } from "path";
250
250
  import { homedir as homedir3 } from "os";
251
251
  var DEFAULT_API_URL = "https://api.solongate.com";
252
+ var accountsFile = () => join3(homedir3(), ".solongate", "accounts.json");
253
+ function listAccounts() {
254
+ let list5 = [];
255
+ try {
256
+ const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
257
+ if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
258
+ } catch {
259
+ }
260
+ const active2 = loginCredentialFile();
261
+ if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
262
+ list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL });
263
+ }
264
+ return list5;
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
+ }
283
+ var viewOverride = null;
284
+ function setViewCredentials(creds) {
285
+ viewOverride = creds;
286
+ cached2 = null;
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
+ }
252
308
  var ApiError = class extends Error {
253
309
  status;
254
310
  code;
@@ -293,15 +349,8 @@ function dotenvApiKey() {
293
349
  return void 0;
294
350
  }
295
351
  var cached2 = null;
296
- function isAuthenticated() {
297
- try {
298
- resolveCredentials();
299
- return true;
300
- } catch {
301
- return false;
302
- }
303
- }
304
352
  function resolveCredentials(apiUrlOverride) {
353
+ if (viewOverride && !apiUrlOverride) return viewOverride;
305
354
  if (cached2 && !apiUrlOverride) return cached2;
306
355
  const file = loginCredentialFile();
307
356
  const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
@@ -377,6 +426,48 @@ async function request(method, path, opts = {}) {
377
426
  return json;
378
427
  }
379
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
+
380
471
  // src/api-client/policies.ts
381
472
  var policies_exports = {};
382
473
  __export(policies_exports, {
@@ -602,24 +693,24 @@ function list4() {
602
693
  var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
603
694
 
604
695
  // src/tui/notify.ts
605
- import { spawn } from "child_process";
696
+ import { spawn as spawn2 } from "child_process";
606
697
  function desktopNotify(title, msg) {
607
698
  try {
608
699
  if (process.platform === "linux") {
609
- 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 });
610
701
  p.on("error", () => {
611
702
  });
612
703
  p.unref();
613
704
  } else if (process.platform === "win32") {
614
705
  const q = (s) => s.replace(/'/g, "''");
615
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()`;
616
- 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 });
617
708
  p.on("error", () => {
618
709
  });
619
710
  p.unref();
620
711
  } else if (process.platform === "darwin") {
621
712
  const esc = (s) => s.replace(/"/g, '\\"');
622
- 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 });
623
714
  p.on("error", () => {
624
715
  });
625
716
  p.unref();
@@ -1300,8 +1391,8 @@ function LivePanel({ active: active2 }) {
1300
1391
  else if (input === "e") {
1301
1392
  const file = join4(homedir4(), ".solongate", "live-export.jsonl");
1302
1393
  try {
1303
- mkdirSync(join4(homedir4(), ".solongate"), { recursive: true });
1304
- writeFileSync2(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1394
+ mkdirSync2(join4(homedir4(), ".solongate"), { recursive: true });
1395
+ writeFileSync3(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
1305
1396
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
1306
1397
  } catch (err) {
1307
1398
  setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
@@ -2217,7 +2308,7 @@ function RegexTest({ re }) {
2217
2308
  // src/tui/panels/Audit.tsx
2218
2309
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
2219
2310
  import TextInput4 from "ink-text-input";
2220
- import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
2311
+ import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
2221
2312
  import { homedir as homedir5 } from "os";
2222
2313
  import { join as join5 } from "path";
2223
2314
  import { useState as useState6 } from "react";
@@ -2451,8 +2542,8 @@ function AuditPanel({ active: active2, focused }) {
2451
2542
  const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
2452
2543
  rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
2453
2544
  } else rows2 = localFiltered;
2454
- mkdirSync2(dir, { recursive: true });
2455
- writeFileSync3(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
2545
+ mkdirSync3(dir, { recursive: true });
2546
+ writeFileSync4(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
2456
2547
  return { n: rows2.length, file };
2457
2548
  };
2458
2549
  run().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
@@ -3063,16 +3154,147 @@ function SettingsPanel({ active: active2, focused }) {
3063
3154
  ] }) });
3064
3155
  }
3065
3156
 
3066
- // 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";
3067
3160
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
3068
- 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
+ }
3069
3263
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3070
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
3071
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
3072
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
3073
- /* @__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: [
3074
3296
  "press ",
3075
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
3297
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
3076
3298
  " to go live"
3077
3299
  ] }) })
3078
3300
  ] });
@@ -3083,28 +3305,46 @@ var SECTIONS = [
3083
3305
  { label: "Rate Limit", Panel: RateLimitPanel },
3084
3306
  { label: "DLP", Panel: DlpPanel },
3085
3307
  { label: "Audit", Panel: AuditPanel },
3086
- { label: "Settings", Panel: SettingsPanel }
3308
+ { label: "Settings", Panel: SettingsPanel },
3309
+ { label: "Accounts", Panel: AccountsPanel }
3087
3310
  ];
3088
3311
  var BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
3089
3312
  function Banner({ cols }) {
3090
3313
  const wide = cols >= 82;
3091
3314
  if (!wide) {
3092
- return /* @__PURE__ */ jsxs8(Box8, { children: [
3093
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
3094
- /* @__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" })
3095
3318
  ] });
3096
3319
  }
3097
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
3098
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
3099
- /* @__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" })
3100
3323
  ] });
3101
3324
  }
3325
+ var ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
3102
3326
  function App() {
3103
3327
  const { exit } = useApp();
3104
- const [section, setSection] = useState8(0);
3105
- const [focus, setFocus] = useState8("nav");
3106
- const [help, setHelp] = useState8(false);
3107
- useInput7((input, key) => {
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));
3335
+ const cur = accounts[acctIdx];
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
+ };
3343
+ const switchAccount = () => {
3344
+ if (accounts.length < 2) return;
3345
+ viewAccount(accounts[(acctIdx + 1) % accounts.length]);
3346
+ };
3347
+ useInput8((input, key) => {
3108
3348
  if (help) {
3109
3349
  setHelp(false);
3110
3350
  return;
@@ -3117,6 +3357,7 @@ function App() {
3117
3357
  if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
3118
3358
  else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
3119
3359
  else if (key.rightArrow || key.return || key.tab) setFocus("panel");
3360
+ else if (input === "a") switchAccount();
3120
3361
  else if (input === "q") exit();
3121
3362
  } else {
3122
3363
  if (key.escape || key.tab) setFocus("nav");
@@ -3125,18 +3366,24 @@ function App() {
3125
3366
  });
3126
3367
  const { cols, rows } = useTermSize();
3127
3368
  const current = SECTIONS[section];
3128
- if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
3369
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
3129
3370
  if (current.label === "Live" && focus === "panel") {
3130
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
3371
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
3131
3372
  }
3132
3373
  const Panel = current.Panel;
3133
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
3134
- /* @__PURE__ */ jsx8(Banner, { cols }),
3135
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
3136
- /* @__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)) }),
3137
- /* @__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" }) })
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
3138
3381
  ] }),
3139
- /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ["?", "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"]] }) })
3140
3387
  ] });
3141
3388
  }
3142
3389
  var HELP = [
@@ -3145,24 +3392,25 @@ var HELP = [
3145
3392
  ["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["s", "save"], ["x", "discard"]]],
3146
3393
  ["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]],
3147
3394
  ["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["x / X", "delete entry / ALL (press twice)"], ["c", "clear filters"]]],
3148
- ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
3395
+ ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
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"]]]
3149
3397
  ];
3150
3398
  function HelpOverlay({ cols, rows }) {
3151
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
3152
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
3153
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
3154
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
3155
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
3156
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
3157
- /* @__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 })
3158
3406
  ] }, k))
3159
3407
  ] }, group)) }),
3160
- /* @__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" })
3161
3409
  ] });
3162
3410
  }
3163
3411
 
3164
3412
  // src/tui/index.tsx
3165
- import { jsx as jsx9 } from "react/jsx-runtime";
3413
+ import { jsx as jsx10 } from "react/jsx-runtime";
3166
3414
  async function launchTui() {
3167
3415
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
3168
3416
  process.stderr.write(
@@ -3170,13 +3418,9 @@ async function launchTui() {
3170
3418
  );
3171
3419
  return;
3172
3420
  }
3173
- if (!isAuthenticated()) {
3174
- process.stderr.write("Not logged in. Run `solongate login` first.\n");
3175
- return;
3176
- }
3177
3421
  process.stdout.write("\x1B[?1049h\x1B[H");
3178
3422
  try {
3179
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
3423
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
3180
3424
  await waitUntilExit();
3181
3425
  } finally {
3182
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.36",
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": {