skip to content
stevengates.io
back to home
case study·secondary·since 2026

Google Health CLI

Read-only Go CLI that pipes Google Health into agents.

visit live publicGoCLIOAuth2Agent Tooling

the project

Google Health CLIis a small, single-purpose tool: authenticate to Google Health, then emit your personal health data — heart rate, sleep, steps, weight, exercise — as clean JSON on stdout. That's it. No storage, no opinions, no dashboard. It exists so an agent or script can have the data and decide what to do with it.

It ships two ways from one codebase: a standalone command-line binary, and an OpenClaw / ClawHub skill plugin so agents can call it as a tool. One static Go binary, no runtime to install.

the stack

  • Go 1.25, compiled to a single static binary — nothing to pip install, nothing to keep running.
  • golang.org/x/oauth2 for a loopback + PKCE browser flow, spf13/cobra for the command surface, and hashicorp/go-retryablehttp for a resilient client.
  • An embedded 31-type data catalog (datatypes.json) so the tool knows every Google Health type without a runtime dependency.
  • GoReleaser CI/CD — tag-driven, cross-compiled to linux / macOS / Windows × amd64 / arm64, with the version injected from the git tag via ldflags. Also go install-able and auto-published to ClawHub on skill-doc changes.

the hard problems

  • Read-only enforced at build time, not by convention.The core promise is “this never mutates your data.” So init() validates the embedded catalog against a closed allowlist of read operations and panics the binary if any write op (create/update/batchDelete) ever leaks in — caught at startup, guarded by a test. An agent can't be tricked into a write because the write path doesn't compile-and-run.
  • Per-type filter quirks.Google Health rejects the “obvious” time filter on some types — sleep, for instance, won't filter on the start time, only the end. The catalog records the member that's actually filterable per type, and the tool auto-detects the record family (Sample / Interval / Daily) to pick the right RFC3339 / civil / date format.
  • OAuth done properly. A random loopback listener, PKCE verifier, state check, access_type=offline to force a refresh token, a 5-minute window, and an injectable browser-opener so the flow is testable.
  • Credentials where they belong. Tokens live in the non-roaming OS cache dir at 0600, auto-migrated forward from an older path, with a privacy notice printed to stderr on every data-emitting command.

lessons learned

The sharp one: metadata must match behavior, or agents mis-authorize.The catalog's advertised operations drifted from the tool's real read-only behavior once — which is exactly the kind of thing an agent reads to decide what it's allowed to do. The fix wasn't a doc edit; it was the build-time guard and a test that fails if they ever disagree again. Conventions across the CLI family (credential discovery order, permission bits, one-shot migration) are codified in a shared CLI_CONVENTIONS.md reused by its sibling tools.

what this demonstrates

  • Safety as an invariant.“Read-only” is enforced by the binary, not promised in a README.
  • Agent-ready tooling. Clean JSON contracts, one job done well, callable as a skill — the unglamorous plumbing that makes autonomous systems actually work.
  • Real release engineering. Tag-driven, cross-platform, reproducible builds with no manual version bumping.