// Single public entrypoint for HF Spaces: local dashboard + reverse proxy to OpenClaw.
const http = require("http");
const https = require("https");
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_USERNAME && process.env.HF_TOKEN);
const SYNC_INTERVAL = process.env.SYNC_INTERVAL || "600";
const DASHBOARD_BASE = "/dashboard";
const DASHBOARD_STATUS_PATH = `${DASHBOARD_BASE}/status`;
const DASHBOARD_HEALTH_PATH = `${DASHBOARD_BASE}/health`;
const DASHBOARD_UPTIMEROBOT_PATH = `${DASHBOARD_BASE}/uptimerobot/setup`;
const DASHBOARD_APP_BASE = `${DASHBOARD_BASE}/app`;
const APP_BASE = "/app";
function parseRequestUrl(url) {
try {
return new URL(url, "http://localhost");
} catch {
return new URL("http://localhost/");
}
}
function isDashboardRoute(pathname) {
return (
pathname === "/" ||
pathname === DASHBOARD_BASE ||
pathname === `${DASHBOARD_BASE}/`
);
}
function isDashboardAppRoute(pathname) {
return (
pathname === DASHBOARD_APP_BASE ||
pathname.startsWith(`${DASHBOARD_APP_BASE}/`)
);
}
function isAppRoute(pathname) {
return pathname === APP_BASE || pathname.startsWith(`${APP_BASE}/`);
}
function isLocalRoute(pathname) {
return (
pathname === "/health" ||
pathname === "/status" ||
pathname === "/uptimerobot/setup" ||
pathname === DASHBOARD_HEALTH_PATH ||
pathname === DASHBOARD_STATUS_PATH ||
pathname === DASHBOARD_UPTIMEROBOT_PATH ||
isDashboardRoute(pathname)
);
}
function mapAppProxyPath(path) {
if (path === DASHBOARD_APP_BASE) return APP_BASE;
if (path.startsWith(`${DASHBOARD_APP_BASE}/`)) {
return `${APP_BASE}${path.slice(DASHBOARD_APP_BASE.length)}`;
}
if (path === APP_BASE || path.startsWith(`${APP_BASE}/`)) {
return path;
}
return path;
}
function appendForwarded(existingValue, nextValue) {
const cleanNext = nextValue || "";
if (!existingValue) return cleanNext;
if (Array.isArray(existingValue))
return `${existingValue.join(", ")}, ${cleanNext}`;
return `${existingValue}, ${cleanNext}`;
}
function buildProxyHeaders(headers, remoteAddress) {
return {
...headers,
host: headers.host || `${GATEWAY_HOST}:${GATEWAY_PORT}`,
"x-forwarded-for": appendForwarded(
headers["x-forwarded-for"],
remoteAddress,
),
"x-forwarded-host": headers["x-forwarded-host"] || headers.host || "",
"x-forwarded-proto": headers["x-forwarded-proto"] || "https",
};
}
function readSyncStatus() {
try {
if (fs.existsSync("/tmp/sync-status.json")) {
return JSON.parse(fs.readFileSync("/tmp/sync-status.json", "utf8"));
}
} catch {}
if (HF_BACKUP_ENABLED) {
return {
status: "configured",
message: `Backup is enabled. Waiting for the next sync window (${SYNC_INTERVAL}s).`,
};
}
return { status: "unknown", message: "No sync data yet" };
}
function normalizeChannelStatus(channel, configured) {
return {
configured: configured || !!channel,
connected: !!(channel && channel.connected),
};
}
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 renderChannelBadge(channel, configuredLabel) {
if (channel && channel.connected) {
return '
';
}
if (channel && channel.configured) {
return `${configuredLabel}
`;
}
return 'Disabled
';
}
function renderSyncBadge(syncData) {
let badgeClass = "status-offline";
let pulseHtml = "";
if (syncData.status === "success" || syncData.status === "configured") {
badgeClass = "status-online";
pulseHtml = '
';
} else if (syncData.status === "syncing") {
badgeClass = "status-syncing";
pulseHtml = '
';
}
return `${pulseHtml}${String(syncData.status || "unknown").toUpperCase()}
`;
}
function renderDashboard(initialData) {
const controlUiHref = `${APP_BASE}/`;
return `
HuggingClaw Dashboard
🦞 HuggingClaw
Space Dashboard
Model
${initialData.model}
Uptime
${initialData.uptime}
WhatsApp
${renderChannelBadge(initialData.whatsapp, "Ready to pair")}
Telegram
${renderChannelBadge(initialData.telegram, "Configured")}
Open Control UI
Last Sync Activity: ${initialData.sync.timestamp || "Never"}
${initialData.sync.message || "Waiting for first sync..."}
Keep Space Awake
If you use a free Hugging Face Space, it can still sleep.
To keep it awake, create an external UptimeRobot monitor here.
Use your Main API key .
Do not use the Read-only API key or a Monitor-specific API key.
Optional one-time setup. If you already created the monitor before, you do not need to paste the key again.
Set Up Monitor
Not available on private Spaces. External monitors like UptimeRobot cannot reliably access a private Hugging Face Space health URL, so this setup only works on public Spaces.
`;
}
function readRequestBody(req) {
return new Promise((resolve, reject) => {
let body = "";
req.on("data", (chunk) => {
body += chunk;
if (body.length > 1024 * 64) {
reject(new Error("Request too large"));
req.destroy();
}
});
req.on("end", () => resolve(body));
req.on("error", reject);
});
}
function postUptimeRobot(path, form) {
const body = new URLSearchParams(form).toString();
return new Promise((resolve, reject) => {
const request = https.request(
{
hostname: "api.uptimerobot.com",
port: 443,
method: "POST",
path,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Content-Length": Buffer.byteLength(body),
},
},
(response) => {
let raw = "";
response.setEncoding("utf8");
response.on("data", (chunk) => {
raw += chunk;
});
response.on("end", () => {
try {
resolve(JSON.parse(raw));
} catch {
reject(new Error("Unexpected response from UptimeRobot"));
}
});
},
);
request.on("error", reject);
request.write(body);
request.end();
});
}
async function createUptimeRobotMonitor(apiKey, host) {
const cleanHost = String(host || "")
.replace(/^https?:\/\//, "")
.replace(/\/.*$/, "");
if (!cleanHost) {
throw new Error("Missing Space host.");
}
const monitorUrl = `https://${cleanHost}/health`;
const existing = await postUptimeRobot("/v2/getMonitors", {
api_key: apiKey,
format: "json",
logs: "0",
response_times: "0",
response_times_limit: "1",
});
const existingMonitor = Array.isArray(existing.monitors)
? existing.monitors.find((monitor) => monitor.url === monitorUrl)
: null;
if (existingMonitor) {
return {
created: false,
message: `Monitor already exists for ${monitorUrl}`,
};
}
const created = await postUptimeRobot("/v2/newMonitor", {
api_key: apiKey,
format: "json",
type: "1",
friendly_name: `HuggingClaw ${cleanHost}`,
url: monitorUrl,
interval: "300",
});
if (created.stat !== "ok") {
const message =
created?.error?.message ||
created?.message ||
"Failed to create UptimeRobot monitor.";
throw new Error(message);
}
return {
created: true,
message: `Monitor created for ${monitorUrl}`,
};
}
function proxyHttp(req, res, proxyPath = req.url, proxyPort = GATEWAY_PORT) {
const proxyReq = http.request(
{
hostname: GATEWAY_HOST,
port: proxyPort,
method: req.method,
path: proxyPath,
headers: buildProxyHeaders(req.headers, req.socket.remoteAddress),
},
(proxyRes) => {
res.writeHead(proxyRes.statusCode || 502, proxyRes.headers);
proxyRes.pipe(res);
},
);
proxyReq.on("error", (error) => {
res.writeHead(502, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
status: "error",
message: "Gateway unavailable",
detail: error.message,
}),
);
});
req.pipe(proxyReq);
}
function serializeUpgradeHeaders(req, remoteAddress) {
const forwardedHeaders = [];
for (let i = 0; i < req.rawHeaders.length; i += 2) {
const name = req.rawHeaders[i];
const value = req.rawHeaders[i + 1];
const lower = name.toLowerCase();
if (
lower === "x-forwarded-for" ||
lower === "x-forwarded-host" ||
lower === "x-forwarded-proto"
) {
continue;
}
forwardedHeaders.push(`${name}: ${value}`);
}
forwardedHeaders.push(
`X-Forwarded-For: ${appendForwarded(req.headers["x-forwarded-for"], remoteAddress)}`,
);
forwardedHeaders.push(
`X-Forwarded-Host: ${req.headers["x-forwarded-host"] || req.headers.host || ""}`,
);
forwardedHeaders.push(
`X-Forwarded-Proto: ${req.headers["x-forwarded-proto"] || "https"}`,
);
return forwardedHeaders;
}
function proxyUpgrade(
req,
socket,
head,
proxyPath = req.url,
proxyPort = GATEWAY_PORT,
) {
const proxySocket = net.connect(proxyPort, GATEWAY_HOST);
proxySocket.on("connect", () => {
const requestLines = [
`${req.method} ${proxyPath} HTTP/${req.httpVersion}`,
...serializeUpgradeHeaders(req, req.socket.remoteAddress),
"",
"",
];
proxySocket.write(requestLines.join("\r\n"));
if (head && head.length > 0) {
proxySocket.write(head);
}
socket.pipe(proxySocket).pipe(socket);
});
proxySocket.on("error", () => {
if (socket.writable) {
socket.write("HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n");
}
socket.destroy();
});
socket.on("error", () => {
proxySocket.destroy();
});
}
const server = http.createServer((req, res) => {
const parsedUrl = parseRequestUrl(req.url || "/");
const pathname = parsedUrl.pathname;
const uptime = Math.floor((Date.now() - startTime) / 1000);
const uptimeHuman = `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m`;
if (pathname === "/health" || pathname === DASHBOARD_HEALTH_PATH) {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
status: "ok",
uptime,
uptimeHuman,
timestamp: new Date().toISOString(),
}),
);
return;
}
if (pathname === "/status" || pathname === DASHBOARD_STATUS_PATH) {
void (async () => {
const guardianStatus = readGuardianStatus();
res.writeHead(200, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
model: LLM_MODEL,
whatsapp: {
configured: guardianStatus.configured,
connected: guardianStatus.connected,
pairing: guardianStatus.pairing,
},
telegram: normalizeChannelStatus(null, TELEGRAM_ENABLED),
sync: readSyncStatus(),
uptime: uptimeHuman,
}),
);
})();
return;
}
if (
pathname === "/uptimerobot/setup" ||
pathname === DASHBOARD_UPTIMEROBOT_PATH
) {
if (req.method !== "POST") {
res.writeHead(405, { "Content-Type": "application/json" });
res.end(JSON.stringify({ message: "Method not allowed" }));
return;
}
void (async () => {
try {
const body = await readRequestBody(req);
const parsed = JSON.parse(body || "{}");
const apiKey = String(parsed.apiKey || "").trim();
if (!apiKey) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
message: "Paste your UptimeRobot Main API key first.",
}),
);
return;
}
const result = await createUptimeRobotMonitor(apiKey, req.headers.host);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(result));
} catch (error) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(
JSON.stringify({
message:
error && error.message
? error.message
: "Failed to create UptimeRobot monitor.",
}),
);
}
})();
return;
}
if (isDashboardRoute(pathname)) {
const guardianStatus = readGuardianStatus();
const initialData = {
model: LLM_MODEL,
whatsapp: {
configured: guardianStatus.configured,
connected: guardianStatus.connected,
pairing: guardianStatus.pairing,
},
telegram: normalizeChannelStatus(null, TELEGRAM_ENABLED),
sync: readSyncStatus(),
uptime: uptimeHuman,
};
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end(renderDashboard(initialData));
return;
}
if (isDashboardAppRoute(pathname) || isAppRoute(pathname)) {
const proxyPath = mapAppProxyPath(pathname) + (parsedUrl.search || "");
proxyHttp(req, res, proxyPath, GATEWAY_PORT);
return;
}
proxyHttp(req, res);
});
server.on("upgrade", (req, socket, head) => {
const pathname = parseRequestUrl(req.url || "/").pathname;
if (isLocalRoute(pathname)) {
socket.destroy();
return;
}
if (isDashboardAppRoute(pathname) || isAppRoute(pathname)) {
const parsedUrl = parseRequestUrl(req.url || "/");
const proxyPath = mapAppProxyPath(pathname) + (parsedUrl.search || "");
proxyUpgrade(req, socket, head, proxyPath, GATEWAY_PORT);
return;
}
proxyUpgrade(req, socket, head);
});
server.listen(PORT, "0.0.0.0", () => {
console.log(
`Health server listening on port ${PORT}; proxying gateway traffic to ${GATEWAY_HOST}:${GATEWAY_PORT}`,
);
});