Remote control plugin for OpenCode — monitor sessions, send prompts, approve permissions, switch agents, and get notifications from your phone or any browser.
Spin up OpenCode locally, get a web dashboard you can open on your laptop or phone. From the dashboard you can:
- See all your sessions across all your projects (multi-project tabs)
- Send prompts and watch responses stream live
- Approve / deny tool permissions remotely
- Switch agents (build / plan / general / explore) per session
- Track cost per session, per day, per week
- Get sound, browser, push, and Telegram notifications when an agent finishes
- Pin TODOs that survive across sessions
- Diff view of all files changed
- File browser with live filter
- Connect from your phone via QR code (LAN or public tunnel)
All from one keyboard shortcut (? opens the command palette).
opencode-pilot installs globally into your OpenCode config dir (~/.config/opencode on Linux/macOS, %APPDATA%\opencode on Windows). One install and the plugin auto-loads every time you run opencode from any project directory — no per-project setup, no .opencode/ folders to copy around, no duplicate configs.
That's the whole point of running the installer: drop it in once, then just use opencode normally and the dashboard is always there.
npx @lesquel/opencode-pilot init
# or
bunx @lesquel/opencode-pilot initThat's it. The installer:
- Locates your OpenCode config dir (
~/.config/opencodeor your XDG path /%APPDATA%). - Installs the plugin there (
@lesquel/opencode-pilot@latest+@opencode-ai/plugin@latest). - Adds
"@lesquel/opencode-pilot@latest"to bothopencode.json::plugin(for the dashboard server) andtui.json::plugin(for the slash commands). OpenCode uses two separate plugin loaders — one file each. - Cleans up stale wrappers, cache entries, and legacy subpath specs left by earlier (<=1.12.x) installs.
Fully close any running OpenCode sessions and reopen. A toast should appear:
OpenCode Pilot — Remote control plugin loaded. Use
/pilotor/pilot-token.
The banner also prints in the terminal with URL + token + QR. If the toast doesn't appear, see Troubleshooting.
Configuration is done from the dashboard (gear icon → Plugin configuration). You do NOT need to create a
.env— though you still can if you prefer.
# 1. Install in your OpenCode config dir
cd ~/.config/opencode # or your XDG_CONFIG_HOME path
bun add @lesquel/opencode-pilot@latest @opencode-ai/plugin@latest
# 2. Add the spec to BOTH config files:
#
# opencode.json — registers the dashboard server plugin:
# {
# "plugin": ["@lesquel/opencode-pilot@latest"]
# }
#
# tui.json — registers the slash commands (create this file if missing):
# {
# "$schema": "https://opencode.ai/tui.json",
# "plugin": ["@lesquel/opencode-pilot@latest"]
# }OpenCode runs two separate plugin loaders: the server loader reads opencode.json::plugin (for server() exports like the dashboard), and the TUI loader reads tui.json::plugin (for tui() exports like slash commands). A spec in only one of them gets you half the plugin. Do not add wrappers in <config>/plugins/ — they conflict with the server loader's strict validation.
Run opencode from any project directory. Banner prints with URL + token + QR.
Going deeper?
docs/INSTALL.mdexplains OpenCode's two-loader plugin architecture, documents every gotcha we hit, and has a complete troubleshooting matrix. Read it if you're debugging, contributing, or publishing your own OpenCode plugin.
Once installed and OpenCode restarted, you have four entry points:
Type any of these at the prompt:
| Command | What it does |
|---|---|
/pilot |
Show the current dashboard URL + token |
/pilot-token |
Rotate the auth token (invalidates any open dashboards/phones) |
/dashboard |
Same as /pilot — alias |
/remote |
Print connection info (host, port, tunnel URL if active) |
/remote-control |
Full status block with all the above plus QR hint |
Every opencode launch prints a banner with the dashboard URL, a token, and a QR code. Open the URL in any browser on the same machine, or scan the QR with your phone (requires PILOT_HOST=0.0.0.0 for LAN — see below).
Open the URL. Then:
?opens the command palette — every action is reachable from there.- Left sidebar: sessions grouped by folder. Click one to switch.
+creates a new session. - Center pane: the live transcript — user messages appear instantly, the assistant's response streams token by token, tool calls animate pending → running → completed with their inputs and outputs expandable inline.
- Right panel: context usage, MCP servers, LSP status, project path, pinned TODOs.
- Tabs at the top: open multiple projects in parallel (v1.11+).
- Gear icon (⚙): Settings UI — change port/host, enable tunnel, wire up Telegram bot, generate VAPID keys for Web Push. Everything is editable from here; you rarely need to touch
.envfiles anymore.
See Connect from your phone below. Either same-WiFi (LAN) with PILOT_HOST=0.0.0.0, or anywhere with a Cloudflare / ngrok tunnel. The dashboard is mobile-friendly — bottom-sheet modals, 44px tap targets, swipe-to-close. Permissions approvals, prompt sending, session switching all work from the phone.
Other symptoms (port conflicts,
/remotesays "server not running", dashboard 401s, push/telegram silent, wrong tab opens) are covered indocs/TROUBLESHOOTING.md. This section is specifically about the "I installed it but slash commands are missing" case.
If the plugin loads (you see the banner in the terminal) but typing /remo<Tab> in the TUI doesn't autocomplete /remote, /dashboard, /pilot, /pilot-token, or /remote-control:
-
Check the canary toast. On startup the TUI should toast "OpenCode Pilot — Remote control plugin loaded". No toast → the TUI plugin didn't register, even if the dashboard server did.
-
Inspect the latest log:
tail -200 $(ls -t ~/.local/share/opencode/log/*.log | head -1) | grep -iE "pilot|tui.plugin|error"
- Windows:
%LOCALAPPDATA%\opencode\log\<timestamp>.log - macOS:
~/Library/Logs/opencode/<timestamp>.log
- Windows:
-
Re-run init. It cleans up stale wrappers and stale subpath entries left behind by 1.11.x–1.12.x:
npx @lesquel/opencode-pilot@latest init
Then fully close all OpenCode sessions (the plugin loader is cached per running process) and reopen.
-
Verify BOTH
opencode.json::pluginANDtui.json::plugineach have exactly one entry for the pilot:"plugin": ["@lesquel/opencode-pilot@latest"]
If
tui.jsonis missing entirely, the TUI plugin was never registered and slash commands never appear —initin 1.13.1+ creates this file. If you see"@lesquel/opencode-pilot/tui"anywhere or wrapper paths in the array, the old install pattern got in —npx initin step 3 fixes that. -
Verify no stale wrappers in
~/.config/opencode/plugins/. Safe to delete:opencode-pilot.ts,opencode-pilot-tui.ts. These were auto-generated by 1.11.x–1.12.x and are no longer needed.initdoes this cleanup automatically.
- Set
PILOT_HOST=0.0.0.0in.env - Restart OpenCode
- Click the phone icon in the dashboard header (
cshortcut) - Scan the QR from the "Local network" tab with your phone camera
- Done — same dashboard, on your phone
- Install cloudflared (
brew install cloudflaredon Mac) - Add
PILOT_TUNNEL=cloudflaredto your.env - Restart OpenCode — it spawns the tunnel automatically
- Phone modal "Public tunnel" tab now has a QR with a public HTTPS URL
- Works from cellular, hotel WiFi, anywhere
🔐 Security note: the tunnel URL contains your token. Treat it like a password. See
docs/TUNNEL_TESTING.mdfor security checklist.
- Sessions sidebar with folder grouping and agent filter
- Multi-view to watch multiple sessions side-by-side (desktop only)
- Project tabs at the top — open multiple projects in parallel
- Right info panel with Context, MCP servers, LSP clients, project path, instance version
- Pinned TODOs survive across sessions and project switches
- Cost panel with per-session, daily, and weekly totals + budget alerts
- Diff tab showing all files changed in current session
| Type | Trigger | Setup |
|---|---|---|
| Sound | Page hidden + assistant turn complete | Toggle in Settings |
| Browser notification | Page hidden + permission granted | Toggle, allow when prompted |
| Push (Web Push) | Anywhere, even browser closed | VAPID keys in .env (guide) |
| Telegram | Permission requests + completions | Bot token in .env |
- Drawer sidebar with backdrop
- Full-screen modals
- Multi-view auto-hidden (use desktop for split view)
- Right panel as bottom toggle
- Live filter on file browser
- 44×44 touch targets everywhere
Single-key (when no input focused):
| Key | Action |
|---|---|
? |
Open command palette + show shortcuts |
n |
New session |
s |
Toggle sidebar |
m |
Toggle multi-view (desktop only) |
t |
Toggle theme |
c |
Connect from phone modal |
/ |
Focus prompt input |
Esc |
Close modal/picker/palette |
Modifier:
| Key | Action |
|---|---|
Cmd/Ctrl+K |
Command palette |
Cmd/Ctrl+Enter |
Send prompt |
Alt+I |
Toggle right info panel |
Since v1.12 you can configure the plugin two ways (or both):
- Open the dashboard
- Click the gear icon (⚙) in the header
- Go to Plugin configuration
- Edit port, host, tunnel, Telegram token, VAPID keys, permission timeout, and the glob opener toggle
- Click Save — values are written to
~/.opencode-pilot/config.jsonand survive restarts
Some fields (port, host, tunnel, VAPID keys) require an OpenCode restart to take effect — the UI shows an inline warning for those. A Generate VAPID keys button calls the server to create a key pair in one click.
Each field shows a small badge telling you where its current value comes from: saved (UI), .env, shell, or default. Fields set via shell env vars are locked from the UI — you must unset the shell var to override.
Full env-var reference with example .env files for common scenarios lives in docs/CONFIGURATION.md. Quick overview:
| Variable | Default | What |
|---|---|---|
PILOT_PORT |
4097 |
HTTP server port |
PILOT_HOST |
127.0.0.1 |
Bind address (0.0.0.0 for LAN) |
PILOT_TUNNEL |
(off) | cloudflared or ngrok for public access |
PILOT_PERMISSION_TIMEOUT |
300000 |
Permission-request timeout in ms |
PILOT_TELEGRAM_TOKEN |
(off) | Telegram bot token from @BotFather |
PILOT_TELEGRAM_CHAT_ID |
(off) | Your Telegram chat ID |
PILOT_VAPID_PUBLIC_KEY |
(off) | Web Push public key (bunx web-push generate-vapid-keys) |
PILOT_VAPID_PRIVATE_KEY |
(off) | Web Push private key |
PILOT_ENABLE_GLOB_OPENER |
false |
Enable /fs/glob for the dashboard's glob search |
PILOT_FETCH_TIMEOUT_MS |
10000 |
Timeout for outbound HTTP calls (Telegram, push) |
1. Shell env vars e.g. PILOT_PORT=5000 opencode
2. ~/.opencode-pilot/config.json written by the Settings UI
3. .env file (process.cwd or plugin dir) power-user file-based config
4. Hardcoded defaults fallback
The .env file is searched in: (1) process.cwd()/.env then (2) the plugin's install dir. Shell env vars always win over both .env and the Settings UI.
For users
- Troubleshooting runtime issues — start here if
/remotesays "server not running", the dashboard 401s, push/telegram isn't arriving, or the wrong tab opens - Install deep-dive — how OpenCode's two-loader plugin architecture works, every trap we've hit getting a clean install, full troubleshooting matrix
- Configuration reference — every env var, with example
.envfiles for common scenarios - Connect from phone — LAN + tunnel setup
- Tunnel testing — end-to-end verification guide
For contributors and AI agents
AGENTS.md— strict workflow for AI agents and humans editing the repo (hard rules, release process, debugging playbook, Engram protocol)CLAUDE.md— codebase overview, file map, routes, event types- Architecture — design decisions with rationale
- Release checklist — step-by-step execution guide for shipping a new version
- Publishing to npm — one-time npm account setup and scope choice
- Cloud relay v2.0 design — architecture doc for a future centralized service
- Production readiness — deployment-mode verdicts
The plugin runs ONE HTTP+SSE server inside your OpenCode process. The dashboard is a vanilla ES-modules SPA served from the same origin. All API calls go through the SDK with ?directory= for multi-project routing.
If you want the deep version, see the docs/ folder.
- Server: Bun + TypeScript (strict)
- Dashboard: vanilla ES modules, plain CSS, ~9000 LOC. No React, no build step.
- Optional deps:
cloudflared/ngrok(tunnel),@modelcontextprotocol/sdk(MCP),web-push(push notifications) - Tests: 228 (Bun test runner), all green
Issues are welcome. Pull requests are welcome only after maintainer approval.
Please start with an issue first: bug report, feature request, or discussion. The maintainer will triage it and either:
- fix it directly and comment on the issue,
- ask you to send a PR, or
- close it as out of scope / duplicate / needs more information.
Read CONTRIBUTING.md for the full issue-first workflow, PR rules, review expectations, contributor credit, and local development setup.
Maintainer notes for contributors:
- One PR per approved issue.
- Keep PRs small and reviewable.
- Add tests for behavior changes.
- Do not bump versions, edit
CHANGELOG.md, or publish releases. - Follow
AGENTS.mdfor codebase conventions: noany, no serverconsole.log, factory functions, release safety rules.
Security reports are private: see SECURITY.md. Community expectations are in CODE_OF_CONDUCT.md.
Shipping a new version is maintainer-only: follow docs/RELEASE.md. Pushing a vX.Y.Z tag is the only supported way to publish.
MIT © lesquel