// Single public entrypoint for HF Spaces: local dashboard + reverse proxy to Paperclip.
const http = require("http");
const fs = require("fs");
const net = require("net");
const PORT = 7861;
const APP_PORT = 3100;
const APP_HOST = "127.0.0.1";
const startTime = Date.now();
const INVITE_URL_FILE = "/tmp/invite-url.txt";
const SYNC_STATUS_FILE = "/tmp/sync-status.json";
const CLOUDFLARE_KEEPALIVE_STATUS_FILE =
"/tmp/huggingclip-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)) {
const raw = fs.readFileSync(SYNC_STATUS_FILE, "utf8");
const parsed = JSON.parse(raw);
if (!parsed.status && parsed.db_status) parsed.status = parsed.db_status;
if (!parsed.message) {
if (parsed.last_error) parsed.message = parsed.last_error;
else if (parsed.last_sync_time)
parsed.message = `Last sync: ${parsed.last_sync_time}`;
}
return parsed;
}
} catch {}
if (process.env.HF_TOKEN) {
return {
status: "configured",
message: `Backup is enabled. Waiting for sync window (${process.env.SYNC_INTERVAL || 3600}s).`,
};
}
return { status: "disabled", message: "HF_TOKEN not set" };
}
function getKeepaliveStatus() {
try {
if (fs.existsSync(CLOUDFLARE_KEEPALIVE_STATUS_FILE)) {
return JSON.parse(
fs.readFileSync(CLOUDFLARE_KEEPALIVE_STATUS_FILE, "utf8"),
);
}
} catch {}
return null;
}
async function getPluginStatus(timeoutMs = 1500) {
// Try several possible Paperclip API endpoints to list plugins/tools
const paths = ["/api/plugins", "/api/tools", "/api/plugins/list"];
for (const p of paths) {
try {
const result = await new Promise((resolve) => {
const req = http.get(
{ hostname: APP_HOST, port: APP_PORT, path: p, timeout: timeoutMs },
(res) => {
let body = "";
res.on("data", (c) => (body += c));
res.on("end", () => resolve({ statusCode: res.statusCode, body }));
},
);
req.on("timeout", () => {
req.destroy();
resolve(null);
});
req.on("error", () => resolve(null));
});
if (!result || !result.body) continue;
if (result.statusCode >= 400) continue;
try {
const parsed = JSON.parse(result.body);
let list = [];
if (Array.isArray(parsed)) list = parsed;
else if (parsed.plugins && Array.isArray(parsed.plugins))
list = parsed.plugins;
else if (parsed.tools && Array.isArray(parsed.tools))
list = parsed.tools;
else if (parsed.items && Array.isArray(parsed.items))
list = parsed.items;
// Normalize list to array of names/ids
const names = list.map((it) =>
typeof it === "string" ? it : it.name || it.id || JSON.stringify(it),
);
return { count: names.length, list: names };
} catch (e) {
continue;
}
} catch (e) {
continue;
}
}
return null;
}
function getInviteUrl() {
try {
if (fs.existsSync(INVITE_URL_FILE)) {
return fs.readFileSync(INVITE_URL_FILE, "utf8").trim();
}
} catch {}
return null;
}
function probeAppHealth(timeoutMs = 1500) {
return new Promise((resolve) => {
const request = http.get(
{
hostname: APP_HOST,
port: APP_PORT,
path: "/api/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(data.sync.last_sync_time)}`;
}
if (typeof data.sync?.sync_count === "number") {
return `Syncs: ${String(data.sync.sync_count)}`;
}
return "";
})();
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 inviteUrl = getInviteUrl();
const pluginCount = Number(data.plugins?.count || 0);
const pluginList = Array.isArray(data.plugins?.list) ? data.plugins.list : [];
const tiles = [
renderTile({
title: "Paperclip Core",
value: toneBadge(
data.appReady ? "Online" : "Booting",
data.appReady ? "ok" : "warn",
),
detail: `Backend Port ${APP_PORT}`,
tone: data.appReady ? "ok" : "warn",
}),
renderTile({
title: "Database",
value: toneBadge("PostgreSQL", "ok"),
detail: "Embedded cluster active",
tone: "ok",
}),
renderTile({
title: "Runtime",
value: escapeHtml(data.uptimeHuman),
detail: `Exposed on port ${PORT}`,
tone: "neutral",
}),
renderTile({
title: "Backup",
value: toneBadge(syncStatus.toUpperCase(), syncTone),
detail: backupDetail,
tone: syncTone,
meta: data.sync?.timestamp
? `${backupMeta} | `
: backupMeta,
}),
renderTile({
title: "Plugins",
value: toneBadge(
pluginCount ? String(pluginCount) : "0",
pluginCount ? "ok" : "neutral",
),
detail: pluginCount
? escapeHtml(pluginList.slice(0, 6).join(", "))
: "No plugins loaded",
meta: pluginCount > 6 ? `+${pluginCount - 6} more` : "",
tone: pluginCount ? "ok" : "neutral",
}),
renderTile({
title: "Keep Awake",
value: toneBadge(
keepaliveConfigured ? "CF Cron" : keepaliveStatus.toUpperCase(),
keepAliveTone,
),
detail: keepAliveDetail,
tone: keepAliveTone,
}),
].join("");
return `