Witbitz docs HomeTrustAll docs

Code — run OpenCode on your computer from the Spaces app

Status: Beta. The Code section ships in the Spaces app; your computer joins through a single-file download (witbitz-code.mjs, Node ≥ 22). Open source: the connector, pairing, frame codec and relay are on GitHub at github.com/witbitzchat/witbitz-code. A pipx install witbitz-code package on PyPI is built but not yet published.

The Code section (Spaces → ☰ → Code) is a chat for OpenCode, the open-source coding agent — running on your own computer, against your own code, reached from any device you are signed in on: your phone on cellular, a tablet, another laptop. There is no server address to type, no port to open, no VPN to run.

How it works

code
 your phone (Spaces app → Code)                                   your computer
 ┌─────────────────────────┐                                      ┌────────────────────────────────┐
 │ the Code page           │                                      │ opencode serve   127.0.0.1:4096│
 │ seals every request     │                                      │        ▲ local HTTP only       │
 └───────────┬─────────────┘                                      │ witbitz-code (the connector)   │
             │  wss — dials out                                   │ opens and answers each request │
             ▼                                                    └───────────┬────────────────────┘
   ┌──────────────────────────────────────┐          wss — dials out          │
   │ wss://code-relay.witbitz.chat        │◂──────────────────────────────────┘
   │ forwards sealed frames it cannot read│
   └──────────────────────────────────────┘
  1. OpenCode stays on your computer, listening on 127.0.0.1 only. Nothing on the network can reach it.
  2. The connector (witbitz-code serve) runs beside it and makes one outbound WebSocket to the relay. It does for the Code page what a browser on the same machine would do — calls OpenCode's API and streams its events back.
  3. The Code page opens its own outbound WebSocket to the same relay channel. Every frame in both directions is encrypted end to end (AES-256-GCM) with keys only your devices and your computer hold.
  4. The relay — a Witbitz service — pairs the two sockets and forwards bytes it cannot read.

The keys

Pairing creates one random 32-byte secret for this computer and your account. From it, both ends derive (HKDF-SHA-256) the relay channel name and two encryption keys, one per direction. The secret is stored in two places only:

The OpenCode password (OPENCODE_SERVER_PASSWORD) never leaves the computer; the connector adds it locally.

What Witbitz can and cannot see

sees cannot
The relay a random channel name, IP addresses, when sockets connect, frame sizes and timing read or alter any request, response or event; replay a frame
The account store that your encrypted index changed read your computers, their names or secrets
Anyone who guesses nothing nothing join a channel usefully — the name is 256 bits derived from the secret

Two honest limits:

What the connector will do

Only what the Code page itself does: list, open, create, rename, archive and delete sessions; send a turn, stop it, undo or redo it, and compact the conversation; answer a permission prompt or a question the agent asks; read the model, agent, command and skill catalogs, the agent's todo list, and which sessions are running; list folder names under your home folder for a new session; save the files a turn carries and hand them back to your devices (below); show the files a reply produced inside that session's folder; say which suggested tools are installed; keep the record of which sessions you have seen; run Auto mode's checks; and follow the event stream. Any other OpenCode API call is refused on your computer, before it reaches OpenCode — OpenCode can run shell commands, so a leaked secret must not unlock more than the page can. That includes OpenCode's own route for running a /command, which executes shell snippets from the typed arguments without asking: the Code page fills a command's template in itself and sends it as an ordinary message instead.

You don't have to take this on trust. The allowlist is ALLOW in spaces/public/codeRelay.js in the public repository, and the connector that enforces it is tools/opencode-connector.mjs in the same place.

The page's egress lock lists wss://code-relay.witbitz.chat — a Witbitz origin. Your computer's address never appears in the app, and no third-party host is added to the allowlist.

With Hide my IP on (Settings), the app's own traffic goes through OHTTP, but the relay connection is a plain WebSocket that would see your device's IP address. The Code section therefore asks before it connects, and remembers a yes on that device while the setting stays on.

Set it up

You need a computer that stays on while you work (macOS, Linux, or Windows with WSL), and the Spaces app on your phone.

Before you start — the accounts. You pay these companies directly; Witbitz is not in the middle.

1. Install Node.js

Download the LTS installer from nodejs.org and run it (on a Mac you can also use brew install node). witbitz-code needs Node.js 22 or newer.

2. Paste one line in Terminal

Open Terminal on that computer (on a Mac: press ⌘-Space, type Terminal, press Enter; on Windows: open your WSL Ubuntu window). Paste this line and press Enter:

bash
curl -fsSL https://app.witbitz.chat/code.sh | bash

It checks that Node.js is there, downloads witbitz-code.mjs into your home folder, checks the file against the app's asset manifest — the list of file hashes the signed build certificate commits to — and starts the setup. A file that does not match is never used. The script is short and readable at app.witbitz.chat/code.sh. Paste the same line again later to update.

