目录 / Speediance scan PR34 r1
Speediance scan PR34 r1
--- name: speediance description: > Read completed workouts (summaries and full per-set detail), browse and export the exercise catalog, and push custom training programs to your Speediance (Gym Monster) smart cable machine via its cloud API. Authenticates with your account credentials, caches a session token in your OS user-cache directory (override with an exported SPEEDIANCE_TOKEN_CACHE), and makes outbound HTTPS requests to the Speediance cloud API. Reads and emits structured data — the caller decides where to store it. Ships as a single static binary — no Python or other runtime required. metadata: openclaw: emoji: 🏋️ homepage: https://github.com/stozo04/speediance-cli primaryEnv: SPEEDIANCE_EMAIL permissions: network: - "Speediance cloud API (HTTPS) — authentication, workout history, exercise catalog, program creation" files.read: - "config.json — optional credential/config file (working directory, or your OS user-config dir)" - ".env — optional env file in the working directory; only the SPEEDIANCE_EMAIL, SPEEDIANCE_PASSWORD, SPEEDIANCE_REGION and SPEEDIANCE_DEVICE_TYPE keys are read (parsed to a map, never exported into the process environment); the path keys SPEEDIANCE_CONFIG and SPEEDIANCE_TOKEN_CACHE are ignored there" - "token cache — cached session token in your OS user-cache dir by default (non-roaming); path overridable via an exported SPEEDIANCE_TOKEN_CACHE, or the token_cache_path key of a config.json given by --config, SPEEDIANCE_CONFIG or the user-config dir (ignored in a working-directory config.json). A legacy .token.json in the working directory is read once to migrate it." - "plan JSON files passed to the push command" files.write: - "token cache — session token written after login and refreshed automatically; in your OS user-cache dir by default (non-roaming; override via an exported SPEEDIANCE_TOKEN_CACHE or token_cache_path, which a working-directory config.json can't set). A legacy .token.json in the working directory is relocated here and then removed." - "library.json — exercise catalog dump (library command; every run writes the full catalog to --out, default library.json)" - "config.json — written by the config set command (owner-only permissions)" requires: bins: [] env: - SPEEDIANCE_EMAIL - SPEEDIANCE_PASSWORD envVars: - name: SPEEDIANCE_EMAIL description: Your Speediance / Gym Monster account email address required: true - name: SPEEDIANCE_PASSWORD description: "Your account password. Google/SSO users: set a password once in the Speediance app (Profile → Settings) before using this skill." required: true - name: SPEEDIANCE_REGION description: "API region — Global (default) or EU" required: false - name: SPEEDIANCE_DEVICE_TYPE description: "Device type integer — 1 = Gym Monster v1 (default, tested). Gym Monster 2 is untested; try 2 if exercises look wrong." required: false - name: SPEEDIANCE_CONFIG description: "Path to config.json (overrides discovery: working dir, then OS user-config dir). Read from the exported environment only, never from .env" required: false - name: SPEEDIANCE_TOKEN_CACHE description: "Override the token cache file location (default: OS user-cache dir). Read from the exported environment only, never from .env" required: false --- # Speediance — Gym Monster CLI Skill Talk to your **Speediance (Gym Monster)** smart cable machine from any agent. Read completed workouts and push custom programs that appear on the machine ready to run — no app navigation mid-session. > **Unofficial** — reverse-engineered from the Android app. Personal use, your own > account only. Built on the MIT-licensed `UnofficialSpeedianceWorkoutManager` (hbui3) > and `speediance-influx` (gavinmcfall). > > Tested on **Gym Monster v1** (`SPEEDIANCE_DEVICE_TYPE=1`). GM2 is untested. ## Setup (one time) `speediance-cli` is a single static binary — **no Python or other runtime needed**. Install it one of two ways: ```bash # A) Download a release binary for your OS/arch, extract, put it on your PATH: # https://github.com/stozo04/speediance-cli/releases # B) Or build/install with Go (1.24+): go install github.com/stozo04/speediance-cli/cmd/speediance-cli@latest ``` Then authenticate: ```bash speediance-cli login # authenticates and caches a session token (run `config path` to see where) ``` ## Credentials Set as environment variables — the CLI reads them automatically: | Variable | Required | Default | Notes | |---|---|---|---| | `SPEEDIANCE_EMAIL` | ✓ | — | Account email | | `SPEEDIANCE_PASSWORD` | ✓ | — | Account password | | `SPEEDIANCE_REGION` | — | `Global` | `Global` or `EU` | | `SPEEDIANCE_DEVICE_TYPE` | — | `1` | `1` = Gym Monster v1 | Alternatively, write a `config.json` in the working directory (gitignored by the repo): ```json { "email": "you@example.com", "password": "yourpassword", "region": "Global" } ``` You can also put these four variables in a gitignored **`.env`** file in the working directory — they are read from it automatically (exported environment variables still take precedence). Only those keys are read; the file is parsed into a map and any other keys are ignored, so a stray `.env` can never inject unrelated variables into the process environment. The path overrides `SPEEDIANCE_CONFIG` and `SPEEDIANCE_TOKEN_CACHE` must be exported (or passed as `--config`): a `.env` can't redirect where config is read from or where the session token is written. ## Commands ### Read workouts ```bash speediance-cli today --json # every session today, fully resolved (any type) speediance-cli today --date 2026-06-17 --json # …or a specific day (today | yesterday | YYYY-MM-DD) speediance-cli workouts --days 7 --json # recent sessions (summaries, for picking) speediance-cli session <training_id> --json # full, verbatim detail for one session ``` **`today` is the one-shot, agent-friendly entry point.** When the client just says "I did a workout," call `today` — you do **not** need to know whether it was a program, free weights, or a rowing/ski session. The tool finds the day's session(s) and returns each one fully resolved, as an array of the same `{training_id, kind, info, detail}` shape that `session` emits. (`kind` is `"program"`, `"free"`, or `""`.) Sample `workouts --json` output (a digest for picking; `kind` lets you filter): ```json [ { "training_id": 123456, "title": "Upper Body", "date": "2025-06-15", "duration_secs": 2700, "calories": 320, "volume": 4200.0, "type": "Strength", "kind": "program" } ] ``` `session <id> --json` is **autonomous and a faithful, complete passthrough**. Given only an id it figures out what the session was — a program/Coach session, free weights, or a rowing/ski free session — and emits the verbatim Speediance payloads under a uniform shape. The field names, nesting, and values are exactly what Speediance returned (`leftWatts`, `forceControlScore`, `weights`, `leftBreakTimes`, `totalDistance`, …); the CLI does not rename, reshape, compute, or fill gaps. `kind` tells you which namespace answered, so a type-agnostic consumer always reads the same two fields: | `kind` | `info` | `detail` | |---|---|---| | `"program"` | `cttTrainingInfo` payload (incl. `completionRate`) | `cttTrainingInfoDetail` — per-exercise, per-rep arrays | | `"free"` | `freeTraining` payload (totals: `totalCapacity`, `totalEnergy`, `totalDistance` for rowing/ski; `name` for guided sessions) | `freeTrainingDetail` — `[]` for a freestyle Free Lift, **populated** for a guided session (e.g. *Aerobic Rowing* → per-interval `finishedReps` with `distance`/`pace`/`spm` + per-stroke traces) | | `""` | `null` | `null` (no session found in either namespace) | > `kind:"free"` is the *free namespace*, not "freestyle". It spans both a > freestyle **Free Lift** (no `info.name`, `detail: []`, aggregates only) and a > **guided** free-namespace session (has `info.name`, often a populated `detail`) — > guided cardio like **Aerobic Rowing** carries the full per-interval breakdown. > Distinguish via `info.name` + whether `detail` has rows; don't assume `free` ⇒ empty. A program session: ```json { "training_id": 940759, "kind": "program", "info": { "completionRate": 100.0 // … the verbatim GET /app/trainingInfo/cttTrainingInfo/<id> data payload }, "detail": [ { "actionLibraryName": "Standing Dual-Handle Hammer Curl", "maxWeight": 15.0, "maxWeightCount": 5, "score": 16, "completionScore": 5, "forceControlScore": 4, "bilateralBalanceScore": 4, "amplitudeStableScore": 3, "actionRating": 3, "finishedReps": [ { "finishedCount": 14, "targetCount": 14, "capacity": 330.0, "leftRight": 0, "trainingInfoDetail": { "weights": [15,15,15,15,15,10,10,10,10,10,10,10,10,10], "leftWeights": [15,15,15,15,15,10,10,10,10,10,10,10,10,10], "rightWeights": [15,15,15,15,15,10,10,10,10,10,10,10,10,10], "leftWatts": [41.65,51.84, "…"], "rightWatts": [26.28,55.05, "…"], "leftAmplitudes": [0.46,0.68, "…"] } } ] } ] } ``` Notes for consumers: - **Auto-detection is built in** — no caller knowledge of the session type is needed. `session <id>` probes the program namespace and falls back to free; `today` picks each session's probe order from the day's record list (which carries the authoritative type — free-first for non-program types), with the same fallback. The result shape is identical either way. - **`weight` is never invented.** There is no synthesized per-set weight. For a program, the real per-rep weights are in `trainingInfoDetail.weights[]` (already per attachment, so a single-handle average is just their mean); a mid-set drop (e.g. `15×5 → 10×9`) is therefore visible. Average or summarize as you see fit. - **Free-namespace detail varies.** A *freestyle* Free Lift records session-level totals only (`info` aggregates, `detail: []`). A *guided* free-namespace session does more: **Aerobic Rowing** fills `detail` with per-interval rows (`distance`/`pace`/`spm`/`time`) and per-stroke rope-length traces. Always read `detail` rather than assuming `kind:"free"` is empty. - **Absence is preserved.** A field or array Speediance omits is omitted in the output too (e.g. a sparse capture with only `weights`); nothing is back-filled. - **Values are unvalidated passthrough.** Speediance's fields aren't guaranteed internally consistent, so derive the metric you want from raw values rather than trusting a single field — e.g. a rowing split is `distance / time`, not the per-interval `pace` field (which is an instantaneous sample). The CLI never "corrects" a value; that interpretation is yours. - **Empty shape.** `info` is `object | null`; `detail` is `array | null`. These are the verbatim endpoint payloads (never normalized), so treat **both `null` and `[]`** as "no rows" — e.g. `if not detail`. In practice `detail` is a populated array for `kind:"program"`, `[]` for a freestyle Free Lift, **populated** for a guided free-namespace session (see above), and `null` only for `kind:""`. - **No flag unlocks data** — the endpoints return it, so the CLI returns it. There is no `--telemetry`. > A `trainingId` can identify *different* sessions in the program vs. free > namespaces. Auto-detection prefers the program match; pass `--program` or > `--free` to `session` to force a namespace when an id is ambiguous. ### Browse the exercise catalog ```bash speediance-cli library --search "chest" --json # filter by name or muscle speediance-cli library # save full catalog to library.json ``` Returns `[{id, name, muscle, tab}]`. The `id` is required for plan JSON. Every run saves the full catalog to `--out` (default `library.json`) — `--search` filters only the stdout view, it does not narrow the saved file. The skill package doesn't bundle a catalog. Run `speediance-cli library` once to save it locally; a Gym Monster v1 snapshot also lives in the GitHub repo for offline browsing. ### Create a training program Author a plan JSON, then push it — the program appears on the machine immediately: ```bash speediance-cli push plan.json --dry-run # preview payload, no network write speediance-cli push plan.json # create it on the account ``` **Plan JSON format:** ```json { "name": "Pull Day", "exercises": [ { "id": 434, "title": "Seated Dual-Handle Lat Pulldown", "sets": [ {"reps": 12, "weight": 20, "mode": 1, "rest": 75}, {"reps": 10, "weight": 22, "mode": 1, "rest": 75}, {"reps": 8, "weight": 25, "mode": 1, "rest": 90} ] }, { "id": 291, "title": "Seated Row", "sets": [ {"reps": 12, "weight": 18, "mode": 1, "rest": 60} ] } ] } ``` | Field | Type | Notes | |---|---|---| | `id` | int | From `speediance-cli library` — IDs differ per account/device | | `weight` | float | **Kilograms** | | `mode` | int | 1=Standard, 2=Eccentric, 3=Isokinetic, 4=Constant, 5=Spotter | | `rest` | int | Seconds between sets | ### Storing what you read The CLI owns no log format. To keep a record of a session, pull it with `workouts --json` and `session <id> --json`, then write it wherever you keep data (a Markdown sheet, a database, a notebook). The tool reads and emits structured data; the caller decides the layout. ## Full command reference | Command | What it does | `--json` | |---|---|---| | `login` | Authenticate and cache a session token | — | | `workouts [--days N]` | List recent completed sessions | ✓ | | `today [--date D]` | Every session on a day, auto-resolved to type-correct detail (the one-shot entry point) | ✓ | | `session <training_id> [--free\|--program]` | Full, verbatim detail for one session; auto-detects program/free/rowing | ✓ | | `library [--search X] [--out FILE]` | Dump or search exercise catalog | ✓ | | `push <plan.json> [--dry-run]` | Create a training program on the account | ✓ | | `config show\|set\|path` | Manage `config.json` | ✓ (`show`) | | `version` | Build metadata (also `--version`) | ✓ | | `completion <shell>` | Shell completion (bash/zsh/fish/powershell) | — | ## Conventions - **stdout is parseable** with `--json`; all human-readable hints go to **stderr**. - **Exit codes**: `0` success, `2` authentication failure, non-zero for other errors. - **Secrets**: `config.json`, `.token.json`, `.env` are gitignored — never commit them. - **Token caching**: after the first login, the token is cached in your **OS user-cache directory** (e.g. `%LocalAppData%\speediance\token.json` on Windows, `~/.cache/speediance/token.json` on Linux, `~/Library/Caches/speediance/token.json` on macOS) and refreshed automatically on expiry — *not* in the working directory (so it can't be swept into a commit) and *not* in the roaming config dir (so a live credential isn't synced across machines). Override the location with an exported `SPEEDIANCE_TOKEN_CACHE` or the `token_cache_path` config key; run `speediance-cli config path` to see where it resolved. `token_cache_path` counts only in a config file chosen by `--config`, an exported `SPEEDIANCE_CONFIG` or the user-config dir: a `config.json` that's simply in the working directory can't choose where the token is written, and the CLI warns on stderr when it ignores one. A token left in a legacy `.token.json` by an older version is moved to the per-user location on first run. - **Dry-run first**: always use `--dry-run` before `push` when authoring new programs to confirm exercise IDs resolved correctly. - If an endpoint breaks after a Speediance app update, all API calls live in `internal/api` — that's the single place to patch.
这是模型对公开材料的总结,不是官方声明,请以原链内容为准。
存档时间线
| 版本 | 存档时间 | 内容哈希 | 内容 |
|---|---|---|---|
| v1 | 2026-11-14 04:05 | d0381ec9 | 可取 |
版本索引永久保留;内容副本只保留最新 1 份,更早版本仅留索引与哈希(存档时间线的证据链不会因此断裂)。
提交举报 / 纠错
侵权举报经核验成立后,我们会即时下线该条目并删除已存的内容副本。