Skip to content

Feature: Declarative workspace management via +split and +close CLI actions #12556

Description

@dzearing

Problem

There's no cross-platform way to programmatically create and manage terminal workspaces in Ghostty. Users who want to script terminal layouts — opening windows, creating splits, running commands in each pane — have to resort to fragile workarounds like AppleScript keystroke simulation (see #12136) or external tools like tmux.

The existing +new-window action works on Linux via D-Bus but is not supported on macOS. Even on Linux, there's no CLI support for creating splits, targeting specific windows, or tearing down named panes.

Use Cases

AI agent integration: Tools like Claude Code need to open and manage terminal panes programmatically — e.g., opening an editor in one pane, a dev server in another, and a test runner in a third. Today this requires AppleScript hacks or running inside tmux. Native Ghostty commands would make this seamless.

Project startup scripts: Developers frequently set up the same terminal layout for each project. A simple shell script should be able to declare "I want a window with vim on the left, a server on the right, and tests on the bottom" without depending on tmux or a terminal multiplexer.

Idempotent workspace setup: Scripts should be re-runnable. Running the setup script a second time should focus the existing panes, not create duplicates. This enables workflows where the script can be bound to a hotkey or run from a launcher.

Window manager integration: Users with tiling window managers or automation tools should be able to open and close Ghostty panes from external scripts without accessibility permissions or keystroke simulation.

Proposal

Three new CLI capabilities, all cross-platform:

1. Named windows with --target

# Create a named window (or focus it if it already exists)
ghostty +new-window --target=myproject --working-directory=~/project --command=vim

2. +split action with named panes

# Add a named split to a specific window
ghostty +split --target=myproject --name=server --direction=right --command="npm run dev"
ghostty +split --target=myproject --name=tests --direction=down --command="npm test"

# If the pane already exists, focus it instead of creating a duplicate
ghostty +split --target=myproject --name=server --direction=right

3. +close action for teardown

# Close a specific pane
ghostty +close --target=server

# Close an entire window
ghostty +close --target=myproject

# Idempotent — closing something that's already gone is a no-op
ghostty +close --target=myproject

Full re-runnable workspace script

#!/bin/bash
ghostty +new-window --target=dev --working-directory=~/project --command=vim
ghostty +split --target=dev --name=server --direction=right --command="npm run dev"
ghostty +split --target=dev --name=tests --direction=down --command="npm test"

Run it once to set up. Run it again and it just focuses the existing panes. +close --target=dev tears everything down.

Design Considerations

  • Cross-platform: The CLI actions and IPC types should be platform-agnostic. Only the transport layer differs (D-Bus on GTK/Linux, Unix domain sockets on macOS).
  • Idempotent by default: Named targets (windows and panes) are registered in a lightweight registry with weak references. If a window or pane is closed manually, its entry is automatically cleaned up.
  • Split creation bypasses ghostty_surface_split(): The C export only accepts a direction — it can't pass a custom command or working directory. Instead, the handler posts Notification.ghosttyNewSplit (macOS) directly with a SurfaceConfiguration, which is the same mechanism the app uses internally.
  • Shared namespace: Window names and pane names share one flat namespace. A name refers to exactly one thing.
  • Protocol: Length-prefixed JSON over Unix domain sockets (macOS) or D-Bus actions (Linux). Simple, debuggable, no new dependencies.

Proof of Concept

I have a working implementation on macOS covering all three actions with idempotent behavior. Branch: dzearing/ghostty:macos-ipc

Files changed:

  • src/apprt/ipc.zigNewWindow, Split, Close action types
  • src/cli/split.zig, src/cli/close.zig — new CLI actions
  • src/apprt/embedded.zig — Unix socket IPC client
  • macos/Sources/Features/IPC/IPCServer.swift — socket server, target registry, action handlers
  • macos/Sources/Features/IPC/IPCMessage.swift — protocol types

The GTK/Linux side would need equivalent split and close handlers via D-Bus, following the existing new-window D-Bus pattern in src/apprt/gtk/ipc/.

What I'm Looking For

Feedback on whether this direction aligns with Ghostty's vision before submitting a PR. Specifically:

  1. Are +split and +close as top-level CLI actions the right abstraction?
  2. Is the --target/--name naming approach reasonable, or would you prefer a different targeting mechanism?
  3. Any concerns about the Unix socket transport on macOS vs. extending D-Bus on Linux?
  4. Should the idempotent focus-if-exists behavior be opt-in (e.g., --reuse) rather than default?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions