A Claude Code skill that turns Claude into a data-driven cycling coach. It connects to intervals.icu for real training data, generates Zwift workouts, builds periodized training plans, and provides weekly adaptive reviews — all through natural conversation.
| Capability | Description |
|---|---|
| Ride Analysis | Fetch activity data from intervals.icu and get coaching feedback with execution ratings, interval review, and next-session recommendations |
| Weekly Summary | Aggregate the past 7 days — total TSS, zone distribution, power profile, FTP detection |
| Training Plans | Multi-week periodized plans with PMC tracking (CTL/ATL/TSB/ACWR), progressive overload, and block periodization |
| Weekly Reviews | Compare planned vs actual training load, apply adaptation decision trees, adjust the next week |
| Zwift Workouts | Generate .zwo workout files with structured warm-up, intervals, and cool-down — single or batch |
| Training Advice | Zone/FTP questions, race peaking & taper protocols, mid-week check-ins |
| Power Profiling | Coggan-based W/kg classification (sprinter, pursuiter, time trialist, all-rounder) with strength/weakness analysis |
- Claude Code (CLI, desktop app, or IDE extension)
- Python 3.9+
- An intervals.icu account with API key
pip install requestsfitparse is an optional dependency — install it only if you need the .fit file fallback path (scripts/fit_ingest.py): pip install fitparse.
git clone https://github.com/chuazj/cycling-fitness-coach.gitCopy or symlink into your Claude Code skills directory:
# macOS / Linux
cp -r cycling-fitness-coach ~/.claude/skills/cycling-fitness-coach
# Windows (PowerShell)
Copy-Item -Recurse cycling-fitness-coach "$env:USERPROFILE\.claude\skills\cycling-fitness-coach"Create a .env file in the skill root (the directory containing SKILL.md):
INTERVALS_ICU_ATHLETE_ID=i12345
INTERVALS_ICU_API_KEY=your_api_key_here
Get your API key from intervals.icu Settings (permanent key, no refresh needed).
Once installed, Claude Code automatically activates the skill when you mention cycling topics. Just talk to it naturally:
Analyze my workout: https://intervals.icu/activities/i126468486
or just paste an activity ID:
How did my ride go? i126468486
Claude fetches the data via API, computes NP/IF/TSS/zones/peaks, and returns coaching analysis with an execution rating and next-session recommendation.
How was my training week?
Aggregates the last 7 days: total TSS, zone distribution, power profile, and auto-FTP detection.
Build me a 6-week training plan to improve my FTP
Claude bootstraps your PMC from 90-day history, selects the appropriate periodization block, designs week-by-week TSS progression, and generates Zwift workouts for Week 1.
Review my week — how did I do?
Compares planned vs actual training load, checks CTL/ATL/TSB trends, applies adaptation decision trees, and adjusts the upcoming week.
Create a sweet spot workout, 60 minutes
Generates a .zwo file with structured warm-up, main set, and cool-down. Power targets are expressed as FTP fractions for automatic scaling in Zwift.
I have a race in 3 weeks — help me peak
What zone should I focus on if I plateau at sweet spot?
My RPE was 9 but IF was only 0.72 — what's going on?
SKILL.md <- Skill entry point (triggers, coaching rules, workflow dispatch)
workflows/
analyze.md <- Activity analysis + weekly summary
plan.md <- Plan creation + weekly review
generate.md <- Zwift workout generation
advise.md <- Training advice + mid-week check-in + race peaking
scripts/
intervals_icu_api.py <- Thin re-export façade + CLI entry point (preserves `from intervals_icu_api import …` surface)
intervals_icu/ <- intervals.icu API client package (split from former monolith)
api_client.py <- HTTP client, auth, .env loading
metrics.py <- Pure metric computation (NP, IF, TSS, zones, peaks, cardiac drift, power profile)
activity.py <- Single-activity analysis + weekly training summary
wellness.py <- WHOOP wellness fields, baselines, Yellow/Red flag detection
readiness.py <- Pre-ride readiness verdict engine
cli.py <- argparse definition + mode dispatch
generate_zwo.py <- Zwift .zwo XML generator
pmc_calculator.py <- PMC bootstrap (90-day history) + weekly update (planned vs actual)
batch_generate_zwo.py <- Batch .zwo generation from JSON array
sparkline.py <- Pure-Python ASCII sparkline for Peak Power Trends in plans/active_plan.md
rpe_trend.py <- RPE trend aggregator (Obsidian frontmatter scan + functional-overreaching detection)
fit_ingest.py <- .fit file fallback: parses local ride files when activity is not on intervals.icu
zwo_lint.py <- Linter for existing .zwo files (hygiene rules, ERG design, modeled TSS)
prediction_tracker.py <- W5 validation loop: predict/reconcile/seed forecasts, recalibration triggers
references/
training_zones.md <- Power/HR zone definitions, weekly structure
workout_analysis.md <- Analysis framework, coaching response templates
zwo_format.md <- Zwift XML element spec, ERG design constraints, and examples
intervals_icu_api.md <- intervals.icu API endpoints, data models, and dependency notes
block_templates.md <- Block templates, TSS distribution, progressive overload, FTP test protocols, Block Selection Logic
weekly_adaptation.md <- Weekly adaptation decision trees (load, ACWR, TSB, RPE, illness/injury)
race_taper.md <- Race / event peaking — taper structures and TSB projection
durability_strength.md <- Concurrent strength training, heat adaptation, durability concept
fueling.md <- Pre/during/post-ride nutrition, carb targets, GI troubleshooting
adaptation_rules.md <- Per-activity forward-cascade adaptation rules (signals -> severity -> next-session)
plan_state_schema.md <- Structure spec for active training plan state
obsidian_templates.md <- Frontmatter templates for workout reviews, plans, weekly reviews
menstrual_cycle_training.md <- Hormonal-cycle autoregulation protocol for female athletes
rule_registry.md <- Orphan-prevention catalogue of standing coaching rules (W4)
prediction_calibration.md <- W5 predict→measure→calibrate loop: models, ledger, recalibration triggers
plans/
active_plan.md <- Active training plan (generated by Create Plan workflow; gitignored — local-only)
block_history.md <- Archive of completed blocks (gitignored — local-only)
tests/ <- Unit tests (pure functions, mocks, CLI, PMC integration, fit ingest, linter, predictions)
assets/
template_sweetspot.zwo <- Example Zwift workout XML
Activity analysis:
intervals.icu link -> intervals_icu_api.py fetches activity/intervals/streams/power-curve -> computes metrics -> JSON output -> Claude provides coaching analysis
Plan creation:
pmc_calculator.py --bootstrap -> PMC baseline -> Claude designs periodized block -> writes plans/active_plan.md -> batch_generate_zwo.py generates week's .zwo files
Weekly review:
pmc_calculator.py --weekly-update -> planned vs actual comparison -> Claude applies adaptation rules -> updates plan -> generates next week's workouts
# Analyze a single activity
python scripts/intervals_icu_api.py --activity i126468486 --ftp 200 --weight 70
# Auto-fetch FTP/weight from athlete profile
python scripts/intervals_icu_api.py --activity i126468486 --use-athlete-profile
# List recent activities
python scripts/intervals_icu_api.py --list-recent 10
# Weekly summary (last 7 days)
python scripts/intervals_icu_api.py --weekly-summary -o summary.json
# Compact output (fewer tokens for LLM consumption)
python scripts/intervals_icu_api.py --activity i126468486 --compact# Bootstrap: 90-day history with current CTL/ATL/TSB + peak powers
python scripts/pmc_calculator.py --bootstrap --days 90
# Weekly update: compare planned vs actual
python scripts/pmc_calculator.py --weekly-update \
--week 1 --plan-start 2025-03-16 \
--prev-ctl 42.3 --prev-atl 51.2 \
--planned-tss '{"Tue":65,"Thu":70,"Sat":80,"Flex":55}'# Generate a single Zwift workout from JSON definition
python scripts/generate_zwo.py --json workout_def.json --output workout.zwo --ftp 200# Generate all .zwo files for a training week.
# --output-dir should be your Zwift custom workouts folder so the files show up in-game:
# Windows: %LOCALAPPDATA%\Zwift\Workouts\<athlete_id>\week1\
# macOS/Linux: ~/Documents/Zwift/Workouts/<athlete_id>/week1/
python scripts/batch_generate_zwo.py --input week_workouts.json --output-dir "<ZWIFT_WORKOUTS_DIR>/week1/" --ftp 200
# Dry run (validate + compute stats without writing files — any --output-dir is fine here)
python scripts/batch_generate_zwo.py --input week_workouts.json --dry-run --ftp 200Fallback for rides that did not sync to intervals.icu (e.g., Strava-only or local .fit files). Emits the same analysis JSON as the API path.
# Requires: pip install fitparse
python scripts/fit_ingest.py --file <ride.fit> --ftp 188 --weight 74 -o output.jsonValidates an existing .zwo file against the canonical element reference and hygiene rules (ERG design, textevent placement, power range, ramp direction). Reports modeled NP-based TSS.
python scripts/zwo_lint.py path/to/workout.zwo --ftp 188
# Exit code: 0 = clean, 1 = errors, 2 = unreadableW5 validation loop: log forecasts before a session, reconcile against actuals after, and surface recalibration triggers when the model drifts.
# Seed baseline from existing Obsidian reviews (one-time)
python scripts/prediction_tracker.py --mode seed-baseline --vault-path "<CYCLING_VAULT_PATH>/workout-reviews"
# Log a forecast before a session
python scripts/prediction_tracker.py --mode predict --type rpe_at_if --if 0.84 --slot morning --session-date 2026-06-02 --session-type Threshold
# Reconcile open predictions against actuals
python scripts/prediction_tracker.py --mode reconcile --vault-path "<CYCLING_VAULT_PATH>/workout-reviews" -o prediction_report.jsonThe coaching approach is grounded in established sport science:
- Power zones: 7-zone model based on FTP (Coggan)
- Sweet spot training: 88-94% FTP — high stimulus, manageable fatigue, optimal for time-crunched athletes
- Periodization: Block periodization with base, build, peak, and recovery phases
- Load management: PMC-based tracking with CTL (fitness), ATL (fatigue), TSB (form), and ACWR (acute:chronic workload ratio)
- Adaptation: Decision trees for weekly plan adjustments based on compliance, RPE:IF mismatch, and TSB trends
- Race peaking: Taper protocols with progressive volume reduction while maintaining intensity
The skill follows strict coaching process rules:
- Validate before prescribing — presents an assessment of the athlete's current state and waits for confirmation before making recommendations
- Establish zones first — never prescribes zone-specific workouts without confirmed FTP; recommends field tests for unvalidated zones
- Explain the "why" — every prescription includes the physiological purpose and how it connects to the athlete's goal
- Adaptation requires approval — proposes changes based on data but waits for athlete confirmation before modifying the plan
| Integration | Purpose |
|---|---|
| Obsidian | Persistent storage for workout analyses, plans, and weekly reviews with frontmatter metadata |
| Zwift | Load generated .zwo files into custom workouts |
python -m unittest discover tests -v691 tests across 8 files: pure function unit tests, mocked HTTP responses, CLI argument parsing, PMC integration, .fit file ingestion, ZWO linting, and prediction tracker tests. Expected runtime ~0.1s.
To adapt this skill for your own use:
- FTP/Weight: Run scripts with
--use-athlete-profileto auto-fetch from your intervals.icu profile, or set values inplans/active_plan.md→ Athlete Profile after the Create Plan workflow runs (this file is gitignored — your data stays local) - Training days: Auto-detected from your recent activity pattern; can be overridden during plan creation
- Obsidian vault: Set the
CYCLING_VAULT_PATHenvironment variable (seereferences/setup.md→ Obsidian Integration) - Zwift workout folder: Set the
ZWIFT_WORKOUT_DIRenvironment variable (seereferences/setup.md→ Zwift Workout Directory)
MIT