Metadata-Version: 2.4
Name: pace2fit
Version: 0.1.3
Summary: Generate Garmin FIT workout files from a simple DSL
Author: Solal Nathan
License-Expression: GPL-3.0-or-later
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Utilities
Requires-Dist: fastapi>=0.115
Requires-Dist: fit-tool>=0.9.15
Requires-Dist: typer>=0.15
Requires-Dist: uvicorn[standard]>=0.34
Requires-Python: >=3.10
Project-URL: Homepage, https://codeberg.org/Solal/pace2fit
Project-URL: Source, https://codeberg.org/Solal/pace2fit
Description-Content-Type: text/markdown

# pace2fit

[![CI](https://codeberg.org/Solal/pace2fit/badges/workflows/ci.yml/badge.svg)](https://codeberg.org/Solal/pace2fit/actions)
[![PyPI](https://img.shields.io/pypi/v/pace2fit)](https://pypi.org/project/pace2fit/)
[![License](https://img.shields.io/badge/license-GPL--3.0-blue)](LICENSE)

Generate Garmin FIT workout files from a simple text description.

**A public instance is running at [pace2fit.eu](https://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`:

```bash
uvx pace2fit generate '10min@Z2'
```

Or from the git repo directly:

```bash
uvx --from git+https://codeberg.org/Solal/pace2fit pace2fit generate '10min@Z2'
```

## Install

Requires Python 3.10+.

```bash
uv sync
```

Or install as a tool:

```bash
uv tool install .
```

## Usage

### Generate a workout

```bash
# 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:

```bash
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

```bash
# 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

```bash
# 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](https://github.com/j178/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](https://caddyserver.com/)) 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

```bash
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:

```caddyfile
pace2fit.eu {
    reverse_proxy localhost:8000
}
```

Then reload Caddy:

```bash
sudo systemctl reload caddy
```

Caddy automatically provisions HTTPS via Let's Encrypt.

### Updating

```bash
cd pace2fit
git pull
GIT_COMMIT=$(git rev-parse --short HEAD) docker compose up -d --build
```

## Dependencies

- [fit-tool](https://pypi.org/project/fit-tool/) -- FIT file encoding
- [typer](https://typer.tiangolo.com/) -- CLI framework
- [FastAPI](https://fastapi.tiangolo.com/) -- Web interface
- [Pico CSS](https://picocss.com/) -- Minimal CSS framework
