Doc Select
OpenCode plugin: open a file in a tmux pane and read back the text the user selected with the mouse.
0
0
20.0
Multi-signal model
2 days ago
2026-10-02
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@cipher208/opencode-doc-select@0.1.0"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["@cipher208/opencode-doc-select@0.1.0"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D @cipher208/opencode-doc-selectOpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
Your agent re-reads whole files because you cannot hand it a fragment. Drag across the lines you mean in a tmux pane, release, and the agent gets exactly those lines — no copy-paste, no re-reading, no guessing.
agent: doc_open("src/server.ts")
→ pane, raw text with line numbers
user: drags over lines 40–46, releases
agent: doc_selection()
→ {"file":"src/server.ts","startLine":40,"endLine":46,"chars":214,"text":"..."}
Quick Start
Install it, then add one line to ~/.config/opencode/opencode.json:
{ "plugin": ["@cipher208/opencode-doc-select"] }
Restart OpenCode, ask the agent to open a file, drag over what you mean, release.
Requires tmux and bun on PATH.
Features
- Exact fragments. Line range and text, not the file. Character count included.
- Line numbers in the pane, so a selection reads as a citation.
- Shell-safe paths. A filename containing
;,$or a backtick is quoted before it reaches tmux — see SECURITY.md. - No hardcoded paths. State follows
XDG_STATE_HOME, with a$HOMEfallback. - Two export shapes, because neighbouring plugins differ and "the other one works" is not evidence.
Writing a plugin like this
Two contracts that are not in the OpenCode documentation, both learned the hard way:
- A plugin's export must be a function returning the hooks object. A bare object
fails with
Plugin export is not a functionand the plugin is dropped silently. - An empty log proves nothing. A hook gated on a size threshold logs nothing when output is small, so "loaded fine", "never loaded" and "loaded but never triggered" look identical.
Install
opencode plugin @cipher208/opencode-doc-select
Add -g to install into the global config instead of the current project. To do
it by hand, add it to plugin in ~/.config/opencode/opencode.json:
{ "plugin": ["@cipher208/opencode-doc-select"] }
Restart OpenCode afterwards — plugins load at startup.
The package is published from GitHub Actions through npm's trusted publishing (OIDC), so there is no npm token in this repository. See CONTRIBUTING.
Manual install
Copy all five modules from src/ into ~/.config/opencode/plugins/:
plugin.ts doc-select-cli.ts doc-select-core.ts paths.ts shell.ts
The plugin locates its CLI relative to its own file, so they must sit together and nothing needs configuring.
Requirements
tmuxonPATH, and the agent process running inside it ($TMUXset)bunonPATH— OpenCode already runs on it, and the pane script is launched withbun run
Tools
| Tool | Arguments | Returns |
|---|---|---|
doc_open |
path |
pane id and resolved path, or an error |
doc_selection |
— | file, startLine, endLine, text, chars, at |
doc_selection returns {"success": false, "error": "выделений ещё не было"}
until the user has actually selected something.
In the pane
| Key | Action |
|---|---|
| drag | select |
PgUp / PgDn |
page scroll |
| arrows | line scroll |
q, Esc, Ctrl-C |
quit |
The selection is highlighted in reverse video. The status bar shows the current line range and the total line count.
Paths
State lives in ${XDG_STATE_HOME:-~/.local/state}/opencode/doc-select.json.
Nothing is hardcoded to a specific home directory. If neither $HOME nor the XDG
variables are set, doc_open fails with an explicit error instead of writing
somewhere unexpected.
Writing a plugin like this: two contracts that are not in the docs
1. A plugin's export must be a function that returns the hooks object.
export const DocSelect = async () => ({ tool: { doc_open, doc_selection } })
export default DocSelect
Exporting a bare object fails with Plugin export is not a function, and the
plugin is silently dropped — every tool it was supposed to provide just never
appears. The only trace is one line in ~/.local/share/opencode/log/opencode.log:
level=ERROR message="failed to load plugin" path=file://... error="Plugin export is not a function"
Note that neighbouring plugins in the same directory may use different export shapes, so "the other one works" is not evidence that yours will.
2. An empty log does not mean the plugin is fine.
A hook that only fires above a size threshold produces no log entry when the output is small. "No log" can mean "loaded fine" or "never loaded" or "loaded but never triggered". Check for the export-shape error line explicitly, and test with an input that actually crosses the threshold.
Limitations
- Mouse events only. No keyboard selection.
- The mouse wheel is parsed and ignored; there is no wheel scrolling yet.
- ANSI-styled source is shown raw, so escape sequences appear as literal text.
- The selection is whatever the pane rendered; text below the visible window is not selectable until scrolled to.
doc_selectionreturns the last selection only. There is no history.
Development
bun install
bun test # 21 tests: SGR parsing, selection extraction, viewport, path resolution
bun run typecheck
Tests import the real modules. An earlier version of a sibling hook project had tests that re-declared the hook's functions as copies — the hook could be deleted entirely and the suite stayed green. Do not do that.
License
MIT
Similar plugins
Pty
opencode-pty
OpenCode plugin for interactive PTY management - run background processes, send input, read output with regex filtering
Bg
@imransyafi/opencode-bg
OpenCode plugin for interactive PTY management - run background processes, send input, read output with regex filtering
Tmux Indicator
opencode-tmux-indicator
OpenCode CLI plugin that sets a tmux window option when the agent is waiting for input