# Gymbook Workout

> Build and export a GymBook-importable .workout file using the gymbook_connector pydantic models in this repo. Use when asked to create, generate, design, or export a workout, workout program, training block, or exercise routine for GymBook, or to read/modify a .workout file.

- Skill: `anchitrao/gymbook-workout` (Agent Skill)
- Install (CLI): `npx skillmds@latest add anchitrao/gymbook-workout`
- Raw SKILL.md: https://api.skillmd.com/api/skills/anchitrao/gymbook-workout/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: anchitrao (https://skillmd.com/u/anchitrao)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/anchitrao/gymbook-workout

---


Use the pydantic models in `src/gymbook_connector/` — don't hand-write `.workout`
JSON. This is a reverse-engineered format (no official spec); the models encode
everything confirmed by real import testing against the app. Full writeup,
confirmed findings, and open questions: `README.md` at the repo root — read it
before doing anything nonstandard (custom exercises, new taxonomy codes, fields
not shown below).

## The one hard rule

**`quantityUnit` is required on every `WorkoutExerciseSet`, with no exceptions**
— including bodyweight sets that have no `quantityValue` at all. Omitting it on
even one set fails the *entire* file's import with a generic, unhelpful error.
When a set truly has no natural unit, use `"lb"` (or whatever a real export
would plausibly use) rather than leaving it unset. See
`src/gymbook_connector/models/workout.py` for why this was concluded (found via
extensive real-import bisection testing, not guessed).

## Building a workout

```python
from gymbook_connector.io import save_workout
from gymbook_connector.models.exercise import Exercise
from gymbook_connector.models.workout import (
    Workout, WorkoutExercise, WorkoutExerciseGroup, WorkoutExerciseSet, WorkoutGroup,
)

workout = Workout(
    name="Push Day",
    days=["010.monday"],                              # optional; see codes.py
    notes="optional workout-level note",               # optional
    workoutGroup=WorkoutGroup(name="My Program"),       # optional; program grouping
    workoutExercises=[
        WorkoutExercise(
            exercise=my_exercise,                       # see "Picking exercises" below
            notes="optional exercise-level note",
            timerDuration=60,                            # optional, seconds, unconfirmed exact semantics
            workoutExerciseGroup=WorkoutExerciseGroup(name="Superset A"),  # optional
            workoutExerciseSets=[
                WorkoutExerciseSet(
                    type="warmUp",                       # "default" | "warmUp" | "coolDown" (others unconfirmed)
                    frequencyUnit="x",                    # "x"=reps, "s"=seconds, "m"=minutes; free text
                    frequencyValueMin=5, frequencyValueMax=8,  # or just frequencyValueMax for a fixed target
                    quantityUnit="lb",                    # REQUIRED -- see above
                    quantityValue=135,                    # optional; omit for bodyweight (keep quantityUnit!)
                ),
            ],
        ),
    ],
)
save_workout(workout, "/path/to/output.workout")
```

Load an existing file the same way: `load_workout(path)` from
`gymbook_connector.io`. Both are pydantic models, so mutate and re-save freely.

## Picking exercises

Every `workoutExercise.exercise` is a full embedded `Exercise` object (region,
muscles, equipment, an optional `identifier`), not a reference — GymBook's
taxonomy codes (`"070.legs"`, `"060.glutes"`, etc.) are internal and only
observable from real exports.

1. **Prefer a known exercise.** `data/exercise_catalog.json` (rebuild/extend with
   `uv run scripts/ingest.py`) maps exercise names to full, confirmed-correct
   `Exercise` objects pulled from real `.workout` exports. Load it and construct
   `Exercise(**catalog[name])` (mind the camelCase aliases) to reuse one exactly.
2. **Need an exercise not in the catalog?** Look it up by name in
   `data/samples/csv/exercises.csv` (`gymbook_connector.catalog.csv_catalog`) for
   its correct region/muscle names, then build an `Exercise` yourself: omit
   `identifier` (confirmed OK — that's how a custom exercise is expressed), but
   still set `imageIdentifier` to an existing icon id borrowed from a similar
   known exercise (an original export did exactly this: a custom "Snatch Grip
   Barbell Shrug" reused the `imageIdentifier` of the built-in Barbell Shrugs).
   Pick codes from `models/codes.py`'s `KNOWN_*` registries by matching muscle/
   region names — **flag this to the user as unverified** if you must invent a
   code not already in that registry; only ~20 of 485 exercises currently have
   fully confirmed codes, so this is common and expected, not a bug.

## After generating a file

Run `uv run pytest` if you touched the models. There's no way to verify a
generated file actually imports without a human trying it in the app — say so
plainly rather than asserting it will work, and ask the user to test-import it
if this is new/unusual content (e.g. a first-time custom exercise or a taxonomy
code not yet in `KNOWN_*`).

