handmux docs
CLI Reference

Documentation

Complete reference for the handmux CLI. Install once, configure once with handmux setup, run forever.

Quick start

Three steps. If you already have tmux, step one is done.

# 1 — check prerequisites
node -v   # need Node ≥ 18  →  nodejs.org
tmux -V   # need tmux ≥ 3.0  →  brew install tmux

# 2 — install
npm i -g handmux

# 3 — run (LAN-only by default, zero config)
handmux start

# reach it from anywhere:
handmux start --tunnel cloudflare

Tip: scan the QR with your phone camera — it encodes the token so you sign in automatically.

Add to Home Screen

handmux runs as a PWA — add it to your home screen to open it full-screen, like a native app. On iPhone this is also required for push, so it can ping you the moment a pane needs you.

iPhone / iPad — Safari

  1. Tap the Share button (the ⬆︎ box) in Safari.
  2. Tap “More”, or scroll down the actions list.
  3. Choose “Add to Home Screen”, then “Add”.

Note: only Safari can install a PWA on iOS. If you opened the link in another browser, reopen it in Safari first.

Android — Chrome

Tap the browser menu (⋮) and choose “Install app” / “Add to Home screen” — or accept the install banner if it appears.

Requirements

WhatVersionNote
Node.js≥ 18nodejs.org
tmux≥ 3.0brew install tmux · apt install tmux
Phone browserany modernno app to install

Windows (WSL2)

handmux drives tmux, which is Unix-only — there's no native Windows build. Run it inside WSL2 (a real Linux kernel with real tmux) and the quick start above works unchanged.

# one-time, in PowerShell (admin): installs WSL2 + Ubuntu, then reboot
wsl --install

# then open Ubuntu and follow Quick start: apt install tmux · install Node · npm i -g handmux

Use a tunnel. WSL2 is a NAT'd VM with its own IP, so the same-wifi LAN URL won't reach your phone. Start with handmux start --tunnel cloudflare — the public link works regardless.

Autostart needs systemd. handmux service uses systemd; enable it once by adding [boot] / systemd=true to /etc/wsl.conf, then wsl --shutdown. Without it, just run handmux start in a terminal you keep open.

handmux start[flags]

Start the server and tunnel. Reads ~/.handmux/config.json if it exists; any flag overrides one value for this run only and is never persisted.

With no config file, starts LAN-only (--tunnel none), auto-generates a token, and prints the URL + QR code. Use handmux setup to persist settings.

handmux start                        # LAN-only — nothing exposed to the internet
handmux start --tunnel cloudflare    # public https URL (random Cloudflare hostname)
handmux start --port 8080            # listen on port 8080 instead of 19999
handmux start -f                     # foreground mode — Ctrl-C to stop
handmux start --name "Work Box"      # custom browser tab / home-screen name

Note: flags are one-run overrides. To make a change stick, run handmux setup or edit ~/.handmux/config.json.

handmux setup

Interactive wizard: language, app name, tunnel type — a new user defaults to the zero-config Cloudflare quick tunnel — plus optional push-notification and voice-input credentials. Writes ~/.handmux/config.json; re-run any time.

When it's done it also offers to:

  • Install the inbox hooks for Claude Code / Codex — enables the inbox and push notifications
  • Start handmux right away
handmux setup                        # interactive — walks through all options
handmux setup --config ~/work.json   # write to a custom config file

handmux stop·restart·status

Control the running handmux process.

handmux stop       # SIGTERM to supervisor — server + tunnel exit cleanly
handmux restart    # stop, wait 600 ms, start again with the same config
handmux status     # show running state, live URL, token

Example status output

 handmux running
  tunnel   cloudflare   ·   pid 21352
  🌐 open   https://elementary-incidents.trycloudflare.com
  💻 local  localhost:19999
  🔑 token  aicbHOGWdQ
  ✓ reachable

handmux logs[--follow] [--lines N]

View the supervisor log at ~/.handmux/handmux.log.

handmux logs              # last 200 lines
handmux logs --follow     # stream live (tail -f)
handmux logs --lines 50   # last 50 lines

handmux config

Print the effective config that would be used on next start, with each value's origin: flag (this run), file (path shown), env, or default. Secrets are masked; push / voice credentials shown as on or off.

handmux config                        # read ~/.handmux/config.json
handmux config --config ~/work.json   # inspect a different file
handmux config --tunnel cloudflare    # preview with a flag applied

handmux update

Upgrade the global install to the latest published version — a convenience wrapper for npm i -g handmux@latest, run through your own npm so a China mirror / private registry is honoured. It doesn't restart a running instance: after it finishes, run handmux restart to actually run the new version.

handmux update      # npm i -g handmux@latest
handmux restart     # then apply it to the running server

The app nudges you. handmux checks for a newer release about once an hour; when one exists, your phone lights a dot on the settings gear and Settings shows the current / latest version — a reminder to run this on your computer.

handmux hooksinstall | uninstall

Install or remove the Claude Code lifecycle hooks that drive the inbox and push notifications. Hooks are written to ~/.claude/settings.json. Silently skipped if Claude Code is not detected.

handmux hooks install     # wire Claude Code → handmux (inbox · push)
handmux hooks uninstall   # remove the hooks

You usually don't need this. The first time you open the inbox on your phone, it wires these hooks with one tap. Run this only to install them from the computer instead.

After installing, open a new Claude Code session (or restart one) to activate. See Claude Code for details.

handmux serviceinstall | uninstall

Register handmux as a login item so it starts at boot.

  • macOS: launchd plist in ~/Library/LaunchAgents/
  • Linux: systemd user unit in ~/.config/systemd/user/
handmux service install                        # bake current config into a login item
handmux service install --tunnel cloudflare    # override tunnel for the autostart
handmux service uninstall                      # remove the login item

Note: the service bakes the config at install time. After changing ~/.handmux/config.json, re-run handmux service install to update it.

Flags

Flags are one-run overrides — never persisted. Resolution order: flag > config file > env var > built-in default.

General

FlagDescriptionDefault
--tunnelnone · cloudflare · cloudflare-named · sshnone
--portServer listen port19999
--hostServer bind address0.0.0.0
--tokenAuth token (random 8-char token generated if unset)generated
--nameApp name in browser tab and home-screen iconhandmux
--public-urlOverride the URL printed and embedded in the QR. Works with any tunnel including nonefrom tunnel
--preview-domainWildcard subdomain for dynamic site previewsoff
--foreground, -fStay in foreground; Ctrl-C to stop
--no-qrSkip the QR code
--configUse this JSON file instead of ~/.handmux/config.json

SSH tunnel flags only with --tunnel ssh

FlagDescriptionDefault
--ssh-hostuser@host or user@host:port — required for ssh tunnel
--remote-portPort to bind on the SSH host for the reverse forward= --port
--ssh-jumpBastion: user@jump[,user@jump2,…]

Cloudflare named tunnel flags only with --tunnel cloudflare-named

FlagDescriptionDefault
--cf-hostnamePublic hostname, e.g. handmux.example.com — required
--cf-tunnel-nameTunnel name in your Cloudflare dashboardhandmux

Config file

handmux reads ~/.handmux/config.json on every start. Created and maintained by handmux setup; you can also edit it directly. Pass --config PATH to use a different file.

// ~/.handmux/config.json (written by `handmux setup`)
{
  "tunnel":        "cloudflare",     // "none"|"cloudflare"|"cloudflare-named"|"ssh"
  "port":          19999,            // server port
  "host":          "0.0.0.0",        // bind address
  "name":          "My Box",         // browser tab title (null = default)
  "token":         "your-secret",    // omit to regenerate on every start
  "publicUrl":     "https://…",      // override printed URL (optional)

  // SSH tunnel — only read when tunnel = "ssh"
  "sshHost":       "user@vps.example.com",
  "remotePort":    19999,
  "sshJump":       null,

  // Cloudflare named tunnel — only read when tunnel = "cloudflare-named"
  "cfHostname":    "handmux.example.com",
  "cfTunnelName":  "handmux",

  // Web Push — VAPID keys added by `handmux setup` when you enable notifications
  "vapid": { "public": "…", "private": "…", "subject": "mailto:you@example.com" },

  // Voice input — Xfyun credentials added by `handmux setup`
  "xfyun": { "appId": "…", "apiKey": "…", "apiSecret": "…" }
}

Tunnels

Four modes for making the server reachable. Pick one per run with --tunnel, or set it permanently with handmux setup.

--tunnel none (default)

No exposure — reachable only on your LAN. Prints a LAN URL (http://192.168.x.x:19999/…) and local URL. Ideal when your phone and computer share the same WiFi. If your machine already has a public IP or domain, none is all you need — add --public-url to advertise that address, no tunnel required.

--tunnel cloudflare zero-config

One outbound connection to Cloudflare's edge gives you a random public https://xxx.trycloudflare.com URL. No account, no port forwarding, no TLS setup. cloudflared is auto-downloaded to ~/.handmux/ on first use.

handmux start --tunnel cloudflare

Note: the hostname is random and changes every restart — and Cloudflare's public edge can be unstable or unreliable in some regions (e.g. mainland China). For a stable URL, or reliable access there, use cloudflare-named or ssh.

--tunnel cloudflare-named stable domain

A permanent Cloudflare Tunnel on your own domain, e.g. https://handmux.example.com. Requires a Cloudflare account and a domain on Cloudflare DNS.

Run handmux setup and pick cloudflare-named — it walks you through creating the tunnel, setting the DNS CNAME, and saving credentials.

handmux start --tunnel cloudflare-named --cf-hostname handmux.example.com

--tunnel ssh your server

Reverse SSH tunnel to your own VPS via tunlite. Stable, fully under your control, and works where Cloudflare is unreliable (e.g. mainland China).

Requires passwordless SSH to the target. If not configured, handmux start offers to set it up interactively, or run tunlite setup-key user@host yourself.

handmux start --tunnel ssh --ssh-host user@vps.example.com

# with a custom remote port + explicit public URL:
handmux start --tunnel ssh --ssh-host user@vps.example.com \
  --remote-port 9000 --public-url https://vps.example.com:9000

# through a bastion / jump host:
handmux start --tunnel ssh --ssh-host user@inner \
  --ssh-jump user@bastion.example.com

Environment variables

Lower-priority alternative to flags — useful in scripts or containers. Sit below flags and the config file in the resolution order.

VariableDescriptionFlag
HANDMUX_PORTServer port--port
HANDMUX_HOSTBind address--host
HANDMUX_TOKENAuth token--token
HANDMUX_APP_NAMEApp name--name
HANDMUX_PUBLIC_URLOverride advertised URL--public-url
HANDMUX_PREVIEW_DOMAINWildcard subdomain for site previews--preview-domain
HANDMUX_SSH_HOSTSSH tunnel target--ssh-host
HANDMUX_REMOTE_PORTPort on the SSH host--remote-port
HANDMUX_SSH_JUMPSSH bastion--ssh-jump
HANDMUX_CF_HOSTNAMECloudflare named tunnel hostname--cf-hostname
HANDMUX_CF_TUNNEL_NAMECloudflare tunnel name--cf-tunnel-name

Claude Code

handmux drives any agent or TUI — and with Claude Code it goes furthest. Lifecycle hooks report each pane's state (working · needs you · done) to handmux in real time, powering the inbox and push notifications.

Setup

# during handmux setup (recommended — wizard prompts you)
handmux setup

# or on its own:
handmux hooks install

After installing, open a new Claude Code session — hooks load from ~/.claude/settings.json and an existing session only picks them up after a restart.

Inbox states

Once hooks are active, every Claude pane is tagged:

  • working — Claude is running
  • needs you — permission prompt or plan approval waiting; push sent to your phone
  • done — task finished; push sent

To receive pushes, configure Web Push credentials via handmux setup (generates VAPID keys). On the phone, tap the notification bell in the app to subscribe the device.