▼ @textjs/opencode
A local bridge that lets textjs.org/opencode talk to the OpenCode server on your machine. It has no dependencies and runs on Node 22+ or Bun.
bunx @textjs/opencode@latest proxy # or: npx @textjs/opencode@latest proxy
Leave it running, open textjs.org/opencode, enter http://127.0.0.1:4096 and press Connect. Allow local network access if Chrome asks, then enter the pairing code the bridge printed. Stop the bridge with Ctrl+C.
Browsers block a public page from reading http://127.0.0.1 unless the server answers CORS and Private Network Access preflights, and the OpenCode service also requires a password. The bridge adds the headers and the password, and forwards each request to OpenCode.
Options
| Flag | Description |
|---|---|
-p, --port <port> | Port to listen on (default 4096) |
--target <url> | OpenCode server URL. Skips discovery. |
--allow-origin <origin> | Allow another browser origin, such as https://example.com or http://localhost:*. Repeatable. |
-v, --version | Print the version |
-h, --help | Show help |
Without --target, the bridge asks opencode service status for the managed service and reads its password with opencode service get password. It caches the result for 30 seconds and looks again when OpenCode answers 401 or can't be reached, so it follows a service that restarts with a new port or password.
For a foreground server, pass its URL. If the server already uses port 4096, run the bridge on another port and connect the page to that port:
bunx @textjs/opencode@latest proxy --port 4097 --target http://127.0.0.1:4096
For a password-protected foreground server, export OPENCODE_PASSWORD in the bridge's shell. OPENCODE_SERVER_URL works in place of --target.
Security
- Loopback only. The bridge listens on
127.0.0.1and answers only requests whoseHostislocalhost,127.0.0.1or[::1], which blocks DNS rebinding. - Origin allowlist. Browser requests must come from
https://textjs.org,https://www.textjs.orgorhttp://localhost,127.0.0.1and[::1]on any port. Other origins get a403. Add your own with--allow-origin, for example--allow-origin 'https://*.textjs-avg.pages.dev'for textjs.org preview deployments. - Pairing code. The bridge prints a code when it starts. A page sends it once to
POST /__bridge/pairand gets a token for this bridge run, tied to that page's origin. Apart from CORS preflights andGET /__bridge, every request needs the token inx-textjs-bridge-token, or it gets a428and never reaches OpenCode.GET /__bridgesays whether the page is paired, and a wrong code gets a200withok: falseand the attempts left, so the page never logs a failed request. Ten wrong codes lock pairing until the bridge restarts. Every response carriesx-textjs-bridge-pairing: 1, so the page refuses 1.0.x bridges, which forward without a code. - The password stays on your machine. The bridge keeps it in memory and adds it to requests it forwards to OpenCode. It isn't logged, written to disk or sent to the browser.
- Writes need a custom header. Requests other than
GET,HEADandOPTIONSmust sendx-opencode-client: 1, a header HTML forms can't set. Request bodies are capped at 4 MB. - Only a few headers cross. The bridge forwards
Accept,Content-Type,Last-Event-IDandx-opencode-directoryto OpenCode, and returnsContent-Type,Locationandx-request-id. Cookies,Authorizationand the pairing token from the browser are dropped. - Passwords apply to loopback targets only.
OPENCODE_PASSWORDis never sent to a non-loopback--target, and--targetrejects URLs that contain credentials, a path, a query or a hash (TX021).
Text.js projects
@textjs/app runs the same bridge as text proxy, with the same flags.
Errors
| Code | Meaning |
|---|---|
| TX021 | --target isn't a bare HTTP(S) origin |
| TX022 | --port is the OpenCode server's own port |
| TX023 | A bridge is already running on that port |
| TX024 | Something else is using that port |