#!/usr/bin/env tsx import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, statSync, rmSync } from 'node:fs' import { join, dirname, relative, basename, extname, sep, resolve } from 'node:path' import { fileURLToPath } from 'node:url' import { marked, Renderer } from 'marked' const ROOT = fileURLToPath(new URL('..', import.meta.url)) const SRC_DIRS = ['docs/academy', 'docs/adr'] const OUT_DIR = join(ROOT, 'site') const GITHUB_BLOB = 'https://github.com/fworks-tech/agenthood/blob/main' // build-progress output is deliberate (this is a build script); ACADEMY_QUIET // silences it for callers that want a clean stdout const QUIET = process.env.ACADEMY_QUIET === '1' const LOG = (message: string): void => { if (!QUIET) console.log(message) } /** Recursively walk a directory and return all `.md` file paths. */ function walk(dir: string): string[] { const files: string[] = [] for (const entry of readdirSync(dir)) { const full = join(dir, entry) if (statSync(full).isDirectory()) { files.push(...walk(full)) } else if (extname(full) === '.md') { files.push(full) } } return files } const CSS = ` body { max-width: 42rem; margin: 2rem auto; padding: 0 1rem 4rem; font-family: system-ui, -apple-system, sans-serif; line-height: 1.75; color: #f4f4f5; background: #09090b; font-size: 1rem; } h1, h2, h3 { line-height: 1.3; margin-top: 2rem; color: #ffffff; } h1 { font-size: 1.75rem; border-bottom: 2px solid #27272a; padding-bottom: .5rem; } h2 { font-size: 1.35rem; } h3 { font-size: 1.1rem; } a { color: #a78bfa; } a:hover { text-decoration: underline; } pre, code { font-size: .9em; background: #18181b; border-radius: 6px; } code { padding: .15em .3em; border: 1px solid #27272a; } pre { padding: 1rem; overflow-x: auto; border: 1px solid #27272a; } pre code { background: none; padding: 0; border: none; } blockquote { margin: 1rem 0; padding: .5rem 1rem; border-left: 4px solid #a78bfa; background: #18181b; } table { border-collapse: collapse; width: 100%; margin: 1rem 0; } th, td { border: 1px solid #27272a; padding: .5rem; text-align: left; } th { background: #18181b; font-weight: 600; } ol, ul { padding-left: 1.5rem; } li { margin: .25rem 0; } hr { border: none; border-top: 1px solid #27272a; margin: 2rem 0; } main { min-height: 60vh; } ` /** Escape text for a literal HTML context (element content or attribute). */ function escapeHtml(text: string): string { return text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') } /** Wrap HTML body content in a full document with dark‑theme styling and a titled . */ export function htmlTemplate(title: string, body: string): string { // title is derived from the first H1 of a markdown file, so it is rendered // into raw when the file content is trusted — but escape anyway so a // doc containing `