@lifeaitools/clauth 0.7.4 → 0.7.6

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>
@@ -1226,39 +1282,31 @@ function createServer(initPassword, whitelist, port, tunnelHostname = null) {
1226
1282
  // ── Build status (populated via Supabase Realtime) ─────────
1227
1283
  let buildStatus = { active: false, status: "idle", apps: [], updated_at: null };
1228
1284
 
1229
- (async () => {
1285
+ // Poll Supabase REST API for build status (no extra deps needed)
1286
+ async function fetchBuildStatus() {
1230
1287
  try {
1231
1288
  const sbUrl = (api.getBaseUrl() || "").replace("/functions/v1/auth-vault", "");
1232
1289
  const sbKey = api.getAnonKey();
1233
1290
  if (!sbUrl || !sbKey) return;
1234
1291
 
1235
- const { createClient: createSB } = await import("@supabase/supabase-js");
1236
- const sb = createSB(sbUrl, sbKey);
1237
-
1238
- // Initial fetch
1239
- const { data } = await sb.from("prt_storage").select("value").eq("key", "build_status").single();
1240
- if (data?.value) buildStatus = typeof data.value === "string" ? JSON.parse(data.value) : data.value;
1241
-
1242
- // Realtime subscription
1243
- sb.channel("build-status")
1244
- .on("postgres_changes", { event: "*", schema: "public", table: "prt_storage", filter: "key=eq.build_status" },
1245
- (payload) => {
1246
- const val = payload.new?.value;
1247
- if (val) {
1248
- buildStatus = typeof val === "string" ? JSON.parse(val) : val;
1249
- const logMsg = `[${new Date().toISOString()}] Build status update: ${buildStatus.status} (${buildStatus.commit?.slice(0,8) || "?"})\n`;
1250
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
1251
- }
1252
- })
1253
- .subscribe();
1254
-
1255
- const logMsg = `[${new Date().toISOString()}] Build status: Realtime subscription active\n`;
1256
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
1257
- } catch (err) {
1258
- const logMsg = `[${new Date().toISOString()}] Build status subscription failed: ${err.message}\n`;
1259
- try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
1260
- }
1261
- })();
1292
+ const r = await fetch(
1293
+ `${sbUrl}/rest/v1/prt_storage?key=eq.build_status&select=value`,
1294
+ { headers: { apikey: sbKey, Authorization: `Bearer ${sbKey}` }, signal: AbortSignal.timeout(5000) }
1295
+ );
1296
+ if (!r.ok) return;
1297
+ const rows = await r.json();
1298
+ if (rows.length > 0 && rows[0].value) {
1299
+ const prev = buildStatus.status;
1300
+ buildStatus = typeof rows[0].value === "string" ? JSON.parse(rows[0].value) : rows[0].value;
1301
+ if (buildStatus.status !== prev) {
1302
+ const logMsg = `[${new Date().toISOString()}] Build status: ${buildStatus.status} (${buildStatus.commit?.slice(0,8) || "?"})\n`;
1303
+ try { fs.appendFileSync(LOG_FILE, logMsg); } catch {}
1304
+ }
1305
+ }
1306
+ } catch {}
1307
+ }
1308
+ fetchBuildStatus();
1309
+ setInterval(fetchBuildStatus, 15000);
1262
1310
 
1263
1311
  const server = http.createServer(async (req, res) => {
1264
1312
  const remote = req.socket.remoteAddress;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {