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:

  1. Client connects to the socket path defined by SocketControlSettings.stableDefaultSocketPath (typically /tmp/cmux.sock)
  2. TerminalController parses the command via withSocketCommandPolicy (lines 2990‑3014) to determine if focus changes are permitted
  3. TabManager (in Sources/TabManager.swift) executes the actual workspace operations like addWorkspace, selectWorkspace, and closeWorkspace
  4. Response returns as a newline‑terminated string starting with OK or ERROR

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) checks socketCommandPolicyDepth before allowing focus changes
  • Authentication: The socket may require a password (auth <pwd>) unless the mode is set to allowAll in SocketControlSettings.swift
  • Tagged instances: When running debug builds with tags, each instance uses an isolated socket at /tmp/cmux-debug-<tag>.sock. Set the CMUX_SOCKET environment variable to target specific instances

Summary

  • Unix socket location: Default path is /tmp/cmux.sock (configurable via SocketControlSettings)
  • Command protocol: Newline‑terminated v1 strings (workspace.create, workspace.select, etc.) with OK/ERROR responses
  • Python automation: Use tests_v2/cmux.py for type‑safe wrapper methods like new_workspace() and select_workspace()
  • Focus safety: The controller enforces socketCommandPolicyDepth to prevent unauthorized focus changes from CLI commands
  • Direct access: Any language capable of Unix sockets (Bash, Go, Rust) can send raw commands via nc or 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →