catence 0.1.0 → 0.2.0-beta.1
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 +121 -293
- package/dist/contracts/release.d.ts +1 -1
- package/dist/contracts/release.js +1 -1
- package/dist/core/runtime/catalog.d.ts +41 -0
- package/dist/core/runtime/catalog.js +111 -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/elt/application/management.js +4 -2
- package/dist/elt/ingestion/providers/strava/service.js +2 -1
- package/dist/interfaces/cli/commands.js +111 -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 +149 -98
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,389 +1,217 @@
|
|
|
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
|
|
29
|
-
|
|
30
|
-
- **Recovery and readiness:** “What changed in my sleep, HRV, resting heart rate, stress, and recent load before today’s session?”
|
|
31
|
-
- **Training load:** “Is this week harder than my recent baseline, and which sessions contributed most?”
|
|
32
|
-
- **Performance trends:** “How has my threshold pace, cycling power, or swim efficiency changed across the season?”
|
|
33
|
-
- **Session review:** “Compare this long run or interval set with similar recent efforts, including pace/power, heart rate, terrain, and recovery.”
|
|
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
|
|
77
|
-
|
|
78
|
-
After publication, start a no-account MCP server with generated data:
|
|
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.
|
|
79
26
|
|
|
80
27
|
```sh
|
|
81
|
-
|
|
28
|
+
npm install --global catence@0.2.0
|
|
29
|
+
catence-data setup --athlete alex --label "Alex"
|
|
82
30
|
```
|
|
83
31
|
|
|
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:
|
|
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.
|
|
88
33
|
|
|
89
34
|
```sh
|
|
90
|
-
|
|
91
|
-
|
|
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
|
|
92
39
|
```
|
|
93
40
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
After publication, install the package once, then create and synchronize a store that you own:
|
|
41
|
+
Sync and create retrieval context:
|
|
97
42
|
|
|
98
43
|
```sh
|
|
99
|
-
|
|
100
|
-
catence-data --
|
|
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
|
|
44
|
+
catence-data --athlete alex sync --provider all
|
|
45
|
+
catence-data --athlete alex build-retrieval-index
|
|
104
46
|
```
|
|
105
47
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
- Node.js 22 or later
|
|
109
|
-
- Python 3.12 or later and [uv](https://docs.astral.sh/uv/) for Garmin and Strava provider syncs
|
|
110
|
-
|
|
111
|
-
Clone the repository, install the Node and Python dependencies, and create your local `.env` with the following credentials:
|
|
112
|
-
|
|
113
|
-
- Garmin email/password
|
|
114
|
-
- Intervals.icu [API Key and Athlete ID](https://forum.intervals.icu/t/api-access-to-intervals-icu/609)
|
|
115
|
-
- Strava [Client ID and Secret](https://stravalib.readthedocs.io/en/latest/get-started/authenticate-with-strava.html)
|
|
116
|
-
|
|
117
|
-
### Create and sync a local data store
|
|
118
|
-
|
|
119
|
-
By default, runtime data is placed in `.catence/` inside the checkout. You can also choose an absolute path outside the repository and supply it consistently with `--data-dir` (or set `CATENCE_DATA_DIR`). The examples below use an absolute path.
|
|
48
|
+
Start the stdio MCP server:
|
|
120
49
|
|
|
121
50
|
```sh
|
|
122
|
-
|
|
123
|
-
npm run catence-data -- --data-dir /absolute/path/to/catence-data sync --provider all
|
|
124
|
-
npm run catence-data -- --data-dir /absolute/path/to/catence-data build-retrieval-index
|
|
51
|
+
catence
|
|
125
52
|
```
|
|
126
53
|
|
|
127
|
-
|
|
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.
|
|
128
55
|
|
|
129
|
-
###
|
|
130
|
-
|
|
131
|
-
Run these from the repository root. Omit `--data-dir` to use the checkout's `.catence/` directory.
|
|
56
|
+
### Add another athlete
|
|
132
57
|
|
|
133
58
|
```sh
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
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
|
|
154
64
|
```
|
|
155
65
|
|
|
156
|
-
The
|
|
157
|
-
|
|
158
|
-
### Connect Strava
|
|
66
|
+
The catalog layout is deliberately simple:
|
|
159
67
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
...
|
|
164
78
|
```
|
|
165
79
|
|
|
166
|
-
|
|
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.
|
|
167
81
|
|
|
168
|
-
|
|
82
|
+
### Common operations
|
|
169
83
|
|
|
170
84
|
```sh
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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>
|
|
174
91
|
|
|
175
|
-
|
|
92
|
+
# Strava OAuth uses clientId and clientSecret previously stored for this athlete
|
|
93
|
+
catence-data --athlete alex auth strava --callback
|
|
94
|
+
catence-data --athlete alex disconnect strava
|
|
176
95
|
|
|
177
|
-
|
|
178
|
-
|
|
96
|
+
# Choose a non-default catalog location
|
|
97
|
+
catence-data --home /srv/catence setup --athlete alex --label "Alex"
|
|
98
|
+
catence --home /srv/catence
|
|
179
99
|
```
|
|
180
100
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
The Console is Catence’s local chat experience. It sends model calls through LiteLLM in-process and calls the same Streamable HTTP MCP tools that a coding agent uses. Provider keys are never written to Catence configuration or chat storage.
|
|
101
|
+
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.
|
|
184
102
|
|
|
185
|
-
|
|
103
|
+
## Safe demo and Glama
|
|
186
104
|
|
|
187
|
-
|
|
105
|
+
Run a clearly marked generated dataset without provider accounts:
|
|
188
106
|
|
|
189
107
|
```sh
|
|
190
|
-
|
|
108
|
+
npx --yes catence@0.2.0 demo
|
|
191
109
|
```
|
|
192
110
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
For source development, install the locally built `catence-chainlit` wheel and run `uv run --project console catence-console serve` from this repository.
|
|
196
|
-
|
|
197
|
-
### First-run setup
|
|
111
|
+
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.
|
|
198
112
|
|
|
199
|
-
|
|
113
|
+
From a checkout:
|
|
200
114
|
|
|
201
115
|
```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
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
For an existing configuration or an automated setup, add a `console` section to `<data-dir>/config.json`. It defines named providers and deployments and environment-variable *names*, never credential values. The complete shape is in [`config.example.json`](config.example.json). For the two common direct providers, the minimal profiles are:
|
|
211
|
-
|
|
212
|
-
```json
|
|
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
|
-
}
|
|
116
|
+
npm run mcp -- demo
|
|
225
117
|
```
|
|
226
118
|
|
|
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
|
-
}
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
An OpenAI-compatible provider such as OpenCode uses a model such as `openai/your-model` plus `apiBaseEnv` and, if needed, `apiKeyEnv`. LiteLLM handles provider normalization.
|
|
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
|
|
119
|
+
## MCP clients and HTTP
|
|
261
120
|
|
|
262
|
-
|
|
121
|
+
For a packaged installation, point a client at `catence`:
|
|
263
122
|
|
|
264
123
|
```sh
|
|
265
|
-
|
|
124
|
+
codex mcp add catence -- catence
|
|
125
|
+
claude mcp add --transport stdio catence -- catence
|
|
266
126
|
```
|
|
267
127
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
To preflight a profile and an already-running Catence HTTP server, use:
|
|
128
|
+
For a source checkout:
|
|
271
129
|
|
|
272
130
|
```sh
|
|
273
|
-
|
|
274
|
-
--data-dir /absolute/path/to/catence-data \
|
|
275
|
-
--mcp-url http://127.0.0.1:8787/mcp
|
|
131
|
+
codex mcp add catence -- npm --prefix /absolute/path/to/catence run mcp
|
|
276
132
|
```
|
|
277
133
|
|
|
278
|
-
|
|
134
|
+
Optional local Streamable HTTP MCP and dashboard APIs:
|
|
279
135
|
|
|
280
|
-
|
|
136
|
+
```sh
|
|
137
|
+
catence serve --host 127.0.0.1 --port 8787
|
|
138
|
+
```
|
|
281
139
|
|
|
282
|
-
|
|
140
|
+
`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
141
|
|
|
284
|
-
##
|
|
142
|
+
## Catence Console
|
|
285
143
|
|
|
286
|
-
|
|
144
|
+
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
145
|
|
|
288
146
|
```sh
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
147
|
+
export CATENCE_HOME="$HOME/.catence"
|
|
148
|
+
export CATENCE_CONSOLE_USERNAME='coach'
|
|
149
|
+
export CATENCE_CONSOLE_PASSWORD_HASH="$(catence-console auth hash-password)"
|
|
150
|
+
export CHAINLIT_AUTH_SECRET="$(openssl rand -hex 32)"
|
|
151
|
+
export OPENAI_API_KEY='…'
|
|
294
152
|
|
|
295
|
-
|
|
296
|
-
npm --prefix /absolute/path/to/catence run mcp -- --data-dir /absolute/path/to/catence-data
|
|
153
|
+
uvx catence-console@0.2.0 serve
|
|
297
154
|
```
|
|
298
155
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
### Codex
|
|
156
|
+
On the first authenticated visit, the Console wizard asks for an Azure, OpenAI, or Anthropic model and writes only the non-secret model configuration to `~/.catence/config.json`. 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.
|
|
302
157
|
|
|
303
|
-
|
|
158
|
+
To preflight the Console and a running runtime:
|
|
304
159
|
|
|
305
160
|
```sh
|
|
306
|
-
|
|
161
|
+
uvx catence-console@0.2.0 doctor --home "$HOME/.catence" --mcp-url http://127.0.0.1:8787/mcp
|
|
307
162
|
```
|
|
308
163
|
|
|
309
|
-
|
|
164
|
+
Console chats and saved tool context are local at `~/.catence/console/chat-history.sqlite3`.
|
|
310
165
|
|
|
311
|
-
|
|
312
|
-
codex mcp add catence -- catence --data-dir /absolute/path/to/catence-data
|
|
313
|
-
```
|
|
166
|
+
### Docker and Cloudflare Tunnel
|
|
314
167
|
|
|
315
|
-
|
|
168
|
+
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.
|
|
316
169
|
|
|
317
170
|
```sh
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
```
|
|
329
|
-
|
|
330
|
-
### OpenCode
|
|
331
|
-
|
|
332
|
-
Add this local MCP definition to `opencode.json` or `opencode.jsonc`:
|
|
333
|
-
|
|
334
|
-
```jsonc
|
|
335
|
-
{
|
|
336
|
-
"$schema": "https://opencode.ai/config.json",
|
|
337
|
-
"mcp": {
|
|
338
|
-
"catence": {
|
|
339
|
-
"type": "local",
|
|
340
|
-
"command": [
|
|
341
|
-
"npm",
|
|
342
|
-
"--prefix",
|
|
343
|
-
"/absolute/path/to/catence",
|
|
344
|
-
"run",
|
|
345
|
-
"mcp",
|
|
346
|
-
"--",
|
|
347
|
-
"--data-dir",
|
|
348
|
-
"/absolute/path/to/catence-data"
|
|
349
|
-
],
|
|
350
|
-
"enabled": true
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
171
|
+
docker build -f console/Dockerfile -t catence-console .
|
|
172
|
+
docker run --rm -p 127.0.0.1:8000:8000 -v catence-data:/data \
|
|
173
|
+
-e CATENCE_CONSOLE_USERNAME='coach' \
|
|
174
|
+
-e CATENCE_CONSOLE_PASSWORD_HASH='replace-with-bcrypt-hash' \
|
|
175
|
+
-e CHAINLIT_AUTH_SECRET='replace-with-a-long-random-secret' \
|
|
176
|
+
-e OPENAI_API_KEY='…' \
|
|
177
|
+
catence-console
|
|
354
178
|
```
|
|
355
179
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
Add the local stdio server from a terminal:
|
|
180
|
+
Initialize the mounted volume before its first production sync, for example:
|
|
359
181
|
|
|
360
182
|
```sh
|
|
361
|
-
|
|
183
|
+
docker run --rm -it -v catence-data:/data --entrypoint catence-data catence-console \
|
|
184
|
+
setup --athlete alex --label "Alex"
|
|
362
185
|
```
|
|
363
186
|
|
|
364
|
-
Use
|
|
187
|
+
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.
|
|
365
188
|
|
|
366
|
-
|
|
367
|
-
claude mcp list
|
|
368
|
-
```
|
|
189
|
+
## Configuration
|
|
369
190
|
|
|
370
|
-
|
|
191
|
+
[`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`.
|
|
371
192
|
|
|
372
|
-
|
|
193
|
+
For source development:
|
|
373
194
|
|
|
374
195
|
```sh
|
|
375
|
-
|
|
196
|
+
npm ci
|
|
197
|
+
npm run check
|
|
198
|
+
npm test
|
|
199
|
+
UV_CACHE_DIR=$PWD/.cache/uv uv run --project console --group dev python -m pytest console/tests -q
|
|
376
200
|
```
|
|
377
201
|
|
|
378
|
-
|
|
202
|
+
## Beta releases
|
|
379
203
|
|
|
380
|
-
|
|
204
|
+
Registry betas use distinct npm and Python prerelease formats: for example,
|
|
205
|
+
`catence@0.2.0-beta.1` pairs with `catence-console==0.2.0b1` and
|
|
206
|
+
`catence-chainlit==0.2.0b1`. The protected `beta` branch verifies candidates;
|
|
207
|
+
a `v0.2.0-beta.1` tag publishes to npm's `beta` channel and creates a GitHub
|
|
208
|
+
prerelease. Follow [the beta release checklist](release/beta-checklist.md) to
|
|
209
|
+
publish the UI wheel first, deploy the registry artifacts with Compose, and
|
|
210
|
+
test the MCP and authenticated Console on a server without touching production
|
|
211
|
+
data.
|
|
381
212
|
|
|
382
|
-
|
|
213
|
+
## Data and caveats
|
|
383
214
|
|
|
384
|
-
|
|
385
|
-
- `wellness_baselines` returns a trailing mean, standard-deviation band, latest value, and latest z-score.
|
|
386
|
-
- `wellness_anomalies` finds statistical outliers and dates with multiple unusual signals; it does not diagnose or prescribe.
|
|
387
|
-
- `wellness_coverage` shows absent dates and unresolved extraction errors without assuming that missing data means rest or a health outcome.
|
|
215
|
+
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.
|
|
388
216
|
|
|
389
|
-
|
|
217
|
+
See [distribution notes](docs/distribution.md) for release artifacts, including APM and MCPB demo bundles.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Keep these values synchronized with release/manifest.json; release checks
|
|
4
4
|
* deliberately reject drift before a package can be published.
|
|
5
5
|
*/
|
|
6
|
-
export declare const CATENCE_RUNTIME_VERSION = "0.
|
|
6
|
+
export declare const CATENCE_RUNTIME_VERSION = "0.2.0-beta.1";
|
|
7
7
|
export declare const CATENCE_PROTOCOL_VERSION = 1;
|
|
8
8
|
export type CatenceRuntimeCapabilities = {
|
|
9
9
|
mcp: true;
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Keep these values synchronized with release/manifest.json; release checks
|
|
4
4
|
* deliberately reject drift before a package can be published.
|
|
5
5
|
*/
|
|
6
|
-
export const CATENCE_RUNTIME_VERSION = '0.
|
|
6
|
+
export const CATENCE_RUNTIME_VERSION = '0.2.0-beta.1';
|
|
7
7
|
export const CATENCE_PROTOCOL_VERSION = 1;
|
|
8
8
|
export function catenceRuntimeHealth() {
|
|
9
9
|
return {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { CatencePaths } from '../../contracts/runtime.js';
|
|
3
|
+
declare const catalogSchema: z.ZodObject<{
|
|
4
|
+
formatVersion: z.ZodLiteral<1>;
|
|
5
|
+
defaultAthleteId: z.ZodString;
|
|
6
|
+
athletes: z.ZodArray<z.ZodObject<{
|
|
7
|
+
id: z.ZodString;
|
|
8
|
+
label: z.ZodString;
|
|
9
|
+
createdAt: z.ZodString;
|
|
10
|
+
}, z.core.$strict>>;
|
|
11
|
+
}, z.core.$strict>;
|
|
12
|
+
export type Athlete = z.infer<typeof catalogSchema>['athletes'][number];
|
|
13
|
+
export type CatenceCatalog = z.infer<typeof catalogSchema>;
|
|
14
|
+
export type CatalogPaths = {
|
|
15
|
+
root: string;
|
|
16
|
+
catalog: string;
|
|
17
|
+
athletes: string;
|
|
18
|
+
console: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function defaultCatalogHome(): string;
|
|
21
|
+
export declare function resolveCatalogPaths(root?: string): CatalogPaths;
|
|
22
|
+
export declare function athleteStorePaths(catalogPaths: CatalogPaths, athleteId: string): CatencePaths;
|
|
23
|
+
export declare function loadCatalog(paths?: CatalogPaths): Promise<CatenceCatalog>;
|
|
24
|
+
export declare function initializeCatalog(paths: CatalogPaths | undefined, athlete: {
|
|
25
|
+
id: string;
|
|
26
|
+
label: string;
|
|
27
|
+
}): Promise<CatenceCatalog>;
|
|
28
|
+
export declare function addAthlete(paths: CatalogPaths, athlete: {
|
|
29
|
+
id: string;
|
|
30
|
+
label: string;
|
|
31
|
+
setDefault?: boolean;
|
|
32
|
+
}): Promise<CatenceCatalog>;
|
|
33
|
+
export declare function resolveAthlete(paths: CatalogPaths, athleteId: string): Promise<{
|
|
34
|
+
athlete: Athlete;
|
|
35
|
+
paths: CatencePaths;
|
|
36
|
+
}>;
|
|
37
|
+
export declare function defaultAthlete(paths?: CatalogPaths): Promise<{
|
|
38
|
+
athlete: Athlete;
|
|
39
|
+
paths: CatencePaths;
|
|
40
|
+
}>;
|
|
41
|
+
export {};
|