hae-vault 0.1.0 → 0.2.1

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 (61) hide show
  1. package/README.md +12 -1
  2. package/dist/cli/index.d.ts.map +1 -1
  3. package/dist/cli/index.js +3 -1
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/cli/start.d.ts +3 -0
  6. package/dist/cli/start.d.ts.map +1 -0
  7. package/dist/cli/start.js +22 -0
  8. package/dist/cli/start.js.map +1 -0
  9. package/dist/config.js +1 -1
  10. package/dist/config.js.map +1 -1
  11. package/package.json +5 -1
  12. package/.env.example +0 -7
  13. package/CLAUDE.md +0 -220
  14. package/SKILL.md +0 -60
  15. package/docs/COMMANDS.md +0 -315
  16. package/docs/plans/2026-02-18-hae-vault-initial-implementation.md +0 -2015
  17. package/docs/plans/2026-02-18-readme-dashboard-design.md +0 -213
  18. package/docs/plans/2026-02-18-readme-dashboard-plan.md +0 -1306
  19. package/docs/plans/2026-02-18-zip-env-watch-design.md +0 -213
  20. package/docs/plans/2026-02-18-zip-env-watch.md +0 -966
  21. package/src/cli/dashboard.ts +0 -242
  22. package/src/cli/import.ts +0 -85
  23. package/src/cli/index.ts +0 -32
  24. package/src/cli/info.ts +0 -36
  25. package/src/cli/metrics.ts +0 -20
  26. package/src/cli/query.ts +0 -17
  27. package/src/cli/serve.ts +0 -18
  28. package/src/cli/sleep.ts +0 -19
  29. package/src/cli/summary.ts +0 -58
  30. package/src/cli/trends.ts +0 -103
  31. package/src/cli/watch.ts +0 -111
  32. package/src/cli/workouts.ts +0 -19
  33. package/src/config.ts +0 -28
  34. package/src/db/importLog.ts +0 -18
  35. package/src/db/metrics.ts +0 -15
  36. package/src/db/schema.ts +0 -105
  37. package/src/db/sleep.ts +0 -15
  38. package/src/db/workouts.ts +0 -13
  39. package/src/index.ts +0 -4
  40. package/src/parse/metrics.ts +0 -50
  41. package/src/parse/sleep.ts +0 -82
  42. package/src/parse/time.ts +0 -43
  43. package/src/parse/workouts.ts +0 -42
  44. package/src/server/app.ts +0 -46
  45. package/src/server/ingest.ts +0 -68
  46. package/src/types/hae.ts +0 -94
  47. package/src/util/zip.ts +0 -24
  48. package/tests/cli-watch.test.ts +0 -64
  49. package/tests/db-import-log.test.ts +0 -40
  50. package/tests/db-metrics.test.ts +0 -44
  51. package/tests/db-schema.test.ts +0 -55
  52. package/tests/db-sleep.test.ts +0 -36
  53. package/tests/db-workouts.test.ts +0 -34
  54. package/tests/ingest.test.ts +0 -99
  55. package/tests/parse-metrics.test.ts +0 -55
  56. package/tests/parse-sleep.test.ts +0 -65
  57. package/tests/parse-time.test.ts +0 -48
  58. package/tests/parse-workouts.test.ts +0 -43
  59. package/tests/types.test.ts +0 -27
  60. package/tests/util-zip.test.ts +0 -46
  61. package/tsconfig.json +0 -19
package/README.md CHANGED
@@ -21,6 +21,9 @@ hvault summary --color # N-day averages with emoji indicators
21
21
 
