codereach
Turn a local OpenCode session into something your phone can drive.
cd ~/projects/my-app
npx @cultivx/codereach
Install
| Command | Needs | |
|---|---|---|
| npm | npx @cultivx/codereach, or npm install -g @cultivx/codereach | Node 22+ |
The npm package is the bundled JavaScript. opencode has to be on your
PATH; nothing else is needed.
Updating
codereach checks the npm registry once a day and tells you when there is a
newer release, with the command that upgrades your copy:
CodeReach 0.3.0 is available (you have 0.2.11). Update with: npm install -g @cultivx/codereach@latest
Or just ask it to do it:
codereach upgrade
It works out how it was installed and runs the right package manager.
Voice models
Voice is transcribed on your computer. The phone sends one utterance to the machine it is paired with, and nothing goes to a hosted speech service.
Two packs are available:
| Pack | Size | Covers |
|---|---|---|
| English | 541 MB | English, best accuracy |
| Multilingual | 645 MB | 30 languages, detected from speech |
The engine is transcribe.cpp: Metal on Apple silicon, Vulkan on Windows and Linux, with a CPU fallback. The matching native package is installed with this CLI; no GPU or compiler is needed.
Nothing downloads when the agent starts. Install one when you want voice:
codereach voice # what is installed
codereach voice setup # asks which language, then downloads
codereach voice setup --language en # skip the question
codereach voice setup --language multi
codereach voice remove --language en # delete one
codereach voice remove --all # delete every pack
Models live under ~/.codereach/voice/models. Downloads are checked against a
pinned SHA-256 before they are installed, so a broken download never becomes a
model the engine can load. codereach voice setup --force downloads again.
Remote Control ready
─────────────────────
Session: studio-mac
Folder: ~/projects/my-app
OpenCode: 2.0.29
End-to-end encrypted: the relay cannot read this session.
Scan the QR with the app, or paste this pairing link there.
No pairing code: only the QR and the link pair an encrypted session.
Scan with CodeReach:
█▀▀▀▀▀█ ▄▀▄ █▀▀▀▀▀█
█ ███ █ ▀▄▀ █ ███ █
█ ▀▀▀ █ █▀▄ █ ▀▀▀ █
▀▀▀▀▀▀▀ ▀▀▀ ▀▀▀▀▀▀▀
Pairing link: codereach-pair:eyJ2IjoyLCJjb2RlIjoiN0s5U...
If end-to-end encryption slows things down, you can disable it with --no-e2e, at your own risk.
Expires: 12:29:27 pm
Waiting for phone… (Ctrl+C to stop)
With --no-e2e the machine prints a short code instead, and that is the only
artifact that pairs a plaintext session:
Code: 7K9P-2M4X
Plaintext: the relay can read this session.
Identity
Every session reports three things, and the app shows all three:
| Example | Why | |
|---|---|---|
| Session | studio-mac, or whatever --name says | distinguishes two runs on the same machine |
| Machine | studio-mac (hostname) | distinguishes two computers |
| Folder | ~/projects/my-app | distinguishes two checkouts of the same repo |
--name changes only the session label; the hostname is always reported
separately, so naming a session api still tells you it is on your laptop.
The folder is collapsed against your home directory, so the relay never sees
your account name.
What it does
- Finds or starts
opencode serveon 127.0.0.1 only, with a fresh random password held in memory. - Asks the relay for a pairing and prints the pairing artifacts. In an
end-to-end run those are the QR and the pairing link, and they carry a
one-time secret, so they are printed only when stdout is a terminal; piped or
logged output says they are hidden (phones paired earlier still connect). No
short code is printed: a code names the pairing at the relay and carries
none of the material an encrypted session needs, so a phone that typed one
would pair in plaintext and every command would be refused. In
--no-e2emode the code is what the machine prints, because that is what pairs a plaintext session. The relay has no accounts, so it first hands the agent a small proof-of-work challenge (about a second of CPU, solved in short slices so Ctrl+C stays responsive) and the agent sends the answer with its request. - Bridges your phone's commands to OpenCode and streams the results back.
- Cleans up on Ctrl+C: ends the session on the relay and stops the server it started.
No ports are opened on your network. The agent only ever makes outbound connections, so it works behind NAT, CGNAT and corporate egress proxies.
Flags
| Flag | Meaning |
|---|---|
-n, --name <name> | Label for this session in the app. Defaults to the hostname. |
-r, --relay <url> | Relay to use. Defaults to the deployed CodeReach relay. |
-a, --attach <url> | Attach to an OpenCode server you already started. Requires OPENCODE_SERVER_PASSWORD. |
-y, --yes | Skip the first-run consent prompt. CI only: a non-TTY stdin is treated as a decline, so this exists for scripting rather than convenience. It never enables commands from the phone. |
--e2e-auto-accept | End-to-end runs: pair the first phone that scans the QR without typing its six digit code. Anyone who sees the QR can then pair, so it is refused when stdout is not a terminal unless CODEREACH_E2E_SHOW_PAIRING_IN_PIPE=1 is set. --yes does not imply it. |
--no-e2e | Plaintext mode: the relay can read the session. Use it for an existing plaintext pairing, or if end-to-end encryption is too slow, at your own risk. The default is end to end encrypted. |
-v, --verbose | Debug detail to stderr. |
Environment
| Variable | Meaning |
|---|---|
OPENCODE_URL | Use this OpenCode server instead of spawning one. |
OPENCODE_SERVER_PASSWORD | Password for an external OpenCode server. Required with OPENCODE_URL / --attach. |
CODEREACH_RELAY_URL | Same as --relay. |
CODEREACH_HOME | Where the consent record, the agent identity (identity.json) and the trusted phones (devices.json) live. Defaults to ~/.codereach. |
CODEREACH_E2E_SHOW_PAIRING_IN_PIPE | 1 prints the end-to-end pairing QR and link even when stdout is not a terminal, and allows --e2e-auto-accept there. Set it only when that output is private to you: whoever reads it can pair. |
What it can and cannot do
By default the phone can send prompts, answer permission requests, abort a turn, manage sessions (create, rename, pin, delete, switch model and mode), read diffs and files inside your projects, and pull screenshots and other media the session produced. A destructive action still has to pass through OpenCode's own permission system, which surfaces on the phone as a blocking approve/deny sheet.
Running commands from the phone is off. It is remote code execution, so the
first time a phone asks, codereach asks YOU in this terminal
(Allow commands from your phone? [y/N]). Until you answer yes the phone is told
to approve on the computer. --yes does not enable it. The answer is recorded in
~/.codereach/shell-consent.json; delete that file to turn it off again. Commands
only start inside one of your project folders.
Optional tools
Nothing below is required; each feature switches on when the tool is on PATH.
| Tool | Used for |
|---|---|
sips (ships with macOS) | JPEG thumbnails of images |
ffmpeg | WebP and video thumbnails, 720p renditions, poster frames, markup compositing |
ffprobe | video size and length |
lsof | the ports a dev server is listening on |
Logging
Structured JSON to stderr; stdout is reserved for the banner so the QR code
stays scannable. File contents are never logged, prompt text is never logged at
info, and tool output is truncated before it reaches a log line.
Attribution
Not affiliated with or endorsed by the OpenCode project. See the repository
LICENSE and docs/SECURITY.md.