README.md
July 2, 2026 · View on GitHub
Vesta
A native macOS terminal for running AI coding agents in parallel —
built on real libghostty, driven by a scriptable CLI.
Download · Website · Docs · ▶ Watch the tour
Vesta is a Swift/AppKit terminal that links GhosttyKit.xcframework (it is not
a Ghostty fork). It renders with Ghostty's Metal engine, reads your existing
~/.config/ghostty/config as-is, and adds a project sidebar, tmux-style splits,
and an agent-control CLI on top.
Highlights
- Real libghostty — Ghostty's Metal renderer, your ghostty config and theme, zero reimplemented terminal logic.
- Persistent sessions (tmux-style) — shells survive Vesta quitting and
reattach cleanly. A small daemon (
vestad) holds the PTYs; panes connect through a relay (vesta-attach). Prefix-key mode for tmux muscle memory. Restore is lazy: at launch only the visible session reattaches; the rest stay listed in the sidebar and attach instantly on first click, so a big saved workspace opens fast. Until a restored session is first activated it won't ring the attention dot (its shell keeps running under the daemon regardless, and pluginpane-outputtaps still work). - Projects → sessions sidebar — vertical, drag-resizable. Each project owns sessions; rename / recolor / remove from the right-click menu.
- Native splits —
⌘D/⌘⇧D, click-to-focus, zoom, drag dividers. - Command palette —
⌘⇧Popens a searchable list of every action (splits, sessions, browser pane, settings…) plus your plugins'vesta.commandentries. - Scriptable — the
vestaCLI drives and reads the live UI over a Unix socket, so agents can orchestrate it. - Notifications —
vesta.notifyfrom a plugin shows a stacking in-app toast, records it in a titlebar bell (history persists across restarts), and posts a macOS Notification Center banner when Vesta is backgrounded (or when forced). - Self-updating — when a newer release exists, a badge appears at the sidebar bottom; click it to download, install (in place), and relaunch — no manual DMG.
- Pick your app icon — Settings ▸ App Icon swaps between a clean white
flame, a pink one, and ten progressively "corrupted" stages (or click the icon
in the About panel to cycle them). The choice is written onto the
.appbundle, so it sticks in Finder/Dock across quits and survives in-place updates. - Everything from your config — colors, fonts, sidebar width, divider width
are all
vesta-*keys in the same ghostty config file. Empty config = sane defaults.
Build & run
No setup needed — swift build fetches the prebuilt GhosttyKit framework
(libghostty) automatically via a checksum-verified release asset.
swift build # auto-fetches GhosttyKit on first build
.build/arm64-apple-macosx/debug/vesta # run the app (dev)
swift run vesta selfcheck # pure-logic checks
./install.sh # copy vesta + vestad + vesta-attach → /usr/local/bin (CLI)
./make-app.sh # build Vesta.app (double-clickable, logo icon)
open Vesta.app # launch the bundle
The raw debug binary is bundle-less and dies if its launching shell exits (use
nohup .build/.../vesta & disown)../make-app.shpackages a properVesta.app— logo dock icon, "Vesta" menu, double-click launch, detached lifetime. The binary is self-contained (ghostty is statically linked).
The vesta CLI
Drives the running app over ~/Library/Application Support/vesta/control.sock.
vesta help is authoritative; the common verbs:
vesta help # list every verb + config key
vesta open <path> # new session at <path>
vesta split -v | -h # split the focused pane (side-by-side / stacked)
vesta new-pane --cwd <path> # new pane in a dir
vesta focus <id> | vesta focus next
vesta zoom # toggle zoom on the focused pane
vesta close # close the focused pane
vesta send-keys <target> <text> # type into a pane + run it (target = pane id or "focused"; --no-enter to skip the Return)
vesta send-keys --all|--session <P.S>|--project <name> <text> # broadcast: focused session's panes / session P.S / all of a project (reply: pane count)
vesta capture # dump the focused pane's screen
vesta pane status <paneID> # JSON for one pane: cwd, title, alive, attention
vesta list # the focused session's panes (+ tab index/count)
vesta tab new|next|prev|close # tab control
vesta sessions [--json] [--project <name>] # list sessions; --json for structured records (id, name, cwd, panes, active/attention; --project implies --json)
vesta kill <id> # end a session's shell (by paneID)
vesta notify [--desktop] [--title <t>] <msg> # toast + bell; desktop banner when backgrounded (--desktop forces)
Multiplexer & sessions
Shells run under a small daemon (vestad), not the app, so they survive Vesta
quitting and reattach cleanly. The daemon owns one forkpty'd shell per
pane and keeps the last ~256 KB of its raw output; on attach it replays those
bytes and ghostty re-renders them — colors, cursor, full-screen apps and all
(no separate screen model, so nothing to garble). On by default; set
vesta-persist = false for plain non-persistent shells.
What you get:
- Survive quit —
⌘Q, reopen Vesta: panes come back with their shells and recent output. - Close ends the shell —
⌘Wcloses the focused pane (a non-last pane detaches; the last pane closes and kills its session).⌘⇧Wcloses and kills the session. Shells survive only across window-close /⌘Qquit, and reattach on relaunch. To keep a shell but drop the pane, prefix-d(detach). - Prefix mode — tmux muscle memory. Press the prefix (
ctrl+bby default,vesta-prefix), then a key (table below). Emptyvesta-prefixdisables it. - Explicit kill — prefix-
x, orvesta kill <id>— when you actually mean to end the shell.
Verify it works
# 1. survive quit
# in a pane: echo i-was-here && date
# ⌘Q, reopen Vesta.app → the pane shows that output again.
# 2. detached sessions survive
# close the window (not ⌘⇧W) → its shells keep running; relaunch → they reattach.
# or prefix-d a pane to detach it (shell lives on under vestad).
# 3. from the CLI, watch the daemon hold sessions
vesta sessions # lists live + detached sessions with attach counts
vesta kill <id> # ends one for real
If a pane ever says "daemon protocol … update Vesta", an old vestad from a
previous build is still running (pkill -f vestad, then relaunch) — the
daemon is single-instance per user.
Prefix keytable (after ctrl+b)
| Key | Action | Key | Action |
|---|---|---|---|
% | split vertical | c | new session |
" | split horizontal | n / p | next / prev session |
h j k l / arrows | focus pane | , | rename session |
z | zoom pane | d | detach pane |
x | kill shell |
Override bindings with vesta-prefix-bind = key:action, … in your ghostty config.
Configuration
Vesta reads vesta-* keys from your ghostty config (libghostty ignores them).
Standard ghostty keys (theme, background, foreground, cursor-color,
palette = N=#hex) apply live. Every vesta-* default matches the built-in
look, so an untouched config changes nothing.
| Key | Default | Meaning |
|---|---|---|
vesta-accent | theme accent | accent color (rings, dots, focus ticks) |
vesta-surface | theme background | base surface color |
vesta-sidebar-width | 224 | sidebar open width (px) |
vesta-font-family | GeistMono | chrome label font |
vesta-font-mono | MartianMono | mono font |
vesta-font-size | 13 | chrome font size |
vesta-divider-width | 8 | split divider grab width (1px hairline drawn) |
vesta-projects | — | comma-separated project paths to preload |
vesta-persist | true | run shells under vestad (survive quit); false = plain shells |
vesta-persist-scrollback | false | mirror scrollback to disk so it survives a daemon restart. Off by default — terminal output can contain secrets (see SECURITY.md) |
vesta-prefix | ctrl+b | prefix key for tmux-style mode; empty = disabled |
vesta-prefix-bind | — | override prefix bindings: key:action, … |
Keybindings
| Keys | Action |
|---|---|
⌘D / ⌘⇧D | split vertical / horizontal |
⌘W / ⌘⇧W | close pane / close session |
⌘T | new session in active project (cwd = project dir) |
⌘] | focus next pane |
⌘{ / ⌘} | previous / next session |
⌘1–⌘9 | select session N |
⌘B | toggle sidebar |
⌘⇧P | command palette (search + run any action or plugin command) |
ctrl+b then a key | prefix mode (see Multiplexer & sessions) |
Click a pane to focus it; click a project to expand it; right-click a project
to rename / recolor / remove it. ⌘W closes the focused pane; ⌘⇧W closes
and kills its session — see Multiplexer & sessions.
Architecture
Sources/Vesta/Ghostty/— libghostty init, config sync, runtime callbacks.TerminalPane.swift— a ghostty surface (input / IME / mouse / resize / cwd / title).PaneTree.swift— tmux-style splits as nestedNSSplitViews.Tabs.swift— theWorkspacemodel: projects own sessions.Chrome.swift— window, titlebar, sidebar rendering.Control.swift— thevestaCLI + socket server.GhosttyConfig.swift—Theme+VestaConfig(thevesta-*keys).Git.swift— branch / status, shelled out off-main.PrefixMode.swift— tmux-style prefix mode.Sources/vestad/— the session daemon: oneforkpty'd shell per pane + a raw output ring, replayed on attach. No terminal parsing (ghostty does that).Sources/vesta-attach/— the per-pane relay ghostty spawns as its command; a dumb byte pump between the pane and the daemon over a0600unix socket.Sources/VestaMux/— shared wire protocol (MuxProtocol) + paths (MuxPaths).
Roadmap
Designs live in docs/superpowers/specs/. Shipped: persistent sessions
(2026-06-25-mux-rawring-rewrite.md) — vestad/vesta-attach raw-ring
multiplexer, prefix mode. Deferred there: mirroring (one session in two panes),
remote attach (vesta attach ssh://), and inline-image replay across detach.
(Disk-spill scrollback later shipped as vesta-persist-scrollback.) Also in flight: cmux parity
(2026-06-22-cmux-parity-design.md) — worktree-isolated sessions, attention
rings, richer sidebar, embedded browser pane.
Self-checks
.build/arm64-apple-macosx/debug/vesta selfcheck # config, control, git, workspace, chrome