Generate Garmin FIT workout files from a simple DSL
Project description
pace2fit
Generate Garmin FIT workout files from a simple text description.
A public instance is running at pace2fit.eu --
no install required, just open the page, type a workout, and download
the .fit file.
pace2fit generate 'warmup@6:00,3x(8min@threshold+2min@easy),cooldown@6:30' -o workout.fit
Quick start
No install needed -- run directly with uvx:
uvx pace2fit generate '10min@Z2'
Or from the git repo directly:
uvx --from git+https://codeberg.org/Solal/pace2fit pace2fit generate '10min@Z2'
Install
Requires Python 3.10+.
uv sync
Or install as a tool:
uv tool install .
Usage
Generate a workout
# Simple zone workout
pace2fit generate '10min@Z2'
# Structured workout with intervals
pace2fit generate 'warmup@6:00,3x(8min@threshold+2min@easy),cooldown@6:30' \
-o thursday.fit -n 'Thursday Threshold'
# Distance-based
pace2fit generate '5km@5:30-6:00' -o easy_run.fit
Options
pace2fit generate [OPTIONS] WORKOUT
Arguments:
WORKOUT Workout description string (required)
Options:
-o, --output PATH Output .fit file path (default: workout.fit)
-n, --name TEXT Workout name (default: the input string)
--unit [km|mi] Pace unit (default: km)
--help Show help and exit
Web interface
Start a local web UI for writing workouts in the browser:
pace2fit serve
pace2fit serve --port 3000
Open http://127.0.0.1:8000 in your browser. The page includes a DSL syntax cheatsheet with clickable examples. Dark mode follows your OS preference.
DSL Syntax
A workout is a comma-separated list of steps:
warmup@6:00, 10min@Z2, 3x(8min@4:30+2min@6:00), cooldown@6:30
Durations
| Syntax | Meaning |
|---|---|
10min |
10 minutes |
30sec or 30s |
30 seconds |
5km |
5 kilometres |
400m |
400 metres |
warmup |
Open duration (lap button), warmup intensity |
cooldown |
Open duration (lap button), cooldown intensity |
open |
Open duration (lap button) |
Targets
Targets are specified with @ after the duration.
Pace targets (min:sec per km):
| Syntax | Meaning |
|---|---|
@5:30 |
5:30/km (auto-creates a +/-5s range) |
@5:00-6:00 |
Pace range from 5:00 to 6:00/km |
Heart rate zones:
| Syntax | Meaning |
|---|---|
@Z1 .. @Z5 |
HR zone 1 through 5 |
Named pace zones:
| Name | Pace range |
|---|---|
easy |
6:00 - 6:30/km |
recovery |
6:30 - 7:00/km |
tempo |
5:00 - 5:15/km |
threshold |
4:30 - 4:45/km |
marathon |
5:00 - 5:20/km |
hm |
4:40 - 4:55/km |
interval |
3:45 - 4:15/km |
repetition |
3:30 - 3:45/km |
Repeats
3x8min@threshold # 3 repeats of a single step
3x(8min@5:00+2min@6:00) # 3 repeats of work + recovery
4x(1min@Z4+1min@Z2+30s@Z5) # 3 steps repeated 4 times
Full examples
# Easy 30 min run
pace2fit generate '30min@easy'
# 10k tempo
pace2fit generate 'warmup@6:00,10km@tempo,cooldown@6:30' -n '10k Tempo'
# Classic threshold session
pace2fit generate 'warmup@6:00,3x(8min@threshold+2min@easy),cooldown@6:30'
# Track intervals
pace2fit generate 'warmup@6:00,8x(400m@Z5+400m@Z1),cooldown@6:30' -n 'Track 400s'
# Pyramid
pace2fit generate 'warmup@6:00,2min@Z3,4min@Z4,6min@Z4,4min@Z4,2min@Z3,cooldown@6:30'
Output
The generated .fit file can be loaded onto Garmin devices or uploaded to Garmin Connect as a workout.
Development
# Install dependencies
uv sync
# Install pre-commit hooks
prek install
# Run tests
uv run pytest
# Run tests with verbose output
uv run pytest -v
# Run all hooks manually
prek run --all-files
This project uses prek for pre-commit hooks.
Hooks run automatically on every commit and include ruff (lint + format),
pytest, and basic file hygiene checks. See prek.toml for the full
configuration.
Deploying
pace2fit can be self-hosted using Docker behind a reverse proxy.
Prerequisites
- A server with Docker installed
- A reverse proxy (e.g. Caddy) with a domain pointing to your server
DNS
Add an A record for your domain pointing to your server's public IP:
@ IN A <your-server-IPv4>
Build and run
git clone ssh://git@codeberg.org/Solal/pace2fit.git
cd pace2fit
GIT_COMMIT=$(git rev-parse --short HEAD) docker compose up -d --build
This builds the image and starts the container on 127.0.0.1:8000.
Caddy configuration
Add a block to your Caddyfile:
pace2fit.eu {
reverse_proxy localhost:8000
}
Then reload Caddy:
sudo systemctl reload caddy
Caddy automatically provisions HTTPS via Let's Encrypt.
Updating
cd pace2fit
git pull
GIT_COMMIT=$(git rev-parse --short HEAD) docker compose up -d --build
Dependencies
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pace2fit-0.1.3.tar.gz.
File metadata
- Download URL: pace2fit-0.1.3.tar.gz
- Upload date:
- Size: 28.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
87ca03ac608d92cd55d0e8593eb4e35f11d897dc071af769d48cdf162b261d98
|
|
| MD5 |
40c7227167081b3a54f79888bb23c498
|
|
| BLAKE2b-256 |
fb2f994c2952242bfa60e59b8b73f7782e6f438411ec7808f8bda39c8078a1d9
|
File details
Details for the file pace2fit-0.1.3-py3-none-any.whl.
File metadata
- Download URL: pace2fit-0.1.3-py3-none-any.whl
- Upload date:
- Size: 32.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
876255ffcc8eee39aaa5fffffa051a94a0d4e9c88d8e43a9756dd24678f90061
|
|
| MD5 |
292768cb54f2c2d1a398a2f1d4e55b08
|
|
| BLAKE2b-256 |
dd359557262dac62f4fdbf6c7da08d6def989a50378089aa06b5575797274146
|