The setup walks through five steps and skips whatever is already done, so it is safe to run again:

  1. OpenCode — if it is not installed, it offers to install it without sudo: with npm where npm can install for you, otherwise with OpenCode's own installer (curl -fsSL https://opencode.ai/install | bash, into ~/.opencode).
  2. Pairing — a QR code appears. On your phone, open Spaces → Settings → Back up & recovery → Add a device and scan it. The QR carries no secret — your phone seals your account to a one-time key the computer just made.
  3. Your TrustedRouter key — paste it (it shows as *****). TrustedRouter checks it, then it is saved in OpenCode's own credentials file (~/.local/share/opencode/auth.json, readable only by you), where opencode auth login would put it — never in OpenCode's environment, which every shell command the agent runs can read.
  4. Your Tinfoil key — optional; checked the same way, then saved in ~/.opencode-server.env (readable only by you).
  5. Keep it running — it offers to start witbitz-code now and whenever you log in: a systemd user service on Linux, a launch agent on macOS. Where that is not available (WSL without systemd), or if you say no, it runs in the terminal window — keep it open.

If an OpenCode you started yourself is already running, setup asks you to close it first: only an OpenCode that witbitz-code starts sends its TrustedRouter calls through the confidential-model checks.

3. Open Code

On your phone: Spaces → ☰ → Code. Your sessions appear; open one, or start a new one, and type.

Other ways to get witbitz-code

Download it yourself and check the hash. The file ships inside the Spaces app build, so its hash is in the app's asset manifest:

bash
curl -fsSLo witbitz-code.mjs https://app.witbitz.chat/downloads/witbitz-code.mjs
curl -s https://app.witbitz.chat/assets-manifest.json | python3 -c 'import json,sys; print(json.load(sys.stdin)["files"]["downloads/witbitz-code.mjs"])'
shasum -a 256 witbitz-code.mjs       # (Linux: sha256sum) — the two must match
node witbitz-code.mjs setup

Build it from source. The file is built with a pinned esbuild from github.com/witbitzchat/witbitz-code:

bash
git clone https://github.com/witbitzchat/witbitz-code && cd witbitz-code
npm ci && npm run build              # → spaces/public/downloads/witbitz-code.mjs
shasum -a 256 spaces/public/downloads/witbitz-code.mjs      # (Linux: sha256sum) — compare with the manifest line above

Its hash matches the manifest when the repository and the live app are the same release — the repository's latest commit message names the release and its hash.

Python instead of Node? A second, independent implementation of the same protocol installs from the repository: pipx install "git+https://github.com/witbitzchat/witbitz-code#subdirectory=packages/witbitz-code-py", then follow Step by step below with witbitz-code in place of node witbitz-code.mjs (it has no setup yet). It does not yet have the confidential-model enforcement or the text copies of attached documents, so the Node file is the recommended one.

Step by step, without setup

The same thing one command at a time, in the folder that holds witbitz-code.mjs (your home folder, if the line above downloaded it):

bash
npm install -g opencode-ai                       # or:  curl -fsSL https://opencode.ai/install | bash
opencode auth login                              # choose TrustedRouter (or any provider) and paste its API key
node witbitz-code.mjs pair --name "my laptop"    # scan the QR code with the Spaces app
node witbitz-code.mjs tinfoil-key                # optional; the key shows as *****
node witbitz-code.mjs serve                      # starts OpenCode on 127.0.0.1:4096, then the connector — leave it running

To start it with the computer afterwards: node witbitz-code.mjs service install.

Everyday use

Commands run them in your home folder, where the one-line install saved witbitz-code.mjs (a new Terminal window starts there)
See what is paired node witbitz-code.mjs status
Computer offline the Code page says "my laptop is offline — start it" and reconnects by itself when it returns
Several computers pair each one; a picker at the top of the Code list chooses which computer you are looking at
Remove a computer from the phone: Code → Settings → Remove; or on the computer: node witbitz-code.mjs unpair
New secret node witbitz-code.mjs rotate, then restart serve — devices pick up the new secret on their next sync
Background service node witbitz-code.mjs service status (it prints where the logs are); service uninstall stops it starting with the computer
Update paste the install line again — it downloads the new file and runs setup, and the background service switches to it
Another port node witbitz-code.mjs setup --port 4097 — for a second OpenCode, or when 4096 is someone else's; an existing pairing moves with one question, no new scan
Uninstall node witbitz-code.mjs uninstall — see below

Removing witbitz-code

bash
node witbitz-code.mjs uninstall

No witbitz-code.mjs any more? The install line can fetch it and run uninstall instead of setup: curl -fsSL https://app.witbitz.chat/code.sh | bash -s uninstall.

