@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 +89 -0
- package/README.md +46 -19
- package/dist/index.js +1562 -1167
- package/docs/schemas/activity.json +15 -4
- package/docs/schemas/cv-age.json +26 -0
- package/docs/schemas/describe.json +1 -0
- package/docs/schemas/doctor.json +25 -0
- package/docs/schemas/hr.json +10 -3
- package/docs/schemas/readiness.json +15 -4
- package/docs/schemas/sleep-periods.json +26 -0
- package/docs/schemas/sleep.json +15 -4
- package/docs/schemas/spo2.json +15 -4
- package/docs/schemas/stress.json +15 -4
- package/docs/schemas/workout.json +15 -4
- package/package.json +3 -2
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
|
|
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).
|
|
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
|
|
35
|
-
oura-cli
|
|
36
|
-
oura-cli
|
|
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
|
-
|
|
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
|
|
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
|
-
###
|
|
108
|
+
### Raw API records
|
|
81
109
|
|
|
82
|
-
|
|
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
|
|
87
|
-
oura-cli
|
|
88
|
-
oura-cli
|
|
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
|
|
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
|
|
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
|
|