@drakulavich/oura-cli 0.4.5 → 0.5.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.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,93 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.5.1] - 2026-09-05
10
+
11
+ Fixes from the 0.5.0 exploratory testing sessions: the seam between citty and the command runner, and the sync window.
12
+
13
+ ### Changed
14
+ - `sync` JSON: `import.counts` is replaced by `import.fetched` (rows the API returned per table) and `import.added` (net growth: days or samples the table did not hold before; a revised row for a day already stored counts as fetched, not new). The text summary prints `sleep 28 (+0)` in the same spirit, so a repeat run no longer looks like it imported 28 days. (#51)
15
+ - `sync --from YYYY-MM-DD [--to YYYY-MM-DD]` re-fetches an explicit window for every collection, for recovery after an interrupted run or a deliberate backfill. `--to` defaults to today and requires `--from`. (#53)
16
+
17
+ ### Fixed
18
+ - `sync` resumes every collection from its own last stored day instead of a single watermark taken from the three daily tables. A run interrupted after those tables (Ctrl-C, a crash) used to advance the watermark and leave the other six collections with a permanent gap that no later sync could fill. (#53)
19
+ - First sync fetches exactly 30 inclusive days, as documented, not 31. (#46)
20
+ - `fetch sleep-periods --day D` and `sync` no longer miss the requested day's sleep periods, and `fetch workout --from A --to B` no longer drops workouts on day B: Oura treats `start_date` as exclusive for `sleep` and `end_date` as exclusive for `workout`, and the request bounds are now shifted accordingly. (#58)
21
+ - Two `oura-cli` processes on the same cache no longer fail with `database is locked`: connections wait up to 5 s for a lock, `PRAGMA journal_mode = WAL` is only issued when the file is not already in WAL mode (that statement itself needs an exclusive lock), and a lock that does outlast the wait reports a hint naming the cause. (#56)
22
+ - `~/.oura-cli/` and a newly created cache file are created with `0700` / `0600` permissions, matching the token file; existing files are left alone. (#60)
23
+ - `doctor --offline` reports the token check as `skip` rather than `ok`. The new status keeps `ok: true` and produces no next step, but an agent gating on the JSON can now tell "not checked" from "passed"; `docs/schemas/doctor.json` lists the value. (#59)
24
+ - README: bundle size, the sync wording above, the token-missing message shape, and the `fetch sleep` pipe example, which read a readiness contributor (`hrv_balance`) off the sleep collection and printed `null` for every row. (#62)
25
+ - A token containing whitespace or a line break (for example a token file with a second line) is rejected as `TOKEN_INVALID` (exit 2) naming the source; previously it reached the API layer and came back as `UNKNOWN` with the token quoted in the message. Every error message and hint is now passed through the secret redactor. (#47)
26
+ - `DB_ERROR` (exit 4) is reachable: a corrupt cache file, an unusable `--db` path or a failing query in `db *`, `sync` and `report` now report `DB_ERROR` with a hint instead of `UNKNOWN` / exit 1. (#48)
27
+ - Errors raised before a command runs — unknown command, missing positional, no command on a pipe — use the same envelope as every other error: JSON on stderr when piped, text on a TTY, nothing on stdout, `--no-color` honoured. The commands removed in 0.5.0 (`db reset`, `db import`, `sleep|readiness|activity|hr|spo2|stress|workout …`) point at `fetch`/`sync`. `--help` and a bare `oura-cli` on a TTY still show usage, and `--version` now answers even when other flags are present (it used to exit 1 with usage unless it was the only argument), but not when it sits in another flag's value position. (#49)
28
+ - Undeclared flags and extra positionals are `BAD_ARGS` instead of being ignored, for every command including `describe`, `manifest`, `healthcheck` and `login`: `fetch sleep --dayz 7` no longer returns today's data with exit 0, and `db trends -5` no longer falls back to the 30-day default. (#50, part of #52)
29
+ - Global flags placed before the subcommand are no longer moved past a `--` separator, and `oura-cli constructor` (any `Object.prototype` name) is an unknown command instead of a silent exit 0.
30
+
31
+ ## [0.5.0] - 2026-09-05
32
+
33
+ ### Breaking
34
+ - Per-collection API commands replaced by `fetch <collection>`; `db reset` and `db import` removed. See "Changed" and "Removed" below.
35
+
36
+ ### Changed
37
+ - `sync` fetches heartrate over the same window as every other collection (from the last synced day, or 30 days back on first sync) instead of today only, in pieces of at most 30 × 24 hours because the heartrate endpoint rejects longer ranges; consecutive pieces do not share a boundary instant, so no sample is fetched twice. Days the ring synced while `oura-cli sync` did not run are no longer skipped; the unique `(timestamp, source)` index keeps re-fetched samples from duplicating.
38
+ - README "First five minutes" now leads with a concrete report sample,
39
+ documents `login` → `doctor` → `sync` → `report` as the onboarding path,
40
+ states plainly that `login` hides the token as you type, and adds a
41
+ symptom → command recovery table.
42
+ - Text formatters moved from the repo root into `src/render/` (internal).
43
+ - Every data command runs through one runner that resolves the output format, opens and always closes the database, creates the API client and maps errors to exit codes. `sync` progress lines are printed together with the summary instead of streaming. `--no-color` now takes effect before any output (internal).
44
+ - Token resolution lives in `src/api/token.ts` and is shared by the API client and `doctor`; the two SQLite wrappers merged into `src/db/open.ts` (internal). A whitespace-only token is now rejected as missing instead of being sent as an empty bearer token.
45
+ - Every Oura collection is described once in `src/collections/`; table DDL, inserts and the sync loop derive from it. A test proves the derived DDL matches the shipped migrations (internal).
46
+ - **Breaking:** the seven per-collection commands (`sleep`, `readiness`, `activity`, `hr`, `spo2`, `stress`, `workout` × `today|date|week`) are replaced by `oura-cli fetch <collection> [--day D | --from A --to B | --days N]`. `sleep-periods` and `cv-age` are now fetchable too.
47
+ - `describe` and `manifest` are generated from the registered command tree, so they can no longer drift from the CLI; `manifest` examples that referenced a non-existent `--start` flag are gone.
48
+ - `docs/schemas/<collection>.json` are generated (`bun run schemas`) and checked by a test; added `sleep-periods.json` and `cv-age.json`.
49
+
50
+ ### Removed
51
+ - `db reset` and the CSV importer it relied on (it read a hard-coded personal directory), and the `db import` alias of `sync`.
52
+ - `vo2max` no longer appears in `db stats`; the table was never populated. `db stats` now lists tables in registry order (`heartrate` fourth, `cardiovascular_age` last); key by `table`, not position.
53
+
54
+ ### Added
55
+ - `oura-cli doctor` diagnoses token resolution (honoring `--token` like every
56
+ other command), live token validity (skippable with `--offline`), local
57
+ database health, and data freshness across sleep/readiness/activity in one
58
+ pass. `ok` is true only when every check is clean — a warning is enough to
59
+ clear "everything looks healthy". `nextStep` is the first non-ok check's
60
+ fix, so it never recommends a command (like `sync`) that would just hit the
61
+ same root cause (like an unreachable API) a check upstream already found.
62
+ Both `table` and `json` output are supported; the JSON shape is published
63
+ as `docs/schemas/doctor.json`.
64
+
65
+ ### Fixed
66
+ - `fetch --day/--from/--to` and `db date` reject calendar-invalid dates such as `2026-02-30` with `BAD_ARGS` instead of sending them to the API or querying nothing. `db trends <days>` rejects anything but a positive integer instead of failing with `UNKNOWN` (`abc`) or silently returning nothing (`0`).
67
+ - An unknown `--format` is rejected for `fetch` too (it used to be silently accepted); `fetch` errors now render as text on a TTY and as JSON when piped.
68
+ - An unknown `--tz` / `OURA_TZ` is reported as `BAD_ARGS` with a hint instead of an `UNKNOWN` error from the date formatter.
69
+ - `fetch hr` and the heartrate step of `sync` now send `start_datetime`/`end_datetime` (UTC instants covering the requested local days in `OURA_TZ`), which is what the Oura heartrate endpoint takes; every other collection keeps `start_date`/`end_date`. Previously heartrate was queried with date parameters the endpoint does not define.
70
+ - The API client follows `next_token` and returns every page, so ranges longer than one page (heartrate over several days) are no longer silently truncated.
71
+ - Local-day boundaries are computed at local midnight instead of noon, so `fetch hr --day` on a DST transition day covers the whole 23- or 25-hour day rather than shifting by an hour (internal `localDateToUtcRange`, previously unused).
72
+ - Interactive `oura-cli login` now hides the Personal Access Token while it is
73
+ typed and explains how to use `--token` safely in non-interactive contexts.
74
+ - `oura-cli report` now explains when no data is available and directs new
75
+ users to run `oura-cli sync`, instead of showing an all-empty report table.
76
+ - Reports containing only sleep-detail data are no longer mistaken for empty
77
+ reports and continue to show the available sleep metrics.
78
+ - `oura-cli db today` and `oura-cli db week` now explain when the local cache
79
+ has no data for the requested range and point at `oura-cli sync`, instead of
80
+ showing an all-dash table. JSON output is unchanged.
81
+ - `oura-cli sync` now says explicitly when it is a first sync backfilling the
82
+ default 30 days, names the resolved date range for both first and
83
+ incremental syncs, and prints a per-collection import count summary in
84
+ table mode. JSON output gains an additive `import.isFirstSync` boolean.
85
+ - `OuraStressDay.day_summary`, `OuraWorkout.label` and `OuraSleepModel.type` are
86
+ now typed `?: string | null`, matching the Oura OpenAPI spec (checked against
87
+ 1.37), which marks all three nullable and omits them from `required`. The
88
+ declared types claimed a value is always present, so code reading them could
89
+ assume one that never arrives. `importDaily` now maps an absent value to
90
+ `NULL` explicitly instead of relying on `bun:sqlite` silently coercing
91
+ `undefined`.
92
+ - `db week`, `db trends`, `db stats`, `report` and `sync` now compute "today"
93
+ and every window boundary in the configured timezone (`--tz` / `OURA_TZ`);
94
+ previously they used UTC and disagreed with `db today` around midnight.
95
+
9
96
  ## [0.4.5] - 2026-07-18
10
97
 
11
98
  ### Removed
@@ -242,6 +329,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
242
329
  - Local SQLite cache at `~/.oura-cli/oura.db`.
243
330
  - Auth via `oura-cli login`, `OURA_TOKEN`, `OURA_TOKEN_PATH`, or `~/.oura-token`.
244
331
 
332
+ [0.5.1]: https://github.com/drakulavich/oura-cli/releases/tag/v0.5.1
333
+ [0.5.0]: https://github.com/drakulavich/oura-cli/releases/tag/v0.5.0
245
334
  [0.4.4]: https://github.com/drakulavich/oura-cli/releases/tag/v0.4.4
246
335
  [0.4.3]: https://github.com/drakulavich/oura-cli/releases/tag/v0.4.3
247
336
  [0.4.2]: https://github.com/drakulavich/oura-cli/releases/tag/v0.4.2
package/README.md CHANGED
@@ -17,7 +17,7 @@
17
17
  - **Offline-first.** Everything caches into `~/.oura-cli/oura.db` after one `oura-cli sync`. Reports keep working when your internet doesn't.
18
18
  - **Real terminal reports.** `oura-cli report` writes a weekly or monthly digest with averages, trend deltas, and "you slept poorly Tuesday" callouts. No dashboards, no logging in.
19
19
  - **Pipe-friendly.** Output auto-switches to stable JSON when stdout isn't a terminal. Analyse with `jq`, plot with `gnuplot`, or feed it into your own scripts.
20
- - **Single 100 kB binary, MIT, no telemetry.** Built on Bun; zero native dependencies.
20
+ - **Single ~110 kB binary, MIT, no telemetry.** Built on Bun; zero native dependencies.
21
21
 
22
22
  ## Install
23
23
 
@@ -26,17 +26,45 @@ curl -fsSL https://bun.sh/install | bash # if you don't have Bun yet
26
26
  bun add -g @drakulavich/oura-cli
27
27
  ```
28
28
 
29
- You'll also need a [Personal Access Token from Oura](https://cloud.ouraring.com/personal-access-tokens). Paste it into `oura-cli login` once — it lands at `~/.oura-token` with `0600` perms.
29
+ You'll also need a [Personal Access Token from Oura](https://cloud.ouraring.com/personal-access-tokens). Run `oura-cli login` once — it hides the token as you type (nothing is echoed to the terminal) and saves it to `~/.oura-token` with `0600` perms.
30
30
 
31
31
  ## First five minutes
32
32
 
33
+ Five minutes from now you'll have your week of sleep, readiness and activity in a terminal digest like this:
34
+
35
+ ```
36
+ Oura Weekly Report
37
+ 2026-08-24 — 2026-08-30
38
+
39
+ Last 7 Days:
40
+ ────────────────────────────────────────────────────
41
+ Day Sleep Ready Active Steps
42
+ ────────────────────────────────────────────────────
43
+ Mon 24/08 87 74 68 9,668
44
+ Tue 25/08 82 79 74 11,204
45
+ ...
46
+ ```
47
+
48
+ Four commands get you there:
49
+
33
50
  ```bash
34
- oura-cli login # paste your PAT, one time
35
- oura-cli sync # backfill recent days into ~/.oura-cli/oura.db
36
- oura-cli report # weekly digest in the terminal
51
+ oura-cli login # paste your PAT — input is hidden, nothing echoes to the terminal
52
+ oura-cli doctor # confirm the token works and the local database is ready
53
+ oura-cli sync # first sync fetches the last 30 days; later syncs resume from the last stored day
54
+ oura-cli report # weekly digest in the terminal
37
55
  ```
38
56
 
39
- That's it. Subsequent `oura-cli sync` pulls only new days.
57
+ Subsequent `oura-cli sync` re-fetches each collection from its own last stored day (Oura revises recent days, so the overlap is deliberate) and reports rows fetched (+new). `oura-cli sync --from 2026-08-01 [--to 2026-08-07]` re-fetches an explicit window for every collection instead — for example after an interrupted sync. `oura-cli db today` / `oura-cli db week` read the local cache instantly, no API call.
58
+
59
+ ### If something looks wrong
60
+
61
+ | What you see | What to run |
62
+ |---|---|
63
+ | `No Oura data is available for this report yet.` | `oura-cli sync` |
64
+ | `No Oura access token at /…/.oura-token` | `oura-cli login` |
65
+ | `Oura API 401` | `oura-cli login` with a fresh PAT |
66
+ | `db today` empty right after a sync | Normal — Oura publishes a day's summary after that night's sleep syncs from the ring. |
67
+ | Anything else | `oura-cli doctor` |
40
68
 
41
69
  ## Daily use
42
70
 
@@ -58,7 +86,7 @@ oura-cli db date 2026-05-10
58
86
 
59
87
  ```bash
60
88
  oura-cli db week # local cache summary, no API hit
61
- oura-cli sleep week # fresh sleep details direct from Oura
89
+ oura-cli fetch sleep --days 7 # fresh sleep details direct from Oura
62
90
  ```
63
91
 
64
92
  ### Reports
@@ -77,26 +105,25 @@ oura-cli db trends 30 # score trends across the last 30 days
77
105
  oura-cli db stats # row counts, date range, personal bests
78
106
  ```
79
107
 
80
- ### Per-endpoint detail
108
+ ### Raw API records
81
109
 
82
- When you want raw Oura V2 data, every endpoint shares the same shape `today | date <day> | week`:
110
+ `fetch` returns one collection straight from the Oura API as JSON, without touching the local cache.
83
111
 
84
112
  ```bash
85
- oura-cli sleep today
86
- oura-cli readiness date 2026-05-10
87
- oura-cli activity week
88
- oura-cli hr week
89
- oura-cli spo2 week
90
- oura-cli stress week
91
- oura-cli workout week
113
+ oura-cli fetch sleep # today
114
+ oura-cli fetch hr --days 7 # last 7 days
115
+ oura-cli fetch workout --from 2026-05-01 --to 2026-05-31
116
+ oura-cli fetch sleep-periods --day 2026-06-01 | jq '.[] | {day, type, average_hrv}'
92
117
  ```
93
118
 
119
+ Collections: `sleep readiness activity hr spo2 stress workout sleep-periods cv-age`.
120
+
94
121
  ### Piping to other tools
95
122
 
96
123
  Output auto-switches to JSON the moment you pipe it:
97
124
 
98
125
  ```bash
99
- oura-cli sleep week | jq '.[] | {day, score, hrv: .contributors.hrv_balance}'
126
+ oura-cli fetch sleep --days 7 | jq '.[] | {day, score, deep: .contributors.deep_sleep}'
100
127
  oura-cli db trends 90 > trends.json
101
128
  ```
102
129
 
@@ -136,7 +163,7 @@ This tool reads your personal health data — handle the token with care.
136
163
  | Sleep model | Oura V2 `sleep` | `sleep_model` |
137
164
  | Cardiovascular age | Oura V2 `cardiovascular_age` | `cardiovascular_age` |
138
165
 
139
- Runtime: [Bun](https://bun.sh). Storage: built-in `bun:sqlite`. CLI parsing: [citty](https://github.com/unjs/citty). Output styling: [chalk](https://github.com/chalk/chalk). One 100 kB `dist/index.js`, no native deps.
166
+ Runtime: [Bun](https://bun.sh). Storage: built-in `bun:sqlite`. CLI parsing: [citty](https://github.com/unjs/citty). Output styling: [chalk](https://github.com/chalk/chalk). One ~110 kB `dist/index.js`, no native deps.
140
167
 
141
168
  ## Automation (LLM agents, scripts, MCP)
142
169
 
@@ -146,7 +173,7 @@ If you're driving the CLI from a script or LLM harness:
146
173
  - `oura-cli healthcheck` — `{ok, version, latencyMs}` JSON for liveness probes.
147
174
  - Errors emit a stable JSON envelope on stderr: `{"error":{"code":"…","message":"…","hint":"…"}}`.
148
175
  - Documented exit codes: `0` success, `1` user error, `2` auth, `3` API, `4` storage.
149
- - JSON Schemas under [`docs/schemas/`](docs/schemas/) describe every output shape, semver-stable.
176
+ - JSON Schemas under [`docs/schemas/`](docs/schemas/) describe every output shape, semver-stable. Per-collection schemas pin the identity fields (`id`, `day`, `timestamp`) and allow the rest of the Oura record through unchanged, so new upstream fields never break validation.
150
177
 
151
178
  Plays cleanly with [OpenClaw](https://github.com/openclaw/openclaw) — `oura-cli manifest` returns the tool-registry shape. A first-party `oura-mcp` companion is on the roadmap.
152
179