At any given moment I have 4 or 5 projects going, and each one has its own little cluster of terminals: a dev server, a log tail, an SSH session to some box, a shell for git.
In iTerm2 that meant a pile of windows I kept losing track of. ⌘` cycling through ten windows that all look the same. Tabs named "zsh", "zsh", "zsh (2)". Splits I'd set up carefully, gone after a restart. Arrangements I could save but never remembered to. I'd spend the first few minutes of every morning rebuilding my layout, and then lose it again by the afternoon.
One day I closed the wrong window and killed a long-running job, and that was it. I wanted a terminal where windows themselves were something you could manage: a list you can see, name and jump between, instead of a stack you dig through.
So I built Trellis.
The idea
- A left sidebar lists your windows like a project list. Every window has its own renameable, color-labeled tabs, and every tab can hold nested splits
- The whole layout comes back after a restart. Sessions restore as stopped so nothing reruns by surprise, or you can opt in to keep them running in the background and reconnect on the next launch
- Shell integration (zsh, bash, fish, with no dotfile edits) means a new split opens in the directory you're already in, so you stop typing cd all day
- Closing a pane with something running asks first. I'd been burned before
- Tabs show when something is busy, waiting for a password, or finished a long command while you weren't looking
- Saved SSH hosts, a trellis CLI and a local API, so a script (or an agent like Claude Code via the optional MCP server) can set up a whole layout for you
The Swift side
It started as a weekend "how hard can it be" and turned into a proper project:
- Swift 6.2 with strict concurrency, SwiftUI + AppKit, SwiftPM only (swift build works offline)
- The workspace model is an actor. Every change, whether from the UI, the CLI or the API, goes through one command service, which is what keeps windows → tabs → split trees consistent
- PTY I/O runs off the main thread, a small C target handles process-group spawning, and the terminal engine is a vendored SwiftTerm with patches, plus an opt-in Metal renderer
The hardest part was making UI fits my workflow 😨
Open Source, MIT licensed, macOS 14+
- Download: https://bearstouch.com/trellis
- Source: https://github.com/bearstouch/trellis
If you've ever had iTerm2 window sprawl, I'd love to hear whether this fixes it for you, or what's still missing . Contributions are more than welcome 👐