Skip to content

CLI reference

openbox is a single binary that plays three roles — CLI, agent, and control plane — selected by subcommand.

Command summary

CommandWhat it does
openbox login --server URL --token Tauthenticate this machine
openbox whoamishow the logged-in user
openbox control [flags]run the self-hosted control plane
openbox agent [flags]run the node daemon
openbox daemon [flags]run openboxd: hold the transport/mesh open for an instant CLI
openbox node token [--tag …]mint a node enrollment token
openbox node add --host user@ip …bootstrap a remote node over SSH
openbox nodes [--tag t]list your nodes
openbox -t TAG [-s SID] [--docker IMG] <cmd…>run a command on a node
openbox skill <agent|all>install the openbox skill for an AI coding agent
openbox versionprint the version

Running commands

The bare form is the one you’ll use most. openbox <flags> <command…> is shorthand for openbox run <flags> <command…>:

Terminal window
openbox -t mac uname -a
openbox -n node_abc123 'echo $HOME'
openbox -t gpu --docker pytorch/pytorch python train.py

Everything after the flags is the command, passed verbatim — quote shell metacharacters as you normally would.

Targeting

FlagSelects
-t, --tag TAGany node carrying TAG (prefers online nodes)
-n, --node IDone exact node id
-s, --session SIDbind to a persistent shell (see below)

Sessions

-s SID binds a named session. The first command with a given id picks a node (honoring -t/-n) and opens a persistent shell there; every later command with the same id lands on that same node, in that same shell, in order, with cwd and environment preserved.

Terminal window
openbox -t mac -s build cd /tmp/work # picks a mac node, opens shell "build"
openbox -t mac -s build make # same node, same shell, sees the cd
openbox -t mac -s build ./ship.sh

Idle sessions are reaped automatically.

Isolation

The default is native — a dedicated shell on the host, lightweight. Opt up per command:

FlagRuns the command…
(default)natively on the host
--docker IMAGEinside a container (a session keeps one container alive across commands)
--nspawn DIRinside a systemd-nspawn container (Linux)
--isolate native|docker:img|nspawn:direxplicit form

Isolation fails closed: an unknown mode is rejected, never silently downgraded to running on the host.

openbox control

Runs the control plane: user auth, node registry, the SSH CA, the session directory, and the dashboard.

FlagDefaultPurpose
--addr127.0.0.1:8080listen address (use 0.0.0.0:8080 for LAN)
--urlhttp://127.0.0.1:8080public base URL advertised to nodes
--db~/.openbox/control/openbox.dbsqlite database path
--ca~/.openbox/control/ca_keyCA private key path
--mesh, --mesh-control, --mesh-authkeyjoin the mesh so the web console can reach mesh nodes

openbox agent

Runs the node daemon: registers with the control plane, serves cert-verified exec, and heartbeats.

FlagDefaultPurpose
--addr127.0.0.1:7600listen/advertise address (use 0.0.0.0:7600 for LAN)
--server$OPENBOX_SERVERcontrol-plane URL (first registration only)
--token$OPENBOX_ENROLL_TOKENenrollment token (first registration only)
--namehostnamenode display name
--tagtag to request at registration (repeatable)
--mesh, --mesh-control, --mesh-authkey, --mesh-hostnamejoin the mesh

After the first registration the agent saves its identity (node id, host cert, CA pubkey, server URL) under ~/.openbox/agent/ and runs without --server/--token.

openbox daemon

Runs openboxd, a long-lived local process that holds the transport — notably the embedded mesh node — open and runs commands on the CLI’s behalf. It listens on a Unix socket; when it’s running, openbox -t … <cmd> forwards the request to it instead of building its own transport per invocation, so mesh-targeted commands are instant.

FlagDefaultPurpose
--socket~/.openbox/openboxd.sockUnix socket to listen on ($OPENBOX_DAEMON_SOCKET overrides)
--mesh, --mesh-control, --mesh-authkey, --mesh-hostnamejoin the overlay and hold it open

The daemon reuses the same dispatch + mutually-cert-verified exec path as the inline CLI, so node-side behavior is identical. When no daemon is listening the CLI falls back to the inline path automatically; OPENBOX_NO_DAEMON=1 forces it.

Run it under systemd as a user service so it survives logout/reboot:

~/.config/systemd/user/openboxd.service
[Unit]
Description=openboxd
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=%h/.local/bin/openbox daemon --mesh --mesh-control https://headscale.example.com
Restart=always
RestartSec=3
[Install]
WantedBy=default.target
Terminal window
systemctl --user enable --now openboxd.service
loginctl enable-linger "$USER"

openbox node add

Bootstraps a remote node over SSH (enrollment methods 1 & 2): connects, uploads the binary, mints an enrollment token, and launches the agent.

FlagPurpose
--host user@host[:port]target to bootstrap (required)
--passwordSSH password (method 1)
--key, --key-passSSH private key + optional passphrase (method 2)
--binaryopenbox binary to upload (default: this binary; must match remote OS/arch)
--agent-addraddress the agent listens on/advertises
--name, --tagnode name and tags

--binary must match the remote OS/arch. From a Mac enrolling a Linux box, cross-build first: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o openbox-linux ./cmd/openbox, then pass --binary openbox-linux.

openbox skill

Installs the built-in openbox usage guide as a skill / instructions file for an AI coding agent, so the agent knows how to drive your device mesh (targeting by tag, sessions, isolation). Each agent gets the guide in the convention it expects:

AgentFile written
claude.claude/skills/openbox/SKILL.md
cursor.cursor/rules/openbox.mdc
copilot.github/copilot-instructions.md
codexAGENTS.md
opencodeAGENTS.md
Terminal window
openbox skill claude # one agent
openbox skill all # every supported agent (codex + opencode share AGENTS.md)
openbox skill all --dir ./app # install into another project directory
openbox skill --list # list supported agents
openbox skill --print # print the guide to stdout

For the shared files (AGENTS.md, copilot-instructions.md) openbox writes a marked block and preserves the rest of the file; re-running updates that block in place instead of duplicating it. Dedicated files are written whole.