trainbud 0.5.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/.env.example +11 -0
- package/CHANGELOG.md +675 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +182 -0
- package/README.md +322 -0
- package/dist/appDb.d.ts +83 -0
- package/dist/appDb.d.ts.map +1 -0
- package/dist/appDb.js +239 -0
- package/dist/appDb.js.map +1 -0
- package/dist/check.d.ts +12 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +230 -0
- package/dist/check.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +457 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +61 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +241 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard.d.ts +8 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +1004 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/dashboardCharts.d.ts +99 -0
- package/dist/dashboardCharts.d.ts.map +1 -0
- package/dist/dashboardCharts.js +317 -0
- package/dist/dashboardCharts.js.map +1 -0
- package/dist/dashboardData.d.ts +89 -0
- package/dist/dashboardData.d.ts.map +1 -0
- package/dist/dashboardData.js +228 -0
- package/dist/dashboardData.js.map +1 -0
- package/dist/detect/baseline.d.ts +17 -0
- package/dist/detect/baseline.d.ts.map +1 -0
- package/dist/detect/baseline.js +62 -0
- package/dist/detect/baseline.js.map +1 -0
- package/dist/detect/countdown.d.ts +30 -0
- package/dist/detect/countdown.d.ts.map +1 -0
- package/dist/detect/countdown.js +79 -0
- package/dist/detect/countdown.js.map +1 -0
- package/dist/detect/detectors.d.ts +20 -0
- package/dist/detect/detectors.d.ts.map +1 -0
- package/dist/detect/detectors.js +291 -0
- package/dist/detect/detectors.js.map +1 -0
- package/dist/detect/findings.d.ts +29 -0
- package/dist/detect/findings.d.ts.map +1 -0
- package/dist/detect/findings.js +2 -0
- package/dist/detect/findings.js.map +1 -0
- package/dist/detect/forecast.d.ts +33 -0
- package/dist/detect/forecast.d.ts.map +1 -0
- package/dist/detect/forecast.js +122 -0
- package/dist/detect/forecast.js.map +1 -0
- package/dist/detect/index.d.ts +50 -0
- package/dist/detect/index.d.ts.map +1 -0
- package/dist/detect/index.js +114 -0
- package/dist/detect/index.js.map +1 -0
- package/dist/detect/sleepQuality.d.ts +21 -0
- package/dist/detect/sleepQuality.d.ts.map +1 -0
- package/dist/detect/sleepQuality.js +164 -0
- package/dist/detect/sleepQuality.js.map +1 -0
- package/dist/detect/trimp.d.ts +22 -0
- package/dist/detect/trimp.d.ts.map +1 -0
- package/dist/detect/trimp.js +77 -0
- package/dist/detect/trimp.js.map +1 -0
- package/dist/detect/week.d.ts +31 -0
- package/dist/detect/week.d.ts.map +1 -0
- package/dist/detect/week.js +184 -0
- package/dist/detect/week.js.map +1 -0
- package/dist/garmin/auth.d.ts +10 -0
- package/dist/garmin/auth.d.ts.map +1 -0
- package/dist/garmin/auth.js +81 -0
- package/dist/garmin/auth.js.map +1 -0
- package/dist/garmin/cache.d.ts +42 -0
- package/dist/garmin/cache.d.ts.map +1 -0
- package/dist/garmin/cache.js +141 -0
- package/dist/garmin/cache.js.map +1 -0
- package/dist/garmin/client.d.ts +22 -0
- package/dist/garmin/client.d.ts.map +1 -0
- package/dist/garmin/client.js +237 -0
- package/dist/garmin/client.js.map +1 -0
- package/dist/garmin/daily.d.ts +24 -0
- package/dist/garmin/daily.d.ts.map +1 -0
- package/dist/garmin/daily.js +96 -0
- package/dist/garmin/daily.js.map +1 -0
- package/dist/garmin/garminApiTypes.d.ts +70 -0
- package/dist/garmin/garminApiTypes.d.ts.map +1 -0
- package/dist/garmin/garminApiTypes.js +3 -0
- package/dist/garmin/garminApiTypes.js.map +1 -0
- package/dist/garmin/garminConnect.d.ts +17 -0
- package/dist/garmin/garminConnect.d.ts.map +1 -0
- package/dist/garmin/garminConnect.js +5 -0
- package/dist/garmin/garminConnect.js.map +1 -0
- package/dist/garmin/partial.d.ts +32 -0
- package/dist/garmin/partial.d.ts.map +1 -0
- package/dist/garmin/partial.js +38 -0
- package/dist/garmin/partial.js.map +1 -0
- package/dist/garmin/rawApi.d.ts +59 -0
- package/dist/garmin/rawApi.d.ts.map +1 -0
- package/dist/garmin/rawApi.js +113 -0
- package/dist/garmin/rawApi.js.map +1 -0
- package/dist/garmin/types.d.ts +85 -0
- package/dist/garmin/types.d.ts.map +1 -0
- package/dist/garmin/types.js +11 -0
- package/dist/garmin/types.js.map +1 -0
- package/dist/history/capture.d.ts +12 -0
- package/dist/history/capture.d.ts.map +1 -0
- package/dist/history/capture.js +63 -0
- package/dist/history/capture.js.map +1 -0
- package/dist/history/context.d.ts +51 -0
- package/dist/history/context.d.ts.map +1 -0
- package/dist/history/context.js +122 -0
- package/dist/history/context.js.map +1 -0
- package/dist/history/fallback.d.ts +72 -0
- package/dist/history/fallback.d.ts.map +1 -0
- package/dist/history/fallback.js +185 -0
- package/dist/history/fallback.js.map +1 -0
- package/dist/history/ingest.d.ts +44 -0
- package/dist/history/ingest.d.ts.map +1 -0
- package/dist/history/ingest.js +347 -0
- package/dist/history/ingest.js.map +1 -0
- package/dist/history/scheduler.d.ts +20 -0
- package/dist/history/scheduler.d.ts.map +1 -0
- package/dist/history/scheduler.js +54 -0
- package/dist/history/scheduler.js.map +1 -0
- package/dist/history/schema.d.ts +41 -0
- package/dist/history/schema.d.ts.map +1 -0
- package/dist/history/schema.js +91 -0
- package/dist/history/schema.js.map +1 -0
- package/dist/history/store.d.ts +112 -0
- package/dist/history/store.d.ts.map +1 -0
- package/dist/history/store.js +282 -0
- package/dist/history/store.js.map +1 -0
- package/dist/httpServer.d.ts +55 -0
- package/dist/httpServer.d.ts.map +1 -0
- package/dist/httpServer.js +1027 -0
- package/dist/httpServer.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/mcpConfig.d.ts +56 -0
- package/dist/mcpConfig.d.ts.map +1 -0
- package/dist/mcpConfig.js +106 -0
- package/dist/mcpConfig.js.map +1 -0
- package/dist/pairApi.d.ts +14 -0
- package/dist/pairApi.d.ts.map +1 -0
- package/dist/pairApi.js +38 -0
- package/dist/pairApi.js.map +1 -0
- package/dist/paths.d.ts +45 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +105 -0
- package/dist/paths.js.map +1 -0
- package/dist/profile.d.ts +123 -0
- package/dist/profile.d.ts.map +1 -0
- package/dist/profile.js +327 -0
- package/dist/profile.js.map +1 -0
- package/dist/promptApi.d.ts +43 -0
- package/dist/promptApi.d.ts.map +1 -0
- package/dist/promptApi.js +328 -0
- package/dist/promptApi.js.map +1 -0
- package/dist/promptSuggestions.d.ts +16 -0
- package/dist/promptSuggestions.d.ts.map +1 -0
- package/dist/promptSuggestions.js +141 -0
- package/dist/promptSuggestions.js.map +1 -0
- package/dist/selfTest.d.ts +53 -0
- package/dist/selfTest.d.ts.map +1 -0
- package/dist/selfTest.js +225 -0
- package/dist/selfTest.js.map +1 -0
- package/dist/server.d.ts +10 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +67 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.d.ts.map +1 -0
- package/dist/setup.js +210 -0
- package/dist/setup.js.map +1 -0
- package/dist/toolErrors.d.ts +2 -0
- package/dist/toolErrors.d.ts.map +1 -0
- package/dist/toolErrors.js +18 -0
- package/dist/toolErrors.js.map +1 -0
- package/dist/tools/activities.d.ts +18 -0
- package/dist/tools/activities.d.ts.map +1 -0
- package/dist/tools/activities.js +182 -0
- package/dist/tools/activities.js.map +1 -0
- package/dist/tools/bodyComposition.d.ts +10 -0
- package/dist/tools/bodyComposition.d.ts.map +1 -0
- package/dist/tools/bodyComposition.js +139 -0
- package/dist/tools/bodyComposition.js.map +1 -0
- package/dist/tools/context.d.ts +8 -0
- package/dist/tools/context.d.ts.map +1 -0
- package/dist/tools/context.js +148 -0
- package/dist/tools/context.js.map +1 -0
- package/dist/tools/findings.d.ts +7 -0
- package/dist/tools/findings.d.ts.map +1 -0
- package/dist/tools/findings.js +51 -0
- package/dist/tools/findings.js.map +1 -0
- package/dist/tools/heartRate.d.ts +10 -0
- package/dist/tools/heartRate.d.ts.map +1 -0
- package/dist/tools/heartRate.js +105 -0
- package/dist/tools/heartRate.js.map +1 -0
- package/dist/tools/index.d.ts +66 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +101 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/payloads.d.ts +148 -0
- package/dist/tools/payloads.d.ts.map +1 -0
- package/dist/tools/payloads.js +2 -0
- package/dist/tools/payloads.js.map +1 -0
- package/dist/tools/recovery.d.ts +31 -0
- package/dist/tools/recovery.d.ts.map +1 -0
- package/dist/tools/recovery.js +350 -0
- package/dist/tools/recovery.js.map +1 -0
- package/dist/tools/sleep.d.ts +10 -0
- package/dist/tools/sleep.d.ts.map +1 -0
- package/dist/tools/sleep.js +124 -0
- package/dist/tools/sleep.js.map +1 -0
- package/dist/tools/stress.d.ts +11 -0
- package/dist/tools/stress.d.ts.map +1 -0
- package/dist/tools/stress.js +108 -0
- package/dist/tools/stress.js.map +1 -0
- package/dist/tools/trainingInsights.d.ts +15 -0
- package/dist/tools/trainingInsights.d.ts.map +1 -0
- package/dist/tools/trainingInsights.js +79 -0
- package/dist/tools/trainingInsights.js.map +1 -0
- package/dist/tools/types.d.ts +12 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/dist/tools/vo2Max.d.ts +11 -0
- package/dist/tools/vo2Max.d.ts.map +1 -0
- package/dist/tools/vo2Max.js +110 -0
- package/dist/tools/vo2Max.js.map +1 -0
- package/dist/tools/week.d.ts +12 -0
- package/dist/tools/week.d.ts.map +1 -0
- package/dist/tools/week.js +74 -0
- package/dist/tools/week.js.map +1 -0
- package/dist/usage.d.ts +104 -0
- package/dist/usage.d.ts.map +1 -0
- package/dist/usage.js +247 -0
- package/dist/usage.js.map +1 -0
- package/dist/utils/batch.d.ts +2 -0
- package/dist/utils/batch.d.ts.map +1 -0
- package/dist/utils/batch.js +18 -0
- package/dist/utils/batch.js.map +1 -0
- package/dist/utils/helpers.d.ts +32 -0
- package/dist/utils/helpers.d.ts.map +1 -0
- package/dist/utils/helpers.js +188 -0
- package/dist/utils/helpers.js.map +1 -0
- package/dist/utils/jsonFile.d.ts +19 -0
- package/dist/utils/jsonFile.d.ts.map +1 -0
- package/dist/utils/jsonFile.js +24 -0
- package/dist/utils/jsonFile.js.map +1 -0
- package/dist/utils/logger.d.ts +4 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +97 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/secretFile.d.ts +15 -0
- package/dist/utils/secretFile.d.ts.map +1 -0
- package/dist/utils/secretFile.js +47 -0
- package/dist/utils/secretFile.js.map +1 -0
- package/dist/utils/stdio.d.ts +10 -0
- package/dist/utils/stdio.d.ts.map +1 -0
- package/dist/utils/stdio.js +70 -0
- package/dist/utils/stdio.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +7 -0
- package/dist/version.js.map +1 -0
- package/dist/watchApi.d.ts +264 -0
- package/dist/watchApi.d.ts.map +1 -0
- package/dist/watchApi.js +333 -0
- package/dist/watchApi.js.map +1 -0
- package/package.json +77 -0
package/dist/watchApi.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { executeTool } from "./tools/index.js";
|
|
2
|
+
import { generateDailyInsight, isAiConfigured } from "./promptApi.js";
|
|
3
|
+
import { DateTime } from "luxon";
|
|
4
|
+
import { buildDetectorInput, runDetectors } from "./detect/index.js";
|
|
5
|
+
import { restingHrDeltaBpm } from "./detect/detectors.js";
|
|
6
|
+
import { getProfile, stateFor, visibleCards, } from "./profile.js";
|
|
7
|
+
import { budgetState } from "./usage.js";
|
|
8
|
+
import { buildWeekReview } from "./detect/week.js";
|
|
9
|
+
import { nextRace } from "./detect/countdown.js";
|
|
10
|
+
import { buildPromptSuggestions } from "./promptSuggestions.js";
|
|
11
|
+
import { formatDistanceMeters } from "./utils/helpers.js";
|
|
12
|
+
import { activeContext, upcomingContext } from "./history/context.js";
|
|
13
|
+
// SECTION: Payload to card mappers
|
|
14
|
+
const RECOVERY_LABELS = {
|
|
15
|
+
recovered: "Ready",
|
|
16
|
+
good: "Light",
|
|
17
|
+
fatigued: "Rest",
|
|
18
|
+
};
|
|
19
|
+
export function toWatchRecovery(payload) {
|
|
20
|
+
if (!payload) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
score: payload.recovery.score,
|
|
25
|
+
label: RECOVERY_LABELS[payload.recovery.status],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function toWatchSleep(payload) {
|
|
29
|
+
const night = payload?.nights[0];
|
|
30
|
+
if (!payload || !night) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
// A night of zero seconds is a night Connect has no data for -- the watch was
|
|
34
|
+
// off the wrist, or the night is still in progress and nothing has been
|
|
35
|
+
// finalised. It is not a measurement of no sleep.
|
|
36
|
+
//
|
|
37
|
+
// Found on live data at two in the morning: the payload carried
|
|
38
|
+
// `{hours: 0, score: null, label: ""}`, the server graded zero hours against
|
|
39
|
+
// the user's own band and returned "hard", and the wrist would have drawn a
|
|
40
|
+
// red zero for a night that simply had not happened yet. The same trap the
|
|
41
|
+
// Activity card already guards -- Connect reports 0 km for workouts that do
|
|
42
|
+
// not cover ground -- one field over.
|
|
43
|
+
if (night.totalSleepSeconds <= 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const score = night.sleepScore ?? payload.averageScore;
|
|
47
|
+
// "Fair" was the default, so a night Garmin never scored was labelled as
|
|
48
|
+
// though it had been assessed and found middling. Every branch below tests
|
|
49
|
+
// `score !== null` and the fall-through case is exactly the one where there is
|
|
50
|
+
// no score at all -- a verdict on a measurement that does not exist. The
|
|
51
|
+
// duration is still real and still worth showing; the judgement is not.
|
|
52
|
+
let label = "";
|
|
53
|
+
if (score !== null && score >= 80) {
|
|
54
|
+
label = "Great";
|
|
55
|
+
}
|
|
56
|
+
else if (score !== null && score >= 60) {
|
|
57
|
+
label = "Good";
|
|
58
|
+
}
|
|
59
|
+
else if (score !== null && score > 0) {
|
|
60
|
+
label = "Poor";
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
hours: Math.round((night.totalSleepSeconds / 3600) * 10) / 10,
|
|
64
|
+
score,
|
|
65
|
+
label,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function toWatchActivity(payload) {
|
|
69
|
+
const activity = payload?.activity;
|
|
70
|
+
if (!activity) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const units = getProfile().units;
|
|
74
|
+
return {
|
|
75
|
+
name: activity.name,
|
|
76
|
+
distance_km: activity.distanceMeters === null
|
|
77
|
+
? null
|
|
78
|
+
: Math.round((activity.distanceMeters / 1000) * 100) / 100,
|
|
79
|
+
distance_display: activity.distanceMeters === null || activity.distanceMeters <= 0
|
|
80
|
+
? null
|
|
81
|
+
: formatDistanceMeters(activity.distanceMeters, units),
|
|
82
|
+
duration_min: activity.durationSeconds === null ? null : Math.round(activity.durationSeconds / 60),
|
|
83
|
+
avg_hr: activity.averageHeartRate,
|
|
84
|
+
date: activity.startTimeLocal,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function stressLabel(avg) {
|
|
88
|
+
if (avg <= 25) {
|
|
89
|
+
return "Low";
|
|
90
|
+
}
|
|
91
|
+
if (avg <= 50) {
|
|
92
|
+
return "Medium";
|
|
93
|
+
}
|
|
94
|
+
return "High";
|
|
95
|
+
}
|
|
96
|
+
export function toWatchStress(payload) {
|
|
97
|
+
if (!payload || payload.averageStress === null) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
return { avg: payload.averageStress, label: stressLabel(payload.averageStress) };
|
|
101
|
+
}
|
|
102
|
+
export function toWatchVo2Max(payload) {
|
|
103
|
+
if (!payload || payload.current === null) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return { value: Math.round(payload.current * 10) / 10, trend: payload.trend };
|
|
107
|
+
}
|
|
108
|
+
export function toWatchHeartRate(payload) {
|
|
109
|
+
if (!payload || payload.currentResting === null) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return { resting: payload.currentResting, max: payload.days[0]?.maxHeartRate ?? null };
|
|
113
|
+
}
|
|
114
|
+
// SECTION: Summary assembly
|
|
115
|
+
export function toWatchFindings(findings) {
|
|
116
|
+
return findings.map((finding) => ({
|
|
117
|
+
kind: finding.kind,
|
|
118
|
+
severity: finding.severity,
|
|
119
|
+
headline: finding.headline,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* The worst severity among the findings, and how many there are.
|
|
124
|
+
*
|
|
125
|
+
* `info` deliberately does not raise the level above `none`: an informational
|
|
126
|
+
* finding is worth a line on the Today card and is not worth a badge on a
|
|
127
|
+
* watch face.
|
|
128
|
+
*/
|
|
129
|
+
export function toWatchAlert(findings) {
|
|
130
|
+
const level = findings.some((f) => f.severity === "warn")
|
|
131
|
+
? "warn"
|
|
132
|
+
: findings.some((f) => f.severity === "notice")
|
|
133
|
+
? "notice"
|
|
134
|
+
: "none";
|
|
135
|
+
return { level, count: findings.length };
|
|
136
|
+
}
|
|
137
|
+
/** Percent change, or null when the earlier figure is zero and a percentage of
|
|
138
|
+
nothing would be either infinite or a lie. */
|
|
139
|
+
function percentDelta(current, previous) {
|
|
140
|
+
if (current === null || previous === null || previous === 0) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return Math.round(((current - previous) / previous) * 100);
|
|
144
|
+
}
|
|
145
|
+
export function toWatchWeek(review) {
|
|
146
|
+
if (!review) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const load = review.metrics.find((metric) => metric.key === "load");
|
|
150
|
+
return {
|
|
151
|
+
sessions: review.sessions,
|
|
152
|
+
previous_sessions: review.previousSessions,
|
|
153
|
+
moving_minutes: review.movingMinutes,
|
|
154
|
+
load_delta_pct: percentDelta(load?.current ?? null, load?.previous ?? null),
|
|
155
|
+
sleep_debt_h: review.sleep.debtHours,
|
|
156
|
+
sleep_habitual_h: review.sleep.habitualHours,
|
|
157
|
+
sleep_consistency: review.sleep.consistency,
|
|
158
|
+
forecast_ratio: review.forecast.projectedRatio,
|
|
159
|
+
forecast_verdict: review.forecast.verdict,
|
|
160
|
+
ready: review.ready,
|
|
161
|
+
headline: review.headline,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export function toWatchRace(race) {
|
|
165
|
+
return race
|
|
166
|
+
? { text: race.text, days_away: race.daysAway, phase: race.phase }
|
|
167
|
+
: null;
|
|
168
|
+
}
|
|
169
|
+
export function buildWatchSummaryFrom(parts) {
|
|
170
|
+
const recovery = toWatchRecovery(parts.recovery);
|
|
171
|
+
const sleep = toWatchSleep(parts.sleep);
|
|
172
|
+
const stress = toWatchStress(parts.stress);
|
|
173
|
+
const vo2max = toWatchVo2Max(parts.vo2max);
|
|
174
|
+
// Read once. Every grade below has to come from the same profile: reading it
|
|
175
|
+
// per metric would let a save land mid-assembly and colour half the payload
|
|
176
|
+
// against the old bands.
|
|
177
|
+
const profile = getProfile();
|
|
178
|
+
return {
|
|
179
|
+
daily_overview: {
|
|
180
|
+
recovery: recovery?.score ?? null,
|
|
181
|
+
sleep_h: sleep?.hours ?? null,
|
|
182
|
+
stress: stress?.avg ?? null,
|
|
183
|
+
vo2max: vo2max?.value ?? null,
|
|
184
|
+
},
|
|
185
|
+
recovery,
|
|
186
|
+
sleep,
|
|
187
|
+
activity: toWatchActivity(parts.activity),
|
|
188
|
+
stress,
|
|
189
|
+
vo2max,
|
|
190
|
+
heart_rate: toWatchHeartRate(parts.heartRate),
|
|
191
|
+
findings: toWatchFindings(parts.findings),
|
|
192
|
+
coverage: parts.coverage,
|
|
193
|
+
week: toWatchWeek(parts.week),
|
|
194
|
+
race: toWatchRace(parts.race),
|
|
195
|
+
prompts: buildPromptSuggestions({ findings: parts.findings, coverage: parts.coverage }, parts.context, profile.ai.customPrompts),
|
|
196
|
+
ai_configured: parts.aiConfigured,
|
|
197
|
+
states: {
|
|
198
|
+
recovery: stateFor("recovery", recovery?.score ?? null),
|
|
199
|
+
sleep: stateFor("sleepHours", sleep?.hours ?? null),
|
|
200
|
+
stress: stateFor("stress", stress?.avg ?? null),
|
|
201
|
+
// Graded on the distance from this person's own median, not on the rate.
|
|
202
|
+
// 58 bpm is unremarkable for one person and a warning for another, and
|
|
203
|
+
// the absolute number cannot tell them apart.
|
|
204
|
+
resting_hr: stateFor("restingHrDelta", parts.restingHrDeltaBpm),
|
|
205
|
+
},
|
|
206
|
+
display: {
|
|
207
|
+
name: profile.displayName,
|
|
208
|
+
units: profile.units,
|
|
209
|
+
cards: visibleCards(profile),
|
|
210
|
+
},
|
|
211
|
+
alert: toWatchAlert(parts.findings),
|
|
212
|
+
budget: (() => {
|
|
213
|
+
const state = budgetState();
|
|
214
|
+
return { exceeded: state.exceeded, incomplete: state.incomplete };
|
|
215
|
+
})(),
|
|
216
|
+
updated_at: parts.updatedAt,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* One failing tool must not empty the whole screen -- the watch renders each
|
|
221
|
+
* card independently and shows "No data" for the ones that are missing.
|
|
222
|
+
*/
|
|
223
|
+
async function payloadOf(name, args) {
|
|
224
|
+
try {
|
|
225
|
+
const result = await executeTool(name, args);
|
|
226
|
+
return result.data ?? null;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* How old a stored measurement may be before the watch stops drawing it.
|
|
234
|
+
*
|
|
235
|
+
* The tools now answer from TrainBud's own store when Connect will not, which
|
|
236
|
+
* is what an AI client needs -- it can read `storedThrough` and say which day it
|
|
237
|
+
* is describing. THE WATCH CANNOT. Version 1.4.0 is live in the Connect IQ
|
|
238
|
+
* store, it parses this JSON on the device, and it ignores fields it does not
|
|
239
|
+
* know: hand it a sleep card built from 2026-08-21 and it draws "6.4h" under a
|
|
240
|
+
* heading that means last night, on a wrist, with no way for the wearer to tell.
|
|
241
|
+
* A store review takes days, so "ship 1.4.1 and it will render the date" is not
|
|
242
|
+
* an answer for anyone who has already installed it.
|
|
243
|
+
*
|
|
244
|
+
* So the cards take stored data only inside the same grace window the detectors
|
|
245
|
+
* use -- three days, because Garmin finalises a sleep score hours after waking
|
|
246
|
+
* and an unsynced yesterday is normal. Past that the card is null and the watch
|
|
247
|
+
* draws its existing "No data", which is honest.
|
|
248
|
+
*
|
|
249
|
+
* The AI is not gated. `formatFindingsContext` states the record's age in words
|
|
250
|
+
* before the model sees a single number, so the model can hold a fortnight-old
|
|
251
|
+
* figure and say what it is. That asymmetry is the whole point: the surface that
|
|
252
|
+
* can express "as of the 21st" gets the data, and the surface that cannot,
|
|
253
|
+
* does not.
|
|
254
|
+
*/
|
|
255
|
+
const WATCH_STORED_GRACE_DAYS = 3;
|
|
256
|
+
function daysSince(date, today) {
|
|
257
|
+
if (!date) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
const parsed = DateTime.fromISO(date).startOf("day");
|
|
261
|
+
return parsed.isValid ? Math.max(0, Math.round(today.diff(parsed, "days").days)) : null;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Drop a card the watch would draw as current when it is not.
|
|
265
|
+
*
|
|
266
|
+
* The test is the age of the NEWEST day in the card, whatever its source --
|
|
267
|
+
* not whether the store was involved. Two reasons that is the right question:
|
|
268
|
+
*
|
|
269
|
+
* * A partly stored answer whose recent days came off the wire is current
|
|
270
|
+
* where it matters, because every card's headline figure (`nights[0]`,
|
|
271
|
+
* `days[0]`) is the newest day.
|
|
272
|
+
* * The cache could already serve a day-old reading as "current resting HR"
|
|
273
|
+
* before any of this existed. One rule covers both.
|
|
274
|
+
*
|
|
275
|
+
* With a healthy connection the newest day is today or yesterday, so this is a
|
|
276
|
+
* no-op in the normal case.
|
|
277
|
+
*/
|
|
278
|
+
export function freshEnoughForWatch(payload, newestDate, today = DateTime.local().startOf("day"), graceDays = WATCH_STORED_GRACE_DAYS) {
|
|
279
|
+
if (!payload) {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
const age = daysSince(newestDate(payload), today);
|
|
283
|
+
// An undated card is left alone: this exists to catch a date that is too old,
|
|
284
|
+
// not to reject anything it cannot date.
|
|
285
|
+
return age === null || age <= graceDays ? payload : null;
|
|
286
|
+
}
|
|
287
|
+
export async function buildWatchSummary() {
|
|
288
|
+
// Detection is a local SQLite read, so it costs nothing next to six Garmin
|
|
289
|
+
// round trips and does not need to be raced with them.
|
|
290
|
+
const detection = runDetectors();
|
|
291
|
+
const detectorInput = buildDetectorInput();
|
|
292
|
+
const today = DateTime.local().toISODate() ?? "";
|
|
293
|
+
const weekReview = buildWeekReview(detectorInput);
|
|
294
|
+
const race = nextRace([...activeContext(today), ...upcomingContext(today)], today);
|
|
295
|
+
const [recovery, sleep, activity, stress, vo2max, heartRate] = await Promise.all([
|
|
296
|
+
payloadOf("get_recovery_status", {}),
|
|
297
|
+
payloadOf("get_sleep_data", { nights: 1 }),
|
|
298
|
+
payloadOf("get_latest_activity", {}),
|
|
299
|
+
payloadOf("get_stress_levels", { days: 7 }),
|
|
300
|
+
payloadOf("get_vo2_max_trends", { days: 30 }),
|
|
301
|
+
payloadOf("get_heart_rate_trends", { days: 7 }),
|
|
302
|
+
]);
|
|
303
|
+
// See freshEnoughForWatch: the tools may now answer out of the store, and a
|
|
304
|
+
// build already on someone's wrist would draw a fortnight-old figure as
|
|
305
|
+
// today's.
|
|
306
|
+
const startOfToday = DateTime.local().startOf("day");
|
|
307
|
+
const summary = {
|
|
308
|
+
...buildWatchSummaryFrom({
|
|
309
|
+
recovery: freshEnoughForWatch(recovery, (payload) => payload.date, startOfToday),
|
|
310
|
+
sleep: freshEnoughForWatch(sleep, (payload) => payload.nights[0]?.date, startOfToday),
|
|
311
|
+
// Exempt: the last activity is dated on its own card and an old one is
|
|
312
|
+
// a fact about the user's training, not a number mislabelled as today's.
|
|
313
|
+
activity,
|
|
314
|
+
stress: freshEnoughForWatch(stress, (payload) => payload.days[0]?.date, startOfToday),
|
|
315
|
+
// Exempt: Connect only recomputes VO2 max after a qualifying activity, so
|
|
316
|
+
// the newest reading is the current one however old it is.
|
|
317
|
+
vo2max,
|
|
318
|
+
heartRate: freshEnoughForWatch(heartRate, (payload) => payload.days[0]?.date, startOfToday),
|
|
319
|
+
findings: detection.findings,
|
|
320
|
+
coverage: detection.coverage,
|
|
321
|
+
context: activeContext(today),
|
|
322
|
+
week: weekReview,
|
|
323
|
+
race,
|
|
324
|
+
updatedAt: new Date().toISOString(),
|
|
325
|
+
aiConfigured: isAiConfigured(),
|
|
326
|
+
restingHrDeltaBpm: restingHrDeltaBpm(detectorInput),
|
|
327
|
+
}),
|
|
328
|
+
ai_insight: null,
|
|
329
|
+
};
|
|
330
|
+
summary.ai_insight = await generateDailyInsight(summary);
|
|
331
|
+
return summary;
|
|
332
|
+
}
|
|
333
|
+
//# sourceMappingURL=watchApi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watchApi.js","sourceRoot":"","sources":["../src/watchApi.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trainbud",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "TrainBud \u2014 talk to your Connect fitness data through Claude and other MCP clients",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"QUICKSTART.md",
|
|
11
|
+
"CHANGELOG.md",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
".env.example"
|
|
14
|
+
],
|
|
15
|
+
"bin": {
|
|
16
|
+
"trainbud": "dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"dev": "tsx src/index.ts start",
|
|
21
|
+
"start": "node dist/index.js start",
|
|
22
|
+
"test": "node scripts/run-tests.mjs",
|
|
23
|
+
"test:vitest": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"test:coverage": "vitest run --coverage",
|
|
26
|
+
"lint": "eslint src tests",
|
|
27
|
+
"typecheck": "tsc --noEmit && tsc -p tsconfig.test.json",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"garmin",
|
|
32
|
+
"garmin-connect",
|
|
33
|
+
"mcp",
|
|
34
|
+
"model-context-protocol",
|
|
35
|
+
"claude",
|
|
36
|
+
"fitness",
|
|
37
|
+
"health",
|
|
38
|
+
"trainbud"
|
|
39
|
+
],
|
|
40
|
+
"author": "Zsadigzade",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/Zsadigzade/trainbud.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/Zsadigzade/trainbud#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/Zsadigzade/trainbud/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.0.0"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@eslint/js": "^10.0.1",
|
|
55
|
+
"@types/better-sqlite3": "^7.6.13",
|
|
56
|
+
"@types/luxon": "^3.7.2",
|
|
57
|
+
"@types/node": "^26.0.1",
|
|
58
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
59
|
+
"eslint": "^10.5.0",
|
|
60
|
+
"tsx": "^4.22.4",
|
|
61
|
+
"typescript": "^6.0.3",
|
|
62
|
+
"typescript-eslint": "^8.62.0",
|
|
63
|
+
"vitest": "^4.1.9"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@anthropic-ai/sdk": "^0.106.0",
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
68
|
+
"better-sqlite3": "^12.11.1",
|
|
69
|
+
"commander": "^15.0.0",
|
|
70
|
+
"dotenv": "^17.4.2",
|
|
71
|
+
"garmin-connect": "^1.6.2",
|
|
72
|
+
"luxon": "^3.7.2",
|
|
73
|
+
"pino": "^10.3.1",
|
|
74
|
+
"pino-pretty": "^13.1.3",
|
|
75
|
+
"zod": "^4.4.3"
|
|
76
|
+
}
|
|
77
|
+
}
|