From edf1403e0c412ac2b9cdc41916ef66fc696e926e Mon Sep 17 00:00:00 2001 From: Benjamin Shafii Date: Thu, 19 Feb 2026 21:45:49 -0800 Subject: [PATCH] feat(share): add human-readable bundle pages with JSON fallback --- services/openwork-share/README.md | 16 +- services/openwork-share/api/b/[id].js | 21 +- .../api/b/render-bundle-page.js | 470 ++++++++++++++++++ .../api/b/render-bundle-page.test.js | 64 +++ 4 files changed, 567 insertions(+), 4 deletions(-) create mode 100644 services/openwork-share/api/b/render-bundle-page.js create mode 100644 services/openwork-share/api/b/render-bundle-page.test.js diff --git a/services/openwork-share/README.md b/services/openwork-share/README.md index f10001084..7da3599b9 100644 --- a/services/openwork-share/README.md +++ b/services/openwork-share/README.md @@ -12,7 +12,11 @@ It is designed to be deployed on Vercel and backed by Vercel Blob. - Returns `{ "url": "https://share.openwork.software/b/" }`. - `GET /b/:id` - - Proxies the stored object back to the caller. + - Returns an HTML share page by default for browser requests. + - Returns raw JSON for API/programmatic requests: + - send `Accept: application/json`, or + - append `?format=json`. + - Supports `?format=json&download=1` to download the bundle as a file. ## Required Environment Variables @@ -40,6 +44,16 @@ pnpm install vercel dev ``` +## Quick checks + +```bash +# Human-friendly page +curl -i "http://localhost:3000/b/" -H "Accept: text/html" + +# Machine-readable payload (OpenWork parser path) +curl -i "http://localhost:3000/b/?format=json" +``` + ## Notes - Links are public and unguessable (no auth, no encryption). diff --git a/services/openwork-share/api/b/[id].js b/services/openwork-share/api/b/[id].js index 63692535a..19b0e0fd4 100644 --- a/services/openwork-share/api/b/[id].js +++ b/services/openwork-share/api/b/[id].js @@ -1,4 +1,5 @@ import { head } from "@vercel/blob"; +import { renderBundlePage, wantsDownload, wantsJsonResponse } from "./render-bundle-page.js"; function setCors(res) { res.setHeader("Access-Control-Allow-Origin", "*"); @@ -40,9 +41,23 @@ export default async function handler(req, res) { return; } - res.setHeader("Content-Type", blob.contentType || response.headers.get("content-type") || "application/json"); + const rawBuffer = Buffer.from(await response.arrayBuffer()); + const rawJson = rawBuffer.toString("utf8"); + const serveJson = wantsJsonResponse(req); + + res.setHeader("Vary", "Accept"); res.setHeader("Cache-Control", "public, max-age=3600"); - const buffer = Buffer.from(await response.arrayBuffer()); - res.status(200).send(buffer); + if (serveJson) { + res.setHeader("Content-Type", blob.contentType || response.headers.get("content-type") || "application/json"); + if (wantsDownload(req)) { + res.setHeader("Content-Disposition", `attachment; filename="openwork-bundle-${id}.json"`); + } + res.status(200).send(rawBuffer); + return; + } + + const html = renderBundlePage({ id, rawJson, req }); + res.setHeader("Content-Type", "text/html; charset=utf-8"); + res.status(200).send(html); } diff --git a/services/openwork-share/api/b/render-bundle-page.js b/services/openwork-share/api/b/render-bundle-page.js new file mode 100644 index 000000000..922eb4103 --- /dev/null +++ b/services/openwork-share/api/b/render-bundle-page.js @@ -0,0 +1,470 @@ +const OPENWORK_SITE_URL = "https://openwork.software"; +const OPENWORK_DOWNLOAD_URL = "https://openwork.software/download"; + +function escapeHtml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +function escapeJsonForScript(rawJson) { + return rawJson + .replaceAll("<", "\\u003c") + .replaceAll(">", "\\u003e") + .replaceAll("\u2028", "\\u2028") + .replaceAll("\u2029", "\\u2029"); +} + +function maybeString(value) { + return typeof value === "string" ? value : ""; +} + +function humanizeType(type) { + if (!type) return "Bundle"; + return type + .split("-") + .filter(Boolean) + .map((part) => `${part.slice(0, 1).toUpperCase()}${part.slice(1)}`) + .join(" "); +} + +function truncate(value, maxChars = 3200) { + if (value.length <= maxChars) return value; + return `${value.slice(0, maxChars)}\n\n... (truncated for display)`; +} + +function parseBundle(rawJson) { + try { + const parsed = JSON.parse(rawJson); + if (!parsed || typeof parsed !== "object") { + return { + schemaVersion: null, + type: "", + name: "", + description: "", + trigger: "", + content: "", + }; + } + + return { + schemaVersion: typeof parsed.schemaVersion === "number" ? parsed.schemaVersion : null, + type: maybeString(parsed.type).trim(), + name: maybeString(parsed.name).trim(), + description: maybeString(parsed.description).trim(), + trigger: maybeString(parsed.trigger).trim(), + content: maybeString(parsed.content), + }; + } catch { + return { + schemaVersion: null, + type: "", + name: "", + description: "", + trigger: "", + content: "", + }; + } +} + +function prettyJson(rawJson) { + try { + return JSON.stringify(JSON.parse(rawJson), null, 2); + } catch { + return rawJson; + } +} + +function getOrigin(req) { + const protocolHeader = String(req.headers?.["x-forwarded-proto"] ?? "https").split(",")[0].trim(); + const hostHeader = String(req.headers?.["x-forwarded-host"] ?? req.headers?.host ?? "") + .split(",")[0] + .trim(); + + if (!hostHeader) return ""; + const protocol = protocolHeader || "https"; + return `${protocol}://${hostHeader}`; +} + +export function buildBundleUrls(req, id) { + const encodedId = encodeURIComponent(id); + const origin = getOrigin(req); + const path = `/b/${encodedId}`; + + return { + shareUrl: origin ? `${origin}${path}` : path, + jsonUrl: origin ? `${origin}${path}?format=json` : `${path}?format=json`, + downloadUrl: origin ? `${origin}${path}?format=json&download=1` : `${path}?format=json&download=1`, + }; +} + +export function wantsJsonResponse(req) { + const format = String(req.query?.format ?? "").trim().toLowerCase(); + if (format === "json") return true; + if (format === "html") return false; + + const accept = String(req.headers?.accept ?? "").toLowerCase(); + if (!accept) return true; + if (accept.includes("application/json")) return true; + if (accept.includes("text/html") || accept.includes("application/xhtml+xml")) return false; + return true; +} + +export function wantsDownload(req) { + return String(req.query?.download ?? "").trim() === "1"; +} + +export function renderBundlePage({ id, rawJson, req }) { + const bundle = parseBundle(rawJson); + const urls = buildBundleUrls(req, id); + const prettyBundleJson = prettyJson(rawJson); + const schemaVersion = bundle.schemaVersion == null ? "unknown" : String(bundle.schemaVersion); + const typeLabel = humanizeType(bundle.type); + const title = bundle.name || `OpenWork ${typeLabel}`; + const description = + bundle.description || + "OpenWork share links stay human-friendly for reading while still exposing a stable machine-readable JSON bundle."; + const installHint = + bundle.type === "skill" + ? "Open OpenWork, go to Skills, choose Install from link, then paste this URL." + : "Use the JSON endpoint if you want to import this bundle programmatically."; + const contentLabel = bundle.type === "skill" && bundle.content.trim() ? "Skill content" : "Bundle payload"; + const contentPreview = + bundle.type === "skill" && bundle.content.trim() ? truncate(bundle.content.trim()) : truncate(prettyBundleJson); + + return ` + + + + + ${escapeHtml(title)} - OpenWork Share + + + + + + + + +
+
+ + O + OpenWork Share + + Get OpenWork +
+ +
+
${escapeHtml(typeLabel)}
+

