Tower · macOS menu bar + terminal

Every Claude Code agent, on one radar.You are the control tower.

Run a dozen Claude Code agents and you lose the thread — which one needs you, which one quietly failed, which two are editing the same file. Tower is a Mac menu bar app and terminal dashboard that watches every agent, mirrors your real plan usage, pins Claude to a country, and tells you the truth about your connection.

$ curl -fsSL https://ghhrmnzdh.github.io/tower/install.sh | sh
One daemonstdlib Python Two front-endsmenu bar + curses Zerothird-party deps
Guard radarlive
GuardCleared
ReasonIn-country · the path to Anthropic is open
Claude gets→ api.anthropic.com · allowed
The problem

Six agents. One failed twenty minutes ago, one is waiting on an approval you never saw, and two are about to collide. From out here, they look identical.

claude — payments-api
claude — auth-service
claude — web-ui
claude — tower
claude — docs
claude — infra
No signal

You can't see who needs you.

Nothing surfaces. You tab through terminals hunting for the one that stopped — and you find it late, if at all.what you get today · silence

No cause

An error, and nowhere to place it.

Claude stops mid-turn. Your Wi-Fi? A slow hotel network? Anthropic having a moment? You alt-tab to a speed test and guess.what you get today · API Error (500)

No fence

A VPN hops, and the turn dies.

Your location drifts mid-session. The request comes back looking like broken auth instead of simply waiting for you to be back on-country.what you get today · Please run /login

No meter

You don't know what's left.

How much of your plan is gone? Local token math is a guess. You find out for real when you hit the wall, mid-task.what you get today · a guess

What Tower does

Four jobs, one daemon.

One background process holds all the logic and state. Everything you see — in the menu bar or in the terminal — is a thin, honest view over it.

Traffic

Every agent, ranked.

Each running Claude Code agent, live: its model, effort, what it's doing right now, and its momentum. The queue is ordered by who needs you — failed, then blocked, then asking, then done — with notifications, and click-to-focus that jumps straight to that agent's terminal.

auth-servicefailed
⛔︎ apiwaiting for approval
? web-uiasked you a question
docsdone · your turn
Weather

Your internet, or Anthropic.

When Claude errors mid-session, Tower has already answered the only question that matters. It probes your link and times a TLS handshake to Anthropic's edge, continuously and passively — so it can name the fault instead of guessing at it. An on-demand speed test is one click away.

Onlinenet 8 ms · api 214 ms
↓ 412 Mbps · tested 2m ago
Fence

Pin Claude to one country.

A small local proxy holds Claude's requests whenever you are confirmed outside your country — fail-closed, so anything uncertain waits. A held request is answered 503 with a Retry-After, never 403: Claude retries it into its own spinner, and it resumes the moment you're back. Pending, not failed.

inside 🇨🇦 CApass
VPN → 🇳🇱 NLheld
503 · Retry-After 5 — Claude waits, then resumes
Usage

The real number, not an estimate.

Your true plan limits, mirrored from Claude Code's own claude -p /usage — Claude does its own auth, and Tower never reads a token. A local token-and-cost estimate sits beside it, clearly labeled as the guess it is.

Session3%resets in 4h
Weekly33%resets in 2d
Fable31%resets in 2d
source · claude -p /usage · 60s
The menu bar

Every agent, at a glance.

A native menu bar app — Swift, AppKit and SwiftUI. Click the radar in the bar and the whole tower drops down, in strict attention order: what needs you first, then what's working.

The mark is the state.The bar's radar is the guard. Full color only for a hold or for danger; calm and white when you're protected.
Model and effort, per agent.Opus, Sonnet, Haiku, Fable — at MAX, HIGH, MEDIUM or LOW. Each agent's mark comes alive only while it's working.
Collisions, before they land.Two agents in one repo is a warning. Two agents in the same file is a red one.
The done payoff is earned.A checkmark draws on, the row glows once, the counter ticks. Failure never bounces — it fades in, sober.
Tower
Protected
Internet · API8 ms · 214 ms
Needs you
payments-api
done — 214 tests pass, ready to merge
2 agents in tower — both touching towerd.py
Agents 3 at work · 41 done today
Toronto, CA — inside 🇨🇦 CA
Keep awake · lid openMac won't sleep
Plan usage updated 2m ago
Session3%resets in 4h
Weekly33%resets in 2d
Fable31%resets in 2d
Open Dashboard… Settings… Quit & Stop Guard…
The terminal

