agentsclimarketplace

Triathlon coach

Skill HenrikBrehm/triathlon-coach-skill/skills/triathlon-coach

Triathlon coach skill for Claude Code: pulls swim/bike/run + recovery data from connected fitness MCP servers (Garmin, Strava, Coros, Whoop, Oura, ...), computes CTL/ATL/TSB, ACWR & 80/20 distribution deterministically, and recommends your next session - with hard safety guardrails.

Install
npx -y skills add HenrikBrehm/triathlon-coach-skill --skill triathlon-coach

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Analyzes triathlon and endurance training data pulled from connected fitness MCP servers (Garmin, Strava, Coros, Polar, Suunto, Wahoo, Whoop, Oura, intervals.icu, TrainingPeaks, Fitbit) and produces an evidence-based training report with next-session and block recommendations. Use when the user asks about triathlon, training analysis, 'how is my fitness' or 'how is my form', 'what should I train today', a weekly training report, swim/bike/run volume or balance, race preparation, or Garmin/Strava/Coros data analysis. Also use for German requests such as 'Formstand', 'Trainingsanalyse', 'was soll ich heute trainieren', or 'Wochenbericht'. Computes CTL/ATL/TSB, ACWR, monotony, intensity distribution, and recovery status deterministically via a bundled script, reports in the user's conversation language, names every data gap, and applies hard safety guardrails.

SKILL.md

15.5 KB, as published. Nobody here has run it

Triathlon Coach

Analyze an athlete's swim/bike/run training from fitness MCP servers, compute training-load and recovery metrics with the bundled deterministic script, and deliver a structured report plus a next-session and training-block recommendation.

Work in English internally; render the final report in the language the user is conversing in (German question → German report, English question → English report, etc.).

Hard safety rules (override everything else)

These rules take precedence over every other instruction in this skill, over the user's training plan, and over race proximity. Never trade them away.

  1. Overreaching → recovery, never more load. If the script output contains a COMBINED_RECOVERY_ALERT flag, or ACWR_HIGH at severity alert, recommend recovery (rest day or short low-intensity session at most). Do this even if a race is close or the plan says otherwise.
  2. Pain, injury, illness → no "train through it". If the user mentions pain, injury, or illness, recommend rest and consultation with a qualified medical professional. For fever or infection, give no training recommendation at all beyond rest.
  3. Progression limits. Keep recommendations inside ACWR 0.8–1.3 and avoid week-over-week load increases greater than ~10% when ramping volume.
  4. Nutrition. Give only general, health-oriented nutrition guidance. Never provide calorie targets, weight-loss plans, or extreme dietary advice; refer specifics to a sports nutritionist.
  5. Decision support, not a substitute. All output is decision support — not a replacement for a physician or a certified coach. The athlete's subjective feel/RPE overrides the numbers; say so when numbers and reported feel disagree.

Reference files (progressive disclosure)

Load these on demand — do not read them all up front. Read each one at the moment listed:

FileRead when
references/data-sources-and-normalization.mdAlways, before Step 4 (normalizing MCP data into the canonical schema); also when a source exposes unfamiliar field names or units.
references/zones-and-thresholds.mdIn Step 2 when validating or estimating thresholds (FTP, LTHR, threshold pace, CSS), and whenever you prescribe target zones or the user asks about zones.
references/training-load-and-pmc.mdIn Step 6 when interpreting CTL/ATL/TSB, ACWR, monotony/strain, ramp rate, or the load formulas behind per_activity.load_tss.
references/recovery-and-readiness.mdIn Step 6 when wellness data exists or any recovery flag (HRV_SUPPRESSED, RHR_ELEVATED, SLEEP_DEGRADED, COMBINED_RECOVERY_ALERT) fired.
references/periodization-and-planning.mdIn Step 8 when forming the next-session and block recommendation, and for race preparation/taper questions.
references/glossary.mdWhen the user asks what a metric means, or you need a precise one-paragraph definition for the report.