${escapeHtml(title)}

+

${escapeHtml(description)}

+
+ + + View raw JSON + Download JSON +
+
+ +
+
+

Install in OpenWork

+
    +
  1. ${escapeHtml(installHint)}
  2. +
  3. If you are using the API directly, call this link with ?format=json.
  4. +
+
${escapeHtml(urls.shareUrl)}
+
+ +
+

Metadata

+
+
Bundle id
${escapeHtml(id)}
+
Type
${escapeHtml(bundle.type || "unknown")}
+
Schema
${escapeHtml(schemaVersion)}
+
Name
${escapeHtml(bundle.name || "n/a")}
+
Trigger
${escapeHtml(bundle.trigger || "n/a")}
+
+
+
+ +
+

${escapeHtml(contentLabel)}

+
${escapeHtml(contentPreview)}
+
+ +
+

Bundle JSON

+
${escapeHtml(prettyBundleJson)}
+
+
+ + +
+ + +`; +} diff --git a/services/openwork-share/api/b/render-bundle-page.test.js b/services/openwork-share/api/b/render-bundle-page.test.js new file mode 100644 index 000000000..fb394b734 --- /dev/null +++ b/services/openwork-share/api/b/render-bundle-page.test.js @@ -0,0 +1,64 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { buildBundleUrls, renderBundlePage, wantsDownload, wantsJsonResponse } from "./render-bundle-page.js"; + +function makeReq({ accept = "", query = {}, host = "share.openwork.software" } = {}) { + return { + query, + headers: { + accept, + host, + "x-forwarded-proto": "https", + "x-forwarded-host": host, + }, + }; +} + +test("wantsJsonResponse honors explicit format query", () => { + assert.equal(wantsJsonResponse(makeReq({ query: { format: "json" }, accept: "text/html" })), true); + assert.equal(wantsJsonResponse(makeReq({ query: { format: "html" }, accept: "application/json" })), false); +}); + +test("wantsJsonResponse defaults to json unless browser html accept is present", () => { + assert.equal(wantsJsonResponse(makeReq()), true); + assert.equal(wantsJsonResponse(makeReq({ accept: "application/json" })), true); + assert.equal(wantsJsonResponse(makeReq({ accept: "text/html,application/xhtml+xml" })), false); +}); + +test("wantsDownload only enables on download=1", () => { + assert.equal(wantsDownload(makeReq({ query: { download: "1" } })), true); + assert.equal(wantsDownload(makeReq({ query: { download: "0" } })), false); + assert.equal(wantsDownload(makeReq()), false); +}); + +test("buildBundleUrls uses forwarded origin", () => { + const urls = buildBundleUrls(makeReq({ host: "example.test" }), "01ABC"); + assert.equal(urls.shareUrl, "https://example.test/b/01ABC"); + assert.equal(urls.jsonUrl, "https://example.test/b/01ABC?format=json"); + assert.equal(urls.downloadUrl, "https://example.test/b/01ABC?format=json&download=1"); +}); + +test("renderBundlePage includes machine-readable metadata and escaped json script", () => { + const rawJson = JSON.stringify({ + schemaVersion: 1, + type: "skill", + name: "demo skill", + description: "Install me", + trigger: "daily", + content: "# Skill\nHello", + }); + + const html = renderBundlePage({ + id: "01TEST", + rawJson, + req: makeReq({ accept: "text/html", host: "share.openwork.software" }), + }); + + assert.match(html, /data-openwork-share="true"/); + assert.match(html, /data-openwork-bundle-type="skill"/); + assert.match(html, /meta name="openwork:bundle-id" content="01TEST"/); + assert.match(html, /\?format=json/); + assert.match(html, /id="openwork-bundle-json" type="application\/json"/); + assert.match(html, /demo \\u003c\/script\\u003e skill/); +});