@andreasnlarsen/whoop-cli 0.1.2 → 0.1.4
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/README.md +50 -19
- package/dist/auth/oauth.js +4 -2
- package/dist/auth/token-service.js +1 -1
- package/dist/commands/auth.js +1 -1
- package/dist/util/open-browser.js +39 -8
- package/openclaw-skill/SKILL.md +44 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,38 +32,45 @@ There is **no managed/shared auth service** in this repo right now.
|
|
|
32
32
|
- Do **not** embed or publish client secrets/tokens in source code, examples, or public logs.
|
|
33
33
|
- If WHOOP requests naming/branding/compliance changes, maintainers should address them promptly and cooperatively.
|
|
34
34
|
|
|
35
|
+
## Trust & Safety (quick)
|
|
36
|
+
|
|
37
|
+
- Package: `@andreasnlarsen/whoop-cli`
|
|
38
|
+
- Releases are published via GitHub Actions trusted publishing (OIDC) with npm provenance.
|
|
39
|
+
- This integration is unofficial and not affiliated with Whoop, Inc.
|
|
40
|
+
- Never paste OAuth client secrets/tokens into chat. Run login locally:
|
|
41
|
+
- `whoop auth login --client-id ... --client-secret ... --redirect-uri ...`
|
|
42
|
+
- Verify install quickly:
|
|
43
|
+
- `npx -y @andreasnlarsen/whoop-cli --help`
|
|
44
|
+
- `whoop auth status --json`
|
|
45
|
+
- `whoop day-brief --json`
|
|
46
|
+
|
|
35
47
|
---
|
|
36
48
|
|
|
37
49
|
## One-line options (no clone)
|
|
38
50
|
|
|
39
51
|
If you want agents/users to run it immediately without cloning:
|
|
40
52
|
|
|
41
|
-
###
|
|
53
|
+
### Recommended (npm registry)
|
|
42
54
|
|
|
43
55
|
Run once (ephemeral):
|
|
44
56
|
|
|
45
57
|
```bash
|
|
46
|
-
|
|
58
|
+
npx -y @andreasnlarsen/whoop-cli summary --json --pretty
|
|
47
59
|
```
|
|
48
60
|
|
|
49
61
|
Install globally:
|
|
50
62
|
|
|
51
63
|
```bash
|
|
52
|
-
npm install -g
|
|
64
|
+
npm install -g @andreasnlarsen/whoop-cli
|
|
53
65
|
```
|
|
54
66
|
|
|
55
|
-
###
|
|
67
|
+
### Fallback (GitHub source)
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
If npm registry is unavailable for any reason:
|
|
58
70
|
|
|
59
71
|
```bash
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
Install globally:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npm install -g @andreasnlarsen/whoop-cli
|
|
72
|
+
npm exec --yes --package=github:andreasnlarsen/whoop-cli -- whoop summary --json --pretty
|
|
73
|
+
npm install -g github:andreasnlarsen/whoop-cli
|
|
67
74
|
```
|
|
68
75
|
|
|
69
76
|
Then use:
|
|
@@ -177,6 +184,14 @@ Copy the **full redirected URL** from the browser address bar and paste it into
|
|
|
177
184
|
|
|
178
185
|
(If localhost page fails to load, that is usually fine—just copy the URL.)
|
|
179
186
|
|
|
187
|
+
If browser auto-open does not work on your machine, run login with:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
whoop auth login --no-open
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Then open the printed URL manually in your browser.
|
|
194
|
+
|
|
180
195
|
---
|
|
181
196
|
|
|
182
197
|
## For non-technical users
|
|
@@ -304,17 +319,33 @@ One-time setup on npmjs.com (**required**):
|
|
|
304
319
|
3. Optional hardening (recommended): package Settings → Publishing access →
|
|
305
320
|
- "Require two-factor authentication and disallow tokens"
|
|
306
321
|
|
|
307
|
-
Release flow:
|
|
322
|
+
Release flow (branch-protected safe):
|
|
308
323
|
|
|
309
324
|
```bash
|
|
310
|
-
#
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
325
|
+
# 1) prepare release commit on a branch
|
|
326
|
+
git switch main
|
|
327
|
+
git pull --ff-only
|
|
328
|
+
|
|
329
|
+
git switch -c release/vX.Y.Z
|
|
330
|
+
npm version X.Y.Z --no-git-tag-version
|
|
331
|
+
npm install --package-lock-only
|
|
332
|
+
npm run typecheck && npm test && npm run build
|
|
333
|
+
|
|
334
|
+
git add package.json package-lock.json
|
|
335
|
+
git commit -m "chore(release): vX.Y.Z"
|
|
336
|
+
git push -u origin release/vX.Y.Z
|
|
337
|
+
|
|
338
|
+
# 2) open PR: release/vX.Y.Z -> main, then merge
|
|
339
|
+
|
|
340
|
+
# 3) tag from merged main commit
|
|
341
|
+
git switch main
|
|
342
|
+
git fetch origin
|
|
343
|
+
git reset --hard origin/main
|
|
344
|
+
git tag vX.Y.Z
|
|
345
|
+
git push origin vX.Y.Z
|
|
315
346
|
```
|
|
316
347
|
|
|
317
|
-
The GitHub workflow
|
|
348
|
+
The GitHub workflow publishes automatically on `v*` tags via OIDC trusted publishing.
|
|
318
349
|
|
|
319
350
|
### Bootstrap note (first publish)
|
|
320
351
|
|
package/dist/auth/oauth.js
CHANGED
|
@@ -59,15 +59,17 @@ export const exchangeAuthCode = async (config, code) => {
|
|
|
59
59
|
redirect_uri: config.redirectUri,
|
|
60
60
|
});
|
|
61
61
|
};
|
|
62
|
-
export const refreshAuthToken = async (config, refreshToken
|
|
62
|
+
export const refreshAuthToken = async (config, refreshToken) => {
|
|
63
63
|
if (!refreshToken)
|
|
64
64
|
throw usageError('Refresh token is required');
|
|
65
|
+
// WHOOP docs (OAuth refresh flow) require scope=offline in refresh requests.
|
|
66
|
+
// Using token-issued scope strings here can produce malformed refresh requests.
|
|
65
67
|
return exchange(config, {
|
|
66
68
|
grant_type: 'refresh_token',
|
|
67
69
|
refresh_token: refreshToken,
|
|
68
70
|
client_id: config.clientId,
|
|
69
71
|
client_secret: config.clientSecret,
|
|
70
|
-
|
|
72
|
+
scope: 'offline',
|
|
71
73
|
});
|
|
72
74
|
};
|
|
73
75
|
export const parseAuthInput = (input) => {
|
|
@@ -41,7 +41,7 @@ export const refreshProfileToken = async (profileName) => withRefreshLock(profil
|
|
|
41
41
|
if (!refreshToken) {
|
|
42
42
|
throw authError('No refresh token available. Re-run whoop auth login with offline scope.');
|
|
43
43
|
}
|
|
44
|
-
const refreshed = await refreshAuthToken(toOAuthConfig(profile), refreshToken
|
|
44
|
+
const refreshed = await refreshAuthToken(toOAuthConfig(profile), refreshToken);
|
|
45
45
|
profile.tokens = tokenFromOAuth(refreshed, refreshToken);
|
|
46
46
|
await saveProfile(profileName, profile);
|
|
47
47
|
return profile;
|
package/dist/commands/auth.js
CHANGED
|
@@ -70,7 +70,7 @@ export const registerAuthCommands = (program) => {
|
|
|
70
70
|
}, profile.scopes, state);
|
|
71
71
|
let openAttempted = false;
|
|
72
72
|
if (opts.open !== false) {
|
|
73
|
-
openAttempted = tryOpenBrowser(authUrl);
|
|
73
|
+
openAttempted = await tryOpenBrowser(authUrl);
|
|
74
74
|
}
|
|
75
75
|
let code = opts.code;
|
|
76
76
|
if (!code) {
|
|
@@ -1,19 +1,50 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const getOpenCommands = (url) => {
|
|
3
|
+
if (process.platform === 'darwin') {
|
|
4
|
+
return [
|
|
5
|
+
['open', [url]],
|
|
6
|
+
['xdg-open', [url]],
|
|
7
|
+
['cmd', ['/c', 'start', '', url]],
|
|
8
|
+
];
|
|
9
|
+
}
|
|
10
|
+
if (process.platform === 'win32') {
|
|
11
|
+
return [
|
|
12
|
+
['cmd', ['/c', 'start', '', url]],
|
|
13
|
+
['xdg-open', [url]],
|
|
14
|
+
['open', [url]],
|
|
15
|
+
];
|
|
16
|
+
}
|
|
17
|
+
return [
|
|
4
18
|
['xdg-open', [url]],
|
|
5
19
|
['open', [url]],
|
|
6
20
|
['cmd', ['/c', 'start', '', url]],
|
|
7
21
|
];
|
|
22
|
+
};
|
|
23
|
+
const tryCommand = (cmd, args) => new Promise((resolve) => {
|
|
24
|
+
try {
|
|
25
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
26
|
+
let settled = false;
|
|
27
|
+
const finish = (ok) => {
|
|
28
|
+
if (settled)
|
|
29
|
+
return;
|
|
30
|
+
settled = true;
|
|
31
|
+
resolve(ok);
|
|
32
|
+
};
|
|
33
|
+
child.once('error', () => finish(false));
|
|
34
|
+
child.once('spawn', () => finish(true));
|
|
35
|
+
child.unref();
|
|
36
|
+
setTimeout(() => finish(true), 200);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
resolve(false);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
export const tryOpenBrowser = async (url) => {
|
|
43
|
+
const cmds = getOpenCommands(url);
|
|
8
44
|
for (const [cmd, args] of cmds) {
|
|
9
|
-
|
|
10
|
-
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
11
|
-
child.unref();
|
|
45
|
+
if (await tryCommand(cmd, args)) {
|
|
12
46
|
return true;
|
|
13
47
|
}
|
|
14
|
-
catch {
|
|
15
|
-
// try next
|
|
16
|
-
}
|
|
17
48
|
}
|
|
18
49
|
return false;
|
|
19
50
|
};
|
package/openclaw-skill/SKILL.md
CHANGED
|
@@ -1,17 +1,58 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: whoop-cli
|
|
3
|
-
description:
|
|
3
|
+
description: Companion skill for @andreasnlarsen/whoop-cli: agent-friendly WHOOP access via stable CLI JSON (day briefs, health flags, trends, exports) without raw API plumbing.
|
|
4
|
+
metadata:
|
|
5
|
+
openclaw:
|
|
6
|
+
requires:
|
|
7
|
+
bins:
|
|
8
|
+
- whoop
|
|
9
|
+
env:
|
|
10
|
+
- WHOOP_CLIENT_ID
|
|
11
|
+
- WHOOP_CLIENT_SECRET
|
|
12
|
+
- WHOOP_REDIRECT_URI
|
|
13
|
+
primaryEnv: WHOOP_CLIENT_SECRET
|
|
14
|
+
homepage: https://github.com/andreasnlarsen/whoop-cli
|
|
15
|
+
install:
|
|
16
|
+
- kind: node
|
|
17
|
+
package: "@andreasnlarsen/whoop-cli@0.1.3"
|
|
18
|
+
bins:
|
|
19
|
+
- whoop
|
|
20
|
+
label: Install whoop-cli from npm
|
|
4
21
|
---
|
|
5
22
|
|
|
6
23
|
# whoop-cli
|
|
7
24
|
|
|
8
25
|
Use the installed `whoop` command.
|
|
9
26
|
|
|
27
|
+
## Security + credential handling (required)
|
|
28
|
+
|
|
29
|
+
- Never ask users to paste client secrets/tokens into chat.
|
|
30
|
+
- For first-time auth, the user should run login **locally on their own shell**.
|
|
31
|
+
- Prefer read-only operational commands in agent flows (`summary`, `day-brief`, `health`, `trend`, `sync pull`).
|
|
32
|
+
- Do not run `whoop auth login` unless the user explicitly asks for login help.
|
|
33
|
+
- Tokens are stored locally at `~/.whoop-cli/profiles/<profile>.json` by the CLI.
|
|
34
|
+
|
|
35
|
+
## Install / bootstrap
|
|
36
|
+
|
|
37
|
+
If `whoop` is missing:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install -g @andreasnlarsen/whoop-cli@0.1.3
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Optional OpenClaw skill install from package bundle:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
whoop openclaw install-skill --force
|
|
47
|
+
```
|
|
48
|
+
|
|
10
49
|
## Core checks
|
|
11
50
|
|
|
12
51
|
1. `whoop auth status --json`
|
|
13
|
-
2. If unauthenticated
|
|
14
|
-
|
|
52
|
+
2. If unauthenticated, ask the user to run local login:
|
|
53
|
+
- `whoop auth login --client-id ... --client-secret ... --redirect-uri ...`
|
|
54
|
+
3. Validate:
|
|
55
|
+
- `whoop day-brief --json --pretty`
|
|
15
56
|
|
|
16
57
|
## Useful commands
|
|
17
58
|
|