catence 0.2.0-beta.1 → 0.2.0-beta.11

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 (45) hide show
  1. package/README.md +23 -77
  2. package/config.example.json +32 -20
  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 +48 -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 +46 -0
  13. package/dist/core/runtime/catalog.js +17 -7
  14. package/dist/core/runtime/self-update.d.ts +44 -0
  15. package/dist/core/runtime/self-update.js +138 -0
  16. package/dist/elt/application/management.d.ts +2 -0
  17. package/dist/elt/application/management.js +220 -18
  18. package/dist/elt/ingestion/importer.d.ts +3 -2
  19. package/dist/elt/ingestion/importer.js +4 -3
  20. package/dist/elt/ingestion/providers/intervals.d.ts +3 -1
  21. package/dist/elt/ingestion/providers/intervals.js +4 -1
  22. package/dist/elt/ingestion/providers/strava/service.js +7 -1
  23. package/dist/elt/storage/database.d.ts +7 -2
  24. package/dist/elt/storage/database.js +81 -4
  25. package/dist/elt/storage/migrations.js +25 -1
  26. package/dist/elt/storage/progress-sidecar.d.ts +26 -0
  27. package/dist/elt/storage/progress-sidecar.js +87 -0
  28. package/dist/interfaces/cli/commands.js +91 -1
  29. package/dist/interfaces/mcp/server.js +7 -0
  30. package/dist/runtime/index.d.ts +4 -1
  31. package/dist/runtime/index.js +2 -1
  32. package/docs/architecture.md +11 -1
  33. package/docs/configuration.md +221 -0
  34. package/docs/deployment/docker.md +528 -0
  35. package/docs/deployment/local-console.md +230 -0
  36. package/docs/deployment/local-mcp.md +304 -0
  37. package/docs/distribution.md +58 -0
  38. package/docs/llm-providers.md +467 -0
  39. package/docs/mcp-activity-retrieval.md +37 -30
  40. package/package.json +5 -1
  41. package/python/catence/providers/garmin/cli.py +30 -9
  42. package/python/catence/providers/garmin/progress.py +85 -0
  43. package/python/catence/providers/garmin/worker.py +132 -5
  44. package/scripts/discover-opencode-go.mjs +210 -0
  45. package/scripts/discover-opencode-zen.mjs +218 -0