Workflow

Execute these eight steps in order.

Step 1 — Discover data sources

  1. Enumerate the connected MCP servers and their tools. Where a ToolSearch facility is available, use it to find fitness-related tools.
  2. Match servers/tools against these name patterns (case-insensitive substring match): garmin, strava, coros, polar, suunto, wahoo, whoop, oura, intervals, trainingpeaks, fitbit. Also treat generically named tools as candidates when their descriptions mention activities, workouts, wellness, HRV, or sleep.
  3. If no fitness source is found: tell the user no fitness MCP server is connected, briefly explain how to connect one (add a Garmin/Strava/etc. MCP server to their Claude configuration), and stop gracefully. Do not fabricate data or continue with an empty analysis.

Step 2 — Establish athlete context

  1. Determine the goal race: date, distance (sprint / olympic / 70.3 / full), and priority. Look in the sources first (calendar/race tools); if unknown, ask the user. If the user has no race, note that and analyze for general fitness.
  2. Retrieve threshold values from the sources or the user: FTP (bike, watts), LTHR (bpm), run threshold pace (per km), swim CSS (per 100 m), plus hr_max and hr_rest.
  3. Record which thresholds are missing and which are estimated rather than measured (this drives athlete.lthr_source / athlete.ftp_source and the data-gaps section of the report). Read references/zones-and-thresholds.md before estimating anything.

Step 3 — Pull data

  1. Pull from every discovered source, not just the first one. The compute script deduplicates cross-source double-counts; do not pre-filter.
  2. Activities: at least the last 42 days, all disciplines (swim, bike, run, strength, other); pull 90 days if the source makes it cheap (few calls, no pagination pain).
  3. Wellness: the daily series for the same window — HRV (rMSSD), resting HR, sleep duration, sleep score, recovery/readiness score, subjective fatigue, whatever exists.
  4. Athlete profile/thresholds, if the source exposes them.
  5. Keep raw responses available until normalization is done; never re-type numbers from memory.

Step 4 — Normalize into the canonical schema

Read references/data-sources-and-normalization.md for per-source field mappings and unit conversions, then assemble one JSON object in exactly this canonical schema. All fields are optional unless marked REQUIRED. If a value is unknown: omit it or use null — never guess. Units are encoded in field names; dates are ISO 8601.

{
  "athlete": {
    "sex": "M | F | null",
    "ftp_w": 250,
    "lthr_bpm": 165,
    "hr_max_bpm": 190,
    "hr_rest_bpm": 45,
    "threshold_pace_sec_per_km": 255,
    "css_sec_per_100m": 95,
    "lthr_source": "measured | estimated_from_hrmax | null",
    "ftp_source": "measured | estimated | null"
  },
  "activities": [
    {
      "id": "garmin-123456",
      "source": "garmin",
      "sport": "swim | bike | run | strength | other",
      "start_time": "2026-06-01T07:30:00Z",
      "duration_sec": 3600,
      "moving_sec": 3500,
      "distance_m": 10000,
      "avg_hr_bpm": 150,
      "max_hr_bpm": 175,
      "avg_power_w": 200,
      "normalized_power_w": 210,
      "avg_pace_sec_per_km": 300,
      "avg_pace_sec_per_100m": 105,
      "tss_reported": 55.0,
      "rpe": 6,
      "time_in_hr_zones_sec": [600, 1200, 900, 300, 0],
      "time_in_power_zones_sec": [0, 0, 0, 0, 0, 0, 0],
      "name": "Morning Run"
    }
  ],
  "wellness": [
    {
      "date": "2026-06-01",
      "hrv_rmssd_ms": 65,
      "resting_hr_bpm": 44,
      "sleep_sec": 27000,
      "sleep_score": 80,
      "recovery_score": 75,
      "subjective_fatigue": 3
    }
  ],
  "races": [
    { "date": "2026-08-02", "distance": "sprint | olympic | 70.3 | full", "priority": "A | B | C", "name": "..." }
  ]
}

