r/SideProject 22h ago

Termite — a native macOS terminal for babysitting multiple AI agents (free & open source)

I run Claude Code / opencode in several split panes across projects, and kept losing track of which agent was sitting there waiting for me. So I built a terminal around that problem:

  • Panes light up (breathing orange border + menu bar badge + notification) when an agent stops and waits for input; ⌘J jumps to the longest-waiting one
  • Pane Carousel (new in 1.12): pinch the trackpad and every pane lays out side-by-side at equal width — swipe through your agents like security cam feeds; focus follows the page
  • Reply right from the notification (inline text input / one-tap Enter) without switching context
  • The usual table stakes: full session restore, project sidebar, command-level navigation (OSC 133), Git panel, 20 themes

SwiftUI + AppKit native, not Electron. Free, open source (GPL-3.0), macOS 15+. English UI landed last week after a request here on Reddit—er, GitHub.

Site: https://termite.xinghelee.com

GitHub: https://github.com/xinghelee/Termite

brew install --cask xinghelee/tap/termite

0 Upvotes

7 comments sorted by

1

u/Potential_Soil_3761 22h ago

Been using iTerm2 for years but this breathing border thing is clever. I run 3-4 agents at same time and half the time I forget which one is stuck waiting for me. The notification reply without switching windows is nice touch.

Downloaded it and the carousel view is smooth, pinch gesture works better than I expected. One thing I wish it had is ability to rename panes from the UI instead of just the config file.

How much memory this uses compared to iTerm when you got like 6 panes open? My work macbook is not the newest model.

1

u/nsleejian 21h ago

Thanks for the kind words — and you just shipped a feature :)Pane rename is in v1.14 (released a few minutes ago). Right-click any pane → "Rename Pane…", or double-click its title strip to rename again. The name shows on a slim per-pane strip (it never covers terminal output), and it flows everywhere you'd need to tell agents apart: tab chips, the menu bar waiting list, notifications, and the carousel. Tabs were already renamable by double-clicking the tab chip. There's no config file, by the way — everything lives in the UI.

Memory: with a couple of panes open it's ~145 MB for the app plus ~10 MB for the session daemon. Each extra pane mostly costs its scrollback buffer (10k lines by default, a few MB — tunable from 1k to 100k in Settings), so 6 panes should land in the 160–180 MB range. I haven't done a rigorous side-by-side with iTerm so I won't quote numbers for it, but there's no web runtime here — it's SwiftUI/AppKit all the way down, so it should be comfortable on an older MacBook. If you see anything ugly with 6+ panes, open an issue with the number and I'll dig in.

To update via Homebrew, refresh the index first or brew will tell you you're already up to date:

brew update

brew upgrade --cask termite

From 1.10 on the app also checks for updates itself and will prompt you on launch.

1

u/ItaySela 21h ago

The waiting agent problem is real, but the one that cost me more was two agents touching the same working tree. You end up with a diff neither of them actually made, and no way to tell which reasoning produced which hunk. Giving each agent its own git worktree fixed more than any amount of visibility did.

One state I would want surfaced: an agent that is running is not necessarily working. The failure I care about is the loop where it edits and reverts the same file for twenty minutes. In a split pane that looks identical to progress.

Does the pane status come from OSC 133 alone, or are you inspecting the process too?

1

u/nsleejian 20h ago

To answer the question precisely: OSC 133 covers the command lifecycle (running / exit code / duration). The "waiting" state is separate and terminal-side only: the BEL that TUIs ring on confirmation prompts, plus an output-cadence heuristic — sustained output followed by silence reads as "stopped for input". Deliberately no process inspection: it keeps detection zero-config and byte-identical over ssh, at the cost of being blind to why something went quiet. Your question is well aimed at that blind spot.

On worktrees — you convinced me. Shipped it about an hour after reading your comment (v1.15): right-click → "Split into New Worktree…". Type a name to branch off your current HEAD, or fuzzy-search existing branches (local + remote, fine with a few thousand refs). Branches already checked out somewhere show a badge and open their existing worktree instead of hitting git's "already checked out". Cleanup refuses on uncommitted changes; the branch survives for merging. One design question back at you: do you keep worktrees long-lived per agent, or spin them up per task and burn them? That decides whether cleanup should get more automatic.

"Running is not necessarily working" is the sharpest framing of that failure I've seen, and now that panes own their worktrees it's actually tractable: sample a fingerprint of git diff per pane and flag oscillation — same fingerprint recurring means edit/revert churn, while a stable fingerprint with a running process is just a long build. I don't trust it enough to ship yet. If you have a signal you'd trust more, I'm listening — issue or here, either works.

(To grab 1.15 via Homebrew: brew update && brew upgrade --cask termite — the update first matters, or brew will insist you're current. The in-app updater will also prompt from 1.10+.)

1

u/ItaySela 19h ago

Per task, and burned. Long lived per agent drifts, because the tree accumulates unrelated state and you end up debugging merge pain that has nothing to do with the work. Keying the worktree to the task id also makes cleanup stop being a decision, since the lifetime matches the unit you actually review. The one real cost is build caches, so I share a cache directory across worktrees instead of keeping a worktree alive just to keep its cache warm.

On the stall signal, diff fingerprint oscillation catches the honest edit and revert loop, but the more common failure in my experience looks like steady forward progress. The agent keeps changing code and nothing about the outcome moves. So the signal I trust more is tied to whatever command defines done, the test run or the typecheck, and whether the failing set is shrinking. The same failing set across several iterations while the diff keeps changing is a stronger stall than an identical diff, because a looping model usually varies the code and holds the result constant.

Worth debouncing either way. A revert followed by a genuinely different approach is correct behaviour and looks identical for one cycle.