@solongate/proxy 0.81.37 → 0.81.39

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,219 +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
- listAccounts: () => listAccounts,
6565
- request: () => request,
6566
- resolveCredentials: () => resolveCredentials,
6567
- saveAccount: () => saveAccount,
6568
- setViewCredentials: () => setViewCredentials
6569
- });
6570
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync3 } from "fs";
6571
- import { resolve as resolve3, join as join4 } from "path";
6572
- import { homedir as homedir2 } from "os";
6573
- function listAccounts() {
6574
- let list6 = [];
6575
- try {
6576
- const raw = JSON.parse(readFileSync4(accountsFile(), "utf-8"));
6577
- if (Array.isArray(raw)) list6 = raw.filter((a) => a && typeof a.apiKey === "string");
6578
- } catch {
6579
- }
6580
- const active2 = loginCredentialFile();
6581
- if (active2.apiKey && !list6.some((a) => a.apiKey === active2.apiKey)) {
6582
- list6.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL2 });
6583
- }
6584
- return list6;
6585
- }
6586
- function saveAccount(acc) {
6587
- try {
6588
- const list6 = (() => {
6589
- try {
6590
- const raw = JSON.parse(readFileSync4(accountsFile(), "utf-8"));
6591
- return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
6592
- } catch {
6593
- return [];
6594
- }
6595
- })();
6596
- const next = list6.filter((a) => a.apiKey !== acc.apiKey);
6597
- next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
6598
- mkdirSync3(join4(homedir2(), ".solongate"), { recursive: true });
6599
- writeFileSync3(accountsFile(), JSON.stringify(next, null, 2));
6600
- } catch {
6601
- }
6602
- }
6603
- function setViewCredentials(creds) {
6604
- viewOverride = creds;
6605
- cached = null;
6606
- }
6607
- function loginCredentialFile() {
6608
- try {
6609
- const p = join4(homedir2(), ".solongate", "cloud-guard.json");
6610
- if (!existsSync3(p)) return {};
6611
- const c2 = JSON.parse(readFileSync4(p, "utf-8"));
6612
- return c2 && typeof c2 === "object" ? c2 : {};
6613
- } catch {
6614
- return {};
6615
- }
6616
- }
6617
- function dotenvApiKey() {
6618
- try {
6619
- const envPath = resolve3(".env");
6620
- if (!existsSync3(envPath)) return void 0;
6621
- for (const line of readFileSync4(envPath, "utf-8").split("\n")) {
6622
- const trimmed = line.trim();
6623
- if (!trimmed || trimmed.startsWith("#")) continue;
6624
- const eq = trimmed.indexOf("=");
6625
- if (eq === -1) continue;
6626
- const key = trimmed.slice(0, eq).trim();
6627
- if (key !== "SOLONGATE_API_KEY") continue;
6628
- return trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, "");
6629
- }
6630
- } catch {
6631
- }
6632
- return void 0;
6633
- }
6634
- function isAuthenticated() {
6635
- try {
6636
- resolveCredentials();
6637
- return true;
6638
- } catch {
6639
- return false;
6640
- }
6641
- }
6642
- function resolveCredentials(apiUrlOverride) {
6643
- if (viewOverride && !apiUrlOverride) return viewOverride;
6644
- if (cached && !apiUrlOverride) return cached;
6645
- const file = loginCredentialFile();
6646
- const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
6647
- if (!apiKey) throw new NotAuthenticatedError();
6648
- const apiUrl = apiUrlOverride || process.env["SOLONGATE_API_URL"] || file.apiUrl || DEFAULT_API_URL2;
6649
- const creds = { apiKey, apiUrl: apiUrl.replace(/\/$/, "") };
6650
- if (!apiUrlOverride) cached = creds;
6651
- return creds;
6652
- }
6653
- function buildUrl(base, path, query) {
6654
- const url = new URL(`${base}/api/v1${path}`);
6655
- if (query) {
6656
- for (const [k, v] of Object.entries(query)) {
6657
- if (v === void 0) continue;
6658
- url.searchParams.set(k, String(v));
6659
- }
6660
- }
6661
- return url.toString();
6662
- }
6663
- async function request(method, path, opts = {}) {
6664
- const creds = resolveCredentials(opts.apiUrl);
6665
- const url = buildUrl(creds.apiUrl, path, opts.query);
6666
- const headers = {
6667
- Authorization: `Bearer ${creds.apiKey}`
6668
- };
6669
- let bodyInit;
6670
- if (opts.body !== void 0) {
6671
- headers["Content-Type"] = "application/json";
6672
- bodyInit = JSON.stringify(opts.body);
6673
- }
6674
- const attempt = () => fetch(url, {
6675
- method,
6676
- headers: { ...headers, Connection: "close" },
6677
- body: bodyInit,
6678
- signal: AbortSignal.timeout(opts.timeoutMs ?? 15e3)
6679
- });
6680
- const maxTries = method === "GET" ? 3 : 1;
6681
- let res;
6682
- let lastErr;
6683
- for (let i = 0; i < maxTries; i++) {
6684
- try {
6685
- res = await attempt();
6686
- break;
6687
- } catch (err2) {
6688
- lastErr = err2;
6689
- if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
6690
- }
6691
- }
6692
- if (!res) {
6693
- const msg = lastErr instanceof Error ? lastErr.message : String(lastErr);
6694
- throw new ApiError(0, "NETWORK_ERROR", `Cannot reach SolonGate API: ${msg}`);
6695
- }
6696
- const text = await res.text().catch(() => "");
6697
- let json = void 0;
6698
- if (text) {
6699
- try {
6700
- json = JSON.parse(text);
6701
- } catch {
6702
- }
6703
- }
6704
- if (!res.ok) {
6705
- const envelope = json?.error;
6706
- if (envelope && typeof envelope === "object") {
6707
- throw new ApiError(res.status, envelope.code || "ERROR", envelope.message || res.statusText);
6708
- }
6709
- if (typeof envelope === "string") {
6710
- throw new ApiError(res.status, "ERROR", envelope);
6711
- }
6712
- if (res.status === 401) throw new ApiError(401, "AUTHENTICATION_ERROR", "Invalid API key. Run `solongate login`.");
6713
- if (res.status === 429) throw new ApiError(429, "RATE_LIMITED", "Rate limited by the API. Slow down and retry.");
6714
- throw new ApiError(res.status, "ERROR", text || res.statusText || `HTTP ${res.status}`);
6715
- }
6716
- return json;
6717
- }
6718
- var DEFAULT_API_URL2, accountsFile, viewOverride, ApiError, NotAuthenticatedError, cached;
6719
- var init_client = __esm({
6720
- "src/api-client/client.ts"() {
6721
- "use strict";
6722
- DEFAULT_API_URL2 = "https://api.solongate.com";
6723
- accountsFile = () => join4(homedir2(), ".solongate", "accounts.json");
6724
- viewOverride = null;
6725
- ApiError = class extends Error {
6726
- status;
6727
- code;
6728
- constructor(status, code, message) {
6729
- super(message);
6730
- this.name = "ApiError";
6731
- this.status = status;
6732
- this.code = code;
6733
- }
6734
- };
6735
- NotAuthenticatedError = class extends Error {
6736
- constructor() {
6737
- super("Not logged in. Run `solongate login` first.");
6738
- this.name = "NotAuthenticatedError";
6739
- }
6740
- };
6741
- cached = null;
6742
- }
6743
- });
6744
-
6745
6557
  // src/tui/config.ts
