CLI tool
shekohex/opencode-pty avatar
shekohex/opencode-pty

opencode-pty gives an OpenCode agent tabs full of running processes

OpenCode plugin for interactive PTY management - run background processes, send input, read output with regex filtering

606 stars66 forksTypeScriptMIT

At a glance

What is it?
OpenCode's built-in bash tool waits for a command to finish, which is the wrong shape for a dev server, a watch mode or a REPL. This plugin adds pseudo-terminal sessions the agent can spawn, write to, read from with regex filters, and kill, with a React web UI, a live TUI sidebar and a REST API on top.
Who is it for?
opencode-pty fits an OpenCode user whose agent needs to start a dev server, run a watcher or drive an interactive program and keep reading its output afterwards, and it does not fit a workflow where every command is short and completes, because the built-in bash tool already covers that. Before you install, check whether you are on the v1 or v2 plugin API, since the configuration differs, and register the TUI surface separately in .opencode/tui.json if you want the sidebar.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 23 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem is that the built-in bash tool is synchronous

The README explains the motivation in one sentence and then lists the cases it fails.

OpenCode's built-in bash tool runs commands synchronously, so the agent waits for completion. That works for quick commands, but not for dev servers such as npm run dev or cargo watch, not for watch modes such as npm test with the watch flag, not for long-running processes like database servers or tunnels, and not for interactive programs such as REPLs and prompts.

Each of those cases breaks the same way. A command that never exits is a command the agent never gets a result from, so the agent cannot read the output, cannot type into the prompt, and cannot move on to the next step. The usual workaround, backgrounding the process with an ampersand, loses the output stream and the ability to send input.

The plugin's answer is a pseudo-terminal. A PTY gives the process a real terminal to attach to, which means it does not exit when its standard input closes, and it means the agent holds a bidirectional channel rather than a one-shot call. The README describes the result as giving the agent full control over multiple terminal sessions, like tabs in a terminal app.

That framing is accurate and worth taking seriously, because it also implies the cost. These are live processes with a lifecycle, they consume resources, and they need cleaning up, which is why the feature list includes auto-cleanup when OpenCode sessions end.

Five tools, one per action on a session

The plugin exposes five tools, and the set is small enough to hold in your head.

pty_spawn creates a session, taking a command, args, a working directory, environment, a title, a notifyOnExit flag and a timeout in seconds. pty_write sends input to a session, and the parameter documentation mentions escape sequences such as \x03 for Ctrl+C, which is what makes it usable against a program waiting for a keystroke rather than a line of text.

pty_read reads the output buffer, with pagination through an offset and a limit, and with optional regex filtering so the agent can search the output the way grep would. pty_list enumerates the sessions with their status, their process id and their line count. pty_kill terminates a session, and can optionally clean up the buffer as it does so.

The design of that set is what makes this usable in practice. Reading a buffer with a regex filter is not the same as reading a log file, because the buffer is the terminal's own output including everything the process wrote, whether or not the agent was reading at the time. And because pty_read takes a limit, an agent that asks for the last few lines of a noisy dev server does not have to pull the entire scrollback into its context.

Two supporting features sit underneath. Exit notifications mean the agent is told when a process finishes, which the README says eliminates polling. And the plugin respects OpenCode's bash permission settings, so installing it does not quietly widen what the agent is allowed to run.

The v1 and v2 configuration are different files to edit

There are two configuration shapes in the README, and picking the wrong one is the first thing that goes wrong.

For OpenCode V1, the plugin is added by name to the plugin array in the OpenCode config:

json
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-pty"]
}

For OpenCode V2, which uses the new plugin API, you load the v2 subpath and may pass options:

json
{
  "$schema": "https://opencode.ai/config.json",
  "plugin": [
    {
      "package": "opencode-pty/v2",
      "options": {
        "port": 4200,
        "hostname": "127.0.0.1",
        "autostart": false
      }
    }
  ]
}

