hae-vault 0.1.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 (160) hide show
  1. package/.env.example +7 -0
  2. package/CLAUDE.md +220 -0
  3. package/README.md +206 -0
  4. package/SKILL.md +60 -0
  5. package/dist/cli/dashboard.d.ts +3 -0
  6. package/dist/cli/dashboard.d.ts.map +1 -0
  7. package/dist/cli/dashboard.js +206 -0
  8. package/dist/cli/dashboard.js.map +1 -0
  9. package/dist/cli/import.d.ts +3 -0
  10. package/dist/cli/import.d.ts.map +1 -0
  11. package/dist/cli/import.js +78 -0
  12. package/dist/cli/import.js.map +1 -0
  13. package/dist/cli/index.d.ts +3 -0
  14. package/dist/cli/index.d.ts.map +1 -0
  15. package/dist/cli/index.js +31 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/cli/info.d.ts +5 -0
  18. package/dist/cli/info.d.ts.map +1 -0
  19. package/dist/cli/info.js +34 -0
  20. package/dist/cli/info.js.map +1 -0
  21. package/dist/cli/metrics.d.ts +3 -0
  22. package/dist/cli/metrics.d.ts.map +1 -0
  23. package/dist/cli/metrics.js +20 -0
  24. package/dist/cli/metrics.js.map +1 -0
  25. package/dist/cli/query.d.ts +3 -0
  26. package/dist/cli/query.d.ts.map +1 -0
  27. package/dist/cli/query.js +18 -0
  28. package/dist/cli/query.js.map +1 -0
  29. package/dist/cli/serve.d.ts +3 -0
  30. package/dist/cli/serve.d.ts.map +1 -0
  31. package/dist/cli/serve.js +19 -0
  32. package/dist/cli/serve.js.map +1 -0
  33. package/dist/cli/sleep.d.ts +3 -0
  34. package/dist/cli/sleep.d.ts.map +1 -0
  35. package/dist/cli/sleep.js +19 -0
  36. package/dist/cli/sleep.js.map +1 -0
  37. package/dist/cli/summary.d.ts +3 -0
  38. package/dist/cli/summary.d.ts.map +1 -0
  39. package/dist/cli/summary.js +53 -0
  40. package/dist/cli/summary.js.map +1 -0
  41. package/dist/cli/trends.d.ts +3 -0
  42. package/dist/cli/trends.d.ts.map +1 -0
  43. package/dist/cli/trends.js +77 -0
  44. package/dist/cli/trends.js.map +1 -0
  45. package/dist/cli/watch.d.ts +12 -0
  46. package/dist/cli/watch.d.ts.map +1 -0
  47. package/dist/cli/watch.js +89 -0
  48. package/dist/cli/watch.js.map +1 -0
  49. package/dist/cli/workouts.d.ts +3 -0
  50. package/dist/cli/workouts.d.ts.map +1 -0
  51. package/dist/cli/workouts.js +19 -0
  52. package/dist/cli/workouts.js.map +1 -0
  53. package/dist/config.d.ts +9 -0
  54. package/dist/config.d.ts.map +1 -0
  55. package/dist/config.js +25 -0
  56. package/dist/config.js.map +1 -0
  57. package/dist/db/importLog.d.ts +5 -0
  58. package/dist/db/importLog.d.ts.map +1 -0
  59. package/dist/db/importLog.js +10 -0
  60. package/dist/db/importLog.js.map +1 -0
  61. package/dist/db/metrics.d.ts +4 -0
  62. package/dist/db/metrics.d.ts.map +1 -0
  63. package/dist/db/metrics.js +14 -0
  64. package/dist/db/metrics.js.map +1 -0
  65. package/dist/db/schema.d.ts +5 -0
  66. package/dist/db/schema.d.ts.map +1 -0
  67. package/dist/db/schema.js +100 -0
  68. package/dist/db/schema.js.map +1 -0
  69. package/dist/db/sleep.d.ts +4 -0
  70. package/dist/db/sleep.d.ts.map +1 -0
  71. package/dist/db/sleep.js +13 -0
  72. package/dist/db/sleep.js.map +1 -0
  73. package/dist/db/workouts.d.ts +4 -0
  74. package/dist/db/workouts.d.ts.map +1 -0
  75. package/dist/db/workouts.js +11 -0
  76. package/dist/db/workouts.js.map +1 -0
  77. package/dist/index.d.ts +3 -0
  78. package/dist/index.d.ts.map +1 -0
  79. package/dist/index.js +5 -0
  80. package/dist/index.js.map +1 -0
  81. package/dist/parse/metrics.d.ts +17 -0
  82. package/dist/parse/metrics.d.ts.map +1 -0
  83. package/dist/parse/metrics.js +33 -0
  84. package/dist/parse/metrics.js.map +1 -0
  85. package/dist/parse/sleep.d.ts +23 -0
  86. package/dist/parse/sleep.d.ts.map +1 -0
  87. package/dist/parse/sleep.js +58 -0
  88. package/dist/parse/sleep.js.map +1 -0
  89. package/dist/parse/time.d.ts +4 -0
  90. package/dist/parse/time.d.ts.map +1 -0
  91. package/dist/parse/time.js +41 -0
  92. package/dist/parse/time.js.map +1 -0
  93. package/dist/parse/workouts.d.ts +17 -0
  94. package/dist/parse/workouts.d.ts.map +1 -0
  95. package/dist/parse/workouts.js +24 -0
  96. package/dist/parse/workouts.js.map +1 -0
  97. package/dist/server/app.d.ts +5 -0
  98. package/dist/server/app.d.ts.map +1 -0
  99. package/dist/server/app.js +39 -0
  100. package/dist/server/app.js.map +1 -0
  101. package/dist/server/ingest.d.ts +15 -0
  102. package/dist/server/ingest.d.ts.map +1 -0
  103. package/dist/server/ingest.js +41 -0
  104. package/dist/server/ingest.js.map +1 -0
  105. package/dist/types/hae.d.ts +103 -0
  106. package/dist/types/hae.d.ts.map +1 -0
  107. package/dist/types/hae.js +2 -0
  108. package/dist/types/hae.js.map +1 -0
  109. package/dist/util/zip.d.ts +3 -0
  110. package/dist/util/zip.d.ts.map +1 -0
  111. package/dist/util/zip.js +24 -0
  112. package/dist/util/zip.js.map +1 -0
  113. package/docs/COMMANDS.md +315 -0
  114. package/docs/plans/2026-02-18-hae-vault-initial-implementation.md +2015 -0
  115. package/docs/plans/2026-02-18-readme-dashboard-design.md +213 -0
  116. package/docs/plans/2026-02-18-readme-dashboard-plan.md +1306 -0
  117. package/docs/plans/2026-02-18-zip-env-watch-design.md +213 -0
  118. package/docs/plans/2026-02-18-zip-env-watch.md +966 -0
  119. package/package.json +57 -0
  120. package/src/cli/dashboard.ts +242 -0
  121. package/src/cli/import.ts +85 -0
  122. package/src/cli/index.ts +32 -0
  123. package/src/cli/info.ts +36 -0
  124. package/src/cli/metrics.ts +20 -0
  125. package/src/cli/query.ts +17 -0
  126. package/src/cli/serve.ts +18 -0
  127. package/src/cli/sleep.ts +19 -0
  128. package/src/cli/summary.ts +58 -0
  129. package/src/cli/trends.ts +103 -0
  130. package/src/cli/watch.ts +111 -0
  131. package/src/cli/workouts.ts +19 -0
  132. package/src/config.ts +28 -0
  133. package/src/db/importLog.ts +18 -0
  134. package/src/db/metrics.ts +15 -0
  135. package/src/db/schema.ts +105 -0
  136. package/src/db/sleep.ts +15 -0
  137. package/src/db/workouts.ts +13 -0
  138. package/src/index.ts +4 -0
  139. package/src/parse/metrics.ts +50 -0
  140. package/src/parse/sleep.ts +82 -0
  141. package/src/parse/time.ts +43 -0
  142. package/src/parse/workouts.ts +42 -0
  143. package/src/server/app.ts +46 -0
  144. package/src/server/ingest.ts +68 -0
  145. package/src/types/hae.ts +94 -0
  146. package/src/util/zip.ts +24 -0
  147. package/tests/cli-watch.test.ts +64 -0
  148. package/tests/db-import-log.test.ts +40 -0
  149. package/tests/db-metrics.test.ts +44 -0
  150. package/tests/db-schema.test.ts +55 -0
  151. package/tests/db-sleep.test.ts +36 -0
  152. package/tests/db-workouts.test.ts +34 -0
  153. package/tests/ingest.test.ts +99 -0
  154. package/tests/parse-metrics.test.ts +55 -0
  155. package/tests/parse-sleep.test.ts +65 -0
  156. package/tests/parse-time.test.ts +48 -0
  157. package/tests/parse-workouts.test.ts +43 -0
  158. package/tests/types.test.ts +27 -0
  159. package/tests/util-zip.test.ts +46 -0
  160. package/tsconfig.json +19 -0
