OpenCode integration
OpenCode integration
TL;DR. @phux/opencode adds six bounded terminal tools to OpenCode while
an external local phux server continues to own the terminals. Targets resolve
from an explicit argument, this plugin instance’s most recently created pane,
then PHUX_TARGET. The plugin uses public OpenCode server hooks; it does not
embed a TUI, paste, or connect to remote phux transports.
Requirements
The package requires Node.js 20 or newer, OpenCode, a compatible external
phux executable, and a running local phux server. It does not bundle or start
phux. The current adapter expects the documented phux 0.1.0 CLI shapes:
phux --version
The package is built against the public root API of @opencode-ai/plugin
1.18.1. Both OpenCode and this integration are pre-1.0 surfaces, so validate a
new OpenCode version before rolling it out broadly.
Install and load
OpenCode documents two plugin-loading paths: npm package names in
opencode.json, and JavaScript or TypeScript modules discovered under
.opencode/plugins/ or ~/.config/opencode/plugins/. Use the package-name
form for a registry release:
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@phux/opencode"]
}
OpenCode installs npm plugins at startup. Do not also add a local shim for the same package; local and npm plugins are separate load sources and would create two plugin instances.
Load from a checkout
Build the integration, install it as a dependency of the project’s OpenCode config directory, and expose it through OpenCode’s documented local-plugin directory:
cd /absolute/path/to/phux/integrations/opencode
npm ci
npm run build
cd /absolute/path/to/your/project
mkdir -p .opencode/plugins
npm install --prefix .opencode --save-exact /absolute/path/to/phux/integrations/opencode
cat > .opencode/plugins/phux.js <<'EOF'
export { default as PhuxPlugin } from "@phux/opencode"
EOF
The named re-export gives OpenCode one local plugin function. The project does
not need a plugin entry in opencode.json for an automatically discovered
local file.
Load a packed artifact
Packing tests the same files that a registry release contains without claiming that a version is published:
cd /absolute/path/to/phux/integrations/opencode
npm ci
npm pack --pack-destination /tmp
cd /absolute/path/to/your/project
mkdir -p .opencode/plugins
npm install --prefix .opencode --save-exact /tmp/phux-opencode-0.1.0.tgz
cat > .opencode/plugins/phux.js <<'EOF'
export { default as PhuxPlugin } from "@phux/opencode"
EOF
The packed runtime is standalone JavaScript plus declarations and the package
README. It still requires the external phux executable.
Runtime configuration
The normal configuration boundary is the environment inherited by OpenCode:
PHUX_SOCKET=/absolute/path/to/phux.sock \
PHUX_TARGET=@42 \
opencode
PHUX_SOCKET chooses a non-default local Unix socket. PHUX_TARGET is an
optional initial target and is read when the plugin instance starts. Restart
OpenCode after changing either variable.
The public OpenCode plugin config type also accepts options alongside an npm package entry:
{
"$schema": "https://opencode.ai/config.json",
"plugin": [
["@phux/opencode", {
"executable": "/absolute/path/to/phux",
"socket": "/absolute/path/to/phux.sock",
"lifecycleTimeoutMs": 1000
}]
]
}
executable and socket override the CLI path and socket for that plugin
instance. lifecycleTimeoutMs bounds each best-effort metadata subprocess;
its default is 1000 ms. These options apply to a configured npm entry. An
automatically discovered local shim should use PATH, PHUX_SOCKET, and
PHUX_TARGET instead.
The six tools
| Tool | OpenCode-facing behavior |
|---|---|
phux_list | Lists sessions without changing phux focus. |
phux_create | Creates a named session without attaching and selects its seed pane for this plugin instance. Its optional command is argv. |
phux_snapshot | Reads a bounded screen projection without attaching or resizing. |
phux_send_keys | Sends named or literal key items. It is not paste. |
phux_run | Runs one shell command string through the phux sentinel and returns its result. |
phux_wait | Waits for visible text, idleness, or indefinitely when all conditions and deadlines are omitted. |
The headless CLI owns selector syntax, command semantics, JSON shapes, and exit codes. Use the agent CLI guide for that contract rather than inferring a second CLI from these tool descriptions.
until and idle_ms are mutually exclusive. timeout_seconds is the phux
operation deadline; local_timeout_ms is the adapter subprocess deadline.
Short operations default the local deadline to 10 seconds. phux_run and
phux_wait have no implicit local deadline, preserving their documented
indefinite forms. OpenCode’s tool abort signal is passed to every subprocess.
Snapshot, run, and wait output sent to the model is limited to the newest 200
lines and 12 KiB, with an explicit notice when the adapter or phux truncated
it.
Target selection and concurrency
Every targeted tool resolves its pane in this exact order:
- the tool’s explicit
targetargument; - the seed pane selected by the latest successful
phux_createin this plugin instance; PHUX_TARGETcaptured at plugin startup.
The tool fails when all three are absent. It never silently uses phux focus. An explicit target overrides both selected and environment targets.
Selection belongs to the plugin instance, not to an OpenCode session. Two concurrent OpenCode sessions using the same instance therefore share one mutable selected target, and concurrent creates are last-completion-wins. Use explicit targets when sessions or tool calls can overlap.
The plugin does not lock the PTY, serialize terminal tools, reserve a prompt,
or make snapshot followed by input transactional. A human attach, another
agent, and phux_send_keys or phux_run can interleave. Lifecycle metadata is
queued to avoid overlapping metadata writes, but it is status, not an input
lock. Coordinate writers and prefer discrete phux_run calls where possible.
Lifecycle metadata and gaps
Lifecycle reporting is best effort and uses only the public OpenCode server plugin surface.
| Public signal | Behavior |
|---|---|
session.status with busy | Publishes a working record for the current target. |
session.status with idle | Publishes an idle record for the current target. |
session.idle | Publishes idle. |
successful phux_create | Publishes working for that tool’s public OpenCode session if no status was observed yet. |
session.deleted | Ownership-checks and clears that session’s declaration. |
plugin dispose | Best-effort ownership-checks and clears declarations known to this instance. |
Records use name=opencode, kind=opencode, and owner
opencode:<public OpenCode session id>. Before clearing, the plugin reads the
current declaration and requires its name, kind, and owner to still match. It
therefore preserves metadata replaced by another owner.
Retry status, session.created, session.error, and unrelated events do not
invent transitions. OpenCode 1.18.1 has no documented event that distinguishes
a plugin reload from final disposal, so reload preservation is not claimed.
A process crash, SIGKILL, or other forced termination cannot run disposal.
Metadata failures and local deadlines do not fail terminal tools.
Shared CLI boundary and other adapters
The source reuses the host-independent PhuxCli adapter maintained with the
Pi integration. The OpenCode build bundles that adapter, its schema
validation, and the public OpenCode tool helper into the artifact. The packed
runtime has no dependency on @phux/pi or @opencode-ai/plugin; it still
executes the external phux CLI. This shared implementation boundary does not
make Pi target persistence, commands, or lifecycle behavior part of the
OpenCode contract.
Use Pi when Pi-native target persistence and human commands are the needed host surface. Use phux-mcp when a client speaks MCP over stdio and needs that adapter’s broader catalog. Those guides own their own contracts; this page does not duplicate them.
Human attach and current safety boundaries
To join a session, construct argv for a separate real terminal, for example:
["phux", "attach", "--socket", "/absolute/path/to/phux.sock", "work"]
Treat this as argv, not a shell string to eval. The OpenCode plugin does not
execute attach, open a nested terminal, or navigate the human client. A human
attach is a live writer, not a read-only monitor, so coordinate it with agent
input.
Current boundaries are explicit:
- There is no TUI embedding and no dependency on OpenCode TUI internals.
- There is no WASM build; this is a Node adapter around an external native process.
- There is no paste tool.
phux_send_keyssends key items and must not be presented as clipboard or bracketed-paste support. - There is no remote pairing or remote transport configuration. The plugin accepts a local Unix socket, not QUIC, WebSocket, bearer-token, certificate, or pairing arguments.
- Never place pairing tokens, certificate material, or other remote credentials in tool arguments, targets, lifecycle records, attach guidance, or smoke output.
Package-development commands and the opt-in real smoke live in
../../integrations/opencode/README.md.