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.
- package/README.md +21 -217
- package/dist/contracts/progress.d.ts +1 -1
- package/dist/contracts/progress.js +1 -0
- package/dist/contracts/release.d.ts +1 -1
- package/dist/contracts/release.js +1 -1
- package/dist/core/query/repository.js +5 -1
- package/dist/elt/application/management.js +35 -28
- package/dist/elt/storage/progress-sidecar.d.ts +61 -0
- package/dist/elt/storage/progress-sidecar.js +180 -0
- package/docs/architecture.md +11 -1
- package/docs/configuration.md +221 -0
- package/docs/deployment/docker.md +528 -0
- package/docs/deployment/local-console.md +230 -0
- package/docs/deployment/local-mcp.md +308 -0
- package/docs/distribution.md +58 -0
- package/docs/mcp-activity-retrieval.md +37 -30
- package/package.json +1 -1
- package/docs/deployment.md +0 -338
- package/docs/docker-deployment.md +0 -232
|
@@ -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,308 @@
|
|
|
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
|
+
Progress also stays live through the entire run, not just worker extraction: a
|
|
213
|
+
30-second keep-alive covers the JSONL import phase (which emits no worker
|
|
214
|
+
output) and the `intervals` phase of a provider-all sync or backfill.
|
|
215
|
+
|
|
216
|
+
Other read-only tools (`status`, `query_read_only_data`, and friends) still
|
|
217
|
+
need the database and therefore return a clean `data_sync_in_progress` error
|
|
218
|
+
while a sync run is active. That is expected behavior: the store is locked
|
|
219
|
+
until the run completes, then they are served from the same snapshot.
|
|
220
|
+
|
|
221
|
+
### Interruption and stale-run recovery
|
|
222
|
+
|
|
223
|
+
- SIGINT, SIGTERM, and SIGHUP (Ctrl+C, terminal close, `kill`) are trapped on
|
|
224
|
+
both the Node runtime and the Python worker. The child stops between days and
|
|
225
|
+
the run is marked `interrupted` in `sync_runs` — never a zombie `running`
|
|
226
|
+
row — and the process exits with 130/143/129.
|
|
227
|
+
- Before every sync or retry, runs still stuck in `running` for more than 15
|
|
228
|
+
minutes are marked `timed_out` (with `completed_at` set), so a dead SSH
|
|
229
|
+
session cannot block later syncs.
|
|
230
|
+
- Resume a failed or interrupted run without re-reading history with
|
|
231
|
+
`retry --run <run-id>`.
|
|
232
|
+
|
|
233
|
+
### Watching progress
|
|
234
|
+
|
|
235
|
+
```sh
|
|
236
|
+
catence-data --athlete alex progress # one-shot JSON snapshot
|
|
237
|
+
catence-data --athlete alex progress --watch # live view; exits when no run is active
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`--watch` renders a compact progress table on a TTY and exits when the last
|
|
241
|
+
active run finishes; piped output stays JSON.
|
|
242
|
+
|
|
243
|
+
The read-only MCP tool `catence_sync_progress` returns the same snapshot —
|
|
244
|
+
active runs with their latest heartbeat plus the most recent runs — and takes
|
|
245
|
+
`athleteId` like every other personal-data tool.
|
|
246
|
+
|
|
247
|
+
## Safe demo and Glama
|
|
248
|
+
|
|
249
|
+
Run a clearly marked generated dataset without provider accounts:
|
|
250
|
+
|
|
251
|
+
```sh
|
|
252
|
+
npx --yes catence@beta demo
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
It creates or reuses `~/.catence-demo` with one `demo` athlete. The generated
|
|
256
|
+
data has explicit caveats in every tool response and never contains personal
|
|
257
|
+
measurements. The Glama registry entry uses this command for **Try in
|
|
258
|
+
Browser**, so the hosted sandbox has no access to local credentials or
|
|
259
|
+
personal data.
|
|
260
|
+
|
|
261
|
+
From a checkout:
|
|
262
|
+
|
|
263
|
+
```sh
|
|
264
|
+
npm run mcp -- demo
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
`catence-data demo` and `catence demo` both use `~/.catence-demo` by default
|
|
268
|
+
and refuse to modify an existing directory unless it contains the Catence demo
|
|
269
|
+
marker.
|
|
270
|
+
|
|
271
|
+
## MCP clients and HTTP
|
|
272
|
+
|
|
273
|
+
For a packaged installation, point a client at `catence`:
|
|
274
|
+
|
|
275
|
+
```sh
|
|
276
|
+
codex mcp add catence -- catence
|
|
277
|
+
claude mcp add --transport stdio catence -- catence
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
For a source checkout:
|
|
281
|
+
|
|
282
|
+
```sh
|
|
283
|
+
codex mcp add catence -- npm --prefix /absolute/path/to/catence run mcp
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Streamable HTTP server
|
|
287
|
+
|
|
288
|
+
Optional local Streamable HTTP MCP and dashboard APIs:
|
|
289
|
+
|
|
290
|
+
```sh
|
|
291
|
+
catence serve --host 127.0.0.1 --port 8787
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
By default it is loopback-only. `CATENCE_HTTP_HOST` and `CATENCE_HTTP_PORT`
|
|
295
|
+
override the host and port when the flags are omitted.
|
|
296
|
+
|
|
297
|
+
- `GET /api/v1/athletes` returns IDs and labels only.
|
|
298
|
+
- `GET /api/v1/dashboard` requires `athleteId`, for example
|
|
299
|
+
`http://127.0.0.1:8787/api/v1/dashboard?athleteId=alex&days=28` (`endDate`
|
|
300
|
+
is an optional `YYYY-MM-DD`; `days` ranges 1-90 and defaults to 28).
|
|
301
|
+
- Browser origins must be listed with `--allow-origin` (repeatable). The
|
|
302
|
+
packaged Console instead proxies the dashboard through its authenticated
|
|
303
|
+
same-origin route.
|
|
304
|
+
|
|
305
|
+
Connect HTTP-based clients to `http://127.0.0.1:8787/mcp`. There is **no
|
|
306
|
+
authentication on the MCP server itself** — it trusts the caller's `athleteId`
|
|
307
|
+
parameter — so keep it on loopback or restrict it at the network layer. For
|
|
308
|
+
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.
|