catence 0.2.0-beta.10 → 0.2.0-beta.12

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.
@@ -142,35 +142,12 @@ differs from the activity summary, report both values and the difference.
142
142
 
143
143
  ## Proposed dedicated MCP endpoints
144
144
 
145
- These endpoints are drafts, not registered tools yet. Add them incrementally
146
- only after their shared service methods and fixture tests exist.
147
-
148
- ## Implemented fitness and hydration endpoints
149
-
150
- - `describe_dataset(dataset)` returns one cataloged schema and its coverage. `training_metric_observations` also reports its observed sports, metric names, units, and source types.
151
- - `get_ftp_history`, `get_vo2max_history`, `power_curve_trend`, and `power_coverage_report` read normalized, source-aware fitness facts. Power tools require an explicit sport or sport family, use `power_bests`/`power_best_facts` for FIT-derived duration values, and do not treat sparse `avg_power` summaries as coverage. `get_vo2max_history` does not default to cycling: omission returns available source labels and requires an explicit choice. For Garmin running VO₂max, request `sport: "running"`; the tool reads the raw `generic` source series and preserves that label on returned observations.
152
- - `find_activities` is implemented for compact, canonical, paginated activity/race discovery by sport, distance, name, and date. Its race flags are transparent heuristics, not provider-confirmed race metadata.
153
- - `get_activity_segments` is the required segment/climb path. It performs the
154
- targeted Strava hydration before returning the persisted effort and segment
155
- facts; an empty result must be interpreted alongside its hydration status.
156
- - `latest_cycling_activities` returns Garmin source records to avoid silently double-counting linked Intervals summaries; optional multisport parents are explicitly flagged.
157
- - `cycling_progress_report` composes the preceding read-only outputs with monthly canonical volume/load. It is descriptive, not a physiological model.
158
- - `hydrate_recent_strava_activities` accepts an explicit list or a bounded date/sport window and awaits each write in sequence. Its unmatched output includes the exact Strava search window and safe-match rejection diagnostics.
159
-
160
- ### Power investigation procedure
161
-
162
- For a question about running or cycling power, do not begin with
163
- `canonical_activities.avg_power`: it is an activity-summary field and can be
164
- null even when the FIT stream has power. Instead:
165
-
166
- 1. Call `describe_dataset` for `power_bests` and use `power_coverage_report`
167
- with an explicit `sport` or `sportFamily` to establish coverage and every
168
- available duration.
169
- 2. Use `power_curve_trend` with the same selector and
170
- `sourceQuality: "garmin_fit_derived"` for comparable monthly curve bests.
171
- 3. Inspect `activity_samples` only for a selected `activity_source_id` when a
172
- specific extreme needs validation. Raw sample reads are paginated and are
173
- not evidence that the global power dataset is absent.
145
+ These endpoints are **drafts, not registered tools yet**. The current server
146
+ (`src/interfaces/mcp/server.ts`) does not expose them, so an agent cannot call
147
+ them today; they are specified here so semantics and acceptance criteria are
148
+ agreed before implementation. Add them incrementally only after their shared
149
+ service methods and fixture tests exist, then move them to the implemented
150
+ list below.
174
151
 
175
152
  ### `list_recent_activities`
176
153
 
@@ -330,9 +307,39 @@ manifest-only Parquet access, and null handling. It returns no data—not an
330
307
  error—when the source has no registered stream, with a caveat explaining that
331
308
  sample availability is provider/activity-specific.
332
309
 
