Skip to content

youngharold/termusher

Repository files navigation

TermUsher

TermUsher — macOS terminal window manager

Test

A macOS menu bar app that seats your terminal windows in a tidy grid. Built for developers who keep many AI-CLI sessions open at once — Claude Code, Codex, Cursor, Aider, plain shells, you name it. When you're juggling a dozen terminals chasing different agents and tasks, TermUsher tiles them so you can see them all, then quietly steps aside.

Click a tile to zoom it for typing — others tuck into a sidebar strip. Drag a terminal's title bar to reorder. ⌘⌥G or hover the top edge of the screen returns to the even grid. The app can also auto-return after you press Enter and go idle (perfect for chat-style LLM workflows). Stop tiling and TermUsher restores each window to where it was before.

Built for the AI-CLI workflow. If your day involves ten Claude Code sessions, three Codex tabs, and a Cursor in the corner, this is for you.

Tech stack: Swift Package Manager · AppKit · ApplicationServices (Accessibility API) · ServiceManagement (Launch at Login) · XCTest · GitHub Actions CI on macos-14 · swift build --arch arm64 --arch x86_64 (universal binary).

Current version: 0.5.0 — see CHANGELOG.md for release notes and docs/ARCHITECTURE.md for module layout.

Build

./build-app.sh

Produces TermUsher.app in the project root.

swift test

Runs pure unit tests (layout math + settings; no Accessibility grant required).

Install

cp -R TermUsher.app /Applications/
open /Applications/TermUsher.app

On first launch macOS will prompt for Accessibility permission. Grant it in System Settings → Privacy & Security → Accessibility, then re-launch.

For stable Accessibility grants across rebuilds (recommended for local development):

./scripts/setup-codesigning.sh
# follow the printed sudo trust step once
./build-app.sh

Auto-launch on login

Click the menu bar icon → Launch at Login. (Uses SMAppService — requires the app to be installed in /Applications.)

Use

Click the grid icon in the menu bar:

  • Tile Terminal Windows — lay out every standard Terminal window in an even grid (one grid per display, based on where each window currently sits).
  • Click any tile → it zooms. Four zoom styles in the Zoom Style submenu:
    • Side Strip — focused fills 78% on the left, others stack on the right.
    • Full Screen — focused fills the screen entirely.
    • Full Column — focused expands vertically only (1/N width × full screen height).
    • Disabled — clicking does nothing; the static grid stays put.
  • ⌘⌥T — toggle tiling from anywhere.
  • ⌘⌥G — return to the even grid (works from any app; doesn't conflict with vim/REPL Esc behavior in Terminal).
  • ⌘⌥⇧T — Stop tiling and leave windows where they are (no restore).
  • Return to Grid / Refresh Window List — re-tile or re-detect windows (auto-detects most cases).
  • Exclude Focused Window — drop the currently-focused tile from tiling and restore its original position. Useful for log tails or monitoring panes that shouldn't move. Re-tile (stop + start) to bring it back.
  • Stop Tiling — submenu with two choices: Restore Originals (snap each window back to its position at the moment tiling started) or Leave Where They Are (just stop managing in place).

Keybindings

Shortcut Action
⌘⌥T Toggle tiling (start, or Stop & Restore)
⌘⌥G Return to the even grid (cancels a zoom)
⌘⌥⇧T Stop & Leave (windows stay where they are)

Hotkeys are global — they fire from any app. They use charactersIgnoringModifiers so they map to the physical keys on Dvorak / AZERTY / QWERTZ as well as US-QWERTY.

Auto Return to Grid

Three triggers can return a zoomed window to the grid without you reaching for a hotkey, all toggleable in the Auto Return to Grid submenu:

  • After N min idle — no input on screen for the configured interval (default 5 minutes) → grid.
  • On hover at top edge — move mouse to the very top of the screen for 0.3s → grid.
  • After ⏎ + Ns idle — press Return while Terminal.app is frontmost, then no further keystrokes for the configured interval (default 3 seconds) → grid. Best for Claude-CLI / chat-style usage. Enter in other apps is ignored.

All three are off by default. Enable any combination. Monitors are only installed for triggers you turn on.

Override durations (optional)

# Idle threshold (seconds)
defaults write com.youngharold.termusher TermUsher.autoReturnIdleSeconds -float 300

# Post-Return idle (seconds)
defaults write com.youngharold.termusher TermUsher.autoReturnAfterSendSeconds -float 3

Menu labels update to match the configured values.

Drag to reorder

While tiled, drag a Terminal window's title bar to move it. On release, TermUsher snaps it into the closest grid slot and swaps with whatever was there. Order persists for the session — restarting tiling resets to the original order. Windows dragged to another display re-tile on that display on the next Return to Grid.

Notes

  • New Terminal windows are auto-detected and added to the grid (~150ms after they appear).
  • Closed windows are dropped automatically; if only one Terminal window remains, tiling stops and that window is restored.
  • Windows on multiple displays tile within their own display (no cross-display merging). Unreadable-grid checks are per display.
  • Display plug/unplug or resolution changes while tiling re-index and re-tile automatically.
  • "Restore Originals" snaps each window back to the position it had at the moment tiling started — not to a pre-Terminal factory default.
  • Grid cells are pixel-snapped so odd display widths don't leave 1pt gaps between tiles.

Repository layout

Sources/TermUsher/          Menu bar app (AppDelegate, main)
Sources/TermUsherCore/      Layout, settings, WindowManager
Tests/TermUsherCoreTests/   XCTest suite
docs/ARCHITECTURE.md        Module responsibilities & data flow
CHANGELOG.md                Release history
build-app.sh                Universal .app bundle + codesign
scripts/                    Codesigning setup, diagram generators

Troubleshooting

  • Menu bar icon is there but tiling does nothing. Accessibility permission isn't granted to this exact build. Open System Settings → Privacy & Security → Accessibility — if TermUsher isn't listed, drag the app in; if it's listed but off, toggle it on. The hotkey monitor retries every ~1.5s, so you don't need to relaunch the app afterwards. Prefer the stable signing identity (see Install) so grants stick across rebuilds.
  • ⌘⌥T does nothing. Same as above — global key monitoring is gated by Accessibility.
  • Tile button shows "Too many Terminal windows". Cells would be smaller than 320×200 pt on that display. Move some windows to another display, or close a few.
  • Auto-return after Enter fires when I'm in another app. Fixed in 0.5.0 — only Terminal frontmost counts. Update if you're on an older build.
  • One Terminal window stays tiled fullscreen after closing siblings. This shouldn't happen — the app stops and restores when the count drops below 2. If it does, click Stop & Restore Originals in the menu.

Known limitations

  • Terminal.app only. iTerm2, Ghostty, Alacritty, and Warp use different AX subroles or aren't standard windows; they aren't detected.
  • Single-instance. Launching a second copy will alert and quit.
  • Ad-hoc signed (unless you run setup-codesigning.sh). Distributing the prebuilt .app outside this Mac will trip Gatekeeper. Build from source with ./build-app.sh instead.
  • Exclude is one-way for the session. Bring a window back by stopping and starting tiling (or opening a new window and refreshing).

Requirements

  • macOS 13+
  • Swift 5.9+
  • Apple Silicon or Intel (the build script produces a universal binary when both arch flags are used)

License

MIT

About

TermUsher — macOS menu bar app that seats your terminal windows in a tidy grid. Built for developers running multiple AI-CLI sessions (Claude Code, Codex, Cursor) at once. Tile, zoom, drag-to-reorder, auto-return on idle.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors