feat(skills): add zwift-workout skill (.zwo builder, validator, preview, publishing)
Bike-only Zwift .zwo generator with human-readable preview + confirmation loop. - scripts/zwo_builder.py: spec<->.zwo build/validate/parse/render/TSS estimate - scripts/zwo.py: CLI (build, from-template, validate, parse, render, templates) - references/zwo-format.md: condensed offline format reference - assets/example-vo2max.zwo + tests/test_zwo.py (9 tests) - Publishes via hosting skill; files result into Knowledge Wiki Format ground truth: h4l/zwift-workout-file-reference
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: zwift-workout
|
||||
description: Create, preview, edit, validate and publish Zwift .zwo workout files for cycling (bike-only). Use when the user asks to build a Zwift workout, generate a .zwo file, convert a training idea into a Zwift session, preview/adjust an existing workout, or publish a workout for import into Zwift.
|
||||
---
|
||||
|
||||
# 🚴 Zwift Workout (.zwo) Builder
|
||||
|
||||
Builds valid Zwift `.zwo` files from a small JSON spec, renders a human-readable
|
||||
preview for confirmation, and publishes the finished file. **Bike-only.**
|
||||
|
||||
## When to use
|
||||
|
||||
- "Bau mir ein Zwift-Workout …" / "Create a .zwo for 5×3min VO2max"
|
||||
- "Zeig mir das Workout" / "änder die Intervalle auf 4×5min"
|
||||
- "Lade das Workout in Zwift" (publish to hosting for import)
|
||||
|
||||
## Core workflow (always follow this order)
|
||||
|
||||
1. **Draft the spec.** Either pick a deterministic template
|
||||
(`zwo.py templates` → `endurance | sweetspot | vo2max | sprint`) or write a
|
||||
JSON spec from the user's request. Keep power as FTP fractions
|
||||
(`0.88` = 88 %, `1.15` = 115 %); durations in **seconds**.
|
||||
2. **Render the preview and ask for confirmation.** Run
|
||||
`python3 {baseDir}/scripts/zwo.py render <spec.json>` (or `build … --preview`)
|
||||
and show the table to the user. **Do not publish before the user confirms.**
|
||||
3. **Apply changes.** Edit the JSON spec and re-render; repeat until confirmed.
|
||||
4. **Build + publish on confirmation.** Run
|
||||
`python3 {baseDir}/scripts/zwo.py build <spec.json> -o <name>.zwo --publish`.
|
||||
This validates, writes the file, and uploads it via the `hosting` skill
|
||||
(`https://hosting.haumdaucher.de/docs/…`).
|
||||
5. **Deliver.** Post the hosting link (and the `.zwo` as `MEDIA:<path>` if the
|
||||
user wants the file directly). Optionally offer a QR code (hosting skill rule).
|
||||
6. **File the result into the wiki** under `syntheses/` or `concepts/` per the
|
||||
Knowledge Wiki protocol (see below).
|
||||
|
||||
## JSON spec format
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "VO2 Max 5x3",
|
||||
"author": "IT Guy",
|
||||
"description": "5 × 3 min @ 115 % FTP, 3 min Pause.",
|
||||
"sportType": "bike",
|
||||
"tags": ["vo2max", "structured"],
|
||||
"steps": [
|
||||
{"type": "Warmup", "duration": 600, "powerLow": 0.50, "powerHigh": 0.75},
|
||||
{"type": "IntervalsT", "repeat": 5, "onDuration": 180, "offDuration": 180,
|
||||
"onPower": 1.15, "offPower": 0.55},
|
||||
{"type": "Cooldown", "duration": 300, "powerLow": 0.75, "powerHigh": 0.40}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Supported step types (bike)
|
||||
|
||||
| `type` | Required fields | Optional |
|
||||
|---|---|---|
|
||||
| `Warmup` | `duration`, `powerLow`, `powerHigh` | `cadence` |
|
||||
| `Cooldown` | `duration`, `powerLow`, `powerHigh` | `cadence` |
|
||||
| `SteadyState` | `duration`, `power` | `cadence` |
|
||||
| `Ramp` | `duration`, `powerLow`, `powerHigh` | — |
|
||||
| `IntervalsT` | `repeat`, `onDuration`, `offDuration`, `onPower`, `offPower` | `cadence`, `cadenceResting` |
|
||||
| `FreeRide` | `duration` | `flatRoad` |
|
||||
| `MaxEffort` | `duration` | — |
|
||||
| `SolidState` | `duration`, `power` | — |
|
||||
| `RestDay` | — | — |
|
||||
|
||||
Power values are **fractions of FTP**; duration is in **seconds**. See
|
||||
`references/zwo-format.md` for the full tag/attribute reference.
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
python3 {baseDir}/scripts/zwo.py templates # list templates
|
||||
python3 {baseDir}/scripts/zwo.py from-template vo2max --title "VO2 Max 5x3" --publish
|
||||
python3 {baseDir}/scripts/zwo.py render my.json # preview only
|
||||
python3 {baseDir}/scripts/zwo.py build my.json -o out.zwo --preview --publish
|
||||
python3 {baseDir}/scripts/zwo.py validate existing.zwo # check a file
|
||||
python3 {baseDir}/scripts/zwo.py parse existing.zwo --json # .zwo -> spec (editable)
|
||||
```
|
||||
|
||||
## Guardrails
|
||||
|
||||
- **Power range:** `0.20 – 1.50` FTP. Outside → hard error (catches unit mistakes).
|
||||
- **Durations:** integer seconds, `> 0`; `< 5 s` and `> 4 h` produce warnings.
|
||||
- **Structure:** exactly one `<workout>`, root `<workout_file>`, `name` required,
|
||||
`sportType` must be `bike`.
|
||||
- **Round-trip check:** output XML is re-parsed with `xml.etree` before writing;
|
||||
text fields are escaped, so umlauts/quotes never corrupt the file.
|
||||
- **Never publish without explicit user confirmation** of the rendered preview.
|
||||
|
||||
## Reference & fixtures
|
||||
|
||||
- `references/zwo-format.md` — condensed tag/attribute reference (offline copy).
|
||||
- `assets/example-vo2max.zwo` — known-good fixture; use for regression tests.
|
||||
|
||||
## Knowledge Wiki filing
|
||||
|
||||
After a workout is confirmed and published, file it per the Karpathy LLM Wiki
|
||||
protocol: create/update a `concepts/` or `syntheses/` page recording the workout
|
||||
name, structure, power targets, hosting URL, and rationale, then append a
|
||||
`log.md` entry (`ingest`/`synthesis`). Cross-link the Fitness agent page.
|
||||
|
||||
## Inter-agent synergy
|
||||
|
||||
`agents/fitness.md` owns training intent (Tacx Flux 2, FTP 275 W). This skill is
|
||||
the execution layer for structured cycling sessions. For evidence-based
|
||||
protocols (e.g. SIT 30 s / 4 min), consult the Knowledge Wiki first via
|
||||
`wiki_search`. A future refactor can share one spec module with `garmin-workout`
|
||||
(one source spec → Garmin workout + `.zwo`).
|
||||
Reference in New Issue
Block a user