From 841d68e5e66445f3b2ef5a590d751afa80e35fda Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Fri, 17 Apr 2026 17:40:06 -0700 Subject: [PATCH] lith: proxy /api/pack-html + /api/bundle-html + /api/os to oven MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit netlify.toml had status=200 rewrites from /api/pack-html → oven.aesthetic.computer, but production runs on lith now, not Netlify. Caddy sends /api/* to lith; lith's function resolver had no pack-html function and returned 404. Broke ableton.mjs offline .amxd builder, the pack command, and the m4d prompt command. Mirrors the existing /api/os-image proxy pattern — fetch through, forward Content-{Type,Disposition,Length}, Cache-Control, ETag, and oven's x-ac-os-* / x-build / x-patch headers, stream the body. Co-Authored-By: Claude Opus 4.7 (1M context) --- lith/server.mjs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/lith/server.mjs b/lith/server.mjs index 6353c7ba81..8b0b256254 100644 --- a/lith/server.mjs +++ b/lith/server.mjs @@ -782,6 +782,44 @@ app.get("/api/os-image", async (req, res) => { } }); +// --- /api/pack-html, /api/bundle-html, /api/os (proxy to oven) --- +// netlify.toml used status=200 rewrites for these; on lith we must proxy +// explicitly since no `pack-html` / `os` netlify function exists. The +// ableton.mjs offline-amxd builder + `pack` / `m4d` prompt commands all +// depend on pack-html reaching oven's /pack-html?format=m4d endpoint. +async function proxyToOven(ovenPath, req, res) { + try { + const search = new URLSearchParams(req.query || {}).toString(); + const ovenUrl = `https://oven.aesthetic.computer${ovenPath}` + (search ? `?${search}` : ""); + const fwdHeaders = {}; + if (req.headers.authorization) fwdHeaders.Authorization = req.headers.authorization; + if (req.headers.accept) fwdHeaders.Accept = req.headers.accept; + const ovenRes = await fetch(ovenUrl, { method: "GET", headers: fwdHeaders }); + res.status(ovenRes.status); + const forward = [ + "content-type", "content-disposition", "content-length", "cache-control", + "etag", "last-modified", + "x-ac-os-requested-layout", "x-ac-os-layout", + "x-ac-os-fallback", "x-ac-os-fallback-reason", + "x-build", "x-patch", + ]; + for (const h of forward) { + const v = ovenRes.headers.get(h); + if (v) res.set(h, v); + } + res.set("Access-Control-Allow-Origin", "*"); + if (!ovenRes.body) return res.end(); + const { Readable } = await import("stream"); + Readable.fromWeb(ovenRes.body).pipe(res); + } catch (err) { + console.error(`proxyToOven ${ovenPath} error:`, err.message); + if (!res.headersSent) res.status(502).json({ error: `Oven unavailable: ${err.message}` }); + } +} +app.get("/api/pack-html", (req, res) => proxyToOven("/pack-html", req, res)); +app.get("/api/bundle-html", (req, res) => proxyToOven("/bundle-html", req, res)); +app.get("/api/os", (req, res) => proxyToOven("/os", req, res)); + // --- /media/* handler (ports Netlify edge function media.js) --- app.all("/media/*rest", async (req, res) => { const parts = req.path.split("/").filter(Boolean); // ["media", ...] -- 2.51.2