@solongate/proxy 0.81.37 → 0.81.38

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,38 @@ 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
+ useInput8((input, key) => {
9836
10038
  if (help) {
9837
10039
  setHelp(false);
9838
10040
  return;
@@ -9854,39 +10056,40 @@ function App() {
9854
10056
  });
9855
10057
  const { cols, rows } = useTermSize();
9856
10058
  const current = SECTIONS[section];
9857
- if (help) return /* @__PURE__ */ jsx8(HelpOverlay, { cols, rows });
10059
+ if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
9858
10060
  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) });
10061
+ return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
9860
10062
  }
9861
10063
  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
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
9868
10071
  ] }),
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"]] }) })
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"]] }) })
9874
10077
  ] });
9875
10078
  }
9876
10079
  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 })
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 })
9884
10087
  ] }, k))
9885
10088
  ] }, group)) }),
9886
- /* @__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" })
9887
10090
  ] });
9888
10091
  }
9889
- var SECTIONS, BANNER_HEX, HELP;
10092
+ var SECTIONS, BANNER_HEX, ACCOUNTS_SECTION, HELP;
9890
10093
  var init_App = __esm({
9891
10094
  "src/tui/App.tsx"() {
9892
10095
  "use strict";
@@ -9899,6 +10102,7 @@ var init_App = __esm({
9899
10102
  init_Dlp();
9900
10103
  init_Audit();
9901
10104
  init_Settings();
10105
+ init_Accounts();
9902
10106
  init_hooks();
9903
10107
  init_client();
9904
10108
  SECTIONS = [
@@ -9907,9 +10111,11 @@ var init_App = __esm({
9907
10111
  { label: "Rate Limit", Panel: RateLimitPanel },
9908
10112
  { label: "DLP", Panel: DlpPanel },
9909
10113
  { label: "Audit", Panel: AuditPanel },
9910
- { label: "Settings", Panel: SettingsPanel }
10114
+ { label: "Settings", Panel: SettingsPanel },
10115
+ { label: "Accounts", Panel: AccountsPanel }
9911
10116
  ];
9912
10117
  BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
10118
+ ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
9913
10119
  HELP = [
9914
10120
  ["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
9915
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)"]]],
@@ -9928,7 +10134,7 @@ __export(tui_exports, {
9928
10134
  launchTui: () => launchTui
9929
10135
  });
9930
10136
  import { render } from "ink";
9931
- import { jsx as jsx9 } from "react/jsx-runtime";
10137
+ import { jsx as jsx10 } from "react/jsx-runtime";
9932
10138
  async function launchTui() {
9933
10139
  if (!process.stdout.isTTY || !process.stdin.isTTY) {
9934
10140
  process.stderr.write(
@@ -9936,13 +10142,9 @@ async function launchTui() {
9936
10142
  );
9937
10143
  return;
9938
10144
  }
9939
- if (!isAuthenticated()) {
9940
- process.stderr.write("Not logged in. Run `solongate login` first.\n");
9941
- return;
9942
- }
9943
10145
  process.stdout.write("\x1B[?1049h\x1B[H");
9944
10146
  try {
9945
- const { waitUntilExit } = render(/* @__PURE__ */ jsx9(App, {}));
10147
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
9946
10148
  await waitUntilExit();
9947
10149
  } finally {
9948
10150
  process.stdout.write("\x1B[?1049l");
@@ -9952,7 +10154,6 @@ var init_tui = __esm({
9952
10154
  "src/tui/index.tsx"() {
9953
10155
  "use strict";
9954
10156
  init_App();
9955
- init_client();
9956
10157
  }
9957
10158
  });
9958
10159
 
@@ -11398,7 +11599,7 @@ var init_global_install = __esm({
11398
11599
 
11399
11600
  // src/login.ts
11400
11601
  var login_exports = {};
11401
- import { spawn as spawn2 } from "child_process";
11602
+ import { spawn as spawn3 } from "child_process";
11402
11603
  function startSpinner(text) {
11403
11604
  if (!process.stderr.isTTY) {
11404
11605
  process.stderr.write(` ${text}
@@ -11430,11 +11631,11 @@ function parseArgs2(argv) {
11430
11631
  }
11431
11632
  return { apiUrl, install };
11432
11633
  }
11433
- function openBrowser(url) {
11634
+ function openBrowser2(url) {
11434
11635
  try {
11435
11636
  const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
11436
11637
  const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
11437
- const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
11638
+ const child = spawn3(cmd, args, { stdio: "ignore", detached: true });
11438
11639
  child.on("error", () => {
11439
11640
  });
11440
11641
  child.unref();
@@ -11464,7 +11665,7 @@ async function main() {
11464
11665
  console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
11465
11666
  console.log("");
11466
11667
  void user_code;
11467
- openBrowser(verifyUrl);
11668
+ openBrowser2(verifyUrl);
11468
11669
  const pollMs = Math.max(2, Number(interval) || 3) * 1e3;
11469
11670
  const deadline = Date.now() + (Number(expires_in) || 600) * 1e3;
11470
11671
  let apiKey = "";
@@ -11555,7 +11756,7 @@ __export(shield_exports, {
11555
11756
  });
11556
11757
  import { createServer, request as httpRequest } from "http";
11557
11758
  import { request as httpsRequest } from "https";
11558
- import { spawn as spawn3 } from "child_process";
11759
+ import { spawn as spawn4 } from "child_process";
11559
11760
  import { URL as URL2 } from "url";
11560
11761
  import { readFileSync as readFileSync9, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
11561
11762
  import { resolve as resolve5 } from "path";
@@ -11815,7 +12016,7 @@ async function runShield() {
11815
12016
  const upstream = pickUpstream();
11816
12017
  const { port, close } = await startProxy(upstream);
11817
12018
  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), {
12019
+ const child = spawn4(cmd[0], cmd.slice(1), {
11819
12020
  stdio: "inherit",
11820
12021
  env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
11821
12022
  shell: process.platform === "win32"
@@ -16056,8 +16257,7 @@ function printWelcome() {
16056
16257
  async function main5() {
16057
16258
  const subcommand = process.argv[2];
16058
16259
  if (process.argv.length <= 2) {
16059
- const { isAuthenticated: isAuthenticated2 } = await Promise.resolve().then(() => (init_client(), client_exports));
16060
- if (process.stdout.isTTY && isAuthenticated2()) {
16260
+ if (process.stdout.isTTY && process.stdin.isTTY) {
16061
16261
  const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
16062
16262
  await launchTui2();
16063
16263
  return;