MIT · Official plugin · OpenCode + Codex

Run your AI agents
from your
phone.

Web dashboard with multi-project tabs, live SSE streaming, Telegram and Web Push notifications, optional public tunnel, and a mobile PWA to follow your sessions from anywhere. Works natively with OpenCode and with Codex CLI via a hook bridge.

413 tests · TypeScript strict
~20k LOC · Hexagonal-lite
2 CLIs supported · more coming
~/my-project — opencode
● live
// 01 · The problem

Your AI agent runs perfectly while you stare at the terminal.

OpenCode (and Codex) are agents that live in your terminal. They work great while you sit in front of them — but the moment you walk away, everything stops. The agent asks for permission to run a command, no one is there to approve. You want to follow the run from your phone while doing something else, you can't. You open another project in another terminal and lose the thread.

📋

Approve permissions without sitting at the terminal

Dangerous tools need approval, but you're not there.

📱

Follow long sessions from the couch

From mobile, another laptop, or any device on your network.

🔄

Coordinate multiple parallel projects

Each one with its own tab. Switch context without losing sessions.

🔔

Get notifications when it's done

Web Push or Telegram — in real time, even if the dashboard is closed.

👥

Share the dashboard with teammates

Async pair programming behind a public tunnel.

🤖

One dashboard for several CLIs

OpenCode and Codex usually live in separate tabs — here they coexist.

// 02 · Multi-CLI

One dashboard. Two integrations. Add more in a folder.

The integrations/ layer implements a single AgentIntegration port. Each CLI lives in its own folder and the composition root wires it with one line. Adding a new agent (Cursor, Aider, etc.) is one folder + one line.

Native

OpenCode (SDK plugin)

Native integration via OpenCode SDK hooks. The plugin loads inside the TUI process: it sees session events, permission.ask, tool.start/end, and emits each one to the internal event bus.

integrations/opencode/
  ├── index.ts           # opencodeIntegration
  └── hooks/
      ├── event.ts
      ├── permission.ask.ts
      └── tool.ts
HTTP bridge

Codex CLI (hook bridge)

Codex has no plugin SDK — it uses HTTP hooks. The plugin exposes POST /codex/hooks/:event and Codex posts there. Same event bus, same dashboard, same permission queue.

# ~/.codex/config.toml
[hooks]
endpoint = "http://127.0.0.1:4097/codex/hooks"
token = "${PILOT_HOOK_TOKEN}"
MethodPathDescription
POST /codex/hooks/SessionStart Codex opened a session — emits pilot.session.started
POST /codex/hooks/UserPromptSubmit Prompt received — emits pilot.prompt.received
POST /codex/hooks/PreToolUse Tool about to run — emits pilot.tool.started
POST /codex/hooks/PostToolUse Tool finished — emits pilot.tool.completed
POST /codex/hooks/PermissionRequest Blocking — waits for your approve/deny on the dashboard
POST /codex/hooks/Stop Session ended — emits pilot.session.stopped
// 03 · Features

Everything your CLI does, accessible from the browser.

An HTTP+SSE server runs inside the plugin process. The dashboard is a vanilla ES-modules SPA served from the same origin — no React, no build, no latency.

Multi-project dashboard

Tabs for every project you opened with OpenCode or Codex. Switch context without losing sessions. Each tab remembers its own sessions, messages, and state.

Live SSE streaming

Every token shows up as the agent generates it. No reload, no polling, no latency. Real typewriter effect.

Remote permission approval

When the agent (OpenCode or Codex) asks permission, a banner appears on the dashboard — even on your phone. Approve or reject with one tap. If there's a queue, the 1/N counter shows up.

Command palette + shortcuts

Ctrl/Cmd + K opens the palette. Switch agent, model, provider, create a new session, switch project — all from the keyboard.

Settings UI

Configure port, host, tunnel, Telegram, VAPID keys, hookToken, timeouts and more from a modal. Saved atomically to ~/.opencode-pilot/config.json.