@@ -0,0 +1,230 @@
1
+ # Local Console deployment
2
+
3
+ Catence Console is a password-protected local web chat that starts a matching
4
+ Catence runtime on loopback. It uses Chainlit password login and fails closed
5
+ unless all three authentication variables are supplied.
6
+
7
+ This guide covers the **local** path: the npm package plus the Python Console
8
+ on your machine. The Docker path (one container built from the public
9
+ registries) is covered in [`docker.md`](docker.md). Both paths end with the
10
+ same Console, data directory layout, model configuration, and credentials
11
+ model.
12
+
13
+ ## Prerequisites
14
+
15
+ - Node.js 22+ and Python 3.12+ with [uv](https://docs.astral.sh/uv/).
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/` |
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 | shell exports |
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
+ [`configuration.md`](../configuration.md) for the complete `config.json`
35
+ schema.
36
+
37
+ ## 1. Install the runtime and the Console
38
+
39
+ ```sh
40
+ npm install --global catence@beta # or catence@latest for stable
41
+ uv tool install catence-console # persistent `catence-console` command
42
+ # alternative to the last line: uvx catence-console@<version> serve (ephemeral)
43
+ ```
44
+
45
+ `catence-console serve` launches a matching Catence runtime automatically: it
46
+ uses a globally installed `catence` if present, otherwise
47
+ `npx catence@<pinned>`.
48
+
49
+ ## 2. Create the catalog and first athlete
50
+
51
+ ```sh
52
+ catence-data setup --athlete alex --label "Alex"
53
+ ```
54
+
55
+ This creates `~/.catence/` (or `$CATENCE_HOME`). It also tolerates a directory
56
+ that already holds only Console artifacts (`config.json`, `console/`).
57
+
58
+ ## 3. Store provider credentials (stdin only, never shell history)
59
+
60
+ Garmin (`email`, `password`), Intervals.icu (`apiKey`, `athleteId`), and Strava
61
+ (`clientId`, `clientSecret`):
62
+
63
+ ```sh
64
+ # Garmin
65
+ printf %s 'alex@example.com' | catence-data --athlete alex secret set --provider garmin --field email --value-stdin
66
+ printf %s 'your-garmin-password' | catence-data --athlete alex secret set --provider garmin --field password --value-stdin
67
+ # Intervals.icu
68
+ printf %s 'intervals-api-key' | catence-data --athlete alex secret set --provider intervals --field apiKey --value-stdin
69
+ printf %s '12345' | catence-data --athlete alex secret set --provider intervals --field athleteId --value-stdin
70
+ # Strava (create an API application at https://www.strava.com/settings/api)
71
+ printf %s 'strava-client-id' | catence-data --athlete alex secret set --provider strava --field clientId --value-stdin
72
+ printf %s 'strava-client-secret' | catence-data --athlete alex secret set --provider strava --field clientSecret --value-stdin
73
+ ```
74
+
75
+ Set only the providers you actually sync; `secret set` accepts each field
76
+ independently.
77
+
78
+ ## 4. Sync data and build retrieval context
79
+
80
+ ```sh
81
+ catence-data --athlete alex sync --provider all
82
+ catence-data --athlete alex build-retrieval-index
83
+ ```
84
+
85
+ ## 5. Configure the model
86
+
87
+ On the first chat, the Console wizard asks for a provider and model and writes
88
+ a starter profile to `~/.catence/config.json`. To start from the documented
89
+ profiles instead, copy the `console` section from
90
+ [`config.example.json`](../../config.example.json) into `~/.catence/config.json`.
91
+ To add OpenCode Go models, run the
92
+ [model discovery script](../llm-providers.md#opencode).
93
+
94
+ ## 6. Set the model credentials and Console login
95
+
96
+ ```sh
97
+ export OPENAI_API_KEY='…' # or ANTHROPIC_API_KEY / OPENCODE_GO_API_KEY …
98
+ export CATENCE_CONSOLE_USERNAME='coach'
99
+ export CATENCE_CONSOLE_PASSWORD_HASH="$(catence-console auth hash-password)"
100
+ export CHAINLIT_AUTH_SECRET="$(openssl rand -hex 32)"
101
+ ```
102
+
103
+ The Console fails closed unless `CATENCE_CONSOLE_USERNAME`,
104
+ `CATENCE_CONSOLE_PASSWORD_HASH` (a bcrypt hash), and `CHAINLIT_AUTH_SECRET`
105
+ are all set. There is one shared account; the username/password pair grants a
106
+ single `console-owner` role.
107
+
108
+ ## 7. Start the Console
109
+
110
+ ```sh
111
+ catence-console serve
112
+ # open http://127.0.0.1:8000
113
+ ```
114
+
115
+ The Model dropdown in the settings panel lists every profile's deployments.
116
+ Preflight with:
117
+
118
+ ```sh
119
+ catence-console doctor
120
+ ```
121
+
122
+ ## Console serve options
123
+
124
+ `catence-console serve` accepts:
125
+
126
+ | Option | Default | Meaning |
127
+ | --- | --- | --- |
128
+ | `--home <dir>` | `$CATENCE_HOME` or `~/.catence` | Catalog home the runtime serves |
129
+ | `--mcp-url <url>` | `$CATENCE_MCP_URL` or auto-start | If given, waits for an existing runtime instead of starting one |
130
+ | `--ui-host <host>` | `$CATENCE_CONSOLE_HOST` or `127.0.0.1` | Web UI bind address |
131
+ | `--mcp-host <host>` | `127.0.0.1` | Loopback host for the auto-started runtime |
132
+ | `--mcp-port <port>` | `8787` | Loopback port for the auto-started runtime |
133
+ | `--ui-port <port>` | `8000` | Web UI port |
134
+ | `--no-build-ui` | — | Deprecated no-op |
135
+ | `--external-mcp` | — | Deprecated alias for `--mcp-url` |
136
+
137
+ When no `--mcp-url` is given, the Console spawns a matching runtime
138
+ (`catence serve --home <home> --host <mcp-host> --port <mcp-port>
139
+ --allow-origin http://127.0.0.1:<ui-port> --allow-origin http://localhost:<ui-port>`)
140
+ and waits for `GET /health` to succeed within 20 seconds. The runtime and
141
+ Console must agree on the protocol version; a mismatch is rejected before a
142
+ chat starts.
143
+
144
+ ## How multi-athlete works in the Console
145
+
146
+ The Console shares the same per-athlete stores as MCP, but scoping is
147
+ **server-owned, not model-owned**:
148
+
149
+ - The settings panel has an **Athlete** selector built from
150
+ `GET /api/v1/athletes` on the runtime. The default is the catalog's
151
+ `defaultAthleteId`.
152
+ - On every personal-data tool call, the Console **forces** the selected
153
+ athleteId onto the arguments — the model cannot name or switch athletes, and
154
+ a chat's system message states: *"This Console chat is scoped to athleteId X.
155
+ Every Catence data tool call is forced to that athlete; do not try to select
156
+ or compare another athlete."*
157
+ - The athlete choice is persisted per chat thread in the Console's
158
+ `console_preferences` table (`chat-history.sqlite3`), and the chat header
159
+ announces *"This chat is scoped to athlete **<id>**."*
160
+ - `list_athletes` is exempt from forcing so the roster can be rendered.
161
+
162
+ The dashboard is fetched through the authenticated Console origin: Chainlit
163
+ middleware proxies `GET /api/v1/dashboard` and `GET /api/v1/athletes` to the
164
+ runtime only when the Console's JWT cookie is valid (otherwise 401), so raw
165
+ port 8787 does not need to be exposed.
166
+
167
+ ## Model discovery (OpenCode Go)
168
+
169
+ OpenCode Go publishes an OpenAI-compatible API at
170
+ `https://opencode.ai/zen/go/v1`; its model list is public. The discovery
171
+ script fetches it and merges two ready-made profiles into `config.json`:
172
+ `opencode-go` (chat + responses models, base `…/zen/go/v1`) and
173
+ `opencode-go-messages` (messages models, base `…/zen/go`). Existing profiles,
174
+ limits, and `defaultProfile` are preserved unless `--set-default` is passed.
175
+
176
+ ```sh
177
+ # Local (from a checkout)
178
+ npm run discover:opencode-go -- --write ~/.catence/config.json
179
+ ```
180
+
181
+ Then set the credentials and verify:
182
+
183
+ ```sh
184
+ export OPENCODE_GO_API_KEY='…' # any non-empty value passes the key check
185
+ export OPENCODE_GO_API_BASE='https://opencode.ai/zen/go/v1'
186
+ export OPENCODE_GO_MESSAGES_API_BASE='https://opencode.ai/zen/go'
187
+ catence-console doctor
188
+ ```
189
+
190
+ The `openai/responses/…` models (for example `grok-4.5`) go through LiteLLM's
191
+ responses bridge and are the least battle-tested path; smoke-test one chat turn
192
+ before relying on them.
193
+
194
+ ## Updating
195
+
196
+ From `catence-data update`-capable releases (0.2.0-beta.3 and later), the
197
+ runtime updates both components on the tracked channel — beta installs follow
198
+ the npm `beta` tag and matching PyPI prereleases:
199
+
200
+ ```sh
201
+ catence-data update --check # report only; exit 1 when updates are pending
202
+ catence-data update # npm runtime + uv tool catence-console upgrade
203
+ catence-data update --channel stable # move off a beta onto the stable channel
204
+ ```
205
+
206
+ Older betas (for example a beta 2 install) predate the command; upgrade them
207
+ manually once:
208
+
209
+ ```sh
210
+ npm install --global catence@beta
211
+ uv tool install --upgrade catence-console
212
+ ```
213
+
214
+ ## Troubleshooting
215
+
216
+ - **"No Catence config exists"** — should not happen after a wizard run; ensure
217
+ `~/.catence/config.json` exists and contains a `console` section (copy it
218
+ from [`config.example.json`](../../config.example.json)).
219
+ - **"Refusing to initialize"** — the data home contains unrelated files; the
220
+ error lists them. Console artifacts (`config.json`, `console/`, and
221
+ Chainlit's `.files/`, `.chainlit/`, `public/`) are allowed; anything else
222
+ blocks `setup`.
223
+ - **Settings panel has no Model dropdown** — the Console loads profiles from
224
+ `config.json`; write one via the wizard, a manual copy, or the discovery
225
+ script.
226
+ - **Profile not ready** — `catence-console doctor` lists the missing
227
+ environment variables; set them in the shell and restart the Console.
228
+ - **Runtime not reachable** — `catence-console doctor --home "$HOME/.catence"
229
+ --mcp-url http://127.0.0.1:8787/mcp` checks the handshake; confirm the
230
+ runtime is running on that port and the protocol versions agree.
@@ -0,0 +1,304 @@
1
+ # Local MCP deployment
2
+
3
+ This guide runs the Catence MCP server on your machine: install the runtime,
4
+ create a catalog of athlete stores, sync provider data, and connect MCP clients
5
+ over stdio or Streamable HTTP. It does **not** set up the web Console — see
6
+ [`local-console.md`](local-console.md) for that.
7
+
8
+ ## Prerequisites
9
+
10
+ - Node.js 22+
11
+ - Python 3.12+ and [uv](https://docs.astral.sh/uv/) for the Garmin and Strava
12
+ provider workers
13
+ - Provider credentials for the athletes you choose to sync
14
+
15
+ The default catalog home is `~/.catence`. Set `CATENCE_HOME` or pass `--home`
16
+ to use a different location.
17
+
18
+ ## 1. Install
19
+
20
+ ```sh
21
+ npm install --global catence@beta # or catence@latest for stable
22
+ ```
23
+
24
+ This installs two binaries:
25
+
26
+ - `catence` — the MCP server (`catence` starts stdio, `catence serve` starts
27
+ Streamable HTTP, `catence demo` creates a generated store before starting
28
+ stdio).
29
+ - `catence-data` — catalog setup, athlete selection, per-athlete secrets,
30
+ `sync`, `backfill`, provider authentication, retrieval-index rebuilding, and
31
+ `demo` generation.
32
+
33
+ ## 2. Create the catalog and first athlete
34
+
35
+ ```sh
36
+ catence-data setup --athlete alex --label "Alex"
37
+ ```
38
+
39
+ This creates `~/.catence/` (or `$CATENCE_HOME`) with a catalog and the first
40
+ athlete store. It also tolerates a directory that already holds only Console
41
+ artifacts (`config.json`, `console/`).
42
+
43
+ ## 3. Store provider credentials (stdin only, never shell history)
44
+
45
+ Provider values are written to the selected athlete's owner-only local secret
46
+ file (`secrets/providers.json`, mode 0600). Read them through stdin so they do
47
+ not enter shell history:
48
+
49
+ ```sh
50
+ # Garmin
51
+ printf %s 'alex@example.com' | catence-data --athlete alex secret set --provider garmin --field email --value-stdin
52
+ printf %s 'your-garmin-password' | catence-data --athlete alex secret set --provider garmin --field password --value-stdin
53
+ # Intervals.icu
54
+ printf %s 'intervals-api-key' | catence-data --athlete alex secret set --provider intervals --field apiKey --value-stdin
55
+ printf %s '12345' | catence-data --athlete alex secret set --provider intervals --field athleteId --value-stdin
56
+ # Strava (create an API application at https://www.strava.com/settings/api)
57
+ printf %s 'strava-client-id' | catence-data --athlete alex secret set --provider strava --field clientId --value-stdin
58
+ printf %s 'strava-client-secret' | catence-data --athlete alex secret set --provider strava --field clientSecret --value-stdin
59
+ ```
60
+
61
+ Supported fields per provider: Garmin `email` and `password`; Intervals.icu
62
+ `apiKey` and `athleteId`; Strava `clientId` and `clientSecret`. Set only the
63
+ providers you actually sync; `secret set` accepts each field independently.
64
+
65
+ Secrets are injected into provider workers **only** from this per-athlete file;
66
+ any `GARMIN_*`/`INTERVALS_*`/`STRAVA_*` values inherited by the shared process
67
+ are deliberately stripped. An athlete that has not configured a provider
68
+ cannot silently inherit its credentials.
69
+
70
+ ## 4. Sync data and build retrieval context
71
+
72
+ ```sh
73
+ catence-data --athlete alex sync --provider all
74
+ catence-data --athlete alex build-retrieval-index
75
+ ```
76
+
77
+ ## 5. Start the stdio MCP server
78
+
79
+ ```sh
80
+ catence
81
+ ```
82
+
83
+ An agent first calls `list_athletes`, then includes `athleteId` with every
84
+ data tool. This is intentional: a shared agent may access the stores you
85
+ configured, but no tool implicitly aggregates or crosses between athletes.
86
+
87
+ ## Multi-athlete catalog
88
+
89
+ Catence is designed to serve **several isolated athlete stores** from one
90
+ shared server. Each store has its own DuckDB, raw data, and tokens, so
91
+ athletes never leak into each other.
92
+
93
+ ### MCP behavior
94
+
95
+ - `list_athletes` returns `{ defaultAthleteId, athletes: [{ id, label }] }`.
96
+ - Every personal-data tool in catalog mode requires an `athleteId` input (the
97
+ server auto-prepends it to every tool schema). Calls without it fail with
98
+ `athleteId is required. Call list_athletes to inspect the configured catalog.`
99
+ - The `catence://athletes` resource returns the same catalog.
100
+ - Rate limits are tracked **per athlete**: each athlete has its own sliding
101
+ window for server, tool, and resource limits.
102
+ - MCP responses carry an `athlete` field identifying which store served them.
103
+
104
+ ### Adding another athlete
105
+
106
+ ```sh
107
+ catence-data athlete add --id sam --label "Sam"
108
+ printf %s 'sam@example.com' | catence-data --athlete sam secret set --provider garmin --field email --value-stdin
109
+ printf %s 'sam-password' | catence-data --athlete sam secret set --provider garmin --field password --value-stdin
110
+ catence-data --athlete sam sync --provider garmin
111
+ catence-data athlete list
112
+ ```
113
+
114
+ The catalog layout is deliberately simple:
115
+
116
+ ```text
117
+ ~/.catence/
118
+ catalog.json
119
+ config.json # shared Console configuration; no secrets
120
+ console/chat-history.sqlite3
121
+ athletes/
122
+ alex/ # isolated DuckDB, raw data, and tokens
123
+ secrets/providers.json # mode 0600
124
+ sam/
125
+ ...
126
+ ```
127
+
128
+ Garmin, Intervals, and Strava client credentials are isolated per athlete.
129
+ Strava OAuth tokens remain in that athlete's own store. The old 0.1
130
+ single-store directory cannot be migrated safely: create a fresh home and
131
+ re-sync each athlete.
132
+
133
+ ## Common operations
134
+
135
+ ```sh
136
+ # Incremental or explicit data work for one athlete
137
+ catence-data --athlete alex status
138
+ catence-data --athlete alex sync --provider intervals
139
+ catence-data --athlete alex sync --provider garmin --from 2025-07-29
140
+ catence-data --athlete alex backfill --provider garmin --from 2020-01-01 --refresh
141
+ catence-data --athlete alex retry --run <run-id>
142
+ catence-data --athlete alex progress --watch # follow an active sync run live
143
+
144
+ # Strava OAuth uses clientId and clientSecret previously stored for this athlete
145
+ catence-data --athlete alex auth strava --callback
146
+ catence-data --athlete alex disconnect strava
147
+
148
+ # Update the runtime and Console together on the tracked release channel
149
+ catence-data update --check
150
+ catence-data update
151
+
152
+ # Choose a non-default catalog location
153
+ catence-data --home /srv/catence setup --athlete alex --label "Alex"
154
+ catence --home /srv/catence
155
+ ```
156
+
157
+ `catence-data update` follows the channel of the installed runtime: a beta
158
+ install tracks the npm `beta` tag and the matching PyPI prereleases, a stable
159
+ install tracks `latest`. Pass `--channel stable` to move off a beta. The
160
+ command replaces the global `catence` package and installs or upgrades
161
+ `catence-console` with `uv tool` when `uv` is available; `--check` only
162
+ reports.
163
+
164
+ Register `http://127.0.0.1:8765/strava/callback` with the Strava application
165
+ for the callback flow. The manual `auth strava --code <authorization-code>`
166
+ flow remains available for headless environments.
167
+
168
+ ## Background sync and live progress
169
+
170
+ Long Garmin backfills fetch hundreds of days of data and can take 20-30+
171
+ minutes. Sync runs stream worker output to the console in real time, tolerate
172
+ Ctrl+C and SSH/terminal deaths cleanly, and expose a live progress heartbeat
173
+ you can follow from the CLI, the MCP server, or a Docker helper.
174
+
175
+ ### What happens during a run
176
+
177
+ When a sync starts, Catence creates a row in `sync_runs` with status `running`
178
+ and begins persisting heartbeats to the `sync_run_progress` table (schema
179
+ migration 14). The Garmin staging worker emits one compact JSON progress record
180
+ every few seconds on stdout; the runtime parses it, normalizes it, and upserts
181
+ it. Even if the worker cannot emit — for example while stuck in login — the
182
+ runtime persists a 30-second fallback heartbeat so a run is always observable
183
+ and never falsely looks stale.
184
+
185
+ Each progress record follows the same contract:
186
+
187
+ - `runId`, `provider` — which run the record belongs to
188
+ - `stage` — `starting`, `login`, `singletons`, `daily`, `range`,
189
+ `ftp_history`, `max_metrics`, `hrv_history`, `scores`, `activities`,
190
+ `collections` while extracting; `completed` on success; `failed`,
191
+ `interrupted`, or `timed_out` as terminal states written by the runtime
192
+ - `currentStep` — the endpoint, date, or activity being fetched right now
193
+ - `completedUnits` / `totalUnits` — for example days fetched out of the total
194
+ days in the window
195
+ - `percentComplete` — 0 to 100
196
+ - `elapsedSeconds` / `estimatedRemainingSeconds` — the ETA is computed once the
197
+ run has a known total and has completed at least one unit
198
+ - `heartbeatAt` — UTC ISO timestamp of the record
199
+
200
+ #### Live progress never needs the database lock
201
+
202
+ DuckDB is a single-writer database: a running sync holds an exclusive lock on
203
+ the store file, so no other process — not even a read-only one — can open it
204
+ until the run finishes. To keep progress observable during a run, every
205
+ heartbeat is also written to a sidecar file at
206
+ `staging/garmin/<run-id>.progress.json` (atomic write, no database access).
207
+ `progress`, `catence_sync_progress`, and the MCP server read the sidecar files
208
+ for live runs and only touch the `sync_run_progress` table for recent-run
209
+ history once the lock is free — so they keep working while a backfill holds the
210
+ lock, and never crash with a lock error.
211
+
212
+ Other read-only tools (`status`, `query_read_only_data`, and friends) still
213
+ need the database and therefore return a clean `data_sync_in_progress` error
214
+ while a sync run is active. That is expected behavior: the store is locked
215
+ until the run completes, then they are served from the same snapshot.
216
+
217
+ ### Interruption and stale-run recovery
218
+
219
+ - SIGINT, SIGTERM, and SIGHUP (Ctrl+C, terminal close, `kill`) are trapped on
220
+ both the Node runtime and the Python worker. The child stops between days and
221
+ the run is marked `interrupted` in `sync_runs` — never a zombie `running`
222
+ row — and the process exits with 130/143/129.
223
+ - Before every sync or retry, runs still stuck in `running` for more than 15
224
+ minutes are marked `timed_out` (with `completed_at` set), so a dead SSH
225
+ session cannot block later syncs.
226
+ - Resume a failed or interrupted run without re-reading history with
227
+ `retry --run <run-id>`.
228
+
229
+ ### Watching progress
230
+
231
+ ```sh
232
+ catence-data --athlete alex progress # one-shot JSON snapshot
233
+ catence-data --athlete alex progress --watch # live view; exits when no run is active
234
+ ```
235
+
236
+ `--watch` renders a compact progress table on a TTY and exits when the last
237
+ active run finishes; piped output stays JSON.
238
+
239
+ The read-only MCP tool `catence_sync_progress` returns the same snapshot —
240
+ active runs with their latest heartbeat plus the most recent runs — and takes
241
+ `athleteId` like every other personal-data tool.
242
+
243
+ ## Safe demo and Glama
244
+
245
+ Run a clearly marked generated dataset without provider accounts:
246
+
247
+ ```sh
248
+ npx --yes catence@beta demo
249
+ ```
250
+
251
+ It creates or reuses `~/.catence-demo` with one `demo` athlete. The generated
252
+ data has explicit caveats in every tool response and never contains personal
253
+ measurements. The Glama registry entry uses this command for **Try in
254
+ Browser**, so the hosted sandbox has no access to local credentials or
255
+ personal data.
256
+
257
+ From a checkout:
258
+
259
+ ```sh
260
+ npm run mcp -- demo
261
+ ```
262
+
263
+ `catence-data demo` and `catence demo` both use `~/.catence-demo` by default
264
+ and refuse to modify an existing directory unless it contains the Catence demo
265
+ marker.
266
+
267
+ ## MCP clients and HTTP
268
+
269
+ For a packaged installation, point a client at `catence`:
270
+
271
+ ```sh
272
+ codex mcp add catence -- catence
273
+ claude mcp add --transport stdio catence -- catence
274
+ ```
275
+
276
+ For a source checkout:
277
+
278
+ ```sh
279
+ codex mcp add catence -- npm --prefix /absolute/path/to/catence run mcp
280
+ ```
281
+
282
+ ### Streamable HTTP server
283
+
284
+ Optional local Streamable HTTP MCP and dashboard APIs:
285
+
286
+ ```sh
287
+ catence serve --host 127.0.0.1 --port 8787
288
+ ```
289
+
290
+ By default it is loopback-only. `CATENCE_HTTP_HOST` and `CATENCE_HTTP_PORT`
291
+ override the host and port when the flags are omitted.
292
+
293
+ - `GET /api/v1/athletes` returns IDs and labels only.
294
+ - `GET /api/v1/dashboard` requires `athleteId`, for example
295
+ `http://127.0.0.1:8787/api/v1/dashboard?athleteId=alex&days=28` (`endDate`
296
+ is an optional `YYYY-MM-DD`; `days` ranges 1-90 and defaults to 28).
297
+ - Browser origins must be listed with `--allow-origin` (repeatable). The
298
+ packaged Console instead proxies the dashboard through its authenticated
299
+ same-origin route.
300
+
301
+ Connect HTTP-based clients to `http://127.0.0.1:8787/mcp`. There is **no
302
+ authentication on the MCP server itself** — it trusts the caller's `athleteId`
303
+ parameter — so keep it on loopback or restrict it at the network layer. For
304
+ remote access over SSH: `ssh -L 8787:127.0.0.1:8787 server`.
@@ -0,0 +1,58 @@
1
+ # Distribution and release readiness
2
+
3
+ Catence now has a repeatable distribution path.
4
+
5
+ ## Artifacts
6
+
7
+ | Surface | Source | Consumer path |
8
+ | --- | --- | --- |
9
+ | npm | `package.json` and compiled `dist/` | `npm install --global catence` or `npx --yes catence@<version> demo` |
10
+ | PyPI Console | `console/pyproject.toml` | `uvx catence-console@<python-version> serve`; it starts the matching npm runtime unless `--mcp-url` is supplied |
11
+ | PyPI UI runtime | `catence-ui` fork | `catence-chainlit==<python-version>` provides the prebuilt maintained Chainlit frontend used by Console |
12
+ | APM | `apm.yml` | `apm install rifusaki/catence#v<version>` configures normal and generated-demo MCP entries |
13
+ | MCPB | `mcpb/manifest.json` | Attach the platform-specific `catence-demo-<version>-<platform>-<arch>.mcpb` release asset in an MCPB-capable desktop client |
14
+
15
+ The package exposes two binaries:
16
+
17
+ - `catence` starts the stdio MCP server, `catence serve` starts the loopback Streamable HTTP server, and `catence demo` creates a generated store before starting stdio.
18
+ - `catence-data` manages catalog `setup`, athlete selection, per-athlete secrets,
19
+ `sync`, `backfill`, provider authentication, retrieval-index rebuilding, and
20
+ `demo` generation.
21
+
22
+ `catence-data demo` and `catence demo` use `~/.catence-demo` by default. Both
23
+ commands refuse to modify an existing directory unless it contains the Catence
24
+ demo marker. Generated data is marked in every MCP tool response.
25
+
26
+ ## Release gates
27
+
28
+ `release/manifest.json` maps the npm version to its PEP 440 Console and
29
+ Chainlit versions. The release check requires those fields and the protocol
30
+ version to agree with the npm package, Console package, runtime health
31
+ contract, APM manifest, and MCPB manifest. When invoked for a Git tag, it also
32
+ requires the tag to be `v<package-version>`.
33
+
34
+ The verification workflow runs TypeScript checking, a moderate-or-higher npm dependency audit, all tests, a npm-pack dry run, and MCPB-manifest validation. The tag workflow then:
35
+
36
+ 1. builds an MCPB on Linux, macOS, and Windows, with the bundle manifest restricted to its native platform because DuckDB includes a native binding;
37
+ 2. publishes npm with provenance;
38
+ 3. validates that the versioned APM manifest installs the two intended MCP entries in a clean consumer root; and
39
+ 4. builds and publishes the matching `catence-console` wheel after confirming that the manifest's exact `catence-chainlit` version is already available; and
40
+ 5. uploads the MCPB artifacts to the GitHub release. The tagged source tree is itself the APM package.
41
+
42
+ The `release` GitHub environment is intentional: publishing waits for that environment's approval rules. Configure npm trusted publishing for `rifusaki/catence` before creating the first `v0.1.0` tag. Its publish job upgrades to npm 11.5.1 or newer and deliberately avoids `setup-node`'s registry authentication so npm uses the GitHub Actions OIDC identity. Before releasing the two Python artifacts, configure PyPI Trusted Publishing for the `pypi` environment in both the maintained `catence-ui` fork (`catence-chainlit`) and this repository (`catence-console`).
43
+
44
+ For beta tags, the dedicated `Beta` workflow publishes npm with the `beta`
45
+ dist-tag and creates a GitHub prerelease after the corresponding Chainlit beta
46
+ is available. The server procedure and acceptance criteria are in
47
+ [release/beta-checklist.md](../release/beta-checklist.md).
48
+
49
+ ## Current operational gaps
50
+
51
+ - The npm package name is available at the time this document was written, but it is not claimed until the first successful npm publish.
52
+ - The MCPB demo bundle includes Node dependencies for its target platform. It is intentionally a generated-data experience; live Garmin/Intervals/Strava setup remains explicit so a desktop extension never receives provider credentials by surprise.
53
+ - Live Garmin and Strava extraction still require Python 3.12 and `uv`; a future fully bundled provider runtime would remove that prerequisite.
54
+ - MCPB artifacts are unsigned until a production code-signing certificate is configured. MCPB-capable clients may show an unsigned-extension warning.
55
+ - `catence-chainlit` must be released from the maintained UI fork before each matching Catence tag. Until the first public wheel exists, Console source development uses a locally built wheel.
56
+ - CI produces native MCPB assets for the standard GitHub runner architectures. ARM Linux and Windows-on-ARM need additional runners before assets can be advertised for those targets.
57
+
58
+ These gaps are product/release work, not silent runtime fallbacks: the npm and APM paths make their assumptions explicit, and the demo stays synthetic until the user runs the separate live sync workflow.