Files
hermes-agent/optional-skills/web-development/scrollcraft/scripts/serve.mjs
T
Teknium 56cc2bd814 feat(skills): scrollcraft — premium scroll-driven landing pages (port of nateherkai/scroll-craft, 1.2k★ MIT)
Optional skill: scroll-as-timeline landing pages on a deterministic
CSS/JS engine, with interview → page grammar → signature move workflow
and screenshot-based scroll verification. Engine and scripts vendored
verbatim; asset generation re-anchored on image_generate with the
upstream kie.ai flow kept as an optional path.
2026-09-13 21:11:25 -07:00

53 lines
2.0 KiB
JavaScript

#!/usr/bin/env node
/**
* scrollcraft static server.
*
* A scrollcraft page cannot be verified from file://. The engine fetches each
* clip as a Blob, and file:// fetches are blocked by CORS in every browser, so
* the page silently falls back to posters and looks fine while proving nothing.
* Serve it.
*
* node serve.mjs --root builds/perkform --port 4500
*/
import http from "node:http";
import fs from "node:fs";
import path from "node:path";
const argv = process.argv.slice(2);
const arg = (n, d) => { const i = argv.indexOf(n); return i > -1 && argv[i + 1] ? argv[i + 1] : d; };
const ROOT = path.resolve(arg("--root", "."));
const PORT = parseInt(arg("--port", "4500"), 10);
const TYPES = {
".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8",
".js": "text/javascript; charset=utf-8", ".json": "application/json",
".mp4": "video/mp4", ".webm": "video/webm",
".webp": "image/webp", ".png": "image/png", ".jpg": "image/jpeg",
".svg": "image/svg+xml", ".woff2": "font/woff2",
};
http.createServer((req, res) => {
const url = decodeURIComponent(req.url.split("?")[0]);
let file = path.join(ROOT, url === "/" ? "/index.html" : url);
// Refuse to serve outside the root even if the path walks up.
if (!file.startsWith(ROOT)) { res.writeHead(403).end("forbidden"); return; }
if (fs.existsSync(file) && fs.statSync(file).isDirectory()) file = path.join(file, "index.html");
if (!fs.existsSync(file)) { res.writeHead(404).end("not found"); return; }
const ext = path.extname(file).toLowerCase();
const stat = fs.statSync(file);
res.writeHead(200, {
"Content-Type": TYPES[ext] || "application/octet-stream",
"Content-Length": stat.size,
// No caching: verification loops re-shoot the same URLs after edits, and a
// cached clip or stylesheet makes you screenshot the previous build.
"Cache-Control": "no-store",
"Accept-Ranges": "bytes",
});
fs.createReadStream(file).pipe(res);
}).listen(PORT, () => {
console.log(`scrollcraft: ${ROOT}\n http://localhost:${PORT}`);
});