You are reading this on a fresh macOS user account that has never seen creator-kit. By the end of this hour you have a published essay site at <your-name>-essays.highline.work returning 200, a passing ck validate --offline, and at least one entry in docs/agent-runtime/ledger.md. You read only this file. You do not need to ask anyone for help.
If something does not work the way the runbook says it will, that is a runbook bug. Open an issue against candlefish-ai/creator-kit with the exact command you ran and the output. Do not skip ahead and improvise. The kit’s contract is that this runbook produces the expected end-state; an improvisation hides a bug.
Peer contributors on creator-kit (M3 W12)
The kit has five peer contributors on candlefish-ai/creator-kit, all with admin access on the repo:
- Patrick Smith (
aspenas,[email protected]) - Aaron Westphal (
aaronwestphal,[email protected]) - Max Zajec (
maxzajec,[email protected]) - Tyler (
tyler812,[email protected]) - James Puttmann (
jamesputtmann,[email protected])
If you are any of the five picking this up cold: any friction you hit walking the runbook is the most useful kind of feedback the kit can get — log it as you go in docs/agent-runtime/ledger.md of your scaffolded project, and the kit’s next iteration absorbs the fix. There is no engagement-lead-and-tech-guide hierarchy here; everyone is on equal footing as a contributor. The substrate behind this site (Astro 5 + @aspenas/design-system + Cloudflare Pages) is the proper-tools-not-handwritten posture; the runbook below is its first contact with anyone other than the author.
Prerequisites — 5 minutes
-
macOS 14 or later. The kit has only been tested on Apple Silicon; Intel may work but is not validated.
-
A GitHub account on the
candlefish-aiorg. -
Tailscale installed and connected. The publishing surface uses cloudflared, but several internal probes assume the tailnet is up.
-
Cloudflared installed (
brew install cloudflared) and a tunnel already authenticated against your Cloudflare account. If you do not have one, see the publishing runbook for the one-time tunnel setup. -
Cloudflare account membership for the publishing zone (
highline.work). The publish step in this runbook uses<your-name>-essays.highline.work, which lives on Patrick’s Cloudflare account. You must be a member of that account with two specific Cloudflare roles:DNS— grants the CNAME edit thatcloudflared tunnel route dnswritesCloudflare Zero Trust— grants theteams.editpermission, which is what thecloudflared tunnel loginbrowser flow’s Authorize button checks under the hood (it surfaces as “Cloudflare One Networks Write” in the permission-error message). TheCloudflare Accessrole is not sufficient by itself — despite the name, it only grants edit on Cloudflare Access (the Zero Trust app-policy product), not Cloudflare Tunnel. They are different products under the Zero Trust umbrella; the parentCloudflare Zero Trustrole covers both.
Verify your roles before walking the runbook: run
cloudflared tunnel login. Three failure modes, three remediations:- Empty zone picker on the login page → you have no invite, or a pending invite you never accepted. Ping
[email protected]; he either sends a fresh invite (one API call,POST /accounts/{account_id}/members) or nudges you to find the original in your inbox (searchfrom:[email protected]). - Zone picker shows
highline.workbut Authorize fails with “Failed common permission check against resources. (Permission group: ‘Cloudflare One Networks Write’)” → you have DNS + Access but not Zero Trust. Ping Patrick; he adds theCloudflare Zero Trustrole to your membership (one PUT to/accounts/{account_id}/members/{member_id}). No re-accept needed. - Authorize succeeds → you’re done with the prereq, continue to Step 1.
Without the right roles, Steps 7-8 fail at the DNS-route step and
ck publish --executetimes out at the 200-on-/healthcheck. If you are publishing to a zone you own yourself, swap the surface in--hostand confirm you have DNS-edit and Zero-Trust-edit on that account instead.
Step 1 — clone and bootstrap, 5 minutes
git clone [email protected]:candlefish-ai/creator-kit.git ~/Work/creator-kit
cd ~/Work/creator-kit
./bootstrap.sh --user <your-name> --email <[email protected]i>
The bootstrap installs Homebrew packages idempotently, installs Claude Code, Codex, jq, uv, fswatch, cloudflared, and gh. It writes ~/.creator-kit/profile (mode 600) with your name and email, sets your global git author identity from the profile, and runs make install which symlinks the kit’s seven hooks and three agents into ~/.claude/.
Expected end state: bootstrap exits 0 and prints bin/ck doctor as the next step.
If bootstrap fails on gh auth login: run echo $GITHUB_TOKEN | gh auth login --with-token (using a token with repo scope) and re-run bootstrap.
Step 2 — verify with doctor, 1 minute
./bin/ck doctor
Expected: 0 FAIL rows, exit 0. The eleven checks are CLI presence (claude, codex, cloudflared, jq, uv, fswatch, gh authenticated), profile (mode 600 with user and email keys), git author email matches profile, the creator-dev agent is symlinked into ~/.claude/agents/, and fonts.candlefish.ai returns 200.
codex is the one optional CLI through M2. If you do not have it installed, doctor prints [WARN] codex CLI: not on PATH (optional through M2; required from M3 W10 — install via \brew install —cask codex`)`. The kit runs end-to-end without it through M2 W8; from M3 W10 onward the autonomous Codex drift loop needs it. The summary line distinguishes WARN from FAIL, and exit code stays 0 as long as no FAIL row prints.
If any check is FAIL, fix that one first before continuing. The most common is gh CLI authenticated — run gh auth login --web (or the keychain-based variant if you have a token cached locally).
Step 3 — scaffold your project, 1 minute
./bin/ck new <your-name>-essays --template essay-series --host <your-name>-essays.highline.work --quarter "M1 W3"
What happens:
- The kit copies
examples/essay-series/to~/Work/<your-name>-essays/. - It runs
ck init-runtimeagainst the new project, which renders six templates —CLAUDE.md,docs/agent-runtime/{README,current,known-drift,ledger}.md,docs/contracts/agent-runtime-files.json— with your name, email, surface, and quarter substituted. - It runs
git init, sets the per-repo author from your profile, and creates a single initial commit. - It runs
ck validate --offlineagainst the fresh tree. Expected: 6 PASS, 0 FLAG, 0 FAIL.
If validate is not all-PASS, the kit has a real bug. Stop and file an issue.
Step 4 — preview locally, 10 minutes
cd ~/Work/<your-name>-essays
uv venv .venv --python 3.13
uv pip install --python .venv/bin/python -r server/requirements.txt
.venv/bin/uvicorn server.serve:app --port 18210 --reload &
sleep 2
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18210/health # 200 expected
open "http://127.0.0.1:18210/"
Why --python 3.13: the example templates ship a .python-version pinning 3.13 (matches the kit’s own pin), but a fresh uv venv .venv with no flag falls back to the system python3 on macOS — which is 3.9.6 even on macOS 14+. The kit’s requirements.txt (FastAPI, Pydantic 2.x) needs 3.10+ for X | Y union syntax; without the pin you get a SyntaxError on uvicorn boot. The explicit --python 3.13 is belt-and-suspenders against the system-Python fallback (uv will fetch a managed 3.13 if needed). tyler-agent surfaced this bug 2026-05-04 walking the runbook.
If you are used to Python venvs that ship pip baked in: uv venv deliberately does not, because uv’s own resolver is the substitute. uv pip install --python <venv-python> writes into the named venv without needing to activate it. The .venv/bin/uvicorn invocation on the next line works because uv pip install populated the venv’s bin/ directory.
The two seed essays are at content/essays/00-on-open-loops.mdx and 01-what-this-site-is.mdx. They are real essays, not lorem ipsum — read them once. They demonstrate the Candlefish register the validate_essays.py validator enforces.
When you are ready to write your own, add content/essays/02-<your-slug>.mdx with the same frontmatter shape. The validator will run on your essay automatically as part of ck validate --offline.
Kill the local uvicorn before continuing: kill %1.
Step 5 — write your first real essay, 15 to 30 minutes
Replace content/essays/00-on-open-loops.mdx with one of yours, or add a new file. The frontmatter requirements are:
---
title: <sentence-case title, no terminal period>
slug: <lower-kebab-case>
date: 2026-05-02 # ISO-8601
tags: [register, candlefish] # YAML list
summary: <one sentence — the central claim>
---
The voice rules validate_essays.py enforces (run python3 validate_essays.py in the project root any time):
- Sentence case in titles. No emoji. No exclamation points outside fenced or inline code.
- Em-dashes (
—) preferred over double-hyphen (--). Inline code spans (`--offline`) are exempt. - Italic is rare. Bold for the central claim, not for emphasis.
- Every image carries non-empty alt text.
When the validator returns PASS, commit:
git add content/essays
git commit -m "feat: add <slug>"
Step 6 — provision the publish secret, 3 minutes
The Basic Auth password lives in ~/.config/<your-name>-essays.env, mode 600. It is never committed.
PROJECT_SLUG=<your-name>-essays
mkdir -p ~/.config && cat > ~/.config/$PROJECT_SLUG.env <<EOF
export ARCH_USER=<your-name>
export ARCH_PASSWORD=$(python3 -c 'import secrets; print(secrets.token_urlsafe(24))')
export ARCH_PORT=18210
EOF
chmod 600 ~/.config/$PROJECT_SLUG.env
Save the password somewhere durable (1Password, etc) so you can share with anyone who needs to view the surface.
Step 7 — publish, 5 minutes
ck publish # dry-run first (default)
ck publish --execute # actually launches the surface
What --execute does:
- Renders
serve.py,run-service.sh, the launchd plist, and the cloudflared ingress rule into.deploy/. - Copies the plist to
~/Library/LaunchAgents/and bootstraps it undergui/$(id -u). - Splices the cloudflared ingress rule into
~/.cloudflared/config.ymlabove the catch-all 404. - Kickstarts
com.cloudflaredso the new rule takes effect. - Waits up to 90 seconds for
https://<your-name>-essays.highline.work/healthto return 200. - Appends a publish row to
docs/agent-runtime/ledger.md.
If step 5 times out, run ck publish --diagnose — that prints the four standard checks (DNS, cloudflared launchd state, auth-pair env file, surface health) so you can see which step broke. The most common failures are: cloudflared tunnel route dns <tunnel> <surface> was never run (one-time DNS step), the surface is on fonts.candlefish.ai’s allowlist but its CSS request is being CORS-blocked, or the tunnel auth has expired. The publishing runbook covers each failure mode in detail.
Step 8 — verify and run the live validator, 2 minutes
curl -I https://<your-name>-essays.highline.work/health
# expect: HTTP/2 200, body {"status":"ok"}
ck validate --live # adds the live-route checks to the offline pass
Once ck validate --live returns zero FAIL rows and the ledger has at least two entries (the scaffold row from ck new plus the publish row from ck publish --execute), the M1 ship-test gate is met. You have moved from no kit to live published surface in under one hour.
Step 9 — append a closing ledger entry, 1 minute
cat >> docs/agent-runtime/ledger.md <<EOF
- $(date -u +%Y-%m-%dT%H:%M:%SZ) human: first-hour runbook complete. Surface at https://<your-name>-essays.highline.work/ returning 200; ck validate --live PASS; <N> essays published.
EOF
git add docs/agent-runtime/ledger.md
git commit -m "chore: ledger — first-hour runbook complete"
# Push to GitHub. Use `gh repo create` (HTTPS via gh's keychain) so this
# works on a fresh account that hasn't registered an SSH key with GitHub.
gh repo create <your-name>-essays --source=. --push --private
If you prefer SSH and have an SSH key registered with GitHub: git remote add origin [email protected]:<your-handle>/<your-name>-essays.git && git push -u origin main is the older two-step. gh repo create does both in one shot and uses HTTPS, which works without SSH-key setup.
The ledger is the cross-agent baton. Whoever picks up your project next — Codex during a drift-closing run, Claude during a synthesis pass, you yourself in a week — reads the ledger first.
What you do not need to do
- Configure secrets manually outside the env file. Everything else flows from
~/.creator-kit/profile. - Edit
CLAUDE.mdto rename project paths. The token replacement inck init-runtimealready did it. - Touch
~/Library/LaunchAgents/directly.ck publish --executeis the only path. - Manually splice cloudflared rules. Same.
- Vendor the Candlefish Design System by hand. The kit carries versioned copies of the generated tokens (
design/_system/tokens.css,aliases-cds.css;design/sync.shrefreshes them) plus the kit-authored_patterns.csssemantic layer; every surface loads them in order (tokens.css,aliases-cds.css,_patterns.css, then_example.css).
What to do when the runbook breaks
If a step does not produce the documented output, file the runbook bug. Do not improvise around it; the next reader inherits whatever workaround you build. The kit’s contract is that this file works end-to-end on a clean account.
Running this on a colleague’s machine
Same runbook. The kit is parameterized by --user and --email from the start, so there is nothing in bootstrap.sh, ck doctor, or any subsequent step that hardcodes one creator’s identity. The first creator’s surface and the second creator’s surface coexist on the same machine because cloudflared rules are per-hostname and launchd plists are per-project-slug.
See also
- Publishing deep-dive —
ck publishdeep-dive: cloudflared tunnel auth, fonts.candlefish.ai allowlist, Basic Auth secret rotation. - Templates — what each template ships and the route shape it forks to.