Files
tty7/mobile
l0ng-ai 618bf887e3 feat(mobile): reach the machines the desktop is linked to over SSH
A machine's screen now lists, under its own workspaces, every machine its
tty7 holds an SSH link to, with that machine's workspaces. Panes there open,
take input and get new tabs like local ones. "Needs you" gathers panes from
all of them.

- The gateway routes through the local server over links it already holds,
  the same way `tty7 -m <machine>` does. It never dials a down link, because
  that would guess at credentials the phone does not have. A down link
  shows as "Link down", with a note to reconnect it on the desktop.
- Protocol, additive: `Tree.remotes`, and an optional `machine` (the link
  key) on `Open::Pane` and `Open::NewTab`. A local pane is asked for exactly
  as before, so older gateways still understand it.
- Rebuilding a route target from a link key moves from the CLI into
  tty7-core as `RouteInfo::target` / `RouteInfo::host`, so the CLI and the
  gateway share one rule. The CLI now calls it.
2026-09-27 09:26:18 +02:00
..

tty7 mobile

Watch and drive your desktop's tty7 panes and agents from a phone.

phone (Tauri: WebView + Rust)  ──iroh──▶  tty7-gateway  ──local sockets──▶  tty7 server
   xterm.js ◀─ raw bytes ─ tty7-mobile-client          (crates/tty7-gateway)
  • Transport: iroh. The phone dials the desktop by public key. Connections hole-punch to a direct path when the network allows and fall back to a relay when it doesn't. Either way they're end-to-end encrypted. No port forwarding, no VPN.
  • Protocol: crates/tty7-mobile-proto. The phone never speaks the daemon's own protocols. The gateway exposes a small vocabulary: pair, a live tree of workspaces, tabs, panes and agent status, and one stream per open pane.
  • Panes are observed, not attached. A phone never resizes a pane or takes it away from the desktop window showing it. Keystrokes go in beside the observer (SendInput). The terminal keeps the desktop's size, and the app shrinks the font to fit the width.
  • Auth: pairing with a one-time code, valid 10 minutes, single use. After that the gateway admits only the phone keys on its device list (<config dir>/mobile/devices.json).

Run it

On the desktop, next to a running tty7:

cargo run -p tty7-gateway -- serve     # keep this running
cargo run -p tty7-gateway -- pair      # prints a QR code + a tty7pair:… code
cargo run -p tty7-gateway -- devices   # paired phones; `revoke <name>` removes one

The app on the desktop (fastest loop)

Tauri builds the same app for macOS, which is the quickest way to work on the UI:

cd mobile
npm install
npm run tauri dev

Paste the tty7pair: code and tap Pair.

iOS

Needs the full Xcode, not just the Command Line Tools, plus an Apple developer account to run on a device.

rustup target add aarch64-apple-ios aarch64-apple-ios-sim
cd mobile
npm run tauri ios init       # once: generates src-tauri/gen/apple
npm run tauri ios dev        # simulator, or pick a connected device

Android

Needs Android Studio's SDK and NDK, with ANDROID_HOME and NDK_HOME set.

rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
cd mobile
npm run tauri android init
npm run tauri android dev

Without a phone

crates/tty7-mobile-client/examples/probe.rs is a phone in a shell. It pairs, prints the tree, and times keystroke echo on a pane:

cargo run -p tty7-mobile-client --example probe -- pair '<code>'
cargo run -p tty7-mobile-client --example probe -- tree
cargo run -p tty7-mobile-client --example probe -- type 1 'echo hi'

Not done yet

  • QR scanning. The app takes a pasted code for now. Next step is tauri-plugin-barcode-scanner on mobile.
  • Push notifications when an agent starts waiting. The gateway sees the status change, but APNs/FCM delivery needs a small native plugin and a push relay.
  • Keychain / Keystore for the phone's key. It lives in the app's private data directory today.
  • Remote machines. The gateway serves the local machine's panes. Panes the desktop reaches over SSH aren't exposed yet.
  • Take over. Attaching a pane at the phone's size, for when the desktop isn't in use.
  • A self-hosted iroh relay for production, instead of n0's public ones.