Soprano

Native macOS · Swift 6 + libghostty · MIT

Soprano

A tiling terminal multiplexer for the room full of coding agents you're already running.

Codex, Claude Code, and OpenCode side by side in real GPU-rendered terminals. Every pane says what its agent is doing, and macOS tells you which one is waiting on you.

The problem

One agent is easy. Four is a scheduling problem.

They run asynchronously, they all stop to ask you something eventually, and terminal tabs hide the one that is blocked. So you cycle through tabs looking for the agent that went quiet for the wrong reason.

Soprano is built around the question you actually have all day: which agent needs me right now? Panes answer it themselves — the launchers install lifecycle hooks per launch, so nothing scrapes output and nothing touches your global agent config.

  • Starting

    The launcher is up, the agent has not reported in yet.

  • Working

    A turn is in flight. Leave it alone.

  • Ready

    Idle and waiting for a prompt.

  • Needs input

    A question or a permission prompt. This is the one you came for.

  • Error

    The turn failed. The pane keeps the state until you look.

  • Stopped

    The process exited. The layout stays where you left it.

Attention

It tells you where to look, not just that something happened.

Notifications only fire for panes that are not focused, and they are subtitled window ▸ pane so the banner names the location. Clicking one activates Soprano and focuses that exact tab. A pane's banners group together instead of stacking up.

Click it and Soprano focuses that exact tab.window ▸ pane

Agent Dashboard

Monitoring 4 agents across 2 windows

⇧⌘D
2Working
1Needs input
0Error

soprano▸Codex

editing SplitNode.swift

Working

soprano▸Claude Code

Drop the legacy column? [y/n]

Needs input

webapp▸OpenCode

writing tests

Working

webapp▸Terminal

252 tests, 0 failures

Ready
JKNavigate⇥Reply↵Open

Live agent states across every logical window. Navigate with J/K, open with Return.

The layout

Every pane is a node. Every split is a decision.

Panes live in a recursive split tree, so a window is never a fixed grid you have to fight. Then there is depth: any pane can open a private workspace on the window's z-axis, so one pane becomes a whole layout while its siblings keep running behind it.

One pane becomes a layout — and every layout comes back on the next launch.

What it does

A real terminal, arranged the way you think.

libghostty surfaces

Every pane is a real GPU-rendered terminal hosted in AppKit — full keyboard routing, sided modifiers, a clipboard confirmation policy for paste. No Electron, no web view, no reimplemented VT parser.

Agent launchers ⌘1⌘2⌘3

Codex, Claude Code, and OpenCode start with lifecycle hooks scoped to that launch. Your global agent configuration is never modified.

Custom agents

Define your own launcher under agents — command, args, env, cwd or a launchScript, colour, and its own launch key. It shows up in the sidebar and settings like a built-in.

Browser panes ⌘B

A WebKit pane with an address bar and history controls. Agents drive it from any terminal: "$SOPRANO_BIN" browser click @e1, with snapshots that assign element refs.

Copy mode ⌃A[

Vim motions over the scrollback — viewport and buffer jumps, character and line selection, clipboard yank.

Command palette ⌘P

Every action in one place, plus a project launcher and search over your configured project directories on ⇧⌘P.

Window switcher ⌘F

Searches window titles and nested pane details at once, prioritising window-title matches.

Sessions ⇧⌘S

The last workspace restores on launch. Save named sessions and toggle back to the previous one with ⌃A⇧L.

Drag and drop

Files, folders, URLs, and macOS screenshot thumbnails dropped on a terminal become shell-safe paths at the cursor.

Sidebar

Every pane with its git branch, a spawn menu, a sessions footer, and jump hints that appear while ⌃ is held.

tmux handoff

Pane navigation passes through to tmux at layout boundaries, scoped to the issuing process, pane, and tab — so nesting behaves.

settings.json

One commented file at ~/.config/soprano/settings.json. Rebind any action by id, or set it to null to turn it off. Saving applies immediately — no restart.

Keymap

defaults · prefix ⌃A
⌘1⌘2⌘3Launch Codex, Claude Code, or OpenCode in the active pane
⌃A|⌃A-Split the active pane vertically or horizontally
⌃H⌃J⌃K⌃LMove between panes — no prefix, wraps at layout boundaries
⌃AH/J/K/LResize the active pane by the configured step
⌃AMMaximize the active pane, again to restore
⌃AI⌃AODive into a pane's private workspace, and come back out
⌃1…⌃9Select a pane tab by number
⌃A⇧PJump to the most recently active window, again to toggle back
⌘=⌘-⌘0Terminal zoom in, out, and reset
⇧⌘DAgent dashboard across every window

Every binding is rebindable by action id in settings.json.

The family

Four apps on the same paper.

Soprano, Soprano Git, Soprano Cast and Soprano Tab share one design: paper and ink, a serif for titles, and one pencil mark where your keyboard is.

Themes

Folio and seven more ship with it. Try them on this page.

These are the real palettes from Theme.swift, applied live to this page. Themes recolour open terminals — background, ANSI palette, pane headers — without a restart. Folio is the default: Paper in light mode, Ink in dark.

Your choice is remembered while you browse. It does not change how Soprano is configured.

Install

Build it from source.

Soprano links libghostty statically, so the first build compiles it once. After that you only repeat it when the ghostty submodule moves.

Fresh clone to installed app
# 1 · Toolchain. Homebrew Swift is required — the system CLT Swift has broken SPM.
$ brew install swift zig
$ xcodebuild -downloadComponent MetalToolchain

# 2 · Clone with the ghostty submodule
$ git clone --recurse-submodules https://github.com/frknue/soprano.git
$ cd soprano

# 3 · Build libghostty once (a few minutes)
$ ./scripts/build-ghostty.sh

# 4 · Build the release app into /Applications
$ ./install.sh 

Requirements: macOS 14 Sonoma or newer · full Xcode with the Metal Toolchain (Command Line Tools alone cannot build libghostty) · Homebrew Swift 6.2+ · Zig 0.15.2+.