Seahelm macOS 14+ · Swift + AppKit · 247 files

A native macOS workspace for coding agents

One window for every agent you have running.

A repo is a tab. A git worktree is a card. A split pane is one agent. Seahelm renders every one of them on the Ghostty engine, watches what they are doing, and keeps their sessions alive across a reboot. Twelve agent CLIs work out of the box.

openbeta/seahelm · 3 worktrees · 5 panes 1 needs input 2 running
mainidle
claude
Idle · 4m
codex
Idle · 22m
feat/island-usagerunning
claude
Edit · UsageSummaryFormatter.swift
codex
Bash · xcodebuild test
fix/zmx-recoveryneeds input
claude
Allow rm -rf .build?

Example fleet. Every status above is one the app really reports.

$ curl -fsSL https://raw.githubusercontent.com/BetaYao/seahelm/main/scripts/install.sh | sh
github.com/BetaYao/seahelm

Forty-seven seconds of it working

A real session, not a mock-up: worktrees down the sidebar, an agent working in one of them, and a suggestion card asking which way to go.

Deck one · on deck

What you steer

Seahelm has no editor and no diff-review ceremony. The whole surface is about placing agents — which repo, which worktree, which pane — and then showing you which of them needs a human.

APP PROJECT WORKTREE PANE seahelm — one window, one keyboard openbeta/seahelm main claude codex feat/island-usage claude codex acme/api fix/auth-retry claude — needs input main aider
Four tiers, one source of truth. A project is a git repo you added; its worktrees come from git worktree list --porcelain; a pane is a leaf of that worktree's split tree, and holds exactly one agent. AgentRegistry.shared knows the status of every leaf in the picture.
Splits & layouts
A binary split tree per worktree, dragged by dividers or driven from the keyboard. Four dashboard layouts — grid, left/right, top-small, top-large — and the whole tree serializes to config, so relaunch restores it.
Modal keyboard
NORMAL and INSERT, with Space as a which-key leader. h j k l moves focus, Cmd+P is the quick switcher, Cmd+N opens a new branch.
Side panel
File tree, code editor, Markdown preview, and git diff review — without leaving the worktree or interrupting the agent in it.
Token usage
Claude and Codex session logs parsed into token and quota figures, rotating one readout at a time in the status pill.
Remote helm
Message the fleet from your phone. Commands arrive over iMessage prefixed sea ; replies go back stamped helm , which is also what stops the bridge from answering itself.

The Island

A pill at the top of the screen that stays shut until a worktree needs you. It opens for one of three things: something is running, something is waiting, something broke. Agent suggestions arrive in it as clickable cards.

First Mate

An autonomous supervisor on status transitions. Green-zone actions it just takes — watch, inspect, auto-commit. Red-zone actions — broadcast an order, return a worktree to port — queue up for your approval first.

BEFORE AFTER main claude codex feat/x — just created empty placeholder hook cwd moveLeaf main codex feat/x claude Station + zmx session kept placeholder destroyed
The pane follows its agent. Claude Code makes a worktree and works in it; every hook payload carries cwd, so Seahelm matches the longest worktree prefix and moves that one pane — keeping its Ghostty surface and its zmx session — instead of standing up a stranger beside it. Two gates keep it honest: the destination must belong to the same repo, and after any move auto-follow holds for ten minutes, because an agent's cwd bounces while it works.
Twelve agents, three depths of integration
AgentStatusEvent hooksSuggestion cards
Claude Code✓ manifest + hooks✓ native
Codex✓ manifest + hooks✓ native
opencode✓ manifest + hooks✓ pluginmodel-volunteered
Nine more✓ screen scan

agent · aider · amp · claude · cline · codex · cursor · gemini · goose · kiro · opencode · pi

Deck two · below deck

What runs it

247 Swift files in four layers, over the Ghostty terminal engine. No Combine, no SwiftUI — AppKit and delegates, because the thing on screen is a Metal surface with a PTY behind it.

LayerPathFilesWhat lives there
CoordinatorsSources/App/15 Window, tabs, split operations, side panels, the modal keyboard machine.
UISources/UI/75 Dashboard layouts, the Island, split container, worktree sidebar, diff viewer, settings, onboarding.
Core servicesSources/Core/ · Status/133 AgentRegistry, the detection pipeline, the manifest engine, First Mate, the control socket, hook installers.
Terminal & gitSources/Terminal/ · Git/19 The Ghostty C API bridge, Station surface lifecycle, the split tree, worktree discovery.
SOURCE BRIDGE INTERPRET DECODE REDUCE CONSUME Claude Code Codex · opencode seahelm-hook socket ▸ webhook hook events SessionStart · Stop Pre/PostToolUse HookDecoder exec JSON map any of 12 CLIs no integration Ghostty screen text under a lock StatusDetector exit ▸ OSC 133 ▸ text patterns ScanDecoder 12 JSON manifests rules renders poll verdict per event per poll NormalizedEvent PaneReducer · pure AgentRegistry source of truth delta publishes to all three Island · dashboard status bar First Mate green ▸ red zone EventHub CLI subscribers
Two lanes, one event shape. Agents that report hooks take the fast lane; the other nine are read off the screen every two seconds and matched against a priority-ordered manifest — process exit beats an OSC 133 shell phase, which beats a text pattern. Both lanes decode into the same NormalizedEvent, so the reducer stays a pure function and the UI never learns which lane a status came from. Ask any pane which rule decided it: seahelm pane explain <pane>.
Runninga tool call is in flight
Needs inputblocked on a human
Idleprompt is back, waiting
Errorthe agent broke
Dormantprocess exited

The same five states drive the dot, the card, the Island and the rollup. There is no sixth.

an agent, in a pane SEAHELM_PANE_ID seahelm CLI ~/.local/bin/seahelm ControlSocketServer 0600 unix socket EventHub bounded ring buffer pane.split pane.run wait.agent_status runs JSON-RPC mutates panes events an agent can drive the app that is watching it
The loop closes. Seahelm watches the agent; the agent, holding $SEAHELM_PANE_ID, can call back and split a pane, run a command in a sibling, read its scrollback, or block until another pane goes idle.

The surface an agent gets

A python3 wrapper on PATH, newline-delimited JSON-RPC over a 0600 socket. Nothing to authenticate to, nothing on the network.

$ seahelm pane list
$ seahelm pane read <pane> --lines 50
$ seahelm pane split <pane> --direction right
$ seahelm pane run <pane> "npm test"
$ seahelm wait agent-status <pane> --status Idle
$ seahelm pane explain <pane>   # which rule?
$ seahelm layout export

Hook shims install non-destructively into each tool's own config — and stay out of the way. CwdChanged is deliberately not registered: taking it made Claude Code hand worktree creation to us and broke --worktree.

seahelm.app window · SplitTree · Station · Metal surface GONE ON QUIT you quit the app · the machine reboots zmx session  seahelm-<repo>-<worktree> PTY · shell · the agent process, still thinking SURVIVES git worktree + branch on disk SURVIVES re-attach by name
Sessions outlive the app. Each split leaf claims a zmx session named for its worktree; a health check three seconds after creation catches a stale one and rebuilds it. Quit, reboot, reopen — the agent is where you left it, mid-thought.

Two threads, one terminal engine

The status poll runs on a background queue while you type on the main one, and both call into the same C library. A single lock serializes them — except key input, which deliberately takes no lock: Ghostty is thread-safe for keys, and holding it there would deadlock on a synchronous callback.

The poll is not uniform either. The worktree you are looking at is read every cycle; the rest every third, so a fleet of twenty panes costs about what three do.

Built on

Ghostty for the terminal engine, linked as a C xcframework — zmx for session persistence — Sparkle 2 for updates, rendered into an inline banner instead of its own modals.