Neovim watcher for opencode - streams opencode.log human-friendly per project, shows agent progress, handles inline edits via opencode serve.
demo.mp4
Use <C-.> to yank the visual selected text to over opencode://prompt buffer
Save the buffer :w to send the request
2026-09-23.16-39-11.mp4
For author's original notes and design rationale, see
author_notes.txt.
- Utilize
opencodeevents - touching/editing files, tool execution, looping, permission stuck, failure, progress across tasks, buffer refresh on format. - Inline edits - visual yank via
getregion(),File: lineheader, vsplit prompt, save to send, session reuse per project. :OpencodeWatcherLogs- prettified live log per project (persistent terminal).:OpencodeRefresh- clear logs + new session.:OpencodeModel- change model per session.:OpencodeDebugLogs- plugin debug log.
{
"me-imfhd/opencode-watcher.nvim",
config = function()
require("opencode-watcher").setup({
exclude = "MSG,SYNC,LLM,PERM:bash,read,edit",
prompt = {
key = "<C-.>",
win = "vsplit",
instructions = "Keep loop/tool calls as few as possible. Just focus on fixing the task at hand and nothing else. No need to explain when the task is done. You are expected to give results fast, short and accurate, not long, unless specified in the prompt.",
server = { auto_approve = true },
},
})
end,
}All other options default: debug=false, floating top-right 42x12, lines=0 live, hide_delay=2500, max_lines=100, server 127.0.0.1:4096.
require("opencode-watcher").setup({
debug = false, -- false: only INFO/WARN/ERROR to stdpath("log")/opencode-watcher-debug.log
position = "top-right", -- "bottom-right"
width = 42, height = 12, border = "rounded",
tail_script = nil, -- auto <plugin>/opencode-log-tail.sh
exclude = nil, -- nil = show all; e.g. "MSG,SYNC,LLM,PERM:bash" hide noisy
lines = 0, -- 0 live only, 50 with history
hide_delay = 2500, -- ms after LOOP exit
max_lines = 100,
prompt = {
enabled = true,
key = "<C-.>", -- n: open, x: yank selection
win = "vsplit", -- vsplit only
vsplit = { width = 60 },
close_on_submit = true,
clear_on_submit = true,
instructions = "Keep loop...", -- prepended to every prompt
server = {
hostname = "127.0.0.1", port = 4096,
auto_approve = true, -- --auto
model = nil, agent = nil, timeout_ms = 30000,
},
},
logger = { level = "INFO", path = nil },
icons = { BOOT="", FILE="", PERM="", LLM="", LOOP="", ... },
highlights = { BOOT="Special", FILE="Directory", LOOP="Statement", ERR="ErrorMsg", ... },
})- Source
~/.local/share/opencode/log/opencode.logJSON lines (timestamp,level,message,directory/cwd/file/session.id). - Dir resolved auto
git rev-parse --show-toplevelelsepwdwatcher.lua:resolve_dirsame asopencode-log-tail.sh:40. opencode-log-tail.sh:127classifies into TAGS:BOOT WATCH CFG FILE SYNC LLM PERM LOOP MSG SESS CMD EVT CLEAN MISC LOG ERR RAWPERMeval: perm=<type> pat=<pattern> -> ask|allow(read/write/edit/bash/grep/glob),asking: perm= patterns=,repliedopencode-log-tail.sh:275FILEtouching: file=,formatting: file=opencode-log-tail.sh:242LOOPloop: started(step=0),loop: step=<n>,exit/completedopencode-log-tail.sh:294- All events show
[basename]at frontopencode-log-tail.sh:106, filteredLINE_DIR == TARGET_DIRopencode-log-tail.sh:96. Multiple sessions sharing same dir interleave - no per-session filter by design.
watcher.lua:85startsjobstart({ tail_script, "--dir", dir, "--lines", "0" })(tail -F --retryopencode-log-tail.sh:341), optional--exclude.watcher.lua:on_stdoutwatcher.lua:94handles tags:LOOP step=0->loop_active=true,ui.cancel_hide(),ui.ensure_window()LOOP exit->loop_active=false,ui.hide_delayed()2500msERR/aborted/cancelled/stream error-> pinui.cancel_hide()+ensure_window()(no auto-hide)watcher.lua:99- Otherwise non-loop ->
ensure_window()+idle_hide_timer 5000mswatcher.lua:146thenui.hide()which clearsui.lua:82.
ui.lua:humanizeui.lua:224maps to icons and truncates word-aware>30to….--excludeopts outMSG/SYNC/LLMduplicates.ui.lua:push_rawui.lua:372refreshes visible buffers onFILE formattingorreverting/unreverting/restore(debounced 150ms) viachecktime+e!for!modifiedwindowsui.lua:186.
Usage: :OpencodeWatcherLogs live detailed pretty log in terminal (persistent), floating window ephemeral cleaner view; filter by directory + --exclude for cleaner view.
prompt.lua:233getregion()visual selection (fallback tonvim_buf_get_lines),capture_context()prompt.lua:363addsFile: rel:line+ fenced```ftheader for file buffers (quickfix/trouble stays raw by design).Ctrl+.init.lua:90prompt.open_or_yank(false)/(true)->vsplit 60prompt.lua:165(opencode://promptacwriteprompt.lua:17),startinsertprompt.lua:219.- Type instructions,
:w(BufWriteCmd)prompt.lua:33triggersM.submit()prompt.lua:491which saves other!modifiedfile bufssilent! write, prependsinstructionsprompt.lua:526, thenserver.sendserver.lua:381. server.lua:229ensure_server()checkshttp://127.0.0.1:4096/docelseopencode serve --port 4096 --hostname 127.0.0.1 --print-logs(detachserver.lua:251, poll 10s). Validates existing session viaGET /api/session/<id>server.lua:413thenopencode run --attach http://host:port --dir <dir> --format json --auto --session <id> <prompt>server.lua:405, capturessessionIDfrom stdout JSONserver.lua:477->stdpath("data")/sessions.jsonpernormalize_dirserver.lua:34for reuse,active_runstracked forabort.- When opencode executes, it streams events into
opencode.logwhich watcher visualizes and refreshes buffers.
DB note: session/message/part tables (part.json: type step-start/tool/text, state.input.filepath, metadata.diff), useful for :OpencodeSession opencode -s "id" inspection.
:OpencodeWatcherStart start watcher (tail filtered by git root)
:OpencodeWatcherStop stop watcher
:OpencodePrompt open prompt vsplit (Ctrl-. to yank)
:OpencodeWatcherLogs [N] pretty log in terminal --lines N (default 50) `opencode-log-tail.sh --dir <project> --lines N`
:OpencodeWatcherDebugLogs plugin debug log (stdpath("log")/opencode-watcher-debug.log)
:OpencodeRefresh abort loop + clear session + clear logs + clear prompt (next prompt new session)
:OpencodeAbort abort looping agent for project (also new session)
:OpencodeSession echo `opencode -s "id"` for inline session (yanks to +)
:OpencodeModel [provider/model:variant] change model per session only
Debug logs (when debug=false only INFO/WARN/ERROR via vim.notify errors):
-- plugin debug: stdpath("log")/opencode-watcher-debug.log (~/.local/state/nvim/...)
-- opencode log: ~/.local/share/opencode/log/opencode.log
-- pretty: opencode-log-tail.sh --dir <project> --lines 50 --exclude MSG,SYNC,LLM
-- sessions: curl http://127.0.0.1:4096/api/session?directory=<project>
opencode-log-tail.sh:1- pretty tail,[basename]front, dir filterlua/opencode-watcher/{watcher,ui,prompt,server,config,logger}.lua- nvim integration
--dir DIR (auto git root/pwd), --lines N, --exclude TYPES (PERM:eval, bash, read etc opencode-log-tail.sh:60), --raw / --raw=MSG opencode-log-tail.sh:60.
This project was assisted by AI