@lifeaitools/clauth 0.7.3 → 0.7.5

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.
@@ -154,6 +154,16 @@ function dashboardHtml(port, whitelist) {
154
154
  .btn-tunnel-stop{background:#1e293b;color:#f87171;border:1px solid #334155;padding:6px 12px;font-size:.8rem;border-radius:6px;cursor:pointer;font-weight:500}
155
155
  .btn-tunnel-stop:hover{background:#2d1f1f;border-color:#f87171}
156
156
  .tunnel-err{font-size:.78rem;color:#f87171;width:100%;margin-top:4px}
157
+ .build-panel{background:#0f1a2d;border:1px solid #1e3a5f;border-radius:8px;padding:1rem 1.25rem;margin-bottom:1.25rem;display:flex;align-items:center;gap:12px;flex-wrap:wrap}
158
+ .build-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
159
+ .build-dot.idle{background:#64748b}
160
+ .build-dot.building{background:#3b82f6;animation:pulse 1s infinite}
161
+ .build-dot.succeeded{background:#22c55e}
162
+ .build-dot.failed{background:#ef4444}
163
+ .build-dot.timeout{background:#f59e0b}
164
+ .build-label{font-size:.85rem;color:#94a3b8;flex:1}
165
+ .build-label strong{color:#e2e8f0}
166
+ .build-meta{font-family:'Courier New',monospace;font-size:.75rem;color:#64748b}
157
167
  .mcp-setup{background:#0f1d2d;border:1px solid #1e3a5f;border-radius:8px;padding:1rem 1.25rem;margin-bottom:1.25rem;display:none}
158
168
  .mcp-setup.open{display:block}
159
169
  .mcp-setup-title{font-size:.85rem;font-weight:600;color:#e2e8f0;margin-bottom:.75rem}
@@ -216,6 +226,15 @@ function dashboardHtml(port, whitelist) {
216
226
  <div>Services: <span id="s-services">${whitelist ? whitelist.join(", ") : "all"}</span></div>
217
227
  <div>Failures: <span id="s-fails">—</span></div>
218
228
  </div>
229
+ <div class="build-panel" id="build-panel">
230
+ <div class="build-dot idle" id="build-dot"></div>
231
+ <div class="build-label" id="build-label">
232
+ <strong>Deploy</strong> — idle
233
+ </div>
234
+ <span id="build-sha" style="font-family:'Courier New',monospace;font-size:.72rem;color:#60a5fa;background:#0a0f1a;border:1px solid #1e3a5f;border-radius:4px;padding:2px 8px;letter-spacing:.3px"></span>
235
+ <div class="build-meta" id="build-meta" style="width:100%;margin-top:4px"></div>
236
+ </div>
237
+
219
238
  <div class="toolbar">
220
239
  <button class="btn-refresh" onclick="loadServices()">↻ Refresh</button>
221
240
  <button class="btn-add" onclick="toggleAddService()">+ Add Service</button>
@@ -413,6 +432,7 @@ function showMain(ping) {
413
432
  ping.failures + "/" + (ping.failures + ping.failures_remaining);
414
433
  }
415
434
  pollTunnel();
435
+ updateBuildStatus();
416
436
  }
417
437
 
418
438
  // ── Unlock ──────────────────────────────────
@@ -1001,6 +1021,42 @@ function copyMcp(elId) {
1001
1021
  }).catch(() => {});
1002
1022
  }
1003
1023
 
1024
+ // ── Build Status ──────────────────────────────────
1025
+ async function updateBuildStatus() {
1026
+ try {
1027
+ const data = await fetch(BASE + "/builds").then(r => r.json());
1028
+ const dot = document.getElementById("build-dot");
1029
+ const label = document.getElementById("build-label");
1030
+ const meta = document.getElementById("build-meta");
1031
+ const sha = document.getElementById("build-sha");
1032
+
1033
+ // Update dot class
1034
+ dot.className = "build-dot " + (data.status || "idle");
1035
+
1036
+ // Update label
1037
+ const statusText = {
1038
+ idle: "idle", building: "building…", succeeded: "deployed", failed: "FAILED", timeout: "timeout"
1039
+ }[data.status] || data.status || "idle";
1040
+ label.innerHTML = "<strong>Deploy</strong> — " + statusText;
1041
+
1042
+ // Update SHA badge
1043
+ sha.textContent = data.commit ? data.commit.slice(0, 7) : "";
1044
+
1045
+ // Update meta
1046
+ const parts = [];
1047
+ if (data.message) parts.push(data.message.slice(0, 60));
1048
+ if (data.finished_at) {
1049
+ const ago = Math.floor((Date.now() - new Date(data.finished_at).getTime()) / 60000);
1050
+ parts.push(ago < 1 ? "just now" : ago < 60 ? ago + "m ago" : Math.floor(ago/60) + "h ago");
1051
+ } else if (data.started_at) {
1052
+ const ago = Math.floor((Date.now() - new Date(data.started_at).getTime()) / 60000);
1053
+ parts.push(ago < 1 ? "just started" : ago + "m");
1054
+ }
1055
+ meta.textContent = parts.join(" · ");
1056
+ } catch {}
1057
+ }
1058
+ setInterval(updateBuildStatus, 10000);
1059
+
1004
1060
  boot();
1005
1061
  </script>
1006
1062
  </body>
@@ -1223,6 +1279,43 @@ function createServer(initPassword, whitelist, port, tunnelHostname = null) {
1223
1279
  res.end(JSON.stringify(data));
1224
1280
  }
1225
1281
 
1282
+ // ── Build status (populated via Supabase Realtime) ─────────
1283
+ let buildStatus = { active: false, status: "idle", apps: [], updated_at: null };
1284
+
1285
+ (async () => {
1286
+ try {
1287
+ const sbUrl = (api.getBaseUrl() || "").replace("/functions/v1/auth-vault", "");
1288
+ const sbKey = api.getAnonKey();
1289
+ if (!sbUrl || !sbKey) return;
1290
+
1291
+ const { createClient: createSB } = await import("@supabase/supabase-js");
1292
+ const sb = createSB(sbUrl, sbKey);
1293
+
1294
+ // Initial fetch
1295
+ const { data } = await sb.from("prt_storage").select("value").eq("key", "build_status").single();
1296
+ if (data?.value) buildStatus = typeof data.value === "string" ? JSON.parse(data.value) : data.value;
1297
+
1298
+ // Realtime subscription
1299
+ sb.channel("build-status")
1300
+ .on("postgres_changes", { event: "*", schema: "public", table: "prt_storage", filter: "key=eq.build_status" },
1301
+ (payload) => {
1302
+ const val = payload.new?.value;
1303
+ if (val) {
1304
+ buildStatus = typeof val === "string" ? JSON.parse(val) : val;
1305
+ const logMsg = `[${new Date().toISOString()}] Build status update: ${buildStatus.status} (${buildStatus.commit?.slice(0,8) || "?"})\n`;
1306
+ try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
1307
+ }
1308
+ })
1309
+ .subscribe();
1310
+
1311
+ const logMsg = `[${new Date().toISOString()}] Build status: Realtime subscription active\n`;
1312
+ try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
1313
+ } catch (err) {
1314
+ const logMsg = `[${new Date().toISOString()}] Build status subscription failed: ${err.message}\n`;
1315
+ try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
1316
+ }
1317
+ })();
1318
+
1226
1319
  const server = http.createServer(async (req, res) => {
1227
1320
  const remote = req.socket.remoteAddress;
1228
1321
  const isLocal = remote === "127.0.0.1" || remote === "::1" || remote === "::ffff:127.0.0.1";
@@ -1596,6 +1689,11 @@ function createServer(initPassword, whitelist, port, tunnelHostname = null) {
1596
1689
  });
1597
1690
  }
1598
1691
 
1692
+ // GET /builds — CI build status (no auth required, same as /ping)
1693
+ if (method === "GET" && reqPath === "/builds") {
1694
+ return ok(res, buildStatus);
1695
+ }
1696
+
1599
1697
  // GET /mcp-setup — OAuth credentials for claude.ai MCP setup (localhost only)
1600
1698
  if (method === "GET" && reqPath === "/mcp-setup") {
1601
1699
  return ok(res, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "0.7.3",
3
+ "version": "0.7.5",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {