@solongate/proxy 0.81.37 → 0.81.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +487 -275
- package/dist/tui/index.js +285 -67
- 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,40 @@ 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
|
+
const locked = accounts.length === 0;
|
|
10038
|
+
const effectiveSection = locked ? ACCOUNTS_SECTION : section;
|
|
10039
|
+
useInput8((input, key) => {
|
|
9836
10040
|
if (help) {
|
|
9837
10041
|
setHelp(false);
|
|
9838
10042
|
return;
|
|
@@ -9841,6 +10045,12 @@ function App() {
|
|
|
9841
10045
|
setHelp(true);
|
|
9842
10046
|
return;
|
|
9843
10047
|
}
|
|
10048
|
+
if (locked) {
|
|
10049
|
+
if (key.rightArrow || key.return || key.tab) setFocus("panel");
|
|
10050
|
+
else if (key.escape) setFocus("nav");
|
|
10051
|
+
else if (input === "q" && focus === "nav") exit();
|
|
10052
|
+
return;
|
|
10053
|
+
}
|
|
9844
10054
|
if (focus === "nav") {
|
|
9845
10055
|
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
9846
10056
|
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
@@ -9853,40 +10063,45 @@ function App() {
|
|
|
9853
10063
|
}
|
|
9854
10064
|
});
|
|
9855
10065
|
const { cols, rows } = useTermSize();
|
|
9856
|
-
const current = SECTIONS[
|
|
9857
|
-
if (help) return /* @__PURE__ */
|
|
10066
|
+
const current = SECTIONS[effectiveSection];
|
|
10067
|
+
if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
|
|
9858
10068
|
if (current.label === "Live" && focus === "panel") {
|
|
9859
|
-
return /* @__PURE__ */
|
|
10069
|
+
return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
|
|
9860
10070
|
}
|
|
9861
10071
|
const Panel = current.Panel;
|
|
9862
|
-
|
|
9863
|
-
|
|
9864
|
-
/* @__PURE__ */
|
|
9865
|
-
|
|
9866
|
-
/* @__PURE__ */
|
|
9867
|
-
|
|
10072
|
+
const panelBody = current.label === "Accounts" ? /* @__PURE__ */ jsx9(AccountsPanel, { active: true, focused: focus === "panel", viewApiKey: viewKey, onView: viewAccount }) : /* @__PURE__ */ jsx9(Panel, { active: focus === "panel" || current.label !== "Live", focused: focus === "panel" });
|
|
10073
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
10074
|
+
/* @__PURE__ */ jsx9(Banner, { cols }),
|
|
10075
|
+
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
10076
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
|
|
10077
|
+
/* @__PURE__ */ jsx9(Text9, { color: locked ? theme.warn : theme.accentBright, bold: true, children: acctLabel }),
|
|
10078
|
+
locked ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 log in from Accounts to unlock the dataroom" }) : accounts.length > 1 ? /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: ` (${acctIdx + 1}/${accounts.length} \xB7 a switch \xB7 Accounts to manage)` }) : /* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \xB7 Accounts to add another" })
|
|
9868
10079
|
] }),
|
|
9869
|
-
/* @__PURE__ */
|
|
9870
|
-
/* @__PURE__ */
|
|
9871
|
-
|
|
9872
|
-
|
|
9873
|
-
|
|
10080
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
|
|
10081
|
+
/* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: 16, borderStyle: "round", borderColor: focus === "nav" ? theme.accent : "gray", paddingX: 1, children: SECTIONS.map((s, i) => {
|
|
10082
|
+
const isCur = i === effectiveSection;
|
|
10083
|
+
const disabled = locked && s.label !== "Accounts";
|
|
10084
|
+
return /* @__PURE__ */ jsx9(Text9, { color: isCur ? theme.accentBright : disabled ? theme.dim : void 0, bold: isCur, dimColor: disabled, children: (isCur ? "\u25B8 " : disabled ? "\u2298 " : " ") + s.label }, s.label);
|
|
10085
|
+
}) }),
|
|
10086
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
|
|
10087
|
+
] }, viewNonce),
|
|
10088
|
+
/* @__PURE__ */ jsx9(Box9, { children: locked ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2192/enter", "open Accounts"], ["n", "log in"], ["q", "quit"]] }) : focus === "nav" ? /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2191\u2193", "section"], ["\u2192/enter", "open"], ...accounts.length > 1 ? [["a", "account"]] : [], ["?", "help"], ["q", "quit"]] }) : /* @__PURE__ */ jsx9(KeyHints, { hints: [["\u2190/esc", "back"], ["\u2191\u2193", "in-panel"], ["space/s", "act"]] }) })
|
|
9874
10089
|
] });
|
|
9875
10090
|
}
|
|
9876
10091
|
function HelpOverlay({ cols, rows }) {
|
|
9877
|
-
return /* @__PURE__ */
|
|
9878
|
-
/* @__PURE__ */
|
|
9879
|
-
/* @__PURE__ */
|
|
9880
|
-
/* @__PURE__ */
|
|
9881
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
9882
|
-
/* @__PURE__ */
|
|
9883
|
-
/* @__PURE__ */
|
|
10092
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
10093
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
10094
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
|
|
10095
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
|
|
10096
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
10097
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
10098
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
|
|
9884
10099
|
] }, k))
|
|
9885
10100
|
] }, group)) }),
|
|
9886
|
-
/* @__PURE__ */
|
|
10101
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
|
|
9887
10102
|
] });
|
|
9888
10103
|
}
|
|
9889
|
-
var SECTIONS, BANNER_HEX, HELP;
|
|
10104
|
+
var SECTIONS, BANNER_HEX, ACCOUNTS_SECTION, HELP;
|
|
9890
10105
|
var init_App = __esm({
|
|
9891
10106
|
"src/tui/App.tsx"() {
|
|
9892
10107
|
"use strict";
|
|
@@ -9899,6 +10114,7 @@ var init_App = __esm({
|
|
|
9899
10114
|
init_Dlp();
|
|
9900
10115
|
init_Audit();
|
|
9901
10116
|
init_Settings();
|
|
10117
|
+
init_Accounts();
|
|
9902
10118
|
init_hooks();
|
|
9903
10119
|
init_client();
|
|
9904
10120
|
SECTIONS = [
|
|
@@ -9907,9 +10123,11 @@ var init_App = __esm({
|
|
|
9907
10123
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
9908
10124
|
{ label: "DLP", Panel: DlpPanel },
|
|
9909
10125
|
{ label: "Audit", Panel: AuditPanel },
|
|
9910
|
-
{ label: "Settings", Panel: SettingsPanel }
|
|
10126
|
+
{ label: "Settings", Panel: SettingsPanel },
|
|
10127
|
+
{ label: "Accounts", Panel: AccountsPanel }
|
|
9911
10128
|
];
|
|
9912
10129
|
BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
|
|
10130
|
+
ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
|
|
9913
10131
|
HELP = [
|
|
9914
10132
|
["Global", [["\u2191\u2193", "move between sections"], ["\u2192 / enter", "open a section"], ["\u2190 / esc", "back to the menu"], ["?", "this help"], ["q", "quit"]]],
|
|
9915
10133
|
["Live", [["\u2191\u2193", "select a stream row"], ["enter", "full entry content"], ["w", "whitelist the selected DENY"], ["b", "block the selected ALLOW"], ["d / x / r", "filter denies / dlp / rate-limit"], ["f", "local / cloud filter"], ["/", "search"], ["s", "sessions (\u2191\u2193 pick, enter open)"], ["space", "copy mode (freeze)"]]],
|
|
@@ -9928,7 +10146,7 @@ __export(tui_exports, {
|
|
|
9928
10146
|
launchTui: () => launchTui
|
|
9929
10147
|
});
|
|
9930
10148
|
import { render } from "ink";
|
|
9931
|
-
import { jsx as
|
|
10149
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
9932
10150
|
async function launchTui() {
|
|
9933
10151
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
9934
10152
|
process.stderr.write(
|
|
@@ -9936,13 +10154,9 @@ async function launchTui() {
|
|
|
9936
10154
|
);
|
|
9937
10155
|
return;
|
|
9938
10156
|
}
|
|
9939
|
-
if (!isAuthenticated()) {
|
|
9940
|
-
process.stderr.write("Not logged in. Run `solongate login` first.\n");
|
|
9941
|
-
return;
|
|
9942
|
-
}
|
|
9943
10157
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
9944
10158
|
try {
|
|
9945
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
10159
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
|
|
9946
10160
|
await waitUntilExit();
|
|
9947
10161
|
} finally {
|
|
9948
10162
|
process.stdout.write("\x1B[?1049l");
|
|
@@ -9952,7 +10166,6 @@ var init_tui = __esm({
|
|
|
9952
10166
|
"src/tui/index.tsx"() {
|
|
9953
10167
|
"use strict";
|
|
9954
10168
|
init_App();
|
|
9955
|
-
init_client();
|
|
9956
10169
|
}
|
|
9957
10170
|
});
|
|
9958
10171
|
|
|
@@ -11398,7 +11611,7 @@ var init_global_install = __esm({
|
|
|
11398
11611
|
|
|
11399
11612
|
// src/login.ts
|
|
11400
11613
|
var login_exports = {};
|
|
11401
|
-
import { spawn as
|
|
11614
|
+
import { spawn as spawn3 } from "child_process";
|
|
11402
11615
|
function startSpinner(text) {
|
|
11403
11616
|
if (!process.stderr.isTTY) {
|
|
11404
11617
|
process.stderr.write(` ${text}
|
|
@@ -11430,11 +11643,11 @@ function parseArgs2(argv) {
|
|
|
11430
11643
|
}
|
|
11431
11644
|
return { apiUrl, install };
|
|
11432
11645
|
}
|
|
11433
|
-
function
|
|
11646
|
+
function openBrowser2(url) {
|
|
11434
11647
|
try {
|
|
11435
11648
|
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
11436
11649
|
const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
11437
|
-
const child =
|
|
11650
|
+
const child = spawn3(cmd, args, { stdio: "ignore", detached: true });
|
|
11438
11651
|
child.on("error", () => {
|
|
11439
11652
|
});
|
|
11440
11653
|
child.unref();
|
|
@@ -11464,7 +11677,7 @@ async function main() {
|
|
|
11464
11677
|
console.log(` ${c.cyan}${verifyUrl}${c.reset}`);
|
|
11465
11678
|
console.log("");
|
|
11466
11679
|
void user_code;
|
|
11467
|
-
|
|
11680
|
+
openBrowser2(verifyUrl);
|
|
11468
11681
|
const pollMs = Math.max(2, Number(interval) || 3) * 1e3;
|
|
11469
11682
|
const deadline = Date.now() + (Number(expires_in) || 600) * 1e3;
|
|
11470
11683
|
let apiKey = "";
|
|
@@ -11555,7 +11768,7 @@ __export(shield_exports, {
|
|
|
11555
11768
|
});
|
|
11556
11769
|
import { createServer, request as httpRequest } from "http";
|
|
11557
11770
|
import { request as httpsRequest } from "https";
|
|
11558
|
-
import { spawn as
|
|
11771
|
+
import { spawn as spawn4 } from "child_process";
|
|
11559
11772
|
import { URL as URL2 } from "url";
|
|
11560
11773
|
import { readFileSync as readFileSync9, existsSync as existsSync7, readdirSync, statSync as statSync4 } from "fs";
|
|
11561
11774
|
import { resolve as resolve5 } from "path";
|
|
@@ -11815,7 +12028,7 @@ async function runShield() {
|
|
|
11815
12028
|
const upstream = pickUpstream();
|
|
11816
12029
|
const { port, close } = await startProxy(upstream);
|
|
11817
12030
|
log4(`redacting secrets on the LLM path \u2192 masking before ${upstream.host} (127.0.0.1:${port})`);
|
|
11818
|
-
const child =
|
|
12031
|
+
const child = spawn4(cmd[0], cmd.slice(1), {
|
|
11819
12032
|
stdio: "inherit",
|
|
11820
12033
|
env: { ...process.env, ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}` },
|
|
11821
12034
|
shell: process.platform === "win32"
|
|
@@ -16056,8 +16269,7 @@ function printWelcome() {
|
|
|
16056
16269
|
async function main5() {
|
|
16057
16270
|
const subcommand = process.argv[2];
|
|
16058
16271
|
if (process.argv.length <= 2) {
|
|
16059
|
-
|
|
16060
|
-
if (process.stdout.isTTY && isAuthenticated2()) {
|
|
16272
|
+
if (process.stdout.isTTY && process.stdin.isTTY) {
|
|
16061
16273
|
const { launchTui: launchTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
16062
16274
|
await launchTui2();
|
|
16063
16275
|
return;
|