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.
Files changed (274) hide show
  1. package/.env.example +11 -0
  2. package/CHANGELOG.md +675 -0
  3. package/LICENSE +21 -0
  4. package/QUICKSTART.md +182 -0
  5. package/README.md +322 -0
  6. package/dist/appDb.d.ts +83 -0
  7. package/dist/appDb.d.ts.map +1 -0
  8. package/dist/appDb.js +239 -0
  9. package/dist/appDb.js.map +1 -0
  10. package/dist/check.d.ts +12 -0
  11. package/dist/check.d.ts.map +1 -0
  12. package/dist/check.js +230 -0
  13. package/dist/check.js.map +1 -0
  14. package/dist/cli.d.ts +3 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +457 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/config.d.ts +61 -0
  19. package/dist/config.d.ts.map +1 -0
  20. package/dist/config.js +241 -0
  21. package/dist/config.js.map +1 -0
  22. package/dist/dashboard.d.ts +8 -0
  23. package/dist/dashboard.d.ts.map +1 -0
  24. package/dist/dashboard.js +1004 -0
  25. package/dist/dashboard.js.map +1 -0
  26. package/dist/dashboardCharts.d.ts +99 -0
  27. package/dist/dashboardCharts.d.ts.map +1 -0
  28. package/dist/dashboardCharts.js +317 -0
  29. package/dist/dashboardCharts.js.map +1 -0
  30. package/dist/dashboardData.d.ts +89 -0
  31. package/dist/dashboardData.d.ts.map +1 -0
  32. package/dist/dashboardData.js +228 -0
  33. package/dist/dashboardData.js.map +1 -0
  34. package/dist/detect/baseline.d.ts +17 -0
  35. package/dist/detect/baseline.d.ts.map +1 -0
  36. package/dist/detect/baseline.js +62 -0
  37. package/dist/detect/baseline.js.map +1 -0
  38. package/dist/detect/countdown.d.ts +30 -0
  39. package/dist/detect/countdown.d.ts.map +1 -0
  40. package/dist/detect/countdown.js +79 -0
  41. package/dist/detect/countdown.js.map +1 -0
  42. package/dist/detect/detectors.d.ts +20 -0
  43. package/dist/detect/detectors.d.ts.map +1 -0
  44. package/dist/detect/detectors.js +291 -0
  45. package/dist/detect/detectors.js.map +1 -0
  46. package/dist/detect/findings.d.ts +29 -0
  47. package/dist/detect/findings.d.ts.map +1 -0
  48. package/dist/detect/findings.js +2 -0
  49. package/dist/detect/findings.js.map +1 -0
  50. package/dist/detect/forecast.d.ts +33 -0
  51. package/dist/detect/forecast.d.ts.map +1 -0
  52. package/dist/detect/forecast.js +122 -0
  53. package/dist/detect/forecast.js.map +1 -0
  54. package/dist/detect/index.d.ts +50 -0
  55. package/dist/detect/index.d.ts.map +1 -0
  56. package/dist/detect/index.js +114 -0
  57. package/dist/detect/index.js.map +1 -0
  58. package/dist/detect/sleepQuality.d.ts +21 -0
  59. package/dist/detect/sleepQuality.d.ts.map +1 -0
  60. package/dist/detect/sleepQuality.js +164 -0
  61. package/dist/detect/sleepQuality.js.map +1 -0
  62. package/dist/detect/trimp.d.ts +22 -0
  63. package/dist/detect/trimp.d.ts.map +1 -0
  64. package/dist/detect/trimp.js +77 -0
  65. package/dist/detect/trimp.js.map +1 -0
  66. package/dist/detect/week.d.ts +31 -0
  67. package/dist/detect/week.d.ts.map +1 -0
  68. package/dist/detect/week.js +184 -0
  69. package/dist/detect/week.js.map +1 -0
  70. package/dist/garmin/auth.d.ts +10 -0
  71. package/dist/garmin/auth.d.ts.map +1 -0
  72. package/dist/garmin/auth.js +81 -0
  73. package/dist/garmin/auth.js.map +1 -0
  74. package/dist/garmin/cache.d.ts +42 -0
  75. package/dist/garmin/cache.d.ts.map +1 -0
  76. package/dist/garmin/cache.js +141 -0
  77. package/dist/garmin/cache.js.map +1 -0
  78. package/dist/garmin/client.d.ts +22 -0
  79. package/dist/garmin/client.d.ts.map +1 -0
  80. package/dist/garmin/client.js +237 -0
  81. package/dist/garmin/client.js.map +1 -0
  82. package/dist/garmin/daily.d.ts +24 -0
  83. package/dist/garmin/daily.d.ts.map +1 -0
  84. package/dist/garmin/daily.js +96 -0
  85. package/dist/garmin/daily.js.map +1 -0
  86. package/dist/garmin/garminApiTypes.d.ts +70 -0
  87. package/dist/garmin/garminApiTypes.d.ts.map +1 -0
  88. package/dist/garmin/garminApiTypes.js +3 -0
  89. package/dist/garmin/garminApiTypes.js.map +1 -0
  90. package/dist/garmin/garminConnect.d.ts +17 -0
  91. package/dist/garmin/garminConnect.d.ts.map +1 -0
  92. package/dist/garmin/garminConnect.js +5 -0
  93. package/dist/garmin/garminConnect.js.map +1 -0
  94. package/dist/garmin/partial.d.ts +32 -0
  95. package/dist/garmin/partial.d.ts.map +1 -0
  96. package/dist/garmin/partial.js +38 -0
  97. package/dist/garmin/partial.js.map +1 -0
  98. package/dist/garmin/rawApi.d.ts +59 -0
  99. package/dist/garmin/rawApi.d.ts.map +1 -0
  100. package/dist/garmin/rawApi.js +113 -0
  101. package/dist/garmin/rawApi.js.map +1 -0
  102. package/dist/garmin/types.d.ts +85 -0
  103. package/dist/garmin/types.d.ts.map +1 -0
  104. package/dist/garmin/types.js +11 -0
  105. package/dist/garmin/types.js.map +1 -0
  106. package/dist/history/capture.d.ts +12 -0
  107. package/dist/history/capture.d.ts.map +1 -0
  108. package/dist/history/capture.js +63 -0
  109. package/dist/history/capture.js.map +1 -0
  110. package/dist/history/context.d.ts +51 -0
  111. package/dist/history/context.d.ts.map +1 -0
  112. package/dist/history/context.js +122 -0
  113. package/dist/history/context.js.map +1 -0
  114. package/dist/history/fallback.d.ts +72 -0
  115. package/dist/history/fallback.d.ts.map +1 -0
  116. package/dist/history/fallback.js +185 -0
  117. package/dist/history/fallback.js.map +1 -0
  118. package/dist/history/ingest.d.ts +44 -0
  119. package/dist/history/ingest.d.ts.map +1 -0
  120. package/dist/history/ingest.js +347 -0
  121. package/dist/history/ingest.js.map +1 -0
  122. package/dist/history/scheduler.d.ts +20 -0
  123. package/dist/history/scheduler.d.ts.map +1 -0
  124. package/dist/history/scheduler.js +54 -0
  125. package/dist/history/scheduler.js.map +1 -0
  126. package/dist/history/schema.d.ts +41 -0
  127. package/dist/history/schema.d.ts.map +1 -0
  128. package/dist/history/schema.js +91 -0
  129. package/dist/history/schema.js.map +1 -0
  130. package/dist/history/store.d.ts +112 -0
  131. package/dist/history/store.d.ts.map +1 -0
  132. package/dist/history/store.js +282 -0
  133. package/dist/history/store.js.map +1 -0
  134. package/dist/httpServer.d.ts +55 -0
  135. package/dist/httpServer.d.ts.map +1 -0
  136. package/dist/httpServer.js +1027 -0
  137. package/dist/httpServer.js.map +1 -0
  138. package/dist/index.d.ts +3 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +11 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/mcpConfig.d.ts +56 -0
  143. package/dist/mcpConfig.d.ts.map +1 -0
  144. package/dist/mcpConfig.js +106 -0
  145. package/dist/mcpConfig.js.map +1 -0
  146. package/dist/pairApi.d.ts +14 -0
  147. package/dist/pairApi.d.ts.map +1 -0
  148. package/dist/pairApi.js +38 -0
  149. package/dist/pairApi.js.map +1 -0
  150. package/dist/paths.d.ts +45 -0
  151. package/dist/paths.d.ts.map +1 -0
  152. package/dist/paths.js +105 -0
  153. package/dist/paths.js.map +1 -0
  154. package/dist/profile.d.ts +123 -0
  155. package/dist/profile.d.ts.map +1 -0
  156. package/dist/profile.js +327 -0
  157. package/dist/profile.js.map +1 -0
  158. package/dist/promptApi.d.ts +43 -0
  159. package/dist/promptApi.d.ts.map +1 -0
  160. package/dist/promptApi.js +328 -0
  161. package/dist/promptApi.js.map +1 -0
  162. package/dist/promptSuggestions.d.ts +16 -0
  163. package/dist/promptSuggestions.d.ts.map +1 -0
  164. package/dist/promptSuggestions.js +141 -0
  165. package/dist/promptSuggestions.js.map +1 -0
  166. package/dist/selfTest.d.ts +53 -0
  167. package/dist/selfTest.d.ts.map +1 -0
  168. package/dist/selfTest.js +225 -0
  169. package/dist/selfTest.js.map +1 -0
  170. package/dist/server.d.ts +10 -0
  171. package/dist/server.d.ts.map +1 -0
  172. package/dist/server.js +67 -0
  173. package/dist/server.js.map +1 -0
  174. package/dist/setup.d.ts +2 -0
  175. package/dist/setup.d.ts.map +1 -0
  176. package/dist/setup.js +210 -0
  177. package/dist/setup.js.map +1 -0
  178. package/dist/toolErrors.d.ts +2 -0
  179. package/dist/toolErrors.d.ts.map +1 -0
  180. package/dist/toolErrors.js +18 -0
  181. package/dist/toolErrors.js.map +1 -0
  182. package/dist/tools/activities.d.ts +18 -0
  183. package/dist/tools/activities.d.ts.map +1 -0
  184. package/dist/tools/activities.js +182 -0
  185. package/dist/tools/activities.js.map +1 -0
  186. package/dist/tools/bodyComposition.d.ts +10 -0
  187. package/dist/tools/bodyComposition.d.ts.map +1 -0
  188. package/dist/tools/bodyComposition.js +139 -0
  189. package/dist/tools/bodyComposition.js.map +1 -0
  190. package/dist/tools/context.d.ts +8 -0
  191. package/dist/tools/context.d.ts.map +1 -0
  192. package/dist/tools/context.js +148 -0
  193. package/dist/tools/context.js.map +1 -0
  194. package/dist/tools/findings.d.ts +7 -0
  195. package/dist/tools/findings.d.ts.map +1 -0
  196. package/dist/tools/findings.js +51 -0
  197. package/dist/tools/findings.js.map +1 -0
  198. package/dist/tools/heartRate.d.ts +10 -0
  199. package/dist/tools/heartRate.d.ts.map +1 -0
  200. package/dist/tools/heartRate.js +105 -0
  201. package/dist/tools/heartRate.js.map +1 -0
  202. package/dist/tools/index.d.ts +66 -0
  203. package/dist/tools/index.d.ts.map +1 -0
  204. package/dist/tools/index.js +101 -0
  205. package/dist/tools/index.js.map +1 -0
  206. package/dist/tools/payloads.d.ts +148 -0
  207. package/dist/tools/payloads.d.ts.map +1 -0
  208. package/dist/tools/payloads.js +2 -0
  209. package/dist/tools/payloads.js.map +1 -0
  210. package/dist/tools/recovery.d.ts +31 -0
  211. package/dist/tools/recovery.d.ts.map +1 -0
  212. package/dist/tools/recovery.js +350 -0
  213. package/dist/tools/recovery.js.map +1 -0
  214. package/dist/tools/sleep.d.ts +10 -0
  215. package/dist/tools/sleep.d.ts.map +1 -0
  216. package/dist/tools/sleep.js +124 -0
  217. package/dist/tools/sleep.js.map +1 -0
  218. package/dist/tools/stress.d.ts +11 -0
  219. package/dist/tools/stress.d.ts.map +1 -0
  220. package/dist/tools/stress.js +108 -0
  221. package/dist/tools/stress.js.map +1 -0
  222. package/dist/tools/trainingInsights.d.ts +15 -0
  223. package/dist/tools/trainingInsights.d.ts.map +1 -0
  224. package/dist/tools/trainingInsights.js +79 -0
  225. package/dist/tools/trainingInsights.js.map +1 -0
  226. package/dist/tools/types.d.ts +12 -0
  227. package/dist/tools/types.d.ts.map +1 -0
  228. package/dist/tools/types.js +2 -0
  229. package/dist/tools/types.js.map +1 -0
  230. package/dist/tools/vo2Max.d.ts +11 -0
  231. package/dist/tools/vo2Max.d.ts.map +1 -0
  232. package/dist/tools/vo2Max.js +110 -0
  233. package/dist/tools/vo2Max.js.map +1 -0
  234. package/dist/tools/week.d.ts +12 -0
  235. package/dist/tools/week.d.ts.map +1 -0
  236. package/dist/tools/week.js +74 -0
  237. package/dist/tools/week.js.map +1 -0
  238. package/dist/usage.d.ts +104 -0
  239. package/dist/usage.d.ts.map +1 -0
  240. package/dist/usage.js +247 -0
  241. package/dist/usage.js.map +1 -0
  242. package/dist/utils/batch.d.ts +2 -0
  243. package/dist/utils/batch.d.ts.map +1 -0
  244. package/dist/utils/batch.js +18 -0
  245. package/dist/utils/batch.js.map +1 -0
  246. package/dist/utils/helpers.d.ts +32 -0
  247. package/dist/utils/helpers.d.ts.map +1 -0
  248. package/dist/utils/helpers.js +188 -0
  249. package/dist/utils/helpers.js.map +1 -0
  250. package/dist/utils/jsonFile.d.ts +19 -0
  251. package/dist/utils/jsonFile.d.ts.map +1 -0
  252. package/dist/utils/jsonFile.js +24 -0
  253. package/dist/utils/jsonFile.js.map +1 -0
  254. package/dist/utils/logger.d.ts +4 -0
  255. package/dist/utils/logger.d.ts.map +1 -0
  256. package/dist/utils/logger.js +97 -0
  257. package/dist/utils/logger.js.map +1 -0
  258. package/dist/utils/secretFile.d.ts +15 -0
  259. package/dist/utils/secretFile.d.ts.map +1 -0
  260. package/dist/utils/secretFile.js +47 -0
  261. package/dist/utils/secretFile.js.map +1 -0
  262. package/dist/utils/stdio.d.ts +10 -0
  263. package/dist/utils/stdio.d.ts.map +1 -0
  264. package/dist/utils/stdio.js +70 -0
  265. package/dist/utils/stdio.js.map +1 -0
  266. package/dist/version.d.ts +2 -0
  267. package/dist/version.d.ts.map +1 -0
  268. package/dist/version.js +7 -0
  269. package/dist/version.js.map +1 -0
  270. package/dist/watchApi.d.ts +264 -0
  271. package/dist/watchApi.d.ts.map +1 -0
  272. package/dist/watchApi.js +333 -0
  273. package/dist/watchApi.js.map +1 -0
  274. package/package.json +77 -0
