(日本語)
A herdr plugin that focuses the next agent pane in blocked (needs attention) or done (finished, unread) state, and brings the hosting terminal app to the front. If no pane needs attention, it still brings the terminal app to the front. An optional global hotkey (default Ctrl+Shift+O) triggers it from any app.
Inspired by a post from Tatsuhiko Miyagawa (@miyagawa). The core logic here — cycling through blocked/done panes by priority — follows the same idea as the herdr-unread script he had bound to prefix+shift+u.
herdr plugin install trapple/herdr-focusThat's it. No build step. Invoke the focus action from a terminal:
herdr plugin action invoke trapple.herdr-focus.focusPriority is blocked > done. Repeated invocations cycle to the next pane within the same group.
Or bind it to a key inside herdr (herdr config edit):
[[keys.command]]
key = "prefix+o"
type = "plugin_action"
command = "trapple.herdr-focus.focus"
description = "focus next agent pane needing attention"To trigger the focus action from any app with a system-wide hotkey, install the resident daemon. This step (and only this step) requires Xcode Command Line Tools (swiftc):
herdr plugin action invoke trapple.herdr-focus.hotkey-installThis opens an overlay pane inside herdr that streams the build and install progress live (press Enter to close it when it finishes — errors stay on screen until you do). It compiles the daemon into the plugin's state directory and registers a LaunchAgent (com.trapple.herdr-focus-hotkeyd), so it auto-starts at login and launchd restarts it if it crashes. The default hotkey is Ctrl+Shift+O.
The daemon captures the hotkey via Carbon's RegisterEventHotKey API, which doesn't require Accessibility permission. The first time the hotkey fires, macOS may show a one-time prompt asking whether "herdr-focus-hotkeyd" wants to control "Ghostty" (or whatever terminal app is configured). Allow it.
To remove the daemon (do this before herdr plugin uninstall):
herdr plugin action invoke trapple.herdr-focus.hotkey-uninstallSettings live in a config file inside the plugin's config directory:
$(herdr plugin config-dir trapple.herdr-focus)/confighotkey-install creates it with defaults. If you don't use the hotkey and only want to change the terminal app, create it yourself:
TERMINAL_APP="Ghostty"
HOTKEY="ctrl+shift+o"TERMINAL_APP: the app hosting herdr, brought to the front after focusing. Use the app name as AppleScript sees it (e.g.Terminal,iTerm2). Takes effect immediately — no re-registration needed. TheHERDR_FOCUS_TERMINAL_APPenvironment variable, when set, overrides this.HOTKEY: the global hotkey. Modifiers:ctrl/shift/alt/cmd; key:a-z,0-9,f1-f12(e.g.ctrl+shift+o,cmd+alt+f6). After changing it, re-runhotkey-installto re-register the daemon:
herdr plugin action invoke trapple.herdr-focus.hotkey-installherdr-plugin.toml # Plugin manifest: declares the three actions below
bin/
├── focus # Core logic (bash). Cycles blocked/done panes, activates the terminal app
├── hotkey-install # Builds the daemon into the state dir, validates config, registers the LaunchAgent
└── hotkey-uninstall # Removes the LaunchAgent and the built daemon
src/herdr-focus-hotkeyd.swift # Global hotkey daemon source (compiled on demand by hotkey-install)
build.sh # swiftc build script (output dir given as argument)
Every trigger path goes through the same plugin action, so there's no duplicated logic to maintain:
global hotkey → hotkeyd → herdr plugin action invoke trapple.herdr-focus.focus
herdr keybinding (plugin_action) ─────────────────────→ same action
manual CLI invoke ────────────────────────────────────→ same action
Because the action always runs inside herdr's plugin environment, bin/focus reads the herdr binary from HERDR_BIN_PATH and the config from HERDR_PLUGIN_CONFIG_DIR — no hardcoded paths.
After reinstalling the plugin (e.g. to update), re-run hotkey-install to rebuild and refresh the daemon.
- herdr >= 0.7.0
jq- macOS (uses
osascriptand Carbon) - Xcode Command Line Tools (
swiftc) — only if you use the global hotkey
Earlier versions installed via ./install.sh into ~/bin with a LaunchAgent. To migrate:
herdr plugin install trapple/herdr-focus- If you use the global hotkey:
herdr plugin action invoke trapple.herdr-focus.hotkey-install— the LaunchAgent label is unchanged, so this replaces the old daemon automatically. If you don't:launchctl bootout gui/$(id -u)/com.trapple.herdr-focus-hotkeydand delete~/Library/LaunchAgents/com.trapple.herdr-focus-hotkeyd.plist - Remove the old binaries:
rm ~/bin/herdr-focus ~/bin/herdr-focus-hotkeyd - If you exported
HERDR_FOCUS_TERMINAL_APPin your shell profile, remove it and setTERMINAL_APPin the config file instead
plugin action invoke starts the action and prints a command log record; the action's own output goes to the plugin log, not your terminal (hotkey-install is the exception — it streams its output in an overlay pane). Check the log with:
herdr plugin log list --plugin trapple.herdr-focusgit clone https://github.com/trapple/herdr-focus
cd herdr-focus
herdr plugin link "$PWD"Manual verification checklist:
herdr plugin action list --plugin trapple.herdr-focusshows the three actionsherdr plugin action invoke trapple.herdr-focus.focuscycles through blocked/done panes — works without any build (the hotkey really is optional)herdr plugin action invoke trapple.herdr-focus.hotkey-installopens an overlay pane streaming the build, then the hotkey fires- Editing
TERMINAL_APPin the config takes effect on the next invocation without re-registering - Editing
HOTKEY+ re-runninghotkey-installswitches the hotkey herdr plugin action invoke trapple.herdr-focus.hotkey-uninstallremoves the daemon and the hotkey stops firing
Thanks to Tatsuhiko Miyagawa (@miyagawa) for the original idea and script that inspired this project.
