hevy2garmin 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. hevy2garmin-0.1.1/.env.example +19 -0
  2. hevy2garmin-0.1.1/.github/workflows/ci.yml +68 -0
  3. hevy2garmin-0.1.1/.github/workflows/publish.yml +67 -0
  4. hevy2garmin-0.1.1/.gitignore +44 -0
  5. hevy2garmin-0.1.1/Dockerfile +13 -0
  6. hevy2garmin-0.1.1/LICENSE +21 -0
  7. hevy2garmin-0.1.1/PKG-INFO +359 -0
  8. hevy2garmin-0.1.1/README.md +325 -0
  9. hevy2garmin-0.1.1/api/index.py +11 -0
  10. hevy2garmin-0.1.1/docs/AI_HANDOVER.md +58 -0
  11. hevy2garmin-0.1.1/docs/screenshots/calories.png +0 -0
  12. hevy2garmin-0.1.1/docs/screenshots/dashboard.png +0 -0
  13. hevy2garmin-0.1.1/docs/screenshots/hr-chart.png +0 -0
  14. hevy2garmin-0.1.1/docs/screenshots/mappings.png +0 -0
  15. hevy2garmin-0.1.1/docs/screenshots/workouts.png +0 -0
  16. hevy2garmin-0.1.1/pyproject.toml +49 -0
  17. hevy2garmin-0.1.1/requirements.txt +10 -0
  18. hevy2garmin-0.1.1/src/hevy2garmin/__init__.py +8 -0
  19. hevy2garmin-0.1.1/src/hevy2garmin/cli.py +264 -0
  20. hevy2garmin-0.1.1/src/hevy2garmin/config.py +156 -0
  21. hevy2garmin-0.1.1/src/hevy2garmin/db.py +122 -0
  22. hevy2garmin-0.1.1/src/hevy2garmin/db_interface.py +58 -0
  23. hevy2garmin-0.1.1/src/hevy2garmin/db_postgres.py +251 -0
  24. hevy2garmin-0.1.1/src/hevy2garmin/db_sqlite.py +146 -0
  25. hevy2garmin-0.1.1/src/hevy2garmin/fit.py +379 -0
  26. hevy2garmin-0.1.1/src/hevy2garmin/garmin.py +213 -0
  27. hevy2garmin-0.1.1/src/hevy2garmin/hevy.py +92 -0
  28. hevy2garmin-0.1.1/src/hevy2garmin/mapper.py +700 -0
  29. hevy2garmin-0.1.1/src/hevy2garmin/matcher.py +196 -0
  30. hevy2garmin-0.1.1/src/hevy2garmin/server.py +1281 -0
  31. hevy2garmin-0.1.1/src/hevy2garmin/static/favicon.svg +42 -0
  32. hevy2garmin-0.1.1/src/hevy2garmin/sync.py +190 -0
  33. hevy2garmin-0.1.1/src/hevy2garmin/templates/base.html +242 -0
  34. hevy2garmin-0.1.1/src/hevy2garmin/templates/dashboard.html +441 -0
  35. hevy2garmin-0.1.1/src/hevy2garmin/templates/history.html +49 -0
  36. hevy2garmin-0.1.1/src/hevy2garmin/templates/mappings.html +318 -0
  37. hevy2garmin-0.1.1/src/hevy2garmin/templates/partials/autosync_status.html +23 -0
  38. hevy2garmin-0.1.1/src/hevy2garmin/templates/partials/sync_result.html +24 -0
  39. hevy2garmin-0.1.1/src/hevy2garmin/templates/settings.html +215 -0
  40. hevy2garmin-0.1.1/src/hevy2garmin/templates/setup.html +180 -0
  41. hevy2garmin-0.1.1/src/hevy2garmin/templates/workouts.html +449 -0
  42. hevy2garmin-0.1.1/tests/__init__.py +0 -0
  43. hevy2garmin-0.1.1/tests/conftest.py +84 -0
  44. hevy2garmin-0.1.1/tests/test_cli.py +64 -0
  45. hevy2garmin-0.1.1/tests/test_config.py +80 -0
  46. hevy2garmin-0.1.1/tests/test_db.py +158 -0
  47. hevy2garmin-0.1.1/tests/test_fit.py +129 -0
  48. hevy2garmin-0.1.1/tests/test_garmin.py +101 -0
  49. hevy2garmin-0.1.1/tests/test_hevy.py +78 -0
  50. hevy2garmin-0.1.1/tests/test_mapper.py +93 -0
  51. hevy2garmin-0.1.1/tests/test_sync.py +126 -0
  52. hevy2garmin-0.1.1/vercel.json +12 -0
  53. hevy2garmin-0.1.1/worker/index.js +274 -0
  54. hevy2garmin-0.1.1/worker/wrangler.toml +3 -0
