@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/index.js CHANGED
@@ -6203,6 +6203,7 @@ __export(global_install_exports, {
6203
6203
  isGuardInstalled: () => isGuardInstalled,
6204
6204
  lockProtected: () => lockProtected,
6205
6205
  removeClaudeShim: () => removeClaudeShim,
6206
+ repairQuiet: () => repairQuiet,
6206
6207
  runGlobalInstall: () => runGlobalInstall,
6207
6208
  runGlobalRestore: () => runGlobalRestore,
6208
6209
  runRepair: () => runRepair,
@@ -6579,44 +6580,57 @@ function runGlobalRestore() {
6579
6580
  }
6580
6581
  console.log(" Global SolonGate enforcement uninstalled. Restart Claude Code.");
6581
6582
  }
6582
- async function runRepair() {
6583
+ function repairQuiet() {
6583
6584
  const p = globalPaths();
6584
- const out2 = (s) => void process.stderr.write(s + "\n");
6585
6585
  const has = (f) => existsSync3(f);
6586
6586
  const guardFile = join4(p.hooksDir, "guard.mjs");
6587
+ const line = (label, ok, yes, no) => ({ label, ok, detail: ok ? yes : no });
6588
+ const before = [
6589
+ line("guard hook file", has(guardFile), "present", "MISSING"),
6590
+ line("cloud credential", has(p.configPath), "present", "MISSING"),
6591
+ line("Claude Code settings", isGuardInstalled(), "guard registered", "guard NOT registered"),
6592
+ line("Antigravity hooks", has(p.antigravityHooksPath), "present", "MISSING"),
6593
+ line("Codex hooks", isCodexGuardInstalled(), "guard registered", "guard NOT registered")
6594
+ ];
6595
+ const r = installGlobalQuiet();
6596
+ if (!r.ok) return { ok: false, message: r.message, before, after: [], notes: [] };
6597
+ const after = [
6598
+ { label: "guard hook file", ok: true, detail: `present (v${installedGuardVersion() ?? "?"})` },
6599
+ line("Claude Code settings", isGuardInstalled(), "guard registered", "NOT registered"),
6600
+ line("Antigravity hooks", has(p.antigravityHooksPath), "present", "MISSING"),
6601
+ line("Codex hooks", isCodexGuardInstalled(), "guard registered", "NOT registered")
6602
+ ];
6603
+ const notes = [];
6604
+ const cx = codexHooksStatus();
6605
+ if (cx.registered && !cx.trusted) {
6606
+ 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).");
6607
+ }
6608
+ if (cx.disabled) {
6609
+ 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.");
6610
+ }
6611
+ return { ok: true, message: "guard repaired. Open a new AI session for it to take effect.", before, after, notes };
6612
+ }
6613
+ async function runRepair() {
6614
+ const out2 = (s) => void process.stderr.write(s + "\n");
6615
+ const rep = repairQuiet();
6587
6616
  out2("");
6588
6617
  out2(" SolonGate repair");
6589
6618
  out2("");
6590
6619
  out2(" before:");
6591
- out2(` guard hook file ${has(guardFile) ? "present" : "MISSING"}`);
6592
- out2(` cloud credential ${has(p.configPath) ? "present" : "MISSING"}`);
6593
- out2(` Claude Code settings ${isGuardInstalled() ? "guard registered" : "guard NOT registered"}`);
6594
- out2(` Antigravity hooks ${has(p.antigravityHooksPath) ? "present" : "MISSING"}`);
6595
- out2(` Codex hooks ${isCodexGuardInstalled() ? "guard registered" : "guard NOT registered"}`);
6620
+ for (const l of rep.before) out2(` ${l.label.padEnd(20)} ${l.detail}`);
6596
6621
  out2("");
6597
- const r = installGlobalQuiet();
6598
- if (!r.ok) {
6599
- out2(` \u2717 ${r.message}`);
6622
+ if (!rep.ok) {
6623
+ out2(` \u2717 ${rep.message}`);
6600
6624
  return 1;
6601
6625
  }
6602
6626
  out2(" restored:");
6603
- out2(` guard hook file present (v${installedGuardVersion() ?? "?"})`);
6604
- out2(` Claude Code settings ${isGuardInstalled() ? "guard registered" : "NOT registered"}`);
6605
- out2(` Antigravity hooks ${has(p.antigravityHooksPath) ? "present" : "MISSING"}`);
6606
- out2(` Codex hooks ${isCodexGuardInstalled() ? "guard registered" : "NOT registered"}`);
6627
+ for (const l of rep.after) out2(` ${l.label.padEnd(20)} ${l.detail}`);
6607
6628
  out2("");
6608
- const cx = codexHooksStatus();
6609
- if (cx.registered && !cx.trusted) {
6610
- out2(" Codex only: run `/hooks` inside Codex once and trust the SolonGate hooks");
6611
- out2(" (Codex skips any hook it has not been told to trust).");
6629
+ for (const n of rep.notes) {
6630
+ out2(` ${n}`);
6612
6631
  out2("");
6613
6632
  }
6614
- if (cx.disabled) {
6615
- out2(" Codex only: hooks are turned OFF in ~/.codex/config.toml ([features] hooks = false)");
6616
- out2(" \u2014 the guard cannot run there until that line is removed.");
6617
- out2("");
6618
- }
6619
- out2(" \u2713 guard repaired. Open a new AI session for it to take effect.");
6633
+ out2(` \u2713 ${rep.message}`);
6620
6634
  out2("");
6621
6635
  return 0;
6622
6636
  }
@@ -11095,6 +11109,220 @@ var init_Audit = __esm({
11095
11109
  }
11096
11110
  });
11097
11111
 
11112
+ // src/commands/format.ts
11113
+ function printJson(value) {
11114
+ out(JSON.stringify(value, null, 2));
11115
+ }
11116
+ function usage(title, tagline, rows, footer = "Add --json for machine-readable output.") {
11117
+ const W = 40;
11118
+ const lines = ["", ` ${c.bold}${c.blue4}${title}${c.reset} ${c.dim}${tagline}${c.reset}`, ""];
11119
+ for (const [syntax, desc] of rows) {
11120
+ if (!syntax) {
11121
+ lines.push("");
11122
+ continue;
11123
+ }
11124
+ if (desc === void 0) {
11125
+ lines.push(` ${c.cyan}${syntax}${c.reset}`);
11126
+ continue;
11127
+ }
11128
+ if (syntax.length <= W) {
11129
+ lines.push(` ${c.cyan}${syntax}${c.reset}${" ".repeat(W - syntax.length)}${c.dim}${desc}${c.reset}`);
11130
+ } else {
11131
+ lines.push(` ${c.cyan}${syntax}${c.reset}`);
11132
+ lines.push(` ${" ".repeat(W)}${c.dim}${desc}${c.reset}`);
11133
+ }
11134
+ }
11135
+ if (footer) lines.push("", ` ${c.dim}${footer}${c.reset}`);
11136
+ return lines.join("\n");
11137
+ }
11138
+ function decisionColor2(decision) {
11139
+ const d = decision.toUpperCase();
11140
+ if (d === "ALLOW") return green(d);
11141
+ if (d === "DENY" || d === "DENIED") return red(d);
11142
+ return dim(d);
11143
+ }
11144
+ function table(headers, rows) {
11145
+ const cols = headers.length;
11146
+ const w = new Array(cols).fill(0);
11147
+ for (let i = 0; i < cols; i++) w[i] = width(headers[i] ?? "");
11148
+ for (const row of rows) {
11149
+ for (let i = 0; i < cols; i++) w[i] = Math.max(w[i], width(row[i] ?? ""));
11150
+ }
11151
+ const pad = (s, i) => s + " ".repeat(Math.max(0, w[i] - width(s)));
11152
+ err(" " + headers.map((h, i) => dim(pad(h, i))).join(" "));
11153
+ for (const row of rows) {
11154
+ err(" " + row.map((cell, i) => pad(cell ?? "", i)).join(" "));
11155
+ }
11156
+ }
11157
+ function truncate3(s, n) {
11158
+ if (s.length <= n) return s;
11159
+ return s.slice(0, Math.max(0, n - 1)) + "\u2026";
11160
+ }
11161
+ function sparkline(values) {
11162
+ if (values.length === 0) return "";
11163
+ const max = Math.max(...values, 0);
11164
+ if (max === 0) return BLOCKS[0].repeat(values.length);
11165
+ return values.map((v) => BLOCKS[Math.min(BLOCKS.length - 1, Math.round(v / max * (BLOCKS.length - 1)))]).join("");
11166
+ }
11167
+ var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS;
11168
+ var init_format = __esm({
11169
+ "src/commands/format.ts"() {
11170
+ "use strict";
11171
+ init_cli_utils();
11172
+ out = (s = "") => void process.stdout.write(s + "\n");
11173
+ err = (s = "") => void process.stderr.write(s + "\n");
11174
+ dim = (s) => `${c.dim}${s}${c.reset}`;
11175
+ bold = (s) => `${c.bold}${s}${c.reset}`;
11176
+ green = (s) => `${c.green}${s}${c.reset}`;
11177
+ red = (s) => `${c.red}${s}${c.reset}`;
11178
+ yellow = (s) => `${c.yellow}${s}${c.reset}`;
11179
+ cyan = (s) => `${c.cyan}${s}${c.reset}`;
11180
+ ANSI = /\x1b\[[0-9;]*m/g;
11181
+ width = (s) => s.replace(ANSI, "").length;
11182
+ BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
11183
+ }
11184
+ });
11185
+
11186
+ // src/commands/args.ts
11187
+ function parse(argv) {
11188
+ const positionals = [];
11189
+ const flags = {};
11190
+ for (let i = 0; i < argv.length; i++) {
11191
+ const tok = argv[i];
11192
+ if (tok.startsWith("--")) {
11193
+ const body = tok.slice(2);
11194
+ const eq = body.indexOf("=");
11195
+ if (eq !== -1) {
11196
+ flags[body.slice(0, eq)] = body.slice(eq + 1);
11197
+ continue;
11198
+ }
11199
+ const next = argv[i + 1];
11200
+ if (next !== void 0 && !next.startsWith("--")) {
11201
+ flags[body] = next;
11202
+ i++;
11203
+ } else {
11204
+ flags[body] = true;
11205
+ }
11206
+ } else {
11207
+ positionals.push(tok);
11208
+ }
11209
+ }
11210
+ return { positionals, flags };
11211
+ }
11212
+ function flagStr(flags, name) {
11213
+ const v = flags[name];
11214
+ return typeof v === "string" ? v : void 0;
11215
+ }
11216
+ function flagNum(flags, name) {
11217
+ const v = flagStr(flags, name);
11218
+ if (v === void 0) return void 0;
11219
+ const n = Number(v);
11220
+ return Number.isFinite(n) ? n : void 0;
11221
+ }
11222
+ function flagBool(flags, name) {
11223
+ return flags[name] === true || flags[name] === "true";
11224
+ }
11225
+ var init_args = __esm({
11226
+ "src/commands/args.ts"() {
11227
+ "use strict";
11228
+ }
11229
+ });
11230
+
11231
+ // src/commands/doctor.ts
11232
+ import { existsSync as existsSync6, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
11233
+ import { homedir as homedir10 } from "os";
11234
+ import { join as join12 } from "path";
11235
+ async function collectChecks() {
11236
+ const checks = [];
11237
+ if (!isAuthenticated()) {
11238
+ checks.push({ name: "login", ok: false, detail: "not logged in - run `solongate`, add your account in the Accounts panel" });
11239
+ } else {
11240
+ const { apiUrl } = resolveCredentials();
11241
+ checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
11242
+ try {
11243
+ const active2 = await api.policies.active();
11244
+ if (active2.policy) {
11245
+ 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}` });
11246
+ } else {
11247
+ checks.push({ name: "active policy", ok: "warn", detail: "no policy resolves - every call falls back to default" });
11248
+ }
11249
+ const sec = active2.security;
11250
+ checks.push({ name: "rate limit", ok: sec?.rateLimit ? true : "warn", detail: sec?.rateLimit ? `${sec.rateLimit.perMinute}/min` : "off" });
11251
+ checks.push({ name: "dlp", ok: sec?.dlpBlock ? true : "warn", detail: sec?.dlpBlock ? `block \xB7 ${sec.dlpBlock.patterns.length} patterns` : sec?.dlpRedact ? "redact" : "off" });
11252
+ checks.push({ name: "self-protection", ok: active2.self_protection_enabled ? true : "warn", detail: active2.self_protection_enabled ? "on" : "off" });
11253
+ } catch (e) {
11254
+ checks.push({ name: "api", ok: false, detail: "unreachable: " + (e instanceof Error ? e.message : String(e)) });
11255
+ }
11256
+ try {
11257
+ const g = await api.settings.getGuardStatus();
11258
+ 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` });
11259
+ } catch {
11260
+ }
11261
+ }
11262
+ if (codexDetected()) {
11263
+ const cx = codexHooksStatus();
11264
+ if (!cx.registered) {
11265
+ checks.push({ name: "codex hooks", ok: false, detail: "guard not registered - run `solongate repair`" });
11266
+ } else if (cx.disabled) {
11267
+ checks.push({ name: "codex hooks", ok: false, detail: "hooks disabled in ~/.codex/config.toml ([features] hooks = false)" });
11268
+ } else if (!cx.trusted) {
11269
+ checks.push({ name: "codex hooks", ok: "warn", detail: "registered - run `/hooks` in Codex once and trust them (Codex skips untrusted hooks)" });
11270
+ } else {
11271
+ checks.push({ name: "codex hooks", ok: true, detail: "registered + trusted" });
11272
+ }
11273
+ }
11274
+ try {
11275
+ const raw = readFileSync10(join12(homedir10(), ".solongate", ".key-rejected.json"), "utf-8");
11276
+ const m = JSON.parse(raw);
11277
+ const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
11278
+ checks.push({
11279
+ name: "hook credential",
11280
+ ok: false,
11281
+ 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`
11282
+ });
11283
+ } catch {
11284
+ }
11285
+ const LOCAL_LOG2 = localLogFile();
11286
+ if (existsSync6(LOCAL_LOG2)) {
11287
+ const st = statSync2(LOCAL_LOG2);
11288
+ const ageMin = (Date.now() - st.mtimeMs) / 6e4;
11289
+ 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"}` });
11290
+ } else {
11291
+ checks.push({ name: "local logs", ok: "warn", detail: "off (logs go to cloud) - enable in dashboard \u2192 Settings" });
11292
+ }
11293
+ return checks;
11294
+ }
11295
+ async function run(argv) {
11296
+ const { flags } = parse(argv);
11297
+ const json = flagBool(flags, "json");
11298
+ const checks = await collectChecks();
11299
+ if (json) return printJson(checks), checks.some((c2) => c2.ok === false) ? 1 : 0;
11300
+ err("");
11301
+ err(` ${bold("SolonGate doctor")}`);
11302
+ err("");
11303
+ for (const c2 of checks) {
11304
+ const mark = c2.ok === true ? green("\u2713") : c2.ok === "warn" ? yellow("!") : red("\u2717");
11305
+ err(` ${mark} ${c2.name.padEnd(16)} ${dim(c2.detail)}`);
11306
+ }
11307
+ err("");
11308
+ const bad = checks.filter((c2) => c2.ok === false).length;
11309
+ const warn = checks.filter((c2) => c2.ok === "warn").length;
11310
+ if (bad) err(` ${red(`${bad} problem(s)`)}${warn ? dim(` \xB7 ${warn} warning(s)`) : ""}`);
11311
+ else if (warn) err(` ${yellow(`${warn} warning(s)`)} ${dim("- guard is working")}`);
11312
+ else err(` ${green("all good")}`);
11313
+ return bad ? 1 : 0;
11314
+ }
11315
+ var init_doctor = __esm({
11316
+ "src/commands/doctor.ts"() {
11317
+ "use strict";
11318
+ init_api_client();
11319
+ init_global_install();
11320
+ init_local_log();
11321
+ init_format();
11322
+ init_args();
11323
+ }
11324
+ });
11325
+
11098
11326
  // src/tui/panels/Settings.tsx
11099
11327
  import { Box as Box8, Text as Text8, useInput as useInput7 } from "ink";
11100
11328
  import TextInput6 from "ink-text-input";
@@ -11117,6 +11345,24 @@ function SettingsPanel({
11117
11345
  const [busy, setBusy] = useState8(false);
11118
11346
  const [editor, setEditor] = useState8(null);
11119
11347
  const [latest, setLatest] = useState8(null);
11348
+ const [diagBusy, setDiagBusy] = useState8(null);
11349
+ const [diag, setDiag] = useState8(null);
11350
+ const [diagStep, setDiagStep] = useState8(0);
11351
+ const DIAG_STEPS = {
11352
+ doctor: ["login", "active policy", "guard hook", "agent hooks", "local logs"],
11353
+ repair: ["inspecting protection files", "rewriting hook files", "registering with agents", "re-locking"]
11354
+ };
11355
+ const settle = async (started, of) => {
11356
+ const min = DIAG_STEPS[of].length * 220 + 160;
11357
+ const left = min - (Date.now() - started);
11358
+ if (left > 0) await new Promise((res) => setTimeout(res, left));
11359
+ };
11360
+ useEffect7(() => {
11361
+ if (!diagBusy) return;
11362
+ const total = DIAG_STEPS[diagBusy].length;
11363
+ const step = setInterval(() => setDiagStep((s) => Math.min(s + 1, total - 1)), 220);
11364
+ return () => clearInterval(step);
11365
+ }, [diagBusy]);
11120
11366
  const ver = currentVersion();
11121
11367
  useEffect7(() => {
11122
11368
  let live2 = true;
@@ -11155,10 +11401,11 @@ function SettingsPanel({
11155
11401
  return () => clearInterval(t);
11156
11402
  }, []);
11157
11403
  useEffect7(() => {
11158
- if (!login || login.phase === "done" || login.phase === "error") return;
11404
+ const loggingIn = login && login.phase !== "done" && login.phase !== "error";
11405
+ if (!loggingIn && !diagBusy) return;
11159
11406
  const t = setInterval(() => setTick((n) => n + 1), 120);
11160
11407
  return () => clearInterval(t);
11161
- }, [login]);
11408
+ }, [login, diagBusy]);
11162
11409
  const localQ = useLoader(() => listAccounts().length ? api.settings.getLocalLogs() : Promise.resolve(null));
11163
11410
  const whQ = useLoader(() => listAccounts().length ? api.settings.getWebhooks() : Promise.resolve(null));
11164
11411
  const alertQ = useLoader(() => listAccounts().length ? api.settings.getAlerts() : Promise.resolve(null));
@@ -11190,6 +11437,8 @@ function SettingsPanel({
11190
11437
  ...locked ? [] : [
11191
11438
  { kind: "guard" },
11192
11439
  { kind: "self" },
11440
+ { kind: "doctor" },
11441
+ { kind: "repair" },
11193
11442
  { kind: "ll-enabled" },
11194
11443
  { kind: "ll-path" },
11195
11444
  { kind: "ll-server" },
@@ -11308,6 +11557,64 @@ function SettingsPanel({
11308
11557
  setMsg({ text: (res.ok ? "\u2713 " : "\u2717 ") + res.message, level: res.ok ? "ok" : "bad" });
11309
11558
  refreshGuard();
11310
11559
  guardQ.reload();
11560
+ } else if (r.kind === "doctor") {
11561
+ if (diagBusy) return;
11562
+ setDiagBusy("doctor");
11563
+ setDiagStep(0);
11564
+ setDiag(null);
11565
+ void (async () => {
11566
+ const started = Date.now();
11567
+ try {
11568
+ const checks = await collectChecks();
11569
+ await settle(started, "doctor");
11570
+ const bad = checks.filter((c2) => c2.ok === false).length;
11571
+ const warn = checks.filter((c2) => c2.ok === "warn").length;
11572
+ setDiag({
11573
+ of: "doctor",
11574
+ lines: checks.map((c2) => ({
11575
+ text: `${c2.ok === true ? "\u2713" : c2.ok === "warn" ? "!" : "\u2717"} ${c2.name.padEnd(16)} ${c2.detail}`,
11576
+ level: c2.ok === true ? "ok" : c2.ok === "warn" ? "warn" : "bad"
11577
+ }))
11578
+ });
11579
+ setMsg(
11580
+ 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" }
11581
+ );
11582
+ } catch (e) {
11583
+ setMsg({ text: "\u2717 doctor failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" });
11584
+ } finally {
11585
+ setDiagBusy(null);
11586
+ }
11587
+ })();
11588
+ } else if (r.kind === "repair") {
11589
+ if (diagBusy) return;
11590
+ setDiagBusy("repair");
11591
+ setDiagStep(0);
11592
+ setDiag(null);
11593
+ void (async () => {
11594
+ try {
11595
+ const started = Date.now();
11596
+ await new Promise((res) => setTimeout(res, 60));
11597
+ const rep = repairQuiet();
11598
+ await settle(started, "repair");
11599
+ setDiag({
11600
+ of: "repair",
11601
+ lines: [
11602
+ ...(rep.ok ? rep.after : rep.before).map((l) => ({
11603
+ text: `${l.ok ? "\u2713" : "\u2717"} ${l.label.padEnd(20)} ${l.detail}`,
11604
+ level: l.ok ? "ok" : "bad"
11605
+ })),
11606
+ ...rep.notes.map((n) => ({ text: n, level: "warn" }))
11607
+ ]
11608
+ });
11609
+ setMsg({ text: (rep.ok ? "\u2713 " : "\u2717 ") + rep.message, level: rep.ok ? "ok" : "bad" });
11610
+ refreshGuard();
11611
+ guardQ.reload();
11612
+ } catch (e) {
11613
+ setMsg({ text: "\u2717 repair failed: " + (e instanceof Error ? e.message : String(e)), level: "bad" });
11614
+ } finally {
11615
+ setDiagBusy(null);
11616
+ }
11617
+ })();
11311
11618
  } else if (r.kind === "self") {
11312
11619
  if (!selfProt) return;
11313
11620
  run10(selfProt.enabled ? "self-protection disabled" : "self-protection enabled", () => api.settings.setSelfProtection(!selfProt.enabled), selfQ.reload);
@@ -11597,6 +11904,18 @@ function SettingsPanel({
11597
11904
  selfProt ? onOff(selfProt.enabled) : /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "\u2026" }),
11598
11905
  /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: " blocks agents editing SolonGate\u2019s own hooks/config \xB7 enter toggles" })
11599
11906
  ] });
11907
+ case "doctor":
11908
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
11909
+ cursor(r),
11910
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "doctor".padEnd(11) }),
11911
+ 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" })
11912
+ ] });
11913
+ case "repair":
11914
+ return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
11915
+ cursor(r),
11916
+ /* @__PURE__ */ jsx8(Text8, { color: theme.dim, children: "repair".padEnd(11) }),
11917
+ 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" })
11918
+ ] });
11600
11919
  case "ll-enabled":
