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 @@
1
+ {"version":3,"file":"week.js","sourceRoot":"","sources":["../../src/detect/week.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AACvC,OAAO,EAAE,YAAY,EAAqB,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,YAAY,EAAqB,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAI3D,yBAAyB;AACzB,EAAE;AACF,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,uDAAuD;AACvD,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,kDAAkD;AAElD,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB;;;;;;;;;;;GAWG;AACH,MAAM,eAAe,GAAG,SAAS,GAAG,CAAC,GAAG,CAAC,CAAC;AA+B1C,2FAA2F;AAC3F,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAC9B,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAE9B,SAAS,KAAK,CAAC,KAAa,EAAE,MAAM,GAAG,CAAC;IACtC,MAAM,MAAM,GAAG,EAAE,IAAI,MAAM,CAAC;IAC5B,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;AAC7C,CAAC;AAED,SAAS,YAAY,CAAC,KAAoB,EAAE,SAAiB;IAC3D,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,SAAS,EAAE,CAAC;QAChC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CACjB,KAAoB,EACpB,IAAgB;IAEhB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IAClE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;IAEnD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC;aAAM,CAAC;YACN,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,UAAU,CACjB,GAAW,EACX,KAAa,EACb,IAAY,EACZ,OAAiB,EACjB,QAAkB,EAClB,OAAkE;IAElE,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IAEnC,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;IAEvC,MAAM,MAAM,GAAG,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC;IAClF,MAAM,cAAc,GAAG,aAAa,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5F,MAAM,KAAK,GACT,MAAM,KAAK,IAAI,IAAI,cAAc,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,cAAc,EAAE,MAAM,CAAC,CAAC;IAE7F,OAAO;QACL,GAAG;QACH,KAAK;QACL,IAAI;QACJ,OAAO,EAAE,MAAM;QACf,QAAQ,EAAE,cAAc;QACxB,KAAK;QACL,SAAS,EAAE,YAAY,CAAC,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC;QACpD,OAAO,EAAE,KAAK,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,YAAY;KACnE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAoB;IAClD,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,GAAG,CAAC,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IACrE,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC;IAElE,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;IACrD,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,GAAG,MAAM,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC;IAE1E,MAAM,aAAa,GAAG,CAAC,IAAuB,EAAU,EAAE,CACxD,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,QAAQ,CAAC,eAAe,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/E,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAClD,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,EAAE,eAAe,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IAE/C,MAAM,OAAO,GAAiB;QAC5B,UAAU,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,QAAQ,EAAE;YAC/D,KAAK,EAAE,gBAAgB;YACvB,YAAY,EAAE,GAAG;SAClB,CAAC;QACF,UAAU,CAAC,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,QAAQ,EAAE;YACnF,MAAM,EAAE,CAAC;YACT,YAAY,EAAE,CAAC;SAChB,CAAC;QACF,UAAU,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE;YACxD,MAAM,EAAE,CAAC;YACT,YAAY,EAAE,CAAC;SAChB,CAAC;QACF,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,QAAQ,EAAE;YAClE,MAAM,EAAE,CAAC;YACT,YAAY,EAAE,CAAC;SAChB,CAAC;KACH,CAAC;IAEF,2EAA2E;IAC3E,wEAAwE;IACxE,eAAe;IACf,MAAM,OAAO,GAAG,iBAAiB,CAC/B,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,GAAG,CAAC,CAAC,EACzC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,GAAG,CAAC,CAAC,CACtC,CAAC;IAEF,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,KAAK,GAAG,UAAU,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,CAAC,WAAmB,EAAU,EAAE;YAChD,IAAI,KAAK,GAAG,CAAC,CAAC;YACd,KAAK,IAAI,MAAM,GAAG,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,EAAE,MAAM,IAAI,CAAC,EAAE,CAAC;gBAC7E,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,SAAS,EAAE,CAAC;gBACvD,IAAI,IAAI,EAAE,CAAC;oBACT,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAChC,CAAC;YACH,CAAC;YACD,OAAO,KAAK,CAAC;QACf,CAAC,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,MAAM,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;QACtD,MAAM,KAAK,GAAG,WAAW,GAAG,YAAY,CAAC;QAEzC,OAAO,CAAC,OAAO,CAAC;YACd,GAAG,EAAE,MAAM;YACX,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,OAAO;YACb,OAAO,EAAE,WAAW;YACpB,QAAQ,EAAE,YAAY;YACtB,KAAK;YACL,SAAS,EAAE,YAAY,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAAC,CAAC;YACjE,OAAO,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAAC;SAC9D,CAAC,CAAC;IACL,CAAC;IAED,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,sEAAsE;IACtE,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,CAC3B,KAAK,CAAC,MAAM,CAAC,YAAY,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,MAAM,EAChD,KAAK,CAAC,MAAM,CAAC,eAAe,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,MAAM,CACpD,CAAC;IACF,MAAM,KAAK,GAAG,YAAY,IAAI,UAAU,CAAC;IAEzC,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IACrC,MAAM,YAAY,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAEzC,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,CAAC,KAAK;QACrB,CAAC,CAAC,QAAQ,YAAY,sFAAsF;QAC5G,CAAC,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC;YACpB,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,uDAAuD;YACtH,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,WAAW,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,cAAc;gBAC3E,OAAO;qBACJ,GAAG,CACF,CAAC,MAAM,EAAE,EAAE,CACT,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAC9G;qBACA,IAAI,CAAC,IAAI,CAAC;gBACb,oBAAoB,CAAC;IAE3B,OAAO;QACL,KAAK;QACL,GAAG;QACH,KAAK;QACL,QAAQ,EAAE,QAAQ,CAAC,MAAM;QACzB,gBAAgB,EAAE,QAAQ,CAAC,MAAM;QACjC,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,kBAAkB,CAAC;QACvE,qBAAqB,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,QAAQ,CAAC,GAAG,kBAAkB,CAAC;QAC/E,OAAO;QACP,QAAQ;QACR,KAAK,EAAE,YAAY;QACnB,QAAQ;KACT,CAAC;AACJ,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { GarminConnectInstance } from "./garminConnect.js";
2
+ import type { StoredSession } from "./types.js";
3
+ export declare function sessionDirectory(): string;
4
+ export declare function sessionExists(): boolean;
5
+ export declare function readStoredSession(): StoredSession | null;
6
+ export declare function writeStoredSession(session: StoredSession): void;
7
+ export declare function clearStoredSession(): void;
8
+ export declare function createGarminClient(): GarminConnectInstance;
9
+ export declare function authenticateGarmin(force?: boolean): Promise<GarminConnectInstance>;
10
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/garmin/auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAMhE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAgB,iBAAiB,IAAI,aAAa,GAAG,IAAI,CAmBxD;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAG/D;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAKzC;AAID,wBAAgB,kBAAkB,IAAI,qBAAqB,CAM1D;AAED,wBAAsB,kBAAkB,CAAC,KAAK,UAAQ,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAkCtF"}
@@ -0,0 +1,81 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { GarminConnect } from "./garminConnect.js";
4
+ import { appConfig, assertGarminCredentials } from "../config.js";
5
+ import { logger } from "../utils/logger.js";
6
+ import { readJsonFile } from "../utils/jsonFile.js";
7
+ import { writeSecretFile } from "../utils/secretFile.js";
8
+ // SECTION: Session Persistence
9
+ export function sessionDirectory() {
10
+ return path.dirname(appConfig.sessionPath);
11
+ }
12
+ export function sessionExists() {
13
+ return fs.existsSync(appConfig.sessionPath);
14
+ }
15
+ export function readStoredSession() {
16
+ const sessionPath = appConfig.sessionPath;
17
+ if (!fs.existsSync(sessionPath)) {
18
+ return null;
19
+ }
20
+ try {
21
+ const parsed = readJsonFile(sessionPath);
22
+ if (!parsed.oauth1 || !parsed.oauth2) {
23
+ return null;
24
+ }
25
+ return parsed;
26
+ }
27
+ catch (error) {
28
+ logger.warn({ error }, "Failed to read stored Garmin session");
29
+ return null;
30
+ }
31
+ }
32
+ export function writeStoredSession(session) {
33
+ // Live OAuth tokens for the Connect account -- owner-readable only.
34
+ writeSecretFile(appConfig.sessionPath, JSON.stringify(session, null, 2));
35
+ }
36
+ export function clearStoredSession() {
37
+ const sessionPath = appConfig.sessionPath;
38
+ if (fs.existsSync(sessionPath)) {
39
+ fs.unlinkSync(sessionPath);
40
+ }
41
+ }
42
+ // SECTION: Authentication
43
+ export function createGarminClient() {
44
+ assertGarminCredentials();
45
+ return new GarminConnect({
46
+ username: appConfig.garminEmail,
47
+ password: appConfig.garminPassword,
48
+ });
49
+ }
50
+ export async function authenticateGarmin(force = false) {
51
+ const client = createGarminClient();
52
+ if (!force) {
53
+ const storedSession = readStoredSession();
54
+ if (storedSession) {
55
+ client.loadToken(storedSession.oauth1, storedSession.oauth2);
56
+ try {
57
+ await client.getUserProfile();
58
+ logger.info("Restored Garmin session from disk");
59
+ return client;
60
+ }
61
+ catch (error) {
62
+ logger.warn({ error }, "Stored Garmin session expired, re-authenticating");
63
+ }
64
+ }
65
+ }
66
+ else {
67
+ clearStoredSession();
68
+ }
69
+ try {
70
+ await client.login();
71
+ const tokens = client.exportToken();
72
+ writeStoredSession(tokens);
73
+ logger.info("Authenticated with Garmin Connect");
74
+ return client;
75
+ }
76
+ catch (error) {
77
+ const message = error instanceof Error ? error.message : "Unknown authentication error";
78
+ throw new Error(`Garmin authentication failed: ${message}. Verify credentials in .env. MFA is not supported yet.`, { cause: error });
79
+ }
80
+ }
81
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/garmin/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * How long a result that is missing days because requests FAILED may be kept.
3
+ * A complete answer earns its full TTL; a degraded one gets long enough to stop
4
+ * a retry storm and no longer.
5
+ */
6
+ export declare const PARTIAL_CACHE_TTL_SECONDS = 60;
7
+ export declare class GarminCache {
8
+ private readonly db;
9
+ constructor(databasePath?: string);
10
+ private initialize;
11
+ buildKey(tool: string, params: Record<string, unknown>): string;
12
+ get<T>(key: string): T | null;
13
+ set<T>(key: string, value: T, ttlSeconds: number): void;
14
+ delete(key: string): void;
15
+ clear(): number;
16
+ stats(): {
17
+ entries: number;
18
+ expiredEntries: number;
19
+ };
20
+ close(): void;
21
+ }
22
+ export declare function getCache(): GarminCache;
23
+ export declare function closeCache(): void;
24
+ export declare function buildToolCacheKey(tool: string, params: Record<string, unknown>): string;
25
+ export interface CacheOptions<T> {
26
+ /**
27
+ * True when the value is incomplete because a request failed rather than
28
+ * because nothing was measured. Such a value is cached for
29
+ * `partialTtlSeconds`, not for the full TTL.
30
+ */
31
+ isPartial?: (value: T) => boolean;
32
+ partialTtlSeconds?: number;
33
+ }
34
+ /**
35
+ * A thrown fetcher caches nothing -- that has always been true and is the point
36
+ * of the `await` sitting outside `cache.set`. What was not true is that a
37
+ * fetcher which swallowed its own failures and returned an empty result got the
38
+ * full success TTL, so one rate-limited minute froze "no data" in for two hours.
39
+ * A caller that can tell the difference now says so through `isPartial`.
40
+ */
41
+ export declare function withCache<T>(key: string, ttlSeconds: number, fetcher: () => Promise<T>, options?: CacheOptions<T>): Promise<T>;
42
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/garmin/cache.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAI5C,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;gBAE3B,YAAY,SAAsB;IAU9C,OAAO,CAAC,UAAU;IAWlB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM;IAI/D,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI;IAwB7B,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAqBvD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAIzB,KAAK,IAAI,MAAM;IAKf,KAAK,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE;IAepD,KAAK,IAAI,IAAI;CAGd;AAID,wBAAgB,QAAQ,IAAI,WAAW,CAMtC;AAED,wBAAgB,UAAU,IAAI,IAAI,CAKjC;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAEvF;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,CAAC,EAC/B,GAAG,EAAE,MAAM,EACX,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACzB,OAAO,GAAE,YAAY,CAAC,CAAC,CAAM,GAC5B,OAAO,CAAC,CAAC,CAAC,CAkBZ"}
@@ -0,0 +1,141 @@
1
+ import Database from "better-sqlite3";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { appConfig } from "../config.js";
5
+ import { hashParams } from "../utils/helpers.js";
6
+ import { logger } from "../utils/logger.js";
7
+ import { restrictExistingFile } from "../utils/secretFile.js";
8
+ /**
9
+ * Bumped whenever a cached value changes shape. The cache is keyed by tool and
10
+ * parameters only, so without this a build that starts storing
11
+ * `{ values, unreachableDays }` under a key that already holds a bare array
12
+ * reads the old row back, casts it to the new type and dereferences a field
13
+ * that is not there. An upgrade must invalidate, not reinterpret.
14
+ */
15
+ const CACHE_SCHEMA_VERSION = "v2";
16
+ /**
17
+ * How long a result that is missing days because requests FAILED may be kept.
18
+ * A complete answer earns its full TTL; a degraded one gets long enough to stop
19
+ * a retry storm and no longer.
20
+ */
21
+ export const PARTIAL_CACHE_TTL_SECONDS = 60;
22
+ // SECTION: SQLite Cache
23
+ export class GarminCache {
24
+ db;
25
+ constructor(databasePath = appConfig.cachePath) {
26
+ fs.mkdirSync(path.dirname(databasePath), { recursive: true });
27
+ this.db = new Database(databasePath);
28
+ // Holds whatever the tools last fetched: sleep, stress, resting heart rate.
29
+ // Shorter-lived than history.db and exactly as personal.
30
+ restrictExistingFile(databasePath);
31
+ this.initialize();
32
+ }
33
+ initialize() {
34
+ this.db.exec(`
35
+ CREATE TABLE IF NOT EXISTS cache (
36
+ key TEXT PRIMARY KEY,
37
+ data TEXT NOT NULL,
38
+ cached_at INTEGER NOT NULL,
39
+ ttl INTEGER NOT NULL
40
+ )
41
+ `);
42
+ }
43
+ buildKey(tool, params) {
44
+ return `${tool}:${CACHE_SCHEMA_VERSION}:${hashParams(params)}`;
45
+ }
46
+ get(key) {
47
+ const row = this.db
48
+ .prepare("SELECT key, data, cached_at, ttl FROM cache WHERE key = ?")
49
+ .get(key);
50
+ if (!row) {
51
+ return null;
52
+ }
53
+ const expiresAt = row.cached_at + row.ttl;
54
+ if (Date.now() > expiresAt * 1000) {
55
+ this.delete(key);
56
+ return null;
57
+ }
58
+ try {
59
+ return JSON.parse(row.data);
60
+ }
61
+ catch (error) {
62
+ logger.warn({ error, key }, "Failed to parse cached payload");
63
+ this.delete(key);
64
+ return null;
65
+ }
66
+ }
67
+ set(key, value, ttlSeconds) {
68
+ const cachedAt = Math.floor(Date.now() / 1000);
69
+ this.db
70
+ .prepare(`
71
+ INSERT INTO cache (key, data, cached_at, ttl)
72
+ VALUES (@key, @data, @cached_at, @ttl)
73
+ ON CONFLICT(key) DO UPDATE SET
74
+ data = excluded.data,
75
+ cached_at = excluded.cached_at,
76
+ ttl = excluded.ttl
77
+ `)
78
+ .run({
79
+ key,
80
+ data: JSON.stringify(value),
81
+ cached_at: cachedAt,
82
+ ttl: ttlSeconds,
83
+ });
84
+ }
85
+ delete(key) {
86
+ this.db.prepare("DELETE FROM cache WHERE key = ?").run(key);
87
+ }
88
+ clear() {
89
+ const result = this.db.prepare("DELETE FROM cache").run();
90
+ return result.changes;
91
+ }
92
+ stats() {
93
+ const now = Math.floor(Date.now() / 1000);
94
+ const entries = this.db.prepare("SELECT COUNT(*) as count FROM cache").get();
95
+ const expiredEntries = this.db
96
+ .prepare("SELECT COUNT(*) as count FROM cache WHERE cached_at + ttl < ?")
97
+ .get(now);
98
+ return {
99
+ entries: entries.count,
100
+ expiredEntries: expiredEntries.count,
101
+ };
102
+ }
103
+ close() {
104
+ this.db.close();
105
+ }
106
+ }
107
+ let cacheInstance = null;
108
+ export function getCache() {
109
+ if (!cacheInstance) {
110
+ cacheInstance = new GarminCache();
111
+ }
112
+ return cacheInstance;
113
+ }
114
+ export function closeCache() {
115
+ if (cacheInstance) {
116
+ cacheInstance.close();
117
+ cacheInstance = null;
118
+ }
119
+ }
120
+ export function buildToolCacheKey(tool, params) {
121
+ return getCache().buildKey(tool, params);
122
+ }
123
+ /**
124
+ * A thrown fetcher caches nothing -- that has always been true and is the point
125
+ * of the `await` sitting outside `cache.set`. What was not true is that a
126
+ * fetcher which swallowed its own failures and returned an empty result got the
127
+ * full success TTL, so one rate-limited minute froze "no data" in for two hours.
128
+ * A caller that can tell the difference now says so through `isPartial`.
129
+ */
130
+ export async function withCache(key, ttlSeconds, fetcher, options = {}) {
131
+ const cache = getCache();
132
+ const cached = cache.get(key);
133
+ if (cached !== null) {
134
+ return cached;
135
+ }
136
+ const fresh = await fetcher();
137
+ const partial = options.isPartial?.(fresh) ?? false;
138
+ cache.set(key, fresh, partial ? (options.partialTtlSeconds ?? PARTIAL_CACHE_TTL_SECONDS) : ttlSeconds);
139
+ return fresh;
140
+ }
141
+ //# sourceMappingURL=cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/garmin/cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { GarminConnectInstance } from "./garminConnect.js";
2
+ /** Injectable for tests; production always authenticates for real. */
3
+ export type Authenticator = (force: boolean) => Promise<GarminConnectInstance>;
4
+ export declare function getGarminClient(forceAuth?: boolean, authenticator?: Authenticator): Promise<GarminConnectInstance>;
5
+ /**
6
+ * Forget the session. NOT the rate limit.
7
+ *
8
+ * These were one function, and the retry path in `withGarminClient` called it
9
+ * -- so the single code path reached by exactly the failure the cooldown exists
10
+ * for was the one that deleted the cooldown, and then attempted a fresh login
11
+ * into the live block. The doubling could never escalate past its 60-second
12
+ * floor either, because every failure reset the ladder to the bottom rung.
13
+ *
14
+ * Two different jobs had been sharing one name: `trainbud auth` wants both
15
+ * forgotten, since the user has just supplied new credentials and is entitled
16
+ * to one attempt. A failed request wants only the session forgotten.
17
+ */
18
+ export declare function resetGarminSession(): void;
19
+ /** Forget the session AND the rate limit. What `trainbud auth` asks for. */
20
+ export declare function resetGarminClient(): void;
21
+ export declare function withGarminClient<T>(operation: (client: GarminConnectInstance) => Promise<T>, authenticator?: Authenticator): Promise<T>;
22
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/garmin/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAyFhE,sEAAsE;AACtE,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,qBAAqB,CAAC,CAAC;AAI/E,wBAAsB,eAAe,CACnC,SAAS,UAAQ,EACjB,aAAa,GAAE,aAAoC,GAClD,OAAO,CAAC,qBAAqB,CAAC,CAwChC;AAuDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AA+CD,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,SAAS,EAAE,CAAC,MAAM,EAAE,qBAAqB,KAAK,OAAO,CAAC,CAAC,CAAC,EACxD,aAAa,GAAE,aAAoC,GAClD,OAAO,CAAC,CAAC,CAAC,CA4BZ"}
@@ -0,0 +1,237 @@
1
+ import { authenticateGarmin } from "./auth.js";
2
+ import { logger } from "../utils/logger.js";
3
+ import { GarminApiError } from "./types.js";
4
+ import { deleteSetting, getSetting, setSetting } from "../appDb.js";
5
+ // SECTION: Garmin Client Singleton
6
+ let clientInstance = null;
7
+ let clientPromise = null;
8
+ /**
9
+ * When a rate-limited login stops being retried.
10
+ *
11
+ * Garmin's SSO sits behind Cloudflare, which answers a burst of logins with a
12
+ * 429 that says, in the body, "wait at least 30 seconds, then retry with
13
+ * exponential backoff". Nothing here read that. The in-flight login promise is
14
+ * deliberately cleared on failure -- so that fixing a bad password does not
15
+ * require a restart -- and the consequence was that every subsequent caller
16
+ * started a fresh login of its own. `trainbud check` walks nine tools in
17
+ * sequence, so one expired session became nine logins into a rate limit in
18
+ * about five seconds, each one deepening it, each one dumping a kilobyte of
19
+ * Cloudflare JSON into the log. The command that exists to tell you what is
20
+ * wrong was reliably causing it.
21
+ *
22
+ * One shared cooldown, honoured before any login is attempted. Failing fast
23
+ * with the reason is both faster and kinder to the upstream than nine identical
24
+ * failures.
25
+ */
26
+ const COOLDOWN_UNTIL_KEY = "garmin_auth_blocked_until";
27
+ const COOLDOWN_STEP_KEY = "garmin_auth_backoff_seconds";
28
+ /** Cloudflare's own floor for this limit, from the 1015 body. */
29
+ const MIN_COOLDOWN_SECONDS = 60;
30
+ /** Past this, waiting longer is not the problem and something else is wrong. */
31
+ const MAX_COOLDOWN_SECONDS = 30 * 60;
32
+ /**
33
+ * The cooldown has to outlive the process.
34
+ *
35
+ * It was a module variable, which protects a single `trainbud serve` and does
36
+ * nothing at all for the CLI: every `trainbud backfill` is a new process, so it
37
+ * started with no memory of the limit, attempted a fresh login, and was refused
38
+ * again. Running the command a few times in a row is the single most natural
39
+ * thing for a user to do when it fails, and it was the exact behaviour that kept
40
+ * Garmin's login limit alive. Observed doing precisely that.
41
+ *
42
+ * Stored in the settings table, which every entry point already opens.
43
+ *
44
+ * The window doubles each time the limit is hit again, because that is what the
45
+ * upstream asks for in the 1015 body: "wait at least 30 seconds, then retry with
46
+ * exponential backoff". A fixed 60 seconds ignores an escalating block and walks
47
+ * straight back into it.
48
+ */
49
+ function readCooldownUntil() {
50
+ const stored = getSetting(COOLDOWN_UNTIL_KEY);
51
+ if (!stored) {
52
+ return null;
53
+ }
54
+ const value = Number.parseInt(stored, 10);
55
+ return Number.isFinite(value) ? value : null;
56
+ }
57
+ function remainingCooldownSeconds() {
58
+ const until = readCooldownUntil();
59
+ if (until === null) {
60
+ return 0;
61
+ }
62
+ return Math.max(0, Math.ceil((until - Date.now()) / 1000));
63
+ }
64
+ function startCooldown(upstreamRetryAfter) {
65
+ const previous = Number.parseInt(getSetting(COOLDOWN_STEP_KEY) ?? "", 10);
66
+ const base = Number.isFinite(previous) && previous > 0 ? previous * 2 : MIN_COOLDOWN_SECONDS;
67
+ const seconds = Math.min(MAX_COOLDOWN_SECONDS, Math.max(base, upstreamRetryAfter ?? 0, MIN_COOLDOWN_SECONDS));
68
+ setSetting(COOLDOWN_UNTIL_KEY, String(Date.now() + seconds * 1000));
69
+ setSetting(COOLDOWN_STEP_KEY, String(seconds));
70
+ return seconds;
71
+ }
72
+ function clearCooldown() {
73
+ deleteSetting(COOLDOWN_UNTIL_KEY);
74
+ deleteSetting(COOLDOWN_STEP_KEY);
75
+ }
76
+ const defaultAuthenticator = (force) => authenticateGarmin(force);
77
+ export async function getGarminClient(forceAuth = false, authenticator = defaultAuthenticator) {
78
+ // An existing session is still usable while the login endpoint is cooling
79
+ // down: the cooldown is about signing in again, not about the API.
80
+ if (clientInstance && !forceAuth) {
81
+ return clientInstance;
82
+ }
83
+ const cooldown = remainingCooldownSeconds();
84
+ if (cooldown > 0) {
85
+ throw new GarminApiError(`Garmin is rate limiting sign-in. ${cooldown}s left to wait — every attempt before then extends it.`, 429, cooldown);
86
+ }
87
+ if (forceAuth) {
88
+ clientInstance = await trackAuthRateLimit(() => authenticator(true));
89
+ clientPromise = Promise.resolve(clientInstance);
90
+ return clientInstance;
91
+ }
92
+ if (!clientPromise) {
93
+ // The in-flight promise is memoised so concurrent callers share one login.
94
+ // It used to be memoised on failure too: a single bad password poisoned
95
+ // every later call with the same rejected promise, so fixing the
96
+ // credentials changed nothing until the process was restarted. Clear it on
97
+ // rejection and let the next caller try again.
98
+ clientPromise = trackAuthRateLimit(() => authenticator(false))
99
+ .then((client) => {
100
+ clientInstance = client;
101
+ return client;
102
+ })
103
+ .catch((error) => {
104
+ clientPromise = null;
105
+ throw error;
106
+ });
107
+ }
108
+ return clientPromise;
109
+ }
110
+ /**
111
+ * Runs a login and records a cooldown if the upstream rate limited it.
112
+ *
113
+ * The window comes from the error itself where the upstream supplied one, so a
114
+ * server that asks for longer gets longer. Cleared on success, so a login that
115
+ * works immediately after the window costs nothing.
116
+ */
117
+ /**
118
+ * Runs a login without letting the upstream library print to the console.
119
+ *
120
+ * garmin-connect's handleHttpError does `console.error(msg)` and then
121
+ * `throw new Error(msg)` with the same string, so the kilobyte of Cloudflare
122
+ * 1015 JSON a user sees is a duplicate of an error we already catch and render
123
+ * as one actionable line. Printed first and unconditionally, it buried that line
124
+ * and made a "wait 60 seconds" look like a crash -- and the natural response to
125
+ * a crash is to run the command again, which extends the block.
126
+ *
127
+ * Kept at debug level rather than dropped: it is still the upstream's own
128
+ * account of what happened, and LOG_LEVEL=debug brings it back.
129
+ */
130
+ async function withQuietUpstreamErrors(fn) {
131
+ const original = console.error;
132
+ console.error = (...args) => {
133
+ logger.debug({ upstream: args.map(String).join(" ") }, "garmin-connect console output");
134
+ };
135
+ try {
136
+ return await fn();
137
+ }
138
+ finally {
139
+ console.error = original;
140
+ }
141
+ }
142
+ async function trackAuthRateLimit(login) {
143
+ try {
144
+ const client = await withQuietUpstreamErrors(login);
145
+ clearCooldown();
146
+ return client;
147
+ }
148
+ catch (error) {
149
+ const rateLimited = toRateLimitError(error);
150
+ if (rateLimited) {
151
+ const seconds = startCooldown(rateLimited.retryAfterSeconds ?? null);
152
+ throw new GarminApiError(`Garmin is rate limiting sign-in. Waiting ${seconds}s before the next attempt; retrying sooner makes it last longer.`, 429, seconds);
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+ /**
158
+ * Forget the session. NOT the rate limit.
159
+ *
160
+ * These were one function, and the retry path in `withGarminClient` called it
161
+ * -- so the single code path reached by exactly the failure the cooldown exists
162
+ * for was the one that deleted the cooldown, and then attempted a fresh login
163
+ * into the live block. The doubling could never escalate past its 60-second
164
+ * floor either, because every failure reset the ladder to the bottom rung.
165
+ *
166
+ * Two different jobs had been sharing one name: `trainbud auth` wants both
167
+ * forgotten, since the user has just supplied new credentials and is entitled
168
+ * to one attempt. A failed request wants only the session forgotten.
169
+ */
170
+ export function resetGarminSession() {
171
+ clientInstance = null;
172
+ clientPromise = null;
173
+ }
174
+ /** Forget the session AND the rate limit. What `trainbud auth` asks for. */
175
+ export function resetGarminClient() {
176
+ resetGarminSession();
177
+ clearCooldown();
178
+ }
179
+ function isAuthError(error) {
180
+ if (!(error instanceof Error)) {
181
+ return false;
182
+ }
183
+ const message = error.message.toLowerCase();
184
+ return (message.includes("401") ||
185
+ message.includes("403") ||
186
+ message.includes("unauthorized") ||
187
+ message.includes("token") ||
188
+ message.includes("login failed"));
189
+ }
190
+ function toRateLimitError(error) {
191
+ if (!(error instanceof Error)) {
192
+ return null;
193
+ }
194
+ // Already ours: it carries the real remaining cooldown, which is the number
195
+ // the user needs. Re-wrapping it replaced that with a hardcoded 60 seconds and
196
+ // told someone eight minutes into an escalated block to try again in one.
197
+ if (error instanceof GarminApiError && error.statusCode === 429) {
198
+ return error;
199
+ }
200
+ const message = error.message.toLowerCase();
201
+ if (!message.includes("429") && !message.includes("rate limit")) {
202
+ return null;
203
+ }
204
+ // Cloudflare puts the wait in the body it just sent us. Reading it beats
205
+ // guessing, and it is the difference between honouring an escalating block and
206
+ // walking back into it.
207
+ const retryAfter = /"retry_after"\s*:\s*(\d+)/.exec(error.message)?.[1];
208
+ const seconds = retryAfter ? Number.parseInt(retryAfter, 10) : 60;
209
+ return new GarminApiError(`Garmin rate limit reached. Retry in about ${seconds} seconds.`, 429, Number.isFinite(seconds) ? seconds : 60);
210
+ }
211
+ export async function withGarminClient(operation, authenticator = defaultAuthenticator) {
212
+ try {
213
+ const client = await getGarminClient(false, authenticator);
214
+ return await operation(client);
215
+ }
216
+ catch (error) {
217
+ const rateLimitError = toRateLimitError(error);
218
+ if (rateLimitError) {
219
+ throw rateLimitError;
220
+ }
221
+ if (!isAuthError(error)) {
222
+ throw error;
223
+ }
224
+ logger.warn({ error }, "Garmin request failed auth, retrying once");
225
+ // The session, not the cooldown. This used to call `resetGarminClient`,
226
+ // which deletes both -- so the retry erased a live rate-limit block and
227
+ // walked straight back into it, on the single path the block exists to
228
+ // guard. With the cooldown left in place, the `getGarminClient` call below
229
+ // refuses on its own: its cooldown check runs ahead of the `forceAuth`
230
+ // branch, so no extra guard is needed here and adding one would put the
231
+ // same decision in two places.
232
+ resetGarminSession();
233
+ const client = await getGarminClient(true, authenticator);
234
+ return await operation(client);
235
+ }
236
+ }
237
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/garmin/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ import type { HeartRateData, IActivity, SleepData, WeightDataResponse } from "./garminApiTypes.js";
2
+ import type { GarminConnectInstance } from "./garminConnect.js";
3
+ import { type DailyStressSummary, type Vo2MaxEntry } from "./rawApi.js";
4
+ import type { ActivitySummary, BodyCompositionEntry, HeartRateDaySummary, SleepNightSummary } from "./types.js";
5
+ export interface DailyFetch<T> {
6
+ raw: unknown;
7
+ mapped: T | null;
8
+ }
9
+ export declare function mapSleepData(date: Date, sleepData: SleepData): SleepNightSummary | null;
10
+ export declare function mapHeartRateData(date: Date, heartRate: HeartRateData | null): HeartRateDaySummary | null;
11
+ /**
12
+ * Connect omits these for activities they do not apply to -- strength training
13
+ * reports no distance or pace, an indoor ride no elevation. They were once
14
+ * typed as plain numbers, which type-checked while crashing on
15
+ * `undefined.toFixed`.
16
+ */
17
+ export declare function mapActivity(activity: IActivity): ActivitySummary;
18
+ export declare function mapWeightData(date: Date, weightData: WeightDataResponse): BodyCompositionEntry[];
19
+ export declare function fetchSleepDay(client: GarminConnectInstance, date: Date): Promise<DailyFetch<SleepNightSummary>>;
20
+ export declare function fetchHeartRateDay(client: GarminConnectInstance, date: Date): Promise<DailyFetch<HeartRateDaySummary>>;
21
+ export declare function fetchStressDay(client: GarminConnectInstance, date: Date): Promise<DailyFetch<DailyStressSummary>>;
22
+ export declare function fetchVo2MaxDay(client: GarminConnectInstance, date: Date): Promise<DailyFetch<Vo2MaxEntry>>;
23
+ export declare function fetchBodyCompositionDay(client: GarminConnectInstance, date: Date): Promise<DailyFetch<BodyCompositionEntry[]>>;
24
+ //# sourceMappingURL=daily.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"daily.d.ts","sourceRoot":"","sources":["../../src/garmin/daily.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,SAAS,EACT,kBAAkB,EACnB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAKL,KAAK,kBAAkB,EACvB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EACV,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,EAClB,MAAM,YAAY,CAAC;AAepB,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;CAClB;AAID,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,GAAG,iBAAiB,GAAG,IAAI,CAmBvF;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,aAAa,GAAG,IAAI,GAC9B,mBAAmB,GAAG,IAAI,CAyB5B;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,SAAS,GAAG,eAAe,CAchE;AAED,wBAAgB,aAAa,CAC3B,IAAI,EAAE,IAAI,EACV,UAAU,EAAE,kBAAkB,GAC7B,oBAAoB,EAAE,CAQxB;AAID,wBAAsB,aAAa,CACjC,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAGxC;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAG1C;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAGzC;AAED,wBAAsB,cAAc,CAClC,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAGlC;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,qBAAqB,EAC7B,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,UAAU,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAI7C"}