diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/next/CHANGELOG.md | 10 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/cli-reference.mdx | 81 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/configuration.mdx | 11 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/index.mdx | 10 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/marketplace.mdx | 36 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/plugins.mdx | 189 | ||||
| -rw-r--r-- | docs/next/website/src/content/docs/socket-api.mdx | 247 |
7 files changed, 574 insertions, 10 deletions
diff --git a/docs/next/CHANGELOG.md b/docs/next/CHANGELOG.md index c4271cf6..13e84347 100644 --- a/docs/next/CHANGELOG.md +++ b/docs/next/CHANGELOG.md @@ -3,6 +3,16 @@ ## Unreleased ### Added +- Added local plugin v1 support with `plugin.link/list/unlink/enable/disable`, + manifest-declared actions, event hooks, managed plugin panes, link handlers, + command logs, keybinding integration, and authoring docs under Preview docs. +- Added `herdr plugin install <owner>/<repo>[/subdir...]`, `plugin uninstall`, + source metadata in `plugin.list`, offline registry fallback, and a + human-readable default `plugin list` with `--json` for scripts. +- Added supporting plugin host APIs for `pane.current`, `pane.process_info`, + `client.window_title.set/clear`, `layout.export/apply`, plugin pane placement, + plugin invocation context/env injection, and plugin pane ownership across + `pane.move`. - Added `pane.move` and `herdr pane move` to relocate a running pane into another tab, a new tab, or a new workspace without restarting its terminal process. (#299) ### Changed diff --git a/docs/next/website/src/content/docs/cli-reference.mdx b/docs/next/website/src/content/docs/cli-reference.mdx index fc6be710..106823b3 100644 --- a/docs/next/website/src/content/docs/cli-reference.mdx +++ b/docs/next/website/src/content/docs/cli-reference.mdx @@ -69,7 +69,7 @@ Use `default` as the session name when you need to stop the default session expl ```bash herdr workspace list -herdr workspace create [--cwd PATH] [--label TEXT] [--focus] [--no-focus] +herdr workspace create [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] herdr workspace get <workspace_id> herdr workspace focus <workspace_id> herdr workspace rename <workspace_id> <label> @@ -99,7 +99,7 @@ Worktrees are normal Herdr workspaces with Git checkout provenance. `worktree cr ```bash herdr tab list [--workspace <workspace_id>] -herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--label TEXT] [--focus] [--no-focus] +herdr tab create [--workspace <workspace_id>] [--cwd PATH] [--label TEXT] [--env KEY=VALUE] [--focus] [--no-focus] herdr tab get <tab_id> herdr tab focus <tab_id> herdr tab rename <tab_id> <label> @@ -110,15 +110,17 @@ herdr tab close <tab_id> ```bash herdr pane list [--workspace <workspace_id>] +herdr pane current [--pane ID|--current] herdr pane get <pane_id> herdr pane layout [--pane ID|--current] +herdr pane process-info [--pane ID|--current] herdr pane neighbor --direction left|right|up|down [--pane ID|--current] herdr pane edges [--pane ID|--current] herdr pane focus --direction left|right|up|down [--pane ID|--current] herdr pane resize --direction left|right|up|down [--amount FLOAT] [--pane ID|--current] herdr pane zoom [<pane_id>|--pane ID|--current] [--toggle|--on|--off] herdr pane rename <pane_id> <label>|--clear -herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--focus] [--no-focus] +herdr pane split [<pane_id>|--pane ID|--current] --direction right|down [--ratio FLOAT] [--cwd PATH] [--env KEY=VALUE] [--focus] [--no-focus] herdr pane swap --direction left|right|up|down [--pane ID|--current] herdr pane swap --source-pane ID --target-pane ID herdr pane move <pane_id> --tab <tab_id> --split right|down [--target-pane ID] [--ratio FLOAT] [--focus|--no-focus] @@ -181,6 +183,10 @@ herdr pane report-metadata <pane_id> \ `STATUS` is one of `idle`, `working`, `blocked`, `done`, or `unknown`. `--agent` is a guard for the authoritative agent label. `--applies-to-source` is a guard for the active lifecycle authority source. Use `--display-agent` to change the visible name. +Metadata text is normalized before storage. Herdr trims surrounding whitespace, removes control characters, caps `--custom-status` at 32 characters, and caps `--title`, `--display-agent`, and each `--state-label` value at 80 characters. Empty normalized values are ignored. + +`--source` and `--applies-to-source` must be 80 characters or fewer and may contain only ASCII letters, digits, colon, dot, underscore, and hyphen. `--ttl-ms` makes metadata expire automatically and must be between `1` and `86400000` milliseconds. Omit it for metadata that should stay until replaced, cleared, or the pane closes. `--seq` lets Herdr ignore stale reports from the same `--source`; stale reports are accepted by the API but ignored by pane state. + ## Agents ```bash @@ -192,7 +198,7 @@ herdr agent rename <target> <name>|--clear herdr agent focus <target> herdr agent wait <target> --status <idle|working|blocked|unknown> [--timeout MS] herdr agent attach <target> [--takeover] -herdr agent start <name> [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--focus|--no-focus] -- <argv...> +herdr agent start <name> [--cwd PATH] [--workspace ID] [--tab ID] [--split right|down] [--env KEY=VALUE] [--focus|--no-focus] -- <argv...> herdr agent explain <target> [--json|--verbose] herdr agent explain --file PATH --agent LABEL [--json|--verbose] ``` @@ -209,9 +215,12 @@ Use `pane send-text`, `pane send-keys`, `pane run`, and `terminal attach` for or ```bash herdr terminal attach <terminal_id> [--takeover] +herdr terminal title set <title> +herdr terminal title clear ``` Detach from direct attach with `ctrl+b q`. Send literal `ctrl+b` with `ctrl+b ctrl+b`. +`terminal title clear` restores Herdr's default outer terminal window title. ## Waits @@ -259,6 +268,66 @@ herdr integration uninstall cursor herdr integration status [--outdated-only] ``` +## Plugins + +Plugin commands install and run local executable workflow plugins. A plugin is a manifest plus out-of-process commands; Herdr owns the host surface and plugins own their implementation language. + +Install, list, and remove plugins: + +```bash +herdr plugin install <owner>/<repo>[/subdir...] [--ref REF] [--yes] +herdr plugin list [--plugin ID] [--json] +herdr plugin uninstall <plugin_id|owner/repo[/subdir...]> +herdr plugin enable <plugin_id> +herdr plugin disable <plugin_id> +``` + +`plugin install` accepts GitHub shorthand only, such as `ogulcancelik/herdr-plugin-examples/worktree-bootstrap`. It uses `git`, shows a trust preview in interactive terminals, runs supported manifest build commands, and stores GitHub installs in a Herdr-managed directory. Use `--yes` for noninteractive installs. Reinstalling a GitHub-managed plugin replaces that managed checkout. Installing over a locally linked plugin is refused. `plugin list` is human-readable by default; pass `--json` for the raw API response. + +Local development: + +```bash +herdr plugin link <path> [--disabled] +herdr plugin unlink <plugin_id> +``` + +`plugin link` accepts a plugin directory containing `herdr-plugin.toml` or a direct manifest path. It is still the right command while authoring or testing a plugin from a local checkout. `plugin unlink` unregisters the plugin and leaves files alone. `plugin uninstall` unregisters a plugin and also removes Herdr-managed GitHub checkout files. For GitHub installs, uninstall accepts either the plugin id or the same `owner/repo[/subdir...]` shorthand used by install. Actions, event hooks, panes, and link handlers are declared in the manifest; runtime action registration is not part of v1. + +Actions: + +```bash +herdr plugin action list [--plugin ID] +herdr plugin action invoke <action_id> [--plugin ID] +``` + +`plugin action invoke` starts the manifest command for an installed, enabled, +platform-compatible plugin action and prints the started command log record in +the JSON response. Use the qualified action id (`plugin.id.action`) when more +than one plugin uses the same action id. Local action ids cannot contain dots, +so qualified ids remain unambiguous even when plugin ids contain dots. + +Logs: + +```bash +herdr plugin log list [--plugin ID] [--limit N] +``` + +Managed terminal panes: + +```bash +herdr plugin pane open --plugin ID --entrypoint ID [--placement overlay|split|tab|zoomed] [--workspace ID] [--target-pane PANE] [--direction right|down] [--cwd PATH] [--env KEY=VALUE] [--focus|--no-focus] +herdr plugin pane focus <pane_id> +herdr plugin pane close <pane_id> +``` + +`plugin pane open` requires the plugin to be linked, enabled, and compatible +with the current platform. It starts a manifest-declared `[[panes]]` command as +a Herdr-managed terminal pane. The manifest default is `overlay`, which opens a +temporary zoomed overlay over the active pane. It can also open as a split, a +new tab, or a zoomed pane. Native non-terminal plugin panes are a later surface. + +`--env KEY=VALUE` can be repeated on process-launching commands. It applies to the newly launched process only. Herdr-managed variables such as `HERDR_SOCKET_PATH`, `HERDR_BIN_PATH`, `HERDR_ENV`, `HERDR_WORKSPACE_ID`, `HERDR_TAB_ID`, `HERDR_PANE_ID`, `HERDR_PLUGIN_ID`, `HERDR_PLUGIN_ROOT`, `HERDR_PLUGIN_CONFIG_DIR`, `HERDR_PLUGIN_STATE_DIR`, `HERDR_PLUGIN_ENTRYPOINT_ID`, and `HERDR_PLUGIN_CONTEXT_JSON` stay authoritative when they conflict with caller-provided env. + ## Read sources | Source | Meaning | @@ -275,5 +344,9 @@ herdr integration status [--outdated-only] | `HERDR_CONFIG_PATH` | Override the config file path. | | `HERDR_SESSION` | Select a named session for CLI commands. | | `HERDR_SOCKET_PATH` | Low-level socket path override. | +| `HERDR_ENV` | Set to `1` inside Herdr-managed pane processes. | +| `HERDR_PANE_ID` | Public pane id for the running pane process. | +| `HERDR_TAB_ID` | Public tab id for the running pane process. | +| `HERDR_WORKSPACE_ID` | Public workspace id for the running pane process. | | `HERDR_LOG` | Set log filter, for example `HERDR_LOG=herdr=debug`. | | `HERDR_DISABLE_SOUND` | Disable sound playback even when sound notifications are enabled. | diff --git a/docs/next/website/src/content/docs/configuration.mdx b/docs/next/website/src/content/docs/configuration.mdx index c22e0107..8fc35afb 100644 --- a/docs/next/website/src/content/docs/configuration.mdx +++ b/docs/next/website/src/content/docs/configuration.mdx @@ -224,6 +224,17 @@ description = "run lazygit" `type = "shell"` runs detached in the background. +`type = "plugin_action"` invokes an installed plugin action id. Use the +qualified id when action ids are not globally unique: + +```toml +[[keys.command]] +key = "prefix+l" +type = "plugin_action" +command = "example.layout.apply" +description = "apply layout" +``` + An optional `description` can be provided. When specified, this description is displayed in the keybind help panel (opened with `prefix+?`) in place of the default `'custom command'` label. Custom commands receive `HERDR_SOCKET_PATH`, `HERDR_BIN_PATH`, `HERDR_ACTIVE_WORKSPACE_ID`, `HERDR_ACTIVE_TAB_ID`, `HERDR_ACTIVE_PANE_ID`, and `HERDR_ACTIVE_PANE_CWD` when those values are available. Shell commands run from the focused pane's working directory when Herdr can detect it. diff --git a/docs/next/website/src/content/docs/index.mdx b/docs/next/website/src/content/docs/index.mdx index 68e1d869..1502462d 100644 --- a/docs/next/website/src/content/docs/index.mdx +++ b/docs/next/website/src/content/docs/index.mdx @@ -64,4 +64,14 @@ The guide teaches your agent Herdr's concepts, setup, configuration, and common [Read the API guide →](/docs/socket-api/) </Card> + <Card title="Plugins"> + Author local executable workflow plugins with manifest actions and event hooks. + + [Write a plugin →](/docs/plugins/) + </Card> + <Card title="Marketplace"> + Share plugins from GitHub today and tag your repo to be listed when the marketplace launches. + + [Publish a plugin →](/docs/marketplace/) + </Card> </CardGrid> diff --git a/docs/next/website/src/content/docs/marketplace.mdx b/docs/next/website/src/content/docs/marketplace.mdx new file mode 100644 index 00000000..e06a1f79 --- /dev/null +++ b/docs/next/website/src/content/docs/marketplace.mdx @@ -0,0 +1,36 @@ +--- +title: Marketplace +description: Publish and share Herdr plugins, and get listed when the marketplace launches. +--- + +The Herdr plugin marketplace is a discoverable index of installable plugins. It +is not live yet. This page is how to be ready for it. + +## Share a plugin today + +The marketplace adds discovery on top of installation; it does not replace it. +Anyone can already install your plugin straight from GitHub: + +```bash +herdr plugin install owner/repo[/subdir...] +``` + +Publish a normal public GitHub repository with a `herdr-plugin.toml` manifest at +its root, or in a subdirectory, and that command works now. See +[Plugins](/docs/plugins/) for the manifest and authoring reference. + +## Get listed when it launches + +Add the GitHub topic `herdr-plugin` to your repository. That topic is the exact +signal the future index will use for discovery, so tagging now means your plugin +is indexed on day one, with no migration and no resubmission. + +## What a listing will show + +A marketplace card will surface your plugin's `id`, `name`, `description`, +declared `platforms`, and a link back to the source repository. Getting that +manifest metadata right today is what makes your listing accurate later. Declare +`platforms` honestly, because the index will use it to show where a plugin runs. + +Plugins stay ordinary GitHub repositories. The marketplace is a discovery and +trust layer on top of the same `herdr plugin install` path you can use now. diff --git a/docs/next/website/src/content/docs/plugins.mdx b/docs/next/website/src/content/docs/plugins.mdx new file mode 100644 index 00000000..652b44a0 --- /dev/null +++ b/docs/next/website/src/content/docs/plugins.mdx @@ -0,0 +1,189 @@ +--- +title: Plugins +description: Author local Herdr plugins with manifest actions, event hooks, and panes. +--- + +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. + +A plugin directory contains `herdr-plugin.toml`. Herdr installs or links that +manifest, then runs declared action, event, and pane commands out of process. +The manifest is the contract. Runtime action registration and native +non-terminal plugin UI are not part of plugin v1. + +## Manifest + +```toml +id = "example.layout" +name = "Layout" +version = "0.1.0" +description = "Apply project layouts" +platforms = ["linux", "macos", "windows"] + +[[build]] +command = ["npm", "ci"] + +[[build]] +command = ["npm", "run", "build"] +platforms = ["linux", "macos"] + +[[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" +``` + +Actions are manifest-only. Pane commands are manifest-only too; +`plugin.pane.open` selects a declared `[[panes]]` entrypoint and does not +accept runtime argv. 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. + +Use `platforms` to declare where the plugin can run. Local plugins without +`platforms` link with a warning. + +## Install and link + +Install an example plugin: + +```bash +herdr plugin install ogulcancelik/herdr-plugin-examples/agent-telegram-notify +herdr plugin list +herdr plugin action list --plugin examples.agent-telegram-notify +``` + +When you are authoring a local plugin, link the working directory instead: + +```bash +herdr plugin link /path/to/plugin +herdr plugin action list --plugin example.layout +herdr plugin action invoke example.layout.apply +herdr plugin pane open --plugin example.layout --entrypoint board +herdr plugin log list --plugin example.layout +``` + +`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. +Installing over a locally linked plugin is refused; unlink or uninstall the +local plugin first. + +`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`. These are examples to copy, +not maintained 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. + +## 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`; event hooks receive `HERDR_PLUGIN_EVENT` and +`HERDR_PLUGIN_EVENT_JSON`; 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 does not read, create, migrate, 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. + +## 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`, `split`, `tab`, or `zoomed`. + +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. + +## Keybindings + +Bind a key to an installed plugin action: + +```toml +[[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 Linux and Windows, and Command/Super on macOS. `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 + +A plugin marketplace is coming. You can already publish a GitHub repository and +share it with `herdr plugin install owner/repo[/subdir]`. Tag your repo with the +GitHub topic `herdr-plugin` now to be listed when the marketplace launches. See +[Marketplace](/docs/marketplace/) for how publishing and discovery will work. diff --git a/docs/next/website/src/content/docs/socket-api.mdx b/docs/next/website/src/content/docs/socket-api.mdx index fb124ac9..4f8071ad 100644 --- a/docs/next/website/src/content/docs/socket-api.mdx +++ b/docs/next/website/src/content/docs/socket-api.mdx @@ -82,13 +82,16 @@ Raw socket method names use dot notation: | --- | --- | | Server | `ping`, `server.stop`, `server.reload_config`, `server.agent_manifests`, `server.reload_agent_manifests` | | Notification | `notification.show` | +| Client | `client.window_title.set`, `client.window_title.clear` | | Workspace | `workspace.create`, `workspace.list`, `workspace.get`, `workspace.focus`, `workspace.rename`, `workspace.close` | | Worktree | `worktree.list`, `worktree.create`, `worktree.open`, `worktree.remove` | | Tab | `tab.create`, `tab.list`, `tab.get`, `tab.focus`, `tab.rename`, `tab.close` | -| Pane | `pane.split`, `pane.swap`, `pane.move`, `pane.zoom`, `pane.layout`, `pane.neighbor`, `pane.edges`, `pane.focus_direction`, `pane.resize`, `pane.list`, `pane.get`, `pane.rename`, `pane.send_text`, `pane.send_keys`, `pane.send_input`, `pane.read`, `pane.report_agent`, `pane.report_agent_session`, `pane.report_metadata`, `pane.clear_agent_authority`, `pane.release_agent`, `pane.close`, `pane.wait_for_output` | +| Pane | `pane.split`, `pane.swap`, `pane.move`, `pane.zoom`, `pane.layout`, `pane.process_info`, `pane.neighbor`, `pane.edges`, `pane.focus_direction`, `pane.resize`, `pane.list`, `pane.current`, `pane.get`, `pane.rename`, `pane.send_text`, `pane.send_keys`, `pane.send_input`, `pane.read`, `pane.report_agent`, `pane.report_agent_session`, `pane.report_metadata`, `pane.clear_agent_authority`, `pane.release_agent`, `pane.close`, `pane.wait_for_output` | +| Layout | `layout.export`, `layout.apply` | | Agent | `agent.list`, `agent.get`, `agent.read`, `agent.explain`, `agent.send`, `agent.rename`, `agent.focus`, `agent.start` | | Events | `events.subscribe`, `events.wait` | | Integrations | `integration.install`, `integration.uninstall` | +| Plugins | `plugin.link`, `plugin.list`, `plugin.unlink`, `plugin.enable`, `plugin.disable`, `plugin.action.list`, `plugin.action.invoke`, `plugin.log.list`, `plugin.pane.open`, `plugin.pane.focus`, `plugin.pane.close` | Some CLI commands are conveniences around these methods. For example, `herdr agent wait` resolves an agent target and then subscribes to pane agent state events. @@ -97,20 +100,83 @@ schema makes `pane_id` optional use the server's active focused pane when it is omitted. `pane.move` always requires the source `pane_id`. ```json +{"id":"req_current","method":"pane.current","params":{"caller_pane_id":"w1:p1"}} {"id":"req_layout","method":"pane.layout","params":{"pane_id":"w1:p1"}} {"id":"req_neighbor","method":"pane.neighbor","params":{"pane_id":"w1:p1","direction":"right"}} {"id":"req_edges","method":"pane.edges","params":{"pane_id":"w1:p1"}} {"id":"req_focus","method":"pane.focus_direction","params":{"direction":"right"}} {"id":"req_resize","method":"pane.resize","params":{"pane_id":"w1:p1","direction":"right","amount":0.1}} {"id":"req_zoom","method":"pane.zoom","params":{"pane_id":"w1:p1","mode":"toggle"}} -{"id":"req_split","method":"pane.split","params":{"direction":"right","ratio":0.333}} +{"id":"req_split","method":"pane.split","params":{"direction":"right","ratio":0.333,"env":{"HERDR_ROLE":"tests"}}} +{"id":"req_process","method":"pane.process_info","params":{"pane_id":"w1:p1"}} ``` +`pane.current` returns a single `PaneInfo`. When `caller_pane_id` is present, +Herdr returns that pane. When it is omitted, Herdr returns the active focused +pane. + `pane.layout` returns the tab layout snapshot with `workspace_id`, `tab_id`, `zoomed`, outer `area`, `focused_pane_id`, pane rects, and split rects/ratios. `pane.neighbor` and `pane.edges` include that same layout snapshot so clients can make the next decision without private layout state. +`pane.process_info` returns the pane's shell pid, foreground process group id +when available, and foreground processes with pid, name, argv/cmdline, and cwd +when the platform exposes them. + +`layout.export` returns a portable tab layout tree. Omit `tab_id` and `pane_id` +to export the active tab, pass `tab_id` to export that tab, or pass `pane_id` to +export the tab containing that pane. + +```json +{"id":"req_export","method":"layout.export","params":{"tab_id":"w1:t1"}} +``` + +The response includes `workspace_id`, `tab_id`, `zoomed`, `focused_pane_id`, and +`root`. `root` is a BSP tree of `pane` and `split` nodes. Pane nodes can include +`pane_id`, `label`, `cwd`, and argv `command`. Split nodes use +`direction` (`right` or `down`), `ratio`, `first`, and `second`. + +`layout.apply` creates a fresh tab from a declarative tree. If `tab_id` is +provided, Herdr creates the replacement tab first and then closes the old tab. +This restores structure, labels, cwd, env, and optional argv commands; it does +not preserve live PTYs, scrollback, or running processes. + +```json +{ + "id": "req_apply", + "method": "layout.apply", + "params": { + "workspace_id": "wabc", + "tab_label": "dev", + "focus": true, + "root": { + "type": "split", + "direction": "right", + "ratio": 0.65, + "first": { + "type": "pane", + "label": "editor", + "cwd": "/repo" + }, + "second": { + "type": "pane", + "label": "tests", + "cwd": "/repo", + "command": ["sh", "-c", "just test"], + "env": { "HERDR_ROLE": "tests" } + } + } + } +} +``` + +Process-launching methods accept an `env` object. Herdr applies those key/value +pairs to the newly launched process only. Herdr also injects `HERDR_SOCKET_PATH`, +`HERDR_ENV=1`, `HERDR_WORKSPACE_ID`, `HERDR_TAB_ID`, and `HERDR_PANE_ID` into +managed pane processes. Herdr-managed variables are authoritative when they +conflict with caller-provided env. + `pane.swap` supports directional and explicit forms: ```json @@ -185,6 +251,17 @@ The response reports whether anything was shown: Possible reasons are `shown`, `disabled`, `rate_limited`, `no_foreground_client`, and `busy`. `disabled` means `ui.toast.delivery = "off"`. `busy` means an existing in-app toast was not replaced. Terminal and system delivery are best-effort through the current foreground attached Herdr client. +Set or clear the foreground client's outer terminal window title: + +```json +{"id":"req_title","method":"client.window_title.set","params":{"title":"herdr api"}} +{"id":"req_title_clear","method":"client.window_title.clear","params":{}} +``` + +`client.window_title.clear` restores Herdr's default title. The response is +`type: "client_window_title"` with `changed` and reason `set`, `cleared`, or +`no_foreground_client`. + Worktree methods manage Git checkouts as Herdr workspaces. `worktree.create` creates a checkout and returns the new `workspace`, `tab`, `root_pane`, and `worktree` records. `worktree.open` opens an existing checkout or returns the already-open workspace. `worktree.remove` runs `git worktree remove` against a linked child workspace and never deletes the branch. Create a worktree from a source workspace: @@ -207,6 +284,154 @@ Remove a linked checkout: Use at most one of `workspace_id` or `cwd` for `worktree.list`, `worktree.create`, and `worktree.open`; omit both to use the active workspace. Use exactly one of `path` or `branch` for `worktree.open`. Raw socket `cwd` and `path` values must be absolute; the CLI expands relative `--cwd` and `--path` values before sending requests. Workspace responses include optional `worktree` provenance when a workspace belongs to a Herdr worktree group. Worktree commands can emit `workspace.updated` when an existing workspace gains or changes worktree provenance. +Worktree commands also emit lifecycle events. `worktree.create` emits `workspace.created`, `tab.created`, `pane.created`, and `worktree.created`. `worktree.open` emits `worktree.opened`, and it also emits workspace/tab/pane creation events when it opens a new Herdr workspace. `worktree.remove` emits `worktree.removed`; if the linked workspace is still open, it also emits `workspace.closed`. + +## Plugin APIs + +The plugin API is an early host surface for executable workflow tools. A plugin +is a package with a `herdr-plugin.toml` manifest. The manifest declares +shareable actions, event hooks, terminal pane entrypoints, and link handlers. +Actions and panes are manifest-only; runtime action registration and runtime +argv pane creation are not part of v1. + +Installed and linked plugins persist across restarts. Herdr writes a +`plugins.json` registry file alongside `session.json` on `plugin.link`, +`plugin.unlink`, `plugin.enable`, and `plugin.disable`. The +`herdr plugin install` CLI also writes the same registry when Herdr is not +running, then startup loads it automatically. On startup, Herdr re-reads each +manifest from its original path; if the file is missing or unparseable the entry +is kept with a `warnings` field so `plugin.list` surfaces it. + +Event hook `on` values are validated against the known Herdr event names at link time. An unrecognised name is not an error — the link still succeeds — but the returned plugin info includes a warning (e.g. `"unknown event 'worktree.craeted'"`). Check the `warnings` field in the `plugin.link` and `plugin.list` responses. + +Link a local plugin manifest: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/path/to/plugin","enabled":true}} +``` + +`plugin.link` also accepts optional `source` metadata. The CLI uses this when it installs from GitHub so `plugin.list` can show origin, requested ref, resolved commit, and managed checkout path: + +```json +{"id":"req_plugin_link","method":"plugin.link","params":{"path":"/managed/plugin/herdr-plugin.toml","enabled":true,"source":{"kind":"github","owner":"ogulcancelik","repo":"herdr-plugin-examples","subdir":"worktree-bootstrap","requested_ref":"main","resolved_commit":"abc123","managed_path":"/data/plugins/github/example.worktree-bootstrap","installed_unix_ms":1780000000000}}} +``` + +The path can be a plugin directory containing `herdr-plugin.toml` or a direct +manifest path. The manifest shape is: + +```toml +id = "example.worktree-bootstrap" +name = "Worktree Bootstrap" +version = "0.1.0" +description = "Prepare new worktrees" +platforms = ["linux", "macos"] + +[[build]] +command = ["bun", "install"] + +[[actions]] +id = "bootstrap" +title = "Bootstrap worktree" +contexts = ["workspace"] +command = ["bun", "run", "bootstrap.ts"] + +[[actions]] +id = "bootstrap-win" +title = "Bootstrap worktree (Windows)" +contexts = ["workspace"] +platforms = ["windows"] +command = ["powershell.exe", "-ExecutionPolicy", "Bypass", "-File", "bootstrap.ps1"] + +[[events]] +on = "worktree.created" +command = ["bun", "run", "bootstrap.ts"] + +[[panes]] +id = "board" +title = "Worktree board" +placement = "overlay" +command = ["bun", "run", "board.ts"] + +[[link_handlers]] +id = "github-issue" +title = "Open GitHub issue" +pattern = "^https://github\\.com/[^/]+/[^/]+/(issues|pull)/[0-9]+$" +action = "bootstrap" +``` + +Declare `platforms` at the top level with the OS identifiers (`linux`, `macos`, +`windows`) your plugin supports. Omitting `platforms` is allowed for local +development — `plugin.link` succeeds but the response includes a warning. +Individual build commands, actions, event hooks, panes, and link handlers can +declare their own `platforms` to override the plugin-level list; if omitted they +inherit from the plugin. Invoking an action or opening a pane whose effective +platforms do not include the current OS returns a `platform_unsupported` error. + +List, enable, disable, or unlink linked plugins: + +```json +{"id":"req_plugin_list","method":"plugin.list","params":{}} +{"id":"req_plugin_disable","method":"plugin.disable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_enable","method":"plugin.enable","params":{"plugin_id":"example.worktree-bootstrap"}} +{"id":"req_plugin_unlink","method":"plugin.unlink","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +Actions are resolved from the linked manifest. `plugin.action.list` returns all actions across installed plugins; pass `plugin_id` to filter. + +```json +{"id":"req_plugin_actions","method":"plugin.action.list","params":{}} +{"id":"req_plugin_actions_filtered","method":"plugin.action.list","params":{"plugin_id":"example.worktree-bootstrap"}} +``` + +Invoke an action by its qualified id or bare action id: + +```json +{"id":"req_plugin_invoke","method":"plugin.action.invoke","params":{"action_id":"example.worktree-bootstrap.bootstrap","context":{"invocation_source":"keybinding"}}} +``` + +`plugin.action.invoke` resolves the manifest action, starts the manifest +command, and returns the Herdr-built invocation context plus the started command +log record. Missing context fields are filled from the active workspace, tab, +focused pane, worktree provenance, and request id. Invoking an action from a +disabled plugin returns a `plugin_disabled` error. + +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 available +`HERDR_WORKSPACE_ID`, `HERDR_TAB_ID`, and `HERDR_PANE_ID` values. Action +commands also receive `HERDR_PLUGIN_ACTION_ID`; event hooks receive +`HERDR_PLUGIN_EVENT` and `HERDR_PLUGIN_EVENT_JSON`; pane commands receive +`HERDR_PLUGIN_ENTRYPOINT_ID`. + +List recent action and event command logs: + +```json +{"id":"req_plugin_logs","method":"plugin.log.list","params":{"plugin_id":"example.worktree-bootstrap","limit":20}} +``` + +Event hooks run for enabled installed plugins when Herdr emits a matching event +name such as `worktree.created`. + +There is no Herdr-managed plugin storage API in v1. `HERDR_PLUGIN_CONFIG_DIR` +and `HERDR_PLUGIN_STATE_DIR` are path discovery only; plugins own their files, +schemas, migrations, and cleanup. + +Open a managed terminal UI: + +```json +{"id":"req_plugin_pane","method":"plugin.pane.open","params":{"plugin_id":"example.board","entrypoint":"board","placement":"zoomed","target_pane_id":"w1:p1","env":{"HERDR_ROLE":"board"},"focus":true}} +``` + +`plugin.pane.open` requires an installed, enabled, platform-compatible plugin, +then launches the requested manifest `[[panes]]` entrypoint as an argv-backed +terminal pane. Manifest pane `placement` defaults to `overlay`; request +`placement` overrides the manifest with `overlay`, `split`, `tab`, or `zoomed`. +Overlay panes target the active pane. Split and zoomed panes target an existing +pane; tab panes can target a workspace. The pane +behaves like a normal Herdr pane, but `plugin.pane.focus` and +`plugin.pane.close` only operate on panes opened through the plugin API. Focus +returns `plugin_pane_focused`; close returns `plugin_pane_closed`. + ## Socket transport Herdr uses newline-delimited JSON over a Unix domain socket. @@ -266,7 +491,7 @@ Integrations report agent state with `pane.report_agent`. `state` is semantic. It affects waits, notifications, and rollups. -`custom_status` is visual. It can show a short label like `indexing` without changing semantic behavior. +`custom_status` is visual. It can show a short activity label like `indexing` without changing semantic behavior. Session-only official integrations report native session references with `pane.report_agent_session`. State-reporting integrations can still include native session references in `pane.report_agent`. State-independent session reports do not affect waits, notifications, or rollups. @@ -323,7 +548,15 @@ Use `pane.report_metadata` when a user hook wants to customize presentation with } ``` -Metadata reports are display-only. Valid metadata can override the pane title, displayed agent name, compact custom status, and visible state labels. `working`, `blocked`, `idle`, waits, notifications, and rollups still come from semantic state. Native session restore comes from stored official session references. `agent` is an optional guard for the authoritative agent label; `applies_to_source` is an optional guard for the active lifecycle authority source. Use `display_agent` to change the visible name. `state_labels` keys must be `idle`, `working`, `blocked`, `done`, or `unknown`. Use clear fields such as `clear_custom_status: true` with the same `source` to remove one presentation override. +Metadata reports are display-only. Valid metadata can override the pane title, displayed agent name, compact activity label, and visible state labels. `working`, `blocked`, `idle`, waits, notifications, and rollups still come from semantic state. Native session restore comes from stored official session references. `agent` is an optional guard for the authoritative agent label; `applies_to_source` is an optional guard for the active lifecycle authority source. Use `display_agent` to change the visible name. `state_labels` keys must be `idle`, `working`, `blocked`, `done`, or `unknown`. Use clear fields such as `clear_custom_status: true` with the same `source` to remove one presentation override. + +Presentation text is normalized before storage. Herdr trims surrounding whitespace, removes control characters, caps `custom_status` at 32 characters, and caps `title`, `display_agent`, and each state label at 80 characters. Empty normalized values are ignored. + +`source` and `applies_to_source` are source identifiers. They must be 80 characters or fewer and may contain only ASCII letters, digits, colon, dot, underscore, and hyphen. + +Use `ttl_ms` for short-lived metadata. It must be between `1` and `86400000` milliseconds. Omit `ttl_ms` for metadata that should stay until replaced, cleared, or the pane closes. When the TTL expires, Herdr removes that source's metadata and emits a presentation change if the visible pane presentation changed. + +Use `seq` when a hook may send updates out of order. For the same `source`, reports with a sequence number less than or equal to the last accepted sequence are accepted by the API but ignored by the pane state. ## Event subscriptions @@ -343,12 +576,14 @@ Subscribe to events when you need a long-lived stream: The first response acknowledges the subscription. Later lines are pushed events. -Workspace event subscriptions include `workspace.created`, `workspace.updated`, `workspace.renamed`, `workspace.closed`, and `workspace.focused`. +Workspace event subscriptions include `workspace.created`, `workspace.updated`, `workspace.renamed`, `workspace.closed`, and `workspace.focused`. Workspace events describe Herdr UI/runtime lifecycle. `workspace.created` includes optional `workspace.worktree` provenance when the workspace belongs to a worktree group. `workspace.closed` includes a final `workspace` snapshot when Herdr can still identify it before removal. Pane event subscriptions include `pane.created`, `pane.closed`, `pane.focused`, `pane.moved`, `pane.exited`, `pane.agent_detected`, `pane.output_matched`, and `pane.agent_status_changed`. -Use `events.wait` when you want one matching event and then a response. +Worktree event subscriptions include `worktree.created`, `worktree.opened`, and `worktree.removed`. Worktree events describe Git checkout lifecycle. `worktree.created` includes the opened `workspace` and created `worktree`. `worktree.opened` includes the target `workspace`, opened `worktree`, and `already_open`. `worktree.removed` includes the `workspace_id`, removed `worktree`, and `forced`. + +Use `events.subscribe` for lifecycle events. Dedicated wait helpers are documented separately when a one-shot wait is supported. ## Reading panes |
