zeekayandHanzo Dev 4a6aaa2982 no-transform: Cloudflare was injecting its beacon into the page
Routing `/` through a Worker changed one thing nobody asked for: this zone has
Web Analytics auto-injection on, and Cloudflare appends a
static.cloudflareinsights.com <script> to HTML a Worker returns. It leaves plain
assets alone — /page.html comes back byte-identical to the build — so the page
carried no beacon while it was a static file and picked one up the moment `/`
became a Worker response. hanzo.ai and hanzo.bot carry none. This host does not
ship third-party script it did not write; `cache-control: no-transform` is the
standard way to say so.

The deploy gate stops comparing `/` byte-for-byte against the built document,
because an edge injection the Worker requests and does not control must not be
able to fail a deploy. It compares the document at /page.html exactly, still
compares the installer at `/` exactly, and asserts about `/` what actually
matters: a browser gets 15 bytes of <!DOCTYPE html> and a page that names this
build's content-hashed stylesheet. That is the check that catches staleness,
which is the failure this host has actually had.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 16:28:46 -07:00
2026-02-14 05:26:47 -08:00

hanzo.sh

The install surface for the Hanzo tools. One command:

curl -fsSL https://hanzo.sh | sh

That downloads one prebuilt native binary per tool, verifies its sha256, and puts it on PATH — no runtime, no package manager, no build step:

tool also installed as source
hanzo hanzo-node hanzoai/cli
hanzo-mcp mcp hanzoai/mcp

Each pair is one build, symlinked, so the two names can never be two versions. Re-run to upgrade. One tool at a time:

curl -fsSL https://hanzo.sh/cli | sh
curl -fsSL https://hanzo.sh/mcp | sh

This repo

/ is two resources, chosen by the Accept header in worker.js: the built document (dist/page.html) to a browser, public/install.sh to curl. It used to be one polyglot file that was both at once — HTML wrapped in a shell heredoc — which no browser can be handed honestly: bytes before <!DOCTYPE html> put the document in quirks mode and every <head> element ends up in <body>. A POSIX script cannot begin with <! either, so there was no arrangement that worked. scripts/postbuild.js moves the document off index.html (an asset at that name outranks the Worker, and curl would get HTML) and writes /install next to /install.sh from the same bytes.

Downloading is NOT implemented here. hanzoai/cli/install.sh is the one implementation of "fetch a Hanzo binary" — platform detection, asset naming, checksum verification, the second-name symlink — and this drives it once per tool. Keep it that way; a second copy of platform detection is a second thing to drift.

pnpm install
pnpm dev      # vite, :8080
pnpm build    # -> dist/, page.html + install.sh
pnpm lint

Serve it exactly as Cloudflare will — same Worker, same asset rules — and test an installer change without deploying, under both published shells:

pnpm build && npx wrangler@3 dev --local --port 8788
curl -fsSL http://127.0.0.1:8788 | sh
curl -fsSL http://127.0.0.1:8788 | bash
curl -fsSL http://127.0.0.1:8788 -H 'Accept: text/html' | head -1   # <!DOCTYPE html>

Deploying

.github/workflows/deploy.yml builds, publishes the Cloudflare Worker, then re-fetches https://hanzo.sh and fails if the live bytes are not the bytes it just built. This host was hand-published for a long time, which is how a merged fix sat unseen for weeks while curl hanzo.sh | sh kept handing out the old installer — so verifying the live URL is part of deploying, not a follow-up.

A push to main deploys — measured, not assumed (run 30823263229 published 8a5d333). To republish without a commit:

gh workflow run deploy.yml -R hanzoai/hanzo.sh --ref main

What the job asserts, after publishing, is what a stranger gets:

curl -sS https://hanzo.sh | md5sum                        # the installer
curl -sS -H 'Accept: text/html' https://hanzo.sh | md5sum # the document

LLM.md has the details, including why the hanzoai/static image path is not a drop-in replacement for the Worker.

S
Description
Mirrored from git.hanzo.ai
Readme MIT
1.2 MiB
Languages
TypeScript 86.5%
Shell 8.6%
JavaScript 2.6%
HTML 1.2%
CSS 1.1%