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.
@@ -2,172 +2,327 @@ export const COMMANDS = {
2
2
  help: {
3
3
  summary: "Show global help or command help.",
4
4
  usage: [
5
- "node src/cli.mjs help",
6
- "node src/cli.mjs help <command>",
7
- "node src/cli.mjs <command> --help",
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
- "node src/cli.mjs discover",
14
- "node src/cli.mjs discover --level 1|2|3 [--json]",
15
- "node src/cli.mjs discover --command future --level 3 --json",
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: ["node src/cli.mjs capabilities [--json]"],
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
- "node src/cli.mjs login --username <u> --password <p> [--return-path /app/career/<username>]",
26
- "node src/cli.mjs login --username <u> --password-stdin",
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: ["node src/cli.mjs whoami [--json]"],
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
- "node src/cli.mjs timeline [--target <username>] [--public] [--full] [--json]",
37
- "node src/cli.mjs timeline --target quinnsprouse --public --json",
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
- "node src/cli.mjs train-now [--duration <minutes>] [--num-suggestions <n>] [--category climbing|endurance|attacking] [--json|--jsonl]",
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
- "node src/cli.mjs events [--full] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--type <value>] [--contains <text>] [--min-tss <n>] [--max-tss <n>] [--sort date|date-desc|name|name-desc|tss|tss-desc] [--result-limit <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs annotations [--full] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--type <value>] [--contains <text>] [--sort date|date-desc|name|name-desc] [--result-limit <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs levels [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--type <zone|progressionId>] [--contains <text>] [--sort name|name-desc|date|date-desc] [--result-limit <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs 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 <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs weight-history [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--contains <text>] [--sort date|date-desc] [--result-limit <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs today [--date YYYY-MM-DD] [--target <username>] [--public] [--details] [--type <value>] [--contains <text>] [--min-tss <n>] [--max-tss <n>] [--sort date|date-desc|tss|tss-desc] [--result-limit <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs future [--days <n>] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--target <username>] [--public] [--details] [--type <value>] [--contains <text>] [--min-tss <n>] [--max-tss <n>] [--sort date|date-desc|tss|tss-desc] [--result-limit <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs past [--days <n>] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--limit <n>] [--target <username>] [--public] [--details] [--type <value>] [--contains <text>] [--min-tss <n>] [--max-tss <n>] [--sort date|date-desc|tss|tss-desc] [--result-limit <n>] [--fields a,b] [--records-only] [--json|--jsonl]",
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
- "node src/cli.mjs ftp [--target <username>] [--public] [--history-limit <n>] [--json|--jsonl]",
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: ["node src/cli.mjs ftp-prediction [--json]"],
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: ["node src/cli.mjs power-ranking [--json|--jsonl]"],
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
- "node src/cli.mjs power-records [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD] [--row-type 100|101] [--indoor-only true|false] [--limit <n>] [--full] [--json|--jsonl]",
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
- 'node src/cli.mjs 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 <n>] [--max-tss <n>] [--min-level <n>] [--max-level <n>] [--sort level|level-desc|duration|duration-desc|tss|tss-desc|name|name-desc] [--limit <n>] [--page-size <n>] [--json|--jsonl]',
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
- 'node src/cli.mjs 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 <n>] [--max-tss <n>] [--min-level <n>] [--max-level <n>] [--target-duration <minutes>] [--target-tss <n>] [--target-level <n>] [--count <n>] [--candidate-limit <n>] [--page-size <n>] [--sort level|level-desc|duration|duration-desc|tss|tss-desc|name|name-desc] [--json|--jsonl]',
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
- "node src/cli.mjs workout-details --id <workout-id> [--include-chart true|false] [--chart-point-limit <n>] [--json|--jsonl]",
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
- "node src/cli.mjs add-workout --workout-id <workout-id> --date YYYY-MM-DD [--outside true|false] [--json|--jsonl]",
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
- "node src/cli.mjs copy-workout --id <planned-activity-id> --date YYYY-MM-DD [--json|--jsonl]",
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
- "node src/cli.mjs workout-alternates --id <planned-activity-id> [--category similar|easier|harder|longer|shorter] [--json|--jsonl]",
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
- "node src/cli.mjs move-workout --id <planned-activity-id> --to YYYY-MM-DD [--json|--jsonl]",
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
- "node src/cli.mjs replace-workout --id <planned-activity-id> --alternate-id <workout-id> [--update-duration true|false] [--json|--jsonl]",
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
- "node src/cli.mjs switch-workout --id <planned-activity-id> --mode inside|outside [--json|--jsonl]",
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: ["node src/cli.mjs logout"],
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
  };