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,182 @@
1
+ import { appConfig } from "../config.js";
2
+ import { buildToolCacheKey, withCache } from "../garmin/cache.js";
3
+ import { withGarminClient } from "../garmin/client.js";
4
+ import { mapActivity } from "../garmin/daily.js";
5
+ import { storedActivitiesBetween } from "../history/fallback.js";
6
+ import { logger } from "../utils/logger.js";
7
+ import { DateTime } from "luxon";
8
+ import { filterActivitiesByRange, formatDistanceMeters, formatDuration, formatIsoDate, formatPaceMetersPerSecond, parseActivityLocalDateTime, } from "../utils/helpers.js";
9
+ import { getProfile } from "../profile.js";
10
+ const ACTIVITIES_PAGE_SIZE = 100;
11
+ const MAX_ACTIVITIES_FETCH = 500;
12
+ // SECTION: Activity Mapping
13
+ export function formatActivitySummary(activity, units = "metric") {
14
+ return [
15
+ `Activity: ${activity.name}`,
16
+ `Type: ${activity.type}`,
17
+ `Date: ${activity.startTimeLocal}`,
18
+ `Distance: ${formatDistanceMeters(activity.distanceMeters, units)}`,
19
+ `Duration: ${formatDuration(activity.durationSeconds)}`,
20
+ `Pace: ${formatPaceMetersPerSecond(activity.averageSpeedMps, units)}`,
21
+ `Avg HR: ${activity.averageHeartRate ?? "n/a"} bpm`,
22
+ `Max HR: ${activity.maxHeartRate ?? "n/a"} bpm`,
23
+ `Elevation gain: ${activity.elevationGainMeters === null ? "n/a" : `${activity.elevationGainMeters.toFixed(0)} m`}`,
24
+ `Calories: ${activity.calories ?? "n/a"}`,
25
+ ].join("\n");
26
+ }
27
+ async function fetchActivitiesPage(start, limit) {
28
+ return withGarminClient(async (client) => {
29
+ const activities = await client.getActivities(start, limit);
30
+ return activities.map(mapActivity);
31
+ });
32
+ }
33
+ async function fetchActivitiesPool() {
34
+ const all = [];
35
+ let start = 0;
36
+ while (all.length < MAX_ACTIVITIES_FETCH) {
37
+ const page = await fetchActivitiesPage(start, ACTIVITIES_PAGE_SIZE);
38
+ if (page.length === 0) {
39
+ break;
40
+ }
41
+ all.push(...page);
42
+ if (page.length < ACTIVITIES_PAGE_SIZE) {
43
+ break;
44
+ }
45
+ start += ACTIVITIES_PAGE_SIZE;
46
+ }
47
+ return {
48
+ activities: all,
49
+ truncated: all.length >= MAX_ACTIVITIES_FETCH,
50
+ };
51
+ }
52
+ /**
53
+ * How far back the stored activity table is read when Connect will not answer.
54
+ * Two years covers every window any tool here asks for; the table is small
55
+ * enough that the bound is about intent rather than cost.
56
+ */
57
+ const STORED_POOL_DAYS = 730;
58
+ /**
59
+ * Activities are a paged list upstream rather than a per-date fetch, so they do
60
+ * not go through `fetchDaysOrStore`. The rule is the same one: a failed request
61
+ * is not an absence of training. `get_latest_activity` used to answer "No
62
+ * activities found in your Garmin Connect account" whenever the session had
63
+ * expired, which is a statement about the user's life made out of a login
64
+ * error, with 23 activities sitting in the store.
65
+ */
66
+ async function fetchActivityPool() {
67
+ try {
68
+ const live = await fetchActivitiesPool();
69
+ return { ...live, fromStore: false };
70
+ }
71
+ catch (error) {
72
+ logger.info({ err: error }, "Activity fetch failed; answering from the stored history");
73
+ const today = DateTime.local().startOf("day");
74
+ const activities = storedActivitiesBetween(today.minus({ days: STORED_POOL_DAYS }).toISODate() ?? "", today.toISODate() ?? "");
75
+ return { activities, truncated: false, fromStore: true };
76
+ }
77
+ }
78
+ export async function getActivitiesPool() {
79
+ const cacheKey = buildToolCacheKey("activities_pool", {});
80
+ return withCache(cacheKey, appConfig.cacheTtlActivities, fetchActivityPool, {
81
+ // A stored pool is cached briefly so a restored connection is picked up,
82
+ // rather than being frozen in for the full activities TTL.
83
+ isPartial: (pool) => pool.fromStore,
84
+ });
85
+ }
86
+ // SECTION: Tool Handlers
87
+ export function renderLatestActivityText(payload, units = "metric") {
88
+ if (!payload.activity) {
89
+ return payload.fromStore
90
+ ? "Garmin could not be reached just now, and TrainBud's stored history holds no activities either."
91
+ : "No activities found in your Garmin Connect account.";
92
+ }
93
+ const note = payload.fromStore
94
+ ? [
95
+ "Garmin could not be reached just now, so this comes from TrainBud's own stored history.",
96
+ "It is a real activity previously fetched from Connect; anything recorded since is not here yet.",
97
+ "",
98
+ "",
99
+ ].join("\n")
100
+ : "";
101
+ return note + formatActivitySummary(payload.activity, units);
102
+ }
103
+ export async function getLatestActivity() {
104
+ const cacheKey = buildToolCacheKey("get_latest_activity", {});
105
+ const latest = await withCache(cacheKey, appConfig.cacheTtlActivities, async () => {
106
+ const { activities, fromStore } = await getActivitiesPool();
107
+ return { activity: activities[0] ?? null, fromStore };
108
+ }, { isPartial: (value) => value.fromStore });
109
+ const payload = latest;
110
+ return {
111
+ type: "text",
112
+ text: renderLatestActivityText(payload, getProfile().units),
113
+ data: payload,
114
+ };
115
+ }
116
+ export function buildActivitiesRangePayload(activities, startDate, endDate, truncated, fromStore = false) {
117
+ return { startDate, endDate, truncated, activities, fromStore };
118
+ }
119
+ export function renderActivitiesRangeText(payload, units = "metric") {
120
+ const storedNote = payload.fromStore
121
+ ? [
122
+ "Garmin could not be reached just now, so these come from TrainBud's own stored history rather than from Connect.",
123
+ "They are real activities previously fetched; anything recorded since is not here yet.",
124
+ "",
125
+ "",
126
+ ].join("\n")
127
+ : "";
128
+ if (payload.activities.length === 0) {
129
+ return payload.fromStore
130
+ ? `Garmin could not be reached, and TrainBud's stored history holds no activities between ${payload.startDate} and ${payload.endDate}.`
131
+ : `No activities found between ${payload.startDate} and ${payload.endDate}.`;
132
+ }
133
+ const lines = payload.activities.map((activity, index) => {
134
+ const activityDate = parseActivityLocalDateTime(activity.startTimeLocal).toISODate() ??
135
+ formatIsoDate(new Date(activity.startTimeLocal));
136
+ return [
137
+ `${index + 1}. ${activity.name} (${activity.type})`,
138
+ ` ${activityDate} | ${formatDistanceMeters(activity.distanceMeters, units)} | ${formatDuration(activity.durationSeconds)}`,
139
+ ].join("\n");
140
+ });
141
+ const warning = payload.truncated
142
+ ? "\n\nNote: Results may be incomplete — only the most recent 500 activities were scanned."
143
+ : "";
144
+ return (storedNote +
145
+ [`Found ${payload.activities.length} activities:`, "", ...lines].join("\n") +
146
+ warning);
147
+ }
148
+ export async function getActivitiesRange(input) {
149
+ const start_date = input.start_date;
150
+ const end_date = input.end_date;
151
+ const { activities: pool, truncated, fromStore } = await getActivitiesPool();
152
+ const payload = buildActivitiesRangePayload(filterActivitiesByRange(pool, start_date, end_date), start_date, end_date, truncated, fromStore);
153
+ return {
154
+ type: "text",
155
+ text: renderActivitiesRangeText(payload, getProfile().units),
156
+ data: payload,
157
+ };
158
+ }
159
+ export const activityToolDefinitions = [
160
+ {
161
+ name: "get_latest_activity",
162
+ description: "Returns the most recent Garmin activity with distance, duration, pace, and heart rate stats.",
163
+ inputSchema: {},
164
+ handler: getLatestActivity,
165
+ },
166
+ {
167
+ name: "get_activities_range",
168
+ description: "Returns Garmin activities within an ISO 8601 date range.",
169
+ inputSchema: {
170
+ start_date: {
171
+ type: "string",
172
+ description: "Start date in ISO 8601 format, e.g. 2026-06-01",
173
+ },
174
+ end_date: {
175
+ type: "string",
176
+ description: "End date in ISO 8601 format, e.g. 2026-06-07",
177
+ },
178
+ },
179
+ handler: getActivitiesRange,
180
+ },
181
+ ];
182
+ //# sourceMappingURL=activities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activities.js","sourceRoot":"","sources":["../../src/tools/activities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { BodyCompositionEntry, ToolResult } from "../garmin/types.js";
2
+ import type { BodyCompositionPayload, StoredProvenance } from "./payloads.js";
3
+ import type { ToolDefinition } from "./types.js";
4
+ export declare function buildBodyCompositionPayload(entries: BodyCompositionEntry[], requestedDays: number, stored?: StoredProvenance): BodyCompositionPayload;
5
+ export declare function renderBodyCompositionText(payload: BodyCompositionPayload): string;
6
+ export declare function getBodyComposition(input: {
7
+ days?: number;
8
+ }): Promise<ToolResult<BodyCompositionPayload>>;
9
+ export declare const bodyCompositionToolDefinitions: ToolDefinition[];
10
+ //# sourceMappingURL=bodyComposition.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bodyComposition.d.ts","sourceRoot":"","sources":["../../src/tools/bodyComposition.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC3E,OAAO,KAAK,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAoEjD,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,gBAIP,GACA,sBAAsB,CAwBxB;AAED,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,sBAAsB,GAAG,MAAM,CAuCjF;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACvB,OAAO,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAkB7C;AAED,eAAO,MAAM,8BAA8B,EAAE,cAAc,EAY1D,CAAC"}
@@ -0,0 +1,139 @@
1
+ import { appConfig } from "../config.js";
2
+ import { buildToolCacheKey, withCache } from "../garmin/cache.js";
3
+ import { withGarminClient } from "../garmin/client.js";
4
+ import { fetchBodyCompositionDay, mapWeightData } from "../garmin/daily.js";
5
+ import { mapInBatches } from "../utils/batch.js";
6
+ import { fetchDaysOrStore, isPartialOrStored, storedFetchNote } from "../history/fallback.js";
7
+ import { calculateTrend, getDateRange } from "../utils/helpers.js";
8
+ // SECTION: Body Composition Mapping
9
+ /**
10
+ * A weight response covers a whole day and may carry several readings, so this
11
+ * flattens to one entry per date rather than one per request -- but it still
12
+ * goes through the same fallback as every other metric, because a scale reading
13
+ * that was fetched last week is still the user's weight when Connect will not
14
+ * answer today.
15
+ */
16
+ async function fetchBodyComposition(days) {
17
+ const dates = getDateRange(days);
18
+ const result = await fetchDaysOrStore({
19
+ dates,
20
+ source: "body_composition",
21
+ live: async () => {
22
+ const batches = await withGarminClient(async (client) => mapInBatches(dates, async (date) => (await fetchBodyCompositionDay(client, date)).mapped ?? []));
23
+ return { values: batches.flat(), unreachableDays: 0, requestedDays: dates.length };
24
+ },
25
+ fromRaw: (date, payload) => mapWeightData(date, payload)[0] ?? null,
26
+ fromMetrics: (date, metrics) => {
27
+ const weightKg = metrics.get("weight_kg") ?? null;
28
+ const bodyFatPercent = metrics.get("body_fat_pct") ?? null;
29
+ const muscleMassKg = metrics.get("muscle_mass_kg") ?? null;
30
+ if (weightKg === null && bodyFatPercent === null && muscleMassKg === null) {
31
+ return null;
32
+ }
33
+ return { date, weightKg, bodyFatPercent, muscleMassKg, bmi: null };
34
+ },
35
+ dateOf: (entry) => entry.date,
36
+ });
37
+ return {
38
+ entries: result.values,
39
+ storedDays: result.storedDays,
40
+ storedThrough: result.storedThrough,
41
+ storedWindowMoved: result.storedWindowMoved,
42
+ unreachableDays: result.unreachableDays,
43
+ };
44
+ }
45
+ // SECTION: Tool Handler
46
+ function delta(current, baseline) {
47
+ if (current === null || current === undefined) {
48
+ return null;
49
+ }
50
+ if (baseline === null || baseline === undefined) {
51
+ return null;
52
+ }
53
+ return current - baseline;
54
+ }
55
+ export function buildBodyCompositionPayload(entries, requestedDays, stored = {
56
+ storedDays: 0,
57
+ storedThrough: null,
58
+ storedWindowMoved: false,
59
+ }) {
60
+ // Entries are sorted newest first, so the last one in the range is the
61
+ // baseline the deltas are measured against.
62
+ const current = entries[0] ?? null;
63
+ const baseline = entries[entries.length - 1] ?? null;
64
+ const measured = (select) => entries.map(select).filter((value) => value !== null);
65
+ return {
66
+ requestedDays,
67
+ recordedDays: entries.length,
68
+ current,
69
+ baseline,
70
+ weightDeltaKg: delta(current?.weightKg, baseline?.weightKg),
71
+ bodyFatDeltaPercent: delta(current?.bodyFatPercent, baseline?.bodyFatPercent),
72
+ weightTrend: calculateTrend(measured((entry) => entry.weightKg), true),
73
+ bodyFatTrend: calculateTrend(measured((entry) => entry.bodyFatPercent), true),
74
+ muscleTrend: calculateTrend(measured((entry) => entry.muscleMassKg), false),
75
+ entries,
76
+ storedDays: stored.storedDays,
77
+ storedThrough: stored.storedThrough,
78
+ storedWindowMoved: stored.storedWindowMoved,
79
+ };
80
+ }
81
+ export function renderBodyCompositionText(payload) {
82
+ const note = storedFetchNote({
83
+ values: payload.entries,
84
+ unreachableDays: 0,
85
+ requestedDays: payload.requestedDays,
86
+ storedDays: payload.storedDays,
87
+ storedThrough: payload.storedThrough,
88
+ storedWindowMoved: payload.storedWindowMoved,
89
+ }, "days");
90
+ if (payload.recordedDays === 0) {
91
+ return note || `No body composition data found for the last ${payload.requestedDays} days.`;
92
+ }
93
+ const lines = payload.entries.slice(0, 10).map((entry) => {
94
+ return `${entry.date}: ${entry.weightKg?.toFixed(1) ?? "n/a"} kg | body fat ${entry.bodyFatPercent?.toFixed(1) ?? "n/a"}% | muscle ${entry.muscleMassKg?.toFixed(1) ?? "n/a"} kg`;
95
+ });
96
+ // Prepended rather than filtered into the list: the blank line before
97
+ // "Recent entries:" is deliberate, so a `.filter(Boolean)` here would drop the
98
+ // separator along with the empty note.
99
+ return [
100
+ ...(note ? [note, ""] : []),
101
+ `Body composition over ${payload.recordedDays} recorded days:`,
102
+ `Current weight: ${payload.current?.weightKg?.toFixed(1) ?? "n/a"} kg`,
103
+ `Current body fat: ${payload.current?.bodyFatPercent?.toFixed(1) ?? "n/a"}%`,
104
+ `Current muscle mass: ${payload.current?.muscleMassKg?.toFixed(1) ?? "n/a"} kg`,
105
+ `Weight change from baseline: ${payload.weightDeltaKg !== null ? `${payload.weightDeltaKg.toFixed(1)} kg` : "n/a"}`,
106
+ `Body fat change from baseline: ${payload.bodyFatDeltaPercent !== null ? `${payload.bodyFatDeltaPercent.toFixed(1)}%` : "n/a"}`,
107
+ `Weight trend: ${payload.weightTrend}`,
108
+ `Body fat trend: ${payload.bodyFatTrend}`,
109
+ `Muscle trend: ${payload.muscleTrend}`,
110
+ "",
111
+ "Recent entries:",
112
+ ...lines,
113
+ ].join("\n");
114
+ }
115
+ export async function getBodyComposition(input) {
116
+ const days = input.days ?? 30;
117
+ const cacheKey = buildToolCacheKey("get_body_composition", { days });
118
+ const fetched = await withCache(cacheKey, appConfig.cacheTtlStats, async () => fetchBodyComposition(days), { isPartial: isPartialOrStored });
119
+ const payload = buildBodyCompositionPayload(fetched.entries, days, fetched);
120
+ return {
121
+ type: "text",
122
+ text: renderBodyCompositionText(payload),
123
+ data: payload,
124
+ };
125
+ }
126
+ export const bodyCompositionToolDefinitions = [
127
+ {
128
+ name: "get_body_composition",
129
+ description: "Returns weight, body fat, and muscle mass trends over a time period.",
130
+ inputSchema: {
131
+ days: {
132
+ type: "number",
133
+ description: "Number of days to analyze. Defaults to 30.",
134
+ },
135
+ },
136
+ handler: getBodyComposition,
137
+ },
138
+ ];
139
+ //# sourceMappingURL=bodyComposition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bodyComposition.js","sourceRoot":"","sources":["../../src/tools/bodyComposition.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { ToolResult } from "../garmin/types.js";
2
+ import type { ContextListPayload, RememberContextPayload, SubjectivePayload } from "./payloads.js";
3
+ import type { ToolDefinition } from "./types.js";
4
+ export declare function rememberContext(input: Record<string, unknown>): Promise<ToolResult<RememberContextPayload>>;
5
+ export declare function getUserContext(input: Record<string, unknown>): Promise<ToolResult<ContextListPayload>>;
6
+ export declare function logSubjectiveEntry(input: Record<string, unknown>): Promise<ToolResult<SubjectivePayload>>;
7
+ export declare const contextToolDefinitions: ToolDefinition[];
8
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EACV,kBAAkB,EAClB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAkCjD,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,UAAU,CAAC,sBAAsB,CAAC,CAAC,CAgB7C;AAED,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAyBzC;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAyBxC;AAED,eAAO,MAAM,sBAAsB,EAAE,cAAc,EAiElD,CAAC"}
@@ -0,0 +1,148 @@
1
+ import { DateTime } from "luxon";
2
+ import { activeContext, addContextEntry, allContext, logSubjective, subjectiveSeries, } from "../history/context.js";
3
+ import { CONTEXT_KINDS, SUBJECTIVE_KINDS, } from "../history/schema.js";
4
+ // SECTION: Context tools
5
+ //
6
+ // The richest input a user will ever give is a sentence they were already
7
+ // typing. "Half marathon on October 12, left achilles grumbling since Tuesday"
8
+ // becomes two records with no UI at all, which is why these exist before any
9
+ // form does.
10
+ function parseKind(value, valid, label) {
11
+ if (typeof value === "string" && valid.includes(value)) {
12
+ return value;
13
+ }
14
+ throw new Error(`Unknown ${label} "${String(value)}". Choose one of: ${valid.join(", ")}.`);
15
+ }
16
+ function optionalDate(value, label) {
17
+ if (value === undefined || value === null || value === "") {
18
+ return undefined;
19
+ }
20
+ if (typeof value !== "string" || !DateTime.fromISO(value).isValid) {
21
+ throw new Error(`${label} must be an ISO date such as 2026-10-12.`);
22
+ }
23
+ return value;
24
+ }
25
+ function describeEntry(entry) {
26
+ const until = entry.effectiveTo ? ` until ${entry.effectiveTo}` : "";
27
+ return `${entry.kind}: ${entry.text} (from ${entry.effectiveFrom}${until})`;
28
+ }
29
+ export async function rememberContext(input) {
30
+ const kind = parseKind(input.kind, CONTEXT_KINDS, "context kind");
31
+ const text = typeof input.text === "string" ? input.text : "";
32
+ const entry = addContextEntry(kind, text, {
33
+ effectiveFrom: optionalDate(input.effective_from, "effective_from"),
34
+ effectiveTo: optionalDate(input.effective_to, "effective_to"),
35
+ });
36
+ const payload = { entry };
37
+ return {
38
+ type: "text",
39
+ text: `Recorded ${describeEntry(entry)}`,
40
+ data: payload,
41
+ };
42
+ }
43
+ export async function getUserContext(input) {
44
+ const includeClosed = input.include_closed === true;
45
+ const onDate = optionalDate(input.on_date, "on_date") ?? DateTime.local().toISODate() ?? "";
46
+ const entries = includeClosed ? allContext() : activeContext(onDate);
47
+ const payload = { onDate, includeClosed, entries };
48
+ if (entries.length === 0) {
49
+ return {
50
+ type: "text",
51
+ text: includeClosed
52
+ ? "Nothing has been recorded yet. Use remember_context to add a goal, race, injury or note."
53
+ : `Nothing on record as of ${onDate}. Use remember_context to add a goal, race, injury or note.`,
54
+ data: payload,
55
+ };
56
+ }
57
+ return {
58
+ type: "text",
59
+ text: [
60
+ includeClosed ? "Everything on record:" : `On record as of ${onDate}:`,
61
+ ...entries.map((entry) => `- ${describeEntry(entry)}`),
62
+ ].join("\n"),
63
+ data: payload,
64
+ };
65
+ }
66
+ export async function logSubjectiveEntry(input) {
67
+ const kind = parseKind(input.kind, SUBJECTIVE_KINDS, "rating kind");
68
+ const value = typeof input.value === "number" ? input.value : Number.NaN;
69
+ const date = optionalDate(input.date, "date") ?? DateTime.local().toISODate() ?? "";
70
+ const note = typeof input.note === "string" ? input.note : undefined;
71
+ logSubjective(date, kind, value, note);
72
+ const payload = {
73
+ date,
74
+ kind,
75
+ value,
76
+ note: note?.trim() || null,
77
+ recent: subjectiveSeries(kind, DateTime.fromISO(date).minus({ days: 30 }).toISODate() ?? date, date),
78
+ };
79
+ return {
80
+ type: "text",
81
+ text: `Recorded ${kind} ${value}/10 for ${date}${note ? ` — ${note.trim()}` : ""}`,
82
+ data: payload,
83
+ };
84
+ }
85
+ export const contextToolDefinitions = [
86
+ {
87
+ name: "remember_context",
88
+ description: "Records something about the user that Garmin does not know: a goal, a race and its date, an injury, or a free-form note. Use this whenever the user mentions one in passing.",
89
+ inputSchema: {
90
+ kind: {
91
+ type: "string",
92
+ description: `One of: ${CONTEXT_KINDS.join(", ")}.`,
93
+ },
94
+ text: {
95
+ type: "string",
96
+ description: "What to remember, in the user's own words where possible.",
97
+ },
98
+ effective_from: {
99
+ type: "string",
100
+ description: "ISO date this became true. Defaults to today.",
101
+ },
102
+ effective_to: {
103
+ type: "string",
104
+ description: "ISO date this stops being true, for a race date or a healed injury.",
105
+ },
106
+ },
107
+ handler: rememberContext,
108
+ },
109
+ {
110
+ name: "get_user_context",
111
+ description: "Returns the goals, races, injuries and notes on record for the user. Read this before giving training advice.",
112
+ inputSchema: {
113
+ on_date: {
114
+ type: "string",
115
+ description: "ISO date to evaluate against. Defaults to today.",
116
+ },
117
+ include_closed: {
118
+ type: "boolean",
119
+ description: "Include entries that have since ended. Defaults to false.",
120
+ },
121
+ },
122
+ handler: getUserContext,
123
+ },
124
+ {
125
+ name: "log_subjective",
126
+ description: "Records how a day or session actually felt, rated 1-10: perceived effort (rpe), soreness, or mood. Garmin has no equivalent.",
127
+ inputSchema: {
128
+ kind: {
129
+ type: "string",
130
+ description: `One of: ${SUBJECTIVE_KINDS.join(", ")}.`,
131
+ },
132
+ value: {
133
+ type: "number",
134
+ description: "A rating from 1 to 10.",
135
+ },
136
+ date: {
137
+ type: "string",
138
+ description: "ISO date the rating is for. Defaults to today.",
139
+ },
140
+ note: {
141
+ type: "string",
142
+ description: "Optional free-text detail.",
143
+ },
144
+ },
145
+ handler: logSubjectiveEntry,
146
+ },
147
+ ];
148
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { ToolResult } from "../garmin/types.js";
2
+ import type { FindingsPayload } from "./payloads.js";
3
+ import type { ToolDefinition } from "./types.js";
4
+ export declare function renderFindingsText(payload: FindingsPayload): string;
5
+ export declare function getFindings(): Promise<ToolResult<FindingsPayload>>;
6
+ export declare const findingsToolDefinitions: ToolDefinition[];
7
+ //# sourceMappingURL=findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../../src/tools/findings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAYjD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAuBnE;AAED,wBAAsB,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC,CAYxE;AAED,eAAO,MAAM,uBAAuB,EAAE,cAAc,EAQnD,CAAC"}
@@ -0,0 +1,51 @@
1
+ import { describeFindingsCoverage, runDetectors } from "../detect/index.js";
2
+ // SECTION: Findings tool
3
+ //
4
+ // The one tool that answers a question Connect cannot: not "what is my resting
5
+ // heart rate" but "is anything unusual about it, for me".
6
+ //
7
+ // It is also the only tool that reads the local store rather than Garmin, which
8
+ // makes it the one thing an AI client can still learn about the user when the
9
+ // connection is down -- so what it says when `ready` is false matters more here
10
+ // than anywhere else in the app.
11
+ export function renderFindingsText(payload) {
12
+ // Cold start is a different answer from a clean bill of health, and a record
13
+ // that stops three weeks ago is a third thing again: it has plenty to say and
14
+ // none of it is about this week. Reporting any of the three as another is the
15
+ // failure that matters here.
16
+ if (!payload.coverage.ready) {
17
+ const coverage = describeFindingsCoverage(payload.coverage);
18
+ return [coverage.detail, coverage.fix].filter(Boolean).join("\n");
19
+ }
20
+ if (payload.findings.length === 0) {
21
+ return `Nothing stands out against the user's own baselines, across ${payload.coverage.days} days of history.`;
22
+ }
23
+ return [
24
+ `${payload.findings.length} finding(s) across ${payload.coverage.days} days of history:`,
25
+ "",
26
+ ...payload.findings.map((finding) => [`[${finding.severity}] ${finding.headline}`, ` ${finding.detail}`].join("\n")),
27
+ "",
28
+ "These describe measurements against the user's own baseline. They are not diagnoses.",
29
+ ].join("\n");
30
+ }
31
+ export async function getFindings() {
32
+ const result = runDetectors();
33
+ const payload = {
34
+ findings: result.findings,
35
+ coverage: result.coverage,
36
+ };
37
+ return {
38
+ type: "text",
39
+ text: renderFindingsText(payload),
40
+ data: payload,
41
+ };
42
+ }
43
+ export const findingsToolDefinitions = [
44
+ {
45
+ name: "get_findings",
46
+ description: "Returns what stands out in the user's stored history against their own 28-day baselines: resting heart rate elevation, sleep debt, HRV trend breaks, and training load ratio. Prefer this over reading raw metrics when asked how things are going.",
47
+ inputSchema: {},
48
+ handler: getFindings,
49
+ },
50
+ ];
51
+ //# sourceMappingURL=findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.js","sourceRoot":"","sources":["../../src/tools/findings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAK5E,yBAAyB;AACzB,EAAE;AACF,+EAA+E;AAC/E,0DAA0D;AAC1D,EAAE;AACF,gFAAgF;AAChF,8EAA8E;AAC9E,gFAAgF;AAChF,iCAAiC;AAEjC,MAAM,UAAU,kBAAkB,CAAC,OAAwB;IACzD,6EAA6E;IAC7E,8EAA8E;IAC9E,8EAA8E;IAC9E,6BAA6B;IAC7B,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,wBAAwB,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC5D,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAClC,OAAO,+DAA+D,OAAO,CAAC,QAAQ,CAAC,IAAI,mBAAmB,CAAC;IACjH,CAAC;IAED,OAAO;QACL,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,sBAAsB,OAAO,CAAC,QAAQ,CAAC,IAAI,mBAAmB;QACxF,EAAE;QACF,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAClC,CAAC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,QAAQ,EAAE,EAAE,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAChF;QACD,EAAE;QACF,sFAAsF;KACvF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,OAAO,GAAoB;QAC/B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC;IAEF,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,kBAAkB,CAAC,OAAO,CAAC;QACjC,IAAI,EAAE,OAAO;KACd,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,uBAAuB,GAAqB;IACvD;QACE,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,qPAAqP;QACvP,WAAW,EAAE,EAAE;QACf,OAAO,EAAE,WAAW;KACrB;CACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { HeartRateDaySummary, ToolResult } from "../garmin/types.js";
2
+ import type { HeartRatePayload, StoredProvenance } from "./payloads.js";
3
+ import type { ToolDefinition } from "./types.js";
4
+ export declare function buildHeartRatePayload(days: HeartRateDaySummary[], requestedDays: number, unreachableDays?: number, stored?: StoredProvenance): HeartRatePayload;
5
+ export declare function renderHeartRateText(payload: HeartRatePayload): string;
6
+ export declare function getHeartRateTrends(input: {
7
+ days?: number;
8
+ }): Promise<ToolResult<HeartRatePayload>>;
9
+ export declare const heartRateToolDefinitions: ToolDefinition[];
10
+ //# sourceMappingURL=heartRate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heartRate.d.ts","sourceRoot":"","sources":["../../src/tools/heartRate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AA6CjD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,EAAE,EAC3B,aAAa,EAAE,MAAM,EACrB,eAAe,SAAI,EACnB,MAAM,GAAE,gBAIP,GACA,gBAAgB,CAoBlB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAgCrE;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACvB,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAkBvC;AAED,eAAO,MAAM,wBAAwB,EAAE,cAAc,EAYpD,CAAC"}