The same tower, in the shell.

A zero-dependency curses dashboard — pure Python stdlib, reading the same state file as the app. Change a setting in one and the other reflects it in about a second.

One word to open it.Type tower. The installer puts it on your PATH — no paths to remember, nothing to double-click. Or click Terminal Dashboard… in the menu bar popover and it opens for you.
Deliberately non-reactive. Fully clickable.A stray keypress does nothing, so you can't fat-finger the guard off. But click a row and it acts: toggle routing, pick a country, run a speed test, focus an agent's terminal.
Answers on sight.A red header — INTERNET DOWN, WI-FI LOGIN REQUIRED, ANTHROPIC API ISSUE — means stop and look here. A net fault always outranks the guard.
Repaints only on change.~80 ms input, ~60 ms to confirm an action at the daemon, no idle cost.
tower
TOWERProtected · target: Canada
NETWORK LOCATION
✔ inside target
IP 142.114.9.2
Country Canada (CA)
Location Toronto, ON
GUARD
Route Claude on
Enforce on
Allowed 1284
Blocked 3
3 at work · 41 done today· 1 needs you
payments-api opus·MAX done · your turn 8✓ 1m
khamzadeh_flow opus·MAX running cd 17✓ 2m
tower opus·HIGH editing towerd.py 56✓ 2m
web-ui sonnet·MED running tests 12✓ 5m
⚠︎ 2 agents in tower — both touching towerd.py
› resting · 4
● online · net 8 ms · api 214 ms
api ▂▃▂▅▃▂▄▆▃▂▅▃↓ 412 Mbps (2m ago)
PLAN USAGEupdated 2m ago
Session ░░░░░░░░░░░░░░░░░░░ 3% · resets in 4h
Weekly ███████░░░░░░░░░░░░░ 33% · resets in 2d
Fable ██████░░░░░░░░░░░░░░ 31% · resets in 2d
LIVE TRAFFIC1284 allowed · 3 blocked
09:41:22 ✓ api.anthropic.comallowed
09:41:08 ✓ api.anthropic.comallowed
The marks

One living glyph per model.

Deliberately monochrome and minimal. Still at rest — and while its agent works, each comes alive in its own way. The tier's color appears only in the small label beside it, never on the mark itself.

The philosophy

If something looks wrong, that is the app doing its job — not the app being broken.

Fail-closed, never fail-confusing.Claude is allowed only when Tower has confirmed you're in-country and that there's a usable path to Anthropic. Anything uncertain is held — and a hold clears itself the moment your location or your connection recovers. There is nothing to quit.
It always un-routes on shutdown.Quitting removes the proxy from settings.json first, so Claude Code returns to a direct connection. A crashed front-end can't break Claude.
Never through the shell, never in your traffic.Routing edits settings.json only — passive, reversible, backed up before the first edit. The proxy tunnels HTTPS with CONNECT: it sees hostnames, and never decrypts a byte.
Read-only toward your sessions.Tower never writes into ~/.claude and never types into a session. It reads process and network tables, and tails transcripts. That's all.

Flight rule · a block is pending, not failed

1
403 would kill the turn.Claude Code reads it as broken auth — "run /login" — and the work is gone.
2
So Tower answers 503 + Retry-After.The request is held a few seconds first, re-checking, so a sub-second blip clears with no visible retry at all.
3
Claude shows its own spinner."Retrying · attempt 2/10" — its native, durable pending state. Nothing is lost.
4
It resumes on its own.The moment the guard clears, the turn continues where it stopped.
Inbound

On the radar.

Tower is open source — use it, fork it, port it. One portable daemon with thin front-ends is what makes each of these tractable.

Planned

Linux front-end

