@drakulavich/oura-cli 0.1.3 → 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.
- package/CHANGELOG.md +36 -0
- package/README.md +28 -0
- package/dist/index.js +33 -15
- package/docs/schemas/describe.json +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,40 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.1] - 2026-05-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `CONTRIBUTING.md` — local setup, PR expectations, release flow (#1, item 13).
|
|
13
|
+
- `README.md` "Security" section — token storage, env exposure, telemetry
|
|
14
|
+
statement (#1, items 19 & 20).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
- `describe.test.ts` — replaced per-element `.toContain` loops with sorted
|
|
18
|
+
`.toEqual` for clearer diff on failure (#1, item 12).
|
|
19
|
+
|
|
20
|
+
## [0.2.0] - 2026-05-13
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- **BREAKING (typescript only):** `ErrorCode` is now a closed union of the
|
|
24
|
+
documented codes (`BAD_ARGS`, `TOKEN_MISSING`, `TOKEN_INVALID`, `API_ERROR`,
|
|
25
|
+
`DB_ERROR`, `UNKNOWN`). External code constructing `CliError` with a custom
|
|
26
|
+
string code will fail to compile. Runtime behaviour for already-built code
|
|
27
|
+
is unchanged. (#1, item 5)
|
|
28
|
+
- `exitCodeFor` is now an exhaustive `switch` over the closed union; future
|
|
29
|
+
additions to `ErrorCode` require a corresponding branch.
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- `describe` manifest now includes `compatManifestCommand: "oura-cli manifest"`
|
|
33
|
+
so agents can discover the OpenClaw-compatible second manifest. (#1, item 6)
|
|
34
|
+
- README documents the two-manifest split under "Manifest formats".
|
|
35
|
+
- `getGlobalOpts(command)` helper in `src/commands/helpers.ts` walks the
|
|
36
|
+
commander parent chain to the root program; api-command.ts and others now
|
|
37
|
+
use it instead of `command.parent!.parent!.opts()`. (#1, item 7)
|
|
38
|
+
|
|
39
|
+
### Security
|
|
40
|
+
- API error messages now redact `Bearer <token>` and `"token":"<value>"`
|
|
41
|
+
patterns and truncate bodies past 200 chars before printing. (#1, item 18)
|
|
42
|
+
|
|
9
43
|
## [0.1.3] - 2026-05-13
|
|
10
44
|
|
|
11
45
|
### Fixed
|
|
@@ -63,6 +97,8 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
63
97
|
- Local SQLite cache at `~/.oura-cli/oura.db`.
|
|
64
98
|
- Auth via `oura-cli login`, `OURA_TOKEN`, `OURA_TOKEN_PATH`, or `~/.oura-token`.
|
|
65
99
|
|
|
100
|
+
[0.2.1]: https://github.com/drakulavich/oura-cli/releases/tag/v0.2.1
|
|
101
|
+
[0.2.0]: https://github.com/drakulavich/oura-cli/releases/tag/v0.2.0
|
|
66
102
|
[0.1.3]: https://github.com/drakulavich/oura-cli/releases/tag/v0.1.3
|
|
67
103
|
[0.1.2]: https://github.com/drakulavich/oura-cli/releases/tag/v0.1.2
|
|
68
104
|
[0.1.1]: https://github.com/drakulavich/oura-cli/releases/tag/v0.1.1
|
package/README.md
CHANGED
|
@@ -90,6 +90,22 @@ oura-cli healthcheck # JSON: {ok, version, latencyMs}
|
|
|
90
90
|
| 3 | API or network error |
|
|
91
91
|
| 4 | database or local storage error |
|
|
92
92
|
|
|
93
|
+
## Manifest formats
|
|
94
|
+
|
|
95
|
+
Two manifest commands, two audiences:
|
|
96
|
+
|
|
97
|
+
- **`oura-cli describe`** — neutral, agent-friendly. Lists every command, its
|
|
98
|
+
args, output schema refs, and exit-code semantics. Use this when integrating
|
|
99
|
+
with generic LLM harnesses, MCP wrappers, or your own custom scripts.
|
|
100
|
+
- **`oura-cli manifest`** — [OpenClaw](https://github.com/openclaw/openclaw)
|
|
101
|
+
`tool-registry` shape. Strictly smaller, optimised for OpenClaw's skill
|
|
102
|
+
discovery and health-aggregation flow. Use this only if you're plugging
|
|
103
|
+
oura-cli into an OpenClaw gateway.
|
|
104
|
+
|
|
105
|
+
Both return JSON. `describe` references `manifest` via the
|
|
106
|
+
`compatManifestCommand` field so an agent can discover the second format
|
|
107
|
+
without prior knowledge.
|
|
108
|
+
|
|
93
109
|
## What's Inside
|
|
94
110
|
|
|
95
111
|
| Endpoint | Source | Cached table |
|
|
@@ -116,6 +132,18 @@ Runtime: [Bun](https://bun.sh). Storage: built-in `bun:sqlite`. CLI parsing: [Co
|
|
|
116
132
|
| Timezone | `--tz` | `OURA_TZ` | system timezone, else `UTC` |
|
|
117
133
|
| Output format | `--format` | | auto-detect (TTY → table) |
|
|
118
134
|
|
|
135
|
+
## Security
|
|
136
|
+
|
|
137
|
+
This tool reads your personal health data — handle the access token with care.
|
|
138
|
+
|
|
139
|
+
- `~/.oura-token` is written with `0600` permissions on POSIX (`chmod 0600` in `oura-cli login`). On Windows the file is written but ACL hardening is left to you.
|
|
140
|
+
- `OURA_TOKEN` as an environment variable is convenient for CI and containers, but it appears in `ps auxe`, heap dumps, and core dumps. Prefer the file-based path for interactive use.
|
|
141
|
+
- `--token <pat>` is the least safe option: the value lands in your shell history. Avoid it outside of throw-away scripts.
|
|
142
|
+
- Token revocation is done at <https://cloud.ouraring.com/personal-access-tokens>, not via this CLI.
|
|
143
|
+
- API responses are truncated to 200 chars and `Bearer`/`"token":"…"` patterns are redacted before being printed in error messages.
|
|
144
|
+
|
|
145
|
+
oura-cli performs **no telemetry**. The only outbound network traffic is your authenticated Oura Cloud API calls.
|
|
146
|
+
|
|
119
147
|
## Integrations
|
|
120
148
|
|
|
121
149
|
- **OpenClaw** — drop into your LLM agent as an [OpenClaw skill](https://github.com/openclaw/openclaw). `oura-cli manifest` and `oura-cli healthcheck` report back in the tool-registry shape, so the agent can discover the binary and audit its DB health automatically.
|
package/dist/index.js
CHANGED
|
@@ -2647,17 +2647,25 @@ class CliError extends Error {
|
|
|
2647
2647
|
this.name = "CliError";
|
|
2648
2648
|
}
|
|
2649
2649
|
}
|
|
2650
|
-
var EXIT_CODE_BY_CODE = {
|
|
2651
|
-
BAD_ARGS: 1,
|
|
2652
|
-
TOKEN_MISSING: 2,
|
|
2653
|
-
TOKEN_INVALID: 2,
|
|
2654
|
-
API_ERROR: 3,
|
|
2655
|
-
DB_ERROR: 4
|
|
2656
|
-
};
|
|
2657
2650
|
function exitCodeFor(err) {
|
|
2658
|
-
if (err instanceof CliError)
|
|
2659
|
-
return
|
|
2660
|
-
|
|
2651
|
+
if (!(err instanceof CliError))
|
|
2652
|
+
return 1;
|
|
2653
|
+
switch (err.code) {
|
|
2654
|
+
case "BAD_ARGS":
|
|
2655
|
+
return 1;
|
|
2656
|
+
case "TOKEN_MISSING":
|
|
2657
|
+
case "TOKEN_INVALID":
|
|
2658
|
+
return 2;
|
|
2659
|
+
case "API_ERROR":
|
|
2660
|
+
return 3;
|
|
2661
|
+
case "DB_ERROR":
|
|
2662
|
+
return 4;
|
|
2663
|
+
case "UNKNOWN":
|
|
2664
|
+
return 1;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
function redactSecrets(s) {
|
|
2668
|
+
return s.replace(/Bearer\s+[A-Za-z0-9._\-]{8,}/g, "Bearer [REDACTED]").replace(/"token"\s*:\s*"[^"]{8,}"/g, '"token":"[REDACTED]"');
|
|
2661
2669
|
}
|
|
2662
2670
|
function formatError(err, format) {
|
|
2663
2671
|
const code = err instanceof CliError ? err.code : "UNKNOWN";
|
|
@@ -2706,7 +2714,9 @@ class OuraClient {
|
|
|
2706
2714
|
headers: { Authorization: `Bearer ${this.token}` }
|
|
2707
2715
|
});
|
|
2708
2716
|
if (!response.ok) {
|
|
2709
|
-
const
|
|
2717
|
+
const rawBody = await response.text();
|
|
2718
|
+
const redacted = redactSecrets(rawBody);
|
|
2719
|
+
const body = redacted.length > 200 ? redacted.slice(0, 200) + "\u2026 (truncated)" : redacted;
|
|
2710
2720
|
if (response.status === 401 || response.status === 403) {
|
|
2711
2721
|
throw new CliError("TOKEN_INVALID", `Oura API ${response.status}: ${body}`);
|
|
2712
2722
|
}
|
|
@@ -2752,6 +2762,13 @@ function resolveDefaultTimezone() {
|
|
|
2752
2762
|
}
|
|
2753
2763
|
|
|
2754
2764
|
// src/commands/helpers.ts
|
|
2765
|
+
function getGlobalOpts(command) {
|
|
2766
|
+
let node = command;
|
|
2767
|
+
while (node?.parent) {
|
|
2768
|
+
node = node.parent;
|
|
2769
|
+
}
|
|
2770
|
+
return node?.opts() ?? {};
|
|
2771
|
+
}
|
|
2755
2772
|
function getClient(opts) {
|
|
2756
2773
|
return new OuraClient(opts.token ? { token: opts.token } : {});
|
|
2757
2774
|
}
|
|
@@ -2770,19 +2787,19 @@ function dateRange(days, timezone) {
|
|
|
2770
2787
|
function createApiCommand(name, description, endpoint) {
|
|
2771
2788
|
const cmd = new Command(name).description(description);
|
|
2772
2789
|
cmd.command("today").description(`Today's ${name} data`).action(async (_, command) => {
|
|
2773
|
-
const opts = command
|
|
2790
|
+
const opts = getGlobalOpts(command);
|
|
2774
2791
|
const client = getClient(opts);
|
|
2775
2792
|
const data = await client.fetch(endpoint, todayDate(), todayDate());
|
|
2776
2793
|
console.log(JSON.stringify(data, null, 2));
|
|
2777
2794
|
});
|
|
2778
2795
|
cmd.command("date <day>").description(`${name} data for specific date (YYYY-MM-DD)`).action(async (day, _, command) => {
|
|
2779
|
-
const opts = command
|
|
2796
|
+
const opts = getGlobalOpts(command);
|
|
2780
2797
|
const client = getClient(opts);
|
|
2781
2798
|
const data = await client.fetch(endpoint, day, day);
|
|
2782
2799
|
console.log(JSON.stringify(data, null, 2));
|
|
2783
2800
|
});
|
|
2784
2801
|
cmd.command("week").description(`Last 7 days of ${name} data`).action(async (_, command) => {
|
|
2785
|
-
const opts = command
|
|
2802
|
+
const opts = getGlobalOpts(command);
|
|
2786
2803
|
const client = getClient(opts);
|
|
2787
2804
|
const { start, end } = dateRange(7);
|
|
2788
2805
|
const data = await client.fetch(endpoint, start, end);
|
|
@@ -3723,6 +3740,7 @@ function buildManifest(version) {
|
|
|
3723
3740
|
return {
|
|
3724
3741
|
name: "oura-cli",
|
|
3725
3742
|
version,
|
|
3743
|
+
compatManifestCommand: "oura-cli manifest",
|
|
3726
3744
|
auth: {
|
|
3727
3745
|
envVars: ["OURA_TOKEN", "OURA_TOKEN_PATH"],
|
|
3728
3746
|
tokenFile: "~/.oura-token",
|
|
@@ -3858,7 +3876,7 @@ function describeCommand(version) {
|
|
|
3858
3876
|
}
|
|
3859
3877
|
|
|
3860
3878
|
// src/index.ts
|
|
3861
|
-
var VERSION = "0.1
|
|
3879
|
+
var VERSION = "0.2.1";
|
|
3862
3880
|
if (process.argv.includes("--no-color") || process.env.NO_COLOR) {
|
|
3863
3881
|
source_default.level = 0;
|
|
3864
3882
|
}
|
package/package.json
CHANGED