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,243 @@
1
+ # Catence configuration reference
2
+
3
+ This is the complete schema for `config.json` in the data home
4
+ (`~/.catence/config.json` locally, `/data/config.json` in Docker). It is
5
+ validated by the runtime before every run: unknown fields or invalid values
6
+ produce an `Invalid Catence config at <path>: …` error. A missing or empty
7
+ file is treated as `{}` (no rate limits, no budget, no Console profiles).
8
+
9
+ The file is shared between the runtime (MCP rate limits, Strava budget) and
10
+ the Console (model profiles and limits). It deliberately contains **no
11
+ credential values** — only the *names* of environment variables that hold the
12
+ values. See [`config.example.json`](../config.example.json) for a complete
13
+ worked example and [`llm-providers.md`](llm-providers.md) for the provider
14
+ profile how-to.
15
+
16
+ ## Top-level sections
17
+
18
+ ```jsonc
19
+ {
20
+ "mcp": { "rateLimits": { … } }, // optional
21
+ "providers": { "strava": { "budget": { … } } }, // optional
22
+ "console": { "defaultProfile": …, "limits": { … }, "profiles": { … } } // optional
23
+ }
24
+ ```
25
+
26
+ Every section is optional and strict: the schema rejects unknown top-level
27
+ keys and unknown keys inside each section.
28
+
29
+ ## `mcp.rateLimits`
30
+
31
+ Limits the MCP server's sliding-window limiter. Each entry is
32
+ `{ "requests": <positive int>, "windowSeconds": <positive int> }` or `null`.
33
+
34
+ ```jsonc
35
+ {
36
+ "mcp": {
37
+ "rateLimits": {
38
+ "server": { "requests": 240, "windowSeconds": 60 },
39
+ "tools": {
40
+ "*": { "requests": 60, "windowSeconds": 60 },
41
+ "hydrate_strava_activity": { "requests": 6, "windowSeconds": 60 },
42
+ "hydrate_recent_strava_activities": { "requests": 1, "windowSeconds": 60 },
43
+ "hydrate_strava_segment_history": { "requests": 4, "windowSeconds": 60 }
44
+ },
45
+ "resources": {
46
+ "*": { "requests": 120, "windowSeconds": 60 }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ - `tools` keys are tool names; `resources` keys are resource names; `*` is the
54
+ catch-all default for that kind.
55
+ - A specific entry overrides `*`, which overrides `server`.
56
+ - **`null` disables the limit** for that specific tool/resource name (or for
57
+ the whole server when set on `server`). This is the escape hatch for
58
+ long-running hydration tools.
59
+ - Limits are enforced **per athlete**: each athlete gets its own window
60
+ (keyed `athleteId:tool:<name>` / `athleteId:resource:<name>`).
61
+
62
+ ## `providers.strava.budget`
63
+
64
+ Throttles Strava API usage during sync and hydration.
65
+
66
+ ```jsonc
67
+ {
68
+ "providers": {
69
+ "strava": {
70
+ "budget": {
71
+ "maxConcurrentRequests": 1,
72
+ "readRequestsPer15Minutes": 80,
73
+ "readRequestsPerDay": 800
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ - `readRequestsPer15Minutes` and `readRequestsPerDay` are enforced: a sync or
81
+ hydration throws a retryable `rate_limited` / `StravaRateLimitError` when
82
+ the rolling usage reaches either threshold, and when Strava itself reports a
83
+ `blocked_until` timestamp.
84
+ - `maxConcurrentRequests` is validated and documented but **not yet enforced**
85
+ by the current sync/hydration worker (hydration already runs serially); it
86
+ is reserved for a future concurrent worker. Set each value to `null` to
87
+ disable that particular check.
88
+
89
+ ## `console`
90
+
91
+ The Console model configuration. `defaultProfile` must name one of
92
+ `profiles`.
93
+
94
+ ```jsonc
95
+ {
96
+ "console": {
97
+ "defaultProfile": "openai",
98
+ "limits": { "toolRounds": 8, "toolResultCharacters": 24000 },
99
+ "profiles": { … }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### `console.limits`
105
+
106
+ | Field | Default | Range |
107
+ | --- | --- | --- |
108
+ | `toolRounds` | 8 | 1–32 — max model tool-calling iterations per chat turn |
109
+ | `toolResultCharacters` | 24000 | 1,000–250,000 — max characters of a tool result passed to the model (truncated past the limit) |
110
+
111
+ ### `console.profiles`
112
+
113
+ Each profile holds one set of credentials and one or more model deployments:
114
+
115
+ ```jsonc
116
+ "profiles": {
117
+ "openai": {
118
+ "label": "OpenAI",
119
+ "model": "openai/gpt-5-mini",
120
+ "apiKeyEnv": "OPENAI_API_KEY"
121
+ }
122
+ }
123
+ ```
124
+
125
+ Allowed profile fields (strict):
126
+
127
+ | Field | Required | Meaning |
128
+ | --- | --- | --- |
129
+ | `label` | — | Display name in the Model dropdown |
130
+ | `model` | exactly one of `model`/`models` | Single LiteLLM model name, e.g. `openai/gpt-5-mini` |
131
+ | `models` | exactly one of `model`/`models` | Map of deployment id → per-model object for multiple deployments |
132
+ | `defaultModel` | with `models` | Which deployment id is preselected; must name one of `models` (defaults to the first) |
133
+ | `defaultReasoningEffort` | — | One of `minimal`, `low`, `medium`, `high`, `xhigh` |
134
+ | `apiKeyEnv` | — | Name of the env var holding the API key |
135
+ | `apiBaseEnv` | — | Name of the env var holding the base URL (OpenAI-compatible endpoints) |
136
+ | `apiVersionEnv` | — | Name of the env var holding an API version, for endpoints that require one |
137
+
138
+ Every `*Env` value must be a valid uppercase environment-variable name
139
+ (`/^[A-Z][A-Z0-9_]*$/`); a raw secret there is rejected. Credentials are read
140
+ from the environment only, never persisted.
141
+
142
+ Per-model objects (under `models`):
143
+
144
+ | Field | Meaning |
145
+ | --- | --- |
146
+ | `label` | Display name |
147
+ | `model` | LiteLLM model name, e.g. `openai/deepseek-v4-flash-free` |
148
+ | `reasoningEffort` | Fixed reasoning-effort level for every request |
149
+ | `variants` | Map of dropdown label → exact value sent to the provider (non-OpenAI schemes); `{}` disables reasoning effort for the model |
150
+
151
+ Validation rules:
152
+
153
+ - A profile must define **exactly one** of `model` or `models`.
154
+ - `defaultModel` requires `models` and must name one of its keys.
155
+ - `defaultReasoningEffort` must be one of the OpenAI-standard levels.
156
+ - A model cannot combine `variants: {}` with a fixed `reasoningEffort`.
157
+ - `console.profiles` must be non-empty; `models` must be non-empty.
158
+
159
+ Precedence for reasoning effort: per-chat dropdown selection > model
160
+ `reasoningEffort` > profile `defaultReasoningEffort` > provider default. See
161
+ [`llm-providers.md`](llm-providers.md#reasoning-effort) for the full behavior.
162
+
163
+ The Console's **Models** page edits this section in place (enable/disable,
164
+ add custom variants, remove, set defaults). Disabled choices are stored in the
165
+ Console database rather than here; every write preserves all non-console
166
+ sections and is re-validated with the same strict parser before the file is
167
+ replaced.
168
+
169
+ ### The Console wizard
170
+
171
+ On the first authenticated chat, the Console wizard (`write_provider_setup`)
172
+ offers presets for `openai-compatible`, `openai`, or `anthropic`. It writes a
173
+ single-profile `console` section and **replaces the whole `console` section**,
174
+ so re-running it overwrites any hand-edited profiles. The discovery scripts
175
+ (`discover:opencode-go`, `discover:opencode-zen`) instead **merge** their
176
+ profiles and preserve existing ones, limits, and `defaultProfile` unless
177
+ `--set-default` is passed.
178
+
179
+ ## Environment variables
180
+
181
+ ### Catalog home and runtime
182
+
183
+ | Variable | Purpose |
184
+ | --- | --- |
185
+ | `CATENCE_HOME` | Catalog home (default `~/.catence`); equivalent to `--home` |
186
+ | `CATENCE_HTTP_HOST` / `CATENCE_HTTP_PORT` | Defaults for `catence serve --host/--port` (host `127.0.0.1`, port `8787`) |
187
+
188
+ Provider credentials are normally stored per athlete with
189
+ `catence-data secret set` (see [`local-mcp.md`](deployment/local-mcp.md#3-store-provider-credentials-stdin-only-never-shell-history)); those secrets are injected
190
+ into the provider worker environment, and inherited `GARMIN_*`/`INTERVALS_*`/
191
+ `STRAVA_*` variables are stripped first so an athlete without a provider cannot
192
+ inherit its credentials.
193
+
194
+ For local and Docker development you can also keep credentials in env without
195
+ `secret set` — see the two fallback modes below. File values always win over
196
+ env, so a `providers.json` entry overrides any env fallback for that field.
197
+
198
+ ### Provider credentials via env (dev fallback)
199
+
200
+ Two opt-in env fallbacks are checked **only when a `providers.json` field is
201
+ missing** — file values always win. See `src/core/runtime/secrets.ts:51`.
202
+
203
+ | Mode | Variables | When allowed | Example for athlete `alex` |
204
+ |------|-----------|--------------|----------------------------|
205
+ | **Per-athlete scoped** (recommended, multi-athlete safe) | `CATENCE_ATHLETE_<ID>_<PROVIDER_VAR>` — prefix is `CATENCE_ATHLETE_` + upper-cased athlete id with `-` → `_` + `_` | **Always** — checked even when the generic flag is off. Only maps to that one athlete. | `CATENCE_ATHLETE_ALEX_GARMIN_EMAIL=alex@example.com` `CATENCE_ATHLETE_ALEX_INTERVALS_API_KEY=…` |
206
+ | **Generic** (single-athlete dev, opt-in) | `GARMIN_EMAIL`, `GARMIN_PASSWORD`, `INTERVALS_API_KEY`, `INTERVALS_ATHLETE_ID`, `STRAVA_CLIENT_ID`, `STRAVA_CLIENT_SECRET` | Only when `CATENCE_ALLOW_ENV_SECRETS` (or alias `CATENCE_SECRETS_FROM_ENV`) is `1`/`true`/`yes`/`on` in the worker's env. Without the flag, bare provider env vars are stripped. | `CATENCE_ALLOW_ENV_SECRETS=1` + `GARMIN_EMAIL=…` |
207
+
208
+ Resolution order per field: `providers.json` → `CATENCE_ATHLETE_<ID>_<VAR>` → (if flag set) `VAR`. Set `CATENCE_ALLOW_ENV_SECRETS=1` in `.env.dev` / `docker-compose.dev.yml` for single-athlete Docker dev, or prefer per-athlete vars for a shared catalog.
209
+
210
+ ### Console auth and serving
211
+
212
+ | Variable | Purpose |
213
+ | --- | --- |
214
+ | `CATENCE_CONSOLE_USERNAME` | Console login username (required) |
215
+ | `CATENCE_CONSOLE_PASSWORD_HASH` | bcrypt hash for the login password (required; `catence-console auth hash-password`) |
216
+ | `CHAINLIT_AUTH_SECRET` | JWT signing secret for the Console session (required) |
217
+ | `CATENCE_HOME` | Data home served by the Console (default `~/.catence`) |
218
+ | `CATENCE_MCP_URL` | MCP endpoint for the Console (default `http://127.0.0.1:8787/mcp`; when unset the Console auto-starts a runtime) |
219
+ | `CATENCE_CONSOLE_HOST` | Default for `--ui-host` (`127.0.0.1`) |
220
+
221
+ ### Model providers
222
+
223
+ Each env var name is referenced by a profile's `apiKeyEnv` / `apiBaseEnv` /
224
+ `apiVersionEnv`; see [`llm-providers.md`](llm-providers.md) for the full
225
+ provider guide. Common ones from [`config.example.json`](../config.example.json):
226
+
227
+ | Variable | Profile |
228
+ | --- | --- |
229
+ | `OPENAI_API_KEY`, `OPENAI_API_BASE`, `OPENAI_API_VERSION` | `openai`, `openai-compatible`, Azure, Opencode proxy |
230
+ | `ANTHROPIC_API_KEY` | `anthropic` |
231
+ | `OPENCODE_GO_API_KEY`, `OPENCODE_GO_API_BASE`, `OPENCODE_GO_MESSAGES_API_BASE` | `opencode-go`, `opencode-go-messages` |
232
+ | `OPENCODE_API_KEY`, `OPENCODE_ZEN_API_BASE`, `OPENCODE_ZEN_RESPONSES_API_BASE` | `opencode-zen`, `opencode-zen-responses` |
233
+
234
+ ### Docker (`.env`, generated by `deploy-console.sh`)
235
+
236
+ | Variable | Purpose |
237
+ | --- | --- |
238
+ | `CATENCE_NPM_VERSION` | npm package version to install in the image |
239
+ | `CATENCE_CONSOLE_VERSION` | PyPI `catence-console` version (PEP 440) |
240
+ | `CATENCE_CONSOLE_PORT` | Host port for the Console (default 8000) |
241
+ | `CATENCE_MCP_BIND` | MCP bind: `127.0.0.1` loopback (default) or `0.0.0.0` for network/Tailscale exposure; also drives the compose port mapping |
242
+
243
+ See [`docker.md`](deployment/docker.md) for the Docker workflow.