A tray via AppIndicator, and the curses dashboard already runs. The daemon is stdlib-portable as-is.

Idea

Deeper collisions

Past same-repo and same-file: the exact lines two agents are about to fight over, and one tap to pause the other.

Idea

Usage history

A longer memory for plan usage — weekly burn-down, and a heads-up before you hit a limit rather than after.

Idea

More fences

Beyond country: time-of-day windows, per-project rules, allowlists. The same fail-closed proxy, more ways to draw a fence.

Questions

Before you install it.

macOS says Tower is from an “unidentified developer.” What now?

Use the one-line install and you'll never see that. Tower isn't notarized with Apple, and macOS quarantines anything you download in a browser — so it checks the signature, doesn't recognize it, and refuses to open the app. A file fetched with curl is never quarantined, so macOS doesn't run that check and Tower opens normally. If you'd rather download the zip by hand, clear the flag yourself with xattr -rd com.apple.quarantine /Applications/Tower.app, or open it once and choose Open Anyway in System Settings → Privacy & Security. (Right-click → Open used to work; macOS 15 removed it.)

Piping a script from the internet into sh — should I be nervous?

You're right to ask, and you shouldn't take anyone's word for it. Read the script first — it's short, and it's the same file the command fetches. It downloads the release from GitHub, unpacks it to /Applications, links the tower command, and opens the app. It asks for no password and never uses sudo. If you'd still rather not, download the zip from Releases and do it by hand.

Does Tower read my Anthropic API key or tokens?

No. Real plan usage is mirrored from Claude Code's own claude -p /usage — Claude does its own auth, and Tower never reads a token. The proxy tunnels HTTPS with CONNECT, so it sees hostnames and never decrypts your traffic.

How does Tower pin Claude Code to a country?

It runs a small local proxy and points Claude Code at it through the HTTPS_PROXY key in ~/.claude/settings.json — never through your shell. When you are confirmed outside your target country, a Claude request is held and answered 503 with Retry-After, so Claude retries it into its own spinner. The request is pending, not failed, and it resumes the moment you are back on-country.

What happens if Tower quits or crashes?

Quitting removes the proxy from settings.json first, so Claude Code returns to a direct connection. The guard un-routes on every clean exit and via atexit, and the daemon is single-instance. A crashed front-end can't break Claude.

How do I tell whether an outage is my internet or Anthropic?

Tower probes both, passively and continuously: your link, and a timed TLS handshake to api.anthropic.com. It reports online, degraded, offline, captive portal, or an Anthropic-side problem — plus an on-demand speed test. No guessing, and no Claude request is spent finding out.

Is Tower free? Does it run on Windows or Linux?

Tower is free and open source. The full experience — menubar app plus terminal dashboard — runs on macOS 14 or later, on Apple Silicon.

On Windows 10/11, the daemon and the terminal dashboard already run — same guard, same fail-closed proxy, no dependencies. Treat it as experimental: it's written and reviewed, but not yet tested on real Windows hardware, so expect rough edges (and please report them). The native tray app is next. A Linux front-end is planned — the curses dashboard should already run there.

Get Tower

Give your agents a tower.

One line installs Tower, puts the tower command on your PATH, and starts it. No Gatekeeper warning, nothing to drag. The app starts the daemon for you — you never have to arm the guard by hand.

$ curl -fsSL https://ghhrmnzdh.github.io/tower/install.sh | sh

Installs to /Applications, puts tower on your PATH, and opens the app. No password, no sudo, no Gatekeeper warning. Run the same line again to upgrade.

$ git clone https://github.com/ghhrmnzdh/tower && cd tower && ./build.sh

Downloading by hand? Grab Tower.app.zip — not GitHub's “Source code (zip)”, which has no built app inside it. macOS quarantines browser downloads, so it will call the app unidentified and refuse to open it; clear that with xattr -rd com.apple.quarantine, or choose Open Anyway in System Settings → Privacy & Security. The line above avoids it.

macOS 14+ · Apple SiliconWindows 10/11 · terminal only, experimental Python 3.8+ at runtimeXcode CLT to build the Mac appClaude Code installed