git clone https://github.com/jincai/openclaw-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/jincai/openclaw-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/whoop" ~/.claude/skills/jincai-openclaw-skills-whoop && rm -rf "$T"
T=$(mktemp -d) && git clone --depth=1 https://github.com/jincai/openclaw-skills "$T" && mkdir -p ~/.openclaw/skills && cp -r "$T/whoop" ~/.openclaw/skills/jincai-openclaw-skills-whoop && rm -rf "$T"
whoop/SKILL.mdWHOOP Health Data Sync
Sync WHOOP health data (recovery, sleep, strain, workouts) to local markdown files. Pure Python, zero dependencies — uses only stdlib (
urllib, json, http.server) + curl.
What's Synced
| Category | Metrics |
|---|---|
| Recovery | Score (🔴🟡🟢), HRV (RMSSD), Resting HR, SpO2, Skin Temp |
| Sleep | Performance/Efficiency/Consistency, Stages (Light/Deep/REM/Awake), Respiratory Rate, Sleep Need breakdown (baseline + debt + strain), Balance (surplus/deficit) |
| Day Strain | Strain (0-21), Calories (kJ + kcal), Avg/Max HR |
| Workouts | Sport name, Duration, Strain, Calories, Avg/Max HR, Distance, Elevation (↑gain / net), HR Zones |
| Weekly | Averages for recovery/HRV/RHR, sleep performance/duration, strain, workout count |
Setup
Step 1: Create WHOOP Developer App
- Go to https://developer-dashboard.whoop.com/
- Sign in with your WHOOP account
- Click Create Application
- Name: anything (e.g. "My Health Sync")
- Redirect URI:
http://localhost:9527/callback - Scopes: select all
scopes +read:*offline
- Note your Client ID and Client Secret
⚠️ Redirect URI must be exactly
— this is whathttp://localhost:9527/callbacklistens on.auth.py
Step 2: Store Credentials
Option A: 1Password (recommended for OpenClaw users)
- In 1Password, create a Login item in your Agent vault:
- Name:
whoop - Username:
<your Client ID> - Password:
<your Client Secret>
- Name:
- The scripts auto-read from 1P via Service Account (reads
)~/.openclaw/.op-token
Option B: Environment Variables
export WHOOP_CLIENT_ID="your-client-id" export WHOOP_CLIENT_SECRET="your-client-secret"
Pass inline when running the scripts. Don't add to ~/.zshrc — use 1P or pass per-command.
Step 3: Authorize (one-time OAuth flow)
Authorization works differently depending on where OpenClaw runs:
🖥️ Local Machine (browser on same machine)
Your browser can reach
localhost:9527 directly — fully automatic:
python3 scripts/auth.py
- Script prints an authorization URL — open it in your browser
- Log in to WHOOP and click Authorize
- Browser redirects to
— script catches it automaticallylocalhost:9527/callback - Tokens saved ✅
🌐 Remote Server (OpenClaw on VPS/server, browser on your laptop)
The server can't receive the
localhost:9527 callback from your browser. Two-step manual flow:
Step A — Get the authorization URL:
python3 scripts/auth.py --print-url
Open the printed URL in your local browser, log in and authorize.
Step B — After authorizing, the browser redirects to
which won't load (that's fine!).localhost:9527/callback?code=xxx&state=yyy
Copy the full URL from your browser's address bar, then either:
# Option 1: Paste the full callback URL python3 scripts/auth.py --callback-url "http://localhost:9527/callback?code=xxx&state=yyy" # Option 2: Extract just the 'code' parameter python3 scripts/auth.py --code "the-code-value"
Or tell your agent: paste the callback URL directly in chat, or save the code to 1Password — the agent can read it from there.
Usage
# Sync today python3 scripts/sync.py # Sync specific date python3 scripts/sync.py --date 2026-03-07 # Sync last 7 days python3 scripts/sync.py --days 7 # Weekly summary report python3 scripts/sync.py --weekly # Custom output directory python3 scripts/sync.py --output-dir /tmp/whoop-data
Output:
~/.openclaw/workspace/health/whoop-YYYY-MM-DD.md
Cron (automated daily sync)
openclaw cron add \ --name whoop-daily \ --schedule "0 10 * * *" \ --timezone Asia/Shanghai \ --task "Run: python3 ~/.openclaw/workspace/skills/whoop/scripts/sync.py --days 2. Then read the generated markdown files and send me the latest day's report."
This syncs yesterday + today at 10:00 AM daily (WHOOP finalizes sleep data after waking).
File Structure
skills/whoop/ ├── SKILL.md # This file ├── scripts/ │ ├── auth.py # OAuth authorization (local + remote modes) │ └── sync.py # Daily sync + weekly reports └── data/ ├── tokens.json # OAuth tokens (auto-refreshed, chmod 600) └── .auth_state # Temp state for remote auth flow (auto-cleaned)
Token Refresh — 授权一次,永久自动续期
正常情况下只需授权一次,后续完全自动。
工作原理:
- Access token 有效期 1 小时,每次
运行时自动检测过期并 refreshsync.py - Refresh token 是滚动更新的:每次刷新都会返回新的 refresh token,旧的作废
- 只要 cron 每天跑(每天至少 refresh 一次),token 链就永远不会断
什么时候需要重新授权:
- WHOOP 端主动撤销(你在 WHOOP app/网站里手动撤销了应用授权)
- 长时间未使用(比如停了几个月没跑 sync,refresh token 可能过期)
- WHOOP 更新了 OAuth 策略(极少发生)
如果 sync.py 报
Token refresh failed (403),重新跑一次 auth.py 即可。
Token exchange 使用
发请求,避免 Python urllib 被 WHOOP 的 Cloudflare CDN 拦截(error 1010)。curl
Troubleshooting
| Problem | Fix |
|---|---|
| Run first |
| Re-run to re-authorize |
+ | Cloudflare block — should be fixed (uses curl). If still failing, check network/proxy |
| You opened a different auth URL than what auth.py generated. Restart auth.py and use its URL |
| WHOOP may not have data yet (sleep scores finalize after waking) |
| Check exists, or use env vars instead |
| Port 9527 in use | then retry |
Agent Notes
When the user asks to re-authorize WHOOP:
- Local: run
in background, capture its auth URL (useauth.py
for unbuffered stdout), send the URL to the user-u - Remote: run
, send the URL, then wait for user to provide the callback URL or codeauth.py --print-url - After authorization, verify by running
to confirm tokens worksync.py --days 1