// Meeseeks HF Space — Bun.serve HTTP server. // Routes: // GET / → static index.html // GET /static/ → static assets (css/js/images) // GET /samples → list sample skill names // GET /samples/.json → sample skill JSON // POST /api/plan → {skill, inputs} → ExecutionPlan // POST /api/export → {skill} → SkillBundle // POST /api/validate → {skill} → {ok, issues} // GET /receipts// → list receipt files // GET /receipts// → receipt assets // // HF Spaces convention: bind to port 7860. import { readdir } from "node:fs/promises"; import { extname, join, normalize, resolve } from "node:path"; import { SkillCore } from "./lib/skill.ts"; import { planSkill } from "./lib/plan.ts"; import { exportSkill } from "./lib/exporter.ts"; const PORT = Number(process.env.PORT ?? 7860); const ROOT = resolve(import.meta.dir, ".."); const STATIC_DIR = join(ROOT, "src/static"); const SAMPLES_DIR = join(ROOT, "samples"); const RECEIPTS_DIR = join(ROOT, "receipts"); const MIME: Record = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".png": "image/png", ".svg": "image/svg+xml", ".webp": "image/webp", ".ico": "image/x-icon", }; function mimeFor(path: string): string { return MIME[extname(path).toLowerCase()] ?? "application/octet-stream"; } function safeJoin(base: string, rel: string): string | null { const target = normalize(join(base, rel)); if (!target.startsWith(base)) return null; return target; } async function serveFile(absPath: string): Promise { const file = Bun.file(absPath); if (!(await file.exists())) { return new Response("not found", { status: 404 }); } return new Response(file, { headers: { "Content-Type": mimeFor(absPath) } }); } async function readJsonBody(req: Request): Promise { const text = await req.text(); if (!text) return {}; try { return JSON.parse(text); } catch (e) { throw new Response(JSON.stringify({ error: "invalid JSON body", detail: (e as Error).message }), { status: 400, headers: { "Content-Type": "application/json" }, }); } } function jsonResponse(data: unknown, init: ResponseInit = {}): Response { return new Response(JSON.stringify(data, null, 2), { ...init, headers: { "Content-Type": "application/json", ...(init.headers ?? {}) }, }); } async function listSamples(): Promise<{ name: string; bytes: number }[]> { try { const entries = await readdir(SAMPLES_DIR); const out: { name: string; bytes: number }[] = []; for (const e of entries.filter((x) => x.endsWith(".json")).sort()) { const file = Bun.file(join(SAMPLES_DIR, e)); out.push({ name: e.replace(/\.json$/, ""), bytes: file.size }); } return out; } catch { return []; } } async function listReceiptFiles(demo: string): Promise { const dir = safeJoin(RECEIPTS_DIR, demo); if (!dir) return []; try { const entries = await readdir(dir); return entries.sort(); } catch { return []; } } const server = Bun.serve({ port: PORT, hostname: "0.0.0.0", async fetch(req) { const url = new URL(req.url); const path = url.pathname; const method = req.method; try { if (method === "GET" && (path === "/" || path === "/index.html")) { return serveFile(join(STATIC_DIR, "index.html")); } if (method === "GET" && path.startsWith("/static/")) { const rel = path.slice("/static/".length); const abs = safeJoin(STATIC_DIR, rel); if (!abs) return new Response("forbidden", { status: 403 }); return serveFile(abs); } if (method === "GET" && path === "/samples") { return jsonResponse(await listSamples()); } if (method === "GET" && path.startsWith("/samples/")) { const rel = path.slice("/samples/".length); const abs = safeJoin(SAMPLES_DIR, rel); if (!abs) return new Response("forbidden", { status: 403 }); return serveFile(abs); } if (method === "GET" && path === "/receipts") { try { const dirs = (await readdir(RECEIPTS_DIR)).sort(); const out: { name: string; files: string[] }[] = []; for (const d of dirs) out.push({ name: d, files: await listReceiptFiles(d) }); return jsonResponse(out); } catch { return jsonResponse([]); } } if (method === "GET" && path.startsWith("/receipts/")) { const rel = path.slice("/receipts/".length); const abs = safeJoin(RECEIPTS_DIR, rel); if (!abs) return new Response("forbidden", { status: 403 }); return serveFile(abs); } if (method === "POST" && path === "/api/validate") { const body = await readJsonBody(req); const result = SkillCore.safeParse(body.skill ?? body); if (result.success) { return jsonResponse({ ok: true, name: result.data.name, steps: result.data.steps.length, placeholders: result.data.meta.placeholders, }); } return jsonResponse( { ok: false, issues: result.error.issues.slice(0, 20) }, { status: 400 }, ); } if (method === "POST" && path === "/api/plan") { const body = await readJsonBody(req); const skill = SkillCore.parse(body.skill ?? body); const inputs = (body.inputs ?? {}) as Record; return jsonResponse(planSkill(skill, inputs)); } if (method === "POST" && path === "/api/export") { const body = await readJsonBody(req); const skill = SkillCore.parse(body.skill ?? body); return jsonResponse(exportSkill(skill)); } if (method === "GET" && path === "/api/health") { return jsonResponse({ ok: true, port: PORT }); } return new Response("not found", { status: 404 }); } catch (e) { if (e instanceof Response) return e; const msg = (e as Error).message ?? "unknown"; return jsonResponse({ error: msg.slice(0, 600) }, { status: 500 }); } }, }); console.log(`meeseeks hf-space listening at http://${server.hostname}:${server.port}/`);