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-nameformatgithub_token: Personal Access Token withreposcope. Create at: https://github.com/settings/tokensgithub_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;