catence 0.2.0-beta.2 → 0.2.0-beta.21

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.
Files changed (66) hide show
  1. package/README.md +28 -82
  2. package/config.example.json +25 -0
  3. package/dist/contracts/index.d.ts +1 -0
  4. package/dist/contracts/index.js +1 -0
  5. package/dist/contracts/progress.d.ts +23 -0
  6. package/dist/contracts/progress.js +49 -0
  7. package/dist/contracts/release.d.ts +1 -1
  8. package/dist/contracts/release.js +1 -1
  9. package/dist/core/logging.d.ts +16 -0
  10. package/dist/core/logging.js +68 -0
  11. package/dist/core/query/repository.d.ts +2 -0
  12. package/dist/core/query/repository.js +48 -0
  13. package/dist/core/runtime/catalog.js +17 -7
  14. package/dist/core/runtime/configuration.d.ts +3 -1
  15. package/dist/core/runtime/configuration.js +5 -1
  16. package/dist/core/runtime/opencode-go.d.ts +58 -0
  17. package/dist/core/runtime/opencode-go.js +246 -0
  18. package/dist/core/runtime/secrets.js +51 -13
  19. package/dist/core/runtime/self-update.d.ts +44 -0
  20. package/dist/core/runtime/self-update.js +138 -0
  21. package/dist/elt/application/detached-sync.d.ts +50 -0
  22. package/dist/elt/application/detached-sync.js +99 -0
  23. package/dist/elt/application/management.d.ts +15 -0
  24. package/dist/elt/application/management.js +435 -19
  25. package/dist/elt/ingestion/importer.d.ts +3 -2
  26. package/dist/elt/ingestion/importer.js +5 -4
  27. package/dist/elt/ingestion/providers/intervals.d.ts +12 -1
  28. package/dist/elt/ingestion/providers/intervals.js +37 -2
  29. package/dist/elt/ingestion/providers/registry.d.ts +13 -0
  30. package/dist/elt/ingestion/providers/registry.js +18 -1
  31. package/dist/elt/ingestion/providers/strava/service.js +7 -1
  32. package/dist/elt/normalization/normalizers.js +111 -15
  33. package/dist/elt/storage/database.d.ts +21 -2
  34. package/dist/elt/storage/database.js +184 -6
  35. package/dist/elt/storage/migrations.js +25 -1
  36. package/dist/elt/storage/progress-sidecar.d.ts +61 -0
  37. package/dist/elt/storage/progress-sidecar.js +200 -0
  38. package/dist/interfaces/cli/commands.js +116 -1
  39. package/dist/interfaces/http/generation-status.d.ts +19 -0
  40. package/dist/interfaces/http/generation-status.js +55 -0
  41. package/dist/interfaces/http/server.d.ts +7 -1
  42. package/dist/interfaces/http/server.js +184 -1
  43. package/dist/interfaces/mcp/server.d.ts +2 -1
  44. package/dist/interfaces/mcp/server.js +50 -13
  45. package/dist/runtime/index.d.ts +8 -1
  46. package/dist/runtime/index.js +4 -1
  47. package/docs/architecture.md +11 -1
  48. package/docs/configuration.md +243 -0
  49. package/docs/deployment/docker.md +528 -0
  50. package/docs/deployment/local-console.md +263 -0
  51. package/docs/deployment/local-mcp.md +329 -0
  52. package/docs/development.md +337 -0
  53. package/docs/distribution.md +58 -0
  54. package/docs/llm-providers.md +249 -6
  55. package/docs/mcp-activity-retrieval.md +37 -30
  56. package/package.json +5 -1
  57. package/pyproject.toml +1 -1
  58. package/python/catence/providers/garmin/cli.py +43 -9
  59. package/python/catence/providers/garmin/progress.py +85 -0
  60. package/python/catence/providers/garmin/registry.py +1 -0
  61. package/python/catence/providers/garmin/streams.py +38 -0
  62. package/python/catence/providers/garmin/swim_backfill.py +83 -0
  63. package/python/catence/providers/garmin/worker.py +372 -11
  64. package/scripts/discover-opencode-go.mjs +102 -0
  65. package/scripts/discover-opencode-zen.mjs +218 -0
  66. package/uv.lock +3 -3
