claude-coach 0.0.1

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.
Files changed (43) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +84 -0
  3. package/bin/claude-coach.js +10 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +277 -0
  6. package/dist/db/client.d.ts +4 -0
  7. package/dist/db/client.js +45 -0
  8. package/dist/db/migrate.d.ts +1 -0
  9. package/dist/db/migrate.js +14 -0
  10. package/dist/lib/config.d.ts +27 -0
  11. package/dist/lib/config.js +86 -0
  12. package/dist/lib/logging.d.ts +13 -0
  13. package/dist/lib/logging.js +28 -0
  14. package/dist/schema/training-plan.d.ts +288 -0
  15. package/dist/schema/training-plan.js +88 -0
  16. package/dist/strava/api.d.ts +5 -0
  17. package/dist/strava/api.js +63 -0
  18. package/dist/strava/oauth.d.ts +4 -0
  19. package/dist/strava/oauth.js +113 -0
  20. package/dist/strava/types.d.ts +46 -0
  21. package/dist/strava/types.js +1 -0
  22. package/dist/viewer/lib/export/erg.d.ts +26 -0
  23. package/dist/viewer/lib/export/erg.js +206 -0
  24. package/dist/viewer/lib/export/fit.d.ts +25 -0
  25. package/dist/viewer/lib/export/fit.js +307 -0
  26. package/dist/viewer/lib/export/ics.d.ts +13 -0
  27. package/dist/viewer/lib/export/ics.js +138 -0
  28. package/dist/viewer/lib/export/index.d.ts +50 -0
  29. package/dist/viewer/lib/export/index.js +229 -0
  30. package/dist/viewer/lib/export/zwo.d.ts +21 -0
  31. package/dist/viewer/lib/export/zwo.js +230 -0
  32. package/dist/viewer/lib/utils.d.ts +14 -0
  33. package/dist/viewer/lib/utils.js +118 -0
  34. package/dist/viewer/main.d.ts +5 -0
  35. package/dist/viewer/main.js +6 -0
  36. package/dist/viewer/stores/changes.d.ts +21 -0
  37. package/dist/viewer/stores/changes.js +49 -0
  38. package/dist/viewer/stores/plan.d.ts +4 -0
  39. package/dist/viewer/stores/plan.js +19 -0
  40. package/dist/viewer/stores/settings.d.ts +53 -0
  41. package/dist/viewer/stores/settings.js +207 -0
  42. package/package.json +55 -0
  43. 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
+ };
@@ -0,0 +1,5 @@
1
+ import type { Tokens } from "../lib/config.js";
2
+ import type { StravaActivity, StravaAthlete } from "./types.js";
3
+ export declare function getAthlete(tokens: Tokens): Promise<StravaAthlete>;
4
+ export declare function getActivities(tokens: Tokens, after: number, before?: number, page?: number, perPage?: number): Promise<StravaActivity[]>;
5
+ export declare function getAllActivities(tokens: Tokens, afterDate: Date): Promise<StravaActivity[]>;
@@ -0,0 +1,63 @@
1
+ import { log } from "../lib/logging.js";
2
+ const API_BASE = "https://www.strava.com/api/v3";
3
+ async function fetchWithRetry(url, options, retries = 3) {
4
+ const response = await fetch(url, options);
5
+ if (response.status === 429) {
6
+ const retryAfter = parseInt(response.headers.get("retry-after") || "60");
7
+ log.warn(`Rate limited. Waiting ${retryAfter}s...`);
8
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
9
+ return fetchWithRetry(url, options, retries);
10
+ }
11
+ if (!response.ok && retries > 0) {
12
+ log.warn(`Request failed (${response.status}), retrying...`);
13
+ await new Promise((resolve) => setTimeout(resolve, 1000));
14
+ return fetchWithRetry(url, options, retries - 1);
15
+ }
16
+ return response;
17
+ }
18
+ export async function getAthlete(tokens) {
19
+ const response = await fetchWithRetry(`${API_BASE}/athlete`, {
20
+ headers: { Authorization: `Bearer ${tokens.access_token}` },
21
+ });
22
+ if (!response.ok) {
23
+ throw new Error(`Failed to fetch athlete: ${response.statusText}`);
24
+ }
25
+ return response.json();
26
+ }
27
+ export async function getActivities(tokens, after, before, page = 1, perPage = 100) {
28
+ const url = new URL(`${API_BASE}/athlete/activities`);
29
+ url.searchParams.set("after", after.toString());
30
+ if (before) {
31
+ url.searchParams.set("before", before.toString());
32
+ }
33
+ url.searchParams.set("page", page.toString());
34
+ url.searchParams.set("per_page", perPage.toString());
35
+ const response = await fetchWithRetry(url.toString(), {
36
+ headers: { Authorization: `Bearer ${tokens.access_token}` },
37
+ });
38
+ if (!response.ok) {
39
+ throw new Error(`Failed to fetch activities: ${response.statusText}`);
40
+ }
41
+ return response.json();
42
+ }
43
+ export async function getAllActivities(tokens, afterDate) {
44
+ const after = Math.floor(afterDate.getTime() / 1000);
45
+ const activities = [];
46
+ let page = 1;
47
+ const perPage = 100;
48
+ log.start(`Fetching activities since ${afterDate.toISOString().split("T")[0]}...`);
49
+ while (true) {
50
+ const batch = await getActivities(tokens, after, undefined, page, perPage);
51
+ activities.push(...batch);
52
+ log.progress(` Fetched ${activities.length} activities...`);
53
+ if (batch.length < perPage) {
54
+ break;
55
+ }
56
+ page++;
57
+ // Small delay to be nice to the API
58
+ await new Promise((resolve) => setTimeout(resolve, 100));
59
+ }
60
+ log.progressEnd();
61
+ log.success(`Fetched ${activities.length} activities total`);
62
+ return activities;
63
+ }
@@ -0,0 +1,4 @@
1
+ import { type Tokens } from "../lib/config.js";
2
+ export declare function authorize(): Promise<Tokens>;
3
+ export declare function refreshTokens(): Promise<Tokens>;
4
+ export declare function getValidTokens(): Promise<Tokens>;
@@ -0,0 +1,113 @@
1
+ import { createServer } from "http";
2
+ import { URL } from "url";
3
+ import open from "open";
4
+ import { loadConfig, loadTokens, saveTokens, tokensExist, tokensExpired, } from "../lib/config.js";
5
+ import { log } from "../lib/logging.js";
6
+ const REDIRECT_PORT = 8765;
7
+ const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}/callback`;
8
+ const AUTHORIZE_URL = "https://www.strava.com/oauth/authorize";
9
+ const TOKEN_URL = "https://www.strava.com/oauth/token";
10
+ export async function authorize() {
11
+ const config = loadConfig();
12
+ const { client_id, client_secret } = config.strava;
13
+ const authUrl = new URL(AUTHORIZE_URL);
14
+ authUrl.searchParams.set("client_id", client_id);
15
+ authUrl.searchParams.set("response_type", "code");
16
+ authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
17
+ authUrl.searchParams.set("scope", "activity:read_all");
18
+ authUrl.searchParams.set("approval_prompt", "auto");
19
+ log.info("Opening browser for Strava authorization...");
20
+ const code = await new Promise((resolve, reject) => {
21
+ const server = createServer((req, res) => {
22
+ const url = new URL(req.url, `http://localhost:${REDIRECT_PORT}`);
23
+ if (url.pathname === "/callback") {
24
+ const code = url.searchParams.get("code");
25
+ const error = url.searchParams.get("error");
26
+ if (error) {
27
+ res.writeHead(400, { "Content-Type": "text/html" });
28
+ res.end(`<h1>Authorization Failed</h1><p>${error}</p>`);
29
+ server.close();
30
+ reject(new Error(`Authorization failed: ${error}`));
31
+ return;
32
+ }
33
+ if (code) {
34
+ res.writeHead(200, { "Content-Type": "text/html" });
35
+ res.end("<h1>✅ Authorization Successful!</h1><p>You can close this window.</p>");
36
+ server.close();
37
+ resolve(code);
38
+ }
39
+ }
40
+ });
41
+ server.listen(REDIRECT_PORT, () => {
42
+ open(authUrl.toString());
43
+ });
44
+ server.on("error", (err) => {
45
+ reject(new Error(`Failed to start callback server: ${err.message}`));
46
+ });
47
+ });
48
+ log.success("Authorization code received, exchanging for tokens...");
49
+ const tokenResponse = await fetch(TOKEN_URL, {
50
+ method: "POST",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify({
53
+ client_id,
54
+ client_secret,
55
+ code,
56
+ grant_type: "authorization_code",
57
+ }),
58
+ });
59
+ if (!tokenResponse.ok) {
60
+ const error = await tokenResponse.text();
61
+ throw new Error(`Token exchange failed: ${error}`);
62
+ }
63
+ const data = await tokenResponse.json();
64
+ const tokens = {
65
+ access_token: data.access_token,
66
+ refresh_token: data.refresh_token,
67
+ expires_at: data.expires_at,
68
+ athlete_id: data.athlete.id,
69
+ };
70
+ saveTokens(tokens);
71
+ log.success(`Authenticated as ${data.athlete.firstname} ${data.athlete.lastname}`);
72
+ return tokens;
73
+ }
74
+ export async function refreshTokens() {
75
+ const config = loadConfig();
76
+ const oldTokens = loadTokens();
77
+ const { client_id, client_secret } = config.strava;
78
+ log.start("Refreshing access token...");
79
+ const response = await fetch(TOKEN_URL, {
80
+ method: "POST",
81
+ headers: { "Content-Type": "application/json" },
82
+ body: JSON.stringify({
83
+ client_id,
84
+ client_secret,
85
+ refresh_token: oldTokens.refresh_token,
86
+ grant_type: "refresh_token",
87
+ }),
88
+ });
89
+ if (!response.ok) {
90
+ const error = await response.text();
91
+ throw new Error(`Token refresh failed: ${error}`);
92
+ }
93
+ const data = await response.json();
94
+ const tokens = {
95
+ access_token: data.access_token,
96
+ refresh_token: data.refresh_token,
97
+ expires_at: data.expires_at,
98
+ athlete_id: oldTokens.athlete_id,
99
+ };
100
+ saveTokens(tokens);
101
+ log.success("Token refreshed");
102
+ return tokens;
103
+ }
104
+ export async function getValidTokens() {
105
+ if (!tokensExist()) {
106
+ return authorize();
107
+ }
108
+ const tokens = loadTokens();
109
+ if (tokensExpired(tokens)) {
110
+ return refreshTokens();
111
+ }
112
+ return tokens;
113
+ }
@@ -0,0 +1,46 @@
1
+ export interface StravaAthlete {
2
+ id: number;
3
+ firstname: string;
4
+ lastname: string;
5
+ weight?: number;
6
+ ftp?: number;
7
+ }
8
+ export interface StravaTokenResponse {
9
+ token_type: string;
10
+ access_token: string;
11
+ refresh_token: string;
12
+ expires_at: number;
13
+ expires_in: number;
14
+ athlete: StravaAthlete;
15
+ }
16
+ export interface StravaActivity {
17
+ id: number;
18
+ name: string;
19
+ sport_type: string;
20
+ start_date: string;
21
+ elapsed_time: number;
22
+ moving_time: number;
23
+ distance: number;
24
+ total_elevation_gain: number;
25
+ average_speed: number;
26
+ max_speed: number;
27
+ average_heartrate?: number;
28
+ max_heartrate?: number;
29
+ average_watts?: number;
30
+ max_watts?: number;
31
+ weighted_average_watts?: number;
32
+ kilojoules?: number;
33
+ suffer_score?: number;
34
+ average_cadence?: number;
35
+ calories?: number;
36
+ description?: string;
37
+ workout_type?: number;
38
+ gear_id?: string;
39
+ }
40
+ export interface StravaStream {
41
+ type: string;
42
+ data: number[];
43
+ series_type: string;
44
+ original_size: number;
45
+ resolution: string;
46
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * ERG/MRC Export
3
+ *
4
+ * Generates ERG/MRC workout files for indoor cycling trainers.
5
+ * Widely supported by TrainerRoad, Zwift, PerfPRO, Golden Cheetah, and others.
6
+ *
7
+ * - ERG format: Uses absolute watts
8
+ * - MRC format: Uses percentage of FTP (more portable)
9
+ *
10
+ * We generate MRC format since it scales to each user's FTP.
11
+ */
12
+ import type { Workout, Sport } from "../../../schema/training-plan.js";
13
+ import type { Settings } from "../../stores/settings.js";
14
+ /**
15
+ * Check if a sport is supported by ERG/MRC export
16
+ * Only cycling workouts make sense for trainer files
17
+ */
18
+ export declare function isErgSupported(sport: Sport): boolean;
19
+ /**
20
+ * Generate a complete MRC file for a workout
21
+ */
22
+ export declare function generateMrc(workout: Workout, _settings: Settings): string;
23
+ /**
24
+ * Generate ERG file (absolute watts) - requires FTP
25
+ */
26
+ export declare function generateErg(workout: Workout, settings: Settings): string;