From 74ae858867eb27ce90083ec268fba56c02d488ea Mon Sep 17 00:00:00 2001
From: DTTerastar
Date: Sat, 25 Apr 2026 18:19:19 -0400
Subject: [PATCH] feat: add 'prime' subcommand for LLM agent orientation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Per quantcli shared contract §6, every CLI exposes 'prime' printing a
one-screen primer aimed at LLM agents calling the CLI as a tool. Same
section structure across all three repos (WHAT IT IS / OUTPUT FORMATS /
AUTH / DATE FLAGS / SUBCOMMANDS / EXAMPLES / GOTCHAS) so an agent that
has read crono's prime knows where to look in withings's.
https://github.com/quantcli/common/blob/main/CONTRACT.md#6-the-prime-subcommand
Withings-specific content covers:
- All five subcommands (activity / sleep / workouts / measurements /
intraday) with output schema sketches
- OAuth2 setup including the redirectmeto.com workaround for Withings's
HTTPS callback requirement
- Rate-limit warning (status 601) and intraday firehose caveat
- jq recipes for sleep efficiency, HR distribution, weight latest, etc.
Also extends the rootCmd Long description so 'withings-export --help'
points agents at 'prime' from the top.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
cmd/prime.go | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++
cmd/root.go | 7 +++
2 files changed, 165 insertions(+)
create mode 100644 cmd/prime.go
diff --git a/cmd/prime.go b/cmd/prime.go
new file mode 100644
index 0000000..9a03462
--- /dev/null
+++ b/cmd/prime.go
@@ -0,0 +1,158 @@
+package cmd
+
+import (
+ "fmt"
+
+ "github.com/spf13/cobra"
+)
+
+const primeText = `withings-export — primer for LLM agents
+=======================================
+
+WHAT IT IS
+ A CLI that reads your personal Withings health data — activity, sleep,
+ workouts, body measurements (weight/fat/BP/etc), and minute-level
+ intraday samples (HR/HRV/SpO2/steps) — and prints it on stdout.
+
+OUTPUT FORMATS
+ Default: narrow, fitdown-style markdown — date-grouped headings, tight
+ one-line stat blocks per row, easy to skim and easy for an LLM to
+ consume inline.
+
+ --format json Pretty-printed JSON ARRAY of full rows. Use this when
+ you want the complete row, when piping to jq, or when
+ round-tripping into other tools.
+
+ --format csv Spreadsheet-friendly columnar output. Useful for
+ pandas, Excel, or quick correlations across rows.
+
+ Errors go to stderr. You do NOT need '2>&1'. Exit code is 0 on
+ success and non-zero on auth or network failure. An empty result is
+ success — markdown is empty, JSON is '[]', CSV has just the header.
+
+AUTH
+ Withings auth is OAuth2. You need a Withings developer app — create
+ one at https://developer.withings.com/. Withings requires an HTTPS
+ callback URL; the recommended workaround is to register
+ https://redirectmeto.com/http://localhost:8128/oauth/authorize and set
+ WITHINGS_CALLBACK_URL to that exact value (the CLI binds locally and
+ catches the redirect).
+
+ 'withings-export auth login' opens a browser to authorize and writes
+ ~/.config/withings-export/auth.json (access/refresh tokens, user id,
+ client id/secret). Subsequent calls auto-refresh ~5 min before expiry.
+
+ 'withings-export auth status' is a fast local check that exits 0 when a
+ saved token is present and not yet expired, 1 with a clear "not logged
+ in" or "token expired" message otherwise. No network call.
+
+ 'withings-export auth refresh' forces a refresh now.
+ 'withings-export auth logout' deletes the stored tokens.
+
+ Optional env vars (read by 'auth login' so the prompts can be skipped):
+ WITHINGS_CLIENT_ID developer app client id
+ WITHINGS_CLIENT_SECRET developer app client secret
+ WITHINGS_CALLBACK_URL redirect URI registered with Withings
+
+DATE FLAGS (every export subcommand accepts these)
+ --since VALUE inclusive lower bound
+ --until VALUE inclusive upper bound; defaults to now
+ VALUE: today | yesterday | YYYY-MM-DD | Nd/Nw/Nm/Ny
+
+ See https://github.com/quantcli/common/blob/main/CONTRACT.md#3-date-flags
+ for the cross-CLI specification.
+
+SUBCOMMANDS
+
+ activity — daily activity rollups (steps, distance, calories,
+ active-time bands, HR average/min/max, HR zones).
+ Default window: 30d.
+ JSON keys: date, timezone, steps, distance, elevation, soft, moderate,
+ intense, active, calories, totalcalories, hr_average,
+ hr_min, hr_max, hr_zone_0..3.
+
+ sleep — nightly sleep summaries (light/deep/REM minutes,
+ latency, score, HR & RR ranges, snore/apnea). Default
+ window: 30d.
+ --derive for nights with no Withings summary, polyfill
+ start/end from intraday HR samples. Adds a
+ 'source' column distinguishing summary vs
+ derived rows.
+ JSON keys: id, timezone, startdate, enddate, date, data{...}, source.
+
+ workouts — workouts (runs/walks/bikes/swims/lifts/etc) with
+ calories, effective duration, distance, HR/zones,
+ device id, category code. Default window: 90d.
+ JSON keys: id, category, timezone, startdate, enddate, date,
+ deviceid, data{...}.
+
+ measurements — body measurements: weight, body fat %, lean mass,
+ BP, heart pulse, SpO2, body temp, hydration, bone mass,
+ PWV, VO2 max. Default window: 30d.
+ --types LIST comma-separated measure type codes (e.g.
+ '1,6,76' = weight,fat_ratio,muscle_mass).
+ JSON keys: date, type, type_code, value, device_id, grp_id.
+
+ intraday — minute-level samples (HR, HRV rmssd/sdnn1, SpO2, steps,
+ distance) from Apple Watch via HealthKit or native
+ Withings trackers. Default window: 1d.
+ Withings caps requests at 24h windows; the CLI auto-
+ chunks wider ranges.
+
+EXAMPLES
+
+ # Last week's sleep, scannable
+ withings-export sleep --since 7d
+
+ # Workout HR distribution as JSON
+ withings-export workouts --since 30d --format json | jq '
+ .[] | { date: .date, category: .category, hr_avg: .data.hr_average }'
+
+ # Latest weight reading
+ withings-export measurements --since 30d --types 1 --format json |
+ jq 'sort_by(.date) | last'
+
+ # Resting HR across last 30 days (intraday is dense — narrow window)
+ withings-export intraday --since 1d --format json |
+ jq '[.[] | select(.heart_rate > 0) | .heart_rate] | min'
+
+ # Sleep efficiency derived from light+deep+rem vs in-bed time
+ withings-export sleep --since 30d --format json | jq '.[] | {
+ date,
+ sleep_min: ((.data.lightsleepduration + .data.deepsleepduration +
+ .data.remsleepduration) / 60),
+ inbed_min: ((.enddate - .startdate) / 60)
+ }'
+
+GOTCHAS
+ - Times are LOCAL. RFC3339 fields in CSV/markdown carry the user's
+ offset; epoch seconds are timezone-agnostic in JSON.
+ - 'intraday' is a firehose. A 7d query returns ~10K rows and takes
+ minutes (auto-chunked into 24h slices, each rate-limited). Stick
+ to 1-2d unless you need the full history.
+ - Withings rate-limits aggressive callers (HTTP 601 "Too many
+ requests"). 'sleep --derive' on a wide window throttles itself
+ (250ms between calls); ad-hoc loops should do the same.
+ - Sleep score and apnea fields are populated only on supported devices.
+ 'data.sleep_score' is null on unsupported wakeup-light models.
+ - 'workouts' category codes are integers; common ones are mapped to
+ string names (walk/run/bicycling/...) but unknown codes pass through
+ as 'unknown' with the numeric category_code preserved.
+`
+
+var primeCmd = &cobra.Command{
+ Use: "prime",
+ Short: "Print an LLM-targeted primer (output formats, subcommands, jq recipes)",
+ Long: `Print a one-screen primer aimed at LLM agents calling this CLI as a tool.
+Covers the output formats (markdown by default, --format json/csv for structured),
+auth subcommands and OAuth setup, the subcommands and what their rows look like,
+the shared date flags, and a few jq recipes for common questions.`,
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ _, err := fmt.Fprint(cmd.OutOrStdout(), primeText)
+ return err
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(primeCmd)
+}
diff --git a/cmd/root.go b/cmd/root.go
index c31bc3a..70b5ba4 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -9,6 +9,13 @@ import (
var rootCmd = &cobra.Command{
Use: "withings-export",
Short: "CLI to export health data from Withings",
+ Long: `withings-export reads your personal Withings health data — activity,
+sleep, workouts, body measurements, intraday samples — and prints it on
+stdout. Default output is narrow, fitdown-style markdown; pass
+--format json or --format csv for structured output.
+
+LLM agents: run 'withings-export prime' for a one-screen orientation
+(I/O contract, subcommands, date flags, jq recipes).`,
}
func Execute() {