trainerroad-cli 0.2.0 → 0.4.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/CHANGELOG.md +28 -0
- package/README.md +65 -1
- package/package.json +11 -2
- package/src/cli.mjs +120 -37
- package/src/commands/annotation-mutations.mjs +253 -0
- package/src/commands/auth.mjs +3 -2
- package/src/commands/discovery.mjs +9 -8
- package/src/commands/event-mutations.mjs +174 -0
- package/src/commands/plan.mjs +23 -5
- package/src/commands/power.mjs +2 -1
- package/src/commands/workout-image.mjs +99 -0
- package/src/commands/workout-library.mjs +1 -0
- package/src/commands/workout-mutations.mjs +186 -46
- package/src/commands/workout-tools.mjs +104 -41
- package/src/lib/command-manifest.mjs +458 -35
- package/src/lib/planning-normalizers.mjs +110 -4
- package/src/trainerroad-client.mjs +246 -36
|
@@ -2,172 +2,327 @@ export const COMMANDS = {
|
|
|
2
2
|
help: {
|
|
3
3
|
summary: "Show global help or command help.",
|
|
4
4
|
usage: [
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
5
|
+
"trainerroad-cli help",
|
|
6
|
+
"trainerroad-cli help <command>",
|
|
7
|
+
"trainerroad-cli <command> --help",
|
|
8
|
+
],
|
|
9
|
+
examples: [
|
|
10
|
+
"trainerroad-cli help future",
|
|
11
|
+
"trainerroad-cli help future --json",
|
|
8
12
|
],
|
|
9
13
|
},
|
|
10
14
|
discover: {
|
|
11
15
|
summary: "Agent-oriented command discovery with progressive disclosure levels.",
|
|
12
16
|
usage: [
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
17
|
+
"trainerroad-cli discover",
|
|
18
|
+
"trainerroad-cli discover --level 1|2|3 [--json]",
|
|
19
|
+
"trainerroad-cli discover --command future --level 3 --json",
|
|
20
|
+
],
|
|
21
|
+
examples: [
|
|
22
|
+
"trainerroad-cli discover --level 1",
|
|
23
|
+
"trainerroad-cli discover --command workout-library --level 3 --json",
|
|
16
24
|
],
|
|
17
25
|
},
|
|
18
26
|
capabilities: {
|
|
19
27
|
summary: "Show supported auth/data capabilities (private + public modes).",
|
|
20
|
-
usage: ["
|
|
28
|
+
usage: ["trainerroad-cli capabilities [--json]"],
|
|
29
|
+
examples: ["trainerroad-cli capabilities --json"],
|
|
21
30
|
},
|
|
22
31
|
login: {
|
|
23
32
|
summary: "Authenticate and persist cookie session for private data access.",
|
|
24
33
|
usage: [
|
|
25
|
-
"
|
|
26
|
-
"
|
|
34
|
+
"trainerroad-cli login --username <username> --password <password> [--return-path /app/career/<username>]",
|
|
35
|
+
"trainerroad-cli login --username <username> --password-stdin",
|
|
36
|
+
],
|
|
37
|
+
examples: [
|
|
38
|
+
"trainerroad-cli login --username quinnsprouse --password-stdin",
|
|
39
|
+
"TR_PASSWORD='<password>' trainerroad-cli login --username quinnsprouse",
|
|
27
40
|
],
|
|
28
41
|
},
|
|
29
42
|
whoami: {
|
|
30
43
|
summary: "Fetch authenticated member profile info (`/app/api/member-info`).",
|
|
31
|
-
usage: ["
|
|
44
|
+
usage: ["trainerroad-cli whoami [--json]"],
|
|
45
|
+
examples: ["trainerroad-cli whoami --json"],
|
|
32
46
|
},
|
|
33
47
|
timeline: {
|
|
34
48
|
summary: "Get profile summary (private full timeline or public TSS-derived summary).",
|
|
35
49
|
usage: [
|
|
36
|
-
"
|
|
37
|
-
|
|
50
|
+
"trainerroad-cli timeline [--target <username>] [--public] [--full] [--json]",
|
|
51
|
+
],
|
|
52
|
+
examples: [
|
|
53
|
+
"trainerroad-cli timeline --json",
|
|
54
|
+
"trainerroad-cli timeline --target quinnsprouse --public --json",
|
|
38
55
|
],
|
|
39
56
|
},
|
|
40
57
|
"train-now": {
|
|
41
58
|
summary: "Fetch TrainerRoad AI suggested workouts (TrainNow) for a target duration (private mode).",
|
|
42
59
|
usage: [
|
|
43
|
-
"
|
|
60
|
+
"trainerroad-cli train-now [--duration <minutes>] [--num-suggestions <count>] [--category climbing|endurance|attacking] [--json|--jsonl]",
|
|
61
|
+
],
|
|
62
|
+
examples: [
|
|
63
|
+
"trainerroad-cli train-now --duration 60 --json",
|
|
64
|
+
"trainerroad-cli train-now --duration 90 --category endurance --json",
|
|
44
65
|
],
|
|
45
66
|
},
|
|
46
67
|
events: {
|
|
47
68
|
summary: "Show calendar events/races from timeline (private mode).",
|
|
48
69
|
usage: [
|
|
49
|
-
"
|
|
70
|
+
"trainerroad-cli events [--full] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--type <value>] [--contains <text>] [--min-tss <number>] [--max-tss <number>] [--sort date|date-desc|name|name-desc|tss|tss-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
71
|
+
],
|
|
72
|
+
examples: [
|
|
73
|
+
"trainerroad-cli events --from 2026-03-01 --to 2026-03-31 --json",
|
|
74
|
+
"trainerroad-cli events --contains gravel --sort date --result-limit 10 --json",
|
|
50
75
|
],
|
|
51
76
|
},
|
|
52
77
|
annotations: {
|
|
53
78
|
summary: "Show timeline annotations (time off, notes, illness/injury markers) (private mode).",
|
|
54
79
|
usage: [
|
|
55
|
-
"
|
|
80
|
+
"trainerroad-cli annotations [--full] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--type <value>] [--contains <text>] [--sort date|date-desc|name|name-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
81
|
+
],
|
|
82
|
+
examples: [
|
|
83
|
+
"trainerroad-cli annotations --from 2026-01-01 --json",
|
|
84
|
+
"trainerroad-cli annotations --contains injury --sort date-desc --json",
|
|
56
85
|
],
|
|
57
86
|
},
|
|
58
87
|
levels: {
|
|
59
88
|
summary: "Show progression levels by zone (private mode).",
|
|
60
89
|
usage: [
|
|
61
|
-
"
|
|
90
|
+
"trainerroad-cli levels [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--type <zone|progressionId>] [--contains <text>] [--sort name|name-desc|date|date-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
91
|
+
],
|
|
92
|
+
examples: [
|
|
93
|
+
"trainerroad-cli levels --json",
|
|
94
|
+
"trainerroad-cli levels --type endurance --json",
|
|
62
95
|
],
|
|
63
96
|
},
|
|
64
97
|
plan: {
|
|
65
98
|
summary: "Show training plan data (current plan, phases, or all plans) (private mode).",
|
|
66
99
|
usage: [
|
|
67
|
-
"
|
|
100
|
+
"trainerroad-cli plan [--view current|phases|plans] [--full] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--type <value>] [--contains <text>] [--sort date|date-desc|name|name-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
101
|
+
],
|
|
102
|
+
examples: [
|
|
103
|
+
"trainerroad-cli plan --view current --json",
|
|
104
|
+
"trainerroad-cli plan --view phases --json",
|
|
68
105
|
],
|
|
69
106
|
},
|
|
70
107
|
"weight-history": {
|
|
71
108
|
summary: "Show historical body-weight entries (private mode).",
|
|
72
109
|
usage: [
|
|
73
|
-
"
|
|
110
|
+
"trainerroad-cli weight-history [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--contains <text>] [--sort date|date-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
111
|
+
],
|
|
112
|
+
examples: [
|
|
113
|
+
"trainerroad-cli weight-history --json",
|
|
114
|
+
"trainerroad-cli weight-history --from 2026-01-01 --sort date-desc --json",
|
|
74
115
|
],
|
|
75
116
|
},
|
|
76
117
|
today: {
|
|
77
118
|
summary: "Show today's planned/completed activity for private or public profile mode.",
|
|
78
119
|
usage: [
|
|
79
|
-
"
|
|
120
|
+
"trainerroad-cli today [--date YYYY-MM-DD] [--target <username>] [--public] [--details] [--type <value>] [--contains <text>] [--min-tss <number>] [--max-tss <number>] [--sort date|date-desc|tss|tss-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
121
|
+
],
|
|
122
|
+
examples: [
|
|
123
|
+
"trainerroad-cli today --json",
|
|
124
|
+
"trainerroad-cli today --target quinnsprouse --public --tz America/New_York --json",
|
|
80
125
|
],
|
|
81
126
|
},
|
|
82
127
|
future: {
|
|
83
128
|
summary: "Show future plan data for private or public profile mode.",
|
|
84
129
|
usage: [
|
|
85
|
-
"
|
|
130
|
+
"trainerroad-cli future [--days <count>] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--target <username>] [--public] [--details] [--type <value>] [--contains <text>] [--min-tss <number>] [--max-tss <number>] [--sort date|date-desc|tss|tss-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
131
|
+
],
|
|
132
|
+
examples: [
|
|
133
|
+
"trainerroad-cli future --days 30 --json",
|
|
134
|
+
"trainerroad-cli future --from 2026-03-01 --to 2026-03-31 --fields id,title,tss,date --json",
|
|
86
135
|
],
|
|
87
136
|
},
|
|
88
137
|
past: {
|
|
89
138
|
summary: "Show past activity data for private or public profile mode.",
|
|
90
139
|
usage: [
|
|
91
|
-
"
|
|
140
|
+
"trainerroad-cli past [--days <count>] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit <count>] [--target <username>] [--public] [--details] [--type <value>] [--contains <text>] [--min-tss <number>] [--max-tss <number>] [--sort date|date-desc|tss|tss-desc] [--result-limit <count>] [--fields a,b] [--records-only] [--json|--jsonl]",
|
|
141
|
+
],
|
|
142
|
+
examples: [
|
|
143
|
+
"trainerroad-cli past --days 30 --json",
|
|
144
|
+
"trainerroad-cli past --days 90 --min-tss 80 --sort tss-desc --result-limit 20 --jsonl",
|
|
92
145
|
],
|
|
93
146
|
},
|
|
94
147
|
ftp: {
|
|
95
148
|
summary: "Show FTP snapshot and FTP history (private or public mode).",
|
|
96
149
|
usage: [
|
|
97
|
-
"
|
|
150
|
+
"trainerroad-cli ftp [--target <username>] [--public] [--history-limit <count>] [--json|--jsonl]",
|
|
151
|
+
],
|
|
152
|
+
examples: [
|
|
153
|
+
"trainerroad-cli ftp --json",
|
|
154
|
+
"trainerroad-cli ftp --target quinnsprouse --public --history-limit 12 --json",
|
|
98
155
|
],
|
|
99
156
|
},
|
|
100
157
|
"ftp-prediction": {
|
|
101
158
|
summary: "Show AI FTP detection eligibility/status and progression impact (private mode).",
|
|
102
|
-
usage: ["
|
|
159
|
+
usage: ["trainerroad-cli ftp-prediction [--json]"],
|
|
160
|
+
examples: ["trainerroad-cli ftp-prediction --json"],
|
|
103
161
|
},
|
|
104
162
|
"power-ranking": {
|
|
105
163
|
summary: "Show best-power percentile ranking by duration (private mode).",
|
|
106
|
-
usage: ["
|
|
164
|
+
usage: ["trainerroad-cli power-ranking [--json|--jsonl]"],
|
|
165
|
+
examples: ["trainerroad-cli power-ranking --json"],
|
|
107
166
|
},
|
|
108
167
|
"power-records": {
|
|
109
168
|
summary: "Show date-range personal power records from TrainerRoad PR endpoint (private mode).",
|
|
110
169
|
usage: [
|
|
111
|
-
"
|
|
170
|
+
"trainerroad-cli power-records [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD] [--row-type 100|101] [--indoor-only true|false] [--limit <count>] [--full] [--json|--jsonl]",
|
|
171
|
+
],
|
|
172
|
+
examples: [
|
|
173
|
+
"trainerroad-cli power-records --start-date 2026-01-01 --end-date 2026-03-01 --json",
|
|
174
|
+
"trainerroad-cli power-records --row-type 101 --indoor-only true --limit 20 --jsonl",
|
|
112
175
|
],
|
|
113
176
|
},
|
|
114
177
|
"workout-library": {
|
|
115
178
|
summary: "Search the TrainerRoad workout library with agent-friendly filters (private mode).",
|
|
116
179
|
usage: [
|
|
117
|
-
|
|
180
|
+
"trainerroad-cli workout-library [--search <text>] [--zone <name>|--zone-id <id>] [--profile <name>|--profile-id <id>] [--outside true|false] [--has-instructions true|false] [--min-duration <minutes>] [--max-duration <minutes>] [--min-tss <number>] [--max-tss <number>] [--min-level <number>] [--max-level <number>] [--sort level|level-desc|duration|duration-desc|tss|tss-desc|name|name-desc] [--limit <count>] [--page-size <count>] [--json|--jsonl]",
|
|
181
|
+
],
|
|
182
|
+
examples: [
|
|
183
|
+
"trainerroad-cli workout-library --zone Endurance --profile \"Sustained Power\" --min-duration 45 --max-duration 75 --json",
|
|
184
|
+
"trainerroad-cli workout-library --search Baxter --limit 5 --json",
|
|
118
185
|
],
|
|
119
186
|
},
|
|
120
187
|
"workout-recommend": {
|
|
121
188
|
summary: "Recommend library workouts by ranking candidates against target duration/level/TSS (private mode).",
|
|
122
189
|
usage: [
|
|
123
|
-
|
|
190
|
+
"trainerroad-cli workout-recommend [--search <text>] [--zone <name>|--zone-id <id>] [--profile <name>|--profile-id <id>] [--outside true|false] [--has-instructions true|false] [--min-duration <minutes>] [--max-duration <minutes>] [--min-tss <number>] [--max-tss <number>] [--min-level <number>] [--max-level <number>] [--target-duration <minutes>] [--target-tss <number>] [--target-level <number>] [--count <count>] [--candidate-limit <count>] [--page-size <count>] [--sort level|level-desc|duration|duration-desc|tss|tss-desc|name|name-desc] [--json|--jsonl]",
|
|
191
|
+
],
|
|
192
|
+
examples: [
|
|
193
|
+
"trainerroad-cli workout-recommend --zone Endurance --profile \"Sustained Power\" --target-duration 60 --target-level 1.0 --count 3 --json",
|
|
194
|
+
"trainerroad-cli workout-recommend --search threshold --target-duration 90 --count 5 --json",
|
|
124
195
|
],
|
|
125
196
|
},
|
|
126
197
|
"workout-details": {
|
|
127
198
|
summary: "Fetch detailed workout-library metadata for a workout ID (private mode).",
|
|
128
199
|
usage: [
|
|
129
|
-
"
|
|
200
|
+
"trainerroad-cli workout-details --id <workout-id> [--include-chart true|false] [--chart-point-limit <count>] [--json|--jsonl]",
|
|
201
|
+
],
|
|
202
|
+
examples: [
|
|
203
|
+
"trainerroad-cli workout-details --id 18128 --json",
|
|
204
|
+
"trainerroad-cli workout-details --id 18128 --include-chart --chart-point-limit 50 --json",
|
|
130
205
|
],
|
|
131
206
|
},
|
|
132
207
|
"add-workout": {
|
|
133
208
|
summary: "Add a library workout to the calendar on a target date (private mode; reconciles flaky API responses).",
|
|
134
209
|
usage: [
|
|
135
|
-
"
|
|
210
|
+
"trainerroad-cli add-workout --workout-id <workout-id> --date YYYY-MM-DD [--outside true|false] [--dry-run] [--json|--jsonl]",
|
|
211
|
+
],
|
|
212
|
+
examples: [
|
|
213
|
+
"trainerroad-cli add-workout --workout-id 18128 --date 2026-03-16 --dry-run",
|
|
214
|
+
"trainerroad-cli add-workout --workout-id 18128 --date 2026-03-16 --json",
|
|
136
215
|
],
|
|
137
216
|
},
|
|
138
217
|
"copy-workout": {
|
|
139
218
|
summary: "Copy an existing planned workout to another date (private mode).",
|
|
140
219
|
usage: [
|
|
141
|
-
"
|
|
220
|
+
"trainerroad-cli copy-workout --id <planned-activity-id> --date YYYY-MM-DD [--dry-run] [--json|--jsonl]",
|
|
221
|
+
],
|
|
222
|
+
examples: [
|
|
223
|
+
"trainerroad-cli copy-workout --id 123456 --date 2026-03-16 --dry-run",
|
|
224
|
+
"trainerroad-cli copy-workout --id 123456 --date 2026-03-16 --json",
|
|
142
225
|
],
|
|
143
226
|
},
|
|
144
227
|
"workout-alternates": {
|
|
145
228
|
summary: "List alternate workout options for a planned workout (private mode).",
|
|
146
229
|
usage: [
|
|
147
|
-
"
|
|
230
|
+
"trainerroad-cli workout-alternates --id <planned-activity-id> [--category similar|easier|harder|longer|shorter] [--json|--jsonl]",
|
|
231
|
+
],
|
|
232
|
+
examples: [
|
|
233
|
+
"trainerroad-cli workout-alternates --id 123456 --json",
|
|
234
|
+
"trainerroad-cli workout-alternates --id 123456 --category easier --json",
|
|
148
235
|
],
|
|
149
236
|
},
|
|
150
237
|
"move-workout": {
|
|
151
238
|
summary: "Move a planned workout to a different date (private mode).",
|
|
152
239
|
usage: [
|
|
153
|
-
"
|
|
240
|
+
"trainerroad-cli move-workout --id <planned-activity-id> --to YYYY-MM-DD [--dry-run] [--json|--jsonl]",
|
|
241
|
+
],
|
|
242
|
+
examples: [
|
|
243
|
+
"trainerroad-cli move-workout --id 123456 --to 2026-03-13 --dry-run",
|
|
244
|
+
"trainerroad-cli move-workout --id 123456 --to 2026-03-13 --json",
|
|
154
245
|
],
|
|
155
246
|
},
|
|
156
247
|
"replace-workout": {
|
|
157
248
|
summary: "Replace a planned workout with a specific alternate workout ID (private mode).",
|
|
158
249
|
usage: [
|
|
159
|
-
"
|
|
250
|
+
"trainerroad-cli replace-workout --id <planned-activity-id> --alternate-id <workout-id> [--update-duration true|false] [--dry-run] [--json|--jsonl]",
|
|
251
|
+
],
|
|
252
|
+
examples: [
|
|
253
|
+
"trainerroad-cli replace-workout --id 123456 --alternate-id 18128 --dry-run",
|
|
254
|
+
"trainerroad-cli replace-workout --id 123456 --alternate-id 18128 --json",
|
|
160
255
|
],
|
|
161
256
|
},
|
|
162
257
|
"switch-workout": {
|
|
163
258
|
summary: "Switch a planned workout between inside and outside variants (private mode).",
|
|
164
259
|
usage: [
|
|
165
|
-
"
|
|
260
|
+
"trainerroad-cli switch-workout --id <planned-activity-id> --mode inside|outside [--dry-run] [--json|--jsonl]",
|
|
261
|
+
],
|
|
262
|
+
examples: [
|
|
263
|
+
"trainerroad-cli switch-workout --id 123456 --mode outside --dry-run",
|
|
264
|
+
"trainerroad-cli switch-workout --id 123456 --mode outside --json",
|
|
265
|
+
],
|
|
266
|
+
},
|
|
267
|
+
"add-event": {
|
|
268
|
+
summary:
|
|
269
|
+
"Add a race or event to the calendar with discipline, priority, duration, and a TSS or intensity estimate. Remove it later with remove-workout (private mode).",
|
|
270
|
+
usage: [
|
|
271
|
+
"trainerroad-cli add-event --name <text> --date YYYY-MM-DD --discipline <name|id> --duration <minutes> (--tss <number> | --intensity <1-10>) [--priority A|B|C] [--notes <text>] [--dry-run] [--json|--jsonl]",
|
|
272
|
+
],
|
|
273
|
+
examples: [
|
|
274
|
+
"trainerroad-cli add-event --name \"Black Fork\" --date 2027-05-01 --discipline gravel --priority A --duration 300 --tss 340 --dry-run",
|
|
275
|
+
"trainerroad-cli add-event --name \"Tuesday crit\" --date 2026-10-06 --discipline criterium --priority C --duration 60 --intensity 9 --json",
|
|
276
|
+
],
|
|
277
|
+
},
|
|
278
|
+
"remove-workout": {
|
|
279
|
+
summary:
|
|
280
|
+
"Remove a planned workout or event from the calendar by planned-activity id. TrainerRoad may rebuild the plan around the gap (private mode).",
|
|
281
|
+
usage: ["trainerroad-cli remove-workout --id <planned-activity-id> [--dry-run] [--json|--jsonl]"],
|
|
282
|
+
examples: [
|
|
283
|
+
"trainerroad-cli remove-workout --id 05a68215-0fd5-431e-ba3f-b3bf01210c29 --dry-run",
|
|
284
|
+
"trainerroad-cli remove-workout --id 05a68215-0fd5-431e-ba3f-b3bf01210c29 --json",
|
|
285
|
+
],
|
|
286
|
+
},
|
|
287
|
+
"workout-image": {
|
|
288
|
+
summary: "Save a workout's power-profile chart as PNG (default) or SVG (private mode).",
|
|
289
|
+
usage: [
|
|
290
|
+
"trainerroad-cli workout-image --id <workout-id> [--file <path>] [--format png|svg] [--width <px>] [--background <css-color>] [--json]",
|
|
291
|
+
],
|
|
292
|
+
examples: [
|
|
293
|
+
"trainerroad-cli workout-image --id 1592808 --file fishers.png",
|
|
294
|
+
"trainerroad-cli workout-image --id 1592808 --format svg --file fishers.svg --json",
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
"annotation-details": {
|
|
298
|
+
summary: "Fetch one calendar annotation with its title and notes (private mode).",
|
|
299
|
+
usage: ["trainerroad-cli annotation-details --id <annotation-id> [--full] [--json|--jsonl]"],
|
|
300
|
+
examples: ["trainerroad-cli annotation-details --id 1818fb12-7294-4a98-b494-b4b90160edc1 --json"],
|
|
301
|
+
},
|
|
302
|
+
"add-annotation": {
|
|
303
|
+
summary:
|
|
304
|
+
"Add a calendar annotation: time off, illness, injury, or a note. Multi-day via --days or --end-date. TrainerRoad may adapt nearby workouts (private mode).",
|
|
305
|
+
usage: [
|
|
306
|
+
"trainerroad-cli add-annotation --type time-off|illness|injury|note --date YYYY-MM-DD [--days <count> | --end-date YYYY-MM-DD] [--title <text>] [--notes <text>] [--dry-run] [--json|--jsonl]",
|
|
307
|
+
],
|
|
308
|
+
examples: [
|
|
309
|
+
"trainerroad-cli add-annotation --type time-off --date 2026-09-21 --days 3 --title \"Travel\" --dry-run",
|
|
310
|
+
"trainerroad-cli add-annotation --type illness --date 2026-09-21 --end-date 2026-09-23 --notes \"Head cold\" --json",
|
|
311
|
+
"trainerroad-cli add-annotation --type note --date 2026-09-21 --title \"New saddle\" --json",
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
"remove-annotation": {
|
|
315
|
+
summary: "Remove a calendar annotation by id. No-op if it is already gone (private mode).",
|
|
316
|
+
usage: ["trainerroad-cli remove-annotation --id <annotation-id> [--dry-run] [--json|--jsonl]"],
|
|
317
|
+
examples: [
|
|
318
|
+
"trainerroad-cli remove-annotation --id 1818fb12-7294-4a98-b494-b4b90160edc1 --dry-run",
|
|
319
|
+
"trainerroad-cli remove-annotation --id 1818fb12-7294-4a98-b494-b4b90160edc1 --json",
|
|
166
320
|
],
|
|
167
321
|
},
|
|
168
322
|
logout: {
|
|
169
323
|
summary: "Clear local persisted session.",
|
|
170
|
-
usage: ["
|
|
324
|
+
usage: ["trainerroad-cli logout"],
|
|
325
|
+
examples: ["trainerroad-cli logout"],
|
|
171
326
|
},
|
|
172
327
|
};
|
|
173
328
|
|
|
@@ -176,6 +331,7 @@ export const PROJECT_NOTICE = "Unofficial tool. Not affiliated with or endorsed
|
|
|
176
331
|
export const GLOBAL_NOTES = [
|
|
177
332
|
PROJECT_NOTICE,
|
|
178
333
|
"Environment: TR_USERNAME, TR_PASSWORD, TR_SESSION_FILE, TR_TIMEZONE",
|
|
334
|
+
"Non-interactive by default: pass flags or stdin; commands do not prompt.",
|
|
179
335
|
"Timezone: --tz <IANA timezone> (for example America/New_York).",
|
|
180
336
|
"Output modes: default JSON, --json, --jsonl, --output <path>",
|
|
181
337
|
"Session file default: .trainerroad/session.json",
|
|
@@ -183,6 +339,7 @@ export const GLOBAL_NOTES = [
|
|
|
183
339
|
"Public mode: username-based endpoint (`/app/api/tss/{username}`) with limited detail.",
|
|
184
340
|
"Agent filters: --from --to --type --contains --min-tss --max-tss --sort --result-limit --fields",
|
|
185
341
|
"Agent output: --records-only",
|
|
342
|
+
"Write commands: use --dry-run to preview calendar mutations before applying them.",
|
|
186
343
|
];
|
|
187
344
|
|
|
188
345
|
export const AGENT_FILTER_OPTIONS = [
|
|
@@ -216,6 +373,182 @@ export const FILTERABLE_COMMANDS = new Set([
|
|
|
216
373
|
"weight-history",
|
|
217
374
|
]);
|
|
218
375
|
|
|
376
|
+
export const COMMAND_REQUIRED_FLAGS = {
|
|
377
|
+
"workout-details": ["id"],
|
|
378
|
+
"add-workout": ["workout-id", "date"],
|
|
379
|
+
"copy-workout": ["id", "date"],
|
|
380
|
+
"workout-alternates": ["id"],
|
|
381
|
+
"move-workout": ["id", "to"],
|
|
382
|
+
"replace-workout": ["id", "alternate-id"],
|
|
383
|
+
"switch-workout": ["id", "mode"],
|
|
384
|
+
"add-event": ["name", "date", "discipline", "duration"],
|
|
385
|
+
"remove-workout": ["id"],
|
|
386
|
+
"workout-image": ["id"],
|
|
387
|
+
"annotation-details": ["id"],
|
|
388
|
+
"add-annotation": ["type", "date"],
|
|
389
|
+
"remove-annotation": ["id"],
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
export const FLAG_DETAILS = {
|
|
393
|
+
help: { description: "Show command help and exit." },
|
|
394
|
+
output: { placeholder: "<path>", description: "Write command output to a file instead of stdout." },
|
|
395
|
+
json: { description: "Emit machine-readable JSON output." },
|
|
396
|
+
jsonl: { description: "Emit newline-delimited JSON records." },
|
|
397
|
+
"session-file": {
|
|
398
|
+
placeholder: "<path>",
|
|
399
|
+
description: "Override the persisted session file path.",
|
|
400
|
+
},
|
|
401
|
+
username: { placeholder: "<username>", description: "TrainerRoad account username." },
|
|
402
|
+
password: {
|
|
403
|
+
placeholder: "<password>",
|
|
404
|
+
description: "TrainerRoad account password. Prefer --password-stdin or TR_PASSWORD.",
|
|
405
|
+
},
|
|
406
|
+
"password-stdin": {
|
|
407
|
+
description: "Read the password from stdin so the command stays non-interactive and shell-history safe.",
|
|
408
|
+
},
|
|
409
|
+
"return-path": {
|
|
410
|
+
placeholder: "<path>",
|
|
411
|
+
description: "Override the login ReturnUrl used during the auth flow.",
|
|
412
|
+
},
|
|
413
|
+
level: { placeholder: "1|2|3", description: "Discovery detail level." },
|
|
414
|
+
command: { placeholder: "<command>", description: "Limit discovery/help output to one command." },
|
|
415
|
+
target: { placeholder: "<username>", description: "Public TrainerRoad profile username to query." },
|
|
416
|
+
public: {
|
|
417
|
+
description: "Force public-mode requests even when an authenticated session exists.",
|
|
418
|
+
},
|
|
419
|
+
full: { description: "Return fuller upstream payload data when the command supports it." },
|
|
420
|
+
duration: { placeholder: "<minutes>", description: "Requested workout duration in minutes." },
|
|
421
|
+
"num-suggestions": {
|
|
422
|
+
placeholder: "<count>",
|
|
423
|
+
description: "Maximum TrainNow suggestions to request.",
|
|
424
|
+
},
|
|
425
|
+
category: {
|
|
426
|
+
placeholder: "<value>",
|
|
427
|
+
description: "Command-specific category filter (for example easier, endurance, or outside).",
|
|
428
|
+
},
|
|
429
|
+
from: { placeholder: "YYYY-MM-DD", description: "Inclusive lower date bound." },
|
|
430
|
+
to: { placeholder: "YYYY-MM-DD", description: "Inclusive upper date bound or move target date." },
|
|
431
|
+
type: { placeholder: "<value>", description: "Command-specific record type filter." },
|
|
432
|
+
contains: { placeholder: "<text>", description: "Case-insensitive substring filter." },
|
|
433
|
+
"min-tss": { placeholder: "<number>", description: "Minimum TSS threshold." },
|
|
434
|
+
"max-tss": { placeholder: "<number>", description: "Maximum TSS threshold." },
|
|
435
|
+
sort: { placeholder: "<mode>", description: "Command-specific sort mode." },
|
|
436
|
+
"result-limit": { placeholder: "<count>", description: "Limit records after filters are applied." },
|
|
437
|
+
fields: { placeholder: "a,b,c", description: "Project record fields for leaner downstream payloads." },
|
|
438
|
+
"records-only": {
|
|
439
|
+
description: "Return only the envelope and records arrays when supported.",
|
|
440
|
+
},
|
|
441
|
+
view: { placeholder: "current|phases|plans", description: "Plan view to return." },
|
|
442
|
+
date: { placeholder: "YYYY-MM-DD", description: "Target calendar date." },
|
|
443
|
+
details: { description: "Include additional workout/activity detail in the payload." },
|
|
444
|
+
days: { placeholder: "<count>", description: "Relative day window size." },
|
|
445
|
+
limit: { placeholder: "<count>", description: "Upstream or record limit for the command." },
|
|
446
|
+
"history-limit": {
|
|
447
|
+
placeholder: "<count>",
|
|
448
|
+
description: "Maximum FTP history entries to include.",
|
|
449
|
+
},
|
|
450
|
+
"start-date": { placeholder: "YYYY-MM-DD", description: "Personal records window start date." },
|
|
451
|
+
"end-date": { placeholder: "YYYY-MM-DD", description: "Personal records window end date." },
|
|
452
|
+
"row-type": {
|
|
453
|
+
placeholder: "100|101",
|
|
454
|
+
description: "TrainerRoad PR row type to query.",
|
|
455
|
+
},
|
|
456
|
+
"indoor-only": {
|
|
457
|
+
placeholder: "true|false",
|
|
458
|
+
description: "Restrict personal-record queries to indoor rides.",
|
|
459
|
+
},
|
|
460
|
+
slot: {
|
|
461
|
+
placeholder: "<value>",
|
|
462
|
+
description: "Reserved upstream query slot used by the power-records endpoint.",
|
|
463
|
+
},
|
|
464
|
+
search: { placeholder: "<text>", description: "Free-text workout search query." },
|
|
465
|
+
zone: { placeholder: "<name>", description: "Workout zone name filter." },
|
|
466
|
+
"zone-id": { placeholder: "<id>", description: "Workout zone numeric identifier." },
|
|
467
|
+
profile: { placeholder: "<name>", description: "Workout profile name filter." },
|
|
468
|
+
"profile-id": { placeholder: "<id>", description: "Workout profile numeric identifier." },
|
|
469
|
+
outside: {
|
|
470
|
+
placeholder: "true|false",
|
|
471
|
+
description: "Filter or create the outside workout variant where supported.",
|
|
472
|
+
},
|
|
473
|
+
"has-instructions": {
|
|
474
|
+
placeholder: "true|false",
|
|
475
|
+
description: "Filter workouts by whether text instructions exist.",
|
|
476
|
+
},
|
|
477
|
+
"min-duration": { placeholder: "<minutes>", description: "Minimum workout duration." },
|
|
478
|
+
"max-duration": { placeholder: "<minutes>", description: "Maximum workout duration." },
|
|
479
|
+
"min-level": { placeholder: "<number>", description: "Minimum workout progression level." },
|
|
480
|
+
"max-level": { placeholder: "<number>", description: "Maximum workout progression level." },
|
|
481
|
+
"target-duration": {
|
|
482
|
+
placeholder: "<minutes>",
|
|
483
|
+
description: "Target duration used when ranking recommended workouts.",
|
|
484
|
+
},
|
|
485
|
+
"target-tss": {
|
|
486
|
+
placeholder: "<number>",
|
|
487
|
+
description: "Target TSS used when ranking recommended workouts.",
|
|
488
|
+
},
|
|
489
|
+
"target-level": {
|
|
490
|
+
placeholder: "<number>",
|
|
491
|
+
description: "Target progression level used when ranking recommended workouts.",
|
|
492
|
+
},
|
|
493
|
+
count: { placeholder: "<count>", description: "Number of recommendations to return." },
|
|
494
|
+
"candidate-limit": {
|
|
495
|
+
placeholder: "<count>",
|
|
496
|
+
description: "Maximum candidate workouts to score before ranking.",
|
|
497
|
+
},
|
|
498
|
+
"page-size": {
|
|
499
|
+
placeholder: "<count>",
|
|
500
|
+
description: "Upstream workout library page size.",
|
|
501
|
+
},
|
|
502
|
+
id: { placeholder: "<id>", description: "Workout, planned activity, annotation, or record identifier." },
|
|
503
|
+
name: { placeholder: "<text>", description: "Event name shown on the calendar." },
|
|
504
|
+
discipline: {
|
|
505
|
+
placeholder: "<name|id>",
|
|
506
|
+
description:
|
|
507
|
+
"Event discipline: gravel, criterium, time-trial, gran-fondo, climbing-road-race, rolling-road-race, cyclocross, xc-olympic, xc-marathon, short-track, gravity, enduro, or a triathlon type; numeric ids accepted.",
|
|
508
|
+
},
|
|
509
|
+
priority: { placeholder: "A|B|C", description: "Race priority. A drives the plan, C is a training race (default B)." },
|
|
510
|
+
tss: { placeholder: "<number>", description: "Expected TSS for the event. Use this or --intensity." },
|
|
511
|
+
intensity: { placeholder: "<1-10>", description: "Expected intensity on TrainerRoad's 1-10 scale. Use this or --tss." },
|
|
512
|
+
file: { placeholder: "<path>", description: "Destination file for the image (default workout-<id>.png)." },
|
|
513
|
+
format: { placeholder: "png|svg", description: "Image format. Inferred from --file extension when omitted." },
|
|
514
|
+
width: { placeholder: "<px>", description: "PNG width in pixels (default 1200)." },
|
|
515
|
+
background: { placeholder: "<css-color>", description: "PNG background colour (default #1c1c1c)." },
|
|
516
|
+
title: { placeholder: "<text>", description: "Annotation title shown on the calendar. Defaults to the type name." },
|
|
517
|
+
notes: { placeholder: "<text>", description: "Free-text notes stored with the annotation." },
|
|
518
|
+
"color-id": { placeholder: "<id>", description: "TrainerRoad annotation colour id (default 2)." },
|
|
519
|
+
"include-chart": {
|
|
520
|
+
placeholder: "true|false",
|
|
521
|
+
description: "Include workout chart/sample data in workout-details.",
|
|
522
|
+
},
|
|
523
|
+
"chart-point-limit": {
|
|
524
|
+
placeholder: "<count>",
|
|
525
|
+
description: "Maximum chart points returned with --include-chart.",
|
|
526
|
+
},
|
|
527
|
+
"workout-id": {
|
|
528
|
+
placeholder: "<workout-id>",
|
|
529
|
+
description: "TrainerRoad workout library identifier.",
|
|
530
|
+
},
|
|
531
|
+
"dry-run": {
|
|
532
|
+
description: "Preview the write command and exit without mutating the calendar.",
|
|
533
|
+
},
|
|
534
|
+
"alternate-id": {
|
|
535
|
+
placeholder: "<workout-id>",
|
|
536
|
+
description: "Alternate workout ID to apply during replace-workout.",
|
|
537
|
+
},
|
|
538
|
+
"update-duration": {
|
|
539
|
+
placeholder: "true|false",
|
|
540
|
+
description: "Let TrainerRoad update the duration during replace-workout.",
|
|
541
|
+
},
|
|
542
|
+
mode: {
|
|
543
|
+
placeholder: "inside|outside",
|
|
544
|
+
description: "Target workout delivery mode for switch-workout.",
|
|
545
|
+
},
|
|
546
|
+
tz: {
|
|
547
|
+
placeholder: "<IANA timezone>",
|
|
548
|
+
description: "Override local-day bucketing (defaults to TR_TIMEZONE or system timezone).",
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
|
|
219
552
|
function trimFlagPrefix(flag) {
|
|
220
553
|
return String(flag ?? "").replace(/^--/, "").trim();
|
|
221
554
|
}
|
|
@@ -228,6 +561,38 @@ function mergeFlagGroups(...groups) {
|
|
|
228
561
|
);
|
|
229
562
|
}
|
|
230
563
|
|
|
564
|
+
// Per-command overrides for flags whose meaning differs from the shared FLAG_DETAILS entry.
|
|
565
|
+
export const COMMAND_FLAG_DETAILS = {
|
|
566
|
+
"add-annotation": {
|
|
567
|
+
type: {
|
|
568
|
+
placeholder: "time-off|illness|injury|note",
|
|
569
|
+
description: "Annotation type. Also accepts a numeric TrainerRoad typeId.",
|
|
570
|
+
},
|
|
571
|
+
date: { placeholder: "YYYY-MM-DD", description: "First day of the annotation." },
|
|
572
|
+
days: { placeholder: "<count>", description: "Number of days the annotation covers (default 1)." },
|
|
573
|
+
"end-date": { placeholder: "YYYY-MM-DD", description: "Last day of the annotation, inclusive. Overrides --days." },
|
|
574
|
+
},
|
|
575
|
+
"annotation-details": {
|
|
576
|
+
id: { placeholder: "<annotation-id>", description: "Annotation id from `annotations`." },
|
|
577
|
+
full: { description: "Include the raw upstream annotation payload." },
|
|
578
|
+
},
|
|
579
|
+
"workout-image": {
|
|
580
|
+
id: { placeholder: "<workout-id>", description: "Library workout id (from workout-library, workout-details, or future --details)." },
|
|
581
|
+
},
|
|
582
|
+
"remove-workout": {
|
|
583
|
+
id: { placeholder: "<planned-activity-id>", description: "Planned activity id from `future --details` or `events`." },
|
|
584
|
+
},
|
|
585
|
+
"add-event": {
|
|
586
|
+
date: { placeholder: "YYYY-MM-DD", description: "Event date." },
|
|
587
|
+
duration: { placeholder: "<minutes>", description: "Expected event duration in minutes." },
|
|
588
|
+
notes: { placeholder: "<text>", description: "Free-text description stored with the event." },
|
|
589
|
+
full: { description: "Include TrainerRoad's raw create response." },
|
|
590
|
+
},
|
|
591
|
+
"remove-annotation": {
|
|
592
|
+
id: { placeholder: "<annotation-id>", description: "Annotation id from `annotations`." },
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
|
|
231
596
|
const SHARED_FLAGS = {
|
|
232
597
|
help: ["help"],
|
|
233
598
|
output: ["output"],
|
|
@@ -238,6 +603,7 @@ const SHARED_FLAGS = {
|
|
|
238
603
|
jsonAndJsonl: ["json", "jsonl"],
|
|
239
604
|
agentFilters: AGENT_FILTER_OPTIONS.map((option) => trimFlagPrefix(option.flag)),
|
|
240
605
|
agentOutput: AGENT_OUTPUT_OPTIONS.map((option) => trimFlagPrefix(option.flag)),
|
|
606
|
+
writeSafety: ["dry-run"],
|
|
241
607
|
};
|
|
242
608
|
|
|
243
609
|
export const COMMAND_FLAG_ALLOWLIST = {
|
|
@@ -460,6 +826,7 @@ export const COMMAND_FLAG_ALLOWLIST = {
|
|
|
460
826
|
SHARED_FLAGS.jsonAndJsonl,
|
|
461
827
|
SHARED_FLAGS.session,
|
|
462
828
|
SHARED_FLAGS.credentials,
|
|
829
|
+
SHARED_FLAGS.writeSafety,
|
|
463
830
|
["workout-id", "date", "outside"],
|
|
464
831
|
),
|
|
465
832
|
"copy-workout": mergeFlagGroups(
|
|
@@ -468,6 +835,7 @@ export const COMMAND_FLAG_ALLOWLIST = {
|
|
|
468
835
|
SHARED_FLAGS.jsonAndJsonl,
|
|
469
836
|
SHARED_FLAGS.session,
|
|
470
837
|
SHARED_FLAGS.credentials,
|
|
838
|
+
SHARED_FLAGS.writeSafety,
|
|
471
839
|
["id", "date"],
|
|
472
840
|
),
|
|
473
841
|
"workout-alternates": mergeFlagGroups(
|
|
@@ -484,6 +852,7 @@ export const COMMAND_FLAG_ALLOWLIST = {
|
|
|
484
852
|
SHARED_FLAGS.jsonAndJsonl,
|
|
485
853
|
SHARED_FLAGS.session,
|
|
486
854
|
SHARED_FLAGS.credentials,
|
|
855
|
+
SHARED_FLAGS.writeSafety,
|
|
487
856
|
["id", "to"],
|
|
488
857
|
),
|
|
489
858
|
"replace-workout": mergeFlagGroups(
|
|
@@ -492,6 +861,7 @@ export const COMMAND_FLAG_ALLOWLIST = {
|
|
|
492
861
|
SHARED_FLAGS.jsonAndJsonl,
|
|
493
862
|
SHARED_FLAGS.session,
|
|
494
863
|
SHARED_FLAGS.credentials,
|
|
864
|
+
SHARED_FLAGS.writeSafety,
|
|
495
865
|
["id", "alternate-id", "update-duration"],
|
|
496
866
|
),
|
|
497
867
|
"switch-workout": mergeFlagGroups(
|
|
@@ -500,7 +870,60 @@ export const COMMAND_FLAG_ALLOWLIST = {
|
|
|
500
870
|
SHARED_FLAGS.jsonAndJsonl,
|
|
501
871
|
SHARED_FLAGS.session,
|
|
502
872
|
SHARED_FLAGS.credentials,
|
|
873
|
+
SHARED_FLAGS.writeSafety,
|
|
503
874
|
["id", "mode"],
|
|
504
875
|
),
|
|
876
|
+
"add-event": mergeFlagGroups(
|
|
877
|
+
SHARED_FLAGS.help,
|
|
878
|
+
SHARED_FLAGS.output,
|
|
879
|
+
SHARED_FLAGS.jsonAndJsonl,
|
|
880
|
+
SHARED_FLAGS.session,
|
|
881
|
+
SHARED_FLAGS.credentials,
|
|
882
|
+
SHARED_FLAGS.writeSafety,
|
|
883
|
+
["name", "date", "discipline", "priority", "duration", "tss", "intensity", "notes", "full"],
|
|
884
|
+
),
|
|
885
|
+
"remove-workout": mergeFlagGroups(
|
|
886
|
+
SHARED_FLAGS.help,
|
|
887
|
+
SHARED_FLAGS.output,
|
|
888
|
+
SHARED_FLAGS.jsonAndJsonl,
|
|
889
|
+
SHARED_FLAGS.session,
|
|
890
|
+
SHARED_FLAGS.credentials,
|
|
891
|
+
SHARED_FLAGS.writeSafety,
|
|
892
|
+
["id"],
|
|
893
|
+
),
|
|
894
|
+
"workout-image": mergeFlagGroups(
|
|
895
|
+
SHARED_FLAGS.help,
|
|
896
|
+
SHARED_FLAGS.output,
|
|
897
|
+
SHARED_FLAGS.json,
|
|
898
|
+
SHARED_FLAGS.session,
|
|
899
|
+
SHARED_FLAGS.credentials,
|
|
900
|
+
["id", "file", "format", "width", "background"],
|
|
901
|
+
),
|
|
902
|
+
"annotation-details": mergeFlagGroups(
|
|
903
|
+
SHARED_FLAGS.help,
|
|
904
|
+
SHARED_FLAGS.output,
|
|
905
|
+
SHARED_FLAGS.jsonAndJsonl,
|
|
906
|
+
SHARED_FLAGS.session,
|
|
907
|
+
SHARED_FLAGS.credentials,
|
|
908
|
+
["id", "full"],
|
|
909
|
+
),
|
|
910
|
+
"add-annotation": mergeFlagGroups(
|
|
911
|
+
SHARED_FLAGS.help,
|
|
912
|
+
SHARED_FLAGS.output,
|
|
913
|
+
SHARED_FLAGS.jsonAndJsonl,
|
|
914
|
+
SHARED_FLAGS.session,
|
|
915
|
+
SHARED_FLAGS.credentials,
|
|
916
|
+
SHARED_FLAGS.writeSafety,
|
|
917
|
+
["type", "date", "days", "end-date", "title", "notes", "color-id"],
|
|
918
|
+
),
|
|
919
|
+
"remove-annotation": mergeFlagGroups(
|
|
920
|
+
SHARED_FLAGS.help,
|
|
921
|
+
SHARED_FLAGS.output,
|
|
922
|
+
SHARED_FLAGS.jsonAndJsonl,
|
|
923
|
+
SHARED_FLAGS.session,
|
|
924
|
+
SHARED_FLAGS.credentials,
|
|
925
|
+
SHARED_FLAGS.writeSafety,
|
|
926
|
+
["id"],
|
|
927
|
+
),
|
|
505
928
|
logout: mergeFlagGroups(SHARED_FLAGS.help, SHARED_FLAGS.output, SHARED_FLAGS.session),
|
|
506
929
|
};
|