An opinionated deck manager for Herdr: one picker to browse, launch, and tear down agent workspaces bound to Git worktrees.
herdr-deck runs inside a Herdr pane and drives everything by shelling out to the
herdr and wt (worktrunk) CLIs.
No daemon, no async runtime, one small binary.
Important
This is my personal workflow extracted into a public binary, not a generic Herdr workspace manager. The picker is reusable; the deck it builds is deliberately coupled to my Neovim, agent, and terminal setup. Read Requirements and compatibility before installing.
╭─ herdr-deck [projects] ─╮╭──────────────────── dotfiles/main ────────────────────╮
│> dot▌ 3 / 42││ nvim │ codex │
╰─────────────────────────╯│ │ │
╭──────── Results ────────╮│ │ │
│> ● dotfiles/main ││──────────────────────────────┴────────────────────────│
│ ● dotfiles/ratatui-ux ││ zsh │
│ ▸ ~/dotfiles.wt/... ││ │
╰─────────────────────────╯╰───────────────────────────────────────────────────────╯
- Browse: live Herdr workspaces first (agents blocked on you sort to the top), then worktrunk worktrees, then your zoxide directories. Type to filter. The preview shows a live 2D thumbnail of each workspace's actual pane layout, or worktree status (branch, merge state, dirty flags), or a directory listing.
- Remotes: set
HERDR_DECK_REMOTESto a comma/space-separated list of SSH aliases and each remote Herdr server becomes an entry (⇄); Enter opens aherdr --remotethin client in its own terminal window, leaving the local session alone. Running it inside a pane would nest Herdr in Herdr. - Open: Enter on a live workspace focuses it. Enter on a worktree or
directory opens a launch form: pick an agent (found on your
PATHfrom Herdr's known-agent list), optionally enter a branch or Worktrunk shortcut (^,-,@,pr:N,mr:N, or a PR/MR URL), and go. Worktrunk creates or resolves the checkout, runs its lifecycle hooks, and returns its path to herdr-deck. The resulting deck is an editor + agent pane- full-width terminal + lazygit tab.
- Resume:
ctrl-sswitches to a separate session-history source, so past conversations never pollute workspace/path search. Type searches the first prompt and project path; Tab filters by agent. Claude, Codex, and Pi sessions resume in a recreated deck rooted at the session's original directory. Cursor opens its native session picker in that deck because its CLI does not expose a queryable local history store. - Create:
ctrl-nprompts for a new directory. A new worktree in an existing repo is just Enter on the repo plus the branch field. - Destroy:
ctrl-dcloses a workspace, or removes a worktree — but only when its branch is merged (worktrunk'sintegrated/emptystate); an unmerged worktree gets an explicit force-remove confirmation instead. Removed paths are purged from zoxide. - Clean up:
ctrl-gopens a separate cleanable-worktree source containing every integrated/empty linked worktree found through the known repositories. Clean entries show✓; integrated worktrees with staged, modified, untracked, renamed, or deleted files show!and are never included in batch removal. Filter the review list if desired, thenctrl-xremoves all visible clean entries after one count-and-project confirmation. Every entry is revalidated immediately before removal and no force flags are used.
Try Herdr Navigator. It is a configurable Herdr plugin that fuzzy-searches workspaces, agents, projects, sessions, remotes, directories, and actions. Sources can be disabled, custom command/JSON integrations can be added without changing Rust, and missing optional tools degrade quietly.
The distinction is intent: Herdr Navigator helps you jump to anything; herdr-deck recreates my particular working deck around the workspace, session, worktree, or directory you selected.
- Herdr 0.7.0 or newer, with
herdr-decklaunched from inside a Herdr session. It talks directly to Herdr's socket CLI and exits otherwise. Repository identity and linked-worktree state come from Herdr's native worktree API, with Git as a compatibility fallback. - A Unix-like environment. The core local workflow is intended for macOS or Linux; shell command construction and agent discovery assume Unix paths and process behavior.
gitfor repository detection, branch labels, and worktree-aware behavior.nvimfor every deck launch. It is not optional: the editor pane always runsnvim.- The CLI for whichever agent you select, available on
PATH. Herdr's direct agent integrations are strongly recommended so status indicators work.
Rust and Cargo are needed only to build from source. The binary does not install or modify Neovim plugins; the editor bridge is a separate conventional plugin.
| feature | dependency | behavior when missing |
|---|---|---|
| linked-worktree create/remove | worktrunk (wt) with JSON output |
ordinary directory decks still work; worktree actions and status do not |
| directory discovery | zoxide and/or fd |
that source becomes sparse or empty |
| directory preview | eza, with ls fallback |
falls back to plain ls -la |
| git tab | lazygit |
the tab is still created, but its command fails |
| Claude deck | Claude Code CLI + herdr-agents.nvim + claudecode.nvim | nvim opens, but Claude does not auto-start |
| Codex deck | Codex CLI + herdr-agents.nvim + codex.nvim | nvim opens, but Codex does not auto-start |
| agent-pane identification | pgrep, ps or Linux /proc, grep, sed, tr, sh |
same-tab geometry remains as a startup fallback |
| saved sessions | agent-owned local history files | only histories found at the supported hardcoded locations appear |
| remote entries | macOS open + Ghostty |
remote launch is unavailable on other terminals/platforms |
herdr-deck currently reads saved sessions from ~/.claude/projects,
~/.codex/sessions, and ~/.pi/agent/sessions. Cursor exposes its sessions
only through its own picker, so herdr-deck opens cursor-agent ls. The agents
own these storage formats and may change them without notice.
Claude and Codex are intentionally launched by their Neovim plugins, after the editor-side IDE server is ready. The reusable bridge now lives in herdr-agents.nvim, with claudecode.nvim and codex.nvim declared through your normal plugin manager. For lazy.nvim:
local inside_herdr = vim.env.HERDR_SOCKET_PATH
and vim.env.HERDR_SOCKET_PATH ~= ""
return {
{
"ctbaum/herdr-agents.nvim",
cond = inside_herdr,
lazy = false,
dependencies = {
{ "coder/claudecode.nvim", dependencies = { "folke/snacks.nvim" } },
{ "ishiooon/codex.nvim", dependencies = { "folke/snacks.nvim" } },
},
opts = {},
},
}The plugin manager owns installation, updates, pins, and removal. Existing
dependency checkouts are reused rather than duplicated. If those upstream
plugins already have specs in your configuration, keep one spec for each and
avoid calling their usual setup() inside Herdr—the bridge supplies the
terminal providers there. Outside Herdr, retain their normal configuration.
herdr-agents.nvim installs no key mappings and reserves no leader namespace.
It exposes the upstream :ClaudeCode* and :Codex* commands plus
:ClaudeHerdrSendSelection and :ClaudeHerdrSendDiagnostics; users bind only
what they want. Run :checkhealth herdr-agents for local diagnostics.
herdr-agents.nvim provides the editor-side integration:
- external terminal providers and IDE environment forwarding;
- prompt-readiness waits and a same-tab startup fallback;
- agent focus, send, selection, diagnostics, and native diff commands; and
- duplicate-agent protection.
To identify the correct agent pane after startup, the plugin matches the IDE
connection details against local process environments. It reads them with
pgrep and ps, or from /proc on Linux. herdr-deck forwards the launch
arguments and IDE environment variables to the new Herdr pane.
| variable | value set by herdr-deck |
|---|---|
HERDR_NVIM_AGENT |
claude or codex |
HERDR_NVIM_AGENT_ARGS_JSON |
JSON array containing the dangerous-mode flag when enabled and any saved-session resume arguments |
This ordering matters: both plugins create an editor-side server and pass connection variables to the agent process. Starting the CLI independently at the same time as Neovim introduces a race and can leave the agent running with no IDE connection. The binary only sets this launch contract; all editor-side behavior belongs to herdr-agents.nvim.
The shell-readiness match defaults to ➜. If your prompt does not contain that
symbol, set HERDR_NVIM_PROMPT_MATCH to stable text from your prompt; the agent
still launches after an eight-second timeout if it never matches.
| variable | direction | purpose |
|---|---|---|
HERDR_NVIM_AGENT, HERDR_NVIM_AGENT_ARGS_JSON |
herdr-deck → workspace | launcher-neutral editor-agent startup contract described above |
HERDR_DECK_REMOTES |
user → herdr-deck | comma/space-separated SSH aliases shown as remote entries |
HERDR_NVIM_PROMPT_MATCH |
user → Neovim adapter | shell-prompt text awaited before launching the agent; defaults to ➜ |
HERDR_NAV_PASSTHROUGH_RE |
user → navigation plugin | lets ctrl-j/k reach herdr-deck when using seamless pane navigation |
HERDR_* |
Herdr → processes | inherited session/socket identity; scrubbed only when opening a remote Ghostty window |
The launch form starts with dangerous mode enabled. Claude, Codex, Cursor,
Devin, Droid, Kimi, OpenCode, Kilo, Hermes, Qoder CLI, and other known agents
receive their built-in bypass/yolo flag or environment override when one is
known. Disable the toggle before launching to omit it. Review src/ext.rs
before using this on a machine or repository where that default is not
acceptable.
Herdr can install the repository, build the release binary, and expose its
herdr-deck.open action:
herdr plugin install ctbaum/herdr-deckBind that action in ~/.config/herdr/config.toml:
[[keys.command]]
key = "prefix+o"
type = "plugin_action"
command = "herdr-deck.open"
description = "Open herdr-deck"The plugin action uses Herdr's injected workspace context, so the temporary picker pane starts at the workspace root. Plugin installation requires Cargo because Herdr builds the Rust binary from source.
Install directly from GitHub (no clone needed):
cargo install --git https://github.com/ctbaum/herdr-deckOr from a clone:
git clone https://github.com/ctbaum/herdr-deck
cd herdr-deck
cargo install --path .Either standalone route puts the binary in ~/.cargo/bin (make sure that's on
your PATH). Install herdr-agents.nvim with your Neovim plugin manager as
described above, then bind the binary directly:
[[keys.command]]
key = "prefix+o"
type = "pane"
command = "herdr-deck"Both installation modes run the same binary. herdr-deck exits when its pane loses focus, so the temporary pane never sticks around.
| key | action |
|---|---|
| type | filter (esc clears) |
↵ |
focus workspace / open remote window / launch form / resume session |
ctrl-s |
switch projects / past sessions source |
ctrl-g |
toggle cleanable integrated-worktree source |
tab / shift-tab |
sessions: cycle agent filter |
ctrl-n |
new directory, then launch form |
ctrl-d |
close workspace / merge-gated worktree remove |
ctrl-x |
cleanable source: remove all visible clean entries |
ctrl-r |
reload |
ctrl-j/k |
move selection (needs passthrough, see below) |
? |
help |
esc |
back / quit |
If you use a Herdr Ctrl-H/J/K/L pane-navigation plugin (for example,
vim-herdr-navigation), add herdr-deck to its passthrough list so ctrl-j/k
reach the picker:
export HERDR_NAV_PASSTHROUGH_RE='^(lazygit|herdr-deck)$'herdr-deck mirrors my own personal workflow and layout:
- the deck layout is fixed: editor top-left, agent top-right, terminal bottom, lazygit on a new unfocused tab;
claudeandcodexare special-cased to start through Neovim and their IDE plugins, using the environment contract above; I plan to add Pi and OpenCode next.- remote entries spawn their window via macOS
open+ Ghostty, hardcoded.
The dependency table above describes the available fallbacks. The fixed layout and remote launcher are current design choices, not configurable paths.
herdr-deck recognizes any directory with a .git file as a worktree, so any
Worktrunk worktree-path layout works. Once selected, Worktrunk's JSON result
is authoritative for the checkout path and Herdr's native worktree metadata is
authoritative for repository identity. Removing a checkout also closes any
dedicated Herdr workspace rooted there; mixed workspaces lose only panes rooted
inside the removed checkout.
These constraints are part of herdr-deck's current opinionated scope. Issues describing broader workflows are welcome, but configurability is not guaranteed.
