Skip to the content.

local-pilot

Run and control Claude Code from any browser on your tailnet.

A small web app that runs on a single box, drives Claude Code through the Claude Agent SDK, and gives you a mobile-friendly chat UI you can reach from your phone, tablet, or laptop over Tailscale — no SSH session held open, no terminal emulator.


What you get


Setup

Prerequisites

You need all of these on the host machine (the box you’re running local-pilot on — usually a Linux server, NAS, or workstation):

Requirement Why Install
Node 20+ Runs the server and builds the UI nvm install 20, or your distro’s package manager
claude CLI local-pilot reuses its login & config — you don’t sign into Anthropic again Anthropic install docs — then run claude once and log in
systemd For the recommended --user service install Already on most Linux distros
Tailscale The only network path into the app curl -fsSL https://tailscale.com/install.sh \| sh
ffmpeg (optional) Only needed for voice input apt install ffmpeg / brew install ffmpeg

Install

git clone https://github.com/DaveForan/local-pilot
cd local-pilot
npm install
npm run service:install

That command does four things:

  1. Builds the React UI into web/dist.
  2. Installs a systemd --user service named local-pilot.
  3. Generates a random access token into ~/.local-pilot/token (mode 0600) and prints it to the terminal — copy it somewhere safe, you’ll sign in with it on each device.
  4. Starts the service. It binds 127.0.0.1 only — never exposed on your LAN.

Want it to keep running after you log out and across reboots?

sudo loginctl enable-linger "$USER"

Expose it on your tailnet

sudo tailscale serve --bg 8787

That gives you an HTTPS URL like https://<your-host>.<tailnet>.ts.net. The HTTPS is what makes push notifications and microphone access work in the browser — both are blocked on plain HTTP except on localhost.

Sign in

  1. From any tailnet device, open https://<your-host>.<tailnet>.ts.net.
  2. Paste the token printed at install time.
  3. The token is exchanged for an HttpOnly session cookie — the token itself is never stored in the browser.

Lost the token? It’s at ~/.local-pilot/token on the host. Or rotate it from Settings → Security → Rotate access token.

Add to your phone home screen

Open the URL in Safari (iOS) or Chrome (Android) → share menu → Add to Home Screen. That gives you a full-screen icon that looks and behaves like a native app, and means push notifications land in your notification tray when a session needs you.

Voice (optional)

Both halves of the conversation loop run on-device — no paid services:

npm run whisper:install   # speech-to-text (whisper.cpp, base.en model)
npm run piper:install     # text-to-speech (Piper neural voices)
systemctl --user restart local-pilot

Without these, the browser’s built-in Web Speech API is used as a fallback — voice still works, just less accurate and more robotic.

Common commands

systemctl --user status local-pilot      # is it running?
systemctl --user restart local-pilot     # restart after config changes
journalctl --user -u local-pilot -f      # tail logs

Mobile interface

The UI is built mobile-first. A floating hamburger sits over the chat on the left edge; everything else hides behind it.

Mobile — chat view
Chat view: clean paper-tone bubbles, per-turn activity block, send/voice/image buttons in the composer.
Mobile — session drawer
Session drawer: tap the floating hamburger to slide it in. Switch sessions, see model + context usage + cost, archive/rename, change permission mode.
Mobile — elicitation modal
Elicitation modal: when Claude asks a question or wants permission, the modal pops over everything so you can't miss it. Multi-choice answers tap-to-pick.
Mobile — conversation mode
Hands-free conversation mode: mic listens for utterances, replies are read back through Piper TTS, the loop rearms automatically.
Mobile — activity log
Activity log: every command, file edit and tool result Claude ran during a turn. File edits render as proper diffs.
Mobile — settings
Settings: MCP servers, hooks, plugins, voice picker, push notifications, security (token rotation + backup/restore).

Screenshots will appear here once they're added to docs/screenshots/. Until then each tile shows a placeholder with the expected filename — see docs/screenshots/README.md for what to capture.


Security model

What an access token gets you. Anyone holding it can create a session in any directory and have Claude execute tools there — that’s the whole point of the app. Treat the token like an SSH key. The tailscale serve gate limits who can even reach the login page; the token gates the rest.


Configuration

The defaults are sensible. Override via environment variables when launching the service:

Variable Default Purpose
PORT 8787 HTTP / WebSocket port
HOST 127.0.0.1 Bind address — loopback only
LOCAL_PILOT_DATA ~/.local-pilot Where state is stored
LOCAL_PILOT_DEFAULT_CWD ~/Projects Default working dir for new sessions
LOCAL_PILOT_TOKEN (auto-generated) Override the access token
LOCAL_PILOT_WHISPER_MODEL base.en Whisper model
LOCAL_PILOT_PIPER_VOICE en_US-amy-medium Piper voice

To set them for the systemd service, edit ~/.config/systemd/user/local-pilot.service and add Environment="VAR=value" lines under [Service], then:

systemctl --user daemon-reload
systemctl --user restart local-pilot

Architecture

┌──────────────────────────────────────────────────────────┐
│           Browser (tailnet device, anywhere)             │
│   React + Vite UI · session cookie · WebSocket           │
└────────────────┬─────────────────────────────────────────┘
                 │ HTTPS via `tailscale serve`
┌────────────────▼─────────────────────────────────────────┐
│                local-pilot server (loopback)             │
│  Node + Express · WebSocket hub · auth · push            │
│  SessionManager → one ClaudeRunner per session           │
│  Whisper (STT) · Piper (TTS) spawned on demand           │
└────────────────┬─────────────────────────────────────────┘
                 │ Claude Agent SDK (in-process)
┌────────────────▼─────────────────────────────────────────┐
│   The same Claude Code your `claude` CLI runs.           │
│   Uses your existing login, MCP servers, skills,         │
│   project settings, CLAUDE.md.                           │
└──────────────────────────────────────────────────────────┘

Project status

v1 is feature-complete. See the README on GitHub for the changelog.

License

MIT.


local-pilot is not affiliated with Anthropic. “Claude” and “Claude Code” are trademarks of Anthropic.