diff --git a/docs/assets/refract-docs-badge.png b/docs/assets/refract-docs-badge.png deleted file mode 100644 index 8f2f3a8..0000000 Binary files a/docs/assets/refract-docs-badge.png and /dev/null differ diff --git a/docs/assets/refract-docs-blur.png b/docs/assets/refract-docs-blur.png deleted file mode 100644 index 8e5f736..0000000 Binary files a/docs/assets/refract-docs-blur.png and /dev/null differ diff --git a/docs/assets/refract-docs-card.png b/docs/assets/refract-docs-card.png deleted file mode 100644 index dc98c0b..0000000 Binary files a/docs/assets/refract-docs-card.png and /dev/null differ diff --git a/docs/assets/refract-docs-circle.png b/docs/assets/refract-docs-circle.png deleted file mode 100644 index aef4b20..0000000 Binary files a/docs/assets/refract-docs-circle.png and /dev/null differ diff --git a/docs/assets/refract-docs-dark-og.png b/docs/assets/refract-docs-dark-og.png deleted file mode 100644 index 8b32c35..0000000 Binary files a/docs/assets/refract-docs-dark-og.png and /dev/null differ diff --git a/docs/assets/refract-docs-dark.png b/docs/assets/refract-docs-dark.png deleted file mode 100644 index 8586de0..0000000 Binary files a/docs/assets/refract-docs-dark.png and /dev/null differ diff --git a/docs/assets/refract-docs-demo.png b/docs/assets/refract-docs-demo.png deleted file mode 100644 index c93e7ba..0000000 Binary files a/docs/assets/refract-docs-demo.png and /dev/null differ diff --git a/docs/assets/refract-docs-email.png b/docs/assets/refract-docs-email.png deleted file mode 100644 index 26a3347..0000000 Binary files a/docs/assets/refract-docs-email.png and /dev/null differ diff --git a/docs/assets/refract-docs-favicon.png b/docs/assets/refract-docs-favicon.png deleted file mode 100644 index 648c49e..0000000 Binary files a/docs/assets/refract-docs-favicon.png and /dev/null differ diff --git a/docs/assets/refract-docs-github-preview.png b/docs/assets/refract-docs-github-preview.png deleted file mode 100644 index dffcd8f..0000000 Binary files a/docs/assets/refract-docs-github-preview.png and /dev/null differ diff --git a/docs/assets/refract-docs-github.png b/docs/assets/refract-docs-github.png deleted file mode 100644 index f33e884..0000000 Binary files a/docs/assets/refract-docs-github.png and /dev/null differ diff --git a/docs/assets/refract-docs-header.png b/docs/assets/refract-docs-header.png deleted file mode 100644 index e02a6a9..0000000 Binary files a/docs/assets/refract-docs-header.png and /dev/null differ diff --git a/docs/assets/refract-docs-mastodon.png b/docs/assets/refract-docs-mastodon.png deleted file mode 100644 index c70a14a..0000000 Binary files a/docs/assets/refract-docs-mastodon.png and /dev/null differ diff --git a/docs/assets/refract-docs-og.png b/docs/assets/refract-docs-og.png deleted file mode 100644 index 5581fca..0000000 Binary files a/docs/assets/refract-docs-og.png and /dev/null differ diff --git a/docs/assets/refract-docs-square.png b/docs/assets/refract-docs-square.png deleted file mode 100644 index 90181ef..0000000 Binary files a/docs/assets/refract-docs-square.png and /dev/null differ diff --git a/docs/assets/refract-docs-touch-icon.png b/docs/assets/refract-docs-touch-icon.png deleted file mode 100644 index b4eb9d4..0000000 Binary files a/docs/assets/refract-docs-touch-icon.png and /dev/null differ diff --git a/docs/assets/refract-docs-touch.png b/docs/assets/refract-docs-touch.png deleted file mode 100644 index 39d7fc7..0000000 Binary files a/docs/assets/refract-docs-touch.png and /dev/null differ diff --git a/docs/assets/refract-docs-unfurl.png b/docs/assets/refract-docs-unfurl.png deleted file mode 100644 index d3a667c..0000000 Binary files a/docs/assets/refract-docs-unfurl.png and /dev/null differ diff --git a/docs/assets/source.svg b/docs/assets/source.svg deleted file mode 100644 index 93efcc3..0000000 --- a/docs/assets/source.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - Refract documentation - - - - - - - - - - - - - - - - - - refract-docs - - - - - - Documentation for the refract ecosystem - \ No newline at end of file diff --git a/docs/mcp.md b/docs/mcp.md index 847016b..a62b8b0 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1,6 +1,6 @@ # Wikipedia & MediaWiki revision-history MCP server -`refract mcp` starts a JSON-RPC server over stdio that exposes the Refract engine to AI agents via the Model Context Protocol. (`wikihistory mcp` also works.) +`refract mcp` starts a Model Context Protocol server over stdio that lets AI agents analyze page revision histories from Wikipedia or any MediaWiki API. The MCP server supports **sampling** โ€” it can request the host's LLM to interpret events at any BYO-inference boundary without managing API keys. @@ -12,7 +12,7 @@ The MCP server supports **sampling** โ€” it can request the host's LLM to interp refract mcp ``` -The server starts and waits for MCP client connections on stdio. Connect any MCP-compatible client. +The server starts and waits for MCP client connections on stdio (`wikihistory mcp` also works). Connect any MCP-compatible client. ## Connecting AI coding agents diff --git a/package.json b/package.json index 5daadb7..7a3f6bd 100644 --- a/package.json +++ b/package.json @@ -18,9 +18,7 @@ "test": "vitest run", "lint": "biome check .", "format": "biome format --write .", - "prepare": "husky", - "assets:check": "bun run scripts/check-assets.ts", - "assets:generate": "bun run scripts/generate-scenes.ts" + "prepare": "husky" }, "devDependencies": { "@biomejs/biome": "^2.5.15", diff --git a/scripts/check-assets.ts b/scripts/check-assets.ts deleted file mode 100644 index ef68616..0000000 --- a/scripts/check-assets.ts +++ /dev/null @@ -1,172 +0,0 @@ -#!/usr/bin/env bun -import { readdirSync, readFileSync, statSync } from "node:fs"; -import { basename, join } from "node:path"; - -const ASSETS_DIR = join( - import.meta.dirname ?? __dirname, - "..", - "docs", - "assets", -); - -const PNG_SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]); - -type Spec = { width: number; height: number; minBytes: number }; - -const SPECS: Record = { - badge: { width: 240, height: 96, minBytes: 2_000 }, - blur: { width: 50, height: 28, minBytes: 300 }, - card: { width: 400, height: 300, minBytes: 7_000 }, - circle: { width: 540, height: 540, minBytes: 8_000 }, - dark: { width: 960, height: 540, minBytes: 10_000 }, - demo: { width: 720, height: 405, minBytes: 12_000 }, - email: { width: 600, height: 200, minBytes: 5_000 }, - favicon: { width: 64, height: 64, minBytes: 400 }, - github: { width: 1280, height: 640, minBytes: 15_000 }, - header: { width: 1920, height: 400, minBytes: 12_000 }, - mastodon: { width: 1200, height: 600, minBytes: 15_000 }, - og: { width: 1200, height: 675, minBytes: 15_000 }, - square: { width: 1080, height: 1080, minBytes: 15_000 }, - touch: { width: 360, height: 360, minBytes: 8_000 }, - unfurl: { width: 1200, height: 628, minBytes: 15_000 }, -}; - -const COLOR_TYPE_LABELS: Record = { - 0: "grayscale", - 2: "RGB", - 3: "indexed", - 4: "grayscale+alpha", - 6: "RGBA", -}; - -type PngInfo = { - width: number; - height: number; - colorType: number; - bitDepth: number; - byteLength: number; -}; - -function readPngInfo(filePath: string): PngInfo { - const buf = readFileSync(filePath); - if (buf.length < 33) throw new Error("file too small for valid PNG"); - if (!buf.subarray(0, 8).equals(PNG_SIGNATURE)) - throw new Error("not a valid PNG"); - return { - width: buf.readUInt32BE(16), - height: buf.readUInt32BE(20), - colorType: buf.readUInt8(25), - bitDepth: buf.readUInt8(24), - byteLength: buf.length, - }; -} - -function main() { - const files = readdirSync(ASSETS_DIR) - .filter((f) => f.endsWith(".png")) - .sort(); - if (files.length === 0) { - console.error(`FAIL: no PNGs found in ${ASSETS_DIR}`); - process.exit(1); - } - - const errors: string[] = []; - const found = new Set(); - const filesWithSizes = files.map((f) => ({ - name: f, - size: statSync(join(ASSETS_DIR, f)).size, - })); - - const prefix = basename(process.cwd()); - console.log(`\n๐Ÿ” Checking assets for ยซ${prefix}ยป in ${ASSETS_DIR}\n`); - - for (const { name, size } of filesWithSizes) { - const filePath = join(ASSETS_DIR, name); - let typeKey = name - .replace(/\.png$/, "") - .replace(new RegExp(`^${prefix}-`), ""); - - if (typeKey.startsWith("dark-og")) typeKey = "dark"; - else if (typeKey.startsWith("touch")) typeKey = "touch"; - else if (typeKey.startsWith("github-preview")) typeKey = "github"; - - const spec = SPECS[typeKey]; - if (!spec) { - try { - const info = readPngInfo(filePath); - found.add(typeKey); - console.log( - ` โš  ${name} (${info.width}ร—${info.height}) โ€” no spec, skipped`, - ); - } catch (e: unknown) { - errors.push(`${name}: invalid PNG โ€” ${(e as Error).message}`); - } - continue; - } - - found.add(typeKey); - try { - const info = readPngInfo(filePath); - - if (info.width !== spec.width || info.height !== spec.height) { - errors.push( - `${name}: expected ${spec.width}ร—${spec.height}, got ${info.width}ร—${info.height}`, - ); - continue; - } - if (info.colorType === 3) { - errors.push(`${name}: indexed color (type 3) โ€” posterized`); - continue; - } - if (info.bitDepth < 8) { - errors.push(`${name}: low bit depth (${info.bitDepth}-bit)`); - continue; - } - if (size < spec.minBytes) { - errors.push( - `${name}: ${size}B < ${spec.minBytes}B min โ€” possible posterization or truncation`, - ); - continue; - } - - const label = - COLOR_TYPE_LABELS[info.colorType] ?? `type${info.colorType}`; - console.log( - ` โœ… ${name} ${info.width}ร—${info.height} ${size.toLocaleString()}B ${label}`, - ); - } catch (e: unknown) { - errors.push(`${name}: ${(e as Error).message}`); - } - } - - const svgPath = join(ASSETS_DIR, "source.svg"); - try { - const svg = readFileSync(svgPath, "utf8").trim(); - if (svg.startsWith(" root)"); - } - } catch { - errors.push("source.svg: missing โ€” design source file required"); - } - - const expectedTypes = Object.keys(SPECS); - const missing = expectedTypes.filter((t) => !found.has(t)); - if (missing.length > 0) { - console.log(`\n โš  Missing types: ${missing.join(", ")}`); - } - - console.log( - `\n${errors.length === 0 ? "โœ… All assets valid" : "โŒ FAILURES:"} (${files.length} PNGs, 1 SVG checked)\n`, - ); - - if (errors.length > 0) { - for (const err of errors) console.error(` โŒ ${err}`); - process.exit(1); - } -} - -main(); diff --git a/scripts/generate-scenes.ts b/scripts/generate-scenes.ts deleted file mode 100644 index f5b8e69..0000000 --- a/scripts/generate-scenes.ts +++ /dev/null @@ -1,241 +0,0 @@ -#!/usr/bin/env bun - -/* eslint-disable no-console, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access, no-empty */ -/** - * generate-scenes.ts โ€” renders source.svg into scene-specific PNGs with variation. - * Usage: bun run scripts/generate-scenes.ts [--dry-run] - */ - -import { execSync } from "node:child_process"; -import { - existsSync, - mkdirSync, - readFileSync, - statSync, - writeFileSync, -} from "node:fs"; -import { basename, join } from "node:path"; - -const ASSETS_DIR = join(process.cwd(), "docs", "assets"); -const SVG_PATH = join(ASSETS_DIR, "source.svg"); -const PROJECT = basename(process.cwd()); - -type SceneSpec = { - width: number; - height: number; - minBytes: number; - bg: string; - gradient?: boolean; - center?: boolean; - scale?: number; -}; - -const SCENES: Record = { - badge: { width: 240, height: 96, minBytes: 2_000, bg: "#1a1a2e", scale: 0.8 }, - blur: { width: 50, height: 28, minBytes: 300, bg: "#1a1a2e", scale: 0.5 }, - card: { - width: 400, - height: 300, - minBytes: 8_000, - bg: "#16213e", - center: true, - scale: 0.7, - }, - circle: { - width: 540, - height: 540, - minBytes: 8_000, - bg: "#0f3460", - center: true, - gradient: true, - }, - dark: { - width: 960, - height: 540, - minBytes: 10_000, - bg: "#0a0a0a", - gradient: true, - }, - demo: { - width: 720, - height: 405, - minBytes: 12_000, - bg: "#1a1a2e", - gradient: true, - }, - email: { width: 600, height: 200, minBytes: 5_000, bg: "#16213e" }, - favicon: { width: 64, height: 64, minBytes: 600, bg: "#e94560", scale: 0.95 }, - github: { - width: 1280, - height: 640, - minBytes: 15_000, - bg: "#1a1a2e", - gradient: true, - }, - header: { - width: 1920, - height: 400, - minBytes: 12_000, - bg: "#1a1a2e", - gradient: true, - }, - mastodon: { - width: 1200, - height: 600, - minBytes: 15_000, - bg: "#16213e", - gradient: true, - }, - og: { - width: 1200, - height: 675, - minBytes: 15_000, - bg: "#1a1a2e", - gradient: true, - }, - square: { - width: 1080, - height: 1080, - minBytes: 15_000, - bg: "#0f3460", - center: true, - gradient: true, - }, - touch: { - width: 360, - height: 360, - minBytes: 8_000, - bg: "#16213e", - center: true, - }, - unfurl: { - width: 1200, - height: 628, - minBytes: 15_000, - bg: "#1a1a2e", - gradient: true, - }, -}; - -function hueFromProject(name: string): number { - let hash = 0; - for (let i = 0; i < name.length; i++) - hash = (hash << 5) - hash + name.charCodeAt(i); - return Math.abs(hash) % 360; -} - -function rotateHex(hex: string, hue: number): string { - const r = parseInt(hex.slice(1, 3), 16); - const g = parseInt(hex.slice(3, 5), 16); - const b = parseInt(hex.slice(5, 7), 16); - const f = (hue / 360) * 0.25 + 0.875; // 0.875-1.125 - const nr = Math.min(255, Math.round(r * f)); - const ng = Math.min(255, Math.round(g * (1 + ((hue % 40) - 20) / 200))); - const nb = Math.min( - 255, - Math.round(b * (1 + (((hue + 120) % 40) - 20) / 200)), - ); - return `#${nr.toString(16).padStart(2, "0")}${ng.toString(16).padStart(2, "0")}${nb.toString(16).padStart(2, "0")}`; -} - -function buildOverlay(spec: SceneSpec, hue: number): string { - const bg = rotateHex(spec.bg, hue); - const { width, height, gradient } = spec; - let svg = ``; - svg += ``; - - if (gradient) { - svg += ` - - - `; - } - - const fs = Math.max(10, Math.min(width, height) * 0.025); - svg += `${PROJECT}`; - return `${svg}`; -} - -function main() { - const dryRun = process.argv.includes("--dry-run"); - - if (!existsSync(SVG_PATH)) { - console.error(`โŒ No source.svg at ${SVG_PATH}`); - process.exit(1); - } - - const svgRaw = readFileSync(SVG_PATH, "utf-8"); - const innerMatch = svgRaw.match(/]*>([\s\S]*?)<\/svg>/); - const innerSvg = innerMatch ? innerMatch[1] : svgRaw; // Inner content only, no wrapper - - mkdirSync(ASSETS_DIR, { recursive: true }); - const hue = hueFromProject(PROJECT); - - let ok = 0, - warn = 0, - err = 0; - - for (const [scene, spec] of Object.entries(SCENES)) { - const outPath = join(ASSETS_DIR, `${PROJECT}-${scene}.png`); - const overlayPath = join(ASSETS_DIR, `_${scene}_overlay.svg`); - const compPath = join(ASSETS_DIR, `_${scene}_comp.svg`); - - if (dryRun) { - console.log(` [dry] ${scene}: ${spec.width}ร—${spec.height} ${spec.bg}`); - continue; - } - - try { - // 1. Build overlay SVG with per-project hue variation - const overlay = buildOverlay(spec, hue); - writeFileSync(overlayPath, overlay); - - // 2. Build composed SVG: overlay background + scaled source.svg content - const scale = spec.scale ?? 0.8; - const sw = Math.round(spec.width * scale); - const sh = Math.round(spec.height * scale); - const sx = spec.center - ? Math.round((spec.width - sw) / 2) - : Math.round(spec.width * 0.05); - const sy = spec.center - ? Math.round((spec.height - sh) / 2) - : Math.round(spec.height * 0.05); - - // Render source content ON TOP of overlay - const composed = overlay.replace( - "", - `\n ${innerSvg}\n`, - ); - // Also add a subtle pattern for depth - writeFileSync(compPath, composed); - - // 3. Render via rsvg-convert - execSync( - `rsvg-convert -w ${spec.width} -h ${spec.height} -o "${outPath}" "${compPath}"`, - { stdio: "pipe", timeout: 10000 }, - ); - - // 4. Verify - const sz = statSync(outPath).size; - if (sz < spec.minBytes / 2) { - console.log(` โš ๏ธ ${scene}: ${sz}B (below ${spec.minBytes}B)`); - warn++; - } else { - ok++; - } - } catch (e) { - const errObj = e as { stderr?: string; message?: string }; - console.error(` โŒ ${scene}: ${errObj.stderr || errObj.message}`); - err++; - } finally { - try { - execSync(`rm -f "${overlayPath}" "${compPath}"`); - } catch {} - } - } - - console.log(`\n${dryRun ? "DRY RUN " : ""}โœ… ${ok} โš ๏ธ ${warn} โŒ ${err}`); - if (err > 0) process.exit(1); -} - -main();