Your Mac's tmux, in your pocket.
A menu-bar agent for the Mac and a native iPhone terminal that attach to the same tmux server — over Wi-Fi or Tailscale, with no cloud in between.
waylake.github.io/pockettmux · Download 1.0.0 · Protocol · Architecture
The pane your Mac's terminals see is the pane your phone renders. A key tapped on the phone is a tmux key in the real pane; the pane's bytes stream back and are drawn by a native VT engine (SwiftTerm). tmux stays the single source of truth — the phone is one more client, exactly like a terminal window on the Mac.
flowchart LR
subgraph P["iPhone — PocketTmux"]
UI["Macs · Sessions · Terminal"] --- TV["SwiftTerm view"]
end
subgraph M["Mac — PocketTmux.app / pockettmuxd"]
AS["AgentServer<br/>WebSocket + Bonjour"] --> TC["TmuxControl<br/>tmux -CC on a pty"]
end
T["tmux server<br/>sessions · windows · panes"]
MT["Terminal.app,<br/>iTerm, Ghostty…"]
P <-- "ws v2 · LAN / Tailscale" --> AS
TC <--> T
MT <--> T
Two apps, one Swift package, no third-party services and no daemon to install beyond tmux itself.
![]() First run Pair by QR, or pick a Mac found over Bonjour |
![]() Macs Saved Macs in the Keychain · nearby Macs live |
![]() Sessions Live tmux sessions, round-trip time, new / rename / kill |
![]() Menu bar Agent state, addresses, connected iPhones, live sessions |
![]() Pair iPhone… QR + link + token, address picker (Tailscale first) |
![]() Settings Launch at login, keep awake, Bonjour, port, token, tmux path |
PocketTmux for Mac — macOS 14+, menu bar, not sandboxed (it execs your tmux and opens a pty)
| Agent | Start/stop, port, status and every reachable address (Tailscale listed first) |
| Pairing | QR / pockettmux:// link / copyable token, with the address you choose |
| Visibility | Connected iPhones and the session each one is on; live tmux sessions with open in Terminal and kill |
| Settings | Launch at login, auto-start, keep the Mac awake while a phone is attached, Bonjour advertising, display name, port, token regenerate, tmux path, ring log |
| Headless | pockettmuxd — the same agent as a CLI for remote or scripted Macs |
PocketTmux for iPhone — iOS 16+
| Macs | Many saved Macs (Keychain), nearby Macs over Bonjour, add by QR / link / manual entry |
| Sessions | Live list with window and client counts, attach, create, rename, kill, RTT next to the status dot |
| Terminal | SwiftTerm rendering (TrueColor, alternate screen, mouse reporting), accessory keyboard (esc ctrl ⇥ ~ | arrows), window strip (switch / new / rename / kill), paste with bracketed-paste semantics, font size, haptic bell, keep-awake |
| Scrolling | Swipe scrollback inside TUIs and shells — the first paint after any attach is rebuilt from tmux's own state, so nothing is blank or garbled (why this is hard) |
| Resilience | Jittered backoff reconnect with automatic re-attach after Wi-Fi drops, backgrounding and Mac sleep |
# 1 · download PocketTmux-macOS-*.zip from Releases, then
mv PocketTmux.app /Applications
brew install tmux # the only prerequisite
open /Applications/PocketTmux.appCI builds are unsigned — the first launch needs right-click → Open. The app appears in the menu bar; choose Pair iPhone….
Note
macOS shows the "allow incoming connections?" firewall prompt on first start. Allow it, or the phone cannot reach the agent.
Download PocketTmux-iOS-*-unsigned.ipa from Releases
and re-sign it with your own Apple ID (Sideloadly, AltStore), or build and run the PocketTmux
scheme on your device from Xcode — no paid developer account needed either way.
- Mac: menu bar → Pair iPhone…
- iPhone: + → Scan QR (on the same Wi-Fi the Mac also appears under Nearby Macs)
- Tap a session. You're in.
Tip
Tailscale works out of the box and needs no port forwarding: the pairing QR carries the
100.x.y.z address, and mDNS is not required.
sequenceDiagram
participant P as iPhone
participant A as Agent
participant T as tmux
P->>A: hello{v:2, token}
A-->>P: hello.ack{host, caps}
P->>A: session.attach{id, cols, rows}
A->>T: forkpty → tmux -CC attach -t $1
A->>T: resize-window + window-size manual
A->>T: display-message #{alternate_on}… · capture-pane -p -e
A-->>P: session.attached{session, windows}
A-->>P: screen{reset} — primed escapes + content + cursor
T-->>A: %output %7 …
A-->>P: screen{update} every 16 ms
Control mode gives exact bytes and stable ids but replays nothing that predates the client, so the agent primes the first frame from tmux's own state — the trick that makes scrollback and TUIs work on the phone. Full flows in ARCHITECTURE §4; frames in PROTOCOL.
Prereqs: macOS 26, Xcode 26.2+, brew install xcodegen tmux (swiftlint optional).
git clone https://github.com/waylake/pockettmux.git
cd pockettmux/App
xcodegen generate # PocketTmux.xcodeproj from Project.yml (git-ignored)
open PocketTmux.xcodeproj # schemes: PocketTmux (iOS) · PocketTmuxMac · pockettmuxdCommand line — every xcodebuild needs -skipPackagePluginValidation
# Mac app
xcodebuild build -scheme PocketTmuxMac -destination 'platform=macOS' \
-derivedDataPath build/Mac -skipPackagePluginValidation
open build/Mac/Build/Products/Debug/PocketTmux.app
# iPhone app (simulator)
xcodebuild build -scheme PocketTmux -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
-derivedDataPath build/Sim -skipPackagePluginValidation
# headless agent + pairing QR, no Mac app involved
../scripts/start-agent.sh && ../scripts/pair.sh
# tests
swift test --package-path PocketTmuxKit # 23 tests: protocol, parsers, agent logic
xcodebuild test -scheme PocketTmux -destination 'platform=iOS Simulator,name=iPhone 17 Pro' \
-derivedDataPath build/Sim -skipPackagePluginValidation # 15 iOS unit tests
python3 ../scripts/check-attach-prime.py # e2e against a running agentSwiftTerm ships a build-tool plugin whose validation step fails on Xcode 26.2 — without the flag the build aborts before compilation. Details in CLAUDE.md.
App/
├── Project.yml XcodeGen spec — four targets (source of truth for the .xcodeproj)
├── PocketTmuxKit/ Swift package
│ ├── PocketTmuxKit protocol v2 codec · tmux control-mode parser · -F formats · pairing URL
│ └── PocketTmuxAgent AgentServer · AgentConnection · TmuxControl · TmuxRunner · ScreenPrimer
├── iOS/ PocketTmux for iPhone (SwiftUI + SwiftTerm)
├── macOS/ PocketTmux for Mac (menu-bar app)
├── Daemon/ pockettmuxd (CLI agent)
└── iOSTests/ iOS unit tests
docs/ product · architecture · protocol · stack · roadmap · troubleshooting
scripts/ start/stop-agent · pair (QR) · check-attach-prime (e2e) · make-icon
Anything protocol- or tmux-related lives in the package, never duplicated in an app target.
| Doc | What it covers |
|---|---|
| PRODUCT | Jobs, screens, behaviour, design language, release scope |
| ARCHITECTURE | Components, request flows, decisions & alternatives, failure modes |
| PROTOCOL | Wire protocol v2: frames, priming, pairing, discovery |
| TECH_STACK | Every dependency with version, license and rationale |
| ROADMAP | Shipped in v1.0; next: notifications, pane picker, TLS, iPad |
| TROUBLESHOOTING | Real failures, root causes, and the research behind each fix |
Trust is same network + token: a 32-character random token per Mac in ~/.pockettmux/token
(mode 0600), compared in constant time, presented in the first frame. There is no TLS in v1 —
the intended deployments are a home LAN and Tailscale, which is already encrypted. Regenerate the
token any time from Settings › Security; TLS with a pinned certificate and per-device tokens are
planned for v1.2.
CONTRIBUTING.md covers setup, workflow, Conventional Commits and tests.
Semantic Versioning; pushing a vX.Y.Z tag builds the unsigned .ipa, the Mac app zip and the
pockettmuxd tarball into a GitHub Release with the CHANGELOG section as notes.








