Package Exports
- @lesquel/opencode-pilot/server
- @lesquel/opencode-pilot/tui
Readme
@lesquel/opencode-pilot
Remote control plugin for OpenCode — monitor sessions, send prompts, approve permissions, switch agents, and get notifications from your phone or any browser.
What it does
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).
Install once, use everywhere
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.
Quick Start
One command (recommended)
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.
Manual install (if you want to)
# 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.
How to use it day-to-day
Once installed and OpenCode restarted, you have four entry points:
1. Slash commands from inside OpenCode TUI
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 |
2. Terminal banner at startup
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).
3. The web dashboard itself
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.
4. From your phone
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.
Troubleshooting — slash commands don't appear
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.
Connect from your phone
Same WiFi (LAN access)
- 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
Anywhere (public tunnel)
- 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.
Features
Dashboard
- 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
Notifications
| 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 |
Mobile-friendly
- 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
Keyboard shortcuts
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 |
Configuration — two ways
Since v1.12 you can configure the plugin two ways (or both):
Easy: the Settings UI
- 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.
Power user: .env file
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) |
Priority (highest wins)
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 fallbackThe .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.
Documentation
- Configuration reference — every env var, with example
.envfiles for common scenarios - Connect from phone — LAN + tunnel setup
- Tunnel testing — end-to-end verification guide
- Publishing to npm — for contributors who want to publish forks
- Cloud relay v2.0 design — architecture doc for a future centralized service
- Production readiness — deployment-mode verdicts
Architecture (one line)
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.
Tech stack
- 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: 181 (Bun test runner), all green
Contributing
Issues and PRs welcome at github.com/lesquel/open-remote-control.
Local dev:
git clone https://github.com/lesquel/open-remote-control
cd open-remote-control/opencode-pilot
bun install
bun test
bun run typecheckTo test changes against your local OpenCode without publishing:
# In opencode-pilot/
bun pm pack # creates lesquel-opencode-pilot-X.X.X.tgz
# In a test project with opencode.json:
bun add /path/to/lesquel-opencode-pilot-X.X.X.tgzLicense
MIT © lesquel