endurance-coach 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +21 -0
- package/README.md +94 -0
- package/bin/claude-coach.js +10 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +1077 -0
- package/dist/db/client.d.ts +8 -0
- package/dist/db/client.js +111 -0
- package/dist/db/migrate.d.ts +1 -0
- package/dist/db/migrate.js +14 -0
- package/dist/db/schema.sql +105 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +13 -0
- package/dist/lib/config.d.ts +27 -0
- package/dist/lib/config.js +86 -0
- package/dist/lib/logging.d.ts +13 -0
- package/dist/lib/logging.js +28 -0
- package/dist/schema/training-plan.d.ts +288 -0
- package/dist/schema/training-plan.js +88 -0
- package/dist/schema/training-plan.schema.d.ts +1875 -0
- package/dist/schema/training-plan.schema.js +418 -0
- package/dist/strava/api.d.ts +5 -0
- package/dist/strava/api.js +63 -0
- package/dist/strava/oauth.d.ts +4 -0
- package/dist/strava/oauth.js +113 -0
- package/dist/strava/types.d.ts +46 -0
- package/dist/strava/types.js +1 -0
- package/dist/viewer/lib/UpdatePlan.d.ts +48 -0
- package/dist/viewer/lib/UpdatePlan.js +209 -0
- package/dist/viewer/lib/export/erg.d.ts +26 -0
- package/dist/viewer/lib/export/erg.js +208 -0
- package/dist/viewer/lib/export/fit.d.ts +25 -0
- package/dist/viewer/lib/export/fit.js +308 -0
- package/dist/viewer/lib/export/ics.d.ts +13 -0
- package/dist/viewer/lib/export/ics.js +142 -0
- package/dist/viewer/lib/export/index.d.ts +50 -0
- package/dist/viewer/lib/export/index.js +229 -0
- package/dist/viewer/lib/export/zwo.d.ts +21 -0
- package/dist/viewer/lib/export/zwo.js +233 -0
- package/dist/viewer/lib/utils.d.ts +14 -0
- package/dist/viewer/lib/utils.js +123 -0
- package/dist/viewer/main.d.ts +5 -0
- package/dist/viewer/main.js +6 -0
- package/dist/viewer/stores/changes.d.ts +21 -0
- package/dist/viewer/stores/changes.js +49 -0
- package/dist/viewer/stores/plan.d.ts +11 -0
- package/dist/viewer/stores/plan.js +40 -0
- package/dist/viewer/stores/settings.d.ts +53 -0
- package/dist/viewer/stores/settings.js +215 -0
- package/package.json +74 -0
- package/templates/plan-viewer.html +70 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Training Plan JSON Schema
|
|
3
|
+
*
|
|
4
|
+
* Designed to be comprehensive enough for export to:
|
|
5
|
+
* - Zwift (.zwo workouts)
|
|
6
|
+
* - Garmin Connect (.fit workouts)
|
|
7
|
+
* - TrainingPeaks
|
|
8
|
+
* - Other training platforms
|
|
9
|
+
*/
|
|
10
|
+
export type Sport = "swim" | "bike" | "run" | "strength" | "brick" | "race" | "rest";
|
|
11
|
+
export type WorkoutType = "rest" | "recovery" | "endurance" | "tempo" | "threshold" | "intervals" | "vo2max" | "sprint" | "race" | "brick" | "technique" | "openwater" | "hills" | "long";
|
|
12
|
+
export type IntensityUnit = "percent_ftp" | "percent_lthr" | "hr_zone" | "pace_zone" | "rpe" | "css_offset";
|
|
13
|
+
export type DurationUnit = "seconds" | "minutes" | "hours" | "meters" | "kilometers" | "miles" | "yards" | "laps";
|
|
14
|
+
export type StepType = "warmup" | "work" | "recovery" | "rest" | "cooldown" | "interval_set";
|
|
15
|
+
export type SwimDistanceUnit = "meters" | "yards";
|
|
16
|
+
export type LandDistanceUnit = "kilometers" | "miles";
|
|
17
|
+
export type FirstDayOfWeek = "monday" | "sunday";
|
|
18
|
+
export interface UnitPreferences {
|
|
19
|
+
swim: SwimDistanceUnit;
|
|
20
|
+
bike: LandDistanceUnit;
|
|
21
|
+
run: LandDistanceUnit;
|
|
22
|
+
firstDayOfWeek: FirstDayOfWeek;
|
|
23
|
+
}
|
|
24
|
+
export declare const defaultPreferences: UnitPreferences;
|
|
25
|
+
export interface IntensityTarget {
|
|
26
|
+
unit: IntensityUnit;
|
|
27
|
+
value: number;
|
|
28
|
+
valueLow?: number;
|
|
29
|
+
valueHigh?: number;
|
|
30
|
+
description?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface DurationTarget {
|
|
33
|
+
unit: DurationUnit;
|
|
34
|
+
value: number;
|
|
35
|
+
}
|
|
36
|
+
export interface WorkoutStep {
|
|
37
|
+
type: StepType;
|
|
38
|
+
name?: string;
|
|
39
|
+
duration: DurationTarget;
|
|
40
|
+
intensity: IntensityTarget;
|
|
41
|
+
cadence?: {
|
|
42
|
+
low: number;
|
|
43
|
+
high: number;
|
|
44
|
+
};
|
|
45
|
+
notes?: string;
|
|
46
|
+
}
|
|
47
|
+
export interface IntervalSet {
|
|
48
|
+
type: "interval_set";
|
|
49
|
+
name?: string;
|
|
50
|
+
repeats: number;
|
|
51
|
+
steps: WorkoutStep[];
|
|
52
|
+
}
|
|
53
|
+
export interface StructuredWorkout {
|
|
54
|
+
warmup?: WorkoutStep[];
|
|
55
|
+
main: (WorkoutStep | IntervalSet)[];
|
|
56
|
+
cooldown?: WorkoutStep[];
|
|
57
|
+
totalDuration?: DurationTarget;
|
|
58
|
+
estimatedTSS?: number;
|
|
59
|
+
estimatedIF?: number;
|
|
60
|
+
}
|
|
61
|
+
export interface Workout {
|
|
62
|
+
id: string;
|
|
63
|
+
sport: Sport;
|
|
64
|
+
type: WorkoutType;
|
|
65
|
+
name: string;
|
|
66
|
+
description: string;
|
|
67
|
+
durationMinutes?: number;
|
|
68
|
+
distanceMeters?: number;
|
|
69
|
+
primaryZone?: string;
|
|
70
|
+
targetHR?: {
|
|
71
|
+
low: number;
|
|
72
|
+
high: number;
|
|
73
|
+
};
|
|
74
|
+
targetPower?: {
|
|
75
|
+
low: number;
|
|
76
|
+
high: number;
|
|
77
|
+
};
|
|
78
|
+
targetPace?: {
|
|
79
|
+
low: string;
|
|
80
|
+
high: string;
|
|
81
|
+
};
|
|
82
|
+
rpe?: number;
|
|
83
|
+
structure?: StructuredWorkout;
|
|
84
|
+
humanReadable?: string;
|
|
85
|
+
completed: boolean;
|
|
86
|
+
completedAt?: string;
|
|
87
|
+
actualDuration?: number;
|
|
88
|
+
actualDistance?: number;
|
|
89
|
+
notes?: string;
|
|
90
|
+
}
|
|
91
|
+
export interface TrainingDay {
|
|
92
|
+
date: string;
|
|
93
|
+
dayOfWeek: string;
|
|
94
|
+
workouts: Workout[];
|
|
95
|
+
}
|
|
96
|
+
export interface WeekSummary {
|
|
97
|
+
totalHours: number;
|
|
98
|
+
totalTSS?: number;
|
|
99
|
+
bySport: {
|
|
100
|
+
[key in Sport]?: {
|
|
101
|
+
sessions: number;
|
|
102
|
+
hours: number;
|
|
103
|
+
km?: number;
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export interface TrainingWeek {
|
|
108
|
+
weekNumber: number;
|
|
109
|
+
startDate: string;
|
|
110
|
+
endDate: string;
|
|
111
|
+
phase: string;
|
|
112
|
+
focus: string;
|
|
113
|
+
targetHours: number;
|
|
114
|
+
days: TrainingDay[];
|
|
115
|
+
summary: WeekSummary;
|
|
116
|
+
isRecoveryWeek: boolean;
|
|
117
|
+
}
|
|
118
|
+
export interface HeartRateZones {
|
|
119
|
+
lthr: number;
|
|
120
|
+
zones: {
|
|
121
|
+
zone: number;
|
|
122
|
+
name: string;
|
|
123
|
+
percentLow: number;
|
|
124
|
+
percentHigh: number;
|
|
125
|
+
hrLow: number;
|
|
126
|
+
hrHigh: number;
|
|
127
|
+
}[];
|
|
128
|
+
}
|
|
129
|
+
export interface PowerZones {
|
|
130
|
+
ftp: number;
|
|
131
|
+
zones: {
|
|
132
|
+
zone: number;
|
|
133
|
+
name: string;
|
|
134
|
+
percentLow: number;
|
|
135
|
+
percentHigh: number;
|
|
136
|
+
wattsLow: number;
|
|
137
|
+
wattsHigh: number;
|
|
138
|
+
}[];
|
|
139
|
+
}
|
|
140
|
+
export interface SwimZones {
|
|
141
|
+
css: string;
|
|
142
|
+
cssSeconds: number;
|
|
143
|
+
zones: {
|
|
144
|
+
zone: number;
|
|
145
|
+
name: string;
|
|
146
|
+
paceOffset: number;
|
|
147
|
+
pace: string;
|
|
148
|
+
}[];
|
|
149
|
+
}
|
|
150
|
+
export interface PaceZones {
|
|
151
|
+
thresholdPace: string;
|
|
152
|
+
thresholdPaceSeconds: number;
|
|
153
|
+
zones: {
|
|
154
|
+
zone: string;
|
|
155
|
+
name: string;
|
|
156
|
+
pace: string;
|
|
157
|
+
paceSeconds: number;
|
|
158
|
+
}[];
|
|
159
|
+
}
|
|
160
|
+
export interface AthleteZones {
|
|
161
|
+
run?: {
|
|
162
|
+
hr?: HeartRateZones;
|
|
163
|
+
pace?: PaceZones;
|
|
164
|
+
};
|
|
165
|
+
bike?: {
|
|
166
|
+
hr?: HeartRateZones;
|
|
167
|
+
power?: PowerZones;
|
|
168
|
+
};
|
|
169
|
+
swim?: SwimZones;
|
|
170
|
+
maxHR?: number;
|
|
171
|
+
restingHR?: number;
|
|
172
|
+
weight?: number;
|
|
173
|
+
}
|
|
174
|
+
export interface AthleteAssessment {
|
|
175
|
+
foundation: {
|
|
176
|
+
raceHistory: string[];
|
|
177
|
+
peakTrainingLoad: number;
|
|
178
|
+
foundationLevel: "beginner" | "intermediate" | "advanced" | "elite";
|
|
179
|
+
yearsInSport: number;
|
|
180
|
+
};
|
|
181
|
+
currentForm: {
|
|
182
|
+
weeklyVolume: {
|
|
183
|
+
total: number;
|
|
184
|
+
swim?: number;
|
|
185
|
+
bike?: number;
|
|
186
|
+
run?: number;
|
|
187
|
+
};
|
|
188
|
+
longestSessions: {
|
|
189
|
+
swim?: number;
|
|
190
|
+
bike?: number;
|
|
191
|
+
run?: number;
|
|
192
|
+
};
|
|
193
|
+
consistency: number;
|
|
194
|
+
timeSincePeakFitness?: string;
|
|
195
|
+
reasonForTimeOff?: string;
|
|
196
|
+
};
|
|
197
|
+
strengths: {
|
|
198
|
+
sport: Sport;
|
|
199
|
+
evidence: string;
|
|
200
|
+
}[];
|
|
201
|
+
limiters: {
|
|
202
|
+
sport: Sport;
|
|
203
|
+
evidence: string;
|
|
204
|
+
}[];
|
|
205
|
+
constraints: string[];
|
|
206
|
+
}
|
|
207
|
+
export interface TrainingPhase {
|
|
208
|
+
name: string;
|
|
209
|
+
startWeek: number;
|
|
210
|
+
endWeek: number;
|
|
211
|
+
focus: string;
|
|
212
|
+
weeklyHoursRange: {
|
|
213
|
+
low: number;
|
|
214
|
+
high: number;
|
|
215
|
+
};
|
|
216
|
+
keyWorkouts: string[];
|
|
217
|
+
physiologicalGoals: string[];
|
|
218
|
+
}
|
|
219
|
+
export interface RaceStrategy {
|
|
220
|
+
event: {
|
|
221
|
+
name: string;
|
|
222
|
+
date: string;
|
|
223
|
+
type: string;
|
|
224
|
+
distances?: {
|
|
225
|
+
swim?: number;
|
|
226
|
+
bike?: number;
|
|
227
|
+
run?: number;
|
|
228
|
+
};
|
|
229
|
+
};
|
|
230
|
+
pacing: {
|
|
231
|
+
swim?: {
|
|
232
|
+
target: string;
|
|
233
|
+
notes: string;
|
|
234
|
+
};
|
|
235
|
+
bike?: {
|
|
236
|
+
targetPower: string;
|
|
237
|
+
targetHR: string;
|
|
238
|
+
notes: string;
|
|
239
|
+
};
|
|
240
|
+
run?: {
|
|
241
|
+
targetPace: string;
|
|
242
|
+
targetHR: string;
|
|
243
|
+
notes: string;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
nutrition: {
|
|
247
|
+
preRace: string;
|
|
248
|
+
during: {
|
|
249
|
+
carbsPerHour: number;
|
|
250
|
+
fluidPerHour: string;
|
|
251
|
+
products: string[];
|
|
252
|
+
};
|
|
253
|
+
notes: string;
|
|
254
|
+
};
|
|
255
|
+
taper: {
|
|
256
|
+
startDate: string;
|
|
257
|
+
volumeReduction: number;
|
|
258
|
+
notes: string;
|
|
259
|
+
};
|
|
260
|
+
raceDay: {
|
|
261
|
+
wakeUpTime?: string;
|
|
262
|
+
preRaceMeal?: string;
|
|
263
|
+
warmUp?: string;
|
|
264
|
+
mentalCues?: string[];
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
export interface TrainingPlan {
|
|
268
|
+
version: "1.0";
|
|
269
|
+
meta: {
|
|
270
|
+
id: string;
|
|
271
|
+
athlete: string;
|
|
272
|
+
event: string;
|
|
273
|
+
eventDate: string;
|
|
274
|
+
planStartDate: string;
|
|
275
|
+
planEndDate: string;
|
|
276
|
+
createdAt: string;
|
|
277
|
+
updatedAt: string;
|
|
278
|
+
totalWeeks: number;
|
|
279
|
+
generatedBy: string;
|
|
280
|
+
};
|
|
281
|
+
preferences: UnitPreferences;
|
|
282
|
+
assessment: AthleteAssessment;
|
|
283
|
+
zones: AthleteZones;
|
|
284
|
+
phases: TrainingPhase[];
|
|
285
|
+
weeks: TrainingWeek[];
|
|
286
|
+
raceStrategy: RaceStrategy;
|
|
287
|
+
}
|
|
288
|
+
export declare const exampleWorkout: Workout;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Training Plan JSON Schema
|
|
3
|
+
*
|
|
4
|
+
* Designed to be comprehensive enough for export to:
|
|
5
|
+
* - Zwift (.zwo workouts)
|
|
6
|
+
* - Garmin Connect (.fit workouts)
|
|
7
|
+
* - TrainingPeaks
|
|
8
|
+
* - Other training platforms
|
|
9
|
+
*/
|
|
10
|
+
// Default preferences (metric)
|
|
11
|
+
export const defaultPreferences = {
|
|
12
|
+
swim: "meters",
|
|
13
|
+
bike: "kilometers",
|
|
14
|
+
run: "kilometers",
|
|
15
|
+
firstDayOfWeek: "monday",
|
|
16
|
+
};
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// Example/Template
|
|
19
|
+
// ============================================================================
|
|
20
|
+
export const exampleWorkout = {
|
|
21
|
+
id: "week1-tue-swim",
|
|
22
|
+
sport: "swim",
|
|
23
|
+
type: "technique",
|
|
24
|
+
name: "Technique + Endurance",
|
|
25
|
+
description: "Focus on catch and pull mechanics with aerobic base work",
|
|
26
|
+
durationMinutes: 60,
|
|
27
|
+
distanceMeters: 2500,
|
|
28
|
+
primaryZone: "Zone 2",
|
|
29
|
+
targetHR: { low: 120, high: 135 },
|
|
30
|
+
structure: {
|
|
31
|
+
warmup: [
|
|
32
|
+
{
|
|
33
|
+
type: "warmup",
|
|
34
|
+
name: "Easy swim",
|
|
35
|
+
duration: { unit: "meters", value: 300 },
|
|
36
|
+
intensity: { unit: "css_offset", value: 15, description: "CSS + 15s/100m" },
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: "warmup",
|
|
40
|
+
name: "Drill set",
|
|
41
|
+
duration: { unit: "meters", value: 200 },
|
|
42
|
+
intensity: { unit: "rpe", value: 3, description: "Easy" },
|
|
43
|
+
notes: "4x50m: catch-up, fingertip drag, fist drill, swim",
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
main: [
|
|
47
|
+
{
|
|
48
|
+
type: "interval_set",
|
|
49
|
+
name: "Threshold set",
|
|
50
|
+
repeats: 5,
|
|
51
|
+
steps: [
|
|
52
|
+
{
|
|
53
|
+
type: "work",
|
|
54
|
+
duration: { unit: "meters", value: 100 },
|
|
55
|
+
intensity: { unit: "css_offset", value: 0, description: "CSS pace" },
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: "rest",
|
|
59
|
+
duration: { unit: "seconds", value: 15 },
|
|
60
|
+
intensity: { unit: "rpe", value: 1 },
|
|
61
|
+
},
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: "work",
|
|
66
|
+
name: "Aerobic pull",
|
|
67
|
+
duration: { unit: "meters", value: 800 },
|
|
68
|
+
intensity: { unit: "css_offset", value: 10, description: "CSS + 10s" },
|
|
69
|
+
notes: "With pull buoy, focus on rotation",
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
cooldown: [
|
|
73
|
+
{
|
|
74
|
+
type: "cooldown",
|
|
75
|
+
name: "Easy swim",
|
|
76
|
+
duration: { unit: "meters", value: 200 },
|
|
77
|
+
intensity: { unit: "rpe", value: 2 },
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
totalDuration: { unit: "minutes", value: 60 },
|
|
81
|
+
estimatedTSS: 45,
|
|
82
|
+
},
|
|
83
|
+
humanReadable: `Warm-up: 300m easy, 4x50m drills
|
|
84
|
+
Main: 5x100m @ CSS, 15s rest
|
|
85
|
+
800m pull @ CSS+10
|
|
86
|
+
Cool-down: 200m easy`,
|
|
87
|
+
completed: false,
|
|
88
|
+
};
|