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,213 @@
1
+ # Design: ZIP support, env config, watch command, import deduplication
2
+
3
+ **Date:** 2026-02-18
4
+ **Status:** Approved
5
+
6
+ ---
7
+
8
+ ## Summary
9
+
10
+ Four additions to the existing `hae-vault` codebase:
11
+
12
+ 1. **ZIP support** in `hvault import` — extract `HealthAutoExport-*.json` from a `.zip`, ignore `.gpx`
13
+ 2. **Env config** via `.env` + env vars — full Docker-compatible configuration
14
+ 3. **`hvault watch`** — polls a directory, auto-imports new HAE exports
15
+ 4. **`import_log` table + SHA-256 dedup** — skip already-imported files before parsing
16
+
17
+ ---
18
+
19
+ ## 1. Env Config (`src/config.ts`)
20
+
21
+ New module, loaded once at process startup via a top-level import in `src/index.ts`.
22
+
23
+ ### Load order (highest priority first)
24
+
25
+ ```
26
+ CLI flag > env var (process.env) > .env file > hardcoded default
27
+ ```
28
+
29
+ Dotenv loads from:
30
+ 1. `HVAULT_ENV_FILE` env var if set (explicit path to `.env`)
31
+ 2. `./.env` in CWD (local dev convenience)
32
+
33
+ In Docker: set env vars directly — no `.env` file needed.
34
+
35
+ ### Variables
36
+
37
+ | Env var | Default | Used by |
38
+ |-----------------------|------------------------------|----------------------|
39
+ | `HVAULT_ENV_FILE` | — | config bootstrap |
40
+ | `HVAULT_DB_PATH` | `~/.hae-vault/health.db` | all commands |
41
+ | `HVAULT_PORT` | `4242` | `serve` |
42
+ | `HVAULT_TOKEN` | — | `serve` |
43
+ | `HVAULT_WATCH_DIR` | — | `watch` |
44
+ | `HVAULT_WATCH_INTERVAL` | `60` | `watch` |
45
+ | `HVAULT_TARGET` | `default` | `import`, `watch` |
46
+
47
+ ### Tilde expansion
48
+
49
+ `HVAULT_DB_PATH=~/.hae-vault/health.db` — dotenv reads `~` as a literal character. `config.ts` replaces a leading `~/` with `os.homedir() + '/'`.
50
+
51
+ ### config object
52
+
53
+ ```typescript
54
+ export const config = {
55
+ dbPath: string, // resolved absolute path
56
+ port: number,
57
+ token: string | undefined,
58
+ watchDir: string | undefined,
59
+ watchInterval: number, // seconds
60
+ target: string,
61
+ }
62
+ ```
63
+
64
+ ### CLI flag override pattern
65
+
66
+ Each command reads its default from `config`, CLI flags override:
67
+
68
+ ```typescript
69
+ // serve.ts
70
+ .option('-p, --port <number>', 'Port', String(config.port))
71
+ .option('--token <secret>', 'Bearer token', config.token)
72
+ ```
73
+
74
+ ---
75
+
76
+ ## 2. ZIP support in `hvault import`
77
+
78
+ Library: **`adm-zip`** — pure JS, synchronous, no native bindings. Matches existing sync codebase.
79
+
80
+ ### Logic in `src/cli/import.ts`
81
+
82
+ ```
83
+ if file.endsWith('.zip'):
84
+ open zip
85
+ find entry matching /HealthAutoExport.*\.json$/i
86
+ if not found → error "No HealthAutoExport-*.json found in zip"
87
+ read entry buffer → parse as JSON
88
+ else:
89
+ readFileSync(file) → parse as JSON
90
+ ```
91
+
92
+ GPX files are ignored entirely — no extraction, no error.
93
+
94
+ ### Hash computation
95
+
96
+ SHA-256 of the **raw file bytes** (the `.zip` or `.json` file itself, before extraction). Computed with `node:crypto` — no extra dependency.
97
+
98
+ ---
99
+
100
+ ## 3. `import_log` table (deduplication)
101
+
102
+ ### Schema addition to `src/db/schema.ts`
103
+
104
+ ```sql
105
+ CREATE TABLE IF NOT EXISTS import_log (
106
+ id INTEGER PRIMARY KEY,
107
+ filename TEXT NOT NULL,
108
+ file_hash TEXT NOT NULL UNIQUE, -- SHA-256 hex of file content
109
+ imported_at TEXT NOT NULL, -- ISO8601
110
+ metrics_added INTEGER,
111
+ sleep_added INTEGER,
112
+ workouts_added INTEGER
113
+ );
114
+ ```
115
+
116
+ ### Dedup flow (in `src/cli/import.ts`)
117
+
118
+ ```
119
+ 1. hash = sha256(fileBuffer)
120
+ 2. existing = db.prepare('SELECT id FROM import_log WHERE file_hash = ?').get(hash)
121
+ 3. if existing → print { skipped: true, reason: 'already imported', hash } → exit 0
122
+ 4. ingest(payload)
123
+ 5. INSERT INTO import_log (filename, file_hash, imported_at, metrics_added, sleep_added, workouts_added)
124
+ ```
125
+
126
+ ### `ingest()` return type change
127
+
128
+ `ingest()` currently returns `void`. Change to return `IngestResult`:
129
+
130
+ ```typescript
131
+ export interface IngestResult {
132
+ metricsAdded: number;
133
+ sleepAdded: number;
134
+ workoutsAdded: number;
135
+ }
136
+ ```
137
+
138
+ This is a breaking change to the function signature — existing `ingest.test.ts` will need updating (callers that ignore the return value are fine; tests asserting `void` need a minor fix).
139
+
140
+ ---
141
+
142
+ ## 4. `hvault watch` command (`src/cli/watch.ts`)
143
+
144
+ Polls a directory on an interval, picks up new HAE export files, imports them.
145
+
146
+ ### File matching pattern
147
+
148
+ `HealthAutoExport-*.{zip,json}` — case-insensitive regex: `/^HealthAutoExport.*\.(zip|json)$/i`
149
+
150
+ ### Flow
151
+
152
+ ```
153
+ startup:
154
+ resolve watchDir (--dir flag ?? HVAULT_WATCH_DIR ?? error)
155
+ open DB
156
+ log "Watching <dir> every <N>s"
157
+
158
+ each tick:
159
+ list files in dir matching pattern
160
+ for each file:
161
+ hash = sha256(readFileSync(file))
162
+ if import_log has this hash → skip
163
+ import(file) → log result
164
+ sleep N seconds → repeat
165
+ ```
166
+
167
+ ### Options
168
+
169
+ ```bash
170
+ hvault watch [--dir <path>] [--interval <seconds>] [--target <name>]
171
+ ```
172
+
173
+ Defaults from `config.watchDir`, `config.watchInterval`, `config.target`.
174
+
175
+ `--dir` is required if `HVAULT_WATCH_DIR` is not set — error and exit if neither provided.
176
+
177
+ ### Output
178
+
179
+ Each tick logs to stdout as JSON lines:
180
+
181
+ ```json
182
+ { "tick": "2026-02-18T10:00:00Z", "dir": "/Downloads", "found": 3, "imported": 1, "skipped": 2 }
183
+ ```
184
+
185
+ On import, also logs the import result (same format as `hvault import`).
186
+
187
+ ---
188
+
189
+ ## 5. Files changed / created
190
+
191
+ | File | Change |
192
+ |------|--------|
193
+ | `package.json` | add `adm-zip` dependency |
194
+ | `src/config.ts` | NEW — dotenv load + config singleton |
195
+ | `src/index.ts` | add `import './config.js'` at top |
196
+ | `src/db/schema.ts` | add `import_log` table |
197
+ | `src/server/ingest.ts` | return `IngestResult` instead of `void` |
198
+ | `src/cli/import.ts` | ZIP support + hash dedup + `import_log` write |
199
+ | `src/cli/serve.ts` | read port/token defaults from `config` |
200
+ | `src/cli/watch.ts` | NEW |
201
+ | `src/cli/index.ts` | register `watchCommand` |
202
+ | `tests/ingest.test.ts` | update for `IngestResult` return type |
203
+ | `tests/import-zip.test.ts` | NEW — test ZIP extraction + dedup |
204
+ | `tests/watch.test.ts` | NEW — test watch polling logic |
205
+
206
+ ---
207
+
208
+ ## Out of scope
209
+
210
+ - File deletion after import (watch leaves files in place)
211
+ - Recursive directory scanning (flat dir only)
212
+ - `.env` file creation tooling
213
+ - Docker Compose / Dockerfile (not part of this package)