// Single public entrypoint for HF Spaces: local dashboard + reverse proxy to OpenClaw. const http = require("http"); const fs = require("fs"); const net = require("net"); const PORT = 7861; const GATEWAY_PORT = 7860; const GATEWAY_HOST = "127.0.0.1"; const startTime = Date.now(); const LLM_MODEL = process.env.LLM_MODEL || "Not Set"; const TELEGRAM_ENABLED = !!process.env.TELEGRAM_BOT_TOKEN; const WHATSAPP_ENABLED = /^true$/i.test(process.env.WHATSAPP_ENABLED || ""); const WHATSAPP_STATUS_FILE = "/tmp/huggingclaw-wa-status.json"; const HF_BACKUP_ENABLED = !!process.env.HF_TOKEN; const SYNC_INTERVAL = process.env.SYNC_INTERVAL || "180"; const APP_BASE = "/app"; const SYNC_STATUS_FILE = "/tmp/sync-status.json"; const CLOUDFLARE_KEEPALIVE_STATUS_FILE = "/tmp/huggingclaw-cloudflare-keepalive-status.json"; function parseRequestUrl(url) { try { return new URL(url, "http://localhost"); } catch { return new URL("http://localhost/"); } } function getSyncStatus() { try { if (fs.existsSync(SYNC_STATUS_FILE)) { return JSON.parse(fs.readFileSync(SYNC_STATUS_FILE, "utf8")); } } catch {} if (HF_BACKUP_ENABLED) { return { status: "configured", message: `Backup is enabled. Waiting for sync window (${SYNC_INTERVAL}s).`, }; } return { status: "unknown", message: "No sync data yet" }; } function readGuardianStatus() { if (!WHATSAPP_ENABLED) { return { configured: false, connected: false, pairing: false }; } try { if (fs.existsSync(WHATSAPP_STATUS_FILE)) { const parsed = JSON.parse(fs.readFileSync(WHATSAPP_STATUS_FILE, "utf8")); return { configured: parsed.configured !== false, connected: parsed.connected === true, pairing: parsed.pairing === true, }; } } catch {} return { configured: true, connected: false, pairing: false }; } function getKeepaliveStatus() { try { if (fs.existsSync(CLOUDFLARE_KEEPALIVE_STATUS_FILE)) { return JSON.parse( fs.readFileSync(CLOUDFLARE_KEEPALIVE_STATUS_FILE, "utf8"), ); } } catch {} return null; } function probeGatewayHealth(timeoutMs = 1500) { return new Promise((resolve) => { const request = http.get( { hostname: GATEWAY_HOST, port: GATEWAY_PORT, path: "/health", timeout: timeoutMs, }, (response) => { response.resume(); resolve(response.statusCode >= 200 && response.statusCode < 400); }, ); request.on("timeout", () => { request.destroy(); resolve(false); }); request.on("error", () => resolve(false)); }); } function formatUptime(ms) { const total = Math.floor(ms / 1000); const days = Math.floor(total / 86400); const hours = Math.floor((total % 86400) / 3600); const minutes = Math.floor((total % 3600) / 60); if (days) return `${days}d ${hours}h ${minutes}m`; if (hours) return `${hours}h ${minutes}m`; return `${minutes}m`; } function escapeHtml(value) { return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function toneBadge(label, tone = "neutral") { return `${escapeHtml(label)}`; } function renderTile({ title, value, detail = "", tone = "neutral", meta = "", }) { return `
${escapeHtml(title)}
${value}
${detail ? `
${detail}
` : ""} ${meta ? `
${meta}
` : ""}
`; } function renderDashboard(data) { const syncStatus = String(data.sync?.status || "unknown"); const syncTone = ["success", "restored", "synced", "configured"].includes( syncStatus, ) ? "ok" : syncStatus === "disabled" ? "warn" : "neutral"; const backupDetail = data.sync?.message ? escapeHtml(data.sync.message) : "No status yet"; const keepaliveConfigured = data.keepalive?.configured === true; const keepaliveStatus = String( data.keepalive?.status || (process.env.CLOUDFLARE_WORKERS_TOKEN ? "pending" : "not configured"), ); const keepAliveTone = keepaliveConfigured ? "ok" : process.env.CLOUDFLARE_WORKERS_TOKEN ? "warn" : "neutral"; const keepAliveDetail = keepaliveConfigured ? `Pinging ${escapeHtml(data.keepalive.targetUrl || "/health")}` : process.env.CLOUDFLARE_WORKERS_TOKEN ? "Worker pending or failed" : "Not configured"; const tiles = [ renderTile({ title: "Gateway", value: toneBadge( data.gatewayReady ? "Online" : "Offline", data.gatewayReady ? "ok" : "off", ), detail: `Internal Port ${GATEWAY_PORT}`, tone: data.gatewayReady ? "ok" : "off", }), renderTile({ title: "Model", value: `${escapeHtml(LLM_MODEL)}`, detail: `Primary LLM Configured`, tone: "neutral", }), renderTile({ title: "Runtime", value: escapeHtml(data.uptimeHuman), detail: `Public Port ${PORT}`, tone: "neutral", }), renderTile({ title: "Telegram", value: toneBadge( TELEGRAM_ENABLED ? "Enabled" : "Disabled", TELEGRAM_ENABLED ? "ok" : "neutral", ), detail: TELEGRAM_ENABLED ? "Bot Channel active" : "Not configured", tone: TELEGRAM_ENABLED ? "ok" : "neutral", }), renderTile({ title: "Backup", value: toneBadge(syncStatus.toUpperCase(), syncTone), detail: backupDetail, tone: syncTone, meta: data.sync?.timestamp ? `` : "", }), renderTile({ title: "Keep Awake", value: toneBadge( keepaliveConfigured ? "CF Cron" : keepaliveStatus.toUpperCase(), keepAliveTone, ), detail: keepAliveDetail, tone: keepAliveTone, }), ].join(""); return ` HuggingClaw

