A daily log and training planner that runs on your own machine. Nutrition tracks what you eat and drink against your goals. Train follows a multi-week training plan set by set, and a coach you can talk to (a local model) answers questions and changes the plan when you ask. It is a web app you add to your phone's home screen, served from a desktop over your own network and backed by one SQLite file. No accounts, no cloud.
On a desktop: a food is searched for, picked and logged, the day takes it in, then the week and the month. Then Train: today's sessions, a set ticked off, and the coach.
The same flow on a phone.
The screenshots use demo data, a made-up plan included. To regenerate them:
npm run seed:demo && npm run dev # one terminal
npm run screenshots # another; needs Google ChromeThe app has two sections, along the bottom: Nutrition, which it opens on, and Train. They share one date, so switching keeps the same day in view.
- Meals log calories, protein, carbs, fat and fiber per entry, in a day, week and month view. Goals are a daily calorie range (or just a cap) and minimums for protein, fat and fiber; carbs are counted, with no goal.
- Food lookup works three ways. Search the USDA FoodData Central and Open Food Facts databases, scan a nutrition label from a photo for a local vision model to read, or ask for an online lookup, where a local model with a web-research tool reads the restaurant's or brand's own page.
- Label scanning turns a picture into a food. Upload a photo or screenshot of a nutrition label, paste one, or drop it anywhere on the page. A local vision model reads it and fills in the Manual tab: name, brand, serving and macros. The food is ticked to save to My foods, so it is there for next time once you have checked it.
- My foods keeps anything you have typed or confirmed, for one-tap logging, and saved meals log several at once.
- Water and tea count toward one daily water goal; alcohol is tracked against a weekly cap.
- Notes are two or three short paragraphs on a week or a month, written from your own numbers against your goals.
- A training plan you follow day by day. Each session is laid out set by set, with weights worked out from your PRs and rounded to each lift's plates. Tap a set to tick it off at the planned numbers, or note what you actually lifted. Deload and test weeks, a PR-attempt ladder, and "repeat this week" are built in. Add an optional yoga or pilates session to a day, or to that weekday every week, from the bottom of Today.
- PRs and progress. Set a new PR and every weight in the plan follows. Charts show your top sets, estimated maxes, and baselines like dead hang time.
- A WOD bank of about 100 workouts, offered three at a time to fit the day's slot and the equipment you have. A barbell becomes dumbbells and a jump rope becomes bike calories, and dumbbell weights match the pairs you own. If your gear is split between rooms, each WOD is kept to one room, and its card says which.
- A Library of every WOD, as you'd do it with your gear and filtered by room, and every movement they use, with how it's done. Tap a movement in a WOD to read it.
- A coach you talk to. Ask how your training is going or what it thinks of the plan, or ask for a change: move a day, skip one, go lighter for a sore joint, travel with less gear, make yoga optional, rename a session or an exercise, change how long a ride runs, reorder a session, change the deload or test week, add a lift to track, take back an earlier change, untick something logged by mistake, or put a class you did (or will do) that isn't on the plan onto that day, ticked off if it's done. You can send a screenshot to show what you mean. Every change comes back as a card spelling out exactly what it would do. Nothing changes until you apply it, and anything applied can be undone. It can also correct past days, or rewrite the rest of the plan.
- The next block, written by your local model from how this one went, set by set, and reviewed before it's followed.
- Workouts, meditation and dog walks each have their own tracker, with week summaries.
Plans are written as training schemas, a documented JSON shape you can import (npm run import:schema).
Every lookup, scan and coach change produces something for you to check. Nothing a model read, saw or suggested is logged or applied on its own.
your phone or desktop browser
│ http, over your own network
▼
Cairn ── one Node process on one computer ── .data/cairn.sqlite
│
├──▶ a model server on the same computer (label scanning, online lookups, notes, the coach, training plans)
└──▶ USDA and Open Food Facts (food search, over the internet)
Cairn is one Node process that does two jobs: it serves the app to any browser on your network, and it keeps your log in a single SQLite file on that computer. Your phone talks only to Cairn. For the model features, Cairn talks to a model server running beside it, so no model, token or data ever reaches the browser. Logging by hand needs nothing but Node.
| For | You need |
|---|---|
| Logging food, water, workouts and the rest by hand | Node 24 or newer, on the computer that will keep the data |
| Food search | Internet access. A free USDA API key is optional; the built-in DEMO_KEY is rate limited |
| Scanning a nutrition label from a photo | A model server with a model that can see images |
| Looking a restaurant or brand item up online | A model server with a chat model that calls tools well, plus the deep-research MCP server (below) |
| Week and month notes | A model server with any chat model; a small one is enough |
| A training plan | Nothing extra: write one as a training schema and import it |
| The coach | A model server with a mid-size chat model (a 27B answers in under a minute); one that can see images, for screenshots |
| Writing the next block, or rewriting a plan | A model server with a capable chat model; a large one plans better, in a few minutes |
| Opening it on your phone | The phone on the same Wi-Fi, or Tailscale on both devices (see Keeping it running) |
Node 24 is the floor because the log is stored with node:sqlite, which
arrived in that version. Check with node --version.
Cairn sends its requests to a model server's /api/v1/chat endpoint, the
API that LM Studio 0.4 introduced, because it
carries tool calls, images and streamed replies in one request. LM Studio
is the server these steps use. Any other server that implements that same
API works the same way; one that offers only the OpenAI chat API does not,
yet. The server runs on the same computer as Cairn, and nothing leaves your
network.
- Install the server and download the models you want. In LM Studio that
is the Discover tab. For label scanning pick a model that can see images
(LM Studio marks these with a vision badge). For online lookups pick one
that calls tools well;
qwen/qwen3.8-27bis what this is tested with. For notes, a small model such asqwen/qwen3.5-9bis enough. - Start the server. In LM Studio that is the Developer tab. Its
default address,
http://127.0.0.1:1234, is what Cairn's Settings start with; change it there if your server listens elsewhere. - If the server asks for a token (in LM Studio, Require Authentication under Manage Tokens), paste it into Cairn's Settings. It is kept on the server and never sent to the browser. LM Studio requires authentication for the online lookup, which uses an MCP tool.
- In Cairn's Settings, pick the model for each feature: Model for the online lookup, Label model for scanning, Notes model for the notes, Coach model for the coach, and Training model for writing training plans. The notes, coach and training models fall back to the lookup model when blank. A model that can't be told not to think needs more time per reply, so pick one that can for the coach.
The online lookup's tool. The lookup gives the model a web-research
tool so it can read a restaurant's or brand's own nutrition page. Install
minimal-mcp-deep-research
and register it with your model server under the name deep-research. In
LM Studio that means an entry in ~/.lmstudio/mcp.json and Allow calling
servers from mcp.json in the Developer settings.
npm install
npm run dev # http://localhost:8200, scratch database in .data-dev/
npm run check # typecheck + lint + tests
npm run build
npm run preview # the persistent instance on :8210, data in .data/
npm run seed:demo # fill the dev database with two weeks of demo data and a made-up plan
npm run screenshots # regenerate docs/screenshots/ from the dev server
npm run import:schema -- plan.json # load a training plan (add --dir .data-dev for the dev database)On your phone, open the Network address that Vite prints (for example
http://192.168.1.50:8200/) and add it to the home screen.
npm run deploy rebuilds and restarts a launchd job named
com.amie.cairn that runs vite preview. Create that job once with a plist
that runs npm run preview from this directory with KeepAlive on. To
reach it from anywhere, put :8210 behind Tailscale Serve and open the
.ts.net address:
/Applications/Tailscale.app/Contents/MacOS/Tailscale serve --bg --https=443 http://127.0.0.1:8210The app is served from the browser's cache on every launch, so it opens at once on the phone even before the tunnel is up, and says so if the server cannot be reached.
Vite + React 19 + TypeScript, with the API as a Vite plugin so one process
serves both. Data lives in .data/ as SQLite (node:sqlite, Node 24+).
Tests run under vitest; lint is oxlint.
server/ api.ts (routes), store.ts (SQLite), foods.ts (USDA + Open Food Facts),
label.ts (vision model), research.ts (online lookup), notes.ts,
store/training.ts (plans, sets, PRs, the coach's changes),
training-chat.ts (the coach), training-gen.ts (writing plans)
src/ App.tsx, components/ (trackers, sheets, the Add flow in add/, Train in train/),
lib/ (lib/training/: the schema, resolving weeks, the WOD bank)
public/ sw.js, the app shell cache
scripts/ deploy.sh, seed-demo.ts, screenshots.mjs, import-schema.ts
docs/ training-schema.md, screenshots/ for this README












