Plugins
Herdr plugins are shareable, executable workflow packages. A plugin can be a Bash script, JavaScript app, Lua script, Rust binary, or any other argv command your machine can run. Herdr owns the host surface: installation, manifest validation, keybindings, terminal panes, events, invocation context, and socket access. The plugin owns its implementation language, dependencies, files, and durable state.
Plugins exist so Herdr can stay lean. The core stays focused on terminal workspaces, panes, agents, and a stable CLI/socket API. Plugins turn that existing extension surface into reusable workflows that people can build, install, and share without adding every workflow to Herdr itself.
A plugin is a directory with a herdr-plugin.toml manifest and commands
Herdr can launch. Herdr validates the manifest, injects runtime context,
starts the declared commands, and records logs. The commands call back
into Herdr through the CLI or socket when they need to do more work.
There is no separate plugin SDK or restricted command set. The entire
Herdr CLI is the plugin API. Every command in the CLI
reference is available to a plugin, and a plugin
can run anything you can run yourself as herdr .... Most plugins
should call Herdr through HERDR_BIN_PATH, which points at the running
Herdr binary. That keeps plugins portable across Unix sockets and
Windows named pipes. Use the socket API when you
want to send raw JSON requests yourself.
Runtime action registration and native non-terminal plugin UI are not part of plugin v1. Actions, event hooks, panes, and link handlers are all declared in the manifest.
Trust and security
A plugin is ordinary code that runs on your machine. Its build and runtime commands run as your user, inherit your environment, and can call the full Herdr CLI. Treat a plugin like any extension you add to an editor, shell, or coding agent.
Install or link plugins only from authors and repositories you trust.
Before installing or linking one, skim the herdr-plugin.toml manifest
and the scripts or binaries it runs. herdr plugin install shows a
preview of the source and the commands it will run in interactive
terminals, so you can review before confirming. Use --yes for sources
you already trust, and pin --ref when you want a specific revision.
Herdr validates the manifest and keeps each plugin’s config and state in its own directory, but it does not review or sandbox plugin code. Third-party plugins come from their authors, not Herdr; you are responsible for deciding whether to run them.
Manifest
The manifest is the contract between Herdr and the plugin. It declares package metadata, supported platforms, optional build commands, and the entrypoints Herdr can run.
id = "example.layout"name = "Layout"version = "0.1.0"min_herdr_version = "0.7.0"description = "Apply project layouts"platforms = ["linux", "macos", "windows"]
[[build]]command = ["npm", "ci"]
[[build]]command = ["npm", "run", "build"]platforms = ["linux", "macos"]
[[startup]]command = ["node", "dist/restore.js"]
[[actions]]id = "apply"title = "Apply layout"contexts = ["workspace"]command = ["node", "dist/apply.js"]
[[events]]on = "worktree.created"command = ["herdr", "workspace", "list"]
[[panes]]id = "board"title = "Project board"placement = "overlay"command = ["herdr-board"]
[[link_handlers]]id = "github-issue"title = "Open GitHub issue"pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$"action = "apply"
Top-level id, name, version, and min_herdr_version are required.
Set min_herdr_version to the oldest Herdr version that supports the
plugin APIs, event names, and manifest fields your plugin uses. Herdr
refuses to link or install a plugin when its minimum version is newer
than the current binary. description is optional. Plugin ids may use
ASCII letters, digits, dot, colon, underscore, and hyphen.
Action ids, pane ids, and link handler ids are local ids inside the
plugin. They may use ASCII letters, digits, colon, underscore, and
hyphen, but not dots. Each id type must be unique inside a plugin. Herdr
qualifies action ids as plugin.id.action when it needs a globally
unique name.
Use platforms = ["linux", "macos", "windows"] to declare where the
plugin can run. Build commands, startup hooks, actions, event hooks,
panes, and link handlers can also declare their own platforms;
item-level platforms override the top-level list. Local plugins without
top-level platforms link with a warning.
command values are argv arrays. Herdr does not run them through a
shell, so there is no shell expansion unless your command starts a shell
itself. Put language-specific behavior in your script or binary.
First plugin
Start with a directory that contains herdr-plugin.toml and one
executable script or program:
my-plugin/ herdr-plugin.toml index.jsid = "example.workspace-tools"name = "Workspace Tools"version = "0.1.0"min_herdr_version = "0.7.0"description = "Small workspace helpers"platforms = ["linux", "macos", "windows"]
[[actions]]id = "list-workspaces"title = "List workspaces"contexts = ["workspace"]command = ["node", "index.js"]
Inside the command, call back into Herdr with HERDR_BIN_PATH:
const { spawnSync } = require("node:child_process");
const herdr = process.env.HERDR_BIN_PATH ?? "herdr";const result = spawnSync(herdr, ["workspace", "list"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],});
process.stdout.write(result.stdout);process.stderr.write(result.stderr);process.exit(result.status ?? 1);
This example uses Node, but nothing about plugins requires Node. The manifest could launch Bash, PowerShell, Python, Rust, Go, Lua, Bun, or any other command available on the user’s machine.
Install and link
Install an example plugin:
herdr plugin install ogulcancelik/herdr-plugin-examples/agent-telegram-notifyherdr plugin config-dir examples.agent-telegram-notifyherdr plugin listherdr plugin action list --plugin examples.agent-telegram-notify
Terminal window
When you are authoring a local plugin, link the working directory instead:
herdr plugin link /path/to/pluginherdr plugin config-dir example.layoutherdr plugin action list --plugin example.layoutherdr plugin action invoke example.layout.applyherdr plugin pane open --plugin example.layout --entrypoint boardherdr plugin log list --plugin example.layout
Terminal window
plugin install accepts GitHub shorthand only, such as
owner/repo/subdir. It clones with git, shows a preview in
interactive terminals, runs supported build commands, then stores the
checkout under Herdr-managed plugin data and registers it. Use --yes
for noninteractive installs. Reinstalling a GitHub-managed plugin
replaces that managed checkout. Installed and linked plugins, including
their enabled state, are global to the current user and available in
every Herdr session. Both plugin install and plugin link can
register plugins while no Herdr server is running. Plugins installed
only in a named session on Herdr 0.7.3 must be installed or linked
again. Existing plugin config and state remain in place. Installing over
a locally linked plugin is refused; unlink or uninstall the local plugin
first. plugin install and plugin link create the plugin’s config and
state directories, and plugin config-dir <id> prints the config
directory for setup docs and shell scripts.
plugin uninstall <id-or-source> unregisters the plugin. For
GitHub-managed installs it also removes the managed checkout, and it
accepts either the plugin id or the same owner/repo[/subdir...]
shorthand used by install. plugin unlink <id> only unregisters a
plugin and leaves files alone, which is useful for local development.
There is no separate plugin update in v1; reinstall from GitHub to
refresh a managed plugin.
The example cookbook repo is ogulcancelik/herdr-plugin-examples. It
contains separate example plugins in subdirectories, including
agent-telegram-notify, github-link-preview, and
dev-layout-bootstrap. Use them as examples to copy; Herdr does not
maintain them as official plugins.
Build commands
Build commands run during GitHub plugin install after confirmation and
before Herdr registers the plugin. If a build command fails, install
aborts and the plugin is not registered. plugin link does not run
build commands; local authors build their working tree themselves. Build
commands may generate files, but changing herdr-plugin.toml after the
install preview aborts install. Build failures show the plugin id, build
index, working directory, command, exit status or spawn error, and
capped stdout/stderr without interpreting tool output.
Build commands are plain argv commands too, but they do not receive
runtime plugin context or Herdr socket env. Plugin authors should
document required system tools such as cargo, npm, bun, or lua;
Herdr reports build failures but does not install missing toolchains.
Startup hooks
[[startup]] commands run once for each enabled plugin after Herdr
restores the session and its API socket is ready. They run again when a
new server takes over during live handoff, but not when a client
attaches, config reloads, or a plugin is linked or enabled. Herdr starts
them asynchronously and records their completion in the normal plugin
command log. A startup failure does not stop the server.
Startup hooks are one-shot initialization commands rather than
supervised daemons. A hook should restore plugin-owned state, call any
required Herdr APIs, and exit. For example, a plugin can save a
declarative Agent view under HERDR_PLUGIN_STATE_DIR, then read and
reapply that view from its startup hook.
Startup hooks receive the normal runtime plugin environment and
HERDR_PLUGIN_EVENT=startup. The install preview lists every startup
command so users can review code that will run automatically.
Commands and environment
Runtime commands run with the plugin directory as their working
directory. Herdr injects HERDR_SOCKET_PATH, HERDR_BIN_PATH,
HERDR_ENV=1, HERDR_PLUGIN_ID, HERDR_PLUGIN_ROOT,
HERDR_PLUGIN_CONFIG_DIR, HERDR_PLUGIN_STATE_DIR,
HERDR_PLUGIN_CONTEXT_JSON, and any available HERDR_WORKSPACE_ID,
HERDR_TAB_ID, and HERDR_PANE_ID. Action commands also receive
HERDR_PLUGIN_ACTION_ID; startup and event hooks receive
HERDR_PLUGIN_EVENT (startup for startup hooks), event hooks
additionally receive HERDR_PLUGIN_EVENT_JSON, and pane commands
receive HERDR_PLUGIN_ENTRYPOINT_ID.
HERDR_PLUGIN_ROOT is the installed or linked plugin directory. Do not
store user credentials or durable state there, because GitHub-installed
plugin roots are managed source checkouts. Put user-editable config such
as .env files under HERDR_PLUGIN_CONFIG_DIR, and put local runtime
state under HERDR_PLUGIN_STATE_DIR. Herdr creates those directories
and seeds HERDR_PLUGIN_CONFIG_DIR from the legacy plugin config
locations when present, but it does not validate, sync, or delete their
contents. The plugin owns the file format and lifecycle.
HERDR_PLUGIN_CONTEXT_JSON can include workspace, tab, focused pane,
worktree, agent, selected text, clicked URL, and link handler fields
when they are available for that invocation. Shell plugins can read the
individual env vars for common ids, or parse the context JSON for the
full shape.
Use HERDR_BIN_PATH when a plugin needs to call Herdr portably from
Node, PowerShell, Bash, or another runtime. The raw socket transport
behind HERDR_SOCKET_PATH is OS-specific: Unix clients connect to a
Unix socket path, while Windows clients connect to a named pipe. CLI
calls through HERDR_BIN_PATH avoid that transport difference. See the
CLI reference for available commands and socket
API for raw request shapes.
Panes
Manifest pane placement defaults to overlay, which opens a temporary
zoomed overlay over the active pane and restores the previous focus and
zoom when it closes. A plugin.pane.open request can override the
manifest placement with overlay, popup, split, tab, or zoomed.
placement = "popup" opens a session-modal terminal popup without
changing the tiled layout. It accepts optional width and height
fields in the manifest or open request; omit them for the default
half-size popup, use numbers for outer terminal-cell dimensions, or use
strings like "80%" for a percentage of the terminal area. It receives
all terminal input, including Escape, and closes when the command exits
or a popup.close request is sent. Dimensions smaller than the popup
minimum are clamped.
Declare the placement directly on a plugin pane entrypoint when the pane should always be transient:
[[panes]]id = "picker"title = "Picker"platforms = ["linux", "macos"]placement = "popup"width = "80%"height = 20command = ["sh", "picker.sh"]
Split, tab, zoomed, and overlay plugin panes are normal Herdr panes
after they open. Plugins can call standard pane APIs such as
pane.move, pane.swap, pane.resize, and pane.zoom through the
socket or CLI; Herdr keeps plugin pane ownership attached to the
underlying pane when it moves across tabs or workspaces. A popup is a
singleton session resource rather than a Herdr pane: it has no pane ID,
does not change plugin focus context, emits no pane lifecycle events,
and does not participate in pane, layout, persistence, or agent APIs.
Its process does not receive HERDR_PANE_ID; the underlying tiled pane
remains available through HERDR_PLUGIN_CONTEXT_JSON. Opening a popup
returns ui_busy while Settings, Copy mode, or another Herdr modal is
active, and plugin.pane.open returns an ok result after launch.
On Windows, build commands, action commands, and event commands resolve
common PATHEXT shims such as npm.cmd, bun.cmd, and pnpm.cmd when
the bare command is on PATH. Pane commands use Herdr’s normal Windows
pane launcher and must still be valid Windows argv commands.
Keybindings
Bind a key to an installed plugin action:
[[keys.command]]key = "prefix+l"type = "plugin_action"command = "example.layout.apply"description = "apply layout"Link handlers
Use [[link_handlers]] to route modified clicks on matching terminal
URLs to a plugin action instead of opening the URL in the browser. The
modified-click modifier is Control on every platform, including macOS,
because captured terminal mouse reports do not expose Command/Super
separately from a plain click. pattern is a Rust regular expression
matched against the clicked URL, and action must name an action
declared by the same plugin. Link handler actions receive
invocation_source = "link_click", clicked_url, and link_handler_id
in HERDR_PLUGIN_CONTEXT_JSON; shell plugins can also read
HERDR_PLUGIN_CLICKED_URL and HERDR_PLUGIN_LINK_HANDLER_ID. Handlers
are checked in manifest order inside each plugin.
Storage
There is no Herdr-managed plugin storage API in v1. Plugins that need durable state should own their files or database.
Marketplace
Community plugins are discoverable in the marketplace, an
automatic index of public GitHub repositories tagged with herdr-plugin
that contain one or more herdr-plugin.toml files whose required
metadata can be parsed. Plugins stay ordinary GitHub repositories:
publish one, then share herdr plugin install owner/repo[/subdir].
To get plugins listed, add the GitHub topic herdr-plugin and place
their manifests at the root or in subdirectories of the repository’s
default branch. One repository card can contain multiple separately
installable plugins. The index refreshes every 30 minutes. See
Marketplace for how discovery works.
Last updated Oct 08, 2026