Coding

Speediance Gym Monster

Try it

Read Speediance Gym Monster workouts and push custom programs to the machine from any agent.

What it does

Talk to your Speediance (Gym Monster) smart cable machine from any agent without opening the app. The CLI reads completed workouts with auto-detected session type and verbatim per-set detail, browses and exports the exercise catalog, and pushes custom training programs that appear on the machine ready to run. Authentication uses your account credentials with a session token cached in your OS user-cache directory; structured JSON in, structured JSON out — the caller decides where to store results. Ships as a single static binary — no Python or other runtime required.

When to use it

  • Fetching today's full session detail without opening the phone mid-workout
  • Listing recent sessions as a digest for picking which one to analyze
  • Searching the exercise catalog by name or muscle and exporting it to JSON
  • Authoring a plan JSON and pushing it as a new training program on the account

The skill document

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:

# 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:

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:

VariableRequiredDefaultNotes
SPEEDIANCE_EMAIL✓—Account email
SPEEDIANCE_PASSWORD✓—Account password
SPEEDIANCE_REGION—GlobalGlobal or EU
SPEEDIANCE_DEVICE_TYPE—11 = Gym Monster v1

Alternatively, write a config.json in the working directory (gitignored by the repo):

{
  "email": "you@example.com",
  "password": "yourpassword",
  "region": "Global"
}

You can also put these variables in a gitignored .env file in the working directory — the SPEEDIANCE_* keys 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.

Commands

Read workouts

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  --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):

[
  {
    "training_id": 123456,
    "title": "Upper Body",
    "date": "2025-06-15",
    "duration_secs": 2700,
    "calories": 320,
    "volume": 4200.0,
    "type": "Strength",
    "kind": "program"
  }
]

session --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:

kindinfodetail
"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)
""nullnull (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:

{
  "training_id": 940759,
  "kind": "program",
  "info": {
    "completionRate": 100.0
    // … the verbatim GET /app/trainingInfo/cttTrainingInfo/ 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 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

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. A committed library.json snapshot ships with the repo (Gym Monster v1) for offline browsing — regenerate with speediance-cli library to get the freshest catalog or a different device's exercises.

Create a training program

Author a plan JSON, then push it — the program appears on the machine immediately:

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:

{
  "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}
      ]
    }
  ]
}
FieldTypeNotes
idintFrom speediance-cli library — IDs differ per account/device
weightfloatKilograms
modeint1=Standard, 2=Eccentric, 3=Isokinetic, 4=Constant, 5=Spotter
restintSeconds 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 --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

CommandWhat it does--json
loginAuthenticate 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 [--free|--program]Full, verbatim detail for one session; auto-detects program/free/rowing✓
library [--search X] [--out FILE]Dump or search exercise catalog✓
push [--dry-run]Create a training program on the account✓
config show|set|pathManage config.json✓ (show)
versionBuild metadata (also --version)✓
completion 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 SPEEDIANCE_TOKEN_CACHE or the token_cache_path config key; run speediance-cli config path to see where it resolved. 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.

Related skills

Generate and edit Draw.io, Mermaid, and Excalidraw diagrams from natural language using a structured JSON spec.

by nssa.io1.0k installs47 stars

Find why your productivity system keeps failing, then apply the smallest fix — capacity math, bottleneck routing, durable local notes.

by Iván855 installs69 stars

Stores durable facts in a categorized, plain-markdown vault on disk, alongside your agent's built-in memory.

by Iván559 installs18 stars

Fetch raw ad creative, app, ranking, and revenue data from AdMapix as structured JSON.

by fly0pants4.3k installs296 stars

Join a video meeting as an AI bot with voice, avatar, and screenshare across four operating modes.

by johnpatternai22 installs8 stars

More from stozo04

Browse all skills

Read your Google Health data from any agent. Lists data points for every Google Health data type (heart rate, resting heart rate, sleep, steps, distance, weight, blood oxygen, VO2 max, exercise, and more), returns server-side daily roll-ups (one reconciled total per day), gives a parsed view of exercise sessions, and can GET any read-only v4 API path. A self-contained, read-only client for the Google Health v4 API: authenticates via OAuth2 (token cached locally and auto-refreshed) and emits JSON. It does NO filtering, merging, or writing — the caller decides what to do with the data. Single static binary, no Python or other runtime. NOTE: needs your own Google Cloud OAuth client and a one-time interactive browser login; runs headless afterward.

by stozo045 installs

Read your Lose It! nutrition from any agent. Logs in with your Lose It email/password (self-healing — it obtains and refreshes its own session token), fetches your data export, and emits per-day nutrition as JSON — calories, protein, carbs, fat, fiber, a per-meal breakdown, and Lose It's own budget / under / exercise-adjustment figures. A self-contained, read-only extractor: it never changes your Lose It account and does no application writing (no daily log, no sync) — the caller decides what to do with the data. The only file it writes locally is its own session-token cache (a credential, mode 0600). Single static binary, no Python or other runtime. Auth options: email/password (recommended — set LOSEIT_EMAIL/LOSEIT_PASSWORD or put them in config.json), a downloaded export ZIP (--zip, no credentials), or a saved liauth cookie.

by stozo044 installs