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.
- hevy2garmin-0.1.1/.env.example +19 -0
- hevy2garmin-0.1.1/.github/workflows/ci.yml +68 -0
- hevy2garmin-0.1.1/.github/workflows/publish.yml +67 -0
- hevy2garmin-0.1.1/.gitignore +44 -0
- hevy2garmin-0.1.1/Dockerfile +13 -0
- hevy2garmin-0.1.1/LICENSE +21 -0
- hevy2garmin-0.1.1/PKG-INFO +359 -0
- hevy2garmin-0.1.1/README.md +325 -0
- hevy2garmin-0.1.1/api/index.py +11 -0
- hevy2garmin-0.1.1/docs/AI_HANDOVER.md +58 -0
- hevy2garmin-0.1.1/docs/screenshots/calories.png +0 -0
- hevy2garmin-0.1.1/docs/screenshots/dashboard.png +0 -0
- hevy2garmin-0.1.1/docs/screenshots/hr-chart.png +0 -0
- hevy2garmin-0.1.1/docs/screenshots/mappings.png +0 -0
- hevy2garmin-0.1.1/docs/screenshots/workouts.png +0 -0
- hevy2garmin-0.1.1/pyproject.toml +49 -0
- hevy2garmin-0.1.1/requirements.txt +10 -0
- hevy2garmin-0.1.1/src/hevy2garmin/__init__.py +8 -0
- hevy2garmin-0.1.1/src/hevy2garmin/cli.py +264 -0
- hevy2garmin-0.1.1/src/hevy2garmin/config.py +156 -0
- hevy2garmin-0.1.1/src/hevy2garmin/db.py +122 -0
- hevy2garmin-0.1.1/src/hevy2garmin/db_interface.py +58 -0
- hevy2garmin-0.1.1/src/hevy2garmin/db_postgres.py +251 -0
- hevy2garmin-0.1.1/src/hevy2garmin/db_sqlite.py +146 -0
- hevy2garmin-0.1.1/src/hevy2garmin/fit.py +379 -0
- hevy2garmin-0.1.1/src/hevy2garmin/garmin.py +213 -0
- hevy2garmin-0.1.1/src/hevy2garmin/hevy.py +92 -0
- hevy2garmin-0.1.1/src/hevy2garmin/mapper.py +700 -0
- hevy2garmin-0.1.1/src/hevy2garmin/matcher.py +196 -0
- hevy2garmin-0.1.1/src/hevy2garmin/server.py +1281 -0
- hevy2garmin-0.1.1/src/hevy2garmin/static/favicon.svg +42 -0
- hevy2garmin-0.1.1/src/hevy2garmin/sync.py +190 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/base.html +242 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/dashboard.html +441 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/history.html +49 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/mappings.html +318 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/partials/autosync_status.html +23 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/partials/sync_result.html +24 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/settings.html +215 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/setup.html +180 -0
- hevy2garmin-0.1.1/src/hevy2garmin/templates/workouts.html +449 -0
- hevy2garmin-0.1.1/tests/__init__.py +0 -0
- hevy2garmin-0.1.1/tests/conftest.py +84 -0
- hevy2garmin-0.1.1/tests/test_cli.py +64 -0
- hevy2garmin-0.1.1/tests/test_config.py +80 -0
- hevy2garmin-0.1.1/tests/test_db.py +158 -0
- hevy2garmin-0.1.1/tests/test_fit.py +129 -0
- hevy2garmin-0.1.1/tests/test_garmin.py +101 -0
- hevy2garmin-0.1.1/tests/test_hevy.py +78 -0
- hevy2garmin-0.1.1/tests/test_mapper.py +93 -0
- hevy2garmin-0.1.1/tests/test_sync.py +126 -0
- hevy2garmin-0.1.1/vercel.json +12 -0
- hevy2garmin-0.1.1/worker/index.js +274 -0
- 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,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
|
+
|  |  |
|
|
73
|
+
| **HR Timeline** | **Calorie Breakdown** |
|
|
74
|
+
|  |  |
|
|
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
|
+
[](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
|