@@ -0,0 +1,19 @@
1
+ # hevy2garmin environment variables
2
+
3
+ # Required: Hevy API key (requires Hevy Pro subscription)
4
+ # Get yours at https://hevy.com/settings > Integrations & API > Generate API Key
5
+ HEVY_API_KEY=
6
+
7
+ # Optional: GitHub PAT with repo+workflow scopes (enables auto-sync via GitHub Actions)
8
+ # Create one at: https://github.com/settings/tokens/new?scopes=repo,workflow&description=hevy2garmin
9
+ GITHUB_PAT=
10
+
11
+ # Required: Garmin Connect credentials (used for initial auth on all platforms)
12
+ GARMIN_EMAIL=
13
+ GARMIN_PASSWORD=
14
+
15
+ # ── Vercel-specific (auto-set by Vercel) ──────────────────────────────────
16
+ # DATABASE_URL= # Auto-set by Vercel Postgres integration
17
+ # VERCEL=1 # Auto-set when running on Vercel
18
+ # VERCEL_GIT_REPO_OWNER= # Auto-set: GitHub username
19
+ # VERCEL_GIT_REPO_SLUG= # Auto-set: repo name
@@ -0,0 +1,68 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ check-sqlite:
11
+ name: Tests (SQLite, Python ${{ matrix.python-version }})
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ python-version: ["3.10", "3.12"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install package with dev dependencies
25
+ run: pip install -e ".[dev]"
26
+
27
+ - name: Import check
28
+ run: python -c "from hevy2garmin.hevy import HevyClient; from hevy2garmin.mapper import lookup_exercise; from hevy2garmin.fit import generate_fit; print('OK')"
29
+
30
+ - name: CLI check
31
+ run: hevy2garmin --help
32
+
33
+ - name: Run tests
34
+ run: python -m pytest tests/ -v
35
+
36
+ check-postgres:
37
+ name: Tests (Postgres)
38
+ runs-on: ubuntu-latest
39
+
40
+ services:
41
+ postgres:
42
+ image: postgres:16
43
+ env:
44
+ POSTGRES_DB: hevy2garmin_test
45
+ POSTGRES_USER: test
46
+ POSTGRES_PASSWORD: test
47
+ ports:
48
+ - 5432:5432
49
+ options: >-
50
+ --health-cmd pg_isready
51
+ --health-interval 10s
52
+ --health-timeout 5s
53
+ --health-retries 5
54
+
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - uses: actions/setup-python@v5
59
+ with:
60
+ python-version: "3.12"
61
+
62
+ - name: Install package with cloud + dev dependencies
63
+ run: pip install -e ".[dev,cloud]"
64
+
65
+ - name: Run tests with Postgres backend
66
+ env:
67
+ DATABASE_URL: postgresql://test:test@localhost:5432/hevy2garmin_test
68
+ run: python -m pytest tests/ -v
@@ -0,0 +1,67 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ check-version:
9
+ runs-on: ubuntu-latest
10
+ outputs:
11
+ should_publish: ${{ steps.check.outputs.changed }}
12
+ version: ${{ steps.check.outputs.version }}
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ with:
16
+ fetch-depth: 2
17
+
18
+ - name: Check if version changed
19
+ id: check
20
+ run: |
21
+ # Get current version from pyproject.toml
22
+ CURRENT=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
23
+ echo "version=$CURRENT" >> "$GITHUB_OUTPUT"
24
+
25
+ # Get previous version
26
+ PREVIOUS=$(git show HEAD~1:pyproject.toml 2>/dev/null | grep '^version' | head -1 | sed 's/.*"\(.*\)".*/\1/' || echo "")
27
+
28
+ if [ "$CURRENT" != "$PREVIOUS" ] && [ -n "$PREVIOUS" ]; then
29
+ echo "Version changed: $PREVIOUS → $CURRENT"
30
+ echo "changed=true" >> "$GITHUB_OUTPUT"
31
+ else
32
+ echo "Version unchanged: $CURRENT"
33
+ echo "changed=false" >> "$GITHUB_OUTPUT"
34
+ fi
35
+
36
+ publish:
37
+ needs: check-version
38
+ if: needs.check-version.outputs.should_publish == 'true'
39
+ runs-on: ubuntu-latest
40
+ environment: pypi
41
+ permissions:
42
+ id-token: write # Required for trusted publishing
43
+ contents: write # Required for creating git tags
44
+
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+
48
+ - uses: actions/setup-python@v5
49
+ with:
50
+ python-version: "3.12"
51
+
52
+ - name: Install build tools
53
+ run: pip install build
54
+
55
+ - name: Build package
56
+ run: python -m build
57
+
58
+ - name: Create git tag
59
+ run: |
60
+ VERSION="${{ needs.check-version.outputs.version }}"
61
+ git config user.name "github-actions"
62
+ git config user.email "github-actions@github.com"
63
+ git tag -a "v$VERSION" -m "Release v$VERSION"
64
+ git push origin "v$VERSION"
65
+
66
+ - name: Publish to PyPI
67
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,44 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ dist/
7
+ build/
8
+ .eggs/
9
+
10
+ # Virtual environment
11
+ .venv/
12
+ venv/
13
+
14
+ # Environment
15
+ .env
16
+ .env.*
17
+ !.env.example
18
+
19
+ # IDE
20
+ .idea/
21
+ .vscode/
22
+ *.swp
23
+ *.swo
24
+
25
+ # OS
26
+ .DS_Store
27
+ Thumbs.db
28
+
29
+ # Project private
30
+ docs/plans/
31
+ .claude/
32
+ *.log
33
+
34
+ # Tokens (never commit)
35
+ .garminconnect/
36
+ .garmin-auth/
37
+
38
+ # FIT output
39
+ fit_output/
40
+
41
+ # SQLite DB
42
+ *.db
43
+ *.sqlite
44
+ worker/.wrangler/
@@ -0,0 +1,13 @@
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY pyproject.toml README.md ./
6
+ COPY src/ src/
7
+
8
+ RUN pip install --no-cache-dir .
9
+
10
+ EXPOSE 8123
11
+
12
+ ENTRYPOINT ["hevy2garmin"]
13
+ CMD ["status"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Konstantinos Georgiou
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,359 @@
1
+ Metadata-Version: 2.4
2
+ Name: hevy2garmin
3
+ Version: 0.1.1
4
+ Summary: Sync Hevy gym workouts to Garmin Connect — exercise mapping, FIT file generation, and automatic upload
5
+ Project-URL: Homepage, https://github.com/drkostas/hevy2garmin
6
+ Project-URL: Repository, https://github.com/drkostas/hevy2garmin
7
+ Author-email: Konstantinos Georgiou <delfinas7kostas@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: fit-file,fitness,garmin,hevy,strength-training,sync,workout
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: fastapi>=0.110
18
+ Requires-Dist: fit-tool>=0.9.15
19
+ Requires-Dist: garmin-auth>=0.2.1
20
+ Requires-Dist: garminconnect<0.3.0,>=0.2.38
21
+ Requires-Dist: jinja2>=3.1
22
+ Requires-Dist: psycopg2-binary>=2.9
23
+ Requires-Dist: pynacl>=1.5
24
+ Requires-Dist: python-multipart>=0.0.9
25
+ Requires-Dist: requests>=2.31
26
+ Requires-Dist: uvicorn>=0.29
27
+ Provides-Extra: cloud
28
+ Requires-Dist: psycopg2-binary>=2.9; extra == 'cloud'
29
+ Requires-Dist: pynacl>=1.5; extra == 'cloud'
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest-mock>=3.12; extra == 'dev'
32
+ Requires-Dist: pytest>=8.0; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ <p align="center">
36
+ <img src="src/hevy2garmin/static/favicon.svg" width="80" height="80" alt="hevy2garmin logo">
37
+ </p>
38
+
39
+ <h1 align="center">hevy2garmin</h1>
40
+
41
+ <p align="center">
42
+ <a href="https://github.com/drkostas/hevy2garmin/actions/workflows/ci.yml"><img src="https://github.com/drkostas/hevy2garmin/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
43
+ <a href="https://pypi.org/project/hevy2garmin/"><img src="https://img.shields.io/pypi/v/hevy2garmin" alt="PyPI"></a>
44
+ <a href="https://pypi.org/project/hevy2garmin/"><img src="https://img.shields.io/pypi/pyversions/hevy2garmin" alt="Python"></a>
45
+ </p>
46
+
47
+ <p align="center">
48
+ Sync your <a href="https://hevyapp.com">Hevy</a> gym workouts to <a href="https://connect.garmin.com">Garmin Connect</a> with correct exercise names, sets, reps, weights, calorie estimation, and optional heart rate overlay from your Garmin watch.
49
+ </p>
50
+
51
+ <p align="center">
52
+ <img src="docs/screenshots/dashboard.png" alt="Dashboard" width="800">
53
+ </p>
54
+
55
+ > **Hevy Pro required.** The Hevy API is only available with a [Hevy Pro](https://hevyapp.com) subscription. Without it, hevy2garmin cannot access your workouts.
56
+
57
+ ## Why?
58
+
59
+ Hevy is great for tracking gym workouts but doesn't sync to Garmin. This tool bridges the gap:
60
+
61
+ - **Maps 433+ Hevy exercises** to Garmin FIT SDK categories so bench press shows as bench press, not "Other"
62
+ - **Generates proper FIT files** with exercise structure, sets, reps, weights, and timing
63
+ - **Uploads to Garmin Connect** with the correct activity name and a detailed description
64
+ - **Estimates calories** using the Keytel formula (weight, age, VO2max, heart rate)
65
+ - **Overlays heart rate data** from your Garmin watch onto workout charts with per-exercise segments
66
+ - **Tracks synced workouts** so nothing gets duplicated
67
+
68
+ ## Screenshots
69
+
70
+ | Workouts | Mappings |
71
+ |----------|----------|
72
+ | ![Workouts](docs/screenshots/workouts.png) | ![Mappings](docs/screenshots/mappings.png) |
73
+ | **HR Timeline** | **Calorie Breakdown** |
74
+ | ![HR Chart](docs/screenshots/hr-chart.png) | ![Calories](docs/screenshots/calories.png) |
75
+
76
+ ## Requirements
77
+
78
+ - **[Hevy Pro](https://hevyapp.com) subscription** (required for API access)
79
+ - A [Garmin Connect](https://connect.garmin.com) account
80
+ - Python 3.10+ (for local install only, not needed for one-click deploy)
81
+
82
+ ## Quick Start
83
+
84
+ Pick the option that fits you best:
85
+
86
+ ### One-Click Deploy (no coding required)
87
+
88
+ Deploy from your phone or computer in about 5 minutes. No terminal or coding needed.
89
+
90
+ > **You need [Hevy Pro](https://hevyapp.com) for API access.** Free Hevy accounts cannot use hevy2garmin.
91
+
92
+ **Step 1: Get your Hevy API key**
93
+
94
+ Open [hevy.com/settings](https://hevy.com/settings), scroll to **Integrations & API**, click **Generate API Key**, and copy it. If you don't see this section, you need to upgrade to Hevy Pro.
95
+
96
+ **Step 2: Create a free GitHub account** (skip if you already have one)
97
+
98
+ Sign up at [github.com](https://github.com/signup). You'll use this to sign into Vercel too.
99
+
100
+ **Step 3: Create a GitHub access token**
101
+
102
+ This token lets hevy2garmin set up automatic syncing on your behalf. Open [this link](https://github.com/settings/tokens/new?scopes=repo,workflow&description=hevy2garmin) (sign in if prompted):
103
+
104
+ 1. Set **Expiration** to **No expiration** (otherwise auto-sync stops when it expires)
105
+ 2. Scroll to the bottom, click **Generate token**
106
+ 3. **Copy the token immediately** (starts with `ghp_`). GitHub only shows it once.
107
+
108
+ **Step 4: Deploy to Vercel**
109
+
110
+ [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdrkostas%2Fhevy2garmin&env=HEVY_API_KEY,GARMIN_EMAIL,GARMIN_PASSWORD,GITHUB_PAT&envDescription=Hevy%20API%20key%2C%20Garmin%20credentials%2C%20and%20GitHub%20PAT%20(repo%2Bworkflow%20scopes)&stores=%5B%7B%22type%22%3A%22postgres%22%7D%5D&project-name=hevy2garmin)
111
+
112
+ Click the button above. Sign in with GitHub if prompted. Vercel will ask for 4 values:
113
+
114
+ | Field | What to paste |
115
+ |-------|--------------|
116
+ | `HEVY_API_KEY` | The API key from step 1 |
117
+ | `GARMIN_EMAIL` | Your Garmin Connect email |
118
+ | `GARMIN_PASSWORD` | Your Garmin Connect password |
119
+ | `GITHUB_PAT` | The token from step 3 |
120
+
121
+ Vercel will also create a free Postgres database automatically. Leave the default settings and click **Deploy**. Wait about a minute for it to build.
122
+
123
+ **Step 5: Connect Garmin**
124
+
125
+ Open your new app (Vercel gives you a URL like `hevy2garmin-yourname.vercel.app`). Click **Save & Continue** on the setup page.
126
+
127
+ The app will ask you to sign into Garmin through your browser:
128
+
129
+ 1. Tap **Sign into Garmin** (opens Garmin's login page in a new tab)
130
+ 2. Log in with your Garmin email and password
131
+ 3. After login, **copy the URL** from your browser's address bar
132
+ 4. Go back to the setup tab and **paste it** in the box
133
+ 5. Click **Connect**
134
+
135
+ This is needed because Garmin blocks automated logins from cloud servers. Your browser does the login from your own internet connection, then the app stores the tokens securely.
136
+
137
+ **Step 6: Sync your workouts**
138
+
139
+ You're on the dashboard. Click **Sync All Workouts** to backfill your history. The app syncs one workout at a time (you can close the page and come back, it picks up where it left off).
140
+
141
+ To keep future workouts syncing automatically, toggle **Auto-sync** on the dashboard. This creates a background job that syncs new workouts every 2 hours.
142
+
143
+ **That's it.** Check [Garmin Connect](https://connect.garmin.com/modern/activities) to see your workouts with proper exercise names, sets, reps, and weights.
144
+
145
+ ### Web Dashboard (local install)
146
+
147
+ ```bash
148
+ pip install hevy2garmin
149
+ hevy2garmin serve
150
+ ```
151
+
152
+ > Not on PyPI yet? Install from source: `git clone https://github.com/drkostas/hevy2garmin.git && cd hevy2garmin && pip install .`
153
+
154
+ Open [localhost:8123](http://localhost:8123). The setup wizard walks you through connecting Hevy and Garmin.
155
+
156
+ Once you click **Sync Now**, your workouts appear in [Garmin Connect](https://connect.garmin.com/modern/activities) within a few seconds. Enable **auto-sync** on the dashboard to keep things synced on a schedule (30 min to 24 hours).
157
+
158
+ To keep the server running in the background:
159
+
160
+ ```bash
161
+ nohup hevy2garmin serve > /dev/null 2>&1 &
162
+ ```
163
+
164
+ <details>
165
+ <summary>systemd service file (Linux)</summary>
166
+
167
+ Save as `/etc/systemd/system/hevy2garmin.service`:
168
+
169
+ ```ini
170
+ [Unit]
171
+ Description=hevy2garmin dashboard
172
+ After=network.target
173
+
174
+ [Service]
175
+ ExecStart=hevy2garmin serve
176
+ Restart=always
177
+ User=your-username
178
+ Environment=HEVY_API_KEY=your-key
179
+ Environment=GARMIN_EMAIL=your-email
180
+
181
+ [Install]
182
+ WantedBy=multi-user.target
183
+ ```
184
+
185
+ Then `sudo systemctl enable --now hevy2garmin`.
186
+
187
+ </details>
188
+
189
+ ### CLI
190
+
191
+ ```bash
192
+ pip install hevy2garmin
193
+
194
+ # Interactive setup (Hevy API key + Garmin credentials)
195
+ hevy2garmin init
196
+
197
+ # Sync your 10 most recent workouts
198
+ hevy2garmin sync
199
+
200
+ # List recent workouts (checkmark = already synced)
201
+ hevy2garmin list
202
+
203
+ # Check sync status
204
+ hevy2garmin status
205
+
206
+ # Dry run (generate FIT files without uploading)
207
+ hevy2garmin sync --dry-run
208
+
209
+ # Sync last 5 workouts only
210
+ hevy2garmin sync -n 5
211
+ ```
212
+
213
+ After syncing, check [Garmin Connect](https://connect.garmin.com/modern/activities) to see your workouts.
214
+
215
+ **Recurring sync without the dashboard:** set up a crontab after running `hevy2garmin init`:
216
+
217
+ ```bash
218
+ # Sync every 2 hours (uses credentials saved by hevy2garmin init)
219
+ 0 */2 * * * hevy2garmin sync
220
+ ```
221
+
222
+ ### Docker
223
+
224
+ ```bash
225
+ git clone https://github.com/drkostas/hevy2garmin.git
226
+ cd hevy2garmin
227
+ docker build -t hevy2garmin .
228
+ ```
229
+
230
+ Before running in Docker, you need Garmin auth tokens. Either:
231
+ - Run `pip install hevy2garmin && hevy2garmin init` locally (if you have Python), or
232
+ - Run `docker run -it -v ~/.garminconnect:/root/.garminconnect hevy2garmin init` to set up inside Docker interactively
233
+
234
+ **Web dashboard with auto-sync:**
235
+
236
+ ```bash
237
+ docker run -d -p 8123:8123 --restart unless-stopped \
238
+ -v ~/.hevy2garmin:/root/.hevy2garmin \
239
+ -v ~/.garminconnect:/root/.garminconnect \
240
+ -e HEVY_API_KEY=... \
241
+ -e GARMIN_EMAIL=... \
242
+ hevy2garmin serve
243
+ ```
244
+
245
+ Open [localhost:8123](http://localhost:8123) and enable auto-sync on the dashboard.
246
+
247
+ **One-off sync:**
248
+
249
+ ```bash
250
+ docker run --rm \
251
+ -v ~/.hevy2garmin:/root/.hevy2garmin \
252
+ -v ~/.garminconnect:/root/.garminconnect \
253
+ -e HEVY_API_KEY=... \
254
+ -e GARMIN_EMAIL=... \
255
+ hevy2garmin sync
256
+ ```
257
+
258
+ ### Python API
259
+
260
+ ```bash
261
+ pip install hevy2garmin
262
+ ```
263
+
264
+ Before using the API, make sure credentials are available via `~/.hevy2garmin/config.json` (run `hevy2garmin init`), environment variables, or pass them directly.
265
+
266
+ ```python
267
+ from hevy2garmin.sync import sync
268
+
269
+ # Uses config from ~/.hevy2garmin/config.json (or env vars)
270
+ result = sync()
271
+ print(f"Synced: {result['synced']}, Skipped: {result['skipped']}")
272
+
273
+ # Or pass credentials directly (no config file needed)
274
+ result = sync(hevy_api_key="...", garmin_email="...", garmin_password="...")
275
+ ```
276
+
277
+ ```python
278
+ # Just the exercise mapper
279
+ from hevy2garmin.mapper import lookup_exercise
280
+
281
+ cat, subcat, name = lookup_exercise("Bench Press (Barbell)")
282
+ # (0, 1, "Bench Press (Barbell)")
283
+
284
+ # Just FIT generation (see Hevy API docs for workout dict format:
285
+ # https://docs.hevy.com/#tag/workout/operation/workout)
286
+ from hevy2garmin.fit import generate_fit
287
+
288
+ result = generate_fit(hevy_workout_dict, hr_samples=None, output_path="workout.fit")
289
+ ```
290
+
291
+ For cloud deployments (Vercel, CI/CD), install with Postgres support:
292
+
293
+ ```bash
294
+ pip install hevy2garmin[cloud]
295
+ ```
296
+
297
+ This adds `psycopg2-binary` and enables automatic Postgres backend detection via `DATABASE_URL`.
298
+
299
+ ## Getting Your Hevy API Key
300
+
301
+ > **Hevy Pro is required.** API access is not available on the free plan.
302
+
303
+ 1. Go to [Hevy Settings](https://hevyapp.com/settings) > Integrations & API
304
+ 2. Click **Generate API Key** and copy it
305
+ 3. Paste it into `hevy2garmin init`, the web dashboard setup, or set as `HEVY_API_KEY` env var
306
+
307
+ If you don't see the Integrations & API section, you need to upgrade to [Hevy Pro](https://hevyapp.com).
308
+
309
+ ## Credentials
310
+
311
+ **Three ways to provide credentials** (in order of precedence):
312
+ 1. CLI flags: `--hevy-api-key`, `--garmin-email`, `--garmin-password`
313
+ 2. Environment variables: `HEVY_API_KEY`, `GARMIN_EMAIL`, `GARMIN_PASSWORD`
314
+ 3. Config file: `~/.hevy2garmin/config.json` (created by `hevy2garmin init` or the web dashboard)
315
+
316
+ See [`.env.example`](.env.example) for all available env vars.
317
+
318
+ **Garmin authentication:** Only needs the password for initial login. After that, tokens are cached (in `~/.garminconnect` locally or in Postgres for cloud deploys) and refresh automatically.
319
+
320
+ > **Cloud deploys (Vercel):** Garmin blocks automated logins from cloud servers. The setup wizard handles this by having you sign into Garmin through your browser, then securely storing the tokens. This only needs to be done once.
321
+
322
+ ## How It Works
323
+
324
+ 1. Pulls workouts from the Hevy API
325
+ 2. Maps each exercise to a Garmin FIT SDK category and subcategory (433+ built-in mappings, plus any custom ones you add)
326
+ 3. Generates a structured FIT file with timing, sets, reps, weights, and calories
327
+ 4. Optionally fetches HR data from Garmin daily monitoring and overlays it on the workout
328
+ 5. Authenticates with Garmin via [garmin-auth](https://pypi.org/project/garmin-auth/) (self-healing OAuth)
329
+ 6. Uploads the FIT file, renames the activity, and sets the description
330
+ 7. Tracks synced workouts in SQLite (local) or Postgres (cloud) to avoid duplicates
331
+
332
+ ## Exercise Mapping
333
+
334
+ 433+ Hevy exercises are mapped to Garmin FIT SDK categories. If an exercise isn't mapped it falls back to "Unknown" (category 65534). The web dashboard shows unmapped exercises and lets you add custom mappings with a few clicks. You can also add them via CLI:
335
+
336
+ ```bash
337
+ hevy2garmin map "My Custom Exercise" --category 28 --subcategory 0
338
+ ```
339
+
340
+ ## Development
341
+
342
+ ```bash
343
+ git clone https://github.com/drkostas/hevy2garmin.git
344
+ cd hevy2garmin
345
+ python -m venv .venv && source .venv/bin/activate
346
+ pip install -e ".[dev]"
347
+ pytest tests/ -v
348
+ ```
349
+
350
+ To test the Postgres backend locally:
351
+
352
+ ```bash
353
+ pip install -e ".[dev,cloud]"
354
+ DATABASE_URL=postgresql://user:pass@localhost:5432/hevy2garmin pytest tests/ -v
355
+ ```
356
+
357
+ ## License
358
+
359
+ MIT