The three options are all about the web UI observer server. The port defaults to 0, which means an ephemeral port, so the server picks a free one. The hostname defaults to the IPv6 loopback address, and the autostart flag defaults to false, so the server does not come up when OpenCode starts.

There is a third registration point that is easy to miss. The TUI sidebar is part of the same package, but OpenCode loads the TUI surface from a separate config, so it has to be registered there too, in a .opencode/tui.json file:

json
{
  "$schema": "https://opencode.ai/tui.json",
  "plugin": ["opencode-pty"]
}

Updates are handled by OpenCode itself, which checks for and installs plugin updates on startup. If you ever need a clean reinstall, the README gives a one-line recipe that clears the cached copy of the plugin and starts OpenCode again.

The web UI is a React app with a REST API behind it

The plugin includes a web interface for monitoring and interacting with the sessions, built as a modern React application, and the README links a video of it in action.

You start it by running OpenCode with the plugin and then using the slash command that opens the background sessions observer cockpit server and launches the browser. The web interface itself has a session list with status indicators, real-time output streamed over a WebSocket, an input field for sending commands to a running process, controls to kill sessions directly, and a visual indicator of the WebSocket connection status.

Underneath, the server exposes a REST API for session management, with endpoints to list all sessions, create a new one, get the details of one, and more in the truncated part of the table. The same server is what the TUI sidebar talks to.

One piece of guidance in the README is worth more than it looks, because it is a failure mode of agent behaviour rather than of the plugin. If you instruct the coding agent to run something in the background, you have to name it a session. If you name it a task or a process or anything else, the agent will sometimes run it as a background subprocess with an ampersand instead, which is exactly the situation the plugin exists to solve. So the wording of the prompt matters: ask for a background session and the agent will use the PTY tools.

The port question is also yours to answer. The observer server binds a port, by default an ephemeral one on the loopback interface, and with a fixed port and hostname you choose, so you can decide whether the interface is reachable from other machines on your network. The README does not describe any authentication for the REST API or the WebSocket, so the safe default is to keep it on loopback.

The TUI sidebar, and how it finds the server port

The terminal UI plugin is the part of the package you use without leaving OpenCode, and the README describes its behaviour in unusual detail, which suggests it was the most fiddly part to build.

The panel lists one row per session, with a bullet coloured by state, running, failed or stopped, the truncated title, and the line count or the exit code. Clicking a row opens that session's live output in a dialog. A session is removed with a cross, and the plugin asks for confirmation first if the session is still running, and there is a separate action that drops every stopped session at once.

The server starts on demand when you expand the panel, and the README stresses that starting it is never a side effect of OpenCode starting up. The panel is collapsible and remembers that state across restarts.

The port discovery is the interesting engineering detail. The server publishes its address to a small per-user runtime record when it starts, and the panel reads that record, with no /proc scan and no session spent purely to learn a port. The subtlety is what happens when two instances on the same project are running: the record cannot say which server is whose, so the panel asks its own instance instead, through the same show-server-url round trip run in a throwaway session.

That is a small piece of distributed systems thinking in a plugin, and it is the kind of detail that only shows up once two instances exist.

The package layout and the test setup

The repository is a TypeScript package built with Bun, and the tooling is visible in the files.

There is a bun.lock for the runtime, a biome.json for formatting and linting, a playwright.config.ts with an end-to-end test script that runs Playwright through Bun, and a vite.config.ts. The scripts include a typecheck with tsc, a unittest script that runs bun test, and a test:e2e script that sets a couple of environment variables before invoking the Playwright runner under Bun.

The environment is managed with Nix. There is a .envrc, a devenv.nix, a devenv.yaml and a devenv.lock, which together suggest that the maintainer develops in a pinned environment rather than whatever happens to be installed, and that the environment is shared with the plugin runtime, since Bun is listed among the package keywords.

The package metadata shows a deliberate export surface. The root export points at the built plugin, and there are dedicated subpaths for the v2 API, the server and the TUI, followed by a set of wildcard exports at increasing depths that expose the internal modules directly. The published files list is just dist, so nothing but the build is shipped. The version is 0.4.0, the licence is MIT, and the package is an ES module with the type set to module.

There is also an .opencode directory in the repository, which is the configuration directory for OpenCode itself, and a release.sh script for cutting a version.

Where this plugin stops being the answer

Four situations argue against it.

The first is the simple case. If every command your agent runs finishes in under a second, the built-in bash tool is enough and the PTY machinery is overhead. The plugin's own list of motivating cases is a good test: if none of them applies to your work, you do not need tabs.

The second is a workflow where the agent should not have long-lived processes at all. A plugin that spawns background processes that persist until explicitly killed, and that cleans them up only when the OpenCode session ends, changes the failure mode of a misbehaving agent. An agent that forgets to kill a session leaves a dev server running, and a CI environment that runs many agents in parallel accumulates processes that nothing is watching.

The third is security review. This plugin exists to give an agent the ability to run interactive programs, to type into them, and to read their output, and it also ships a web server with a REST API and a WebSocket. Any deployment where the OpenCode process is reachable from a network, or where the agent is less trusted than the machine it runs on, has to account for that server, and the README documents no authentication for it.

The fourth is version churn in the host. The plugin supports two different plugin APIs, with different configuration and different package subpaths, and it is at version 0.4.0 with releases spread across 2026, so it is tracking OpenCode's own changes closely. If your OpenCode version moves in a way the plugin has not caught up with, the fallback is the built-in bash tool, and the sessions you were relying on will not be there.

Editorial conclusion

opencode-pty fits an OpenCode user whose agent needs to start a dev server, run a watcher or drive an interactive program and keep reading its output afterwards, and it does not fit a workflow where every command is short and completes, because the built-in bash tool already covers that. Before you install, check whether you are on the v1 or v2 plugin API, since the configuration differs, and register the TUI surface separately in .opencode/tui.json if you want the sidebar. Also note the plugin ships a web server that binds a port, so decide whether it listens only on the loopback interface. The project is MIT licensed, version 0.4.0 was released on 2026-09-18, and the last push was on 2026-10-01.

Frequently asked questions

Why do I need opencode-pty instead of the built-in bash tool?

The built-in bash tool runs commands synchronously, so the agent waits for completion, which does not work for dev servers, watch modes, long-running processes such as database servers or tunnels, and interactive programs such as REPLs. The plugin gives the agent pseudo-terminal sessions that persist and can be written to and read from on demand.

How do I configure opencode-pty for OpenCode V2?

In the OpenCode config, list the plugin with the v2 subpath and optional options, for example a package of opencode-pty/v2 with options for port, hostname and autostart. The port defaults to 0 for an ephemeral port, the hostname defaults to the IPv6 loopback address and autostart defaults to false.

What tools does opencode-pty give the agent?

Five: pty_spawn to create a session with a command, args, workdir, env, title, notifyOnExit and timeoutSeconds, pty_write to send input including escape sequences such as Ctrl+C, pty_read to read the output buffer with pagination and optional regex filtering, pty_list to list sessions with status, PID and line count, and pty_kill to terminate a session and optionally clean up its buffer.

How do I get the TUI sidebar in opencode-pty?

The TUI surface is part of the same package but OpenCode loads it from a separate config, so you register the plugin in a .opencode/tui.json file as well. The panel then lists one row per session with a bullet coloured by state, the truncated title and the line count or exit code, and can open a session's live output in a dialog.

How do I make the agent use the PTY tools for a background process?

The README says to name the process a session, for example running something as a background SESSION. If you call it a task or a process, the agent will sometimes run it as a background subprocess with an ampersand instead, which loses the output stream and the ability to send input.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. shekohex/opencode-pty on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/shekohex-opencode-pty.svg)](https://hysenlabs.com/projects/shekohex-opencode-pty)

Was this useful?