catence 0.1.0 → 0.2.0-beta.10
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.
- package/README.md +239 -269
- package/config.example.json +32 -20
- package/dist/contracts/index.d.ts +1 -0
- package/dist/contracts/index.js +1 -0
- package/dist/contracts/progress.d.ts +23 -0
- package/dist/contracts/progress.js +48 -0
- package/dist/contracts/release.d.ts +1 -1
- package/dist/contracts/release.js +1 -1
- package/dist/core/logging.d.ts +16 -0
- package/dist/core/logging.js +68 -0
- package/dist/core/query/repository.d.ts +2 -0
- package/dist/core/query/repository.js +42 -0
- package/dist/core/runtime/catalog.d.ts +41 -0
- package/dist/core/runtime/catalog.js +121 -0
- package/dist/core/runtime/configuration.d.ts +1 -1
- package/dist/core/runtime/configuration.js +3 -3
- package/dist/core/runtime/secrets.d.ts +24 -0
- package/dist/core/runtime/secrets.js +68 -0
- package/dist/core/runtime/self-update.d.ts +44 -0
- package/dist/core/runtime/self-update.js +138 -0
- package/dist/elt/application/management.d.ts +2 -0
- package/dist/elt/application/management.js +197 -18
- package/dist/elt/ingestion/importer.d.ts +3 -2
- package/dist/elt/ingestion/importer.js +4 -3
- package/dist/elt/ingestion/providers/intervals.d.ts +3 -1
- package/dist/elt/ingestion/providers/intervals.js +4 -1
- package/dist/elt/ingestion/providers/strava/service.js +9 -2
- package/dist/elt/storage/database.d.ts +7 -2
- package/dist/elt/storage/database.js +81 -4
- package/dist/elt/storage/migrations.js +25 -1
- package/dist/interfaces/cli/commands.js +201 -46
- package/dist/interfaces/http/cli-options.d.ts +2 -2
- package/dist/interfaces/http/cli-options.js +10 -10
- package/dist/interfaces/http/server.d.ts +4 -1
- package/dist/interfaces/http/server.js +31 -4
- package/dist/interfaces/mcp/cli-options.d.ts +2 -2
- package/dist/interfaces/mcp/cli-options.js +14 -14
- package/dist/interfaces/mcp/main.js +12 -5
- package/dist/interfaces/mcp/server.d.ts +2 -2
- package/dist/interfaces/mcp/server.js +156 -98
- package/dist/runtime/index.d.ts +8 -1
- package/dist/runtime/index.js +4 -1
- package/docs/deployment.md +338 -0
- package/docs/docker-deployment.md +232 -0
- package/docs/llm-providers.md +467 -0
- package/package.json +6 -2
- package/python/catence/providers/garmin/cli.py +30 -9
- package/python/catence/providers/garmin/progress.py +85 -0
- package/python/catence/providers/garmin/worker.py +132 -5
- package/scripts/discover-opencode-go.mjs +210 -0
- package/scripts/discover-opencode-zen.mjs +218 -0
package/README.md
CHANGED
|
@@ -1,389 +1,359 @@
|
|
|
1
1
|
# Catence
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Catence is a local-first fitness MCP server for Garmin, Intervals.icu, and targeted Strava enrichment. It gives an agent evidence-backed access to normalized training, wellness, activity, stream, segment, and gear data without uploading the underlying dataset to Catence.
|
|
4
4
|
|
|
5
|
-

