@solongate/proxy 0.82.82 → 0.82.84
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/commands/doctor.d.ts +12 -0
- package/dist/commands/index.js +7 -3
- package/dist/global-install.d.ts +19 -0
- package/dist/global-install.js +38 -24
- package/dist/index.js +390 -255
- package/dist/tui/index.js +262 -42
- package/package.json +1 -1
package/dist/tui/index.js
CHANGED
|
@@ -338,6 +338,36 @@ function codexHooksStatus() {
|
|
|
338
338
|
}
|
|
339
339
|
return { registered, trusted, disabled };
|
|
340
340
|
}
|
|
341
|
+
function repairQuiet() {
|
|
342
|
+
const p = globalPaths();
|
|
343
|
+
const has = (f) => existsSync2(f);
|
|
344
|
+
const guardFile = join3(p.hooksDir, "guard.mjs");
|
|
345
|
+
const line = (label, ok, yes, no) => ({ label, ok, detail: ok ? yes : no });
|
|
346
|
+
const before = [
|
|
347
|
+
line("guard hook file", has(guardFile), "present", "MISSING"),
|
|
348
|
+
line("cloud credential", has(p.configPath), "present", "MISSING"),
|
|
349
|
+
line("Claude Code settings", isGuardInstalled(), "guard registered", "guard NOT registered"),
|
|
350
|
+
line("Antigravity hooks", has(p.antigravityHooksPath), "present", "MISSING"),
|
|
351
|
+
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "guard NOT registered")
|
|
352
|
+
];
|
|
353
|
+
const r = installGlobalQuiet();
|
|
354
|
+
if (!r.ok) return { ok: false, message: r.message, before, after: [], notes: [] };
|
|
355
|
+
const after = [
|
|
356
|
+
{ label: "guard hook file", ok: true, detail: `present (v${installedGuardVersion() ?? "?"})` },
|
|
357
|
+
line("Claude Code settings", isGuardInstalled(), "guard registered", "NOT registered"),
|
|
358
|
+
line("Antigravity hooks", has(p.antigravityHooksPath), "present", "MISSING"),
|
|
359
|
+
line("Codex hooks", isCodexGuardInstalled(), "guard registered", "NOT registered")
|
|
360
|
+
];
|
|
361
|
+
const notes = [];
|
|
362
|
+
const cx = codexHooksStatus();
|
|
363
|
+
if (cx.registered && !cx.trusted) {
|
|
364
|
+
notes.push("Codex only: run `/hooks` inside Codex once and trust the SolonGate hooks (Codex skips any hook it has not been told to trust).");
|
|
365
|
+
}
|
|
366
|
+
if (cx.disabled) {
|
|
367
|
+
notes.push("Codex only: hooks are turned OFF in ~/.codex/config.toml ([features] hooks = false) - the guard cannot run there until that line is removed.");
|
|
368
|
+
}
|
|
369
|
+
return { ok: true, message: "guard repaired. Open a new AI session for it to take effect.", before, after, notes };
|
|
370
|
+
}
|
|
341
371
|
function installGlobalQuiet() {
|
|
342
372
|
try {
|
|
343
373
|
const p = globalPaths();
|
|
@@ -500,8 +530,8 @@ var init_global_install = __esm({
|
|
|
500
530
|
|
|
501
531
|
// src/tui/index.tsx
|
|
502
532
|
import { appendFileSync, mkdirSync as mkdirSync7 } from "fs";
|
|
503
|
-
import { homedir as
|
|
504
|
-
import { join as
|
|
533
|
+
import { homedir as homedir10 } from "os";
|
|
534
|
+
import { join as join10 } from "path";
|
|
505
535
|
import { render } from "ink";
|
|
506
536
|
|
|
507
537
|
// src/tui/App.tsx
|
|
@@ -960,6 +990,14 @@ function dotenvApiKey() {
|
|
|
960
990
|
return void 0;
|
|
961
991
|
}
|
|
962
992
|
var cached2 = null;
|
|
993
|
+
function isAuthenticated() {
|
|
994
|
+
try {
|
|
995
|
+
resolveCredentials();
|
|
996
|
+
return true;
|
|
997
|
+
} catch {
|
|
998
|
+
return false;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
963
1001
|
function resolveCredentials(apiUrlOverride) {
|
|
964
1002
|
if (viewOverride && !apiUrlOverride) return viewOverride;
|
|
965
1003
|
if (cached2 && !apiUrlOverride) return cached2;
|
|
@@ -1005,8 +1043,8 @@ async function request(method, path, opts = {}) {
|
|
|
1005
1043
|
try {
|
|
1006
1044
|
res = await attempt();
|
|
1007
1045
|
break;
|
|
1008
|
-
} catch (
|
|
1009
|
-
lastErr =
|
|
1046
|
+
} catch (err2) {
|
|
1047
|
+
lastErr = err2;
|
|
1010
1048
|
if (i < maxTries - 1) await new Promise((r) => setTimeout(r, 600 * (i + 1)));
|
|
1011
1049
|
}
|
|
1012
1050
|
}
|
|
@@ -1931,8 +1969,8 @@ function LivePanel({ active: active2 }) {
|
|
|
1931
1969
|
until: Date.now() + 7e3
|
|
1932
1970
|
});
|
|
1933
1971
|
pushLog(`${act === "whitelist" ? "\u2713 whitelisted" : "\u26D4 blocked"} ${e.tool}`, act === "whitelist" ? "ok" : "warn");
|
|
1934
|
-
} catch (
|
|
1935
|
-
setActionMsg({ text: "\u2717 " + (
|
|
1972
|
+
} catch (err2) {
|
|
1973
|
+
setActionMsg({ text: "\u2717 " + (err2 instanceof Error ? err2.message : String(err2)), level: "bad", until: Date.now() + 7e3 });
|
|
1936
1974
|
}
|
|
1937
1975
|
};
|
|
1938
1976
|
useInput(
|
|
@@ -2035,8 +2073,8 @@ function LivePanel({ active: active2 }) {
|
|
|
2035
2073
|
mkdirSync3(join5(homedir5(), ".solongate"), { recursive: true });
|
|
2036
2074
|
writeFileSync4(file, visibleDesc.map((x) => JSON.stringify(x)).join("\n") + "\n");
|
|
2037
2075
|
setActionMsg({ text: `\u2713 exported ${visibleDesc.length} lines \u2192 ${file}`, level: "ok", until: Date.now() + 6e3 });
|
|
2038
|
-
} catch (
|
|
2039
|
-
setActionMsg({ text: "\u2717 export failed: " + (
|
|
2076
|
+
} catch (err2) {
|
|
2077
|
+
setActionMsg({ text: "\u2717 export failed: " + (err2 instanceof Error ? err2.message : String(err2)), level: "bad", until: Date.now() + 6e3 });
|
|
2040
2078
|
}
|
|
2041
2079
|
} else if (key.downArrow) setSel((n) => Math.min(visibleDesc.length - 1, n + 1));
|
|
2042
2080
|
else if (key.upArrow) setSel((n) => Math.max(0, n - 1));
|
|
@@ -2461,7 +2499,7 @@ function DryRunPanel({ focused }) {
|
|
|
2461
2499
|
const totalLogs = statsQ.data?.total_calls ?? null;
|
|
2462
2500
|
const [res, setRes] = useState3(null);
|
|
2463
2501
|
const [running, setRunning] = useState3(false);
|
|
2464
|
-
const [
|
|
2502
|
+
const [err2, setErr] = useState3(null);
|
|
2465
2503
|
const [off, setOff] = useState3(0);
|
|
2466
2504
|
const { cols, rows } = usePanelSize();
|
|
2467
2505
|
const selected = policies[Math.min(pi, Math.max(0, policies.length - 1))];
|
|
@@ -2475,7 +2513,7 @@ function DryRunPanel({ focused }) {
|
|
|
2475
2513
|
api.policies.backtest({ rules: p.rules ?? [], mode: p.mode, limit: lim }).then((r) => setRes(r)).catch((e) => setErr(e instanceof Error ? e.message : String(e))).finally(() => setRunning(false));
|
|
2476
2514
|
}
|
|
2477
2515
|
useEffect3(() => {
|
|
2478
|
-
if (policies.length === 0 || res || running ||
|
|
2516
|
+
if (policies.length === 0 || res || running || err2) return;
|
|
2479
2517
|
let idx = 0;
|
|
2480
2518
|
if (pendingPolicyId) {
|
|
2481
2519
|
const found = policies.findIndex((p) => p.id === pendingPolicyId);
|
|
@@ -2508,7 +2546,7 @@ function DryRunPanel({ focused }) {
|
|
|
2508
2546
|
const w = Math.max(40, cols);
|
|
2509
2547
|
const lines = [];
|
|
2510
2548
|
const head = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { bold: true, color: theme.accentBright, children: t }, `h${lines.length}`));
|
|
2511
|
-
const
|
|
2549
|
+
const dim2 = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: t }, `d${lines.length}`));
|
|
2512
2550
|
const row = (t) => lines.push(/* @__PURE__ */ jsx3(Text3, { wrap: "truncate", children: t }, `r${lines.length}`));
|
|
2513
2551
|
const blank = () => lines.push(/* @__PURE__ */ jsx3(Text3, { children: " " }, `b${lines.length}`));
|
|
2514
2552
|
if (res) {
|
|
@@ -2534,7 +2572,7 @@ function DryRunPanel({ focused }) {
|
|
|
2534
2572
|
blank();
|
|
2535
2573
|
if (res.per_rule.length) {
|
|
2536
2574
|
head("Per-rule impact");
|
|
2537
|
-
|
|
2575
|
+
dim2(` ${"RULE".padEnd(38)}${"MATCHED".padEnd(9)}${"%".padEnd(8)}${"NEW-BLK".padEnd(9)}NEW-ALW`);
|
|
2538
2576
|
for (const r of res.per_rule.slice(0, 30)) {
|
|
2539
2577
|
row(` ${truncate(r.rule_id, 36).padEnd(38)}${String(r.matched).padEnd(9)}${pct(r.matched, s.evaluated).padEnd(8)}${String(r.newly_blocked).padEnd(9)}${r.newly_allowed}`);
|
|
2540
2578
|
}
|
|
@@ -2542,7 +2580,7 @@ function DryRunPanel({ focused }) {
|
|
|
2542
2580
|
}
|
|
2543
2581
|
if (res.per_agent.length) {
|
|
2544
2582
|
head("Per-agent impact");
|
|
2545
|
-
|
|
2583
|
+
dim2(` ${"AGENT".padEnd(24)}${"EVALUATED".padEnd(11)}${"CHANGED".padEnd(9)}${"NEW-BLK".padEnd(9)}NEW-ALW`);
|
|
2546
2584
|
for (const a of res.per_agent) {
|
|
2547
2585
|
row(` ${truncate(a.agent || "-", 22).padEnd(24)}${String(a.evaluated).padEnd(11)}${String(a.changed).padEnd(9)}${String(a.newly_blocked).padEnd(9)}${a.newly_allowed}`);
|
|
2548
2586
|
}
|
|
@@ -2550,7 +2588,7 @@ function DryRunPanel({ focused }) {
|
|
|
2550
2588
|
}
|
|
2551
2589
|
if (res.per_tool.length) {
|
|
2552
2590
|
head("Per-tool breakdown");
|
|
2553
|
-
|
|
2591
|
+
dim2(` ${"TOOL".padEnd(24)}${"EVALUATED".padEnd(11)}${"CHANGED".padEnd(9)}${"NEW-BLK".padEnd(9)}NEW-ALW`);
|
|
2554
2592
|
for (const t of res.per_tool) {
|
|
2555
2593
|
row(` ${truncate(t.tool || "-", 22).padEnd(24)}${String(t.evaluated).padEnd(11)}${String(t.changed).padEnd(9)}${String(t.newly_blocked).padEnd(9)}${t.newly_allowed}`);
|
|
2556
2594
|
}
|
|
@@ -2599,8 +2637,8 @@ function DryRunPanel({ focused }) {
|
|
|
2599
2637
|
] }),
|
|
2600
2638
|
/* @__PURE__ */ jsx3(Text3, { color: theme.dim, wrap: "truncate", children: focused ? `\u2191\u2193 scroll \xB7 p policy \xB7 n logs \xB7 r re-run${above ? ` \xB7 \u25B2${above}` : ""}${below ? ` \xB7 \u25BC${below}` : ""}` : "press \u2192 to open" }),
|
|
2601
2639
|
running ? /* @__PURE__ */ jsx3(Text3, { color: theme.warn, children: "replaying recent traffic\u2026" }) : null,
|
|
2602
|
-
|
|
2603
|
-
!running && !
|
|
2640
|
+
err2 ? /* @__PURE__ */ jsx3(Text3, { color: theme.bad, wrap: "truncate", children: "\u2717 " + err2 }) : null,
|
|
2641
|
+
!running && !err2 && !res ? /* @__PURE__ */ jsx3(Text3, { color: theme.dim, children: "no result yet, press r to run" }) : null,
|
|
2604
2642
|
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", children: win })
|
|
2605
2643
|
] }) });
|
|
2606
2644
|
}
|
|
@@ -4238,19 +4276,85 @@ import TextInput6 from "ink-text-input";
|
|
|
4238
4276
|
import { useEffect as useEffect7, useRef as useRef3, useState as useState8 } from "react";
|
|
4239
4277
|
init_global_install();
|
|
4240
4278
|
|
|
4279
|
+
// src/commands/doctor.ts
|
|
4280
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
4281
|
+
import { homedir as homedir7 } from "os";
|
|
4282
|
+
import { join as join7 } from "path";
|
|
4283
|
+
init_global_install();
|
|
4284
|
+
async function collectChecks() {
|
|
4285
|
+
const checks = [];
|
|
4286
|
+
if (!isAuthenticated()) {
|
|
4287
|
+
checks.push({ name: "login", ok: false, detail: "not logged in - run `solongate`, add your account in the Accounts panel" });
|
|
4288
|
+
} else {
|
|
4289
|
+
const { apiUrl } = resolveCredentials();
|
|
4290
|
+
checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
|
|
4291
|
+
try {
|
|
4292
|
+
const active2 = await api.policies.active();
|
|
4293
|
+
if (active2.policy) {
|
|
4294
|
+
checks.push({ name: "active policy", ok: true, detail: `${active2.policy.name} v${active2.version} \xB7 ${active2.policy.mode ?? "denylist"} \xB7 matched by ${active2.matched_by}` });
|
|
4295
|
+
} else {
|
|
4296
|
+
checks.push({ name: "active policy", ok: "warn", detail: "no policy resolves - every call falls back to default" });
|
|
4297
|
+
}
|
|
4298
|
+
const sec = active2.security;
|
|
4299
|
+
checks.push({ name: "rate limit", ok: sec?.rateLimit ? true : "warn", detail: sec?.rateLimit ? `${sec.rateLimit.perMinute}/min` : "off" });
|
|
4300
|
+
checks.push({ name: "dlp", ok: sec?.dlpBlock ? true : "warn", detail: sec?.dlpBlock ? `block \xB7 ${sec.dlpBlock.patterns.length} patterns` : sec?.dlpRedact ? "redact" : "off" });
|
|
4301
|
+
checks.push({ name: "self-protection", ok: active2.self_protection_enabled ? true : "warn", detail: active2.self_protection_enabled ? "on" : "off" });
|
|
4302
|
+
} catch (e) {
|
|
4303
|
+
checks.push({ name: "api", ok: false, detail: "unreachable: " + (e instanceof Error ? e.message : String(e)) });
|
|
4304
|
+
}
|
|
4305
|
+
try {
|
|
4306
|
+
const g = await api.settings.getGuardStatus();
|
|
4307
|
+
checks.push({ name: "guard hook", ok: g.up_to_date ? true : "warn", detail: g.up_to_date ? `v${g.installed} (latest) \xB7 ${g.device_count} device(s)` : `v${g.installed} \u2192 v${g.latest} available \xB7 update from \`solongate\` \u2192 Settings \u2192 guard` });
|
|
4308
|
+
} catch {
|
|
4309
|
+
}
|
|
4310
|
+
}
|
|
4311
|
+
if (codexDetected()) {
|
|
4312
|
+
const cx = codexHooksStatus();
|
|
4313
|
+
if (!cx.registered) {
|
|
4314
|
+
checks.push({ name: "codex hooks", ok: false, detail: "guard not registered - run `solongate repair`" });
|
|
4315
|
+
} else if (cx.disabled) {
|
|
4316
|
+
checks.push({ name: "codex hooks", ok: false, detail: "hooks disabled in ~/.codex/config.toml ([features] hooks = false)" });
|
|
4317
|
+
} else if (!cx.trusted) {
|
|
4318
|
+
checks.push({ name: "codex hooks", ok: "warn", detail: "registered - run `/hooks` in Codex once and trust them (Codex skips untrusted hooks)" });
|
|
4319
|
+
} else {
|
|
4320
|
+
checks.push({ name: "codex hooks", ok: true, detail: "registered + trusted" });
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
try {
|
|
4324
|
+
const raw = readFileSync5(join7(homedir7(), ".solongate", ".key-rejected.json"), "utf-8");
|
|
4325
|
+
const m = JSON.parse(raw);
|
|
4326
|
+
const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
|
|
4327
|
+
checks.push({
|
|
4328
|
+
name: "hook credential",
|
|
4329
|
+
ok: false,
|
|
4330
|
+
detail: `rejected by ${m.apiUrl ?? "the API"}${ageMin != null ? ` ${ageMin}m ago` : ""} \xB7 key from ${m.keySource ?? "?"}${m.cwd ? ` (agent cwd ${m.cwd})` : ""} - nothing is being logged from there`
|
|
4331
|
+
});
|
|
4332
|
+
} catch {
|
|
4333
|
+
}
|
|
4334
|
+
const LOCAL_LOG2 = localLogFile();
|
|
4335
|
+
if (existsSync4(LOCAL_LOG2)) {
|
|
4336
|
+
const st = statSync2(LOCAL_LOG2);
|
|
4337
|
+
const ageMin = (Date.now() - st.mtimeMs) / 6e4;
|
|
4338
|
+
checks.push({ name: "local logs", ok: true, detail: `on \xB7 ${(st.size / 1024).toFixed(0)}KB \xB7 last write ${ageMin < 1 ? "just now" : Math.round(ageMin) + "m ago"}` });
|
|
4339
|
+
} else {
|
|
4340
|
+
checks.push({ name: "local logs", ok: "warn", detail: "off (logs go to cloud) - enable in dashboard \u2192 Settings" });
|
|
4341
|
+
}
|
|
4342
|
+
return checks;
|
|
4343
|
+
}
|
|
4344
|
+
|
|
4241
4345
|
// src/logs-server-daemon.ts
|
|
4242
4346
|
import { spawn as spawn3 } from "child_process";
|
|
4243
|
-
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as
|
|
4244
|
-
import { homedir as
|
|
4245
|
-
import { dirname as dirname2, join as
|
|
4347
|
+
import { mkdirSync as mkdirSync5, openSync as openSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
4348
|
+
import { homedir as homedir8 } from "os";
|
|
4349
|
+
import { dirname as dirname2, join as join8 } from "path";
|
|
4246
4350
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4247
|
-
var DIR =
|
|
4248
|
-
var STATE_FILE =
|
|
4249
|
-
var LOG_FILE =
|
|
4351
|
+
var DIR = join8(homedir8(), ".solongate");
|
|
4352
|
+
var STATE_FILE = join8(DIR, ".logs-server.json");
|
|
4353
|
+
var LOG_FILE = join8(DIR, "logs-server.log");
|
|
4250
4354
|
var LOGS_SERVER_PORT = 8788;
|
|
4251
4355
|
function readState() {
|
|
4252
4356
|
try {
|
|
4253
|
-
const s = JSON.parse(
|
|
4357
|
+
const s = JSON.parse(readFileSync6(STATE_FILE, "utf-8"));
|
|
4254
4358
|
return s && typeof s === "object" ? s : {};
|
|
4255
4359
|
} catch {
|
|
4256
4360
|
return {};
|
|
@@ -4286,7 +4390,7 @@ function startLogsServerDaemon() {
|
|
|
4286
4390
|
try {
|
|
4287
4391
|
mkdirSync5(DIR, { recursive: true });
|
|
4288
4392
|
const log = openSync2(LOG_FILE, "a");
|
|
4289
|
-
const cli =
|
|
4393
|
+
const cli = join8(dirname2(fileURLToPath2(import.meta.url)), "index.js");
|
|
4290
4394
|
const p = spawn3(process.execPath, [cli, "logs-server"], {
|
|
4291
4395
|
detached: true,
|
|
4292
4396
|
stdio: ["ignore", log, log],
|
|
@@ -4318,18 +4422,18 @@ function stopLogsServerDaemon() {
|
|
|
4318
4422
|
|
|
4319
4423
|
// src/self-update.ts
|
|
4320
4424
|
import { execFile, execFileSync as execFileSync2, spawn as spawn4 } from "child_process";
|
|
4321
|
-
import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as
|
|
4322
|
-
import { homedir as
|
|
4323
|
-
import { dirname as dirname3, join as
|
|
4425
|
+
import { mkdirSync as mkdirSync6, openSync as openSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync7 } from "fs";
|
|
4426
|
+
import { homedir as homedir9 } from "os";
|
|
4427
|
+
import { dirname as dirname3, join as join9 } from "path";
|
|
4324
4428
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4325
4429
|
var PKG = "@solongate/proxy";
|
|
4326
4430
|
var CHECK_EVERY_MS = 30 * 60 * 1e3;
|
|
4327
4431
|
var ATTEMPT_EVERY_MS = 6 * 60 * 60 * 1e3;
|
|
4328
|
-
var STATE_FILE2 =
|
|
4329
|
-
var LOG_FILE2 =
|
|
4432
|
+
var STATE_FILE2 = join9(homedir9(), ".solongate", ".self-update.json");
|
|
4433
|
+
var LOG_FILE2 = join9(homedir9(), ".solongate", "self-update.log");
|
|
4330
4434
|
function readState2() {
|
|
4331
4435
|
try {
|
|
4332
|
-
const s = JSON.parse(
|
|
4436
|
+
const s = JSON.parse(readFileSync7(STATE_FILE2, "utf-8"));
|
|
4333
4437
|
return s && typeof s === "object" ? s : {};
|
|
4334
4438
|
} catch {
|
|
4335
4439
|
return {};
|
|
@@ -4337,14 +4441,14 @@ function readState2() {
|
|
|
4337
4441
|
}
|
|
4338
4442
|
function writeState2(s) {
|
|
4339
4443
|
try {
|
|
4340
|
-
mkdirSync6(
|
|
4444
|
+
mkdirSync6(join9(homedir9(), ".solongate"), { recursive: true });
|
|
4341
4445
|
writeFileSync7(STATE_FILE2, JSON.stringify(s));
|
|
4342
4446
|
} catch {
|
|
4343
4447
|
}
|
|
4344
4448
|
}
|
|
4345
4449
|
function currentVersion() {
|
|
4346
4450
|
try {
|
|
4347
|
-
const pkg = JSON.parse(
|
|
4451
|
+
const pkg = JSON.parse(readFileSync7(join9(dirname3(fileURLToPath3(import.meta.url)), "..", "package.json"), "utf-8"));
|
|
4348
4452
|
return pkg.version ?? "0.0.0";
|
|
4349
4453
|
} catch {
|
|
4350
4454
|
return "0.0.0";
|
|
@@ -4381,20 +4485,20 @@ async function fetchLatest() {
|
|
|
4381
4485
|
function runGlobalInstall(version) {
|
|
4382
4486
|
return new Promise((resolve3) => {
|
|
4383
4487
|
try {
|
|
4384
|
-
mkdirSync6(
|
|
4488
|
+
mkdirSync6(join9(homedir9(), ".solongate"), { recursive: true });
|
|
4385
4489
|
execFile(
|
|
4386
4490
|
"npm",
|
|
4387
4491
|
["install", "-g", `${PKG}@${version}`],
|
|
4388
4492
|
{ timeout: 3e5, windowsHide: true, shell: process.platform === "win32" },
|
|
4389
|
-
(
|
|
4493
|
+
(err2, stdout, stderr) => {
|
|
4390
4494
|
try {
|
|
4391
|
-
writeFileSync7(LOG_FILE2, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${
|
|
4495
|
+
writeFileSync7(LOG_FILE2, `${(/* @__PURE__ */ new Date()).toISOString()} install ${version}: ${err2 ? "FAILED" : "ok"}
|
|
4392
4496
|
${stdout}
|
|
4393
4497
|
${stderr}
|
|
4394
4498
|
`, { flag: "a" });
|
|
4395
4499
|
} catch {
|
|
4396
4500
|
}
|
|
4397
|
-
resolve3(!
|
|
4501
|
+
resolve3(!err2);
|
|
4398
4502
|
}
|
|
4399
4503
|
);
|
|
4400
4504
|
} catch {
|
|
@@ -4457,6 +4561,24 @@ function SettingsPanel({
|
|
|
4457
4561
|
const [busy, setBusy] = useState8(false);
|
|
4458
4562
|
const [editor, setEditor] = useState8(null);
|
|
4459
4563
|
const [latest, setLatest] = useState8(null);
|
|
4564
|
+
const [diagBusy, setDiagBusy] = useState8(null);
|
|
4565
|
+
const [diag, setDiag] = useState8(null);
|
|
4566
|
+
const [diagStep, setDiagStep] = useState8(0);
|
|
4567
|
+
const DIAG_STEPS = {
|
|
4568
|
+
doctor: ["login", "active policy", "guard hook", "agent hooks", "local logs"],
|
|
4569
|
+
repair: ["inspecting protection files", "rewriting hook files", "registering with agents", "re-locking"]
|
|
4570
|
+
};
|
|
4571
|
+
const settle = async (started, of) => {
|
|
4572
|
+
const min = DIAG_STEPS[of].length * 220 + 160;
|
|
4573
|
+
const left = min - (Date.now() - started);
|
|
4574
|
+
if (left > 0) await new Promise((res) => setTimeout(res, left));
|
|
4575
|
+
};
|
|
4576
|
+
useEffect7(() => {
|
|
4577
|
+
if (!diagBusy) return;
|
|
4578
|
+
const total = DIAG_STEPS[diagBusy].length;
|
|
4579
|
+
const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
|
|
4580
|
+
return () => clearInterval(step);
|
|
4581
|
+
}, [diagBusy]);
|
|
4460
4582
|
const ver = currentVersion();
|
|
4461
4583
|
useEffect7(() => {
|
|
4462
4584
|
let live2 = true;
|
|
@@ -4495,10 +4617,11 @@ function SettingsPanel({
|
|
|
4495
4617
|
return () => clearInterval(t);
|
|
4496
4618
|
}, []);
|
|
4497
4619
|
useEffect7(() => {
|
|
4498
|
-
|
|
4620
|
+
const loggingIn = login && login.phase !== "done" && login.phase !== "error";
|
|
4621
|
+
if (!loggingIn && !diagBusy) return;
|
|
4499
4622
|
const t = setInterval(() => setTick((n) => n + 1), 120);
|
|
4500
4623
|
return () => clearInterval(t);
|
|
4501
|
-
}, [login]);
|
|
4624
|
+
}, [login, diagBusy]);
|
|
4502
4625
|
const localQ = useLoader(() => listAccounts().length ? api.settings.getLocalLogs() : Promise.resolve(null));
|
|
4503
4626
|
const whQ = useLoader(() => listAccounts().length ? api.settings.getWebhooks() : Promise.resolve(null));
|
|
4504
4627
|
const alertQ = useLoader(() => listAccounts().length ? api.settings.getAlerts() : Promise.resolve(null));
|
|
@@ -4530,6 +4653,8 @@ function SettingsPanel({
|
|
|
4530
4653
|
...locked ? [] : [
|
|
4531
4654
|
{ kind: "guard" },
|
|
4532
4655
|
{ kind: "self" },
|
|
4656
|
+
{ kind: "doctor" },
|
|
4657
|
+
{ kind: "repair" },
|
|
4533
4658
|
{ kind: "ll-enabled" },
|
|
4534
4659
|
{ kind: "ll-path" },
|
|
4535
4660
|
{ kind: "ll-server" },
|
|
@@ -4648,6 +4773,64 @@ function SettingsPanel({
|
|
|
4648
4773
|
setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
|
|
4649
4774
|
refreshGuard();
|
|
4650
4775
|
guardQ.reload();
|
|
4776
|
+
} else if (r.kind === "doctor") {
|
|
4777
|
+
if (diagBusy) return;
|
|
4778
|
+
setDiagBusy("doctor");
|
|
4779
|
+
setDiagStep(0);
|
|
4780
|
+
setDiag(null);
|
|
4781
|
+
void (async () => {
|
|
4782
|
+
const started = Date.now();
|
|
4783
|
+
try {
|
|
4784
|
+
const checks = await collectChecks();
|
|
4785
|
+
await settle(started, "doctor");
|
|
4786
|
+
const bad = checks.filter((c2) => c2.ok === false).length;
|
|
4787
|
+
const warn = checks.filter((c2) => c2.ok === "warn").length;
|
|
4788
|
+
setDiag({
|
|
4789
|
+
of: "doctor",
|
|
4790
|
+
lines: checks.map((c2) => ({
|
|
4791
|
+
text: `${c2.ok === true ? "\u2713" : c2.ok === "warn" ? "!" : "\u2717"} ${c2.name.padEnd(16)} ${c2.detail}`,
|
|
4792
|
+
level: c2.ok === true ? "ok" : c2.ok === "warn" ? "warn" : "bad"
|
|
4793
|
+
}))
|
|
4794
|
+
});
|
|
4795
|
+
setMsg(
|
|
4796
|
+
bad ? { text: `\u2717 ${bad} problem(s)${warn ? ` \xB7 ${warn} warning(s)` : ""}`, level: "bad" } : { text: warn ? `\u2713 guard is working \xB7 ${warn} warning(s)` : "\u2713 all good", level: "ok" }
|
|
4797
|
+
);
|
|
4798
|
+
} catch (e) {
|
|
4799
|
+
setMsg({ text: "\u2717 doctor failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" });
|
|
4800
|
+
} finally {
|
|
4801
|
+
setDiagBusy(null);
|
|
4802
|
+
}
|
|
4803
|
+
})();
|
|
4804
|
+
} else if (r.kind === "repair") {
|
|
4805
|
+
if (diagBusy) return;
|
|
4806
|
+
setDiagBusy("repair");
|
|
4807
|
+
setDiagStep(0);
|
|
4808
|
+
setDiag(null);
|
|
4809
|
+
void (async () => {
|
|
4810
|
+
try {
|
|
4811
|
+
const started = Date.now();
|
|
4812
|
+
await new Promise((res) => setTimeout(res, 60));
|
|
4813
|
+
const rep = repairQuiet();
|
|
4814
|
+
await settle(started, "repair");
|
|
4815
|
+
setDiag({
|
|
4816
|
+
of: "repair",
|
|
4817
|
+
lines: [
|
|
4818
|
+
...(rep.ok ? rep.after : rep.before).map((l) => ({
|
|
4819
|
+
text: `${l.ok ? "\u2713" : "\u2717"} ${l.label.padEnd(20)} ${l.detail}`,
|
|
4820
|
+
level: l.ok ? "ok" : "bad"
|
|
4821
|
+
})),
|
|
4822
|
+
...rep.notes.map((n) => ({ text: n, level: "warn" }))
|
|
4823
|
+
]
|
|
4824
|
+
});
|
|
4825
|
+
setMsg({ text: (rep.ok ? "\u2713 " : "\u2717 ") + rep.message, level: rep.ok ? "ok" : "bad" });
|
|
4826
|
+
refreshGuard();
|
|
4827
|
+
guardQ.reload();
|
|
4828
|
+
} catch (e) {
|
|
4829
|
+
setMsg({ text: "\u2717 repair failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" });
|
|
4830
|
+
} finally {
|
|
4831
|
+
setDiagBusy(null);
|
|
4832
|
+
}
|
|
4833
|
+
})();
|
|
4651
4834
|
} else if (r.kind === "self") {
|
|
4652
4835
|
if (!selfProt) return;
|
|
4653
4836
|
run(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
|
|
@@ -4937,6 +5120,18 @@ function SettingsPanel({
|
|
|
4937
5120
|
selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2026" }),
|
|
4938
5121
|
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
|
|
4939
5122
|
] });
|
|
5123
|
+
case "doctor":
|
|
5124
|
+
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5125
|
+
cursor(r),
|
|
5126
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "doctor".padEnd(11) }),
|
|
5127
|
+
diagBusy === "doctor" ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: `${spin} running health check\u2026` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "health check: login, policy, guard, hooks, local logs \xB7 enter runs it" })
|
|
5128
|
+
] });
|
|
5129
|
+
case "repair":
|
|
5130
|
+
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
5131
|
+
cursor(r),
|
|
5132
|
+
/* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "repair".padEnd(11) }),
|
|
5133
|
+
diagBusy === "repair" ? /* @__PURE__ */ jsx8(Text8, { color: theme.accentBright, children: `${spin} restoring protection\u2026` }) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "restore every protection file after tampering or deletion \xB7 enter runs it" })
|
|
5134
|
+
] });
|
|
4940
5135
|
case "ll-enabled":
|
|
4941
5136
|
return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
|
|
4942
5137
|
cursor(r),
|
|
@@ -4989,10 +5184,10 @@ function SettingsPanel({
|
|
|
4989
5184
|
] });
|
|
4990
5185
|
}
|
|
4991
5186
|
};
|
|
4992
|
-
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
5187
|
+
const sectionOf = (r) => r.kind === "acct" || r.kind === "acct-add" ? "ACCOUNTS" : r.kind === "guard" || r.kind === "self" || r.kind === "doctor" || r.kind === "repair" ? "PROTECTION" : r.kind === "ll-enabled" || r.kind === "ll-path" || r.kind === "ll-server" ? "LOCAL LOGS" : r.kind === "wh" || r.kind === "wh-add" ? "WEBHOOKS" : "ALERTS";
|
|
4993
5188
|
const SECTION_DESC = {
|
|
4994
5189
|
ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
|
|
4995
|
-
PROTECTION: "guard hook: enter install/update
|
|
5190
|
+
PROTECTION: "guard hook: enter install/update \xB7 d remove \xB7 self-protection \xB7 doctor + repair",
|
|
4996
5191
|
"LOCAL LOGS": "mirror every decision to a file + dashboard link",
|
|
4997
5192
|
WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
|
|
4998
5193
|
ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
|
|
@@ -5024,6 +5219,31 @@ function SettingsPanel({
|
|
|
5024
5219
|
}
|
|
5025
5220
|
lineEls.push(/* @__PURE__ */ jsx8(Box8, { children: rowLine(r) }, keyOf(r)));
|
|
5026
5221
|
lineKey.push(keyOf(r));
|
|
5222
|
+
if ((r.kind === "doctor" || r.kind === "repair") && diagBusy === r.kind) {
|
|
5223
|
+
DIAG_STEPS[r.kind].forEach((label, i) => {
|
|
5224
|
+
const done = i < diagStep;
|
|
5225
|
+
lineEls.push(
|
|
5226
|
+
/* @__PURE__ */ jsx8(Text8, { wrap: "truncate", color: done ? theme.ok : i === diagStep ? theme.accentBright : theme.dim, children: ` ${done ? "\u2713" : i === diagStep ? spin : " "} ${label}` }, `step:${r.kind}:${i}`)
|
|
5227
|
+
);
|
|
5228
|
+
lineKey.push("");
|
|
5229
|
+
});
|
|
5230
|
+
}
|
|
5231
|
+
if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair")) {
|
|
5232
|
+
diag.lines.forEach((l, i) => {
|
|
5233
|
+
lineEls.push(
|
|
5234
|
+
/* @__PURE__ */ jsx8(
|
|
5235
|
+
Text8,
|
|
5236
|
+
{
|
|
5237
|
+
wrap: "truncate",
|
|
5238
|
+
color: l.level === "ok" ? theme.ok : l.level === "warn" ? theme.warn : l.level === "bad" ? theme.bad : theme.dim,
|
|
5239
|
+
children: " " + l.text
|
|
5240
|
+
},
|
|
5241
|
+
`diag:${diag.of}:${i}`
|
|
5242
|
+
)
|
|
5243
|
+
);
|
|
5244
|
+
lineKey.push("");
|
|
5245
|
+
});
|
|
5246
|
+
}
|
|
5027
5247
|
});
|
|
5028
5248
|
if (hasEmailAlert && hasTgAlert) {
|
|
5029
5249
|
lineEls.push(
|
|
@@ -5258,11 +5478,11 @@ async function launchTui() {
|
|
|
5258
5478
|
return;
|
|
5259
5479
|
}
|
|
5260
5480
|
process.stdout.write("\x1B[?1049h\x1B[H");
|
|
5261
|
-
const debugLog =
|
|
5481
|
+
const debugLog = join10(homedir10(), ".solongate", "dataroom-debug.log");
|
|
5262
5482
|
const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
|
|
5263
5483
|
const toFile = (level) => (...args) => {
|
|
5264
5484
|
try {
|
|
5265
|
-
mkdirSync7(
|
|
5485
|
+
mkdirSync7(join10(homedir10(), ".solongate"), { recursive: true });
|
|
5266
5486
|
appendFileSync(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
5267
5487
|
`);
|
|
5268
5488
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solongate/proxy",
|
|
3
|
-
"version": "0.82.
|
|
3
|
+
"version": "0.82.84",
|
|
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": {
|