| name | google-health-cli |
| description | Read your Google Health data from any agent. Lists data points for every Google Health data type (heart rate, resting heart rate, sleep, steps, distance, weight, blood oxygen, VO2 max, exercise, and more), returns server-side daily roll-ups (one reconciled total per day), gives a parsed view of exercise sessions, and can GET any read-only v4 API path. A self-contained, read-only client for the Google Health v4 API: authenticates via OAuth2 (token cached locally and auto-refreshed) and emits JSON. It does NO filtering, merging, or writing — the caller decides what to do with the data. Single static binary, no Python or other runtime. NOTE: needs your own Google Cloud OAuth client and a one-time interactive browser login; runs headless afterward.
|
| metadata | {"openclaw":{"emoji":"🩺","homepage":"https://github.com/stozo04/google-health-cli","primaryEnv":"GOOGLE_HEALTH_CLIENT_ID","permissions":{"network":"[Truncated]","files.read":"[Truncated]","files.write":"[Truncated]"},"requires":{"bins":"[Truncated]","env":"[Truncated]"},"envVars":["[Truncated]","[Truncated]","[Truncated]","[Truncated]","[Truncated]"]}} |
Google Health — read-only data extractor
Read your Google Health data from any agent. This is a generic, read-only client
for the Google Health v4 API: it authenticates, pulls data points for any data type, and
prints JSON. It does no filtering, merging, or interpretation — you get the data and
parse whatever you care about. Single static binary — no Python or other runtime.
⚠️ Read-only. It requests only read-only Google Health scopes and never writes your
health data.
🔒 Privacy — sensitive health data leaves on stdout. Every data list, rollup daily,
sessions, and api get prints your personal health information (heart rate, sleep, weight,
exercise, blood oxygen, profile, …) to stdout as JSON. In an agent or pipeline, stdout may be
logged, summarized, persisted, or transmitted to other tools, model providers, or third
parties. Treat this output as sensitive PII: only run it in contexts you trust to handle health
data, and do not persist or forward it unless you intend to. The tool never sends your data
anywhere itself (it only talks to Google's API over HTTPS) — what happens to stdout is the
caller's responsibility. Every data-emitting command also prints a one-line
privacy notice to stderr at run time as an execution-time reminder (it stays on stderr, never
on the JSON stdout).
🧭 Data minimization & consent. Request only the data types and the narrowest time
window you actually need (--days/--date/--from/--to, not --all); prefer the typed
data/rollup/sessions commands over api get; and don't persist or forward the output beyond
the task at hand. Run this only against an account whose owner has knowingly consented to
having their health data read by this agent — the one-time auth login is the owner consenting to
read-only access for the tool, not consent for a downstream agent to collect, retain, or transmit
that data more broadly.
🩺 doctor prints local environment metadata. The doctor JSON includes your
token-cache and config file paths, the configured account (user), and the API
base URL — local filesystem layout and account identifiers rather than health data, but
still local environment metadata that an agent or pipeline may log, forward, or persist.
It exists to diagnose your own setup; treat its output like any other local path/account
detail and don't paste it into untrusted contexts.
Setup (one time)
Unlike a simple email/password tool, Google Health uses OAuth, so first-time setup needs a
human in a browser once. After that it runs headless (the token auto-refreshes).
- Install the binary:
go install github.com/stozo04/google-health-cli/cmd/google-health-cli@latest
- Create a Google Cloud OAuth client (Desktop app, read-only Health scopes, ~10 min,
no fees). Full walkthrough: the repo's OAUTH_SETUP.md.
- Provide the client via
GOOGLE_HEALTH_CLIENT_ID / GOOGLE_HEALTH_CLIENT_SECRET
(or a config.json with those keys in the working directory).
- Authorize once (opens a browser, asks a human to consent to read-only access):
google-health-cli auth login
google-health-cli doctor
The token is cached 0600 and auto-refreshed; every later run is headless.
Credentials
| Variable | Required | Notes |
|---|
GOOGLE_HEALTH_CLIENT_ID | ✓ | OAuth client ID (Desktop app) |
GOOGLE_HEALTH_CLIENT_SECRET | ✓ | OAuth client secret |
GOOGLE_HEALTH_CONFIG | — | Path to a config.json holding the above instead |
GOOGLE_HEALTH_TOKEN_CACHE | — | Override the cached-token path |
GOOGLE_HEALTH_BASE_URL | — | Override the API base URL |
config.json (gitignored) is an alternative to the env vars:
{
"client_id": "PASTE_CLIENT_ID",
"client_secret": "PASTE_CLIENT_SECRET"
}
🔑 Protect your credentials. The OAuth client_id/client_secret and the cached token
are sensitive secrets stored on disk in plaintext (config.json / token.json, written 0600).
They're gitignored — keep it that way: never commit them, copy them into a shared folder, back them
up to an untrusted location, or paste them into logs or chat. Rotate the OAuth client (and run
auth logout) if one may have leaked.
Commands
Discover the data types
google-health-cli types list
google-health-cli types describe heart-rate
Read data points (the core)
google-health-cli data list heart-rate --days 1
google-health-cli data list sleep --days 14
google-health-cli data list daily-resting-heart-rate --days 30
google-health-cli data list steps --date 2026-06-16
google-health-cli data list weight --from 2026-01-01T00:00:00Z --to 2026-07-01T00:00:00Z
google-health-cli data list distance --all
stdout is always a JSON array of the raw data points (verbatim from the API); a one-line
count goes to stderr. Window flags (precedence: --all > --from/--to > --date/--days):
--date today|yesterday|YYYY-MM-DD, --days N (default 7), --from/--to (RFC3339, together),
--all (no filter).
Example (data list daily-resting-heart-rate):
[
{
"dataSource": { "platform": "FITBIT", "device": { "displayName": "Google Pixel Watch 4 (45mm)" } },
"dailyRestingHeartRate": { "date": { "year": 2026, "month": 6, "day": 17 }, "beatsPerMinute": "78" }
}
]
A few types are roll-up/reconcile-only and can't be listed (types list marks listable ones
with *); read those with rollup daily <type> instead (below). One type —
daily-heart-rate-zones — supports neither list nor rollup daily; it is reachable only via
the api get escape hatch.
Daily roll-ups (server-side totals)
google-health-cli rollup daily steps --days 7
google-health-cli rollup daily active-minutes --days 7
rollup daily <type> returns the API's daily roll-up rows — one value per civil (local)
calendar day — instead of the raw per-minute/per-sample points data list returns. It's the cheap
way to get daily totals (e.g. a steps total per day rather than a per-minute dump) and the only
way to read the roll-up-only types that have no list op (active-minutes, total-calories,
floors, calories-in-heart-rate-zone, time-in-heart-rate-zone). stdout is a JSON array of the
raw roll-up rows; an N <type> daily rollup(s) count goes to stderr. Each value is reconciled
across data sources (and excludes off-wrist intervals), so it is not a naive sum of data list
points. Window flags mirror data list minus --all (the range is required).
[
{
"civilStartTime": { "date": { "year": 2026, "month": 6, "day": 16 }, "time": {} },
"civilEndTime": { "date": { "year": 2026, "month": 6, "day": 17 }, "time": {} },
"steps": { "countSum": "8000" }
}
Parsed exercise sessions (convenience)
google-health-cli sessions --days 14 --json
google-health-cli sessions --raw
[
{ "date": "2026-06-16", "exercise_type": "ELLIPTICAL", "duration_min": 35,
"avg_hr": 130, "calories": 236, "platform": "FITBIT" }
]
sessions returns all exercise types (no filtering); the caller keeps what it wants.
Raw read-only API access
google-health-cli api get /v4/users/me/profile
google-health-cli api get /v4/users/me/settings
⚠️ Sensitive endpoints — broader than the typed metrics. api get can read any
read-only v4 path, including users/me/profile and users/me/settings — account, identity, and
settings data that the typed commands don't surface. Its responses are personal data printed to
stdout, so the same Privacy caution above applies, and the broad reach makes
over-collection easy. It stays GET-only (no path can write or delete) and is
constrained to the read-only v4 surface (/v4/...): a non-v4/ path, an absolute URL, or a
.. traversal — literal or percent-encoded (%2e%2e, ..%2f) — is rejected (exit 64) before
any request is made. Still, prefer the typed
data/rollup/sessions commands and reach for api get only when you need an endpoint they
don't model.
Full command reference
| Command | What it does | --json |
|---|
auth login | logout | status | OAuth login (one-time browser), token mgmt | status |
doctor | Config + token validity; reports configFound/clientIdLoaded and exits non-zero (78) when no config / no client_id is found, else 2 if not authed | always |
types list | describe <type> | Inspect the data-type catalog (no network) | ✓ |
data list <type> | List data points for a type (always JSON) | n/a (always) |
rollup daily <type> | Server-side daily totals, reconciled per civil day | n/a (always) |
sessions [--days N] [--raw] | Parsed exercise sessions (convenience) | ✓ |
api get <path> | Raw read-only GET of any v4 path | n/a |
config show | path | Inspect resolved config (client_secret masked) | show |
version | Build metadata (also --version) | ✓ |
completion <shell> | Shell completion (bash/zsh/fish/powershell) | — |
Conventions
- stdout is parseable; human hints, counts, and logs go to stderr, never interleaved.
- Exit codes:
0 success, 2 auth/API failure (incl. doctor when unauthenticated),
64 usage error (incl. an expired token with no OAuth client credentials to refresh it),
78 config error. An expired token plus an undiscoverable/credential-less config fails fast
with an actionable message — not the cryptic oauth2 "Could not determine client ID" error.
- Read-only: six read-only
googlehealth.*.readonly scopes; no write operations exist.
- Secrets:
config.json and the token cache are gitignored — never commit them.
- Time-filter formats are handled for you per data type (civil wall-clock, RFC3339
instant, or date-only); a
--date/--days window always covers your local calendar
days. If a type rejects server-side filtering, re-run with --all.
See the repo's AGENTS.md for the exact --json shapes and exit-code contract.