@lifeaitools/clauth 1.30.25 → 1.31.1

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.
Files changed (39) hide show
  1. package/.clauth-skill/SKILL.md +0 -31
  2. package/.clauth-skill/references/keys-guide.md +270 -270
  3. package/.clauth-skill/references/operator-guide.md +0 -27
  4. package/README.md +2 -48
  5. package/cli/api.js +238 -238
  6. package/cli/commands/agent-pool.js +51 -15
  7. package/cli/commands/install.js +396 -396
  8. package/cli/commands/login.js +135 -0
  9. package/cli/commands/login.test.js +73 -0
  10. package/cli/commands/serve.js +9 -815
  11. package/cli/commands/uninstall.js +164 -164
  12. package/cli/commands/watchdog.js +1 -1
  13. package/cli/index.js +29 -20
  14. package/cli/supervisor-registry.js +1 -6
  15. package/cli/watchdog-registry.js +2 -30
  16. package/cli/watchdog-registry.test.js +5 -28
  17. package/cli/webdav-service.js +339 -339
  18. package/install.ps1 +102 -102
  19. package/install.sh +49 -49
  20. package/package.json +4 -6
  21. package/scripts/bin/bootstrap-linux +0 -0
  22. package/scripts/bin/bootstrap-macos +0 -0
  23. package/scripts/bin/bootstrap-win.exe +0 -0
  24. package/scripts/bootstrap.cjs +121 -121
  25. package/scripts/build.mjs +66 -0
  26. package/scripts/build.sh +5 -45
  27. package/scripts/postinstall.js +189 -189
  28. package/supabase/functions/auth-vault/index.ts +350 -350
  29. package/supabase/migrations/001_clauth_schema.sql +94 -94
  30. package/supabase/migrations/002_vault_helpers.sql +90 -90
  31. package/supabase/migrations/20260317_lockout.sql +26 -26
  32. package/cli/commands/ops-install.js +0 -211
  33. package/cli/commands/ops.js +0 -69
  34. package/cli/ops/coolify-adapter.js +0 -80
  35. package/cli/ops/deployment-adapter.js +0 -63
  36. package/cli/ops/job-store.js +0 -116
  37. package/cli/ops/operation-policy.js +0 -51
  38. package/cli/ops/pm2-adapter.js +0 -128
  39. package/cli/ops/serialized-executor.js +0 -9
@@ -14,7 +14,7 @@ import { getMachineHash, deriveToken, deriveSeedHash } from "../fingerprint.js";
14
14
  import * as api from "../api.js";
15
15
  import chalk from "chalk";
16
16
  import ora from "ora";
17
- import { execFileSync, execSync as execSyncTop, spawn } from "child_process";
17
+ import { execFileSync, execSync as execSyncTop } from "child_process";
18
18
  import Conf from "conf";
19
19
  import { getConfOptions } from "../conf-path.js";
20
20
  import { appendFile, readdir, readFile, writeFile, rm, mkdir, stat, rename, cp } from "node:fs/promises";
@@ -30,24 +30,6 @@ import {
30
30
  readWatchdogEvents,
31
31
  restartWatchdogService,
32
32
  } from "../watchdog-registry.js";