@@ -0,0 +1,58 @@
1
+ import { parseHaeTime, toIso, toDateStr } from './time.js';
2
+ export function detectSleepVariant(dp) {
3
+ if ('startDate' in dp)
4
+ return 'detailed';
5
+ if ('core' in dp)
6
+ return 'aggregated_v2';
7
+ return 'aggregated_v1';
8
+ }
9
+ export function normalizeSleep(dp, target, sessionId) {
10
+ const variant = detectSleepVariant(dp);
11
+ if (variant === 'detailed') {
12
+ const raw = dp;
13
+ const start = parseHaeTime(raw.startDate);
14
+ return {
15
+ date: toDateStr(start),
16
+ sleep_start: toIso(start),
17
+ sleep_end: toIso(parseHaeTime(raw.endDate)),
18
+ in_bed_start: null, in_bed_end: null,
19
+ core_h: null, deep_h: null, rem_h: null, awake_h: null, asleep_h: null, in_bed_h: null,
20
+ schema_ver: 'detailed',
21
+ source: raw.source,
22
+ target,
23
+ meta: JSON.stringify(dp),
24
+ session_id: sessionId,
25
+ };
26
+ }
27
+ if (variant === 'aggregated_v2') {
28
+ const v2 = dp;
29
+ const sleepStart = parseHaeTime(v2.sleepStart);
30
+ return {
31
+ date: toDateStr(sleepStart),
32
+ sleep_start: toIso(sleepStart),
33
+ sleep_end: toIso(parseHaeTime(v2.sleepEnd)),
34
+ in_bed_start: null, in_bed_end: null,
35
+ core_h: v2.core, deep_h: v2.deep, rem_h: v2.rem, awake_h: v2.awake,
36
+ asleep_h: v2.asleep, in_bed_h: v2.inBed,
37
+ schema_ver: 'aggregated_v2',
38
+ source: v2.source,
39
+ target, meta: null, session_id: sessionId,
40
+ };
41
+ }
42
+ // aggregated_v1
43
+ const v1 = dp;
44
+ const sleepStart = parseHaeTime(v1.sleepStart);
45
+ return {
46
+ date: toDateStr(sleepStart),
47
+ sleep_start: toIso(sleepStart),
48
+ sleep_end: toIso(parseHaeTime(v1.sleepEnd)),
49
+ in_bed_start: v1.inBedStart ? toIso(parseHaeTime(v1.inBedStart)) : null,
50
+ in_bed_end: v1.inBedEnd ? toIso(parseHaeTime(v1.inBedEnd)) : null,
51
+ core_h: null, deep_h: null, rem_h: null, awake_h: null,
52
+ asleep_h: v1.asleep, in_bed_h: v1.inBed,
53
+ schema_ver: 'aggregated_v1',
54
+ source: v1.sleepSource ?? null,
55
+ target, meta: null, session_id: sessionId,
56
+ };
57
+ }
58
+ //# sourceMappingURL=sleep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../src/parse/sleep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function parseHaeTime(s: string): Date;
2
+ export declare function toIso(d: Date): string;
3
+ export declare function toDateStr(d: Date): string;
4
+ //# sourceMappingURL=time.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.d.ts","sourceRoot":"","sources":["../../src/parse/time.ts"],"names":[],"mappings":"AAiBA,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAiB5C;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAErC;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAEzC"}
@@ -0,0 +1,41 @@
1
+ // HAE outputs 5 different timestamp formats depending on iPhone locale.
2
+ function parse24h(date, h, m, s, tz) {
3
+ const tzFormatted = `${tz.slice(0, 3)}:${tz.slice(3)}`;
4
+ return new Date(`${date}T${h}:${m}:${s}${tzFormatted}`);
5
+ }
6
+ function parse12h(date, h, m, s, ampm, tz) {
7
+ let hour = parseInt(h, 10);
8
+ const isAm = ampm.toLowerCase() === 'am';
9
+ if (isAm && hour === 12)
10
+ hour = 0;
11
+ if (!isAm && hour !== 12)
12
+ hour += 12;
13
+ const hh = String(hour).padStart(2, '0');
14
+ const tzFormatted = `${tz.slice(0, 3)}:${tz.slice(3)}`;
15
+ return new Date(`${date}T${hh}:${m}:${s}${tzFormatted}`);
16
+ }
17
+ export function parseHaeTime(s) {
18
+ // Try 24-hour format: "2026-01-15 14:30:00 +0000"
19
+ const m24 = s.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}):(\d{2}):(\d{2}) ([+-]\d{4})$/);
20
+ if (m24) {
21
+ const d = parse24h(m24[1], m24[2], m24[3], m24[4], m24[5]);
22
+ if (!isNaN(d.getTime()))
23
+ return d;
24
+ }
25
+ // Try 12-hour format (space or narrow non-breaking space \u202f before AM/PM)
26
+ // "2026-01-15 2:30:00 PM +0000" or "2026-01-15 2:30:00\u202fPM +0000"
27
+ const m12 = s.match(/^(\d{4}-\d{2}-\d{2}) (\d{1,2}):(\d{2}):(\d{2})[\u202f ]([APap][Mm]) ([+-]\d{4})$/);
28
+ if (m12) {
29
+ const d = parse12h(m12[1], m12[2], m12[3], m12[4], m12[5], m12[6]);
30
+ if (!isNaN(d.getTime()))
31
+ return d;
32
+ }
33
+ throw new Error(`Failed to parse HAE timestamp: "${s}"`);
34
+ }
35
+ export function toIso(d) {
36
+ return d.toISOString();
37
+ }
38
+ export function toDateStr(d) {
39
+ return d.toISOString().slice(0, 10);
40
+ }
41
+ //# sourceMappingURL=time.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"time.js","sourceRoot":"","sources":["../../src/parse/time.ts"],"names":[],"mappings":"AAAA,wEAAwE;AAExE,SAAS,QAAQ,CAAC,IAAY,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,EAAU;IACzE,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,OAAO,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,WAAW,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY,EAAE,CAAS,EAAE,CAAS,EAAE,CAAS,EAAE,IAAY,EAAE,EAAU;IACvF,IAAI,IAAI,GAAG,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,KAAK,IAAI,CAAC;IACzC,IAAI,IAAI,IAAI,IAAI,KAAK,EAAE;QAAE,IAAI,GAAG,CAAC,CAAC;IAClC,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,EAAE;QAAE,IAAI,IAAI,EAAE,CAAC;IACrC,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzC,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,OAAO,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,WAAW,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,CAAS;IACpC,kDAAkD;IAClD,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;IACjF,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,8EAA8E;IAC9E,sEAAsE;IACtE,MAAM,GAAG,GAAG,CAAC,CAAC,KAAK,CAAC,kFAAkF,CAAC,CAAC;IACxG,IAAI,GAAG,EAAE,CAAC;QACR,MAAM,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,CAAO;IAC3B,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,CAAO;IAC/B,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { WorkoutData } from '../types/hae.js';
2
+ export interface NormalizedWorkout {
3
+ ts: string;
4
+ date: string;
5
+ name: string;
6
+ duration_s: number | null;
7
+ calories_kj: number | null;
8
+ distance: number | null;
9
+ distance_unit: string | null;
10
+ avg_hr: number | null;
11
+ max_hr: number | null;
12
+ target: string;
13
+ meta: string;
14
+ session_id: string | null;
15
+ }
16
+ export declare function parseWorkout(w: WorkoutData, target: string, sessionId: string | null): NormalizedWorkout;
17
+ //# sourceMappingURL=workouts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workouts.d.ts","sourceRoot":"","sources":["../../src/parse/workouts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,iBAAiB,CAuBxG"}
@@ -0,0 +1,24 @@
1
+ import { parseHaeTime, toIso, toDateStr } from './time.js';
2
+ export function parseWorkout(w, target, sessionId) {
3
+ const start = parseHaeTime(w.start);
4
+ const end = parseHaeTime(w.end);
5
+ const duration_s = Math.round((end.getTime() - start.getTime()) / 1000);
6
+ const hrValues = (w.heartRateData ?? []).map((h) => h.qty).filter((v) => typeof v === 'number');
7
+ const avg_hr = hrValues.length > 0 ? hrValues.reduce((a, b) => a + b, 0) / hrValues.length : null;
8
+ const max_hr = hrValues.length > 0 ? Math.max(...hrValues) : null;
9
+ return {
10
+ ts: toIso(start),
11
+ date: toDateStr(start),
12
+ name: w.name,
13
+ duration_s,
14
+ calories_kj: w.activeEnergyBurned?.qty ?? null,
15
+ distance: w.distance?.qty ?? null,
16
+ distance_unit: w.distance?.units ?? null,
17
+ avg_hr,
18
+ max_hr,
19
+ target,
20
+ meta: JSON.stringify(w),
21
+ session_id: sessionId,
22
+ };
23
+ }
24
+ //# sourceMappingURL=workouts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workouts.js","sourceRoot":"","sources":["../../src/parse/workouts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAkB3D,MAAM,UAAU,YAAY,CAAC,CAAc,EAAE,MAAc,EAAE,SAAwB;IACnF,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;IAExE,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IAC7G,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAClG,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAElE,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,KAAK,CAAC;QAChB,IAAI,EAAE,SAAS,CAAC,KAAK,CAAC;QACtB,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,UAAU;QACV,WAAW,EAAE,CAAC,CAAC,kBAAkB,EAAE,GAAG,IAAI,IAAI;QAC9C,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,GAAG,IAAI,IAAI;QACjC,aAAa,EAAE,CAAC,CAAC,QAAQ,EAAE,KAAK,IAAI,IAAI;QACxC,MAAM;QACN,MAAM;QACN,MAAM;QACN,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC;QACvB,UAAU,EAAE,SAAS;KACtB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type Database from 'better-sqlite3';
2
+ export declare function createApp(db: Database.Database, opts?: {
3
+ token?: string;
4
+ }): import("express-serve-static-core").Express;
5
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAI3C,wBAAgB,SAAS,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,+CAwC7E"}
@@ -0,0 +1,39 @@
1
+ import express from 'express';
2
+ import { ingest } from './ingest.js';
3
+ export function createApp(db, opts = {}) {
4
+ const app = express();
5
+ app.use(express.json({ limit: '50mb' }));
6
+ app.post('/api/ingest', (req, res) => {
7
+ // Optional bearer token auth
8
+ if (opts.token) {
9
+ const authHeader = req.headers['authorization'] ?? '';
10
+ const apiKey = req.headers['x-api-key'] ?? '';
11
+ const bearer = authHeader.startsWith('Bearer ') ? authHeader.slice(7) : '';
12
+ if (bearer !== opts.token && apiKey !== opts.token) {
13
+ res.status(401).json({ error: 'Unauthorized' });
14
+ return;
15
+ }
16
+ }
17
+ try {
18
+ const payload = req.body;
19
+ if (!payload?.data) {
20
+ res.status(400).json({ error: 'Missing data field' });
21
+ return;
22
+ }
23
+ ingest(db, payload, {
24
+ target: req.query['target'] ?? 'default',
25
+ sessionId: req.headers['session-id'] ?? null,
26
+ automationName: req.headers['automation-name'],
27
+ automationPeriod: req.headers['automation-period'],
28
+ });
29
+ res.json({ ok: true });
30
+ }
31
+ catch (err) {
32
+ console.error('Ingest error:', err);
33
+ res.status(400).json({ error: String(err) });
34
+ }
35
+ });
36
+ app.get('/health', (_req, res) => res.json({ status: 'ok' }));
37
+ return app;
38
+ }
39
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA,OAAO,OAAwC,MAAM,SAAS,CAAC;AAE/D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAGrC,MAAM,UAAU,SAAS,CAAC,EAAqB,EAAE,OAA2B,EAAE;IAC5E,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC;IAEzC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,CAAC,GAAY,EAAE,GAAa,EAAE,EAAE;QACtD,6BAA6B;QAC7B,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;YACtD,MAAM,MAAM,GAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAY,IAAI,EAAE,CAAC;YAC1D,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC3E,IAAI,MAAM,KAAK,IAAI,CAAC,KAAK,IAAI,MAAM,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC;gBACnD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;gBAChD,OAAO;YACT,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,GAAG,CAAC,IAAkB,CAAC;YACvC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC;gBACnB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBACtD,OAAO;YACT,CAAC;YAED,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE;gBAClB,MAAM,EAAG,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAY,IAAI,SAAS;gBACpD,SAAS,EAAG,GAAG,CAAC,OAAO,CAAC,YAAY,CAAY,IAAI,IAAI;gBACxD,cAAc,EAAE,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAuB;gBACpE,gBAAgB,EAAE,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAuB;aACzE,CAAC,CAAC;YAEH,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;YACpC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE9D,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { HaePayload } from '../types/hae.js';
3
+ export interface IngestOptions {
4
+ target: string;
5
+ sessionId: string | null;
6
+ automationName?: string;
7
+ automationPeriod?: string;
8
+ }
9
+ export interface IngestResult {
10
+ metricsAdded: number;
11
+ sleepAdded: number;
12
+ workoutsAdded: number;
13
+ }
14
+ export declare function ingest(db: Database.Database, payload: HaePayload, opts: IngestOptions): IngestResult;
15
+ //# sourceMappingURL=ingest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.d.ts","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,UAAU,EAAkB,MAAM,iBAAiB,CAAC;AAQlE,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,YAAY,CA6CpG"}
@@ -0,0 +1,41 @@
1
+ import { parseMetric } from '../parse/metrics.js';
2
+ import { normalizeSleep } from '../parse/sleep.js';
3
+ import { parseWorkout } from '../parse/workouts.js';
4
+ import { upsertMetrics } from '../db/metrics.js';
5
+ import { upsertSleep } from '../db/sleep.js';
6
+ import { upsertWorkout } from '../db/workouts.js';
7
+ export function ingest(db, payload, opts) {
8
+ const { target, sessionId } = opts;
9
+ const { data } = payload;
10
+ let metricsAdded = 0;
11
+ let sleepAdded = 0;
12
+ let workoutsAdded = 0;
13
+ // Process metrics
14
+ for (const m of data.metrics ?? []) {
15
+ if (m.name === 'sleep_analysis') {
16
+ for (const dp of m.data) {
17
+ const row = normalizeSleep(dp, target, sessionId);
18
+ upsertSleep(db, row);
19
+ sleepAdded++;
20
+ }
21
+ }
22
+ else {
23
+ const rows = parseMetric(m, target, sessionId);
24
+ upsertMetrics(db, rows);
25
+ metricsAdded += rows.length;
26
+ }
27
+ }
28
+ // Process workouts
29
+ for (const w of data.workouts ?? []) {
30
+ const row = parseWorkout(w, target, sessionId);
31
+ upsertWorkout(db, row);
32
+ workoutsAdded++;
33
+ }
34
+ // Log sync
35
+ db.prepare(`
36
+ INSERT INTO sync_log (received_at, target, session_id, metrics_count, workouts_count, automation_name, automation_period)
37
+ VALUES (?, ?, ?, ?, ?, ?, ?)
38
+ `).run(new Date().toISOString(), target, sessionId, metricsAdded, workoutsAdded, opts.automationName ?? null, opts.automationPeriod ?? null);
39
+ return { metricsAdded, sleepAdded, workoutsAdded };
40
+ }
41
+ //# sourceMappingURL=ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ingest.js","sourceRoot":"","sources":["../../src/server/ingest.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAelD,MAAM,UAAU,MAAM,CAAC,EAAqB,EAAE,OAAmB,EAAE,IAAmB;IACpF,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IAEzB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,aAAa,GAAG,CAAC,CAAC;IAEtB,kBAAkB;IAClB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,IAAI,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;YAChC,KAAK,MAAM,EAAE,IAAK,CAAC,CAAC,IAAyB,EAAE,CAAC;gBAC9C,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;gBAClD,WAAW,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;gBACrB,UAAU,EAAE,CAAC;YACf,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;YAC/C,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;YACxB,YAAY,IAAI,IAAI,CAAC,MAAM,CAAC;QAC9B,CAAC;IACH,CAAC;IAED,mBAAmB;IACnB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QACpC,MAAM,GAAG,GAAG,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;QAC/C,aAAa,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACvB,aAAa,EAAE,CAAC;IAClB,CAAC;IAED,WAAW;IACX,EAAE,CAAC,OAAO,CAAC;;;GAGV,CAAC,CAAC,GAAG,CACJ,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EACxB,MAAM,EACN,SAAS,EACT,YAAY,EACZ,aAAa,EACb,IAAI,CAAC,cAAc,IAAI,IAAI,EAC3B,IAAI,CAAC,gBAAgB,IAAI,IAAI,CAC9B,CAAC;IAEF,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,103 @@
1
+ export interface RawDatapoint {
2
+ date: string;
3
+ qty?: number;
4
+ Min?: number;
5
+ Avg?: number;
6
+ Max?: number;
7
+ systolic?: number;
8
+ diastolic?: number;
9
+ units?: string;
10
+ source?: string;
11
+ }
12
+ export interface SleepAnalysisRaw {
13
+ startDate: string;
14
+ endDate: string;
15
+ value: string;
16
+ source: string;
17
+ qty?: number;
18
+ }
19
+ export interface AggregatedSleepV2 {
20
+ sleepStart: string;
21
+ sleepEnd: string;
22
+ core: number;
23
+ deep: number;
24
+ rem: number;
25
+ awake: number;
26
+ asleep: number;
27
+ inBed: number;
28
+ source: string;
29
+ }
30
+ export interface AggregatedSleepV1 {
31
+ sleepStart: string;
32
+ sleepEnd: string;
33
+ inBedStart: string;
34
+ inBedEnd: string;
35
+ asleep: number;
36
+ inBed: number;
37
+ sleepSource?: string;
38
+ inBedSource?: string;
39
+ }
40
+ export type SleepDatapoint = SleepAnalysisRaw | AggregatedSleepV2 | AggregatedSleepV1;
41
+ export interface MetricData {
42
+ name: string;
43
+ units: string;
44
+ data: RawDatapoint[] | SleepDatapoint[];
45
+ }
46
+ export interface WorkoutData {
47
+ name: string;
48
+ start: string;
49
+ end: string;
50
+ duration?: number;
51
+ activeEnergyBurned?: {
52
+ qty: number;
53
+ units: string;
54
+ };
55
+ distance?: {
56
+ qty: number;
57
+ units: string;
58
+ };
59
+ heartRateData?: Array<{
60
+ date: string;
61
+ qty: number;
62
+ units: string;
63
+ }>;
64
+ heartRateRecovery?: Array<{
65
+ date: string;
66
+ qty: number;
67
+ units: string;
68
+ }>;
69
+ route?: Array<{
70
+ lat: number;
71
+ lon: number;
72
+ altitude: number;
73
+ timestamp: string;
74
+ }>;
75
+ elevation?: {
76
+ ascent: number;
77
+ descent: number;
78
+ units: string;
79
+ };
80
+ [key: string]: unknown;
81
+ }
82
+ export interface HaePayload {
83
+ data: {
84
+ metrics?: MetricData[];
85
+ workouts?: WorkoutData[];
86
+ stateOfMind?: unknown[];
87
+ medications?: unknown[];
88
+ symptoms?: unknown[];
89
+ cycleTracking?: unknown[];
90
+ ecg?: unknown[];
91
+ heartRateNotifications?: unknown[];
92
+ };
93
+ }
94
+ export interface HaeHeaders {
95
+ 'automation-name'?: string;
96
+ 'automation-id'?: string;
97
+ 'automation-aggregation'?: string;
98
+ 'automation-period'?: string;
99
+ 'session-id'?: string;
100
+ 'authorization'?: string;
101
+ 'x-api-key'?: string;
102
+ }
103
+ //# sourceMappingURL=hae.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hae.d.ts","sourceRoot":"","sources":["../../src/types/hae.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hae.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hae.js","sourceRoot":"","sources":["../../src/types/hae.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { HaePayload } from '../types/hae.js';
2
+ export declare function extractPayloadFromZip(buf: Buffer): HaePayload | null;
3
+ //# sourceMappingURL=zip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip.d.ts","sourceRoot":"","sources":["../../src/util/zip.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIlD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAkBpE"}
@@ -0,0 +1,24 @@
1
+ import AdmZip from 'adm-zip';
2
+ const HAE_JSON_PATTERN = /HealthAutoExport.*\.json$/i;
3
+ export function extractPayloadFromZip(buf) {
4
+ try {
5
+ const zip = new AdmZip(buf);
6
+ const entry = zip.getEntries().find(e => HAE_JSON_PATTERN.test(e.entryName));
7
+ if (!entry)
8
+ return null;
9
+ let payload;
10
+ try {
11
+ payload = JSON.parse(entry.getData().toString('utf-8'));
12
+ }
13
+ catch {
14
+ return null;
15
+ }
16
+ if (!payload || typeof payload !== 'object' || !('data' in payload))
17
+ return null;
18
+ return payload;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ //# sourceMappingURL=zip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip.js","sourceRoot":"","sources":["../../src/util/zip.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,SAAS,CAAC;AAG7B,MAAM,gBAAgB,GAAG,4BAA4B,CAAC;AAEtD,MAAM,UAAU,qBAAqB,CAAC,GAAW;IAC/C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG,GAAG,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAC7E,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QAExB,IAAI,OAAgB,CAAC;QACrB,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QACjF,OAAO,OAAqB,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}