@solongate/proxy 0.81.36 → 0.81.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client/client.d.ts +19 -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 +6 -1
- package/dist/index.js +515 -254
- package/dist/login.js +24 -0
- package/dist/tui/index.js +306 -62
- package/dist/tui/panels/Accounts.d.ts +7 -0
- package/package.json +1 -1
package/dist/login.js
CHANGED
|
@@ -263,6 +263,29 @@ async function runGlobalInstall(opts = {}) {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
|
|
266
|
+
// src/api-client/client.ts
|
|
267
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync2 } from "fs";
|
|
268
|
+
import { resolve as resolve2, join as join2 } from "path";
|
|
269
|
+
import { homedir as homedir2 } from "os";
|
|
270
|
+
var accountsFile = () => join2(homedir2(), ".solongate", "accounts.json");
|
|
271
|
+
function saveAccount(acc) {
|
|
272
|
+
try {
|
|
273
|
+
const list = (() => {
|
|
274
|
+
try {
|
|
275
|
+
const raw = JSON.parse(readFileSync2(accountsFile(), "utf-8"));
|
|
276
|
+
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
277
|
+
} catch {
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
})();
|
|
281
|
+
const next = list.filter((a) => a.apiKey !== acc.apiKey);
|
|
282
|
+
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
283
|
+
mkdirSync2(join2(homedir2(), ".solongate"), { recursive: true });
|
|
284
|
+
writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
266
289
|
// src/login.ts
|
|
267
290
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
268
291
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -360,6 +383,7 @@ async function main() {
|
|
|
360
383
|
if (data?.project?.name) {
|
|
361
384
|
console.log(` ${c.dim}Project:${c.reset} ${c.cyan}${data.project.name}${c.reset}`);
|
|
362
385
|
}
|
|
386
|
+
saveAccount({ apiKey, apiUrl, project: data?.project?.name, user: data?.user?.name || data?.user?.email });
|
|
363
387
|
break;
|
|
364
388
|
}
|
|
365
389
|
if (data?.status === "expired" || data?.status === "not_found") {
|
package/dist/tui/index.js
CHANGED
|
@@ -8,8 +8,8 @@ var __export = (target, all) => {
|
|
|
8
8
|
import { render } from "ink";
|
|
9
9
|
|
|
10
10
|
// src/tui/App.tsx
|
|
11
|
-
import { Box as
|
|
12
|
-
import { useState as
|
|
11
|
+
import { Box as Box9, Text as Text9, useApp, useInput as useInput8 } from "ink";
|
|
12
|
+
import { useState as useState9 } from "react";
|
|
13
13
|
|
|
14
14
|
// src/cli-utils.ts
|
|
15
15
|
var c = {
|
|
@@ -160,7 +160,7 @@ function KeyHints({ hints }) {
|
|
|
160
160
|
// src/tui/panels/Live.tsx
|
|
161
161
|
import { Box as Box2, Text as Text2, useInput } from "ink";
|
|
162
162
|
import TextInput from "ink-text-input";
|
|
163
|
-
import { mkdirSync, writeFileSync as
|
|
163
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
164
164
|
import { homedir as homedir4 } from "os";
|
|
165
165
|
import { join as join4 } from "path";
|
|
166
166
|
|
|
@@ -245,10 +245,66 @@ function parseLocalLines(lines) {
|
|
|
245
245
|
import { useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
|
|
246
246
|
|
|
247
247
|
// src/api-client/client.ts
|
|
248
|
-
import { readFileSync as readFileSync3, existsSync } from "fs";
|
|
248
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync, existsSync } from "fs";
|
|
249
249
|
import { resolve, join as join3 } from "path";
|
|
250
250
|
import { homedir as homedir3 } from "os";
|
|
251
251
|
var DEFAULT_API_URL = "https://api.solongate.com";
|
|
252
|
+
var accountsFile = () => join3(homedir3(), ".solongate", "accounts.json");
|
|
253
|
+
function listAccounts() {
|
|
254
|
+
let list5 = [];
|
|
255
|
+
try {
|
|
256
|
+
const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
|
|
257
|
+
if (Array.isArray(raw)) list5 = raw.filter((a) => a && typeof a.apiKey === "string");
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
const active2 = loginCredentialFile();
|
|
261
|
+
if (active2.apiKey && !list5.some((a) => a.apiKey === active2.apiKey)) {
|
|
262
|
+
list5.unshift({ apiKey: active2.apiKey, apiUrl: active2.apiUrl || DEFAULT_API_URL });
|
|
263
|
+
}
|
|
264
|
+
return list5;
|
|
265
|
+
}
|
|
266
|
+
function saveAccount(acc) {
|
|
267
|
+
try {
|
|
268
|
+
const list5 = (() => {
|
|
269
|
+
try {
|
|
270
|
+
const raw = JSON.parse(readFileSync3(accountsFile(), "utf-8"));
|
|
271
|
+
return Array.isArray(raw) ? raw.filter((a) => a && a.apiKey) : [];
|
|
272
|
+
} catch {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
})();
|
|
276
|
+
const next = list5.filter((a) => a.apiKey !== acc.apiKey);
|
|
277
|
+
next.unshift({ ...acc, addedAt: acc.addedAt ?? Date.now() });
|
|
278
|
+
mkdirSync(join3(homedir3(), ".solongate"), { recursive: true });
|
|
279
|
+
writeFileSync2(accountsFile(), JSON.stringify(next, null, 2));
|
|
280
|
+
} catch {
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
var viewOverride = null;
|
|
284
|
+
function setViewCredentials(creds) {
|
|
285
|
+
viewOverride = creds;
|
|
286
|
+
cached2 = null;
|
|
287
|
+
}
|
|
288
|
+
function isActiveAccount(apiKey) {
|
|
289
|
+
return loginCredentialFile().apiKey === apiKey;
|
|
290
|
+
}
|
|
291
|
+
function setActiveAccount(creds) {
|
|
292
|
+
try {
|
|
293
|
+
const dir = join3(homedir3(), ".solongate");
|
|
294
|
+
mkdirSync(dir, { recursive: true });
|
|
295
|
+
const p = join3(dir, ["cloud", "guard.json"].join("-"));
|
|
296
|
+
let existing = {};
|
|
297
|
+
try {
|
|
298
|
+
existing = JSON.parse(readFileSync3(p, "utf-8"));
|
|
299
|
+
} catch {
|
|
300
|
+
}
|
|
301
|
+
writeFileSync2(p, JSON.stringify({ ...existing, apiKey: creds.apiKey, apiUrl: creds.apiUrl }, null, 2));
|
|
302
|
+
cached2 = null;
|
|
303
|
+
return true;
|
|
304
|
+
} catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
252
308
|
var ApiError = class extends Error {
|
|
253
309
|
status;
|
|
254
310
|
code;
|
|
@@ -293,15 +349,8 @@ function dotenvApiKey() {
|
|
|
293
349
|
return void 0;
|
|
294
350
|
}
|
|
295
351
|
var cached2 = null;
|
|
296
|
-
function isAuthenticated() {
|
|
297
|
-
try {
|
|
298
|
-
resolveCredentials();
|
|
299
|
-
return true;
|
|
300
|
-
} catch {
|
|
301
|
-
return false;
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
352
|
function resolveCredentials(apiUrlOverride) {
|
|
353
|
+
if (viewOverride && !apiUrlOverride) return viewOverride;
|
|
305
354
|
if (cached2 && !apiUrlOverride) return cached2;
|
|
306
355
|
const file = loginCredentialFile();
|
|
307
356
|
const apiKey = process.env["SOLONGATE_API_KEY"] || file.apiKey || dotenvApiKey();
|
|
@@ -377,6 +426,48 @@ async function request(method, path, opts = {}) {
|
|
|
377
426
|
return json;
|
|
378
427
|
}
|
|
379
428
|
|
|
429
|
+
// src/api-client/device-login.ts
|
|
430
|
+
import { spawn } from "child_process";
|
|
431
|
+
function openBrowser(url) {
|
|
432
|
+
try {
|
|
433
|
+
const cmd = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
434
|
+
const args = process.platform === "win32" ? ["/c", "start", '""', url] : [url];
|
|
435
|
+
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
436
|
+
child.on("error", () => {
|
|
437
|
+
});
|
|
438
|
+
child.unref();
|
|
439
|
+
} catch {
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async function startDeviceLogin(apiUrl = DEFAULT_API_URL) {
|
|
443
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/device/start`, { method: "POST" });
|
|
444
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
445
|
+
const j = await res.json();
|
|
446
|
+
return {
|
|
447
|
+
deviceCode: j.device_code,
|
|
448
|
+
verifyUrl: j.verification_uri_complete || j.verification_uri || "",
|
|
449
|
+
intervalMs: Math.max(2, Number(j.interval) || 3) * 1e3,
|
|
450
|
+
expiresAt: Date.now() + (Number(j.expires_in) || 600) * 1e3
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
async function pollDeviceLogin(apiUrl, deviceCode) {
|
|
454
|
+
try {
|
|
455
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/device/poll`, {
|
|
456
|
+
method: "POST",
|
|
457
|
+
headers: { "Content-Type": "application/json" },
|
|
458
|
+
body: JSON.stringify({ device_code: deviceCode })
|
|
459
|
+
});
|
|
460
|
+
const j = await res.json().catch(() => ({}));
|
|
461
|
+
if (j.status === "approved" && j.api_key) {
|
|
462
|
+
return { status: "approved", apiKey: j.api_key, project: j.project?.name, user: j.user?.name || j.user?.email };
|
|
463
|
+
}
|
|
464
|
+
if (j.status === "expired" || j.status === "not_found") return { status: j.status };
|
|
465
|
+
return { status: "pending" };
|
|
466
|
+
} catch {
|
|
467
|
+
return { status: "pending" };
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
380
471
|
// src/api-client/policies.ts
|
|
381
472
|
var policies_exports = {};
|
|
382
473
|
__export(policies_exports, {
|
|
@@ -602,24 +693,24 @@ function list4() {
|
|
|
602
693
|
var api = { policies: policies_exports, settings: settings_exports, stats: stats_exports, audit: audit_exports, agents: agents_exports, keys: keys_exports, mcp: mcp_exports };
|
|
603
694
|
|
|
604
695
|
// src/tui/notify.ts
|
|
605
|
-
import { spawn } from "child_process";
|
|
696
|
+
import { spawn as spawn2 } from "child_process";
|
|
606
697
|
function desktopNotify(title, msg) {
|
|
607
698
|
try {
|
|
608
699
|
if (process.platform === "linux") {
|
|
609
|
-
const p =
|
|
700
|
+
const p = spawn2("notify-send", ["-a", "SolonGate", title, msg], { stdio: "ignore", detached: true });
|
|
610
701
|
p.on("error", () => {
|
|
611
702
|
});
|
|
612
703
|
p.unref();
|
|
613
704
|
} else if (process.platform === "win32") {
|
|
614
705
|
const q = (s) => s.replace(/'/g, "''");
|
|
615
706
|
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()`;
|
|
616
|
-
const p =
|
|
707
|
+
const p = spawn2("powershell", ["-NoProfile", "-NonInteractive", "-Command", ps], { stdio: "ignore", detached: true, windowsHide: true });
|
|
617
708
|
p.on("error", () => {
|
|
618
709
|
});
|
|
619
710
|
p.unref();
|
|
620
711
|
} else if (process.platform === "darwin") {
|
|
621
712
|
const esc = (s) => s.replace(/"/g, '\\"');
|
|
622
|
-
const p =
|
|
713
|
+
const p = spawn2("osascript", ["-e", `display notification "${esc(msg)}" with title "${esc(title)}"`], { stdio: "ignore", detached: true });
|
|
623
714
|
p.on("error", () => {
|
|
624
715
|
});
|
|
625
716
|
p.unref();
|
|
@@ -1300,8 +1391,8 @@ function LivePanel({ active: active2 }) {
|
|
|
1300
1391
|
else if (input === "e") {
|
|
1301
1392
|
const file = join4(homedir4(), ".solongate", "live-export.jsonl");
|
|
1302
1393
|
try {
|
|
1303
|
-
|
|
1304
|
-
|
|
1394
|
+
mkdirSync2(join4(homedir4(), ".solongate"), { recursive: true });
|
|
1395
|
+
writeFileSync3(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
1305
1396
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
1306
1397
|
} catch (err) {
|
|
1307
1398
|
setActionMsg({ text: "\u2717 export failed: " + (err instanceof Error ? err.message : String(err)), level: "bad", until: Date.now() + 6e3 });
|
|
@@ -2217,7 +2308,7 @@ function RegexTest({ re }) {
|
|
|
2217
2308
|
// src/tui/panels/Audit.tsx
|
|
2218
2309
|
import { Box as Box6, Text as Text6, useInput as useInput5 } from "ink";
|
|
2219
2310
|
import TextInput4 from "ink-text-input";
|
|
2220
|
-
import { mkdirSync as
|
|
2311
|
+
import { mkdirSync as mkdirSync3, writeFileSync as writeFileSync4 } from "fs";
|
|
2221
2312
|
import { homedir as homedir5 } from "os";
|
|
2222
2313
|
import { join as join5 } from "path";
|
|
2223
2314
|
import { useState as useState6 } from "react";
|
|
@@ -2451,8 +2542,8 @@ function AuditPanel({ active: active2, focused }) {
|
|
|
2451
2542
|
const r = await api.audit.list({ ...query, limit: 1e4, offset: 0 });
|
|
2452
2543
|
rows2 = r.entries.map(cloudRow).sort((a, b) => b.at - a.at);
|
|
2453
2544
|
} else rows2 = localFiltered;
|
|
2454
|
-
|
|
2455
|
-
|
|
2545
|
+
mkdirSync3(dir, { recursive: true });
|
|
2546
|
+
writeFileSync4(file, rows2.map((x) => JSON.stringify(x)).join("\n") + (rows2.length ? "\n" : ""));
|
|
2456
2547
|
return { n: rows2.length, file };
|
|
2457
2548
|
};
|
|
2458
2549
|
run().then(({ n, file }) => setMsg({ text: `\u2713 exported ${n} rows \u2192 ${file}`, level: "ok" })).catch((e) => setMsg({ text: "\u2717 export failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" }));
|
|
@@ -3063,16 +3154,147 @@ function SettingsPanel({ active: active2, focused }) {
|
|
|
3063
3154
|
] }) });
|
|
3064
3155
|
}
|
|
3065
3156
|
|
|
3066
|
-
// src/tui/
|
|
3157
|
+
// src/tui/panels/Accounts.tsx
|
|
3158
|
+
import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
|
|
3159
|
+
import { useEffect as useEffect6, useRef as useRef3, useState as useState8 } from "react";
|
|
3067
3160
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
3068
|
-
|
|
3161
|
+
var SPIN2 = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
3162
|
+
function AccountsPanel({
|
|
3163
|
+
focused,
|
|
3164
|
+
viewApiKey,
|
|
3165
|
+
onView
|
|
3166
|
+
}) {
|
|
3167
|
+
const [accounts, setAccounts] = useState8(() => listAccounts());
|
|
3168
|
+
const [sel, setSel] = useState8(0);
|
|
3169
|
+
const [msg, setMsg] = useState8(null);
|
|
3170
|
+
const [login, setLogin] = useState8(null);
|
|
3171
|
+
const [tick, setTick] = useState8(0);
|
|
3172
|
+
const abort = useRef3(false);
|
|
3173
|
+
const refresh = () => setAccounts(listAccounts());
|
|
3174
|
+
useEffect6(() => {
|
|
3175
|
+
if (!login || login.phase === "done" || login.phase === "error") return;
|
|
3176
|
+
const t = setInterval(() => setTick((n) => n + 1), 120);
|
|
3177
|
+
return () => clearInterval(t);
|
|
3178
|
+
}, [login]);
|
|
3179
|
+
const beginLogin = () => {
|
|
3180
|
+
if (login && (login.phase === "starting" || login.phase === "waiting")) return;
|
|
3181
|
+
abort.current = false;
|
|
3182
|
+
setLogin({ phase: "starting" });
|
|
3183
|
+
void (async () => {
|
|
3184
|
+
let start;
|
|
3185
|
+
try {
|
|
3186
|
+
start = await startDeviceLogin(DEFAULT_API_URL);
|
|
3187
|
+
} catch (e) {
|
|
3188
|
+
setLogin({ phase: "error", msg: "could not reach SolonGate: " + (e instanceof Error ? e.message : String(e)) });
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
setLogin({ phase: "waiting", url: start.verifyUrl });
|
|
3192
|
+
openBrowser(start.verifyUrl);
|
|
3193
|
+
while (Date.now() < start.expiresAt && !abort.current) {
|
|
3194
|
+
await new Promise((r) => setTimeout(r, start.intervalMs));
|
|
3195
|
+
if (abort.current) return;
|
|
3196
|
+
const res = await pollDeviceLogin(DEFAULT_API_URL, start.deviceCode);
|
|
3197
|
+
if (res.status === "approved" && res.apiKey) {
|
|
3198
|
+
saveAccount({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user });
|
|
3199
|
+
setLogin({ phase: "done", msg: `\u2713 added ${res.project || res.user || "account"}` });
|
|
3200
|
+
refresh();
|
|
3201
|
+
setViewCredentials({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL });
|
|
3202
|
+
onView?.({ apiKey: res.apiKey, apiUrl: DEFAULT_API_URL, project: res.project, user: res.user });
|
|
3203
|
+
return;
|
|
3204
|
+
}
|
|
3205
|
+
if (res.status === "expired" || res.status === "not_found") {
|
|
3206
|
+
setLogin({ phase: "error", msg: "code expired \u2014 press n to retry" });
|
|
3207
|
+
return;
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
if (!abort.current) setLogin({ phase: "error", msg: "timed out \u2014 press n to retry" });
|
|
3211
|
+
})();
|
|
3212
|
+
};
|
|
3213
|
+
useInput7(
|
|
3214
|
+
(input, key) => {
|
|
3215
|
+
if (login && (login.phase === "starting" || login.phase === "waiting")) {
|
|
3216
|
+
if (key.escape) {
|
|
3217
|
+
abort.current = true;
|
|
3218
|
+
setLogin(null);
|
|
3219
|
+
}
|
|
3220
|
+
return;
|
|
3221
|
+
}
|
|
3222
|
+
if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
3223
|
+
else if (key.downArrow) setSel((n) => Math.min(accounts.length - 1, n + 1));
|
|
3224
|
+
else if (key.return) {
|
|
3225
|
+
const a = accounts[sel];
|
|
3226
|
+
if (a) {
|
|
3227
|
+
setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
|
|
3228
|
+
onView?.(a);
|
|
3229
|
+
setMsg({ text: `viewing ${a.project || a.user || a.apiKey.slice(0, 8)}`, level: "ok" });
|
|
3230
|
+
}
|
|
3231
|
+
} else if (input === "m") {
|
|
3232
|
+
const a = accounts[sel];
|
|
3233
|
+
if (a) {
|
|
3234
|
+
const ok = setActiveAccount({ apiKey: a.apiKey, apiUrl: a.apiUrl });
|
|
3235
|
+
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" });
|
|
3236
|
+
refresh();
|
|
3237
|
+
}
|
|
3238
|
+
} else if (input === "n") beginLogin();
|
|
3239
|
+
else if (input === "r") refresh();
|
|
3240
|
+
},
|
|
3241
|
+
{ isActive: focused }
|
|
3242
|
+
);
|
|
3243
|
+
const spin = SPIN2[tick % SPIN2.length];
|
|
3244
|
+
if (login && (login.phase === "starting" || login.phase === "waiting")) {
|
|
3245
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
3246
|
+
/* @__PURE__ */ jsx8(Text8, { bold: true, color: theme.accentBright, children: "Add an account" }),
|
|
3247
|
+
login.phase === "starting" ? /* @__PURE__ */ jsxs8(Text8, { color: theme.dim, children: [
|
|
3248
|
+
spin,
|
|
3249
|
+
" starting device pairing\u2026"
|
|
3250
|
+
] }) : /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", marginTop: 1, children: [
|
|
3251
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "Opening your browser to authorize. If it didn't open, visit:" }),
|
|
3252
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, bold: true, wrap: "truncate", children: login.url }),
|
|
3253
|
+
/* @__PURE__ */ jsxs8(Box8, { marginTop: 1, children: [
|
|
3254
|
+
/* @__PURE__ */ jsxs8(Text8, { color: theme.warn, children: [
|
|
3255
|
+
spin,
|
|
3256
|
+
" waiting for authorization\u2026 "
|
|
3257
|
+
] }),
|
|
3258
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "esc to cancel" })
|
|
3259
|
+
] })
|
|
3260
|
+
] })
|
|
3261
|
+
] });
|
|
3262
|
+
}
|
|
3069
3263
|
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
3070
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.
|
|
3071
|
-
/* @__PURE__ */ jsx8(Text8, { color: theme.
|
|
3072
|
-
/* @__PURE__ */ jsx8(
|
|
3073
|
-
|
|
3264
|
+
/* @__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" }),
|
|
3265
|
+
msg ? /* @__PURE__ */ jsx8(Text8, { color: msg.level === "bad" ? theme.bad : theme.ok, children: truncate(msg.text, 70) }) : login?.msg ? /* @__PURE__ */ jsx8(Text8, { color: login.phase === "error" ? theme.bad : theme.ok, children: login.msg }) : /* @__PURE__ */ jsx8(Text8, { children: " " }),
|
|
3266
|
+
/* @__PURE__ */ jsx8(Box8, { marginTop: 1, flexDirection: "column", children: accounts.length === 0 ? /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
3267
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "No accounts on this device yet." }),
|
|
3268
|
+
/* @__PURE__ */ jsxs8(Text8, { children: [
|
|
3269
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: "n" }),
|
|
3270
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " to log in \u2014 pairs this device and starts protecting your Claude Code sessions." })
|
|
3271
|
+
] })
|
|
3272
|
+
] }) : accounts.map((a, i) => {
|
|
3273
|
+
const isSel = i === sel && focused;
|
|
3274
|
+
const isView = viewApiKey ? a.apiKey === viewApiKey : i === 0;
|
|
3275
|
+
const isActive = isActiveAccount(a.apiKey);
|
|
3276
|
+
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", backgroundColor: isSel ? "#333333" : void 0, bold: isSel, children: [
|
|
3277
|
+
/* @__PURE__ */ jsx8(Text8, { color: isSel ? theme.accentBright : theme.dim, children: isSel ? "\u25B8 " : " " }),
|
|
3278
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.accent, children: truncate(a.project || a.user || a.apiKey.slice(0, 12), 30).padEnd(31) }),
|
|
3279
|
+
isView ? /* @__PURE__ */ jsx8(Text8, { color: theme.ok, children: "\u25CF viewing " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
|
|
3280
|
+
isActive ? /* @__PURE__ */ jsx8(Text8, { color: theme.warn, children: "ACTIVE " }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " " }),
|
|
3281
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: a.user && a.project ? truncate(a.user, 24) : "" })
|
|
3282
|
+
] }, a.apiKey);
|
|
3283
|
+
}) }),
|
|
3284
|
+
/* @__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" }) })
|
|
3285
|
+
] });
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
// src/tui/App.tsx
|
|
3289
|
+
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3290
|
+
function LiveHint() {
|
|
3291
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3292
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: "\u25B6 REALTIME CONSOLE" }),
|
|
3293
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "Fullscreen live monitor \u2014 streaming tool calls, active charts," }),
|
|
3294
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "counters, DLP hits and denial alerts. Updates every 2s." }),
|
|
3295
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3074
3296
|
"press ",
|
|
3075
|
-
/* @__PURE__ */
|
|
3297
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accent, children: "enter" }),
|
|
3076
3298
|
" to go live"
|
|
3077
3299
|
] }) })
|
|
3078
3300
|
] });
|
|
@@ -3083,28 +3305,46 @@ var SECTIONS = [
|
|
|
3083
3305
|
{ label: "Rate Limit", Panel: RateLimitPanel },
|
|
3084
3306
|
{ label: "DLP", Panel: DlpPanel },
|
|
3085
3307
|
{ label: "Audit", Panel: AuditPanel },
|
|
3086
|
-
{ label: "Settings", Panel: SettingsPanel }
|
|
3308
|
+
{ label: "Settings", Panel: SettingsPanel },
|
|
3309
|
+
{ label: "Accounts", Panel: AccountsPanel }
|
|
3087
3310
|
];
|
|
3088
3311
|
var BANNER_HEX = ["white", "white", "white", "white", "white", "white"];
|
|
3089
3312
|
function Banner({ cols }) {
|
|
3090
3313
|
const wide = cols >= 82;
|
|
3091
3314
|
if (!wide) {
|
|
3092
|
-
return /* @__PURE__ */
|
|
3093
|
-
/* @__PURE__ */
|
|
3094
|
-
/* @__PURE__ */
|
|
3315
|
+
return /* @__PURE__ */ jsxs9(Box9, { children: [
|
|
3316
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate" }),
|
|
3317
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " \u2014 security control center" })
|
|
3095
3318
|
] });
|
|
3096
3319
|
}
|
|
3097
|
-
return /* @__PURE__ */
|
|
3098
|
-
BANNER_FULL.map((line, i) => /* @__PURE__ */
|
|
3099
|
-
/* @__PURE__ */
|
|
3320
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
|
|
3321
|
+
BANNER_FULL.map((line, i) => /* @__PURE__ */ jsx9(Text9, { bold: true, color: BANNER_HEX[i], children: line }, i)),
|
|
3322
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: " security control center \xB7 manage policies, rate limits, DLP & more" })
|
|
3100
3323
|
] });
|
|
3101
3324
|
}
|
|
3325
|
+
var ACCOUNTS_SECTION = SECTIONS.findIndex((s) => s.label === "Accounts");
|
|
3102
3326
|
function App() {
|
|
3103
3327
|
const { exit } = useApp();
|
|
3104
|
-
const [
|
|
3105
|
-
const [
|
|
3106
|
-
const [
|
|
3107
|
-
|
|
3328
|
+
const [accounts, setAccounts] = useState9(() => listAccounts());
|
|
3329
|
+
const [viewKey, setViewKey] = useState9(() => accounts[0]?.apiKey);
|
|
3330
|
+
const [viewNonce, setViewNonce] = useState9(0);
|
|
3331
|
+
const [section, setSection] = useState9(accounts.length === 0 ? ACCOUNTS_SECTION : 0);
|
|
3332
|
+
const [focus, setFocus] = useState9("nav");
|
|
3333
|
+
const [help, setHelp] = useState9(false);
|
|
3334
|
+
const acctIdx = Math.max(0, accounts.findIndex((a) => a.apiKey === viewKey));
|
|
3335
|
+
const cur = accounts[acctIdx];
|
|
3336
|
+
const acctLabel = cur ? cur.project || cur.user || cur.apiKey.slice(0, 8) : "not logged in";
|
|
3337
|
+
const viewAccount = (a) => {
|
|
3338
|
+
setViewCredentials({ apiKey: a.apiKey, apiUrl: a.apiUrl });
|
|
3339
|
+
setAccounts(listAccounts());
|
|
3340
|
+
setViewKey(a.apiKey);
|
|
3341
|
+
setViewNonce((n) => n + 1);
|
|
3342
|
+
};
|
|
3343
|
+
const switchAccount = () => {
|
|
3344
|
+
if (accounts.length < 2) return;
|
|
3345
|
+
viewAccount(accounts[(acctIdx + 1) % accounts.length]);
|
|
3346
|
+
};
|
|
3347
|
+
useInput8((input, key) => {
|
|
3108
3348
|
if (help) {
|
|
3109
3349
|
setHelp(false);
|
|
3110
3350
|
return;
|
|
@@ -3117,6 +3357,7 @@ function App() {
|
|
|
3117
3357
|
if (key.upArrow) setSection((n) => (n - 1 + SECTIONS.length) % SECTIONS.length);
|
|
3118
3358
|
else if (key.downArrow) setSection((n) => (n + 1) % SECTIONS.length);
|
|
3119
3359
|
else if (key.rightArrow || key.return || key.tab) setFocus("panel");
|
|
3360
|
+
else if (input === "a") switchAccount();
|
|
3120
3361
|
else if (input === "q") exit();
|
|
3121
3362
|
} else {
|
|
3122
3363
|
if (key.escape || key.tab) setFocus("nav");
|
|
@@ -3125,18 +3366,24 @@ function App() {
|
|
|
3125
3366
|
});
|
|
3126
3367
|
const { cols, rows } = useTermSize();
|
|
3127
3368
|
const current = SECTIONS[section];
|
|
3128
|
-
if (help) return /* @__PURE__ */
|
|
3369
|
+
if (help) return /* @__PURE__ */ jsx9(HelpOverlay, { cols, rows });
|
|
3129
3370
|
if (current.label === "Live" && focus === "panel") {
|
|
3130
|
-
return /* @__PURE__ */
|
|
3371
|
+
return /* @__PURE__ */ jsx9(Box9, { flexDirection: "column", width: cols, height: rows, children: /* @__PURE__ */ jsx9(LivePanel, { active: true, focused: true }, viewNonce) });
|
|
3131
3372
|
}
|
|
3132
3373
|
const Panel = current.Panel;
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
/* @__PURE__ */
|
|
3136
|
-
|
|
3137
|
-
/* @__PURE__ */
|
|
3374
|
+
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" });
|
|
3375
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 1, paddingTop: 1, children: [
|
|
3376
|
+
/* @__PURE__ */ jsx9(Banner, { cols }),
|
|
3377
|
+
/* @__PURE__ */ jsxs9(Text9, { wrap: "truncate", children: [
|
|
3378
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "account: " }),
|
|
3379
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, bold: true, children: acctLabel }),
|
|
3380
|
+
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
|
|
3138
3381
|
] }),
|
|
3139
|
-
/* @__PURE__ */
|
|
3382
|
+
/* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexGrow: 1, children: [
|
|
3383
|
+
/* @__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)) }),
|
|
3384
|
+
/* @__PURE__ */ jsx9(Box9, { flexGrow: 1, borderStyle: "round", borderColor: focus === "panel" ? theme.accent : "gray", paddingX: 1, paddingY: 0, children: panelBody })
|
|
3385
|
+
] }, viewNonce),
|
|
3386
|
+
/* @__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"]] }) })
|
|
3140
3387
|
] });
|
|
3141
3388
|
}
|
|
3142
3389
|
var HELP = [
|
|
@@ -3145,24 +3392,25 @@ var HELP = [
|
|
|
3145
3392
|
["Policies", [["\u2191\u2193", "browse / select"], ["a", "activate (pin) selected policy"], ["x", "deactivate \u2014 no active policy"], ["enter", "open rules \u2192 open a rule"], ["space", "toggle a rule on/off"], ["e", "flip effect"], ["n", "new rule"], ["d", "delete rule"], ["m", "flip mode"], ["D", "dry-run the draft"], ["s", "save"], ["x", "discard"]]],
|
|
3146
3393
|
["Rate limit / DLP", [["\u2191\u2193", "field / pattern"], ["\u2190\u2192", "adjust (shift = \xB110)"], ["space", "toggle pattern"], ["m", "mode"], ["a / d", "add / remove custom"], ["g", "ghost mode"], ["P", "self-protection"], ["s", "save"]]],
|
|
3147
3394
|
["Audit", [["v", "logs \u2194 sessions"], ["s", "source: cloud \u2194 local"], ["\u2190 \u2192", "prev / next page (500 each)"], ["\u2191\u2193", "select (list scrolls)"], ["enter", "full entry / session logs"], ["f / g", "decision / signal filter"], ["t / n / /", "tool / agent / search"], ["x / X", "delete entry / ALL (press twice)"], ["c", "clear filters"]]],
|
|
3148
|
-
["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]]
|
|
3395
|
+
["Settings", [["\u2191\u2193", "move"], ["enter / space", "toggle \xB7 edit \xB7 add"], ["e", "webhook events"], ["d d", "delete"], ["r", "refresh"]]],
|
|
3396
|
+
["Accounts", [["a", "switch account (view another account logged in on this device)"], ["", "the header shows which account you are viewing; guard/logging keep the active key"]]]
|
|
3149
3397
|
];
|
|
3150
3398
|
function HelpOverlay({ cols, rows }) {
|
|
3151
|
-
return /* @__PURE__ */
|
|
3152
|
-
/* @__PURE__ */
|
|
3153
|
-
/* @__PURE__ */
|
|
3154
|
-
/* @__PURE__ */
|
|
3155
|
-
keys.map(([k, desc]) => /* @__PURE__ */
|
|
3156
|
-
/* @__PURE__ */
|
|
3157
|
-
/* @__PURE__ */
|
|
3399
|
+
return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", width: cols, height: rows, paddingX: 2, paddingTop: 1, children: [
|
|
3400
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accentBright, children: "SolonGate \u2014 keyboard shortcuts" }),
|
|
3401
|
+
/* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: HELP.map(([group, keys]) => /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", marginBottom: 1, children: [
|
|
3402
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, color: theme.accent, children: group }),
|
|
3403
|
+
keys.map(([k, desc]) => /* @__PURE__ */ jsxs9(Text9, { children: [
|
|
3404
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.accentBright, children: (" " + k).padEnd(16) }),
|
|
3405
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: desc })
|
|
3158
3406
|
] }, k))
|
|
3159
3407
|
] }, group)) }),
|
|
3160
|
-
/* @__PURE__ */
|
|
3408
|
+
/* @__PURE__ */ jsx9(Text9, { color: theme.dim, children: "press any key to close" })
|
|
3161
3409
|
] });
|
|
3162
3410
|
}
|
|
3163
3411
|
|
|
3164
3412
|
// src/tui/index.tsx
|
|
3165
|
-
import { jsx as
|
|
3413
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
3166
3414
|
async function launchTui() {
|
|
3167
3415
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
3168
3416
|
process.stderr.write(
|
|
@@ -3170,13 +3418,9 @@ async function launchTui() {
|
|
|
3170
3418
|
);
|
|
3171
3419
|
return;
|
|
3172
3420
|
}
|
|
3173
|
-
if (!isAuthenticated()) {
|
|
3174
|
-
process.stderr.write("Not logged in. Run `solongate login` first.\n");
|
|
3175
|
-
return;
|
|
3176
|
-
}
|
|
3177
3421
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
3178
3422
|
try {
|
|
3179
|
-
const { waitUntilExit } = render(/* @__PURE__ */
|
|
3423
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx10(App, {}));
|
|
3180
3424
|
await waitUntilExit();
|
|
3181
3425
|
} finally {
|
|
3182
3426
|
process.stdout.write("\x1B[?1049l");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.81.
|
|
3
|
+
"version": "0.81.38",
|
|
4
4
|
"description": "AI tool security proxy: protect any AI tool server with customizable policies, path/command constraints, rate limiting, and audit logging. No code changes required.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|