Skip to content

Repository files navigation

PocketTmux

PocketTmux

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.

CI Release macOS 14+ iOS 16+ Swift 6 tmux 3.7 MIT

waylake.github.io/pockettmux  ·  Download 1.0.0  ·  Protocol  ·  Architecture

Terminal attached to a tmux session Neovim running in a tmux window Accessory keyboard with Esc, Ctrl and arrows


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
Loading

Two apps, one Swift package, no third-party services and no daemon to install beyond tmux itself.

Screens

First run
First run
Pair by QR, or pick a Mac found over Bonjour
Macs
Macs
Saved Macs in the Keychain · nearby Macs live
Sessions
Sessions
Live tmux sessions, round-trip time, new / rename / kill
Mac menu-bar panel
Menu bar
Agent state, addresses, connected iPhones, live sessions
Pair iPhone window
Pair iPhone…
QR + link + token, address picker (Tailscale first)
Mac settings
Settings
Launch at login, keep awake, Bonjour, port, token, tmux path

Features

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

Install

Mac

# 1 · download PocketTmux-macOS-*.zip from Releases, then
mv PocketTmux.app /Applications
brew install tmux            # the only prerequisite
open /Applications/PocketTmux.app

CI 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.

iPhone

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.

Pair

  1. Mac: menu bar → Pair iPhone…
  2. iPhone: +Scan QR (on the same Wi-Fi the Mac also appears under Nearby Macs)
  3. 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.

How an attach works

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
Loading

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.

Build from source

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 · pockettmuxd
Command 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 agent

SwiftTerm 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.

Repository

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.

Documentation

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

Security model

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

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.

MIT · built on tmux and SwiftTerm.

About

Your Mac's tmux, in your pocket — a macOS menu-bar agent and a native iPhone terminal that attach to the same tmux server over Wi-Fi or Tailscale. No cloud.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages