Local HTML hosting for AI coding agents.
An agent (Claude Code, Pi the coding agent, anything that can shell out) generates a directory of static HTML, runs crate push ./dir name, and gets back http://localhost:7777/name/. The human opens that URL and sees the rendered artifact — a plan, an explainer, a code review — instead of skimming raw markdown in a chat window.
"Pi" throughout this repo means the Pi coding agent, not Raspberry Pi hardware.
Status: v0.1.0-dev — laptop-only out of the box, optional Docker for persistence, optional Tailscale exposure via tsdproxy. Caddy / nginx in front of the daemon is roadmap.
Requires Go 1.26+ and Task (brew install go-task).
task build # produces ./bin/crate and ./bin/crated
./bin/crated & # starts the daemon, generates config + token on first run of either binary
./bin/crate status # confirm the daemon is up
./bin/crate push ./some-html-dir my-site
./bin/crate open my-siteDefault URL is http://localhost:7777/. The daemon binds to 127.0.0.1 only (override with CRATE_LISTEN_ADDR).
For a persistent daemon that survives terminal sessions:
task docker:build # build the crate-html image
task docker:up # start crated on :7777 with persistent volumes
task docker:token # print just the bearer token (via `crate token` inside the container)
task docker:logs # tail the container's logs
task docker:down # stop the container (volumes preserved)The host crate CLI talks to the dockerized daemon via env vars — no config-file editing required:
eval "$(task docker:env)" # exports CRATE_TOKEN and CRATE_BASE_URL
./bin/crate ls # now talks to the dockerized daemon
./bin/crate push ./my-site demoThe env vars override anything in config.yaml for the lifetime of the shell. Unset them or open a new terminal to go back to the host-side daemon.
task docker:nuke deletes the volumes too — use when you want a clean slate.
For a real hostname like https://crate.<your-tailnet>.ts.net/, add three tsdproxy labels to the crated service in docker-compose.yml:
labels:
tsdproxy.enable: "true"
tsdproxy.name: "crate"
tsdproxy.port.1: "443/https:7777/http"tsdproxy auto-provisions the Tailscale node and TLS cert. Full setup including prerequisites: docs/deploy.md.
crate push <src> <name> upload a site; expires after 24h by default
crate push <src> <name> --expires 90m set a custom lifetime
crate push <src> <name> --expires never retain the site indefinitely
crate push <src> <name> -o push, then open the URL in a browser
crate ls list deployed sites
crate rm <name> remove a site
crate open <name> open the site in your default browser
crate status show daemon version and site count
crate token print the bearer token from the loaded config
Site names must match ^[a-z0-9][a-z0-9._-]{0,62}$. A global --config <path> flag (on both crate and crated) overrides the XDG config-file location.
The daemon checks expiry deadlines once a minute and removes elapsed sites. Expiry metadata is stored separately from site assets, so it is not publicly served. Sites created by older versions without expiry metadata are retained indefinitely.
cmd/crate/ CLI (kong) — push, ls, rm, open, status, token
cmd/crated/ HTTP daemon
internal/wire/ request/response types — the API contract
internal/config/ XDG config loader, first-run token generation
internal/storage/ filesystem ops, tar in/out, atomic site replacement
internal/server/ net/http handlers — bearer auth on /api/sites (status is public), static serve
internal/cliclient/ HTTP client used by `crate`
internal/builtin/ sites embedded in the binary (e.g. cratesplainer)
testdata/sites/ site fixtures used by smoke tests
.claude/skills/ Claude Code skill wrapping `crate push`
One Go module. internal/wire is the seam — both binaries import it; they don't import each other.
crated ships with one site embedded in the binary via go:embed:
/cratesplainer/— a deliberately-overexplained guide to using crate, useful when a new user lands on the daemon for the first time.
Disk sites win conflicts: if you crate push ./my-version cratesplainer, your copy is served instead. crate rm cratesplainer removes your override and the built-in resurfaces.
Paths resolve via github.com/adrg/xdg, which follows the XDG Base Directory Spec on Linux and the platform-native location on macOS.
| Purpose | Linux | macOS |
|---|---|---|
| Config | ~/.config/crate/config.yaml |
~/Library/Application Support/crate/config.yaml |
| Sites | ~/.local/share/crate/sites/ |
~/Library/Application Support/crate/sites/ |
| Logs | ~/.local/state/crate/log/ |
~/Library/Application Support/crate/log/ |
To force pure-XDG layout on macOS, set XDG_CONFIG_HOME / XDG_DATA_HOME / XDG_STATE_HOME in your shell.
task build # go build ./cmd/...
task test # go test ./...
task vet # go vet ./...
task tidy # go mod tidydocs/architecture.md— how it works end-to-end (logical view, API, push/serve protocols, deployment topology)docs/deploy.md— three deployment shapes (local, Docker, Docker + tsdproxy on Tailscale)docs/design.md— why the architecture is shaped the way it isdocs/naming.md— naming rationale and availability checkdocs/releases.md— Conventional Commit and Release Please workflowdocs/roadmap.md— near-term work and explicit non-goalsexamples/— optional compose overrides (currently: tsdproxy on Tailscale)
ClawBox from OpenClaw — a plug-and-play appliance hosting an agent-facing service. crate-html is the same shape, specialized for HTML, and runs locally rather than as an appliance.
MIT. Share, modify, ship it in your product — just keep the license notice with any substantial copy.