310
+ ## Implemented fitness and hydration endpoints
311
+
312
+ - `describe_dataset(dataset)` returns one cataloged schema and its coverage. `training_metric_observations` also reports its observed sports, metric names, units, and source types.
313
+ - `get_ftp_history`, `get_vo2max_history`, `power_curve_trend`, and `power_coverage_report` read normalized, source-aware fitness facts. Power tools require an explicit sport or sport family, use `power_bests`/`power_best_facts` for FIT-derived duration values, and do not treat sparse `avg_power` summaries as coverage. `get_vo2max_history` does not default to cycling: omission returns available source labels and requires an explicit choice. For Garmin running VO₂max, request `sport: "running"`; the tool reads the raw `generic` source series and preserves that label on returned observations.
314
+ - `find_activities` is implemented for compact, canonical, paginated activity/race discovery by sport, distance, name, and date. Its race flags are transparent heuristics, not provider-confirmed race metadata.
315
+ - `get_activity_segments` is the required segment/climb path. It performs the
316
+ targeted Strava hydration before returning the persisted effort and segment
317
+ facts; an empty result must be interpreted alongside its hydration status.
318
+ - `latest_cycling_activities` returns Garmin source records to avoid silently double-counting linked Intervals summaries; optional multisport parents are explicitly flagged.
319
+ - `cycling_progress_report` composes the preceding read-only outputs with monthly canonical volume/load. It is descriptive, not a physiological model.
320
+ - `hydrate_recent_strava_activities` accepts an explicit list or a bounded date/sport window and awaits each write in sequence. Its unmatched output includes the exact Strava search window and safe-match rejection diagnostics.
321
+
322
+ ### Power investigation procedure
323
+
324
+ For a question about running or cycling power, do not begin with
325
+ `canonical_activities.avg_power`: it is an activity-summary field and can be
326
+ null even when the FIT stream has power. Instead:
327
+
328
+ 1. Call `describe_dataset` for `power_bests` and use `power_coverage_report`
329
+ with an explicit `sport` or `sportFamily` to establish coverage and every
330
+ available duration.
331
+ 2. Use `power_curve_trend` with the same selector and
332
+ `sourceQuality: "garmin_fit_derived"` for comparable monthly curve bests.
333
+ 3. Inspect `activity_samples` only for a selected `activity_source_id` when a
334
+ specific extreme needs validation. Raw sample reads are paginated and are
335
+ not evidence that the global power dataset is absent.
336
+
333
337
  ## Intended agent behaviour
334
338
 
335
- For “how did this go in the context of my recent swims?” an agent should:
339
+ This workflow is the target behaviour once the proposed endpoints above are
340
+ implemented and registered; today the agent must approximate it with
341
+ `find_activities`, `get_activity_segments`, and `read_series`. For “how did
342
+ this go in the context of my recent swims?” an agent should:
336
343
 
337
344
  1. Use `list_recent_activities` filtered to swimming to identify the selected
338
345
  source and comparable source records.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catence",
