EMOS CLI¶
The emos CLI manages installation, recipes, the dashboard daemon, and device configuration on a robot. Every long-form action it performs is also exposed over the dashboard’s REST API (see internal/server/openapi.yaml), so anything you can do on the terminal you can also drive from a browser or an agentic skill.
Quick Reference¶
Command |
Description |
|---|---|
|
Install EMOS (interactive mode selection) |
|
Remove EMOS (mode-aware cleanup) |
|
Update EMOS to the latest version |
|
Show installation status |
|
Run the dashboard daemon (REST API + web UI) |
|
Inspect or modify device configuration, pairing tokens, TLS |
|
List recipes available for download |
|
Download a recipe |
|
List locally installed recipes |
|
Run a recipe (foreground, blocking) |
|
Show sensor/topic requirements for a recipe |
|
Mapping tools (record, edit) |
|
Install and manage the robot plugin |
|
Show CLI version |
Tip
Every command supports -h/--help. The CLI is also a single static binary — copy /usr/local/bin/emos to another machine and it just works (no Python, no runtime dependencies).
Typical Workflows¶
First-time setup¶
# 1. Install EMOS (interactive mode menu)
emos install
# 2. Start the dashboard (printed pairing code is shown once)
emos serve
# 3. (optional) Make the dashboard auto-start at boot
sudo emos serve install-service
After step 2, point a browser at http://emos.local:8765 (or scan the QR), enter the pairing code, and you’re in. See Dashboard.
CLI-only recipe loop¶
emos recipes # browse the catalog
emos pull vision_follower # download a recipe
emos info vision_follower # check what sensors it needs
emos run vision_follower # launch it (blocks until exit)
Running Recipes¶
emos run <name> adapts to the install mode – starting the container in container mode, sourcing ROS in native, activating the pixi env in pixi – before exec-ing the recipe and streaming logs to ~/emos/logs/<recipe>_<timestamp>.log. Logs are also visible from the dashboard’s Run console.
For the full guide to writing, dropping in, and launching custom recipes (including the install-mode pitfalls of running them directly via python), see Running Recipes.
Recipe Layout¶
A recipe is a directory under ~/emos/recipes/ with the following structure:
~/emos/recipes/
my_recipe/
recipe.py # Main entry point (required)
manifest.json # Optional: Zenoh config / display name / description
manifest.json¶
{
"name": "My Recipe",
"description": "Does the thing.",
"zenoh_router_config_file": "my_recipe/zenoh_config.json5"
}
name — display name for the dashboard’s recipe cards. Falls back to the directory name.
description — short blurb shown on the recipe detail page.
zenoh_router_config_file — path (relative to
~/emos/recipes/) to a Zenoh router.json5config file. Only consulted when the recipe runs underrmw_zenoh_cpp.
Note
Sensor requirements are auto-extracted from recipe.py by parsing Topic(name=..., msg_type=...) declarations. You don’t need to list them in the manifest. Run emos info <recipe> (or open the recipe in the dashboard) to see the inferred requirements.
For the full walkthrough – writing the recipe, dropping it in, verifying discovery, and launching it via emos run or the dashboard – see Running Recipes.
Command Reference¶
emos install¶
emos install # interactive mode menu
emos install --mode container # OSS container (no ROS required on host)
emos install --mode native # native (uses host's ROS 2)
emos install --mode pixi # self-contained ROS via pixi (no system ROS)
emos install --mode licensed <key> # licensed deployment (requires license key)
emos install --distro jazzy # pin a ROS distro for container/native mode
Flag |
Default |
Description |
|---|---|---|
|
(prompt) |
One of: |
|
(prompt) |
ROS 2 distribution: |
The installer offers, at the end, to:
Install a systemd unit so the dashboard auto-starts at boot (see Make the dashboard start automatically).
Persist the chosen device name and a fresh pairing code to
~/.config/emos/config.json.
Note
Pixi mode requires pixi on the host (emos install --mode pixi errors with install instructions if it’s missing). It clones the EMOS workspace and builds it under ~/.local/share/emos, independent of any system ROS. Currently pinned to ROS 2 Jazzy. See Installation.
emos uninstall¶
sudo emos uninstall # interactive
sudo emos uninstall --yes # non-interactive
sudo emos uninstall --keep-data # preserve recipes + logs
sudo emos uninstall --keep-config # preserve dashboard auth state
sudo emos uninstall --remove-image # also docker rmi (container / licensed)
Stops the dashboard service and runs mode-specific cleanup. By default also removes ~/emos/recipes, ~/emos/logs, and ~/.config/emos.
Flag |
Default |
Description |
|---|---|---|
|
|
Preserve |
|
|
Preserve |
|
|
Also |
|
|
Skip the confirmation prompt. |
Mode-specific behavior:
Container / licensed:
docker stop+docker rmthe EMOS container; licensed also removes the container auto-restart unit and~/emos/robot/.Native: removes the build workspace and
pip uninstallskompass-core. EMOS package files in/opt/ros/<distro>/are co-mingled with ROS by colcon and cannot be cleanly removed – the command prints the manualrmcommands rather than running them, so you can review and apply if you want.Pixi: removes the EMOS-owned pixi workspace at
~/.local/share/emos(cloned repo + env + build) wholesale. A workspace you cloned yourself elsewhere is preserved — only its build artifacts are stripped.
The CLI binary at /usr/local/bin/emos is never removed automatically – a running process can’t reliably unlink itself. The command prints the sudo rm one-liner for you.
Tip
Run emos uninstall before switching install modes (e.g. native -> pixi). It clears auth tokens and mode-specific state that would otherwise carry over and confuse the new install.
emos update¶
emos update
Detects the install mode and updates accordingly. Container mode pulls the latest image and recreates the container; native mode pulls the latest source, rebuilds, and re-installs into /opt/ros/{distro}/; pixi mode runs git pull, refreshes the pixi env (pixi install), and rebuilds the EMOS packages (pixi run setup). If a robot plugin is installed, it is also pulled to its latest commit and rebuilt.
emos status¶
emos status
Shows install mode, ROS distro, container/service state, and dashboard service health. Subset of what emos config show reports.
emos serve¶
Run the dashboard daemon. See the dedicated Dashboard page for the UX.
emos serve # foreground; HTTP on the configured port
emos serve --tls # opt into HTTPS (self-signed cert)
emos serve --addr :9000 # bind to a custom port for one run
emos serve --qr # print a QR for the dashboard URL and exit
Flag |
Default |
Description |
|---|---|---|
|
(empty) |
|
|
|
Skip mDNS announcement. The dashboard is then only reachable by IP / explicit hostname. |
|
|
Dev only. Accept all requests without a bearer token. |
|
|
Serve over HTTPS using a self-signed cert under |
|
|
Print a QR code with the dashboard URL and exit (no daemon). |
|
|
Log every HTTP request (including reads) at DEBUG. |
emos serve install-service¶
sudo emos serve install-service
Writes /etc/systemd/system/emos-dashboard.service, enables it, and starts it. The unit’s ExecStart points at the binary you ran the command with, so it follows the active install (/usr/local/bin/emos if installed via the script).
emos serve uninstall-service¶
sudo emos serve uninstall-service
Stops, disables, and removes the unit. Does not remove the cert, config, or recipes.
emos config¶
Inspect and modify everything in ~/.config/emos/config.json — install info, device name, port, paired-device tokens, and TLS material.
emos config show # human-readable device state
emos config get [key] # print one value or the whole config as JSON
emos config set <key> <value> # writable keys: name, port
emos config path # print the config file path
emos config tokens # list paired browsers / agents
emos config revoke-token <id|label> # revoke a single paired device
emos config rotate-pairing # issue a fresh pairing code (existing tokens stay valid)
emos config tls-fingerprint # print the dashboard TLS cert SHA-256 fingerprint
emos config tls-regenerate # re-mint the self-signed TLS cert (use after IP change)
emos config reset # reset device state (pairing/name/port); keeps install info
emos config show¶
Prints a single-pane summary:
EMOS DEVICE STATE
Identity: epic-otter
Mode: native
ROS distro: jazzy
Dashboard port: 8765
Recipes: /home/you/emos/recipes
Logs: /home/you/emos/logs
Config file: /home/you/.config/emos/config.json
Pairing configured: yes
Active tokens: 2
Dashboard service: active (emos-dashboard.service)
emos config get [key]¶
With no argument, prints the full config as JSON. With a key (name, mode, ros_distro, port), prints a single value — useful in shell scripts.
emos config set <key> <value>¶
Writable keys: name (mDNS hostname segment, validated against [a-z0-9-]), port (1–65535). Other fields are managed by the installer / serve daemon.
emos config set name happy-robot
emos config set port 9000
sudo systemctl restart emos-dashboard.service # if running as a service
emos config tokens and revoke-token¶
Lists paired browsers without leaking the underlying token hash:
ID LABEL ISSUED EXPIRES
4d0e9c01 phone 2026-04-12 10:32 2026-07-11 10:32
71a3f82b laptop 2026-04-12 11:07 2026-07-11 11:07
Revoke one device:
emos config revoke-token 4d0e9c01 # by short id (prefix of hash)
emos config revoke-token phone # by exact label
emos config rotate-pairing¶
Issues a new six-digit pairing code, without revoking already-paired tokens. Useful when a code may have been seen by someone who shouldn’t get further access.
emos config rotate-pairing
# ✓ New pairing code (shown once): 829471
emos config tls-fingerprint / tls-regenerate¶
See HTTPS (Optional) below.
emos pull¶
emos pull <recipe_short_name>
Downloads a recipe from the Automatika catalog and extracts it to ~/emos/recipes/<name>/. Overwrites the existing version if present. Requires internet.
emos ls¶
emos ls
Lists everything under ~/emos/recipes/. The dashboard’s Recipes → Installed tab shows the same set.
emos info¶
emos info <recipe_name_or_path>
Inspects a recipe’s Python source via AST and prints its sensor and topic requirements. Accepts either a recipe name (looked up in ~/emos/recipes/) or a path to a .py file:
emos info vision_follower # ~/emos/recipes/vision_follower/recipe.py
emos info ./my_recipe.py # explicit path
The output groups topics into:
Required Sensors —
Image,LaserScan,Imu,Audio,Odometry,RGBD,PointCloud2,CompressedImage. Hardware label and suggested apt packages tailored to your distro.Other Topics — non-sensor topics declared by the recipe.
emos run¶
emos run <recipe_short_name>
emos run <recipe> --rmw rmw_cyclonedds_cpp
emos run <recipe> --skip-sensor-check
Flag |
Default |
Description |
|---|---|---|
|
|
One of: |
|
|
Skip the 10-second sensor-topic verification. |
What happens during emos run¶
Reads
recipe.pyand extractsTopic(...)declarations.Identifies sensor topics.
Starts the Zenoh router (only when using
rmw_zenoh_cpp).Launches
~/emos/robot/launch/bringup_robot.pyif it exists (native / pixi only; container mode uses an in-container bringup).Verifies each sensor topic is publishing (polls
ros2 topic listfor up to 10 s).Executes the recipe — output streams to the terminal and is saved to
~/emos/logs/.
When to use --skip-sensor-check¶
Sensors that publish on-demand (service-triggered cameras).
Replaying a rosbag whose topic names differ from the recipe.
Pure AI recipes (LLM chat, TTS) that don’t require sensor data.
Warning
If you skip the check and a sensor topic never arrives, the recipe may hang silently waiting for data. Use ros2 topic hz /topic_name to diagnose.
See also
Troubleshooting for common errors during recipe execution.
emos plugin¶
Install and manage the robot plugin — a ROS package that adapts a specific robot to the EMOS stack. A robot runs one plugin at a time. See Robot Plugins for the full guide.
emos plugin list # browse the catalog (active plugin marked)
emos plugin install <name> # install + activate (replaces any active plugin)
emos plugin inspect # show the active plugin's feedbacks/commands/actions/events
emos plugin remove # remove the active plugin
Subcommand |
Description |
|---|---|
|
List plugins available in the Automatika catalog; flags the currently active one. |
|
Clone, build (for your install mode), and activate a plugin. Prompts before replacing one. |
|
Pretty-print the active plugin’s introspection tree (same data the dashboard System page shows). |
|
Remove the active plugin from the robot. |
Installing a plugin makes it importable; a recipe opts in with Launcher(robot_plugin=MyRobotPlugin()). The dashboard’s Plugins page drives the same flow from a browser.
emos map¶
Mapping subcommands for creating and editing environment maps:
emos map record # record mapping data on the robot
emos map install-editor # install the map editor container (one-time)
emos map edit <file.tar.gz> # process a ROS bag into a PCD map
emos version¶
emos version
# emos vX.Y.Z
Prints the CLI version. The dashboard’s /api/v1/info returns the same value.
HTTPS (Optional)¶
The dashboard serves plain HTTP by default. That’s the right default on a trusted LAN — bearer-token auth gates every write, so eavesdropping yields nothing useful unless someone can also intercept the pairing handshake.
You can opt in to HTTPS with a self-signed certificate:
emos serve --tls
What the flag does¶
On first launch under --tls, the daemon mints a 2-year ECDSA P-256 certificate and persists it under ~/.config/emos/:
Path |
Mode |
Purpose |
|---|---|---|
|
|
PEM-encoded leaf certificate (also acts as its own CA). |
|
|
PEM-encoded private key. Never copy this off the robot. |
The certificate’s SubjectAltName covers:
localhost,<device-name>.local,emos.local127.0.0.1,::1Every LAN IPv4 the device sees at mint time (excluding loopback / docker / veth / tailscale-style virtual interfaces).
It re-uses the persisted cert on subsequent launches and auto-rotates inside the 30-day-before-expiry window. Network-level changes (new IP, new mDNS name) don’t trigger automatic rotation — see tls-regenerate below.
Trusting the cert¶
A self-signed cert produces a “Not Secure” warning the first time a browser hits it. The warning is encryption-preserving — TLS still negotiates a session — but the trust chain is empty, so browsers refuse to call the connection authenticated. Two paths from there:
Click through, every time. Fine for a workshop / one-off install. The encryption protects against passive sniffing on the LAN, the bearer token still protects against unauthenticated access, and the warning is mostly cosmetic. Verify the cert before clicking through:
emos config tls-fingerprintCompare the printed SHA-256 fingerprint with the one the browser shows under “View Certificate” before trusting it.
Add the cert to a trust store so the warning goes away permanently for that device:
Firefox keeps its own trust store: Settings → Privacy & Security → Certificates → View Certificates → Authorities → Import
~/.config/emos/tls.crt, then tick “Trust this CA to identify websites.”Chrome / Edge follow the OS trust store. On Linux:
sudo cp tls.crt /usr/local/share/ca-certificates/emos-<name>.crt && sudo update-ca-certificates. On macOS, drop the cert into Keychain Access and mark it Always Trust. On Windows,certmgr.msc → Trusted Root Certification Authorities → Import.
When to regenerate¶
The SAN list is fixed at mint time. If the robot’s primary IP changes (new network, new DHCP lease) or you renamed it (emos config set name), the cert no longer matches the new address and the browser will show a different error (NET::ERR_CERT_COMMON_NAME_INVALID). Regenerate:
emos config tls-regenerate
sudo systemctl restart emos-dashboard.service # if running as a service
Inspect the active fingerprint at any time:
emos config tls-fingerprint
# TLS CERTIFICATE
# Fingerprint (SHA-256):
# 8B:F2:0C:...:E7
# Expires: 2028-04-28
# Certificate: /home/you/.config/emos/tls.crt
# Private key: /home/you/.config/emos/tls.key
When you actually need HTTPS¶
Most EMOS deployments are happy on HTTP. Reach for --tls when:
You’re building dashboard features that need a secure context —
getUserMedia(microphone / camera), Web Audio capture, Service Workers, Web Bluetooth. Browsers won’t expose these APIs over plain HTTP.You’re running on a network you don’t fully control (a venue Wi-Fi, a colocated factory) where token-only auth feels too thin.
An organisational policy requires HTTPS end-to-end.
Tip
Per-recipe Sugarcoat web UIs that need a secure context handle their own TLS independently of the dashboard. See the Dynamic Web UI page.
Running as a service with TLS¶
The systemd unit installed by emos serve install-service runs plain HTTP. To switch the service to HTTPS, edit its ExecStart:
sudo systemctl edit --full emos-dashboard.service
# add --tls to the ExecStart line, e.g.
# ExecStart=/usr/local/bin/emos serve --addr :8765 --tls
sudo systemctl daemon-reload
sudo systemctl restart emos-dashboard.service
Files & Paths¶
Path |
Purpose |
|---|---|
|
Single source of truth: install info, device name, port, paired-device tokens (hashed). Mode |
|
Self-signed TLS material (created by |
|
Installed recipes. Each subdirectory is one recipe. |
|
Per-run log files: |
|
Native-mode build workspace. |
|
Pixi-mode install: cloned EMOS workspace, pixi env, and colcon overlay ( |
|
Robot-plugin source and build overlay ( |
|
Dashboard auto-start unit (created by |
|
Container auto-restart unit (created by licensed install). |
Tip
~/.config/emos/config.json is the persistent state. emos config reset clears the dashboard’s device-side state (paired browsers, custom name, custom port) but preserves install info (mode, ROS distro, license key) so the dashboard keeps recognising the device as installed afterwards.