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.
- package/LICENSE.md +21 -0
- package/README.md +84 -0
- package/bin/claude-coach.js +10 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +277 -0
- package/dist/db/client.d.ts +4 -0
- package/dist/db/client.js +45 -0
- package/dist/db/migrate.d.ts +1 -0
- package/dist/db/migrate.js +14 -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/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/export/erg.d.ts +26 -0
- package/dist/viewer/lib/export/erg.js +206 -0
- package/dist/viewer/lib/export/fit.d.ts +25 -0
- package/dist/viewer/lib/export/fit.js +307 -0
- package/dist/viewer/lib/export/ics.d.ts +13 -0
- package/dist/viewer/lib/export/ics.js +138 -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 +230 -0
- package/dist/viewer/lib/utils.d.ts +14 -0
- package/dist/viewer/lib/utils.js +118 -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 +4 -0
- package/dist/viewer/stores/plan.js +19 -0
- package/dist/viewer/stores/settings.d.ts +53 -0
- package/dist/viewer/stores/settings.js +207 -0
- package/package.json +55 -0
- package/templates/plan-viewer.html +70 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Workout } from "../../schema/training-plan.js";
|
|
2
|
+
/**
|
|
3
|
+
* Tracks all user modifications to the plan.
|
|
4
|
+
* These are stored as overlays on top of the original plan data.
|
|
5
|
+
*/
|
|
6
|
+
export interface PlanChanges {
|
|
7
|
+
moved: Record<string, string>;
|
|
8
|
+
edited: Record<string, Partial<Workout>>;
|
|
9
|
+
deleted: string[];
|
|
10
|
+
added: Record<string, {
|
|
11
|
+
date: string;
|
|
12
|
+
workout: Workout;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare function emptyChanges(): PlanChanges;
|
|
16
|
+
export declare function loadChanges(): PlanChanges;
|
|
17
|
+
export declare function saveChanges(changes: PlanChanges): void;
|
|
18
|
+
export declare function generateWorkoutId(): string;
|
|
19
|
+
export declare function getWorkoutDate(workoutId: string, originalDate: string, changes: PlanChanges): string;
|
|
20
|
+
export declare function getEffectiveWorkout(workout: Workout, changes: PlanChanges): Workout;
|
|
21
|
+
export declare function isWorkoutDeleted(workoutId: string, changes: PlanChanges): boolean;
|
|
@@ -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,19 @@
|
|
|
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
|
+
export function loadCompleted() {
|
|
14
|
+
const saved = localStorage.getItem(storageKey);
|
|
15
|
+
return saved ? JSON.parse(saved) : {};
|
|
16
|
+
}
|
|
17
|
+
export function saveCompleted(completed) {
|
|
18
|
+
localStorage.setItem(storageKey, JSON.stringify(completed));
|
|
19
|
+
}
|
|
@@ -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,207 @@
|
|
|
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
|
+
settings.run.hrZones = zones.run.hr.zones.map((z) => ({
|
|
69
|
+
zone: z.zone,
|
|
70
|
+
name: z.name,
|
|
71
|
+
low: z.hrLow,
|
|
72
|
+
high: z.hrHigh,
|
|
73
|
+
}));
|
|
74
|
+
}
|
|
75
|
+
if (zones?.run?.pace) {
|
|
76
|
+
settings.run.thresholdPace = zones.run.pace.thresholdPace || settings.run.thresholdPace;
|
|
77
|
+
if (zones.run.pace.zones) {
|
|
78
|
+
settings.run.paceZones = zones.run.pace.zones.map((z) => ({
|
|
79
|
+
zone: z.zone,
|
|
80
|
+
name: z.name,
|
|
81
|
+
pace: z.pace,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (zones?.bike?.hr) {
|
|
86
|
+
settings.bike.lthr = zones.bike.hr.lthr;
|
|
87
|
+
settings.bike.hrZones = zones.bike.hr.zones.map((z) => ({
|
|
88
|
+
zone: z.zone,
|
|
89
|
+
name: z.name,
|
|
90
|
+
low: z.hrLow,
|
|
91
|
+
high: z.hrHigh,
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
if (zones?.bike?.power) {
|
|
95
|
+
settings.bike.ftp = zones.bike.power.ftp;
|
|
96
|
+
settings.bike.powerZones = zones.bike.power.zones.map((z) => ({
|
|
97
|
+
zone: z.zone,
|
|
98
|
+
name: z.name,
|
|
99
|
+
low: z.wattsLow,
|
|
100
|
+
high: z.wattsHigh,
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
if (zones?.swim) {
|
|
104
|
+
settings.swim.css = zones.swim.css || settings.swim.css;
|
|
105
|
+
settings.swim.cssSeconds = zones.swim.cssSeconds || settings.swim.cssSeconds;
|
|
106
|
+
if (zones.swim.zones) {
|
|
107
|
+
settings.swim.paceZones = zones.swim.zones.map((z) => ({
|
|
108
|
+
zone: z.zone,
|
|
109
|
+
name: z.name,
|
|
110
|
+
offset: z.paceOffset,
|
|
111
|
+
pace: z.pace,
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Override with user's saved settings
|
|
116
|
+
const saved = localStorage.getItem(storageKey);
|
|
117
|
+
if (saved) {
|
|
118
|
+
const userSettings = JSON.parse(saved);
|
|
119
|
+
if (userSettings.theme)
|
|
120
|
+
settings.theme = userSettings.theme;
|
|
121
|
+
if (userSettings.units)
|
|
122
|
+
settings.units = { ...settings.units, ...userSettings.units };
|
|
123
|
+
if (userSettings.firstDayOfWeek)
|
|
124
|
+
settings.firstDayOfWeek = userSettings.firstDayOfWeek;
|
|
125
|
+
if (userSettings.run)
|
|
126
|
+
settings.run = { ...settings.run, ...userSettings.run };
|
|
127
|
+
if (userSettings.bike)
|
|
128
|
+
settings.bike = { ...settings.bike, ...userSettings.bike };
|
|
129
|
+
if (userSettings.swim)
|
|
130
|
+
settings.swim = { ...settings.swim, ...userSettings.swim };
|
|
131
|
+
}
|
|
132
|
+
return settings;
|
|
133
|
+
}
|
|
134
|
+
export function saveSettings(settings) {
|
|
135
|
+
localStorage.setItem(storageKey, JSON.stringify(settings));
|
|
136
|
+
}
|
|
137
|
+
// Utility functions
|
|
138
|
+
export function paceToSeconds(pace) {
|
|
139
|
+
const parts = pace.split(":");
|
|
140
|
+
return parseInt(parts[0]) * 60 + parseInt(parts[1] || "0");
|
|
141
|
+
}
|
|
142
|
+
export function secondsToPace(seconds) {
|
|
143
|
+
const mins = Math.floor(seconds / 60);
|
|
144
|
+
const secs = Math.round(seconds % 60);
|
|
145
|
+
return `${mins}:${secs.toString().padStart(2, "0")}`;
|
|
146
|
+
}
|
|
147
|
+
export function recalculateHrZones(lthr) {
|
|
148
|
+
const percentages = [
|
|
149
|
+
{ zone: 1, name: "Recovery", lowPct: 0, highPct: 81 },
|
|
150
|
+
{ zone: 2, name: "Aerobic", lowPct: 81, highPct: 89 },
|
|
151
|
+
{ zone: 3, name: "Tempo", lowPct: 89, highPct: 94 },
|
|
152
|
+
{ zone: 4, name: "Threshold", lowPct: 94, highPct: 100 },
|
|
153
|
+
{ zone: 5, name: "VO2max", lowPct: 100, highPct: 106 },
|
|
154
|
+
];
|
|
155
|
+
return percentages.map((p) => ({
|
|
156
|
+
zone: p.zone,
|
|
157
|
+
name: p.name,
|
|
158
|
+
low: Math.round((lthr * p.lowPct) / 100),
|
|
159
|
+
high: Math.round((lthr * p.highPct) / 100),
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
export function recalculatePowerZones(ftp) {
|
|
163
|
+
const percentages = [
|
|
164
|
+
{ zone: 1, name: "Active Recovery", lowPct: 0, highPct: 55 },
|
|
165
|
+
{ zone: 2, name: "Endurance", lowPct: 55, highPct: 75 },
|
|
166
|
+
{ zone: 3, name: "Tempo", lowPct: 75, highPct: 90 },
|
|
167
|
+
{ zone: 4, name: "Threshold", lowPct: 90, highPct: 105 },
|
|
168
|
+
{ zone: 5, name: "VO2max", lowPct: 105, highPct: 120 },
|
|
169
|
+
];
|
|
170
|
+
return percentages.map((p) => ({
|
|
171
|
+
zone: p.zone,
|
|
172
|
+
name: p.name,
|
|
173
|
+
low: Math.round((ftp * p.lowPct) / 100),
|
|
174
|
+
high: Math.round((ftp * p.highPct) / 100),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
export function recalculateRunPaceZones(thresholdPace) {
|
|
178
|
+
const tSeconds = paceToSeconds(thresholdPace);
|
|
179
|
+
const zones = [
|
|
180
|
+
{ zone: "E", name: "Easy", factor: 1.18 },
|
|
181
|
+
{ zone: "M", name: "Marathon", factor: 1.07 },
|
|
182
|
+
{ zone: "T", name: "Threshold", factor: 1.0 },
|
|
183
|
+
{ zone: "I", name: "Interval", factor: 0.89 },
|
|
184
|
+
{ zone: "R", name: "Repetition", factor: 0.82 },
|
|
185
|
+
];
|
|
186
|
+
return zones.map((z) => ({
|
|
187
|
+
zone: z.zone,
|
|
188
|
+
name: z.name,
|
|
189
|
+
pace: secondsToPace(Math.round(tSeconds * z.factor)),
|
|
190
|
+
}));
|
|
191
|
+
}
|
|
192
|
+
export function recalculateSwimPaceZones(css) {
|
|
193
|
+
const cssSeconds = paceToSeconds(css);
|
|
194
|
+
const offsets = [
|
|
195
|
+
{ zone: 1, name: "Recovery", offset: 20 },
|
|
196
|
+
{ zone: 2, name: "Aerobic", offset: 10 },
|
|
197
|
+
{ zone: 3, name: "Tempo", offset: 5 },
|
|
198
|
+
{ zone: 4, name: "Threshold", offset: 0 },
|
|
199
|
+
{ zone: 5, name: "VO2max", offset: -5 },
|
|
200
|
+
];
|
|
201
|
+
return offsets.map((z) => ({
|
|
202
|
+
zone: z.zone,
|
|
203
|
+
name: z.name,
|
|
204
|
+
offset: z.offset,
|
|
205
|
+
pace: secondsToPace(cssSeconds + z.offset),
|
|
206
|
+
}));
|
|
207
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-coach",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"author": "Felix Rieseberg <felix@felixrieseberg.com> (https://felixrieseberg.com)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"claude-coach": "bin/claude-coach.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"dist/**/*.js",
|
|
13
|
+
"dist/**/*.d.ts",
|
|
14
|
+
"templates"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "tsx src/cli.ts",
|
|
18
|
+
"build": "npm run build:ts && npm run build:viewer && npm run build:skill",
|
|
19
|
+
"build:ts": "tsc",
|
|
20
|
+
"build:viewer": "vite build --config vite.config.viewer.ts",
|
|
21
|
+
"build:skill": "mkdir -p dist && cd skill && zip -r ../dist/coach-skill.zip . -x '*.DS_Store' && echo 'Skill packaged to dist/coach-skill.zip'",
|
|
22
|
+
"dev": "tsx watch src/cli.ts",
|
|
23
|
+
"dev:viewer": "vite --config vite.config.viewer.ts",
|
|
24
|
+
"test": "vitest",
|
|
25
|
+
"test:run": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"format": "prettier --write .",
|
|
28
|
+
"format:check": "prettier --check .",
|
|
29
|
+
"prepare": "husky",
|
|
30
|
+
"prepublishOnly": "npm run build"
|
|
31
|
+
},
|
|
32
|
+
"lint-staged": {
|
|
33
|
+
"*.{js,ts,json,md,html,css,svelte}": "prettier --write"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@garmin/fitsdk": "^21.178.0",
|
|
37
|
+
"consola": "^3.2.3",
|
|
38
|
+
"jszip": "^3.10.1",
|
|
39
|
+
"open": "^10.1.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@sveltejs/vite-plugin-svelte": "^6.2.1",
|
|
43
|
+
"@types/node": "^22.10.2",
|
|
44
|
+
"husky": "^9.1.7",
|
|
45
|
+
"lint-staged": "^16.2.7",
|
|
46
|
+
"prettier": "^3.4.2",
|
|
47
|
+
"prettier-plugin-svelte": "^3.4.1",
|
|
48
|
+
"svelte": "^5.43.12",
|
|
49
|
+
"tsx": "^4.19.2",
|
|
50
|
+
"typescript": "^5.7.2",
|
|
51
|
+
"vite": "^7.2.6",
|
|
52
|
+
"vite-plugin-singlefile": "^2.3.0",
|
|
53
|
+
"vitest": "^3.2.4"
|
|
54
|
+
}
|
|
55
|
+
}
|