From f511880a512c36d542598fe3975998fa6e858217 Mon Sep 17 00:00:00 2001 From: kiwi Date: Fri, 23 Oct 2026 12:33:51 +0900 Subject: [PATCH] Cloudflare edge worker for Markdown negotiation + Markdown 404 (not deployed) --- _cloudflare/README.md | 16 +++++++++ _cloudflare/worker.js | 68 +++++++++++++++++++++++++++++++++++++++ _cloudflare/wrangler.toml | 7 ++++ 3 files changed, 91 insertions(+) create mode 100644 _cloudflare/README.md create mode 100644 _cloudflare/worker.js create mode 100644 _cloudflare/wrangler.toml diff --git a/_cloudflare/README.md b/_cloudflare/README.md new file mode 100644 index 0000000..437b702 --- /dev/null +++ b/_cloudflare/README.md @@ -0,0 +1,16 @@ +# Cloudflare in front of 207studio.dev (plan) + +Goal: fix the two is-agentic essentials GitHub Pages cannot do: `Accept: text/markdown` answers in Markdown with `Vary: Accept`, and a Markdown 404 body. + +State on 2026-10-23 12:40 KST: registrar Spaceship, nameservers `launch1/launch2.spaceship.net`, DNSSEC ON (DS keyTag 27026), A = GitHub Pages 185.199.108-111.153, MX iCloud, TXT: SPF iCloud, apple-domain, anthropic-domain-verification. No Cloudflare account in Kiwi. + +## Steps (유일 님 = needs login/signup; agent can do the rest once logged in) +1. 유일 님: create/log in to a Cloudflare account (Free plan, no card needed) and add site `207studio.dev`. Cloudflare imports DNS; check A×4, MX×2, TXT×3 are all there (MX/TXT "DNS only"). +2. 유일 님: in Spaceship, turn DNSSEC off (remove DS record) FIRST. Wait for the DS to disappear (up to 24 h). Changing nameservers with the old DS still published breaks the domain and mail. +3. 유일 님: in Spaceship, change nameservers to the two Cloudflare gives. +4. When the zone is Active: SSL/TLS mode Full; GitHub Pages "Enforce HTTPS" stays on. +5. Deploy this worker: `npx wrangler login` (유일 님 browser) then `cd _cloudflare && npx wrangler deploy`. Route `207studio.dev/*`. +6. Optional: re-enable DNSSEC in Cloudflare and add its DS record at Spaceship. +7. Check: `curl -sI -H 'Accept: text/markdown' https://207studio.dev/` → `content-type: text/markdown`, `vary: Accept`; `curl -s -H 'Accept: text/markdown' https://207studio.dev/nope` → 404 Markdown. Then is-agentic Rescan. + +Rollback: delete the worker route, or switch nameservers back to `launch1/launch2.spaceship.net` (DNS records stay at Spaceship). diff --git a/_cloudflare/worker.js b/_cloudflare/worker.js new file mode 100644 index 0000000..a2393a6 --- /dev/null +++ b/_cloudflare/worker.js @@ -0,0 +1,68 @@ +// 207studio.dev edge worker: Markdown for agents in front of GitHub Pages. +// Accept: text/markdown -> Markdown body (Vary: Accept on every response). +// Subrequests to the same hostname go to the origin (GitHub Pages) without re-entering the worker. +const NOT_FOUND_MD = `# 404: page not found + +This page does not exist on 207studio.dev. + +- All products: https://207studio.dev/products/ +- Product list for AI agents: https://207studio.dev/llms.txt +- Full reference with prices and buy links: https://207studio.dev/llms-full.txt +- Contact: support@207studio.dev +`; + +function wantsMarkdown(req) { + const a = (req.headers.get("Accept") || "").toLowerCase(); + return a.includes("text/markdown") || a.includes("text/x-markdown"); +} + +function md(body, status = 200) { + return new Response(body, { + status, + headers: { + "Content-Type": "text/markdown; charset=utf-8", + "Vary": "Accept", + "Cache-Control": "public, max-age=300", + }, + }); +} + +function htmlToMarkdown(html) { + let s = html.replace(/<(script|style|noscript|svg)[\s\S]*?<\/\1>/gi, ""); + const t = (s.match(/([\s\S]*?)<\/title>/i) || [])[1] || ""; + s = s.replace(/<head[\s\S]*?<\/head>/i, ""); + s = s.replace(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi, (_, n, x) => `\n\n${"#".repeat(+n)} ${x}\n\n`); + s = s.replace(/<a [^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/gi, (_, h, x) => `[${x}](${h})`); + s = s.replace(/<li[^>]*>/gi, "\n- ").replace(/<(p|div|section|article|tr|br)[^>]*>/gi, "\n"); + s = s.replace(/<[^>]+>/g, ""); + s = s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/g, "'").replace(/ /g, " "); + s = s.split("\n").map((l) => l.trim()).join("\n").replace(/\n{3,}/g, "\n\n").trim(); + return (t && !s.startsWith("# ") ? `# ${t.trim()}\n\n` : "") + s + "\n"; +} + +export default { + async fetch(req) { + const url = new URL(req.url); + const ORIGIN = url.origin; + const originUrl = req.url; + if (!wantsMarkdown(req) || req.method !== "GET") { + const r = await fetch(originUrl, { method: req.method, headers: req.headers, redirect: "manual" }); + const out = new Response(r.body, r); + out.headers.append("Vary", "Accept"); + return out; + } + if (url.pathname === "/" || url.pathname === "/index.html") { + const r = await fetch(ORIGIN + "/llms.txt"); + if (r.ok) return md(await r.text()); + } + const r = await fetch(originUrl, { redirect: "follow" }); + if (r.status === 404) return md(NOT_FOUND_MD, 404); + const type = r.headers.get("Content-Type") || ""; + if (!type.includes("text/html")) { + const out = new Response(r.body, r); + out.headers.append("Vary", "Accept"); + return out; + } + return md(htmlToMarkdown(await r.text()), r.status); + }, +}; diff --git a/_cloudflare/wrangler.toml b/_cloudflare/wrangler.toml new file mode 100644 index 0000000..13e0b59 --- /dev/null +++ b/_cloudflare/wrangler.toml @@ -0,0 +1,7 @@ +name = "207studio-edge" +main = "worker.js" +compatibility_date = "2026-10-01" +workers_dev = false +routes = [ + { pattern = "207studio.dev/*", zone_name = "207studio.dev" }, +]