📂

File browser with glob

Explore the project tree, search by glob pattern with debounce, and open files to view contents (opt-in).

Web Push (VAPID)

Receive notifications even with the dashboard closed. Generate VAPID keys from Settings with a click.

Telegram bot

Configure token + chat ID (with hints in the dashboard). Get permission, error, and end-of-session alerts on Telegram.

!

Browser notifications

Uses the native Notifications API when permission is granted. Inline notifications without losing the session.

PWA & mobile support

Responsive interface. Install the dashboard as an app from the browser menu. Service worker with versioned cache and network change detection.

QR pairing

One QR for LAN, another for public tunnel. Pair in seconds from your phone.

🔗

Active-folder auto-focus

/remote opens the dashboard and automatically focuses the tab for the folder you're working in. If it doesn't exist, it creates it.

📝

Prompt from mobile

Send prompts, approve permissions, and switch agents as if you were at the desktop.

🌐

Optional public tunnel

PILOT_TUNNEL=cloudflared (or ngrok) and the plugin spins up a tunnel automatically, detects the URL, shows it on the dashboard, and renders a QR.

🪝

Codex hook bridge

POST /codex/hooks/:event receives Codex CLI's 6 lifecycle hooks. PermissionRequest is blocking until you approve from the dashboard.

🔄

Automatic token recovery

If OpenCode restarted and your token went stale, the "invalid token" screen lets you paste the fresh URL and extracts the new token by itself.

🔧

CLI with doctor & uninstall

bunx @lesquel/opencode-pilot doctor runs 6 health checks in <2 seconds. uninstall reverts the whole installation (optional --keep-config).

💡

Inline hints & validation

Each Settings field has a hint with where to get the value. On blur, validates basic format without blocking save.

// 04 · Live dashboard

A vanilla SPA that feels native.

Click on the sessions to see how the transcript changes. Everything runs from the same origin as the CLI process — zero artificial latency.

https://localhost:4097/?token=4a9c…
Connected · SSE
agent · build refactor SSE reconnect $0.42 · 12.4k tok
U
SSE reconnect goes wild when I switch networks. Can you add exponential backoff and an explicit pong?
A
Got it. Adding exponential backoff (1s → 30s max) and a 15s heartbeat with timeout. Editing src/dashboard/sse.ts.
edit src/dashboard/sse.ts completed · 142ms
-  const delay = 1000;
+  const delay = Math.min(30_000, 1_000 * 2 ** attempts);
+  scheduleHeartbeat(15_000);
bun test sse.test.ts running…
[sse] 4 pass · 0 fail · 0 skip
[runtime] ✓ reconnects with jittered backoff
[runtime] ✓ heartbeat fires every 15s
⌘↵
// 05 · How it works

Screaming Architecture · 8 layers with a single composition point.

Since v1.18.0, folders announce their purpose, not their technology. The dependency rule is strict: infra → core → (transport, integrations, notifications) → server. Adding a new CLI is one folder + one line in the composition root.

