Skip to content

devtheicetea/handsfree

Repository files navigation

Handsfree

Handsfree

Talk to your coding agent — hands-free, from your phone.

English | 简体中文

Node TypeScript License: MIT PRs welcome

This is the self-hosted backend for the Handsfree iOS app: it runs on the computer where your coding agent lives, drives it on your behalf, and exposes a WebSocket your phone connects to.

100% on-device voice — and completely free. Speech-to-text and text-to-speech run entirely on Apple's built-in, on-device engines inside the iOS app. There's no cloud voice API, no separate voice model to host, no subscription and no per-minute fees — nothing extra to pay for. Talk to your coding agent and hear it reply, fully hands-free, anywhere and any time.

  • Free, on-device voice. All audio stays on the phone (Apple's built-in speech recognition + text-to-speech). No online voice model, no API keys for voice, no extra cost — the bridge itself is text-only.
  • Drives Claude Code (via the Claude Agent SDK) and OpenAI Codex (via the Codex CLI), side by side.
  • Streams replies token-by-token and routes tool-permission prompts and multiple-choice questions to the phone — the bridge stays the permission gate.
  • One live session per (project, agent); multiple phones can view and drive the same session at once.
  • LAN out of the box, or Tailscale for talking to your agent from anywhere. Optional shared-secret token.

The bridge speaks a plain text/JSON WebSocket protocol (see src/protocol.ts).

Architecture

flowchart LR
    subgraph Phones["📱 Handsfree app(s) — iOS"]
      direction TB
      STT["Speech → text<br/>(on-device)"]
      TTS["Text → speech<br/>(on-device)"]
    end

    subgraph Host["💻 Your computer"]
      direction TB
      Bridge["Handsfree Bridge<br/>permission gate · client fan-out<br/>one live session per (project, agent)"]
      Claude["Claude Code<br/>(Agent SDK)"]
      Codex["OpenAI Codex<br/>(CLI)"]
      Files[("Session history<br/>~/.claude · ~/.codex")]
    end

    Phones <-->|"WebSocket — LAN / Tailscale<br/>(optional token)"| Bridge
    Bridge -->|"prompts · permission asks"| Claude
    Bridge --> Codex
    Claude --> Files
    Codex --> Files
Loading

Audio stays on the phone; the bridge only moves text and tool-permission prompts. Multiple phones attach to the same live session and stay in sync — the bridge is the single writer (see Sessions & concurrency).

Prerequisites

  • Node 20+
  • A coding agent installed and logged in:
    • Claude Code, logged in with a Claude Pro/Max subscription. Keep ANTHROPIC_API_KEY unset so it runs on your subscription rather than pay-as-you-go API billing.
    • and/or OpenAI Codex — install the Codex CLI (npm i -g @openai/codex or brew install codex) and run codex login. Optional.
  • (Optional, for remote access) Tailscale on both the computer and the phone, signed in to the same account.

Setup

Three quick steps — the same ones the app walks you through.

1. Get on the same network

Your phone and the computer running your coding agent need to reach each other. Do this first — the bridge picks the right address when it starts.

  • On the same Wi-Fi — if your phone and computer share a network, there's nothing to set up.
  • Away / remote — install Tailscale on both the computer and the phone and sign in to the same account. The bridge then advertises your Tailscale address automatically and the phone reaches it from anywhere — no port forwarding. Leave the default HANDSFREE_BIND=0.0.0.0, or set it to your Tailscale IP to refuse all other interfaces.

The advertised host is chosen automatically: your Tailscale IP if available, otherwise your LAN IP, otherwise localhost. Override it with HANDSFREE_HOST (see Configuration).

Already using a VPN to reach Claude / Codex? (split tunneling)

If your computer needs a VPN to reach your coding agent's API, that VPN and Tailscale both manage routing and can fight each other — a full‑tunnel VPN can starve Tailscale so the phone can't reach the bridge. Two things make them coexist:

  • Your phone never talks to the agent — only to the bridge. So the phone only needs Tailscale (or the same Wi‑Fi), not your Claude/Codex VPN. On the same Wi‑Fi the phone needs no VPN at all.
  • Split‑tunnel the other VPN on the computer so Tailscale's traffic skips it. Tailscale uses the CGNAT ranges 100.64.0.0/10 and fd7a:115c:a1e0::/48; exclude those from your VPN (or, on an inclusive split tunnel, route only your agent's API endpoints through the VPN). This is what Tailscale officially recommends — see their other‑VPNs FAQ.

