Runspector — AI running coach for Garmin

io.github.edxleonPublished by GitHub account edxleonv0.10.0Updated Nov 3, 2026

AI running coach for Garmin: HRV, readiness and VO2max explained, workouts on your watch

VerifiedSTDIODesktop onlyLocation & LifestyleData & Analytics

Overview

Runspector — AI running coach for Garmin's local MCP server; no sign-in required; STDIO.

No AI overview yet: these facts come from the registry entry and the server itself.

Installation

In SourceWeft

  1. Open Runspector — AI running coach for Garmin in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

Runspector — AI running coach for Garmin

[ci] [PyPI] [evals 36/38] [license MIT] [website]

A free, open-source AI running coach for Garmin watches. Runspector syncs your Garmin Connect data - HRV, sleep, Body Battery, resting heart rate, training load and every run - into a file on your own computer, decides each day whether to go hard, keep it easy or rest, and lets Claude explain why and plan your week: in a small local web app, or as a Garmin MCP server in Claude Code and Claude Desktop. No cloud, no API key needed - your own Claude Code sign-in is enough.

https://github.com/user-attachments/assets/0b4d7d86-5ab7-439b-82cb-89a004b71405

The 90-second demo, with voice-over: the synthetic demo athlete, rebuilt from this repo by scripts/video.py (file). No player above? The GitHub mobile app shows no videos: ▶ play the demo in the browser.

