Fitness Tracker
Log workouts via conversation and save to github and analyze workout data with DuckDB.
Chat Mode - Logging Workouts
Use this conversational flow when the user wants to record a workout:
Step 1: Date
Ask for the workout date or default to today. Format: YYYY-MM-DD
Step 2: Muscle Group
Ask what they're working. Categories from reference/exercises.md:
- •Chest, Back, Shoulders, Legs
- •Arms - Biceps, Arms - Triceps
- •Core, Compound, Cardio
Step 3: Exercise Selection
Show exercises from that category. Let user pick or type their own.
Step 4: Sets Entry
User enters sets naturally:
- •"135 for 10, 10" →
- 135 - 10, 10 - •"185 - 8, 8, 8" →
- 185 - 8, 8, 8 - •"10, 10, 10" (bodyweight) →
- 10, 10, 10 - •"30 minutes" (cardio) →
- 30m - •Add "warmup" or other notes as
// comment
Step 5: More Exercises?
Ask if they want to add another exercise. Repeat steps 2-4 as needed.
Step 6: Save
Save the workout to GitHub (or Obsidian as fallback):
- •File:
YYYY-MM-DD.md(one file per day, e.g.,2026-01-02.md) - •If file exists, append new exercises (multiple workouts per day are supported)
- •Push to GitHub using
scripts/github_sync.py
Example Conversation
User: Log my workout Claude: What date? (default: today, 2026-01-02) User: Today Claude: What are you working? (Chest, Back, Shoulders, Legs, Arms, Core, Cardio) User: Chest Claude: Chest exercises: Bench Press, Incline Bench Press, Dumbbell Press, Cable Flyes, Push-ups, Dips... User: Bench Claude: Enter your sets (e.g., "135 - 10, 10" or "185 for 8, 8, 8"): User: 135 for 10, 10 warmup, then 185 - 8, 8, 8 Claude: Got it: Bench Press - 135 - 10, 10 // warmup - 185 - 8, 8, 8 Another exercise? User: Cable flyes, 30 - 12, 12, 12 Claude: Added. Another exercise? User: Done Claude: Saved to 2026-01-02.md
Workout File Format
Files are named YYYY-MM-DD.md (one file per day). Multiple workouts in a day append to the same file.
Example: 2026-01-02.md
Bench - 135 - 10, 10 // warmup - 185 - 8, 8, 8 Situps - 10, 10, 10 Treadmill - 30m
Format Rules
- •Filename:
YYYY-MM-DD.md(date is in the filename) - •Exercise name: Plain text on its own line
- •Weighted sets:
- weight - reps, reps, reps(e.g.,- 185 - 8, 8, 8) - •Bodyweight sets:
- reps, reps, reps(e.g.,- 10, 10, 10) - •Duration:
- 30mor- 1h30m - •Notes:
// commentat end of line
Configuration
Edit scripts/config.json to configure GitHub (default) or Obsidian storage:
{
"github_repo": "username/repo-name",
"github_token": "github_pat_xxxxx",
"github_branch": "main",
"github_workout_dir": "workouts",
"obsidian_workout_dir": "/path/to/obsidian/vault/Fitness"
}
GitHub Configuration (Default)
- •
github_repo: Your repository inusername/repo-nameformat - •
github_token: Personal Access Token withreposcope. Create at: https://github.com/settings/tokens - •
github_branch: Target branch (default:main) - •
github_workout_dir: Directory in repo for workout files (default:workouts)
Obsidian Configuration (Fallback)
- •
obsidian_workout_dir: Local path to Obsidian vault directory (used if GitHub not configured)
Writing Workout Files
When saving workouts in chat mode:
GitHub Workflow
Important: Must cd into the scripts directory so github_sync.py can find config.json.
Step 1: Fetch existing file from GitHub (if any)
cd /mnt/skills/user/fitness-tracker/scripts uv run github_sync.py --fetch workouts/2026-01-02.md > /tmp/2026-01-02.md
If the file doesn't exist yet, create it empty.
Step 2: Append exercises to the local file
cat >> /tmp/2026-01-02.md << 'EOF' Pull-ups - 10, 10, 10 EOF
Step 3: Push back to GitHub
cd /mnt/skills/user/fitness-tracker/scripts uv run github_sync.py \ --file /tmp/2026-01-02.md \ --dest workouts/2026-01-02.md \ -m "Add workout for 2026-01-02"
Step 4: Update CSV
Fetch existing CSV, parse the new workout, and update:
cd /mnt/skills/user/fitness-tracker/scripts uv run github_sync.py --fetch workouts/workouts.csv > /tmp/workouts.csv uv run parse_workout.py /tmp/2026-01-02.md -o /tmp/workouts.csv --append uv run github_sync.py \ --file /tmp/workouts.csv \ --dest workouts/workouts.csv \ -m "Update workout data"
The --append flag replaces rows for the parsed date(s) while keeping other dates intact.
Obsidian (Fallback)
If GitHub is not configured (github_repo is empty):
- •Use
obsidian_workout_dirfrom config - •Target file:
{obsidian_workout_dir}/YYYY-MM-DD.md - •Use Obsidian MCP tools or write directly to filesystem
Parse Mode - Analyzing Workouts
# Parse all daily workout files in a directory uv run scripts/parse_workout.py /path/to/workouts/ -o workouts.csv # Parse a single day and update existing CSV (replaces that date's rows) uv run scripts/parse_workout.py /tmp/2026-01-02.md -o workouts.csv --append # Parse and run a query uv run scripts/parse_workout.py /path/to/workouts/ --query "SELECT * FROM workouts"
The parser matches files named YYYY-MM-DD.md and extracts the date from the filename.
Exercise Validation
The parser checks exercise names against reference/exercises.md and suggests corrections for typos:
Unrecognized exercises: 'Bech Press' - did you mean 'Bench Press'? 'Squatts' - did you mean 'Squats'?
Add new exercises to reference/exercises.md to expand the known list.
Output CSV Schema
| Column | Type | Description |
|---|---|---|
| date | DATE | Workout date (YYYY-MM-DD) |
| exercise | VARCHAR | Exercise name |
| set_num | INTEGER | Set number within exercise |
| weight | DECIMAL | Weight used (empty for bodyweight) |
| reps | INTEGER | Rep count (empty for duration exercises) |
| duration_min | INTEGER | Duration in minutes (for timed exercises) |
| notes | VARCHAR | Comments from // note |
Example Queries
-- Total volume per exercise
SELECT exercise, SUM(weight * reps) as volume
FROM workouts GROUP BY exercise ORDER BY volume DESC;
-- Bench press max weight over time
SELECT date, MAX(weight) as max_weight
FROM workouts WHERE exercise = 'Bench' GROUP BY date;
-- Weekly workout frequency
SELECT DATE_TRUNC('week', date) as week, COUNT(DISTINCT date) as days
FROM workouts GROUP BY 1 ORDER BY 1;
-- Exercises by total sets
SELECT exercise, COUNT(*) as total_sets
FROM workouts GROUP BY exercise ORDER BY total_sets DESC;