@@ -0,0 +1,350 @@
1
+ import { storedRawPayload, isPartialOrStored } from "../history/fallback.js";
2
+ import { getMetricsOn } from "../history/store.js";
3
+ import { logger } from "../utils/logger.js";
4
+ import { appConfig } from "../config.js";
5
+ import { buildToolCacheKey, withCache } from "../garmin/cache.js";
6
+ import { withGarminClient } from "../garmin/client.js";
7
+ import { clamp, formatIsoDate, getDateRange, getYesterday } from "../utils/helpers.js";
8
+ // SECTION: Recovery Scoring
9
+ const DEFAULT_WEIGHTS = {
10
+ hrv: 0.3,
11
+ sleep: 0.3,
12
+ stress: 0.2,
13
+ restingHr: 0.2,
14
+ };
15
+ export function normalizeWeights(weights) {
16
+ // Spreading the caller's object was wrong: getRecoveryStatus builds
17
+ // { hrv: input.hrv_weight, ... } from an input that usually carries no
18
+ // weights at all, so every key is present and explicitly undefined, and a
19
+ // spread of explicit undefined overwrites the default it was meant to fall
20
+ // back to. The total then came out NaN; `NaN <= 0` is false, so the guard
21
+ // below waved it through, every component was multiplied by NaN, and the
22
+ // score was NaN -- which JSON.stringify writes as null. That is how the
23
+ // cache, the tool output and the watch all ended up showing
24
+ // "Recovery score: null/100" with four healthy component scores beneath it.
25
+ const merged = { ...DEFAULT_WEIGHTS };
26
+ for (const key of Object.keys(DEFAULT_WEIGHTS)) {
27
+ const value = weights?.[key];
28
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) {
29
+ merged[key] = value;
30
+ }
31
+ }
32
+ const total = merged.hrv + merged.sleep + merged.stress + merged.restingHr;
33
+ if (!Number.isFinite(total) || total <= 0) {
34
+ return DEFAULT_WEIGHTS;
35
+ }
36
+ return {
37
+ hrv: merged.hrv / total,
38
+ sleep: merged.sleep / total,
39
+ stress: merged.stress / total,
40
+ restingHr: merged.restingHr / total,
41
+ };
42
+ }
43
+ export function scoreFromHrv(hrv, status) {
44
+ if (hrv === null) {
45
+ if (status === "BALANCED") {
46
+ return 80;
47
+ }
48
+ if (status === "LOW") {
49
+ return 45;
50
+ }
51
+ return 60;
52
+ }
53
+ if (hrv >= 60) {
54
+ return 95;
55
+ }
56
+ if (hrv >= 45) {
57
+ return 80;
58
+ }
59
+ if (hrv >= 30) {
60
+ return 60;
61
+ }
62
+ return 40;
63
+ }
64
+ /**
65
+ * Null when the night was not measured at all.
66
+ *
67
+ * The call site passed `sleep?.sleepTimeSeconds ?? 0`, so a night with no sleep
68
+ * record became zero seconds, fell through every band, and scored 35 out of 100
69
+ * -- a confident bad-night verdict manufactured out of an unworn watch. An hour
70
+ * is the floor for "this is a measurement": nobody's real night is shorter, and
71
+ * Garmin does report tiny durations for naps and for partial wear.
72
+ */
73
+ export function scoreFromSleep(score, durationSeconds) {
74
+ if (score !== null) {
75
+ return clamp(score, 0, 100);
76
+ }
77
+ if (durationSeconds < 3600) {
78
+ return null;
79
+ }
80
+ const hours = durationSeconds / 3600;
81
+ if (hours >= 8) {
82
+ return 90;
83
+ }
84
+ if (hours >= 7) {
85
+ return 75;
86
+ }
87
+ if (hours >= 6) {
88
+ return 55;
89
+ }
90
+ return 35;
91
+ }
92
+ export function scoreFromStress(stress) {
93
+ if (stress === null) {
94
+ return 60;
95
+ }
96
+ if (stress <= 15) {
97
+ return 95;
98
+ }
99
+ if (stress <= 25) {
100
+ return 75;
101
+ }
102
+ if (stress <= 35) {
103
+ return 55;
104
+ }
105
+ return 35;
106
+ }
107
+ export function scoreFromRestingHr(restingHr, baseline) {
108
+ if (restingHr === null) {
109
+ return 60;
110
+ }
111
+ if (baseline === null) {
112
+ if (restingHr <= 50) {
113
+ return 90;
114
+ }
115
+ if (restingHr <= 60) {
116
+ return 80;
117
+ }
118
+ if (restingHr <= 70) {
119
+ return 65;
120
+ }
121
+ return 45;
122
+ }
123
+ const delta = restingHr - baseline;
124
+ if (delta <= -2) {
125
+ return 90;
126
+ }
127
+ if (delta <= 2) {
128
+ return 75;
129
+ }
130
+ if (delta <= 5) {
131
+ return 55;
132
+ }
133
+ return 35;
134
+ }
135
+ export function buildRecoveryStatus(components, weights) {
136
+ // A component that was never measured is dropped and its weight redistributed
137
+ // across the rest, rather than being scored as though it had been measured
138
+ // badly. Scoring an absence is how an unworn night became "fatigued".
139
+ const parts = [
140
+ { value: components.hrvScore, weight: weights.hrv },
141
+ { value: components.stressScore, weight: weights.stress },
142
+ { value: components.restingHrScore, weight: weights.restingHr },
143
+ ];
144
+ if (components.sleepScore !== null) {
145
+ parts.push({ value: components.sleepScore, weight: weights.sleep });
146
+ }
147
+ const totalWeight = parts.reduce((sum, part) => sum + part.weight, 0);
148
+ const score = totalWeight > 0
149
+ ? Math.round(parts.reduce((sum, part) => sum + part.value * part.weight, 0) / totalWeight)
150
+ : 0;
151
+ let status = "good";
152
+ let recommendation = "Moderate training is reasonable. Listen to your body during hard efforts.";
153
+ if (score >= 80) {
154
+ status = "recovered";
155
+ recommendation = "You look recovered. Hard training or a quality session is appropriate today.";
156
+ }
157
+ else if (score < 60) {
158
+ status = "fatigued";
159
+ recommendation = "Recovery looks limited. Favor easy training, mobility, or a rest day.";
160
+ }
161
+ return {
162
+ score,
163
+ status,
164
+ recommendation,
165
+ components,
166
+ };
167
+ }
168
+ /**
169
+ * How far back the store is searched for the most recent night with a record.
170
+ * Past this the answer stops being a recovery score and becomes an anecdote,
171
+ * and saying nothing is better than scoring it.
172
+ */
173
+ const STORED_SIGNAL_WINDOW_DAYS = 30;
174
+ /**
175
+ * The same signals, read out of the store, when Connect will not answer.
176
+ *
177
+ * Every component already has an honest null path -- `scoreFromSleep` returns
178
+ * null for a night that was not measured and the weights renormalise around it
179
+ * -- so a partial reconstruction degrades rather than inventing. What it must
180
+ * not do is pretend to be current, which is what `storedThrough` is for: the
181
+ * renderer names the date and `buildWatchSummary` refuses the card once that
182
+ * date is old.
183
+ */
184
+ function storedRecoverySignals() {
185
+ for (const date of getDateRange(STORED_SIGNAL_WINDOW_DAYS)) {
186
+ const iso = formatIsoDate(date);
187
+ const raw = storedRawPayload(iso, "sleep");
188
+ const metrics = getMetricsOn(iso);
189
+ if (raw?.dailySleepDTO) {
190
+ return {
191
+ sleepData: raw,
192
+ restingHeartRate: metrics.get("resting_hr") ?? null,
193
+ baselineRestingHeartRate: null,
194
+ storedThrough: iso,
195
+ };
196
+ }
197
+ // No archived response, but the derived rows survive pruning forever.
198
+ const sleepSeconds = metrics.get("sleep_seconds");
199
+ if (sleepSeconds !== undefined) {
200
+ return {
201
+ sleepData: {
202
+ dailySleepDTO: {
203
+ sleepTimeSeconds: sleepSeconds,
204
+ avgSleepStress: metrics.get("sleep_stress") ?? null,
205
+ sleepScores: { overall: { value: metrics.get("sleep_score") ?? null } },
206
+ },
207
+ avgOvernightHrv: metrics.get("hrv_overnight") ?? null,
208
+ hrvStatus: null,
209
+ },
210
+ restingHeartRate: metrics.get("resting_hr") ?? null,
211
+ baselineRestingHeartRate: null,
212
+ storedThrough: iso,
213
+ };
214
+ }
215
+ }
216
+ return null;
217
+ }
218
+ async function fetchRecoverySignals() {
219
+ const today = new Date();
220
+ const candidates = [getYesterday(), ...getDateRange(3).slice(1)];
221
+ try {
222
+ return await withGarminClient(async (client) => {
223
+ let sleepData = { dailySleepDTO: undefined };
224
+ for (const date of candidates) {
225
+ const candidate = await client.getSleepData(date);
226
+ if (candidate.dailySleepDTO) {
227
+ sleepData = candidate;
228
+ break;
229
+ }
230
+ }
231
+ const heartRate = await client.getHeartRate(today);
232
+ return {
233
+ sleepData,
234
+ restingHeartRate: heartRate.restingHeartRate ?? null,
235
+ baselineRestingHeartRate: heartRate.lastSevenDaysAvgRestingHeartRate ?? null,
236
+ storedThrough: null,
237
+ };
238
+ });
239
+ }
240
+ catch (error) {
241
+ // A recovery score built on an expired session used to be a thrown tool
242
+ // error, which an MCP client reports as having no access to the user's
243
+ // data. The signals are in the store; what changes is the date they
244
+ // describe, and that is something to say rather than a reason to say
245
+ // nothing at all.
246
+ const stored = storedRecoverySignals();
247
+ if (!stored) {
248
+ throw error;
249
+ }
250
+ logger.info({ err: error, storedThrough: stored.storedThrough }, "Recovery signals came from the stored history");
251
+ return stored;
252
+ }
253
+ }
254
+ // SECTION: Tool Handler
255
+ /**
256
+ * The date is passed in rather than read from the clock here, which is what
257
+ * keeps this pure and testable -- the handler below supplies it.
258
+ */
259
+ export function renderRecoveryText(payload) {
260
+ const { recovery } = payload;
261
+ const provenance = payload.storedThrough
262
+ ? [
263
+ `Garmin could not be reached, so this was computed from TrainBud's stored record for ${payload.storedThrough} rather than from today's figures.`,
264
+ "It describes that day. Do not present it as current, and do not report it as an absence of data.",
265
+ "",
266
+ ]
267
+ : [];
268
+ return [
269
+ ...provenance,
270
+ `Recovery score: ${recovery.score}/100 (${recovery.status})`,
271
+ recovery.recommendation,
272
+ "",
273
+ "Component scores:",
274
+ `- HRV: ${recovery.components.hrvScore}`,
275
+ `- Sleep: ${recovery.components.sleepScore ?? "not measured"}`,
276
+ `- Stress: ${recovery.components.stressScore}`,
277
+ `- Resting HR: ${recovery.components.restingHrScore}`,
278
+ "",
279
+ `Date: ${payload.date}`,
280
+ ].join("\n");
281
+ }
282
+ export async function getRecoveryStatus(input) {
283
+ const weights = normalizeWeights({
284
+ hrv: input.hrv_weight,
285
+ sleep: input.sleep_weight,
286
+ stress: input.stress_weight,
287
+ restingHr: input.resting_hr_weight,
288
+ });
289
+ const cacheKey = buildToolCacheKey("get_recovery_status", {
290
+ hrv: weights.hrv,
291
+ sleep: weights.sleep,
292
+ stress: weights.stress,
293
+ restingHr: weights.restingHr,
294
+ });
295
+ const computed = await withCache(cacheKey, appConfig.cacheTtlStats, async () => {
296
+ const signals = await fetchRecoverySignals();
297
+ const sleep = signals.sleepData.dailySleepDTO;
298
+ const components = {
299
+ hrvScore: scoreFromHrv(signals.sleepData.avgOvernightHrv ?? null, signals.sleepData.hrvStatus ?? null),
300
+ sleepScore: scoreFromSleep(sleep?.sleepScores?.overall?.value ?? null, sleep?.sleepTimeSeconds ?? 0),
301
+ stressScore: scoreFromStress(sleep?.avgSleepStress ?? null),
302
+ restingHrScore: scoreFromRestingHr(signals.restingHeartRate, signals.baselineRestingHeartRate),
303
+ };
304
+ return {
305
+ recovery: buildRecoveryStatus(components, weights),
306
+ storedThrough: signals.storedThrough,
307
+ };
308
+ }, {
309
+ isPartial: (value) => isPartialOrStored({ unreachableDays: 0, storedDays: value.storedThrough ? 1 : 0 }),
310
+ });
311
+ const payload = {
312
+ // The day the score describes, which is the stored day when the signals
313
+ // came from the store. Stamping it with today is how a fortnight-old night
314
+ // would have been served as this morning's recovery.
315
+ date: computed.storedThrough ?? formatIsoDate(new Date()),
316
+ recovery: computed.recovery,
317
+ storedThrough: computed.storedThrough,
318
+ };
319
+ return {
320
+ type: "text",
321
+ text: renderRecoveryText(payload),
322
+ data: payload,
323
+ };
324
+ }
325
+ export const recoveryToolDefinitions = [
326
+ {
327
+ name: "get_recovery_status",
328
+ description: "Combines HRV, sleep, stress, and resting heart rate into a recovery score and training recommendation.",
329
+ inputSchema: {
330
+ hrv_weight: {
331
+ type: "number",
332
+ description: "Optional weight for HRV in the recovery score.",
333
+ },
334
+ sleep_weight: {
335
+ type: "number",
336
+ description: "Optional weight for sleep in the recovery score.",
337
+ },
338
+ stress_weight: {
339
+ type: "number",
340
+ description: "Optional weight for stress in the recovery score.",
341
+ },
342
+ resting_hr_weight: {
343
+ type: "number",
344
+ description: "Optional weight for resting heart rate in the recovery score.",
345
+ },
346
+ },
347
+ handler: getRecoveryStatus,
348
+ },
349
+ ];
350
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/tools/recovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { SleepNightSummary, ToolResult } from "../garmin/types.js";
2
+ import type { SleepPayload, StoredProvenance } from "./payloads.js";
3
+ import type { ToolDefinition } from "./types.js";
4
+ export declare function buildSleepPayload(nights: SleepNightSummary[], requestedNights: number, unreachableNights?: number, stored?: StoredProvenance): SleepPayload;
5
+ export declare function renderSleepText(payload: SleepPayload): string;
6
+ export declare function getSleepDataTool(input: {
7
+ nights?: number;
8
+ }): Promise<ToolResult<SleepPayload>>;
9
+ export declare const sleepToolDefinitions: ToolDefinition[];
10
+ //# sourceMappingURL=sleep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.d.ts","sourceRoot":"","sources":["../../src/tools/sleep.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAqEjD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,iBAAiB,EAAE,EAC3B,eAAe,EAAE,MAAM,EACvB,iBAAiB,SAAI,EACrB,MAAM,GAAE,gBAIP,GACA,YAAY,CAoBd;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CA0B7D;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACzB,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC,CAkBnC;AAED,eAAO,MAAM,oBAAoB,EAAE,cAAc,EAYhD,CAAC"}
@@ -0,0 +1,124 @@
1
+ import { appConfig } from "../config.js";
2
+ import { buildToolCacheKey, withCache } from "../garmin/cache.js";
3
+ import { withGarminClient } from "../garmin/client.js";
4
+ import { fetchSleepDay, mapSleepData } from "../garmin/daily.js";
5
+ import { fetchEachDay } from "../garmin/partial.js";
6
+ import { fetchDaysOrStore, isPartialOrStored, storedFetchNote, } from "../history/fallback.js";
7
+ import { formatDuration, getDateRange } from "../utils/helpers.js";
8
+ // SECTION: Sleep Mapping
9
+ async function fetchSleepNights(days) {
10
+ const dates = getDateRange(days);
11
+ return fetchDaysOrStore({
12
+ dates,
13
+ source: "sleep",
14
+ live: () => withGarminClient(async (client) => fetchEachDay(dates, async (date) => (await fetchSleepDay(client, date)).mapped, "sleep")),
15
+ // The same mapper the live path uses, re-run over the archived response. A
16
+ // second mapping here would be a second place for "which field is the sleep
17
+ // score" to be answered, and this project has already shipped a mapper
18
+ // reading a field Connect does not send.
19
+ fromRaw: (date, payload) => mapSleepData(date, payload),
20
+ fromMetrics: (date, metrics) => {
21
+ const totalSleepSeconds = metrics.get("sleep_seconds");
22
+ if (totalSleepSeconds === undefined) {
23
+ return null;
24
+ }
25
+ return {
26
+ date,
27
+ totalSleepSeconds,
28
+ deepSleepSeconds: null,
29
+ lightSleepSeconds: null,
30
+ remSleepSeconds: null,
31
+ awakeCount: null,
32
+ sleepScore: metrics.get("sleep_score") ?? null,
33
+ avgSleepStress: metrics.get("sleep_stress") ?? null,
34
+ avgOvernightHrv: metrics.get("hrv_overnight") ?? null,
35
+ hrvStatus: null,
36
+ };
37
+ },
38
+ dateOf: (night) => night.date,
39
+ });
40
+ }
41
+ function formatSleepNight(night) {
42
+ const stages = night.deepSleepSeconds === null &&
43
+ night.lightSleepSeconds === null &&
44
+ night.remSleepSeconds === null
45
+ ? " Stages: not kept for this night"
46
+ : ` Deep: ${formatDuration(night.deepSleepSeconds)} | Light: ${formatDuration(night.lightSleepSeconds)} | REM: ${formatDuration(night.remSleepSeconds)}`;
47
+ return [
48
+ `${night.date}:`,
49
+ ` Total sleep: ${formatDuration(night.totalSleepSeconds)}`,
50
+ stages,
51
+ ` Score: ${night.sleepScore ?? "n/a"} | Awakenings: ${night.awakeCount ?? "n/a"}`,
52
+ ` Avg sleep stress: ${night.avgSleepStress ?? "n/a"}`,
53
+ ].join("\n");
54
+ }
55
+ // SECTION: Tool Handler
56
+ export function buildSleepPayload(nights, requestedNights, unreachableNights = 0, stored = {
57
+ storedDays: 0,
58
+ storedThrough: null,
59
+ storedWindowMoved: false,
60
+ }) {
61
+ const scored = nights
62
+ .map((night) => night.sleepScore)
63
+ .filter((score) => score !== null);
64
+ const averageScore = scored.length > 0
65
+ ? Math.round(scored.reduce((sum, score) => sum + score, 0) / scored.length)
66
+ : null;
67
+ return {
68
+ requestedNights,
69
+ recordedNights: nights.length,
70
+ unreachableNights,
71
+ averageScore,
72
+ nights,
73
+ storedNights: stored.storedDays,
74
+ storedThrough: stored.storedThrough,
75
+ storedWindowMoved: stored.storedWindowMoved,
76
+ };
77
+ }
78
+ export function renderSleepText(payload) {
79
+ const note = storedFetchNote({
80
+ values: payload.nights,
81
+ unreachableDays: payload.unreachableNights,
82
+ requestedDays: payload.requestedNights,
83
+ storedDays: payload.storedNights,
84
+ storedThrough: payload.storedThrough,
85
+ storedWindowMoved: payload.storedWindowMoved,
86
+ }, "nights");
87
+ if (payload.recordedNights === 0) {
88
+ return note || `No sleep data found for the last ${payload.requestedNights} nights.`;
89
+ }
90
+ return [
91
+ note,
92
+ `Sleep summary for last ${payload.recordedNights} recorded nights:`,
93
+ payload.averageScore !== null ? `Average sleep score: ${payload.averageScore}` : "",
94
+ "",
95
+ ...payload.nights.map(formatSleepNight),
96
+ ]
97
+ .filter(Boolean)
98
+ .join("\n");
99
+ }
100
+ export async function getSleepDataTool(input) {
101
+ const nights = input.nights ?? 7;
102
+ const cacheKey = buildToolCacheKey("get_sleep_data", { nights });
103
+ const fetched = await withCache(cacheKey, appConfig.cacheTtlSleep, async () => fetchSleepNights(nights), { isPartial: isPartialOrStored });
104
+ const payload = buildSleepPayload(fetched.values, nights, fetched.unreachableDays, fetched);
105
+ return {
106
+ type: "text",
107
+ text: renderSleepText(payload),
108
+ data: payload,
109
+ };
110
+ }
111
+ export const sleepToolDefinitions = [
112
+ {
113
+ name: "get_sleep_data",
114
+ description: "Returns sleep duration, quality score, stage breakdown, and interruptions for recent nights.",
115
+ inputSchema: {
116
+ nights: {
117
+ type: "number",
118
+ description: "Number of recent nights to include. Defaults to 7.",
119
+ },
120
+ },
121
+ handler: getSleepDataTool,
122
+ },
123
+ ];
124
+ //# sourceMappingURL=sleep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/tools/sleep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { type DailyStressSummary } from "../garmin/rawApi.js";
2
+ import type { ToolResult } from "../garmin/types.js";
3
+ import type { StressPayload, StoredProvenance } from "./payloads.js";
4
+ import type { ToolDefinition } from "./types.js";
5
+ export declare function buildStressPayload(days: DailyStressSummary[], requestedDays: number, unreachableDays?: number, stored?: StoredProvenance): StressPayload;
6
+ export declare function renderStressText(payload: StressPayload): string;
7
+ export declare function getStressLevels(input: {
8
+ days?: number;
9
+ }): Promise<ToolResult<StressPayload>>;
10
+ export declare const stressToolDefinitions: ToolDefinition[];
11
+ //# sourceMappingURL=stress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stress.d.ts","sourceRoot":"","sources":["../../src/tools/stress.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACrE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAwCjD,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,kBAAkB,EAAE,EAC1B,aAAa,EAAE,MAAM,EACrB,eAAe,SAAI,EACnB,MAAM,GAAE,gBAIP,GACA,aAAa,CAgBf;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,GAAG,MAAM,CAqC/D;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACvB,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC,CAkBpC;AAED,eAAO,MAAM,qBAAqB,EAAE,cAAc,EAYjD,CAAC"}
@@ -0,0 +1,108 @@
1
+ import { appConfig } from "../config.js";
2
+ import { buildToolCacheKey, withCache } from "../garmin/cache.js";
3
+ import { withGarminClient } from "../garmin/client.js";
4
+ import { fetchStressDay } from "../garmin/daily.js";
5
+ import { mapDailyStress } from "../garmin/rawApi.js";
6
+ import { fetchEachDay } from "../garmin/partial.js";
7
+ import { fetchDaysOrStore, isPartialOrStored, storedFetchNote, } from "../history/fallback.js";
8
+ import { average, calculateTrend, getDateRange } from "../utils/helpers.js";
9
+ async function fetchStressDays(days) {
10
+ const dates = getDateRange(days);
11
+ return fetchDaysOrStore({
12
+ dates,
13
+ source: "stress",
14
+ live: () => withGarminClient(async (client) => fetchEachDay(dates, async (date) => (await fetchStressDay(client, date)).mapped, "stress")),
15
+ fromRaw: (date, payload) => mapDailyStress(date, payload),
16
+ fromMetrics: (date, metrics) => {
17
+ const averageStress = metrics.get("stress_avg") ?? null;
18
+ const maxStress = metrics.get("stress_max") ?? null;
19
+ if (averageStress === null && maxStress === null) {
20
+ return null;
21
+ }
22
+ return {
23
+ date,
24
+ averageStress,
25
+ maxStress,
26
+ restStress: null,
27
+ stressDurationSeconds: null,
28
+ };
29
+ },
30
+ dateOf: (day) => day.date,
31
+ });
32
+ }
33
+ export function buildStressPayload(days, requestedDays, unreachableDays = 0, stored = {
34
+ storedDays: 0,
35
+ storedThrough: null,
36
+ storedWindowMoved: false,
37
+ }) {
38
+ const averages = days
39
+ .map((day) => day.averageStress)
40
+ .filter((value) => value !== null);
41
+ return {
42
+ requestedDays,
43
+ recordedDays: days.length,
44
+ unreachableDays,
45
+ averageStress: averages.length > 0 ? Math.round(average(averages)) : null,
46
+ trend: calculateTrend(averages, true),
47
+ days,
48
+ storedDays: stored.storedDays,
49
+ storedThrough: stored.storedThrough,
50
+ storedWindowMoved: stored.storedWindowMoved,
51
+ };
52
+ }
53
+ export function renderStressText(payload) {
54
+ const note = storedFetchNote({
55
+ values: payload.days,
56
+ unreachableDays: payload.unreachableDays,
57
+ requestedDays: payload.requestedDays,
58
+ storedDays: payload.storedDays,
59
+ storedThrough: payload.storedThrough,
60
+ storedWindowMoved: payload.storedWindowMoved,
61
+ }, "days");
62
+ if (payload.recordedDays === 0) {
63
+ return note || `No stress data found for the last ${payload.requestedDays} days.`;
64
+ }
65
+ // The trend line is conditional on more than one *measured* day, not on more
66
+ // than one recorded day: Connect returns -1 and -2 as sentinels for days the
67
+ // watch was not worn, and those are mapped to null rather than averaged in.
68
+ const measuredDays = payload.days.filter((day) => day.averageStress !== null).length;
69
+ const lines = payload.days.slice(0, 7).map((day) => {
70
+ return `${day.date}: avg ${day.averageStress ?? "n/a"}, max ${day.maxStress ?? "n/a"}`;
71
+ });
72
+ return [
73
+ note,
74
+ `Stress levels over ${payload.recordedDays} recorded days:`,
75
+ payload.averageStress !== null ? `Average stress: ${payload.averageStress}` : "",
76
+ measuredDays > 1 ? `Trend: ${payload.trend}` : "",
77
+ "",
78
+ "Recent days:",
79
+ ...lines,
80
+ ]
81
+ .filter(Boolean)
82
+ .join("\n");
83
+ }
84
+ export async function getStressLevels(input) {
85
+ const days = input.days ?? 7;
86
+ const cacheKey = buildToolCacheKey("get_stress_levels", { days });
87
+ const fetched = await withCache(cacheKey, appConfig.cacheTtlStats, async () => fetchStressDays(days), { isPartial: isPartialOrStored });
88
+ const payload = buildStressPayload(fetched.values, days, fetched.unreachableDays, fetched);
89
+ return {
90
+ type: "text",
91
+ text: renderStressText(payload),
92
+ data: payload,
93
+ };
94
+ }
95
+ export const stressToolDefinitions = [
96
+ {
97
+ name: "get_stress_levels",
98
+ description: "Returns daily stress averages and trends from Garmin Connect.",
99
+ inputSchema: {
100
+ days: {
101
+ type: "number",
102
+ description: "Number of days to analyze. Defaults to 7.",
103
+ },
104
+ },
105
+ handler: getStressLevels,
106
+ },
107
+ ];
108
+ //# sourceMappingURL=stress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stress.js","sourceRoot":"","sources":["../../src/tools/stress.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { ToolResult } from "../garmin/types.js";
2
+ import type { TrainingInsightsPayload } from "./payloads.js";
3
+ import type { ToolDefinition } from "./types.js";
4
+ /**
5
+ * The sub-tools' rendered text is passed in rather than re-derived here. This
6
+ * tool embeds their output verbatim, so calling three renderers from a fourth
7
+ * would duplicate the composition the handler already does -- and would make
8
+ * this function impure for no gain.
9
+ */
10
+ export declare function renderTrainingInsightsText(payload: TrainingInsightsPayload, sleepText: string, recoveryText: string, stressText: string): string;
11
+ export declare function getTrainingInsights(input: {
12
+ days?: number;
13
+ }): Promise<ToolResult<TrainingInsightsPayload>>;
14
+ export declare const trainingInsightsToolDefinitions: ToolDefinition[];
15
+ //# sourceMappingURL=trainingInsights.d.ts.map