Skip to content

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 25 Oct 15:22
· 42 commits to main since this release
ad86f35

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) and GET /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_activity with detail laps or series adds workout: 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_id links the activity to the delivered proposal it carried out, and list_workouts gains activity_ids on 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.
  • 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_status and 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 records garmin_key_renewed with its outcome.
  • Notes that expire (docs/design/expiring-notes.md): upsert_note takes an optional valid_until (today up to 366 days ahead) for facts that stop being true. After that day get_notes leaves the note out unless include_expired is true, and reports expired_count; each note shows valid_until and expired. 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): with COACHD_WEBHOOK_URL set, 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 from COACHD_WEBHOOK_TOKEN_FILE; retries like ntfy; new metric coachd_webhook_sends_total{outcome}.
  • Your post-activity rating (docs/design/perceived-effort.md): list_activities and get_activity show perceived: {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_activity that 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