Fuzzy-jump to — or move — any pane across all Herdr workspaces (spaces), in one key.
Herdr's indexed switches (prefix+1..9) only reach tabs within the current
workspace, and there's no built-in "focus a specific tab/pane in another space"
action. Moving a pane between spaces has no keybinding at all — it's CLI-only
(herdr pane move). herdr-jump fills both gaps: it lists every pane across
every workspace and lets you pick one with fzf.
$ herdr-jump
enter: jump to it ctrl-o: move it elsewhere
jump>
rhesca / RTT / claude [idle]
rhesca / TCM1002 / claude [idle]
private / vecview / claude [working] *
private / notes / shell [unknown]
(* marks the pane you're currently in.) enter focuses the selected pane,
switching space and tab as needed. ctrl-o moves it instead — same list, so one
key covers both. (ctrl-m can't be used for this: terminals send it as enter.)
$ herdr-jump --move # or ctrl-o in the list above
move which pane> … # same list
move private / notes / shell to> # then: where?
rhesca / RTT
rhesca / TCM1002
private / vecview
+ new tab in rhesca
+ new tab in private
+ new space
Picking a tab splits it and drops the pane in — enter splits to the right,
ctrl-s splits downwards. + new tab puts the pane on a fresh tab in that
space, + new space gives it a space of its own. Focus follows the pane unless
you pass --stay. If the move empties the pane's old tab or space, Herdr closes
them for you.
A tab Herdr creates on its own is named after its number, so herdr pane move
on its own leaves a moved pane on a tab called "4". Both new-tab destinations
carry the source tab's name over instead (--label / --tab-label), unless that
name was itself just an auto-assigned number.
herdr(the CLI and socket API of the running session)fzfpython3
Drop the script on your PATH and make it executable:
curl -fsSL https://raw.githubusercontent.com/barewalker/herdr-jump/main/herdr-jump \
-o ~/.local/bin/herdr-jump
chmod +x ~/.local/bin/herdr-jumpThen bind it to keys with custom commands in ~/.config/herdr/config.toml:
[[keys.command]]
key = "prefix+f"
type = "pane"
command = "herdr-jump" # use an absolute path if ~/.local/bin isn't on the command's PATH
description = "jump to any pane across all spaces"
[[keys.command]]
key = "prefix+m"
type = "pane"
command = "herdr-jump --move"
description = "move a pane to another space"Apply it live (no restart):
herdr server reload-configNow prefix f (e.g. ctrl+z f) opens the switcher and prefix m the mover.
Why
type = "pane"and not"popup"? A popup restores focus to the invoking pane when it closes, which races with the focus change this script performs — over a remote/laggy client (herdr --remote, or SSH into the host) that desync can leave the client unresponsive until you detach and reattach.type = "pane"avoids it.
herdr-jump [--move] [--stay]
(no args) pick a pane across all spaces and focus it
--move start on the same list, but move the pane instead of jumping
--stay when moving, leave focus where it is instead of following the pane
--move only decides what enter does on the first list; ctrl-o gets you
there from a plain herdr-jump anyway. Binding both keys is just a shortcut for
"I already know I want to move something".
It's a plain script wired to a key via Herdr's custom-command keybinding — not a Herdr plugin. Everything happens outside Herdr's process:
[[keys.command]] type = "pane"runs the script in a temporary pane (which givesfzfa TTY) and closes it on exit.- The script calls
herdr workspace/tab/pane list(live JSON over the socket) and formats one row per pane as<pane_id>|<ws_id>|<tab_id>\t<space> / <tab> / <label> [status], piped tofzfwith the id column hidden (--with-nth=2). The pane the script itself runs in ($HERDR_PANE_ID) is left out — it's a meaningless choice, and a broken one to move.fzf --expect=ctrl-oreports which key ended the selection, which is how one list serves both jumping and moving. - Jumping focuses the target explicitly: space, then tab, then pane. Each step matters — focusing a pane does not pull its space and tab along — and the explicit path is more deterministic over a laggy/remote client than letting one call resolve against whichever pane the UI thinks is active.
- Moving builds a second list from the same live state (every tab except the pane's
own, plus a "new tab" row per space and a "new space" row) and runs the matching
herdr pane movevariant. A moved pane gets a new pane id, so the id to focus afterwards is read back out of the move result.
Because it re-reads live state every time, it never holds stale IDs and nothing is hardcoded.
The CLI's only direct-pane focus is herdr agent focus <target>, which resolves
agents — on a pane with no agent running (a plain shell) it fails with
agent_not_found, leaving you on the right tab but the wrong pane. The socket API
has a plain pane.focus, so the script sends that one request itself over
$HERDR_SOCKET_PATH (one request per connection — the server closes it after
replying) and falls back to the CLI if the socket isn't reachable.
herdr-jump is an ordinary script — edit it to show more columns (cwd, process),
change the sort order, or drop the + new space destination if you never want it.
Rebind the keys in config.toml and herdr server reload-config.
MIT — see LICENSE.