It lists what it will remove and asks first: the background service, this computer from every account it is paired with (your devices stop showing it), ~/.witbitz/code (the pairing secrets, the files you attached in Code, the Auto-mode log), the project-notes plugin if it is installed, and the downloaded witbitz-code.mjs itself. Then it asks — Enter keeps each — whether to also remove:

Your projects always stay. If the computer is offline, uninstall still removes everything here and tells you to remove the computer from your phone (Code → Settings → Remove). For scripts: uninstall --yes, plus --remove-keys, --remove-notes, --remove-sessions, --remove-opencode for each of those.

In the Code section

What the agent is doing

A reply's work — its commands, file reads and edits, and what it wrote between them — collapses into one line. While it runs, the line says what it is doing now and for how long ("✶ Running a command · 1m 12s"), or "Waiting for your approval" when a card is up; afterwards it sums up ("Ran 3 commands, created 5 files · 2m 35s"). Tap it for an outline: a line per group of steps, with the agent's note before each one; tap a step for its command, output or diff. The answer itself stays outside, in full, once the reply is done.

The agent works quietly. When something comes up that you should know before it finishes — a significant discovery, a change of direction, a limit it ran into, a decision where your view helps — it tells you in a short update that stands in the conversation, and its work carries on in a new line under it. A short task has none; a long one a few.

When the agent starts agents of its own (to explore a codebase in parallel, say), each one gets a card in the conversation, and the agents glyph at the top right lists them with a count. Tap one to read its own conversation, live; ‹ takes you back.

Modes: Manual · Accept edits · Plan · Auto

The chip beside the model sets how much the agent may do without asking (Shift+Tab in the message box steps through it):

Tools for the agent

The first time a computer connects, a card at the top of the session list names the tools it is missing that coding and document work often needs — Git, ripgrep, jq, Python 3, uv, ffmpeg, Whisper, Poppler, qpdf, Tesseract, Pandoc and ImageMagick — each with a line on why. Untick what you do not want, then Install with the agent: it opens a Set up this computer conversation in your home folder in Manual mode, so every command asks. It never runs sudo: on Linux, the packages that need admin rights are one line on the card for you to copy and run. Not now is remembered for that computer; Settings → Tools checks again. The setup conversation tidies itself away once everything it was asked for is installed.

Files you attach

Tap + to attach a file (up to 5 MB each): a PDF, a Word or Excel file, an image, text. The way it reaches the agent is the way a coding agent on your own machine works — the file is saved on your computer and the agent reads it there:

  1. witbitz-code saves it under ~/.witbitz/code/attachments/<session>/ (readable only by you) and puts a note in your message saying where it is. Apart from an image (which a model that can see still gets directly), OpenCode never receives the raw file — it cannot take Word or Excel files at all.
  2. For a PDF, Word or Excel file it also writes a text copy beside it, made by Tinfoil's attested document reader with your own Tinfoil API key (node witbitz-code.mjs tinfoil-key). The note tells the agent to read that copy. Without a key there is no copy; the agent reads what it can and asks before converting anything.
  3. The agent may read that session's folder without asking; any other folder outside your project still asks.
  4. Your message shows the file as a chip — a preview for a PDF or an image. Tap it: the download glyph saves it (on a phone, the share sheet — Save to Files, AirDrop…; on a computer, a plain download), and View text shows the copy the agent read.

Saved files are deleted with their session, or after 30 days unused. Limits: 25 MB per file, 200 MB per session, 2 GB in all. (The Python package saves files but makes no text copies yet.)

Files a reply produces

When a reply creates or changes a file — a PDF, a picture, a spreadsheet — it shows under the answer, without asking: a PDF's first page, the picture, the first lines of a table or a text; a Word, Excel or PowerPoint file, an archive or a recording as a chip. Code files show nothing here (their changes are in the steps). Tap one to see it large, with the download glyph and ✕ at the top.

Confidential models

With TrustedRouter connected in OpenCode, the model menu includes models marked · confidential (DeepSeek V4 Flash, Kimi K3, GLM 5.3, GPT-OSS 120B, Gemma 4 31B, Llama 3.3 70B). The mark is enforced on your computer, not asserted: OpenCode's TrustedRouter calls go through witbitz-code on 127.0.0.1, and for these models it

  1. sends the request only after TrustedRouter's gateway proves (Google Confidential Space, Intel TDX) that it runs its published code, and with TrustedRouter's hard floor min_privacy: confidential — never routed to an ordinary endpoint;
  2. checks TrustedRouter's signed receipt for that exact request and answer: which enclave served it and that the gateway verified that enclave. A receipt that does not verify ends the step with "not confidential: …" — no tool runs on it;
  3. reads images and documents a confidential model cannot see through Tinfoil's attested enclave: witbitz-code verifies Tinfoil's AMD SEV-SNP evidence and pins the connection to the attested key before a byte is sent, then gives the model the text. This uses your own Tinfoil API key:

