Something went wrong. Try again.
about things notes.zzstoatzz.io
notes
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247// build the notes wiki: walk the top-level section directories, copy every// markdown file into site-out/docs/, and emit a manifest.json whose shape the// client app (site/app.js, adapted from zat.dev) renders as a sidebar tree —// one top-level entry per section, files as children, section README as the// section index. the home page is generated from the tree.//// run: bun ./scripts/build-site.mjs
import { readdir, readFile, mkdir, rm, cp, writeFile, access } from "node:fs/promises";import path from "node:path";import { execFile } from "node:child_process";import { promisify } from "node:util";import { analyzeMarkdown } from "./markdown.mjs";import { renderOverviewTree } from "./tree.mjs";
const repoRoot = path.resolve(new URL("..", import.meta.url).pathname);const siteSrcDir = path.join(repoRoot, "site");const outDir = path.join(repoRoot, "site-out");const outDocsDir = path.join(outDir, "docs");
const execFileAsync = promisify(execFile);
// non-section directories at the repo rootconst EXCLUDE = new Set(["site", "site-out", "scripts", ".tangled", ".git", "node_modules"]);
async function exists(p) { try { await access(p); return true; } catch { return false; }}
async function listMarkdownFiles(dir, prefix = "") { const entries = await readdir(dir, { withFileTypes: true }); const out = []; for (const e of entries) { if (e.name.startsWith(".")) continue; const rel = path.join(prefix, e.name); const abs = path.join(dir, e.name); if (e.isDirectory()) { out.push(...(await listMarkdownFiles(abs, rel))); } else if (e.isFile() && e.name.toLowerCase().endsWith(".md")) { out.push(rel.replaceAll(path.sep, "/")); } } return out.sort((a, b) => a.localeCompare(b));}
// fold a section's flat file list into a tree: directories become nodes// (linked to their README when they have one), files become leaves. the// sidebar renders this recursively instead of flat "redis/streams" labels.function buildTree(section, files, titles) { const root = { children: [] }; const dirNodes = new Map([["", root]]);
function dirNode(dirPath) { if (dirNodes.has(dirPath)) return dirNodes.get(dirPath); const parent = dirNode(dirPath.split("/").slice(0, -1).join("/")); const node = { title: dirPath.split("/").pop(), children: [] }; dirNodes.set(dirPath, node); parent.children.push(node); return node; }
for (const rel of files) { const dir = rel.split("/").slice(0, -1).join("/"); const base = path.basename(rel, ".md"); const node = dirNode(dir); if (base.toLowerCase() === "readme") { if (dir === "") continue; // the section's own README is the section link node.path = `${section}/${rel}`; } else { node.children.push({ path: `${section}/${rel}`, title: titles.get(rel) || base }); } }
const sortNodes = (nodes) => { nodes.sort((a, b) => a.title.localeCompare(b.title)); for (const n of nodes) if (n.children) sortNodes(n.children); }; sortNodes(root.children); return root.children;}
async function getBuildId() { try { const { stdout } = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: repoRoot }); const full = String(stdout || "").trim(); if (full) return full.slice(0, 12); } catch { // not a git checkout — fall through } return String(Date.now());}
async function main() { await rm(outDir, { recursive: true, force: true }); await mkdir(outDir, { recursive: true }); await cp(siteSrcDir, outDir, { recursive: true });
// cache-bust immutable assets per commit const buildId = await getBuildId(); const outIndex = path.join(outDir, "index.html"); let html = await readFile(outIndex, "utf8"); html = html.replace(/(href|src)="\.\/(style\.css|app\.js|favicon\.svg|vendor\/[^"]+)"/g, `$1="./$2?v=${buildId}"`); await writeFile(outIndex, html, "utf8");
const outApp = path.join(outDir, "app.js"); let app = await readFile(outApp, "utf8"); app = app.replace(/from "(\.\/[^"?]+\.js)"/g, `from "$1?v=${buildId}"`); await writeFile(outApp, app, "utf8");
await mkdir(outDocsDir, { recursive: true });
// sources/ is hosted but not navigable: raw captures, not notes. copy the // files so links from writeups resolve, keep them out of the sidebar/home. const sourcesDir = path.join(repoRoot, "sources"); if (await exists(sourcesDir)) { for (const rel of await listMarkdownFiles(sourcesDir)) { const dst = path.join(outDocsDir, "sources", rel); await mkdir(path.dirname(dst), { recursive: true }); await cp(path.join(sourcesDir, rel), dst); } }
// discover sections: top-level directories containing markdown const rootEntries = await readdir(repoRoot, { withFileTypes: true }); const sectionDirs = rootEntries .filter( (e) => e.isDirectory() && !e.name.startsWith(".") && !EXCLUDE.has(e.name) && e.name !== "sources", ) .map((e) => e.name) .sort((a, b) => a.localeCompare(b));
const pages = []; const corpus = []; let fileCount = 0;
for (const section of sectionDirs) { const dir = path.join(repoRoot, section); const files = await listMarkdownFiles(dir); if (files.length === 0) continue;
const titles = new Map(); let sectionIndex = null; let noteCount = 0;
for (const rel of files) { const src = path.join(dir, rel); const dst = path.join(outDocsDir, section, rel); await mkdir(path.dirname(dst), { recursive: true }); await cp(src, dst); fileCount++; noteCount++;
const md = await readFile(src, "utf8"); const { title } = analyzeMarkdown(md, rel); titles.set(rel, title); corpus.push({ path: `${section}/${rel}`, title, content: md }); if (rel.toLowerCase() === "readme.md") sectionIndex = `${section}/${rel}`; }
const children = buildTree(section, files, titles);
// A section with no README gets a generated, progressively disclosed // index. Parent groups start closed; their own README remains available // through a small overview link. (The generated index isn't a note.) if (!sectionIndex) { const indexMd = [`# ${section}`, "", renderOverviewTree(children, section), ""].join("\n"); sectionIndex = `${section}/README.md`; await writeFile(path.join(outDocsDir, section, "README.md"), indexMd, "utf8"); }
pages.push({ path: sectionIndex, title: section, children, noteCount }); }
// generated home page: the taxonomy at a glance const homeMd = [ "# notes", "", "working notes, kept as they're learned. maintained in the open at", "[tangled.org/zzstoatzz.io/notes](https://tangled.org/zzstoatzz.io/notes).", "", "**provenance**: most of these notes aren't handwritten — they're distilled", "by an agent from working sessions with me, kept as a living corpus and meant", "to get more accurate over time. some are written by hand; the voice is", "shared. either way there will be mistakes nobody has noticed yet — treat a", "note as a useful distillation, not a final word. corrections welcome as", "issues or pulls.", "", ...pages.map((s) => `- **[${s.title}](${s.path})** — ${s.noteCount} note${s.noteCount === 1 ? "" : "s"}`), "", ].join("\n"); await writeFile(path.join(outDocsDir, "index.md"), homeMd, "utf8"); pages.unshift({ path: "index.md", title: "notes" });
await writeFile( path.join(outDir, "manifest.json"), JSON.stringify({ pages }, null, 2) + "\n", "utf8", );
// llms.txt (index per llmstxt.org) + llms-full.txt (the whole corpus in one // file, so `curl .../llms-full.txt | pbcopy` is the entire workflow) const SITE = "https://notes.zzstoatzz.io"; const bySection = new Map(); for (const doc of corpus) { const section = doc.path.split("/")[0]; if (!bySection.has(section)) bySection.set(section, []); bySection.get(section).push(doc); } const llms = [ "# notes.zzstoatzz.io", "", "> nate's working notes — ai, systems, operations, languages, protocols, storage, web.", "> mostly distilled by an agent from working sessions and curated over time;", "> treat a note as a useful distillation, not a final word.", "", `everything in one file: ${SITE}/llms-full.txt`, "", ]; for (const [section, docs] of bySection) { llms.push(`## ${section}`, ""); for (const doc of docs) { llms.push(`- [${doc.title}](${SITE}/docs/${doc.path})`); } llms.push(""); } await writeFile(path.join(outDir, "llms.txt"), llms.join("\n"), "utf8");
const full = corpus .map((doc) => `<!-- ${doc.path} — ${SITE}/docs/${doc.path} -->\n\n${doc.content.trim()}`) .join("\n\n---\n\n"); await writeFile( path.join(outDir, "llms-full.txt"), llms.slice(0, 6).join("\n") + "\n\n---\n\n" + full + "\n", "utf8", );
process.stdout.write(`built notes site: ${fileCount} note(s), ${pages.length - 1} section(s) -> ${outDir}\n`);}
await main();