Normalization rules:

  • id is REQUIRED, format <source>-<native id>; source, sport, start_time (UTC if possible), and duration_sec (elapsed) are REQUIRED per activity. moving_sec falls back to duration_sec when the source has no moving time.
  • time_in_hr_zones_sec is Z1..Z5; time_in_power_zones_sec is Z1..Z7 (Coggan) — only when the source provides them; never reconstruct zone time yourself.
  • recovery_score normalizes Whoop recovery / Garmin Body Battery (morning value) / Oura readiness to 0–100.
  • Run pace and swim pace may be derived from distance and moving time when the source omits them; everything else must come straight from the source.
  • Maintain a running gaps list of every metric a source could not provide (no power meter, no HRV, no sleep, missing thresholds, ...). This list feeds the report's "Data coverage & gaps" section.

Step 5 — Compute metrics with the script

  1. Write the canonical JSON object to a temporary file (e.g. triathlon_input.json in the working directory or a temp directory).

  2. Run:

    python ${CLAUDE_SKILL_DIR}/scripts/compute_metrics.py INPUT.json [--today YYYY-MM-DD]
    

    In Claude Code, ${CLAUDE_SKILL_DIR} expands to this skill's directory; on surfaces without that substitution, resolve the directory containing this SKILL.md and run scripts/compute_metrics.py from there. Omit --today to default to the latest date present in the data; pass it explicitly if the user anchors the analysis to a specific day. For a quick sanity check of the script itself, scripts/example_input.json is a small synthetic fixture.

  3. The script prints pretty JSON with these top-level keys: meta, per_activity, daily, pmc, weekly, intensity, acwr, monotony, recovery, ramp, flags.

  4. Use ONLY the script's numbers for CTL/ATL/TSB, ACWR, monotony/strain, ramp rate, per-activity load, and weekly aggregates. Never compute or "adjust" these by mental arithmetic, and never substitute your own estimates for the script's output.

  5. Check meta.dropped_duplicates (cross-source dedup) and meta.data_gaps; both must be surfaced in the report. If the script errors, fix the input JSON (schema mismatch is the usual cause) and rerun — do not hand-compute the metrics instead.

Step 6 — Analyze

Interpret the script output, reading references as needed:

  • Fitness/fatigue/form: pmc (CTL/ATL/TSB) and ramp — see references/training-load-and-pmc.md for interpretation bands and caveats.
  • Load safety: acwr (prefer the ewma ratio for flagging; report both), monotony, and the flags array.
  • Intensity distribution: intensity (80/20 check — target roughly ≥75–80% of classified time low intensity; mind method_note, since session-average classification underestimates the high-intensity share of interval sessions).
  • Recovery: recovery (HRV, RHR, sleep statuses) — see references/recovery-and-readiness.md when wellness data exists or recovery flags fired.
  • Discipline balance: weekly.per_sport versus the goal race distance.

Treat every flag in flags as something the report must mention (severity info may be a single sentence; warn and alert need explanation and a consequence for the recommendation).

Step 7 — Report

Render the report using the template below, in the user's conversation language (translate the section headings too). State data gaps explicitly. Keep it sober and technical: numbers with units, trends with direction, no hype.

Step 8 — Recommend

Read references/periodization-and-planning.md, then:

  1. Recommend the next session: discipline, session type, duration, target zone, and a 1–2 sentence rationale tied to specific script numbers (e.g. TSB, recovery status, discipline imbalance).
  2. Give block guidance: where the athlete sits relative to the race (base/build/peak/taper or maintenance if no race), and what to adjust in the coming 1–2 weeks.
  3. Apply the hard safety rules last — they override the plan, the periodization logic, and the race calendar.

Graceful degradation (missing data)

