[IMPORTANT: The user has invoked the "hevy" skill, indicating they want you to follow its instructions. The full skill content is loaded below.] --- name: hevy description: Query workout data from Hevy including workouts, routines, exercises, and history. Use when user asks about their workouts, gym sessions, exercise progress, or fitness routines. homepage: https://hevy.com metadata: clawdbot: emoji: "🏋️" requires: bins: ["hevy"] env: ["HEVY_API_KEY"] --- # Hevy CLI CLI for the Hevy workout tracking API. Query workouts, routines, exercises, and track progress. ## Setup Requires Hevy Pro subscription for API access. 1. Get API key from https://hevy.com/settings?developer 2. Set environment variable: `export HEVY_API_KEY="your-key"` ## Troubleshooting / Environment Checks Use this when the user asks whether Hevy is connected, whether the API key exists, or when `hevy status` fails. 1. Check the CLI wrapper and direct skill CLI: ```bash command -v hevy hevy status node ~/.hermes/skills/openclaw-imports/hevy/dist/cli.js status ``` 2. If `hevy` prints `hevy CLI is not built yet: ~/.openclaw/workspace/skills/hevy/dist/cli.js`, the wrapper is stale from the old OpenClaw skill path. Update `~/.local/bin/hevy` so `SKILL_DIR="$HOME/.hermes/skills/openclaw-imports/hevy"`. 3. Search likely config/env locations for the key before asking the user: ```bash grep -R "HEVY_API_KEY\|hevyapp\|HEVY" ~/.hermes ~/.openclaw ~/hermes-webui ~/hermes-workspace 2>/dev/null ``` Redact any key values in user-facing output. 4. If the key is expected in Bitwarden, verify session state: ```bash source ~/.bash_aliases 2>/dev/null || true bw-sync || true bw status ``` If Bitwarden is `locked` or the saved session expired, you cannot retrieve the key until the user unlocks it (`bw-unlock`) or provides the key securely. 5. Only after `HEVY_API_KEY` is present, verify connection with: ```bash hevy status hevy workouts --limit 5 --kg ``` ## Workout Program Planning & Load Calibration Use this skill not only for raw Hevy queries, but also when the user wants to restart training, adopt a published program/spreadsheet/PDF, choose starting loads, estimate rep maxes, or log/calibrate a new routine in Hevy. For Ignacio's Jeff Nippard Min-Max setup, read `references/jeff-minmax-hevy.md` before changing routines or schedules. It captures the source-grounded workout order, RIR rules, Open Park substitutions, fixed schedule (Vie D1 Full Body, Sáb yoga, Dom off, Lun D2 Upper, Mar D3 Lower, Mié D4 Arms/Delts, Jue yoga Hatha; W5 is an irregular transition week with D1 on Sáb 6-Jun), transition weeks, therapy Tuesday logistics, and yoga class details. See jeff-minmax-hevy.md for the full W5-vs-steady-state breakdown. **Critical Min-Max scheduling pitfall:** do not let the fixed weekday calendar override the actual Hevy sequence. Before telling Chicho what to train today, always verify Argentina time and recent Hevy logs, then infer next workout from the completed order `Full Body → Upper → Lower → Arms/Delts`. If he says he is already at the gym for a specific session, trust that live correction and update any cron/prompt context that was relying on stale calendar assumptions. **Week-numbering rule (DO NOT trust routine titles):** Routine titles get re-written by every cron run and can drift. The source of truth is the Hevy workout LOG (`hevy workouts --kg`), not the routine titles. - Anchor: Chicho's first Block 1 D1 was `Min-Max 4x D1 — Full Body (W3 gym casa)` on Sun 24-May-2026. - A "D1" = any Hevy workout whose title starts with `Min-Max 4x D1` (case-insensitive, ignore trailing tags like "(W3 gym casa)"). - Today's week = W3 + (number of D1 sessions completed before today). Example: if 2 D1s were completed (24-May, 30-May), today is W5. - D2/D3/D4 share the same week as the most recent D1. The very first D2/D3/D4 in a new calendar week does NOT start a new training week — only a new D1 starts a new training week. - The calibration session `W1D4 — Arms/Delts Calibration` (13-May) is standalone and must NOT be counted in Block 1. - When in doubt, count D1 entries in `hevy workouts --kg --limit 30` whose title matches `D1`, then add that count to 2. - W7 (deload, 22-Jun) and W8+ (Block 2) are out of scope; today is firmly in Block 1. Concrete verification on 2026-06-06: Hevy log shows exactly 2 Block-1 D1s completed (24-May W3, 30-May W4). Today = W5 D1. The cron's previous run labelled 02-04 Jun sessions as "W5" because Chicho skipped W4 D2-D4 — that label was wrong. Treat the Hevy workout log as canonical, not the routine titles. For MVP VPT / coach mode — integrating Hevy, Health Bridge readiness, nutrition/alcohol notes, weight/measurements, and direct plan-first outputs — read `references/vpt-operating-loop.md`. For Fitbit Sense 2 / wearable Hevy companion work (live sets/reps/weights/RIR, rest timer, sideloading, Pixel bridge, or Hevy live-state reverse engineering), read `references/fitbit-sense2-hevy-companion.md`. For the live-bridge server that connects ADB → Fitbit, use `scripts/hevy-bridge-server.py`. Starter templates for the Fitbit watch app, companion, and view are in `templates/fitbit-watch-app.js`, `templates/fitbit-companion.js`, and `templates/fitbit-index.view`. For a full-session cold start after `/reset`, use `templates/kickstart-fitness-session.md` — copy-paste it into a new chat to load all fitness context immediately. The complete training plan with all weights, reps, RIR, and exercise notes is at `~/.hermes/references/minmax-training-plan.md` — read it when rebuilding routines or verifying program integrity. When the user asks for an ongoing training schedule — especially if frustrated — answer with a concrete fixed calendar first: today/tomorrow/next few days, then the recurring weekly template, then only the minimal adjustment rules. Avoid long justification. Use the user's stated current date/time/timezone if provided; otherwise verify with `date` before assigning weekdays/dates. Recommended workflow: 0. In MVP VPT mode, make a decision before explaining: plan first, details after. Verify the real date/time, check Health Bridge readiness when it may affect training, then use Hevy logs/routines to prescribe the exact session. Treat sleep, food, hydration/alcohol, weight, measurements, pain, and readiness as programming inputs, not side notes. 1. Retrieve the program source if provided (Drive folder, spreadsheet, PDF). For public Google Drive folders, `web_extract` can list filenames; if needed, parse the Drive page HTML for file IDs and download with `https://drive.google.com/uc?export=download&id=`. 2. Prefer the spreadsheet for exact programming tables. If Python lacks spreadsheet deps, install/use `openpyxl` and inspect sheets with coordinates so merged cells and headers are clear. 3. Use the PDF for explanatory rules: RIR definitions, warm-up protocol, progression rules, and safety caveats. 4. Query recent Hevy history (`hevy workouts --limit 10 --kg`, exercise history where useful) to distinguish a true beginner from a detrained returning lifter. 5. For returning lifters or beginners, do **not** recommend true 1RM testing by default. Estimate e1RM/training max from submaximal sets inside the program's target rep ranges using RIR: ```text possible_reps = completed_reps + estimated_RIR e1RM ≈ weight × (1 + possible_reps / 30) ``` Treat this as a rough guide; the actionable output is the next-session load for the target rep range and RIR. 6. For heavy compounds (squat variants, incline/bench press, RDL, leg press, hip thrust), keep first-week testing submaximal and avoid failure, especially after a long layoff. Use machines/Smith/hack/leg press substitutions when safer. 7. For isolation exercises, do not over-focus on 1RM; choose loads by target reps + RIR and log them. 8. Recommend logging calibration workouts as normal Hevy workouts with explicit notes like `Program W1D1 — Calibration`, plus per-exercise notes for target reps/RIR, actual RIR, pain, technique, and chosen substitution. 9. After the workout, read the Hevy log and compute next-session recommendations: repeat, raise, or lower load based on whether reps and RIR matched the prescription. 10. When the user adapts a program to a real gym, update the Hevy routine templates to match actual substitutions instead of repeatedly fighting unavailable equipment. Read the logged workout, identify substitutions (e.g. machine missing → dumbbell/cable variant), then update the remaining routines with consistent available variants and clear notes. **CRITICAL:** After prescribing weights/reps for a session, update the Hevy routine immediately — do NOT ask whether to update. The user expects the routine to reflect the plan. The update is part of the prescription, not a separate decision. Use `write_file` to save the modified routine JSON, then `hevy update-routine --file `. The CLI may exit non-zero with `Cannot read properties of undefined` — this is a known false error. Always verify by re-reading the routine with `hevy routine ` afterward. 11. If the user consistently wants cardio/abs around sessions, encode them in routines as explicit exercises where appropriate (e.g. 10 min elliptical warm-up; knee raise/crunch as an abs slot), but keep the base program intact and avoid adding fatigue that interferes with heavy lower-body work. 12. For hybrid lifting + yoga/stretching plans, make lifting progression the anchor and treat classes as recovery unless the user explicitly wants more conditioning. Put lower-fatigue options (Hatha, stretching, meditation/restorative yoga, easy Pilates) between or after lifting days; avoid recommending dynamic/vinyasa/flow classes early in a strength block if they may interfere with recovery. 13. When parsing gym class PDFs or grid schedules, do not trust plain text order if columns are ambiguous. Extract word coordinates (e.g. PyMuPDF `get_text('words')`), group by y-coordinate, and map words into known day/time columns before making calendar recommendations. Verify suspicious classes against column positions before answering. ### Schedule Output Shape For ongoing plans, prefer this shape: ```text Hoy — Mañana — Pasado — Sábado — Domingo — Desde el lunes / Semana N: Lun — D1 ... Mar — recovery class ... Mié — D2 ... Jue — D3 ... Vie — D4 ... Sáb — catch-up or optional recovery class Dom — rest ``` Keep it direct and operational; no motivational filler. ## Commands ### Status ```bash # Check configuration and connection hevy status ``` ### Workouts ```bash # List recent workouts (default 5) hevy workouts hevy workouts --limit 10 # Fetch all workouts hevy workouts --all # Show detailed workout hevy workout # JSON output hevy workouts --json hevy workout --json # Show weights in kg (default is lbs) hevy workouts --kg ``` ### Routines ```bash # List all routines hevy routines # Show detailed routine hevy routine # JSON output hevy routines --json ``` ### Exercises ```bash # List all exercise templates hevy exercises # Search by name hevy exercises --search "bench press" # Filter by muscle group hevy exercises --muscle chest # Show only custom exercises hevy exercises --custom # JSON output hevy exercises --json ``` ### Exercise History ```bash # Show history for specific exercise hevy history hevy history --limit 50 # JSON output hevy history --json ``` ### Creating Routines ```bash # Create routine from JSON (stdin) echo '{"routine": {...}}' | hevy create-routine # Create routine from file hevy create-routine --file routine.json # Create a routine folder hevy create-folder "Push Pull Legs" # Update existing routine echo '{"routine": {...}}' | hevy update-routine # Create custom exercise (checks for duplicates first!) hevy create-exercise --title "My Exercise" --muscle chest --type weight_reps # Force create even if duplicate exists hevy create-exercise --title "My Exercise" --muscle chest --force ``` **⚠️ Duplicate Prevention:** `create-exercise` checks if an exercise with the same name already exists and will error if found. Use `--force` to create anyway (not recommended). **Routine JSON format:** ```json { "routine": { "title": "Push Day 💪", "folder_id": null, "notes": "Chest, shoulders, triceps", "exercises": [ { "exercise_template_id": "79D0BB3A", "notes": "Focus on form", "rest_seconds": 90, "sets": [ { "type": "warmup", "weight_kg": 20, "reps": 15 }, { "type": "normal", "weight_kg": 60, "reps": 8 } ] } ] } } ``` **Routine creation API pitfalls learned in practice:** - Consult/ground against https://api.hevyapp.com/docs/ before write operations; the API is young and stricter than some local TypeScript types. - `POST /v1/routine_folders` body is exactly: ```json { "routine_folder": { "title": "Folder Name" } } ``` - `POST /v1/routines` accepts `folder_id` on create, but `PUT /v1/routines/{id}` does **not** accept `routine.folder_id`; you cannot move a routine between folders via PUT. - `DELETE /v1/routines/{id}` is not supported. If a duplicate is created, the API cannot delete it; at best rename it via PUT (for example `ZZ IGNORE — duplicate ...`) and tell the user to delete manually in the app. - `routine.exercises[].sets[].rpe` is rejected on routine creation (`"rpe" is not allowed`) even though workout/history types may include RPE. Omit `rpe` from routine template sets. - `rep_range` on routine sets is accepted and useful for program templates: ```json { "type": "normal", "weight_kg": null, "reps": null, "rep_range": { "start": 6, "end": 8 } } ``` - `POST /v1/routines` may return `{ "routine": [ ... ] }` rather than a bare object; unwrap arrays carefully and verify by listing/getting routines afterward. - `PUT /v1/routines/{id}` is strict about routine JSON shape. Do **not** feed raw `hevy routine --json` output back into `update-routine`; strip read-only/output fields like `routine.id`, `folder_id`, `updated_at`, `created_at`, and per-exercise/per-set `index`/`title`. A safe update body is usually `{ "routine": { "title": ..., "exercises": [{ "exercise_template_id", "notes", "rest_seconds", "sets": [...] }] } }`, with sets containing only allowed fields (`type`, `weight_kg`, `reps`, `rep_range`, `distance_meters`, `duration_seconds`, `custom_metric`). - `hevy update-routine` can apply the update successfully but still exit non-zero with output like `✅ Updated routine: undefined` / `Cannot read properties of undefined (reading 'length')` because the CLI expects a different response shape. After this message, **verify by re-reading the routine before retrying**; do not assume failure or retry blindly. - Some `hevy ... --json` CLI commands print status text before the JSON payload (for example `Fetching exercise templates...`). When scripting, strip everything before the first `[` or `{` before `json.loads`, or call the REST API directly. Very large workout JSON can also be truncated by terminal output; prefer direct REST calls or write to a temp file when parsing recent logs. - Avoid blind retry after a 400/partial failure: first list routines/folders to see what was already created or changed. Folder creation and routine updates can succeed even when subsequent CLI formatting/parsing fails, and retrying without verification creates clutter or repeated edits. - Before creating program routines, fetch all exercise templates and verify every `exercise_template_id` with `GET /v1/exercise_templates/{id}`; invalid IDs cause the whole routine create to fail. - For published programs, put RIR/progression cues in exercise `notes`; Hevy routine templates do not have a first-class RIR field. ### Other ```bash # Total workout count hevy count # List routine folders hevy folders ``` ## Usage Examples **User asks "What did I do at the gym?"** ```bash hevy workouts ``` **User asks "Show me my last chest workout"** ```bash hevy workouts --limit 10 # Find relevant workout ID hevy workout # Get details ``` **User asks "How am I progressing on bench press?"** ```bash hevy exercises --search "bench press" # Get exercise template ID hevy history # View progression ``` **User asks "What routines do I have?"** ```bash hevy routines hevy routine # For details ``` **User asks "Find leg exercises"** ```bash hevy exercises --muscle quadriceps hevy exercises --muscle hamstrings hevy exercises --muscle glutes ``` **User asks "Create a push day routine"** ```bash # 1. Find exercise IDs hevy exercises --search "bench press" hevy exercises --search "shoulder press" # 2. Create routine JSON with those IDs and pipe to create-routine ``` ## Notes - **Duplicate Prevention:** `create-exercise` checks for existing exercises with the same name before creating. Use `--force` to override (not recommended). - **API Limitations:** Hevy API does NOT support deleting or editing exercise templates - only creating. Delete exercises manually in the app. - **API Rate Limits:** Be mindful when fetching all data (--all flag) - **Weights:** Defaults to lbs, use --kg for kilograms - **Pagination:** Most commands auto-paginate, but limit flags help reduce API calls - **IDs:** Workout/routine/exercise IDs are UUIDs, shown in detailed views ## API Reference Full API docs: https://api.hevyapp.com/docs/ ### Available Endpoints - `GET /v1/workouts` - List workouts (paginated) - `GET /v1/workouts/{id}` - Get single workout - `GET /v1/workouts/count` - Total workout count - `GET /v1/routines` - List routines - `GET /v1/routines/{id}` - Get single routine - `GET /v1/exercise_templates` - List exercises - `GET /v1/exercise_templates/{id}` - Get single exercise - `GET /v1/exercise_history/{id}` - Exercise history - `GET /v1/routine_folders` - List folders ### Write Operations (supported but use carefully) - `POST /v1/workouts` - Create workout - `PUT /v1/workouts/{id}` - Update workout - `POST /v1/routines` - Create routine - `PUT /v1/routines/{id}` - Update routine - `POST /v1/exercise_templates` - Create custom exercise - `POST /v1/routine_folders` - Create folder The CLI focuses on read operations. Write operations are available via the API client for programmatic use. [IMPORTANT: The user has invoked the "health-bridge" skill, indicating they want you to follow its instructions. The full skill content is loaded below.] --- name: health-bridge description: Manage private Android Health Connect bridge systems — Kotlin apps that sync Health Connect data to a server, with concrete Health Bridge deploy/build/troubleshooting notes. category: fitness --- # Health Bridge Class-level playbook for private Android Health Connect bridge systems: a Kotlin app reads on-device Health Connect data, syncs idempotent telemetry snapshots/raw records to a server, and exposes validated summaries for dashboards, coaching, or agents. The current concrete deployment is Chicho's Pixel 9a → Oracle VPS → Pipo pipeline, but the workflow applies to any personal Health Connect → server bridge. ## Umbrella scope This skill now absorbs the former `android-health-connect`, `android-health-connect-bridge`, and `health-connect-bridge` skills. Use it for: - Android Health Connect app integrations: manifest hooks, permission rationale activities, runtime consent, foreground/background reads, pagination, SDK 1.1.0 API quirks, and WorkManager scheduling. - Private bridge architecture: subject-scoped raw records, idempotent daily aggregate snapshots, provenance/fingerprints, summary rebuilds, and debug endpoints. - Concrete Health Bridge operations: rebuilding the APK, deploying the FastAPI/SQLite service, querying summaries/debug endpoints, and troubleshooting sync gaps. Keep generic Android SDK/Gradle/AAPT2 setup in `android-dev`; load that too when a build fails before app logic runs. For phone↔wearable companion bridges (Fitbit watch apps, AccessibilityService-based local bridges), load `android-watch-companion-bridges`. ## Architecture ``` Pixel 9a (Health Bridge app) │ WorkManager every 12h at ~10:00 and ~22:00 ART (auto, background-permission gated) │ or manual "Sync" button (triggers syncHistorical/full recent sync depending on app version) ▼ POST http://100.87.116.90:3007/ingest (Tailscale mesh) │ Bearer token auth ▼ Oracle VPS (systemd: health-bridge) │ FastAPI + SQLite (~/health-bridge/health.db) ▼ Pipo queries: /summary, /data ``` ## Health Connect app integration rules ### Manifest and discoverability - Declare every requested `android.permission.health.*` permission in `AndroidManifest.xml`; if Android App Info says “no permissions requested,” inspect the manifest before debugging UI code. - Add Health Connect package visibility: ```xml ``` - Register the rationale/usage activities so the app appears correctly in Health Connect permission screens: - `androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE` - `android.intent.action.VIEW_PERMISSION_USAGE` - category `android.intent.category.HEALTH_PERMISSIONS` - permission `android.permission.START_VIEW_PERMISSION_USAGE` ### Runtime permission flow - Use the official Health Connect contract/controller, not normal Android runtime permission APIs and not raw settings intents: ```kotlin private val requestPermissions = registerForActivityResult( PermissionController.createRequestPermissionResultContract() ) { granted -> /* update UI */ } requestPermissions.launch(PERMISSIONS) ``` - Check `HealthConnectClient.getSdkStatus(context)` before launching consent. - Sync by granted permission groups; broad personal bridges should upload whatever the user granted rather than failing all data because one category was denied. - Background reads require both feature support and `READ_HEALTH_DATA_IN_BACKGROUND`; workers must re-check before every read. ### Read patterns - Daily aggregates (steps/distance/floors/calories) should use local-day start/end boundaries, not rolling sync windows. - Always send daily aggregate rows even when values are zero so stale nonzero summaries can be overwritten. - Paginate every `readRecords` call with `pageToken`; first-page-only success silently drops dense data such as heart-rate samples. - Split interval records (sleep, exercise, nutrition, hydration) across local-day boundaries when daily summaries depend on minutes/calories. - Health Connect SDK 1.1.0 returns unit wrappers/constants in many places. Prefer explicit accessors (`.inKilocalories`, `.inGrams`, `.inMillimetersOfMercury`) and `.toString()` over fragile enum `.name` assumptions. ## Bridge data model rules - Treat syncs as idempotent telemetry snapshots plus raw records, never append-only deltas. - Scope every raw and derived key by `subject`/person; `date` alone is a future multi-person bug. - Store stable `record_key`, `data_type`, `local_date`, source/provenance, raw JSON payload, and `received_at` for every normalized record. - Rebuild affected summaries from raw records after ingest; never increment existing daily totals during rebuild. - Keep burned calories (`active_kcal`, `total_kcal`) separate from intake (`nutrition_kcal`). - Expose a debug endpoint that can explain which records were used, ignored, deduped, or preferred for one subject/date before declaring a dashboard number correct. ## Codex / review gate pattern If the user requests a sign-off gate before APK build/install/deploy: 1. Ensure the source is in a clean inspectable git repo. 2. Run compile/static checks only if allowed, but do not build APK/deploy/install before sign-off. 3. Ask Codex for exact gate wording: `SIGN OFF FOR DEPLOY AND BUILD` or `DO NOT BUILD`. 4. If Codex says `DO NOT BUILD`, fix blockers and repeat the review. ## Server (Oracle VPS) **Service:** `systemd health-bridge` **Port:** 3007 (HTTP, private/Tailscale/local for admin endpoints) **Code:** `~/health-bridge/server.py` **DB:** `~/health-bridge/health.db` **Token:** `XXMYWq-ft146fRL0AlNV_5P9Vg4NNJJa4pHxqDvzRkg` **Public signed ingest path (Pixel no longer needs Tailscale):** - Public URL: `https://miopenclaw-vnic.tail9799d2.ts.net:8443/ingest_signed` - Funnel `:8443` proxies only to `systemd health-bridge-public-ingest` on `127.0.0.1:3017` (`~/health-bridge/ingest_public_proxy.py`). - Public proxy allows `/health` and `/ingest_signed` only; `/data`, `/summary`, `/debug/day` return 404 publicly. - `server.py` validates `X-HB-Device`, `X-HB-Timestamp`, `X-HB-Nonce`, `X-HB-Signature` using HMAC-SHA256 over `device\ntimestamp\nnonce\nsha256(body)` and stores replay nonces in `ingest_nonces`. - Android app has `SERVER_URL` set to the public signed URL and signs requests with `DEVICE_ID=pixel9a-chicho`. Keep ADB/Tailscale only for debugging/install, not routine sync. ### Endpoints | Method | Path | Auth | Purpose | |--------|------|------|---------| | GET | `/health` | none | Ping | | GET | `/` | none | Status | | POST | `/ingest` | Bearer | Receive Health Connect data | | GET | `/summary?days=7` | Bearer | Daily aggregated summary | | GET | `/data?data_type=steps&limit=100` | Bearer | Raw records | | GET | `/debug/day?date=YYYY-MM-DD&subject=chicho` | Bearer | Audit one day's classified inputs, fingerprints, and final summary | | GET | `/download` | none | Download latest APK | For Android Health Connect manifest/permission/read patterns, see `references/android-health-connect-integration.md`. For generic bridge architecture/data-model rules, see `references/health-connect-bridge-architecture.md`. For aggregation provenance/dedupe rules, see `references/provenance-dedupe-model.md`. For full-history backfills and Health Connect quota/binder limits, see `references/historical-health-connect-import.md`. For Google Fit REST cloud backfills (OAuth, estimated_steps, 30-day chunks, ingestion), see `references/google-fit-rest-backfill.md`. For expanded Google Fit scopes, distance import, and the pitfall that all-metrics aggregate queries can exceed Google's 100k-point limit, see `references/google-fit-expanded-scopes-and-distance.md`. For the Google Health API vs Google Fit vs Health Connect migration landscape (Fit REST deprecated end of 2026, Fitbit Web API → Google Health API, Health Connect unchanged), see `references/google-health-vs-fit.md`. For importing secondary metrics (heart_rate, weight, body_fat, height, nutrition, hydration) from Google Fit REST with adaptive chunk sizing to avoid the 100k-point limit, see `scripts/import_google_fit_secondary_metrics.py`. For importing steps + calories + distance in a single efficient pass, see `scripts/import_google_fit_steps_cal_distance.py`. For MVP VPT coaching decisions that combine Health Bridge readiness with Hevy training and nutrition/alcohol/weight context, see the Hevy skill reference `references/vpt-operating-loop.md`. For the 10:00/22:00 ART phone sync → 10:05/22:05 ART VPT cron flow, contextual HR script, and WorkManager/JobScheduler pitfalls, see `references/morning-vpt-sync-and-hr-context.md`. For refreshing the Pixel 9a wireless debugging port from a screenshot and keeping the Health Bridge cron script pointed at the live ADB connection port, see `references/wireless-debugging-port-refresh.md`. For Discord cron delivery pitfalls (`:new` creates new threads), bridge APK version verification via ADB/checksums, and Femmto raw-vs-summary visibility, see `references/discord-cron-delivery-and-bridge-version-check.md`. For OKOK vs Femmto smart-scale integration, Health Connect permission differences, and rich body-composition fallback handling, see `references/okok-femmto-scale-health-connect.md`. For recovering missing cloud history when Health Connect returns zero rows/steps, see `references/cloud-backfill-google-fit-fitbit.md`fferences, and rich body-composition fallback handling, see `references/okok-femmto-scale-health-connect.md`. For recovering missing cloud history when Health Connect returns zero rows/steps, see `references/cloud-backfill-google-fit-fitbit.md` ### Management ```bash sudo systemctl status health-bridge sudo systemctl restart health-bridge sudo journalctl -u health-bridge -f # Test ingest curl -s -X POST http://100.87.116.90:3007/ingest \ -H "Authorization: Bearer XXMYWq-ft146fRL0AlNV_5P9Vg4NNJJa4pHxqDvzRkg" \ -H "Content-Type: application/json" \ -d '{"records":[{"type":"steps","count":5000}]}' # Query summary curl -s http://100.87.116.90:3007/summary?days=3 \ -H "Authorization: Bearer XXMYWq-ft146fRL0AlNV_5P9Vg4NNJJa4pHxqDvzRkg" ``` ## Android App **Package:** `com.healthbridge.app` **Source:** `~/health-bridge/android-app/` **APK:** `~/health-bridge/static/health-bridge.apk` (served at `/download`) ### Data Types Synced (22 total) Scheduled/recent sync window: last 12h (`syncRecent()` uses `Instant.now().minus(12, ChronoUnit.HOURS)`), running every 12h around 10:00 and 22:00 ART. Full historical import remains separate/manual. Aggregated: steps, distance, floors, active kcal, total kcal Heart: heart rate samples, resting HR, HRV Vitals: blood pressure, blood glucose, O₂ saturation, body temperature, respiratory rate Body: weight, body fat, height, lean body mass, bone mass, basal metabolic rate when apps write them to Health Connect Sleep: sessions + Health Connect stages (`duration_minutes`, effective asleep `minutes`, `awake/light/deep/rem` minutes when provider exposes stages) Exercise: sessions Nutrition: meals, hydration Metabolic: BMR Sleep limitation learned 2026-05-23: Google Health/Fitbit UI may show `sleep_score`, `Poor/Good`, and `restlessness`; Health Connect `SleepSessionRecord` exposes stages but not Fitbit's sleep score. Fitbit appears to encode app "restlessness" inside HC awake stage time. For exact sleep score/restlessness, add Fitbit Web API ingestion; HC alone can approximate readiness with duration/asleep/awake/deep/REM. ### Build & Deploy **Prerequisites on Oracle ARM64:** - Java 17 (`openjdk-17-jdk-headless`) - Android SDK at `/opt/android-sdk` (cmdline-tools, platform 36, build-tools 34.0.0) - `box64` for x86_64 AAPT2 emulation - Gradle 8.11.1+ (wrapper in project) ```bash cd ~/health-bridge/android-app ./gradlew assembleDebug --no-daemon # Copy to static cp app/build/outputs/apk/debug/app-debug.apk ~/health-bridge/static/health-bridge.apk # Install on Pixel 9a (ADB must be connected) adb -s 100.111.239.9: install -r ~/health-bridge/static/health-bridge.apk ``` ## ADB Connection to Pixel See `pixel-6a` skill for full ADB flow. Pixel 9a IP: `100.111.239.9` (varies). Quick reconnect: ```bash # Pair first (needs fresh code from phone) adb pair 100.111.239.9: # Connect (different port!) adb connect 100.111.239.9: adb devices # confirm "device" status ``` ### ADB cron port refresh from Wireless debugging screenshot When Chicho sends a Wireless debugging screenshot, treat `IP address & Port` as the live ADB **connection** endpoint and update the Health Bridge cron script if it was pinned to an older port: ```bash # Example from screenshot: 100.111.239.9:36963 adb kill-server || true adb start-server adb connect 100.111.239.9: adb devices -l # Keep this in sync with the screenshot port # /home/ubuntu/.hermes/scripts/health_bridge_morning_sync.sh: PHONE="100.111.239.9:" /home/ubuntu/.hermes/scripts/health_bridge_morning_sync.sh ``` Verify two separate things: (1) the script discovers and triggers the current WorkManager JobScheduler ID, and (2) the server later receives a fresh ingest row. A successful forced job does not guarantee immediate new DB data. ### VPT cron Discord delivery If Chicho complains that the VPT cron "created threads" or didn't deliver, inspect the cron delivery target before debugging Health Connect data. `deliver=discord::new` does NOT work for Discord — it fails with `"Value ... is not snowflake"` because `:new` isn't a valid thread ID format. Use `discord:` to post to the channel, or `discord::` for an existing thread. See `references/discord-cron-delivery-and-bridge-version-check.md`. ### Verifying App Health via ADB ```bash # Check app installed adb -s 100.111.239.9: shell "pm list packages | grep healthbridge" # Check WorkManager job scheduled and discover the current JobScheduler ID adb -s 100.111.239.9: shell "dumpsys jobscheduler | grep -i -A8 -B4 'healthbridge\\|com.healthbridge'" # Recent app logs (may be rotated) adb -s 100.111.239.9: logcat -d -t 500 | grep -i healthbridge # Force-run the periodic sync job; replace with the ID discovered above adb -s 100.111.239.9: shell cmd jobscheduler run -f com.healthbridge.app ``` Do not hard-code the WorkManager JobScheduler ID; it can change between installs/schedules. ### Verifying Recent Syncs on Server ```bash # Last 20 raw records from health_connect source python3 - <<'PY' import sqlite3 con = sqlite3.connect('/home/ubuntu/health-bridge/health.db') for row in con.execute(""" select received_at, local_date, source, json_extract(data_json,'$.type') as dtype, json_extract(data_json,'$.steps') as steps from raw_data where subject='chicho' and source='health_connect' order by received_at desc limit 15 """): print(row) PY ``` ## Health Connect Intent The app opens Health Connect settings via: ```kotlin Intent(Intent.ACTION_MAIN).apply { setClassName( "com.google.android.healthconnect.controller", "com.android.healthconnect.controller.navigation.TrampolineActivity" ) flags = Intent.FLAG_ACTIVITY_NEW_TASK } ``` Fallback: `ConnectAppsOnboardingActivity` ## Pitfalls - **Health Connect API 1.1.0** changed types significantly: Energy/Length/Mass values need `.inKilocalories`/`.inMeters`/etc., not raw numbers. `SleepSessionRecord.stage` → removed. `NutritionRecord.time` → `startTime`. `BloodPressureRecord.bodyPosition` is non-nullable. - **compileSdk 36 required** for `health.connect:connect-client:1.1.0` - **Box64 needed** on ARM64 — Android SDK build-tools ship x86_64 AAPT2 binaries - **Permissions:** User must grant ALL types in Health Connect manually. The app opens the HC settings screen; it cannot auto-grant. - **HTTP only** (Tailscale mesh encrypts). `usesCleartextTraffic=true` in manifest. - **Daily aggregate sanity:** Android should compute `today` aggregates from local start-of-day → now, not from the sync lookback window. Backend daily summaries should be idempotent overwrites/upserts, not additive sums of repeated manual/hourly syncs. After rebuilding/installing, ask Chicho to open Health Bridge and tap Sync Now, then compare `/summary?days=1` against Google Fit/Fitbit today values. - **Historical imports:** Do not run full-history backfill as the scheduled background job. Keep WorkManager lightweight (recent daily window) and expose historical backfill as a separate manual path. For multi-year daily totals, chunk by year and use `aggregateGroupByPeriod(..., Period.ofDays(1))`; one aggregate call per day can hit API quota, and one giant read can hit `TransactionTooLargeException`. See `references/historical-health-connect-import.md`. - **Cloud-vs-local history:** Health Connect only exposes data present in the local Health Connect store. A successful backfill over old dates may still produce zero-step rows for years/months where Google Fit/Fitbit did not write historical data into Health Connect. Do a monthly coverage audit (`days_with_steps`, `step_sum`) before concluding the import worked. For older real step history, use Google Fit REST (`estimated_steps` with `fitness.activity.read`), Fitbit Web API (`activities/steps/date/{start}/{end}`), or Google Takeout rather than repeatedly querying Health Connect. See `references/cloud-backfill-google-fit-fitbit.md`. - **Google Fit cloud backfill:** When Health Connect has missing historical local data but Google Fit cloud has it, use Google Fit REST `dataset:aggregate` with `derived:com.google.step_count.delta:com.google.android.gms:estimated_steps`. Use a separate Fitness OAuth token (`fitness.activity.read`) via `scripts/google_fit_oauth.py`; if `invalid_client` occurs, prefer the working `~/.config/gog/credentials.json` over stale `~/.config/gogcli/client_secret.json`. Query in ~30-day chunks because larger daily-period aggregates can return `aggregate duration too large`. With `fitness.location.read`, include `com.google.distance.delta` to backfill `distance_m`. For heart/sleep/body/nutrition/hydration, avoid one giant all-metrics aggregate over long windows: Google can reject it with the 100k-point limit. Split by metric/source and narrower windows. See `references/google-fit-rest-backfill.md` and `references/google-fit-expanded-scopes-and-distance.md`. - **Mixed-source provenance:** Do not collapse explicit workouts, automatic movement, nutrition intake, and burned energy into ambiguous totals. Keep `workout_minutes`, `auto_activity_minutes`, `exercise_minutes = workout + auto`, and `nutrition_kcal` separate. Use canonical fingerprints for dedupe and `/debug/day` to audit final numbers. See `references/provenance-dedupe-model.md`. - **SQLite migration safety:** Use explicit column lists in endpoints and rebuild scripts; avoid `SELECT *` because additive columns can break tuple unpacking or clients after schema changes. - **Google Fit REST sunset:** Google announced the Fit REST API will be deprecated and shut down by end of 2026 with **no cloud replacement**. Health Connect (on-device Android API) is the forward path. The Google Health API (announced 2026) replaces only the Fitbit Web API, not Google Fit REST. Our historical backfills from Google Fit REST cloud will stop working in 2027; plan to complete all cloud-import work before then. See `references/google-health-vs-fit.md`. - **2026 Health Connect data gaps:** HR, weight, body fat, nutrition, and hydration may be empty in Health Connect for recent years even if historical Google Fit cloud has them. Health Connect only exposes data written by apps on the device. If Chicho isn't actively using apps that write those types to Health Connect (Fitbit companion, Femmto for scale data, Hevy/other apps for body metrics, MyFitnessPal for nutrition), the auto-sync will return zero records for those types. OKOK (`com.chipsea.btcontrol.en`) did not request Health Connect permissions in May 2026; prefer Femmto (`com.femmto.app`) for Health Connect smart-scale writes. The server's daily_summary will correctly show 0/null for missing metrics while showing imported Google Fit cloud data for 2023-2025. The user has provided the following instruction alongside the skill invocation: [IMPORTANT: You are running as a scheduled cron job. DELIVERY: Your final response will be automatically delivered to the user — do NOT use send_message or try to deliver the output yourself. Just produce your report/output as your final response and the system handles the rest. SILENT: If there is genuinely nothing new to report, respond with exactly "[SILENT]" (nothing else) to suppress delivery. Never combine [SILENT] with content — either report your findings normally, or say [SILENT] and nothing more.] Act as Chicho's elite-track Virtual Personal Trainer after a Health Bridge phone sync. CRITICAL CONTEXT RULES — do not rely on stale fixed-calendar assumptions: 1. Always verify Argentina time first: `TZ=America/Argentina/Buenos_Aires date`. 2. Always query recent Hevy workouts before deciding today's workout: `hevy workouts --limit 8 --kg` and, if needed, fetch the relevant routine/log details. Infer the next Min-Max day from the latest completed workout order: Full Body → Upper → Lower → Arms/Delts. Use weekdays only as a planning aid after checking the actual Hevy sequence. 3. Preserve Jeff Nippard Min-Max order above weekday rigidity. MANDATORY AUTO-UPDATE WORKFLOW — the cron must update Hevy routines so Chicho never has to ask: 1. Verify Argentina time. 2. Query Health Bridge `/home/ubuntu/health-bridge/health.db` for latest daily_summary (sleep, weight, steps, HR). 3. Query Hevy: `hevy workouts --limit 8 --kg` to determine next workout in sequence. 4. Get the LAST session of that same workout type with full details (`hevy workout --json --kg`). 5. Get the ROUTINE for that workout type (`hevy routine --json`). 6. Compute aggressive W3/W4 progression: - e1RM = weight × (1 + (reps + estimated_RIR) / 30) - If last set RPE ≤ 8 (RIR ≥ 2): +5-10kg on compounds, +2.5-5kg on isolations - If last set RPE 8.5-9.5 (RIR 0.5-1.5): +2.5-5kg on compounds, same or +2.5 on isolations - If last set RPE 10 (RIR 0) and reps in range: keep weight, push for +1 rep or +2.5kg 7. **UPDATE the Hevy routine** with new weights using `hevy update-routine --file /tmp/routine_update.json`. - Build safe JSON: only include `exercise_template_id`, `sets` (type, weight_kg, reps, rep_range, duration_seconds), `notes`, `rest_seconds`. - The CLI may print "undefined" — verify with `hevy routine --json` after update. 8. If HR samples exist, run `/home/ubuntu/health-bridge/scripts/analyze_hr_context.py --date --json`. Output requirements: - Spanish. - ONE Discord message, under 1800 characters. No multi-message splitting. - Start with title: `VPT 10:05 — D# ...` or `VPT 22:05 — D# ...`. - Content: readiness verdict (sleep/recovery semáforo), today's training decision (what workout, key lifts with updated weights), next 1-3 hour actions, elite move. - Mention: "Rutina actualizada en Hevy ✅" so Chicho knows he just opens the app. - Be direct, coach serio + hype útil. No filler. - Always include one `Elite move:` small compounding action. - Mention missing high-leverage data only if relevant: MacroFactor/protein/hydration/alcohol/measurements. CRITICAL: After computing the plan, you MUST update the Hevy routine before delivering your final message. The user should open Hevy and see the updated weights waiting for him. ## Response Readiness semáforo en verde. Rutina W6 ya actualizada en Hevy ✅ --- **VPT 10:05 — D1 Full Body (W6)** 🏋️ **Readiness 🟢** (semáforo: verde) - Último sueño fiable (Mar 2 Jun): 7h 55min, eficiencia 94.6%, 63min deep, 123min REM — élite - RHR 48-51 bpm (muestra 2 Jun) — señal cardio sólida - 48h desde D4 Arms/Delts (Jue 4 Jun) → CNS fresco - Peso 93.4kg (31-May), sin cambios recientes **Secuencia Hevy:** último = D4 W5 (Jue 4 Jun) → **hoy toca D1 W6**. Nuevo bloque Min-Max. **Sesión W6 — cargas ya en la app (abrir Hevy y listo):** - Leg Curl: 52.5×6-8 / 62.5×6-8 *(S2 nuevo, S3 sostengo)* - Squat Smith: 32.5×8 / 32.5×8 *(sostengo, 8 reps limpios)* - Incline Bench: 32.5×6-8 / 32.5×6-8 *(sostengo, pausa 1s abajo)* - Lat Pulldown: 50×6-8 / 55×8 *(S2 nuevo, S3 sostengo)* - Y-Raise: 5kg×8-10 *(sostengo, target 10 reps)* - Calf Press: 127.5×10-12 *(nuevo +2.5)* - Knee Raise: 10-12×2 **Próximas 1-3h:** 1. Hidratá 500ml agua ahora (deshidratación nocturna) 2. Comida pre-gym 16:00-17:00 → 30g proteína + 30-40g carbs complejos 3. Open Park ~17:00, mismo slot que el último D1 (Sáb 30 May) 4. Elíptica/caminata 5-10min warm-up, no te agotes **Elite move 💡:** Bajá un video tuyo del Squat Smith 32.5kg W6 S1 y comparálo con el W4 (32.5×6@9.5). Si la profundidad/control mejoraron, W7 subimos a 35kg. **Datos de video = progreso real visible.** **Rutina actualizada en Hevy ✅** — abrí la app, la W6 te espera. *Falta loguear en HC: protein/hidratación/mediciones. Si pesaste esta semana, pasame el número y lo agrego. La app Femmto o MacroFactor写到 Health Connect te cierra el loop de nutrición.*