trainerroad-cli 0.1.1 → 0.3.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,112 +2,272 @@ 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",
55
+ ],
56
+ },
57
+ "train-now": {
58
+ summary: "Fetch TrainerRoad AI suggested workouts (TrainNow) for a target duration (private mode).",
59
+ usage: [
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",
38
65
  ],
39
66
  },
40
67
  events: {
41
68
  summary: "Show calendar events/races from timeline (private mode).",
42
69
  usage: [
43
- "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",
44
75
  ],
45
76
  },
46
77
  annotations: {
47
78
  summary: "Show timeline annotations (time off, notes, illness/injury markers) (private mode).",
48
79
  usage: [
49
- "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",
50
85
  ],
51
86
  },
52
87
  levels: {
53
88
  summary: "Show progression levels by zone (private mode).",
54
89
  usage: [
55
- "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",
56
95
  ],
57
96
  },
58
97
  plan: {
59
98
  summary: "Show training plan data (current plan, phases, or all plans) (private mode).",
60
99
  usage: [
61
- "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",
62
105
  ],
63
106
  },
64
107
  "weight-history": {
65
108
  summary: "Show historical body-weight entries (private mode).",
66
109
  usage: [
67
- "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",
68
115
  ],
69
116
  },
70
117
  today: {
71
118
  summary: "Show today's planned/completed activity for private or public profile mode.",
72
119
  usage: [
73
- "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",
74
125
  ],
75
126
  },
76
127
  future: {
77
128
  summary: "Show future plan data for private or public profile mode.",
78
129
  usage: [
79
- "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",
80
135
  ],
81
136
  },
82
137
  past: {
83
138
  summary: "Show past activity data for private or public profile mode.",
84
139
  usage: [
85
- "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",
86
145
  ],
87
146
  },
88
147
  ftp: {
89
148
  summary: "Show FTP snapshot and FTP history (private or public mode).",
90
149
  usage: [
91
- "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",
92
155
  ],
93
156
  },
94
157
  "ftp-prediction": {
95
158
  summary: "Show AI FTP detection eligibility/status and progression impact (private mode).",
96
- usage: ["node src/cli.mjs ftp-prediction [--json]"],
159
+ usage: ["trainerroad-cli ftp-prediction [--json]"],
160
+ examples: ["trainerroad-cli ftp-prediction --json"],
97
161
  },
98
162
  "power-ranking": {
99
163
  summary: "Show best-power percentile ranking by duration (private mode).",
100
- usage: ["node src/cli.mjs power-ranking [--json|--jsonl]"],
164
+ usage: ["trainerroad-cli power-ranking [--json|--jsonl]"],
165
+ examples: ["trainerroad-cli power-ranking --json"],
101
166
  },
102
167
  "power-records": {
103
168
  summary: "Show date-range personal power records from TrainerRoad PR endpoint (private mode).",
104
169
  usage: [
105
- "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",
175
+ ],
176
+ },
177
+ "workout-library": {
178
+ summary: "Search the TrainerRoad workout library with agent-friendly filters (private mode).",
179
+ usage: [
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",
185
+ ],
186
+ },
187
+ "workout-recommend": {
188
+ summary: "Recommend library workouts by ranking candidates against target duration/level/TSS (private mode).",
189
+ usage: [
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",
195
+ ],
196
+ },
197
+ "workout-details": {
198
+ summary: "Fetch detailed workout-library metadata for a workout ID (private mode).",
199
+ usage: [
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",
205
+ ],
206
+ },
207
+ "add-workout": {
208
+ summary: "Add a library workout to the calendar on a target date (private mode; reconciles flaky API responses).",
209
+ usage: [
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",
215
+ ],
216
+ },
217
+ "copy-workout": {
218
+ summary: "Copy an existing planned workout to another date (private mode).",
219
+ usage: [
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",
225
+ ],
226
+ },
227
+ "workout-alternates": {
228
+ summary: "List alternate workout options for a planned workout (private mode).",
229
+ usage: [
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",
235
+ ],
236
+ },
237
+ "move-workout": {
238
+ summary: "Move a planned workout to a different date (private mode).",
239
+ usage: [
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",
245
+ ],
246
+ },
247
+ "replace-workout": {
248
+ summary: "Replace a planned workout with a specific alternate workout ID (private mode).",
249
+ usage: [
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",
255
+ ],
256
+ },
257
+ "switch-workout": {
258
+ summary: "Switch a planned workout between inside and outside variants (private mode).",
259
+ usage: [
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",
106
265
  ],
107
266
  },
