@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/index.js CHANGED
@@ -6554,179 +6554,31 @@ var init_cli_utils = __esm({
6554
6554
  }
6555
6555
  });
6556
6556
 
6557
- // src/api-client/client.ts
6558
- var client_exports = {};
6559
- __export(client_exports, {
6560
- ApiError: () => ApiError,
6561
- DEFAULT_API_URL: () => DEFAULT_API_URL2,
6562
- NotAuthenticatedError: () => NotAuthenticatedError,
6563
- isAuthenticated: () => isAuthenticated,
6564
- request: () => request,
6565
- resolveCredentials: () => resolveCredentials
6566
- });
6567
- import { readFileSync as readFileSync4, existsSync as existsSync3 } from "fs";
6568
- import { resolve as resolve3, join as join4 } from "path";
6569
- import { homedir as homedir2 } from "os";
6570
- function loginCredentialFile() {
6571
- try {
6572
- const p = join4(homedir2(), ".solongate", "cloud-guard.json");
6573
- if (!existsSync3(p)) return {};
6574
- const c2 = JSON.parse(readFileSync4(p, "utf-8"));
6575
- return c2 && typeof c2 === "object" ? c2 : {};
6576
- } catch {
6577
- return {};
6578
- }
6579
- }
6580
- function dotenvApiKey() {
6581
- try {
6582
- const envPath = resolve3(".env");
6583
- if (!existsSync3(envPath)) return void 0;
6584
- for (const line of readFileSync4(envPath, "utf-8").split("\n")) {
6585
- const trimmed = line.trim();
6586
- if (!trimmed || trimmed.startsWith("#")) continue;
6587
- const eq = trimmed.indexOf("=");
6588
- if (eq === -1) continue;
6589
- const key = trimmed.slice(0, eq).trim();
6590
- if (key !== "SOLONGATE_API_KEY") continue;
6591
- return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
6592
- }
6593
- } catch {
6594
- }
6595
- return void 0;
6596
- }
6597
- function isAuthenticated() {
6598
- try {
6599
- resolveCredentials();
6600
- return true;
6601
- } catch {
6602
- return false;
6603
- }
6604
- }
6605
- function resolveCredentials(apiUrlOverride) {
6606
- if (cached && !apiUrlOverride) return cached;
6607
- const file = loginCredentialFile();
6608
- const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
6609
- if (!apiKey) throw new NotAuthenticatedError();
6610
- const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL2;
6611
- const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
6612
- if (!apiUrlOverride) cached = creds;
6613
- return creds;
6614
- }
6615
- function buildUrl(base, path, query) {
6616
- const url = new URL(`${base}/api/v1${path}`);
6617
- if (query) {
6618
- for (const [k, v] of Object.entries(query)) {
6619
- if (v === void 0) continue;
6620
- url.searchParams.set(k, String(v));
6621
- }
6622
- }
6623
- return url.toString();
6624
- }
6625
- async function request(method, path, opts = {}) {
6626
- const creds = resolveCredentials(opts.apiUrl);
6627
- const url = buildUrl(creds.apiUrl, path, opts.query);
6628
- const headers = {
6629
- Authorization: `Bearer ${creds.apiKey}`
6630
- };
6631
- let bodyInit;
6632
- if (opts.body !== void 0) {
6633
- headers["Content-Type"] = "application/json";
6634
- bodyInit = JSON.stringify(opts.body);
6635
- }
6636
- const attempt = () => fetch(url, {
6637
- method,
6638
- headers: { ...headers, Connection: "close" },
6639
- body: bodyInit,
6640
- signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
6641
- });
6642
- const maxTries = method === "GET" ? 3 : 1;
6643
- let res;
6644
- let lastErr;
6645
- for (let i = 0; i < maxTries; i++) {
6646
- try {
6647
- res = await attempt();
6648
- break;
6649
- } catch (err2) {
6650
- lastErr = err2;
6651
- if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
6652
- }
6653
- }
6654
- if (!res) {
6655
- const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
6656
- throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
6657
- }
6658
- const text = await res.text().catch(() => "");
6659
- let json = void 0;
6660
- if (text) {
6661
- try {
6662
- json = JSON.parse(text);
6663
- } catch {
6664
- }
6665
- }
6666
- if (!res.ok) {
6667
- const envelope = json?.error;
6668
- if (envelope && typeof envelope === "object") {
6669
- throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
6670
- }
6671
- if (typeof envelope === "string") {
6672
- throw new ApiError(res.status, "ERROR", envelope);
6673
- }
6674
- if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
6675
- if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
6676
- throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
6677
- }
6678
- return json;
6679
- }
6680
- var DEFAULT_API_URL2, ApiError, NotAuthenticatedError, cached;
6681
- var init_client = __esm({
6682
- "src/api-client/client.ts"() {
6683
- "use strict";
6684
- DEFAULT_API_URL2 = "https://api.solongate.com";
6685
- ApiError = class extends Error {
6686
- status;
6687
- code;
6688
- constructor(status, code, message) {
6689
- super(message);
6690
- this.name = "ApiError";
6691
- this.status = status;
6692
- this.code = code;
6693
- }
6694
- };
6695
- NotAuthenticatedError = class extends Error {
6696
- constructor() {
6697
- super("Not logged in. Run `solongate login` first.");
6698
- this.name = "NotAuthenticatedError";
6699
- }
6700
- };
6701
- cached = null;
6702
- }
6703
- });
6704
-
6705
6557
  // src/tui/config.ts