[Runspector Today tab: training readiness verdict from Garmin data, the decision for today, the week plan, HRV, sleep and Body Battery] [Runspector Coach tab: a coaching card Claude wrote from the Garmin data] [Runspector Runs tab: each run's intensity bands and its detected interval structure] [Runspector Trend tab, light theme: VO2max as steps, lactate threshold, 80/20 intensity share]

The built-in synthetic athlete (runspector serve --demo). Every image is regenerated by python scripts/screenshots.py, and the coach card is an unedited agent run against that same demo database, kept in demo-card.json so the picture cannot drift from the app — the decision it explains (EASY, a day after a hard session) is the one on the Today tab beside it. docs/screenshots/ has each of the five tabs full-length, in both themes — e.g. today.png and today-light.png.

I came to running late, and for a long time my VO2max would not move. I had always trained a lot, and it had never really shown up in that number — so I assumed more of the same would eventually work. It did not. I was training wrong, in ways the watch on my wrist had the data to show me and never did: Garmin collects everything and explains nothing.

That was the idea. The data is already there; let an AI read it and coach — with the training aimed squarely at raising VO2max rather than at collecting kilometres, and with the decision rules written down so the coach explains them instead of improvising. This repository started as that tool for one athlete. It is now installable for anyone with a Garmin watch and Claude Code (a Claude plan is enough).

No cloud backend, no API key needed, no Docker. One Python package that syncs your Garmin data into a SQLite file on your machine, shows it in a small web app, and lets Claude act as your coach — with a deterministic readiness engine underneath, so the AI explains decisions instead of inventing them.

If you came for the engineering rather than the running, the five decisions this repository is actually about — each with the measurement behind it — are in Design decisions. The short version: the verdict is pure code and only the explanation is AI · the agent runs on an allowlist, not a denylist (--tools "", one MCP server, empty temp cwd) · prompts are regression-tested by an LLM-as-judge suite whose last run is in the repo · quantities that exist in two languages are pinned by executing the shipped JavaScript against the Python · and the release gate scans binaries too, with a false-positive counter-case. CI runs lint, 1600 tests, 151 frontend tests and that gate on three operating systems and two Python versions, with every action pinned to a commit SHA — and a second job that builds the wheel, installs it as a user would and drives the installed executable through this quick start on all three systems, because the suite proves the code and only an install proves the package.

Formerly runcoach-ai: same project, renamed in v0.5.0. Old links redirect here. Coming from it: three steps to switch over, and your data moves by itself.

Not affiliated with Garmin. Uses the unofficial python-garminconnect library against your own account. This is a training tool, not medical advice.

Quick start

bash
# 1. install (needs uv: https://docs.astral.sh/uv/)uv tool install runspector#    without uv: pipx install runspector
# 2. look around without any accountrunspector serve --demo
# 3. the real thingrunspector serve        # first start: checks Garmin and Claude, opens http://127.0.0.1:8765 -                        # sign in to Garmin right there (Setup → Sign in to Garmin; only a                        # session token is stored) - and syncs the last 30 days in the background

Garmin is signed into on the page itself (Setup → Sign in to Garmin, or Sign in again when the session has expired): e-mail, password and Garmin's code if your account uses one, and the sync starts right after. runspector login does the same from any terminal, and a running app picks it up on ↻ - no restart. Claude is only needed for the coach cards: see Connecting Claude.

runspector doctor tells you what is missing — it checks the Garmin session by actually using it, and reports the data age, because a token that quietly expired is how this kind of app dies. Use runspector doctor --offline to skip the Garmin call. Exit 1 means something needs fixing, so runspector doctor || notify-me works in a cron; a missing Claude CLI is reported but does not set it, since everything except the coach cards works without it.

Garmin MCP server for Claude Code and Claude Desktop

The same data, and the same rules, as an MCP server with 18 tools - among them today's readiness verdict, recovery, training load, recent runs, intensity distribution, the VO2max history, what the coach has learned about you, a Garmin sync, a check-in when you say you are ill or hurt - and propose, change, apply and undo for the workouts on your watch.

bash
claude mcp add runspector -- runspector mcp# then just ask: "should I train today?"# or: "plan me an easy 40 minutes for tomorrow" - you see the session, and it goes# on your watch only after you say yes ("take it back off" undoes it)

In Claude Desktop, or any MCP client with a JSON configuration (sign in once first: runspector login):

json
{ "mcpServers": { "runspector": { "command": "uvx", "args": ["runspector", "mcp"] } } }

Keep it fresh without the app (cron / Task Scheduler): runspector sync. For the morning: runspector today --sync prints today's call in a few lines - GO / EASY / REST with its sentence, the signals behind it, today's planned session with its easier option - and exits 3 when there is nothing from today yet, so a cron and any notifier can tell "not synced" from "rest day".

Later on:

bash
uv tool upgrade runspector     # the newest release; your data and Garmin session stay where they areuv tool uninstall runspector   # removes the program only - delete ~/.runspector yourself if you want the data gone

uv tool install runspector takes the latest release from PyPI (runspector==0.10.0 pins one; pipx install runspector and pipx upgrade runspector do the same without uv); uv tool install git+https://github.com/edxleon/runspector takes whatever is on main.

Connecting Claude

runspector does not connect to Claude itself and never asks for a Claude password or key. The coach runs on Claude software you sign into with your own account, through Anthropic's own sign-in.

What you wantWhat you need
Today's verdict and its reasons, your runs, the trends, the syncnothing from Claude
The coach cards in the web app (Ask the coach, Should I train today?, Analyze this run, Review my week) and the sessions they put on your watchClaude Code on this computer, signed in
Coaching in a chat instead, sessions on your watch includedClaude Desktop or Claude Code, with runspector added as an MCP server

What "a Claude plan" means. Claude Code needs a paid Claude subscription from claude.com/pricing - Pro, Max, Team or Enterprise - or an Anthropic Console (API) account, which is billed per use. The free Claude plan does not include Claude Code. On a plan, the coach cards count against that plan's usage like any other Claude Code use: there is no key to create and no bill per card. runspector itself is free.

With Claude Code (needed for the coach cards)

Claude Code is Anthropic's command-line tool. You only install it and sign in once; runspector starts it in the background for each card.

bash
# 1. install - macOS / Linux (Windows PowerShell: irm https://claude.ai/install.ps1 | iex)curl -fsSL https://claude.ai/install.sh | bash        # or: brew install --cask claude-code
# 2. sign in - opens your browser, sign in with your Claude accountclaude auth login
# 3. checkrunspector doctor        # "Claude: signed in with your Claude subscription"

More install options (WinGet, npm, Linux packages): Claude Code setup. If Claude Code was missing when you started runspector serve, reload the page afterwards - no restart.

If your Claude Code is signed in with a Console API key or a cloud provider (Bedrock, Vertex) instead of a plan, the cards run on that and are billed there; the setup check says so. An ANTHROPIC_API_KEY that is only exported in your shell, for other work, is removed from the card runs unless you set RUNSPECTOR_ALLOW_API_KEY=1.

With Claude Desktop only (no command-line tool for Claude)

If you use the Claude desktop app (claude.ai/download), you can coach in its chat without Claude Code:

  1. Install runspector and sign in to Garmin once (runspector login, or the page of runspector serve).
  2. In Claude Desktop: the Claude menu → Settings… → Developer → Edit Config, and add:
    json
    { "mcpServers": { "runspector": { "command": "runspector", "args": ["mcp"] } } }
    (the file is ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows; keep any servers already in it).
  3. Quit and restart Claude Desktop, then ask: "should I train today?"

If the server does not show up, Claude Desktop may not find the command: put the full path from which runspector (Windows: where runspector) into "command". The web app's coach cards still need Claude Code; in Desktop the same coaching happens in the chat.

Without Claude

The readiness verdict and its reasons, the runs, the trends and the sync all work. What needs Claude is everything the coach writes: the cards, and the sessions it proposes for your watch.

Questions people ask

Does Garmin have an AI coach?

Garmin offers three kinds of coaching of its own. Garmin Coach is a set of free adaptive training plans in Garmin Connect for a 5K, 10K or half marathon, which adapt to the runs you log. Many watches show daily suggested workouts, built from your activities once the watch has a training status and a VO2max estimate. Garmin Connect+, a paid subscription since March 2025, adds Active Intelligence: AI-powered insights and suggestions through the day, launched in beta; everything that was in Garmin Connect before stays free. Runspector reads the same Garmin data and is a different kind of tool: it decides today with every reason written out by rules you can read, lets you ask a coach in your own words, and plans sessions that go on your watch only after your yes - on your own computer.

Is there a free AI running coach for Garmin?

Runspector is free and open source (MIT). It installs from PyPI and keeps your data on your own computer. Today's verdict, your runs, the trends and the sync need nothing but your Garmin account. The coach itself runs on Claude, through Claude Code or Claude Desktop with your own Claude account; Connecting Claude says which plan that needs. Garmin's own Garmin Coach plans are free as well.

Which Garmin watches work?

Any Garmin watch that syncs to Garmin Connect gives runspector your runs. Today's verdict reads four recovery signals - HRV status, sleep score, Body Battery and your resting heart rate against your own baseline - plus your training load. A watch that records no HRV status overnight still gets a verdict from the others, but a GO that rests on fewer than two signals becomes EASY: thin data is no green light. To put sessions on the watch, it has to run structured workouts from Garmin Connect, with heart-rate or pace targets. Current Forerunner, fenix, epix, Venu and Instinct models are examples; whether a model records HRV status or Body Battery, its specifications on Garmin's site say.

How do I connect my Garmin data to Claude?

Install runspector, sign in to Garmin once (runspector login, or the button on the page), and either open the app (runspector serve) or add it to Claude Code as an MCP server: claude mcp add runspector -- runspector mcp. Then ask "should I train today?". See the MCP server for Claude Desktop.

Do I need a Garmin developer account or an API key?

No. runspector signs in with your own Garmin account, through a community library - see Can Garmin block my account? below. On the Claude side no API key is needed either: a Claude plan and Claude Code are enough.

Does it work with Apple Watch, Coros, Polar or Strava?

No - Garmin only. Every number comes from Garmin Connect, including Garmin's own readings (HRV status, Body Battery, training load, VO2max), and the workouts it plans go back onto a Garmin watch.

Is my Garmin password stored?

No. It goes to Garmin once, to sign in. Garmin answers with a session token, and only that is kept, on your computer, in a folder only your user account can read. The page's sign-in takes the password only on the computer runspector runs on: opened from a phone, the page is plain HTTP across your network, so there it points to runspector login instead.

Does it cost anything extra?

runspector is free (MIT). Coach cards use your Claude plan the way any Claude Code use does, with no API key and no bill per card. Without Claude, everything except the cards still works.

What does Claude see?

The numbers this app shows (readiness, recent runs, zones, weekly load), and only when you start a card or ask in Claude Code. It never sees your Garmin password, the session token or any raw file. It reaches Claude through your own account, under your plan's terms. And you can see it too: under every card, How this card was made lists everything the coach looked at and checks each number on the card against it; the You tab lists what chats in Claude read. What you mention about yourself while asking on a card - your time on weekdays, your goal, an old injury - the coach notes for later, each fact checked against what you wrote before it is kept, and never without saying so; the You tab lists each with the question it came from and lets it go.

Can it mess up my watch or my calendar?

Reading is all it does on its own. Something goes to Garmin only when you press "Put on watch" or say yes in Claude Code, and then only the session shown to you. "Take off watch" removes it again. The coach's own card runs cannot write at all.

Can Garmin block my account?

runspector signs in through python-garminconnect, a community library that Garmin does not officially support. A daily sync makes about 25-40 requests and the first one (a month of data) a few hundred. It pauses between workout downloads, never fetches more than 90 days, and stops at the first rate-limit answer instead of retrying into it. That is no guarantee, but it is not scraping either.

Is this medical advice?

No. It is a training tool. Pain, dizziness or anything that feels wrong goes to a doctor, and the coach says so instead of planning around it.

How do I remove everything?

Delete ~/.runspector (the app's footer shows the folder) and run uv tool uninstall runspector. To cut the token off at Garmin as well, change your Garmin password.

What you get

TabWhat it answers
TodayGO / EASY / REST with the signals behind it, one decision sentence for today and, on a hard day, which hard session (VO2max intervals, threshold reps, or the steady block Garmin measures VO2max from), Garmin's own Training Readiness beside it on watches that have one (its opinion - the call is the app's), four buttons for what the watch cannot see - ill, pain, sore legs, stressed - that change the call (ill or pain rest the day, and as many easy days follow as it lasted), your VO2max with the days since it last really changed, this week planned vs. done - tap a day to see what a session is (length, distance, pace, its steps) and change it: move it, make it shorter, longer or another kind, take it out, add a run, or tell the coach in your own words; the coach says what it thinks of the change, you decide, and it reaches the watch with "Put on watch" and comes off again with an undo -, last night's sleep, your zone bounds with their origin. Coming back to the page after a while syncs first
RunsEvery run with its intensity band, real interval structure (5×4′, not "5 minutes"), zones, weather, performance condition — and an Analyze this run button
TrendVO2max as honest steps (and whether a measurement is due) with your body weight beside it - VO2max is per kilogram, so the page says how much of a change the weight alone explains -, lactate threshold history, weekly hard-minute share vs. an 80/20 target, aerobic efficiency (pace at a fixed heart rate) with spread and trend, volume, polarisation, ACWR and monotony (how alike the last seven days were - an even, heavy week holds the next one), and Garmin's own load focus (its 4-week load per band against its target range) as a second opinion
CoachAsk the coach anything in your own words (what am I doing wrong?) - a short card, the biggest lever first. One-tap analyses by Claude as short cards that remember the previous card of their kind, accept your 👍/👎 and can be deleted. A card is a conversation: ask on under it, and every answer is
written with the whole thread; it may offer a next step (Plan my week) as a button that starts with
that conversation; and what you say about yourself there is noted for the coach, each fact checked against your own words. Two start here (Why is VO2max moving? · Review my week); the other two start where their subject is — Should I train today? on the Today tab, Analyze this run on a run
YouYour week, the foundation the planner goes by: runs per week, your days, long run, days you can't run, another sport - read from your history and kept up to date, and yours to change: what you set wins until you hand it back. For your coach: a note in your own words ("gym Mon, Wed, Fri", "less time until December") the coach reads before every answer. And what the coach has learned about you, each with its evidence: how consistent you are, your intensity pattern, the VO2max trend, Garmin's load focus, resting-HR baseline and zones - and since when, where something changed. Every card and the MCP tool get_athlete_profile read all three

How it works

mermaid
flowchart TD    G[Garmin Connect] -- "token resume, read" --> S[sync.py]    S --> DB[(SQLite<br/>~/.runspector)]    DB --> L[logic.py<br/>readiness · decision<br/>intervals · bands]    L --> SNAP[snapshot.py]    SNAP --> WEB[web app<br/>127.0.0.1:8765]    L --> MCP[MCP server<br/>18 tools<br/>14 read-only · sync · check-in · apply · undo]    WEB -- "spawn job" --> A["claude --print<br/>(your Claude sign-in)"]    A -- "only mcp__runspector__*" --> MCP    A -- "JSON card" --> WEB    MCP --- CC[Claude Code / Desktop]

Design decisions

The verdict is code, the explanation is AI. GO/EASY/REST, today's decision, interval detection and intensity bands are pure functions in logic.py — unit-tested, no network, no LLM. The facts and the decision are computed once there, and the agent is handed the finished decision through get_training_readiness rather than left to derive its own: a coach card is a reading of that decision, not a second, independently produced verdict. Claude may explain it, add the context a rule cannot — or disagree, in which case it has to say which number it disagrees with. Claude's job is the part rules are bad at: weighing a conflict, phrasing a plan, answering a follow-up.

Your own Claude Code sign-in, not a key in the app. Coach jobs run claude --print as a subprocess, signed in the way you signed Claude Code in. On a Claude plan there is no key to leak, no per-token bill, nothing to configure. API-key variables exported for other work are stripped from the card runs by default, so they never bill a card unasked; RUNSPECTOR_ALLOW_API_KEY=1 keeps them. A key or a cloud provider configured in Claude Code itself is beyond any variable: the cards use it, and the setup check says so - it asks Claude Code in the same environment and the same kind of empty directory the cards run in. If your quota is exhausted the job waits for the next window instead of failing.

The agent gets an allowlist, not a denylist. Workout titles are attacker-controlled text that ends up in a prompt. So the coach agent runs with --tools "" (no Bash, no file access, no web), --strict-mcp-config with exactly one server — this one — and only mcp__runspector__* pre-approved, in an empty temp directory. The card comes back as JSON and is validated and written by the app, not by the agent — and an answer produced without a single tool round trip is discarded: a model that cannot reach the data will otherwise invent it (this was measured, not assumed). See web/agent.py.

What that does and does not buy, stated plainly: an injected workout title cannot reach anything but this app's own tools — no shell, no filesystem, no network, no other MCP server, and no write path from a card run: every tool is read-only except sync_garmin, which triggers authenticated requests to your own Garmin account and so is worth a rate limit rather than nothing, and apply_workout, the one tool that writes to Garmin — which the app's card runs cannot call at all (--disallowedTools, pinned by a test), because applying a proposal is a human's click on the card or their word in a Claude Code session, never a job's decision. What it can still do is influence what a card says — and the card is the product. And cards are remembered: the previous card of the same kind is fed back as context for the next one, so a bad card echoes forward until you delete it (the UI has a delete button per card).

Aggregates for the model, series for the UI. MCP tools never return day-by-day rows for a period — daily series bloat a context window and get misread. The agent sees summaries and weekly buckets; only the frontend, which draws curves, gets points.

Honest numbers. A few examples of what that means in practice:

  • Garmin carries VO2max forward on days without a measurement, so a change is only reported when the value actually varied — never as a difference of window endpoints.
  • A self-computed ACWR needs ≥ 21 days and ≥ 8 workouts, is labelled computed, and may dampen a verdict to EASY but never drive REST.
  • A GO from fewer than two recovery signals is downgraded: thin data is not green.
  • If the sync is stale, today's decision is unknown rather than yesterday's verdict in today's clothes.
  • An incompletely fetched calendar month does not replace the local mirror ("nothing planned" would be a lie from a degraded source).
  • Garmin's lactate-threshold pace field is off by a factor of ten; it is normalised, range-checked, and loudly dropped if the unit ever changes.
  • A planned week lands on the athlete's own days: how many days, which weekdays and the long-run day are read from the last eight weeks with running (looking back half a year, and before a pause while coming back from one), and the quality sessions keep clear of a weekly other sport and the day after it - a gym Tuesday leaves room for one quality session, not two. A rotating every-other-day rhythm has no weekdays of its own and is planned in the default rhythm. Each value says where it came from: "long run on sat - your longest run fell on sat in 8 of 8 weeks".
  • A planned week is sized from the athlete's own running weeks - rolling weeks ending on the day of planning, never more than 10 % over the bigger of the last two - and then read against their history, in code: the load ratio can hold or unload it, every fourth week is a down week, ten days without a run make a return week of easy running only (half the weeks before after more than three weeks off), and twenty-eight days without a new VO2max value put a steady block in the threshold slot. A budget too small for the planned days plans a shorter week - one quality session at three days, never a lone hard one - instead of inflating the sessions. A week with one quality session takes the one that is due (VO2max until the zone-5 stimulus is set, then threshold, or the measurement). Back from a pause the athlete rebuilds: the step grows from the last seven days, not from a median that lags the climb, and there is no down week until the weeks are back to 80 % of the level before the pause. A step is never smaller than the week planned before it, and a week held or unloaded on the load ratio is never bigger: the median it holds rises with every week run above plan, which is what raised the ratio. Each rule is a named assumption on the proposal, none of them a prompt's discretion. On a hard day the decision also says which hard session, from what the last seven days did and did not contain; after a pause the day is easy instead.
  • Without a measured threshold pace, sessions are sized from Garmin's own 5 km and 10 km predictions rather than from a 5:00/km default that fitted one athlete - and once the pace has such a source, the reps go to the watch as a pace band: heart rate lags a minute behind a four-minute rep.
  • Every hard session and long run says in advance what to do on a day it is too much ("if tired: 48 min easy"), in the preview and as the workout's description on the watch - one rule, the same one the calendar fit and the red-day swap use.
  • A weekday the athlete always keeps short (from the history) keeps every session there to that length and no quality session; a week of heavy, alike days (Foster's monotony) is held, not stepped up.
  • Those rules are run, not only unit-tested: evals/sim.py puts sixteen synthetic athletes - one who follows the plan, one who skips 40 %, one who takes three weeks off, one who falls ill, one who always runs harder, one who runs the same every day, one with a short Tuesday, one whose profile contradicts itself, one who says yes to the week three days late, one whose calf hurts now and then and who says so - through sixteen weeks of the app, day by day against the real tools, and checks what a runner would expect of a coach over the whole course. It found the two planner bugs above, and a held week that jumped past the last regular one after a down week. It cannot tell whether the training works - its body is a toy - and it finds only what its data model knows, so the live test on a real account stays.

Prompts are tested like code. The coach's behaviour lives in two Markdown files (skills/) with an LLM-as-judge regression suite (evals/): 38 cases such as "a run already happened today → no second hard session", "warning signs of low energy availability are not explained away", "an instruction inside a workout name is not followed". They run without Garmin data against the real CLI — though only over those two files, not over the other two pieces of the shipped prompt (templates.json and the JSON-card frame in web/agent.py, which tests/test_web.py covers deterministically instead), and under --safe-mode on whichever model the suite is pointed at, neither of which production can use. The framing itself is imported from the app rather than copied, so the suite cannot drift from what ships. --no-skills re-runs a case with the skill files removed: a case that still passes is testing the model, not the prompt. Two more suites run the parts the coach's memory rests on with the real model: the check before a fact is kept (memory_check.yaml - a "not" that stays, a pain of now, a change that must change that fact, each case three times, every run of every model logged), and conversations end to end against the demo athlete - what is kept and what must not be, the check-in a card offers, the first question still in the coach's task at the sixth. The card frame is measured there, by a model.

Boring technology. Python stdlib HTTP server, sqlite3, vanilla ES modules, inline SVG charts. No framework, no build step, no ORM. ~40 SQL queries, all in store.py. Runs on Windows, macOS and Linux (CI matrix).

More detail: docs/architecture.md.

Privacy

Everything stays in ~/.runspector/ (override with RUNSPECTOR_HOME): the SQLite database, Garmin session tokens, coach cards, and the record of what the AI read (below). Your password is never stored. On Linux and macOS the directory is created 0700, so on a shared machine other local accounts cannot read those files; Windows inherits the user profile's ACL instead. While runspector serve runs, that no longer covers them: without a token any local account or process can ask 127.0.0.1:8765 for your data, and since v0.2 apply or undo a proposal with your Garmin session. On a machine you share, start it with RUNSPECTOR_TOKEN set - the server then wants the token on loopback too, and prints the link to open once. The only outbound connections are to Garmin (sync) and — when you trigger a coach card — to Claude through your own CLI session, carrying aggregated training data. The server binds to 127.0.0.1; listening on the network requires RUNSPECTOR_TOKEN and the server refuses to start without it. Note that --host 0.0.0.0 serves plain HTTP with no TLS, so on that network the token and everything the app returns travel unencrypted and readable by anyone who can see the traffic — it is meant for your own phone on your own LAN, not for a network you do not control.

What the AI read is kept, so you can check what it says. The coach reaches your data only through the app's own tools. For every coach card the app keeps everything the coach looked at - each tool, its arguments, what came back - and what it was given (what the app knows about you, your note, the task); How this card was made under the card shows it, and checks every number on the card against it. The check is literal: a number found was in front of the coach, which is not proof it was read rightly; a number not found was worked out by the coach or is not backed by your data, and the card says which numbers those are. Each point also says where it comes from - your data, you said or coach's reading - and a point marked as your data that the record does not back says so under it. Chats in Claude Code or Claude Desktop are recorded too: one line per tool call, with what it returned. The You tab lists the latest. Where it lives: ~/.runspector/evidence/ - a card's record as long as the card (the newest 60, and the newest of each kind), the chat record for 14 days. None of it leaves the machine; delete the folder to clear it, nothing else depends on it. Card runs are started so that Claude Code keeps no session of its own for them; a chat you have with Claude yourself stays in Claude's own history, as any chat does, and what Claude Code does there with its other tools (reading a file, say) is not in this record.

Configuration

VariableDefaultPurpose
RUNSPECTOR_HOME~/.runspectordata directory
RUNSPECTOR_TZsystem zoneIANA zone for "which day was this run"
RUNSPECTOR_TOKEN–required for --host 0.0.0.0 (phone in your home network)
RUNSPECTOR_MODELsonnetmodel for coach cards
RUNSPECTOR_GARMIN_TOKENS~/.runspector/garminreuse an existing python-garminconnect token dir
RUNSPECTOR_AUTOSYNC_MIN30in a Claude Code / Desktop chat, get_training_readiness syncs first when the last sync is older than this; 0 turns it off
RUNSPECTOR_QUOTA_WAIT_S3600how long a job waits for subscription quota
RUNSPECTOR_JOB_TIMEOUT_S600hard timeout for a single coach job
RUNSPECTOR_ALLOW_API_KEYunset1 keeps exported ANTHROPIC_* (and Bedrock/Vertex) variables for coach cards, billed per token; unset, the cards strip them. A sign-in configured in Claude Code itself is used either way
RUNSPECTOR_ACTIVITY_BACKFILL_DAYS182minimum window of the workout list a sync fetches (your habits look back 26 weeks; zone detail is fetched for the last 35 days)
RUNSPECTOR_LT_HISTORY_DAYS180how far back the lactate-threshold history is fetched
RUNSPECTOR_LOGWARNINGlog level — set INFO or DEBUG to watch a sync. INFO and DEBUG also log every HTTP request of the web app

Four more (RUNSPECTOR_DB, RUNSPECTOR_DEMO, RUNSPECTOR_CLAUDE_CMD, RUNSPECTOR_PII_EXTRA) exist for internal plumbing and tests; they are not part of the supported surface.

Optional athlete profile: runspector profile --max-hr 182 --goal "sub-50 10k". Your week - days per week, your days, long run, days off, another sport - is set on the You tab (or with --days-per-week and --long-run-day); whatever you leave open the planner reads from your history: the days you usually run, and the weekday your longest run falls on. A day off is never planned on. The note to your coach is context for the coach, not a rule: the days are what the planner itself keeps to.

Development

bash
git clone https://github.com/edxleon/runspector && cd runspectoruv run pytest -q                   # no database server, no network, no Garmin account needednode --test "web-tests/*.test.mjs" # frontend logicuv run ruff check                  # lint — config is real and the backlog is zeronpx --yes @biomejs/[email protected] lint --error-on-warnings   # frontend lint (biome.jsonc)uv run python evals/run_evals.py   # prompt regression (uses your Claude subscription)uv run python scripts/pii_gate.py  # release gate

CONTRIBUTING.md says what a change pulls along, and which changes get a deep review before the commit - with Claude Code that is the /deep-review skill in .claude/skills/.

Status & roadmap

v0.1 was read-only towards Garmin. v0.2 adds the write path: propose_workout builds a session for your route from your own zones and files it, propose_week files a polarised week as one package, a proposal can replace the hard session the calendar had on a red day, and apply_workout uploads, schedules, pushes to the watch and reads back to verify. The app's card runs cannot apply anything; in the app a proposal is applied by a click on the card ("Put on watch", two taps), in Claude Code by your answer — and taken back off the same way, because a write you cannot undo is a write nobody makes the first time.

What has been run against a real account, not only the fake Garmin client the suite uses: on 2026-10-28, one easy session through runspector mcp — proposed from the athlete's own zones, uploaded, scheduled, pushed, seen on the watch, read back and verified, then undone — with every step cross-checked by a second, independent Garmin client. The app's button calls the same plan.apply and plan.undo. Not yet run for real: propose_week as a package, and the red-day swap that unschedules a calendar entry. One athlete, one account, one watch.

Roadmap

docs/roadmap.md is the backlog, ordered by what it would do for the runner. First on it: an app that is set up and started without a terminal, a readiness light that does not rest a day on one weak signal, what each run did against what it was meant to be (easy kept easy, hard made hard) over the months, the proposed session matched against the run that followed - with the same reps compared across weeks -, a week on the watch without Claude, the page and the coach in your language, and sessions with your own structure. After them: session sizes from your own time per weekday, how a run felt, the week's load before you say yes, the kind of a strength day, moving a session, a fitness trend beside VO2max, deeper history, a one-file install for Claude Desktop, and race preparation towards a goal race. v0.3 made the planner read the history for what comes next, v0.6 brought every small item, v0.7 the athlete's own check-in, and v0.8 where each point on a card comes from, with a record of what the AI read, and how each run was run - easy, moderate or hard - by its own zones; the reasoning and the order are there.

License

MIT

Source: README.md at commit 07a10cd

Tools

0
SourceWeft has not started this package in its sandbox yet, so its tools are not known.

Version history

5
  1. v0.10.0LatestNov 3, 2026
  2. v0.7.0Nov 1, 2026
  3. v0.6.0Oct 31, 2026
  4. v0.5.1Oct 30, 2026
  5. v0.5.0Oct 30, 2026

Score

50% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts

    Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.

  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listed15 pts

    Answer tools/list without a credential, or ship a package that starts in a sandbox without one.

  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts