Give OpenCode the instruments it does not ship with.
Documentation → · Install · Shell · Statusline · Changelog
A tool call has to finish. A dev server does not, and neither does the context window filling up behind you. Cockpit is the instrument panel: things your agent can use, and things that tell you what it is doing.
opencode plugin opencode-cockpit --globalYour agent starts a dev server and the tool call blocks until you kill it. It backgrounds one instead and loses the output. Shell gives it terminals with a real PTY that outlive the turn, wait for a port or a pattern, and hand back the part that matters — and gives you a panel where every one of them reports its own health.
A real recording. Every demo here is generated from a live OpenCode session by
bun run record and re-run on release, so none of them can drift from what
ships.
9 agent tools · 35 watch presets · docs · @opencode-cockpit/shell
How full is the context? Where did the tokens go? What has changed? OpenCode answers the first in a corner and the rest not at all. Statusline answers them where you are already looking.
The default line — no configuration written at all. Every part is a segment you can reshape, recolour or remove, or write yourself in TypeScript. Your Claude Code statusline script runs here unchanged, colours and all.
14 segments · 2 surfaces · docs · @opencode-cockpit/status
Each bay is its own npm package with a switch in config. They share the daemon, the config file and the keys, so the second costs nothing and moving between them changes nothing you already set up.
"Start the dev server and wait until it's actually ready." The agent starts it in a real terminal and blocks on the port opening — not a guess, not a sleep:
shell_start command="npm run dev" waitFor={ port: 5173 }
→ condition met: port is accepting connections
1| VITE v7.3.1 ready in 431 ms
"Run the tests, keep working, tell me if they fail." The suite runs in the background. When it exits, the agent is messaged once, with the error line already picked out:
<shell_exited id="sh_9wq2f1ab" title="unit tests">
exited with code 1 after 48s
last output: 37| FAIL src/auth.test.ts > refresh token expiry
"How is DB Monitoring doing?" Shells are shared across sessions and can be addressed by name:
shell_read name="DB Monitoring"
Logs that don't eat your context. Colour codes stripped, progress-bar redraws collapsed to
their final frame, repeated lines folded to (×12), and every read returns a cursor so the next
one only brings what's new. For full-screen programs (vitest --ui, htop, prompts) the agent can
ask for the screen instead of the log.
It notices breakage on its own. Watch a process that never exits and the agent hears only about changes, never about a thousand identical recompiles:
shell_start command="tsc --watch --noEmit" description="type checker" watch=true
→ tsc: ok → fail · src/auth.ts(42,3): error TS2339: Property 'id' does not exist
Presets cover about 35 tools (tsc, vitest, jest, eslint, cargo, go, gradle, pytest, vite, next, docker compose…), and anything else takes three regexes of its own. A watched process that dies counts as a failure, so a crashed dev server is reported too.
You can find things in a huge log. / in the console filters the scrollback to matching lines,
keeping line numbers and highlighting matches — and output keeps the colours the program printed.
It can type. Prompts, REPLs, migration wizards: shell_send sends text or named keys
(ctrl+c, up, enter) and returns whatever the program printed back.
Built-in bash tool |
Cockpit Shell | |
|---|---|---|
| Long-running processes | Blocks until exit | Runs in the background, survives the turn |
| Knowing something is ready | Guess, or sleep and poll | Blocks on a port, a pattern, silence or exit |
| Interactive programs | Not possible (no TTY) | Real PTY: prompts, REPLs, ctrl+c |
| Reading output | Whole log, every time | Clean lines from a cursor, with grep |
| Noticing a break later | Never | Watchers report health changes |
| Your visibility | None until it finishes | Live panel, console and sidebar |
| After OpenCode restarts | Gone | Still running |
| Key / command | Does |
|---|---|
ctrl+x o · /shells |
Toggle the shells panel under the chat |
ctrl+x i · /shell |
Open the shell console |
/shell-new |
Start a shell yourself |
/shells-clear |
Remove finished shells |
/cockpit-update |
Update the plugin when a newer release exists |
Status reads the same everywhere — RUN (with a spinner), FAIL, STOP, DONE — running shells
and recent failures stay in view, the rest folds behind ▸ N more. In the console: i types
straight into the program (ctrl+] to stop), c sends ctrl+c, r restarts, x stops, tab
switches between the live screen and the scrollback, ? shows details.
Everything
opencode plugin opencode-cockpit --globalOnly what you want
opencode plugin @opencode-cockpit/shell --globalRestart OpenCode. Requires OpenCode 1.18+ on macOS or Linux. Install a feature either through
opencode-cockpit or on its own — if both are configured, the first one loaded is used and
OpenCode warns you which entry to remove.
Turn features off (in both opencode.json and tui.json):
{
"plugin": [["opencode-cockpit", { "features": { "shell": true } }]]
}Configure them in one file, read by both halves of the plugin and by every project:
~/.config/opencode-cockpit/config.json → <project>/.cockpit.json → plugin-entry options
{
"kinds": { "e2e": "playwright|cypress" },
"defaults": { "logFile": true, "timeoutSeconds": 900 },
"ui": { "dockHeight": 16, "historyMinutes": 60 }
}Later sources win key by key, and an invalid file is ignored rather than fatal. You can categorize your own commands, define watch rules, cap how long shells live, choose what may interrupt the agent, and trade context tokens for accuracy. Each feature's README documents its own settings: Shell.
OpenCode TUI thread ── feature plugins (tui) ──┐
├── unix socket, JSON-RPC ── cockpitd ── processes
OpenCode server worker ─ feature plugins (server) ┘
OpenCode runs its interface and its server in separate threads, so a plugin's two halves can't
share memory. Both talk to cockpitd, a small daemon that owns every long-lived process: it
starts on demand, is shared by every OpenCode window, upgrades itself when a newer plugin connects,
cleans up processes left by a crash, and exits when idle. That's why shells outlive OpenCode
restarts, and why one session can look at a shell another session started.
Each shell's output feeds three views at once: a normalized log for the agent, an emulated screen for you, and a raw ring buffer so a panel opened late can catch up.
| Package | What it is | Docs |
|---|---|---|
opencode-cockpit |
The bundle: every bay, each switchable | README |
@opencode-cockpit/shell |
Bay 01 — background terminals | README · docs |
@opencode-cockpit/status |
Bay 02 — the statusline | README · docs |
@opencode-cockpit/daemon |
cockpitd, the shared process host |
README |
@opencode-cockpit/client |
Typed, auto-spawning client | README |
@opencode-cockpit/protocol |
Wire contracts and schemas | README |
Not a roadmap of promises — the next thing, and why it is next.
Review — a pull request, in the terminal. A turn ends and you read the whole diff at once, or you read none of it and hope. GitHub solved the reading part years ago: comment on a line, comment on a file, mark a file read, suggest the change instead of describing it — and nothing reaches the author until you press submit review. The same shape fits here, with the review going to the chat as one message instead of six interruptions.
OpenCode already exposes what it needs: session.diff returns each file's before and after in
full, session.revert undoes a single message or part, and a plugin can write to the prompt. What
is missing is the view and the keys.
Doctor. One command that checks your setup and says how to fix it.
Issues and pull requests are welcome. CONTRIBUTING.md covers the architecture, the invariants worth knowing before changing anything, and how to run your working copy inside OpenCode.
bun install
bun run check # lint, typecheck, tests (real PTYs, real daemon)
bun run pack:check # pack, install the tarballs, run a shell through them
bun run smoke:tui # drive a real OpenCode against the packed plugin
