A native macOS terminal for developers who keep many projects — and the AI coding agents working in them — open at once.
Zetty puts every project in one window: a sidebar of projects, each with its own tabs and split panes, terminals that survive quitting the app, and a live view of which agents are working, waiting on you, or failed. It is built on libghostty, so every pane is a full Ghostty terminal, with a Swift AppKit app around it.
- Projects, tabs and splits — pin, reorder, group into Spaces, hibernate the ones you're not using, or fork one into a disposable copy-on-write clone.
- Agent-aware — status dots for Claude Code, Codex, Hermes and more; launch an agent when you open a pane; run several Claude or Codex logins side by side; restart an agent without losing its conversation.
- Tile mode — a grid of live terminals from any project, so you can answer three agents without switching tabs.
- tmux-style keys — a
Ctrl+Bprefix layer, vi copy mode, zoom and broadcast input, alongside native ⌘ shortcuts. - Sessions that survive — with
preserve-sessions, panes keep running across quits and come back with their scrollback, even after a restart. - Scriptable — a
zettyCLI to inspect, drive and reshape the workspace, usable by the agents running inside it.
Platforms: macOS 14+ (Apple Silicon & Intel). Linux later.
- Download the latest
Zetty-<version>.dmgfrom Releases. - Open it and drag Zetty into Applications.
- Launch Zetty.
Install the zetty CLI: Settings (⌘,) → Command Line → Install links
zetty into ~/.local/bin — make sure that's on your PATH.
Folder-access prompts. macOS asks the first time a program running in a
pane touches Desktop, Documents, Downloads, iCloud Drive or an external volume —
every terminal does this. To stop the prompts for good, add
/Applications/zetty.app under System Settings → Privacy & Security → Full
Disk Access, then relaunch Zetty.
To build from source, see DEVELOPMENT.md.
- Click + in the sidebar (or
⌘O) to add a project — pick an existing folder, or New Folder to create one (optionallygit init). - Open tabs with
⌘Tand split with⌘D/⇧⌘D. The focused pane is the one with the accent dot. - Press
⌘Kfor the command palette — every action is in it.
Your layout, tab titles and sidebar persist across relaunches. Home — the
row at the top of the sidebar — is a permanent terminal at your home folder
(or zetty-home-path); ⌃⌘N opens a throwaway scratch terminal.
| Shortcut | Action |
|---|---|
⌘T |
New tab |
⌘D / ⇧⌘D |
Split vertically / horizontally |
⌥⌘T |
Break focused pane into its own tab |
⌥⌘← ⌥⌘→ ⌥⌘↑ ⌥⌘↓ |
Resize the focused pane |
⌘W / ⇧⌘W |
Close pane / close tab |
⌘} / ⌘{ |
Next / previous tab (tile view, while the grid is up) |
⌘1–⌘9 |
Jump to tab (tile view, while the grid is up) |
⌘J |
Toggle Sessions (docked drawer, or its own window) |
⇧⌘G |
Toggle tile mode |
⇧⌘J |
Toggle the tile manager (saved tile views) |
⌘K |
Command palette |
⌘B |
Toggle sidebar — pinned → hidden → drawer |
⇧⌘F |
Toggle the focused pane's file tree |
⌘↓ |
Scroll the focused pane back to the live tail |
⌘O (or ⇧⌘N) |
Add project |
⌃⌘N |
New scratch terminal |
⌘H |
Close Zetty to the menu bar |
⌘, / ⌥⌘, |
Settings / Project Settings |
⇧⌘, |
Reload configuration |
⇧⌘T / ⇧⌘A |
Cycle color scheme / appearance |
⇧⌘B |
Cycle broadcast scope (Off → Tab → Project → Agents → Workspace) |
⌘C / ⌘V |
Copy / paste |
Closing the window keeps Zetty and its terminals running from the menu-bar icon, which lists your projects and their agents' status.
Press Ctrl+B, then:
| Key | Action |
|---|---|
% / " |
Split vertically / horizontally |
h j k l / arrows |
Focus pane in that direction |
o |
Cycle pane focus |
x |
Close pane |
z |
Zoom / unzoom pane |
! |
Break pane into a new tab |
e |
Toggle the pane's file tree |
g |
Toggle tile mode |
c |
New tab |
n / p |
Next / previous tab |
1–9 |
Jump to tab |
, |
Rename tab |
[ / ] |
Copy mode / paste |
Ctrl+B |
Send a literal Ctrl+B |
Esc |
Cancel |
Copy mode is vi-keyed: h/j/k/l w/b/e 0/$ g/G to move,
Ctrl+U/D/F/B to page, v/V to select, y or Enter to yank, q/Esc to
exit. The status bar shows PREFIX, COPY, ZOOM and BROADCAST while each is
active.
Running tmux or screen in a pane? While it's the pane's foreground program,
Ctrl+B goes to it (so Ctrl+B d detaches) and ⌘ shortcuts keep working. This
needs preserve-sessions; turn it off with zetty-tmux-passthrough = false.
Over ssh, press Ctrl+B twice.
Remap anything in the config file:
prefix = ctrl+b
bind = s split-vertical
bind = ctrl+a broadcast-cycle
copy-bind = n copy-cursor-down
- Organize — drag project rows to reorder them; pin the ones you use most. Right-click → Move to Space ▸ files a project into a named, colored, collapsible sidebar section (New Space… makes one).
- Customize — right-click → Project Settings… for a name, color, icon (SF Symbol or emoji), theme, environment variables, and per-project overrides for session preservation and notifications.
- Hibernate — right-click → Hibernate Project, or the moon in the
gutter of any of its panes, frees a project's processes and keeps its
layout; it moves to the Hibernating section until you wake it.
hibernate-after = 60mdoes this automatically for idle projects (never Home), measured from when a project was last on screen, typed into, or written to by one of its agents. An agent sitting idle at an empty prompt no longer keeps its project awake; a working agent, a command or subagent an agent left running, a draft in the prompt box or a running command still does, and so does a project you woke yourself until you type into it. With preserved sessions, each Claude or Codex pane hands off first, in its own chat where you can watch it: a turn in progress is stopped, Zetty types/compact(for Claude, with handoff instructions) and the agent compacts its conversation, and only then is the project put away. A Claude sitting idle is not counted as busy: hibernating asks first (and the CLI wants--force) only for other running work. Until then it stays awake, the sidebar row and a strip above its panes read "Writing handoffs…", and typing in it, Cancel on the strip, or waking it calls the hibernation off (a chat that has already compacted stays compacted). A hibernated project's screen lists its handoffs, one per agent pane, in the same list the new-tab chooser uses, and you pick what to start with: a Resume: … row resumes that handoff (the pane shows "Resuming handoff…" until the agent is up, then the agent says where things stand), an agent's row starts a new conversation, and Standard session opens a plain shell. The project comes back as a single pane. The handoffs you didn't pick wait: adding a tab or a split offers them, ahead of the agents, in the same chooser. The bin at the end of a handoff, on that screen or in the chooser, deletes it; the conversation itself stays in the agent's own history. Waking from the sidebar or the palette opens that screen first.zetty wake, and a CLI verb that wakes a project to drive one of its panes, put the whole layout back instead, each agent resuming its own handoff. If a pane cannot hand off, most often because its prompt box holds a draft or a question, nothing is typed over it: the project is left awake and says "Handoff failed" and why, with Hibernate Anyway to put it away without one. A cleared or brand-new chat has nothing to hand off and does not hold the hibernate up. Hibernating or closing a Codex pane, or quitting with its sessions, also tells it to stop any command it still has running, which would otherwise outlive it. - Layout templates — save a project's tabs and splits (each pane's folder
and an optional startup command) to a committable
.zetty/project.json; it is applied when the project is added, or from Project Settings.
Each pane has a thin top strip with its focus dot and buttons to open its folder in an editor or Finder, split, break into a tab and close; right-click the strip for the same actions plus Scroll to Bottom.
- File tree —
⇧⌘F,Ctrl+B eor the strip's button. Follows the pane's folder, filters fuzzily, peeks a file on click and opens it in your editor on double-click. Read-only. - Peek a file — ⌘-click a path in any pane's output (a compiler error, a
grephit) to open it read-only, highlighted and scrolled to the line; Open in ▾ hands it to your editor at that line. Non-text files open in their default app. Needspreserve-sessions;zetty view <path>works regardless. Highlighting usesbatwhen installed. - Drop a file — drag a file from Finder onto a pane to paste its path; dropped text or a link pastes as text.
- Broadcast —
⇧⌘Bsends your typing to every pane in the tab, the project, the workspace, or only panes running an AI agent. Off by default, set per project.
⇧⌘G (or Ctrl+B g, or zetty tiles) shows a grid of live terminals from
any awake project — type into the focused tile and it reaches that shell.
- Fill it — click an empty slot's + Attach to pick any pane, drag a tab from the sidebar onto it, or start a New session there.
- Shape it — split a slot (
Ctrl+B %/", or its header button), drag the boundaries, drag a tile's header onto another to swap them. - Keep it — each view is a tab in the tab bar and saves itself as a profile;
reopen, rename or delete views from the tile manager (
⇧⌘J). - A tile's
×only detaches the pane; its stop button ends the session. Double-click a header to leave the grid for that pane.
⌘J lists every session Zetty runs, grouped by project, with live CPU and
memory. From there: Reveal Pane, Interrupt (Ctrl-C), Kill Session…,
or hibernate a whole project with its moon button.
Turn on Settings (⌘,) → Sessions → Preserve sessions (Zetty offers to
download zmx if needed), or set preserve-sessions = true.
Every pane then runs in its own zmx session:
- Quitting keeps them running — relaunching reattaches every pane with its programs and full scrollback intact.
- Closing a pane ends its session.
- Restarts are recovered — on a macOS restart or shutdown Zetty snapshots
each pane's screen and, on relaunch, replays it and resumes the Claude Code or
Codex conversation it was running (
zetty-restart-recovery). Turn on Launch at login in the same settings to make that automatic.
Status dots. Turn on the harnesses you use under Settings → Agent Status Hooks (Claude Code, Codex, Hermes). Tabs, projects and tiles then show: green — working · yellow — needs you (optional sound, Dock badge and notification) · red — Claude's last turn failed · dim — idle.
Launch agents. Enable agents under Project Settings → Agents (Claude
Code, Codex, Hermes, Gemini, opencode, Pi, Cursor). A new tab or split then asks
which to start — arrow keys or 1–9, ⏎ to launch, Standard session for
a plain shell.
Restart an agent, keeping its conversation. A pane running Claude or Codex
has a ⟳ button: Zetty quits the agent and resumes the same conversation in
place, scrollback kept. Needs preserve-sessions. From inside Claude,
/z-reload does the same.
Hand the work to a fresh session. The pane's hand-off button (beside ⟳)
asks the Claude or Codex in it to write a brief, then quits it and starts a new
session in the same pane, on the same login or one you pick from its menu
(another account, or the other harness). The new session reads the brief and
carries on; the brief is deleted once its first turn ends. A draft in the
prompt box is never typed over. Needs preserve-sessions. From inside Claude,
/z-handoff [account] does the same: an account by its handle, such as
claude-devops (its hint lists yours), or claude / codex for that
harness's default login.
Multiple accounts. Run a work login in one tab and a personal one in the
next (Claude Code and Codex). Settings → Accounts → Add Account… creates
one, optionally sharing your skills, commands and CLAUDE.md. Choose an account
per project (Project Settings → Account), per pane (the agent chooser lists each
agent once per account), or from anywhere with zetty run <account> or the
generated claude-<account> command. A status-bar chip and colored dots show
which account a pane is on, and warn when one nears a rate limit.
Claude Code integration. Zetty loads a small Claude Code mod into every Claude pane. It adds:
/zetty panes,/zetty fleet,/zetty peek <path>[:line]and/zetty split [command…]commands;/z-reload(restart this Claude on the same conversation),/z-handoff [account](a fresh session in this pane, on this login or that account, picks the work up from a brief this Claude writes),/z-hibernate [project](this project by default; its agents hand off first, this Claude included, which compacts its own conversation;--force/--no-handoffwork as on the CLI) and/z-open(this project in youreditor, or Finder);- tools Claude can call to show you a file, list this project's panes, and open
and read a pane of its own (
zetty-claude-tools = falseturns those off); - per-account rate limits on the account chip, a red dot when a turn fails, and notifications that say what Claude is asking for.
Turn the mod off with Settings → Agents → Load Zetty's mod or
zetty-claude-mod = false.
What Zetty changes in your agent setup. The Claude mod is on by default; status hooks and accounts change nothing until you set them up. Turning hooks or the mod off removes what they added:
- Status hooks add entries to
~/.claude/settings.json, thenotifyline of~/.codex/config.toml(your originalnotifyis backed up and restored) and~/.hermes/config.yaml, each calling~/.zetty/hooks/zetty-hook.py. Agent accounts get the same entries in their own config folders. Events are logged to~/.zetty/agent-events.jsonl, trimmed at launch. - The Claude Code mod is copied to
~/.zetty/mods/zetty-bridgeand reaches Claude throughCLAUDE_CODE_PLUGIN_DIRS, alongside any folders you set there. If asettings.jsonsets that variable itself, Zetty adds its folder to the list there too. - Accounts live in
~/.zetty/accounts/<name>, and theirclaude-<name>/codex-<name>shortcuts in~/.local/bin— a file of your own at that name is never overwritten. Removing an account leaves its folder and login on disk. - Every pane gets
ZETTY=1andZETTY_SURFACEin its environment, which is how hooks and the CLI know which pane they are in.
Right-click a project → Clone Project… (or zetty clone) for an instant
copy-on-write copy on its own git branch — untracked files, .env and
node_modules included — nested under its source in the sidebar. Clones are
disposable: when you're done, right-click → Merge to Source… to merge the
work into the source locally or push the branch for a pull request, then
Remove Clone…. (A source that isn't a git repo gets a per-file diff to copy
changes back instead.)
Zetty checks GitHub for new releases; click the ↑ Update pill to download,
verify and install one in place (check-updates = false to opt out). It also
handles ssh:// links, opening each in a new Home tab.
- Zetty won't launch, with a "Library not loaded" error naming
ZettyGhostty.framework— the app bundle is incomplete. Reinstall from the latest DMG; your workspace and settings are kept. - Reporting a bug — include your macOS and Zetty versions (About Zetty). A
blank or wrong file peek has a Copy diagnostics button in its footer, and
everything else is in the system log:
/usr/bin/log show --last 15m --predicate 'subsystem == "co.webteractive.zetty"' --style compact.zetty status --jsonhelps with CLI and session issues.
Zetty reads ~/.config/zetty/config (or $XDG_CONFIG_HOME/zetty/config), seeded
with a documented starter file on first launch. Lines are key = value;
comments are full-line #. Reload with ⇧⌘,.
Every key below can also be set from Settings (⌘,): General (Home
directory, update checks), Appearance, Sessions, Files (file tree and
viewer), Keys (prefix key, tmux passthrough, bind/copy-bind lines) and
Terminal (pasted ghostty lines). The Keys and Terminal editors apply with
their Apply button and name any line they couldn't use.
| Key | Default | Meaning |
|---|---|---|
appearance |
system |
system follows macOS; dark/light pin one |
theme-dark / theme-light |
Twilight / Daylight |
Scheme per appearance |
sidebar-position |
left |
Side of the window for the sidebar |
preserve-sessions |
false |
Keep panes alive across quit/relaunch (needs zmx) |
restore-scrollback |
true |
Replay preserved panes' scrollback on relaunch |
zetty-restart-recovery |
true |
Recover panes and agent conversations after a macOS restart |
hibernate-after |
off |
Hibernate a project after it has been idle this long (e.g. 60m, 2h) |
zetty-hibernate-handoffs |
true |
Hibernating has each Claude or Codex pane leave a handoff, and waking starts a fresh agent from it (needs preserve-sessions) |
check-updates |
true |
Notify when a newer release is available |
notify-sound / notify-badge / notify-system |
true |
Agent needs-attention alerts |
zetty-claude-mod |
true |
Load Zetty's Claude Code mod into new Claude panes |
zetty-claude-tools |
true |
Let that mod give Claude its pane and file tools |
editor |
— | App for "Open in Editor" |
viewer-highlight-command |
bat --style=plain --color=always --paging=never |
Highlighter for the file viewer; off disables it |
viewer-max-bytes |
2097152 |
Largest file the viewer renders |
zetty-home-path |
— | Folder the Home project opens in (~ allowed) |
zetty-sessions-view |
drawer |
Show Sessions docked (drawer) or as a window |
zetty-tile-manager-view |
drawer |
Show the tile manager docked (drawer) or as a window |
zetty-file-tree-show-hidden |
true |
Show dotfiles in the file tree |
zetty-file-tree-respect-gitignore |
false |
Hide what .gitignore excludes |
zetty-file-tree-ignore |
— | Extra names to hide, comma-separated (e.g. node_modules, vendor) |
zetty-file-tree-width |
220 |
Width a file tree opens at |
prefix / bind / copy-bind |
tmux defaults | Prefix-layer remapping |
zetty-tmux-passthrough |
true |
Hand Ctrl+B to tmux/screen running in the focused pane |
Any other line is a Ghostty directive, passed straight to libghostty — paste
your existing Ghostty config in (Zetty doesn't read Ghostty's own file).
font-family and font-size set the terminal and status-bar font; the rest of
the interface keeps the system font. If Ghostty rejects a directive, Zetty
alerts you once and keeps its own settings working.
Color schemes — dark: Midnight, Nocturne, Frost, Twilight, Ember, Velvet, Eclipse, Rosewood, Neon, Ukiyo · light: Daylight, Paper, Glacier, Dawn, Latte, Porcelain, Harvest, Citrus, Daybreak, Sakura.
zetty drives the running app over a local socket. Pane targets are a unique
prefix of the pane id, a unique --cwd, or the focused pane.
zetty status --json # projects → tabs → panes, agent status
zetty send --cwd ~/work/api 'ls' --enter # type into a pane
zetty send --key C-c # send a control key
zetty capture --lines 100 # recent output of a pane
zetty view README.md:20 # peek a file at line 20
zetty new-tab --project api # open a tab; prints the new pane id
zetty split --pane 1a2b3c4d --horizontal # split a pane; prints the new pane id
zetty break --pane 1a2b3c4d # move a pane into its own tab
zetty focus --cwd ~/work/api # switch to a pane
zetty close --pane 1a2b3c4d --tab # close a pane, or its whole tab
zetty restart-agent --pane 1a2b3c4d # the pane's ⟳: restart its Claude/Codex
zetty handoff --pane 1a2b3c4d --to work # a fresh session on "work" takes over from a brief
zetty edit --pane 1a2b3c4d # open the pane's project in your editor
zetty add-project ~/work/api # add a folder as a project
zetty new-project ~/work/new --git # create a folder and add it
zetty remove-project api # remove a project
zetty hibernate api # put a project away (keeps its layout)
zetty hibernate api --no-handoff # same, without writing handoffs
zetty wake api # bring it back; agents resume their handoffs
zetty wake api --fresh # ...with new conversations (or --shell: no agents)
zetty clone --project api --name fork-1 # copy-on-write clone on its own branch
zetty merge-clone fork-1 # land a clone's work in its source
zetty push-clone fork-1 # or push its branch for a pull request
zetty new-space "Client Acme" --color sky
zetty move-to-space api "Client Acme"
zetty scratch # throwaway terminal; prints its pane id
zetty tiles # toggle the tile grid
zetty tiles --profile morning # open a saved tile view
zetty accounts # list agent accounts
zetty run personal # run that account's agent here
zetty reload # same as ⇧⌘,
zetty quit --kill-sessions # quit and end preserved sessions- Background by default.
new-tab,split,breakandscratchdon't move your focus; add--focusto switch to the result. New panes start right away, so their ids can be used withsendandcaptureimmediately. - Hibernated or not-yet-viewed panes still work —
sendandfocuswake them on demand.hibernatereturns at once:statusmarks a pane‹handoff: writing›while its project waits to be put away,‹handoff: ready›once it is hibernated and waking will resume it, and‹handoff: failed›on the pane that left its project awake.wakeon a project still writing its handoffs cancels the hibernation. - Destructive commands never wait on a dialog.
close,remove-project,hibernate,scratch-clearandquit --kill-sessionsrefuse busy panes with an error naming them; pass--forceto go ahead. Forhibernate, an idle Claude that will hand off is not busy. zetty <command> --helpshows usage and does nothing else. Runzetty --helpfor every command.
Zetty is pre-release (0.1.x): interfaces and config keys may still change.
Contributions are welcome — see CONTRIBUTING.md, and
DEVELOPMENT.md for building, testing and releasing.
Zetty is released under the MIT License. It includes third-party
software under its own licenses — Ghostty and libghostty-spm (MIT), FreeType,
Oniguruma, libpng, zlib and others, plus Ghostty's bash and zsh
shell-integration scripts (GPLv3). See
THIRD_PARTY_NOTICES.md for the full list; the notices
and license texts also ship inside the app.
- Ghostty and libghostty-spm — the terminal core
- zmx — session persistence
- JetBrains Mono — the bundled font
- simple-icons and lobe-icons — tool logos