|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
## What
|
|
7
|
+
## What is included
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Garmin, Intervals.icu, and Strava ingestion with source-aware normalization.
|
|
10
|
+
- Read-only MCP tools, plus explicitly named, lock-guarded Strava hydration tools.
|
|
11
|
+
- One shared Catence agent can serve several isolated athlete stores. Every personal-data MCP call names an `athleteId`; Catence never silently combines athletes.
|
|
12
|
+
- A password-protected Chainlit Console with an authenticated dashboard.
|
|
13
|
+
- A generated demo catalog for safe evaluation in Glama, desktop MCP clients, or local development.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
Catence helps explore recovery, training load, trends, activity detail, swimming/cycling/running progress, segments, gear, and data quality. It reports the evidence and coverage it can see; it does not diagnose or prescribe training.
|
|
12
16
|
|
|
13
|
-
|
|
17
|
+
## Requirements
|
|
14
18
|
|
|
15
|
-
-
|
|
16
|
-
- [
|
|
17
|
-
-
|
|
18
|
-
- [x] Local Streamable HTTP server
|
|
19
|
-
- [x] Release-ready npm, [APM](https://github.com/microsoft/apm), and MCPB distribution artifacts
|
|
20
|
-
- [x] Generated demo store for safe MCP evaluation
|
|
21
|
-
- [x] Bundled local Chainlit Console with persisted chat history
|
|
22
|
-
- [ ] Data writing (training sessions, plans)
|
|
23
|
-
- [ ] TrainingPeaks support?
|
|
24
|
-
- [ ] Limited multi-user support (see Caveats)
|
|
19
|
+
- Node.js 22+
|
|
20
|
+
- Python 3.12+ and [uv](https://docs.astral.sh/uv/) for Garmin and Strava provider workers
|
|
21
|
+
- Provider credentials for the athletes you choose to sync
|
|
25
22
|
|
|
26
|
-
##
|
|
23
|
+
## Quick start
|
|
27
24
|
|
|
28
|
-
Catence
|
|
25
|
+
Install Catence and create the first athlete. The default catalog home is `~/.catence`; set `CATENCE_HOME` or pass `--home` to use a different location. For the full local or Docker deployment walkthrough, see [deployment notes](docs/deployment.md).
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
- **Segments and gear:** “Show my history on this climb,” or “which shoes and bikes have carried the most recent training volume?”
|
|
35
|
-
- **Data-quality review:** “Which recent activities lack streams, power, health context, or a matching provider record?”
|
|
36
|
-
|
|
37
|
-
## Sources
|
|
38
|
-
|
|
39
|
-
The current available sources respond to my own used platforms. I used some wrappers:
|
|
40
|
-
|
|
41
|
-
- [cyberjunky/python-garminconnect](https://github.com/cyberjunky/python-garminconnect)
|
|
42
|
-
- [paladini/node-intervals-icu](https://github.com/paladini/node-intervals-icu)
|
|
43
|
-
- [stravalib/stravalib](https://github.com/stravalib/stravalib)
|
|
44
|
-
|
|
45
|
-
Strava is only used for segments and gear.
|
|
46
|
-
|
|
47
|
-
### Caveats
|
|
48
|
-
|
|
49
|
-
There are two caveats to the data fetching, both coming from the fact that this was created to be used by a single person. Accessing the full Garmin API requires applying via a company—which I don't have—and getting Strava data for multiple users is another headache I don't want to get into.
|
|
50
|
-
|
|
51
|
-
- I don't use the official Garmin Connect API. The library uses email/pwd.
|
|
52
|
-
- Connecting Strava requires the user to be a Strava Premium subscriber and create an [API application](https://stravalib.readthedocs.io/en/latest/get-started/authenticate-with-strava.html). The rotating token is stored locally in
|
|
53
|
-
`<data-dir>/secrets/strava.json`.
|
|
54
|
-
|
|
55
|
-
What I mean by multi-user is being able to manage and serve various users' data on the same server.
|
|
56
|
-
|
|
57
|
-
## Data
|
|
58
|
-
|
|
59
|
-
Priority is assigned per data type. Missing values mean the provider did not supply that metric.
|
|
60
|
-
|
|
61
|
-
| Data group | Priority and provenance | Data available per activity or date | Aggregated data |
|
|
62
|
-
| --- | --- | --- | --- |
|
|
63
|
-
| Generic activity data | <ol><li>Garmin (canonical activity data)</li><li>Intervals.icu (supplemental analysis)</li></ol> | Date/time, timezone, sport, name, distance, elapsed/moving time, elevation, calories, average/max HR, source identity, and quality flags | Activity count, distance, duration, elevation, and calories by date range |
|
|
64
|
-
| Running — indoor and outdoor | <ol><li>Garmin (activity facts and streams)</li><li>Intervals.icu (analysis values)</li></ol> | Pace/speed, cadence, HR, power when available, GPS, elevation, grade, splits, intervals, VO₂max, lactate-threshold metrics, and race predictions | Running volume, pace/load trends, and training-metric history |
|
|
65
|
-
| Cycling — indoor and outdoor | <ol><li>Garmin (activity facts, FIT streams, and settings)</li><li>Intervals.icu (load, RPE, feel, and weighted power)</li></ol> | Average/weighted power, cadence, speed, HR, GPS, elevation, grade, temperature, cycling dynamics, splits, and FTP observations | Power-duration bests, power coverage, cycling volume/load, and FTP trends |
|
|
66
|
-
| Swimming — pool and open water | <ol><li>Garmin (explicit lengths and summaries)</li><li>Intervals.icu (auto-detected sets)</li></ol> | Lengths/laps, pool length, distance, duration, stroke count/rate, SWOLF, average/max HR, sets, and GPS when supplied | Swim volume, session comparisons, and data-completeness summaries |
|
|
67
|
-
| Structured intervals and sets | <ol><li>Intervals.icu (Intervals-based intervals)</li><li>Garmin (Garmin splits and sets)</li></ol> | Interval labels, repetitions, duration, moving time, distance, pace, power, HR, intensity, and swim-set details | Interval volume, intensity, and provider-specific comparisons |
|
|
68
|
-
| Daily health and wellness | <ol><li>Garmin (primary health data)</li></ol> | HR, resting HR, HRV, sleep, sleep score, stress, Body Battery, readiness, SpO₂, weight, steps, hydration, and nutrition | Daily, weekly, and monthly health and nutrition trends |
|
|
69
|
-
| Strava segments and gear | <ol><li>Strava (source of record)</li></ol> | Segment names, grades, climb category, elevation, effort time/distance, power, HR, cadence, PR/KOM ranks, bikes, shoes, and historic efforts | Segment, climb, effort-history, and gear comparisons |
|
|
70
|
-
| Derived and cross-source analytics | <ol><li>Garmin (canonical inputs)</li><li>Intervals.icu (interval inputs)</li><li>Strava (segment and gear inputs)</li><li>Catence (derived results)</li></ol> | Reconciled activity values and source-quality flags | Rolling statistics, baselines, correlations, seasonal comparisons, trends, descriptive model fits, and progress reports |
|
|
71
|
-
|
|
72
|
-
## Set up
|
|
73
|
-
|
|
74
|
-
Catence exposes the same local MCP server over stdio and optional Streamable HTTP. A tagged release publishes the `catence` npm package, platform-specific MCPB demo bundles, and an APM package. Until that first public release is available, the source-checkout commands below remain fully supported.
|
|
75
|
-
|
|
76
|
-
### Safe one-command demo
|
|
27
|
+
```sh
|
|
28
|
+
npm install --global catence@0.2.0
|
|
29
|
+
catence-data setup --athlete alex --label "Alex"
|
|
30
|
+
```
|
|
77
31
|
|
|
78
|
-
|
|
32
|
+
Store each provider value through stdin so it does not enter shell history. Values are written to the selected athlete’s owner-only local secret file.
|
|
79
33
|
|
|
80
34
|
```sh
|
|
81
|
-
|
|
35
|
+
printf %s 'alex@example.com' | catence-data --athlete alex secret set --provider garmin --field email --value-stdin
|
|
36
|
+
printf %s 'your-garmin-password' | catence-data --athlete alex secret set --provider garmin --field password --value-stdin
|
|
37
|
+
printf %s 'intervals-api-key' | catence-data --athlete alex secret set --provider intervals --field apiKey --value-stdin
|
|
38
|
+
printf %s '12345' | catence-data --athlete alex secret set --provider intervals --field athleteId --value-stdin
|
|
82
39
|
```
|
|
83
40
|
|
|
84
|
-
|
|
85
|
-
contains lagged training-load/recovery effects, a multi-signal anomaly period, and some missing sleep-score dates. Every MCP result includes a generated-data disclaimer, so it cannot be mistaken for a personal health record.
|
|
86
|
-
|
|
87
|
-
From a source checkout, the equivalent command is:
|
|
41
|
+
Sync and create retrieval context:
|
|
88
42
|
|
|
89
43
|
```sh
|
|
90
|
-
|
|
91
|
-
|
|
44
|
+
catence-data --athlete alex sync --provider all
|
|
45
|
+
catence-data --athlete alex build-retrieval-index
|
|
92
46
|
```
|
|
93
47
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
After publication, install the package once, then create and synchronize a store that you own:
|
|
48
|
+
Start the stdio MCP server:
|
|
97
49
|
|
|
98
50
|
```sh
|
|
99
|
-
|
|
100
|
-
catence-data --data-dir /absolute/path/to/catence-data init
|
|
101
|
-
catence-data --data-dir /absolute/path/to/catence-data sync --provider all
|
|
102
|
-
catence-data --data-dir /absolute/path/to/catence-data build-retrieval-index
|
|
103
|
-
catence --data-dir /absolute/path/to/catence-data
|
|
51
|
+
catence
|
|
104
52
|
```
|
|
105
53
|
|
|
106
|
-
|
|
54
|
+
An agent first calls `list_athletes`, then includes `athleteId` with every data tool. This is intentional: a shared agent may access the stores you configured, but no tool implicitly aggregates or crosses between athletes.
|
|
107
55
|
|
|
108
|
-
|
|
109
|
-
- Python 3.12 or later and [uv](https://docs.astral.sh/uv/) for Garmin and Strava provider syncs
|
|
56
|
+
### Add another athlete
|
|
110
57
|
|
|
111
|
-
|
|
58
|
+
```sh
|
|
59
|
+
catence-data athlete add --id sam --label "Sam"
|
|
60
|
+
printf %s 'sam@example.com' | catence-data --athlete sam secret set --provider garmin --field email --value-stdin
|
|
61
|
+
printf %s 'sam-password' | catence-data --athlete sam secret set --provider garmin --field password --value-stdin
|
|
62
|
+
catence-data --athlete sam sync --provider garmin
|
|
63
|
+
catence-data athlete list
|
|
64
|
+
```
|
|
112
65
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
66
|
+
The catalog layout is deliberately simple:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
~/.catence/
|
|
70
|
+
catalog.json
|
|
71
|
+
config.json # shared Console configuration; no secrets
|
|
72
|
+
console/chat-history.sqlite3
|
|
73
|
+
athletes/
|
|
74
|
+
alex/ # isolated DuckDB, raw data, and tokens
|
|
75
|
+
secrets/providers.json # mode 0600
|
|
76
|
+
sam/
|
|
77
|
+
...
|
|
78
|
+
```
|
|
116
79
|
|
|
117
|
-
|
|
80
|
+
Garmin, Intervals, and Strava client credentials are isolated per athlete. Strava OAuth tokens remain in that athlete’s own store. The old 0.1 single-store directory cannot be migrated safely: create a fresh home and re-sync each athlete.
|
|
118
81
|
|
|
119
|
-
|
|
82
|
+
### Common operations
|
|
120
83
|
|
|
121
84
|
```sh
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
85
|
+
# Incremental or explicit data work for one athlete
|
|
86
|
+
catence-data --athlete alex status
|
|
87
|
+
catence-data --athlete alex sync --provider intervals
|
|
88
|
+
catence-data --athlete alex sync --provider garmin --from 2025-07-29
|
|
89
|
+
catence-data --athlete alex backfill --provider garmin --from 2020-01-01 --refresh
|
|
90
|
+
catence-data --athlete alex retry --run <run-id>
|
|
91
|
+
catence-data --athlete alex progress --watch # follow an active sync run live
|
|
92
|
+
|
|
93
|
+
# Strava OAuth uses clientId and clientSecret previously stored for this athlete
|
|
94
|
+
catence-data --athlete alex auth strava --callback
|
|
95
|
+
catence-data --athlete alex disconnect strava
|
|
96
|
+
|
|
97
|
+
# Update the runtime and Console together on the tracked release channel
|
|
98
|
+
catence-data update --check
|
|
99
|
+
catence-data update
|
|
100
|
+
|
|
101
|
+
# Choose a non-default catalog location
|
|
102
|
+
catence-data --home /srv/catence setup --athlete alex --label "Alex"
|
|
103
|
+
catence --home /srv/catence
|
|
125
104
|
```
|
|
126
105
|
|
|
127
|
-
|
|
106
|
+
`catence-data update` follows the channel of the installed runtime: a beta
|
|
107
|
+
install tracks the npm `beta` tag and the matching PyPI prereleases, a stable
|
|
108
|
+
install tracks `latest`. Pass `--channel stable` to move off a beta. The command
|
|
109
|
+
replaces the global `catence` package and installs or upgrades
|
|
110
|
+
`catence-console` with `uv tool` when `uv` is available; `--check` only reports.
|
|
128
111
|
|
|
129
|
-
|
|
112
|
+
Register `http://127.0.0.1:8765/strava/callback` with the Strava application for the callback flow. The manual `auth strava --code <authorization-code>` flow remains available for headless environments.
|
|
130
113
|
|
|
131
|
-
|
|
114
|
+
## Background sync and live progress
|
|
132
115
|
|
|
133
|
-
|
|
134
|
-
# Inspect local coverage and sync individual providers
|
|
135
|
-
npm run catence-data -- status
|
|
136
|
-
npm run catence-data -- sync --provider intervals
|
|
137
|
-
npm run catence-data -- sync --provider garmin --from 2025-07-29
|
|
138
|
-
npm run catence-data -- sync --provider strava
|
|
139
|
-
npm run catence-data -- sync --provider all
|
|
140
|
-
|
|
141
|
-
# Recover or import a specific historical range
|
|
142
|
-
npm run catence-data -- retry --run <run-id>
|
|
143
|
-
npm run catence-data -- backfill --from 2020-01-01
|
|
144
|
-
npm run catence-data -- backfill --provider garmin --from 2026-07-01 --refresh
|
|
145
|
-
|
|
146
|
-
# Refresh derived search context and launch the local MCP server
|
|
147
|
-
npm run catence-data -- build-retrieval-index
|
|
148
|
-
npm run mcp
|
|
149
|
-
npm run mcp -- --data-dir /absolute/path/to/catence-data
|
|
150
|
-
|
|
151
|
-
# Optional local Streamable HTTP MCP and dashboard API
|
|
152
|
-
npm run mcp -- serve --data-dir /absolute/path/to/catence-data --allow-origin http://127.0.0.1:8000
|
|
153
|
-
npm run mcp -- serve --allow-origin http://127.0.0.1:8000
|
|
154
|
-
```
|
|
116
|
+
Long Garmin backfills fetch hundreds of days of data and can take 20-30+ minutes. Sync runs stream worker output to the console in real time, tolerate Ctrl+C and SSH/terminal deaths cleanly, and expose a live progress heartbeat you can follow from the CLI, the MCP server, or a Docker helper.
|
|
155
117
|
|
|
156
|
-
|
|
118
|
+
### What happens during a run
|
|
157
119
|
|
|
158
|
-
|
|
120
|
+
When a sync starts, Catence creates a row in `sync_runs` with status `running` and begins persisting heartbeats to the `sync_run_progress` table (schema migration 14). The Garmin staging worker emits one compact JSON progress record every few seconds on stdout; the runtime parses it, normalizes it, and upserts it. Even if the worker cannot emit — for example while stuck in login — the runtime persists a 30-second fallback heartbeat so a run is always observable and never falsely looks stale.
|
|
159
121
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
```sh
|
|
163
|
-
npm run catence-data -- auth strava --callback
|
|
164
|
-
```
|
|
122
|
+
Each progress record follows the same contract:
|
|
165
123
|
|
|
166
|
-
|
|
124
|
+
- `runId`, `provider` — which run the record belongs to
|
|
125
|
+
- `stage` — `starting`, `login`, `singletons`, `daily`, `range`, `ftp_history`, `max_metrics`, `hrv_history`, `scores`, `activities`, `collections` while extracting; `completed` on success; `failed`, `interrupted`, or `timed_out` as terminal states written by the runtime
|
|
126
|
+
- `currentStep` — the endpoint, date, or activity being fetched right now
|
|
127
|
+
- `completedUnits` / `totalUnits` — for example days fetched out of the total days in the window
|
|
128
|
+
- `percentComplete` — 0 to 100
|
|
129
|
+
- `elapsedSeconds` / `estimatedRemainingSeconds` — the ETA is computed once the run has a known total and has completed at least one unit
|
|
130
|
+
- `heartbeatAt` — UTC ISO timestamp of the record
|
|
167
131
|
|
|
168
|
-
|
|
132
|
+
### Interruption and stale-run recovery
|
|
169
133
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```
|
|
134
|
+
- SIGINT, SIGTERM, and SIGHUP (Ctrl+C, terminal close, `kill`) are trapped on both the Node runtime and the Python worker. The child stops between days and the run is marked `interrupted` in `sync_runs` — never a zombie `running` row — and the process exits with 130/143/129.
|
|
135
|
+
- Before every sync or retry, runs still stuck in `running` for more than 15 minutes are marked `timed_out` (with `completed_at` set), so a dead SSH session cannot block later syncs.
|
|
136
|
+
- Resume a failed or interrupted run without re-reading history with `retry --run <run-id>`.
|
|
174
137
|
|
|
175
|
-
|
|
138
|
+
### Watching progress
|
|
176
139
|
|
|
177
140
|
```sh
|
|
178
|
-
|
|
141
|
+
catence-data --athlete alex progress # one-shot JSON snapshot
|
|
142
|
+
catence-data --athlete alex progress --watch # live view; exits when no run is active
|
|
179
143
|
```
|
|
180
144
|
|
|
181
|
-
|
|
145
|
+
`--watch` renders a compact progress table on a TTY and exits when the last active run finishes; piped output stays JSON.
|
|
182
146
|
|
|
183
|
-
The
|
|
147
|
+
The read-only MCP tool `catence_sync_progress` returns the same snapshot — active runs with their latest heartbeat plus the most recent runs — and takes `athleteId` like every other personal-data tool.
|
|
184
148
|
|
|
185
|
-
###
|
|
149
|
+
### Docker deployments
|
|
186
150
|
|
|
187
|
-
|
|
151
|
+
`scripts/deploy-console.sh` generates a `sync.sh` helper that gained a `progress` command and a `--detach` flag:
|
|
188
152
|
|
|
189
153
|
```sh
|
|
190
|
-
|
|
154
|
+
cd catence-deploy
|
|
155
|
+
./sync.sh --athlete alex sync --detach # start the run in the background and return
|
|
156
|
+
./sync.sh --athlete alex progress --watch # follow it live
|
|
157
|
+
./sync.sh --athlete alex progress # one-shot snapshot
|
|
191
158
|
```
|
|
192
159
|
|
|
193
|
-
|
|
160
|
+
`--detach` works with `backfill` too. A detached run keeps streaming into the container logs (`docker compose -f catence-deploy/docker-compose.yml logs -f console`) and keeps writing heartbeats, so it survives the shell that started it and is recoverable by the next sync.
|
|
194
161
|
|
|
195
|
-
|
|
162
|
+
## Safe demo and Glama
|
|
196
163
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
Launch the Console once with your selected provider’s environment variables in the terminal. If `<data-dir>/config.json` has no Console section, the browser walks through provider and model selection (Azure, OpenAI, or Anthropic) and writes only non-secret configuration. For example:
|
|
164
|
+
Run a clearly marked generated dataset without provider accounts:
|
|
200
165
|
|
|
201
166
|
```sh
|
|
202
|
-
|
|
203
|
-
export AZURE_API_KEY='…'
|
|
204
|
-
export AZURE_API_BASE='https://your-resource.openai.azure.com'
|
|
205
|
-
export AZURE_API_VERSION='preview'
|
|
206
|
-
|
|
207
|
-
uvx catence-console@0.1.0 serve --data-dir /absolute/path/to/catence-data
|
|
167
|
+
npx --yes catence@0.2.0 demo
|
|
208
168
|
```
|
|
209
169
|
|
|
210
|
-
|
|
170
|
+
It creates or reuses `~/.catence-demo` with one `demo` athlete. The generated data has explicit caveats in every tool response and never contains personal measurements. The Glama registry entry uses this command for **Try in Browser**, so the hosted sandbox has no access to local credentials or personal data.
|
|
211
171
|
|
|
212
|
-
|
|
213
|
-
{
|
|
214
|
-
"openai": {
|
|
215
|
-
"label": "OpenAI",
|
|
216
|
-
"model": "openai/gpt-5-mini",
|
|
217
|
-
"apiKeyEnv": "OPENAI_API_KEY"
|
|
218
|
-
},
|
|
219
|
-
"anthropic": {
|
|
220
|
-
"label": "Anthropic",
|
|
221
|
-
"model": "anthropic/claude-sonnet-4-5",
|
|
222
|
-
"apiKeyEnv": "ANTHROPIC_API_KEY"
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
```
|
|
172
|
+
From a checkout:
|
|
226
173
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
```json
|
|
230
|
-
{
|
|
231
|
-
"console": {
|
|
232
|
-
"defaultProfile": "provider",
|
|
233
|
-
"limits": {
|
|
234
|
-
"toolRounds": 8,
|
|
235
|
-
"toolResultCharacters": 24000
|
|
236
|
-
},
|
|
237
|
-
"profiles": {
|
|
238
|
-
"azure-foundry": {
|
|
239
|
-
"label": "Provider",
|
|
240
|
-
"defaultModel": "terra",
|
|
241
|
-
"defaultReasoningEffort": "medium",
|
|
242
|
-
"models": {
|
|
243
|
-
"terra": { "label": "GPT-5.6 Terra", "model": "provider/gpt-5.6-terra" },
|
|
244
|
-
"luna": { "label": "GPT-5.6 Luna", "model": "provider/gpt-5.6-luna" },
|
|
245
|
-
"sol": { "label": "GPT-5.6 Sol", "model": "provider/gpt-5.6-sol" }
|
|
246
|
-
},
|
|
247
|
-
"apiKeyEnv": "API_KEY",
|
|
248
|
-
"apiBaseEnv": "API_BASE",
|
|
249
|
-
"apiVersionEnv": "API_VERSION"
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
174
|
+
```sh
|
|
175
|
+
npm run mcp -- demo
|
|
254
176
|
```
|
|
255
177
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
The Console shows all configured deployments in its Model selector and sends the selected Thinking effort to LiteLLM as `reasoning_effort`. Choose only efforts supported by the selected deployment. Existing profiles with a single `model` remain supported.
|
|
259
|
-
|
|
260
|
-
### Launching
|
|
178
|
+
## MCP clients and HTTP
|
|
261
179
|
|
|
262
|
-
|
|
180
|
+
For a packaged installation, point a client at `catence`:
|
|
263
181
|
|
|
264
182
|
```sh
|
|
265
|
-
|
|
183
|
+
codex mcp add catence -- catence
|
|
184
|
+
claude mcp add --transport stdio catence -- catence
|
|
266
185
|
```
|
|
267
186
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
To preflight a profile and an already-running Catence HTTP server, use:
|
|
187
|
+
For a source checkout:
|
|
271
188
|
|
|
272
189
|
```sh
|
|
273
|
-
|
|
274
|
-
--data-dir /absolute/path/to/catence-data \
|
|
275
|
-
--mcp-url http://127.0.0.1:8787/mcp
|
|
190
|
+
codex mcp add catence -- npm --prefix /absolute/path/to/catence run mcp
|
|
276
191
|
```
|
|
277
192
|
|
|
278
|
-
|
|
193
|
+
Optional local Streamable HTTP MCP and dashboard APIs:
|
|
279
194
|
|
|
280
|
-
|
|
195
|
+
```sh
|
|
196
|
+
catence serve --host 127.0.0.1 --port 8787
|
|
197
|
+
```
|
|
281
198
|
|
|
282
|
-
|
|
199
|
+
`GET /api/v1/athletes` returns IDs and labels only. `GET /api/v1/dashboard` requires `athleteId`, for example `http://127.0.0.1:8787/api/v1/dashboard?athleteId=alex&days=28`. Browser origins must be listed with `--allow-origin`; the packaged Console instead proxies the dashboard through its authenticated same-origin route.
|
|
283
200
|
|
|
284
|
-
##
|
|
201
|
+
## Catence Console
|
|
285
202
|
|
|
286
|
-
|
|
203
|
+
The Console is a local web chat that starts a matching Catence HTTP server on loopback. It uses Chainlit password login and fails closed unless all three authentication variables are supplied.
|
|
287
204
|
|
|
288
205
|
```sh
|
|
289
|
-
|
|
290
|
-
|
|
206
|
+
export CATENCE_HOME="$HOME/.catence"
|
|
207
|
+
export CATENCE_CONSOLE_USERNAME='coach'
|
|
208
|
+
export CATENCE_CONSOLE_PASSWORD_HASH="$(catence-console auth hash-password)"
|
|
209
|
+
export CHAINLIT_AUTH_SECRET="$(openssl rand -hex 32)"
|
|
210
|
+
export OPENAI_API_KEY='…'
|
|
211
|
+
|
|
212
|
+
uvx catence-console@0.2.0 serve
|
|
291
213
|
```
|
|
292
214
|
|
|
293
|
-
|
|
215
|
+
On the first authenticated visit, the Console wizard asks for an OpenAI-compatible, OpenAI, or Anthropic model and writes only the non-secret model configuration to `~/.catence/config.json`. The `openai-compatible` option covers Azure and Opencode; see [`docs/llm-providers.md`](docs/llm-providers.md). Model-provider keys remain process environment variables. The Console settings include an athlete selector; the server overwrites model-supplied athlete scope with that selected value on every personal-data call.
|
|
216
|
+
|
|
217
|
+
To preflight the Console and a running runtime:
|
|
294
218
|
|
|
295
219
|
```sh
|
|
296
|
-
|
|
220
|
+
uvx catence-console@0.2.0 doctor --home "$HOME/.catence" --mcp-url http://127.0.0.1:8787/mcp
|
|
297
221
|
```
|
|
298
222
|
|
|
299
|
-
|
|
223
|
+
Console chats and saved tool context are local at `~/.catence/console/chat-history.sqlite3`.
|
|
300
224
|
|
|
301
|
-
###
|
|
225
|
+
### Docker and Cloudflare Tunnel
|
|
302
226
|
|
|
303
|
-
|
|
227
|
+
The Console image contains the Node runtime, Catence, and Console. Its MCP server remains loopback-only inside the container; only the Console port is exposed.
|
|
304
228
|
|
|
305
229
|
```sh
|
|
306
|
-
|
|
230
|
+
docker build -f console/Dockerfile -t catence-console .
|
|
231
|
+
docker run --rm -p 127.0.0.1:8000:8000 -v catence-data:/data \
|
|
232
|
+
-e CATENCE_CONSOLE_USERNAME='coach' \
|
|
233
|
+
-e CATENCE_CONSOLE_PASSWORD_HASH='replace-with-bcrypt-hash' \
|
|
234
|
+
-e CHAINLIT_AUTH_SECRET='replace-with-a-long-random-secret' \
|
|
235
|
+
-e OPENAI_API_KEY='…' \
|
|
236
|
+
catence-console
|
|
307
237
|
```
|
|
308
238
|
|
|
309
|
-
|
|
239
|
+
Initialize the mounted volume before its first production sync, for example:
|
|
310
240
|
|
|
311
241
|
```sh
|
|
312
|
-
|
|
242
|
+
docker run --rm -it -v catence-data:/data --entrypoint catence-data catence-console \
|
|
243
|
+
setup --athlete alex --label "Alex"
|
|
313
244
|
```
|
|
314
245
|
|
|
315
|
-
|
|
246
|
+
Use the same `--entrypoint catence-data` pattern for `secret set` and `sync`. Route a Cloudflare Tunnel to `http://127.0.0.1:8000`; do not run a tunnel client in this image. Use HTTPS at the tunnel and retain the Console login—the dashboard is fetched through the authenticated Console origin rather than directly from port 8787.
|
|
247
|
+
|
|
248
|
+
[`scripts/deploy-console.sh`](scripts/deploy-console.sh) deploys this stack from
|
|
249
|
+
the public registries and seeds a starter `config.json` (the Console model
|
|
250
|
+
profiles from [`config.example.json`](config.example.json)) into the data
|
|
251
|
+
volume, so the settings panel works before the first chat. Edit
|
|
252
|
+
`/data/config.json` in the volume (or the bind-mounted `--home` directory) to
|
|
253
|
+
change models and defaults, or discover OpenCode Go profiles from the image:
|
|
316
254
|
|
|
317
255
|
```sh
|
|
318
|
-
|
|
256
|
+
docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
|
|
257
|
+
--entrypoint node console \
|
|
258
|
+
/usr/local/lib/node_modules/catence/scripts/discover-opencode-go.mjs --write /data/config.json
|
|
319
259
|
```
|
|
320
260
|
|
|
321
|
-
|
|
261
|
+
## Configuration
|
|
322
262
|
|
|
323
|
-
|
|
324
|
-
[mcp_servers.catence]
|
|
325
|
-
command = "npm"
|
|
326
|
-
args = ["--prefix", "/absolute/path/to/catence", "run", "mcp", "--", "--data-dir", "/absolute/path/to/catence-data"]
|
|
327
|
-
startup_timeout_sec = 30
|
|
328
|
-
```
|
|
263
|
+
[`config.example.json`](config.example.json) documents rate limits, Strava budgets, and Console model profiles. It intentionally contains environment-variable names rather than credential values. Keep per-athlete provider values in `catence-data secret set`, not in `.env` or `config.json`.
|
|
329
264
|
|
|
330
|
-
###
|
|
265
|
+
### Per-model reasoning effort
|
|
331
266
|
|
|
332
|
-
|
|
267
|
+
Models that support reasoning effort can set a fixed level per model in `config.json`, or expose a per-chat **Thinking effort** dropdown with custom levels via `variants`.
|
|
333
268
|
|
|
334
|
-
|
|
269
|
+
**Fixed level** (all requests for the model use it):
|
|
270
|
+
|
|
271
|
+
```json
|
|
335
272
|
{
|
|
336
|
-
"
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
"
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
"
|
|
349
|
-
|
|
350
|
-
|
|
273
|
+
"console": {
|
|
274
|
+
"profiles": {
|
|
275
|
+
"opencode-zen": {
|
|
276
|
+
"label": "OpenCode Zen",
|
|
277
|
+
"defaultModel": "deepseek-v4-flash-free",
|
|
278
|
+
"models": {
|
|
279
|
+
"deepseek-v4-flash-free": {
|
|
280
|
+
"label": "DeepSeek V4 Flash Free",
|
|
281
|
+
"model": "openai/deepseek-v4-flash-free",
|
|
282
|
+
"reasoningEffort": "high"
|
|
283
|
+
}
|
|
284
|
+
},
|
|
285
|
+
"apiKeyEnv": "OPENCODE_API_KEY",
|
|
286
|
+
"apiBaseEnv": "OPENCODE_ZEN_API_BASE"
|
|
287
|
+
}
|
|
351
288
|
}
|
|
352
289
|
}
|
|
353
290
|
}
|
|
354
291
|
```
|
|
355
292
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Add the local stdio server from a terminal:
|
|
359
|
-
|
|
360
|
-
```sh
|
|
361
|
-
claude mcp add --transport stdio catence -- npm --prefix /absolute/path/to/catence run mcp -- --data-dir /absolute/path/to/catence-data
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
Use Claude Code's MCP listing command to confirm it was added:
|
|
293
|
+
**Custom variants** (per-chat dropdown): `variants` maps each dropdown label to the value sent to the provider. This is how non-OpenAI models (for example DeepSeek V4 Flash, which uses its own Default/High/Max scheme rather than the OpenAI-standard list) expose their native levels:
|
|
365
294
|
|
|
366
|
-
```
|
|
367
|
-
|
|
295
|
+
```json
|
|
296
|
+
"deepseek-v4-flash-free": {
|
|
297
|
+
"label": "DeepSeek V4 Flash Free",
|
|
298
|
+
"model": "openai/deepseek-v4-flash-free",
|
|
299
|
+
"variants": { "High": "high", "Max": "max" }
|
|
300
|
+
}
|
|
368
301
|
```
|
|
369
302
|
|
|
370
|
-
|
|
303
|
+
With `variants`, the chat **Thinking effort** dropdown shows `Provider default` plus each label; selecting one sends that exact value (e.g. `reasoning_effort: "high"`). Without `variants`, the dropdown falls back to the OpenAI-standard set (`minimal`, `low`, `medium`, `high`, `xhigh`).
|
|
371
304
|
|
|
372
|
-
|
|
305
|
+
**Disabling reasoning effort**: an explicitly empty `variants` map (`"variants": {}`) disables the feature for that model — no `reasoning_effort` is ever sent and the **Thinking effort** dropdown is greyed out. This is how models that reject the parameter (for example MiMo, which expects its own `thinking` toggle) are configured:
|
|
373
306
|
|
|
374
|
-
```
|
|
375
|
-
|
|
307
|
+
```json
|
|
308
|
+
"mimo-v2.5": {
|
|
309
|
+
"label": "MiMo V2.5",
|
|
310
|
+
"model": "openai/mimo-v2.5",
|
|
311
|
+
"variants": {}
|
|
312
|
+
}
|
|
376
313
|
```
|
|
377
314
|
|
|
378
|
-
|
|
315
|
+
A model cannot combine `variants: {}` with a fixed `reasoningEffort`.
|
|
379
316
|
|
|
380
|
-
|
|
317
|
+
A fixed `reasoningEffort` overrides any profile-level `defaultReasoningEffort`; the per-chat dropdown selection (a variant value) overrides both. If nothing is set, the provider default is used. The dropdown options always follow the model currently selected in the **Model** dropdown.
|
|
381
318
|
|
|
382
|
-
|
|
319
|
+
For source development:
|
|
383
320
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
321
|
+
```sh
|
|
322
|
+
npm ci
|
|
323
|
+
npm run check
|
|
324
|
+
npm test
|
|
325
|
+
UV_CACHE_DIR=$PWD/.cache/uv uv run --project console --group dev python -m pytest console/tests -q
|
|
326
|
+
```
|
|
388
327
|
|
|
389
|
-
|
|
328
|
+
## Beta releases
|
|
329
|
+
|
|
330
|
+
Registry betas use distinct npm and Python prerelease formats: for example,
|
|
331
|
+
`catence@0.2.0-beta.1` pairs with `catence-console==0.2.0b1` and
|
|
332
|
+
`catence-chainlit==0.2.0b1`. The protected `beta` branch verifies candidates;
|
|
333
|
+
a `v0.2.0-beta.1` tag publishes to npm's `beta` channel and creates a GitHub
|
|
334
|
+
prerelease. Follow [the beta release checklist](release/beta-checklist.md) to
|
|
335
|
+
publish the UI wheel first, deploy the registry artifacts with Compose, and
|
|
336
|
+
test the MCP and authenticated Console on a server without touching production
|
|
337
|
+
data.
|
|
338
|
+
|
|
339
|
+
## Data and caveats
|
|
340
|
+
|
|
341
|
+
Garmin is the primary source for activity and wellness data; Intervals.icu supplements training analysis; Strava is used for targeted segments, efforts, and gear. Missing fields mean a provider did not supply a value. The source credentials and source APIs determine the data available. Catence does not use the official Garmin API; Garmin sync relies on the selected athlete’s email/password credentials.
|
|
342
|
+
|
|
343
|
+
See [distribution notes](docs/distribution.md) for release artifacts, including APM and MCPB demo bundles.
|
|
344
|
+
|
|
345
|
+
##
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
cd catence-deploy
|
|
349
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
350
|
+
catence-data sync --athlete martina --provider all --home /data # incremental sync, all providers
|
|
351
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
352
|
+
catence-data backfill --athlete martina --from 2026-01-01 --home /data # full backfill from a date
|
|
353
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
354
|
+
catence-data status --athlete martina --home /data # coverage/errors
|
|
355
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
356
|
+
catence-data progress --athlete martina --home /data --watch # live sync progress
|
|
357
|
+
docker compose -f docker-compose.yml --env-file .env exec console \
|
|
358
|
+
catence-data athlete list --home /data # list athlete IDs
|
|
359
|
+
```
|