@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 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 EXIT_CODE_BY_CODE[err.code] ?? 1;
2660
- return 1;
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 body = await response.text();
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.parent.parent.opts();
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.parent.parent.opts();
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.parent.parent.opts();
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.3";
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
  }
@@ -7,6 +7,7 @@
7
7
  "properties": {
8
8
  "name": { "const": "oura-cli" },
9
9
  "version": { "type": "string" },
10
+ "compatManifestCommand": { "type": "string" },
10
11
  "auth": {
11
12
  "type": "object",
12
13
  "required": ["envVars", "tokenFile", "loginCommand"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@drakulavich/oura-cli",
3
- "version": "0.1.3",
3
+ "version": "0.2.1",
4
4
  "description": "Oura Ring CLI — query and analyze Oura Ring health data from the command line, designed for humans and AI agents.",
5
5
  "keywords": [
6
6
  "oura",