@wattsonclub/wattson-cli 0.1.0 → 0.1.1
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/CHANGELOG.md +11 -0
- package/README.md +118 -0
- package/dist/bin.js +366 -192
- package/package.json +8 -3
- package/skill/SKILL.md +9 -7
- package/skill/references/commands.md +197 -292
- package/skill/references/workout-building-blocks.md +266 -481
- package/skill/references/workout-text-format.md +71 -409
|
@@ -1,375 +1,280 @@
|
|
|
1
|
-
|
|
1
|
+
# Wattson CLI command reference
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Current offline workout/ride workflow. [Human guide](https://wattson.club/cli) · [Agent discovery](https://wattson.club/llms.txt).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install and authenticate
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- natural language belongs in the agent layer
|
|
9
|
-
- structured inputs belong in planner-api / planner-cli
|
|
10
|
-
- prefer deterministic request bodies over special `...from-prompt` endpoints
|
|
11
|
-
|
|
12
|
-
Operator defaults:
|
|
13
|
-
- use `wattson --help` to inspect the live command surface
|
|
14
|
-
- add `--json` when the output is meant for automation or another agent step
|
|
15
|
-
- add `--base-url <url>` when you are pointing at a non-default planner-api instance
|
|
16
|
-
|
|
17
|
-
## 0. Sanity-check the surface
|
|
7
|
+
Requires Node.js 22+. Shell examples use Bash; JSON transformations require `jq`.
|
|
18
8
|
|
|
19
9
|
```bash
|
|
10
|
+
npm install -g @wattsonclub/wattson-cli
|
|
11
|
+
wattson --help
|
|
20
12
|
wattson health
|
|
21
|
-
wattson
|
|
22
|
-
|
|
13
|
+
wattson auth login
|
|
14
|
+
# Alternatively, on SSH/server/container/agent runtime:
|
|
15
|
+
wattson auth login --remote
|
|
16
|
+
wattson --json auth status
|
|
17
|
+
wattson --json athlete profile
|
|
23
18
|
```
|
|
24
19
|
|
|
25
|
-
|
|
20
|
+
Choose one login flow. Local login uses OAuth authorization code + PKCE and browser callback `http://127.0.0.1:8976/oauth/callback`. Remote login uses device authorization: verification URL and short-lived code appear on stderr; the CLI waits for approval on the user's phone/computer. No inbound port or SSH tunnel is required. Both request `planner:read planner:write`; there is no scope-selection flag. `--timeout SECONDS` overrides the five-minute wait (remote authorization may expire sooner). Denial/timeout is not authentication. Deliver URLs/codes privately to the requester; never approve on their behalf, request passwords, or copy cookies/tokens.
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
wattson recommend daily athlete-1 \
|
|
29
|
-
--available-time-min 20 \
|
|
30
|
-
--prefer-objective vo2max \
|
|
31
|
-
--hard-constraint "indoor only"
|
|
32
|
-
```
|
|
22
|
+
API requests refresh on HTTP 401 and retry once, persisting replacement credentials. A failed refresh-token request removes saved state and requires login again. `auth status` checks authenticated bootstrap; signed-out status exits 1, including JSON mode. JSON auth output excludes tokens but includes identity metadata.
|
|
33
23
|
|
|
34
|
-
|
|
24
|
+
Credentials are unencrypted JSON, written atomically with file mode `0600`; newly created parent directories use `0700`. Unix default: `$XDG_STATE_HOME/trainer-planner/auth.json` or `~/.local/state/trainer-planner/auth.json`. Windows: `%LOCALAPPDATA%/trainer-planner/auth.json`, falling back under the user's AppData/Local. `WATTSON_AUTH_FILE`, then legacy `PLANNER_CLI_AUTH_FILE`, override the path. Keep it private and persistent; do not print/commit credentials or bake them into images. Stored credentials are matched to the API base URL.
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
wattson workout draft athlete-1 \
|
|
38
|
-
--objective vo2max \
|
|
39
|
-
--duration-min 20 \
|
|
40
|
-
--execution-mode erg \
|
|
41
|
-
--style-hint "3 high intensity intervals"
|
|
42
|
-
```
|
|
26
|
+
`wattson auth logout` attempts `/auth/logout` with saved refresh token and bearer credentials, then deletes the local file even if the server call fails. This is **not** a guarantee of OAuth-token revocation or logout of other sessions.
|
|
43
27
|
|
|
44
|
-
|
|
28
|
+
## Global behavior and supported families
|
|
45
29
|
|
|
46
|
-
-
|
|
47
|
-
- agent infers:
|
|
48
|
-
- objective: `vo2max`
|
|
49
|
-
- durationMin: `20`
|
|
50
|
-
- allowedExecutionModes: `["erg"]`
|
|
51
|
-
- styleHints: `["3 high intensity intervals"]`
|
|
52
|
-
- agent calls `workout draft`
|
|
30
|
+
Use `wattson [--base-url URL] [--json] COMMAND`. API precedence: `--base-url`, `WATTSON_API_BASE_URL`, legacy `PLANNER_API_BASE_URL`, default `https://api.wattson.club`. Override only for an intentionally selected environment. Omit optional athlete IDs for the signed-in athlete; explicit IDs are not permission to access others.
|
|
53
31
|
|
|
54
|
-
|
|
32
|
+
`--json` returns the API response, not a universal wrapper. Errors go to stderr; check exit codes. Successful transport for `workout validate` can exit 0 with `validation.status: "fail"`.
|
|
55
33
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
34
|
+
| Family | Supported commands |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Auth | `auth login [--remote] [--timeout SECONDS]`, `auth status`, `auth logout` |
|
|
37
|
+
| Service | `health` |
|
|
38
|
+
| Athlete | `athlete profile [athleteId]`, `athlete readiness latest [athleteId] [--as-of ISO]`, `athlete sessions [athleteId] [--from DATE] [--to DATE] [--limit N]` |
|
|
39
|
+
| Recommendation | `recommend daily [athleteId] --available-time-min N [--date DATE] [--prefer-objective OBJECTIVE] [--hard-constraint TEXT ...]` |
|
|
40
|
+
| Authoring | `workout draft`, `workout revise`, `workout validate`, `workout save` (below) |
|
|
41
|
+
| Workout reads | `workout get ID [--version N]`, `workout revisions ID`, `workout runtime-plan ID --version N` |
|
|
42
|
+
| Ride reads/exports | `session summary ID`, `session telemetry ID`, `session artifacts ID`, `session fit ID` |
|
|
43
|
+
| External publication | `session strava-sync ID` (authorized, asynchronous) |
|
|
44
|
+
| Library/calendar | `workout list [athleteId] [--limit N]`, `workout schedule ID --date YYYY-MM-DD`, `workout unschedule ID`, `workout retire ID` |
|
|
45
|
+
| Courses/discovery/sharing/research | See the full MCP mapping below; structured operations accept `--input-file PATH` or `--input-json JSON`. |
|
|
59
46
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```json
|
|
63
|
-
{
|
|
64
|
-
"athleteId": "athlete-1",
|
|
65
|
-
"objective": "vo2max",
|
|
66
|
-
"durationMin": 20,
|
|
67
|
-
"constraints": {
|
|
68
|
-
"allowedExecutionModes": ["erg"],
|
|
69
|
-
"maxPctFtp": 1.2
|
|
70
|
-
},
|
|
71
|
-
"context": {
|
|
72
|
-
"styleHints": ["3 high intensity intervals"]
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
```
|
|
47
|
+
The CLI covers the current MCP action and research tools. Strava account connection remains in the app, not in either tool surface. Strava availability depends on server access mode. Course imports return a course profile; they do not save or schedule a workout.
|
|
76
48
|
|
|
77
|
-
|
|
78
|
-
- FTP-relative values are fractional in structured payloads
|
|
79
|
-
- `maxPctFtp: 1.2` means `120% FTP`
|
|
80
|
-
- `target: { "mode": "pct_ftp", "value": 0.55 }` means `55% FTP`
|
|
81
|
-
- never pass whole percentages like `120` or `55`
|
|
49
|
+
## Inspect, draft, validate, save, verify
|
|
82
50
|
|
|
83
|
-
|
|
51
|
+
Read profile/readiness before choosing targets. Recommendations are advice, not saved workouts.
|
|
84
52
|
|
|
85
53
|
```bash
|
|
86
|
-
wattson
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Example `/tmp/revise-workout.json`:
|
|
90
|
-
|
|
91
|
-
```json
|
|
92
|
-
{
|
|
93
|
-
"patch": {
|
|
94
|
-
"workoutId": "workout-1",
|
|
95
|
-
"baseVersion": 2,
|
|
96
|
-
"preserve": ["objective", "interval_structure"],
|
|
97
|
-
"changes": {
|
|
98
|
-
"durationDeltaSec": -120,
|
|
99
|
-
"intensityMultiplier": 0.95,
|
|
100
|
-
"addCadenceFocus": false,
|
|
101
|
-
"removeLastInterval": false,
|
|
102
|
-
"switchToRecovery": false
|
|
103
|
-
},
|
|
104
|
-
"naturalLanguage": "Keep the same shape, just make it slightly easier and a bit shorter.",
|
|
105
|
-
"reason": "Rider requested a lighter version."
|
|
106
|
-
}
|
|
107
|
-
}
|
|
54
|
+
wattson --json athlete profile
|
|
55
|
+
wattson --json athlete readiness latest
|
|
56
|
+
wattson --json recommend daily --available-time-min 20 --prefer-objective vo2max
|
|
108
57
|
```
|
|
109
58
|
|
|
110
|
-
This
|
|
111
|
-
- user says: `make it slightly easier`
|
|
112
|
-
- agent chooses a structured patch like:
|
|
113
|
-
- `intensityMultiplier: 0.95`
|
|
114
|
-
- maybe preserve objective / interval structure
|
|
115
|
-
- include the original natural-language request as trace context when helpful
|
|
116
|
-
|
|
117
|
-
## 5. Validate a workout before saving
|
|
59
|
+
Run in a private working directory, signed in, for a request authorizing library creation. This sequence stops on errors and requires validation `pass` before constructing the save request. Review the draft and validation issues before the save step.
|
|
118
60
|
|
|
119
61
|
```bash
|
|
120
|
-
|
|
62
|
+
set -euo pipefail
|
|
63
|
+
umask 077
|
|
64
|
+
wattson --json workout draft --objective vo2max --duration-min 20 --execution-mode erg --style-hint "3 hard intervals" > draft.json
|
|
65
|
+
# Draft output: {workout, validationPreview?}. Validate input: {workout}.
|
|
66
|
+
jq -e '{workout: .workout} | select(.workout != null)' draft.json > validate.json
|
|
67
|
+
wattson --json workout validate --input-file validate.json > validation.json
|
|
68
|
+
jq -e '.validation.status == "pass"' validation.json >/dev/null
|
|
69
|
+
# Validation output has no workout. Preserve the original definition.
|
|
70
|
+
jq -e '{workout: .workout, expectedValidationStatus: "pass"}' validate.json > save.json
|
|
71
|
+
wattson --json workout save --input-file save.json > saved.json
|
|
72
|
+
workout_id=$(jq -er '.workoutId' saved.json)
|
|
73
|
+
workout_version=$(jq -er '.version' saved.json)
|
|
74
|
+
wattson --json workout get "$workout_id" --version "$workout_version"
|
|
75
|
+
wattson --json workout runtime-plan "$workout_id" --version "$workout_version"
|
|
121
76
|
```
|
|
122
77
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
```json
|
|
126
|
-
{
|
|
127
|
-
"workout": {
|
|
128
|
-
"id": "workout-1",
|
|
129
|
-
"version": 2,
|
|
130
|
-
"createdAt": "2026-04-09T00:00:00.000Z",
|
|
131
|
-
"updatedAt": "2026-04-09T00:00:00.000Z",
|
|
132
|
-
"createdBy": { "type": "system", "system": "planner_api" },
|
|
133
|
-
"updatedBy": { "type": "system", "system": "planner_api" },
|
|
134
|
-
"kind": "workout_definition",
|
|
135
|
-
"athleteId": "athlete-1",
|
|
136
|
-
"title": "VO2 20",
|
|
137
|
-
"objective": "vo2max",
|
|
138
|
-
"sport": "cycling_indoor",
|
|
139
|
-
"status": "draft",
|
|
140
|
-
"athleteContext": {
|
|
141
|
-
"ftpWatts": 250,
|
|
142
|
-
"constraints": []
|
|
143
|
-
},
|
|
144
|
-
"blocks": [
|
|
145
|
-
{
|
|
146
|
-
"id": "block-1",
|
|
147
|
-
"type": "steady",
|
|
148
|
-
"durationSec": 300,
|
|
149
|
-
"target": { "mode": "pct_ftp", "value": 0.55 },
|
|
150
|
-
"instructions": ["Warm up"]
|
|
151
|
-
}
|
|
152
|
-
],
|
|
153
|
-
"constraints": {
|
|
154
|
-
"allowedExecutionModes": ["erg"]
|
|
155
|
-
},
|
|
156
|
-
"rationale": {
|
|
157
|
-
"summary": "Short VO2-focused session",
|
|
158
|
-
"whyToday": ["Fits available time"]
|
|
159
|
-
},
|
|
160
|
-
"tags": ["vo2", "short"]
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
```
|
|
78
|
+
For `warn`, review the issues and deliberately use the observed expected status only if acceptable; for `fail`, fix and revalidate. Never merely relabel a workout as validated. Save recomputes validation: `expectedValidationStatus` is a conflict guard. Passing saves persist as `validated` even if input status was `draft`. Save returns `{workoutId, version, status}`, not `{workout}`.
|
|
164
79
|
|
|
165
|
-
|
|
80
|
+
Draft flags also support repeatable `--execution-mode` and `--style-hint`, `--max-pct-ftp`, and `--readiness-snapshot-id`. Structured draft/revise/validate/save input uses **one** of `--input-file` or `--input-json`:
|
|
166
81
|
|
|
167
82
|
```bash
|
|
168
|
-
wattson workout
|
|
83
|
+
wattson --json workout draft --input-json '{"objective":"vo2max","durationMin":20,"constraints":{"allowedExecutionModes":["erg"],"maxPctFtp":1.2},"context":{"styleHints":["3 hard intervals"]}}'
|
|
169
84
|
```
|
|
170
85
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
```json
|
|
174
|
-
{
|
|
175
|
-
"workout": {
|
|
176
|
-
"id": "workout-1",
|
|
177
|
-
"version": 2,
|
|
178
|
-
"createdAt": "2026-04-09T00:00:00.000Z",
|
|
179
|
-
"updatedAt": "2026-04-09T00:00:00.000Z",
|
|
180
|
-
"createdBy": { "type": "system", "system": "planner_api" },
|
|
181
|
-
"updatedBy": { "type": "system", "system": "planner_api" },
|
|
182
|
-
"kind": "workout_definition",
|
|
183
|
-
"athleteId": "athlete-1",
|
|
184
|
-
"title": "VO2 20",
|
|
185
|
-
"objective": "vo2max",
|
|
186
|
-
"sport": "cycling_indoor",
|
|
187
|
-
"status": "validated",
|
|
188
|
-
"athleteContext": {
|
|
189
|
-
"ftpWatts": 250,
|
|
190
|
-
"constraints": []
|
|
191
|
-
},
|
|
192
|
-
"blocks": [
|
|
193
|
-
{
|
|
194
|
-
"id": "block-1",
|
|
195
|
-
"type": "steady",
|
|
196
|
-
"durationSec": 300,
|
|
197
|
-
"target": { "mode": "pct_ftp", "value": 0.55 },
|
|
198
|
-
"instructions": ["Warm up"]
|
|
199
|
-
}
|
|
200
|
-
],
|
|
201
|
-
"constraints": {
|
|
202
|
-
"allowedExecutionModes": ["erg"]
|
|
203
|
-
},
|
|
204
|
-
"rationale": {
|
|
205
|
-
"summary": "Short VO2-focused session",
|
|
206
|
-
"whyToday": ["Fits available time"]
|
|
207
|
-
},
|
|
208
|
-
"tags": ["vo2", "short"]
|
|
209
|
-
},
|
|
210
|
-
"expectedValidationStatus": "pass"
|
|
211
|
-
}
|
|
212
|
-
```
|
|
86
|
+
Signed-in structured drafts may omit `athleteId`. FTP values are fractions: `1.2` = 120%, `0.55` = 55%. See [building blocks](workout-building-blocks.md) and [workout text](workout-text-format.md). Preserve returned IDs, version, attribution, athlete context and blocks; don't invent a partial workout definition.
|
|
213
87
|
|
|
214
|
-
|
|
215
|
-
- use fractional values like `0.55` and `1.2`
|
|
216
|
-
- do not send whole percentages like `55` or `120`
|
|
88
|
+
### Revise a saved version
|
|
217
89
|
|
|
218
|
-
|
|
90
|
+
Using `saved.json` above, only for an authorized edit (retain `set -euo pipefail`):
|
|
219
91
|
|
|
220
92
|
```bash
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
93
|
+
jq '{patch: {workoutId: .workoutId, baseVersion: .version, preserve: ["objective", "interval_structure"], changes: {intensityMultiplier: 0.95}, reason: "Rider requested a slightly easier version."}}' saved.json > revise.json
|
|
94
|
+
wattson --json workout revise --input-file revise.json > revised.json
|
|
95
|
+
jq -e '{workout: .workout} | select(.workout != null)' revised.json > validate.json
|
|
96
|
+
wattson --json workout validate --input-file validate.json > validation.json
|
|
97
|
+
jq -e '.validation.status == "pass"' validation.json >/dev/null
|
|
98
|
+
jq --slurpfile prior saved.json '{workout: .workout, expectedValidationStatus: "pass", expectedLatestVersion: $prior[0].version}' validate.json > save.json
|
|
99
|
+
wattson --json workout save --input-file save.json > revised-saved.json
|
|
100
|
+
wattson --json workout get "$(jq -er '.workoutId' revised-saved.json)" --version "$(jq -er '.version' revised-saved.json)"
|
|
101
|
+
wattson --json workout revisions "$(jq -er '.workoutId' revised-saved.json)"
|
|
225
102
|
```
|
|
226
103
|
|
|
227
|
-
|
|
228
|
-
|
|
104
|
+
`revise` returns `{workout, diff}`; it does not save. `expectedLatestVersion` guards concurrent edits; on conflict, fetch latest and reconsider. Other patch changes: `newTitle`, `newObjective`, `durationDeltaSec`, `addCadenceFocus`, `removeLastInterval`, `switchToRecovery`. Translate natural language into structured intent.
|
|
105
|
+
|
|
106
|
+
## Offline app and uploaded rides
|
|
229
107
|
|
|
230
|
-
|
|
108
|
+
Report **saved to your library, ready for the app to download**. The app downloads workouts; the rider selects and executes them locally. A suspended/offline app syncs when online again. CLI success is not device acknowledgement, ride start or completion. Never fabricate a session to make a workflow look finished.
|
|
231
109
|
|
|
232
110
|
```bash
|
|
233
|
-
wattson
|
|
111
|
+
wattson --json athlete sessions --limit 10
|
|
112
|
+
# Replace SESSION_ID with a real uploaded session ID.
|
|
113
|
+
wattson --json session summary SESSION_ID
|
|
114
|
+
wattson --json session telemetry SESSION_ID
|
|
115
|
+
wattson --json session artifacts SESSION_ID
|
|
234
116
|
```
|
|
235
117
|
|
|
236
|
-
|
|
118
|
+
Telemetry can be `{ "telemetry": null }` when not persisted. Artifacts returns metadata without inline FIT bytes. Treat ride/health data as private; retrieve/export within the user's request.
|
|
237
119
|
|
|
238
|
-
|
|
239
|
-
wattson session load-workout \
|
|
240
|
-
--athlete-id athlete-1 \
|
|
241
|
-
--workout-id workout-1 \
|
|
242
|
-
--version 2
|
|
243
|
-
```
|
|
120
|
+
### FIT export
|
|
244
121
|
|
|
245
|
-
|
|
122
|
+
`session fit` returns `fitFile` with inline `contentBase64`, filename, MIME type, byte count and SHA-256. It does not write a FIT file. If export is requested:
|
|
246
123
|
|
|
247
124
|
```bash
|
|
248
|
-
wattson session
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
125
|
+
wattson --json session fit SESSION_ID > session-fit.json
|
|
126
|
+
node --input-type=module <<'NODE'
|
|
127
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
128
|
+
import { createHash } from 'node:crypto';
|
|
129
|
+
const { fitFile } = JSON.parse(readFileSync('session-fit.json', 'utf8'));
|
|
130
|
+
const bytes = Buffer.from(fitFile.contentBase64, 'base64');
|
|
131
|
+
if (bytes.length !== fitFile.sizeBytes || createHash('sha256').update(bytes).digest('hex') !== fitFile.checksumSha256.toLowerCase()) {
|
|
132
|
+
throw new Error('FIT size/checksum mismatch');
|
|
133
|
+
}
|
|
134
|
+
writeFileSync('ride.fit', bytes, { mode: 0o600, flag: 'wx' });
|
|
135
|
+
NODE
|
|
255
136
|
```
|
|
256
137
|
|
|
257
|
-
|
|
138
|
+
### Strava publication/retry — only when authorized
|
|
258
139
|
|
|
259
140
|
```bash
|
|
260
|
-
wattson
|
|
141
|
+
wattson --json session strava-sync SESSION_ID
|
|
142
|
+
wattson --json session summary SESSION_ID
|
|
143
|
+
wattson --json session artifacts SESSION_ID
|
|
261
144
|
```
|
|
262
145
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
```json
|
|
266
|
-
{
|
|
267
|
-
"status": "completed",
|
|
268
|
-
"completionKind": "completed",
|
|
269
|
-
"startedAt": "2026-04-09T10:00:00.000Z",
|
|
270
|
-
"endedAt": "2026-04-09T10:20:00.000Z",
|
|
271
|
-
"executionMode": "erg",
|
|
272
|
-
"liveSummary": {
|
|
273
|
-
"elapsedSec": 1200
|
|
274
|
-
},
|
|
275
|
-
"actualMetrics": {
|
|
276
|
-
"avgPower": 245,
|
|
277
|
-
"avgHrBpm": 158,
|
|
278
|
-
"maxHrBpm": 173,
|
|
279
|
-
"compliancePct": 98,
|
|
280
|
-
"completedDurationSec": 1200,
|
|
281
|
-
"completedTss": 42
|
|
282
|
-
},
|
|
283
|
-
"notes": ["Strong finish"]
|
|
284
|
-
}
|
|
285
|
-
```
|
|
146
|
+
This enqueues/retries an asynchronous backend operation (HTTP 202). It requires the relevant Strava connection; the CLI has no account-connect command. Inspect `session.externalSync.strava` on later session summaries for status, activity URL or errors. Report pending until the backend confirms delivery; FIT export or HTTP 202 is not proof of Strava publication. Avoid tight polling.
|
|
286
147
|
|
|
287
|
-
##
|
|
148
|
+
## Library calendar and courses
|
|
288
149
|
|
|
289
150
|
```bash
|
|
290
|
-
wattson
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
151
|
+
wattson --json workout list --limit 20
|
|
152
|
+
wattson workout schedule WORKOUT_ID --date 2026-11-13
|
|
153
|
+
wattson workout unschedule WORKOUT_ID
|
|
154
|
+
wattson workout retire WORKOUT_ID
|
|
155
|
+
wattson strava routes --limit 20
|
|
156
|
+
wattson strava starred-segments --limit 20
|
|
157
|
+
wattson course import-strava-route --input-json '{"route":"123456"}'
|
|
158
|
+
wattson course import-strava-segment --input-json '{"segment":"629046"}'
|
|
294
159
|
```
|
|
295
160
|
|
|
296
|
-
|
|
297
|
-
- `reduce_intensity`
|
|
298
|
-
- `increase_intensity`
|
|
299
|
-
- `extend_recovery`
|
|
300
|
-
- `skip_interval`
|
|
301
|
-
- `shorten_workout`
|
|
302
|
-
- `switch_execution_mode`
|
|
161
|
+
Scheduling sets the library workout's calendar date, not a legacy plan slot or remote start. Unscheduling clears that date; retirement removes the workout from the downloadable library. Verify with `workout get` and `workout list`. Course imports also accept `effortPctFtp` as a fraction.
|
|
303
162
|
|
|
304
|
-
|
|
305
|
-
- `skip the next interval` -> yes, good fit
|
|
306
|
-
- `make it a bit easier` -> yes, good fit
|
|
307
|
-
- `add another interval` -> not yet a first-class live op; that would need a richer structural adaptation model later
|
|
163
|
+
## Removed commands
|
|
308
164
|
|
|
309
|
-
|
|
165
|
+
`plan current`, `plan schedule`, `runtime-plan today`, `session load-workout`, `session adapt`, and `session complete` are no longer CLI commands. Their backend compatibility routes are not the current app workflow. Use library scheduling and uploaded-ride reads instead; do not synthesize a ride completion.
|
|
310
166
|
|
|
311
|
-
|
|
312
|
-
wattson session summary session-123
|
|
313
|
-
```
|
|
167
|
+
Finish with an affected read endpoint; distinguish server persistence, app synchronization, recorded riding and external publication.
|
|
314
168
|
|
|
315
|
-
## 14. Inspect canonical telemetry after completion
|
|
316
169
|
|
|
317
|
-
|
|
318
|
-
wattson session telemetry session-123
|
|
319
|
-
wattson --json session telemetry session-123
|
|
320
|
-
```
|
|
170
|
+
## MCP and CLI interchangeability
|
|
321
171
|
|
|
322
|
-
|
|
172
|
+
Use the same authenticated account and API environment for equivalent results. The CLI calls the same backend through the typed client; it does not require an MCP server process. Local/internal athlete-ID inputs do not grant access to other accounts. Server/provider access restrictions still apply.
|
|
323
173
|
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
174
|
+
```bash
|
|
175
|
+
wattson --json tool list
|
|
176
|
+
wattson --json tool schema draft_workout
|
|
177
|
+
wattson --json tool schema validate_workout
|
|
178
|
+
wattson --json tool schema generate_terrain_course
|
|
179
|
+
# Same payload as the MCP tool, same result data:
|
|
180
|
+
wattson --json tool draft_workout --input-json '{"objective":"endurance","durationMin":30}'
|
|
328
181
|
```
|
|
329
182
|
|
|
330
|
-
|
|
183
|
+
`tool TOOL_NAME --input-json JSON` (or `--input-file PATH`) accepts the corresponding MCP input envelope. `tool schema TOOL_NAME` describes fields, types, defaults and constraints; invocation also enforces contract refinements. Inspect the installed version rather than guessing a flag. The normal CLI commands remain available for humans.
|
|
184
|
+
|
|
185
|
+
| MCP tool | CLI command |
|
|
186
|
+
| --- | --- |
|
|
187
|
+
| `list_saved_workouts` | `workout list` |
|
|
188
|
+
| `health` | `health` |
|
|
189
|
+
| `get_athlete_profile` | `athlete profile` |
|
|
190
|
+
| `get_readiness_snapshot` | `athlete readiness latest` |
|
|
191
|
+
| `get_recent_sessions` | `athlete sessions` |
|
|
192
|
+
| `get_saved_workout` | `workout get` |
|
|
193
|
+
| `get_workout_runtime_plan` | `workout runtime-plan` |
|
|
194
|
+
| `list_saved_workout_revisions` | `workout revisions` |
|
|
195
|
+
| `summarize_session` | `session summary` |
|
|
196
|
+
| `get_session_telemetry` | `session telemetry` |
|
|
197
|
+
| `get_session_artifacts` | `session artifacts` |
|
|
198
|
+
| `get_session_fit_export` | `session fit` |
|
|
199
|
+
| `lookup_climb` | `course lookup` |
|
|
200
|
+
| `find_my_strava_segments` | `strava find-segments` |
|
|
201
|
+
| `import_strava_segment` | `course import-strava-segment` |
|
|
202
|
+
| `import_gpx_course` | `course import-gpx` |
|
|
203
|
+
| `generate_terrain_course` | `course generate-terrain` |
|
|
204
|
+
| `import_strava_activity_course` | `course import-strava-activity` |
|
|
205
|
+
| `build_road_course` | `course build-road` |
|
|
206
|
+
| `import_climb_page` | `course import-climb-page` |
|
|
207
|
+
| `get_my_climb_history` | `course history` |
|
|
208
|
+
| `explore_strava_segments` | `strava explore` |
|
|
209
|
+
| `get_my_strava_segment_efforts` | `strava segment-efforts` |
|
|
210
|
+
| `list_my_strava_starred_segments` | `strava starred-segments` |
|
|
211
|
+
| `list_my_favourite_strava_climbs` | `strava favourite-climbs` |
|
|
212
|
+
| `recommend_first_workout` | `recommend first` |
|
|
213
|
+
| `list_my_strava_routes` | `strava routes` |
|
|
214
|
+
| `import_strava_route` | `course import-strava-route` |
|
|
215
|
+
| `draft_workout` | `workout draft` |
|
|
216
|
+
| `revise_workout` | `workout revise` |
|
|
217
|
+
| `validate_workout` | `workout validate` |
|
|
218
|
+
| `save_workout` | `workout save` |
|
|
219
|
+
| `schedule_saved_workout` | `workout schedule` |
|
|
220
|
+
| `retire_saved_workout` | `workout retire` |
|
|
221
|
+
| `trigger_session_strava_sync` | `session strava-sync` |
|
|
222
|
+
| `create_workout_share` | `share create` |
|
|
223
|
+
| `preview_workout_share` | `share preview` |
|
|
224
|
+
| `import_workout_share` | `share import` |
|
|
225
|
+
| `search` | `research search` |
|
|
226
|
+
| `fetch` | `research fetch` |
|
|
227
|
+
|
|
228
|
+
For new course/discovery/sharing/recommendation commands, schema field names map to kebab-case flags (`effortPctFtp` → `--effort-pct-ftp`). Nested fields use dots (`--answers.ideal-minutes 30`); objects/arrays can be JSON. Booleans take explicit `true`/`false`. Do not combine `--input-json`/`--input-file` with field flags. Existing convenience commands keep their documented flags (for example, `workout get --version`); use generic `tool` invocation for a literal MCP payload.
|
|
229
|
+
|
|
230
|
+
### Sharing: create, preview, import
|
|
331
231
|
|
|
332
232
|
```bash
|
|
333
|
-
wattson
|
|
334
|
-
|
|
233
|
+
wattson --json share create --input-json '{"workoutId":"WORKOUT_ID","workoutVersion":1}' > share.json
|
|
234
|
+
share_id=$(jq -er '.shareId' share.json)
|
|
235
|
+
wattson --json share preview "$share_id" > preview.json
|
|
236
|
+
# Only when the user wants to add the shared workout to their own library:
|
|
237
|
+
wattson --json share import "$share_id" > imported.json
|
|
238
|
+
wattson --json workout list
|
|
335
239
|
```
|
|
336
240
|
|
|
337
|
-
|
|
241
|
+
Creation returns `{shareId,url}` for an owned, saved revision. Share IDs are UUIDs, not arbitrary URLs. Sharing exposes a sanitized template (title, objective, blocks, constraints), not the owner's profile, rationale, schedule or ride history. Block instructions are shared: don't put private notes there if sharing the workout. Anyone with the link can access the public preview. Authenticated preview returns `{item}` with recipient-adjusted artifacts and does **not** save. Import returns `{item,alreadyImported}`; repeated imports may reuse an existing copy. It does not start or schedule a ride.
|
|
338
242
|
|
|
339
|
-
|
|
243
|
+
### All course sources and discovery
|
|
340
244
|
|
|
341
245
|
```bash
|
|
342
|
-
wattson
|
|
343
|
-
wattson --json
|
|
246
|
+
wattson --json course lookup "Ventoux" --limit 3
|
|
247
|
+
wattson --json course generate-terrain --input-json '{"distanceM":5000,"averageGradePct":5,"shape":"hairpins","seed":7}'
|
|
248
|
+
wattson --json course import-gpx --gpx-file route.gpx --name "My climb"
|
|
249
|
+
wattson --json course build-road --input-json '{"start":{"place":"Bédoin, France"},"end":{"place":"Mont Ventoux, France"}}'
|
|
250
|
+
wattson --json course import-climb-page --input-json '{"url":"https://climbfinder.com/en/climbs/mont-ventoux-bedoin"}'
|
|
251
|
+
wattson --json course history --limit 20
|
|
252
|
+
wattson --json strava find-segments "Collserola" --limit 5
|
|
253
|
+
wattson --json strava explore --input-json '{"place":"Girona","radiusKm":10}'
|
|
254
|
+
wattson --json strava segment-efforts "629046" --limit 10
|
|
255
|
+
wattson --json strava favourite-climbs --limit 3 --recent-rides 6
|
|
256
|
+
wattson --json course import-strava-activity --input-json '{"activity":"123456","startDistanceM":1000,"endDistanceM":6000}'
|
|
344
257
|
```
|
|
345
258
|
|
|
346
|
-
|
|
347
|
-
- `session fit` returns inline `contentBase64`
|
|
348
|
-
- `session artifacts` is usually the better inspection command when you only need status/metadata
|
|
259
|
+
Replace example Strava IDs with real accessible items. Course sources return profiles; compose a `climb` block from the returned `course`, allow grade execution, then validate/save the workout. Lookup returns a `climbs` array instead of one top-level `course`. GPX takes exactly one of text or a public GPX URL; `--gpx-file` reads local text. Strava activity imports are limited to the rider's own rides. Use `tool schema` for every optional field, bound and default; the [authoring reference](workout-building-blocks.md) explains course embedding, pacing estimates and runtime completion.
|
|
349
260
|
|
|
350
|
-
|
|
261
|
+
Limited Strava mode still permits segment import, name matching against starred segments, and ride upload/retry; it restricts other Strava reads. Changing interfaces does not bypass that policy. Lookup, GPX, terrain, road and supported climb pages are alternatives without those Strava reads.
|
|
262
|
+
|
|
263
|
+
### First-workout recommendations
|
|
351
264
|
|
|
352
265
|
```bash
|
|
353
|
-
wattson
|
|
354
|
-
|
|
266
|
+
wattson --json recommend first --input-json '{"answers":{"idealMinutes":30,"maxMinutes":45,"goal":"fitness","freshness":"normal"},"useStrava":false}' > first.json
|
|
267
|
+
jq '{workout:.draft.workout}' first.json > validate.json
|
|
268
|
+
wattson --json workout validate --input-file validate.json
|
|
355
269
|
```
|
|
356
270
|
|
|
357
|
-
|
|
358
|
-
- this corresponds to an async backend operation
|
|
359
|
-
- the underlying HTTP route returns `202 Accepted`
|
|
360
|
-
- use `session artifacts`, `session fit`, or later session reads to inspect the resulting state
|
|
361
|
-
|
|
362
|
-
## A practical agent loop
|
|
271
|
+
This returns a draft and rationale, not a saved workout. Its definition is at `.draft.workout`, unlike `workout draft`'s `.workout`. Continue the same reviewed validate/save flow above. Use `tool schema recommend_first_workout` for questionnaire choices and all optional answers. Unknown FTP stays omitted; never invent one. `recommend daily` is a separate legacy advisory helper, not this onboarding operation or a saved plan.
|
|
363
272
|
|
|
364
|
-
|
|
273
|
+
### Research search and fetch
|
|
365
274
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
5. ingest final session completion once the ride is done
|
|
371
|
-
6. inspect telemetry / artifacts / FIT / Strava sync state after completion when durability matters
|
|
372
|
-
7. use bounded live adaptation ops during an active session
|
|
275
|
+
```bash
|
|
276
|
+
wattson --json research search "threshold"
|
|
277
|
+
wattson --json research fetch "DOCUMENT_ID_FROM_SEARCH"
|
|
278
|
+
```
|
|
373
279
|
|
|
374
|
-
The
|
|
375
|
-
The agent should do the interpretation.
|
|
280
|
+
These use the same profile/saved-workout/uploaded-ride corpus, ranking and document handles as MCP research. They are not web search. Fetch a returned handle rather than constructing IDs. The output may contain the signed-in athlete's private training data.
|