An fzf picker over herdr's native tab/pane/workspace/worktree/agent actions — new tab, close pane, split, focus/resize/swap/move pane, zoom, worktrees, starting and prompting agents — plus live jump targets (other open tabs, other workspaces, running agents, worktrees of this repo you haven't opened yet).
Every row shows the keybinding herdr actually has for it, so the picker teaches the shortcut instead of replacing it.
herdr quick-action ▸ clo
┌────────────────────────────────────────────────────────────────────┐
│ ↑↓ select · enter run · esc cancel │
│ > Close pane kill remove quit delete │
│ Close tab ctrl+b shift+x kill remove quit delete │
│ Close workspace ctrl+b shift+d kill remove project │
├────────────────────────────────────────────────────────────────────┤
│ herdr pane close <pane> │
└────────────────────────────────────────────────────────────────────┘
This is a companion to
herdr-command-palette
(jt.command-palette), not a replacement. That plugin lists every action
exposed by every installed plugin (herdr plugin action list) — herdr's own
built-in keybindings never show up there, because they aren't plugin actions.
This plugin fills that specific gap.
Native actions, ranked by how often and how recently you pick them (tracked locally, per action; a curated default order breaks ties before you've used it at all):
| Tabs | New tab · New tab (named) · Rename tab · Close tab |
| Panes | Split right/down · Toggle zoom · Close pane · Rename pane |
| Move focus | Focus pane left/right/up/down |
| Reshape | Resize pane left/right/up/down · Swap pane with the one left/right/above/below |
| Relocate | Move pane to another tab · Move pane out to a new tab · Move pane out to a new workspace |
| Agents | Start an agent in a new split · Send a prompt to an agent · Interrupt an agent (esc) · Rename an agent |
| Workspaces | New workspace · New workspace here (named) · Rename workspace · Close workspace |
| Worktrees | New worktree here · New worktree on a branch · Remove this worktree checkout |
| Session | Reload herdr config |
Live jump targets (always shown, never reordered):
- Go to workspace:
<label>— every other workspace, with tab count and agent state in the preview - Go to tab:
<label>— every other open tab in the current workspace - Focus agent:
<title>·<state>— every other running agent - Open worktree:
<branch>— every worktree of the current repo that isn't open as a workspace yet
Actions ending in … ask for the missing bit (a name, a branch, a target tab,
an agent, an agent kind) with a prompt or a second fzf pass. Start an agent in
a new split splits the origin pane, then runs herdr agent start in the new
pane with the kind you picked — the kind list is scraped from the installed
binary's own shell completion, so it never goes stale.
The shortcut column is resolved live, per row:
herdr --default-configsupplies the installed binary's defaults (they ship as commented[keys]assignments), so the column tracks herdr versions instead of a hardcoded table that quietly rots.- Your
~/.config/herdr/config.toml(or$HERDR_CONFIG_PATH) overrides them; an explicit""means you unbound it. prefix+…bindings are rendered as the keys you actually press (ctrl+b shift+x), and herdr's indexedprefix+1..9bindings are filled in per row — so Go to tab: notes showsctrl+b 3when it's tab 3.- A key claimed by a
[[keys.command]]block (a custom command or another plugin's action) shadows the built-in that shipped on it, so those rows show no shortcut. Showing a keybinding that no longer fires is worse than showing none.
Rows with no herdr keybinding at all (pane swap, pane move, the agent actions,
the … variants) simply have an empty shortcut column — the picker is the only
way to reach them. The four Resize pane rows show resize_mode's key, which
is herdr's modal equivalent; the picker's version is a single resize step.
Each row is title · shortcut · dim synonyms, and all three are searchable:
vsplit,maximize,kill,gitand friends hit the right row via the synonym tail, as do the old-style action ids (close_pane,zoom_pane).- The shortcut is matchable too — type
ctrl+b vto find what that key does. - Sorting is fzf's (this is a change from 0.1.0, which disabled it): with an
empty query the list stays in usage-rank order, and scoring kicks in as soon
as you type.
--tiebreak=begin,indexprefers matches near the start of the row — the title, not the synonym tail — and falls back to the usage rank instead of fzf's line-length rule. - The synonym tail is dimmed and sits past the shortcut column, so on a narrow
popup it scrolls out of view while staying searchable (fzf matches text the
window truncates). It has to be on the row: fzf only searches what it
displays —
--nthindexes the fields of the--with-nthoutput, so a hidden TSV column is unreachable.
The preview strip under the list shows the exact herdr command a row will run.
herdr exposes some built-in keybindings (help, settings, detach, goto/jump mode,
toggle sidebar, edit scrollback, resize mode, prev/next tab, workspace or agent
cycling, last pane) that have no equivalent in the herdr CLI's socket API —
they're pure UI interactions with nothing to invoke from a script. Those still
need their real keybinding. Cycling is also redundant here: Go to tab, Go
to workspace and Focus agent jump to a specific target, which beats a bare
next/prev toggle.
Plugin actions aren't listed either — that's jt.command-palette's job.
herdr doesn't bind keys declared in a plugin manifest, so add a binding to
~/.config/herdr/config.toml and reload:
[[keys.command]]
key = "prefix+x"
type = "plugin_action"
command = "es.quick-actions.open"
description = "Quick actions"herdr server reload-configNow prefix then x opens the picker (ctrl+b x with the default prefix).
Note: prefix+x is herdr's built-in default for close_pane — this
override replaces that keybinding rather than adding to it. Close pane is
still reachable through the picker itself, which will show it with an empty
shortcut column because you took its key. Pick a different free key if you'd
rather keep close_pane direct-bound; check your config.toml for what's
already taken.
herdr actions run on the server with no TTY, so an action can't run fzf directly:
es.quick-actions.opencaptures the origin pane/tab/workspace ids fromHERDR_PLUGIN_CONTEXT_JSONand opens a small, centered popup (60%×70% of the terminal,placement = "popup"— a real modal window over the active pane, unlike the full-paneoverlayplacement), which does get a TTY — forwarding those ids in asQA_CONTEXT_JSON. This matters because the popup is a brand-new pane: without forwarding,pane close/split/focus/zoomwould target the popup itself instead of the pane you meant.- Inside the popup,
palette.shbuilds the ranked action list, resolves each row's keybinding, appends the live tab/workspace/agent/worktree targets, and pipes it all tofzf. - On selection it runs the matching
herdr tab|pane|workspace|agent|worktree|servercommand directly — no plugin-action indirection needed, since these are core CLI commands and return a real exit code and stderr synchronously. A picked native action's count and timestamp are updated in$(herdr plugin config-dir es.quick-actions)/usage.json. - When the script exits, herdr tears the popup down and restores your previous pane and zoom state — nothing is left behind.
palette.sh takes three env vars so it can be driven without a TTY:
QA_CTX='{"pane":"w1:p1","tab":"w1:t1","workspace":"w1","cwd":"/repo"}'
# print the generated rows (TSV: kind, payload, display, keywords, hint)
HERDR_PANE_ID= QA_LIST_ONLY=1 QA_CONTEXT_JSON="$QA_CTX" bash palette.sh
# preselect a row and print the herdr command it would run, without running it
HERDR_PANE_ID= QA_DRY_RUN=1 QA_CHOICE=$'static\tswap_left' QA_CONTEXT_JSON="$QA_CTX" bash palette.shClear HERDR_PANE_ID when running it outside a popup, or the script's
self-close will try to close whatever pane you ran it from.
MIT © Eneko Sarasola