This repository contains the first, deliberately narrow slice of the project:
@open-trainer/ftms, a browser-oriented FTMS smart-trainer library.- Trainer Lab, a minimal interface for inspecting telemetry and testing safe control commands.
There is no account system, cloud service, multiplayer code, workout marketplace, or game framework in this phase.
- Node.js 20.19 or newer
- Chrome or Edge for a real trainer
- A secure context: HTTPS in production or
localhostduring development - KICKR CORE firmware 1.1.1 or newer for standard Bluetooth FTMS
npm install
npm run devOpen http://127.0.0.1:4173. Choose Use simulator to exercise the entire public API without hardware.
npm test
npm run typecheck
npm run build
npm run verify:packageThe library follows a test-first workflow. Its current suite covers FTMS packet codecs, reactive state behavior, GATT operation serialization, FTMS command sequencing, trainer state transitions, and package export boundaries.
The Publish npm package GitHub Action is manual and defaults to a dry run. It installs dependencies from the lockfile, runs the complete test/type/build suite, and verifies the package archive before any publish step can execute.
For a real release:
- Merge the Changesets-generated version pull request described below. npm versions cannot be overwritten.
- In the npm package settings, configure a GitHub Actions trusted publisher for user
cemergin, repositoryopen-trainer-ftms, workflow filepublish.yml, environmentnpm, and allow thenpm publishaction. - Optionally protect the repository's
npmenvironment with required reviewers. - Open Actions → Publish npm package → Run workflow, select
publish, choose thenextorlatesttag, and enterpublish @open-trainer/ftmsexactly.
The workflow uses short-lived OIDC authentication and publishes provenance from a GitHub-hosted runner. If an initial token-authenticated publish is required before npm will let you configure the trusted publisher, add a narrowly scoped automation token as the NPM_TOKEN secret on the npm environment, publish once, then remove the secret after trusted publishing is configured.
See npm's trusted publishing documentation for the one-time registry configuration.
Published versions are managed with Changesets:
- For a user-visible library change, run
npm run changesetin the feature branch. - Select
@open-trainer/ftms, choose the semantic version impact, and write a short user-facing summary. - Commit the generated
.changeset/*.mdfile with the pull request. - After changes land on
main, the Version packages action creates or updates one version pull request containing the calculated package version, lockfile, and changelog changes. - Merge that version pull request when ready, then use the manual npm release workflow above.
Changesets combine multiple pending entries and apply the highest required bump. Documentation, tests, CI, and unpublished Trainer Lab changes do not need a changeset. The versioning bot never publishes to npm.
- Update the trainer in the Wahoo app, then fully close Wahoo, Zwift, and other trainer applications.
- Put the bike on the trainer and plug the trainer into power.
- Open Trainer Lab in Chrome or Edge and choose Connect trainer.
- Confirm that power and cadence appear before taking control.
- Choose Take control.
- Set a low target such as 80 W, then choose Start.
- Confirm that Stop resistance returns the trainer to a safe state.
- Download the session log if anything behaves unexpectedly.
Do not perform the first control test on an unoccupied bike. ERG mode can increase resistance sharply when cadence falls.
The trainer core depends on the small FtmsTransport port. The included adapters are:
WebBluetoothFtmsTransportfor physical BLE hardware.MockFtmsTransportfor the lab, unit tests, and the future single-player game.
The future game should consume only the package's public telemetry and control API. It should not parse Bluetooth packets or access GATT characteristics directly.
import type { Trainer } from "@open-trainer/ftms";
import { createWebBluetoothTrainer } from "@open-trainer/ftms/web-bluetooth";
import { createMockTrainer } from "@open-trainer/ftms/testing";Reactive data flows out through read-only state and streams. Async commands flow in through serialized queues. See ARCHITECTURE.md for the boundaries, TDD strategy, and remaining hardware-validation plan.
After hardware validation, the next contained milestone is a local-first single-player workout engine and one playable arena. The trainer library remains independent and publishable.