* Add claude-teams, omo, and __tmux-compat to Go relay CLI These commands previously only existed in the Swift CLI which uses Unix domain sockets and can't connect over TCP relay. The Go relay CLI already handles TCP connections, so adding the commands here makes them work inside `cmux ssh` sessions. - `cmux claude-teams`: creates tmux shim scripts, configures environment (fake TMUX/TMUX_PANE, socket path, workspace/surface IDs), and execs into `claude --teammate-mode auto` - `cmux omo`: same pattern for OpenCode with terminal-notifier shim - `cmux __tmux-compat`: translates tmux commands (split-window, send-keys, capture-pane, display-message, list-panes, etc.) into cmux JSON-RPC calls over the relay socket. Includes main-vertical layout tracking, wait-for signaling, and format string rendering. * Fix: search original PATH before environment modification findExecutable was called after configureAgentEnvironment prepended the shim directory to PATH. The Swift CLI searches the original PATH before modification. Renamed to findExecutableInPath with explicit PATH arg and moved the search before configureAgentEnvironment. * Fix cmux omo hang and port oh-my-opencode plugin setup Root cause: socketRoundTripV2 had no read timeout. When connecting to a stale relay port (accepted TCP but never responded), the read blocked forever. This caused getFocusedContext to hang, blocking agent launch. Fixes: - Add 15s read deadline to socketRoundTripV2 (affects all v2 RPC calls) - Add 5s timeout to getFocusedContext so agent launch proceeds even if system.identify is slow - Port omoEnsurePlugin from Swift: creates shadow config dir, adds oh-my-opencode to plugin list, symlinks node_modules/package.json, installs plugin via bun/npm if missing, configures tmux settings (enabled=true, lower min widths), sets OPENCODE_CONFIG_DIR * Fix: use bun as runtime for node-script opencode when node is missing opencode is installed via bun as a #!/usr/bin/env node script, but on some systems (like the macmini) bun is installed without a standalone node binary. Detect node scripts and fall back to bun as the runtime since bun is node-compatible. * Fix subagent pane theme: preserve COLORTERM, keep TERM_PROGRAM The cmux ssh bootstrap exports COLORTERM=truecolor and TERM_PROGRAM=ghostty. Our configureAgentEnvironment was unsetting TERM_PROGRAM and not setting COLORTERM, causing subagent panes (created via split-window) to lose truecolor detection and render with wrong theme colors. * Restore TERM_PROGRAM unset, keep COLORTERM=truecolor * Force dark colorScheme in opencode shadow config for SSH * Remove hardcoded dark colorScheme, let opencode detect naturally * Detect system color scheme for opencode over SSH * Remove color scheme detection workaround, let opencode handle natively --------- Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com> |
||
|---|---|---|
| .. | ||
| cmd/cmuxd-remote | ||
| go.mod | ||
| README.md | ||
cmuxd-remote (Go)
Go remote daemon for cmux ssh bootstrap, capability negotiation, and remote proxy RPC. It is not in the terminal keystroke hot path.
Commands
cmuxd-remote versioncmuxd-remote serve --stdiocmuxd-remote cli <command> [args...]— relay cmux commands to the local app over the reverse SSH forward
When invoked as cmux (via wrapper/symlink installed during bootstrap), the binary auto-dispatches to the cli subcommand. This is busybox-style argv[0] detection.
RPC methods (newline-delimited JSON over stdio)
hellopingproxy.openproxy.closeproxy.writeproxy.stream.subscribe- async
proxy.stream.data/proxy.stream.eof/proxy.stream.errorevents session.opensession.closesession.attachsession.resizesession.detachsession.status
Current integration in cmux:
workspace.remote.configurenow bootstraps this binary over SSH when missing.- Client sends
hellobefore enabling remote proxy transport. - Local workspace proxy broker serves SOCKS5 + HTTP CONNECT and tunnels stream traffic through
proxy.*RPC overserve --stdio, using daemon-pushed stream events instead of polling reads. - Daemon status/capabilities are exposed in
workspace.remote.status -> remote.daemon(includingsession.resize.min).
workspace.remote.configure contract notes:
port/local_proxy_portaccept integer values and numeric strings; explicitnullclears each field.- Out-of-range values and invalid types return
invalid_params. local_proxy_portis an internal deterministic test hook used by bind-conflict regressions.- SSH option precedence checks are case-insensitive; user overrides for
StrictHostKeyCheckingand control-socket keys prevent default injection.
Distribution
Release and nightly builds publish prebuilt cmuxd-remote binaries on GitHub Releases for:
darwin/arm64darwin/amd64linux/arm64linux/amd64
The app embeds a compact manifest in Info.plist with:
- exact release asset URLs
- pinned SHA-256 digests
- release tag and checksums asset URL
Release and nightly apps download and cache the matching binary locally, verify its SHA-256, then upload it to the remote host if needed. Dev builds can opt into a local go build fallback with CMUX_REMOTE_DAEMON_ALLOW_LOCAL_BUILD=1.
To inspect what a given app build trusts, run:
cmux remote-daemon-statuscmux remote-daemon-status --os linux --arch amd64
The command prints the exact release asset URL, expected SHA-256, local cache status, and a copy-pasteable gh attestation verify command for the selected platform.
CLI relay
The cli subcommand (or cmux wrapper/symlink) connects to the local cmux app through an SSH reverse forward and relays commands. It supports both v1 text protocol and v2 JSON-RPC commands.
Socket discovery order:
--socket <path>flagCMUX_SOCKET_PATHenvironment variable~/.cmux/socket_addrfile (written by the app after the reverse relay establishes)
For TCP addresses, the CLI dials once and only refreshes ~/.cmux/socket_addr a single time if the first address was stale. Relay metadata is published only after the reverse forward is ready, so steady-state use does not rely on polling.
Authenticated relay details:
- Each SSH workspace gets its own relay ID and relay token.
- The app runs a local loopback relay server that requires an HMAC-SHA256 challenge-response before forwarding a command to the real local Unix socket.
- The remote shell never gets direct access to the local app socket. It only gets the reverse-forwarded relay port plus
~/.cmux/relay/<port>.auth, which is written with0600permissions and removed when the relay stops.
Integration additions for the relay path:
- Bootstrap installs
~/.cmux/bin/cmuxwrapper and keeps a default daemon target (~/.cmux/bin/cmuxd-remote-current). - A background
ssh -N -Rprocess reverse-forwards a TCP port to the authenticated local relay server. The relay address is written to~/.cmux/socket_addron the remote. - Relay startup writes
~/.cmux/relay/<port>.daemon_pathso the wrapper can route each shell to the correct daemon binary when multiple local cmux instances or versions coexist. - Relay startup writes
~/.cmux/relay/<port>.authwith the relay ID and token needed for HMAC authentication.