Install
$ agentstack add skill-estruyf-skill-cycling-plan-coach-skill-cycling-plan-coach ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Cycling plan coach
Turn a short conversation into ready-to-use standalone files for coaching: a weekly markdown plan and the structured bike workouts it references. Workouts are generated as .zwo (Zwift). The athlete's identity, goal, language and units come from a config file, not from this skill, so it works for anyone.
Resolve config first (every run)
Before doing any real work:
- Look for the config at
athlete.json(next to this skill). - If it exists and parses, load it and go straight to the main workflow.
- If it is missing or invalid (first run), run the setup flow in
references/onboarding.md, writeathlete.json, then continue into the main workflow in the same turn. Do not make the athlete re-invoke the skill after setup.
athlete.json is the single source of truth for who the plan is for. Fields are documented in references/athlete-config.md.
From the config take: name (greetings), language and units (everything the athlete reads is produced in these), styleNotes (writing rules to honor), goal, age, gender, weightKg, fallbackFtp, targetWkg, groupRideDays, typicalAvailableDays, typicalWorkoutDurationMin, maxStructuredSessions, strengthDefault, and optional riderTypeOverride.
If age is missing from an existing config, ask for it before continuing — it is required to set recovery windows. If gender is missing, ask for it or accept a skip; note that it defaults to gender-neutral W/kg benchmarks when absent.
At the start of every weekly planning run, ask a short weekly-availability intake before training design:
- Available training days for the upcoming week.
- Planned group-ride day(s) for the upcoming week.
- Planned duration per workout day.
- Strength preference this week: strength, core, or both.
- Frequency for strength/core this week (how many times).
- Workout file format: ZWO (Zwift). Store
"zwo"asworkoutFormatinathlete.jsonafter confirmation. - Garmin export (optional): Ask once per session whether the athlete has a Garmin Coach JSON export. If yes, accept the pasted JSON and process it using
references/garmin-data.mdbefore any session design. If the athlete is on Garmin Connect, point them to the Garmin Workout Importer Chrome extension (https://chromewebstore.google.com/detail/garmin-workout-importer/faebbfokokipdpkbolpbpfadmgdbanpo) to generate the export. If the athlete provides neither Strava access nor a Garmin export, continue withfallbackFtpand config values only.
Always offer a one-step option to use predefined values from athlete.json profile config (for example: typical training days, groupRideDays, strengthDefault, and any stored duration preferences). If the athlete chooses profile defaults, confirm what was applied and only ask for overrides.
Main workflow
Step 1 - pull data (Garmin export and/or Strava)
Retrieve the athlete's current numbers and recent history. Use whichever sources are available:
Garmin export (if provided): Process using references/garmin-data.md. Extract FTP, LTHR, zones, readiness (HRV, sleep, body battery, training readiness score), load (ATL, CTL, ACWR, training status, recovery window), and recent activity TSS/NP. These override fallbackFtp and enrich the plan with readiness context that Strava cannot provide.
Strava (if connected): Tool calls and mapping are in references/strava-pull.md. Get: current FTP and zones; gender from the athlete profile (populate athlete.json only if the field is currently absent — never overwrite a stated preference); the last 10 to 14 days of activities; and the power-duration curve from the most recent hard ride. Strava activity names are preferred for archetype extraction.
When both are provided: Use Garmin for zones, readiness, and load; use Strava for activity names and relative effort. If FTP values differ, note the discrepancy and ask the athlete which to use.
When neither is available: Use fallbackFtp and continue — age and gender come from the config.
Summarise last week and the readiness state back to the athlete in their language in one or two sentences. If readiness data triggers a plan adjustment, state it explicitly.
Step 2 - classify rider type (data-driven)
Unless riderTypeOverride is set, classify the athlete from their own power curve using references/rider-types.md. The classification (sprinter/puncheur, all-rounder, diesel/time-triallist) plus the athlete's goal set the session priorities, which sessions to emphasise and which to skip. Do not assume a fixed rider type; derive it. State the detected type and the resulting focus in one line.
Step 3 - confirm intake and session design
Restate the goal from the config in one line, then confirm or complete intake values gathered at the start. Ensure these are explicitly captured: available training days, group-ride day(s), duration per workout day, how many structured key sessions (default maxStructuredSessions), indoor or outdoor preference, week's focus, strength mode (strength/core/both), and strength/core frequency. Keep the intake short with the interactive picker when available. Always provide a "use profile defaults" path so the athlete can accept predefined config values and only override what changed. Factor last week's load into the new week.
Step 4 - apply the training model
Use the FTP from Strava (fallback: fallbackFtp), the rider type from Step 2, and the goal. Convert every % FTP target to watts in the human-readable text using that FTP. Principles and session shapes are in references/training-model.md. Always: polarized week, at most maxStructuredSessions structured days midweek, group-ride days social with no forced structure, never three hard days back to back, steer by power and allow backing off on feel.
Step 5 - build structured workouts
Build the workout files inside this skill in ZWO format.
- Session archetypes, rotation rules, and progression steps live in
references/workout-library.md. - ZWO format spec (Zwift):
references/zwo-format.md— power as FTP fractions, durations in seconds, XML output. - Before selecting sessions, check the archetypes extracted from last week's Strava rides (Step 1). Do not repeat the same archetype unless advancing it by one progression step. Use the rotation table in
references/workout-library.mdto pick this week's pair. - Every structured bike session must produce one workout file per requested format.
- Use
% FTPfor planning and convert to watts in the markdown plan using the week's FTP. - Store power as FTP fractions and durations in seconds. Add localized
texteventcues in the athlete's language. - Keep hard sessions capped by
maxStructuredSessionsand aligned with the polarized model. - Run through the validation checklist in the relevant format reference before delivering.
- Output every workout file inline as a labeled fenced XML code block so the athlete can read and copy it directly from Claude's UI.
Step 6 - produce the files
Save everything to /mnt/user-data/outputs/ and present it. If that path is unavailable, deliver all files inline in the response instead. Human-readable text in the config language; distances and speeds in the config units. Machine keys stay English; filenames stay lowercase-hyphenated and neutral.
- One weekly plan markdown file, named by week id using ISO 8601 week numbering (e.g.
2026-W26-plan.md; note week 1 may start in late December of the prior year). Follow the structure inreferences/plan-format.md. Include: week id, goal, detected rider type, FTP used, one-line last-week summary, day-by-day schedule table, structured session detail blocks (% FTP and converted watts), group-ride guidance, and strength/core exercises (mapped fromreferences/strength-library.md). - One ZWO workout file per structured bike session, built using
references/zwo-format.md. On-screen cues in the config language. Rendered inline as a fenced XML code block. Filenames must be listed in the markdown plan under their matching day. - When there is a full gym session, add a dedicated markdown file (e.g.
2026-W26-strength.md) with sets, reps, rest, and coaching cues in the config language. Reference it from the main plan.
Quality checks before delivering
- A weekly markdown plan file exists and includes all required sections (week id, goal, focus, FTP used, rider type, last-week summary, day-by-day schedule table, structured session detail blocks).
- FTP used in the markdown plan equals the live FTP (or the stated fallback when Strava is unavailable).
- Every structured bike day in the markdown references exactly one workout file per requested format; watt targets in markdown match the % FTP targets at the stated FTP.
- Session priorities match the detected rider type and the goal.
- The week works toward the goal, structured days capped, group days social, strength off the pre-interval day, last week factored in.
- Every workout file passes the validation checklist in its format reference (
references/zwo-format.mdfor ZWO) and is rendered inline as a labeled fenced code block. - All human-readable text is in the config language and units, honoring
styleNotes. State the week id, focus, detected rider type, last week in one line, and which files you created.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: estruyf
- Source: estruyf/skill-cycling-plan-coach
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.