33
- import {
34
- discoverPlugins,
35
- getSupervisorDir,
36
- getSupervisorPort,
37
- loadSupervisorState,
38
- listPlugins,
39
- listRoutes,
40
- listSurfaces,
41
- listTunnels,
42
- readSupervisorEvents,
43
- addTunnelRoute,
44
- removeTunnelRoute,
45
- reconcileSurfaceHealth,
46
- runPluginAction,
47
- runSurfaceAction,
48
- setPluginEnabled,
49
- supervisorHealth,
50
- } from "../supervisor-registry.js";
51
33
  import {
52
34
  AgentPool,
53
35
  DelegationLane,
@@ -58,14 +40,6 @@ import {
58
40
  DEFAULT_BOOTSTRAP,
59
41
  } from "./agent-pool.js";
60
42
  import { AgentCron, cronEnabled, nextRun } from "./agent-cron.js";
61
- import pm2 from "pm2";
62
- import { createPm2Adapter, PM2_OPERATION_CATALOG } from "../ops/pm2-adapter.js";
63
- import { createOperationPolicy } from "../ops/operation-policy.js";
64
- import { createJobStore } from "../ops/job-store.js";
65
- import { createCoolifyAdapter, deploymentUuidFrom } from "../ops/coolify-adapter.js";
66
- import { createDeploymentAdapter, parseDeploymentRegistry } from "../ops/deployment-adapter.js";
67
- import { createSerializedExecutor } from "../ops/serialized-executor.js";
68
- import { requestOps } from "./ops.js";
69
43
 
70
44
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
71
45
  const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
@@ -489,7 +463,6 @@ function createRotationEngine(password, machineHash, logFile) {
489
463
 
490
464
  const PID_FILE = path.join(os.tmpdir(), "clauth-serve.pid");
491
465
  const STAGED_PID_FILE = path.join(os.tmpdir(), "clauth-serve-staged.pid");
492
- const SUPERVISOR_PID_FILE = path.join(os.tmpdir(), "clauth-supervisor.pid");
493
466
  const LOG_FILE = path.join(os.tmpdir(), "clauth-serve.log");
494
467
  const LIVE_PORT = 52437;
495
468
  const STAGED_PORT = 52438;
@@ -514,74 +487,6 @@ function validateWriteToken(req, writeSession) {
514
487
  return a.length === b.length && crypto.timingSafeEqual(a, b);
515
488
  }
516
489
 
517
- export function isLoopbackAddress(remote) {
518
- return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
519
- }
520
-
521
- function summarizeSupervisorTarget(target) {
522
- if (!target || typeof target !== "object") return null;
523
- return {
524
- plugin_id: typeof target.plugin_id === "string" ? target.plugin_id : undefined,
525
- surface_id: typeof target.surface_id === "string" ? target.surface_id : undefined,
526
- tunnel_id: typeof target.tunnel_id === "string" ? target.tunnel_id : undefined,
527
- route_id: typeof target.route_id === "string" ? target.route_id : undefined,
528
- };
529
- }
530
-
531
- function summarizeSupervisorState(value) {
532
- if (!value || typeof value !== "object") return null;
533
- return {
534
- ok: typeof value.ok === "boolean" ? value.ok : undefined,
535
- state: typeof value.state === "string" ? value.state : undefined,
536
- reason: typeof value.reason === "string" ? value.reason : undefined,
537
- status: typeof value.status === "number" ? value.status : undefined,
538
- private: typeof value.private === "boolean" ? value.private : undefined,
539
- public_route: typeof value.public_route === "boolean" ? value.public_route : undefined,
540
- evidence: Array.isArray(value.evidence) ? value.evidence.map((item) => String(item).slice(0, 200)).slice(0, 5) : [],
541
- };
542
- }
543
-
544
- export function supervisorLogDto(event) {
545
- if (!event || typeof event !== "object") return { kind: "unknown" };
546
- if (event.kind === "operation") {
547
- return {
548
- ts: event.ts || event.created_at,
549
- kind: "operation",
550
- operationId: event.operationId,
551
- actor: event.actor,
552
- action: event.action,
553
- target: summarizeSupervisorTarget(event.target),
554
- resulting_state: summarizeSupervisorState(event.resulting_state),
555
- completed_at: event.completed_at,
556
- };
557
- }
558
- return {
559
- ts: event.ts,
560
- kind: event.kind,
561
- plugin_id: event.plugin_id,
562
- source: event.source,
563
- state: event.state,
564
- };
565
- }
566
-
567
- function supervisorOperationDto(receipt) {
568
- return {
569
- operationId: receipt.operationId,
570
- actor: receipt.actor,
571
- action: receipt.action,
572
- target: summarizeSupervisorTarget(receipt.target),
573
- resulting_state: summarizeSupervisorState(receipt.resulting_state),
574
- evidence: Array.isArray(receipt.evidence) ? receipt.evidence.map((item) => String(item).slice(0, 200)).slice(0, 5) : [],
575
- created_at: receipt.created_at,
576
- completed_at: receipt.completed_at,
577
- };
578
- }
579
-
580
- export function supervisorRequiresWriteToken(port = getSupervisorPort(), env = process.env) {
581
- if (port !== getSupervisorPort()) return true;
582
- return env.CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN === "1";
583
- }
584
-
585
490
  // ── PID helpers ──────────────────────────────────────────────
586
491
  function readPid() {
587
492
  try {
@@ -607,18 +512,6 @@ function writeStagedPid(pid, port) {
607
512
  fs.writeFileSync(STAGED_PID_FILE, `${pid}:${port}`, "utf8");
608
513
  }
609
514
 
610
- function readSupervisorPid() {
611
- try {
612
- const raw = fs.readFileSync(SUPERVISOR_PID_FILE, "utf8").trim();
613
- const [pid, port] = raw.split(":");
614
- return { pid: parseInt(pid, 10), port: parseInt(port, 10) };
615
- } catch { return null; }
616
- }
617
-
618
- function writeSupervisorPid(pid, port) {
619
- fs.writeFileSync(SUPERVISOR_PID_FILE, `${pid}:${port}`, "utf8");
620
- }
621
-
622
515
  function removeStagedPid() {
623
516
  try { fs.unlinkSync(STAGED_PID_FILE); } catch {}
624
517
  }
@@ -627,10 +520,6 @@ function removePid() {
627
520
  try { fs.unlinkSync(PID_FILE); } catch {}
628
521
  }
629
522
 
630
- function removeSupervisorPid() {
631
- try { fs.unlinkSync(SUPERVISOR_PID_FILE); } catch {}
632
- }
633
-
634
523
  function isProcessAlive(pid) {
635
524
  try { process.kill(pid, 0); return true; } catch { return false; }
636
525
  }
@@ -916,30 +805,6 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
916
805
  .wiz-test-result{padding:12px 14px;border-radius:8px;font-size:.85rem;margin-top:10px;display:none}
917
806
  .wiz-test-result.ok{background:rgba(74,222,128,.08);border:1px solid rgba(74,222,128,.2);color:#4ade80}
918
807
  .wiz-test-result.fail{background:rgba(248,113,113,.08);border:1px solid rgba(248,113,113,.2);color:#f87171}
919
- .supervisor-panel{margin:14px 16px;border:1px solid #233249;background:linear-gradient(135deg,rgba(15,23,42,.96),rgba(10,15,26,.98));border-radius:12px;padding:14px}
920
- .supervisor-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}
921
- .supervisor-title{font-size:.95rem;font-weight:700;color:#e2e8f0}
922
- .supervisor-sub{font-size:.76rem;color:#94a3b8;margin-top:3px;line-height:1.35}
923
- .supervisor-actions{display:flex;gap:8px;flex-wrap:wrap}
924
- .supervisor-action{background:#0f172a;color:#cbd5e1;border:1px solid #334155;border-radius:6px;padding:6px 10px;font-size:.75rem;cursor:pointer}
925
- .supervisor-action:hover{border-color:#38bdf8;color:#e0f2fe}
926
- .supervisor-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}
927
- .supervisor-card{border:1px solid #1e293b;background:rgba(2,6,23,.52);border-radius:10px;padding:10px;min-width:0}
928
- .supervisor-card h4{margin:0 0 8px;color:#cbd5e1;font-size:.75rem;letter-spacing:.08em;text-transform:uppercase}
929
- .supervisor-kpi{font-size:1.35rem;font-weight:700;color:#f8fafc}
930
- .supervisor-meta{font-size:.72rem;color:#94a3b8;font-family:'Courier New',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
931
- .supervisor-pill{display:inline-flex;align-items:center;border-radius:999px;padding:2px 7px;margin:2px 4px 2px 0;font-size:.68rem;border:1px solid #334155;color:#cbd5e1;background:#0f172a}
932
- .supervisor-pill.ok{border-color:rgba(74,222,128,.35);color:#86efac;background:rgba(34,197,94,.08)}
933
- .supervisor-pill.warn{border-color:rgba(250,204,21,.35);color:#fde68a;background:rgba(250,204,21,.08)}
934
- .supervisor-pill.bad{border-color:rgba(248,113,113,.35);color:#fecaca;background:rgba(248,113,113,.08)}
935
- .supervisor-list{display:flex;flex-direction:column;gap:8px;max-height:320px;overflow:auto}
936
- .supervisor-row{border:1px solid #1e293b;border-radius:8px;padding:8px;background:rgba(15,23,42,.45)}
937
- .supervisor-row-top{display:flex;justify-content:space-between;gap:8px;align-items:center}
938
- .supervisor-name{font-size:.82rem;color:#e2e8f0;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
939
- .supervisor-row-actions{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}
940
- .supervisor-log{font-family:'Courier New',monospace;font-size:.7rem;color:#94a3b8;white-space:pre-wrap;word-break:break-word;max-height:280px;overflow:auto}
941
- .supervisor-feedback{margin-top:8px;padding:7px 9px;border-radius:6px;font-family:'Courier New',monospace;font-size:.72rem;color:#86efac;background:rgba(34,197,94,.08);border:1px solid rgba(74,222,128,.22);display:none}
942
- .supervisor-feedback.bad{color:#fecaca;background:rgba(248,113,113,.08);border-color:rgba(248,113,113,.25)}
943
808
  </style>
944
809
  </head>
945
810
  <body>
@@ -1160,41 +1025,6 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
1160
1025
  </div>
1161
1026
  </div>
1162
1027
 
1163
- <section class="supervisor-panel" id="supervisor-panel">
1164
- <div class="supervisor-head">
1165
- <div>
1166
- <div class="supervisor-title">Local Software Factory Supervisor</div>
1167
- <div class="supervisor-sub">clauth owns local LIFEAI plugin discovery, candidate tests, clauth-owned surface operations, receipts, and supervisor event logs. Dev Center should call this surface instead of PM2 directly.</div>
1168
- </div>
1169
- <div class="supervisor-actions">
1170
- <button class="supervisor-action" onclick="loadSupervisorCockpit()">Refresh</button>
1171
- <button class="supervisor-action" onclick="rescanSupervisorPlugins()">Rescan plugins</button>
1172
- </div>
1173
- </div>
1174
- <div id="supervisor-status" class="supervisor-sub">Loading supervisor state…</div>
1175
- <div id="supervisor-feedback" class="supervisor-feedback" role="status"></div>
1176
- <div class="supervisor-grid" style="margin-top:10px">
1177
- <div class="supervisor-card">
1178
- <h4>Plugins</h4>
1179
- <div class="supervisor-kpi" id="supervisor-plugin-count">—</div>
1180
- <div class="supervisor-meta" id="supervisor-plugin-meta">awaiting data</div>
1181
- <div class="supervisor-list" id="supervisor-plugins" style="margin-top:8px"></div>
1182
- </div>
1183
- <div class="supervisor-card">
1184
- <h4>Surfaces</h4>
1185
- <div class="supervisor-kpi" id="supervisor-surface-count">—</div>
1186
- <div class="supervisor-meta" id="supervisor-surface-meta">destination + owner matrix</div>
1187
- <div class="supervisor-list" id="supervisor-surfaces" style="margin-top:8px"></div>
1188
- </div>
1189
- <div class="supervisor-card">
1190
- <h4>Operations + log</h4>
1191
- <div class="supervisor-kpi" id="supervisor-operation-count">—</div>
1192
- <div class="supervisor-meta" id="supervisor-log-path">events.jsonl</div>
1193
- <div class="supervisor-log" id="supervisor-events" style="margin-top:8px">No events loaded.</div>
1194
- </div>
1195
- </div>
1196
- </section>
1197
-
1198
1028
 
1199
1029
  <div class="tunnel-panel" id="webdav-panel" style="flex-direction:column;align-items:stretch;gap:8px">
1200
1030
  <div style="display:flex;align-items:center;gap:10px;width:100%">
@@ -1239,16 +1069,11 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
1239
1069
  <span id="service-search-count" class="service-search-count"></span>
1240
1070
  </div>
1241
1071
  <div id="grid" class="grid"><p class="loading">Loading services…</p></div>
1242
- <div class="footer" id="originFooter">checking origin… · 10-strike lockout</div>
1072
+ <div class="footer">localhost:${port} · 127.0.0.1 only · 10-strike lockout</div>
1243
1073
  </div>
1244
1074
 
1245
1075
  <script>
1246
- const BASE = location.origin;
1247
- (function reportOrigin() {
1248
- const isLocal = /^(127\\.0\\.0\\.1|localhost|\\[::1\\])$/.test(location.hostname);
1249
- const el = document.getElementById("originFooter");
1250
- if (el) el.textContent = (isLocal ? "LOCAL" : "REMOTE") + " · " + BASE + " · 10-strike lockout";
1251
- })();
1076
+ const BASE = "http://127.0.0.1:${port}";
1252
1077
 
1253
1078
  const SERVICE_HINTS = {
1254
1079
  "neo4j": "neo4j+s://username:password@instance.databases.neo4j.io",
@@ -1480,102 +1305,9 @@ function showMain(ping) {
1480
1305
  pollTunnel();
1481
1306
  loadWebdavMounts();
1482
1307
  updateBuildStatus();
1483
- loadSupervisorCockpit();
1484
1308
  refreshWriteLockUi();
1485
1309
  }
1486
1310
 
1487
- function supervisorBadge(text, kind) {
1488
- return '<span class="supervisor-pill ' + (kind || '') + '">' + htmlEscape(text) + '</span>';
1489
- }
1490
-
1491
- async function supervisorJson(path, options) {
1492
- const response = await fetch(BASE + path, { cache: "no-store", ...(options || {}) });
1493
- const data = await response.json().catch(() => ({}));
1494
- if (!response.ok || data.error) throw new Error(data.error || ("HTTP " + response.status));
1495
- return data;
1496
- }
1497
-
1498
- function supervisorFeedback(text, bad) {
1499
- const el = document.getElementById("supervisor-feedback");
1500
- if (!el) return;
1501
- el.textContent = text || "";
1502
- el.className = "supervisor-feedback" + (bad ? " bad" : "");
1503
- el.style.display = text ? "block" : "none";
1504
- }
1505
-
1506
- async function loadSupervisorCockpit() {
1507
- const status = document.getElementById("supervisor-status");
1508
- if (status) status.textContent = "Loading supervisor state…";
1509
- try {
1510
- const [health, plugins, surfaces, logs] = await Promise.all([
1511
- supervisorJson("/health"),
1512
- supervisorJson("/v1/plugins"),
1513
- supervisorJson("/v1/surfaces"),
1514
- supervisorJson("/v1/logs?limit=40"),
1515
- ]);
1516
- const pluginRows = plugins.plugins || [];
1517
- const surfaceRows = surfaces.surfaces || [];
1518
- document.getElementById("supervisor-plugin-count").textContent = String(pluginRows.length);
1519
- document.getElementById("supervisor-surface-count").textContent = String(surfaceRows.length);
1520
- document.getElementById("supervisor-operation-count").textContent = String((logs.operations || []).length);
1521
- document.getElementById("supervisor-plugin-meta").textContent = "current " + (pluginRows.filter(p => p.state === "current").length) + " · awaiting " + (pluginRows.filter(p => p.state === "awaiting_enable").length);
1522
- document.getElementById("supervisor-surface-meta").textContent = "pm2 home: " + (health.pm2_home || "—");
1523
- document.getElementById("supervisor-log-path").textContent = logs.log_path || "events.jsonl";
1524
- document.getElementById("supervisor-plugins").innerHTML = pluginRows.length ? pluginRows.map(renderSupervisorPlugin).join("") : '<div class="supervisor-sub">No plugins discovered.</div>';
1525
- document.getElementById("supervisor-surfaces").innerHTML = surfaceRows.length ? surfaceRows.map(renderSupervisorSurface).join("") : '<div class="supervisor-sub">No surfaces declared.</div>';
1526
- document.getElementById("supervisor-events").textContent = (logs.events || []).slice(-20).reverse().map(e => {
1527
- const label = e.kind === "operation" ? (e.action + " " + JSON.stringify(e.target || {})) : (e.kind + " " + (e.plugin_id || ""));
1528
- return (e.ts || e.created_at || "") + " " + label;
1529
- }).join("\\n") || "No events yet.";
1530
- if (status) status.innerHTML = supervisorBadge("supervisor " + (health.status === "ok" ? "healthy" : (health.status || "unknown")), health.status === "ok" ? "ok" : "warn") + supervisorBadge("vault " + (health.vault_locked ? "locked" : "unlocked"), health.vault_locked ? "warn" : "ok") + supervisorBadge("v" + (health.clauth_version || "${VERSION}"), "");
1531
- } catch (err) {
1532
- if (status) status.innerHTML = supervisorBadge("supervisor unreachable", "bad") + " " + htmlEscape(err.message || err);
1533
- supervisorFeedback("Supervisor unavailable — actions are disabled until the local control plane returns.", true);
1534
- }
1535
- }
1536
-
1537
- function renderSupervisorPlugin(plugin) {
1538
- const stateKind = plugin.state === "current" ? "ok" : (plugin.state === "manifest_invalid" || plugin.state === "missing" ? "bad" : "warn");
1539
- return '<div class="supervisor-row"><div class="supervisor-row-top"><span class="supervisor-name">' + htmlEscape(plugin.id) + '</span>' + supervisorBadge(plugin.state || "unknown", stateKind) + '</div>' +
1540
- '<div class="supervisor-meta">' + htmlEscape(plugin.publisher || "unknown") + ' · ' + htmlEscape(plugin.version || "—") + ' · ' + htmlEscape(plugin.source || "—") + '</div>' +
1541
- '<div class="supervisor-meta">' + htmlEscape((plugin.manifest_hash || "").slice(0, 12)) + '</div>' +
1542
- '<div class="supervisor-row-actions"><button class="supervisor-action" onclick="runSupervisorPlugin(' + jsArg(plugin.id) + ',' + jsArg(plugin.enabled ? "disable" : "enable") + ')">' + (plugin.enabled ? "Disable" : "Enable") + '</button><button class="supervisor-action" onclick="runSupervisorPlugin(' + jsArg(plugin.id) + ',' + jsArg("test") + ')">Test</button><button class="supervisor-action" onclick="runSupervisorPlugin(' + jsArg(plugin.id) + ',' + jsArg("promote") + ')">Promote</button></div></div>';
1543
- }
1544
-
1545
- function renderSupervisorSurface(surface) {
1546
- const ownerKind = surface.lifecycle_owner === "clauth" ? "ok" : (surface.lifecycle_owner === "plugin" ? "warn" : "");
1547
- const stateKind = surface.state === "current" || surface.status === "healthy" ? "ok" : (surface.state === "unavailable" ? "bad" : "warn");
1548
- return '<div class="supervisor-row"><div class="supervisor-row-top"><span class="supervisor-name">' + htmlEscape(surface.plugin_id + ":" + surface.id) + '</span>' + supervisorBadge(surface.lifecycle_owner || "unknown", ownerKind) + supervisorBadge(surface.state || surface.status || "unknown", stateKind) + '</div>' +
1549
- '<div class="supervisor-meta">' + htmlEscape(surface.destination || "—") + ' · port ' + htmlEscape(surface.port || "—") + '</div>' +
1550
- '<div class="supervisor-meta">' + htmlEscape(surface.health || surface.health_url || "no health") + '</div>' +
1551
- '<div class="supervisor-row-actions">' + ["start","stop","restart","reconcile","test","promote","rollback"].map(a => '<button class="supervisor-action" data-supervisor-action="' + a + '" onclick="runSupervisorSurface(' + jsArg(surface.id) + ',' + jsArg(a) + ')">' + a + '</button>').join("") + '</div></div>';
1552
- }
1553
-
1554
- async function rescanSupervisorPlugins() {
1555
- try {
1556
- await supervisorJson("/v1/plugins/rescan", { method: "POST", headers: writeHeaders() });
1557
- supervisorFeedback("Plugin rescan completed.", false);
1558
- await loadSupervisorCockpit();
1559
- } catch (err) { supervisorFeedback("Plugin rescan failed: " + (err.message || err), true); }
1560
- }
1561
-
1562
- async function runSupervisorPlugin(id, action) {
1563
- try {
1564
- const receipt = await supervisorJson("/v1/plugins/" + encodeURIComponent(id) + "/" + encodeURIComponent(action), { method: "POST", headers: writeHeaders() });
1565
- supervisorFeedback("Plugin " + action + " receipt " + (receipt.operationId || "recorded") + " · " + ((receipt.resulting_state && receipt.resulting_state.state) || "completed"), false);
1566
- await loadSupervisorCockpit();
1567
- } catch (err) { supervisorFeedback("Plugin " + action + " failed: " + (err.message || err), true); }
1568
- }
1569
-
1570
- async function runSupervisorSurface(id, action) {
1571
- try {
1572
- const receipt = await supervisorJson("/v1/surfaces/" + encodeURIComponent(id) + "/actions", { method: "POST", headers: writeHeaders({ "Content-Type": "application/json" }), body: JSON.stringify({ action }) });
1573
- const result = receipt.resulting_state || {};
1574
- supervisorFeedback("Surface " + action + " receipt " + (receipt.operationId || "recorded") + " · " + (result.state || "completed"), result.ok === false);
1575
- await loadSupervisorCockpit();
1576
- } catch (err) { supervisorFeedback("Surface " + action + " failed: " + (err.message || err), true); }
1577
- }
1578
-
1579
1311
  // ── Unlock ──────────────────────────────────
1580
1312
  async function unlock() {
1581
1313
  const input = document.getElementById("lock-input");
@@ -4038,187 +3770,6 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
4038
3770
  });
4039
3771
  },
4040
3772
  });
4041
- const isSupervisorPort = port === getSupervisorPort();
4042
- const supervisorTestNoToken = process.env.CLAUTH_SUPERVISOR_TEST_NO_TOKEN === "1";
4043
- const opsAdapter = createPm2Adapter(pm2);
4044
- const executePm2 = createSerializedExecutor();
4045
- const opsPolicy = createOperationPolicy({
4046
- enabled: String(process.env.CLAUTH_OPS_ENABLED || "").split(",").map((item) => item.trim()).filter(Boolean),
4047
- applications: (() => { try { return JSON.parse(process.env.CLAUTH_OPS_APPLICATIONS || "{}"); } catch { return {}; } })(),
4048
- adminEnabled: String(process.env.CLAUTH_OPS_ADMIN_ENABLED || "").split(",").map((item) => item.trim()).filter(Boolean),
4049
- adminApplications: (() => { try { return JSON.parse(process.env.CLAUTH_OPS_ADMIN_APPLICATIONS || "{}"); } catch { return {}; } })(),
4050
- allowHostWide: process.env.CLAUTH_OPS_ALLOW_HOST_WIDE === "1",
4051
- });
4052
- const opsJobs = createJobStore({
4053
- filePath: process.env.CLAUTH_OPS_JOB_STORE_PATH || path.join(getSupervisorDir(), "ops-jobs.json"),
4054
- });
4055
-
4056
- async function getLoopbackSecret(service) {
4057
- const response = await fetch(`http://127.0.0.1:${LIVE_PORT}/v/${encodeURIComponent(service)}`, { signal: AbortSignal.timeout(5000) });
4058
- if (!response.ok) throw new Error(`${service} unavailable from local clauth`);
4059
- const value = (await response.text()).trim();
4060
- if (!value) throw new Error(`${service} is empty`);
4061
- return value;
4062
- }
4063
- const coolify = createCoolifyAdapter({
4064
- baseUrl: process.env.CLAUTH_COOLIFY_BASE_URL || "https://deploy.regendevcorp.com",
4065
- getToken: () => getLoopbackSecret("coolify-api"),
4066
- });
4067
- const deployments = (() => { try { return parseDeploymentRegistry(process.env.CLAUTH_OPS_DEPLOYMENTS || "{}"); } catch { return {}; } })();
4068
- const deploymentAdapter = createDeploymentAdapter({
4069
- deployments,
4070
- reload: async (target) => {
4071
- await executePm2(async () => {
4072
- await opsAdapter.connect();
4073
- try { await opsAdapter.execute("reload", { target, options: { updateEnv: true } }); } finally { await opsAdapter.disconnect(); }
4074
- });
4075
- },
4076
- });
4077
-
4078
- /**
4079
- * Record an ops failure's upstream message to the LOCAL log only.
4080
- *
4081
- * job-store's sanitizer deliberately drops free-form `error` text so an
4082
- * upstream message cannot carry a credential into the persisted job file or
4083
- * the API response. That protection left every failure with an empty detail,
4084
- * so jobs reported `failed` with no reason at all. Jobs now carry an
4085
- * enumerated `code`; the underlying message goes here, to the same
4086
- * operator-only log as the rest of the daemon's diagnostics.
4087
- */
4088
- function logOpsFailure(kind, operation, error) {
4089
- const message = String(error?.message || error || "unknown");
4090
- try {
4091
- fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] [OPS ${kind}/${operation}] ${message}\n`);
4092
- } catch {}
4093
- }
4094
-
4095
- async function opsBearerRole(req) {
4096
- const header = req.headers.authorization;
4097
- const supplied = Array.isArray(header) ? header[0] : header;
4098
- if (!supplied || !String(supplied).startsWith("Bearer ")) return null;
4099
- const actual = String(supplied).slice(7).trim();
4100
- let admin; let agent;
4101
- try { admin = await getLoopbackSecret(process.env.CLAUTH_OPS_ADMIN_TOKEN_SERVICE || "vultr-ops-admin-token"); } catch {}
4102
- try { agent = await getLoopbackSecret(process.env.CLAUTH_OPS_AGENT_TOKEN_SERVICE || "vultr-ops-api-token"); } catch {}
4103
- if (admin && agent && admin === agent) return null;
4104
- for (const [role, expected] of [["admin", admin], ["agent", agent]]) {
4105
- if (!expected) continue;
4106
- const a = Buffer.from(actual); const b = Buffer.from(expected);
4107
- if (a.length === b.length && crypto.timingSafeEqual(a, b)) return role;
4108
- }
4109
- return null;
4110
- }
4111
-
4112
- async function requireOpsBearer(req, res) {
4113
- const role = await opsBearerRole(req);
4114
- if (role) { req._opsRole = role; return true; }
4115
- res.writeHead(401, { "Content-Type": "application/json", ...CORS });
4116
- res.end(JSON.stringify({ error: "ops_bearer_required" }));
4117
- return false;
4118
- }
4119
-
4120
- function submitOpsJob(operation, input, role = "agent") {
4121
- const authorization = opsPolicy.authorize(operation, input, role);
4122
- const job = opsJobs.create({ kind: "pm2", operation, target: input.target || input.name || null });
4123
- if (!authorization.ok) {
4124
- return opsJobs.event(job.id, "rejected", { code: authorization.code });
4125
- }
4126
- void (async () => {
4127
- opsJobs.event(job.id, "running");
4128
- try {
4129
- const result = await executePm2(async () => {
4130
- await opsAdapter.connect();
4131
- try { return await opsAdapter.execute(operation, input); } finally { await opsAdapter.disconnect(); }
4132
- });
4133
- opsJobs.event(job.id, "succeeded", operationReceipt(operation, result, authorization.allowed_targets || []));
4134
- } catch (error) {
4135
- // `error` alone is dropped by job-store's sanitizer (it refuses
4136
- // free-form upstream text so a credential cannot ride along), which
4137
- // left every failure with an empty detail. Emit an enumerated code so
4138
- // the failure has a reason; keep the message for the local log only.
4139
- logOpsFailure("pm2", operation, error);
4140
- opsJobs.event(job.id, "failed", { code: "pm2_operation_failed" });
4141
- }
4142
- })();
4143
- return opsJobs.get(job.id);
4144
- }
4145
-
4146
- function operationReceipt(operation, result, allowedTargets) {
4147
- if (["list", "describe", "logs"].includes(operation)) {
4148
- const processes = Array.isArray(result)
4149
- ? result.filter((process) => allowedTargets.includes("*") || allowedTargets.includes(process?.name))
4150
- : [];
4151
- return { processes };
4152
- }
4153
- if (operation === "ping") return { status: "connected" };
4154
- return { status: "completed" };
4155
- }
4156
-
4157
- function submitPromotionJob(applicationUuid) {
4158
- const job = opsJobs.create({ kind: "coolify", operation: "promote", target: applicationUuid });
4159
- const enabled = String(process.env.CLAUTH_OPS_ENABLED || "").split(",").map((item) => item.trim()).includes("coolify_promote");
4160
- const allowlist = (() => { try { return JSON.parse(process.env.CLAUTH_COOLIFY_PROMOTE_UUIDS || "[]"); } catch { return []; } })();
4161
- if (!enabled || !Array.isArray(allowlist) || !allowlist.includes(applicationUuid)) {
4162
- return opsJobs.event(job.id, "rejected", { code: "service_not_available" });
4163
- }
4164
- void (async () => {
4165
- opsJobs.event(job.id, "running");
4166
- try {
4167
- const deployment = await coolify.promote(applicationUuid);
4168
- // Coolify answers with a `deployments` ARRAY, not a flat object — see
4169
- // deploymentUuidFrom. Reading the flat field alone marked the job failed
4170
- // while the deployment was actually running.
4171
- const deploymentUuid = deploymentUuidFrom(deployment);
4172
- if (!deploymentUuid) {
4173
- // The deploy request itself SUCCEEDED (no throw); only the UUID was
4174
- // unreadable, so the deployment may well be RUNNING. The code says so
4175
- // explicitly rather than a bare "failed", because a plain failure
4176
- // invites a retry and a duplicate production deploy.
4177
- //
4178
- // An enumerated code, not a free-form error: job-store's sanitizer
4179
- // drops `error` on purpose to keep upstream text (and any credential
4180
- // inside it) out of the persisted job.
4181
- return opsJobs.event(job.id, "failed", { code: "coolify_deploy_accepted_uuid_unreadable" });
4182
- }
4183
- opsJobs.event(job.id, "waiting", { deployment_uuid: deploymentUuid });
4184
- const terminal = await coolify.poll(deploymentUuid, { attempts: Number(process.env.CLAUTH_COOLIFY_POLL_ATTEMPTS || 60), delay: () => new Promise((resolve) => setTimeout(resolve, 5000)) });
4185
- opsJobs.event(job.id, terminal.state === "succeeded" ? "succeeded" : terminal.state, { deployment_uuid: deploymentUuid, status: terminal.deployment?.status || null });
4186
- } catch (error) {
4187
- // The throw may have happened AFTER Coolify accepted the deploy (e.g.
4188
- // the poll lost the network), so this is not proof nothing shipped.
4189
- logOpsFailure("coolify", "promote", error);
4190
- opsJobs.event(job.id, "failed", { code: "coolify_promote_failed" });
4191
- }
4192
- })();
4193
- return opsJobs.get(job.id);
4194
- }
4195
-
4196
- function submitDeploymentJob(application, ref) {
4197
- const job = opsJobs.create({ kind: "deployment", operation: "deploy", target: application });
4198
- const enabled = String(process.env.CLAUTH_OPS_ENABLED || "").split(",").map((item) => item.trim()).includes("deploy");
4199
- if (!enabled || !deployments[application]) return opsJobs.event(job.id, "rejected", { code: "service_not_available" });
4200
- void (async () => {
4201
- opsJobs.event(job.id, "running");
4202
- try { opsJobs.event(job.id, "building"); opsJobs.event(job.id, "succeeded", { result: await deploymentAdapter.deploy({ application, ref }) }); }
4203
- catch (error) { logOpsFailure("deployment", "deploy", error); opsJobs.event(job.id, "failed", { code: "deployment_failed" }); }
4204
- })();
4205
- return opsJobs.get(job.id);
4206
- }
4207
-
4208
- function hasSupervisorWrite(req) {
4209
- if (validateWriteToken(req, writeSession)) return true;
4210
- if (!isSupervisorPort) return false;
4211
- if (!isLoopbackAddress(req.socket?.remoteAddress)) return false;
4212
- return supervisorTestNoToken || !supervisorRequiresWriteToken(port);
4213
- }
4214
-
4215
- function rejectSupervisorWrite(res) {
4216
- res.writeHead(403, { "Content-Type": "application/json", ...CORS });
4217
- return res.end(JSON.stringify({
4218
- error: "write_token_required",
4219
- hint: "Unlock clauth writes, unset CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN for localhost supervisor operations, or set CLAUTH_SUPERVISOR_TEST_NO_TOKEN=1 for temporary localhost-only supervisor tests.",
4220
- }));
4221
- }
4222
3773
 
4223
3774
  // ── MCP SSE session tracking ──────────────────────────────
4224
3775
  const sseSessions = new Map(); // sessionId → { res, initialized }
@@ -4759,7 +4310,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
4759
4310
 
4760
4311
  const server = http.createServer(async (req, res) => {
4761
4312
  const remote = req.socket.remoteAddress;
4762
- const isLocal = isLoopbackAddress(remote);
4313
+ const isLocal = remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
4763
4314
 
4764
4315
  const url = new URL(req.url, `http://127.0.0.1:${port}`);
4765
4316
  const reqPath = url.pathname;
@@ -4799,205 +4350,6 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
4799
4350
  return res.end(JSON.stringify(result));
4800
4351
  }
4801
4352
 
4802
- if (method === "GET" && reqPath === "/health") {
4803
- return ok(res, { ...supervisorHealth(), listening_port: port, process_id: process.pid, vault_locked: !password, clauth_version: VERSION });
4804
- }
4805
-
4806
- // Bearer-gated remote operations surface. It remains loopback-only at this
4807
- // layer; ingress/tunnel policy decides whether it is reachable remotely.
4808
- if (method === "GET" && reqPath === "/v1/ops/catalog") {
4809
- if (!await requireOpsBearer(req, res)) return;
4810
- return ok(res, { schema: "clauth.ops.v1", operations: PM2_OPERATION_CATALOG });
4811
- }
4812
-
4813
- if (method === "GET" && reqPath === "/v1/ops/processes") {
4814
- if (!await requireOpsBearer(req, res)) return;
4815
- const job = submitOpsJob("list", {}, req._opsRole);
4816
- res.writeHead(job.phase === "rejected" ? 403 : 202, { "Content-Type": "application/json", ...CORS });
4817
- return res.end(JSON.stringify(job));
4818
- }
4819
-
4820
- const opsProcessMatch = reqPath.match(/^\/v1\/ops\/processes\/([^/]+)$/);
4821
- if (method === "GET" && opsProcessMatch) {
4822
- if (!await requireOpsBearer(req, res)) return;
4823
- const job = submitOpsJob("describe", { target: decodeURIComponent(opsProcessMatch[1]) }, req._opsRole);
4824
- res.writeHead(job.phase === "rejected" ? 403 : 202, { "Content-Type": "application/json", ...CORS });
4825
- return res.end(JSON.stringify(job));
4826
- }
4827
-
4828
- if (method === "POST" && reqPath === "/v1/ops/operations") {
4829
- if (!await requireOpsBearer(req, res)) return;
4830
- let body;
4831
- try { body = await readBody(req); } catch {
4832
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4833
- return res.end(JSON.stringify({ error: "invalid_json" }));
4834
- }
4835
- const operation = String(body?.operation || "");
4836
- if (!PM2_OPERATION_CATALOG[operation]) {
4837
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4838
- return res.end(JSON.stringify({ error: "unknown_operation" }));
4839
- }
4840
- const job = submitOpsJob(operation, body?.input && typeof body.input === "object" ? body.input : {}, req._opsRole);
4841
- res.writeHead(job.phase === "rejected" ? 403 : 202, { "Content-Type": "application/json", ...CORS });
4842
- return res.end(JSON.stringify(job));
4843
- }
4844
-
4845
- if (method === "POST" && reqPath === "/v1/ops/promotions") {
4846
- if (!await requireOpsBearer(req, res)) return;
4847
- let body;
4848
- try { body = await readBody(req); } catch {
4849
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4850
- return res.end(JSON.stringify({ error: "invalid_json" }));
4851
- }
4852
- const applicationUuid = String(body?.application_uuid || "").trim();
4853
- if (!applicationUuid) {
4854
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4855
- return res.end(JSON.stringify({ error: "application_uuid_required" }));
4856
- }
4857
- const job = submitPromotionJob(applicationUuid);
4858
- res.writeHead(job.phase === "rejected" ? 403 : 202, { "Content-Type": "application/json", ...CORS });
4859
- return res.end(JSON.stringify(job));
4860
- }
4861
-
4862
- if (method === "POST" && reqPath === "/v1/ops/deployments") {
4863
- if (!await requireOpsBearer(req, res)) return;
4864
- let body;
4865
- try { body = await readBody(req); } catch {
4866
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4867
- return res.end(JSON.stringify({ error: "invalid_json" }));
4868
- }
4869
- const application = String(body?.application || "").trim();
4870
- if (!application) {
4871
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4872
- return res.end(JSON.stringify({ error: "application_required" }));
4873
- }
4874
- const job = submitDeploymentJob(application, body?.ref ? String(body.ref) : undefined);
4875
- res.writeHead(job.phase === "rejected" ? 403 : 202, { "Content-Type": "application/json", ...CORS });
4876
- return res.end(JSON.stringify(job));
4877
- }
4878
-
4879
- const opsJobMatch = reqPath.match(/^\/v1\/ops\/jobs\/([^/]+)$/);
4880
- if (method === "GET" && opsJobMatch) {
4881
- if (!await requireOpsBearer(req, res)) return;
4882
- const job = opsJobs.get(decodeURIComponent(opsJobMatch[1]));
4883
- res.writeHead(job ? 200 : 404, { "Content-Type": "application/json", ...CORS });
4884
- return res.end(JSON.stringify(job || { error: "job_not_found" }));
4885
- }
4886
-
4887
- const opsJobEventsMatch = reqPath.match(/^\/v1\/ops\/jobs\/([^/]+)\/events$/);
4888
- if (method === "GET" && opsJobEventsMatch) {
4889
- if (!await requireOpsBearer(req, res)) return;
4890
- const jobId = decodeURIComponent(opsJobEventsMatch[1]);
4891
- if (!opsJobs.get(jobId)) {
4892
- res.writeHead(404, { "Content-Type": "application/json", ...CORS });
4893
- return res.end(JSON.stringify({ error: "job_not_found" }));
4894
- }
4895
- res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", Connection: "keep-alive", ...CORS });
4896
- const unsubscribe = opsJobs.subscribe(jobId, (job) => {
4897
- if (!res.writableEnded) res.write(`event: job\ndata: ${JSON.stringify(job)}\n\n`);
4898
- });
4899
- req.on("close", unsubscribe);
4900
- return;
4901
- }
4902
-
4903
- if (method === "GET" && reqPath === "/v1/plugins") {
4904
- return ok(res, { plugins: listPlugins() });
4905
- }
4906
-
4907
- if (method === "POST" && reqPath === "/v1/plugins/rescan") {
4908
- if (!hasSupervisorWrite(req)) return rejectSupervisorWrite(res);
4909
- return ok(res, discoverPlugins());
4910
- }
4911
-
4912
- const pluginEnableMatch = reqPath.match(/^\/v1\/plugins\/([^/]+)\/(enable|disable|test|promote)$/);
4913
- if (method === "POST" && pluginEnableMatch) {
4914
- if (!hasSupervisorWrite(req)) return rejectSupervisorWrite(res);
4915
- const pluginId = decodeURIComponent(pluginEnableMatch[1]);
4916
- const op = pluginEnableMatch[2];
4917
- const result = op === "enable"
4918
- ? setPluginEnabled(pluginId, true)
4919
- : op === "disable"
4920
- ? setPluginEnabled(pluginId, false)
4921
- : runPluginAction(pluginId, op);
4922
- res.writeHead(result.error ? 400 : 200, { "Content-Type": "application/json", ...CORS });
4923
- return res.end(JSON.stringify(result));
4924
- }
4925
-
4926
- if (method === "GET" && reqPath === "/v1/surfaces") {
4927
- return ok(res, { surfaces: listSurfaces() });
4928
- }
4929
-
4930
- const surfaceActionMatch = reqPath.match(/^\/v1\/surfaces\/([^/]+)\/actions$/);
4931
- if (method === "POST" && surfaceActionMatch) {
4932
- if (!hasSupervisorWrite(req)) return rejectSupervisorWrite(res);
4933
- let body;
4934
- try { body = await readBody(req); } catch {
4935
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4936
- return res.end(JSON.stringify({ error: "Invalid JSON" }));
4937
- }
4938
- const result = runSurfaceAction(decodeURIComponent(surfaceActionMatch[1]), body?.action || "reconcile");
4939
- res.writeHead(result.error ? 400 : 200, { "Content-Type": "application/json", ...CORS });
4940
- return res.end(JSON.stringify(result));
4941
- }
4942
-
4943
- if (method === "GET" && reqPath === "/v1/routes") {
4944
- return ok(res, { routes: listRoutes() });
4945
- }
4946
-
4947
- if (method === "GET" && reqPath === "/v1/tunnels") {
4948
- return ok(res, { tunnels: listTunnels() });
4949
- }
4950
-
4951
- if (method === "GET" && reqPath === "/v1/logs") {
4952
- const limit = Number(url.searchParams.get("limit") || 100);
4953
- const boundedLimit = Number.isFinite(limit) ? Math.max(1, Math.min(limit, 500)) : 100;
4954
- const state = loadSupervisorState();
4955
- return ok(res, {
4956
- schema: "clauth.supervisor.logs.v1",
4957
- log_path: path.join(getSupervisorDir(), "events.jsonl"),
4958
- events: readSupervisorEvents(boundedLimit).map(supervisorLogDto),
4959
- operations: (state.operations || []).slice(0, boundedLimit).map(supervisorOperationDto),
4960
- });
4961
- }
4962
-
4963
- const tunnelRoutesMatch = reqPath.match(/^\/v1\/tunnels\/([^/]+)\/routes$/);
4964
- if (method === "POST" && tunnelRoutesMatch) {
4965
- if (!hasSupervisorWrite(req)) return rejectSupervisorWrite(res);
4966
- let body;
4967
- try { body = await readBody(req); } catch {
4968
- res.writeHead(400, { "Content-Type": "application/json", ...CORS });
4969
- return res.end(JSON.stringify({ error: "Invalid JSON" }));
4970
- }
4971
- return ok(res, addTunnelRoute(decodeURIComponent(tunnelRoutesMatch[1]), body, "localhost"));
4972
- }
4973
-
4974
- const tunnelRouteDeleteMatch = reqPath.match(/^\/v1\/tunnels\/([^/]+)\/routes\/([^/]+)$/);
4975
- if (method === "DELETE" && tunnelRouteDeleteMatch) {
4976
- if (!hasSupervisorWrite(req)) return rejectSupervisorWrite(res);
4977
- return ok(res, removeTunnelRoute(decodeURIComponent(tunnelRouteDeleteMatch[1]), decodeURIComponent(tunnelRouteDeleteMatch[2]), "localhost"));
4978
- }
4979
-
4980
- if (method === "GET" && reqPath.startsWith("/v1/operations/")) {
4981
- const id = decodeURIComponent(reqPath.split("/").pop());
4982
- const operation = (supervisorHealth(), readSupervisorEvents(500)).find((event) => event.operationId === id);
4983
- res.writeHead(operation ? 200 : 404, { "Content-Type": "application/json", ...CORS });
4984
- return res.end(JSON.stringify(operation ? supervisorLogDto(operation) : { error: "operation_not_found" }));
4985
- }
4986
-
4987
- if (method === "GET" && reqPath === "/v1/events") {
4988
- res.writeHead(200, {
4989
- "Content-Type": "text/event-stream",
4990
- "Cache-Control": "no-cache",
4991
- "Connection": "keep-alive",
4992
- ...CORS,
4993
- });
4994
- for (const event of readSupervisorEvents(Number(url.searchParams.get("limit") || 100))) {
4995
- res.write(`event: supervisor\ndata: ${JSON.stringify(supervisorLogDto(event))}\n\n`);
4996
- }
4997
- res.end();
4998
- return;
4999
- }
5000
-
5001
4353
  // ── Hosts that bypass OAuth (fresh domains for claude.ai compatibility) ──
5002
4354
  const NOAUTH_HOSTS = ["fs.regendevcorp.com", "clauth.regendevcorp.com", "chitchat.regendevcorp.com"];
5003
4355
  const requestHost = (req.headers.host || "").split(":")[0].toLowerCase();
@@ -8113,27 +7465,6 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
8113
7465
  });
8114
7466
  }
8115
7467
 
8116
- // The localhost supervisor is the only process allowed to repair clauth-owned
8117
- // local surfaces. Keep this loop out of the vault/staged instances and make
8118
- // the cadence configurable for deterministic tests.
8119
- if (port === getSupervisorPort() && process.env.CLAUTH_SUPERVISOR_HEALTH_RECONCILE !== "0") {
8120
- const configuredInterval = Number(process.env.CLAUTH_SUPERVISOR_HEALTH_INTERVAL_MS || 10000);
8121
- const intervalMs = Number.isFinite(configuredInterval) ? Math.max(1000, Math.min(configuredInterval, 300000)) : 10000;
8122
- let healthReconcileInFlight = false;
8123
- const runHealthReconcile = () => {
8124
- if (healthReconcileInFlight) return;
8125
- healthReconcileInFlight = true;
8126
- reconcileSurfaceHealth().catch((err) => {
8127
- try { fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] supervisor health reconcile failed: ${err.message}\n`); } catch {}
8128
- }).finally(() => { healthReconcileInFlight = false; });
8129
- };
8130
- const healthTimer = setInterval(runHealthReconcile, intervalMs);
8131
- healthTimer.unref?.();
8132
- server.__supervisorHealthTimer = healthTimer;
8133
- server.on("close", () => clearInterval(healthTimer));
8134
- setImmediate(runHealthReconcile);
8135
- }
8136
-
8137
7468
  return server;
8138
7469
  }
8139
7470
 
@@ -8149,61 +7480,6 @@ async function verifyAuth(password) {
8149
7480
  }
8150
7481
  }
8151
7482
 
8152
- async function supervisorResponds(port = getSupervisorPort()) {
8153
- try {
8154
- const resp = await fetch(`http://127.0.0.1:${port}/health`);
8155
- return resp.ok;
8156
- } catch {
8157
- return false;
8158
- }
8159
- }
8160
-
8161
- async function ensureSupervisorStarted(cliEntry) {
8162
- const port = getSupervisorPort();
8163
- const existing = readSupervisorPid();
8164
- if (existing && isProcessAlive(existing.pid) && await supervisorResponds(existing.port)) {
8165
- return { started: false, pid: existing.pid, port: existing.port, state: "already_running" };
8166
- }
8167
- if (existing && !isProcessAlive(existing.pid)) removeSupervisorPid();
8168
- if (await supervisorResponds(port)) {
8169
- return { started: false, pid: existing?.pid || null, port, state: "port_already_live" };
8170
- }
8171
-
8172
- const out = fs.openSync(LOG_FILE, "a");
8173
- const child = spawn(process.execPath, [cliEntry, "serve", "supervisor", "--port", String(port)], {
8174
- detached: true,
8175
- stdio: ["ignore", out, out],
8176
- env: { ...process.env, __CLAUTH_SUPERVISOR_DAEMON: "1" },
8177
- });
8178
- child.unref();
8179
- writeSupervisorPid(child.pid, port);
8180
-
8181
- for (let attempt = 0; attempt < 5; attempt++) {
8182
- await new Promise(r => setTimeout(r, 500));
8183
- if (await supervisorResponds(port)) {
8184
- return { started: true, pid: child.pid, port, state: "started" };
8185
- }
8186
- }
8187
- return { started: true, pid: child.pid, port, state: "start_unverified" };
8188
- }
8189
-
8190
- async function stopSupervisorSibling() {
8191
- const info = readSupervisorPid();
8192
- if (!info) return null;
8193
- if (!isProcessAlive(info.pid)) {
8194
- removeSupervisorPid();
8195
- return { stopped: false, pid: info.pid, port: info.port, state: "stale" };
8196
- }
8197
- try {
8198
- process.kill(info.pid, "SIGTERM");
8199
- await new Promise(r => setTimeout(r, 300));
8200
- removeSupervisorPid();
8201
- return { stopped: true, pid: info.pid, port: info.port, state: "stopped" };
8202
- } catch (err) {
8203
- return { stopped: false, pid: info.pid, port: info.port, state: "stop_failed", error: err.message };
8204
- }
8205
- }
8206
-
8207
7483
  async function actionStart(opts) {
8208
7484
  if (opts.isolated) {
8209
7485
  return actionForeground(opts);
@@ -8405,11 +7681,6 @@ async function actionStart(opts) {
8405
7681
  console.log(chalk.gray(` Port: 127.0.0.1:${info.port}`));
8406
7682
  console.log(chalk.gray(` Services: ${whitelist ? whitelist.join(", ") : "all"}`));
8407
7683
  console.log(chalk.gray(` Log: ${LOG_FILE}`));
8408
- if (!isStaged) {
8409
- const supervisor = await ensureSupervisorStarted(cliEntry);
8410
- const verb = supervisor.started ? "started" : "available";
8411
- console.log(chalk.gray(` Supervisor: ${verb} on 127.0.0.1:${supervisor.port}${supervisor.pid ? ` (PID ${supervisor.pid})` : ""}`));
8412
- }
8413
7684
  if (isStaged) {
8414
7685
  console.log(chalk.yellow(`\n ⚡ Staged on port ${port} — open dashboard to verify, then click "Make Live"`));
8415
7686
  } else if (!password) {
@@ -8427,7 +7698,6 @@ async function actionStart(opts) {
8427
7698
  async function actionStop() {
8428
7699
  const info = readPid();
8429
7700
  if (!info) {
8430
- await stopSupervisorSibling();
8431
7701
  console.log(chalk.yellow("\n No clauth serve PID file found — not running.\n"));
8432
7702
  return;
8433
7703
  }
@@ -8435,7 +7705,6 @@ async function actionStop() {
8435
7705
  if (!isProcessAlive(info.pid)) {
8436
7706
  console.log(chalk.yellow(`\n PID ${info.pid} is not running (stale PID file). Cleaning up.\n`));
8437
7707
  removePid();
8438
- await stopSupervisorSibling();
8439
7708
  return;
8440
7709
  }
8441
7710
 
@@ -8446,7 +7715,6 @@ async function actionStop() {
8446
7715
  await new Promise(r => setTimeout(r, 300));
8447
7716
  console.log(chalk.green(`\n 🛑 clauth serve stopped (was PID ${info.pid}, port ${info.port})\n`));
8448
7717
  removePid();
8449
- await stopSupervisorSibling();
8450
7718
  return;
8451
7719
  }
8452
7720
  } catch {}
@@ -8460,7 +7728,6 @@ async function actionStop() {
8460
7728
  console.log(chalk.yellow(`\n Could not kill PID ${info.pid}: ${err.message}\n`));
8461
7729
  }
8462
7730
  removePid();
8463
- await stopSupervisorSibling();
8464
7731
  }
8465
7732
 
8466
7733
  async function actionPing() {
@@ -8505,9 +7772,7 @@ async function actionRestart(opts) {
8505
7772
  async function actionForeground(opts) {
8506
7773
  const port = parseInt(opts.port || "52437", 10);
8507
7774
  const isolated = !!opts.isolated;
8508
- const containerPassword = process.env.CLAUTH_MASTER_PASSWORD || process.env["clauth-master-password"] || null;
8509
- const password = isolated ? null : (opts.pw || containerPassword);
8510
- const bindHost = process.env.CLAUTH_BIND_HOST || "127.0.0.1";
7775
+ const password = isolated ? null : (opts.pw || null);
8511
7776
  const tunnelHostname = opts.tunnel || null;
8512
7777
  const whitelist = opts.services
8513
7778
  ? opts.services.split(",").map(s => s.trim().toLowerCase())
@@ -8533,14 +7798,14 @@ async function actionForeground(opts) {
8533
7798
  console.log(chalk.yellow("\n Starting in locked state — open browser to unlock"));
8534
7799
  }
8535
7800
 
8536
- console.log(chalk.gray(` Port: ${bindHost}:${port}`));
7801
+ console.log(chalk.gray(` Port: 127.0.0.1:${port}`));
8537
7802
  console.log(chalk.gray(` Services: ${whitelist ? whitelist.join(", ") : "all"}`));
8538
7803
  console.log(chalk.gray(` Lockout: 3 failures → exit\n`));
8539
7804
 
8540
7805
  const server = createServer(password, whitelist, port, tunnelHostname);
8541
- server.listen(port, bindHost, () => {
7806
+ server.listen(port, "127.0.0.1", () => {
8542
7807
  if (!isolated) writePid(process.pid, port);
8543
- console.log(chalk.green(` clauth serve → http://${bindHost}:${port}`));
7808
+ console.log(chalk.green(` clauth serve → http://127.0.0.1:${port}`));
8544
7809
  if (tunnelHostname) {
8545
7810
  console.log(chalk.cyan(` Tunnel: https://${tunnelHostname}/sse`));
8546
7811
  console.log("");
@@ -11617,41 +10882,6 @@ const MCP_TOOLS = [
11617
10882
  additionalProperties: false,
11618
10883
  },
11619
10884
  },
11620
- {
11621
- name: "clauth_ops_catalog",
11622
- description: "Return the Vultr deployment-control API catalog. Availability is still decided on Vultr.",
11623
- inputSchema: { type: "object", properties: {}, additionalProperties: false },
11624
- },
11625
- {
11626
- name: "clauth_ops_processes",
11627
- description: "Submit a scoped PM2 process-status query to the Vultr control plane. Only server-approved applications are returned.",
11628
- inputSchema: { type: "object", properties: {}, additionalProperties: false },
11629
- },
11630
- {
11631
- name: "clauth_ops_describe",
11632
- description: "Submit a scoped PM2 status query for one server-approved application.",
11633
- inputSchema: { type: "object", properties: { application: { type: "string" } }, required: ["application"], additionalProperties: false },
11634
- },
11635
- {
11636
- name: "clauth_ops_deploy",
11637
- description: "Submit one manifest-scoped development deployment to Vultr. The server chooses the repository, build, PM2 process, and allowed ref.",
11638
- inputSchema: { type: "object", properties: { application: { type: "string" }, ref: { type: "string" } }, required: ["application"], additionalProperties: false },
11639
- },
11640
- {
11641
- name: "clauth_ops_promote",
11642
- description: "Submit one server-allowlisted Coolify promotion and return its asynchronous job receipt.",
11643
- inputSchema: { type: "object", properties: { application_uuid: { type: "string" } }, required: ["application_uuid"], additionalProperties: false },
11644
- },
11645
- {
11646
- name: "clauth_ops_job",
11647
- description: "Read the terminal or in-progress receipt for a deployment-control job.",
11648
- inputSchema: { type: "object", properties: { job_id: { type: "string" } }, required: ["job_id"], additionalProperties: false },
11649
- },
11650
- {
11651
- name: "clauth_ops_run",
11652
- description: "Submit one PM2 operation through the Vultr control plane. The remote agent/admin profile decides whether the operation and target are available.",
11653
- inputSchema: { type: "object", properties: { operation: { type: "string" }, input: { type: "object" } }, required: ["operation"], additionalProperties: false },
11654
- },
11655
10885
  ];
11656
10886
 
11657
10887
  const MCP_WRITE_TOOL_NAMES = new Set([
@@ -11659,9 +10889,6 @@ const MCP_WRITE_TOOL_NAMES = new Set([
11659
10889
  "clauth_disable",
11660
10890
  "clauth_set_project",
11661
10891
  "clauth_generate_token",
11662
- "clauth_ops_deploy",
11663
- "clauth_ops_promote",
11664
- "clauth_ops_run",
11665
10892
  ]);
11666
10893
 
11667
10894
  function filterMcpToolsForWriteMode(tools) {
@@ -11695,24 +10922,6 @@ function mcpError(text) {
11695
10922
  return { content: [{ type: "text", text }], isError: true };
11696
10923
  }
11697
10924
 
11698
- async function callOpsFromMcp(vault, method, requestPath, body, { write = false } = {}) {
11699
- if (write && !vault.writeEnabled) return mcpError("MCP write tools are disabled by default. Launch clauth with CLAUTH_MCP_WRITE=1 for an explicit write-capable session.");
11700
- if (!vault.password) return mcpError("Vault is locked — call clauth_unlock first");
11701
- const endpoint = process.env.CLAUTH_OPS_APPROVED_ORIGIN;
11702
- if (!endpoint) return mcpError("service_not_available");
11703
- try {
11704
- const url = new URL(endpoint);
11705
- if (url.protocol !== "https:") return mcpError("service_not_available");
11706
- const service = process.env.CLAUTH_OPS_TOKEN_SERVICE || "vultr-ops-api-token";
11707
- const credential = await vaultRetrieveValue(vault, service);
11708
- if (credential.error || !credential.value) return mcpError("service_not_available");
11709
- const payload = await requestOps({ endpoint: url.toString().replace(/\/$/, ""), token: String(credential.value), method, path: requestPath, body });
11710
- return mcpResult(JSON.stringify(payload, null, 2));
11711
- } catch {
11712
- return mcpError("service_not_available");
11713
- }
11714
- }
11715
-
11716
10925
  // Windows cmd.exe doesn't support single quotes — use bash for gws JSON args
11717
10926
  const GWS_EXEC_OPTS = { encoding: "utf8", timeout: 30000, windowsHide: true, shell: os.platform() === "win32" ? "bash" : undefined };
11718
10927
 
@@ -11723,13 +10932,6 @@ async function handleMcpTool(vault, name, args) {
11723
10932
  };
11724
10933
 
11725
10934
  switch (name) {
11726
- case "clauth_ops_catalog": return callOpsFromMcp(vault, "GET", "/v1/ops/catalog");
11727
- case "clauth_ops_processes": return callOpsFromMcp(vault, "GET", "/v1/ops/processes");
11728
- case "clauth_ops_describe": return callOpsFromMcp(vault, "GET", `/v1/ops/processes/${encodeURIComponent(args.application || "")}`);
11729
- case "clauth_ops_deploy": return callOpsFromMcp(vault, "POST", "/v1/ops/deployments", { application: args.application, ...(args.ref ? { ref: args.ref } : {}) }, { write: true });
11730
- case "clauth_ops_promote": return callOpsFromMcp(vault, "POST", "/v1/ops/promotions", { application_uuid: args.application_uuid }, { write: true });
11731
- case "clauth_ops_job": return callOpsFromMcp(vault, "GET", `/v1/ops/jobs/${encodeURIComponent(args.job_id || "")}`);
11732
- case "clauth_ops_run": return callOpsFromMcp(vault, "POST", "/v1/ops/operations", { operation: args.operation, input: args.input && typeof args.input === "object" ? args.input : {} }, { write: true });
11733
10935
  case "clauth_ping": {
11734
10936
  return mcpResult(
11735
10937
  vault.password
@@ -13930,13 +13132,6 @@ async function actionUpgrade(opts) {
13930
13132
  return actionStart(opts);
13931
13133
  }
13932
13134
 
13933
- async function actionSupervisor(opts) {
13934
- opts.isolated = true;
13935
- opts.port = String(opts.port || getSupervisorPort());
13936
- discoverPlugins();
13937
- return actionForeground(opts);
13938
- }
13939
-
13940
13135
  export async function runServe(opts) {
13941
13136
  const action = opts.action || "foreground";
13942
13137
 
@@ -13947,13 +13142,12 @@ export async function runServe(opts) {
13947
13142
  case "ping": return actionPing();
13948
13143
  case "foreground": return actionForeground(opts);
13949
13144
  case "mcp": return actionMcp(opts);
13950
- case "supervisor": return actionSupervisor(opts);
13951
13145
  case "install": return actionInstall(opts);
13952
13146
  case "uninstall": return actionUninstall();
13953
13147
  case "upgrade": return actionUpgrade(opts);
13954
13148
  default:
13955
13149
  console.log(chalk.red(`\n Unknown serve action: ${action}`));
13956
- console.log(chalk.gray(" Actions: start | stop | restart | ping | foreground | mcp | supervisor | install | uninstall | upgrade\n"));
13150
+ console.log(chalk.gray(" Actions: start | stop | restart | ping | foreground | mcp | install | uninstall | upgrade\n"));
13957
13151
  process.exit(1);
13958
13152
  }
13959
13153
  }