# How to Script Terminal Splits and Panes Using cmux JSON API

> Automate terminal pane creation and layout splits programmatically using the cmux JSON API. Script splits and panes with surface.split and pane.create methods.

- Repository: [manaflow-ai/cmux](https://github.com/manaflow-ai/cmux)
- Tags: how-to-guide
- Published: 2026-03-29

---

**The cmux terminal multiplexer exposes a version-2 JSON-RPC API over a local Unix socket, enabling you to automate pane creation and layout splits programmatically using the `surface.split` and `pane.create` methods.**

The `manaflow-ai/cmux` repository provides a powerful JSON API for terminal automation that allows precise control over window layouts and pane management. By leveraging the JSON-RPC interface exposed via local socket, developers can script terminal splits and panes using cmux JSON API to build complex, automated workspace configurations without manual interaction.

## Understanding the cmux JSON-RPC Architecture

The cmux API follows a standard JSON-RPC protocol routed through `TerminalController.handleV2Call`, which dispatches requests based on the `"method"` field. When scripting terminal splits, you interact with two primary entry points that interface with the underlying Bonsplit layout engine.

### Core API Methods for Layout Control

Two methods handle automated pane creation:

- **`surface.split`** (implemented in [`v2SurfaceSplit`](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift#L4595-L4660)): Creates a new split from an existing surface, accepting parameters for direction, workspace, surface ID, and focus behavior.

- **`pane.create`** (implemented in [`v2PaneCreate`](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift#L5977-L6030)): A higher-level wrapper that returns a dedicated pane identifier, handling orientation logic and insert positioning before calling the same Bonsplit APIs.

Both methods accept a JSON payload specifying `direction` (`left`, `right`, `up`, `down`), optional `workspace_id` and `surface_id`, and a `focus` boolean. The server validates the request through `parseSplitDirection` ([source](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift#L12995-L13008)), which accepts full names or single-letter abbreviations (`l`, `r`, `u`, `d`).

### Request Routing and Context Resolution

When a request arrives at the socket listener, the controller performs the following sequence:

1. Resolves the **TabManager** for the current UI session.
2. Validates the split direction against the `SplitDirection` enum.
3. Derives target workspace and surface from the request parameters or falls back to currently focused contexts.
4. Invokes `tabManager.newSplit(tabId:surfaceId:direction:focus:)` to create the panel and update the Bonsplit tree.

The method returns a rich JSON response containing:

```json
{
  "window_id": "...",
  "workspace_id": "...",
  "pane_id": "...",
  "surface_id": "...",
  "type": "terminal|browser"
}

```

All identifiers are exposed as both raw UUIDs and stable reference strings (`pane_ref`, `surface_ref`) used by the CLI.

## Scripting Terminal Splits: Practical Implementations

The cmux CLI is a thin wrapper around this API. You can script splits using the CLI's JSON mode, direct socket calls, or the bundled Swift client library.

### Method 1: CLI One-Liners with JSON Output

For quick automation, use the `new-split` command with `--json` flag to parse the response:

```bash

# Split the currently focused pane to the right and capture the output

cmux new-split right --json | jq .

```

Example response:

```json
{
  "window_id": "c7a1...",
  "workspace_id": "b9f2...",
  "pane_id": "d4e3...",
  "surface_id": "d4e3...",
  "type": "terminal"
}

```

Chain commands to focus the new pane using its reference:

```bash
NEW_PANE=$(cmux new-split right --json | jq -r .pane_ref)
cmux focus-pane --pane "$NEW_PANE"

```

### Method 2: Bash Scripting with pane.create

For complex workflows, construct JSON payloads manually and use the `pane.create` method. This example creates a browser split to the left of the current surface:

```bash
#!/usr/bin/env bash
set -euo pipefail

DIR="left"
URL="https://developer.apple.com"

# Build the JSON payload

payload=$(cat <<EOF
{
  "direction": "$DIR",
  "type": "browser",
  "url": "$URL"
}
EOF
)

# Send the request via the built-in client library

response=$(cmux --json pane.create "$(printf '%s' "$payload")")
echo "Created pane:"
echo "$response" | jq .

# Focus the newly created pane using its reference

pane_ref=$(echo "$response" | jq -r .pane_ref)
cmux focus-pane --pane "$pane_ref"

```

This approach leverages the higher-level `pane.create` endpoint which automatically determines orientation (horizontal vs vertical) based on direction and manages the `insertFirst` logic for positioning.

### Method 3: Swift Client Integration

For native macOS automation, use the bundled `V2Client` library to communicate directly with the socket at `$CMUX_SOCKET`:

```swift
import Foundation
import cmux

let client = V2Client()  // Connects to $CMUX_SOCKET automatically
let params: [String: Any] = [
    "direction": "down",
    "type": "terminal"
]

do {
    let response = try client.sendV2(method: "surface.split", params: params)
    print("Split created →", response)
} catch {
    print("Failed:", error)
}

```

Compile this using the repository's Swift package manager (`swift build -c release`) to create a binary that communicates natively with the running cmux process.

## Key Source Files and Implementation Details

Understanding the source implementation helps debug automation scripts:

| File | Purpose | Key Functions |
|------|---------|---------------|
| [`Sources/TerminalController.swift`](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift) | Core JSON-RPC handlers | `v2SurfaceSplit` ([L4595-L4660](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift#L4595-L4660)), `v2PaneCreate` ([L5977-L6030](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift#L5977-L6030)), `parseSplitDirection` ([L12995-L13008](https://github.com/manaflow-ai/cmux/blob/main/Sources/TerminalController.swift#L12995-L13008)) |
| [`CLI/cmux.swift`](https://github.com/manaflow-ai/cmux/blob/main/CLI/cmux.swift) | CLI argument parsing and payload construction | New-split handling ([L1718-L1734](https://github.com/manaflow-ai/cmux/blob/main/CLI/cmux.swift#L1718-L1734)) |
| [`docs/v2-api-migration.md`](https://github.com/manaflow-ai/cmux/blob/main/docs/v2-api-migration.md) | API versioning documentation | Migration from legacy split commands to `surface.split` |
| [`docs/remote-daemon-spec.md`](https://github.com/manaflow-ai/cmux/blob/main/docs/remote-daemon-spec.md) | Socket protocol specification | JSON-RPC format and authentication |

## Summary

- The **`surface.split`** and **`pane.create`** JSON-RPC methods are the primary endpoints for scripting terminal layouts in cmux.
- Both methods accept `direction` (left/right/up/down), optional `workspace_id`/`surface_id`, `type` (terminal/browser), and `focus` parameters.
- The API returns comprehensive identifiers including `pane_id`, `surface_id`, `workspace_id`, and human-readable `*_ref` strings for subsequent CLI commands.
- You can automate splits via the `cmux` CLI with `--json` output, direct socket communication using the Swift `V2Client`, or any language capable of writing to the Unix domain socket at `$CMUX_SOCKET`.

## Frequently Asked Questions

### What is the difference between `surface.split` and `pane.create`?

The `surface.split` method creates a new split from an existing surface and returns surface and workspace identifiers, while `pane.create` is a higher-level wrapper that additionally determines orientation (horizontal vs vertical), manages insert positioning, and returns a dedicated `pane_id` suitable for pane-specific operations like focusing or closing.

### How do I specify split directions in the cmux JSON API?

The API accepts direction strings (`left`, `right`, `up`, `down`) or single-letter abbreviations (`l`, `r`, `u`, `d`) in the request payload. The `parseSplitDirection` function in [`TerminalController.swift`](https://github.com/manaflow-ai/cmux/blob/main/TerminalController.swift) validates these against the `SplitDirection` enum before executing the split on the Bonsplit layout engine.

### Can I create browser panes using the JSON API?

Yes, include `"type": "browser"` in your JSON payload when calling `surface.split` or `pane.create`, and provide a `"url"` field to specify the initial webpage. The server will instantiate a browser surface instead of a terminal surface within the split layout.

### How do I focus a newly created pane programmatically?

Extract the `pane_ref` or `pane_id` from the JSON response and pass it to the `focus-pane` CLI command: `cmux focus-pane --pane "$PANE_REF"`. When creating the split, you can also set `"focus": true` in the initial request to automatically shift focus to the new pane upon creation.