bash node witbitz-code.mjs tinfoil-key # the key shows as *****; stored in ~/.opencode-server.env, readable only by you

Without a key, an image for a confidential model is not sent anywhere; the turn stops with a message saying so.

  1. asks once more when a proof lapses mid-answer. TrustedRouter renews its proof of each model's enclave every 15 minutes; an answer still being written when that proof expires gets a receipt that fails the time check. Only that failure is retried — a fresh request, checked from the start — and only when every other part of the receipt holds. Words already on your screen stay; from the new answer only its actions are used, so nothing appears twice. An answer that starts close to the end of a proof is held back until its receipt verifies, so a lapse there is retried unseen. A second lapse, or any other fault, still stops the step.

While a confidential model works, the working line says what it is doing even when nothing has reached the conversation yet: "Kimi K3 is reading the conversation" (a long conversation can take minutes before the first word), "Writing report.pdf · 12 KB" (a file being written is held back until its receipt verifies), "Checking TrustedRouter's receipt", or "TrustedRouter renewed its proof — asking again".

You pay TrustedRouter and Tinfoil directly with your own keys; Witbitz is not on this path. Other models pass through unchanged. (The Python package does not do this yet — use the single file for confidential models.)

More than one account on one computer

Each account pairs separately (run pair again and scan with the other account). OpenCode has no users: every account paired to the same OpenCode sees the same sessions, files and shell. That is fine for your own work and personal accounts. For another person, run a separate OpenCode — another port, ideally another operating-system user — and pair it on its own: node witbitz-code.mjs pair --port 4097, then node witbitz-code.mjs serve --port 4097.

Troubleshooting

you see means do
"… is offline — start it" the connector is not running (or the computer is asleep) run node witbitz-code.mjs setup on it (it starts it, and offers to keep it running)
"OpenCode is not answering at http://127.0.0.1:4096" the connector runs, OpenCode does not start OpenCode, or let serve start it
"pair your computer" this account has no paired computer paste the install line on the computer (step 2)
the QR expires the scan did not complete within 5 minutes run pair again — nothing changed in the meantime
the QR in the terminal looks broken or will not scan the terminal's font draws the half-block characters badly WITBITZ_QR=large node witbitz-code.mjs setup draws it the old, larger way — or open the .svg file it names and scan that
"OpenCode is not installed" opencode is not on PATH run setup — it offers to install it
"… (confidential) cannot read images or files, and this computer has no Tinfoil key" a confidential model was sent a picture node witbitz-code.mjs tinfoil-key, or pick a model that can see
"not confidential: TrustedRouter's receipt … did not verify" the proof for that answer failed; it was not used, and no step it asked for ran send again, and tell us if it repeats — a lapsed proof is retried by itself, so this is another fault
"… TrustedRouter renewed its proof of the model's enclave while this answer was on its way …" a long answer outlived its proof twice, or its receipt had another fault too send your message again — a fresh proof lasts 15 minutes
"… hasn't started answering after 60 s" the model is queued or unavailable (the working line says so instead when a confidential model is just reading a long conversation) wait, stop it, or pick another model
" runs an older witbitz-code" the computer's connector predates a feature the page uses (tools, previews, the seen record) paste the install line on that computer again (step 2)
a reply made a file but shows no card under it the file is outside the conversation's folder, or the computer runs an older witbitz-code move it into the folder, or update witbitz-code
"Code asks before connecting" with Hide my IP on the relay would see this device's IP address connect anyway on this device, or turn Hide my IP off
"That file is no longer on the computer" its session was deleted, or it was unused for 30 days attach it again
"To open saved files from here, update witbitz-code" the computer runs an older witbitz-code paste the install line again (step 2)
no · confidential models in the menu OpenCode was already running when serve started, or TrustedRouter is not connected close that OpenCode and run node witbitz-code.mjs setup — it connects TrustedRouter and starts OpenCode the right way
"Port 4096 is taken by a program that is not yours" during setup something else listens there — on WSL, another Linux distro's OpenCode (distros share ports) say yes to the free port it offers; the pairing moves with it, no new scan
"Already running on 127.0.0.1:4096: opencode …" during setup an OpenCode you started yourself let setup stop it — witbitz-code must start OpenCode itself for the confidential-model checks
"This computer is paired, but for OpenCode on another port" the pairing points at a different OpenCode port than setup used run setup with that port, or setup --port <n> and say yes to moving it
"Background start is not available" during setup WSL without systemd, or another system keep the window open, or turn systemd on in WSL (/etc/wsl.conf: [boot] systemd=true, then wsl --shutdown) and run setup again

Reference

Machine-readable source: opencode.md · Generated 2026-09-15T00:47:25Z · build ee26fb8a · every doc in one fetch: llms-full.txt (HTML) · ← sdk · embed