Repository navigation
v0.4.0
Added
- Watch app v2 (
docs/design/watch-v2.md):- The main screen shows the next workout: its day, name, sport, duration and distance; the sync status moves to a footer.
- START opens a menu that lists the workouts from today on; selecting one shows its steps, notes and the agent's rationale, and START there begins it.
- Agent messages: the new MCP tool
send_message(1 to 300 characters, write scope) stores a short display-only message. The watch shows the latest five from the last seven days under Messages, with an unread count on the main screen. Messages go nowhere else, not to ntfy. - Background checks: every 15 minutes while paired, the watch asks coachd for new messages and workouts and notifies you (a notification on fēnix 7 and later, an "open coachd?" prompt on older watches). It never downloads in the background: open the app to sync. A new Notifications setting in the Connect IQ app turns it off.
- Glance on fēnix 7 and later: the next workout and unread messages.
- The app needs two new permissions, Background and Notifications.
- Device API:
GET /device/v1/workouts/{id}(a workout's detail) andGET /device/v1/messages, both read-only. An older watch app ignores them, and the new app works against an older coachd without details or messages. - Planned vs actual (
docs/design/workout-outcome.md):- For an activity recorded from a structured workout on the watch,
get_activitywith detaillapsorseriesaddsworkout: the steps the watch followed, and each executed step with its time, distance, heart rate, pace, power and, for a heart-rate or pace range, the seconds inside, below and above it. Numbers only, computed from the activity's FIT file at ingest. workout.proposal_idlinks the activity to the delivered proposal it carried out, andlist_workoutsgainsactivity_idson each workout. The link needs the steps the watch recorded to equal the workout coachd delivered, and the dates to be within 3 days.- Garmin Coach and Garmin Connect workouts get the steps too, without a link. Zwift-recorded rides do not: their files carry no step index.
- Not yet checked on a workout delivered by the coachd watch app: if the watch rewrites the steps, the steps still show and only the link is missing.
- For an activity recorded from a structured workout on the watch,
- Approve the week (
docs/design/approve-week.md): when several workouts are waiting, Review all (/review) shows each one in full, then approves all of them with one passkey prompt.- The passkey ceremony binds every workout's ID and content hash; the approval is all or nothing in one transaction, and any change since the page was shown refuses it with the day of the workout that changed.
- Left out, with the reason: workouts whose date has passed, whose content does not match its hash, or that another waiting workout replaces; at most 20 at a time.
- The home page links it when two or more are waiting, and a waiting workout's page says how many more are waiting.
- Renew the Garmin key on the page (
docs/design/garmin-key-renewal.md): Garmin key (/garmin, linked from the home page, which warns 14 days before expiry) takes a new key and its validity (30, 90 or 180 days, or 1 year). coachd checks it with Garmin, stores it in the data directory (garmin-key.json, mode 0600) and switches at once, without a restart;get_statusand the key-expiry metric follow. If Garmin refuses the key, nothing changes. The key is never shown, logged or backed up, and the audit log recordsgarmin_key_renewedwith its outcome. - Notes that expire (
docs/design/expiring-notes.md):upsert_notetakes an optionalvalid_until(today up to 366 days ahead) for facts that stop being true. After that dayget_notesleaves the note out unlessinclude_expiredis true, and reportsexpired_count; each note showsvalid_untilandexpired. Expired notes are never deleted automatically and still count toward the 50-note cap; the "notes full" error says how many agent notes are expired. The notes page shows the date and fades expired notes. - Post-run webhook (
docs/design/post-run-webhook.md): withCOACHD_WEBHOOK_URLset, coachd POSTs{"event":"activity.stored","activity_id",...,"date","sport","proposal_id"}(IDs only) when an activity that started in the last 48 hours is stored, after its FIT file and per-step numbers are in, so your own automation can wake the agent for a post-run review. Optional bearer token fromCOACHD_WEBHOOK_TOKEN_FILE; retries like ntfy; new metriccoachd_webhook_sends_total{outcome}. - Your post-activity rating (
docs/design/perceived-effort.md):list_activitiesandget_activityshowperceived: {feel_pct, rpe}, the "How did you feel?" and RPE answers you give on the watch after a workout, read from the FIT file (the Personal API's JSON does not carry them). Null until the FIT file is read, or when you skipped the questions. Migration 0013 resets the detail cache once more so they fill in.
Changed
- The watch app's status no longer shows the workout count after a sync; the main screen shows "Last sync HH:MM" instead.
- Upgrade note: the activity detail cache (laps, series and now workouts) is reset by a migration and rebuilds from the stored FIT files: newest first, 20 activities per FIT drain, and on each
get_activitythat misses it.
Fixed
- Proposals that expired were not logged when the stale-delivery check after them failed, for example during shutdown. This also made a release test flaky.
Image
ghcr.io/stefiix92/coachd@sha256:84d978cab4df7170eb3692c47229c32fcf0f9f9af3f99c61d6de07656b8ed43c
Verify the signature before you run it:
cosign verify ghcr.io/stefiix92/coachd@sha256:84d978cab4df7170eb3692c47229c32fcf0f9f9af3f99c61d6de07656b8ed43c \
--certificate-identity-regexp "^https://github.com/stefiix92/coachd/.github/workflows/release.yml@refs/tags/v" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com