Access your AI coding assistant from any device. End-to-end encrypted terminal mirroring with structured agent events β for Claude Code, Aider, Copilot, Gemini, and any terminal-based AI tool. Rust core. Zero-knowledge relay. Self-hostable.
farwatch start claude # launches Claude Code in a PTY, prints a QR codeScan the QR from your phone and you get a live, encrypted session β both a raw terminal mirror and a structured view with native UI for tool calls, thinking indicators, and prompts. The relay in the middle only ever sees ciphertext.
βΉοΈ Status β This is a personal project, kept public because the crypto is meant to be auditable. It is not actively developed as a product, so issues and PRs may not get timely responses. The hosted relay is currently offline (it may return) β self-host a relay to run farwatch today; that path needs no account. Backstory: Why farwatch exists.
Your Machine Cloud Your Phone
ββββββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββ
β Claude Code β β Relay Server β β Structured View β
β (real TUI) ββββ PTY βββ (zero- βββ E2E ββββ (native cards) β
β β β knowledge) β encrypted β β
β Desktop: Enter β β β β Terminal View β
β to take over β β β β (raw PTY mirror) β
ββββββββββββββββββββ ββββββββββββββββ ββββββββββββββββββββ
Desktop gets the real Claude Code TUI via takeover mode. Phone gets structured events (thinking, tool calls, text) as native UI cards, plus a terminal view toggle. Both can send input simultaneously. The relay is a dumb pipe β it forwards opaque encrypted bytes and never sees your data.
# Homebrew (macOS / Linux)
brew install yipjunkai/farwatch/farwatch
# Shell script
curl -fsSL https://raw.githubusercontent.com/yipjunkai/farwatch/main/install.sh | sh
# From source
cargo install --git https://github.com/yipjunkai/farwatch -p cli
# Docker (relay server only)
docker pull ghcr.io/yipjunkai/farwatch:latestThe default
farwatch startconnects to the hosted relay, which is currently offline. Self-host a relay (no account needed) and point the CLI at it withFARWATCH_URL.
# Start a session (auto-detects your AI tool)
farwatch start
# Or specify a tool with extra args
farwatch start claude
farwatch start aider --model sonnetScan the QR code from your phone to connect. On the host:
- Enter β take over the terminal (you type directly into Claude Code)
- Double-tap Esc β return to the dashboard
- q β quit the session
farwatch attach --pairing-uri "farwatch://pair?..."For tools that support structured output (currently Claude Code), the phone shows two views.
Structured view (default for Claude Code):
- Thinking blocks (purple cards with reasoning)
- Tool call cards (tool name, arguments)
- Tool results (output, truncated to 32KB)
- Text responses
- Turn markers and busy indicator
- Prompt bar with mic/send button (Telegram-style)
Terminal view (toggle via AppBar button):
- Raw PTY output mirror β exactly what the desktop sees
- Bottom sheet with terminal actions (Ctrl+C, arrows, paste, etc.)
Both views update in real-time as Claude Code works. The structured events come from tailing Claude Code's .jsonl session log (~/.claude/projects/), not from parsing terminal output.
The mobile client lives in a separate repo: farwatch-mobile (Flutter, iOS + Android).
Auto-detected in order of priority:
| Tool | Structured events | Notes |
|---|---|---|
| Claude Code (Anthropic) | Yes | JSONL session log tailing for native mobile UI |
| OpenCode (open source) | PTY only | |
| GitHub Copilot CLI (Microsoft) | PTY only | |
| Gemini CLI (Google) | PTY only | |
| Aider (open source) | PTY only | |
| Any command on PATH | PTY only | farwatch start my-tool |
Tools without structured support work via PTY mirroring β the phone shows a terminal emulator.
For Claude Code, the host sends two parallel streams through the same encrypted channel:
Claude Code (PTY)
βββ Raw PTY bytes βββ SecureMessage::PtyOutput βββ Phone terminal view
β
βββ ~/.claude/projects/<hash>/<session>.jsonl
βββ JSONL watcher (notify/kqueue) βββ SecureMessage::AgentEvent βββ Phone structured view
The JSONL watcher tails Claude Code's session log file using filesystem notifications. It parses assistant messages, tool calls, tool results, thinking blocks, and turn completion (stop_reason: "end_turn"). These are emitted as AgentEvent variants through the same E2E encrypted channel.
Phone input flows back via AgentCommand::Prompt which gets injected into the PTY as keystrokes (text + \r). Tool approvals send y\r, denials send n\r.
Press Enter on the host dashboard to take over the PTY directly:
- TUI dashboard suspends, terminal switches to raw mode
- PTY output displays on your screen (Ctrl+L redraw on entry)
- Your keyboard input goes directly to the PTY
- Terminal resizes are forwarded to the PTY
- PTY output simultaneously flows to the phone via relay
- Double-tap Esc returns to the dashboard
Both desktop and phone can send input at any time. There's no locking β the PTY processes input from both sources.
All WebSocket messages are MessagePack-encoded across three layers:
Relay-level (RelayMessage): Register, Registered, Route, PeerStatus, Ping/Pong, Error
E2E-level (PeerFrame inside Route payload): Handshake, HandshakeConfirm, Secure (AES-GCM sealed), KeepAlive
Application-level (SecureMessage inside Secure):
- Terminal:
PtyInput,PtyOutput,Resize - Agent:
AgentEvent,AgentCommand - Session:
Heartbeat,VersionNotice,Notification,SessionEnded,ReadOnly - Voice:
VoiceCommand
farwatch/
βββ crates/
β βββ cli/ # User-facing binary (farwatch): PTY, JSONL watcher, TUI, takeover
β βββ relay/ # Zero-knowledge relay server
β βββ protocol/ # Shared types, crypto (X25519 + AES-256-GCM), pairing primitives
βββ Dockerfile # Relay image for self-hosters
βββ install.sh # Shell installer (downloads a release binary)
βββ .github/ # Release + relay-image publish workflows
| Crate | Description |
|---|---|
cli |
User-facing binary (farwatch), PTY management, JSONL watcher, TUI, takeover mode |
relay |
Zero-knowledge relay server |
protocol |
Shared protocol types, crypto (X25519 + AES-256-GCM), pairing primitives |
The Flutter mobile app (iOS + Android) is the primary mobile client β see farwatch-mobile.
Farwatch is designed so that no one except you and your connected device can read your terminal data β not us, not the relay operator, not anyone on the network.
The relay server is assumed to be honest-but-curious: it faithfully forwards messages but may attempt to read them. All terminal data is encrypted end-to-end before it reaches the relay, so a compromised or malicious relay learns nothing beyond metadata (session ID, peer role, message timing and size).
Every session establishes a unique encrypted channel between the host (your dev machine) and the client (your phone/tablet/other terminal):
- Key exchange: Each side generates an ephemeral X25519 key pair. Public keys are exchanged via the relay inside
Handshakemessages. - Key derivation: Both sides compute a shared secret via Diffie-Hellman, then derive two 256-bit symmetric keys (one per direction) using HKDF-SHA256 with the session ID as salt and
farwatch/v1/channel-keysas the info string. - Encryption: All terminal I/O and agent events are sealed with AES-256-GCM before transmission. Each frame carries a monotonically increasing nonce.
- Key confirmation: After key derivation, both sides exchange an HMAC-SHA256 over the handshake transcript, proving each peer holds the private key corresponding to their advertised public key.
Every encrypted frame includes a strictly monotonic 64-bit nonce. The receiver rejects any frame with a nonce less than or equal to the last accepted nonce.
The pairing URI (displayed as a QR code) includes a SHA-256 fingerprint of the host's public key. The client verifies this fingerprint on connection, detecting any man-in-the-middle substitution by the relay.
The relay server sees only session ID, peer role, message size, and timing. It never sees plaintext terminal content, keystrokes, agent events, public keys, or encryption keys. The relay cannot decrypt, modify, or forge messages β any tampering is detected by AES-GCM authentication.
| Purpose | Algorithm | Notes |
|---|---|---|
| Key exchange | X25519 | Ephemeral per-session key pairs |
| Key derivation | HKDF-SHA256 | Session ID as salt, domain-separated info string |
| Authenticated cipher | AES-256-GCM | Per-frame encryption with monotonic nonce |
| Key confirmation | HMAC-SHA256 | MAC over handshake transcript |
| Fingerprint | SHA-256 (truncated) | First 8 bytes of public key hash |
| At-rest encryption | AES-256-GCM | Random nonce per file, machine-local key |
| Nonce construction | 4 zero bytes + 8-byte BE counter | 96-bit nonce from 64-bit counter |
- Compromised endpoints: If your machine or phone is compromised, the attacker has access to the decrypted session.
- Traffic analysis: The relay can see message timing and sizes, revealing activity patterns.
- Denial of service: A malicious relay can drop or delay messages (but cannot read or forge content).
Run your own relay server β no account, API key, or control API needed:
# From source
cargo run -p relay -- --bind 0.0.0.0:8080
# With Docker
docker run -p 8080:8080 ghcr.io/yipjunkai/farwatch:latestPoint the CLI at your relay:
FARWATCH_URL=ws://your-server:8080/ws farwatch startNote:
farwatch authis for the hosted service only. Self-hosted relays run unauthenticated by default.
# Run the relay server locally
cargo run -p relay -- --bind 0.0.0.0:8080
# Run the CLI against local relay
FARWATCH_URL=ws://127.0.0.1:8080/ws cargo run -p cli -- start
# Run with hosted service features (auth, device flow)
FARWATCH_URL=ws://127.0.0.1:8080/ws cargo run -p cli --features hosted -- start
# Run tests
cargo test| Build command | Includes auth? | Use case |
|---|---|---|
cargo build -p cli |
No | Self-hosted / contributors |
cargo build -p cli --features hosted |
Yes | Hosted service users |
The AI tool launches in your current working directory, not the repo directory. To run the dev CLI from elsewhere, pass --manifest-path /path/to/farwatch/Cargo.toml.
I wanted to check on and steer Claude Code from my phone without handing my terminal, prompts, or code to a third-party server. So farwatch encrypts everything end-to-end and treats the relay as a dumb pipe that only sees ciphertext β the crypto is open (the protocol crate) specifically so that claim is auditable.
Since it was built, first-party remote control shipped in Claude Code, Codex, and Copilot CLI, so farwatch is no longer the only way to do this. It stays useful where those don't reach: any terminal tool rather than a single vendor, fully self-hostable, and end-to-end encrypted with nothing stored on anyone's server. These days I maintain it as a personal tool rather than a product.
This is a personal project without active maintenance commitments β issues and PRs are welcome but may not get a timely response. If you want to build on it, forking is encouraged (the license allows it).
Dual-licensed under MIT or Apache 2.0, at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in farwatch by you, as defined in the Apache-2.0 license, shall be dually licensed as above, without any additional terms or conditions.