Alternatively, skip the system VPN entirely and send only the bridge's outbound API calls through a proxy: the Claude CLI and Codex honor HTTPS_PROXY / ALL_PROXY, so HTTPS_PROXY=… npx handsfree-bridge@latest tunnels the agent traffic while leaving Tailscale untouched.

Caveats: this can't work if the other VPN is an exit‑node‑style full tunnel that won't let you exclude routes, or if it itself uses the 100.64.0.0/10 range (a direct conflict with Tailscale).

2. Start the bridge

On the computer where your coding agent runs, start the bridge and leave it running:

npx handsfree-bridge@latest

Needs Node 20+, with your coding agent installed and logged in (see Prerequisites). Configure it with the environment variables in Configuration, e.g. HANDSFREE_TOKEN=… npx handsfree-bridge@latest.

Or run from source
git clone https://github.com/devtheicetea/handsfree.git
cd handsfree
npm install && npm run build
npm start

3. Connect to the bridge

On start, the bridge prints a pairing QR code and a handsfree://connect?… URL in its terminal, with the host and port (default 8744) beneath it. In the Handsfree app:

  • tap Scan QR Code and point the camera at it, or
  • tap Enter connection manually and type the host and port shown.

That's it — start talking to your coding agent, hands-free.

Configuration

All configuration is via environment variables:

Variable Default Description
HANDSFREE_PORT 8744 Listen port.
HANDSFREE_BIND 0.0.0.0 Bind address. Set to your Tailscale IP to listen only there.
HANDSFREE_HOST auto Host advertised in the pairing QR/URL. Auto = Tailscale IP → LAN IP → localhost.
HANDSFREE_TOKEN (none) Optional shared secret; the client must send it in its hello.
HANDSFREE_SAFELIST Read,Grep,Glob,LS,TodoWrite,CodexApplyPatch Comma-separated tools auto-approved in safelist permission mode.
HANDSFREE_MODEL (SDK default) Model for Claude sessions (e.g. sonnet, opus, or a full model id).
HANDSFREE_CODEX_PATH resolve from PATH Full path to the codex binary.
HANDSFREE_ENV prod Run mode. debug turns on verbose per-message console logging; prod keeps the console quiet.

Permissions

Every tool call is gated. Each session runs in one of three modes, switchable from the phone:

  • safelist — tools in HANDSFREE_SAFELIST auto-approve; everything else asks.
  • ask_all — every tool call prompts on the phone.
  • auto — everything auto-approves (use with care).

The bridge is always the gate: prompts stream to the connected client(s) and the agent waits for an Allow / Allow-for-session / Deny answer.

Sessions & concurrency

The bridge assumes the cardinal rule of Claude Code / the Agent SDK: exactly one live process may write to a session's history at a time. Two live writers on one session produce interleaved, divergent, or corrupt history. Everything below is a strategy to respect that rule.

  • Many phones, one session, at the same time → yes. Multiple Handsfree clients subscribe to the same single bridge-owned session and fan out: there's still one live writer (the bridge), and the phones are just synchronized views with input.
  • Phone joining a session your terminal is running (mirror) → safe by forking. The phone watches the live CLI session read-only; on its first prompt the bridge forks a new session id (copies the history, then diverges) instead of becoming a second writer on the terminal's session.
  • Resuming a bridge session in your terminal (claude --resume <id> / codex resume) → sequential handoff only. This continues the same id (no fork), so stop the bridge first to release the session, then resume in the terminal. Running the bridge and a terminal resume on one id simultaneously is two writers — don't.

In short: same live session on multiple devices at once is supported through the bridge's fan-out; the bridge and a native resume co-driving one id is not.

Security

  • The bridge has no auth beyond an optional HANDSFREE_TOKEN. Don't expose it to the public internet. Bind to localhost/LAN, or use Tailscale so only your own devices can reach it.
  • The agent runs with your local permissions in the chosen project directory. Pick your permission mode accordingly.

License

MIT © Contrast Studio LLC

About

No description, website, or topics provided.

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors