How to Use the cmux CLI to Create and Manage Workspaces Programmatically
The cmux CLI exposes a Unix-socket-based control API that allows external scripts and automation tools to create, rename, select, and close workspaces by sending simple string commands to /tmp/cmux.sock.
The cmux terminal multiplexer from manaflow-ai/cmux provides a Unix-socket-based control API that enables programmatic workspace management without UI interaction. By connecting to the stable socket path and sending version-1 (v1) string commands, you can automate complex workspace workflows from Python, Bash, or any language capable of Unix socket communication.
How the cmux Socket API Works
The programmatic interface follows a command-dispatcher architecture centered in Sources/TerminalController.swift. When you send a command to the Unix socket, the TerminalController receives the raw string, splits it into a command key and JSON arguments, and executes it through a socket-command policy that tracks focus-mutation permissions.
According to the source code, the flow works as follows:
- Client connects to the socket path defined by
SocketControlSettings.stableDefaultSocketPath(typically/tmp/cmux.sock) - TerminalController parses the command via
withSocketCommandPolicy(lines 2990‑3014) to determine if focus changes are permitted - TabManager (in
Sources/TabManager.swift) executes the actual workspace operations likeaddWorkspace,selectWorkspace, andcloseWorkspace - Response returns as a newline‑terminated string starting with
OKorERROR
The command dispatcher maps string commands to private methods in TerminalController. For example, at line 1675, the "new_workspace" command maps to newWorkspace(_:) (lines 12178‑12190), which delegates to TabManager.addWorkspace (line 1193).
Creating and Managing Workspaces with the Python Client
The repository includes an official Python client library at tests_v2/cmux.py that wraps the socket protocol. This client translates method calls into v1 string commands and parses JSON responses automatically.
Connecting to the Socket
Import the client and instantiate it to connect to the default socket path:
from tests_v2.cmux import cmux
# Connects to /tmp/cmux.sock by default
c = cmux()
Creating a New Workspace
Call new_workspace() to create a workspace and receive its UUID:
# Returns the UUID of the newly created workspace
ws_id = c.new_workspace()
print("Created workspace:", ws_id)
As implemented in tests_v2/cmux.py (lines 24‑30), this builds a "workspace.create" command string. The controller executes newWorkspace(_:) (lines 12178‑12190) and returns the workspace identifier.
Listing and Selecting Workspaces
Retrieve current workspaces and switch focus programmatically:
# List all workspaces with index, UUID, title, and selection status
for index, ws_uuid, title, selected in c.list_workspaces():
print(f"{index}: {ws_uuid} ({title}) {'← selected' if selected else ''}")
# Select by UUID or index
c.select_workspace(ws_id) # by UUID
c.select_workspace(0) # by index (first workspace)
The select_workspace method sends "workspace.select" to the controller, which triggers selectWorkspace(_:) (line 1691) and calls tabManager.selectWorkspace(_:) (TabManager.swift line 2765).
Renaming and Reordering Workspaces
Modify workspace metadata and positioning without UI interaction:
# Rename current workspace or a specific one
c.rename_workspace("Production Server", ws_id) # specific workspace
c.rename_workspace("Local Dev") # current workspace
# Reorder: move to specific index or before another workspace
c.reorder_workspace(ws_id, index=2)
c.reorder_workspace(ws_id, before_workspace=other_ws_id)
The rename command maps to "workspace.rename" and delegates to TabManager.renameWorkspace(title:workspace:), while reordering triggers TabManager.reorderWorkspace.
Closing Workspaces
Destroy workspaces and their associated surfaces:
c.close_workspace(ws_id) # destroys workspace and its surfaces
This sends "workspace.close" to TerminalController.closeWorkspace(_:), which invokes TabManager.closeWorkspace(workspaceId:).
Complete Automation Example
Combine operations to set up an isolated environment:
from tests_v2.cmux import cmux
c = cmux()
# 1️⃣ Create and name a workspace
ws = c.new_workspace()
c.rename_workspace("CI Build Environment", ws)
# 2️⃣ Focus it
c.select_workspace(ws)
# 3️⃣ Execute your automation here
# (Run commands, open splits, etc.)
# 4️⃣ Clean up
c.close_workspace(ws)
print("Workspace closed successfully")
Raw Socket Communication Without a Library
For lightweight automation, you can communicate directly using standard Unix tools like nc (netcat) or socat. The protocol uses newline‑terminated ASCII commands with optional key=value arguments.
#!/usr/bin/env bash
SOCK="/tmp/cmux.sock" # Use `cmux --print-socket-path` to verify
# Helper function to send commands
cmd() {
printf "%s\n" "$1" | nc -U "$SOCK"
}
# Create workspace and capture UUID
WS_ID=$(cmd "workspace.create" | awk '{print $2}')
echo "Workspace ID: $WS_ID"
# Rename it
cmd "workspace.rename workspace_id=$WS_ID title='Bash Demo'"
# List workspaces
cmd "workspace.list"
Each command returns a single line starting with OK (followed by data) or ERROR (followed by a message). The Python client wraps this protocol to raise cmuxError exceptions on failures.
Understanding Command Permissions and Policies
The cmux socket API implements a focus-mutation policy to prevent accidental UI disruptions. By default, commands in focusIntentV1Commands (such as select_workspace) can change focus, while others cannot unless explicitly permitted.
Key implementation details from the source:
- Policy enforcement:
withSocketCommandPolicy(lines 2990‑3014) checkssocketCommandPolicyDepthbefore allowing focus changes - Authentication: The socket may require a password (
auth <pwd>) unless the mode is set toallowAllinSocketControlSettings.swift - Tagged instances: When running debug builds with tags, each instance uses an isolated socket at
/tmp/cmux-debug-<tag>.sock. Set theCMUX_SOCKETenvironment variable to target specific instances
Summary
- Unix socket location: Default path is
/tmp/cmux.sock(configurable viaSocketControlSettings) - Command protocol: Newline‑terminated v1 strings (
workspace.create,workspace.select, etc.) withOK/ERRORresponses - Python automation: Use
tests_v2/cmux.pyfor type‑safe wrapper methods likenew_workspace()andselect_workspace() - Focus safety: The controller enforces
socketCommandPolicyDepthto prevent unauthorized focus changes from CLI commands - Direct access: Any language capable of Unix sockets (Bash, Go, Rust) can send raw commands via
ncor native socket libraries
Frequently Asked Questions
What socket path does cmux use by default?
By default, cmux creates its control socket at /tmp/cmux.sock as defined in SocketControlSettings.stableDefaultSocketPath. When running tagged debug builds, the path becomes /tmp/cmux-debug-<tag>.sock. Use cmux --print-socket-path to discover the active socket location.
Can I change workspace focus via the CLI API?
Yes, but only through commands explicitly marked as focus‑intents. The select_workspace command (mapped to "workspace.select") is permitted to change focus because it exists in focusIntentV1Commands. Other commands run under socketCommandPolicyDepth restrictions that prevent unexpected focus mutations unless explicitly configured.
How does cmux authenticate socket connections?
Authentication depends on the socket mode configured in the Settings UI under Automation → Socket Control. The default mode may require a password sent as auth <password> before executing commands, or you can set the mode to allowAll to permit unrestricted local access. Check SocketControlSettings.swift for implementation details on access modes.
What programming languages can I use to script cmux?
Any language capable of opening Unix domain sockets can control cmux. The repository provides a reference Python client (tests_v2/cmux.py), but you can implement the same protocol in Bash (using nc or socat), Go, Rust, Node.js, or Ruby. The protocol is simple ASCII: send a command string ending in newline, receive a response line starting with OK or ERROR.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →