Annotate inside Herdr: comment on any terminal text, review whole Markdown documents and your agent's replies, and send the feedback straight back to the agent. Document review is powered by plannotator-tui, which also runs on its own outside Herdr.
Watch the demos
- Herdr 0.8.0 or later
- Bun
- macOS, Linux, or Windows
On Linux, install wl-clipboard, xclip, or xsel for clipboard access.
On Windows, native Herdr plugin support is preview/best-effort. Bun must be on PATH. Clipboard access uses PowerShell; no extra clipboard package is required. The install, keybinding, configuration check, reload, and use instructions below also apply on Windows.
Pick one. Installing the other later just swaps it (same plugin id).
Full: annotate terminal text, review documents and agent replies, send feedback to the agent. Wraps Plannotator TUI (macOS and Linux today). Demo
herdr plugin install plannotator/herdr-annotateLite: the simple version: select text, prefix+a, comment in a popover. Demo
herdr plugin install plannotator/herdr-annotate/liteRequired. Bind the keys in Herdr's config.
Full install keys: terminal annotations + document and agent-reply review
# Terminal annotations
[[keys.command]]
key = "prefix+a"
type = "plugin_action"
command = "annotate.capture"
description = "annotate text"
[[keys.command]]
key = "prefix+shift+a"
type = "plugin_action"
command = "annotate.copy-context"
description = "copy annotations as context"
[[keys.command]]
key = "prefix+m"
type = "plugin_action"
command = "annotate.manage"
description = "manage annotations"
# Document review (plannotator-tui)
[[keys.command]]
key = "prefix+o"
type = "plugin_action"
command = "annotate.open"
description = "review documents in this folder"
[[keys.command]]
key = "prefix+shift+o"
type = "plugin_action"
command = "annotate.last"
description = "review the agent's last reply"Lite install keys: terminal annotations only
[[keys.command]]
key = "prefix+a"
type = "plugin_action"
command = "annotate.capture"
description = "annotate text"
[[keys.command]]
key = "prefix+shift+a"
type = "plugin_action"
command = "annotate.copy-context"
description = "copy annotations as context"
[[keys.command]]
key = "prefix+m"
type = "plugin_action"
command = "annotate.manage"
description = "manage annotations"Check and reload:
herdr config check
herdr server reload-config| Key | Action |
|---|---|
Ctrl+B A |
comment on the selected text · Ctrl+S saves |
Ctrl+B Shift+A |
copy all annotations as Markdown |
Ctrl+B M |
manage · y copy one · c copy all · Shift+C copy and archive · Tab archives (y copy · u restore · d d delete) |
Full install. Works with Claude Code, Codex, pi, Copilot CLI, Droid, Oh My Pi, Hermes CLI and OpenCode (1 and 2).
| Key | Opens |
|---|---|
Ctrl+B O |
this folder, with a file tree |
Ctrl+B Shift+O |
the agent's recent replies |
Ctrl-click a file://…md link |
that file |
Send (or E) makes the review the agent's next message. q closes.
| Option | Where |
|---|---|
| Agents request reviews themselves | npx skills add plannotator/herdr-annotate --skill plannotator-tui -g |
| Open as full tab, split, or popup | [herdr] placement = "overlay" | "split" | "popup" in ~/.config/plannotator-tui/config.toml |
| Use without Herdr | plannotator-tui |
Over SSH or herdr --remote, the plugin runs on the server, and two things get in the way:
Herdr's default copy-on-select clears the selection on mouse-up, and the prefix keypress
clears whatever selection remains before a bound action runs
(herdrdev/herdr#3380). A headless server also
has no clipboard for the plugin to fall back to.
What works today:
-
On the server, keep the selection after mouse-up:
# remote server: ~/.config/herdr/config.toml [ui] copy_on_select = false # the selection stays; copy explicitly with Ctrl+C
-
Trigger the action without a keypress in Herdr, while the selection is still highlighted. From your laptop, bound to any key in your terminal or OS:
ssh <host> herdr plugin action invoke annotate.capture # named session on the server: ssh <host> HERDR_SESSION=<name> herdr plugin action invoke annotate.capture
The action reads the focused pane's selection through Herdr's API, which never touches the keyboard path, so the text arrives. Verified: the same selection gives
selected_textthis way and nothing throughprefix+a. -
In Neovim, use the mapping below; it hands the selection over in a file.
Server-side key bindings and herdr --remote <host> --remote-keybindings server are still
needed for the manager (prefix+m) and other plugin keys; without the flag, herdr --remote
uses your local keys and drops plugin bindings. prefix+a itself will work once
herdrdev/herdr#3380 is fixed.
Herdr Annotate reads text that Herdr copies to the system clipboard. The plugin cannot read selection state from Neovim or another terminal application.
bun install
bun test
bun run typecheck
herdr plugin link "$PWD"To test a local plannotator-tui build instead of the pinned release, put it in bin/
before linking: PLANNOTATOR_TUI_BIN=/path/to/plannotator-tui bash scripts/fetch-plannotator-tui.sh.
herdr plugin link replaces any existing annotate link; link the other directory to switch back.
Before a release, HERDR_SESSION=<disposable session> bash scripts/smoke.sh installs fresh, upgrades
from the first shipped commit, installs lite, swaps to full, and opens the review pane, then restores
whatever was installed.
Add this visual-mode mapping to ~/.config/nvim/lua/config/keymaps.lua for LazyVim, or to init.lua:
vim.keymap.set("x", "<leader>a", function()
-- Hand the selection to the plugin through a file: works on headless servers too.
vim.cmd('normal! "zy')
local base = os.getenv("XDG_RUNTIME_DIR")
if not base or base == "" then base = vim.fn.fnamemodify(vim.fn.tempname(), ":h") end
local dir = base .. "/herdr-annotate-" .. vim.loop.getuid()
vim.fn.mkdir(dir, "p", "0700")
vim.fn.writefile(vim.split(vim.fn.getreg("z"), "\n"), dir .. "/selection")
vim.fn.jobstart({ "herdr", "plugin", "action", "invoke", "annotate.capture" })
end, { desc = "Annotate in Herdr" })Select text with the mouse or Visual mode. Then press <leader>a to open Herdr Annotate.
The file is read once and removed; a file older than 15 seconds is ignored.
LazyVim uses Space as <leader> by default. The mapping keeps mouse support and leaves normal Neovim commands unchanged.
