Football and Fantasy Premier League for five friends. No scroll, no bait.
Live: fiveaside.pages.dev · why this exists
Built on a simple model borrowed from antifeed: a static site whose only database is a JSON file, plus a brain — headless Claude Code, run locally — that gathers the facts, reads the week's discourse, and writes one calm, opinionated page.
The boundary rule: Python produces facts, the brain produces judgment, the site produces pixels.
| Room | URL | Job |
|---|---|---|
| touchline | / |
What happened. The league, the same for everyone. |
| the gaffers | /gaffers/ |
What we did about it. Five squads, five weekly reads. Behind an invite code. |
| the locker room | /locker/ |
What we know. Every player, evidence first. |
Any player's name, in any room, opens their file — that join is what makes it one product rather than three tabs.
fiveaside.config.json club, competitions, timezone, feeds, voice, the five
src/touchline/ facts CLI: `touchline facts` and `touchline fpl`
brain/ two prompts, three validators, the curate scripts
functions/ Pages Functions: FPL proxy, stars, auth, private data
site/ static reader; no framework, no build step, no CDNs
The rule the whole repo turns on:
| File | Written by | Contains |
|---|---|---|
players.json |
touchline fpl |
Every player, evidence only. Mechanical. |
gaffers.json |
touchline fpl |
Squads, picks, captaincy, chips. Mechanical, not published. |
fpl.json |
the brain | Judgment only — opinion with its reasoning attached. |
digests.json |
the brain | The league page. Append-only, one entry per date. |
If a value could be copied from the API, it does not belong in the file a language model writes. Routing 600 player records through one cost ~100k tokens a run to retype numbers, and every retyped number is a chance to get one wrong.
./brain/curate.sh --no-deploy # the league room
./brain/curate-fpl.sh --no-deploy # the gaffers room
./deploy.sh # stamp, split public/private, publishBoth brains also run themselves hourly via launchd (brain/auto.sh): the
mechanical data refreshes every hour, each brain at most once a day.
uv run pytest -q # Python suite
uv run ruff check . # lint
node brain/validate.mjs # the league database
node brain/validate-fpl.mjs # the judgment layer
node brain/validate-players.mjs # the player file
brain/test/smoke.sh https://fiveaside.pages.dev/ # live room/theme/control smoke suite
cd site && python3 -m http.server # local previewTo exercise the private room locally, run Pages Functions with a throwaway secret, then mint the invite into Wrangler's local KV state:
npx wrangler pages dev site --local --port 8787 \
--binding SESSION_SECRET=local-only-test-secret
node brain/invite.mjs --local "Xabi"The generated link points at http://localhost:8787; the code is never
written to production KV or the repository.
Working on this? Read ROADMAP.md for the state of play, then
AGENTS.md for the rules and the traps that have already cost
someone a debugging round.
The machine is generic; only the config is ours. Fork it, put your own club
and competitions in fiveaside.config.json, pick a data source
(espn+thesportsdb needs no API key), install
Claude Code, uv and node, then
npx wrangler login and ./deploy.sh.
Nothing runs centrally: the brain runs on your machine, with your preferences, and publishes to your own Cloudflare Pages project.
MIT — see LICENSE.