README
¶
herdr-plus
herdr-plus is an add-on platform for herdr — a place to build extensions and plugins on top of herdr's terminal panes. The same binary can run in different modes; each mode decides what to do when it talks to herdr.
We're in explore mode: the list of modes will grow over time.
Modes
Pick a mode with --mode=<slug>. With no flag, the default mode runs.
| Mode | Slug | Key | What it does |
|---|---|---|---|
| Control | control (default) |
prefix+up |
herdr-plus's home base — a full-screen workspace for driving herdr. First feature: Projects. |
| Quick Actions | quick-actions |
prefix+down |
A fuzzy launcher: pick an action and run it in a split. |
herdr-plus # default mode (control)
herdr-plus --mode=quick-actions # the fuzzy launcher
herdr-plus version # print the version and exit
Control mode & Projects
Pressing prefix+up opens a brand-new, full-screen herdr workspace titled
Herdr Plus with a projects tab, and runs the projects browser there. This is
control mode — over time it will gain more features; today it has Projects.
A project is a declarative herdr workspace template: a name, a description, a
working directory, and an ordered list of tabs (each with an optional startup
command). Fuzzy-find a project, press enter, and herdr-plus spins up a whole
workspace — every tab created and every command running — then closes the ephemeral
"Herdr Plus" workspace. It replaces hand-written workspace shell scripts with simple
config files.
Projects live in ~/.config/herdr-plus/projects/ (honoring $XDG_CONFIG_HOME), one
TOML file per project (the file name doesn't matter):
name = "Options Cafe"
description = "The main options.cafe monorepo"
working_dir = "~/Development/options-cafe/options.cafe" # ~ and $VARS expand
[[tabs]]
name = "claude"
command = "claude --dangerously-skip-permissions --chrome"
[[tabs]]
name = "lazygit"
command = "lazygit"
[[tabs]]
name = "terminal" # no command — just an empty shell
Tabs open in file order; the first tab reuses the workspace's root tab and the rest
are created behind it. A tab with no command is just an empty shell. With no
project files yet, control mode shows an onboarding screen explaining all of this.
Split panes within a tab
A tab can hold up to 4 panes. Instead of a single command, give the tab
[[tabs.panes]] entries. Each pane after the first sets split to "down"
(stacked, top/bottom) or "right" (side by side) — the direction it splits off the
previous pane. An omitted split defaults to "down".
[[tabs]]
name = "server"
[[tabs.panes]]
command = "php artisan serve"
[[tabs.panes]]
command = "npm run dev"
split = "down"
A tab uses either command or [[tabs.panes]], not both. In the projects list,
split tabs are shown with a ×N pane count (e.g. server ×2).
Installing
Homebrew (the repo is its own tap):
brew tap cloudmanic/herdr-plus https://github.com/cloudmanic/herdr-plus
brew install cloudmanic/herdr-plus/herdr-plus
Install script (Linux/macOS, no Homebrew):
curl -fsSL https://raw.githubusercontent.com/cloudmanic/herdr-plus/main/install.sh | sh
From source:
make build && make install-bin
Every merge to main auto-bumps the patch version and cuts a new GitHub Release
with cross-compiled binaries; brew upgrade / re-running the install script
pulls the latest.
The bare command opens a focused split beneath your current pane and runs the picker there. Choose an action, and the pane closes itself when the action runs.
Install the keybinding
herdr-plus install # binds BOTH modes: prefix+up -> control, prefix+down -> quick-actions
herdr-plus install --mode=quick-actions # bind just quick-actions (prefix+down)
herdr-plus install --key=prefix+a # override the key for a single mode
A bare herdr-plus install wires up every mode on its own default key (control →
prefix+up, quick-actions → prefix+down) in one shot. Pass --mode to install
just one, and --key to override that mode's key.
install adds a [[keys.command]] entry to herdr's config.toml that runs the
absolute path of the binary you invoked, then reloads the running herdr
server. It is idempotent (it won't duplicate an existing herdr-plus binding) and
refuses to overwrite a key already bound to something else. After installing,
press your herdr prefix (default ctrl+b) followed by the bound key.
Configuration
All config lives under ~/.config/herdr-plus/ (honoring $XDG_CONFIG_HOME):
~/.config/herdr-plus/
projects/ # one *.toml per project (control mode)
options-cafe.toml
bevio.toml
...
quick-actions/ # one *.toml per action (quick-actions mode)
github.toml
google.toml
...
For quick-actions, each *.toml defines one action; the directory is seeded
with editable examples the first time you run the mode. For projects (see
Control mode & Projects), each *.toml defines one
project and the directory starts empty — control mode's onboarding screen explains
how to add your first one. In both cases: add a file to add an entry, delete a file
to remove it.
Per-project quick actions
A repo can ship its own quick actions. Add a .herdr-plus/ directory at the repo
root that mirrors the global layout, and drop one *.toml per action into its
quick-actions/ subdirectory — same format as your global actions:
your-repo/
.herdr-plus/
quick-actions/
make-build.toml
make-test.toml
When you launch the quick-actions picker from inside that repo, its project
actions appear grouped under a Project heading, above your Global ones, so
it is always clear which is which. (Start typing to filter and the two groups
merge into a single ranked list.) Launch from a repo with no .herdr-plus
directory and the picker looks exactly as before — a single, ungrouped list. The
directory is read-only and never auto-created: it shows up only when a repo
actually provides it. This repo ships one as a live example (make build /
make test).
Actions
An action has a name, a description, a command, and a type. The command
is run through sh -c, in the working directory you launched from, with the
context exported as HERDR_PLUS_* environment variables.
The command is a Go text/template rendered
against the run context (see Variables).
Type: command (default)
Runs immediately when selected.
name = "GitHub"
description = "Open https://github.com"
command = "open https://github.com"
Type: select
Shows a second fuzzy list of options. The chosen option's value becomes
{{.Value}}. If value is omitted, the label is used. An optional
description shows dim text next to the label (the value itself is never
shown, so you can encode data into it without cluttering the list).
name = "Open Repo on GitHub"
description = "Pick a repo and open it"
type = "select"
command = "open https://github.com/cloudmanic/{{.Value}}"
[[options]]
label = "Herdr Plus"
value = "herdr-plus"
description = "cloudmanic/herdr-plus"
[[options]]
label = "Options Cafe"
value = "options-cafe"
description = "cloudmanic/options-cafe"
To visually group options, add a separator: an option with no label. Give
it a heading to show a dim group title, or leave it blank for a plain spacer.
Separators are not selectable, are skipped when navigating, and disappear while
you filter.
[[options]]
heading = "Cascade" # a labeled group header
[[options]]
label = "Options Cafe"
value = "cascade https://github.com/users/cloudmanic/projects/8"
[[options]] # a blank spacer (no label, no heading)
[[options]]
label = "Options Cafe (Rager)"
value = "rager https://github.com/users/cloudmanic/projects/8"
Type: form
Shows a text field. What you type becomes {{.Value}}. The [form] table is
optional.
name = "Search Google"
description = "Type a query and open the results"
type = "form"
command = "open 'https://www.google.com/search?q={{.Value | urlquery}}'"
[form]
prompt = "Search Google for"
placeholder = "e.g. herdr terminal multiplexer"
Passing the value
If your command references {{.Value}}, the value is placed exactly there. If it
doesn't, the value is appended as a single shell-quoted final argument — so
command = "my-script" becomes my-script 'the value'.
Variables
Every action's command template can use these fields. The same values are also
exported to the command's environment with a HERDR_PLUS_ prefix (e.g.
{{.WorkDir}} is also $HERDR_PLUS_WORKDIR).
| Template | Env var | Meaning |
|---|---|---|
{{.Value}} |
HERDR_PLUS_VALUE |
Selected option / entered text (select & form). |
{{.WorkDir}} |
HERDR_PLUS_WORKDIR |
Directory you launched herdr-plus from. |
{{.SessionTitle}} |
HERDR_PLUS_SESSION_TITLE |
herdr workspace label (often the repo name). |
{{.SessionId}} |
HERDR_PLUS_SESSION_ID |
herdr workspace id. |
{{.WorkspaceLabel}} |
HERDR_PLUS_WORKSPACE_LABEL |
Same as SessionTitle. |
{{.WorkspaceId}} |
HERDR_PLUS_WORKSPACE_ID |
Same as SessionId. |
{{.TabLabel}} |
HERDR_PLUS_TAB_LABEL |
herdr tab label. |
{{.TabId}} |
HERDR_PLUS_TAB_ID |
herdr tab id. |
{{.PaneId}} |
HERDR_PLUS_PANE_ID |
Pane you launched from. |
{{.TerminalId}} |
HERDR_PLUS_TERMINAL_ID |
herdr terminal id. |
{{.Agent}} |
HERDR_PLUS_AGENT |
Agent running in the pane, if any. |
{{.AgentSessionId}} |
HERDR_PLUS_AGENT_SESSION_ID |
That agent's session id. |
{{.Home}} |
— | Your home directory. |
Building
go build -o herdr-plus .
go test ./...
Adding a mode
- Add a
Modevalue and register it inmodesinmode.go. - (Optional) Add bundled example actions under
examples/<slug>/. - Teach the launcher/picker how the mode behaves where it differs.
Documentation
¶
There is no documentation for this package.