Skip to content

Latest commit

 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

herdr-quick-actions

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.

What's in the list

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.

Shortcuts

The shortcut column is resolved live, per row:

  1. herdr --default-config supplies 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.
  2. Your ~/.config/herdr/config.toml (or $HERDR_CONFIG_PATH) overrides them; an explicit "" means you unbound it.
  3. prefix+… bindings are rendered as the keys you actually press (ctrl+b shift+x), and herdr's indexed prefix+1..9 bindings are filled in per row — so Go to tab: notes shows ctrl+b 3 when it's tab 3.
  4. 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.

Search

Each row is title · shortcut · dim synonyms, and all three are searchable:

  • vsplit, maximize, kill, git and 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 v to 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,index prefers 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 — --nth indexes the fields of the --with-nth output, so a hidden TSV column is unreachable.

The preview strip under the list shows the exact herdr command a row will run.

What's deliberately not here

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.

Requirements

Bind a key

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-config

Now 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.

How it works

herdr actions run on the server with no TTY, so an action can't run fzf directly:

  1. es.quick-actions.open captures the origin pane/tab/workspace ids from HERDR_PLUGIN_CONTEXT_JSON and opens a small, centered popup (60%×70% of the terminal, placement = "popup" — a real modal window over the active pane, unlike the full-pane overlay placement), which does get a TTY — forwarding those ids in as QA_CONTEXT_JSON. This matters because the popup is a brand-new pane: without forwarding, pane close/split/focus/zoom would target the popup itself instead of the pane you meant.
  2. Inside the popup, palette.sh builds the ranked action list, resolves each row's keybinding, appends the live tab/workspace/agent/worktree targets, and pipes it all to fzf.
  3. On selection it runs the matching herdr tab|pane|workspace|agent|worktree|server command 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.
  4. When the script exits, herdr tears the popup down and restores your previous pane and zoom state — nothing is left behind.

Debugging

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.sh

Clear 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.

License

MIT © Eneko Sarasola

About

fzf picker for herdr's native tab/pane/workspace actions, ranked by usage — stop memorizing keybindings

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages