Personal training manager that connects to the Hevy internal API — fetch your full workout history, push routines, and view progress in a rich local dashboard with an AI coach.
Goal: Muscle hypertrophy — 5 strength days/week + triathlon maintenance (swim · bike · run).
make install # create venv + install deps
make run # → http://localhost:8085/dashboard.htmlNo
make? Run manually —python -m venv .venv→ activate →pip install -r requirements.txt→python server.pyNo server? Open
reports/dashboard.htmldirectly in any browser (view-only, no fetch button).
| Command | What it does |
|---|---|
make install |
Create .venv and install all dependencies |
make run |
Start dashboard server on :8085 |
make fetch |
Fetch latest Hevy data (needs token in .env) |
make push-routines |
Sync routines from data/routines.json to Hevy |
make build |
Regenerate reports/dashboard.html from source |
make test |
Run all 36 unit tests |
make lint |
Lint with ruff |
make fmt |
Auto-format with ruff |
make check |
Full CI suite (lint + format + tests) |
Screenshots live in
docs/screenshots/.
| Dot color | Meaning |
|---|---|
| ⚫ gray | No token saved — paste one to connect |
| 🟢 green | Token saved, used within the last 15 min |
| 🟡 yellow | Token may be expired (>15 min) — paste a fresh one |
Click ✏️ Edit on any card to open the inline editor:
Validation: sets 1–20, reps 1–50, name required. Save writes to routines.json (via server) and updates localStorage.
On first open → picks LLM (GPT-4o / Gemini / Claude) + API key (stored in localStorage). Say "generate a new plan" → AI outputs JSON → Apply plan button appears → updates the Training Plan section above.
Gemini model name is configurable — when you select Gemini in the setup modal a "Model name" field appears (default: gemini-2.5-flash). Change it any time without touching code if Google deprecates a model.
If the plan has more than 4 routines an inline warning reminds you that Hevy's free tier only syncs the first 4.
| Script | What it does |
|---|---|
scripts/fetch_data.py |
Fetches full Hevy history → reports/data.js |
scripts/generate_dashboard.py |
Writes reports/dashboard.html (no API needed) |
scripts/push_routines.py |
Pushes routines from data/routines.json to Hevy |
server.py |
Companion server on :8085 — serves dashboard + handles fetch/save |
python server.py # start dashboard server (recommended)
python scripts/fetch_data.py # CLI fetch (needs .env token)
python scripts/generate_dashboard.py # rebuild HTML only
python scripts/push_routines.py # sync routines to HevyHevy's public API requires a paid subscription. This project uses the internal API authenticated with a session token from your browser.
- Open hevy.com and log in
- Press F12 → Network tab → click any request to
api.hevyapp.com - Copy the full
authorizationheader value:Bearer eyJ... - Paste it into the dashboard token bar (click the status dot in the header)
Tokens expire in ~15–20 min. The status dot turns yellow when yours may be stale.
CLI only — paste into .env:
HEVY_BEARER_TOKEN=Bearer your_token_here
Routines live in data/routines.json — committed, no credentials, safe to edit.
Each entry has display info (used by the dashboard) and Hevy template IDs (used by push_routines.py). Edit the JSON or use the dashboard editor, then run push_routines.py to sync.
Hevy free tier limit: 4 routines max (enforced server-side).
| Day | Strength | Endurance |
|---|---|---|
| Monday | Chest + Triceps | — |
| Tuesday | Back + Biceps | — |
| Wednesday | Legs | — |
| Thursday | — | Swim |
| Friday | Chest + Shoulders | — |
| Saturday | Arms | Bike (morning) |
| Sunday | — | Run |
Cardio tracked via Strava / Garmin — not pushed to Hevy.
python -m pytest tests/ -v36 unit tests — all mocked (no real API calls):
tests/test_fetch_data.py— Epley 1RM, IQR outlier filtering, process pipeline, duration clamping, MAX_REPS filter, empty workouts, zero-weight sets, calendar aggregationtests/test_push_routines.py— payload builder, set indexing, optional fields, header validation, input validation errors
GitHub Actions runs on every push and pull request to main:
- Lint —
ruff check(pyflakes, pycodestyle, bugbear, isort, pyupgrade) - Format —
ruff format --check - Test —
pyteston Python 3.11 and 3.12
scripts/
├── fetch_data.py # GET Hevy history → reports/data.js
├── push_routines.py # POST routines to Hevy from data/routines.json
└── generate_dashboard.py # writes reports/dashboard.html
data/
└── routines.json # routine definitions (committed)
reports/
├── dashboard.html # generated UI (gitignored)
├── data.js # your workout data (gitignored)
└── data.sample.js # sample data for preview (committed)
tests/
├── test_fetch_data.py
└── test_push_routines.py
server.py # companion server (:8085)