🦞 HuggingClaw

OpenClaw Gateway Dashboard
Open Control UI ->
${tiles}
`; } const server = http.createServer(async (req, res) => { const url = parseRequestUrl(req.url); const pathname = url.pathname; // 1. Dashboard Routes if (pathname === "/health") { const gatewayReady = await probeGatewayHealth(); res.writeHead(gatewayReady ? 200 : 503, { "Content-Type": "application/json", }); return res.end( JSON.stringify({ status: gatewayReady ? "ok" : "degraded", gatewayReady, uptime: formatUptime(Date.now() - startTime), sync: getSyncStatus(), keepalive: getKeepaliveStatus(), }), ); } if (pathname === "/status") { const gatewayReady = await probeGatewayHealth(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end( JSON.stringify({ model: LLM_MODEL, uptime: formatUptime(Date.now() - startTime), gatewayReady, sync: getSyncStatus(), whatsapp: readGuardianStatus(), keepalive: getKeepaliveStatus(), }), ); } if (pathname === "/" || pathname === "/dashboard") { const gatewayReady = await probeGatewayHealth(); res.writeHead(200, { "Content-Type": "text/html" }); return res.end( renderDashboard({ uptimeHuman: formatUptime(Date.now() - startTime), gatewayReady, sync: getSyncStatus(), whatsapp: readGuardianStatus(), keepalive: getKeepaliveStatus(), }), ); } // 2. OpenClaw Proxy Logic const proxyHeaders = { ...req.headers, host: `${GATEWAY_HOST}:${GATEWAY_PORT}`, "x-forwarded-for": req.socket.remoteAddress, "x-forwarded-host": req.headers.host, "x-forwarded-proto": "https", }; const proxyReq = http.request( { hostname: GATEWAY_HOST, port: GATEWAY_PORT, path: pathname + url.search, method: req.method, headers: proxyHeaders, }, (proxyRes) => { res.writeHead(proxyRes.statusCode, proxyRes.headers); proxyRes.pipe(res); proxyRes.on("error", (err) => { console.error("proxyRes error:", err); res.end(); }); }, ); req.on("error", (err) => { console.error("req error:", err); proxyReq.destroy(); }); res.on("error", (err) => { console.error("res error:", err); proxyReq.destroy(); }); proxyReq.on("error", (err) => { console.error("proxyReq error:", err); if (!res.headersSent) { res.writeHead(503, { "Content-Type": "application/json" }); res.end( JSON.stringify({ status: "starting", message: "Gateway is initializing... or connection failed", }), ); } else { res.end(); } }); req.pipe(proxyReq); }); server.on("upgrade", (req, socket, head) => { const url = parseRequestUrl(req.url); const proxyPath = url.pathname; const proxySocket = net.connect(GATEWAY_PORT, GATEWAY_HOST, () => { proxySocket.write( `${req.method} ${proxyPath}${url.search} HTTP/${req.httpVersion}\r\n`, ); for (let i = 0; i < req.rawHeaders.length; i += 2) { proxySocket.write(`${req.rawHeaders[i]}: ${req.rawHeaders[i + 1]}\r\n`); } proxySocket.write("\r\n"); if (head && head.length) proxySocket.write(head); proxySocket.pipe(socket).pipe(proxySocket); }); proxySocket.on("error", () => socket.destroy()); }); server.timeout = 0; server.keepAliveTimeout = 65000; server.listen(PORT, "0.0.0.0", () => console.log( `🦞 HuggingClaw Dashboard on ${PORT} -> Gateway on ${GATEWAY_PORT}`, ), );