@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/api-client/client.d.ts +8 -0
- package/dist/api-client/device-login.d.ts +16 -0
- package/dist/api-client/index.d.ts +1 -0
- package/dist/commands/index.js +3 -0
- package/dist/index.js +474 -274
- package/dist/tui/index.js +272 -66
- package/dist/tui/panels/Accounts.d.ts +7 -0
- package/package.json +1 -1
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
|
|
6747
|
-
import { homedir as
|
|
6748
|
-
import { join as
|
|
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 (
|
|
6562
|
+
if (cached) return cached;
|
|
6751
6563
|
const defaults = { notifications: true };
|
|
6752
6564
|
try {
|
|
6753
|
-
const raw =
|
|
6565
|
+
const raw = readFileSync4(join4(homedir2(), ".solongate", "tui-config.json"), "utf-8");
|
|
6754
6566
|
const j = JSON.parse(raw);
|
|
6755
|
-
|
|
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
|
-
|
|
6573
|
+
cached = defaults;
|
|
6762
6574
|
}
|
|
6763
|
-
return
|
|
6575
|
+
return cached;
|
|
6764
6576
|
}
|
|
6765
|
-
var
|
|
6577
|
+
var cached;
|
|
6766
6578
|
var init_config2 = __esm({
|
|
6767
6579
|
"src/tui/config.ts"() {
|
|
6768
6580
|
"use strict";
|
|
6769
|
-
|
|
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
|
|
6880
|
-
import { homedir as
|
|
6881
|
-
import { join as
|
|
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 =
|
|
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)
|
|
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 =
|
|
6924
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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/
|
|
9791
|
-
import { Box as Box8, Text as Text8,
|
|
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
|
|
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.
|
|
9797
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.
|
|
9798
|
-
/* @__PURE__ */ jsx8(
|
|
9799
|
-
|
|
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__ */
|
|
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__ */
|
|
9810
|
-
/* @__PURE__ */
|
|
9811
|
-
/* @__PURE__ */
|
|
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__ */
|
|
9815
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
9816
|
-
/* @__PURE__ */
|
|
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 [
|
|
9822
|
-
const [
|
|
9823
|
-
const [
|
|
9824
|
-
const [
|
|
9825
|
-
const [
|
|
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) : "
|
|
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
|
-
|
|
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
|
-
|
|
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__ */
|
|
10059
|
+
if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
|
|
9858
10060
|
if (current.label === "Live" && focus === "panel") {
|
|
9859
|
-
return /* @__PURE__ */
|
|
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
|
-
|
|
9863
|
-
|
|
9864
|
-
/* @__PURE__ */
|
|
9865
|
-
|
|
9866
|
-
/* @__PURE__ */
|
|
9867
|
-
|
|
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__ */
|
|
9870
|
-
/* @__PURE__ */
|
|
9871
|
-
/* @__PURE__ */
|
|
9872
|
-
] },
|
|
9873
|
-
/* @__PURE__ */
|
|
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__ */
|
|
9878
|
-
/* @__PURE__ */
|
|
9879
|
-
/* @__PURE__ */
|
|
9880
|
-
/* @__PURE__ */
|
|
9881
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
9882
|
-
/* @__PURE__ */
|
|
9883
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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
|
|
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__ */
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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;
|