site/build.py
back to source
"""Build the numericalos.pages.dev static site.
Produces, into public/:
- the content pages, wrapped in a common shell
- numericalos.git/ a real dumb-HTTP git remote (git clone works)
- browse/ generated file tree, blobs, and commit log
- skills/ the agentic build skills, served as plain text
- index.json machine-readable skill index
- llms.txt agent discovery entry point
- AGENTS.md the multi-agent protocol
- skills.zip the whole skill bundle
Python 3 stdlib only. Run from the repo root: py site/build.py
"""
import html
import io
import json
import os
import shutil
import subprocess
import zipfile
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SITE = os.path.join(ROOT, "site")
# Overridable: on Windows another process can hold a handle on the git pack
# files under the output tree, which makes the pre-build clean fail. Building
# to a fresh directory sidesteps that without killing anyone else's process.
OUT = os.environ.get("NUMOS_OUT") or os.path.join(ROOT, "public")
SITE_URL = "https://numericalos.com"
NAV = [
("/", "home"),
("/docs/", "docs"),
("/whitepaper/", "whitepaper"),
("/skills/", "skills"),
("/browse/", "source"),
]
def _force_remove(func, path, _exc):
"""rmtree onerror hook: git packs are read-only, and Windows honors that."""
os.chmod(path, 0o700)
func(path)
def rmtree(path):
if os.path.isdir(path):
shutil.rmtree(path, onerror=_force_remove)
def git(*args):
"""Run a git command in the repo and return stripped stdout."""
return subprocess.run(
["git", "-C", ROOT] + list(args),
capture_output=True, text=True, check=True,
).stdout.strip()
def write(relpath, text):
path = os.path.join(OUT, relpath)
parent = os.path.dirname(path)
if parent and not os.path.isdir(parent):
os.makedirs(parent)
with open(path, "w", encoding="utf-8", newline="\n") as handle:
handle.write(text)
def shell(title, body, active="", description=""):
"""Wrap a body fragment in the common page shell."""
nav = "".join(
'<a href="%s"%s>%s</a>' % (href, ' class="on"' if label == active else "", label)
for href, label in NAV
)
return PAGE % {
"title": html.escape(title),
"description": html.escape(description or title),
"nav": nav,
"body": body,
}
PAGE = """<!doctype html>
<html lang="en"><head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>%(title)s</title>
<meta name="description" content="%(description)s">
<link rel="stylesheet" href="/assets/site.css">
</head><body>
<header><a class="brand" href="/">NumericalOS</a><nav>%(nav)s</nav></header>
<main>%(body)s</main>
<footer>
<p>NumericalOS — a Linux userspace whose init is a graph.
<strong>Status: bootstrap logic tested; boot unverified.</strong></p>
<p><a href="/numericalos.git">git</a> ·
<a href="/llms.txt">llms.txt</a> ·
<a href="/AGENTS.md">AGENTS.md</a> ·
<a href="/index.json">index.json</a> ·
<a href="/skills.zip">skills.zip</a></p>
</footer>
</body></html>
"""
# ---------------------------------------------------------------- skills
def parse_frontmatter(text):
"""Return (meta, body) for a SKILL.md with YAML-ish frontmatter."""
if not text.startswith("---"):
return {}, text
end = text.find("\n---", 3)
if end == -1:
return {}, text
raw = text[3:end].strip()
body = text[end + 4:].lstrip("\n")
meta = {}
key = None
for line in raw.split("\n"):
if line and not line[0].isspace() and ":" in line:
key, _, value = line.partition(":")
key = key.strip()
meta[key] = value.strip()
elif key and line.strip():
meta[key] = (meta[key] + " " + line.strip()).strip()
return meta, body
def collect_skills():
"""Read every skills/*/SKILL.md into a sorted list of dicts."""
skills = []
base = os.path.join(ROOT, "skills")
for name in sorted(os.listdir(base)):
path = os.path.join(base, name, "SKILL.md")
if not os.path.isfile(path):
continue
with open(path, encoding="utf-8") as handle:
text = handle.read()
meta, _ = parse_frontmatter(text)
skills.append({
"name": meta.get("name", name),
"description": meta.get("description", ""),
"path": "skills/%s/SKILL.md" % name,
"url": "%s/skills/%s/SKILL.md" % (SITE_URL, name),
"bytes": len(text.encode("utf-8")),
"text": text,
})
return skills
def build_skills(skills):
"""Serve each SKILL.md verbatim, plus an index page."""
for skill in skills:
rel = skill["path"].replace("skills/", "skills/", 1)
write(rel, skill["text"])
rows = "".join(
'<tr><td><a href="/%s">%s</a></td><td>%s</td></tr>'
% (skill["path"], html.escape(skill["name"]),
html.escape(skill["description"]))
for skill in skills
)
body = """
<h1>Agentic build skills</h1>
<p class="lead">These skills support <em>you</em> building NumericalOS targets on
your own machine. Nothing here generates images and nothing here hosts them.</p>
<p>Point your agent at <a href="/AGENTS.md">AGENTS.md</a> for the protocol, or
take the whole bundle: <a href="/skills.zip">skills.zip</a>.</p>
<table class="skills"><thead><tr><th>skill</th><th>when it fires</th></tr></thead>
<tbody>%s</tbody></table>
<h2>The honesty contract</h2>
<p>Only <code>numericalos-verify-boot</code> may claim a boot, and only for the
artifact and architecture it observed. Build skills produce artifacts and say
exactly that. This project has never been booted by its authors — if you
build a target, you may be producing its first real boot.</p>
""" % rows
write("skills/index.html", shell("Skills - NumericalOS", body, "skills",
"Agentic skills for building NumericalOS targets locally."))
def build_bundle(skills):
"""skills.zip - the whole bundle including AGENTS.md."""
buffer = io.BytesIO()
with zipfile.ZipFile(buffer, "w", zipfile.ZIP_DEFLATED) as archive:
for skill in skills:
archive.writestr(skill["path"], skill["text"])
with open(os.path.join(ROOT, "skills", "AGENTS.md"), encoding="utf-8") as handle:
archive.writestr("skills/AGENTS.md", handle.read())
with open(os.path.join(OUT, "skills.zip"), "wb") as handle:
handle.write(buffer.getvalue())
def build_discovery(skills, head):
"""index.json + llms.txt + AGENTS.md at the root."""
index = {
"name": "numericalos",
"description": "A Linux userspace whose init is a graph export.",
"site": SITE_URL,
"status": "bootstrap logic tested; boot unverified",
"commit": head,
"clone": "%s/numericalos.git" % SITE_URL,
"agents": "%s/AGENTS.md" % SITE_URL,
"bundle": "%s/skills.zip" % SITE_URL,
"skills": [
{k: s[k] for k in ("name", "description", "path", "url", "bytes")}
for s in skills
],
}
write("index.json", json.dumps(index, indent=2, sort_keys=True) + "\n")
lines = [
"# NumericalOS",
"",
"> A Linux userspace whose init is a graph export. No unit files: every",
"> supervisable thing, boot ordering constraint, health rule, and",
"> remediation path is a node in an M/G/S/MGS graph quartet.",
"",
"Status: bootstrap logic tested; boot unverified. The authors have never",
"booted it, on hardware or in emulation. See /docs/status.",
"",
"## Build it yourself",
"",
"These skills drive YOUR agent to build targets on YOUR machine. This site",
"does not generate images and does not host them.",
"",
]
for skill in skills:
lines.append("- [%s](%s): %s" % (skill["name"], skill["url"], skill["description"]))
lines += [
"",
"## Protocol",
"",
"- [AGENTS.md](%s/AGENTS.md): multi-agent build protocol and honesty contract" % SITE_URL,
"- [index.json](%s/index.json): machine-readable index" % SITE_URL,
"- [skills.zip](%s/skills.zip): the whole bundle" % SITE_URL,
"",
"## Source",
"",
" git clone %s/numericalos.git" % SITE_URL,
"",
"Served as static files over dumb-HTTP. No forge, no account, no",
"server-side code. Verify what you got before you run it.",
"",
]
write("llms.txt", "\n".join(lines))
shutil.copyfile(os.path.join(ROOT, "skills", "AGENTS.md"),
os.path.join(OUT, "AGENTS.md"))
# ---------------------------------------------------------------- git
def build_git_remote():
"""A real dumb-HTTP git remote: git clone <site>/numericalos.git works.
Packed into a single packfile so the object count stays small, then
update-server-info writes the indexes the dumb protocol needs.
"""
dest = os.path.join(OUT, "numericalos.git")
rmtree(dest)
subprocess.run(["git", "clone", "--bare", "--quiet", ROOT, dest], check=True)
# Explode the pack into loose objects.
#
# The dumb protocol probes objects/<sha> directly. With everything packed
# that probe 404s, and git inflates the 404 body before checking status --
# printing "inflate: data stream error ... corrupt" and only THEN falling
# back to the pack. The clone succeeds, but a documented clone command
# that prints "corrupt" is not shippable. Loose objects make the probe hit.
packdir = os.path.join(dest, "objects", "pack")
packs = [p for p in os.listdir(packdir) if p.endswith(".pack")]
for pack in packs:
with open(os.path.join(packdir, pack), "rb") as handle:
subprocess.run(["git", "-C", dest, "unpack-objects", "-q"],
stdin=handle, check=True)
for name in os.listdir(packdir):
os.chmod(os.path.join(packdir, name), 0o700)
os.unlink(os.path.join(packdir, name))
subprocess.run(["git", "-C", dest, "update-server-info"], check=True)
# A bare clone carries hooks samples and a local-path origin; neither
# belongs on a public remote.
rmtree(os.path.join(dest, "hooks"))
subprocess.run(["git", "-C", dest, "remote", "remove", "origin"],
capture_output=True)
return dest
# ---------------------------------------------------------------- browse
TEXT_EXT = {".py", ".sh", ".md", ".json", ".txt", ".toml", ".cfg", ".yml", ".yaml"}
def build_browse(head):
"""Generated source browser: file tree, blobs, and commit log."""
listing = git("ls-tree", "-r", "--name-only", "HEAD").split("\n")
listing = [p for p in listing if p]
rows = []
for path in listing:
size = git("cat-file", "-s", "HEAD:%s" % path)
rows.append(
'<tr><td><a href="/browse/%s.html">%s</a></td><td class="num">%s</td></tr>'
% (html.escape(path), html.escape(path), size)
)
_build_blob(path)
log = git("log", "--pretty=format:%h\x1f%an\x1f%ad\x1f%s", "--date=short").split("\n")
entries = []
for line in log:
parts = line.split("\x1f")
if len(parts) != 4:
continue
sha, _author, date, subject = parts
entries.append(
'<tr><td class="mono">%s</td><td class="num">%s</td><td>%s</td></tr>'
% (html.escape(sha), html.escape(date), html.escape(subject))
)
body = """
<h1>Source</h1>
<p class="lead">Generated from commit <code>%s</code>. This is a rendering; the
repository itself is the authority:</p>
<pre class="cmd">git clone %s/numericalos.git</pre>
<h2>Files (%d)</h2>
<table class="files"><thead><tr><th>path</th><th class="num">bytes</th></tr></thead>
<tbody>%s</tbody></table>
<h2>History (%d commits)</h2>
<table class="files"><thead><tr><th>commit</th><th class="num">date</th><th>subject</th></tr></thead>
<tbody>%s</tbody></table>
""" % (head, SITE_URL, len(listing), "".join(rows), len(entries), "".join(entries))
write("browse/index.html", shell("Source - NumericalOS", body, "source",
"Browse the NumericalOS source and history."))
def _build_blob(path):
ext = os.path.splitext(path)[1]
if ext not in TEXT_EXT:
return
try:
content = git("show", "HEAD:%s" % path)
except subprocess.CalledProcessError:
return
body = """
<h1 class="mono">%s</h1>
<p><a href="/browse/">back to source</a></p>
<pre class="code">%s</pre>
""" % (html.escape(path), html.escape(content))
write("browse/%s.html" % path, shell(path + " - NumericalOS", body, "source"))
# ---------------------------------------------------------------- content
def build_content(head):
"""Wrap the hand-written content fragments in the site shell."""
content_dir = os.path.join(SITE, "content")
for name in sorted(os.listdir(content_dir)):
if not name.endswith(".html"):
continue
with open(os.path.join(content_dir, name), encoding="utf-8") as handle:
raw = handle.read()
title, _, fragment = raw.partition("\n")
title = title.replace("<!--", "").replace("-->", "").strip()
fragment = fragment.replace("{{COMMIT}}", head)
stem = name[:-5]
if stem == "index":
rel, active = "index.html", "home"
elif stem == "whitepaper":
rel, active = "whitepaper/index.html", "whitepaper"
else:
rel, active = "docs/%s/index.html" % stem, "docs"
if stem == "docs":
rel = "docs/index.html"
write(rel, shell(title, fragment, active))
def build_404():
"""A real 404 page - load-bearing, not decoration.
Without 404.html, Cloudflare Pages answers unmatched paths with the root
index.html and status 200. Git's dumb-HTTP protocol probes for loose
objects that legitimately do not exist (everything is packed); receiving
HTML with a 200 instead of a 404, it reports 'inflate: data stream error'
and the clone breaks. A real 404 lets git fall through to the packfile.
"""
body = """
<h1>404</h1>
<p class="lead">No such page.</p>
<p><a href="/">home</a> · <a href="/docs/">docs</a> ·
<a href="/skills/">skills</a> · <a href="/browse/">source</a></p>
"""
write("404.html", shell("404 - NumericalOS", body))
def build_headers():
write("_headers", """/*
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
/numericalos.git/*
Content-Type: application/octet-stream
Cache-Control: no-cache
/skills/*
Content-Type: text/markdown; charset=utf-8
/llms.txt
Content-Type: text/plain; charset=utf-8
/AGENTS.md
Content-Type: text/markdown; charset=utf-8
""")
write("robots.txt", "User-agent: *\nAllow: /\nSitemap: %s/sitemap.xml\n" % SITE_URL)
def main():
rmtree(OUT)
os.makedirs(OUT)
head = git("rev-parse", "--short", "HEAD")
skills = collect_skills()
shutil.copytree(os.path.join(SITE, "assets"), os.path.join(OUT, "assets"))
build_content(head)
build_skills(skills)
build_bundle(skills)
build_discovery(skills, head)
build_browse(head)
build_git_remote()
build_404()
build_headers()
count = sum(len(files) for _, _, files in os.walk(OUT))
print("built %s at commit %s: %d files, %d skills" % (OUT, head, count, len(skills)))
if __name__ == "__main__":
main()