┌─────────────────────┐      ┌─────────────────────────┐
│   OpenCode TUI      │      │   Codex CLI (hooks)     │
│   (SDK plugin)      │      │   POST /codex/hooks/*   │
└──────────┬──────────┘      └──────────┬──────────────┘
           │                            │
           ▼                            ▼
   ┌──────────────────────────────────────────┐
   │  integrations/  ports.ts                 │
   │  ├── opencode/  (native SDK)             │
   │  └── codex/     (HTTP bridge)            │
   └─────────────────┬────────────────────────┘
                     │
   ┌─────────────────▼────────────────────────┐
   │  core/                                   │  ← domain
   │  permissions · events · audit · settings │
   └─────────────────┬────────────────────────┘
                     │
   ┌─────────────────▼────────────────────────┐
   │  transport/http   ←→   notifications/    │
   │  Bun.serve + SSE       Telegram + Push   │
   └─────────────────┬────────────────────────┘
                     │
   ┌─────────────────▼────────────────────────┐
   │  dashboard/  (vanilla SPA, same origin)  │
   └──────────────────────────────────────────┘

core/

domain

Permissions, events, audit, settings, state, errors. Zero HTTP / Telegram / Codex.

transport/

how-exposed

Bun.serve + routes + handlers + middlewares. The way the core is exposed to the world.

integrations/

adapters

OpenCode (SDK hooks) and Codex (HTTP hooks). Single port: AgentIntegration.

notifications/

fan-out

Telegram and Web Push. Single port: NotificationChannel. Pipeline orchestrates the fan-out.

infra/

plumbing

Tunnel, QR, banner, logger, network, auth, paths, dotenv, circuit breaker.

dashboard/

browser-spa

Vanilla SPA served by transport. No React, no build, native ES modules.

tui/ + cli/

entry-points

Slash commands for OpenCode TUI + CLI binary (init, doctor, uninstall).

server/

composition

Façade: the only file that imports across all layers. Wires and returns the handle.

// 06 · Installation

One command. Three steps. Zero configuration.

The installer locates your OpenCode config dir (XDG, APPDATA, etc.), registers the plugin in opencode.json and tui.json, and cleans up old wrappers. For Codex, you add the endpoint in ~/.codex/config.toml.

Pre-requisites

OpenCode (npm i -g opencode) or Codex CLI installed. Bun (recommended) or modern Node.

✓ OpenCode ≥ 0.1.0 · ✓ Node 18.20+ or Bun · ✓ APPDATA/XDG_CONFIG_HOME accessible

Run init

A single line. Detects XDG_CONFIG_HOME / APPDATA automatically.

$bunx @lesquel/opencode-pilot init

Reopen OpenCode

Close every TUI session and reopen. You'll see a toast: "Remote control plugin loaded".

$opencode

Open the dashboard

Type /remote in the TUI. The browser opens. The banner prints URL, token, and QR.

/remote
Uninstall
$bunx @lesquel/opencode-pilot uninstall
Removes everything (config + state)
$bunx @lesquel/opencode-pilot uninstall --keep-config
Preserves ~/.opencode-pilot/config.json
Diagnostics
$bunx @lesquel/opencode-pilot doctor
Reports in <2s: plugin installed, config edits present, OpenCode running, pilot responsive, state file valid.
// 07 · Use cases

Real situations where it changes the game.

Solo dev approving from mobile

Your agent is running a long refactor. You step away for coffee. On your phone, a Web Push notification tells you it's asking permission to run rm -rf node_modules. You approve with one tap.

Team collaborating on sessions

You enable the tunnel (PILOT_TUNNEL=cloudflared). The dashboard becomes accessible via a public link. A teammate follows the session live for async pair programming.

OpenCode + Codex in parallel

You have OpenCode working on a refactor and Codex generating tests on another terminal. Both events flow into the same dashboard, in separate tabs, with coordinated permissions.

Multiple parallel projects

Three terminals, three projects, one dashboard. Tabs save state across switches. Zero context-switch tax.

Debugging remote sessions

You need to see what the agent did 2 hours ago on the CI server. You connect via the tunnel, open the session, read the full history, download the diff.

Team notification channels

Telegram bot connected to a team channel. Whenever an agent finishes a long task, the channel gets a summary. Everyone sees progress without opening the dashboard.

// 08 · Mobile

From the couch, the bed, or a hotel in Tokyo.

Same WiFi with PILOT_HOST=0.0.0.0, or expose a public tunnel with Cloudflare or ngrok. The dashboard is mobile-first: drawer sidebar, bottom-sheets, 44×44 tap targets.

LAN
PILOT_HOST=0.0.0.0
Tunnel
PILOT_TUNNEL=cloudflared
Shortcut
c

🔒 The tunnel URL contains the token. Treat it like a password — rotate it with /pilot-token.

Connect phone
Scan with camera app
192.168.1.14:4097/?t=4a9c…
Same WiFi · 44ms
// 09 · Keybinds & commands

Everything is a shortcut.

For people who prefer typing over clicking. Press ? inside the dashboard for the full palette.

Keyboard shortcuts global · without focused input

Command palette · palette + shortcuts
?
Command palette (modifier)
K
New session
n
Toggle sidebar
s
Toggle multi-view
m
Toggle theme
t
Connect phone
c
Focus prompt
/
Send prompt
Toggle info panel
I
Close modal/palette
Esc

Slash commands from OpenCode TUI

/pilot · current URL + token
/pilot
/pilot-token · rotate token, invalidate dashboards
/pilot-token
/dashboard · alias for /pilot
/dashboard
/remote · connection info (host, port, tunnel)
/remote
/remote-control · full status + QR hint
/remote-control
If the slash commands don't show up, check that tui.json::plugin includes the spec. OpenCode uses two loaders — one for the server, one for the TUI — and they need separate registrations.
// 10 · Config

Two paths: UI or .env.

Since v1.12 you edit everything from the gear icon. Values live in ~/.opencode-pilot/config.json. If you prefer files, a .env works too — and shell env vars always win.

Priority → 1. shell env · 2. Settings UI · 3. .env · 4. defaults
VariableDefaultDescription
PILOT_PORT 4097 HTTP server port
PILOT_HOST 127.0.0.1 Bind address. 0.0.0.0 for LAN.
PILOT_TUNNEL cloudflared or ngrok for public access
PILOT_PERMISSION_TIMEOUT 300000 Permission request timeout (ms)
PILOT_HOOK_TOKEN new Dedicated bearer token for /codex/hooks/*. Accepted alongside the main token.
PILOT_CODEX_PERMISSION_TIMEOUT_MS new 250000 Timeout for Codex PermissionRequest (max 250000 — Bun.serve idleTimeout cap)
PILOT_PROJECT_STATE new auto auto | always | off. Controls per-project pilot-state writes
PILOT_DEV new false Re-reads dashboard HTML on every request (DX)
PILOT_TELEGRAM_TOKEN @BotFather bot token for Telegram notifications
PILOT_TELEGRAM_CHAT_ID Chat ID to send alerts to
PILOT_VAPID_PUBLIC_KEY Web Push public key (generate with one click in Settings)
PILOT_VAPID_PRIVATE_KEY Web Push private key
PILOT_ENABLE_GLOB_OPENER false Enables /fs/glob and /fs/read for glob search from the dashboard
PILOT_FETCH_TIMEOUT_MS 10000 Timeout for outbound HTTP calls (Telegram, push)
// 11 · Roadmap

Next step: leave the terminal and live in the editor.

OpenCode Pilot started as a remote dashboard. Today it's the bridge between your agent and any device. The next step is clear: integrate it where we spend the most time.

Today · v1.18.2

OpenCode (SDK) + Codex CLI (HTTP bridge)

Two real integrations in production. 6 Codex hook endpoints, 9+ shared event types, same dashboard, same permission queue.

Next · Q2 2026

Visual Studio Code extension

A native VS Code extension. Embedded webview with the dashboard, palette commands, status bar with active sessions, notifications via the VS Code Notifications API, and a URI handler to open from external links. Goal: opening the dashboard becomes a shortcut instead of a /remote.

Exploring

Cloud relay v2 + more adapters

Multi-tenant relay service so NAT and firewalls stop being a blocker (design in docs/CLOUD_RELAY_v2_DESIGN.md). More adapters: Cursor, Aider, Continue.dev — the AgentIntegration port is ready, only each bridge has to be written.

Want to contribute or suggest? Open issues at GitHub Issues — especially the VS Code integration.

// 12 · Docs

Complete documentation.

The repo's docs/ folder covers everything from the loader architecture to the future relay design.

Tweaks theme + effects
dotted grid overlay
retro scanline effect
subtle title jitter