@wattsonclub/wattson-cli 0.0.0-stage → 0.1.0

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/package.json CHANGED
@@ -1,6 +1,34 @@
1
1
  {
2
2
  "name": "@wattsonclub/wattson-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "private": false,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "bin": {
7
+ "wattson": "./dist/bin.js",
8
+ "trainer-planner": "./dist/bin.js"
9
+ },
10
+ "scripts": {
11
+ "typecheck": "tsc -p tsconfig.json --noEmit",
12
+ "test": "vitest run",
13
+ "build": "node build.mjs",
14
+ "prepack": "npm run build"
15
+ },
16
+ "description": "Wattson training planner CLI",
17
+ "license": "UNLICENSED",
18
+ "engines": {
19
+ "node": ">=22"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "skill",
24
+ "README.md",
25
+ "CHANGELOG.md"
26
+ ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "devDependencies": {
31
+ "zod": "^3.25.76",
32
+ "esbuild": "^0.25.12"
33
+ }
34
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: wattson
3
+ description: Use the Wattson CLI to authenticate a local or remote agent, inspect athlete profiles and uploaded rides, and draft, validate and save structured cycling workouts. Use for terminal automation with Wattson, not remote control of an offline ride.
4
+ ---
5
+
6
+ # Wattson
7
+
8
+ ## 1. Install and inspect
9
+
10
+ Requires Node.js 22 or newer. Install `npm install -g @wattsonclub/wattson-cli`, then run `wattson --help`. Use the commands advertised by the installed version; do not invent MCP-equivalent commands.
11
+
12
+ The default API is `https://api.wattson.club`. Override with `--base-url`, `WATTSON_API_BASE_URL` or legacy `PLANNER_API_BASE_URL` only for an explicitly chosen environment. Confirm `wattson health` succeeds before continuing.
13
+
14
+ ## 2. Authenticate
15
+
16
+ Run `wattson auth status` (exit 1 means signed out, including with `--json`). If not signed in:
17
+
18
+ - On the user's own computer: `wattson auth login` opens browser OAuth with a localhost callback.
19
+ - On an SSH host, server, container, or agent runtime: `wattson auth login --remote` prints a verification URL and a short-lived user code and waits for approval. The user opens that URL on their own phone/laptop, signs in, checks the code and explicitly approves. No inbound port or SSH tunnel is needed.
20
+
21
+ Deliver login URLs and codes privately to the requesting user, never in a shared group. Never request their password, copy browser cookies, approve on their behalf, or expose access/refresh tokens. A timeout or denial means not authenticated; start a new login only at the user's request. For an unattended agent, complete the initial approval interactively; subsequent token refresh is automatic.
22
+
23
+ Verify `wattson auth status` and `wattson --json athlete profile`. `wattson auth logout` clears local credentials. Do not assume that local logout revokes other sessions. Keep the credential directory persistent and private for containers; do not bake credentials into an image.
24
+
25
+ ## 3. Inspect before changing
26
+
27
+ Use `--json` for machine consumption and check exit status. Login instructions are interactive information, not a successful login result.
28
+
29
+ ```sh
30
+ wattson --json athlete profile
31
+ wattson --json athlete readiness latest
32
+ wattson --json athlete sessions --limit 10
33
+ wattson --json workout get WORKOUT_ID --version 1
34
+ wattson --json workout revisions WORKOUT_ID
35
+ wattson --json session summary SESSION_ID
36
+ ```
37
+
38
+ Omit athlete IDs to use the authenticated athlete. Do not infer permission to access someone else's athlete ID.
39
+
40
+ ## 4. Draft, validate, save
41
+
42
+ Translate natural language into structured inputs. Read [workout building blocks](references/workout-building-blocks.md) for payload structure and [workout text format](references/workout-text-format.md) for readable previews.
43
+
44
+ ```sh
45
+ wattson --json workout draft --objective vo2max --duration-min 20 --execution-mode erg --style-hint "3 hard intervals"
46
+ wattson --json workout validate --input-file validate.json
47
+ wattson --json workout save --input-file save.json
48
+ ```
49
+
50
+ Draft output is not a save request: construct the appropriate payload described in [command examples](references/commands.md). A draft alone is not published. Fix validation errors before saving. FTP-relative values are fractions: `0.55` means 55%, not `55`.
51
+
52
+ Only save when the user's task authorizes publishing the workout. After saving, verify the returned workout ID/version with `workout get`. Report “saved to your library, ready for the app to download,” not confirmed device delivery. Revisions require validation and save too.
53
+
54
+ ## 5. Respect the offline ride model
55
+
56
+ The rider's app downloads saved workouts and runs the selected ride locally. A suspended/offline app syncs when it returns online. Uploaded rides can then be reviewed with `athlete sessions`, `session summary`, `session telemetry` and `session artifacts`.
57
+
58
+ Legacy CLI `plan schedule`, `session load-workout`, `session adapt` and `session complete` are compatibility APIs, not remote start/control of the current app. Do not synthesize a ride completion. The CLI is not full MCP parity: use MCP for library listing/retirement/calendar scheduling and course discovery/import when those commands are absent from `wattson --help`.
59
+
60
+ Finish by verifying the affected read endpoint and summarizing the result. Distinguish a successful local command from device acknowledgement or Strava delivery.
@@ -0,0 +1,375 @@
1
+ > Command examples inherited from the compatibility CLI. Current offline app behavior is described in ../SKILL.md; legacy scheduling/session calls do not remotely control the app.
2
+
3
+ # Planner CLI examples
4
+
5
+ This file exists to make the intended agent/operator flow concrete.
6
+
7
+ Design rule:
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
18
+
19
+ ```bash
20
+ wattson health
21
+ wattson --json health
22
+ wattson --base-url http://192.168.1.50:3000 health
23
+ ```
24
+
25
+ ## 1. Recommend a workout for today
26
+
27
+ ```bash
28
+ wattson recommend daily athlete-1 \
29
+ --available-time-min 20 \
30
+ --prefer-objective vo2max \
31
+ --hard-constraint "indoor only"
32
+ ```
33
+
34
+ ## 2. Draft a workout from simple flags
35
+
36
+ ```bash
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
+ ```
43
+
44
+ This is the intended translation pattern for agents:
45
+
46
+ - user says: `Create me a 20 min session with 3 high intensity intervals`
47
+ - agent infers:
48
+ - objective: `vo2max`
49
+ - durationMin: `20`
50
+ - allowedExecutionModes: `["erg"]`
51
+ - styleHints: `["3 high intensity intervals"]`
52
+ - agent calls `workout draft`
53
+
54
+ ## 3. Draft a workout from a full JSON body
55
+
56
+ ```bash
57
+ wattson workout draft --input-file /tmp/draft-workout.json
58
+ ```
59
+
60
+ Example `/tmp/draft-workout.json`:
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
+ ```
76
+
77
+ Note:
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`
82
+
83
+ ## 4. Revise a workout from a structured patch
84
+
85
+ ```bash
86
+ wattson workout revise --input-file /tmp/revise-workout.json
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
+ }
108
+ ```
109
+
110
+ This is the intended translation pattern for agents:
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
118
+
119
+ ```bash
120
+ wattson workout validate --input-file /tmp/validate-workout.json
121
+ ```
122
+
123
+ Example `/tmp/validate-workout.json`:
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
+ ```
164
+
165
+ ## 6. Save a validated workout
166
+
167
+ ```bash
168
+ wattson workout save --input-file /tmp/save-workout.json
169
+ ```
170
+
171
+ Example `/tmp/save-workout.json`:
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
+ ```
213
+
214
+ The same `%FTP` rule applies here:
215
+ - use fractional values like `0.55` and `1.2`
216
+ - do not send whole percentages like `55` or `120`
217
+
218
+ ## 7. Schedule a saved workout
219
+
220
+ ```bash
221
+ wattson plan schedule athlete-1 \
222
+ --workout-id workout-1 \
223
+ --version 2 \
224
+ --date 2026-04-10
225
+ ```
226
+
227
+ Note:
228
+ - the CLI takes `athleteId` here and hides the current HTTP `planId` convention (`<athleteId>:current-plan`)
229
+
230
+ ## 8. Inspect the compiled runtime plan
231
+
232
+ ```bash
233
+ wattson workout runtime-plan workout-1 --version 2
234
+ ```
235
+
236
+ ## 9. Queue the saved workout into an active trainer session
237
+
238
+ ```bash
239
+ wattson session load-workout \
240
+ --athlete-id athlete-1 \
241
+ --workout-id workout-1 \
242
+ --version 2
243
+ ```
244
+
245
+ ## 10. Complete a session from simple flags
246
+
247
+ ```bash
248
+ wattson session complete session-123 \
249
+ --status completed \
250
+ --completion-kind completed \
251
+ --elapsed-sec 1200 \
252
+ --completed-duration-sec 1200 \
253
+ --compliance-pct 98 \
254
+ --note "Strong finish"
255
+ ```
256
+
257
+ ## 11. Complete a session from a structured JSON body
258
+
259
+ ```bash
260
+ wattson session complete session-123 --input-file /tmp/complete-session.json
261
+ ```
262
+
263
+ Example `/tmp/complete-session.json`:
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
+ ```
286
+
287
+ ## 12. Apply a bounded live adaptation
288
+
289
+ ```bash
290
+ wattson session adapt session-123 \
291
+ --op skip_interval \
292
+ --reason "Rider requested it" \
293
+ --payload-json '{"stepIndex":2}'
294
+ ```
295
+
296
+ Allowed live ops today:
297
+ - `reduce_intensity`
298
+ - `increase_intensity`
299
+ - `extend_recovery`
300
+ - `skip_interval`
301
+ - `shorten_workout`
302
+ - `switch_execution_mode`
303
+
304
+ That means:
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
308
+
309
+ ## 13. Inspect the summary after completion
310
+
311
+ ```bash
312
+ wattson session summary session-123
313
+ ```
314
+
315
+ ## 14. Inspect canonical telemetry after completion
316
+
317
+ ```bash
318
+ wattson session telemetry session-123
319
+ wattson --json session telemetry session-123
320
+ ```
321
+
322
+ If no telemetry has been persisted yet, JSON mode returns:
323
+
324
+ ```json
325
+ {
326
+ "telemetry": null
327
+ }
328
+ ```
329
+
330
+ ## 15. Inspect persisted artifact metadata
331
+
332
+ ```bash
333
+ wattson session artifacts session-123
334
+ wattson --json session artifacts session-123
335
+ ```
336
+
337
+ Use this when you want artifact state/metadata without pulling inline FIT base64 content.
338
+
339
+ ## 16. Fetch the deterministic FIT export
340
+
341
+ ```bash
342
+ wattson session fit session-123
343
+ wattson --json session fit session-123 > /tmp/session-fit.json
344
+ ```
345
+
346
+ Important:
347
+ - `session fit` returns inline `contentBase64`
348
+ - `session artifacts` is usually the better inspection command when you only need status/metadata
349
+
350
+ ## 17. Enqueue or retry Strava sync
351
+
352
+ ```bash
353
+ wattson session strava-sync session-123
354
+ wattson --json session strava-sync session-123
355
+ ```
356
+
357
+ Important:
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
363
+
364
+ A practical agent should usually do this:
365
+
366
+ 1. interpret the user request
367
+ 2. choose the smallest structured planner action that matches it
368
+ 3. keep natural language as context / rationale, not as the API contract itself
369
+ 4. validate before saving when changing workout structure
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
373
+
374
+ The planner API should stay structured.
375
+ The agent should do the interpretation.