Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app-store/whoop/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
WHOOP_CLIENT_ID=
WHOOP_CLIENT_SECRET=
# for bootstrap_whoop_oauth.py
WHOOP_REDIRECT_URI=http://127.0.0.1:8765/callback
1 change: 1 addition & 0 deletions app-store/whoop/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
artifacts/*
55 changes: 55 additions & 0 deletions app-store/whoop/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""Configuration helpers for the WHOOP Truffle app."""

from __future__ import annotations

import os
from dataclasses import dataclass
from pathlib import Path


DEFAULT_WHOOP_API_BASE = "https://api.prod.whoop.com/developer"
DEFAULT_WHOOP_TOKEN_URL = "https://api.prod.whoop.com/oauth/oauth2/token"
DEFAULT_TOKEN_STORE_PATH = Path.home() / ".whoop-truffle" / "oauth.json"


def _parse_optional_float(raw: str | None) -> float | None:
value = (raw or "").strip()
if not value:
return None
try:
return float(value)
except ValueError:
return None


@dataclass(frozen=True, slots=True)
class WhoopConfig:
client_id: str = ""
client_secret: str = ""
redirect_uri: str = ""
access_token: str = ""
refresh_token: str = ""
api_base: str = DEFAULT_WHOOP_API_BASE
token_url: str = DEFAULT_WHOOP_TOKEN_URL
token_store_path: Path = DEFAULT_TOKEN_STORE_PATH
access_token_expires_at: float | None = None
token_scope: str = ""
token_type: str = "bearer"

@classmethod
def from_env(cls) -> WhoopConfig:
token_store_raw = os.getenv("WHOOP_TOKEN_STORE_PATH", "").strip()
token_store_path = Path(token_store_raw) if token_store_raw else DEFAULT_TOKEN_STORE_PATH
return cls(
client_id=os.getenv("WHOOP_CLIENT_ID", "").strip(),
client_secret=os.getenv("WHOOP_CLIENT_SECRET", "").strip(),
redirect_uri=os.getenv("WHOOP_REDIRECT_URI", "").strip(),
access_token=os.getenv("WHOOP_ACCESS_TOKEN", "").strip(),
refresh_token=os.getenv("WHOOP_REFRESH_TOKEN", "").strip(),
api_base=os.getenv("WHOOP_API_BASE", DEFAULT_WHOOP_API_BASE).strip() or DEFAULT_WHOOP_API_BASE,
token_url=os.getenv("WHOOP_TOKEN_URL", DEFAULT_WHOOP_TOKEN_URL).strip() or DEFAULT_WHOOP_TOKEN_URL,
token_store_path=token_store_path,
access_token_expires_at=_parse_optional_float(os.getenv("WHOOP_ACCESS_TOKEN_EXPIRES_AT")),
token_scope=os.getenv("WHOOP_TOKEN_SCOPE", "").strip(),
token_type=os.getenv("WHOOP_TOKEN_TYPE", "bearer").strip() or "bearer",
)
Binary file added app-store/whoop/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 10 additions & 0 deletions app-store/whoop/prompts/whoop_cli_prompts.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Check my WHOOP connection status.
If auth fails, explain exactly what failed and what needs to be reauthorized.
---
Pull my latest WHOOP summary and give me recovery, sleep quality, day strain, and recent workouts.
---
Show my last 7 recovery records and summarize the trend in recovery score, HRV, and resting heart rate.
---
Show my last 7 cycles and tell me whether day strain is rising, falling, or flat.
---
Pull my WHOOP sleep records from the last week and tell me which night had the best and worst sleep performance.
Loading