The Brief → a team change with Undo → food in your words · watch the full 2.0 trailer · fictional demo data only
![]() |
It reads your day.Today opens on an answer, not a dashboard. Rest, easy or train, in one sentence, with the evidence a tap away. That evidence is last night's sleep, your HRV against your own band, the load you carried this week, and the knee you mentioned on Tuesday. When the answer is easy, it offers easy things that still count. When you want to train anyway, the button says so.A suggestion, never a gate. A missing night says nothing, never "bad". |
The team decides. You can Undo.Strength, endurance, nutrition, physio and an informational health seat each watch their part of you. When the evidence is clear, a bounded change (the next load step, an eased calorie target) just lands, with its why written in plain words. Every change sits in one Changes feed, labelled by the server with exactly what Undo restores. Anything clinical, irreversible or goal-changing still asks first.Autonomy is server policy, not model discretion. Every decision is recorded with a falsifiable expectation. |
![]() |
![]() |
Food, in your words."Salmon rice bowl, about 140 g of fish." One composer, shared by Fuel and chat, turns a line per food (or a photo of the plate) into a meal card you can correct row by row. Ideas come from your own staples, one meal at a time, and nothing counts as eaten until you log it. A thin logging day reads as absent, never as "low".Adaptive nutrition that never blames you. change: false is the common answer. |
A race build sized to you.Name a half marathon and Horizon draws the road to it week by week, from the run engine's own next steps. The best week you've already run safely sets the floor, and the peak aims just past it. A down week is recovery, not lost ground. A hard lifting day never voids a running week. The finish estimate is a fit word (fits, stretch, beyond horizon), never a grade.Kilometres or miles, your call. Strength-led athletes keep progressing all the way to race week. |
![]() |
![]() |
Six stones. One picture.You is a cairn: strength, endurance, fuel, recovery, body and heart, each with one word for where it stands right now. Tap a stone to see what it is made of and which of its neighbours it leans on. A ferritin that is still catching up quietly holds the running back. A heart stone "worth noting" points at the lab that says why.Words and your own baseline, never a 0–100. |
Labs that travel.A flagged marker doesn't sit in a PDF. It propagates into the meals, the training and the watch, each with its why and a citation. Records are grouped in clinical order, with the lab's own flag kept apart from the optimal band. Before a visit, the doctor packet builds itself: findings, your questions, body composition, results by panel. Toggle sections, preview, hand it over.Informational, never medical advice. Anything clinical defers to a clinician. |
![]() |
![]() |
One line of time.Horizon puts the week, the race and the season on the same line. The week shows every sport as a dose: the moved long run is still the long run, and Saturday's ride is the cross-training day. The season shows the goal line with its likely window, with lab draws and scans pinned where they happened.Every logged activity counts. No sport is ever dose zero. |
![]() Today. The Brief as the page's voice, your path to the race, today's lift and fuel at a glance. |
![]() Session. Compact cards, with a dawn ring on the lift you're on. |
![]() Changes. What the team changed, why, and exactly what Undo restores. |
![]() Ask: what if. The ripple across every stone first. Nothing moves unless you say so. |
![]() Horizon. The race build as terrain: logged weeks, planned weeks, the long run. |
![]() Race. This week's shape, next week's runs, the ladder to race day. |
![]() You. Six parts of you, read together. |
![]() A stone, opened. What it's made of and the neighbours it leans on. |
![]() Health. Where you stand, and what each finding connects to. |
![]() Doctor packet. Clinical order, your questions, ready to hand over. |
![]() Train. Today's lift leads, then a voice instead of a stat wall. |
![]() And at night. Every surface, in a dark theme built for it. |
![]() Logged in Cairn… A session as you logged it on your phone: sets, loads, a timed hold. |
![]() …lands on Garmin. The same workout, as that day's strength activity, muscle map and all. |
![]() In the watch's own words. Every set in Garmin's exercise vocabulary. |
![]() Cairn 2.0. Self-hosted. MIT. Runs happily on a Raspberry Pi. |
npm run seed:demo, and re-record every clip with npm run promo.
## Quickstart (30 seconds)
The easiest way is the one command, which asks whether Cairn should run on Railway or on this
machine, sets it up and opens it signed in ([`docs/INSTALL.md`](docs/INSTALL.md)):
```bash
curl -fsSL https://cairn.fit/install | sh
```
Rather run the container yourself? You don't need the source. The image is published to GHCR
(multi-arch, amd64 + arm64):
```bash
docker run -d --name cairn -p 127.0.0.1:8787:8787 \
-v cairn-data:/data -v cairn-home:/home/app \
-v cairn-tools:/home/app/.cairn-tools \
--restart unless-stopped ghcr.io/zilet/cairn:latest
```
(Podman works as-is: `podman run …`.) Open **http://localhost:8787** — you land on the Brief immediately. Three named volumes keep your
data (`cairn-data`), your CLI logins (`cairn-home`), and any tools you install (`cairn-tools`)
across updates, so rebuilds touch none of them. To update: `docker pull ghcr.io/zilet/cairn:latest`
and re-run. Add `-e TZ=America/New_York` for your timezone — set your own.
Prefer a compose file, with the env vars and loopback-safe defaults already wired up? Or want the
source, to build locally and develop?
```bash
curl -LO https://github.com/zilet/cairn/releases/latest/download/docker-compose.yml && docker compose up -d
git clone https://github.com/zilet/cairn.git && cd cairn && ./quickstart.sh
```
`quickstart.sh` resolves a running container engine — Docker, Podman, or Apple's `container`,
whichever is installed and answering (preferred, no Node needed on the host) — or falls back to
local Node, starts Cairn, waits for health, and prints the URL. **Node 24+ is required** (26 recommended — it's what the image ships) for a
non-container run — that's where `node:sqlite` is unflagged. The container image bundles it.
**First paint is real**, no agent required. For chat, adaptive coaching, and meal plans, add **one**
agent: open **You → Agents**, tap **Install** on the provider you use, then **Connect**. A
terminal opens in the browser and walks you through that provider's sign-in — no `docker exec`
needed. An agent you haven't connected stays out of the rotation rather than failing requests. Full
detail, including terminal logins and the Grok API-key path:
[Connect your first agent](docs/QUICKSTART.md#connect-your-first-agent).
> [!IMPORTANT]
> **Security & where to run it.** Cairn ships with **no authentication by default** — it is a
> single-user app built for a network you already trust, **not the public internet**. Run it on your
> own machine, a home server, or a small VM. The simplest setup that is both private and reachable
> from your phone: put the host on a [Tailscale](https://tailscale.com) (or similar) network and open
> it by its **MagicDNS** name — no ports forwarded, no certificates to manage. **Never expose port
> `8787` to the open internet.** If any untrusted device can reach it, set `CAIRN_AUTH_TOKEN` to
> require a shared token, and serve it over HTTPS (Tailscale Serve / a private reverse proxy) so the
> PWA can install offline. See [`SECURITY.md`](SECURITY.md) and [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md).
> The one-click hosted templates ([`docs/HOSTING.md`](docs/HOSTING.md)) do put Cairn on a public URL,
> so they always require a token (`CAIRN_REQUIRE_AUTH=1`) and serve it over the platform's HTTPS.
### What works out of the box vs. what needs a coaching agent
Cairn is **fully usable the moment it boots**, with no agent and no API key. A coaching agent
adds the conversational and generative layer on top — and the app stays useful while you set
one up.
| Works out of the box (no agent) | Needs a logged-in coaching agent |
|---|---|
| The Brief — calm rest/easy/train suggestion | The agentic Brief sentence (the plain-language read on top) |
| Set-by-set logging, history, PRs, est-1RM | Coach **chat** |
| The plan editor (add/remove/reorder days) | Agent-shaped training and meal adaptations |
| Bodyweight chart, goal feasibility check | Health-review **narrative** |
| Optimal-zone marker trends & the marker catalog | Lab-document **marker extraction** (upload → structured markers) & quiet **insights** / weekly read |
| Recovery view, deterministic TDEE / expenditure | Recipe generation, single-meal swaps |
| Activities, food notes, memory, family, life context | Background enrichment of free-text logs |
| Endurance stats, run compliance, race countdown, PRs | Agent-refined weekly run prescriptions |
| Movement considerations, strength milestones and fit words | The weekly team review (one step per milestone) |
A **coaching agent** means one of the supported CLIs — **Claude Code**, **Codex**,
**Antigravity**, or **Grok** — installed into Cairn's persistent home volume **and logged in** with
your own account. There is no shared key and no built-in model: install only providers you use. The
built-in `stub` agent exercises Cairn's offline agent contract with no key, for smoke testing before
you connect a real coach.
### Pick a run target
| If you want... | Start here |
|---|---|
| The easiest setup, anywhere | `curl -fsSL https://cairn.fit/install \| sh` (Railway or this machine) |
| Just run it on your laptop (no clone) | the `docker run … ghcr.io/zilet/cairn:latest` above |
| Build from source / develop | `./quickstart.sh` |
| Keep it always-on at home | `./scripts/quickstart-rpi.sh` on a Raspberry Pi or small home box |
| Put it on your phone as an installable PWA | `./scripts/setup-phone.sh` (Tailscale Serve, tailnet-only) |
| Give a household member a private profile | Run one isolated released instance per person; see [`docs/HOUSEHOLDS.md`](docs/HOUSEHOLDS.md) |
| Run it on a cheap VM | Docker + Tailscale; see [`docs/QUICKSTART.md#small-vm-private-online-box`](docs/QUICKSTART.md#small-vm-private-online-box) |
| Try it on demand in the cloud | [`docs/SANDBOX.md`](docs/SANDBOX.md) for Daytona / Codespaces |
## MCP server
Cairn is agent-native: the same logic the PWA uses is exposed as an MCP server at `/mcp`
(Streamable HTTP), so a Claude client — or any other MCP client — can read and write everything.
Point Claude Code at it with one command:
```bash
claude mcp add --transport http cairn http://localhost:8787/mcp
```
**300+ MCP tools** span the plan, sessions and exercises, the accountable coaching loop, profile and
goal, activities and bodyweight, memory, meal plans and recipes, health records and markers, the
connected-brain directives and insights, recovery, chat, Garmin sync, and settings. A representative
slice: `get_plan`, `log_set`, `get_day_read`, `suggest_session`, `draft_plan_update`,
`apply_proposal`, `draft_meal_plan`, `swap_meal`, `get_priority_markers`, `list_directives`,
`generate_insight`, `log_activity`, `log_food_note`, `set_profile`, `sync_garmin`.
This is where vision and loose natural language belong — snap a plate and say *"this was lunch,
estimate it"*, or *"log my ride: 2h in the fells, felt strong"*. Packaged Claude Code and Codex
skills at `.claude/skills/cairn/` and `.agents/skills/cairn/` map everyday phrases to these tools.
The same surface is reachable over REST — **300+ routes** under `/api`. Both
indexes are generated from source and never hand-edited: [`docs/MCP-TOOLS.md`](docs/MCP-TOOLS.md)
and [`docs/API.md`](docs/API.md).
## Why not just use…
**MacroFactor?** Its adherence-neutral expenditure model is the right way to do adaptive nutrition,
and Cairn adopts the same philosophy on purpose. The difference is that here nutrition is one domain
rather than the whole product — a meal target can be shaped by a flagged lab or suppressed during a
travel week, automatically. *MacroFactor wins* if you want a refined single-purpose nutrition app
maintained for you and you don't want to host anything.
**Oura / Garmin / Whoop?** Wearables are the best in the world at measurement, and Cairn reads
*from* them rather than replacing them. What it adds is synthesis instead of a metric wall, and the
loop your watch leaves open: Garmin records the run you did, Cairn writes the run you should do
next — and a finished strength session goes back out onto the watch as that day's exercise sets.
*The wearable wins* on precise passive measurement — keep it, and let Cairn talk to it.
**ChatGPT and a spreadsheet?** The closest comparison, and genuinely capable. Cairn is what happens
when you make that loop durable: every coaching call is already grounded in your profile, plan,
sessions, labs and memory, a flagged lab *propagates* into directives still there next week, and
nothing changes your plan outside server-owned policy: a bounded change lands with its why and an
Undo, and anything clinical still asks. *ChatGPT wins* for a one-off question
with zero setup.
**Another self-hosted tracker?** Most are excellent ledgers — they record what you did, well. Cairn
is trying to be the thing that reads across those records and points at one action. *A focused
tracker wins* if you want a stable offline log and no agent in the loop at all.
The longer, fully honest version is [`docs/WHY-CAIRN.md`](docs/WHY-CAIRN.md).
## What Cairn is not
- **Not a social app.** No feed, no friends, no leaderboards, no sharing.
- **Not a multi-user SaaS.** Cairn is single-user by design. No account system, no billing, no team
management.
- **Not medical advice.** Health findings are informational. It is a buddy who reads your numbers,
not a doctor — anything clinical defers to a clinician.
- **Not a wearable.** It has no sensors of its own; it reads from the ones you already have.
- **Not an engagement machine.** No streaks, points, badges, push nags, or upsell. It has nothing to
sell and no one to retain. If it ever feels like it's trying to keep you in the app, that's a bug.
- **Not zero-setup.** It's self-hosted. That's the price of owning your data and your model.
## Your data: what stays, what leaves
Everything Cairn knows about you lives in one SQLite file on your own machine — there's no
Cairn-run server for it to sync to. When an agentic feature runs (chat, adaptive coaching, a
generated meal plan, a health review), your coach context goes to whichever model provider you
connected — Anthropic, OpenAI, Google, or xAI — for that one call, and nowhere else. With no agent
configured, the deterministic core (the Brief, logging, plans, charts, the connected brain) sends
none of your data anywhere. Garmin and Apple Health sync talk only to those services, using your own
credentials. Feedback reaches the project only when you press Send, the weekly usage ping is off
unless you turn it on, and the installer counts an install anonymously (`--no-telemetry` skips it).
The complete list, including the daily release check:
[What leaves your install](docs/HOSTING.md#what-leaves-your-install).
## Docs
| Doc | What it covers |
|---|---|
| [`docs/QUICKSTART.md`](docs/QUICKSTART.md) | 30-second start, Raspberry Pi, VM, Docker, Node, agent setup |
| [`docs/HOSTING.md`](docs/HOSTING.md) | Run it without being an engineer: one command, two choices (Railway or this machine), the no-terminal Railway button, or Docker |
| [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) · [`docs/OPERATIONS.md`](docs/OPERATIONS.md) | Tailscale, HTTPS PWA, updates, migrations, backup/restore |
| [`docs/HOUSEHOLDS.md`](docs/HOUSEHOLDS.md) · [`docs/SANDBOX.md`](docs/SANDBOX.md) | Private profiles per person; Daytona / Codespaces |
| [`docs/APPLE_HEALTH.md`](docs/APPLE_HEALTH.md) · [`docs/GARMIN.md`](docs/GARMIN.md) | Bringing sleep, HRV and activities in; Garmin sync is two-way, with finished strength sessions going back out |
| [`docs/API.md`](docs/API.md) · [`docs/MCP-TOOLS.md`](docs/MCP-TOOLS.md) | Generated REST + MCP reference (`npm run docs:index`) |
| [`docs/VISION.md`](docs/VISION.md) · [`docs/WHY-CAIRN.md`](docs/WHY-CAIRN.md) | The product constitution; how Cairn compares, at length |
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) · [`CHANGELOG.md`](CHANGELOG.md) | Subsystem depth for contributors; release history |
Schema migrations run automatically on every boot, so an update is `docker pull` and re-run.
## Why I built this
I had the data — years of lifts, a watch on my wrist, bloodwork in a folder — and none of it talked
to anything else. Every app was excellent at its one column and blind to the other five. I didn't
want another dashboard to interpret; I wanted something that had already read all of it before I
opened it, and had one honest thing to say.
So the rules came first. No scores, because a number you must decode is the opposite of calm. No
notifications, because a tool that has to interrupt you hasn't earned being opened. Suggestions
rather than gates, because it's my body and my day. Self-hosted, because a system that knows this
much about you should live on hardware you own.
A cairn is a stack of stones on a trail. It doesn't shout, it doesn't follow you, and it doesn't
tell you where to go — it sits at the junction and marks the path, and you consult it when you get
there. That's the whole idea.
## Help it find the people it's for
Cairn is young, and it grows the way it behaves: quietly, by word of mouth, from people it actually
helps. If that's you, three things move it further than anything else:
- **Star the repo** if these are the rules you'd want for your own data. It's how the next person
finds it.
- **Show how you run it** in [Discussions](https://github.com/zilet/cairn/discussions): a Pi on a
shelf, a NAS, a VPS, a household of three.
- **Open an issue when a read gets you wrong.** A read that missed is the most useful bug report
Cairn can get.
## Contributing & license
Contributions welcome — see [`CONTRIBUTING.md`](CONTRIBUTING.md) (Node 26, the thin-adapter rule,
the migration + service-worker conventions) and [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md).
Licensed under [MIT](LICENSE). Built with Node 26 + TypeScript, Express, `node:sqlite`,
`@modelcontextprotocol/sdk`, a vanilla PWA, and Docker.