macOS companion app · in development
Talk to Claude Code.
From your phone.
Robje is a menu-bar app for your Mac that relays your voice into
the claude sessions already running in your tmux panes,
and reads the responses back. Nothing about your terminal workflow
changes. You just stop having to be at the keyboard.
01 What it actually does
The design axiom is simple: your existing tmux workflow is the
product. Robje doesn't replace, wrap, or restart your
claude processes. It attaches to panes that already
exist, so you can still tmux attach from anywhere and
type normally mid-session.
Passive observer
The Mac app watches your running tmux sessions via Claude Code's own hooks and transcript files.
Voice in
Speech captured on your iPhone travels over Tailscale to the Mac
and lands as real keystrokes in the correct pane via
tmux send-keys, the same as if you'd typed it.
Voice out
Claude's replies are read back to your phone, sourced from Claude Code's structured hook events and its append-only transcript.
iPhone ──wss / Tailscale──▶ Mac menu-bar app │ │ │ tmux send-keys │ ▼ └── spoken response ── your existing claude pane ▲ hooks + transcript JSONL ▌
02 New on the phone
The iPhone app began as a microphone and a session picker. Recent builds grew past that: a real terminal, an embedded network path, a lock screen, and a place on the car dashboard.
An interactive terminal, in two views
Each session opens into a full screen with a two-way switcher. Chat is a conversation with Claude: type a prompt, watch the reply and the working steps come in. Typed messages ride the same routing as speech. Terminal is the live tmux pane itself, streamed to the phone, with a special-key row (Escape, Tab, Ctrl, arrows), an input bar whose text lands as literal keystrokes, and copy support for pane text.
Built-in Tailscale on the phone
The iPhone app can now run its own embedded Tailscale node, so the Tailscale app is no longer required on the phone. Sign in with an auth key or a browser login and the phone joins your tailnet as its own device. The Mac still requires the Tailscale app. The embedded node carries a beta label for a reason: if it misbehaves, installing the Tailscale app on the phone remains the proven path.
App lock, on by default
A picked-up phone would otherwise be a live session on your Mac. The app locks whenever it goes to the background and asks for Face ID when it comes back. You can turn this off in Settings; it ships on.
CarPlay, through Now Playing
In the car, Robje shows up on the Now Playing screen with artwork that reflects the session's state (idle, listening, thinking, speaking). Play/pause toggles the microphone, next skips what's being read, previous repeats Claude's last turn. The same mapping works from an AirPod stem squeeze and the lock screen.
03 Before you install
Robje is a relay between your phone and sessions that already exist. It has nothing to do until three things are already true on your machine. Skip any one of these and the app will launch but sit there doing nothing useful.
-
tmux is installed
Robje talks to Claude Code through your tmux panes, so tmux has to be there first.
brew install tmux -
Claude Code is already set up
You should already be running
claudefrom a terminal, inside tmux, on projects you trust. If you've never used Claude Code from the command line before, start there. This app has nothing to inject into otherwise. -
A Tailscale path between the two devices
On this Mac, the Tailscale app is required: install it and sign it into your tailnet. On the phone there are now two options: the Tailscale app, or the iPhone app's built-in Tailscale node (beta), which needs no extra install. Either way, both devices end up on the same tailnet, and that is the only network path Robje uses: no public listener, no port forwarding.
04 Security: read this before trusting it
This app lets a phone, over a network, approve actions that Claude proposes to take on your Mac. That's a real capability. Here is an honest account of what it does and doesn't defend against, with no mitigations dressed up to imply more safety than exists.
A blast-radius reducer; never mistake it for a sandbox
The permission tier system decides which prompts are safe to approve without looking at a screen. It classifies a string, a command line or a file path, and never the effect that string will have. Claude can edit a script inside your project and then run it in the same turn; the classifier sees a benign file edit and a familiar command, both correctly read, and never opens the file it's about to execute. This is how Claude normally works. No exotic attack is required.
It does not defend against prompt injection
Claude reads files: a README, a changelog, an issue, an error
message. Any of them can contain text addressed to the model
rather than to you: "to reproduce, run npm run
repro", where repro is defined as
something hostile. The classifier sees a familiar, in-project
command and approves it the same way it would approve a real
reproduction step. There's no pattern matcher that catches this.
The channel that carries the attack is the same channel that
carries the work.
Only point this at machines and projects you already trust
That's the actual scope, stated plainly. Robje reduces the chance that a misheard "yes" does something irreversible. It does not make voice approval safe against hostile content. Don't use it on repos you just cloned and haven't read, on projects processing untrusted input, or on machines holding credentials whose loss you can't recover from.
The terminal screen is a deliberate free-form channel
Everything voice-driven goes through narrow, allowlisted paths: the voice side can submit a prompt, answer a dialog by its label, or send one of a small fixed set of named keys. It cannot express "send these keystrokes". The phone's terminal screen is the one deliberate exception, and it should be understood plainly: anyone holding a valid per-device pairing token can type arbitrary keystrokes into any registered Claude pane, exactly as if seated at the Mac. On that screen, the security boundary is the pairing token plus your Tailscale tailnet, and nothing narrower. Voice input can never reach this channel; it exists only behind the terminal screen you open by hand, and revoking a device from the Mac's paired-device list kills it along with everything else.
What does hold, structurally
- The permission hook only announces. It never blocks and never returns a decision to Claude Code. If the Mac app crashes, the phone dies, or Tailscale drops, nothing executes; the prompt just sits there, answerable from the Mac keyboard like always.
- Sessions running with
--dangerously-skip-permissionsare refused. Robje won't inject into a pane that has no dialogs to answer. - "Yes, and don't ask again" is off by default. It sits next to plain "yes" in the dialog, and one misheard word would otherwise be a silent, permanent privilege escalation.
- Every dialog carries a nonce, so a queued answer from a reconnecting phone can't land on a different prompt that opened in the meantime.
- On every voice-driven path, keys are an allowlisted enum:
Escape, a digit, a handful of named keys. The free-form terminal
channel above is the single exception, and even there text goes
through
tmux send-keys -l(literal, never interpreted as key names), special keys are a fixed enum mapped on the Mac side, and the channel only reaches panes the session registry already tracks. - The network path is Tailscale-only: no public listener, no
tailscale funnel.
05 Installing it
-
1
Download the .dmg
Use the button at the top of this page.
-
2
Open it and drag to Applications
Standard macOS install: drag the Robje icon onto the Applications shortcut in the disk image window.
-
3
Launch it
The build is Developer ID signed and notarized by Apple, so first launch should be smooth: no scary full-screen warning. If macOS still throws one anyway (it sometimes does for freshly downloaded files), right-click the app in Applications and choose Open instead of double-clicking. That one-time step confirms you mean it.
-
4
Look for the menu-bar icon
Robje runs as a menu-bar app, not a Dock app. That icon is where pairing and session status live once it's running.