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,49 @@
|
|
|
1
|
+
import { planData } from "./plan.js";
|
|
2
|
+
const storageKey = `plan-${planData.meta.id}-changes`;
|
|
3
|
+
export function emptyChanges() {
|
|
4
|
+
return {
|
|
5
|
+
moved: {},
|
|
6
|
+
edited: {},
|
|
7
|
+
deleted: [],
|
|
8
|
+
added: {},
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function loadChanges() {
|
|
12
|
+
const saved = localStorage.getItem(storageKey);
|
|
13
|
+
if (!saved)
|
|
14
|
+
return emptyChanges();
|
|
15
|
+
try {
|
|
16
|
+
const parsed = JSON.parse(saved);
|
|
17
|
+
return {
|
|
18
|
+
moved: parsed.moved || {},
|
|
19
|
+
edited: parsed.edited || {},
|
|
20
|
+
deleted: parsed.deleted || [],
|
|
21
|
+
added: parsed.added || {},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return emptyChanges();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function saveChanges(changes) {
|
|
29
|
+
localStorage.setItem(storageKey, JSON.stringify(changes));
|
|
30
|
+
}
|
|
31
|
+
// Helper to generate unique IDs for new workouts
|
|
32
|
+
export function generateWorkoutId() {
|
|
33
|
+
return `user-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
34
|
+
}
|
|
35
|
+
// Get the effective date for a workout (considering moves)
|
|
36
|
+
export function getWorkoutDate(workoutId, originalDate, changes) {
|
|
37
|
+
return changes.moved[workoutId] || originalDate;
|
|
38
|
+
}
|
|
39
|
+
// Get the effective workout data (considering edits)
|
|
40
|
+
export function getEffectiveWorkout(workout, changes) {
|
|
41
|
+
const edits = changes.edited[workout.id];
|
|
42
|
+
if (!edits)
|
|
43
|
+
return workout;
|
|
44
|
+
return { ...workout, ...edits };
|
|
45
|
+
}
|
|
46
|
+
// Check if a workout is deleted
|
|
47
|
+
export function isWorkoutDeleted(workoutId, changes) {
|
|
48
|
+
return changes.deleted.includes(workoutId);
|
|
49
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TrainingPlan } from "../../schema/training-plan.js";
|
|
2
|
+
export declare const planData: TrainingPlan;
|
|
3
|
+
/**
|
|
4
|
+
* Load completed status from both:
|
|
5
|
+
* 1. The embedded JSON plan data (workouts with completed: true)
|
|
6
|
+
* 2. localStorage (user's local changes)
|
|
7
|
+
*
|
|
8
|
+
* localStorage takes precedence for any conflicts.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadCompleted(): Record<string, boolean>;
|
|
11
|
+
export declare function saveCompleted(completed: Record<string, boolean>): void;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Load plan from embedded JSON
|
|
2
|
+
function loadPlanData() {
|
|
3
|
+
const el = document.getElementById("plan-data");
|
|
4
|
+
if (!el)
|
|
5
|
+
throw new Error("Plan data not found");
|
|
6
|
+
return JSON.parse(el.textContent || "{}");
|
|
7
|
+
}
|
|
8
|
+
// Reactive state using Svelte 5's $state rune is only available in .svelte files
|
|
9
|
+
// So we export the raw data and let components create reactive state
|
|
10
|
+
export const planData = loadPlanData();
|
|
11
|
+
// Completed workouts stored in localStorage
|
|
12
|
+
const storageKey = `plan-${planData.meta.id}-completed`;
|
|
13
|
+
/**
|
|
14
|
+
* Load completed status from both:
|
|
15
|
+
* 1. The embedded JSON plan data (workouts with completed: true)
|
|
16
|
+
* 2. localStorage (user's local changes)
|
|
17
|
+
*
|
|
18
|
+
* localStorage takes precedence for any conflicts.
|
|
19
|
+
*/
|
|
20
|
+
export function loadCompleted() {
|
|
21
|
+
// First, extract completed status from the plan JSON
|
|
22
|
+
const fromPlan = {};
|
|
23
|
+
planData.weeks?.forEach((week) => {
|
|
24
|
+
week.days?.forEach((day) => {
|
|
25
|
+
day.workouts?.forEach((workout) => {
|
|
26
|
+
if (workout.completed) {
|
|
27
|
+
fromPlan[workout.id] = true;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
// Then load from localStorage (user's local changes)
|
|
33
|
+
const saved = localStorage.getItem(storageKey);
|
|
34
|
+
const fromStorage = saved ? JSON.parse(saved) : {};
|
|
35
|
+
// Merge: localStorage overrides plan data
|
|
36
|
+
return { ...fromPlan, ...fromStorage };
|
|
37
|
+
}
|
|
38
|
+
export function saveCompleted(completed) {
|
|
39
|
+
localStorage.setItem(storageKey, JSON.stringify(completed));
|
|
40
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface HrZone {
|
|
2
|
+
zone: number;
|
|
3
|
+
name: string;
|
|
4
|
+
low: number;
|
|
5
|
+
high: number;
|
|
6
|
+
}
|
|
7
|
+
export interface PowerZone {
|
|
8
|
+
zone: number;
|
|
9
|
+
name: string;
|
|
10
|
+
low: number;
|
|
11
|
+
high: number;
|
|
12
|
+
}
|
|
13
|
+
export interface PaceZone {
|
|
14
|
+
zone: string | number;
|
|
15
|
+
name: string;
|
|
16
|
+
pace: string;
|
|
17
|
+
offset?: number;
|
|
18
|
+
}
|
|
19
|
+
export type Theme = "dark" | "light";
|
|
20
|
+
export interface Settings {
|
|
21
|
+
theme: Theme;
|
|
22
|
+
units: {
|
|
23
|
+
swim: "meters" | "yards";
|
|
24
|
+
bike: "kilometers" | "miles";
|
|
25
|
+
run: "kilometers" | "miles";
|
|
26
|
+
};
|
|
27
|
+
firstDayOfWeek: "monday" | "sunday";
|
|
28
|
+
run: {
|
|
29
|
+
lthr: number;
|
|
30
|
+
hrZones: HrZone[];
|
|
31
|
+
thresholdPace: string;
|
|
32
|
+
paceZones: PaceZone[];
|
|
33
|
+
};
|
|
34
|
+
bike: {
|
|
35
|
+
lthr: number;
|
|
36
|
+
hrZones: HrZone[];
|
|
37
|
+
ftp: number;
|
|
38
|
+
powerZones: PowerZone[];
|
|
39
|
+
};
|
|
40
|
+
swim: {
|
|
41
|
+
css: string;
|
|
42
|
+
cssSeconds: number;
|
|
43
|
+
paceZones: PaceZone[];
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export declare function loadSettings(): Settings;
|
|
47
|
+
export declare function saveSettings(settings: Settings): void;
|
|
48
|
+
export declare function paceToSeconds(pace: string): number;
|
|
49
|
+
export declare function secondsToPace(seconds: number): string;
|
|
50
|
+
export declare function recalculateHrZones(lthr: number): HrZone[];
|
|
51
|
+
export declare function recalculatePowerZones(ftp: number): PowerZone[];
|
|
52
|
+
export declare function recalculateRunPaceZones(thresholdPace: string): PaceZone[];
|
|
53
|
+
export declare function recalculateSwimPaceZones(css: string): PaceZone[];
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { planData } from "./plan.js";
|
|
2
|
+
const defaultSettings = {
|
|
3
|
+
theme: "dark",
|
|
4
|
+
units: { swim: "meters", bike: "kilometers", run: "kilometers" },
|
|
5
|
+
firstDayOfWeek: "monday",
|
|
6
|
+
run: {
|
|
7
|
+
lthr: 165,
|
|
8
|
+
hrZones: [
|
|
9
|
+
{ zone: 1, name: "Recovery", low: 0, high: 134 },
|
|
10
|
+
{ zone: 2, name: "Aerobic", low: 134, high: 147 },
|
|
11
|
+
{ zone: 3, name: "Tempo", low: 147, high: 156 },
|
|
12
|
+
{ zone: 4, name: "Threshold", low: 156, high: 165 },
|
|
13
|
+
{ zone: 5, name: "VO2max", low: 165, high: 180 },
|
|
14
|
+
],
|
|
15
|
+
thresholdPace: "4:30",
|
|
16
|
+
paceZones: [
|
|
17
|
+
{ zone: "E", name: "Easy", pace: "5:20" },
|
|
18
|
+
{ zone: "M", name: "Marathon", pace: "4:50" },
|
|
19
|
+
{ zone: "T", name: "Threshold", pace: "4:30" },
|
|
20
|
+
{ zone: "I", name: "Interval", pace: "4:00" },
|
|
21
|
+
{ zone: "R", name: "Repetition", pace: "3:40" },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
bike: {
|
|
25
|
+
lthr: 160,
|
|
26
|
+
hrZones: [
|
|
27
|
+
{ zone: 1, name: "Recovery", low: 0, high: 130 },
|
|
28
|
+
{ zone: 2, name: "Aerobic", low: 130, high: 142 },
|
|
29
|
+
{ zone: 3, name: "Tempo", low: 142, high: 151 },
|
|
30
|
+
{ zone: 4, name: "Threshold", low: 151, high: 160 },
|
|
31
|
+
{ zone: 5, name: "VO2max", low: 160, high: 175 },
|
|
32
|
+
],
|
|
33
|
+
ftp: 200,
|
|
34
|
+
powerZones: [
|
|
35
|
+
{ zone: 1, name: "Active Recovery", low: 0, high: 110 },
|
|
36
|
+
{ zone: 2, name: "Endurance", low: 110, high: 150 },
|
|
37
|
+
{ zone: 3, name: "Tempo", low: 150, high: 180 },
|
|
38
|
+
{ zone: 4, name: "Threshold", low: 180, high: 210 },
|
|
39
|
+
{ zone: 5, name: "VO2max", low: 210, high: 240 },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
swim: {
|
|
43
|
+
css: "1:50",
|
|
44
|
+
cssSeconds: 110,
|
|
45
|
+
paceZones: [
|
|
46
|
+
{ zone: 1, name: "Recovery", offset: 20, pace: "2:10" },
|
|
47
|
+
{ zone: 2, name: "Aerobic", offset: 10, pace: "2:00" },
|
|
48
|
+
{ zone: 3, name: "Tempo", offset: 5, pace: "1:55" },
|
|
49
|
+
{ zone: 4, name: "Threshold", offset: 0, pace: "1:50" },
|
|
50
|
+
{ zone: 5, name: "VO2max", offset: -5, pace: "1:45" },
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
const storageKey = `plan-${planData.meta.id}-settings`;
|
|
55
|
+
export function loadSettings() {
|
|
56
|
+
const settings = JSON.parse(JSON.stringify(defaultSettings));
|
|
57
|
+
// Merge with plan preferences
|
|
58
|
+
if (planData.preferences) {
|
|
59
|
+
settings.units.swim = planData.preferences.swim || settings.units.swim;
|
|
60
|
+
settings.units.bike = planData.preferences.bike || settings.units.bike;
|
|
61
|
+
settings.units.run = planData.preferences.run || settings.units.run;
|
|
62
|
+
settings.firstDayOfWeek = planData.preferences.firstDayOfWeek || settings.firstDayOfWeek;
|
|
63
|
+
}
|
|
64
|
+
// Merge with plan zones
|
|
65
|
+
const zones = planData.zones;
|
|
66
|
+
if (zones?.run?.hr) {
|
|
67
|
+
settings.run.lthr = zones.run.hr.lthr;
|
|
68
|
+
if (zones.run.hr.zones) {
|
|
69
|
+
settings.run.hrZones = zones.run.hr.zones.map((z) => ({
|
|
70
|
+
zone: z.zone,
|
|
71
|
+
name: z.name,
|
|
72
|
+
low: z.hrLow,
|
|
73
|
+
high: z.hrHigh,
|
|
74
|
+
}));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (zones?.run?.pace) {
|
|
78
|
+
settings.run.thresholdPace = zones.run.pace.thresholdPace || settings.run.thresholdPace;
|
|
79
|
+
if (zones.run.pace.zones) {
|
|
80
|
+
settings.run.paceZones = zones.run.pace.zones.map((z) => ({
|
|
81
|
+
zone: z.zone,
|
|
82
|
+
name: z.name,
|
|
83
|
+
pace: z.pace,
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (zones?.bike?.hr) {
|
|
88
|
+
settings.bike.lthr = zones.bike.hr.lthr;
|
|
89
|
+
if (zones.bike.hr.zones) {
|
|
90
|
+
settings.bike.hrZones = zones.bike.hr.zones.map((z) => ({
|
|
91
|
+
zone: z.zone,
|
|
92
|
+
name: z.name,
|
|
93
|
+
low: z.hrLow,
|
|
94
|
+
high: z.hrHigh,
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (zones?.bike?.power) {
|
|
99
|
+
settings.bike.ftp = zones.bike.power.ftp;
|
|
100
|
+
if (zones.bike.power.zones) {
|
|
101
|
+
settings.bike.powerZones = zones.bike.power.zones.map((z) => ({
|
|
102
|
+
zone: z.zone,
|
|
103
|
+
name: z.name,
|
|
104
|
+
low: z.wattsLow,
|
|
105
|
+
high: z.wattsHigh,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (zones?.swim) {
|
|
110
|
+
settings.swim.css = zones.swim.css || settings.swim.css;
|
|
111
|
+
settings.swim.cssSeconds = zones.swim.cssSeconds || settings.swim.cssSeconds;
|
|
112
|
+
if (zones.swim.zones) {
|
|
113
|
+
settings.swim.paceZones = zones.swim.zones.map((z) => ({
|
|
114
|
+
zone: z.zone,
|
|
115
|
+
name: z.name,
|
|
116
|
+
offset: z.paceOffset,
|
|
117
|
+
pace: z.pace,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Override with user's saved settings
|
|
122
|
+
const saved = localStorage.getItem(storageKey);
|
|
123
|
+
if (saved) {
|
|
124
|
+
const userSettings = JSON.parse(saved);
|
|
125
|
+
if (userSettings.theme)
|
|
126
|
+
settings.theme = userSettings.theme;
|
|
127
|
+
if (userSettings.units)
|
|
128
|
+
settings.units = { ...settings.units, ...userSettings.units };
|
|
129
|
+
if (userSettings.firstDayOfWeek)
|
|
130
|
+
settings.firstDayOfWeek = userSettings.firstDayOfWeek;
|
|
131
|
+
if (userSettings.run)
|
|
132
|
+
settings.run = { ...settings.run, ...userSettings.run };
|
|
133
|
+
if (userSettings.bike)
|
|
134
|
+
settings.bike = { ...settings.bike, ...userSettings.bike };
|
|
135
|
+
if (userSettings.swim)
|
|
136
|
+
settings.swim = { ...settings.swim, ...userSettings.swim };
|
|
137
|
+
}
|
|
138
|
+
return settings;
|
|
139
|
+
}
|
|
140
|
+
export function saveSettings(settings) {
|
|
141
|
+
localStorage.setItem(storageKey, JSON.stringify(settings));
|
|
142
|
+
}
|
|
143
|
+
// Utility functions
|
|
144
|
+
export function paceToSeconds(pace) {
|
|
145
|
+
if (!pace)
|
|
146
|
+
return 0;
|
|
147
|
+
const parts = pace.split(":");
|
|
148
|
+
return parseInt(parts[0] ?? "0") * 60 + parseInt(parts[1] ?? "0");
|
|
149
|
+
}
|
|
150
|
+
export function secondsToPace(seconds) {
|
|
151
|
+
const mins = Math.floor(seconds / 60);
|
|
152
|
+
const secs = Math.round(seconds % 60);
|
|
153
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
154
|
+
}
|
|
155
|
+
export function recalculateHrZones(lthr) {
|
|
156
|
+
const percentages = [
|
|
157
|
+
{ zone: 1, name: "Recovery", lowPct: 0, highPct: 81 },
|
|
158
|
+
{ zone: 2, name: "Aerobic", lowPct: 81, highPct: 89 },
|
|
159
|
+
{ zone: 3, name: "Tempo", lowPct: 89, highPct: 94 },
|
|
160
|
+
{ zone: 4, name: "Threshold", lowPct: 94, highPct: 100 },
|
|
161
|
+
{ zone: 5, name: "VO2max", lowPct: 100, highPct: 106 },
|
|
162
|
+
];
|
|
163
|
+
return percentages.map((p) => ({
|
|
164
|
+
zone: p.zone,
|
|
165
|
+
name: p.name,
|
|
166
|
+
low: Math.round((lthr * p.lowPct) / 100),
|
|
167
|
+
high: Math.round((lthr * p.highPct) / 100),
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
export function recalculatePowerZones(ftp) {
|
|
171
|
+
const percentages = [
|
|
172
|
+
{ zone: 1, name: "Active Recovery", lowPct: 0, highPct: 55 },
|
|
173
|
+
{ zone: 2, name: "Endurance", lowPct: 55, highPct: 75 },
|
|
174
|
+
{ zone: 3, name: "Tempo", lowPct: 75, highPct: 90 },
|
|
175
|
+
{ zone: 4, name: "Threshold", lowPct: 90, highPct: 105 },
|
|
176
|
+
{ zone: 5, name: "VO2max", lowPct: 105, highPct: 120 },
|
|
177
|
+
];
|
|
178
|
+
return percentages.map((p) => ({
|
|
179
|
+
zone: p.zone,
|
|
180
|
+
name: p.name,
|
|
181
|
+
low: Math.round((ftp * p.lowPct) / 100),
|
|
182
|
+
high: Math.round((ftp * p.highPct) / 100),
|
|
183
|
+
}));
|
|
184
|
+
}
|
|
185
|
+
export function recalculateRunPaceZones(thresholdPace) {
|
|
186
|
+
const tSeconds = paceToSeconds(thresholdPace);
|
|
187
|
+
const zones = [
|
|
188
|
+
{ zone: "E", name: "Easy", factor: 1.18 },
|
|
189
|
+
{ zone: "M", name: "Marathon", factor: 1.07 },
|
|
190
|
+
{ zone: "T", name: "Threshold", factor: 1.0 },
|
|
191
|
+
{ zone: "I", name: "Interval", factor: 0.89 },
|
|
192
|
+
{ zone: "R", name: "Repetition", factor: 0.82 },
|
|
193
|
+
];
|
|
194
|
+
return zones.map((z) => ({
|
|
195
|
+
zone: z.zone,
|
|
196
|
+
name: z.name,
|
|
197
|
+
pace: secondsToPace(Math.round(tSeconds * z.factor)),
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
export function recalculateSwimPaceZones(css) {
|
|
201
|
+
const cssSeconds = paceToSeconds(css);
|
|
202
|
+
const offsets = [
|
|
203
|
+
{ zone: 1, name: "Recovery", offset: 20 },
|
|
204
|
+
{ zone: 2, name: "Aerobic", offset: 10 },
|
|
205
|
+
{ zone: 3, name: "Tempo", offset: 5 },
|
|
206
|
+
{ zone: 4, name: "Threshold", offset: 0 },
|
|
207
|
+
{ zone: 5, name: "VO2max", offset: -5 },
|
|
208
|
+
];
|
|
209
|
+
return offsets.map((z) => ({
|
|
210
|
+
zone: z.zone,
|
|
211
|
+
name: z.name,
|
|
212
|
+
offset: z.offset,
|
|
213
|
+
pace: secondsToPace(cssSeconds + z.offset),
|
|
214
|
+
}));
|
|
215
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "endurance-coach",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": "Felix Rieseberg",
|
|
5
|
+
"contributors": [
|
|
6
|
+
"Shiva Prasad <sp@shiv19.com> (https://shiv19.com)"
|
|
7
|
+
],
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./schema": {
|
|
18
|
+
"types": "./dist/schema/training-plan.schema.d.ts",
|
|
19
|
+
"import": "./dist/schema/training-plan.schema.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"bin": {
|
|
23
|
+
"endurance-coach": "bin/endurance-coach.js"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"bin",
|
|
27
|
+
"dist/**/*.js",
|
|
28
|
+
"dist/**/*.d.ts",
|
|
29
|
+
"dist/**/*.sql",
|
|
30
|
+
"templates"
|
|
31
|
+
],
|
|
32
|
+
"scripts": {
|
|
33
|
+
"start": "tsx src/cli.ts",
|
|
34
|
+
"build": "npm run build:ts && npm run build:viewer && npm run build:skill",
|
|
35
|
+
"build:ts": "tsc && cp src/db/schema.sql dist/db/",
|
|
36
|
+
"build:viewer": "vite build --config vite.config.viewer.ts",
|
|
37
|
+
"build:skill": "mkdir -p dist && cd endurance-coach-skill && zip -r ../dist/endurance-coach-skill.zip . -x '*.DS_Store' && echo 'Skill packaged to dist/endurance-coach-skill.zip'",
|
|
38
|
+
"build:website": "node scripts/build-website.js",
|
|
39
|
+
"dev": "tsx watch src/cli.ts",
|
|
40
|
+
"dev:viewer": "vite --config vite.config.viewer.ts",
|
|
41
|
+
"test": "vitest",
|
|
42
|
+
"test:run": "vitest run",
|
|
43
|
+
"typecheck": "tsc --noEmit",
|
|
44
|
+
"format": "prettier --write .",
|
|
45
|
+
"format:check": "prettier --check .",
|
|
46
|
+
"prepare": "husky",
|
|
47
|
+
"prepublishOnly": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"lint-staged": {
|
|
50
|
+
"*.{js,ts,json,md,html,css,svelte}": "prettier --write"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"@garmin/fitsdk": "^21.178.0",
|
|
54
|
+
"consola": "^3.2.3",
|
|
55
|
+
"jszip": "^3.10.1",
|
|
56
|
+
"open": "^10.1.0",
|
|
57
|
+
"undici": "^7.16.0",
|
|
58
|
+
"zod": "^4.3.5"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
62
|
+
"@types/node": "^22.10.2",
|
|
63
|
+
"husky": "^9.1.7",
|
|
64
|
+
"lint-staged": "^16.2.7",
|
|
65
|
+
"prettier": "^3.4.2",
|
|
66
|
+
"prettier-plugin-svelte": "^3.4.1",
|
|
67
|
+
"svelte": "^5.43.12",
|
|
68
|
+
"tsx": "^4.19.2",
|
|
69
|
+
"typescript": "^5.7.2",
|
|
70
|
+
"vite": "^7.2.6",
|
|
71
|
+
"vite-plugin-singlefile": "^2.3.0",
|
|
72
|
+
"vitest": "^3.2.4"
|
|
73
|
+
}
|
|
74
|
+
}
|