22
22
  1. Install the [Health Auto Export](https://www.healthexportapp.com) iOS app
23
23
  2. In HAE: Settings → REST API → set server URL to `http://your-server:4242/api/ingest`
24
+ - Endpoint: `POST /api/ingest` (JSON body, max 50mb)
25
+ - Health check: `GET /health` → `{"status":"ok"}`
26
+ - Optional: append `?target=me` to tag data by device/person
24
27
  3. Or: export a ZIP from HAE and run `hvault import export.zip`
25
28
 
26
29
  **Optional:** create `.env` in your working directory to override defaults:
@@ -29,7 +32,9 @@ hvault summary --color # N-day averages with emoji indicators
29
32
  HVAULT_DB_PATH=~/.hae-vault/health.db # SQLite DB location
30
33
  HVAULT_PORT=4242 # ingest server port
31
34
  HVAULT_TOKEN=secret # bearer token for serve (optional)
35
+ HVAULT_AUTH=secret # alias for HVAULT_TOKEN (use if _TOKEN is blocked)
32
36
  HVAULT_WATCH_DIR=~/Downloads # directory to watch for exports
37
+ HVAULT_WATCH_INTERVAL=60 # watch poll interval in seconds (default: 60)
33
38
  ```
34
39
 
35
40
  ## Commands
@@ -38,10 +43,15 @@ HVAULT_WATCH_DIR=~/Downloads # directory to watch for exports
38
43
 
39
44
  | Command | Description |
40
45
  | --- | --- |
41
- | `hvault serve` | Start HTTP server, receive HAE REST API pushes |
46
+ | `hvault start` | Start server + watcher based on env vars (recommended for Docker/daemons) |
47
+ | `hvault serve` | Start HTTP server only |
42
48
  | `hvault import <file>` | Import HAE JSON or ZIP export (idempotent) |
43
49
  | `hvault watch` | Watch directory and auto-import new HAE exports |
44
50
 
51
+ > **`watch` file matching:** only files matching `/^HealthAutoExport.*\.(zip|json)$/i` are picked up.
52
+ > The Health Auto Export app names files this way by default (e.g. `HealthAutoExport-2020-2025.zip`).
53
+ > Files with any other name are silently ignored.
54
+
45
55
  ### Query
46
56
 
47
57
  Output is JSON by default. Add `--pretty` for formatted JSON.
@@ -168,6 +178,7 @@ Load order: CLI flag > env var > `.env` file > default.
168
178
  HVAULT_DB_PATH=~/.hae-vault/health.db # SQLite DB location
169
179
  HVAULT_PORT=4242 # serve port
170
180
  HVAULT_TOKEN=secret # bearer token for serve
181
+ HVAULT_AUTH=secret # alias for HVAULT_TOKEN (use if _TOKEN is blocked by your env)
171
182
  HVAULT_WATCH_DIR=~/Downloads # directory to watch
172
183
  HVAULT_WATCH_INTERVAL=60 # watch poll interval (seconds)
173
184
  HVAULT_TARGET=default # default target name
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,eAAO,MAAM,OAAO,SAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,eAAO,MAAM,OAAO,SAAgB,CAAC"}
package/dist/cli/index.js CHANGED
@@ -10,11 +10,13 @@ import { importCommand } from './import.js';
10
10
  import { watchCommand } from './watch.js';
11
11
  import { dashboardCommand } from './dashboard.js';
12
12
  import { trendsCommand } from './trends.js';
13
+ import { startCommand } from './start.js';
13
14
  export const program = new Command();
14
15
  program
15
16
  .name('hvault')
16
17
  .description('Apple Health data vault — ingest + query')
17
- .version('0.1.0');
18
+ .version('0.2.0');
19
+ program.addCommand(startCommand);
18
20
  program.addCommand(serveCommand);
19
21
  program.addCommand(importCommand);
20
22
  program.addCommand(watchCommand);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AACrC,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AACrC,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,0CAA0C,CAAC;KACvD,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,cAAc,CAAC,CAAC;AACnC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const startCommand: Command;
3
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/cli/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,YAAY,SAiBrB,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { Command } from 'commander';
2
+ import { createApp } from '../server/app.js';
3
+ import { openDb } from '../db/schema.js';
4
+ import { tick } from './watch.js';
5
+ import { config } from '../config.js';
6
+ export const startCommand = new Command('start')
7
+ .description('Start server and watcher based on environment variables (HVAULT_PORT, HVAULT_WATCH_DIR)')
8
+ .action(() => {
9
+ const db = openDb(config.dbPath);
10
+ // Always start HTTP server
11
+ const app = createApp(db, { token: config.token });
12
+ app.listen(config.port, () => {
13
+ console.log(JSON.stringify({ server: `http://0.0.0.0:${config.port}/api/ingest`, auth: !!config.token }));
14
+ });
15
+ // Start watcher only if HVAULT_WATCH_DIR is set
16
+ if (config.watchDir) {
17
+ console.log(JSON.stringify({ watching: config.watchDir, intervalSeconds: config.watchInterval, target: config.target }));
18
+ tick(db, config.watchDir, config.target);
19
+ setInterval(() => tick(db, config.watchDir, config.target), config.watchInterval * 1000);
20
+ }
21
+ });
22
+ //# sourceMappingURL=start.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.js","sourceRoot":"","sources":["../../src/cli/start.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,yFAAyF,CAAC;KACtG,MAAM,CAAC,GAAG,EAAE;IACX,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAEjC,2BAA2B;IAC3B,MAAM,GAAG,GAAG,SAAS,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;IACnD,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QAC3B,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,kBAAkB,MAAM,CAAC,IAAI,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAC5G,CAAC,CAAC,CAAC;IAEH,gDAAgD;IAChD,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QACzH,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;QACzC,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,QAAS,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,aAAa,GAAG,IAAI,CAAC,CAAC;IAC5F,CAAC;AACH,CAAC,CAAC,CAAC"}
package/dist/config.js CHANGED
@@ -17,7 +17,7 @@ const DEFAULT_DB_PATH = join(homedir(), '.hae-vault', 'health.db');
17
17
  export const config = {
18
18
  dbPath: expandTilde(process.env.HVAULT_DB_PATH ?? DEFAULT_DB_PATH),
19
19
  port: Number(process.env.HVAULT_PORT ?? 4242),
20
- token: process.env.HVAULT_TOKEN,
20
+ token: process.env.HVAULT_TOKEN ?? process.env.HVAULT_AUTH,
21
21
  watchDir: process.env.HVAULT_WATCH_DIR ? expandTilde(process.env.HVAULT_WATCH_DIR) : undefined,
22
22
  watchInterval: Number(process.env.HVAULT_WATCH_INTERVAL ?? 60),
23
23
  target: process.env.HVAULT_TARGET ?? 'default',
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,qEAAqE;AACrE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;AAC3E,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,MAAM,EAAS,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,eAAe,CAAC;IACzE,IAAI,EAAW,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC;IACtD,KAAK,EAAU,OAAO,CAAC,GAAG,CAAC,YAAY;IACvC,QAAQ,EAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS;IACnG,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC;IAC9D,MAAM,EAAS,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,SAAS;CAC7C,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,SAAS,WAAW,CAAC,CAAS;IAC5B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,OAAO,OAAO,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAChC,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,qEAAqE;AACrE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;AAC3E,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;IACxB,UAAU,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,EAAE,WAAW,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,MAAM,GAAG;IACpB,MAAM,EAAS,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,eAAe,CAAC;IACzE,IAAI,EAAW,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,IAAI,CAAC;IACtD,KAAK,EAAU,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW;IAClE,QAAQ,EAAO,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS;IACnG,aAAa,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,EAAE,CAAC;IAC9D,MAAM,EAAS,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,SAAS;CAC7C,CAAC"}
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "hae-vault",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "CLI + HTTP server for Apple Health data from Health Auto Export",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "hvault": "./dist/index.js"
8
8
  },