6706
- import { readFileSync as readFileSync5 } from "fs";
6707
- import { homedir as homedir3 } from "os";
6708
- import { join as join5 } from "path";
6558
+ import { readFileSync as readFileSync4 } from "fs";
6559
+ import { homedir as homedir2 } from "os";
6560
+ import { join as join4 } from "path";
6709
6561
  function loadConfig() {
6710
- if (cached2) return cached2;
6562
+ if (cached) return cached;
6711
6563
  const defaults = { notifications: true };
6712
6564
  try {
6713
- const raw = readFileSync5(join5(homedir3(), ".solongate", "tui-config.json"), "utf-8");
6565
+ const raw = readFileSync4(join4(homedir2(), ".solongate", "tui-config.json"), "utf-8");
6714
6566
  const j = JSON.parse(raw);
6715
- cached2 = {
6567
+ cached = {
6716
6568
  notifications: j.notifications !== false,
6717
6569
  accent: typeof j.accent === "string" ? j.accent : void 0,
6718
6570
  pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
6719
6571
  };
6720
6572
  } catch {
6721
- cached2 = defaults;
6573
+ cached = defaults;
6722
6574
  }
6723
- return cached2;
6575
+ return cached;
6724
6576
  }
6725
- var cached2;
6577
+ var cached;
6726
6578
  var init_config2 = __esm({
6727
6579
  "src/tui/config.ts"() {
6728
6580
  "use strict";
6729
- cached2 = null;
6581
+ cached = null;
6730
6582
  }
6731
6583
  });
6732
6584
 
@@ -6836,9 +6688,9 @@ var init_components = __esm({
6836
6688
  });
6837
6689
 
6838
6690
  // src/tui/local-log.ts
6839
- import { closeSync, openSync, readFileSync as readFileSync6, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
6840
- import { homedir as homedir4 } from "os";
6841
- import { join as join6 } from "path";
6691
+ import { closeSync, openSync, readFileSync as readFileSync5, readSync, statSync, writeFileSync as writeFileSync3 } from "fs";
6692
+ import { homedir as homedir3 } from "os";
6693
+ import { join as join5 } from "path";
6842
6694
  function tailLines(file, maxBytes = 131072) {
6843
6695
  try {
6844
6696
  const size = statSync(file).size;
@@ -6856,7 +6708,7 @@ function tailLines(file, maxBytes = 131072) {
6856
6708
  }
6857
6709
  function deleteLocalEntry(at, tool, session) {
6858
6710
  try {
6859
- const lines = readFileSync6(LOCAL_LOG, "utf-8").split("\n");
6711
+ const lines = readFileSync5(LOCAL_LOG, "utf-8").split("\n");
6860
6712
  let removed = 0;
6861
6713
  const kept = lines.filter((line) => {
6862
6714
  if (!line.trim()) return false;
@@ -6880,7 +6732,7 @@ function deleteLocalEntry(at, tool, session) {
6880
6732
  }
6881
6733
  function clearLocalLog() {
6882
6734
  try {
6883
- const n = readFileSync6(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
6735
+ const n = readFileSync5(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
6884
6736
  writeFileSync3(LOCAL_LOG, "");
6885
6737
  return n;
6886
6738
  } catch {
@@ -6912,12 +6764,208 @@ var LOCAL_LOG, DLP_REASON, RL_REASON;
6912
6764
  var init_local_log = __esm({
6913
6765
  "src/tui/local-log.ts"() {
6914
6766
  "use strict";
6915
- LOCAL_LOG = join6(homedir4(), ".solongate", "local-logs", "solongate-audit.jsonl");
6767
+ LOCAL_LOG = join5(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
6916
6768
  DLP_REASON = /security layer \(dlp\)/i;
6917
6769
  RL_REASON = /security layer \(rate limit\)|rate[- ]?limit(?:ed)?\b.*exceed|exceeded \d+ calls/i;
6918
6770
  }
6919
6771
  });
6920
6772
 
6773
+ // src/api-client/client.ts
6774
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
6775
+ import { resolve as resolve3, join as join6 } from "path";
6776
+ import { homedir as homedir4 } from "os";
6777
+ function listAccounts() {
6778
+ let list6 = [];
6779
+ try {
6780
+ const raw = JSON.parse(readFileSync6(accountsFile(), "utf-8"));
6781
+ if (Array.isArray(raw)) list6 = raw.filter((a) => a && typeof a.apiKey === "string");
6782
+ } catch {
6783
+ }
6784
+ const active2 = loginCredentialFile();
6785
+ if (active2.apiKey && !list6.some((a) => a.apiKey === active2.apiKey)) {
6786
+ list6.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL2 });
6787
+ }
6788
+ return list6;
6789
+ }
6790
+ function saveAccount(acc) {
6791
+ try {
6792
+ const list6 = (() => {
6793
+ try {
6794
+ const raw = JSON.parse(readFileSync6(accountsFile(), "utf-8"));
6795
+ return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
6796
+ } catch {
6797
+ return [];
6798
+ }
6799
+ })();
6800
+ const next = list6.filter((a) => a.apiKey !== acc.apiKey);
6801
+ next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
6802
+ mkdirSync3(join6(homedir4(), ".solongate"), { recursive: true });
6803
+ writeFileSync4(accountsFile(), JSON.stringify(next, null, 2));
6804
+ } catch {
6805
+ }
6806
+ }
6807
+ function setViewCredentials(creds) {
6808
+ viewOverride = creds;
6809
+ cached2 = null;
6810
+ }
6811
+ function isActiveAccount(apiKey) {
6812
+ return loginCredentialFile().apiKey === apiKey;
6813
+ }
6814
+ function setActiveAccount(creds) {
6815
+ try {
6816
+ const dir = join6(homedir4(), ".solongate");
6817
+ mkdirSync3(dir, { recursive: true });
6818
+ const p = join6(dir, ["cloud", "guard.json"].join("-"));
6819
+ let existing = {};
6820
+ try {
6821
+ existing = JSON.parse(readFileSync6(p, "utf-8"));
6822
+ } catch {
6823
+ }
6824
+ writeFileSync4(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2));
6825
+ cached2 = null;
6826
+ return true;
6827
+ } catch {
6828
+ return false;
6829
+ }
6830
+ }
6831
+ function loginCredentialFile() {
6832
+ try {
6833
+ const p = join6(homedir4(), ".solongate", "cloud-guard.json");
6834
+ if (!existsSync3(p)) return {};
6835
+ const c2 = JSON.parse(readFileSync6(p, "utf-8"));
6836
+ return c2 && typeof c2 === "object" ? c2 : {};
6837
+ } catch {
6838
+ return {};
6839
+ }
6840
+ }
6841
+ function dotenvApiKey() {
6842
+ try {
6843
+ const envPath = resolve3(".env");
6844
+ if (!existsSync3(envPath)) return void 0;
6845
+ for (const line of readFileSync6(envPath, "utf-8").split("\n")) {
6846
+ const trimmed = line.trim();
6847
+ if (!trimmed || trimmed.startsWith("#")) continue;
6848
+ const eq = trimmed.indexOf("=");
6849
+ if (eq === -1) continue;
6850
+ const key = trimmed.slice(0, eq).trim();
6851
+ if (key !== "SOLONGATE_API_KEY") continue;
6852
+ return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
6853
+ }
6854
+ } catch {
6855
+ }
6856
+ return void 0;
6857
+ }
6858
+ function isAuthenticated() {
6859
+ try {
6860
+ resolveCredentials();
6861
+ return true;
6862
+ } catch {
6863
+ return false;
6864
+ }
6865
+ }
6866
+ function resolveCredentials(apiUrlOverride) {
6867
+ if (viewOverride && !apiUrlOverride) return viewOverride;
6868
+ if (cached2 && !apiUrlOverride) return cached2;
6869
+ const file = loginCredentialFile();
6870
+ const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
6871
+ if (!apiKey) throw new NotAuthenticatedError();
6872
+ const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL2;
6873
+ const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
6874
+ if (!apiUrlOverride) cached2 = creds;
6875
+ return creds;
6876
+ }
6877
+ function buildUrl(base, path, query) {
6878
+ const url = new URL(`${base}/api/v1${path}`);
6879
+ if (query) {
6880
+ for (const [k, v] of Object.entries(query)) {
6881
+ if (v === void 0) continue;
6882
+ url.searchParams.set(k, String(v));
6883
+ }
6884
+ }
6885
+ return url.toString();
6886
+ }
6887
+ async function request(method, path, opts = {}) {
6888
+ const creds = resolveCredentials(opts.apiUrl);
6889
+ const url = buildUrl(creds.apiUrl, path, opts.query);
6890
+ const headers = {
6891
+ Authorization: `Bearer ${creds.apiKey}`
6892
+ };
6893
+ let bodyInit;
6894
+ if (opts.body !== void 0) {
6895
+ headers["Content-Type"] = "application/json";
6896
+ bodyInit = JSON.stringify(opts.body);
6897
+ }
6898
+ const attempt = () => fetch(url, {
6899
+ method,
6900
+ headers: { ...headers, Connection: "close" },
6901
+ body: bodyInit,
6902
+ signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
6903
+ });
6904
+ const maxTries = method === "GET" ? 3 : 1;
6905
+ let res;
6906
+ let lastErr;
6907
+ for (let i = 0; i < maxTries; i++) {
6908
+ try {
6909
+ res = await attempt();
6910
+ break;
6911
+ } catch (err2) {
6912
+ lastErr = err2;
6913
+ if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
6914
+ }
6915
+ }
6916
+ if (!res) {
6917
+ const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
6918
+ throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
6919
+ }
6920
+ const text = await res.text().catch(() => "");
6921
+ let json = void 0;
6922
+ if (text) {
6923
+ try {
6924
+ json = JSON.parse(text);
6925
+ } catch {
6926
+ }
6927
+ }
6928
+ if (!res.ok) {
6929
+ const envelope = json?.error;
6930
+ if (envelope && typeof envelope === "object") {
6931
+ throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
6932
+ }
6933
+ if (typeof envelope === "string") {
6934
+ throw new ApiError(res.status, "ERROR", envelope);
6935
+ }
6936
+ if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
6937
+ if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
6938
+ throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
6939
+ }
6940
+ return json;
6941
+ }
6942
+ var DEFAULT_API_URL2, accountsFile, viewOverride, ApiError, NotAuthenticatedError, cached2;
6943
+ var init_client = __esm({
6944
+ "src/api-client/client.ts"() {
6945
+ "use strict";
6946
+ DEFAULT_API_URL2 = "https://api.solongate.com";
6947
+ accountsFile = () => join6(homedir4(), ".solongate", "accounts.json");
6948
+ viewOverride = null;
6949
+ ApiError = class extends Error {
6950
+ status;
6951
+ code;
6952
+ constructor(status, code, message) {
6953
+ super(message);
6954
+ this.name = "ApiError";
6955
+ this.status = status;
6956
+ this.code = code;
6957
+ }
6958
+ };
6959
+ NotAuthenticatedError = class extends Error {
6960
+ constructor() {
6961
+ super("Not logged in. Run `solongate login` first.");
6962
+ this.name = "NotAuthenticatedError";
6963
+ }
6964
+ };
6965
+ cached2 = null;
6966
+ }
6967
+ });
6968
+
6921
6969
  // src/api-client/types.ts
6922
6970
  var init_types = __esm({
6923
6971
  "src/api-client/types.ts"() {
@@ -6925,6 +6973,54 @@ var init_types = __esm({
6925
6973
  }
6926
6974
  });
6927
6975
 
6976
+ // src/api-client/device-login.ts
6977
+ import { spawn } from "child_process";
6978
+ function openBrowser(url) {
6979
+ try {
6980
+ const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
6981
+ const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
6982
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true });
6983
+ child.on("error", () => {
6984
+ });
6985
+ child.unref();
6986
+ } catch {
6987
+ }
6988
+ }
6989
+ async function startDeviceLogin(apiUrl = DEFAULT_API_URL2) {
6990
+ const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
6991
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
6992
+ const j = await res.json();
6993
+ return {
6994
+ deviceCode: j.device_code,
6995
+ verifyUrl: j.verification_uri_complete || j.verification_uri || "",
6996
+ intervalMs: Math.max(2, Number(j.interval) || 3) * 1e3,
6997
+ expiresAt: Date.now() + (Number(j.expires_in) || 600) * 1e3
6998
+ };
6999
+ }
7000
+ async function pollDeviceLogin(apiUrl, deviceCode) {
7001
+ try {
7002
+ const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
7003
+ method: "POST",
7004
+ headers: { "Content-Type": "application/json" },
7005
+ body: JSON.stringify({ device_code: deviceCode })
7006
+ });
7007
+ const j = await res.json().catch(() => ({}));
7008
+ if (j.status === "approved" && j.api_key) {
7009
+ return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email };
7010
+ }
7011
+ if (j.status === "expired" || j.status === "not_found") return { status: j.status };
7012
+ return { status: "pending" };
7013
+ } catch {
7014
+ return { status: "pending" };
7015
+ }
7016
+ }
7017
+ var init_device_login = __esm({
7018
+ "src/api-client/device-login.ts"() {
7019
+ "use strict";
7020
+ init_client();
7021
+ }
7022
+ });
7023
+
6928
7024
  // src/api-client/policies.ts
6929
7025
  var policies_exports = {};
6930
7026
  __export(policies_exports, {
@@ -7195,6 +7291,7 @@ var init_api_client = __esm({
7195
7291
  "use strict";
7196
7292
  init_client();
7197
7293
  init_types();
7294
+ init_device_login();
7198
7295
  init_policies();
7199
7296
  init_settings();
7200
7297
  init_stats();
@@ -7207,24 +7304,24 @@ var init_api_client = __esm({
7207
7304
  });
7208
7305
 
7209
7306
  // src/tui/notify.ts
7210
- import { spawn } from "child_process";
7307
+ import { spawn as spawn2 } from "child_process";
7211
7308
  function desktopNotify(title, msg) {
7212
7309
  try {
7213
7310
  if (process.platform === "linux") {
7214
- const p = spawn("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
7311
+ const p = spawn2("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
7215
7312
  p.on("error", () => {
7216
7313
  });
7217
7314
  p.unref();
7218
7315
  } else if (process.platform === "win32") {
7219
7316
  const q = (s) => s.replace(/'/g, "''");
7220
7317
  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()`;
7221
- const p = spawn("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "ignore", detached: true, windowsHide: true });
7318
+ const p = spawn2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "ignore", detached: true, windowsHide: true });
7222
7319
  p.on("error", () => {
7223
7320
  });
7224
7321
  p.unref();
7225
7322
  } else if (process.platform === "darwin") {
7226
7323
  const esc = (s) => s.replace(/"/g, '\\"');
7227
- const p = spawn("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
7324
+ const p = spawn2("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
7228
7325
  p.on("error", () => {
7229
7326
  });
7230
7327
  p.unref();
@@ -7310,7 +7407,7 @@ var init_hooks = __esm({
7310
7407
  // src/tui/panels/Live.tsx
7311
7408
  import { Box as Box2, Text as Text2, useInput } from "ink";
7312
7409
  import TextInput from "ink-text-input";
7313
- import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
7410
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
7314
7411
  import { homedir as homedir5 } from "os";
7315
7412
  import { join as join7 } from "path";
7316
7413
  import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
@@ -7861,8 +7958,8 @@ function LivePanel({ active: active2 }) {
7861
7958
  else if (input === "e") {
7862
7959
  const file = join7(homedir5(), ".solongate", "live-export.jsonl");
7863
7960
  try {
7864
- mkdirSync3(join7(homedir5(), ".solongate"), { recursive: true });
7865
- writeFileSync4(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7961
+ mkdirSync4(join7(homedir5(), ".solongate"), { recursive: true });
7962
+ writeFileSync5(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
7866
7963
  setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
7867
7964
  } catch (err2) {
7868
7965
  setActionMsg({ text: "\u2717 export failed: " + (err2 instanceof Error ? err2.message : String(err2)), level: "bad", until: Date.now() + 6e3 });
@@ -8880,7 +8977,7 @@ var init_Dlp = __esm({
8880
8977
  // src/tui/panels/Audit.tsx
8881
8978
  import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
8882
8979
  import TextInput4 from "ink-text-input";
8883
- import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "fs";
8980
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync6 } from "fs";
8884
8981
  import { homedir as homedir6 } from "os";
8885
8982
  import { join as join8 } from "path";
8886
8983
  import { useState as useState6 } from "react";
@@ -9044,8 +9141,8 @@ function AuditPanel({ active: active2, focused }) {
9044
9141
  const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
9045
9142
  rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
9046
9143
  } else rows2 = localFiltered;
9047
- mkdirSync4(dir, { recursive: true });
9048
- writeFileSync5(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
9144
+ mkdirSync5(dir, { recursive: true });
9145
+ writeFileSync6(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
9049
9146
  return { n: rows2.length, file };
9050
9147
  };
9051
9148
  run12().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" }));
@@ -9747,18 +9844,158 @@ var init_Settings = __esm({
9747
9844
  }
9748
9845
  });
9749
9846
 
9750
- // src/tui/App.tsx
9751
- import { Box as Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
9752
- import { useState as useState8 } from "react";
9847
+ // src/tui/panels/Accounts.tsx
9848
+ import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
9849
+ import { useEffect as useEffect6, useRef as useRef3, useState as useState8 } from "react";
9753
9850
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
9754
- function LiveHint() {
9851
+ function AccountsPanel({
9852
+ focused,
9853
+ viewApiKey,
9854
+ onView
9855
+ }) {
9856
+ const [accounts, setAccounts] = useState8(() => listAccounts());
9857
+ const [sel, setSel] = useState8(0);
9858
+ const [msg, setMsg] = useState8(null);
9859
+ const [login, setLogin] = useState8(null);
9860
+ const [tick, setTick] = useState8(0);
9861
+ const abort = useRef3(false);
9862
+ const refresh = () => setAccounts(listAccounts());
9863
+ useEffect6(() => {
9864
+ if (!login || login.phase === "done" || login.phase === "error") return;
9865
+ const t = setInterval(() => setTick((n) => n + 1), 120);
9866
+ return () => clearInterval(t);
9867
+ }, [login]);
9868
+ const beginLogin = () => {
9869
+ if (login && (login.phase === "starting" || login.phase === "waiting")) return;
9870
+ abort.current = false;
9871
+ setLogin({ phase: "starting" });
9872
+ void (async () => {
9873
+ let start;
9874
+ try {
9875
+ start = await startDeviceLogin(DEFAULT_API_URL2);
9876
+ } catch (e) {
9877
+ setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
9878
+ return;
9879
+ }
9880
+ setLogin({ phase: "waiting", url: start.verifyUrl });
9881
+ openBrowser(start.verifyUrl);
9882
+ while (Date.now() < start.expiresAt && !abort.current) {
9883
+ await new Promise((r) => setTimeout(r, start.intervalMs));
9884
+ if (abort.current) return;
9885
+ const res = await pollDeviceLogin(DEFAULT_API_URL2, start.deviceCode);
9886
+ if (res.status === "approved" && res.apiKey) {
9887
+ saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL2, project: res.project, user: res.user });
9888
+ setLogin({ phase: "done", msg: `\u2713 added ${res.project || res.user || "account"}` });
9889
+ refresh();
9890
+ setViewCredentials({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL2 });
9891
+ onView?.({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL2, project: res.project, user: res.user });
9892
+ return;
9893
+ }
9894
+ if (res.status === "expired" || res.status === "not_found") {
9895
+ setLogin({ phase: "error", msg: "code expired \u2014 press n to retry" });
9896
+ return;
9897
+ }
9898
+ }
9899
+ if (!abort.current) setLogin({ phase: "error", msg: "timed out \u2014 press n to retry" });
9900
+ })();
9901
+ };
9902
+ useInput7(
9903
+ (input, key) => {
9904
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
9905
+ if (key.escape) {
9906
+ abort.current = true;
9907
+ setLogin(null);
9908
+ }
9909
+ return;
9910
+ }
9911
+ if (key.upArrow) setSel((n) => Math.max(0, n - 1));
9912
+ else if (key.downArrow) setSel((n) => Math.min(accounts.length - 1, n + 1));
9913
+ else if (key.return) {
9914
+ const a = accounts[sel];
9915
+ if (a) {
9916
+ setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
9917
+ onView?.(a);
9918
+ setMsg({ text: `viewing ${a.project || a.user || a.apiKey.slice(0, 8)}`, level: "ok" });
9919
+ }
9920
+ } else if (input === "m") {
9921
+ const a = accounts[sel];
9922
+ if (a) {
9923
+ const ok = setActiveAccount({ apiKey: a.apiKey, apiUrl: a.apiUrl });
9924
+ 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" });
9925
+ refresh();
9926
+ }
9927
+ } else if (input === "n") beginLogin();
9928
+ else if (input === "r") refresh();
9929
+ },
9930
+ { isActive: focused }
9931
+ );
9932
+ const spin = SPIN2[tick % SPIN2.length];
9933
+ if (login && (login.phase === "starting" || login.phase === "waiting")) {
9934
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9935
+ /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "Add an account" }),
9936
+ login.phase === "starting" ? /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
9937
+ spin,
9938
+ " starting device pairing\u2026"
9939
+ ] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
9940
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
9941
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
9942
+ /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
9943
+ /* @__PURE__ */ jsxs8(Text8, { color: theme.warn, children: [
9944
+ spin,
9945
+ " waiting for authorization\u2026 "
9946
+ ] }),
9947
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "esc to cancel" })
9948
+ ] })
9949
+ ] })
9950
+ ] });
9951
+ }
9755
9952
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9756
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
9757
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
9758
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
9759
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text8, { children: [
9953
+ /* @__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" }),
9954
+ msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate2(msg.text, 70) }) : login?.msg ? /* @__PURE__ */ jsx8(Text8, { color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
9955
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: accounts.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9956
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "No accounts on this device yet." }),
9957
+ /* @__PURE__ */ jsxs8(Text8, { children: [
9958
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "n" }),
9959
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " to log in \u2014 pairs this device and starts protecting your Claude Code sessions." })
9960
+ ] })
9961
+ ] }) : accounts.map((a, i) => {
9962
+ const isSel = i === sel && focused;
9963
+ const isView = viewApiKey ? a.apiKey === viewApiKey : i === 0;
9964
+ const isActive = isActiveAccount(a.apiKey);
9965
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isSel ? "#333333" : void 0, bold: isSel, children: [
9966
+ /* @__PURE__ */ jsx8(Text8, { color: isSel ? theme.accentBright : theme.dim, children: isSel ? "\u25B8 " : " " }),
9967
+ /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate2(a.project || a.user || a.apiKey.slice(0, 12), 30).padEnd(31) }),
9968
+ isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
9969
+ isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
9970
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.user && a.project ? truncate2(a.user, 24) : "" })
9971
+ ] }, a.apiKey);
9972
+ }) }),
9973
+ /* @__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" }) })
9974
+ ] });
9975
+ }
9976
+ var SPIN2;
9977
+ var init_Accounts = __esm({
9978
+ "src/tui/panels/Accounts.tsx"() {
9979
+ "use strict";
9980
+ init_client();
9981
+ init_device_login();
9982
+ init_theme();
9983
+ SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
9984
+ }
9985
+ });
9986
+
9987
+ // src/tui/App.tsx
9988
+ import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
9989
+ import { useState as useState9 } from "react";
9990
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
9991
+ function LiveHint() {
9992
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
9993
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
9994
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
9995
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
9996
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
9760
9997
  "press ",
9761
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
9998
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
9762
9999
  " to go live"
9763
10000
  ] }) })
9764
10001
  ] });
@@ -9766,22 +10003,38 @@ function LiveHint() {
9766
10003
  function Banner({ cols }) {
9767
10004
  const wide = cols >= 82;
9768
10005
  if (!wide) {
9769
- return /* @__PURE__ */ jsxs8(Box8, { children: [
9770
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
9771
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " \u2014 security control center" })
10006
+ return /* @__PURE__ */ jsxs9(Box9, { children: [
10007
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
10008
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
9772
10009
  ] });
9773
10010
  }
9774
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9775
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
9776
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
10011
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
10012
+ BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
10013
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
9777
10014
  ] });
9778
10015
  }
9779
10016
  function App() {
9780
10017
  const { exit } = useApp();
9781
- const [section, setSection] = useState8(0);
9782
- const [focus, setFocus] = useState8("nav");
9783
- const [help, setHelp] = useState8(false);
9784
- useInput7((input, key) => {
10018
+ const [accounts, setAccounts] = useState9(() => listAccounts());
10019
+ const [viewKey, setViewKey] = useState9(() => accounts[0]?.apiKey);
10020
+ const [viewNonce, setViewNonce] = useState9(0);
10021
+ const [section, setSection] = useState9(accounts.length === 0 ? ACCOUNTS_SECTION : 0);
10022
+ const [focus, setFocus] = useState9("nav");
10023
+ const [help, setHelp] = useState9(false);
10024
+ const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
10025
+ const cur = accounts[acctIdx];
10026
+ const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "not logged in";
10027
+ const viewAccount = (a) => {
10028
+ setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
10029
+ setAccounts(listAccounts());
10030
+ setViewKey(a.apiKey);
10031
+ setViewNonce((n) => n + 1);
10032
+ };
10033
+ const switchAccount = () => {
10034
+ if (accounts.length < 2) return;
10035
+ viewAccount(accounts[(acctIdx + 1) % accounts.length]);
10036
+ };
10037
+ useInput8((input, key) => {
9785
10038
  if (help) {
9786
10039
  setHelp(false);
9787
10040
  return;
@@ -9794,6 +10047,7 @@ function App() {
9794
10047
  if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
9795
10048
  else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
9796
10049
  else if (key.rightArrow || key.return || key.tab) setFocus("panel");
10050
+ else if (input === "a") switchAccount();
9797
10051
  else if (input === "q") exit();
9798
10052
  } else {
9799
10053
  if (key.escape || key.tab) setFocus("nav");
@@ -9802,34 +10056,40 @@ function App() {
9802
10056
  });
9803
10057
  const { cols, rows } = useTermSize();
9804
10058
  const current = SECTIONS[section];
9805
- if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
10059
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
9806
10060
  if (current.label === "Live" && focus === "panel") {
9807
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }) });
10061
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
9808
10062
  }
9809
10063
  const Panel = current.Panel;
9810
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
9811
- /* @__PURE__ */ jsx8(Banner, { cols }),
9812
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
9813
- /* @__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)) }),
9814
- /* @__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" }) })
10064
+ 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" });
10065
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
10066
+ /* @__PURE__ */ jsx9(Banner, { cols }),
10067
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
10068
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
10069
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: acctLabel }),
10070
+ 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
9815
10071
  ] }),
9816
- /* @__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"]] }) })
10072
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
10073
+ /* @__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)) }),
10074
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
10075
+ ] }, viewNonce),
10076
+ /* @__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"]] }) })
9817
10077
  ] });
9818
10078
  }
9819
10079
  function HelpOverlay({ cols, rows }) {
9820
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
9821
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
9822
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
9823
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
9824
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
9825
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
9826
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
10080
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
10081
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
10082
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
10083
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
10084
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
10085
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
10086
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
9827
10087
  ] }, k))
9828
10088
  ] }, group)) }),
9829
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
10089
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
9830
10090
  ] });
9831
10091
  }
9832
- var SECTIONS, BANNER_HEX, HELP;
10092
+ var SECTIONS, BANNER_HEX, ACCOUNTS_SECTION, HELP;
9833
10093
  var init_App = __esm({
9834
10094
  "src/tui/App.tsx"() {
9835
10095
  "use strict";
@@ -9842,23 +10102,28 @@ var init_App = __esm({
9842
10102
  init_Dlp();
9843
10103
  init_Audit();
9844
10104
  init_Settings();
10105
+ init_Accounts();
9845
10106
  init_hooks();
10107
+ init_client();
9846
10108
  SECTIONS = [
9847
10109
  { label: "Live", Panel: LiveHint },
9848
10110
  { label: "Policies", Panel: PoliciesPanel },
9849
10111
  { label: "Rate Limit", Panel: RateLimitPanel },
9850
10112
  { label: "DLP", Panel: DlpPanel },
9851
10113
  { label: "Audit", Panel: AuditPanel },
9852
- { label: "Settings", Panel: SettingsPanel }
10114
+ { label: "Settings", Panel: SettingsPanel },
10115
+ { label: "Accounts", Panel: AccountsPanel }
9853
10116
  ];
9854
10117
  BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
10118
+ ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
9855
10119
  HELP = [
9856
10120
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
9857
10121
  ["Live", [["\u2191\u2193", "select a stream row"], ["enter", "full entry content"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
9858
10122
  ["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"]]],
9859
10123
  ["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"]]],
9860
10124
  ["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"]]],
9861
- ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
10125
+ ["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
10126
+ ["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"]]]
9862
10127
  ];
9863
10128
  }
9864
10129
  });
@@ -9869,7 +10134,7 @@ __export(tui_exports, {
9869
10134
  launchTui: () => launchTui
9870
10135
  });
9871
10136
  import { render } from "ink";
9872
- import { jsx as jsx9 } from "react/jsx-runtime";
10137
+ import { jsx as jsx10 } from "react/jsx-runtime";
9873
10138
  async function launchTui() {
9874
10139
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
9875
10140
  process.stderr.write(
@@ -9877,13 +10142,9 @@ async function launchTui() {
9877
10142
  );
9878
10143
  return;
9879
10144
  }
9880
- if (!isAuthenticated()) {
9881
- process.stderr.write("Not logged in. Run `solongate login` first.\n");
9882
- return;
9883
- }
9884
10145
  process.stdout.write("\x1B[?1049h\x1B[H");
9885
10146
  try {
9886
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
10147
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
9887
10148
  await waitUntilExit();
9888
10149
  } finally {
9889
10150
  process.stdout.write("\x1B[?1049l");
@@ -9893,7 +10154,6 @@ var init_tui = __esm({
9893
10154
  "src/tui/index.tsx"() {
9894
10155
  "use strict";
9895
10156
  init_App();
9896
- init_client();
9897
10157
  }
9898
10158
  });
9899
10159
 
@@ -11063,7 +11323,7 @@ __export(global_install_exports, {
11063
11323
  runGlobalRestore: () => runGlobalRestore,
11064
11324
  unlockProtected: () => unlockProtected
11065
11325
  });
11066
- import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync6, mkdirSync as mkdirSync5 } from "fs";
11326
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync6, mkdirSync as mkdirSync6 } from "fs";
11067
11327
  import { resolve as resolve4, join as join11, dirname } from "path";
11068
11328
  import { homedir as homedir9 } from "os";
11069
11329
  import { fileURLToPath } from "url";
@@ -11176,13 +11436,13 @@ function runGlobalRestore() {
11176
11436
  unlockProtected();
11177
11437
  removeClaudeShim();
11178
11438
  if (existsSync6(p.backupPath)) {
11179
- writeFileSync6(p.settingsPath, readFileSync8(p.backupPath, "utf-8"));
11439
+ writeFileSync7(p.settingsPath, readFileSync8(p.backupPath, "utf-8"));
11180
11440
  console.log(` Restored ${p.settingsPath} from backup.`);
11181
11441
  } else if (existsSync6(p.settingsPath)) {
11182
11442
  try {
11183
11443
  const s = JSON.parse(readFileSync8(p.settingsPath, "utf-8"));
11184
11444
  delete s.hooks;
11185
- writeFileSync6(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
11445
+ writeFileSync7(p.settingsPath, JSON.stringify(s, null, 2) + "\n");
11186
11446
  console.log(` Removed SolonGate hooks from ${p.settingsPath}.`);
11187
11447
  } catch {
11188
11448
  }
@@ -11226,8 +11486,8 @@ function writeShimBlock(file, block2) {
11226
11486
  if (content.length && !content.endsWith("\n")) content += "\n";
11227
11487
  content += block2 + "\n";
11228
11488
  }
11229
- mkdirSync5(dirname(file), { recursive: true });
11230
- writeFileSync6(file, content);
11489
+ mkdirSync6(dirname(file), { recursive: true });
11490
+ writeFileSync7(file, content);
11231
11491
  }
11232
11492
  function installClaudeShim(shieldPath) {
11233
11493
  const real = resolveRealClaude();
@@ -11278,22 +11538,22 @@ async function runGlobalInstall(opts = {}) {
11278
11538
  process.exit(1);
11279
11539
  }
11280
11540
  const apiUrl = opts.apiUrl || process.env["SOLONGATE_API_URL"] || "https://api.solongate.com";
11281
- mkdirSync5(p.hooksDir, { recursive: true });
11282
- mkdirSync5(p.claudeDir, { recursive: true });
11541
+ mkdirSync6(p.hooksDir, { recursive: true });
11542
+ mkdirSync6(p.claudeDir, { recursive: true });
11283
11543
  unlockProtected();
11284
- writeFileSync6(join11(p.hooksDir, "guard.mjs"), readGuard());
11285
- writeFileSync6(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11286
- writeFileSync6(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11287
- writeFileSync6(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11544
+ writeFileSync7(join11(p.hooksDir, "guard.mjs"), readGuard());
11545
+ writeFileSync7(join11(p.hooksDir, "audit.mjs"), readHook("audit.mjs"));
11546
+ writeFileSync7(join11(p.hooksDir, "stop.mjs"), readHook("stop.mjs"));
11547
+ writeFileSync7(join11(p.hooksDir, "shield.mjs"), readHook("shield.mjs"));
11288
11548
  console.log(` Installed hooks \u2192 ${p.hooksDir}`);
11289
11549
  installClaudeShim(join11(p.hooksDir, "shield.mjs"));
11290
- writeFileSync6(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11550
+ writeFileSync7(p.configPath, JSON.stringify({ apiKey, apiUrl }, null, 2) + "\n");
11291
11551
  console.log(` Wrote ${p.configPath}`);
11292
11552
  let existing = {};
11293
11553
  if (existsSync6(p.settingsPath)) {
11294
11554
  const raw = readFileSync8(p.settingsPath, "utf-8");
11295
11555
  if (!existsSync6(p.backupPath)) {
11296
- writeFileSync6(p.backupPath, raw);
11556
+ writeFileSync7(p.backupPath, raw);
11297
11557
  console.log(` Backed up existing settings \u2192 ${p.backupPath}`);
11298
11558
  }
11299
11559
  try {
@@ -11316,7 +11576,7 @@ async function runGlobalInstall(opts = {}) {
11316
11576
  Stop: [{ matcher: "", hooks: [{ type: "command", command: hookCmd(stopAbs) }] }]
11317
11577
  }
11318
11578
  };
11319
- writeFileSync6(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11579
+ writeFileSync7(p.settingsPath, JSON.stringify(merged, null, 2) + "\n");
11320
11580
  console.log(` Registered global hooks \u2192 ${p.settingsPath}`);
11321
11581
  if (process.env["SOLONGATE_OS_LOCK"] === "1") {
11322
11582
  lockProtected();
@@ -11339,7 +11599,7 @@ var init_global_install = __esm({
11339
11599
 
11340
11600
  // src/login.ts
11341
11601
  var login_exports = {};
11342
- import { spawn as spawn2 } from "child_process";
11602
+ import { spawn as spawn3 } from "child_process";
11343
11603
  function startSpinner(text) {
11344
11604
  if (!process.stderr.isTTY) {
11345
11605
  process.stderr.write(` ${text}
@@ -11371,11 +11631,11 @@ function parseArgs2(argv) {
11371
11631
  }
11372
11632
  return { apiUrl, install };
11373
11633
  }
11374
- function openBrowser(url) {
11634
+ function openBrowser2(url) {
11375
11635
  try {
11376
11636
  const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
11377
11637
  const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
11378
- const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
11638
+ const child = spawn3(cmd, args, { stdio: "ignore", detached: true });
11379
11639
  child.on("error", () => {
11380
11640
  });
11381
11641
  child.unref();
@@ -11405,7 +11665,7 @@ async function main() {
11405
11665
  console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
11406
11666
  console.log("");
11407
11667
  void user_code;
11408
- openBrowser(verifyUrl);
11668
+ openBrowser2(verifyUrl);
11409
11669
  const pollMs = Math.max(2, Number(interval) || 3) * 1e3;
11410
11670
  const deadline = Date.now() + (Number(expires_in) || 600) * 1e3;
11411
11671
  let apiKey = "";
@@ -11434,6 +11694,7 @@ async function main() {
11434
11694
  if (data?.project?.name) {
11435
11695
  console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
11436
11696
  }
11697
+ saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email });
11437
11698
  break;
11438
11699
  }
11439
11700
  if (data?.status === "expired" || data?.status === "not_found") {
@@ -11478,6 +11739,7 @@ var init_login = __esm({
11478
11739
  "use strict";
11479
11740
  init_cli_utils();
11480
11741
  init_global_install();
11742
+ init_client();
11481
11743
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
11482
11744
  SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
11483
11745
  main().catch((err2) => {
@@ -11494,7 +11756,7 @@ __export(shield_exports, {
11494
11756
  });
11495
11757
  import { createServer, request as httpRequest } from "http";
11496
11758
  import { request as httpsRequest } from "https";
11497
- import { spawn as spawn3 } from "child_process";
11759
+ import { spawn as spawn4 } from "child_process";
11498
11760
  import { URL as URL2 } from "url";
11499
11761
  import { readFileSync as readFileSync9, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11500
11762
  import { resolve as resolve5 } from "path";
@@ -11754,7 +12016,7 @@ async function runShield() {
11754
12016
  const upstream = pickUpstream();
11755
12017
  const { port, close } = await startProxy(upstream);
11756
12018
  log4(`redacting secrets on the LLM path \u2192 masking before ${upstream.host} (127.0.0.1:${port})`);
11757
- const child = spawn3(cmd[0], cmd.slice(1), {
12019
+ const child = spawn4(cmd[0], cmd.slice(1), {
11758
12020
  stdio: "inherit",
11759
12021
  env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
11760
12022
  shell: process.platform === "win32"
@@ -11992,7 +12254,7 @@ var init_logs_server = __esm({
11992
12254
 
11993
12255
  // src/inject.ts
11994
12256
  var inject_exports = {};
11995
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync8, copyFileSync } from "fs";
12257
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync8, copyFileSync } from "fs";
11996
12258
  import { resolve as resolve7 } from "path";
11997
12259
  import { execSync } from "child_process";
11998
12260
  function parseInjectArgs(argv) {
@@ -12300,7 +12562,7 @@ async function main2() {
12300
12562
  log3("");
12301
12563
  log3(` Backup: ${backupPath}`);
12302
12564
  }
12303
- writeFileSync7(entryFile, result.modified);
12565
+ writeFileSync8(entryFile, result.modified);
12304
12566
  log3("");
12305
12567
  log3(" \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\u2510");
12306
12568
  log3(" \u2502 SolonGate SDK injected successfully! \u2502");
@@ -12329,7 +12591,7 @@ var init_inject = __esm({
12329
12591
 
12330
12592
  // src/create.ts
12331
12593
  var create_exports = {};
12332
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync8, existsSync as existsSync9 } from "fs";
12594
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync9, existsSync as existsSync9 } from "fs";
12333
12595
  import { resolve as resolve8, join as join13 } from "path";
12334
12596
  import { execSync as execSync2 } from "child_process";
12335
12597
  function withSpinner(message, fn) {
@@ -12409,7 +12671,7 @@ EXAMPLES
12409
12671
  `);
12410
12672
  }
12411
12673
  function createProject(dir, name, _policy) {
12412
- writeFileSync8(
12674
+ writeFileSync9(
12413
12675
  join13(dir, "package.json"),
12414
12676
  JSON.stringify(
12415
12677
  {
@@ -12439,7 +12701,7 @@ function createProject(dir, name, _policy) {
12439
12701
  2
12440
12702
  ) + "\n"
12441
12703
  );
12442
- writeFileSync8(
12704
+ writeFileSync9(
12443
12705
  join13(dir, "tsconfig.json"),
12444
12706
  JSON.stringify(
12445
12707
  {
@@ -12460,8 +12722,8 @@ function createProject(dir, name, _policy) {
12460
12722
  2
12461
12723
  ) + "\n"
12462
12724
  );
12463
- mkdirSync6(join13(dir, "src"), { recursive: true });
12464
- writeFileSync8(
12725
+ mkdirSync7(join13(dir, "src"), { recursive: true });
12726
+ writeFileSync9(
12465
12727
  join13(dir, "src", "index.ts"),
12466
12728
  `#!/usr/bin/env node
12467
12729
 
@@ -12503,7 +12765,7 @@ console.log('');
12503
12765
  console.log('Press Ctrl+C to stop.');
12504
12766
  `
12505
12767
  );
12506
- writeFileSync8(
12768
+ writeFileSync9(
12507
12769
  join13(dir, ".mcp.json"),
12508
12770
  JSON.stringify(
12509
12771
  {
@@ -12521,12 +12783,12 @@ console.log('Press Ctrl+C to stop.');
12521
12783
  2
12522
12784
  ) + "\n"
12523
12785
  );
12524
- writeFileSync8(
12786
+ writeFileSync9(
12525
12787
  join13(dir, ".env"),
12526
12788
  `SOLONGATE_API_KEY=sg_live_YOUR_KEY_HERE
12527
12789
  `
12528
12790
  );
12529
- writeFileSync8(
12791
+ writeFileSync9(
12530
12792
  join13(dir, ".gitignore"),
12531
12793
  `node_modules/
12532
12794
  dist/
@@ -12546,7 +12808,7 @@ async function main3() {
12546
12808
  process.exit(1);
12547
12809
  }
12548
12810
  withSpinner(`Setting up ${opts.name}...`, () => {
12549
- mkdirSync6(dir, { recursive: true });
12811
+ mkdirSync7(dir, { recursive: true });
12550
12812
  createProject(dir, opts.name, opts.policy);
12551
12813
  });
12552
12814
  if (!opts.noInstall) {
@@ -12620,7 +12882,7 @@ var init_create = __esm({
12620
12882
 
12621
12883
  // src/pull-push.ts
12622
12884
  var pull_push_exports = {};
12623
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync10 } from "fs";
12885
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync10, existsSync as existsSync10 } from "fs";
12624
12886
  import { resolve as resolve9 } from "path";
12625
12887
  function loadEnv() {
12626
12888
  if (process.env.SOLONGATE_API_KEY) return;
@@ -12815,7 +13077,7 @@ async function pull(apiKey, file, policyId) {
12815
13077
  const policy = await fetchCloudPolicy(apiKey, API_URL, policyId);
12816
13078
  const { id: _id, ...policyWithoutId } = policy;
12817
13079
  const json = JSON.stringify(policyWithoutId, null, 2) + "\n";
12818
- writeFileSync9(file, json, "utf-8");
13080
+ writeFileSync10(file, json, "utf-8");
12819
13081
  log5("");
12820
13082
  log5(green2(" Saved to: ") + file);
12821
13083
  log5(` ${dim2("Name:")} ${policy.name}`);
@@ -15995,8 +16257,7 @@ function printWelcome() {
15995
16257
  async function main5() {
15996
16258
  const subcommand = process.argv[2];
15997
16259
  if (process.argv.length <= 2) {
15998
- const { isAuthenticated: isAuthenticated2 } = await Promise.resolve().then(() => (init_client(), client_exports));
15999
- if (process.stdout.isTTY && isAuthenticated2()) {
16260
+ if (process.stdout.isTTY && process.stdin.isTTY) {
16000
16261
  const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
16001
16262
  await launchTui2();
16002
16263
  return;