intervals-kit 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.
@@ -0,0 +1,77 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+
14
+ - name: Install uv
15
+ uses: astral-sh/setup-uv@v4
16
+ with:
17
+ python-version: "3.12"
18
+
19
+ - name: Install dependencies
20
+ run: uv sync
21
+
22
+ - name: Run tests
23
+ run: uv run pytest
24
+
25
+ build:
26
+ needs: test
27
+ runs-on: ubuntu-latest
28
+ steps:
29
+ - uses: actions/checkout@v4
30
+
31
+ - name: Install uv
32
+ uses: astral-sh/setup-uv@v4
33
+ with:
34
+ python-version: "3.12"
35
+
36
+ - name: Build package
37
+ run: uv build
38
+
39
+ - name: Upload dist artifacts
40
+ uses: actions/upload-artifact@v4
41
+ with:
42
+ name: dist
43
+ path: dist/
44
+
45
+ publish-testpypi:
46
+ needs: build
47
+ runs-on: ubuntu-latest
48
+ environment: testpypi
49
+ permissions:
50
+ id-token: write
51
+ steps:
52
+ - name: Download dist artifacts
53
+ uses: actions/download-artifact@v4
54
+ with:
55
+ name: dist
56
+ path: dist/
57
+
58
+ - name: Publish to TestPyPI
59
+ uses: pypa/gh-action-pypi-publish@release/v1
60
+ with:
61
+ repository-url: https://test.pypi.org/legacy/
62
+
63
+ publish-pypi:
64
+ needs: publish-testpypi
65
+ runs-on: ubuntu-latest
66
+ environment: pypi
67
+ permissions:
68
+ id-token: write
69
+ steps:
70
+ - name: Download dist artifacts
71
+ uses: actions/download-artifact@v4
72
+ with:
73
+ name: dist
74
+ path: dist/
75
+
76
+ - name: Publish to PyPI
77
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,28 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.11", "3.12"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v4
21
+ with:
22
+ python-version: ${{ matrix.python-version }}
23
+
24
+ - name: Install dependencies
25
+ run: uv sync
26
+
27
+ - name: Run tests
28
+ run: uv run pytest
@@ -0,0 +1,19 @@
1
+ # Credentials — never commit
2
+ .env
3
+
4
+ # Virtual environment
5
+ .venv/
6
+
7
+ # Build artifacts
8
+ dist/
9
+ *.egg-info/
10
+
11
+ # IDE
12
+ .idea/
13
+ .vscode/
14
+
15
+ # Python
16
+ __pycache__/
17
+ *.py[cod]
18
+ .pytest_cache/
19
+ .mypy_cache/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,105 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this project is
6
+
7
+ A Python package providing an MCP server and CLI for the Intervals.ICU REST API. See `SPECIFICATIONS.md` for the architecture spec (used to guide adding new endpoints) and `src/intervals_kit/cli_tools.md` for the full CLI reference.
8
+
9
+ ## Commands
10
+
11
+ ```bash
12
+ # Setup (first time)
13
+ uv sync
14
+
15
+ # Run MCP server
16
+ uv run intervals-icu-mcp
17
+
18
+ # Run CLI
19
+ uv run intervals-icu-tools --help
20
+
21
+ # Run all tests
22
+ uv run pytest
23
+
24
+ # Run a single test
25
+ uv run pytest tests/test_service.py -k test_name
26
+
27
+ # Run integration tests (real API, requires API key set)
28
+ uv run pytest -m integration
29
+
30
+ # Build for publishing
31
+ uv build
32
+ ```
33
+
34
+ ## Build sequence
35
+
36
+ Follow this exact order — each phase depends on the previous one importing cleanly:
37
+
38
+ 1. **Phase 1 (Skeleton):** Create dirs, `.python-version`, `pyproject.toml`, run `uv sync`
39
+ 2. **Phase 2 (Core):** `errors.py` → `models.py` → `config.py` → `client.py` → `exporters.py` → `service.py`
40
+ - Verify: `uv run python -c "from myapi_tools.service import MyAPIService; print('ok')"`
41
+ 3. **Phase 3 (MCP):** `mcp_server.py`
42
+ - Verify: `uv run intervals-icu-tools-mcp &; kill %1`
43
+ 4. **Phase 4 (CLI):** `cli/__init__.py` → `cli/main.py` → `cli/commands.py`
44
+ - Verify: `uv run intervals-icu-tools --help`
45
+ 5. **Phase 5 (Docs & Tests):** `cli_tools.md`, `tests/`
46
+ 6. **Phase 6 (Publish):** `uv build && uv publish`
47
+
48
+ ## Architecture
49
+
50
+ Three interfaces, one service layer:
51
+
52
+ ```
53
+ MCP Tools (FastMCP) ──┐
54
+ CLI Commands (Click) ──┼──▶ service.py ──▶ client.py ──▶ Intervals.ICU REST API
55
+ Library API (import) ──┘
56
+ ```
57
+
58
+ - **`service.py`** — all business logic lives here. MCP tools and CLI commands are thin wrappers.
59
+ - **`client.py`** — low-level `httpx` async client (auth, retry, config).
60
+ - **`models.py`** — Pydantic models for API request/response shapes.
61
+ - **`config.py`** — priority chain: env vars > `~/.config/.../config.toml` > defaults.
62
+ - **`exporters.py`** — output formatting (JSON, CSV, markdown).
63
+ - **`mcp_server.py`** — FastMCP wrapper; registers tools calling service methods.
64
+ - **`cli/`** — Click group + commands; thin wrappers calling service methods.
65
+ - **`__init__.py`** — exports only the core layer (`MyAPIService`, `ApiConfig`, models, errors). Never import `mcp_server` or `cli` here.
66
+
67
+ ## Key design rules
68
+
69
+ 1. ALL business logic goes in `service.py`. MCP tools and CLI are thin wrappers.
70
+ 2. ALL features are exposed via CLI, MCP, AND as importable Python functions.
71
+ 3. MCP tools return small structured data (<100 items, <50KB) — fits in LLM context.
72
+ 4. CLI commands handle large/binary data (streams to disk, no size cap).
73
+ 5. MCP tool docstrings must include: what it does, when to use it, return shape, and the exact `uvx` CLI command for handoff to bulk/binary operations.
74
+ 6. CLI prints structured, parseable output — no progress bars, no ANSI color codes.
75
+ 7. MCP tools return error dicts on failure (never raise). CLI prints to stderr and exits with structured codes: 0=success, 1=auth/general, 2=rate limit, 3=download, 4=not found.
76
+
77
+ ## Deciding what gets an MCP tool vs CLI command
78
+
79
+ | Endpoint type | MCP tool | CLI command |
80
+ |---|---|---|
81
+ | Small structured response (<100 items) | Yes (full data) | Yes |
82
+ | Large dataset (paginated, bulk) | Yes (capped preview, limit=20) | Yes (full download) |
83
+ | Binary/large blob (PDF, ZIP) | Yes (metadata only) | Yes (streaming download) |
84
+ | Async server-side job | Yes (start job, return job ID) | Yes (start + poll + download) |
85
+ | Mutating (POST/PUT/DELETE) | Yes | Yes |
86
+ | Auth/config/health | No — handle in `client.py` | No |
87
+
88
+ ## Testing
89
+
90
+ - Mock at the HTTP boundary with `respx`, not at the service layer — tests cover the full stack.
91
+ - Test error paths explicitly (LLM relies on structured error output).
92
+ - Test that all MCP tools have docstrings (missing docstring = silent LLM discoverability failure).
93
+ - Test CLI exit codes (they are part of the contract with calling LLM agents).
94
+ - No real API calls in CI — use `respx` mocks. Real API tests use `@pytest.mark.integration`.
95
+
96
+ ## Technology choices
97
+
98
+ | Package | Role | Notes |
99
+ |---|---|---|
100
+ | `fastmcp>=3.0,<4` | MCP server | Import as `from fastmcp import FastMCP`, not the SDK-bundled version |
101
+ | `httpx>=0.27` | HTTP client | Async-native; use `respx` to mock in tests |
102
+ | `pydantic>=2.0` | Data models | Already a transitive dep of FastMCP |
103
+ | `click>=8.0` | CLI | Lighter than `typer` (no `rich`/`shellingham` pull) |
104
+ | `hatchling` | Build backend | Used via `uv build` |
105
+ | `pytest-asyncio` | Async tests | Use `@pytest.mark.asyncio` on async test functions |
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Josua Sassen and Helge Esch
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,281 @@
1
+ Metadata-Version: 2.4
2
+ Name: intervals-kit
3
+ Version: 0.1.1
4
+ Summary: MCP server, CLI, and Python library for the Intervals.ICU fitness tracking API
5
+ Project-URL: Homepage, https://github.com/jrsassen/intervals-kit
6
+ Project-URL: Repository, https://github.com/jrsassen/intervals-kit
7
+ Project-URL: Issues, https://github.com/jrsassen/intervals-kit/issues
8
+ Author: Josua Sassen, Helge Esch
9
+ License: MIT License
10
+
11
+ Copyright (c) 2025 Josua Sassen and Helge Esch
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: api,cycling,fitness,intervals,intervals.icu,mcp,triathlon
32
+ Classifier: Development Status :: 4 - Beta
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3
35
+ Classifier: Programming Language :: Python :: 3.10
36
+ Classifier: Programming Language :: Python :: 3.11
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Programming Language :: Python :: 3.13
39
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
40
+ Requires-Python: >=3.10
41
+ Requires-Dist: click>=8.0
42
+ Requires-Dist: fastmcp<4,>=3.0
43
+ Requires-Dist: httpx>=0.27
44
+ Requires-Dist: pydantic>=2.0
45
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # intervals-kit
49
+
50
+ MCP server, CLI, and Python library for the [Intervals.ICU](https://intervals.icu) fitness tracking API. Exposes activity data (power curves, HR curves, streams, intervals, segments, file downloads) to AI assistants via the Model Context Protocol, and as a command-line tool for scripting and bulk data access.
51
+
52
+ ## Requirements
53
+
54
+ - Python 3.10+
55
+ - [uv](https://docs.astral.sh/uv/getting-started/installation/) — recommended for running the package
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install intervals-kit
61
+ ```
62
+
63
+ Or with uv:
64
+
65
+ ```bash
66
+ uv pip install intervals-kit
67
+ ```
68
+
69
+ ## Configuration
70
+
71
+ The package reads credentials from environment variables or a config file.
72
+
73
+ **Environment variables (recommended):**
74
+
75
+ ```bash
76
+ export INTERVALS_API_KEY=your_api_key # From intervals.icu → Settings → API Key
77
+ export INTERVALS_ATHLETE_ID=iXXXXXX # Your athlete ID (visible in the URL when logged in)
78
+ ```
79
+
80
+ **Config file** (`~/.config/intervals-kit/config.toml`):
81
+
82
+ ```toml
83
+ api_key = "your_api_key"
84
+ athlete_id = "iXXXXXX"
85
+ base_url = "https://intervals.icu" # optional
86
+ ```
87
+
88
+ Environment variables take precedence over the config file.
89
+
90
+ ## Usage as MCP Server (Claude Code / Claude Desktop)
91
+
92
+ The easiest way — no installation needed:
93
+
94
+ ```json
95
+ {
96
+ "mcpServers": {
97
+ "intervals-icu": {
98
+ "command": "uvx",
99
+ "args": ["intervals-kit"],
100
+ "env": {
101
+ "INTERVALS_API_KEY": "your_api_key",
102
+ "INTERVALS_ATHLETE_ID": "iXXXXXX"
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ Once configured, Claude can directly query your training data:
110
+
111
+ > "How many rides did I do last month?"
112
+ > "What was my best 5-minute power in March?"
113
+ > "Download my activities as a CSV for Q1 2025."
114
+
115
+ ### Available MCP Tools
116
+
117
+ | Tool | Description |
118
+ |------|-------------|
119
+ | `list_activities` | List activities for a date range |
120
+ | `get_activity` | Get full details for a single activity |
121
+ | `search_activities` | Search by name or tag |
122
+ | `update_activity` | Update name, description, type, or RPE |
123
+ | `get_activity_intervals` | Get lap/split data |
124
+ | `get_activity_streams` | Get second-by-second time-series (power, HR, cadence, …) |
125
+ | `get_power_curve` | Mean-maximal power curve for an activity |
126
+ | `get_hr_curve` | Heart rate curve for an activity |
127
+ | `get_pace_curve` | Pace curve for running/swimming |
128
+ | `get_best_efforts` | Best efforts detected in an activity |
129
+ | `get_activity_segments` | Matched Strava/Intervals segments |
130
+ | `get_activity_map` | GPS track (lat/lon) |
131
+ | `get_athlete_power_curves` | Best power curves across a date range |
132
+ | `get_athlete_hr_curves` | Best HR curves across a date range |
133
+
134
+ For large data (streams >1 hour, CSV exports, binary files) the MCP tools return metadata and tell Claude to use the CLI for the actual download.
135
+
136
+ ## Usage as CLI
137
+
138
+ ```bash
139
+ uvx --from intervals-kit intervals-icu-tools --help
140
+ ```
141
+
142
+ ### Activity commands
143
+
144
+ ```bash
145
+ # List activities for a date range (JSON to stdout)
146
+ uvx --from intervals-kit intervals-icu-tools list-activities --oldest 2025-01-01 --newest 2025-03-31
147
+
148
+ # Save to a file instead of stdout
149
+ uvx --from intervals-kit intervals-icu-tools -o ./data list-activities --oldest 2025-01-01 --newest 2025-03-31
150
+
151
+ # Get a single activity
152
+ uvx --from intervals-kit intervals-icu-tools get-activity i136292802
153
+
154
+ # Search by name or tag (# prefix for exact tag match)
155
+ uvx --from intervals-kit intervals-icu-tools search-activities "long ride"
156
+ uvx --from intervals-kit intervals-icu-tools search-activities "#race" --full
157
+
158
+ # Update fields
159
+ uvx --from intervals-kit intervals-icu-tools update-activity i136292802 --perceived-exertion 7 --description "Felt strong"
160
+ ```
161
+
162
+ ### Sub-resource commands
163
+
164
+ ```bash
165
+ # Lap/interval data
166
+ uvx --from intervals-kit intervals-icu-tools get-intervals i136292802
167
+
168
+ # Time-series streams (specify types to limit size)
169
+ uvx --from intervals-kit intervals-icu-tools -o ./data get-streams i136292802 --types watts,heartrate,cadence
170
+
171
+ # Power / HR / pace curves
172
+ uvx --from intervals-kit intervals-icu-tools get-power-curve i136292802
173
+ uvx --from intervals-kit intervals-icu-tools get-hr-curve i136292802
174
+ uvx --from intervals-kit intervals-icu-tools get-pace-curve i136292816 # running activity
175
+
176
+ # Best efforts, segments, GPS map
177
+ uvx --from intervals-kit intervals-icu-tools get-best-efforts i136292802
178
+ uvx --from intervals-kit intervals-icu-tools get-segments i136292802
179
+ uvx --from intervals-kit intervals-icu-tools -o ./data get-activity-map i136292802
180
+ ```
181
+
182
+ ### File download commands
183
+
184
+ ```bash
185
+ # Download original upload / FIT / GPX file
186
+ uvx --from intervals-kit intervals-icu-tools -o ./data download-activity-file i136292802 --type fit
187
+ uvx --from intervals-kit intervals-icu-tools -o ./data download-activity-file i136292802 --type gpx
188
+
189
+ # Bulk CSV export for a date range
190
+ uvx --from intervals-kit intervals-icu-tools -o ./data download-activities-csv --oldest 2025-01-01 --newest 2025-03-31
191
+ ```
192
+
193
+ ### Aggregate curves
194
+
195
+ ```bash
196
+ # Best power/HR/pace curves across all activities in a date range
197
+ uvx --from intervals-kit intervals-icu-tools get-athlete-power-curves --oldest 2025-01-01 --newest 2025-03-31
198
+ uvx --from intervals-kit intervals-icu-tools get-athlete-hr-curves --oldest 2025-01-01 --newest 2025-03-31
199
+ uvx --from intervals-kit intervals-icu-tools get-athlete-pace-curves --oldest 2025-01-01 --newest 2025-03-31
200
+ ```
201
+
202
+ ### Global options
203
+
204
+ | Option | Default | Description |
205
+ |--------|---------|-------------|
206
+ | `-o / --output-dir` | `.` (stdout) | Directory to save output files |
207
+ | `-f / --format` | `json` | Output format: `json` or `csv` |
208
+
209
+ ### Exit codes
210
+
211
+ | Code | Meaning |
212
+ |------|---------|
213
+ | 0 | Success |
214
+ | 1 | Auth error or general API error |
215
+ | 2 | Rate limit exceeded (retryable) |
216
+ | 3 | Download / network error (retryable) |
217
+ | 4 | Resource not found |
218
+
219
+ ## Usage as Python Library
220
+
221
+ ```python
222
+ import asyncio
223
+ from intervals_kit import IntervalsService, load_config
224
+
225
+ async def main():
226
+ svc = IntervalsService(load_config())
227
+
228
+ # List March 2025 activities
229
+ activities = await svc.list_activities("2025-03-01", "2025-03-31")
230
+ rides = [a for a in activities if a.type == "Ride"]
231
+ print(f"Rides: {len(rides)}, Runs: {len(activities) - len(rides)}")
232
+
233
+ # Get power curve for first ride
234
+ if rides:
235
+ curve = await svc.get_power_curve(rides[0].id)
236
+ print(f"Power curve keys: {list(curve.keys())}")
237
+
238
+ asyncio.run(main())
239
+ ```
240
+
241
+ ## Development
242
+
243
+ ```bash
244
+ # Clone and install with dev dependencies
245
+ git clone https://github.com/jrsassen/intervals-kit
246
+ cd intervals-kit
247
+ uv sync
248
+
249
+ # Run tests
250
+ uv run pytest
251
+
252
+ # Run a single test
253
+ uv run pytest tests/test_service.py -k test_name
254
+
255
+ # Run integration tests against the real API (requires credentials)
256
+ uv run pytest -m integration
257
+ ```
258
+
259
+ ## Architecture
260
+
261
+ Three interfaces share one service layer — see `SPECIFICATIONS.md` for the full architecture and `src/intervals_kit/cli_tools.md` for the complete CLI reference intended for LLM agents.
262
+
263
+ ```
264
+ MCP Tools (FastMCP) ──┐
265
+ CLI Commands (Click) ──┼──▶ service.py ──▶ client.py ──▶ Intervals.ICU API
266
+ Python import ──┘
267
+ ```
268
+
269
+ Source layout:
270
+
271
+ ```
272
+ src/intervals_kit/
273
+ ├── errors.py # Exception types
274
+ ├── models.py # Pydantic models
275
+ ├── config.py # Configuration loading
276
+ ├── client.py # HTTP client (auth, error mapping, streaming)
277
+ ├── exporters.py # JSON serialization
278
+ ├── service.py # All business logic
279
+ ├── mcp_server.py # FastMCP tool definitions
280
+ └── cli/ # Click command definitions
281
+ ```