9
9
  "main": "./dist/index.js",
10
+ "files": [
11
+ "dist/",
12
+ "README.md"
13
+ ],
10
14
  "scripts": {
11
15
  "dev": "tsx src/index.ts",
12
16
  "build": "tsc",
package/.env.example DELETED
@@ -1,7 +0,0 @@
1
- HVAULT_DB_PATH=~/.hae-vault/health.db
2
- HVAULT_PORT=4242
3
- HVAULT_TOKEN=
4
- HVAULT_WATCH_DIR=~/Downloads
5
- HVAULT_WATCH_INTERVAL=60
6
- HVAULT_TARGET=default
7
- HVAULT_ENV_FILE=
package/CLAUDE.md DELETED
@@ -1,220 +0,0 @@
1
- # hae-vault — Claude Code Guide
2
-
3
- ## Project Identity
4
-
5
- - **npm package name:** `hae-vault`
6
- - **CLI command:** `hvault`
7
- - **Install:** `npm install -g hae-vault`
8
- - **Language:** TypeScript (Node.js 22+, ESM, NodeNext)
9
- - **Status:** Implemented and working — 570k+ rows ingested from real Apple Health data
10
-
11
- ---
12
-
13
- ## What This Project Does
14
-
15
- `hae-vault` is an npm package with three responsibilities:
16
-
17
- 1. **Server** (`hvault serve`): HTTP server that receives health data pushed from the **Health Auto Export** iOS app. POST to `/api/ingest`, writes to local SQLite.
18
-
19
- 2. **Import** (`hvault import <file>`): Bulk import from a HAE JSON or ZIP export file. Idempotent via SHA-256 hash tracking — skips already-imported files.
20
-
21
- 3. **Watch** (`hvault watch`): Polls a directory for new HAE export files, auto-imports on schedule. Configurable via env vars.
22
-
23
- 4. **CLI** (`hvault <command>`): Query interface for AI agents (OpenClaw). Returns JSON.
24
-
25
- ---
26
-
27
- ## Ecosystem Context
28
-
29
- ```
30
- OpenClaw (AI agent platform, remote server)
31
- ├── skill: whoop-up ← existing, WHOOP wearable, live API calls
32
- ├── skill: hae-vault ← Apple Health archive, queries SQLite
33
-
34
- ├── whoop-up CLI ← npm install -g whoop-up (already published)
35
- └── hvault CLI ← npm install -g hae-vault (local install)
36
- └── reads ~/.hae-vault/health.db (SQLite)
37
-
38
- iPhone (Health Auto Export app)
39
- └── REST API automation → POST http://server:4242/api/ingest
40
- └── writes to health.db via hvault serve
41
-
42
- OR
43
-
44
- HAE Manual Export → .zip file → hvault import export.zip
45
- ```
46
-
47
- ---
48
-
49
- ## Source Structure
50
-
51
- ```
52
- hae-vault/
53
- ├── src/
54
- │ ├── index.ts ← entry point, Commander CLI
55
- │ ├── config.ts ← env config singleton (dotenv + HVAULT_* vars)
56
- │ ├── server/
57
- │ │ ├── app.ts ← Express HTTP server
58
- │ │ └── ingest.ts ← parse payload → write to DB, returns IngestResult
59
- │ ├── db/
60
- │ │ ├── schema.ts ← SQLite schema + openDb() + closeDb()
61
- │ │ ├── metrics.ts ← upsertMetrics()
62
- │ │ ├── sleep.ts ← upsertSleep()
63
- │ │ ├── workouts.ts ← upsertWorkout()
64
- │ │ └── importLog.ts ← hasBeenImported() + logImport() (SHA-256 dedup)
65
- │ ├── parse/
66
- │ │ ├── time.ts ← 5-format date parser
67
- │ │ ├── metrics.ts ← MetricData[] → NormalizedMetric[]
68
- │ │ ├── sleep.ts ← detect 3 variants, normalizeSleep()
69
- │ │ └── workouts.ts ← WorkoutData[] → NormalizedWorkout
70
- │ ├── util/
71
- │ │ └── zip.ts ← extractPayloadFromZip() — adm-zip, finds HealthAutoExport-*.json
72
- │ ├── cli/
73
- │ │ ├── index.ts ← program + all command registrations
74
- │ │ ├── serve.ts ← hvault serve
75
- │ │ ├── import.ts ← hvault import <file> (JSON or ZIP, dedup via import_log)
76
- │ │ ├── watch.ts ← hvault watch (polls dir, auto-imports, exports tick())
77
- │ │ ├── metrics.ts ← hvault metrics --metric <name> --days N
78
- │ │ ├── sleep.ts ← hvault sleep --days N
79
- │ │ ├── workouts.ts ← hvault workouts --days N
80
- │ │ ├── summary.ts ← hvault summary --days N
81
- │ │ ├── query.ts ← hvault query "<sql>"
82
- │ │ └── info.ts ← hvault sources | last-sync | stats
83
- │ └── types/
84
- │ └── hae.ts ← TypeScript interfaces for HAE payload
85
- ├── tests/
86
- │ ├── types.test.ts
87
- │ ├── parse-time.test.ts
88
- │ ├── parse-sleep.test.ts
89
- │ ├── parse-metrics.test.ts
90
- │ ├── parse-workouts.test.ts
91
- │ ├── db-schema.test.ts
92
- │ ├── db-metrics.test.ts
93
- │ ├── db-sleep.test.ts
94
- │ ├── db-workouts.test.ts
95
- │ ├── db-import-log.test.ts
96
- │ ├── ingest.test.ts
97
- │ ├── util-zip.test.ts
98
- │ └── cli-watch.test.ts
99
- ├── docs/plans/ ← design + implementation plan docs
100
- ├── CLAUDE.md
101
- ├── SKILL.md ← OpenClaw skill definition
102
- ├── package.json ← bin: { "hvault": "dist/index.js" }
103
- └── tsconfig.json
104
- ```
105
-
106
- ---
107
-
108
- ## CLI Commands
109
-
110
- ```bash
111
- # Server
112
- hvault serve # start HTTP ingest server (default port 4242)
113
- hvault serve --port 4242 # custom port
114
- hvault serve --token <secret> # require Authorization: Bearer <secret>
115
-
116
- # Import from file (JSON or ZIP)
117
- hvault import export.json # import HAE JSON export
118
- hvault import export.zip # import HAE zip (extracts HealthAutoExport-*.json)
119
- hvault import export.zip --target me # tag with device/person name
120
-
121
- # Watch directory for new exports
122
- hvault watch # uses HVAULT_WATCH_DIR env var
123
- hvault watch --dir ~/Downloads # watch specific directory
124
- hvault watch --interval 60 # check every 60 seconds
125
-
126
- # Query (all return JSON, --pretty for formatted)
127
- hvault metrics --metric step_count --days 30
128
- hvault metrics --metric heart_rate --days 7
129
- hvault sleep --days 14
130
- hvault workouts --days 30
131
- hvault summary --days 90
132
- hvault query "<sql>" # raw SQL → JSON
133
- hvault sources # metric coverage in DB
134
- hvault last-sync # last REST API push received
135
- hvault stats # row counts per table
136
- ```
137
-
138
- ---
139
-
140
- ## Environment Variables
141
-
142
- Load order: CLI flag > env var > `.env` file > hardcoded default.
143
-
144
- In Docker: set env vars directly — no `.env` file needed.
145
-
146
- ```bash
147
- HVAULT_ENV_FILE=/path/to/.env # override .env file location (default: CWD/.env)
148
- HVAULT_DB_PATH=~/.hae-vault/health.db # SQLite DB location (tilde expanded)
149
- HVAULT_PORT=4242 # serve port
150
- HVAULT_TOKEN=secret # bearer token for serve
151
- HVAULT_WATCH_DIR=~/Downloads # directory to watch for exports (tilde expanded)
152
- HVAULT_WATCH_INTERVAL=60 # watch poll interval (seconds)
153
- HVAULT_TARGET=default # default target name
154
- ```
155
-
156
- ---
157
-
158
- ## SQLite Schema
159
-
160
- Database: `~/.hae-vault/health.db`
161
-
162
- ### Tables
163
- - `metrics` — all health metrics (steps, HR, HRV, etc.) `UNIQUE(ts, metric, source, target)`
164
- - `sleep` — nightly sleep records (3 schema variants handled) `UNIQUE(date, source, target)`
165
- - `workouts` — workout sessions `UNIQUE(ts, name, target)`
166
- - `sync_log` — REST API push history
167
- - `import_log` — file import history with SHA-256 hash for deduplication `UNIQUE(file_hash)`
168
-
169
- ### Key implementation notes
170
- - **WAL mode** — concurrent reads while server writes
171
- - **`INSERT OR REPLACE`** — idempotent upserts throughout
172
- - **`import_log` hash check** — skip re-importing identical files
173
- - **DB path** from `HVAULT_DB_PATH` env or `~/.hae-vault/health.db`
174
-
175
- ---
176
-
177
- ## Payload Format (HAE REST API)
178
-
179
- Top-level:
180
- ```json
181
- { "data": { "metrics": [], "workouts": [], "stateOfMind": [], ... } }
182
- ```
183
-
184
- ### Date format — 5 variants (all handled by `src/parse/time.ts`)
185
- ```
186
- "2026-01-15 14:30:00 +0000" ← 24-hour
187
- "2026-01-15 2:30:00 PM +0000" ← 12-hour uppercase
188
- "2026-01-15 2:30:00 pm +0000" ← 12-hour lowercase
189
- "2026-01-15 2:30:00\u202fPM +0000" ← narrow non-breaking space before PM
190
- "2026-01-15 2:30:00\u202fpm +0000" ← narrow non-breaking space before pm
191
- ```
192
-
193
- ### Sleep — 3 schema variants (detected by `src/parse/sleep.ts`)
194
- - `detailed` — has `startDate`/`endDate` (non-aggregated phases)
195
- - `aggregated_v2` — has `core`/`deep`/`rem` + `source` (HAE >= 6.6.2)
196
- - `aggregated_v1` — has `sleepSource`/`inBedSource` (HAE < 6.6.2)
197
-
198
- Detection: `'startDate' in dp` → detailed | `'core' in dp` → v2 | else → v1
199
-
200
- ---
201
-
202
- ## Development
203
-
204
- ```bash
205
- npm install
206
- npm run dev -- serve # run server without building
207
- npm run build # compile TypeScript → dist/
208
- npm test # run all tests (59 passing)
209
- npm install -g . # install globally as hvault
210
- ```
211
-
212
- ---
213
-
214
- ## Reference Projects
215
-
216
- Located at `/Volumes/storage/01_Projects/whoop/healthy/`:
217
-
218
- - `health-auto-export-server/` — TypeScript/MongoDB reference. Useful: `MetricName.ts`, `Metric.ts`
219
- - `apple-health-ingester/` — Go reference. Useful: `types.go` (canonical type defs + time parser)
220
- - `restapi.md` — Official HAE REST API documentation
package/SKILL.md DELETED
@@ -1,60 +0,0 @@
1
- ---
2
- name: hae-vault
3
- description: >
4
- Apple Health archive database. Use for: historical Apple Health data (steps,
5
- heart rate, HRV, sleep, workouts, mindfulness, respiratory rate, blood oxygen
6
- from iPhone/Apple Watch), multi-day trends, long-term patterns. Data comes
7
- from Health Auto Export iOS app synced to local SQLite. NOT for WHOOP data —
8
- use whoop-up skill for that. NOT for live/real-time data.
9
- metadata:
10
- openclaw:
11
- emoji: "🍎"
12
- requires:
13
- bins:
14
- - hvault
15
- install:
16
- - id: node
17
- kind: node
18
- package: hae-vault
19
- bins:
20
- - hvault
21
- label: "Install hae-vault (node)"
22
- ---
23
-
24
- # hae-vault
25
-
26
- Query Apple Health data stored locally by `hvault serve` from the Health Auto Export iOS app.
27
-
28
- ## Commands
29
-
30
- ```bash
31
- # Query last 30 days of steps
32
- hvault metrics --metric step_count --days 30
33
-
34
- # Query HRV
35
- hvault metrics --metric heart_rate_variability --days 30
36
-
37
- # Query sleep (last 14 nights)
38
- hvault sleep --days 14
39
-
40
- # Query workouts (last 30 days)
41
- hvault workouts --days 30
42
-
43
- # Summary averages across all metrics (90 days)
44
- hvault summary --days 90
45
-
46
- # Raw SQL for custom queries
47
- hvault query "SELECT date, qty FROM metrics WHERE metric='step_count' ORDER BY date DESC LIMIT 7"
48
-
49
- # What's in the DB?
50
- hvault sources
51
- hvault last-sync
52
- hvault stats
53
- ```
54
-
55
- ## Available Metrics (common)
56
-
57
- step_count, heart_rate, heart_rate_variability, resting_heart_rate,
58
- active_energy, basal_energy_burned, respiratory_rate, blood_oxygen_saturation,
59
- weight_body_mass, body_fat_percentage, sleep_analysis (via hvault sleep),
60
- mindful_minutes, vo2max, walking_running_distance, flights_climbed