From 14b678e5d2cb643126afa102c0af1f40f47ac4a9 Mon Sep 17 00:00:00 2001 From: DTTerastar Date: Sat, 25 Apr 2026 18:17:45 -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 liftoff's. https://github.com/quantcli/common/blob/main/CONTRACT.md#6-the-prime-subcommand Liftoff-specific content covers: - workouts list/show/stats and bodyweights list/stats - Local-zone date semantics - LIFTOFF_API_BASE override for the periodic API host rotation - jq recipes for volume math and bodyweight delta Also extends the rootCmd Long description so 'liftoff-export --help' points agents at 'prime' from the top. Co-Authored-By: Claude Opus 4.7 (1M context) --- cmd/prime.go | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 6 +++ 2 files changed, 137 insertions(+) create mode 100644 cmd/prime.go diff --git a/cmd/prime.go b/cmd/prime.go new file mode 100644 index 0000000..acddb32 --- /dev/null +++ b/cmd/prime.go @@ -0,0 +1,131 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +const primeText = `liftoff-export — primer for LLM agents +====================================== + +WHAT IT IS + A CLI that reads your personal Liftoff (gymbros.com) data — gym workouts + with sets/reps/weights, recorded bodyweights — and prints it on stdout. + +OUTPUT FORMATS + Default: narrow, fitdown-style markdown — date-grouped headings, one + exercise per block with set lines and Nx... compression for repeated + sets, easy to skim and easy for an LLM to consume inline. + + --format json Pretty-printed JSON ARRAY of full posts/exercises. + Use this when you want the complete row, when piping + to jq, or when round-tripping into other tools. + + 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 prints "No workouts found.", JSON prints '[]'. + +AUTH + 'liftoff-export auth login' opens an interactive prompt for email/ + password and writes ~/.config/liftoff-export/auth.json (access token, + refresh token, expiry). Subsequent calls auto-refresh when the access + token is within 5 minutes of expiry. + + 'liftoff-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. + + 'liftoff-export auth refresh' forces a refresh now. + 'liftoff-export auth logout' deletes the stored tokens. + + Liftoff retires version-pinned API hosts periodically. If a refresh + starts failing with "server is deprecated", set LIFTOFF_API_BASE= + https://vX-Y-Z.api.getgymbros.com to point at a current version + without waiting for a new release. + +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 + + workouts list — every workout you've logged. + Markdown: 'Workout MONTH D, YYYY' headings; one exercise block per + movement with set lines. Bodyweight-relative sets render as + 'reps@-assist' (assisted) or 'reps@+added' (banded). + JSON: full Post array. Keys (subset): + id, startedAt, postedAt, sessionDuration, sessionNotes, + bodyweight, caloriesBurned, prCount, + exerciseData: [{ exerciseName, exerciseTypes, setsData: [...] }] + + Filters: --exercise NAME (word-prefix match: 'bench' → 'Bench Press'). + + workouts show DATE + Same shape as 'list' but only workouts on DATE. DATE is the same + vocabulary as --since (today, yesterday, YYYY-MM-DD). Useful for + 'what did I do today' agent prompts. + + workouts stats — per-exercise summaries across the window. + Markdown: one section per exercise with PR/recent and a per-month + bar chart of best weight (or duration for cardio). + JSON: array of ExerciseSummary { name, type, sessions: [SessionStats] }. + Filters: --exercise, --detail (per-session breakdown). + + bodyweights list — recorded bodyweights. + Output: one line per entry, '2026-04-15 187.6 lbs'. + + bodyweights stats — current/high/low, monthly trend chart, plateau + detection on the trailing 6 months. + +EXAMPLES + + # Today's workout, scannable + liftoff-export workouts show today + + # 30-day exercise volume, parsed + liftoff-export workouts stats --since 30d --format json | jq ' + .[] | select(.type == "WR") + | { name, total_volume: ([.sessions[].volume] | add) }' + + # PR over time for one exercise + liftoff-export workouts stats --exercise bench --since 1y --format json | + jq '.[].sessions | map({ date, weight: .bestWeight, reps: .bestReps })' + + # Bodyweight delta vs 90 days ago + liftoff-export bodyweights list --since 90d --format json | + jq '[.[]] | (.[-1].weight - .[0].weight)' + +GOTCHAS + - Workout dates are LOCAL. A 11pm workout buckets on the date you + logged it, not the UTC date. + - Liftoff retires API hosts periodically — see LIFTOFF_API_BASE above. + 'liftoff-export auth status' won't catch this; the failure is a + deprecation message on the next subcommand call. + - Bodyweight is read off Post.bodyweight, which is the value you + entered for that workout — not a separate weigh-in feed. No workout + that day means no bodyweight that day. + - 'workouts stats' silently bins exercises by name. Renaming an + exercise in Liftoff splits it into two summaries. +` + +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 for structured), +auth subcommands and env vars, 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 df8b829..a1cdfcb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,6 +9,12 @@ import ( var rootCmd = &cobra.Command{ Use: "liftoff-export", Short: "CLI for the Liftoff fitness app", + Long: `liftoff-export reads your personal Liftoff (gymbros.com) workout and +bodyweight data and prints it on stdout. Default output is narrow, +fitdown-style markdown; pass --format json for the full structured row. + +LLM agents: run 'liftoff-export prime' for a one-screen orientation +(I/O contract, subcommands, date flags, jq recipes).`, } func Execute() {