@@ -0,0 +1,528 @@
1
+ # Docker deployment
2
+
3
+ This guide runs the full Catence stack — runtime, MCP server, and Console — in
4
+ one container built from the public registries. It is the single deployment
5
+ path for a remote or always-on setup and covers the same Console, data
6
+ directory layout, model configuration, and credentials model as the
7
+ [local path](local-console.md).
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
+ - Docker Engine with the `docker compose` plugin.
15
+ - Provider credentials for the athletes you choose to sync (Garmin,
16
+ Intervals.icu, Strava).
17
+ - One model-provider key (OpenAI, Anthropic, OpenCode Go, Azure, or any
18
+ OpenAI-compatible endpoint).
19
+
20
+ ## What gets stored where
21
+
22
+ | What | Where | Example |
23
+ | --- | --- | --- |
24
+ | Catalog + athlete stores | data home | `/data` (volume or bind mount) |
25
+ | Model profiles (no secrets) | `config.json` in the data home | `console.profiles` |
26
+ | Provider credentials | per-athlete secret file, mode 0600 | `catence-data secret set` |
27
+ | API keys | process environment only | `.env` |
28
+ | Chat history + preferences | data home | `console/chat-history.sqlite3` |
29
+
30
+ Credentials never land in `config.json`; profiles reference environment
31
+ variable *names* (`apiKeyEnv`, `apiBaseEnv`, `apiVersionEnv`). See
32
+ [`llm-providers.md`](../llm-providers.md) for the profile reference and
33
+ [`configuration.md`](../configuration.md) for the complete `config.json`
34
+ schema.
35
+
36
+ ## 1. Deploy with the script
37
+
38
+ ```sh
39
+ curl -fsSL https://raw.githubusercontent.com/rifusaki/catence/beta/scripts/deploy-console.sh | bash -s beta
40
+ # or from a checkout: ./scripts/deploy-console.sh beta
41
+ # use `stable` instead of `beta` for the stable channel
42
+ ```
43
+
44
+ The script resolves the newest matching versions from npm and PyPI, writes a
45
+ self-contained `catence-deploy/` project (Dockerfile, docker-compose.yml, and
46
+ `.env` with placeholders), builds the image, **seeds a starter
47
+ `/data/config.json`** into the data volume, and starts the stack — or, when
48
+ secrets are missing, prints the remaining steps and exits.
49
+
50
+ Script options (run `./deploy-console.sh --help` for the full list):
51
+
52
+ | Option | Default | Meaning |
53
+ | --- | --- | --- |
54
+ | `--channel stable\|beta` | — | Release channel (also the first positional argument) |
55
+ | `--port` | — | Console UI host port |
56
+ | `--bind` | `127.0.0.1` | Console UI bind address on the host |
57
+ | `--mcp-bind` | `127.0.0.1` | MCP server bind (also sets the compose port mapping; see [MCP exposure](#connecting-to-the-docker-based-mcp-from-outside)) |
58
+ | `--dir` | `catence-deploy` | Output directory for the generated scaffold |
59
+ | `--username` | — | Console username written into `.env` |
60
+ | `--password-hash` | — | bcrypt hash for `CATENCE_CONSOLE_PASSWORD_HASH` |
61
+ | `--auth-secret` | — | JWT secret for `CHAINLIT_AUTH_SECRET` |
62
+ | `--home <dir>` | — | Host directory bind-mounted to `/data` instead of a named volume |
63
+ | `--volume` | — | Named volume name (default `catence`/`catence-beta`) |
64
+ | `--env-file` | — | Use an existing `.env` instead of writing a new one |
65
+ | `--npm-version` / `--console-version` | — | Pin exact versions instead of resolving the channel |
66
+ | `--no-build` / `--no-pull` | — | Skip image build / base image pull |
67
+ | `--generate-secrets` | — | Interactively prompt for the password and write the bcrypt hash to `.env` |
68
+ | `--dry-run` | — | Print what would be written without changing anything |
69
+
70
+ ## 2. Fill in `catence-deploy/.env`
71
+
72
+ ```sh
73
+ CATENCE_CONSOLE_USERNAME=coach
74
+ CATENCE_CONSOLE_PASSWORD_HASH='…' # generate: ./catence-deploy/hash-password.sh
75
+ CHAINLIT_AUTH_SECRET='…' # generate: openssl rand -hex 32
76
+ OPENAI_API_KEY='…' # or ANTHROPIC_API_KEY / OPENCODE_GO_API_KEY …
77
+ OPENAI_API_BASE='' # only for OpenAI-compatible endpoints
78
+ OPENCODE_GO_API_KEY='…' # the OpenCode Go base URLs are pre-filled below
79
+ OPENCODE_GO_API_BASE='https://opencode.ai/zen/go/v1'
80
+ OPENCODE_GO_MESSAGES_API_BASE='https://opencode.ai/zen/go'
81
+ ```
82
+
83
+ Generate the Console login hash with the scaffold helper (it prompts on the
84
+ terminal, never echoes, and uses the compose project so it works whatever image
85
+ tag the script built):
86
+
87
+ ```sh
88
+ ./catence-deploy/hash-password.sh
89
+ ```
90
+
91
+ Or re-run the deploy script with `--generate-secrets` and answer the
92
+ interactive prompt. Without the helper (for example an older deployment), the
93
+ equivalent command is:
94
+
95
+ ```sh
96
+ docker run --rm -it --entrypoint /opt/catence-console/bin/catence-console <image-tag> auth hash-password
97
+ ```
98
+
99
+ Then start the stack (or re-run the script, which re-reads the saved `.env`):
100
+
101
+ ```sh
102
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env up -d
103
+ ```
104
+
105
+ ## 3. Initialize an athlete store
106
+
107
+ The data volume already contains a seeded `config.json`, so `setup` accepts
108
+ it. Run through the container:
109
+
110
+ ```sh
111
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
112
+ --entrypoint catence-data console setup --athlete alex --label "Alex"
113
+ ```
114
+
115
+ ## 4. Store provider credentials
116
+
117
+ Garmin (`email`, `password`), Intervals.icu (`apiKey`, `athleteId`), and Strava
118
+ (`clientId`, `clientSecret`), one `printf` per field:
119
+
120
+ ```sh
121
+ # Garmin
122
+ printf %s 'alex@example.com' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
123
+ --entrypoint catence-data console --athlete alex secret set \
124
+ --provider garmin --field email --value-stdin
125
+ printf %s 'your-garmin-password' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
126
+ --entrypoint catence-data console --athlete alex secret set \
127
+ --provider garmin --field password --value-stdin
128
+ # Intervals.icu
129
+ printf %s 'intervals-api-key' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
130
+ --entrypoint catence-data console --athlete alex secret set \
131
+ --provider intervals --field apiKey --value-stdin
132
+ printf %s '12345' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
133
+ --entrypoint catence-data console --athlete alex secret set \
134
+ --provider intervals --field athleteId --value-stdin
135
+ # Strava (create an API application at https://www.strava.com/settings/api)
136
+ printf %s 'strava-client-id' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
137
+ --entrypoint catence-data console --athlete alex secret set \
138
+ --provider strava --field clientId --value-stdin
139
+ printf %s 'strava-client-secret' | docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm -T \
140
+ --entrypoint catence-data console --athlete alex secret set \
141
+ --provider strava --field clientSecret --value-stdin
142
+ ```
143
+
144
+ Set only the providers you actually sync. Use the same `--entrypoint
145
+ catence-data` pattern for `sync`, `backfill`, and `auth strava`.
146
+
147
+ ## 5. Sync data and build retrieval context
148
+
149
+ ```sh
150
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
151
+ catence-data sync --athlete alex --provider all --home /data
152
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
153
+ catence-data build-retrieval-index --athlete alex --home /data
154
+ ```
155
+
156
+ ## 6. Configure the model
157
+
158
+ The seeded `config.json` already offers OpenAI, OpenAI-compatible, and
159
+ Anthropic starter profiles, so the settings panel works before the first chat.
160
+ Edit `/data/config.json` inside the volume:
161
+
162
+ ```sh
163
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
164
+ --entrypoint sh console -c 'cat > /data/config.json' # paste JSON, then Ctrl-D
165
+ ```
166
+
167
+ With `--home <dir>` (bind mount), edit `<dir>/config.json` on the host
168
+ instead. To add OpenCode Go models from the image (the script ships inside the
169
+ npm package):
170
+
171
+ ```sh
172
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
173
+ --entrypoint node console \
174
+ /usr/local/lib/node_modules/catence/scripts/discover-opencode-go.mjs --write /data/config.json
175
+ ```
176
+
177
+ The generated `.env` already contains `OPENCODE_GO_API_BASE` and
178
+ `OPENCODE_GO_MESSAGES_API_BASE`, so on Docker only the key is missing:
179
+
180
+ ```sh
181
+ export OPENCODE_GO_API_KEY='…' # any non-empty value passes the key check
182
+ ```
183
+
184
+ ## 7. Access the Console
185
+
186
+ Open `http://127.0.0.1:8000` (or the `--bind:--port` you configured) and log in
187
+ with `CATENCE_CONSOLE_USERNAME` and the password whose hash is in
188
+ `CATENCE_CONSOLE_PASSWORD_HASH`.
189
+
190
+ ## 8. Verify with doctor
191
+
192
+ `catence-console doctor` checks each profile's environment variables and the
193
+ local Catence runtime. The deploy scaffold includes a helper for it:
194
+
195
+ ```sh
196
+ ./catence-deploy/doctor.sh
197
+ ```
198
+
199
+ The helper runs the doctor *inside the running container* with `exec` — the
200
+ container's own loopback holds the live MCP server at `127.0.0.1:8787`, and the
201
+ provider keys from `.env` are already in its environment. (A `docker compose
202
+ run` container has a separate loopback and would report the runtime as
203
+ unreachable.) The equivalent long form is:
204
+
205
+ ```sh
206
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
207
+ /opt/catence-console/bin/catence-console doctor --home /data --mcp-url http://127.0.0.1:8787/mcp
208
+ ```
209
+
210
+ Expect `"ready": true` once the model key is set; missing keys show up per
211
+ profile without ever printing their values.
212
+
213
+ ## Common operations
214
+
215
+ The `sync.sh` helper in `catence-deploy/` wraps the most frequent
216
+ `catence-data` calls inside the **running** container (so the provider secrets
217
+ from the data volume are already available):
218
+
219
+ ```sh
220
+ # Incremental sync for one athlete (all providers)
221
+ ./sync.sh --athlete alex sync
222
+
223
+ # Sync a specific provider
224
+ ./sync.sh --athlete alex sync --provider intervals
225
+
226
+ # Sync from a specific date (Garmin)
227
+ ./sync.sh --athlete alex sync --provider garmin --from 2025-07-29
228
+
229
+ # Full backfill from a date
230
+ ./sync.sh --athlete alex backfill 2026-01-01
231
+
232
+ # Backfill with refresh (re-fetch existing data)
233
+ ./sync.sh --athlete alex backfill 2026-01-01 --refresh
234
+
235
+ # Retry a failed sync run
236
+ ./sync.sh --athlete alex retry --run <run-id>
237
+
238
+ # Coverage and error status
239
+ ./sync.sh --athlete alex status
240
+
241
+ # List athlete IDs in the catalog
242
+ ./sync.sh athletes
243
+ ```
244
+
245
+ `sync` defaults to `--provider all`; extra flags pass through. Set a default
246
+ athlete with `export CATENCE_ATHLETE=alex` to drop the `--athlete` argument.
247
+ `--detach` starts a sync or backfill in the background and returns immediately;
248
+ a detached run keeps streaming into the container logs (`docker compose -f
249
+ catence-deploy/docker-compose.yml logs -f console`), keeps writing heartbeats,
250
+ and is recoverable by the next sync.
251
+
252
+ ### Sync progress
253
+
254
+ ```sh
255
+ ./sync.sh --athlete alex sync --detach # start the run in the background and return
256
+ ./sync.sh --athlete alex progress --watch # follow it live
257
+ ./sync.sh --athlete alex progress # one-shot snapshot
258
+ ```
259
+
260
+ For the full progress-record contract and the sidecar mechanism, see
261
+ [`local-mcp.md`](local-mcp.md#background-sync-and-live-progress).
262
+
263
+ ### Strava OAuth
264
+
265
+ ```sh
266
+ # Strava OAuth (uses clientId/clientSecret stored for this athlete)
267
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
268
+ catence-data auth strava --athlete alex --callback --home /data
269
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
270
+ catence-data disconnect strava --athlete alex --home /data
271
+ ```
272
+
273
+ #### Strava OAuth via SSH (remote Docker host)
274
+
275
+ **Option 1: SSH port forwarding (recommended).** On your local machine, forward
276
+ the callback port to the remote Docker host:
277
+
278
+ ```sh
279
+ ssh -L 8765:127.0.0.1:8765 user@docker-host
280
+ ```
281
+
282
+ Then run the standard callback flow on the remote host (it uses
283
+ `http://127.0.0.1:8765/strava/callback`):
284
+
285
+ ```sh
286
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env exec console \
287
+ catence-data auth strava --athlete alex --callback --home /data
288
+ ```
289
+
290
+ **Option 2: manual code exchange with `http://localhost`.** On the remote host,
291
+ get the authorization URL:
292
+
293
+ ```sh
294
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
295
+ --entrypoint catence-data console auth strava \
296
+ --athlete alex --redirect-uri "http://localhost" --home /data
297
+ ```
298
+
299
+ Open the printed URL in your local browser, approve Strava, then copy the
300
+ `code` from the redirect URL and exchange it on the remote host:
301
+
302
+ ```sh
303
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env run --rm \
304
+ --entrypoint catence-data console auth strava \
305
+ --athlete alex --code "THE_CODE_FROM_URL" --redirect-uri "http://localhost" --home /data
306
+ ```
307
+
308
+ ### Raw compose exec one-off commands
309
+
310
+ The generated scaffold is self-contained, so the same commands work from
311
+ inside `catence-deploy/` without the `-f`/`--env-file` prefixes:
312
+
313
+ ```bash
314
+ cd catence-deploy
315
+ docker compose -f docker-compose.yml --env-file .env exec console \
316
+ catence-data sync --athlete martina --provider all --home /data # incremental sync, all providers
317
+ docker compose -f docker-compose.yml --env-file .env exec console \
318
+ catence-data backfill --athlete martina --from 2026-01-01 --home /data # full backfill from a date
319
+ docker compose -f docker-compose.yml --env-file .env exec console \
320
+ catence-data status --athlete martina --home /data # coverage/errors
321
+ docker compose -f docker-compose.yml --env-file .env exec console \
322
+ catence-data progress --athlete martina --home /data --watch # live sync progress
323
+ docker compose -f docker-compose.yml --env-file .env exec console \
324
+ catence-data athlete list --home /data # list athlete IDs
325
+ ```
326
+
327
+ ## Connecting to the Docker-based MCP from outside
328
+
329
+ ### The MCP server inside the container
330
+
331
+ By default, the Catence MCP server runs on **loopback only** inside the
332
+ container at `http://127.0.0.1:8787/mcp`. It is **never exposed** to the host
333
+ network or the internet by default — this is intentional for security.
334
+
335
+ The `CATENCE_MCP_BIND` variable controls **two things at once**:
336
+
337
+ 1. **Docker port mapping** — which host interface exposes port 8787 to the
338
+ Docker host.
339
+ 2. **Server listen address** — which address the MCP server binds to inside
340
+ the container.
341
+
342
+ | Value | Docker maps | Server binds | Effect |
343
+ |-------|-------------|--------------|--------|
344
+ | `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. |
345
+ | `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. |
346
+
347
+ Set it at deploy time with `--mcp-bind`, or by editing `CATENCE_MCP_BIND` in
348
+ `.env`:
349
+
350
+ ```sh
351
+ # Default (loopback only — SSH tunnel required for remote access)
352
+ ./scripts/deploy-console.sh beta
353
+
354
+ # Tailscale / network exposure
355
+ ./scripts/deploy-console.sh beta --mcp-bind 0.0.0.0
356
+ ```
357
+
358
+ To change the binding after deployment, update `CATENCE_MCP_BIND` in `.env`
359
+ and restart:
360
+
361
+ ```sh
362
+ sed -i 's/CATENCE_MCP_BIND=.*/CATENCE_MCP_BIND=0.0.0.0/' catence-deploy/.env
363
+ docker compose -f catence-deploy/docker-compose.yml --env-file catence-deploy/.env up -d
364
+ ```
365
+
366
+ ### SSH port forwarding (recommended for remote MCP clients)
367
+
368
+ From your **local machine**, forward the container's MCP port through the
369
+ Docker host:
370
+
371
+ ```sh
372
+ # Forward local port 8787 → Docker host port 8787 → container loopback 8787
373
+ ssh -L 8787:127.0.0.1:8787 user@docker-host
374
+ ```
375
+
376
+ Then point your MCP client at `http://127.0.0.1:8787/mcp` on your local
377
+ machine.
378
+
379
+ ### Exposing MCP on Tailscale (alternative)
380
+
381
+ To expose the MCP server directly on a Tailscale interface (or any network
382
+ interface) without SSH tunneling:
383
+
384
+ ```sh
385
+ # Deploy with MCP bound to all interfaces (0.0.0.0) — restrict via Tailscale ACLs/firewall
386
+ ./scripts/deploy-console.sh beta --mcp-bind 0.0.0.0
387
+ ```
388
+
389
+ Or set the environment variable before deploying:
390
+
391
+ ```sh
392
+ CATENCE_MCP_BIND=0.0.0.0 ./scripts/deploy-console.sh beta
393
+ ```
394
+
395
+ After deployment, the MCP server is available at
396
+ `http://<tailscale-ip>:8787/mcp` from any device on your tailnet.
397
+
398
+ **Security note:** The MCP server has **no authentication** — it trusts the
399
+ caller's `athleteId` parameter. When exposing on `0.0.0.0`, you **must**
400
+ restrict access at the network layer (Tailscale ACLs, firewall rules, etc.).
401
+ The Console UI remains on loopback (`127.0.0.1:8000`) and should still be
402
+ fronted by Cloudflare Tunnel or a reverse proxy.
403
+
404
+ **Examples: common MCP clients**
405
+
406
+ ```sh
407
+ # Via SSH tunnel (default loopback deployment)
408
+ # Opencode
409
+ opencode mcp add catence -- http://127.0.0.1:8787/mcp
410
+
411
+ # Codex
412
+ codex mcp add catence -- http://127.0.0.1:8787/mcp
413
+
414
+ # Claude Desktop
415
+ claude mcp add --transport http catence -- http://127.0.0.1:8787/mcp
416
+ ```
417
+
418
+ ```sh
419
+ # Via Tailscale (when deployed with --mcp-bind 0.0.0.0)
420
+ # Replace <tailscale-ip> with your server's Tailscale IP (from `tailscale ip`)
421
+ opencode mcp add catence -- http://<tailscale-ip>:8787/mcp
422
+ codex mcp add catence -- http://<tailscale-ip>:8787/mcp
423
+ claude mcp add --transport http catence -- http://<tailscale-ip>:8787/mcp
424
+ ```
425
+
426
+ ### Why not expose port 8787 directly?
427
+
428
+ - The MCP server has **no authentication** — it trusts the caller's
429
+ `athleteId` parameter.
430
+ - Exposing it would allow any network caller to read all athlete data in the
431
+ catalog.
432
+ - The Console proxies dashboard requests through its **authenticated**
433
+ same-origin route.
434
+ - SSH port forwarding keeps the trust boundary at your SSH credentials.
435
+
436
+ ## Exposing the Console safely
437
+
438
+ Keep the Console port bound to `127.0.0.1` (the default) and front it with a
439
+ Cloudflare Tunnel or reverse proxy at HTTPS. Never expose Catence's MCP port
440
+ 8787 — it stays loopback-only inside the container. For remote MCP checks use
441
+ SSH port forwarding (`ssh -L 8787:127.0.0.1:8787 server`).
442
+
443
+ ### Cloudflare Tunnel
444
+
445
+ The Console image contains the Node runtime, Catence, and Console. Its MCP
446
+ server remains loopback-only inside the container; only the Console port is
447
+ exposed. Route a Cloudflare Tunnel to `http://127.0.0.1:8000`; do not run a
448
+ tunnel client in this image. Use HTTPS at the tunnel and retain the Console
449
+ login — the dashboard is fetched through the authenticated Console origin
450
+ rather than directly from port 8787.
451
+
452
+ ### Building the image manually
453
+
454
+ For a source build instead of the registry artifacts:
455
+
456
+ ```sh
457
+ docker build -f console/Dockerfile -t catence-console .
458
+ docker run --rm -p 127.0.0.1:8000:8000 -v catence-data:/data \
459
+ -e CATENCE_CONSOLE_USERNAME='coach' \
460
+ -e CATENCE_CONSOLE_PASSWORD_HASH='replace-with-bcrypt-hash' \
461
+ -e CHAINLIT_AUTH_SECRET='replace-with-a-long-random-secret' \
462
+ -e OPENAI_API_KEY='…' \
463
+ catence-console
464
+ ```
465
+
466
+ Initialize the mounted volume before its first production sync, for example:
467
+
468
+ ```sh
469
+ docker run --rm -it -v catence-data:/data --entrypoint catence-data catence-console \
470
+ setup --athlete alex --label "Alex"
471
+ ```
472
+
473
+ Use the same `--entrypoint catence-data` pattern for `secret set` and `sync`.
474
+
475
+ ## Updating
476
+
477
+ Docker deployments: re-run `deploy-console.sh beta` (or `stable`); it
478
+ re-resolves the newest versions from the registries and rebuilds. The data
479
+ volume and your `config.json` are preserved.
480
+
481
+ ```sh
482
+ curl -fsSL https://raw.githubusercontent.com/rifusaki/catence/beta/scripts/deploy-console.sh | bash -s beta
483
+ # or from checkout: ./scripts/deploy-console.sh beta
484
+ ```
485
+
486
+ For local (`catence-data update`) updates see
487
+ [`local-console.md`](local-console.md#updating).
488
+
489
+ ## Non-default catalog location (bind mount)
490
+
491
+ Stop the stack, edit `catence-deploy/docker-compose.yml` to replace the volume
492
+ with a bind mount:
493
+
494
+ ```yaml
495
+ volumes:
496
+ - /srv/catence:/data
497
+ ```
498
+
499
+ Then restart and run the same commands (`--home /data` is implied by
500
+ `CATENCE_HOME`). The deploy script's `--home <dir>` option does this for you at
501
+ deploy time.
502
+
503
+ ## Quick reference: `docker compose run` vs `docker compose exec`
504
+
505
+ | Command Type | Use For | Example |
506
+ |--------------|---------|---------|
507
+ | `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` |
508
+ | `exec console catence-data` | **Recurring ops** — uses the running container's environment (secrets already in volume) | `sync`, `backfill`, `status`, `build-retrieval-index` |
509
+
510
+ The `sync.sh` helper uses `exec` for this reason.
511
+
512
+ ## Troubleshooting
513
+
514
+ - **"No Catence config exists"** — should not happen after a seeded deploy or
515
+ wizard run; ensure `/data/config.json` exists and contains a `console`
516
+ section.
517
+ - **"Refusing to initialize"** — the data home contains unrelated files; the
518
+ error lists them. Console artifacts (`config.json`, `console/`, and
519
+ Chainlit's `.files/`, `.chainlit/`, `public/`) are allowed; anything else
520
+ blocks `setup`.
521
+ - **Settings panel has no Model dropdown** — the Console loads profiles from
522
+ `config.json`; write one via the wizard, a manual copy, or the discovery
523
+ script.
524
+ - **Profile not ready** — `catence-console doctor` lists the missing
525
+ environment variables; set them in `.env` and restart the Console.
526
+ - **Doctor reports runtime unreachable** — use `./catence-deploy/doctor.sh`
527
+ (exec) rather than `docker compose run`; a `run` container has a separate
528
+ loopback that cannot see the running MCP server.