108
267
  logout: {
109
268
  summary: "Clear local persisted session.",
110
- usage: ["node src/cli.mjs logout"],
269
+ usage: ["trainerroad-cli logout"],
270
+ examples: ["trainerroad-cli logout"],
111
271
  },
112
272
  };
113
273
 
@@ -116,6 +276,7 @@ export const PROJECT_NOTICE = "Unofficial tool. Not affiliated with or endorsed
116
276
  export const GLOBAL_NOTES = [
117
277
  PROJECT_NOTICE,
118
278
  "Environment: TR_USERNAME, TR_PASSWORD, TR_SESSION_FILE, TR_TIMEZONE",
279
+ "Non-interactive by default: pass flags or stdin; commands do not prompt.",
119
280
  "Timezone: --tz <IANA timezone> (for example America/New_York).",
120
281
  "Output modes: default JSON, --json, --jsonl, --output <path>",
121
282
  "Session file default: .trainerroad/session.json",
@@ -123,6 +284,7 @@ export const GLOBAL_NOTES = [
123
284
  "Public mode: username-based endpoint (`/app/api/tss/{username}`) with limited detail.",
124
285
  "Agent filters: --from --to --type --contains --min-tss --max-tss --sort --result-limit --fields",
125
286
  "Agent output: --records-only",
287
+ "Write commands: use --dry-run to preview calendar mutations before applying them.",
126
288
  ];
127
289
 
128
290
  export const AGENT_FILTER_OPTIONS = [
@@ -156,6 +318,160 @@ export const FILTERABLE_COMMANDS = new Set([
156
318
  "weight-history",
157
319
  ]);
158
320
 
321
+ export const COMMAND_REQUIRED_FLAGS = {
322
+ "workout-details": ["id"],
323
+ "add-workout": ["workout-id", "date"],
324
+ "copy-workout": ["id", "date"],
325
+ "workout-alternates": ["id"],
326
+ "move-workout": ["id", "to"],
327
+ "replace-workout": ["id", "alternate-id"],
328
+ "switch-workout": ["id", "mode"],
329
+ };
330
+
331
+ export const FLAG_DETAILS = {
332
+ help: { description: "Show command help and exit." },
333
+ output: { placeholder: "<path>", description: "Write command output to a file instead of stdout." },
334
+ json: { description: "Emit machine-readable JSON output." },
335
+ jsonl: { description: "Emit newline-delimited JSON records." },
336
+ "session-file": {
337
+ placeholder: "<path>",
338
+ description: "Override the persisted session file path.",
339
+ },
340
+ username: { placeholder: "<username>", description: "TrainerRoad account username." },
341
+ password: {
342
+ placeholder: "<password>",
343
+ description: "TrainerRoad account password. Prefer --password-stdin or TR_PASSWORD.",
344
+ },
345
+ "password-stdin": {
346
+ description: "Read the password from stdin so the command stays non-interactive and shell-history safe.",
347
+ },
348
+ "return-path": {
349
+ placeholder: "<path>",
350
+ description: "Override the login ReturnUrl used during the auth flow.",
351
+ },
352
+ level: { placeholder: "1|2|3", description: "Discovery detail level." },
353
+ command: { placeholder: "<command>", description: "Limit discovery/help output to one command." },
354
+ target: { placeholder: "<username>", description: "Public TrainerRoad profile username to query." },
355
+ public: {
356
+ description: "Force public-mode requests even when an authenticated session exists.",
357
+ },
358
+ full: { description: "Return fuller upstream payload data when the command supports it." },
359
+ duration: { placeholder: "<minutes>", description: "Requested workout duration in minutes." },
360
+ "num-suggestions": {
361
+ placeholder: "<count>",
362
+ description: "Maximum TrainNow suggestions to request.",
363
+ },
364
+ category: {
365
+ placeholder: "<value>",
366
+ description: "Command-specific category filter (for example easier, endurance, or outside).",
367
+ },
368
+ from: { placeholder: "YYYY-MM-DD", description: "Inclusive lower date bound." },
369
+ to: { placeholder: "YYYY-MM-DD", description: "Inclusive upper date bound or move target date." },
370
+ type: { placeholder: "<value>", description: "Command-specific record type filter." },
371
+ contains: { placeholder: "<text>", description: "Case-insensitive substring filter." },
372
+ "min-tss": { placeholder: "<number>", description: "Minimum TSS threshold." },
373
+ "max-tss": { placeholder: "<number>", description: "Maximum TSS threshold." },
374
+ sort: { placeholder: "<mode>", description: "Command-specific sort mode." },
375
+ "result-limit": { placeholder: "<count>", description: "Limit records after filters are applied." },
376
+ fields: { placeholder: "a,b,c", description: "Project record fields for leaner downstream payloads." },
377
+ "records-only": {
378
+ description: "Return only the envelope and records arrays when supported.",
379
+ },
380
+ view: { placeholder: "current|phases|plans", description: "Plan view to return." },
381
+ date: { placeholder: "YYYY-MM-DD", description: "Target calendar date." },
382
+ details: { description: "Include additional workout/activity detail in the payload." },
383
+ days: { placeholder: "<count>", description: "Relative day window size." },
384
+ limit: { placeholder: "<count>", description: "Upstream or record limit for the command." },
385
+ "history-limit": {
386
+ placeholder: "<count>",
387
+ description: "Maximum FTP history entries to include.",
388
+ },
389
+ "start-date": { placeholder: "YYYY-MM-DD", description: "Personal records window start date." },
390
+ "end-date": { placeholder: "YYYY-MM-DD", description: "Personal records window end date." },
391
+ "row-type": {
392
+ placeholder: "100|101",
393
+ description: "TrainerRoad PR row type to query.",
394
+ },
395
+ "indoor-only": {
396
+ placeholder: "true|false",
397
+ description: "Restrict personal-record queries to indoor rides.",
398
+ },
399
+ slot: {
400
+ placeholder: "<value>",
401
+ description: "Reserved upstream query slot used by the power-records endpoint.",
402
+ },
403
+ search: { placeholder: "<text>", description: "Free-text workout search query." },
404
+ zone: { placeholder: "<name>", description: "Workout zone name filter." },
405
+ "zone-id": { placeholder: "<id>", description: "Workout zone numeric identifier." },
406
+ profile: { placeholder: "<name>", description: "Workout profile name filter." },
407
+ "profile-id": { placeholder: "<id>", description: "Workout profile numeric identifier." },
408
+ outside: {
409
+ placeholder: "true|false",
410
+ description: "Filter or create the outside workout variant where supported.",
411
+ },
412
+ "has-instructions": {
413
+ placeholder: "true|false",
414
+ description: "Filter workouts by whether text instructions exist.",
415
+ },
416
+ "min-duration": { placeholder: "<minutes>", description: "Minimum workout duration." },
417
+ "max-duration": { placeholder: "<minutes>", description: "Maximum workout duration." },
418
+ "min-level": { placeholder: "<number>", description: "Minimum workout progression level." },
419
+ "max-level": { placeholder: "<number>", description: "Maximum workout progression level." },
420
+ "target-duration": {
421
+ placeholder: "<minutes>",
422
+ description: "Target duration used when ranking recommended workouts.",
423
+ },
424
+ "target-tss": {
425
+ placeholder: "<number>",
426
+ description: "Target TSS used when ranking recommended workouts.",
427
+ },
428
+ "target-level": {
429
+ placeholder: "<number>",
430
+ description: "Target progression level used when ranking recommended workouts.",
431
+ },
432
+ count: { placeholder: "<count>", description: "Number of recommendations to return." },
433
+ "candidate-limit": {
434
+ placeholder: "<count>",
435
+ description: "Maximum candidate workouts to score before ranking.",
436
+ },
437
+ "page-size": {
438
+ placeholder: "<count>",
439
+ description: "Upstream workout library page size.",
440
+ },
441
+ id: { placeholder: "<id>", description: "Workout, planned activity, or record identifier." },
442
+ "include-chart": {
443
+ placeholder: "true|false",
444
+ description: "Include workout chart/sample data in workout-details.",
445
+ },
446
+ "chart-point-limit": {
447
+ placeholder: "<count>",
448
+ description: "Maximum chart points returned with --include-chart.",
449
+ },
450
+ "workout-id": {
451
+ placeholder: "<workout-id>",
452
+ description: "TrainerRoad workout library identifier.",
453
+ },
454
+ "dry-run": {
455
+ description: "Preview the write command and exit without mutating the calendar.",
456
+ },
457
+ "alternate-id": {
458
+ placeholder: "<workout-id>",
459
+ description: "Alternate workout ID to apply during replace-workout.",
460
+ },
461
+ "update-duration": {
462
+ placeholder: "true|false",
463
+ description: "Let TrainerRoad update the duration during replace-workout.",
464
+ },
465
+ mode: {
466
+ placeholder: "inside|outside",
467
+ description: "Target workout delivery mode for switch-workout.",
468
+ },
469
+ tz: {
470
+ placeholder: "<IANA timezone>",
471
+ description: "Override local-day bucketing (defaults to TR_TIMEZONE or system timezone).",
472
+ },
473
+ };
474
+
159
475
  function trimFlagPrefix(flag) {
160
476
  return String(flag ?? "").replace(/^--/, "").trim();
161
477
  }
@@ -178,6 +494,7 @@ const SHARED_FLAGS = {
178
494
  jsonAndJsonl: ["json", "jsonl"],
179
495
  agentFilters: AGENT_FILTER_OPTIONS.map((option) => trimFlagPrefix(option.flag)),
180
496
  agentOutput: AGENT_OUTPUT_OPTIONS.map((option) => trimFlagPrefix(option.flag)),
497
+ writeSafety: ["dry-run"],
181
498
  };
182
499
 
183
500
  export const COMMAND_FLAG_ALLOWLIST = {
@@ -212,6 +529,14 @@ export const COMMAND_FLAG_ALLOWLIST = {
212
529
  SHARED_FLAGS.publicProfile,
213
530
  ["full"],
214
531
  ),
532
+ "train-now": mergeFlagGroups(
533
+ SHARED_FLAGS.help,
534
+ SHARED_FLAGS.output,
535
+ SHARED_FLAGS.jsonAndJsonl,
536
+ SHARED_FLAGS.session,
537
+ SHARED_FLAGS.credentials,
538
+ ["duration", "num-suggestions", "category"],
539
+ ),
215
540
  events: mergeFlagGroups(
216
541
  SHARED_FLAGS.help,
217
542
  SHARED_FLAGS.output,
@@ -324,5 +649,120 @@ export const COMMAND_FLAG_ALLOWLIST = {
324
649
  SHARED_FLAGS.credentials,
325
650
  ["start-date", "end-date", "row-type", "indoor-only", "slot", "limit", "full"],
326
651
  ),
652
+ "workout-library": mergeFlagGroups(
653
+ SHARED_FLAGS.help,
654
+ SHARED_FLAGS.output,
655
+ SHARED_FLAGS.jsonAndJsonl,
656
+ SHARED_FLAGS.session,
657
+ SHARED_FLAGS.credentials,
658
+ [
659
+ "search",
660
+ "zone",
661
+ "zone-id",
662
+ "profile",
663
+ "profile-id",
664
+ "outside",
665
+ "has-instructions",
666
+ "min-duration",
667
+ "max-duration",
668
+ "min-tss",
669
+ "max-tss",
670
+ "min-level",
671
+ "max-level",
672
+ "sort",
673
+ "limit",
674
+ "page-size",
675
+ ],
676
+ ),
677
+ "workout-recommend": mergeFlagGroups(
678
+ SHARED_FLAGS.help,
679
+ SHARED_FLAGS.output,
680
+ SHARED_FLAGS.jsonAndJsonl,
681
+ SHARED_FLAGS.session,
682
+ SHARED_FLAGS.credentials,
683
+ [
684
+ "search",
685
+ "zone",
686
+ "zone-id",
687
+ "profile",
688
+ "profile-id",
689
+ "outside",
690
+ "has-instructions",
691
+ "min-duration",
692
+ "max-duration",
693
+ "min-tss",
694
+ "max-tss",
695
+ "min-level",
696
+ "max-level",
697
+ "target-duration",
698
+ "target-tss",
699
+ "target-level",
700
+ "count",
701
+ "candidate-limit",
702
+ "page-size",
703
+ "sort",
704
+ ],
705
+ ),
706
+ "workout-details": mergeFlagGroups(
707
+ SHARED_FLAGS.help,
708
+ SHARED_FLAGS.output,
709
+ SHARED_FLAGS.jsonAndJsonl,
710
+ SHARED_FLAGS.session,
711
+ SHARED_FLAGS.credentials,
712
+ ["id", "include-chart", "chart-point-limit"],
713
+ ),
714
+ "add-workout": mergeFlagGroups(
715
+ SHARED_FLAGS.help,
716
+ SHARED_FLAGS.output,
717
+ SHARED_FLAGS.jsonAndJsonl,
718
+ SHARED_FLAGS.session,
719
+ SHARED_FLAGS.credentials,
720
+ SHARED_FLAGS.writeSafety,
721
+ ["workout-id", "date", "outside"],
722
+ ),
723
+ "copy-workout": mergeFlagGroups(
724
+ SHARED_FLAGS.help,
725
+ SHARED_FLAGS.output,
726
+ SHARED_FLAGS.jsonAndJsonl,
727
+ SHARED_FLAGS.session,
728
+ SHARED_FLAGS.credentials,
729
+ SHARED_FLAGS.writeSafety,
730
+ ["id", "date"],
731
+ ),
732
+ "workout-alternates": mergeFlagGroups(
733
+ SHARED_FLAGS.help,
734
+ SHARED_FLAGS.output,
735
+ SHARED_FLAGS.jsonAndJsonl,
736
+ SHARED_FLAGS.session,
737
+ SHARED_FLAGS.credentials,
738
+ ["id", "category"],
739
+ ),
740
+ "move-workout": mergeFlagGroups(
741
+ SHARED_FLAGS.help,
742
+ SHARED_FLAGS.output,
743
+ SHARED_FLAGS.jsonAndJsonl,
744
+ SHARED_FLAGS.session,
745
+ SHARED_FLAGS.credentials,
746
+ SHARED_FLAGS.writeSafety,
747
+ ["id", "to"],
748
+ ),
749
+ "replace-workout": mergeFlagGroups(
750
+ SHARED_FLAGS.help,
751
+ SHARED_FLAGS.output,
752
+ SHARED_FLAGS.jsonAndJsonl,
753
+ SHARED_FLAGS.session,
754
+ SHARED_FLAGS.credentials,
755
+ SHARED_FLAGS.writeSafety,
756
+ ["id", "alternate-id", "update-duration"],
757
+ ),
758
+ "switch-workout": mergeFlagGroups(
759
+ SHARED_FLAGS.help,
760
+ SHARED_FLAGS.output,
761
+ SHARED_FLAGS.jsonAndJsonl,
762
+ SHARED_FLAGS.session,
763
+ SHARED_FLAGS.credentials,
764
+ SHARED_FLAGS.writeSafety,
765
+ ["id", "mode"],
766
+ ),
327
767
  logout: mergeFlagGroups(SHARED_FLAGS.help, SHARED_FLAGS.output, SHARED_FLAGS.session),
328
768
  };