6746
- import { readFileSync as readFileSync5 } from "fs";
6747
- import { homedir as homedir3 } from "os";
6748
- 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";
6749
6561
  function loadConfig() {
6750
- if (cached2) return cached2;
6562
+ if (cached) return cached;
6751
6563
  const defaults = { notifications: true };
6752
6564
  try {
6753
- const raw = readFileSync5(join5(homedir3(), ".solongate", "tui-config.json"), "utf-8");
6565
+ const raw = readFileSync4(join4(homedir2(), ".solongate", "tui-config.json"), "utf-8");
6754
6566
  const j = JSON.parse(raw);
6755
- cached2 = {
6567
+ cached = {
6756
6568
  notifications: j.notifications !== false,
6757
6569
  accent: typeof j.accent === "string" ? j.accent : void 0,
6758
6570
  pollMs: typeof j.pollMs === "number" ? j.pollMs : void 0
6759
6571
  };
6760
6572
  } catch {
6761
- cached2 = defaults;
6573
+ cached = defaults;
6762
6574
  }
6763
- return cached2;
6575
+ return cached;
6764
6576
  }
6765
- var cached2;
6577
+ var cached;
6766
6578
  var init_config2 = __esm({
6767
6579
  "src/tui/config.ts"() {
6768
6580
  "use strict";
6769
- cached2 = null;
6581
+ cached = null;
6770
6582
  }
6771
6583
  });
6772
6584
 
@@ -6876,9 +6688,9 @@ var init_components = __esm({
6876
6688
  });
6877
6689
 
6878
6690
  // src/tui/local-log.ts
6879
- import { closeSync, openSync, readFileSync as readFileSync6, readSync, statSync, writeFileSync as writeFileSync4 } from "fs";
6880
- import { homedir as homedir4 } from "os";
6881
- 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";
6882
6694
  function tailLines(file, maxBytes = 131072) {
6883
6695
  try {
6884
6696
  const size = statSync(file).size;
@@ -6896,7 +6708,7 @@ function tailLines(file, maxBytes = 131072) {
6896
6708
  }
6897
6709
  function deleteLocalEntry(at, tool, session) {
6898
6710
  try {
6899
- const lines = readFileSync6(LOCAL_LOG, "utf-8").split("\n");
6711
+ const lines = readFileSync5(LOCAL_LOG, "utf-8").split("\n");
6900
6712
  let removed = 0;
6901
6713
  const kept = lines.filter((line) => {
6902
6714
  if (!line.trim()) return false;
@@ -6912,7 +6724,7 @@ function deleteLocalEntry(at, tool, session) {
6912
6724
  }
6913
6725
  return true;
6914
6726
  });
6915
- if (removed) writeFileSync4(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
6727
+ if (removed) writeFileSync3(LOCAL_LOG, kept.length ? kept.join("\n") + "\n" : "");
6916
6728
  return removed;
6917
6729
  } catch {
6918
6730
  return 0;
@@ -6920,8 +6732,8 @@ function deleteLocalEntry(at, tool, session) {
6920
6732
  }
6921
6733
  function clearLocalLog() {
6922
6734
  try {
6923
- const n = readFileSync6(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
6924
- writeFileSync4(LOCAL_LOG, "");
6735
+ const n = readFileSync5(LOCAL_LOG, "utf-8").split("\n").filter(Boolean).length;
6736
+ writeFileSync3(LOCAL_LOG, "");
6925
6737
  return n;
6926
6738
  } catch {
6927
6739
  return 0;
@@ -6952,12 +6764,208 @@ var LOCAL_LOG, DLP_REASON, RL_REASON;
6952
6764
  var init_local_log = __esm({
6953
6765
  "src/tui/local-log.ts"() {
6954
6766
  "use strict";
6955
- LOCAL_LOG = join6(homedir4(), ".solongate", "local-logs", "solongate-audit.jsonl");
6767
+ LOCAL_LOG = join5(homedir3(), ".solongate", "local-logs", "solongate-audit.jsonl");
6956
6768
  DLP_REASON = /security layer \(dlp\)/i;
6957
6769
  RL_REASON = /security layer \(rate limit\)|rate[- ]?limit(?:ed)?\b.*exceed|exceeded \d+ calls/i;
6958
6770
  }
6959
6771
  });
6960
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
+
6961
6969
  // src/api-client/types.ts
6962
6970
  var init_types = __esm({
6963
6971
  "src/api-client/types.ts"() {
@@ -6965,6 +6973,54 @@ var init_types = __esm({
6965
6973
  }
6966
6974
  });
6967
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
+
6968
7024
  // src/api-client/policies.ts
6969
7025
  var policies_exports = {};
6970
7026
  __export(policies_exports, {
@@ -7235,6 +7291,7 @@ var init_api_client = __esm({
7235
7291
  "use strict";
7236
7292
  init_client();
7237
7293
  init_types();
7294
+ init_device_login();
7238
7295
  init_policies();
7239
7296
  init_settings();
7240
7297
  init_stats();
@@ -7247,24 +7304,24 @@ var init_api_client = __esm({
7247
7304
  });
7248
7305
 
7249
7306
  // src/tui/notify.ts
7250
- import { spawn } from "child_process";
7307
+ import { spawn as spawn2 } from "child_process";
7251
7308
  function desktopNotify(title, msg) {
7252
7309
  try {
7253
7310
  if (process.platform === "linux") {
7254
- 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 });
7255
7312
  p.on("error", () => {
7256
7313
  });
7257
7314
  p.unref();
7258
7315
  } else if (process.platform === "win32") {
7259
7316
  const q = (s) => s.replace(/'/g, "''");
7260
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()`;
7261
- 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 });
7262
7319
  p.on("error", () => {
7263
7320
  });
7264
7321
  p.unref();
7265
7322
  } else if (process.platform === "darwin") {
7266
7323
  const esc = (s) => s.replace(/"/g, '\\"');
7267
- 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 });
7268
7325
  p.on("error", () => {
7269
7326
  });
7270
7327
  p.unref();
@@ -9787,18 +9844,158 @@ var init_Settings = __esm({
9787
9844
  }
9788
9845
  });
9789
9846
 
9790
- // src/tui/App.tsx
9791
- import { Box as Box8, Text as Text8, useApp, useInput as useInput7 } from "ink";
9792
- 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";
9793
9850
  import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
9794
- 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
+ }
9795
9952
  return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9796
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
9797
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
9798
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
9799
- /* @__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: [
9800
9997
  "press ",
9801
- /* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "enter" }),
9998
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
9802
9999
  " to go live"
9803
10000
  ] }) })
9804
10001
  ] });
@@ -9806,33 +10003,40 @@ function LiveHint() {
9806
10003
  function Banner({ cols }) {
9807
10004
  const wide = cols >= 82;
9808
10005
  if (!wide) {
9809
- return /* @__PURE__ */ jsxs8(Box8, { children: [
9810
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate" }),
9811
- /* @__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" })
9812
10009
  ] });
9813
10010
  }
9814
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
9815
- BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx8(Text8, { bold: true, color: BANNER_HEX[i], children: line }, i)),
9816
- /* @__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" })
9817
10014
  ] });
9818
10015
  }
9819
10016
  function App() {
9820
10017
  const { exit } = useApp();
9821
- const [section, setSection] = useState8(0);
9822
- const [focus, setFocus] = useState8("nav");
9823
- const [help, setHelp] = useState8(false);
9824
- const [accounts] = useState8(() => listAccounts());
9825
- const [acctIdx, setAcctIdx] = useState8(0);
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));
9826
10025
  const cur = accounts[acctIdx];
9827
- const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "\u2014";
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
+ };
9828
10033
  const switchAccount = () => {
9829
10034
  if (accounts.length < 2) return;
9830
- const next = (acctIdx + 1) % accounts.length;
9831
- const a = accounts[next];
9832
- setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
9833
- setAcctIdx(next);
10035
+ viewAccount(accounts[(acctIdx + 1) % accounts.length]);
9834
10036
  };
9835
- useInput7((input, key) => {
10037
+ const locked = accounts.length === 0;
10038
+ const effectiveSection = locked ? ACCOUNTS_SECTION : section;
10039
+ useInput8((input, key) => {
9836
10040
  if (help) {
9837
10041
  setHelp(false);
9838
10042
  return;
@@ -9841,6 +10045,12 @@ function App() {
9841
10045
  setHelp(true);
9842
10046
  return;
9843
10047
  }
10048
+ if (locked) {
10049
+ if (key.rightArrow || key.return || key.tab) setFocus("panel");
10050
+ else if (key.escape) setFocus("nav");
10051
+ else if (input === "q" && focus === "nav") exit();
10052
+ return;
10053
+ }
9844
10054
  if (focus === "nav") {
9845
10055
  if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
9846
10056
  else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
@@ -9853,40 +10063,45 @@ function App() {
9853
10063
  }
9854
10064
  });
9855
10065
  const { cols, rows } = useTermSize();
9856
- const current = SECTIONS[section];
9857
- if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
10066
+ const current = SECTIONS[effectiveSection];
10067
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
9858
10068
  if (current.label === "Live" && focus === "panel") {
9859
- return /* @__PURE__ */ jsx8(Box8, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx8(LivePanel, { active: true, focused: true }, acctIdx) });
10069
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
9860
10070
  }
9861
10071
  const Panel = current.Panel;
9862
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
9863
- /* @__PURE__ */ jsx8(Banner, { cols }),
9864
- /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
9865
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "account: " }),
9866
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, children: acctLabel }),
9867
- accounts.length > 1 ? /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch)` }) : null
10072
+ 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" });
10073
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
10074
+ /* @__PURE__ */ jsx9(Banner, { cols }),
10075
+ /* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
10076
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
10077
+ /* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel }),
10078
+ locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Accounts to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" })
9868
10079
  ] }),
9869
- /* @__PURE__ */ jsxs8(Box8, { marginTop: 1, flexGrow: 1, children: [
9870
- /* @__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)) }),
9871
- /* @__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" }) })
9872
- ] }, acctIdx),
9873
- /* @__PURE__ */ jsx8(Box8, { children: focus === "nav" ? /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx8(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
10080
+ /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
10081
+ /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
10082
+ const isCur = i === effectiveSection;
10083
+ const disabled = locked && s.label !== "Accounts";
10084
+ return /* @__PURE__ */ jsx9(Text9, { color: isCur ? theme.accentBright : disabled ? theme.dim : void 0, bold: isCur, dimColor: disabled, children: (isCur ? "\u25B8 " : disabled ? "\u2298 " : " ") + s.label }, s.label);
10085
+ }) }),
10086
+ /* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
10087
+ ] }, viewNonce),
10088
+ /* @__PURE__ */ jsx9(Box9, { children: locked ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2192/enter", "open Accounts"], ["n", "log in"], ["q", "quit"]] }) : 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"]] }) })
9874
10089
  ] });
9875
10090
  }
9876
10091
  function HelpOverlay({ cols, rows }) {
9877
- return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
9878
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
9879
- /* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginBottom: 1, children: [
9880
- /* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accent, children: group }),
9881
- keys.map(([k, desc]) => /* @__PURE__ */ jsxs8(Text8, { children: [
9882
- /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
9883
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: desc })
10092
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
10093
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
10094
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
10095
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
10096
+ keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
10097
+ /* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
10098
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
9884
10099
  ] }, k))
9885
10100
  ] }, group)) }),
9886
- /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "press any key to close" })
10101
+ /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
9887
10102
  ] });
9888
10103
  }
9889
- var SECTIONS, BANNER_HEX, HELP;
10104
+ var SECTIONS, BANNER_HEX, ACCOUNTS_SECTION, HELP;
9890
10105
  var init_App = __esm({
9891
10106
  "src/tui/App.tsx"() {
9892
10107
  "use strict";
@@ -9899,6 +10114,7 @@ var init_App = __esm({
9899
10114
  init_Dlp();
9900
10115
  init_Audit();
9901
10116
  init_Settings();
10117
+ init_Accounts();
9902
10118
  init_hooks();
9903
10119
  init_client();
9904
10120
  SECTIONS = [
@@ -9907,9 +10123,11 @@ var init_App = __esm({
9907
10123
  { label: "Rate Limit", Panel: RateLimitPanel },
9908
10124
  { label: "DLP", Panel: DlpPanel },
9909
10125
  { label: "Audit", Panel: AuditPanel },
9910
- { label: "Settings", Panel: SettingsPanel }
10126
+ { label: "Settings", Panel: SettingsPanel },
10127
+ { label: "Accounts", Panel: AccountsPanel }
9911
10128
  ];
9912
10129
  BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
10130
+ ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
9913
10131
  HELP = [
9914
10132
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
9915
10133
  ["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)"]]],
@@ -9928,7 +10146,7 @@ __export(tui_exports, {
9928
10146
  launchTui: () => launchTui
9929
10147
  });
9930
10148
  import { render } from "ink";
9931
- import { jsx as jsx9 } from "react/jsx-runtime";
10149
+ import { jsx as jsx10 } from "react/jsx-runtime";
9932
10150
  async function launchTui() {
9933
10151
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
9934
10152
  process.stderr.write(
@@ -9936,13 +10154,9 @@ async function launchTui() {
9936
10154
  );
9937
10155
  return;
9938
10156
  }
9939
- if (!isAuthenticated()) {
9940
- process.stderr.write("Not logged in. Run `solongate login` first.\n");
9941
- return;
9942
- }
9943
10157
  process.stdout.write("\x1B[?1049h\x1B[H");
9944
10158
  try {
9945
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
10159
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
9946
10160
  await waitUntilExit();
9947
10161
  } finally {
9948
10162
  process.stdout.write("\x1B[?1049l");
@@ -9952,7 +10166,6 @@ var init_tui = __esm({
9952
10166
  "src/tui/index.tsx"() {
9953
10167
  "use strict";
9954
10168
  init_App();
9955
- init_client();
9956
10169
  }
9957
10170
  });
9958
10171
 
@@ -11398,7 +11611,7 @@ var init_global_install = __esm({
11398
11611
 
11399
11612
  // src/login.ts
11400
11613
  var login_exports = {};
11401
- import { spawn as spawn2 } from "child_process";
11614
+ import { spawn as spawn3 } from "child_process";
11402
11615
  function startSpinner(text) {
11403
11616
  if (!process.stderr.isTTY) {
11404
11617
  process.stderr.write(` ${text}
@@ -11430,11 +11643,11 @@ function parseArgs2(argv) {
11430
11643
  }
11431
11644
  return { apiUrl, install };
11432
11645
  }
11433
- function openBrowser(url) {
11646
+ function openBrowser2(url) {
11434
11647
  try {
11435
11648
  const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
11436
11649
  const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
11437
- const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
11650
+ const child = spawn3(cmd, args, { stdio: "ignore", detached: true });
11438
11651
  child.on("error", () => {
11439
11652
  });
11440
11653
  child.unref();
@@ -11464,7 +11677,7 @@ async function main() {
11464
11677
  console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
11465
11678
  console.log("");
11466
11679
  void user_code;
11467
- openBrowser(verifyUrl);
11680
+ openBrowser2(verifyUrl);
11468
11681
  const pollMs = Math.max(2, Number(interval) || 3) * 1e3;
11469
11682
  const deadline = Date.now() + (Number(expires_in) || 600) * 1e3;
11470
11683
  let apiKey = "";
@@ -11555,7 +11768,7 @@ __export(shield_exports, {
11555
11768
  });
11556
11769
  import { createServer, request as httpRequest } from "http";
11557
11770
  import { request as httpsRequest } from "https";
11558
- import { spawn as spawn3 } from "child_process";
11771
+ import { spawn as spawn4 } from "child_process";
11559
11772
  import { URL as URL2 } from "url";
11560
11773
  import { readFileSync as readFileSync9, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11561
11774
  import { resolve as resolve5 } from "path";
@@ -11815,7 +12028,7 @@ async function runShield() {
11815
12028
  const upstream = pickUpstream();
11816
12029
  const { port, close } = await startProxy(upstream);
11817
12030
  log4(`redacting secrets on the LLM path \u2192 masking before ${upstream.host} (127.0.0.1:${port})`);
11818
- const child = spawn3(cmd[0], cmd.slice(1), {
12031
+ const child = spawn4(cmd[0], cmd.slice(1), {
11819
12032
  stdio: "inherit",
11820
12033
  env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
11821
12034
  shell: process.platform === "win32"
@@ -16056,8 +16269,7 @@ function printWelcome() {
16056
16269
  async function main5() {
16057
16270
  const subcommand = process.argv[2];
16058
16271
  if (process.argv.length <= 2) {
16059
- const { isAuthenticated: isAuthenticated2 } = await Promise.resolve().then(() => (init_client(), client_exports));
16060
- if (process.stdout.isTTY && isAuthenticated2()) {
16272
+ if (process.stdout.isTTY && process.stdin.isTTY) {
16061
16273
  const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
16062
16274
  await launchTui2();
16063
16275
  return;