3
- "version": "0.2.0-beta.10",
3
+ "version": "0.2.0-beta.12",
4
4
  "description": "Local MCP access to normalized Garmin, Intervals.icu, and targeted Strava fitness data.",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "repository": {
@@ -1,338 +0,0 @@
1
- # Deploying Catence Console
2
-
3
- Catence Console is a password-protected local web chat that starts a matching
4
- Catence runtime on loopback. There are two supported ways to run it: **locally**
5
- (npm package + Python Console on your machine) or **in Docker** (one container
6
- built from the public registries). Both paths end with the same Console, data
7
- directory layout, model configuration, and credentials model.
8
-
9
- Everything below assumes a recent beta or stable release; channels are
10
- explained in the [beta release notes](../README.md#beta-releases).
11
-
12
- ## Prerequisites
13
-
14
- - Node.js 22+ and Python 3.12+ with [uv](https://docs.astral.sh/uv/)
15
- (local path), or Docker Engine with the `docker compose` plugin (Docker path).
16
- - Provider credentials for the athletes you choose to sync (Garmin,
17
- Intervals.icu, Strava).
18
- - One model-provider key (OpenAI, Anthropic, OpenCode Go, Azure, or any
19
- OpenAI-compatible endpoint).
20
-
21
- ## What gets stored where
22
-
23
- | What | Where | Example |
24
- | --- | --- | --- |
25
- | Catalog + athlete stores | data home | `~/.catence/` or `/data` in Docker |
26
- | Model profiles (no secrets) | `config.json` in the data home | `console.profiles` |
27
- | Provider credentials | per-athlete secret file, mode 0600 | `catence-data secret set` |
28
- | API keys | process environment only | `.env` in Docker, shell exports locally |
29
- | Chat history + preferences | data home | `console/chat-history.sqlite3` |
30
-
31
- Credentials never land in `config.json`; profiles reference environment
32
- variable *names* (`apiKeyEnv`, `apiBaseEnv`, `apiVersionEnv`). See
33
- [`llm-providers.md`](llm-providers.md) for the profile reference and
34
- [`config.example.json`](../config.example.json) for a complete example.
35
-
36
- ---
37
-
38
- ## Path A — Local deployment
39
-
40
- ### 1. Install the runtime and the Console
41
-
42
- ```sh
43
- npm install --global catence@beta # or catence@latest for stable
44
- uv tool install catence-console # persistent `catence-console` command
45
- # alternative to the last line: uvx catence-console@<version> serve (ephemeral)
46
- ```
47
-
48
- `catence-console serve` launches a matching Catence runtime automatically: it
49
- uses a globally installed `catence` if present, otherwise `npx catence@<pinned>`.
50
-
51
- ### 2. Create the catalog and first athlete
52
-
53
- ```sh
54
- catence-data setup --athlete alex --label "Alex"
55
- ```
56
-
57
- This creates `~/.catence/` (or `$CATENCE_HOME`). It also tolerates a directory
58
- that already holds only Console artifacts (`config.json`, `console/`).
59
-
60
- ### 3. Store provider credentials (stdin only, never shell history)
61
-
62
- Garmin (`email`, `password`), Intervals.icu (`apiKey`, `athleteId`), and Strava
63
- (`clientId`, `clientSecret`):
64
-
65
- ```sh
66
- # Garmin
67
- printf %s 'alex@example.com' | catence-data --athlete alex secret set --provider garmin --field email --value-stdin
68
- printf %s 'your-garmin-password' | catence-data --athlete alex secret set --provider garmin --field password --value-stdin
69
- # Intervals.icu
70
- printf %s 'intervals-api-key' | catence-data --athlete alex secret set --provider intervals --field apiKey --value-stdin
71
- printf %s '12345' | catence-data --athlete alex secret set --provider intervals --field athleteId --value-stdin
72
- # Strava (create an API application at https://www.strava.com/settings/api)
73
- printf %s 'strava-client-id' | catence-data --athlete alex secret set --provider strava --field clientId --value-stdin
74
- printf %s 'strava-client-secret' | catence-data --athlete alex secret set --provider strava --field clientSecret --value-stdin
75
- ```
76
-
77
- Set only the providers you actually sync; `secret set` accepts each field
78
- independently.
79
-
80
- ### 4. Sync data and build retrieval context
81
-
82
- ```sh
83
- catence-data --athlete alex sync --provider all
84
- catence-data --athlete alex build-retrieval-index
85
- ```
86
-
87
- ### 5. Configure the model
88
-
89
- On the first chat, the Console wizard asks for a provider and model and writes
90
- a starter profile. To start from the documented profiles instead, copy the
91
- `console` section from [`config.example.json`](../config.example.json) into
92
- `~/.catence/config.json`. To add OpenCode Go models, run the
93
- [model discovery script](#model-discovery-opencode-go) below.
94
-
95
- ### 6. Set the model credentials and Console login
96
-
97
- ```sh
98
- export OPENAI_API_KEY='…' # or ANTHROPIC_API_KEY / OPENCODE_GO_API_KEY …
99
- export CATENCE_CONSOLE_USERNAME='coach'
100
- export CATENCE_CONSOLE_PASSWORD_HASH="$(catence-console auth hash-password)"
101
- export CHAINLIT_AUTH_SECRET="$(openssl rand -hex 32)"
102
- ```
103
-
104
- ### 7. Start the Console
105
-
106
- ```sh
107
- catence-console serve
108
- # open http://127.0.0.1:8000
109
- ```
110
-
111
- The Model dropdown in the settings panel lists every profile's deployments.
112
- Preflight with:
113
-
114
- ```sh
115
- catence-console doctor
116
- ```
117
-
118
- ---
119
-
120
- ## Path B — Docker deployment
121
-
122
- ### 1. Deploy with the script
123
-
124
- ```sh
125
- curl -fsSL https://raw.githubusercontent.com/rifusaki/catence/beta/scripts/deploy-console.sh | bash -s beta
126
- # or from a checkout: ./scripts/deploy-console.sh beta
127
- # use `stable` instead of `beta` for the stable channel
128
- ```
129
-
130
- The script resolves the newest matching versions from npm and PyPI, writes a
131
- self-contained `catence-deploy/` project (Dockerfile, docker-compose.yml, and
132
- `.env` with placeholders), builds the image, **seeds a starter
133
- `/data/config.json`** into the data volume, and starts the stack — or, when
134
- secrets are missing, prints the remaining steps and exits.
135
-
136
- ### 2. Fill in `catence-deploy/.env`
137
-
138
- ```sh
139
- CATENCE_CONSOLE_USERNAME=coach
140
- CATENCE_CONSOLE_PASSWORD_HASH='…' # generate: ./catence-deploy/hash-password.sh
141
- CHAINLIT_AUTH_SECRET='…' # generate: openssl rand -hex 32
142
- OPENAI_API_KEY='…' # or ANTHROPIC_API_KEY / OPENCODE_GO_API_KEY …
143
- OPENAI_API_BASE='' # only for OpenAI-compatible endpoints
144
- OPENCODE_GO_API_KEY='…' # the OpenCode Go base URLs are pre-filled below
145
- OPENCODE_GO_API_BASE='https://opencode.ai/zen/go/v1'
146
- OPENCODE_GO_MESSAGES_API_BASE='https://opencode.ai/zen/go'
147
- ```
148
-
149
- Then start the stack (or re-run the script, which re-reads the saved `.env`):
150
-
151
- ```sh
152
- docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env up -d
153
- ```
154
-
155
- ### 3. Initialize an athlete store
156
-
157
- The data volume already contains a seeded `config.json`, so `setup` accepts
158
- it. Run through the container:
159
-
160
- ```sh
161
- docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
162
- --entrypoint catence-data console setup --athlete alex --label "Alex"
163
- ```
164
-
165
- ### 4. Store provider credentials
166
-
167
- Garmin (`email`, `password`), Intervals.icu (`apiKey`, `athleteId`), and Strava
168
- (`clientId`, `clientSecret`), one `printf` per field:
169
-
170
- ```sh
171
- # Garmin
172
- printf %s 'alex@example.com' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
173
- --entrypoint catence-data console --athlete alex secret set \
174
- --provider garmin --field email --value-stdin
175
- printf %s 'your-garmin-password' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
176
- --entrypoint catence-data console --athlete alex secret set \
177
- --provider garmin --field password --value-stdin
178
- # Intervals.icu
179
- printf %s 'intervals-api-key' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
180
- --entrypoint catence-data console --athlete alex secret set \
181
- --provider intervals --field apiKey --value-stdin
182
- printf %s '12345' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
183
- --entrypoint catence-data console --athlete alex secret set \
184
- --provider intervals --field athleteId --value-stdin
185
- # Strava (create an API application at https://www.strava.com/settings/api)
186
- printf %s 'strava-client-id' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
187
- --entrypoint catence-data console --athlete alex secret set \
188
- --provider strava --field clientId --value-stdin
189
- printf %s 'strava-client-secret' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
190
- --entrypoint catence-data console --athlete alex secret set \
191
- --provider strava --field clientSecret --value-stdin
192
- ```
193
-
194
- Set only the providers you actually sync. Use the same `--entrypoint
195
- catence-data` pattern for `sync`, `backfill`, and `auth strava`.
196
-
197
- ### 5. Configure the model
198
-
199
- The seeded `config.json` already offers OpenAI, OpenAI-compatible, and
200
- Anthropic starter profiles, so the settings panel works before the first chat.
201
- Edit `/data/config.json` inside the volume:
202
-
203
- ```sh
204
- docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
205
- --entrypoint sh console -c 'cat > /data/config.json' # paste JSON, then Ctrl-D
206
- ```
207
-
208
- With `--home <dir>` (bind mount), edit `<dir>/config.json` on the host
209
- instead. To add OpenCode Go models, see [model discovery](#model-discovery-opencode-go).
210
-
211
- ### 6. Verify with doctor
212
-
213
- `catence-console doctor` checks each profile's environment variables and the
214
- local Catence runtime. The deploy scaffold includes a helper for it:
215
-
216
- ```sh
217
- ./catence-deploy/doctor.sh
218
- ```
219
-
220
- The helper runs the doctor *inside the running container* with `exec` — the
221
- container's own loopback holds the live MCP server at `127.0.0.1:8787`, and the
222
- provider keys from `.env` are already in its environment. (A `docker compose
223
- run` container has a separate loopback and would report the runtime as
224
- unreachable.) The equivalent long form is:
225
-
226
- ```sh
227
- docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
228
- /opt/catence-console/bin/catence-console doctor --home /data --mcp-url http://127.0.0.1:8787/mcp
229
- ```
230
-
231
- Expect `"ready": true` once the model key is set; missing keys show up per
232
- profile without ever printing their values.
233
-
234
- ### 7. Sync, backfill, and inspect athlete data
235
-
236
- The deploy scaffold includes a helper that runs `catence-data` *inside the
237
- running container* with `exec`, so the athlete's provider secrets from the
238
- data volume are already available:
239
-
240
- ```sh
241
- ./catence-deploy/sync.sh --athlete alex sync # incremental sync, all providers
242
- ./catence-deploy/sync.sh --athlete alex backfill 2026-01-01 # full backfill from a date
243
- ./catence-deploy/sync.sh --athlete alex status # coverage and errors
244
- ./catence-deploy/sync.sh athletes # list athlete IDs
245
- ```
246
-
247
- `sync` defaults to `--provider all`; extra flags pass through, for example
248
- `./catence-deploy/sync.sh --athlete alex sync --refresh` or
249
- `./catence-deploy/sync.sh --athlete alex backfill 2026-01-01 --provider garmin`.
250
- Set a default athlete with `export CATENCE_ATHLETE=alex` to drop the
251
- `--athlete` argument. The equivalent long form is:
252
-
253
- ```sh
254
- docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
255
- catence-data sync --athlete alex --provider all --home /data
256
- ```
257
-
258
- ### 8. Expose the Console safely
259
-
260
- Keep the Console port bound to `127.0.0.1` (the default) and front it with a
261
- Cloudflare Tunnel or reverse proxy at HTTPS. Never expose Catence's MCP port
262
- 8787 — it stays loopback-only inside the container. For remote MCP checks use
263
- SSH port forwarding (`ssh -L 8787:127.0.0.1:8787 server`).
264
-
265
- ---
266
-
267
- ## Model discovery (OpenCode Go)
268
-
269
- OpenCode Go publishes an OpenAI-compatible API at
270
- `https://opencode.ai/zen/go/v1`; its model list is public. The discovery
271
- script fetches it and merges two ready-made profiles into `config.json`:
272
- `opencode-go` (chat + responses models, base `…/zen/go/v1`) and
273
- `opencode-go-messages` (messages models, base `…/zen/go`). Existing profiles,
274
- limits, and `defaultProfile` are preserved unless `--set-default` is passed.
275
-
276
- ```sh
277
- # Local (from a checkout)
278
- npm run discover:opencode-go -- --write ~/.catence/config.json
279
-
280
- # Docker (the script ships inside the npm package in the image)
281
- docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
282
- --entrypoint node console \
283
- /usr/local/lib/node_modules/catence/scripts/discover-opencode-go.mjs --write /data/config.json
284
- ```
285
-
286
- Then set the credentials and verify. The generated `.env` already contains
287
- `OPENCODE_GO_API_BASE` and `OPENCODE_GO_MESSAGES_API_BASE`, so on Docker only
288
- the key is missing:
289
-
290
- ```sh
291
- export OPENCODE_GO_API_KEY='…' # any non-empty value passes the key check
292
- catence-console doctor
293
- ```
294
-
295
- The `openai/responses/…` models (for example `grok-4.5`) go through LiteLLM's
296
- responses bridge and are the least battle-tested path; smoke-test one chat turn
297
- before relying on them.
298
-
299
- ---
300
-
301
- ## Updating
302
-
303
- From `catence-data update`-capable releases (0.2.0-beta.3 and later), the
304
- runtime updates both components on the tracked channel — beta installs follow
305
- the npm `beta` tag and matching PyPI prereleases:
306
-
307
- ```sh
308
- catence-data update --check # report only; exit 1 when updates are pending
309
- catence-data update # npm runtime + uv tool catence-console upgrade
310
- catence-data update --channel stable # move off a beta onto the stable channel
311
- ```
312
-
313
- Older betas (for example a beta 2 install) predate the command; upgrade them
314
- manually once:
315
-
316
- ```sh
317
- npm install --global catence@beta
318
- uv tool install --upgrade catence-console
319
- ```
320
-
321
- Docker deployments: re-run `deploy-console.sh beta` (or `stable`); it
322
- re-resolves the newest versions from the registries and rebuilds. The data
323
- volume and your `config.json` are preserved.
324
-
325
- ## Troubleshooting
326
-
327
- - **"No Catence config exists"** — should not happen after a seeded deploy or
328
- wizard run; ensure `/data/config.json` (Docker) or `~/.catence/config.json`
329
- exists and contains a `console` section.
330
- - **"Refusing to initialize"** — the data home contains unrelated files; the
331
- error lists them. Console artifacts (`config.json`, `console/`, and Chainlit's
332
- `.files/`, `.chainlit/`, `public/`) are allowed; anything else blocks `setup`.
333
- - **Settings panel has no Model dropdown** — the Console loads profiles from
334
- `config.json`; write one via the wizard, a manual copy, or the discovery
335
- script.
336
- - **Profile not ready** — `catence-console doctor` lists the missing
337
- environment variables; set them in `.env` (Docker) or the shell (local) and
338
- restart the Console.
@@ -1,232 +0,0 @@
1
- # Docker Deployment Quick Start and Common Commands
2
-
3
- This document mirrors the [local quick start](../README.md#quick-start) and [common operations](../README.md#common-operations) for Catence when deployed via Docker. It assumes you have completed the [deployment steps](deployment.md#path-b--docker-deployment) and have a running `catence-deploy` stack.
4
-
5
- ## Quick Start (Docker)
6
-
7
- All commands run from the `catence-deploy` directory created by the deploy script.
8
-
9
- ### 1. Initialize an athlete store
10
-
11
- ```sh
12
- docker compose -f docker-compose.yml --env-file .env run --rm \
13
- --entrypoint catence-data console setup --athlete alex --label "Alex"
14
- ```
15
-
16
- ### 2. Store provider credentials (stdin only, never shell history)
17
-
18
- ```sh
19
- # Garmin
20
- printf %s 'alex@example.com' | docker compose -f docker-compose.yml --env-file .env run --rm -T \
21
- --entrypoint catence-data console --athlete alex secret set \
22
- --provider garmin --field email --value-stdin
23
- printf %s 'your-garmin-password' | docker compose -f docker-compose.yml --env-file .env run --rm -T \
24
- --entrypoint catence-data console --athlete alex secret set \
25
- --provider garmin --field password --value-stdin
26
-
27
- # Intervals.icu
28
- printf %s 'intervals-api-key' | docker compose -f docker-compose.yml --env-file .env run --rm -T \
29
- --entrypoint catence-data console --athlete alex secret set \
30
- --provider intervals --field apiKey --value-stdin
31
- printf %s '12345' | docker compose -f docker-compose.yml --env-file .env run --rm -T \
32
- --entrypoint catence-data console --athlete alex secret set \
33
- --provider intervals --field athleteId --value-stdin
34
-
35
- # Strava (create an API application at https://www.strava.com/settings/api)
36
- printf %s 'strava-client-id' | docker compose -f docker-compose.yml --env-file .env run --rm -T \
37
- --entrypoint catence-data console --athlete alex secret set \
38
- --provider strava --field clientId --value-stdin
39
- printf %s 'strava-client-secret' | docker compose -f docker-compose.yml --env-file .env run --rm -T \
40
- --entrypoint catence-data console --athlete alex secret set \
41
- --provider strava --field clientSecret --value-stdin
42
- ```
43
-
44
- ### 3. Sync data and build retrieval context
45
-
46
- ```sh
47
- docker compose -f docker-compose.yml --env-file .env exec console \
48
- catence-data sync --athlete alex --provider all --home /data
49
- docker compose -f docker-compose.yml --env-file .env exec console \
50
- catence-data build-retrieval-index --athlete alex --home /data
51
- ```
52
-
53
- ### 4. Access the Console
54
-
55
- Open `http://127.0.0.1:8000` (or the `--bind:--port` you configured) and log in with `CATENCE_CONSOLE_USERNAME` and the password whose hash is in `CATENCE_CONSOLE_PASSWORD_HASH`.
56
-
57
- ---
58
-
59
- ## Common Operations (Docker)
60
-
61
- The `sync.sh` helper in `catence-deploy/` wraps the most frequent `catence-data` calls inside the **running** container (so the provider secrets from the data volume are already available).
62
-
63
- ```sh
64
- # Incremental sync for one athlete (all providers)
65
- ./sync.sh --athlete alex sync
66
-
67
- # Sync a specific provider
68
- ./sync.sh --athlete alex sync --provider intervals
69
-
70
- # Sync from a specific date (Garmin)
71
- ./sync.sh --athlete alex sync --provider garmin --from 2025-07-29
72
-
73
- # Full backfill from a date
74
- ./sync.sh --athlete alex backfill 2026-01-01
75
-
76
- # Backfill with refresh (re-fetch existing data)
77
- ./sync.sh --athlete alex backfill 2026-01-01 --refresh
78
-
79
- # Retry a failed sync run
80
- ./sync.sh --athlete alex retry --run <run-id>
81
-
82
- # Coverage and error status
83
- ./sync.sh --athlete alex status
84
-
85
- # List athlete IDs in the catalog
86
- ./sync.sh athletes
87
-
88
- # Strava OAuth (uses clientId/clientSecret stored for this athlete)
89
- docker compose -f docker-compose.yml --env-file .env exec console \
90
- catence-data auth strava --athlete alex --callback --home /data
91
- docker compose -f docker-compose.yml --env-file .env exec console \
92
- catence-data disconnect strava --athlete alex --home /data
93
-
94
- # Strava OAuth via SSH (remote Docker host accessed over SSH/Tailscale)
95
- ## Option 1: SSH Port Forwarding (recommended)
96
- # On your LOCAL machine, forward the callback port to the remote Docker host:
97
- ssh -L 8765:127.0.0.1:8765 user@docker-host
98
- # Then run the standard callback flow on the REMOTE host (uses http://127.0.0.1:8765/strava/callback)
99
- docker compose -f docker-compose.yml --env-file .env exec console \
100
- catence-data auth strava --athlete alex --callback --home /data
101
-
102
- ## Option 2: Manual code exchange with http://localhost
103
- # On the REMOTE host, get the authorization URL:
104
- docker compose -f docker-compose.yml --env-file .env run --rm \
105
- --entrypoint catence-data console auth strava \
106
- --athlete alex --redirect-uri "http://localhost" --home /data
107
- # Open the printed URL in YOUR LOCAL BROWSER, approve Strava, then copy the 'code' from the redirect URL
108
- # Exchange the code on the REMOTE host:
109
- docker compose -f docker-compose.yml --env-file .env run --rm \
110
- --entrypoint catence-data console auth strava \
111
- --athlete alex --code "THE_CODE_FROM_URL" --redirect-uri "http://localhost" --home /data
112
-
113
- # Update the runtime and Console (re-run deploy script on the same channel)
114
- curl -fsSL https://raw.githubusercontent.com/rifusaki/catence/beta/scripts/deploy-console.sh | bash -s beta
115
- # or from checkout: ./scripts/deploy-console.sh beta
116
-
117
- # Choose a non-default catalog location (bind mount instead of named volume)
118
- # Stop the stack, edit docker-compose.yml to replace the volume with:
119
- # volumes:
120
- # - /srv/catence:/data
121
- # Then restart and run the same commands (--home /data is implied by CATENCE_HOME).
122
- ```
123
-
124
- ---
125
-
126
- ## Connecting to the Docker-based MCP from Outside
127
-
128
- ### The MCP Server Inside the Container
129
-
130
- By default, the Catence MCP server runs on **loopback only** inside the container at `http://127.0.0.1:8787/mcp`. It is **never exposed** to the host network or the internet — this is intentional for security.
131
-
132
- The `CATENCE_MCP_BIND` variable controls **two things at once**:
133
-
134
- 1. **Docker port mapping**: which host interface exposes port 8787 to the Docker host.
135
- 2. **Server listen address**: which address the MCP server binds to inside the container.
136
-
137
- | Value | Docker maps | Server binds | Effect |
138
- |-------|-------------|--------------|--------|
139
- | `127.0.0.1` (default) | `127.0.0.1:8787 → container:8787` | `127.0.0.1` | MCP reachable only from the Docker host's loopback — requires SSH tunnel for remote access. |
140
- | `0.0.0.0` | `0.0.0.0:8787 → container:8787` | `0.0.0.0` | MCP reachable from any network interface — suitable for Tailscale or VPN exposure. |
141
-
142
- Set it at deploy time with `--mcp-bind`, or by editing `CATENCE_MCP_BIND` in `.env`:
143
-
144
- ```sh
145
- # Default (loopback only — SSH tunnel required for remote access)
146
- ./scripts/deploy-console.sh beta
147
-
148
- # Tailscale / network exposure
149
- ./scripts/deploy-console.sh beta --mcp-bind 0.0.0.0
150
- ```
151
-
152
- To change the binding after deployment, update `CATENCE_MCP_BIND` in `.env` and restart:
153
-
154
- ```sh
155
- sed -i 's/CATENCE_MCP_BIND=.*/CATENCE_MCP_BIND=0.0.0.0/' catence-deploy/.env
156
- docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env up -d
157
- ```
158
-
159
- ### SSH Port Forwarding (Recommended for Remote MCP Clients)
160
-
161
- From your **local machine**, forward the container's MCP port through the Docker host:
162
-
163
- ```sh
164
- # Forward local port 8787 → Docker host port 8787 → container loopback 8787
165
- ssh -L 8787:127.0.0.1:8787 user@docker-host
166
- ```
167
-
168
- Then point your MCP client at `http://127.0.0.1:8787/mcp` on your local machine.
169
-
170
- ### Exposing MCP on Tailscale (Alternative)
171
-
172
- To expose the MCP server directly on a Tailscale interface (or any network interface) without SSH tunneling:
173
-
174
- ```sh
175
- # Deploy with MCP bound to all interfaces (0.0.0.0) — restrict via Tailscale ACLs/firewall
176
- ./scripts/deploy-console.sh beta --mcp-bind 0.0.0.0
177
- ```
178
-
179
- Or set the environment variable before deploying:
180
-
181
- ```sh
182
- CATENCE_MCP_BIND=0.0.0.0 ./scripts/deploy-console.sh beta
183
- ```
184
-
185
- After deployment, the MCP server is available at `http://<tailscale-ip>:8787/mcp` from any device on your tailnet.
186
-
187
- **Security note**: The MCP server has **no authentication** — it trusts the caller's `athleteId` parameter. When exposing on `0.0.0.0`, you **must** restrict access at the network layer (Tailscale ACLs, firewall rules, etc.). The Console UI remains on loopback (`127.0.0.1:8000`) and should still be fronted by Cloudflare Tunnel or a reverse proxy.
188
-
189
- **Examples: Common MCP Clients**
190
-
191
- ```sh
192
- # Via SSH tunnel (default loopback deployment)
193
- # Opencode
194
- opencode mcp add catence -- http://127.0.0.1:8787/mcp
195
-
196
- # Codex
197
- codex mcp add catence -- http://127.0.0.1:8787/mcp
198
-
199
- # Claude Desktop
200
- claude mcp add --transport http catence -- http://127.0.0.1:8787/mcp
201
- ```
202
-
203
- ```sh
204
- # Via Tailscale (when deployed with --mcp-bind 0.0.0.0)
205
- # Replace <tailscale-ip> with your server's Tailscale IP (from `tailscale ip`)
206
- # Opencode
207
- opencode mcp add catence -- http://<tailscale-ip>:8787/mcp
208
-
209
- # Codex
210
- codex mcp add catence -- http://<tailscale-ip>:8787/mcp
211
-
212
- # Claude Desktop
213
- claude mcp add --transport http catence -- http://<tailscale-ip>:8787/mcp
214
- ```
215
-
216
- ### Why Not Expose Port 8787 Directly?
217
-
218
- - The MCP server has **no authentication** — it trusts the caller's `athleteId` parameter.
219
- - Exposing it would allow any network caller to read all athlete data in the catalog.
220
- - The Console proxies dashboard requests through its **authenticated** same-origin route.
221
- - SSH port forwarding keeps the trust boundary at your SSH credentials.
222
-
223
- ---
224
-
225
- ## Quick Reference: `docker compose run` vs `docker compose exec`
226
-
227
- | Command Type | Use For | Example |
228
- |--------------|---------|---------|
229
- | `run --rm --entrypoint catence-data console` | **One-off setup/secrets** — needs fresh stdin for `--value-stdin`, no running container required | `secret set`, `setup`, `auth strava --code` |
230
- | `exec console catence-data` | **Recurring ops** — uses the running container's environment (secrets already in volume) | `sync`, `backfill`, `status`, `build-retrieval-index` |
231
-
232
- The `sync.sh` helper uses `exec` for this reason.