@lifeaitools/clauth 1.30.10 → 1.30.11

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.
@@ -30,7 +30,9 @@ import {
30
30
  } from "../watchdog-registry.js";
31
31
  import {
32
32
  discoverPlugins,
33
+ getSupervisorDir,
33
34
  getSupervisorPort,
35
+ loadSupervisorState,
34
36
  listPlugins,
35
37
  listRoutes,
36
38
  listSurfaces,
@@ -587,6 +589,65 @@ export function isLoopbackAddress(remote) {
587
589
  return remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
588
590
  }
589
591
 
592
+ function summarizeSupervisorTarget(target) {
593
+ if (!target || typeof target !== "object") return null;
594
+ return {
595
+ plugin_id: typeof target.plugin_id === "string" ? target.plugin_id : undefined,
596
+ surface_id: typeof target.surface_id === "string" ? target.surface_id : undefined,
597
+ tunnel_id: typeof target.tunnel_id === "string" ? target.tunnel_id : undefined,
598
+ route_id: typeof target.route_id === "string" ? target.route_id : undefined,
599
+ };
600
+ }
601
+
602
+ function summarizeSupervisorState(value) {
603
+ if (!value || typeof value !== "object") return null;
604
+ return {
605
+ ok: typeof value.ok === "boolean" ? value.ok : undefined,
606
+ state: typeof value.state === "string" ? value.state : undefined,
607
+ reason: typeof value.reason === "string" ? value.reason : undefined,
608
+ status: typeof value.status === "number" ? value.status : undefined,
609
+ private: typeof value.private === "boolean" ? value.private : undefined,
610
+ public_route: typeof value.public_route === "boolean" ? value.public_route : undefined,
611
+ evidence: Array.isArray(value.evidence) ? value.evidence.map((item) => String(item).slice(0, 200)).slice(0, 5) : [],
612
+ };
613
+ }
614
+
615
+ export function supervisorLogDto(event) {
616
+ if (!event || typeof event !== "object") return { kind: "unknown" };
617
+ if (event.kind === "operation") {
618
+ return {
619
+ ts: event.ts || event.created_at,
620
+ kind: "operation",
621
+ operationId: event.operationId,
622
+ actor: event.actor,
623
+ action: event.action,
624
+ target: summarizeSupervisorTarget(event.target),
625
+ resulting_state: summarizeSupervisorState(event.resulting_state),
626
+ completed_at: event.completed_at,
627
+ };
628
+ }
629
+ return {
630
+ ts: event.ts,
631
+ kind: event.kind,
632
+ plugin_id: event.plugin_id,
633
+ source: event.source,
634
+ state: event.state,
635
+ };
636
+ }
637
+
638
+ function supervisorOperationDto(receipt) {
639
+ return {
640
+ operationId: receipt.operationId,
641
+ actor: receipt.actor,
642
+ action: receipt.action,
643
+ target: summarizeSupervisorTarget(receipt.target),
644
+ resulting_state: summarizeSupervisorState(receipt.resulting_state),
645
+ evidence: Array.isArray(receipt.evidence) ? receipt.evidence.map((item) => String(item).slice(0, 200)).slice(0, 5) : [],
646
+ created_at: receipt.created_at,
647
+ completed_at: receipt.completed_at,
648
+ };
649
+ }
650
+
590
651
  export function supervisorRequiresWriteToken(port = getSupervisorPort(), env = process.env) {
591
652
  if (port !== getSupervisorPort()) return true;
592
653
  return env.CLAUTH_SUPERVISOR_REQUIRE_WRITE_TOKEN === "1";
@@ -905,8 +966,30 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
905
966
  .wiz-link:hover{text-decoration:underline}
906
967
  .wiz-test-result{padding:12px 14px;border-radius:8px;font-size:.85rem;margin-top:10px;display:none}
907
968
  .wiz-test-result.ok{background:rgba(74,222,128,.08);border:1px solid rgba(74,222,128,.2);color:#4ade80}
908
- .wiz-test-result.fail{background:rgba(248,113,113,.08);border:1px solid rgba(248,113,113,.2);color:#f87171}
909
- </style>
969
+ .wiz-test-result.fail{background:rgba(248,113,113,.08);border:1px solid rgba(248,113,113,.2);color:#f87171}
970
+ .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}
971
+ .supervisor-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;margin-bottom:12px}
972
+ .supervisor-title{font-size:.95rem;font-weight:700;color:#e2e8f0}
973
+ .supervisor-sub{font-size:.76rem;color:#94a3b8;margin-top:3px;line-height:1.35}
974
+ .supervisor-actions{display:flex;gap:8px;flex-wrap:wrap}
975
+ .supervisor-action{background:#0f172a;color:#cbd5e1;border:1px solid #334155;border-radius:6px;padding:6px 10px;font-size:.75rem;cursor:pointer}
976
+ .supervisor-action:hover{border-color:#38bdf8;color:#e0f2fe}
977
+ .supervisor-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:10px}
978
+ .supervisor-card{border:1px solid #1e293b;background:rgba(2,6,23,.52);border-radius:10px;padding:10px;min-width:0}
979
+ .supervisor-card h4{margin:0 0 8px;color:#cbd5e1;font-size:.75rem;letter-spacing:.08em;text-transform:uppercase}
980
+ .supervisor-kpi{font-size:1.35rem;font-weight:700;color:#f8fafc}
981
+ .supervisor-meta{font-size:.72rem;color:#94a3b8;font-family:'Courier New',monospace;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
982
+ .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}
983
+ .supervisor-pill.ok{border-color:rgba(74,222,128,.35);color:#86efac;background:rgba(34,197,94,.08)}
984
+ .supervisor-pill.warn{border-color:rgba(250,204,21,.35);color:#fde68a;background:rgba(250,204,21,.08)}
985
+ .supervisor-pill.bad{border-color:rgba(248,113,113,.35);color:#fecaca;background:rgba(248,113,113,.08)}
986
+ .supervisor-list{display:flex;flex-direction:column;gap:8px;max-height:320px;overflow:auto}
987
+ .supervisor-row{border:1px solid #1e293b;border-radius:8px;padding:8px;background:rgba(15,23,42,.45)}
988
+ .supervisor-row-top{display:flex;justify-content:space-between;gap:8px;align-items:center}
989
+ .supervisor-name{font-size:.82rem;color:#e2e8f0;font-weight:600;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
990
+ .supervisor-row-actions{display:flex;gap:5px;flex-wrap:wrap;margin-top:7px}
991
+ .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}
992
+ </style>
910
993
  </head>
911
994
  <body>
912
995
 
@@ -1094,7 +1177,7 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
1094
1177
  </div>
1095
1178
  </div>
1096
1179
 
1097
- <div class="monkey-panel" id="monkey-panel">
1180
+ <div class="monkey-panel" id="monkey-panel">
1098
1181
  <div class="monkey-head">
1099
1182
  <div>
1100
1183
  <div class="monkey-title">Monkey local CLI dispatch</div>
@@ -1119,10 +1202,44 @@ function dashboardHtml(port, whitelist, isStaged = false, initWriteToken = null)
1119
1202
  <span class="monkey-status" id="monkey-status">idle</span>
1120
1203
  </div>
1121
1204
  <div class="monkey-result" id="monkey-result"></div>
1122
- </div>
1123
-
1124
-
1125
- <div class="wizard-panel" id="wizard-panel">
1205
+ </div>
1206
+
1207
+ <section class="supervisor-panel" id="supervisor-panel">
1208
+ <div class="supervisor-head">
1209
+ <div>
1210
+ <div class="supervisor-title">Local Software Factory Supervisor</div>
1211
+ <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>
1212
+ </div>
1213
+ <div class="supervisor-actions">
1214
+ <button class="supervisor-action" onclick="loadSupervisorCockpit()">Refresh</button>
1215
+ <button class="supervisor-action" onclick="rescanSupervisorPlugins()">Rescan plugins</button>
1216
+ </div>
1217
+ </div>
1218
+ <div id="supervisor-status" class="supervisor-sub">Loading supervisor state…</div>
1219
+ <div class="supervisor-grid" style="margin-top:10px">
1220
+ <div class="supervisor-card">
1221
+ <h4>Plugins</h4>
1222
+ <div class="supervisor-kpi" id="supervisor-plugin-count">—</div>
1223
+ <div class="supervisor-meta" id="supervisor-plugin-meta">awaiting data</div>
1224
+ <div class="supervisor-list" id="supervisor-plugins" style="margin-top:8px"></div>
1225
+ </div>
1226
+ <div class="supervisor-card">
1227
+ <h4>Surfaces</h4>
1228
+ <div class="supervisor-kpi" id="supervisor-surface-count">—</div>
1229
+ <div class="supervisor-meta" id="supervisor-surface-meta">destination + owner matrix</div>
1230
+ <div class="supervisor-list" id="supervisor-surfaces" style="margin-top:8px"></div>
1231
+ </div>
1232
+ <div class="supervisor-card">
1233
+ <h4>Operations + log</h4>
1234
+ <div class="supervisor-kpi" id="supervisor-operation-count">—</div>
1235
+ <div class="supervisor-meta" id="supervisor-log-path">events.jsonl</div>
1236
+ <div class="supervisor-log" id="supervisor-events" style="margin-top:8px">No events loaded.</div>
1237
+ </div>
1238
+ </div>
1239
+ </section>
1240
+
1241
+
1242
+ <div class="wizard-panel" id="wizard-panel">
1126
1243
  <div class="wizard-header">
1127
1244
  <div class="tunnel-dot off" id="wiz-dot" style="width:10px;height:10px;border-radius:50%"></div>
1128
1245
  <div class="wizard-title">claude.ai Tunnel Setup</div>
@@ -1372,10 +1489,89 @@ function showMain(ping) {
1372
1489
  document.getElementById("s-fails").textContent =
1373
1490
  ping.failures + "/" + (ping.failures + ping.failures_remaining);
1374
1491
  }
1375
- pollTunnel();
1376
- updateBuildStatus();
1377
- refreshWriteLockUi();
1378
- }
1492
+ pollTunnel();
1493
+ updateBuildStatus();
1494
+ loadSupervisorCockpit();
1495
+ refreshWriteLockUi();
1496
+ }
1497
+
1498
+ function supervisorBadge(text, kind) {
1499
+ return '<span class="supervisor-pill ' + (kind || '') + '">' + htmlEscape(text) + '</span>';
1500
+ }
1501
+
1502
+ async function supervisorJson(path, options) {
1503
+ const response = await fetch(BASE + path, { cache: "no-store", ...(options || {}) });
1504
+ const data = await response.json().catch(() => ({}));
1505
+ if (!response.ok || data.error) throw new Error(data.error || ("HTTP " + response.status));
1506
+ return data;
1507
+ }
1508
+
1509
+ async function loadSupervisorCockpit() {
1510
+ const status = document.getElementById("supervisor-status");
1511
+ if (status) status.textContent = "Loading supervisor state…";
1512
+ try {
1513
+ const [health, plugins, surfaces, logs] = await Promise.all([
1514
+ supervisorJson("/health"),
1515
+ supervisorJson("/v1/plugins"),
1516
+ supervisorJson("/v1/surfaces"),
1517
+ supervisorJson("/v1/logs?limit=40"),
1518
+ ]);
1519
+ const pluginRows = plugins.plugins || [];
1520
+ const surfaceRows = surfaces.surfaces || [];
1521
+ document.getElementById("supervisor-plugin-count").textContent = String(pluginRows.length);
1522
+ document.getElementById("supervisor-surface-count").textContent = String(surfaceRows.length);
1523
+ document.getElementById("supervisor-operation-count").textContent = String((logs.operations || []).length);
1524
+ document.getElementById("supervisor-plugin-meta").textContent = "current " + (pluginRows.filter(p => p.state === "current").length) + " · awaiting " + (pluginRows.filter(p => p.state === "awaiting_enable").length);
1525
+ document.getElementById("supervisor-surface-meta").textContent = "pm2 home: " + (health.pm2_home || "—");
1526
+ document.getElementById("supervisor-log-path").textContent = logs.log_path || "events.jsonl";
1527
+ document.getElementById("supervisor-plugins").innerHTML = pluginRows.length ? pluginRows.map(renderSupervisorPlugin).join("") : '<div class="supervisor-sub">No plugins discovered.</div>';
1528
+ document.getElementById("supervisor-surfaces").innerHTML = surfaceRows.length ? surfaceRows.map(renderSupervisorSurface).join("") : '<div class="supervisor-sub">No surfaces declared.</div>';
1529
+ document.getElementById("supervisor-events").textContent = (logs.events || []).slice(-20).reverse().map(e => {
1530
+ const label = e.kind === "operation" ? (e.action + " " + JSON.stringify(e.target || {})) : (e.kind + " " + (e.plugin_id || ""));
1531
+ return (e.ts || e.created_at || "") + " " + label;
1532
+ }).join("\\n") || "No events yet.";
1533
+ if (status) status.innerHTML = supervisorBadge("supervisor " + (health.status || "unknown"), "ok") + supervisorBadge("vault " + (health.vault_locked ? "locked" : "unlocked"), health.vault_locked ? "warn" : "ok") + supervisorBadge("v" + (health.clauth_version || "${VERSION}"), "");
1534
+ } catch (err) {
1535
+ if (status) status.innerHTML = supervisorBadge("supervisor unreachable", "bad") + " " + htmlEscape(err.message || err);
1536
+ }
1537
+ }
1538
+
1539
+ function renderSupervisorPlugin(plugin) {
1540
+ const stateKind = plugin.state === "current" ? "ok" : (plugin.state === "manifest_invalid" || plugin.state === "missing" ? "bad" : "warn");
1541
+ return '<div class="supervisor-row"><div class="supervisor-row-top"><span class="supervisor-name">' + htmlEscape(plugin.id) + '</span>' + supervisorBadge(plugin.state || "unknown", stateKind) + '</div>' +
1542
+ '<div class="supervisor-meta">' + htmlEscape(plugin.publisher || "unknown") + ' · ' + htmlEscape(plugin.version || "—") + ' · ' + htmlEscape(plugin.source || "—") + '</div>' +
1543
+ '<div class="supervisor-meta">' + htmlEscape((plugin.manifest_hash || "").slice(0, 12)) + '</div>' +
1544
+ '<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>';
1545
+ }
1546
+
1547
+ function renderSupervisorSurface(surface) {
1548
+ const ownerKind = surface.lifecycle_owner === "clauth" ? "ok" : (surface.lifecycle_owner === "plugin" ? "warn" : "");
1549
+ 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) + '</div>' +
1550
+ '<div class="supervisor-meta">' + htmlEscape(surface.destination || "—") + ' · port ' + htmlEscape(surface.port || "—") + '</div>' +
1551
+ '<div class="supervisor-meta">' + htmlEscape(surface.health || surface.health_url || "no health") + '</div>' +
1552
+ '<div class="supervisor-row-actions">' + ["reconcile","restart","test","rollback"].map(a => '<button class="supervisor-action" onclick="runSupervisorSurface(' + jsArg(surface.id) + ',' + jsArg(a) + ')">' + a + '</button>').join("") + '</div></div>';
1553
+ }
1554
+
1555
+ async function rescanSupervisorPlugins() {
1556
+ try {
1557
+ await supervisorJson("/v1/plugins/rescan", { method: "POST", headers: writeHeaders() });
1558
+ await loadSupervisorCockpit();
1559
+ } catch (err) { alert("Supervisor rescan failed: " + (err.message || err)); }
1560
+ }
1561
+
1562
+ async function runSupervisorPlugin(id, action) {
1563
+ try {
1564
+ await supervisorJson("/v1/plugins/" + encodeURIComponent(id) + "/" + encodeURIComponent(action), { method: "POST", headers: writeHeaders() });
1565
+ await loadSupervisorCockpit();
1566
+ } catch (err) { alert("Plugin " + action + " failed: " + (err.message || err)); }
1567
+ }
1568
+
1569
+ async function runSupervisorSurface(id, action) {
1570
+ try {
1571
+ await supervisorJson("/v1/surfaces/" + encodeURIComponent(id) + "/actions", { method: "POST", headers: writeHeaders({ "Content-Type": "application/json" }), body: JSON.stringify({ action }) });
1572
+ await loadSupervisorCockpit();
1573
+ } catch (err) { alert("Surface " + action + " failed: " + (err.message || err)); }
1574
+ }
1379
1575
 
1380
1576
  // ── Unlock ──────────────────────────────────
1381
1577
  async function unlock() {
@@ -3710,6 +3906,18 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3710
3906
  return ok(res, { tunnels: listTunnels() });
3711
3907
  }
3712
3908
 
3909
+ if (method === "GET" && reqPath === "/v1/logs") {
3910
+ const limit = Number(url.searchParams.get("limit") || 100);
3911
+ const boundedLimit = Number.isFinite(limit) ? Math.max(1, Math.min(limit, 500)) : 100;
3912
+ const state = loadSupervisorState();
3913
+ return ok(res, {
3914
+ schema: "clauth.supervisor.logs.v1",
3915
+ log_path: path.join(getSupervisorDir(), "events.jsonl"),
3916
+ events: readSupervisorEvents(boundedLimit).map(supervisorLogDto),
3917
+ operations: (state.operations || []).slice(0, boundedLimit).map(supervisorOperationDto),
3918
+ });
3919
+ }
3920
+
3713
3921
  const tunnelRoutesMatch = reqPath.match(/^\/v1\/tunnels\/([^/]+)\/routes$/);
3714
3922
  if (method === "POST" && tunnelRoutesMatch) {
3715
3923
  if (!hasSupervisorWrite(req)) return rejectSupervisorWrite(res);
@@ -3731,7 +3939,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3731
3939
  const id = decodeURIComponent(reqPath.split("/").pop());
3732
3940
  const operation = (supervisorHealth(), readSupervisorEvents(500)).find((event) => event.operationId === id);
3733
3941
  res.writeHead(operation ? 200 : 404, { "Content-Type": "application/json", ...CORS });
3734
- return res.end(JSON.stringify(operation || { error: "operation_not_found" }));
3942
+ return res.end(JSON.stringify(operation ? supervisorLogDto(operation) : { error: "operation_not_found" }));
3735
3943
  }
3736
3944
 
3737
3945
  if (method === "GET" && reqPath === "/v1/events") {
@@ -3742,7 +3950,7 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3742
3950
  ...CORS,
3743
3951
  });
3744
3952
  for (const event of readSupervisorEvents(Number(url.searchParams.get("limit") || 100))) {
3745
- res.write(`event: supervisor\ndata: ${JSON.stringify(event)}\n\n`);
3953
+ res.write(`event: supervisor\ndata: ${JSON.stringify(supervisorLogDto(event))}\n\n`);
3746
3954
  }
3747
3955
  res.end();
3748
3956
  return;
@@ -17,7 +17,7 @@ import {
17
17
  supervisorHealth,
18
18
  validatePluginManifest,
19
19
  } from "./supervisor-registry.js";
20
- import { isLoopbackAddress, supervisorRequiresWriteToken } from "./commands/serve.js";
20
+ import { isLoopbackAddress, supervisorLogDto, supervisorRequiresWriteToken } from "./commands/serve.js";
21
21
 
22
22
  function withTempSupervisor(fn) {
23
23
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "clauth-supervisor-"));
@@ -187,6 +187,35 @@ test("supervisor write-token policy is temporarily relaxed only for the localhos
187
187
  assert.equal(isLoopbackAddress("192.168.1.25"), false);
188
188
  });
189
189
 
190
+ test("supervisor log DTO strips raw operation payloads", () => {
191
+ const dto = supervisorLogDto({
192
+ ts: "2026-07-30T00:00:00.000Z",
193
+ kind: "operation",
194
+ operationId: "op-secret",
195
+ actor: "localhost",
196
+ action: "restart",
197
+ target: { surface_id: "demo", command: ["node", "--token=secret"] },
198
+ prior_state: { start: ["node", "--token=secret"], cwd: "C:/secrets" },
199
+ resulting_state: {
200
+ ok: false,
201
+ state: "operation_failed",
202
+ stderr: "SECRET=abc123",
203
+ evidence: ["CLAUTH_PM2_HOME=C:/safe"],
204
+ },
205
+ stderr: "SECRET=abc123",
206
+ completed_at: "2026-07-30T00:00:01.000Z",
207
+ });
208
+
209
+ const json = JSON.stringify(dto);
210
+ assert.equal(dto.operationId, "op-secret");
211
+ assert.equal(dto.target.surface_id, "demo");
212
+ assert.equal(dto.resulting_state.state, "operation_failed");
213
+ assert.equal(json.includes("prior_state"), false);
214
+ assert.equal(json.includes("stderr"), false);
215
+ assert.equal(json.includes("SECRET"), false);
216
+ assert.equal(json.includes("--token"), false);
217
+ });
218
+
190
219
  test("tunnel route add and remove produce reversible operation receipts", () => withTempSupervisor((root) => {
191
220
  process.env.CLAUTH_MANAGED_PLUGIN_ROOTS = path.join(root, "managed");
192
221
  process.env.CLAUTH_USER_PLUGIN_ROOTS = path.join(root, "user");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.30.10",
3
+ "version": "1.30.11",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {
Binary file
Binary file
Binary file