11601
11920
  return /* @__PURE__ */ jsxs8(Text8, { wrap: "truncate", children: [
11602
11921
  cursor(r),
@@ -11649,10 +11968,10 @@ function SettingsPanel({
11649
11968
  ] });
11650
11969
  }
11651
11970
  };
11652
- 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";
11971
+ 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";
11653
11972
  const SECTION_DESC = {
11654
11973
  ACCOUNTS: `on this device (${accounts.length}) \xB7 \u25CF viewing \xB7 ACTIVE = guard key \xB7 x removes`,
11655
- PROTECTION: "guard hook: enter install/update (writes the files) \xB7 d remove \xB7 + self-protection",
11974
+ PROTECTION: "guard hook: enter install/update \xB7 d remove \xB7 self-protection \xB7 doctor + repair",
11656
11975
  "LOCAL LOGS": "mirror every decision to a file + dashboard link",
11657
11976
  WEBHOOKS: `POST events to a URL (${webhooks.length}) \xB7 t tests`,
11658
11977
  ALERTS: `one email + one telegram (${alerts.length}) \xB7 enter edits \xB7 m on/off`
@@ -11684,6 +12003,31 @@ function SettingsPanel({
11684
12003
  }
11685
12004
  lineEls.push(/* @__PURE__ */ jsx8(Box8, { children: rowLine(r) }, keyOf(r)));
11686
12005
  lineKey.push(keyOf(r));
12006
+ if ((r.kind === "doctor" || r.kind === "repair") && diagBusy === r.kind) {
12007
+ DIAG_STEPS[r.kind].forEach((label, i) => {
12008
+ const done = i < diagStep;
12009
+ lineEls.push(
12010
+ /* @__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}`)
12011
+ );
12012
+ lineKey.push("");
12013
+ });
12014
+ }
12015
+ if (diag && (r.kind === "doctor" && diag.of === "doctor" || r.kind === "repair" && diag.of === "repair")) {
12016
+ diag.lines.forEach((l, i) => {
12017
+ lineEls.push(
12018
+ /* @__PURE__ */ jsx8(
12019
+ Text8,
12020
+ {
12021
+ wrap: "truncate",
12022
+ color: l.level === "ok" ? theme.ok : l.level === "warn" ? theme.warn : l.level === "bad" ? theme.bad : theme.dim,
12023
+ children: " " + l.text
12024
+ },
12025
+ `diag:${diag.of}:${i}`
12026
+ )
12027
+ );
12028
+ lineKey.push("");
12029
+ });
12030
+ }
11687
12031
  });