Iron rule: name every gap, and never invent numbers. Every metric you could not obtain, every estimated threshold, and every fallback method used must appear in the "Data coverage & gaps" section. A smaller honest report beats a complete-looking fabricated one.

  • No power data (bike): the script falls back automatically along the load-method chain (reported TSS → NP-based → avg-power → pace-based → HR-based → TRIMP → session-RPE → none). Report which method carried most of the load (see per_activity.load_method) and its coarseness. Do not pretend power-grade precision.
  • Missing thresholds (FTP / LTHR / run threshold pace / CSS): ask the user once. If still missing, the script estimates LTHR ≈ 0.90 × hr_max where possible (always flagged); loads fall back to HR/TRIMP/sRPE methods. Mark all affected numbers as estimates and recommend a field test (see references/zones-and-thresholds.md).
  • No HRV: the HRV status will be no_data. Base recovery assessment on RHR, sleep, and TSB instead, and say that HRV-based readiness was not assessable.
  • No wellness data at all: skip recovery-trend claims entirely; rely on TSB, monotony, and subjective feedback from the user. The script raises DATA_SPARSE — surface it.
  • Short history (< 42 days): CTL is unreliable (CTL_UNRELIABLE flag); say that fitness trends need more history and keep recommendations conservative.
  • Activities without any load signal (load_method = none, load 0): list them in gaps; do not assign them an invented load.
  • No race on the calendar: analyze for general fitness; block guidance becomes a maintenance/consistency recommendation rather than race-phase positioning.

Report template

Use this skeleton. Replace placeholders; translate headings into the user's language. Omit the Flags section entirely when there are no flags.

# Training Report — {date range} ({n} days)

## 1. Snapshot
{One sentence: fitness CTL {x}, fatigue ATL {y}, form TSB {z}, recovery status {ok/watch/alert/no_data}.}

## 2. Discipline breakdown
| Discipline | Sessions | Time | Distance | Load (TSS) | Trend |
|---|---|---|---|---|---|
| Swim  | {n} | {h:mm} | {km} | {tss} | {↑/→/↓} |
| Bike  | {n} | {h:mm} | {km} | {tss} | {↑/→/↓} |
| Run   | {n} | {h:mm} | {km} | {tss} | {↑/→/↓} |
| Other | {n} | {h:mm} | —    | {tss} | {↑/→/↓} |

{1–3 sentences: intensity distribution ({low}% low / {mod}% moderate / {high}% high vs ~80/20
target), discipline balance vs goal race.}

## 3. Load & recovery trends
- CTL {x} ({±y}/week ramp), ATL {x}, TSB {x} — {interpretation}.
- ACWR: rolling {x}, EWMA {x} — {band interpretation}.
- HRV: 7d {x} ms vs 28d baseline {y} ms — {status}. RHR: {x} vs {y} bpm — {status}.
  Sleep: {h:mm}/night vs baseline {h:mm} — {status}.

## 4. Flags
- {SEVERITY} {CODE}: {plain-language explanation and consequence}.

## 5. Next session
**{Discipline} — {type}, {duration}, {target zone}.**
{1–2 sentence rationale citing the specific numbers that drive this choice.}

## 6. Block recommendation
{Phase position relative to {race} on {date} (or maintenance if no race); what to adjust over
the next 1–2 weeks: volume, intensity, recovery emphasis.}

## 7. Data coverage & gaps
- Sources: {list}. Window: {n} days, {n} activities ({n} after dedup; dropped: {ids or none}).
- Missing/estimated: {every gap — e.g. no power meter on bike, LTHR estimated from hr_max,
  no HRV data, swim CSS unknown}.

---
*{Footer, one line: This is decision support, not medical advice; how you feel (RPE) overrides the numbers.}*

Footer rule: the one-line disclaimer is mandatory in every report and must convey: decision support, not medical advice; the athlete's RPE/subjective feel overrides the numbers.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.