Skip to content
Migrating from Legacy Systems

Migrate OpenHTF Scripts to TofuPilot CLI

Move OpenHTF scripts from the legacy with TofuPilot wrapper to the TofuPilot CLI runner with deterministic deploys, station auth, and live UI.

JJulien Buteau
intermediate8 min readMay 9, 2026

The TofuPilot CLI is the new way to run OpenHTF scripts on a station. It is the runtime behind Stations v2.0, and it replaces the with TofuPilot(test): wrapper you used before. This guide shows what changes in your script and how to migrate.

Starting fresh? Try a clean deploy first. Before touching your existing script, create a new procedure in the dashboard and clone the tofupilot/template-openhtf-starter template from the new procedure flow. You will go from zero to a working live run in a few minutes, validate the new station setup end to end, and only then migrate your real script.

Deprecation timeline

The legacy operator UI is being deprecated. Target date is July 1st, 2026. Upcoming Python client releases will drop real-time streaming from the with TofuPilot(test): wrapper: the wrapper itself will keep working for post-run upload, but live phase, measurement, log, and attachment streams to the dashboard will only run through the new CLI. Our documentation is being updated to remove every reference to the legacy real-time path. We are reaching out directly to every team still using it to provide guidance and hands-on migration support. Once every active team has migrated, the legacy real-time server will be shut down. After that point only the new CLI-based stations will stream runs live.

Why move to the CLI

Stations v2.0 is powered by the TofuPilot CLI and brings a few things the old wrapper could not:

  • One-command install on the station. No more API key juggling, no manual Python setup. The CLI installs in one shell command from the station setup page and logs in once.
  • Push from your repo, deploy automatically. Connect the procedure to its GitHub repo and pick a branch (typically main) for auto-deploy. Pushes to that branch deploy to the right stations on their own. Other branches stay manual: deploy them from the dashboard when you are ready.
  • Continuous run execution. Stations loop runs back-to-back on their own. Operators scan a serial number, the run finishes, the next one is queued. No while True: loop in your script, no manual relaunch between units.
  • Three UIs out of the box. A local kiosk UI runs on the station for shop-floor operators and works fully offline. A terminal UI runs in your shell when an engineer drives a benchtop test. The web operator UI in the dashboard streams the same run live whenever the station is online, just like before. Customer feedback on the new operator UI has been the strongest of any release this year.
  • Offline-friendly upload queue. Runs are queued locally on the station and uploaded as soon as the network is back, so a flaky link never costs you data.
  • Operator role. A new role for shop-floor users that only sees the stations on its team, lands on a dedicated /operator page, and gets every run auto-attributed to the right person. No shared API key.
  • Self-hosted ready. The new CLI works against self-hosted TofuPilot deployments out of the box. The legacy operator UI never supported self-hosted, so this is the first time on-prem teams get the live operator experience.

The CLI keeps every feature the previous wrapper shipped: identify-unit, validators, attachments, prompts, logs, post-run upload.

Migration steps

1. Update your script (2 minutes)

Drop the tofupilot import and the with block. Drop the procedure ID, batch, and any custom serial-number prompt too: the CLI handles unit identification (serial number, part number, revision, batch) natively through the operator UI before each run.

main.py
from tofupilot.openhtf import TofuPilot # [!code --]import openhtf as htfdef get_serial(): # [!code --]    return input("Scan serial number: ") # [!code --]def measure_voltage(test):    test.measurements.voltage = 3.3test = htf.Test( # [!code --]    measure_voltage, # [!code --]    procedure_id="FVT1", # [!code --]    test_description="Voltage check", # [!code --]) # [!code --]test = htf.Test(measure_voltage) # [!code ++]with TofuPilot(test, batch_number="B-2024-09"): # [!code --]    test.execute(get_serial) # [!code --]test.execute() # [!code ++]

Add a pyproject.toml next to it so the CLI can resolve dependencies:

pyproject.toml
[project]name = "fvt1"version = "0.1.0"dependencies = ["openhtf>=1.6"]

2. Connect the procedure to your repository

On the procedure page in the dashboard, link the GitHub repository that holds your script. As soon as it is linked, every push to the auto-deploy branch creates a deployment ready to roll out to your stations. Pushes to other branches stay manual and ship from the dashboard on demand.

3. Set up the station with the install command

On the station detail page in the dashboard you will find the install command for Linux, Windows, and macOS, pre-filled with the right token. Run it on the station once. The CLI installs, registers itself with your organization, and the station shows up online in the dashboard.

4. Tune the station config

From the station settings, pick the behaviors that match your floor:

  • Launch on boot so the station is ready as soon as it powers up.
  • Kiosk UI for full-screen operator mode, or Terminal UI for an engineer benchtop.
  • Desktop shortcut for one-click launch.
  • Auto-update to keep the CLI current between runs.

You are done. The next operator-triggered run lands in the dashboard live, with attribution, validators, and attachments handled by the CLI.

More Guides

Put this guide into practice