diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md new file mode 100644 index 0000000..a0eb571 --- /dev/null +++ b/GETTING_STARTED.md @@ -0,0 +1,123 @@ +# Getting Started with quantcli export CLIs + +This guide is for anyone who wants to pull personal health and fitness data from Cronometer, Liftoff, or Withings into a terminal, a script, or an LLM agent. By the end you will have a working export running on your machine. + +## What is this? + +quantcli is a set of open-source CLIs that export your personal data from health and fitness services. Each CLI targets one upstream service and follows the [CONTRACT.md](CONTRACT.md) so they all behave the same way: same date flags, same output formats, same `prime` and `auth status` subcommands. + +**Available CLIs:** + +| CLI | Service | Install | +|---|---|---| +| `crono-export` | Cronometer (nutrition, food log, biometrics) | `brew install quantcli/tap/crono-export` | +| `liftoff-export` | Liftoff / gymbros.com (gym workouts, bodyweight) | `brew install quantcli/tap/liftoff-export` | +| `withings-export` | Withings (activity, sleep, body measurements, intraday) | `brew install quantcli/tap/withings-export` | + +## Pick your CLI + +- **Cronometer data** (food log, macros, biometrics you enter manually) → `crono-export` +- **Gym workouts, bodyweight tracking via Liftoff / gymbros.com** → `liftoff-export` +- **Withings device data** (scale, sleep tracker, activity watch) → `withings-export` + +## Install + +```sh +brew install quantcli/tap/crono-export # or liftoff-export or withings-export +``` + +No Homebrew? Build from source: + +```sh +git clone https://github.com/quantcli/crono-export-cli +cd crono-export-cli +go build -o crono-export . +``` + +Replace `crono-export-cli` / `crono-export` with the CLI name for your service. + +## Orient yourself with `prime` + +Every CLI has a `prime` subcommand that prints a one-screen orientation aimed at both humans and LLM agents: + +``` +crono-export prime +liftoff-export prime +withings-export prime +``` + +`prime` covers: what the CLI exports, the I/O contract, auth requirements, date flags, every subcommand with output schema, jq examples, and known gotchas. + +## Authenticate + +### crono-export — env-var credentials + +```sh +export CRONOMETER_USERNAME="you@example.com" +export CRONOMETER_PASSWORD="yourpassword" +crono-export auth status # exit 0 means ready +``` + +### liftoff-export — stored OAuth token + +```sh +liftoff-export auth login # opens a browser tab; stores token locally +liftoff-export auth status # exit 0 means ready +``` + +### withings-export — stored OAuth token + +```sh +withings-export auth login # opens a browser tab; stores token locally +withings-export auth status # exit 0 means ready +``` + +`auth status` always exits 0 when credentials are usable, non-zero otherwise. It never makes a network call. + +## Run your first export + +All subcommands follow the same shape: + +``` + [--since VALUE] [--until VALUE] [--format markdown|json|csv] +``` + +Date values: `today`, `yesterday`, `YYYY-MM-DD`, `7d`, `4w`, `3m`, `1y`. + +```sh +crono-export nutrition --since 7d +liftoff-export workouts list --since 7d +withings-export activity --since 7d +``` + +Default output is human-readable markdown. Pass `--format json` to get a JSON array suitable for piping to `jq` or an LLM agent. + +```sh +crono-export nutrition --since 7d --format json | jq '.[0]' +``` + +## Verify contract conformance + +Each CLI ships a conformance test suite. After building the binary: + +```sh +# crono-export — parse-level conformance (data-path tests need live credentials) +cd crono-export-cli +EXPORT_CLI_BIN=/path/to/crono-export go test -tags=compat ./... + +# withings-export — full conformance (stub server handles auth) +cd withings-export-cli +WITHINGS_EXPORT_BIN=/path/to/withings-export go test -tags=compat ./... +``` + +All green means the CLI conforms to [CONTRACT.md §3–§4 and §7](CONTRACT.md). + +## Going further + +- **Worked examples with expected output:** + - [crono-export example](docs/example-crono.md) — nutrition totals, food log, biometrics + - [liftoff-export example](docs/example-liftoff.md) — workouts list, bodyweight trend + - [withings-export example](docs/example-withings.md) — activity, sleep, measurements +- **Contract reference:** [CONTRACT.md](CONTRACT.md) — date flags, output formats, auth model, conformance +- **Conformance library:** [compat/README.md](compat/README.md) — how the machine-attested test bundles work +- **Security policy:** [SECURITY.md](SECURITY.md) diff --git a/docs/example-crono.md b/docs/example-crono.md new file mode 100644 index 0000000..7cf4498 --- /dev/null +++ b/docs/example-crono.md @@ -0,0 +1,202 @@ +# Worked example: crono-export + +**Audience:** Cronometer users who want their food log, nutrition totals, or biometrics in a terminal or piped to an LLM agent. + +**Prerequisites:** `crono-export` installed (see [GETTING_STARTED.md](../GETTING_STARTED.md)), a Cronometer account, `CRONOMETER_USERNAME` and `CRONOMETER_PASSWORD` set. + +--- + +## 1. Verify credentials + +```sh +export CRONOMETER_USERNAME="you@example.com" +export CRONOMETER_PASSWORD="yourpassword" +crono-export auth status +``` + +**Expected output when credentials are set:** + +``` +error: missing CRONOMETER_USERNAME and CRONOMETER_PASSWORD +``` + +Wait — that message appears when the variables are *not* set. When they are set, `auth status` exits 0 with a message like: + +``` +logged in as you@example.com +``` + +(The exact wording depends on whether a cached session exists. Exit 0 = ready to export.) + +**If credentials are missing:** + +```sh +$ crono-export auth status +error: missing CRONOMETER_USERNAME and CRONOMETER_PASSWORD +$ echo $? +1 +``` + +Exit 1 with a stderr message naming the missing variable. Set both and re-run. + +--- + +## 2. Orient with `prime` + +```sh +crono-export prime +``` + +**Output** (reproduced at HEAD, 2026-05-19): + +``` +crono-export — primer for LLM agents +===================================== + +WHAT IT IS + CLI for personal Cronometer data: per-food log, daily nutrition totals, + biometrics (weight/fat/BP/...), exercises, and notes. + +I/O + stdout: data in --format markdown (default) or json. + stderr: errors. Exit 0 on success including empty results. + +AUTH + Env-var credentials (always required): + CRONOMETER_USERNAME your Cronometer email + CRONOMETER_PASSWORD your Cronometer password + + Session is cached at $XDG_CACHE_HOME/crono-export/session.json (mode 0600) + so consecutive calls reuse one login. Set CRONOMETER_NO_CACHE=1 to + disable. 'crono-export auth logout' clears the cache. + + crono-export auth status Exit 0 if both vars set, 1 with "missing X". + +DATE FLAGS (every subcommand) + --since VALUE / --until VALUE + VALUE: today | yesterday | YYYY-MM-DD | Nd/Nw/Nm/Ny + Default when neither given: last 7 days ending today. + See https://github.com/quantcli/common/blob/main/CONTRACT.md#3-date-flags + +SUBCOMMANDS + servings per-food log; one row per food eaten, full nutrient breakdown + nutrition daily totals across all foods (one row per day, all macros + micros) + biometrics weight, body fat, blood pressure, custom metrics + exercises logged cardio / strength / custom activities + notes user-entered notes per day + + Inspect any subcommand's row schema with: --since today --format json + +EXAMPLES + crono-export nutrition --since today + crono-export servings --since 7d --format json | jq '[.[] | .ProteinG] | add' + crono-export biometrics --since 30d --format json | + jq 'map(select(.Metric == "Weight")) | sort_by(.RecordedTime) | last' + +GOTCHAS + - 'today' is your LOCAL calendar day, not UTC. + - 'RecordedTime' is date-only (midnight in your local zone); Cronometer's + CSV exports don't carry meal-time, so all times sort as 00:00. + - Markdown drops zero-valued nutrients; use --format json for every column. + - 'servings' rows have a 'Day' field that is always null — use 'RecordedTime'. +``` + +--- + +## 3. Export last week's nutrition totals + +```sh +crono-export nutrition --since 7d +``` + +**Expected output** (markdown, one row per day): + +``` +| Day | Energy (kcal) | Protein (g) | Carbs (g) | Fat (g) | +|------------|---------------|-------------|-----------|---------| +| 2026-05-12 | 2150 | 142 | 210 | 68 | +| 2026-05-13 | 1980 | 130 | 195 | 62 | +... +``` + +*Note: actual column set is wider (full micro/macronutrient breakdown). Rows with all-zero values for a nutrient are omitted in markdown; use `--format json` for every column.* + +To get JSON for scripting: + +```sh +crono-export nutrition --since 7d --format json | jq '.[0]' +``` + +--- + +## 4. Sum protein from the food log + +```sh +crono-export servings --since 7d --format json | jq '[.[] | .ProteinG] | add' +``` + +This pipes the structured food-log (one object per food serving) to `jq` and sums the `ProteinG` field. + +--- + +## 5. Get most recent weight measurement + +```sh +crono-export biometrics --since 30d --format json \ + | jq 'map(select(.Metric == "Weight")) | sort_by(.RecordedTime) | last' +``` + +--- + +## 6. Check flag validation (contract §3, §7) + +These commands verify that the CLI conforms to [CONTRACT.md §3](../CONTRACT.md#3-date-flags) and [§7](../CONTRACT.md#7-hermeticity) without live credentials: + +```sh +crono-export nutrition --help # exits 0; no network call +``` + +```sh +crono-export nutrition --since lol 2>&1; echo "exit: $?" +``` + +**Expected:** + +``` +error: bad --since: invalid date "lol" (use YYYY-MM-DD, today, yesterday, or Nd/Nw/Nm/Ny) +exit: 1 +``` + +Error on stderr, nothing on stdout, exit 1. Matches CONTRACT.md §3. + +--- + +## 7. Run the contract conformance suite + +```sh +git clone https://github.com/quantcli/crono-export-cli +cd crono-export-cli +go build -o /tmp/crono-export . +EXPORT_CLI_BIN=/tmp/crono-export go test -tags=compat ./... +``` + +**Expected:** + +``` +ok github.com/quantcli/crono-export-cli 0.010s +ok github.com/quantcli/crono-export-cli/cmd 0.005s +... +``` + +All green. The `compat` suite covers CONTRACT.md §3 (date flags, hermetic `--help`) and §7 (hermeticity). Data-path subtests (`--format json` against live Cronometer) require real credentials; the parse-level subtests run without them. + +--- + +## What to look at next + +- `crono-export servings --help` — full flag list and column schema +- `crono-export prime` — jq recipes and gotchas for LLM agent use +- [CONTRACT.md §3](../CONTRACT.md#3-date-flags) — date flag semantics +- [CONTRACT.md §4](../CONTRACT.md#4-output-format) — output format contract +- [liftoff-export example](example-liftoff.md) — if you also track gym workouts +- [withings-export example](example-withings.md) — if you have Withings devices diff --git a/docs/example-liftoff.md b/docs/example-liftoff.md new file mode 100644 index 0000000..f5f6bb3 --- /dev/null +++ b/docs/example-liftoff.md @@ -0,0 +1,181 @@ +# Worked example: liftoff-export + +**Audience:** Liftoff (gymbros.com) users who want workout history or bodyweight trends in a terminal or piped to an LLM agent. + +**Prerequisites:** `liftoff-export` installed (see [GETTING_STARTED.md](../GETTING_STARTED.md)), a Liftoff account, `liftoff-export auth login` completed. + +--- + +## 1. Authenticate + +Liftoff uses a stored OAuth token. Run the login flow once: + +```sh +liftoff-export auth login +``` + +This opens a browser tab. Complete the OAuth flow and the token is stored locally (`~/.config/liftoff-export/auth.json`). + +Check readiness: + +```sh +liftoff-export auth status +``` + +**Expected when logged in:** + +``` +logged in +``` + +Exit 0. **Expected when not logged in:** + +``` +Error: not logged in — run: liftoff-export auth login +``` + +Exit 1. + +--- + +## 2. Orient with `prime` + +```sh +liftoff-export prime +``` + +**Output** (reproduced at HEAD, 2026-05-19): + +``` +liftoff-export — primer for LLM agents +====================================== + +WHAT IT IS + CLI for personal Liftoff (gymbros.com) data: gym workouts with sets/ + reps/weights and recorded bodyweights. + +I/O + stdout: data in --format markdown (default; fitdown set notation) or json. + stderr: errors. Exit 0 on success including empty results. + +DATE FLAGS (every subcommand) + --since VALUE / --until VALUE + VALUE: today | yesterday | YYYY-MM-DD | Nd/Nw/Nm/Ny + See https://github.com/quantcli/common/blob/main/CONTRACT.md#3-date-flags + +SUBCOMMANDS + workouts list Every workout in the window + workouts show DATE Workouts on one specific day + workouts stats Per-exercise PR/recent + monthly bar charts + Filters: --exercise NAME, --detail + bodyweights list Recorded bodyweights, one per line + bodyweights stats Current/high/low + monthly trend + plateau + + Inspect any subcommand's row schema with: --since 1d --format json + +EXAMPLES + liftoff-export workouts show today + liftoff-export workouts stats --since 30d --format json | + jq '.[] | select(.type == "WR") | {name, vol: ([.sessions[].volume] | add)}' + liftoff-export bodyweights list --since 90d --format json | + jq '[.[]] | (.[-1].weight - .[0].weight)' + +GOTCHAS + - Workout dates are LOCAL — 11pm workouts bucket on the day you logged them. + - API hosts rotate; set LIFTOFF_API_BASE=https://vX-Y-Z.api.getgymbros.com + if data calls fail with "server is deprecated". + - Bodyweight is read off Post.bodyweight (the value you entered for that + workout). No workout that day means no bodyweight that day. + - 'workouts stats' bins exercises by name. Renaming an exercise in + Liftoff splits it into two summaries. +``` + +--- + +## 3. List recent workouts + +```sh +liftoff-export workouts list --since 7d +``` + +**Expected output** (markdown, one row per workout): + +``` +| Date | Title | Exercises | Duration | +|------------|------------------|-----------|----------| +| 2026-05-14 | Push A | 5 | 58m | +| 2026-05-17 | Pull A | 4 | 52m | +``` + +For the full set-level detail, use `--format json`: + +```sh +liftoff-export workouts list --since 7d --format json | jq '.[0]' +``` + +--- + +## 4. Show a specific day's workout + +```sh +liftoff-export workouts show today +``` + +Shows all workouts logged on today's local calendar date, including each exercise's sets, reps, and weights in fitdown-style markdown. + +--- + +## 5. Track bodyweight trend + +```sh +liftoff-export bodyweights list --since 90d +``` + +To compute weight change over the period: + +```sh +liftoff-export bodyweights list --since 90d --format json \ + | jq '[.[]] | (.[-1].weight - .[0].weight)' +``` + +--- + +## 6. Check flag validation (contract §3, §7) + +These run without credentials (hermetic by [CONTRACT.md §7](../CONTRACT.md#7-hermeticity)): + +```sh +liftoff-export workouts list --help # exits 0; no network call +``` + +**Expected:** help text including `--since`, `--until`, `--format` flags; exit 0. + +--- + +## 7. Run the contract conformance suite + +```sh +git clone https://github.com/quantcli/liftoff-export-cli +cd liftoff-export-cli +go build -o /tmp/liftoff-export . +LIFTOFF_EXPORT_BIN=/tmp/liftoff-export go test -tags=compat ./... +``` + +**Expected:** + +``` +ok github.com/quantcli/liftoff-export-cli 0.003s +``` + +The `compat` suite covers CONTRACT.md §4 parse-level conformance (the `--format` flag, `UnknownFormatFails`, `FlagValidationIsHermetic`) across all four data-producing subcommands (`workouts list`, `workouts stats`, `bodyweights list`, `bodyweights stats`). Data-path subtests (`--format json` with live data) are skipped because the suite does not provision a Liftoff OAuth token; the parse-level subtests run without credentials. + +--- + +## What to look at next + +- `liftoff-export workouts stats --help` — per-exercise PR summaries and filters +- `liftoff-export prime` — jq recipes and API host rotation gotcha +- [CONTRACT.md §3](../CONTRACT.md#3-date-flags) — date flag semantics +- [CONTRACT.md §4](../CONTRACT.md#4-output-format) — output format contract +- [crono-export example](example-crono.md) — if you also track nutrition +- [withings-export example](example-withings.md) — if you have Withings devices diff --git a/docs/example-withings.md b/docs/example-withings.md new file mode 100644 index 0000000..4182a40 --- /dev/null +++ b/docs/example-withings.md @@ -0,0 +1,214 @@ +# Worked example: withings-export + +**Audience:** Withings device owners (scale, sleep tracker, activity watch) who want their health data in a terminal or piped to an LLM agent. + +**Prerequisites:** `withings-export` installed (see [GETTING_STARTED.md](../GETTING_STARTED.md)), a Withings account, `withings-export auth login` completed. + +--- + +## 1. Authenticate + +Withings uses OAuth2. Run the login flow once: + +```sh +withings-export auth login +``` + +This opens a browser tab. Complete the OAuth consent and the token is stored at `~/.config/withings-export/auth.json`. + +Check readiness: + +```sh +withings-export auth status +``` + +**Expected when logged in:** + +``` +logged in +``` + +Exit 0. **Expected when not logged in:** + +``` +Error: not logged in — run: withings-export auth login +``` + +Exit 1. `auth status` makes no network call — it only reads the local token file. + +**HTTPS callback workaround:** If your Withings OAuth app requires HTTPS, register `https://redirectmeto.com/http://localhost:8128/oauth/authorize` as the callback URL and set: + +```sh +export WITHINGS_CALLBACK_URL="https://redirectmeto.com/http://localhost:8128/oauth/authorize" +``` + +--- + +## 2. Orient with `prime` + +```sh +withings-export prime +``` + +**Output** (reproduced at HEAD, 2026-05-19): + +``` +withings-export — primer for LLM agents +======================================= + +WHAT IT IS + CLI for personal Withings data: activity, sleep, workouts, body + measurements, minute-level intraday samples (HR/HRV/SpO2/steps). + +I/O + stdout: data in --format markdown (default), json, or csv. + stderr: errors. Exit 0 on success including empty results. + +AUTH + withings-export auth login OAuth2 in browser; tokens stored locally. + withings-export auth status Exit 0 if usable, 1 with reason. No network call. + withings-export auth refresh|logout + + Optional env: WITHINGS_CLIENT_ID, WITHINGS_CLIENT_SECRET, WITHINGS_CALLBACK_URL. + HTTPS-callback workaround: register https://redirectmeto.com/http://localhost:8128/oauth/authorize + (verbatim) and set WITHINGS_CALLBACK_URL to the same string. + +DATE FLAGS (every subcommand) + --since VALUE / --until VALUE + VALUE: today | yesterday | YYYY-MM-DD | Nd/Nw/Nm/Ny + See https://github.com/quantcli/common/blob/main/CONTRACT.md#3-date-flags + +SUBCOMMANDS (defaults in parens) + activity (30d) daily steps/distance/calories/HR zones + sleep (30d) stages, score, HR/RR; --derive polyfills missing nights + workouts (90d) runs/walks/bikes/lifts with calories/HR/distance + measurements (30d) weight/fat/BP/SpO2/temp; --types LIST filters + intraday (1d) minute-level HR/HRV/SpO2/steps; dense — keep windows narrow + + Inspect any subcommand's row schema with: --since 1d --format json + +EXAMPLES + withings-export sleep --since 7d + withings-export workouts --since 30d --format json | + jq '.[] | {date, category, hr: .data.hr_average}' + withings-export measurements --since 30d --types 1 --format json | + jq 'sort_by(.date) | last' + +GOTCHAS + - Times are LOCAL; JSON epoch seconds are zone-agnostic. + - 'intraday' is a firehose — wide windows take minutes. + - Withings rate-limits aggressive callers (HTTP 601). 'sleep --derive' throttles itself. + - Sleep score / apnea fields appear only on supported devices. + - 'workouts.category' is an integer code in JSON; markdown/CSV map common codes to names. +``` + +--- + +## 3. Export last week's activity + +```sh +withings-export activity --since 7d +``` + +**Expected output** (markdown, one row per day): + +``` +| Date | Steps | Distance (km) | Calories | Active (min) | +|------------|-------|---------------|----------|--------------| +| 2026-05-12 | 9240 | 6.8 | 2341 | 42 | +| 2026-05-13 | 7110 | 5.2 | 2190 | 28 | +``` + +--- + +## 4. Export sleep summaries + +```sh +withings-export sleep --since 7d +``` + +For structured data including HR and sleep stages: + +```sh +withings-export sleep --since 7d --format json | jq '.[0]' +``` + +--- + +## 5. Get most recent body weight measurement + +```sh +withings-export measurements --since 30d --types 1 --format json \ + | jq 'sort_by(.date) | last' +``` + +`--types 1` filters to weight measurements (Withings measurement type 1). Omit `--types` to get all measurement types. + +--- + +## 6. Export workout HR data + +```sh +withings-export workouts --since 30d --format json \ + | jq '.[] | {date, category, hr: .data.hr_average}' +``` + +`category` is an integer in JSON; markdown and CSV output maps common codes to names like `Running`, `Cycling`. + +--- + +## 7. Check flag validation (contract §4, §7) + +These run without credentials (hermetic by [CONTRACT.md §7](../CONTRACT.md#7-hermeticity)): + +```sh +withings-export activity --help # exits 0; no network call +``` + +**Expected:** help text including `--since`, `--until`, `--format` flags; exit 0. + +```sh +withings-export activity --format lol 2>&1; echo "exit: $?" +``` + +**Expected:** + +``` +Error: invalid argument "lol" for "--format" flag: must be one of: markdown, json, csv +exit: 1 +``` + +Error on stderr, nothing on stdout, exit 1. + +--- + +## 8. Run the contract conformance suite + +`withings-export` has the most complete compat coverage of the three CLIs: the suite stands up a stub HTTP server and a fake token so the data-path subtests run fully without real Withings credentials. + +```sh +git clone https://github.com/quantcli/withings-export-cli +cd withings-export-cli +go build -o /tmp/withings-export . +WITHINGS_EXPORT_BIN=/tmp/withings-export go test -tags=compat ./... +``` + +**Expected:** + +``` +ok github.com/quantcli/withings-export-cli 0.003s +ok github.com/quantcli/withings-export-cli/internal/auth 0.003s +``` + +The suite covers CONTRACT.md §4 (format flag surface, `--format json` returns a JSON array, `--format csv` returns a header row, default equals `--format markdown`) and §7 (hermeticity) across all five data subcommands. All cells in the CONTRACT.md Status table for `withings-export` are **machine**-attested. + +--- + +## What to look at next + +- `withings-export intraday --help` — minute-level HR/HRV/SpO2 (keep windows narrow) +- `withings-export prime` — jq recipes and rate-limit gotchas +- [CONTRACT.md §3](../CONTRACT.md#3-date-flags) — date flag semantics +- [CONTRACT.md §4](../CONTRACT.md#4-output-format) — output format contract (`csv` is withings-only today) +- [crono-export example](example-crono.md) — if you also track nutrition +- [liftoff-export example](example-liftoff.md) — if you also track gym workouts