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. Apipx install witbitz-codepackage 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
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│
└──────────────────────────────────────┘- OpenCode stays on your computer, listening on
127.0.0.1only. Nothing on the network can reach it. - 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. - 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.
- 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:
- on your computer, in
~/.witbitz/code/pairings.json(readable only by you), and - in your account's encrypted index, which is how every signed-in device learns about the computer — the same end-to-end encrypted sync that carries your Spaces list. Witbitz stores it sealed and cannot read it.
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:
- Your prompts and code go to the model you configure in OpenCode, from your computer. That is OpenCode's own traffic, outside Witbitz. Pick a confidential model (below) and that request is provably handled inside attested hardware; any other model is an ordinary API call to its provider.
- The relay sees metadata (who is online, when, how much). It is the same class of metadata the rest of the platform sees; the content is the part that is sealed.
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.
- TrustedRouter runs the AI models, including the · confidential ones. Sign in with Google or GitHub, add credit, and create an API key at trustedrouter.com/console/api-keys. (Already use another provider in OpenCode? Code works with any model OpenCode has — only the confidential models need TrustedRouter.)
- Tinfoil — optional. It lets confidential models read images, and makes readable copies of the PDF, Word and Excel files you attach. Create a key at dash.tinfoil.sh (API keys).
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:
curl -fsSL https://app.witbitz.chat/code.sh | bashIt 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:
- 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). - 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.
- 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), whereopencode auth loginwould put it — never in OpenCode's environment, which every shell command the agent runs can read. - Your Tinfoil key — optional; checked the same way, then saved in
~/.opencode-server.env(readable only by you). - Keep it running — it offers to start
witbitz-codenow 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:
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 setupBuild it from source. The file is built with a pinned esbuild from github.com/witbitzchat/witbitz-code:
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 aboveIts 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):
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 runningTo 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
node witbitz-code.mjs uninstallNo 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 saved keys: the Tinfoil key, and the TrustedRouter key in OpenCode's credentials;
- your project notes in
~/.local/share/witbitz-notes(they exist only where the notes plugin was installed); - all OpenCode sessions on this computer — every conversation, from Code and from the OpenCode app, since they share
one store (
~/.local/share/opencode). Your provider logins stay. If an OpenCode on this computer still has them open (a terminal running it, or the OpenCode app), it names it and offers to stop it first. - OpenCode itself. It says how OpenCode was installed and removes it that way: OpenCode's installer (
~/.opencode, plus the# opencodePATH line it added to your shell's startup file — the file is kept as<file>.before-witbitz-uninstall), npm (npm uninstall -g opencode-ai; if that needssudo, it tells you the command instead of running it), or Homebrew. Open a new terminal window afterwards.
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
- Type / in the message box for your OpenCode commands and skills, plus /undo, /redo and /compact. Undo takes back your last turn — and, in a git project, the file changes it made; Redo brings them back until you send a new message.
- When the agent asks you a question, or asks permission for a step, the card appears above the composer. A card you missed — asked while the app was closed, or on another device — comes back when you return.
- The agent's todo list shows as one line above the conversation; tap it to see every item.
- The bar shows the conversation's title and, under it, the folder it works in (
~for your home folder). - A spark next to a session in the list means it is running a turn right now, wherever it was started. A green dot means activity you have not seen yet (on ☰ too, while the list is closed). What you have seen is kept on your computer, so a conversation you read on your phone is not news on your laptop.
- Long conversations open on their newest messages; scroll up, or tap Load earlier messages, for the rest. Switching back to a conversation you opened before is instant.
- Write in Hebrew and the agent narrates and answers in Hebrew.
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):
- Manual — every permission the agent asks for is a card.
- Accept edits — file edits in the project go ahead; protected files (
.env, keys,.git/) and everything else still ask. - Plan — the agent plans without changing anything (shown when OpenCode has a plan agent).
- Auto — a check on your computer answers each ask first. Plainly harmless steps (reading files,
git status, edits inside the project) pass at once; the rest is judged by the conversation's own model, which allows routine work, refuses what must not run — telling the agent why, so it tries something safer — and leaves anything unclear to you as a card. Nothing is ever allowed "always". Each decision is a 🛡 line under the step that asked (a refusal in red), and all of them are logged in~/.witbitz/code/auto-log.jsonl. It keeps working with your phone closed.
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:
witbitz-codesaves 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.- 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. - The agent may read that session's folder without asking; any other folder outside your project still asks.
- 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.
- A card is a file the reply wrote with its file tools, or one its answer names — not files it only read, not scratch files it mentioned while working, and never a path from your own message. At most six per reply.
- Only files inside the conversation's folder, and never a name that looks like a secret (
.env, keys,.git/). Your computer refuses anything else before it touches the disk, sends at most 20 MB, and logs each file it hands out in~/.witbitz/code/output-log.jsonl— as a hash of the path, not the path. - A card says "changed since this reply" when the file was modified later, and "not on the computer any more" when it is gone.
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
- 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; - 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;
- reads images and documents a confidential model cannot see through Tinfoil's attested
enclave:
witbitz-codeverifies 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.
- 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 |
| " |
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
- Source: github.com/witbitzchat/witbitz-code — the connector and
pairing (JavaScript and Python), the frame codec, and the relay itself (
relay/relay.mjs). Build the download from it and its hash matches the one in the app's asset manifest for the same release (see Other ways to get witbitz-code). Issues and questions go there too. - Wire format, key derivation, replay protection and the relay's limits:
docs/opencode-relay.mdin the same repository.
Machine-readable source: opencode.md · Generated 2026-09-15T00:47:25Z · build ee26fb8a · every doc in one fetch: llms-full.txt (HTML) · ← sdk · embed →