11688
12032
  if (hasEmailAlert && hasTgAlert) {
11689
12033
  lineEls.push(
@@ -11725,6 +12069,7 @@ var init_Settings = __esm({
11725
12069
  init_client();
11726
12070
  init_device_login();
11727
12071
  init_global_install();
12072
+ init_doctor();
11728
12073
  init_logs_server_daemon();
11729
12074
  init_self_update();
11730
12075
  init_components();
@@ -11968,8 +12313,8 @@ __export(tui_exports, {
11968
12313
  launchTui: () => launchTui
11969
12314
  });
11970
12315
  import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync9 } from "fs";
11971
- import { homedir as homedir10 } from "os";
11972
- import { join as join12 } from "path";
12316
+ import { homedir as homedir11 } from "os";
12317
+ import { join as join13 } from "path";
11973
12318
  import { render } from "ink";
11974
12319
  import { jsx as jsx10 } from "react/jsx-runtime";
11975
12320
  async function launchTui() {
@@ -11980,11 +12325,11 @@ async function launchTui() {
11980
12325
  return;
11981
12326
  }
11982
12327
  process.stdout.write("\x1B[?1049h\x1B[H");
11983
- const debugLog = join12(homedir10(), ".solongate", "dataroom-debug.log");
12328
+ const debugLog = join13(homedir11(), ".solongate", "dataroom-debug.log");
11984
12329
  const saved = { log: console.log, warn: console.warn, error: console.error, info: console.info, debug: console.debug };
11985
12330
  const toFile = (level) => (...args) => {
11986
12331
  try {
11987
- mkdirSync9(join12(homedir10(), ".solongate"), { recursive: true });
12332
+ mkdirSync9(join13(homedir11(), ".solongate"), { recursive: true });
11988
12333
  appendFileSync2(debugLog, `${(/* @__PURE__ */ new Date()).toISOString()} [${level}] ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
11989
12334
  `);
11990
12335
  } catch {
@@ -12010,128 +12355,9 @@ var init_tui = __esm({
12010
12355
  }
12011
12356
  });
12012
12357
 
12013
- // src/commands/format.ts
12014
- function printJson(value) {
12015
- out(JSON.stringify(value, null, 2));
12016
- }
12017
- function usage(title, tagline, rows, footer = "Add --json for machine-readable output.") {
12018
- const W = 40;
12019
- const lines = ["", ` ${c.bold}${c.blue4}${title}${c.reset} ${c.dim}${tagline}${c.reset}`, ""];
12020
- for (const [syntax, desc] of rows) {
12021
- if (!syntax) {
12022
- lines.push("");
12023
- continue;
12024
- }
12025
- if (desc === void 0) {
12026
- lines.push(` ${c.cyan}${syntax}${c.reset}`);
12027
- continue;
12028
- }
12029
- if (syntax.length <= W) {
12030
- lines.push(` ${c.cyan}${syntax}${c.reset}${" ".repeat(W - syntax.length)}${c.dim}${desc}${c.reset}`);
12031
- } else {
12032
- lines.push(` ${c.cyan}${syntax}${c.reset}`);
12033
- lines.push(` ${" ".repeat(W)}${c.dim}${desc}${c.reset}`);
12034
- }
12035
- }
12036
- if (footer) lines.push("", ` ${c.dim}${footer}${c.reset}`);
12037
- return lines.join("\n");
12038
- }
12039
- function decisionColor2(decision) {
12040
- const d = decision.toUpperCase();
12041
- if (d === "ALLOW") return green(d);
12042
- if (d === "DENY" || d === "DENIED") return red(d);
12043
- return dim(d);
12044
- }
12045
- function table(headers, rows) {
12046
- const cols = headers.length;
12047
- const w = new Array(cols).fill(0);
12048
- for (let i = 0; i < cols; i++) w[i] = width(headers[i] ?? "");
12049
- for (const row of rows) {
12050
- for (let i = 0; i < cols; i++) w[i] = Math.max(w[i], width(row[i] ?? ""));
12051
- }
12052
- const pad = (s, i) => s + " ".repeat(Math.max(0, w[i] - width(s)));
12053
- err(" " + headers.map((h, i) => dim(pad(h, i))).join(" "));
12054
- for (const row of rows) {
12055
- err(" " + row.map((cell, i) => pad(cell ?? "", i)).join(" "));
12056
- }
12057
- }
12058
- function truncate3(s, n) {
12059
- if (s.length <= n) return s;
12060
- return s.slice(0, Math.max(0, n - 1)) + "\u2026";
12061
- }
12062
- function sparkline(values) {
12063
- if (values.length === 0) return "";
12064
- const max = Math.max(...values, 0);
12065
- if (max === 0) return BLOCKS[0].repeat(values.length);
12066
- return values.map((v) => BLOCKS[Math.min(BLOCKS.length - 1, Math.round(v / max * (BLOCKS.length - 1)))]).join("");
12067
- }
12068
- var out, err, dim, bold, green, red, yellow, cyan, ANSI, width, BLOCKS;
12069
- var init_format = __esm({
12070
- "src/commands/format.ts"() {
12071
- "use strict";
12072
- init_cli_utils();
12073
- out = (s = "") => void process.stdout.write(s + "\n");
12074
- err = (s = "") => void process.stderr.write(s + "\n");
12075
- dim = (s) => `${c.dim}${s}${c.reset}`;
12076
- bold = (s) => `${c.bold}${s}${c.reset}`;
12077
- green = (s) => `${c.green}${s}${c.reset}`;
12078
- red = (s) => `${c.red}${s}${c.reset}`;
12079
- yellow = (s) => `${c.yellow}${s}${c.reset}`;
12080
- cyan = (s) => `${c.cyan}${s}${c.reset}`;
12081
- ANSI = /\x1b\[[0-9;]*m/g;
12082
- width = (s) => s.replace(ANSI, "").length;
12083
- BLOCKS = ["\u2581", "\u2582", "\u2583", "\u2584", "\u2585", "\u2586", "\u2587", "\u2588"];
12084
- }
12085
- });
12086
-
12087
- // src/commands/args.ts
12088
- function parse(argv) {
12089
- const positionals = [];
12090
- const flags = {};
12091
- for (let i = 0; i < argv.length; i++) {
12092
- const tok = argv[i];
12093
- if (tok.startsWith("--")) {
12094
- const body = tok.slice(2);
12095
- const eq = body.indexOf("=");
12096
- if (eq !== -1) {
12097
- flags[body.slice(0, eq)] = body.slice(eq + 1);
12098
- continue;
12099
- }
12100
- const next = argv[i + 1];
12101
- if (next !== void 0 && !next.startsWith("--")) {
12102
- flags[body] = next;
12103
- i++;
12104
- } else {
12105
- flags[body] = true;
12106
- }
12107
- } else {
12108
- positionals.push(tok);
12109
- }
12110
- }
12111
- return { positionals, flags };
12112
- }
12113
- function flagStr(flags, name) {
12114
- const v = flags[name];
12115
- return typeof v === "string" ? v : void 0;
12116
- }
12117
- function flagNum(flags, name) {
12118
- const v = flagStr(flags, name);
12119
- if (v === void 0) return void 0;
12120
- const n = Number(v);
12121
- return Number.isFinite(n) ? n : void 0;
12122
- }
12123
- function flagBool(flags, name) {
12124
- return flags[name] === true || flags[name] === "true";
12125
- }
12126
- var init_args = __esm({
12127
- "src/commands/args.ts"() {
12128
- "use strict";
12129
- }
12130
- });
12131
-
12132
12358
  // src/commands/policy.ts
12133
- import { readFileSync as readFileSync10 } from "fs";
12134
- async function run(argv) {
12359
+ import { readFileSync as readFileSync11 } from "fs";
12360
+ async function run2(argv) {
12135
12361
  const { positionals, flags } = parse(argv);
12136
12362
  const sub = positionals[0];
12137
12363
  const json = flagBool(flags, "json");
@@ -12287,7 +12513,7 @@ function printRules(rules) {
12287
12513
  }
12288
12514
  async function resolveRules(target) {
12289
12515
  if (target.endsWith(".json")) {
12290
- const parsed = JSON.parse(readFileSync10(target, "utf-8"));
12516
+ const parsed = JSON.parse(readFileSync11(target, "utf-8"));
12291
12517
  return parsed.rules ?? [];
12292
12518
  }
12293
12519
  const p = await api.policies.get(target);
@@ -12316,7 +12542,7 @@ var init_policy = __esm({
12316
12542
  });
12317
12543
 
12318
12544
  // src/commands/ratelimit.ts
12319
- async function run2(argv) {
12545
+ async function run3(argv) {
12320
12546
  const { positionals, flags } = parse(argv);
12321
12547
  const sub = positionals[0] ?? "show";
12322
12548
  const json = flagBool(flags, "json");
@@ -12394,7 +12620,7 @@ var init_ratelimit = __esm({
12394
12620
  });
12395
12621
 
12396
12622
  // src/commands/dlp.ts
12397
- async function run3(argv) {
12623
+ async function run4(argv) {
12398
12624
  const { positionals, flags } = parse(argv);
12399
12625
  const sub = positionals[0] ?? "show";
12400
12626
  const json = flagBool(flags, "json");
@@ -12482,7 +12708,7 @@ var init_dlp = __esm({
12482
12708
  });
12483
12709
 
12484
12710
  // src/commands/stats.ts
12485
- async function run4(argv) {
12711
+ async function run5(argv) {
12486
12712
  const { positionals, flags } = parse(argv);
12487
12713
  const sub = positionals[0] ?? "overview";
12488
12714
  const json = flagBool(flags, "json");
@@ -12560,7 +12786,7 @@ var init_stats2 = __esm({
12560
12786
  });
12561
12787
 
12562
12788
  // src/commands/audit.ts
12563
- async function run5(argv) {
12789
+ async function run6(argv) {
12564
12790
  const { positionals, flags } = parse(argv);
12565
12791
  const json = flagBool(flags, "json");
12566
12792
  if (positionals[0] === "help") return err(USAGE5), 0;
@@ -12689,97 +12915,6 @@ var init_agents2 = __esm({
12689
12915
  }
12690
12916
  });
12691
12917
 
12692
- // src/commands/doctor.ts
12693
- import { existsSync as existsSync6, readFileSync as readFileSync11, statSync as statSync2 } from "fs";
12694
- import { homedir as homedir11 } from "os";
12695
- import { join as join13 } from "path";
12696
- async function run6(argv) {
12697
- const { flags } = parse(argv);
12698
- const json = flagBool(flags, "json");
12699
- const checks = [];
12700
- if (!isAuthenticated()) {
12701
- checks.push({ name: "login", ok: false, detail: "not logged in - run `solongate`, add your account in the Accounts panel" });
12702
- } else {
12703
- const { apiUrl } = resolveCredentials();
12704
- checks.push({ name: "login", ok: true, detail: `paired \xB7 ${apiUrl}` });
12705
- try {
12706
- const active2 = await api.policies.active();
12707
- if (active2.policy) {
12708
- 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}` });
12709
- } else {
12710
- checks.push({ name: "active policy", ok: "warn", detail: "no policy resolves - every call falls back to default" });
12711
- }
12712
- const sec = active2.security;
12713
- checks.push({ name: "rate limit", ok: sec?.rateLimit ? true : "warn", detail: sec?.rateLimit ? `${sec.rateLimit.perMinute}/min` : "off" });
12714
- checks.push({ name: "dlp", ok: sec?.dlpBlock ? true : "warn", detail: sec?.dlpBlock ? `block \xB7 ${sec.dlpBlock.patterns.length} patterns` : sec?.dlpRedact ? "redact" : "off" });
12715
- checks.push({ name: "self-protection", ok: active2.self_protection_enabled ? true : "warn", detail: active2.self_protection_enabled ? "on" : "off" });
12716
- } catch (e) {
12717
- checks.push({ name: "api", ok: false, detail: "unreachable: " + (e instanceof Error ? e.message : String(e)) });
12718
- }
12719
- try {
12720
- const g = await api.settings.getGuardStatus();
12721
- 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` });
12722
- } catch {
12723
- }
12724
- }
12725
- if (codexDetected()) {
12726
- const cx = codexHooksStatus();
12727
- if (!cx.registered) {
12728
- checks.push({ name: "codex hooks", ok: false, detail: "guard not registered - run `solongate repair`" });
12729
- } else if (cx.disabled) {
12730
- checks.push({ name: "codex hooks", ok: false, detail: "hooks disabled in ~/.codex/config.toml ([features] hooks = false)" });
12731
- } else if (!cx.trusted) {
12732
- checks.push({ name: "codex hooks", ok: "warn", detail: "registered - run `/hooks` in Codex once and trust them (Codex skips untrusted hooks)" });
12733
- } else {
12734
- checks.push({ name: "codex hooks", ok: true, detail: "registered + trusted" });
12735
- }
12736
- }
12737
- try {
12738
- const raw = readFileSync11(join13(homedir11(), ".solongate", ".key-rejected.json"), "utf-8");
12739
- const m = JSON.parse(raw);
12740
- const ageMin = m.ts ? Math.round((Date.now() - m.ts) / 6e4) : null;
12741
- checks.push({
12742
- name: "hook credential",
12743
- ok: false,
12744
- 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`
12745
- });
12746
- } catch {
12747
- }
12748
- const LOCAL_LOG2 = localLogFile();
12749
- if (existsSync6(LOCAL_LOG2)) {
12750
- const st = statSync2(LOCAL_LOG2);
12751
- const ageMin = (Date.now() - st.mtimeMs) / 6e4;
12752
- 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"}` });
12753
- } else {
12754
- checks.push({ name: "local logs", ok: "warn", detail: "off (logs go to cloud) - enable in dashboard \u2192 Settings" });
12755
- }
12756
- if (json) return printJson(checks), checks.some((c2) => c2.ok === false) ? 1 : 0;
12757
- err("");
12758
- err(` ${bold("SolonGate doctor")}`);
12759
- err("");
12760
- for (const c2 of checks) {
12761
- const mark = c2.ok === true ? green("\u2713") : c2.ok === "warn" ? yellow("!") : red("\u2717");
12762
- err(` ${mark} ${c2.name.padEnd(16)} ${dim(c2.detail)}`);
12763
- }
12764
- err("");
12765
- const bad = checks.filter((c2) => c2.ok === false).length;
12766
- const warn = checks.filter((c2) => c2.ok === "warn").length;
12767
- if (bad) err(` ${red(`${bad} problem(s)`)}${warn ? dim(` \xB7 ${warn} warning(s)`) : ""}`);
12768
- else if (warn) err(` ${yellow(`${warn} warning(s)`)} ${dim("- guard is working")}`);
12769
- else err(` ${green("all good")}`);
12770
- return bad ? 1 : 0;
12771
- }
12772
- var init_doctor = __esm({
12773
- "src/commands/doctor.ts"() {
12774
- "use strict";
12775
- init_api_client();
12776
- init_global_install();
12777
- init_local_log();
12778
- init_format();
12779
- init_args();
12780
- }
12781
- });
12782
-
12783
12918
  // src/commands/watch.ts
12784
12919
  import { closeSync as closeSync2, existsSync as existsSync7, openSync as openSync4, readSync as readSync2, statSync as statSync3 } from "fs";
12785
12920
  function tailLocal(file, maxBytes = 131072) {
@@ -13023,21 +13158,21 @@ __export(commands_exports, {
13023
13158
  async function dispatch(command, argv) {
13024
13159
  switch (command) {
13025
13160
  case "policy":
13026
- return run(argv);
13027
- case "ratelimit":
13028
13161
  return run2(argv);
13029
- case "dlp":
13162
+ case "ratelimit":
13030
13163
  return run3(argv);
13031
- case "stats":
13164
+ case "dlp":
13032
13165
  return run4(argv);
13033
- case "audit":
13166
+ case "stats":
13034
13167
  return run5(argv);
13168
+ case "audit":
13169
+ return run6(argv);
13035
13170
  case "sessions":
13036
13171
  return runAgents(argv);
13037
13172
  case "session":
13038
13173
  return runAgent(argv);
13039
13174
  case "doctor":
13040
- return run6(argv);
13175
+ return run(argv);
13041
13176
  case "watch":
13042
13177
  return run7(argv);
13043
13178
  case "alerts":