@elizaos/plugin-health 2.0.0-beta.1 → 2.0.3-beta.2

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 (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +33 -27
  3. package/assets/hero.svg +67 -0
  4. package/package.json +67 -4
  5. package/dist/actions/index.d.ts +0 -20
  6. package/dist/actions/index.d.ts.map +0 -1
  7. package/dist/actions/index.js +0 -5
  8. package/dist/actions/index.js.map +0 -1
  9. package/dist/anchors/index.d.ts +0 -19
  10. package/dist/anchors/index.d.ts.map +0 -1
  11. package/dist/anchors/index.js +0 -9
  12. package/dist/anchors/index.js.map +0 -1
  13. package/dist/connectors/contract-stubs.d.ts +0 -112
  14. package/dist/connectors/contract-stubs.d.ts.map +0 -1
  15. package/dist/connectors/contract-stubs.js +0 -1
  16. package/dist/connectors/contract-stubs.js.map +0 -1
  17. package/dist/connectors/index.d.ts +0 -28
  18. package/dist/connectors/index.d.ts.map +0 -1
  19. package/dist/connectors/index.js +0 -202
  20. package/dist/connectors/index.js.map +0 -1
  21. package/dist/contracts/circadian-default.d.ts +0 -15
  22. package/dist/contracts/circadian-default.d.ts.map +0 -1
  23. package/dist/contracts/circadian-default.js +0 -30
  24. package/dist/contracts/circadian-default.js.map +0 -1
  25. package/dist/contracts/circadian.d.ts +0 -92
  26. package/dist/contracts/circadian.d.ts.map +0 -1
  27. package/dist/contracts/circadian.js +0 -14
  28. package/dist/contracts/circadian.js.map +0 -1
  29. package/dist/contracts/health.d.ts +0 -9
  30. package/dist/contracts/health.d.ts.map +0 -1
  31. package/dist/contracts/health.js +0 -21
  32. package/dist/contracts/health.js.map +0 -1
  33. package/dist/contracts/lifeops-connector-degradation.d.ts +0 -9
  34. package/dist/contracts/lifeops-connector-degradation.d.ts.map +0 -1
  35. package/dist/contracts/lifeops-connector-degradation.js +0 -17
  36. package/dist/contracts/lifeops-connector-degradation.js.map +0 -1
  37. package/dist/contracts/lifeops.d.ts +0 -3123
  38. package/dist/contracts/lifeops.d.ts.map +0 -1
  39. package/dist/contracts/lifeops.js +0 -635
  40. package/dist/contracts/lifeops.js.map +0 -1
  41. package/dist/contracts/permissions.d.ts +0 -39
  42. package/dist/contracts/permissions.d.ts.map +0 -1
  43. package/dist/contracts/permissions.js +0 -1
  44. package/dist/contracts/permissions.js.map +0 -1
  45. package/dist/default-packs/bedtime.d.ts +0 -14
  46. package/dist/default-packs/bedtime.d.ts.map +0 -1
  47. package/dist/default-packs/bedtime.js +0 -48
  48. package/dist/default-packs/bedtime.js.map +0 -1
  49. package/dist/default-packs/contract-stubs.d.ts +0 -161
  50. package/dist/default-packs/contract-stubs.d.ts.map +0 -1
  51. package/dist/default-packs/contract-stubs.js +0 -1
  52. package/dist/default-packs/contract-stubs.js.map +0 -1
  53. package/dist/default-packs/index.d.ts +0 -18
  54. package/dist/default-packs/index.d.ts.map +0 -1
  55. package/dist/default-packs/index.js +0 -39
  56. package/dist/default-packs/index.js.map +0 -1
  57. package/dist/default-packs/sleep-recap.d.ts +0 -14
  58. package/dist/default-packs/sleep-recap.d.ts.map +0 -1
  59. package/dist/default-packs/sleep-recap.js +0 -51
  60. package/dist/default-packs/sleep-recap.js.map +0 -1
  61. package/dist/default-packs/wake-up.d.ts +0 -14
  62. package/dist/default-packs/wake-up.d.ts.map +0 -1
  63. package/dist/default-packs/wake-up.js +0 -61
  64. package/dist/default-packs/wake-up.js.map +0 -1
  65. package/dist/health-bridge/health-bridge.d.ts +0 -57
  66. package/dist/health-bridge/health-bridge.d.ts.map +0 -1
  67. package/dist/health-bridge/health-bridge.js +0 -558
  68. package/dist/health-bridge/health-bridge.js.map +0 -1
  69. package/dist/health-bridge/health-connectors.d.ts +0 -23
  70. package/dist/health-bridge/health-connectors.d.ts.map +0 -1
  71. package/dist/health-bridge/health-connectors.js +0 -1018
  72. package/dist/health-bridge/health-connectors.js.map +0 -1
  73. package/dist/health-bridge/health-oauth.d.ts +0 -62
  74. package/dist/health-bridge/health-oauth.d.ts.map +0 -1
  75. package/dist/health-bridge/health-oauth.js +0 -432
  76. package/dist/health-bridge/health-oauth.js.map +0 -1
  77. package/dist/health-bridge/health-provider-registry.d.ts +0 -89
  78. package/dist/health-bridge/health-provider-registry.d.ts.map +0 -1
  79. package/dist/health-bridge/health-provider-registry.js +0 -141
  80. package/dist/health-bridge/health-provider-registry.js.map +0 -1
  81. package/dist/health-bridge/health-records.d.ts +0 -14
  82. package/dist/health-bridge/health-records.d.ts.map +0 -1
  83. package/dist/health-bridge/health-records.js +0 -45
  84. package/dist/health-bridge/health-records.js.map +0 -1
  85. package/dist/health-bridge/index.d.ts +0 -22
  86. package/dist/health-bridge/index.d.ts.map +0 -1
  87. package/dist/health-bridge/index.js +0 -7
  88. package/dist/health-bridge/index.js.map +0 -1
  89. package/dist/health-bridge/service-normalize-health.d.ts +0 -3
  90. package/dist/health-bridge/service-normalize-health.d.ts.map +0 -1
  91. package/dist/health-bridge/service-normalize-health.js +0 -96
  92. package/dist/health-bridge/service-normalize-health.js.map +0 -1
  93. package/dist/index.d.ts +0 -41
  94. package/dist/index.d.ts.map +0 -1
  95. package/dist/index.js +0 -62
  96. package/dist/index.js.map +0 -1
  97. package/dist/screen-time/index.d.ts +0 -23
  98. package/dist/screen-time/index.d.ts.map +0 -1
  99. package/dist/screen-time/index.js +0 -1
  100. package/dist/screen-time/index.js.map +0 -1
  101. package/dist/sleep/awake-probability.d.ts +0 -11
  102. package/dist/sleep/awake-probability.d.ts.map +0 -1
  103. package/dist/sleep/awake-probability.js +0 -163
  104. package/dist/sleep/awake-probability.js.map +0 -1
  105. package/dist/sleep/circadian-rules.d.ts +0 -45
  106. package/dist/sleep/circadian-rules.d.ts.map +0 -1
  107. package/dist/sleep/circadian-rules.js +0 -258
  108. package/dist/sleep/circadian-rules.js.map +0 -1
  109. package/dist/sleep/index.d.ts +0 -21
  110. package/dist/sleep/index.d.ts.map +0 -1
  111. package/dist/sleep/index.js +0 -11
  112. package/dist/sleep/index.js.map +0 -1
  113. package/dist/sleep/sleep-cycle-dispatch.d.ts +0 -75
  114. package/dist/sleep/sleep-cycle-dispatch.d.ts.map +0 -1
  115. package/dist/sleep/sleep-cycle-dispatch.js +0 -102
  116. package/dist/sleep/sleep-cycle-dispatch.js.map +0 -1
  117. package/dist/sleep/sleep-cycle.d.ts +0 -38
  118. package/dist/sleep/sleep-cycle.d.ts.map +0 -1
  119. package/dist/sleep/sleep-cycle.js +0 -418
  120. package/dist/sleep/sleep-cycle.js.map +0 -1
  121. package/dist/sleep/sleep-episode-store.d.ts +0 -25
  122. package/dist/sleep/sleep-episode-store.d.ts.map +0 -1
  123. package/dist/sleep/sleep-episode-store.js +0 -69
  124. package/dist/sleep/sleep-episode-store.js.map +0 -1
  125. package/dist/sleep/sleep-episode-types.d.ts +0 -38
  126. package/dist/sleep/sleep-episode-types.d.ts.map +0 -1
  127. package/dist/sleep/sleep-episode-types.js +0 -14
  128. package/dist/sleep/sleep-episode-types.js.map +0 -1
  129. package/dist/sleep/sleep-recap.d.ts +0 -19
  130. package/dist/sleep/sleep-recap.d.ts.map +0 -1
  131. package/dist/sleep/sleep-recap.js +0 -1
  132. package/dist/sleep/sleep-recap.js.map +0 -1
  133. package/dist/sleep/sleep-regularity.d.ts +0 -19
  134. package/dist/sleep/sleep-regularity.d.ts.map +0 -1
  135. package/dist/sleep/sleep-regularity.js +0 -242
  136. package/dist/sleep/sleep-regularity.js.map +0 -1
  137. package/dist/sleep/sleep-wake-events.d.ts +0 -58
  138. package/dist/sleep/sleep-wake-events.d.ts.map +0 -1
  139. package/dist/sleep/sleep-wake-events.js +0 -135
  140. package/dist/sleep/sleep-wake-events.js.map +0 -1
  141. package/dist/sleep/source-reliability.d.ts +0 -38
  142. package/dist/sleep/source-reliability.d.ts.map +0 -1
  143. package/dist/sleep/source-reliability.js +0 -62
  144. package/dist/sleep/source-reliability.js.map +0 -1
  145. package/dist/util/index.d.ts +0 -10
  146. package/dist/util/index.d.ts.map +0 -1
  147. package/dist/util/index.js +0 -3
  148. package/dist/util/index.js.map +0 -1
  149. package/dist/util/normalize.d.ts +0 -22
  150. package/dist/util/normalize.d.ts.map +0 -1
  151. package/dist/util/normalize.js +0 -62
  152. package/dist/util/normalize.js.map +0 -1
  153. package/dist/util/time-util.d.ts +0 -10
  154. package/dist/util/time-util.d.ts.map +0 -1
  155. package/dist/util/time-util.js +0 -14
  156. package/dist/util/time-util.js.map +0 -1
  157. package/dist/util/time.d.ts +0 -17
  158. package/dist/util/time.d.ts.map +0 -1
  159. package/dist/util/time.js +0 -152
  160. package/dist/util/time.js.map +0 -1
  161. package/dist/util/token-encryption.d.ts +0 -42
  162. package/dist/util/token-encryption.d.ts.map +0 -1
  163. package/dist/util/token-encryption.js +0 -96
  164. package/dist/util/token-encryption.js.map +0 -1
@@ -1,558 +0,0 @@
1
- import { execFile } from "node:child_process";
2
- import { accessSync, constants as fsConstants } from "node:fs";
3
- import { promisify } from "node:util";
4
- import { logger } from "@elizaos/core";
5
- const execFileAsync = promisify(execFile);
6
- const ONE_DAY_MS = 24 * 60 * 60 * 1e3;
7
- function rewriteGoogleUrlForMock(url) {
8
- const mockBase = process.env.ELIZA_MOCK_GOOGLE_BASE;
9
- if (!mockBase) return url;
10
- const mockUrl = new URL(mockBase);
11
- if (!["127.0.0.1", "localhost", "::1", "[::1]"].includes(mockUrl.hostname)) {
12
- throw new HealthBridgeError(
13
- "Google mock base must point to loopback for health mock tests.",
14
- "google-fit"
15
- );
16
- }
17
- return url.replace(
18
- /^https:\/\/(?:fitness)\.googleapis\.com/,
19
- mockUrl.toString().replace(/\/+$/, "")
20
- );
21
- }
22
- class HealthBridgeError extends Error {
23
- constructor(message, backend) {
24
- super(message);
25
- this.backend = backend;
26
- this.name = "HealthBridgeError";
27
- }
28
- backend;
29
- }
30
- function isTruthyEnv(value) {
31
- if (!value) return false;
32
- const normalized = value.trim().toLowerCase();
33
- return normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on" || normalized === "fixture";
34
- }
35
- function isFalsyEnv(value) {
36
- if (!value) return false;
37
- const normalized = value.trim().toLowerCase();
38
- return normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off";
39
- }
40
- function isFixtureHealthBackendEnabled() {
41
- const explicit = process.env.ELIZA_TEST_HEALTH_BACKEND;
42
- if (isFalsyEnv(explicit)) return false;
43
- if (isTruthyEnv(explicit)) return true;
44
- return false;
45
- }
46
- function utcMidnightMs(date) {
47
- const ms = Date.parse(`${date}T00:00:00.000Z`);
48
- if (!Number.isFinite(ms)) {
49
- throw new HealthBridgeError(
50
- `Invalid fixture health date: ${date}`,
51
- "fixture"
52
- );
53
- }
54
- return ms;
55
- }
56
- function todayDateKeyUtc() {
57
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
58
- }
59
- function fixtureDayOffset(date) {
60
- return Math.round(
61
- (utcMidnightMs(date) - utcMidnightMs(todayDateKeyUtc())) / ONE_DAY_MS
62
- );
63
- }
64
- function fixtureSummaryForDate(date) {
65
- const offset = fixtureDayOffset(date);
66
- const distance = Math.abs(offset);
67
- const direction = offset < 0 ? 1 : -1;
68
- const steps = Math.max(
69
- 1500,
70
- 8420 + direction * distance * 260 + distance % 3 * 175
71
- );
72
- const activeMinutes = Math.max(
73
- 18,
74
- 63 + direction * distance * 4 + (distance % 2 === 0 ? 2 : -3)
75
- );
76
- const sleepHours = Math.max(5.2, 7.4 + direction * distance * 0.15);
77
- const heartRateAvg = Math.max(54, 62 - direction * distance);
78
- const calories = Math.max(1650, 2180 + direction * distance * 55);
79
- const distanceMeters = Math.max(1200, steps * 0.78);
80
- return {
81
- date,
82
- steps: Math.round(steps),
83
- activeMinutes: Math.round(activeMinutes),
84
- sleepHours,
85
- heartRateAvg,
86
- calories,
87
- distanceMeters,
88
- source: "fixture"
89
- };
90
- }
91
- function enumerateFixtureDates(startAt, endAt) {
92
- const start = new Date(startAt);
93
- const end = new Date(endAt);
94
- const startMs = start.getTime();
95
- const endMs = end.getTime();
96
- if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {
97
- throw new HealthBridgeError(
98
- "Invalid time window for fixture health data",
99
- "fixture"
100
- );
101
- }
102
- if (endMs < startMs) {
103
- return [];
104
- }
105
- const dates = [];
106
- const cursor = new Date(
107
- Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate())
108
- );
109
- const endCursorMs = Date.UTC(
110
- end.getUTCFullYear(),
111
- end.getUTCMonth(),
112
- end.getUTCDate()
113
- );
114
- while (cursor.getTime() <= endCursorMs) {
115
- dates.push(cursor.toISOString().slice(0, 10));
116
- cursor.setUTCDate(cursor.getUTCDate() + 1);
117
- }
118
- return dates;
119
- }
120
- function fixturePointValue(summary, metric) {
121
- if (metric === "sleep_hours") {
122
- const startAt = `${summary.date}T00:30:00.000Z`;
123
- const endMs = Date.parse(startAt) + Math.round(summary.sleepHours * 60 * 60 * 1e3);
124
- return {
125
- value: summary.sleepHours,
126
- unit: "hours",
127
- startAt,
128
- endAt: new Date(endMs).toISOString()
129
- };
130
- }
131
- return {
132
- value: metric === "steps" ? summary.steps : metric === "active_minutes" ? summary.activeMinutes : metric === "heart_rate" ? summary.heartRateAvg ?? 0 : metric === "calories" ? summary.calories ?? 0 : summary.distanceMeters ?? 0,
133
- unit: metric === "steps" ? "count" : metric === "active_minutes" ? "minutes" : metric === "heart_rate" ? "bpm" : metric === "calories" ? "kcal" : "m",
134
- startAt: `${summary.date}T00:00:00.000Z`,
135
- endAt: `${summary.date}T23:59:59.999Z`
136
- };
137
- }
138
- function fixtureDataPoints(opts) {
139
- return enumerateFixtureDates(opts.startAt, opts.endAt).map((date) => fixtureSummaryForDate(date)).map((summary) => {
140
- const point = fixturePointValue(summary, opts.metric);
141
- return {
142
- metric: opts.metric,
143
- value: point.value,
144
- unit: point.unit,
145
- startAt: point.startAt,
146
- endAt: point.endAt,
147
- source: "fixture"
148
- };
149
- }).filter((point) => point.value > 0);
150
- }
151
- function isExecutable(path) {
152
- try {
153
- accessSync(path, fsConstants.X_OK);
154
- return true;
155
- } catch {
156
- return false;
157
- }
158
- }
159
- function resolveHealthKitCliPath(config) {
160
- const configured = config?.healthKitCliPath?.trim() || process.env.ELIZA_HEALTHKIT_CLI_PATH?.trim();
161
- if (!configured) return null;
162
- if (!isExecutable(configured)) return null;
163
- return configured;
164
- }
165
- function resolveGoogleFitAccessToken(config) {
166
- const token = config?.googleFitAccessToken?.trim() || process.env.ELIZA_GOOGLE_FIT_ACCESS_TOKEN?.trim();
167
- return token ? token : null;
168
- }
169
- async function detectHealthBackend(config) {
170
- if (config?.preferredBackend === "none") {
171
- return "none";
172
- }
173
- if (config?.preferredBackend === "fixture") {
174
- return "fixture";
175
- }
176
- if (isFixtureHealthBackendEnabled()) {
177
- return "fixture";
178
- }
179
- if (config?.preferredBackend) {
180
- if (config.preferredBackend === "healthkit" && resolveHealthKitCliPath(config)) {
181
- return "healthkit";
182
- }
183
- if (config.preferredBackend === "google-fit" && resolveGoogleFitAccessToken(config)) {
184
- return "google-fit";
185
- }
186
- }
187
- if (process.platform === "darwin" && resolveHealthKitCliPath(config)) {
188
- return "healthkit";
189
- }
190
- if (resolveGoogleFitAccessToken(config)) {
191
- return "google-fit";
192
- }
193
- return "none";
194
- }
195
- async function invokeHealthKitCli(cliPath, args) {
196
- const { stdout } = await execFileAsync(cliPath, args, {
197
- timeout: 15e3,
198
- maxBuffer: 8 * 1024 * 1024
199
- });
200
- return JSON.parse(stdout);
201
- }
202
- function finiteNumber(value) {
203
- return typeof value === "number" && Number.isFinite(value) ? value : 0;
204
- }
205
- function optionalFiniteNumber(value) {
206
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
207
- }
208
- async function healthKitDailySummary(date, cliPath) {
209
- const raw = await invokeHealthKitCli(cliPath, [
210
- "daily",
211
- "--date",
212
- date
213
- ]);
214
- return {
215
- date: typeof raw.date === "string" ? raw.date : date,
216
- steps: finiteNumber(raw.steps),
217
- activeMinutes: finiteNumber(raw.activeMinutes),
218
- sleepHours: finiteNumber(raw.sleepHours),
219
- heartRateAvg: optionalFiniteNumber(raw.heartRateAvg),
220
- calories: optionalFiniteNumber(raw.calories),
221
- distanceMeters: optionalFiniteNumber(raw.distanceMeters),
222
- source: "healthkit"
223
- };
224
- }
225
- async function healthKitDataPoints(opts, cliPath) {
226
- const raw = await invokeHealthKitCli(cliPath, [
227
- "points",
228
- "--metric",
229
- opts.metric,
230
- "--start",
231
- opts.startAt,
232
- "--end",
233
- opts.endAt
234
- ]);
235
- if (!Array.isArray(raw)) return [];
236
- return raw.map((p) => ({
237
- metric: p.metric,
238
- value: finiteNumber(p.value),
239
- unit: typeof p.unit === "string" ? p.unit : "",
240
- startAt: p.startAt,
241
- endAt: p.endAt,
242
- source: "healthkit"
243
- }));
244
- }
245
- const GOOGLE_FIT_AGGREGATE_URL = "https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate";
246
- const GOOGLE_FIT_DATA_TYPES = {
247
- steps: {
248
- dataTypeName: "com.google.step_count.delta",
249
- unit: "count"
250
- },
251
- active_minutes: {
252
- dataTypeName: "com.google.active_minutes",
253
- unit: "minutes"
254
- },
255
- calories: {
256
- dataTypeName: "com.google.calories.expended",
257
- unit: "kcal"
258
- },
259
- distance_meters: {
260
- dataTypeName: "com.google.distance.delta",
261
- unit: "m"
262
- },
263
- heart_rate: {
264
- dataTypeName: "com.google.heart_rate.bpm",
265
- unit: "bpm"
266
- }
267
- };
268
- async function callGoogleFitAggregate(accessToken, body) {
269
- const targetUrl = rewriteGoogleUrlForMock(GOOGLE_FIT_AGGREGATE_URL);
270
- const response = await fetch(targetUrl, {
271
- method: "POST",
272
- headers: {
273
- Authorization: `Bearer ${accessToken}`,
274
- "Content-Type": "application/json"
275
- },
276
- body: JSON.stringify(body),
277
- signal: AbortSignal.timeout(12e3)
278
- });
279
- if (!response.ok) {
280
- const status = response.status;
281
- throw new HealthBridgeError(
282
- `Google Fit request failed: HTTP ${status}`,
283
- "google-fit"
284
- );
285
- }
286
- return await response.json();
287
- }
288
- function sumBucketValues(bucket) {
289
- let total = 0;
290
- for (const ds of bucket.dataset ?? []) {
291
- for (const point of ds.point ?? []) {
292
- for (const v of point.value ?? []) {
293
- if (typeof v.fpVal === "number" && Number.isFinite(v.fpVal)) {
294
- total += v.fpVal;
295
- } else if (typeof v.intVal === "number" && Number.isFinite(v.intVal)) {
296
- total += v.intVal;
297
- }
298
- }
299
- }
300
- }
301
- return total;
302
- }
303
- function avgBucketValues(bucket) {
304
- let total = 0;
305
- let count = 0;
306
- for (const ds of bucket.dataset ?? []) {
307
- for (const point of ds.point ?? []) {
308
- for (const v of point.value ?? []) {
309
- const num = typeof v.fpVal === "number" ? v.fpVal : typeof v.intVal === "number" ? v.intVal : null;
310
- if (num !== null && Number.isFinite(num)) {
311
- total += num;
312
- count += 1;
313
- }
314
- }
315
- }
316
- }
317
- return count > 0 ? total / count : void 0;
318
- }
319
- function dayBoundsMs(date) {
320
- const start = (/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getTime();
321
- if (!Number.isFinite(start)) {
322
- throw new HealthBridgeError(
323
- `Invalid date for Google Fit summary: ${date}`,
324
- "google-fit"
325
- );
326
- }
327
- return { startMs: start, endMs: start + 24 * 60 * 60 * 1e3 };
328
- }
329
- function enumerateGoogleFitDates(startAt, endAt) {
330
- const start = new Date(startAt);
331
- const end = new Date(endAt);
332
- const startMs = start.getTime();
333
- const endMs = end.getTime();
334
- if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {
335
- throw new HealthBridgeError(
336
- "Invalid time window for Google Fit data points",
337
- "google-fit"
338
- );
339
- }
340
- if (endMs < startMs) {
341
- return [];
342
- }
343
- const dates = [];
344
- const cursor = new Date(
345
- Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate())
346
- );
347
- const endCursorMs = Date.UTC(
348
- end.getUTCFullYear(),
349
- end.getUTCMonth(),
350
- end.getUTCDate()
351
- );
352
- while (cursor.getTime() <= endCursorMs) {
353
- dates.push(cursor.toISOString().slice(0, 10));
354
- cursor.setUTCDate(cursor.getUTCDate() + 1);
355
- }
356
- return dates;
357
- }
358
- async function googleFitDailySummary(date, accessToken) {
359
- const { startMs, endMs } = dayBoundsMs(date);
360
- const aggregateBy = [
361
- "steps",
362
- "active_minutes",
363
- "calories",
364
- "distance_meters",
365
- "heart_rate"
366
- ].map((k) => ({ dataTypeName: GOOGLE_FIT_DATA_TYPES[k].dataTypeName }));
367
- const response = await callGoogleFitAggregate(accessToken, {
368
- aggregateBy,
369
- bucketByTime: { durationMillis: endMs - startMs },
370
- startTimeMillis: startMs,
371
- endTimeMillis: endMs
372
- });
373
- const bucket = response.bucket?.[0];
374
- const summary = {
375
- date,
376
- steps: 0,
377
- activeMinutes: 0,
378
- sleepHours: 0,
379
- source: "google-fit"
380
- };
381
- if (!bucket) return summary;
382
- const datasets = bucket.dataset ?? [];
383
- const byType = (idx) => ({
384
- dataset: [datasets[idx]].filter(Boolean)
385
- });
386
- summary.steps = Math.round(sumBucketValues(byType(0)));
387
- summary.activeMinutes = Math.round(
388
- sumBucketValues(byType(1))
389
- );
390
- summary.calories = sumBucketValues(byType(2)) || void 0;
391
- summary.distanceMeters = sumBucketValues(byType(3)) || void 0;
392
- summary.heartRateAvg = avgBucketValues(byType(4));
393
- try {
394
- const sleepResponse = await callGoogleFitAggregate(accessToken, {
395
- aggregateBy: [{ dataTypeName: "com.google.sleep.segment" }],
396
- bucketByTime: { durationMillis: endMs - startMs },
397
- startTimeMillis: startMs,
398
- endTimeMillis: endMs
399
- });
400
- const sleepBucket = sleepResponse.bucket?.[0];
401
- if (sleepBucket) {
402
- let sleepMs = 0;
403
- for (const ds of sleepBucket.dataset ?? []) {
404
- for (const point of ds.point ?? []) {
405
- const startNs = Number(point.startTimeNanos ?? "0");
406
- const endNs = Number(point.endTimeNanos ?? "0");
407
- if (Number.isFinite(startNs) && Number.isFinite(endNs) && endNs > startNs) {
408
- sleepMs += (endNs - startNs) / 1e6;
409
- }
410
- }
411
- }
412
- summary.sleepHours = sleepMs / (1e3 * 60 * 60);
413
- }
414
- } catch (error) {
415
- logger.debug(
416
- { boundary: "lifeops", integration: "google-fit" },
417
- "[lifeops] Google Fit sleep aggregation failed"
418
- );
419
- void error;
420
- }
421
- return summary;
422
- }
423
- async function googleFitDataPoints(opts, accessToken) {
424
- if (opts.metric === "sleep_hours") {
425
- const points2 = [];
426
- for (const date of enumerateGoogleFitDates(opts.startAt, opts.endAt)) {
427
- const summary = await googleFitDailySummary(date, accessToken);
428
- if (summary.sleepHours <= 0) {
429
- continue;
430
- }
431
- points2.push({
432
- metric: "sleep_hours",
433
- value: summary.sleepHours,
434
- unit: "hours",
435
- startAt: `${date}T00:00:00.000Z`,
436
- endAt: `${date}T23:59:59.999Z`,
437
- source: "google-fit"
438
- });
439
- }
440
- return points2;
441
- }
442
- const keyMap = {
443
- steps: "steps",
444
- active_minutes: "active_minutes",
445
- heart_rate: "heart_rate",
446
- calories: "calories",
447
- distance_meters: "distance_meters"
448
- };
449
- const key = keyMap[opts.metric];
450
- const { dataTypeName, unit } = GOOGLE_FIT_DATA_TYPES[key];
451
- const startMs = new Date(opts.startAt).getTime();
452
- const endMs = new Date(opts.endAt).getTime();
453
- if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {
454
- throw new HealthBridgeError(
455
- "Invalid time window for Google Fit data points",
456
- "google-fit"
457
- );
458
- }
459
- const response = await callGoogleFitAggregate(accessToken, {
460
- aggregateBy: [{ dataTypeName }],
461
- bucketByTime: { durationMillis: 60 * 60 * 1e3 },
462
- startTimeMillis: startMs,
463
- endTimeMillis: endMs
464
- });
465
- const points = [];
466
- for (const bucket of response.bucket ?? []) {
467
- const value = sumBucketValues(bucket);
468
- if (value === 0) continue;
469
- const bucketStart = Number(bucket.startTimeMillis ?? "0");
470
- const bucketEnd = Number(bucket.endTimeMillis ?? "0");
471
- points.push({
472
- metric: opts.metric,
473
- value,
474
- unit,
475
- startAt: new Date(bucketStart).toISOString(),
476
- endAt: new Date(bucketEnd).toISOString(),
477
- source: "google-fit"
478
- });
479
- }
480
- return points;
481
- }
482
- async function getDailySummary(date, config) {
483
- const backend = await detectHealthBackend(config);
484
- if (backend === "fixture") {
485
- return fixtureSummaryForDate(date);
486
- }
487
- if (backend === "healthkit") {
488
- const cliPath = resolveHealthKitCliPath(config);
489
- if (!cliPath) {
490
- throw new HealthBridgeError(
491
- "HealthKit CLI path not available",
492
- "healthkit"
493
- );
494
- }
495
- return healthKitDailySummary(date, cliPath);
496
- }
497
- if (backend === "google-fit") {
498
- const token = resolveGoogleFitAccessToken(config);
499
- if (!token) {
500
- throw new HealthBridgeError(
501
- "Google Fit access token not available",
502
- "google-fit"
503
- );
504
- }
505
- return googleFitDailySummary(date, token);
506
- }
507
- throw new HealthBridgeError("no health backend available", "none");
508
- }
509
- async function getDataPoints(opts, config) {
510
- const backend = await detectHealthBackend(config);
511
- if (backend === "fixture") {
512
- return fixtureDataPoints(opts);
513
- }
514
- if (backend === "healthkit") {
515
- const cliPath = resolveHealthKitCliPath(config);
516
- if (!cliPath) {
517
- throw new HealthBridgeError(
518
- "HealthKit CLI helper not installed",
519
- "healthkit"
520
- );
521
- }
522
- return healthKitDataPoints(opts, cliPath);
523
- }
524
- if (backend === "google-fit") {
525
- const token = resolveGoogleFitAccessToken(config);
526
- if (!token) {
527
- throw new HealthBridgeError(
528
- "Google Fit access token not available",
529
- "google-fit"
530
- );
531
- }
532
- return googleFitDataPoints(opts, token);
533
- }
534
- throw new HealthBridgeError("no health backend available", "none");
535
- }
536
- async function getRecentSummaries(days, config) {
537
- if (!Number.isFinite(days) || days <= 0) {
538
- return [];
539
- }
540
- const out = [];
541
- const today = /* @__PURE__ */ new Date();
542
- for (let i = days - 1; i >= 0; i--) {
543
- const d = new Date(today);
544
- d.setUTCDate(today.getUTCDate() - i);
545
- const date = d.toISOString().slice(0, 10);
546
- const summary = await getDailySummary(date, config);
547
- out.push(summary);
548
- }
549
- return out;
550
- }
551
- export {
552
- HealthBridgeError,
553
- detectHealthBackend,
554
- getDailySummary,
555
- getDataPoints,
556
- getRecentSummaries
557
- };
558
- //# sourceMappingURL=health-bridge.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/health-bridge/health-bridge.ts"],"sourcesContent":["/**\n * Health / fitness bridge for LifeOps.\n *\n * Provides a uniform read-only surface over:\n * - HealthKit (macOS) via an external native helper binary invoked through\n * `execFile`. The helper is expected to emit JSON. The helper is not\n * shipped in this repo; until it is installed the bridge reports\n * `backend: \"none\"` for detection, but `getDailySummary` without any\n * configured backend throws `HealthBridgeError(\"no health backend\n * available\", \"none\")` so the caller can surface a clear status.\n * - Google Fit REST API as a cross-platform fallback, authenticated via an\n * OAuth access token supplied through config.\n *\n * Never log raw health values in plaintext.\n */\n\nimport { execFile } from \"node:child_process\";\nimport { accessSync, constants as fsConstants } from \"node:fs\";\nimport { promisify } from \"node:util\";\nimport { logger } from \"@elizaos/core\";\n\nconst execFileAsync = promisify(execFile);\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\nfunction rewriteGoogleUrlForMock(url: string): string {\n const mockBase = process.env.ELIZA_MOCK_GOOGLE_BASE;\n if (!mockBase) return url;\n const mockUrl = new URL(mockBase);\n if (![\"127.0.0.1\", \"localhost\", \"::1\", \"[::1]\"].includes(mockUrl.hostname)) {\n throw new HealthBridgeError(\n \"Google mock base must point to loopback for health mock tests.\",\n \"google-fit\",\n );\n }\n return url.replace(\n /^https:\\/\\/(?:fitness)\\.googleapis\\.com/,\n mockUrl.toString().replace(/\\/+$/, \"\"),\n );\n}\n\nexport type HealthBackend = \"healthkit\" | \"google-fit\" | \"fixture\" | \"none\";\n\nexport interface HealthDataPoint {\n metric:\n | \"steps\"\n | \"active_minutes\"\n | \"sleep_hours\"\n | \"heart_rate\"\n | \"calories\"\n | \"distance_meters\";\n value: number;\n unit: string;\n /** ISO-8601 timestamp. */\n startAt: string;\n /** ISO-8601 timestamp. */\n endAt: string;\n source: HealthBackend;\n}\n\nexport interface HealthDailySummary {\n /** Local day key, YYYY-MM-DD. */\n date: string;\n steps: number;\n activeMinutes: number;\n sleepHours: number;\n heartRateAvg?: number;\n calories?: number;\n distanceMeters?: number;\n source: HealthBackend;\n}\n\nexport interface HealthBridgeConfig {\n preferredBackend?: HealthBackend;\n /** Path to a native HealthKit helper binary that emits JSON on stdout. */\n healthKitCliPath?: string;\n /** OAuth2 access token for Google Fit. */\n googleFitAccessToken?: string;\n}\n\nexport class HealthBridgeError extends Error {\n constructor(\n message: string,\n public readonly backend: HealthBackend,\n ) {\n super(message);\n this.name = \"HealthBridgeError\";\n }\n}\n\nfunction isTruthyEnv(value: string | undefined): boolean {\n if (!value) return false;\n const normalized = value.trim().toLowerCase();\n return (\n normalized === \"1\" ||\n normalized === \"true\" ||\n normalized === \"yes\" ||\n normalized === \"on\" ||\n normalized === \"fixture\"\n );\n}\n\nfunction isFalsyEnv(value: string | undefined): boolean {\n if (!value) return false;\n const normalized = value.trim().toLowerCase();\n return (\n normalized === \"0\" ||\n normalized === \"false\" ||\n normalized === \"no\" ||\n normalized === \"off\"\n );\n}\n\nfunction isFixtureHealthBackendEnabled(): boolean {\n const explicit = process.env.ELIZA_TEST_HEALTH_BACKEND;\n if (isFalsyEnv(explicit)) return false;\n if (isTruthyEnv(explicit)) return true;\n return false;\n}\n\nfunction utcMidnightMs(date: string): number {\n const ms = Date.parse(`${date}T00:00:00.000Z`);\n if (!Number.isFinite(ms)) {\n throw new HealthBridgeError(\n `Invalid fixture health date: ${date}`,\n \"fixture\",\n );\n }\n return ms;\n}\n\nfunction todayDateKeyUtc(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\nfunction fixtureDayOffset(date: string): number {\n return Math.round(\n (utcMidnightMs(date) - utcMidnightMs(todayDateKeyUtc())) / ONE_DAY_MS,\n );\n}\n\nfunction fixtureSummaryForDate(date: string): HealthDailySummary {\n const offset = fixtureDayOffset(date);\n const distance = Math.abs(offset);\n const direction = offset < 0 ? 1 : -1;\n const steps = Math.max(\n 1500,\n 8420 + direction * distance * 260 + (distance % 3) * 175,\n );\n const activeMinutes = Math.max(\n 18,\n 63 + direction * distance * 4 + (distance % 2 === 0 ? 2 : -3),\n );\n const sleepHours = Math.max(5.2, 7.4 + direction * distance * 0.15);\n const heartRateAvg = Math.max(54, 62 - direction * distance);\n const calories = Math.max(1650, 2180 + direction * distance * 55);\n const distanceMeters = Math.max(1200, steps * 0.78);\n\n return {\n date,\n steps: Math.round(steps),\n activeMinutes: Math.round(activeMinutes),\n sleepHours,\n heartRateAvg,\n calories,\n distanceMeters,\n source: \"fixture\",\n };\n}\n\nfunction enumerateFixtureDates(startAt: string, endAt: string): string[] {\n const start = new Date(startAt);\n const end = new Date(endAt);\n const startMs = start.getTime();\n const endMs = end.getTime();\n if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {\n throw new HealthBridgeError(\n \"Invalid time window for fixture health data\",\n \"fixture\",\n );\n }\n if (endMs < startMs) {\n return [];\n }\n\n const dates: string[] = [];\n const cursor = new Date(\n Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate()),\n );\n const endCursorMs = Date.UTC(\n end.getUTCFullYear(),\n end.getUTCMonth(),\n end.getUTCDate(),\n );\n\n while (cursor.getTime() <= endCursorMs) {\n dates.push(cursor.toISOString().slice(0, 10));\n cursor.setUTCDate(cursor.getUTCDate() + 1);\n }\n return dates;\n}\n\nfunction fixturePointValue(\n summary: HealthDailySummary,\n metric: HealthDataPoint[\"metric\"],\n): { value: number; unit: string; startAt: string; endAt: string } {\n if (metric === \"sleep_hours\") {\n const startAt = `${summary.date}T00:30:00.000Z`;\n const endMs =\n Date.parse(startAt) + Math.round(summary.sleepHours * 60 * 60 * 1000);\n return {\n value: summary.sleepHours,\n unit: \"hours\",\n startAt,\n endAt: new Date(endMs).toISOString(),\n };\n }\n\n return {\n value:\n metric === \"steps\"\n ? summary.steps\n : metric === \"active_minutes\"\n ? summary.activeMinutes\n : metric === \"heart_rate\"\n ? (summary.heartRateAvg ?? 0)\n : metric === \"calories\"\n ? (summary.calories ?? 0)\n : (summary.distanceMeters ?? 0),\n unit:\n metric === \"steps\"\n ? \"count\"\n : metric === \"active_minutes\"\n ? \"minutes\"\n : metric === \"heart_rate\"\n ? \"bpm\"\n : metric === \"calories\"\n ? \"kcal\"\n : \"m\",\n startAt: `${summary.date}T00:00:00.000Z`,\n endAt: `${summary.date}T23:59:59.999Z`,\n };\n}\n\nfunction fixtureDataPoints(opts: {\n metric: HealthDataPoint[\"metric\"];\n startAt: string;\n endAt: string;\n}): HealthDataPoint[] {\n return enumerateFixtureDates(opts.startAt, opts.endAt)\n .map((date) => fixtureSummaryForDate(date))\n .map((summary) => {\n const point = fixturePointValue(summary, opts.metric);\n return {\n metric: opts.metric,\n value: point.value,\n unit: point.unit,\n startAt: point.startAt,\n endAt: point.endAt,\n source: \"fixture\" as const,\n };\n })\n .filter((point) => point.value > 0);\n}\n\n// ---------------------------------------------------------------------------\n// Backend detection\n// ---------------------------------------------------------------------------\n\nfunction isExecutable(path: string): boolean {\n try {\n accessSync(path, fsConstants.X_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction resolveHealthKitCliPath(config?: HealthBridgeConfig): string | null {\n const configured =\n config?.healthKitCliPath?.trim() ||\n process.env.ELIZA_HEALTHKIT_CLI_PATH?.trim();\n if (!configured) return null;\n if (!isExecutable(configured)) return null;\n return configured;\n}\n\nfunction resolveGoogleFitAccessToken(\n config?: HealthBridgeConfig,\n): string | null {\n const token =\n config?.googleFitAccessToken?.trim() ||\n process.env.ELIZA_GOOGLE_FIT_ACCESS_TOKEN?.trim();\n return token ? token : null;\n}\n\nexport async function detectHealthBackend(\n config?: HealthBridgeConfig,\n): Promise<HealthBackend> {\n if (config?.preferredBackend === \"none\") {\n return \"none\";\n }\n if (config?.preferredBackend === \"fixture\") {\n return \"fixture\";\n }\n if (isFixtureHealthBackendEnabled()) {\n return \"fixture\";\n }\n if (config?.preferredBackend) {\n if (\n config.preferredBackend === \"healthkit\" &&\n resolveHealthKitCliPath(config)\n ) {\n return \"healthkit\";\n }\n if (\n config.preferredBackend === \"google-fit\" &&\n resolveGoogleFitAccessToken(config)\n ) {\n return \"google-fit\";\n }\n }\n\n if (process.platform === \"darwin\" && resolveHealthKitCliPath(config)) {\n return \"healthkit\";\n }\n if (resolveGoogleFitAccessToken(config)) {\n return \"google-fit\";\n }\n return \"none\";\n}\n\n// ---------------------------------------------------------------------------\n// HealthKit backend — invokes external helper CLI.\n// ---------------------------------------------------------------------------\n\ninterface HealthKitDailyJson {\n date: string;\n steps?: number;\n activeMinutes?: number;\n sleepHours?: number;\n heartRateAvg?: number;\n calories?: number;\n distanceMeters?: number;\n}\n\ninterface HealthKitPointJson {\n metric: HealthDataPoint[\"metric\"];\n value: number;\n unit: string;\n startAt: string;\n endAt: string;\n}\n\nasync function invokeHealthKitCli(\n cliPath: string,\n args: string[],\n): Promise<unknown> {\n const { stdout } = await execFileAsync(cliPath, args, {\n timeout: 15_000,\n maxBuffer: 8 * 1024 * 1024,\n });\n return JSON.parse(stdout);\n}\n\nfunction finiteNumber(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\nfunction optionalFiniteNumber(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value)\n ? value\n : undefined;\n}\n\nasync function healthKitDailySummary(\n date: string,\n cliPath: string,\n): Promise<HealthDailySummary> {\n const raw = (await invokeHealthKitCli(cliPath, [\n \"daily\",\n \"--date\",\n date,\n ])) as HealthKitDailyJson;\n return {\n date: typeof raw.date === \"string\" ? raw.date : date,\n steps: finiteNumber(raw.steps),\n activeMinutes: finiteNumber(raw.activeMinutes),\n sleepHours: finiteNumber(raw.sleepHours),\n heartRateAvg: optionalFiniteNumber(raw.heartRateAvg),\n calories: optionalFiniteNumber(raw.calories),\n distanceMeters: optionalFiniteNumber(raw.distanceMeters),\n source: \"healthkit\",\n };\n}\n\nasync function healthKitDataPoints(\n opts: { metric: HealthDataPoint[\"metric\"]; startAt: string; endAt: string },\n cliPath: string,\n): Promise<HealthDataPoint[]> {\n const raw = (await invokeHealthKitCli(cliPath, [\n \"points\",\n \"--metric\",\n opts.metric,\n \"--start\",\n opts.startAt,\n \"--end\",\n opts.endAt,\n ])) as HealthKitPointJson[];\n if (!Array.isArray(raw)) return [];\n return raw.map((p) => ({\n metric: p.metric,\n value: finiteNumber(p.value),\n unit: typeof p.unit === \"string\" ? p.unit : \"\",\n startAt: p.startAt,\n endAt: p.endAt,\n source: \"healthkit\" as const,\n }));\n}\n\n// ---------------------------------------------------------------------------\n// Google Fit backend — REST via `fetch`.\n// ---------------------------------------------------------------------------\n\nconst GOOGLE_FIT_AGGREGATE_URL =\n \"https://www.googleapis.com/fitness/v1/users/me/dataset:aggregate\";\n\ntype GoogleFitMetricKey =\n | \"steps\"\n | \"active_minutes\"\n | \"calories\"\n | \"distance_meters\"\n | \"heart_rate\";\n\nconst GOOGLE_FIT_DATA_TYPES: Record<\n GoogleFitMetricKey,\n { dataTypeName: string; unit: string }\n> = {\n steps: {\n dataTypeName: \"com.google.step_count.delta\",\n unit: \"count\",\n },\n active_minutes: {\n dataTypeName: \"com.google.active_minutes\",\n unit: \"minutes\",\n },\n calories: {\n dataTypeName: \"com.google.calories.expended\",\n unit: \"kcal\",\n },\n distance_meters: {\n dataTypeName: \"com.google.distance.delta\",\n unit: \"m\",\n },\n heart_rate: {\n dataTypeName: \"com.google.heart_rate.bpm\",\n unit: \"bpm\",\n },\n};\n\ninterface GoogleFitAggregateResponse {\n bucket?: Array<{\n startTimeMillis?: string;\n endTimeMillis?: string;\n dataset?: Array<{\n point?: Array<{\n startTimeNanos?: string;\n endTimeNanos?: string;\n value?: Array<{ intVal?: number; fpVal?: number }>;\n }>;\n }>;\n }>;\n}\n\nasync function callGoogleFitAggregate(\n accessToken: string,\n body: Record<string, unknown>,\n): Promise<GoogleFitAggregateResponse> {\n const targetUrl = rewriteGoogleUrlForMock(GOOGLE_FIT_AGGREGATE_URL);\n const response = await fetch(targetUrl, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${accessToken}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n signal: AbortSignal.timeout(12_000),\n });\n if (!response.ok) {\n const status = response.status;\n throw new HealthBridgeError(\n `Google Fit request failed: HTTP ${status}`,\n \"google-fit\",\n );\n }\n return (await response.json()) as GoogleFitAggregateResponse;\n}\n\nfunction sumBucketValues(\n bucket: NonNullable<GoogleFitAggregateResponse[\"bucket\"]>[number],\n): number {\n let total = 0;\n for (const ds of bucket.dataset ?? []) {\n for (const point of ds.point ?? []) {\n for (const v of point.value ?? []) {\n if (typeof v.fpVal === \"number\" && Number.isFinite(v.fpVal)) {\n total += v.fpVal;\n } else if (typeof v.intVal === \"number\" && Number.isFinite(v.intVal)) {\n total += v.intVal;\n }\n }\n }\n }\n return total;\n}\n\nfunction avgBucketValues(\n bucket: NonNullable<GoogleFitAggregateResponse[\"bucket\"]>[number],\n): number | undefined {\n let total = 0;\n let count = 0;\n for (const ds of bucket.dataset ?? []) {\n for (const point of ds.point ?? []) {\n for (const v of point.value ?? []) {\n const num =\n typeof v.fpVal === \"number\"\n ? v.fpVal\n : typeof v.intVal === \"number\"\n ? v.intVal\n : null;\n if (num !== null && Number.isFinite(num)) {\n total += num;\n count += 1;\n }\n }\n }\n }\n return count > 0 ? total / count : undefined;\n}\n\nfunction dayBoundsMs(date: string): { startMs: number; endMs: number } {\n const start = new Date(`${date}T00:00:00Z`).getTime();\n if (!Number.isFinite(start)) {\n throw new HealthBridgeError(\n `Invalid date for Google Fit summary: ${date}`,\n \"google-fit\",\n );\n }\n return { startMs: start, endMs: start + 24 * 60 * 60 * 1000 };\n}\n\nfunction enumerateGoogleFitDates(startAt: string, endAt: string): string[] {\n const start = new Date(startAt);\n const end = new Date(endAt);\n const startMs = start.getTime();\n const endMs = end.getTime();\n if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {\n throw new HealthBridgeError(\n \"Invalid time window for Google Fit data points\",\n \"google-fit\",\n );\n }\n if (endMs < startMs) {\n return [];\n }\n\n const dates: string[] = [];\n const cursor = new Date(\n Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate()),\n );\n const endCursorMs = Date.UTC(\n end.getUTCFullYear(),\n end.getUTCMonth(),\n end.getUTCDate(),\n );\n while (cursor.getTime() <= endCursorMs) {\n dates.push(cursor.toISOString().slice(0, 10));\n cursor.setUTCDate(cursor.getUTCDate() + 1);\n }\n return dates;\n}\n\nasync function googleFitDailySummary(\n date: string,\n accessToken: string,\n): Promise<HealthDailySummary> {\n const { startMs, endMs } = dayBoundsMs(date);\n const aggregateBy = (\n [\n \"steps\",\n \"active_minutes\",\n \"calories\",\n \"distance_meters\",\n \"heart_rate\",\n ] as GoogleFitMetricKey[]\n ).map((k) => ({ dataTypeName: GOOGLE_FIT_DATA_TYPES[k].dataTypeName }));\n\n const response = await callGoogleFitAggregate(accessToken, {\n aggregateBy,\n bucketByTime: { durationMillis: endMs - startMs },\n startTimeMillis: startMs,\n endTimeMillis: endMs,\n });\n\n const bucket = response.bucket?.[0];\n const summary: HealthDailySummary = {\n date,\n steps: 0,\n activeMinutes: 0,\n sleepHours: 0,\n source: \"google-fit\",\n };\n if (!bucket) return summary;\n\n const datasets = bucket.dataset ?? [];\n const byType = (idx: number) => ({\n dataset: [datasets[idx]].filter(Boolean),\n });\n\n summary.steps = Math.round(sumBucketValues(byType(0) as typeof bucket));\n summary.activeMinutes = Math.round(\n sumBucketValues(byType(1) as typeof bucket),\n );\n summary.calories = sumBucketValues(byType(2) as typeof bucket) || undefined;\n summary.distanceMeters =\n sumBucketValues(byType(3) as typeof bucket) || undefined;\n summary.heartRateAvg = avgBucketValues(byType(4) as typeof bucket);\n\n // Google Fit sleep lives in a separate dataset; fetch it with a dedicated call.\n try {\n const sleepResponse = await callGoogleFitAggregate(accessToken, {\n aggregateBy: [{ dataTypeName: \"com.google.sleep.segment\" }],\n bucketByTime: { durationMillis: endMs - startMs },\n startTimeMillis: startMs,\n endTimeMillis: endMs,\n });\n const sleepBucket = sleepResponse.bucket?.[0];\n if (sleepBucket) {\n let sleepMs = 0;\n for (const ds of sleepBucket.dataset ?? []) {\n for (const point of ds.point ?? []) {\n const startNs = Number(point.startTimeNanos ?? \"0\");\n const endNs = Number(point.endTimeNanos ?? \"0\");\n if (\n Number.isFinite(startNs) &&\n Number.isFinite(endNs) &&\n endNs > startNs\n ) {\n sleepMs += (endNs - startNs) / 1_000_000;\n }\n }\n }\n summary.sleepHours = sleepMs / (1000 * 60 * 60);\n }\n } catch (error) {\n // Sleep is optional — record via debug without exposing values.\n logger.debug(\n { boundary: \"lifeops\", integration: \"google-fit\" },\n \"[lifeops] Google Fit sleep aggregation failed\",\n );\n void error;\n }\n\n return summary;\n}\n\nasync function googleFitDataPoints(\n opts: { metric: HealthDataPoint[\"metric\"]; startAt: string; endAt: string },\n accessToken: string,\n): Promise<HealthDataPoint[]> {\n if (opts.metric === \"sleep_hours\") {\n const points: HealthDataPoint[] = [];\n for (const date of enumerateGoogleFitDates(opts.startAt, opts.endAt)) {\n const summary = await googleFitDailySummary(date, accessToken);\n if (summary.sleepHours <= 0) {\n continue;\n }\n points.push({\n metric: \"sleep_hours\",\n value: summary.sleepHours,\n unit: \"hours\",\n startAt: `${date}T00:00:00.000Z`,\n endAt: `${date}T23:59:59.999Z`,\n source: \"google-fit\",\n });\n }\n return points;\n }\n const keyMap: Record<\n Exclude<HealthDataPoint[\"metric\"], \"sleep_hours\">,\n GoogleFitMetricKey\n > = {\n steps: \"steps\",\n active_minutes: \"active_minutes\",\n heart_rate: \"heart_rate\",\n calories: \"calories\",\n distance_meters: \"distance_meters\",\n };\n const key = keyMap[opts.metric];\n const { dataTypeName, unit } = GOOGLE_FIT_DATA_TYPES[key];\n\n const startMs = new Date(opts.startAt).getTime();\n const endMs = new Date(opts.endAt).getTime();\n if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) {\n throw new HealthBridgeError(\n \"Invalid time window for Google Fit data points\",\n \"google-fit\",\n );\n }\n\n const response = await callGoogleFitAggregate(accessToken, {\n aggregateBy: [{ dataTypeName }],\n bucketByTime: { durationMillis: 60 * 60 * 1000 },\n startTimeMillis: startMs,\n endTimeMillis: endMs,\n });\n\n const points: HealthDataPoint[] = [];\n for (const bucket of response.bucket ?? []) {\n const value = sumBucketValues(bucket);\n if (value === 0) continue;\n const bucketStart = Number(bucket.startTimeMillis ?? \"0\");\n const bucketEnd = Number(bucket.endTimeMillis ?? \"0\");\n points.push({\n metric: opts.metric,\n value,\n unit,\n startAt: new Date(bucketStart).toISOString(),\n endAt: new Date(bucketEnd).toISOString(),\n source: \"google-fit\",\n });\n }\n return points;\n}\n\n// ---------------------------------------------------------------------------\n// Public facade\n// ---------------------------------------------------------------------------\n\nexport async function getDailySummary(\n date: string,\n config?: HealthBridgeConfig,\n): Promise<HealthDailySummary> {\n const backend = await detectHealthBackend(config);\n if (backend === \"fixture\") {\n return fixtureSummaryForDate(date);\n }\n if (backend === \"healthkit\") {\n const cliPath = resolveHealthKitCliPath(config);\n if (!cliPath) {\n throw new HealthBridgeError(\n \"HealthKit CLI path not available\",\n \"healthkit\",\n );\n }\n return healthKitDailySummary(date, cliPath);\n }\n if (backend === \"google-fit\") {\n const token = resolveGoogleFitAccessToken(config);\n if (!token) {\n throw new HealthBridgeError(\n \"Google Fit access token not available\",\n \"google-fit\",\n );\n }\n return googleFitDailySummary(date, token);\n }\n throw new HealthBridgeError(\"no health backend available\", \"none\");\n}\n\nexport async function getDataPoints(\n opts: { metric: HealthDataPoint[\"metric\"]; startAt: string; endAt: string },\n config?: HealthBridgeConfig,\n): Promise<HealthDataPoint[]> {\n const backend = await detectHealthBackend(config);\n if (backend === \"fixture\") {\n return fixtureDataPoints(opts);\n }\n if (backend === \"healthkit\") {\n const cliPath = resolveHealthKitCliPath(config);\n if (!cliPath) {\n throw new HealthBridgeError(\n \"HealthKit CLI helper not installed\",\n \"healthkit\",\n );\n }\n return healthKitDataPoints(opts, cliPath);\n }\n if (backend === \"google-fit\") {\n const token = resolveGoogleFitAccessToken(config);\n if (!token) {\n throw new HealthBridgeError(\n \"Google Fit access token not available\",\n \"google-fit\",\n );\n }\n return googleFitDataPoints(opts, token);\n }\n throw new HealthBridgeError(\"no health backend available\", \"none\");\n}\n\nexport async function getRecentSummaries(\n days: number,\n config?: HealthBridgeConfig,\n): Promise<HealthDailySummary[]> {\n if (!Number.isFinite(days) || days <= 0) {\n return [];\n }\n const out: HealthDailySummary[] = [];\n const today = new Date();\n for (let i = days - 1; i >= 0; i--) {\n const d = new Date(today);\n d.setUTCDate(today.getUTCDate() - i);\n const date = d.toISOString().slice(0, 10);\n const summary = await getDailySummary(date, config);\n out.push(summary);\n }\n return out;\n}\n"],"mappings":"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","names":["points"]}
@@ -1,23 +0,0 @@
1
- import type { LifeOpsHealthMetricSample, LifeOpsHealthSleepEpisode, LifeOpsHealthWorkout } from "../contracts/health.js";
2
- import type { StoredHealthConnectorToken } from "./health-oauth.js";
3
- export declare class HealthConnectorApiError extends Error {
4
- readonly status: number;
5
- readonly provider: StoredHealthConnectorToken["provider"];
6
- constructor(status: number, provider: StoredHealthConnectorToken["provider"], message: string);
7
- }
8
- export interface HealthConnectorSyncPayload {
9
- samples: LifeOpsHealthMetricSample[];
10
- workouts: LifeOpsHealthWorkout[];
11
- sleepEpisodes: LifeOpsHealthSleepEpisode[];
12
- identity: Record<string, unknown> | null;
13
- cursor: string | null;
14
- }
15
- interface SyncArgs {
16
- token: StoredHealthConnectorToken;
17
- grantId: string;
18
- startDate: string;
19
- endDate: string;
20
- }
21
- export declare function syncHealthConnectorData(args: SyncArgs): Promise<HealthConnectorSyncPayload>;
22
- export {};
23
- //# sourceMappingURL=health-connectors.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"health-connectors.d.ts","sourceRoot":"","sources":["../../src/health-bridge/health-connectors.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,yBAAyB,EACzB,yBAAyB,EAEzB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,mBAAmB,CAAC;AAWpE,qBAAa,uBAAwB,SAAQ,KAAK;aAE9B,MAAM,EAAE,MAAM;aACd,QAAQ,EAAE,0BAA0B,CAAC,UAAU,CAAC;gBADhD,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,0BAA0B,CAAC,UAAU,CAAC,EAChE,OAAO,EAAE,MAAM;CAKlB;AAED,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,yBAAyB,EAAE,CAAC;IACrC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,aAAa,EAAE,yBAAyB,EAAE,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACzC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,UAAU,QAAQ;IAChB,KAAK,EAAE,0BAA0B,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB;AAulCD,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,QAAQ,GACb,OAAO,CAAC,0BAA0B,CAAC,CAsBrC"}