15 KiB
configuration
herdr reads config from:
~/.config/herdr/config.toml
Named sessions share this config file. Sessions are runtime/socket namespaces, not workspace replacements; per-session sockets and persistent runtime state are separate:
~/.config/herdr/session.json
~/.config/herdr/sessions/<name>/session.json
Use herdr session list, herdr session attach <name>, herdr session stop <name>, and herdr session delete <name> to inspect and manage named session namespaces. Add --json to session commands when scripts need machine-readable output.
In default persistence mode, quitting the UI detaches the current client. Use herdr server stop to stop the shared background server.
print the full default config with:
herdr --default-config
if a config value is invalid, or two navigate actions use the same keybinding, herdr falls back to a safe default and shows a startup warning in the UI.
live reload
After editing config.toml, reload the running app without restarting the persistent server:
herdr server reload-config
You can also use the global menu inside herdr and choose reload config.
Reload is server-owned. In persistent mode the CLI sends a request to the running server, and the server reads, parses, validates, and applies config.toml.
Reloadable now:
- keybindings and prefix
- theme, custom theme colors, and legacy
ui.accent ui.confirm_closeui.agent_panel_scopeui.toast.delivery- server-side
ui.soundpolicy; attached thin clients refresh local sound config after a successful sound-policy change experimental.kitty_graphicsadvanced.scrollback_limit_bytesfor panes created after reloadui.sidebar_widthas the default width; current width updates only while it is still config-owned
Startup-only or special-case:
onboardingdoes not reopen onboarding during reloadexperimental.allow_nestedis checked before launch and needs a restart- existing pane scrollback buffers are not resized during reload
- terminal notifications and sounds are client-local side effects and are sent to the foreground attached client
If the TOML cannot be read or parsed, reload applies nothing and keeps the current running state. If keybindings are invalid, herdr keeps the current keybindings while applying other valid reloadable settings where possible.
onboarding
onboarding = true
| option | default | description |
|---|---|---|
onboarding |
unset | show first-run notification setup; set false after choosing |
notes:
- missing
onboardingcurrently behaves liketrue - set
onboarding = trueto force the setup screen again for testing - continuing from onboarding writes
onboarding = falseand opens the normal settings UI
keybindings
keybindings live under [keys].
supported syntax:
- plain keys:
n,x,-,` - modifiers:
ctrl+b,shift+n,alt+x - special keys:
enter,esc,tab,backspace,left,right,up,down - function keys:
f1,f12 - uppercase letters also imply shift:
Dworks likeshift+d
notes:
- most reliable bindings are plain keys,
ctrl+letter,esc/tab/enter, and function keys alt+...and punctuation-with-modifiers may vary depending on terminal/tmux setup- bindings marked
unsetin the key reference are supported actions with no default key assigned - for navigate-mode actions, duplicate keybindings are treated as config errors; later conflicting bindings fall back to defaults
example:
[keys]
prefix = "ctrl+b"
new_workspace = "n"
rename_workspace = "shift+n"
close_workspace = "X"
reload_config = "" # optional, unset by default
open_notification_target = "" # optional, unset by default
new_tab = "c"
split_vertical = "d"
split_horizontal = "D"
close_pane = "x"
rename_pane = "" # optional, unset by default
fullscreen = "f"
resize_mode = "r"
toggle_sidebar = "b"
previous_workspace = "ctrl+alt+["
next_workspace = "ctrl+alt+]"
previous_agent = "ctrl+["
next_agent = "ctrl+]"
previous_tab = "alt+["
next_tab = "alt+]"
focus_pane_left = "alt+h"
focus_pane_down = "alt+j"
focus_pane_up = "alt+k"
focus_pane_right = "alt+l"
key reference
| key | default | action |
|---|---|---|
prefix |
ctrl+b |
enter or leave navigate mode |
new_workspace |
n |
create a new workspace |
rename_workspace |
shift+n |
rename selected workspace |
close_workspace |
shift+d |
close selected workspace |
detach |
unset | optional explicit detach shortcut in the persistent session |
reload_config |
unset | reload config.toml in the running app/server |
open_notification_target |
unset | jump to the currently visible notification target |
previous_workspace |
unset | switch to the previous workspace directly from terminal mode |
next_workspace |
unset | switch to the next workspace directly from terminal mode |
previous_agent |
unset | focus the previous agent shown in the sidebar agent list |
next_agent |
unset | focus the next agent shown in the sidebar agent list |
new_tab |
c |
create a new tab |
rename_tab |
unset | rename the active tab |
previous_tab |
unset | switch to the previous tab directly from terminal mode |
next_tab |
unset | switch to the next tab directly from terminal mode |
close_tab |
unset | close the active tab |
focus_pane_left |
unset | focus the pane to the left directly from terminal mode |
focus_pane_down |
unset | focus the pane below directly from terminal mode |
focus_pane_up |
unset | focus the pane above directly from terminal mode |
focus_pane_right |
unset | focus the pane to the right directly from terminal mode |
split_vertical |
v |
split pane vertically (side by side) |
split_horizontal |
- |
split pane horizontally (stacked) |
close_pane |
x |
close focused pane |
rename_pane |
unset | rename the focused pane |
fullscreen |
f |
toggle focused pane fullscreen |
resize_mode |
r |
enter or leave resize mode |
toggle_sidebar |
b |
collapse or expand the sidebar |
custom command keybindings
Use [[keys.command]] to bind a prefix-mode key to a command. Press the prefix key, then the configured key.
[[keys.command]]
key = "g"
type = "pane"
command = "lazygit"
type is optional and defaults to shell.
| type | behavior |
|---|---|
shell |
run the command detached in the background |
pane |
open a temporary zoomed pane, run the command there, then close the pane when the command exits |
Commands run through /bin/sh -lc. Herdr sets the command working directory to the active pane cwd when available and provides context through environment variables:
| variable | value |
|---|---|
HERDR_SOCKET_PATH |
active herdr socket path |
HERDR_BIN_PATH |
current herdr binary path |
HERDR_ACTIVE_WORKSPACE_ID |
active workspace id |
HERDR_ACTIVE_TAB_ID |
active tab id |
HERDR_ACTIVE_PANE_ID |
focused pane id |
HERDR_ACTIVE_PANE_CWD |
focused pane cwd |
Example detached helper:
[[keys.command]]
key = "shift+g"
type = "shell"
command = "notify-send herdr 'custom command ran'"
theme
herdr ships with 17 built-in color themes. set one in config:
[theme]
name = "tokyo-night"
built-in themes
| name | description |
|---|---|
catppuccin |
soft pastel mocha palette (default) |
catppuccin-latte |
light catppuccin palette |
tokyo-night |
blue-purple aesthetic |
tokyo-night-day |
light tokyo night palette |
dracula |
purple/pink/green classic |
nord |
frosty scandinavian blues |
gruvbox |
warm retro browns/oranges |
gruvbox-light |
light gruvbox palette |
one-dark |
atom's beloved dark palette |
one-light |
atom's light palette |
solarized |
ethan schoonover's classic dark palette |
solarized-light |
ethan schoonover's classic light palette |
kanagawa |
hokusai-inspired |
kanagawa-lotus |
light kanagawa palette |
rose-pine |
muted, elegant |
rose-pine-dawn |
light rosé pine palette |
vesper |
high-contrast monochrome with peach and mint accents |
theme names are flexible: tokyo-night, tokyonight, and tokyo_night all work.
custom overrides
override individual color tokens on top of any base theme:
[theme]
name = "dracula"
[theme.custom]
panel_bg = "reset"
accent = "#f5c2e7"
red = "rgb(255, 85, 85)"
green = "#a6e3a1"
all tokens are optional — only set what you want to change.
available tokens
| token | used for |
|---|---|
accent |
highlights, active borders, navigation UI |
panel_bg |
floating panel, tab bar, and overlay background |
surface0 |
selected item background |
surface1 |
hover/active backgrounds |
surface_dim |
active workspace background, separators |
overlay0 |
muted text, secondary info |
overlay1 |
slightly brighter secondary text |
text |
primary text |
subtext0 |
workspace names, dimmed labels |
mauve |
git branch names, special labels |
green |
idle/done states |
yellow |
busy/running states |
red |
waiting/needs attention states |
blue |
unseen notifications |
teal |
done notification accents |
peach |
interrupted/warning states |
tokens accept the same color formats as accent: hex (#rrggbb), named colors, or rgb(r,g,b).
for panel_bg, you can also use reset, default, none, or transparent to stop herdr from painting an opaque panel background and instead use the host terminal's default background.
ui
[ui]
sidebar_width = 26
mouse_capture = true
confirm_close = true
show_agent_labels_on_pane_borders = false
agent_panel_scope = "all"
accent = "cyan"
options
| option | default | description |
|---|---|---|
sidebar_width |
26 |
base sidebar width before auto-scaling |
mouse_capture |
true |
capture mouse input for Herdr's mouse UI; set false to let the terminal handle normal clicks while still forwarding mouse to pane apps that request it |
confirm_close |
true |
ask before closing a workspace |
show_agent_labels_on_pane_borders |
false |
show detected/reported agent labels in split pane borders when no manual pane name is set |
agent_panel_scope |
all |
sidebar agent list scope: current or all |
accent |
cyan |
highlight and border color |
Changing the agent panel scope from the sidebar writes agent_panel_scope to config so it survives session resets and upgrades.
accent accepts:
- named colors like
cyan,blue,magenta - hex like
#89b4fa - rgb like
rgb(137,180,250)
toast notifications
[ui.toast]
delivery = "off"
options
| option | default | description |
|---|---|---|
ui.toast.delivery |
off |
where background popup notifications should appear |
available values:
off— disable popup notificationsherdr— show top-right in-app toaststerminal— ask the outer terminal to show a desktop notification. some terminals suppress foreground notifications, including ghostty on macos.system— ask the os notification service directly. on macos, herdr usesterminal-notifierwhen available and falls back to built-inosascript. on linux,systemrequiresnotify-send.
macos system notifications
for best macos support, install terminal-notifier:
brew install terminal-notifier
when terminal-notifier is installed, herdr tries to focus the hosting terminal when a notification is clicked. click-to-return is supported for detected ghostty, iterm2, wezterm, kitty, alacritty, and terminal.app sessions.
without terminal-notifier, herdr falls back to built-in osascript. this still shows a macos notification, but clicking the notification may focus the apple script runner instead of returning to your terminal.
compatibility note:
- older configs may still use
ui.toast.enabled = true|false - herdr still reads that legacy key for compatibility
- if you save toast settings from inside herdr, it rewrites the setting to
ui.toast.delivery
current behavior:
- informational only
- one notification event at a time
- shown for background agent events like
needs attentionandfinished - suppression is tab-aware: the active tab stays quiet, but background tabs in the same workspace can still notify
terminaldelivery is best-effort and depends on terminal supportsystemdelivery is best-effort and depends on the os helper being available- macos
systemdelivery prefersterminal-notifierwhen present and falls back toosascript - currently targets terminals such as ghostty, kitty, iterm2, and wezterm
- inside tmux, herdr wraps notification escapes with tmux passthrough
sound
[ui.sound]
enabled = true
[ui.sound.agents]
claude = "default"
droid = "off"
options
| option | default | description |
|---|---|---|
ui.sound.enabled |
true |
enable background agent sounds |
per-agent values:
defaultonoff
available agent keys:
piclaudecodexgeminicursorclineopen_codegithub_copilotkimidroidamp
experimental
[experimental]
allow_nested = false
kitty_graphics = false
options
| option | default | description |
|---|---|---|
experimental.allow_nested |
false |
allow launching herdr from inside a herdr-managed pane |
experimental.kitty_graphics |
false |
enable experimental local Kitty graphics rendering for attached clients |
nested launches
By default, herdr blocks nested launches when HERDR_ENV=1 is already present.
Set allow_nested = true only for debugging or intentionally nested setups.
Kitty graphics
kitty_graphics enables experimental local Kitty graphics rendering for attached clients.
It requires a Kitty graphics-compatible outer terminal.
Known limitation: resizing the terminal window or changing the terminal font while images are visible can leave existing images misplaced or stale. Restart the pane app or clear and redraw the image output after changing size or font. Please report any findings so this experimental path can improve.
advanced
[advanced]
scrollback_limit_bytes = 10000000
options
| option | default | description |
|---|---|---|
advanced.scrollback_limit_bytes |
10000000 |
maximum scrollback buffer size in bytes retained per pane terminal |
scrollback
scrollback_limit_bytes limits retained terminal scrollback per pane.
The default matches Ghostty's scrollback-limit value.
Set scrollback_limit_bytes = 0 to disable pane scrollback entirely.
The legacy scrollback_lines key is still accepted inside [advanced], but it uses the same byte-based value.
environment variables
| variable | description |
|---|---|
HERDR_LOG |
log level filter (default: herdr=info) |
logs
herdr writes local file logs under:
~/.config/herdr/
common files:
~/.config/herdr/herdr.log
~/.config/herdr/herdr-client.log
~/.config/herdr/herdr-server.log
notes:
herdr.logis used by monolithic--no-sessionmode and some top-level startup paths- persistent session mode mainly uses
herdr-client.logandherdr-server.log - logs rotate automatically by size and keep a few older files as
.1,.2, and so on - default logs are metadata-focused and are intended to be shareable for issue diagnosis
HERDR_LOGcan increase verbosity when you need a local repro or deeper debugging