patriceckhart/hrdx

Yet another minimal and lightweight terminal multiplexer built for the agent era.

48

stars

78

commits

Go

primary language

Sep 9, 2026

updated

hrdx.dev
agent
agent-orchestration
ai
ai-agents
cli
developer-tools
go
multiplexer
tui
workspace-manager

README


license Go 1.25+

hrdx.dev

What is it?

hrdx is a experimental, minimal and lightweight terminal multiplexer built for the agent era: your projects as workspaces in a sidebar, tabs per workspace, and real terminal panes running Codex CLI, Claude Code, pi, zot or plain shells side by side. Kick off an agent in one project, switch to the next, and let the sidebar spinners tell you who is still working.

  • Real terminals, not wrappers. Every pane is a genuine PTY session with a full terminal emulator behind it. Agent TUIs run exactly as they do standalone: streaming, slash commands, sessions, mouse support, all of it. Panes present a clean terminal identity so capability-sniffing TUIs pick rendering paths that work inside a multiplexer, and HRDX=1 lets tools detect they run inside hrdx.
  • Everything in view. The sidebar shows one hierarchy of workspaces, Git branches, and panes, adding tab rows only when a workspace has multiple tabs. Every pane has a shared status circle: agents become animated braille spinners while working, and an unfocused agent turns orange when it finishes. Focusing the pane acknowledges it and restores green.
  • Feels like your terminal. Scrollback, mouse selection with clipboard copy, drag-to-resize splits, drag-to-reorder workspaces, right-click context menus, and kitty keyboard protocol pass-through so even exotic chords like ctrl+1 reach your agent.
  • Picks up where you left off. Quit and relaunch: shells and agents keep running in a lightweight session holder and reattach exactly where they were, running commands and all. Workspaces, tabs, splits, and ratios come back too, and if a session is truly gone, agents resume their latest conversation from their own session store.
  • Yours to tune. A settings window (ctrl+b , or the gear in the sidebar) lets you switch individual agents on or off, pick a notification sound for finished turns (including your own audio files), and change the color theme, with user themes as simple JSON files. All persisted. See Themes.
  • Bring your own agent. Register any agent CLI as a custom harness via a small JSON file, including its own busy detection for the sidebar spinner and finish sound. It shows up in pickers, cycling, and settings like the built-ins. See Custom harnesses.
  • Scriptable from outside. A JSON socket API lets scripts and editors inspect workspaces and pane states, open projects, spawn panes, type into agents, wait for them to finish, read their screens, and subscribe to live events. See Socket API.

Install

curl -fsSL https://www.hrdx.dev/install.sh | bash
irm https://www.hrdx.dev/install.ps1 | iex

macOS, Linux, or Windows (10 1809+ / 11, via ConPTY), plus at least one agent CLI on your PATH: codex, claude, pi or zot. Update on any supported platform with hrdx update.

Run

hrdx

Open several projects at once, or pick your default agent:

hrdx --cwd ~/Developer/api --cwd ~/Developer/web
hrdx --agent claude

Flags

FlagPurpose
--cwd PATHOpen a project as a workspace, repeatable
--agent IDDefault agent for new panes: zot, pi, claude, codex (default zot)
--provider IDPass a provider to every zot pane (zot only)
--model IDPass a model to every zot pane (zot only)
--reasoning LEVELSet the reasoning level (zot only)
--continueResume each project's latest session
--codex-bin PATHUse a specific codex binary
--claude-bin PATHUse a specific claude binary
--pi-bin PATHUse a specific pi binary
--zot-bin PATHUse a specific zot binary
--shell PATHShell for shell panes (default $SHELL; on Windows only when resolvable, otherwise %COMSPEC%/powershell.exe)
--state PATHState file for workspace persistence (empty disables)
--freshIgnore saved workspaces and start clean
--apiServe the control API on a unix socket (default on, --api=false disables)
--persistKeep pane processes alive across restarts via the session holder (default on)

A native Windows hrdx.exe launched from Git Bash ignores an MSYS-only SHELL value such as /usr/bin/bash, which Windows cannot resolve, and falls back to %COMSPEC%. To use Git Bash for panes, pass a native path explicitly, for example hrdx --shell "C:/Program Files/Git/bin/bash.exe".

Keys

All keys go to the focused terminal, except the ctrl+b prefix (tmux style):

After ctrl+bAction
c or CSplit right / below (opens a picker: installed agents or shell)
a or ASplit right / below with the default agent directly
s or SSplit with a new shell pane directly (right / below), also %/\" and |/-
wNew workspace (directory prompt with tab completion, then agent/shell picker)
tNew tab in the current workspace (opens the agent/shell picker)
n or pNext / previous tab
] or [Next / previous workspace
tab or shift+tabNext / previous pane; stays in prefix mode for repeated jumps, esc exits
/Fuzzy finder over every workspace, tab, and pane: type to filter, arrows select, enter jumps
rRename the focused pane
mOpen the pane context menu
=Equalize all splits
u or d (or pgup/pgdown)Scroll the focused pane's history
esc / GBack to live output, clear selection
,Settings window: enable / disable agents, notifications
xClose pane (sibling takes its room)
XClose workspace
ctrl+bSend a literal ctrl+b to the pane
qQuit
left / rightScroll the hint row in the footer (narrow terminals)

Panes whose process exits (for example exit in a shell) close automatically; the sibling pane takes the room. Panes that fail to start stay visible with the error.

Custom keys

Prefix keys are remappable via a keys.json next to the state file (~/Library/Application Support/hrdx/keys.json on macOS, $XDG_CONFIG_HOME/hrdx/keys.json on Linux, %AppData%\hrdx\keys.json on Windows). It maps action names to a single key; an override replaces that action's default keys. The prefix action remaps the ctrl+b trigger itself, not just an action inside it:

{
  "find": "f",
  "quit": "Q",
  "agent-cycle": "g"
}

Actions: prefix, literal, quit, picker-right, picker-down, agent-right, agent-down, agent-cycle (unbound by default), shell-right, shell-down, workspace, tab-new, tab-next, tab-prev, space-next, space-prev, pane-next, pane-prev, find, close-pane, close-space, equalize, rename, menu, settings, scroll-up, scroll-down, live.

Mouse

Everything is clickable: workspace, tab, and pane rows in the sidebar, the main tab bar, menus, and the settings entry at the bottom. Drag workspaces to reorder them, drag pane borders to resize, right-click for context menus, and drag with the left button to select text (copied straight to your clipboard). Wheel events go to the pane under the cursor: agent TUIs scroll themselves, shells scroll their local history, and shift+pgup / shift+pgdn do the same from the keyboard.

Remote and container panes

Every pane is a real PTY, so a shell pane can connect to a remote host, Docker container, or Kubernetes workload. For an interactive shell:

ssh user@host
docker exec -it -w /workspace container-name sh
kubectl exec -it -n namespace deploy/app -- sh

An agent installed at the target can be launched directly instead:

ssh -tt user@host 'cd /path/to/project && exec codex'
docker exec -it -w /workspace container-name codex
kubectl exec -it -n namespace deploy/app -- codex

Use the same pattern for any supported or custom agent. Authentication and the agent executable, configuration, credentials, and project files must be available at the target.

To make remote and container agents appear in pickers, agent cycling, settings, and the sidebar, register their client command as a custom harness:

[
  {
    "kind": "remote-codex",
    "binary": "ssh",
    "args": ["-tt", "user@host", "cd /path/to/project && exec codex"]
  },
  {
    "kind": "docker-codex",
    "binary": "docker",
    "args": ["exec", "-it", "-w", "/workspace", "container-name", "codex"]
  },
  {
    "kind": "k8s-codex",
    "binary": "kubectl",
    "args": ["exec", "-it", "-n", "namespace", "deploy/app", "--", "codex"]
  }
]

Wrapper scripts are useful when the host, container, namespace, pod, working directory, or authentication setup is dynamic. Set the harness binary to the wrapper path and put any fixed parameters in args.

The session holder keeps the local SSH, Docker, or Kubernetes client process alive when hrdx restarts. It cannot keep an agent alive when its remote host, container, pod, or network connection ends, and it does not automatically reconnect. Workspace Git details are read from the local workspace path, not from the remote filesystem.

Custom harnesses

Any agent CLI beyond the built-ins can be registered by dropping a harness.json next to the state file (~/Library/Application Support/hrdx/ on macOS, $XDG_CONFIG_HOME/hrdx/ on Linux, %AppData%\hrdx\ on Windows). Registered harnesses appear everywhere the built-ins do: in the pickers, in agent cycling, as agent panes in the sidebar hierarchy, and in the settings window for enabling and disabling.

[
  {
    "kind": "aider",
    "binary": "aider",
    "args": ["--no-auto-commits"],
    "resume": ["--restore-chat-history"],
    "busy": "Waiting for the model"
  },
  {
    "kind": "goose",
    "idle_title": "goose idle",
    "attention_title": "goose waiting"
  }
]
FieldPurpose
kindIdentifier used in pickers and pane names (required, must not collide with built-ins)
binaryExecutable to launch (default: same as kind)
argsExtra arguments passed on every launch
resumeArguments that resume the latest session when a restored pane relaunches
resume_firstPut the resume args before args (for subcommands like resume --last)
busyA substring visible on screen only while the harness is working; drives the busy spinner and the finish sound. Empty: braille spinner detection, like the built-ins
idle_titleTerminal-title substring emitted when the harness is idle; overrides a stale visible spinner
attention_titleTerminal-title substring emitted while waiting for user input; overrides the spinner and shows an orange dot when unfocused

Both title fields are optional and have no defaults, since every harness publishes its own markers. Leave them out and the harness is detected purely from the screen, exactly as busy describes. Set them when the harness keeps a spinner on screen while it is really idle or blocked on a prompt: a matching title always outranks the screen scrape. Check what your harness emits with printf '\e]2;...\a'-style OSC titles before picking a substring.

Socket API

While hrdx runs it serves a control API on a unix socket next to the state file (hrdx.sock), so scripts, editors, and coding agents can inspect and drive a running session. Disable with --api=false.

The protocol is newline-delimited JSON: send one request per line, receive one response line with the same id.

SOCK="$HOME/Library/Application Support/hrdx/hrdx.sock"   # macOS
# SOCK="$XDG_CONFIG_HOME/hrdx/hrdx.sock"                  # Linux
# hrdx.sock is a native Windows AF_UNIX socket too (%AppData%\hrdx\hrdx.sock).
# WSL has a separate socket namespace and cannot connect to it directly; Git
# Bash does not ship a compatible `nc -U`. Use a native Windows client, such
# as .NET UnixDomainSocketEndPoint or Go's net.DialUnix.

echo '{"id": "1", "method": "status"}' | nc -U "$SOCK"
echo '{"id": "2", "method": "workspace.create", "params": {"path": "~/Developer/api", "agent": "claude"}}' | nc -U "$SOCK"
echo '{"id": "3", "method": "pane.create", "params": {"workspace": "api", "kind": "shell", "split": "down"}}' | nc -U "$SOCK"
echo '{"id": "4", "method": "pane.send_text", "params": {"pane_id": 3, "text": "run the tests", "enter": true}}' | nc -U "$SOCK"
echo '{"id": "5", "method": "pane.wait", "params": {"pane_id": 3, "until": "idle"}}' | nc -U "$SOCK"
echo '{"id": "6", "method": "pane.read", "params": {"pane_id": 3}}' | nc -U "$SOCK"
MethodEffect
pingLiveness check, returns pong
statusWorkspaces, tabs, and panes with id, kind, running, and busy state
workspace.createOpen a directory as a workspace (path, optional agent)
workspace.closeClose a workspace by name or path
pane.createAdd a pane (workspace name or path, kind, split: right, down, tab)
pane.send_textType into a pane (pane_id, text, optional enter)
pane.readThe pane's visible screen as plain text
pane.waitBlock until a pane's agent is idle or busy (until, optional timeout_ms)
pane.closeClose a pane by id
events.subscribeKeep the connection open and push events

Successful responses are {"id": "...", "result": {...}}; failures are {"id": "...", "error": {"code": "not_found", "message": "..."}} with codes not_found, invalid_params, unknown_method, timeout, and error.

After events.subscribe the connection stays open and hrdx pushes lines like {"event": "pane.busy_changed", "data": {"pane_id": 3, "busy": false}}. Events: workspace.created, workspace.closed, pane.created, pane.closed, and pane.busy_changed, so a script can react the moment an agent finishes instead of polling.

Every request is answered by the TUI's own update loop, so the API always sees exactly what is on screen. pane.wait plus pane.send_text is enough to build simple agent pipelines: prompt an agent, wait until it is idle, read the screen, move on.

Themes

hrdx themes are JSON files that override any subset of the built-in colors; missing values inherit the default look. Drop them into a themes/ directory next to the state file (~/Library/Application Support/hrdx/themes/ on macOS, $XDG_CONFIG_HOME/hrdx/themes/ on Linux, %AppData%\hrdx\themes\ on Windows) and pick them in the settings window's theme section. The change applies immediately and persists.

{
  "name": "neon",
  "description": "Pink accent, near-black bars.",
  "colors": {
    "accent": 201,
    "bar_bg": "#101010"
  }
}

Values are ANSI 256 color numbers or "#rrggbb" strings.

ColorUsed for
accentFocused pane frames, highlights, logo, selected items
altPrefix badge, behind-count in the sidebar
mutedSecondary text, hints, idle pane names
faintInactive pane borders, sidebar divider
goodRunning dots, input badge
busyBusy spinner and completed-work attention dot
badErrors, exited dots
bar_bg / bar_fgHeader and footer bars
inkText on accent backgrounds, tab bar strip

See examples/themes/ for a full example.

Notifications

The notification section of the settings window has two independent toggles for finished agent turns: play a sound (built-in ding and chime, or your own audio files) and a system notification, which rings the terminal bell so your platform's native attention indicator fires: dock badge and bounce on macOS, the window manager's urgency hint on Linux, the taskbar/window attention flash on Windows Terminal (depends on its bellStyle setting). No notification daemon or permission required. Add custom sounds with a sounds.json next to the state file; they appear as choices and are previewed when selected:

[
  { "name": "sheep", "file": "~/sounds/maehhh.wav" },
  { "name": "gong", "file": "/Users/me/sounds/gong.aiff" }
]

name is the label in settings (must not collide with built-ins), file any audio file your OS player understands (afplay on macOS, paplay/aplay on Linux, PowerShell's SoundPlayer on Windows — WAV only there). Missing files are reported in the footer and skipped.

Persistence

Quitting hrdx does not kill your sessions. Pane processes live in a small background process (the session holder) that hrdx starts on demand and talks to over a local socket. Close the TUI, reopen it, and every shell and agent reattaches exactly where it was: running commands keep running, scrollback and screen state are replayed, nothing restarts. The holder is the same hrdx binary, uses no resources worth mentioning, and goes away when you kill its sessions.

Workspaces, panes, split layout, ratios, selection, and holder session ids are saved automatically (default: ~/Library/Application Support/hrdx/state.json on macOS, $XDG_CONFIG_HOME/hrdx/state.json on Linux, %AppData%\hrdx\state.json on Windows). On the next launch the layout is restored and each pane reattaches to its held session. When a held session is gone (rebooted machine, killed holder), the pane starts fresh instead: shell panes get a new shell, and agent panes relaunch resuming their latest session for that directory via the agent's own session store.

--persist=false disables the holder (panes die with the TUI, like a plain terminal). --fresh skips restoring and cleans up now-unreferenced held sessions; --state "" disables persistence entirely.

Development

make check

Windows without make on PATH: go vet ./... && gofmt -l . && go test ./....

License

MIT

Contributors

patriceckhart

64 commits

mi-skam

9 commits

m1lian

5 commits

patriceckhart/hrdx

Yet another minimal and lightweight terminal multiplexer built for the agent era.

48

stars

78

commits

Go

primary language

Sep 9, 2026

updated

hrdx.dev
agent
agent-orchestration
ai
ai-agents
cli
developer-tools
go
multiplexer
tui
workspace-manager

README


license Go 1.25+

hrdx.dev

What is it?

hrdx is a experimental, minimal and lightweight terminal multiplexer built for the agent era: your projects as workspaces in a sidebar, tabs per workspace, and real terminal panes running Codex CLI, Claude Code, pi, zot or plain shells side by side. Kick off an agent in one project, switch to the next, and let the sidebar spinners tell you who is still working.

  • Real terminals, not wrappers. Every pane is a genuine PTY session with a full terminal emulator behind it. Agent TUIs run exactly as they do standalone: streaming, slash commands, sessions, mouse support, all of it. Panes present a clean terminal identity so capability-sniffing TUIs pick rendering paths that work inside a multiplexer, and HRDX=1 lets tools detect they run inside hrdx.
  • Everything in view. The sidebar shows one hierarchy of workspaces, Git branches, and panes, adding tab rows only when a workspace has multiple tabs. Every pane has a shared status circle: agents become animated braille spinners while working, and an unfocused agent turns orange when it finishes. Focusing the pane acknowledges it and restores green.
  • Feels like your terminal. Scrollback, mouse selection with clipboard copy, drag-to-resize splits, drag-to-reorder workspaces, right-click context menus, and kitty keyboard protocol pass-through so even exotic chords like ctrl+1 reach your agent.
  • Picks up where you left off. Quit and relaunch: shells and agents keep running in a lightweight session holder and reattach exactly where they were, running commands and all. Workspaces, tabs, splits, and ratios come back too, and if a session is truly gone, agents resume their latest conversation from their own session store.
  • Yours to tune. A settings window (ctrl+b , or the gear in the sidebar) lets you switch individual agents on or off, pick a notification sound for finished turns (including your own audio files), and change the color theme, with user themes as simple JSON files. All persisted. See Themes.
  • Bring your own agent. Register any agent CLI as a custom harness via a small JSON file, including its own busy detection for the sidebar spinner and finish sound. It shows up in pickers, cycling, and settings like the built-ins. See Custom harnesses.
  • Scriptable from outside. A JSON socket API lets scripts and editors inspect workspaces and pane states, open projects, spawn panes, type into agents, wait for them to finish, read their screens, and subscribe to live events. See Socket API.

Install

curl -fsSL https://www.hrdx.dev/install.sh | bash
irm https://www.hrdx.dev/install.ps1 | iex

macOS, Linux, or Windows (10 1809+ / 11, via ConPTY), plus at least one agent CLI on your PATH: codex, claude, pi or zot. Update on any supported platform with hrdx update.

Run

hrdx

Open several projects at once, or pick your default agent:

hrdx --cwd ~/Developer/api --cwd ~/Developer/web
hrdx --agent claude

Flags

FlagPurpose
--cwd PATHOpen a project as a workspace, repeatable
--agent IDDefault agent for new panes: zot, pi, claude, codex (default zot)
--provider IDPass a provider to every zot pane (zot only)
--model IDPass a model to every zot pane (zot only)
--reasoning LEVELSet the reasoning level (zot only)
--continueResume each project's latest session
--codex-bin PATHUse a specific codex binary
--claude-bin PATHUse a specific claude binary
--pi-bin PATHUse a specific pi binary
--zot-bin PATHUse a specific zot binary
--shell PATHShell for shell panes (default $SHELL; on Windows only when resolvable, otherwise %COMSPEC%/powershell.exe)
--state PATHState file for workspace persistence (empty disables)
--freshIgnore saved workspaces and start clean
--apiServe the control API on a unix socket (default on, --api=false disables)
--persistKeep pane processes alive across restarts via the session holder (default on)

A native Windows hrdx.exe launched from Git Bash ignores an MSYS-only SHELL value such as /usr/bin/bash, which Windows cannot resolve, and falls back to %COMSPEC%. To use Git Bash for panes, pass a native path explicitly, for example hrdx --shell "C:/Program Files/Git/bin/bash.exe".

Keys

All keys go to the focused terminal, except the ctrl+b prefix (tmux style):

After ctrl+bAction
c or CSplit right / below (opens a picker: installed agents or shell)
a or ASplit right / below with the default agent directly
s or SSplit with a new shell pane directly (right / below), also %/\" and |/-
wNew workspace (directory prompt with tab completion, then agent/shell picker)
tNew tab in the current workspace (opens the agent/shell picker)
n or pNext / previous tab
] or [Next / previous workspace
tab or shift+tabNext / previous pane; stays in prefix mode for repeated jumps, esc exits
/Fuzzy finder over every workspace, tab, and pane: type to filter, arrows select, enter jumps
rRename the focused pane
mOpen the pane context menu
=Equalize all splits
u or d (or pgup/pgdown)Scroll the focused pane's history
esc / GBack to live output, clear selection
,Settings window: enable / disable agents, notifications
xClose pane (sibling takes its room)
XClose workspace
ctrl+bSend a literal ctrl+b to the pane
qQuit
left / rightScroll the hint row in the footer (narrow terminals)

Panes whose process exits (for example exit in a shell) close automatically; the sibling pane takes the room. Panes that fail to start stay visible with the error.

Custom keys

Prefix keys are remappable via a keys.json next to the state file (~/Library/Application Support/hrdx/keys.json on macOS, $XDG_CONFIG_HOME/hrdx/keys.json on Linux, %AppData%\hrdx\keys.json on Windows). It maps action names to a single key; an override replaces that action's default keys. The prefix action remaps the ctrl+b trigger itself, not just an action inside it:

{
  "find": "f",
  "quit": "Q",
  "agent-cycle": "g"
}

Actions: prefix, literal, quit, picker-right, picker-down, agent-right, agent-down, agent-cycle (unbound by default), shell-right, shell-down, workspace, tab-new, tab-next, tab-prev, space-next, space-prev, pane-next, pane-prev, find, close-pane, close-space, equalize, rename, menu, settings, scroll-up, scroll-down, live.

Mouse

Everything is clickable: workspace, tab, and pane rows in the sidebar, the main tab bar, menus, and the settings entry at the bottom. Drag workspaces to reorder them, drag pane borders to resize, right-click for context menus, and drag with the left button to select text (copied straight to your clipboard). Wheel events go to the pane under the cursor: agent TUIs scroll themselves, shells scroll their local history, and shift+pgup / shift+pgdn do the same from the keyboard.

Remote and container panes

Every pane is a real PTY, so a shell pane can connect to a remote host, Docker container, or Kubernetes workload. For an interactive shell:

ssh user@host
docker exec -it -w /workspace container-name sh
kubectl exec -it -n namespace deploy/app -- sh

An agent installed at the target can be launched directly instead:

ssh -tt user@host 'cd /path/to/project && exec codex'
docker exec -it -w /workspace container-name codex
kubectl exec -it -n namespace deploy/app -- codex

Use the same pattern for any supported or custom agent. Authentication and the agent executable, configuration, credentials, and project files must be available at the target.

To make remote and container agents appear in pickers, agent cycling, settings, and the sidebar, register their client command as a custom harness:

[
  {
    "kind": "remote-codex",
    "binary": "ssh",
    "args": ["-tt", "user@host", "cd /path/to/project && exec codex"]
  },
  {
    "kind": "docker-codex",
    "binary": "docker",
    "args": ["exec", "-it", "-w", "/workspace", "container-name", "codex"]
  },
  {
    "kind": "k8s-codex",
    "binary": "kubectl",
    "args": ["exec", "-it", "-n", "namespace", "deploy/app", "--", "codex"]
  }
]

Wrapper scripts are useful when the host, container, namespace, pod, working directory, or authentication setup is dynamic. Set the harness binary to the wrapper path and put any fixed parameters in args.

The session holder keeps the local SSH, Docker, or Kubernetes client process alive when hrdx restarts. It cannot keep an agent alive when its remote host, container, pod, or network connection ends, and it does not automatically reconnect. Workspace Git details are read from the local workspace path, not from the remote filesystem.

Custom harnesses

Any agent CLI beyond the built-ins can be registered by dropping a harness.json next to the state file (~/Library/Application Support/hrdx/ on macOS, $XDG_CONFIG_HOME/hrdx/ on Linux, %AppData%\hrdx\ on Windows). Registered harnesses appear everywhere the built-ins do: in the pickers, in agent cycling, as agent panes in the sidebar hierarchy, and in the settings window for enabling and disabling.

[
  {
    "kind": "aider",
    "binary": "aider",
    "args": ["--no-auto-commits"],
    "resume": ["--restore-chat-history"],
    "busy": "Waiting for the model"
  },
  {
    "kind": "goose",
    "idle_title": "goose idle",
    "attention_title": "goose waiting"
  }
]
FieldPurpose
kindIdentifier used in pickers and pane names (required, must not collide with built-ins)
binaryExecutable to launch (default: same as kind)
argsExtra arguments passed on every launch
resumeArguments that resume the latest session when a restored pane relaunches
resume_firstPut the resume args before args (for subcommands like resume --last)
busyA substring visible on screen only while the harness is working; drives the busy spinner and the finish sound. Empty: braille spinner detection, like the built-ins
idle_titleTerminal-title substring emitted when the harness is idle; overrides a stale visible spinner
attention_titleTerminal-title substring emitted while waiting for user input; overrides the spinner and shows an orange dot when unfocused

Both title fields are optional and have no defaults, since every harness publishes its own markers. Leave them out and the harness is detected purely from the screen, exactly as busy describes. Set them when the harness keeps a spinner on screen while it is really idle or blocked on a prompt: a matching title always outranks the screen scrape. Check what your harness emits with printf '\e]2;...\a'-style OSC titles before picking a substring.

Socket API

While hrdx runs it serves a control API on a unix socket next to the state file (hrdx.sock), so scripts, editors, and coding agents can inspect and drive a running session. Disable with --api=false.

The protocol is newline-delimited JSON: send one request per line, receive one response line with the same id.

SOCK="$HOME/Library/Application Support/hrdx/hrdx.sock"   # macOS
# SOCK="$XDG_CONFIG_HOME/hrdx/hrdx.sock"                  # Linux
# hrdx.sock is a native Windows AF_UNIX socket too (%AppData%\hrdx\hrdx.sock).
# WSL has a separate socket namespace and cannot connect to it directly; Git
# Bash does not ship a compatible `nc -U`. Use a native Windows client, such
# as .NET UnixDomainSocketEndPoint or Go's net.DialUnix.

echo '{"id": "1", "method": "status"}' | nc -U "$SOCK"
echo '{"id": "2", "method": "workspace.create", "params": {"path": "~/Developer/api", "agent": "claude"}}' | nc -U "$SOCK"
echo '{"id": "3", "method": "pane.create", "params": {"workspace": "api", "kind": "shell", "split": "down"}}' | nc -U "$SOCK"
echo '{"id": "4", "method": "pane.send_text", "params": {"pane_id": 3, "text": "run the tests", "enter": true}}' | nc -U "$SOCK"
echo '{"id": "5", "method": "pane.wait", "params": {"pane_id": 3, "until": "idle"}}' | nc -U "$SOCK"
echo '{"id": "6", "method": "pane.read", "params": {"pane_id": 3}}' | nc -U "$SOCK"
MethodEffect
pingLiveness check, returns pong
statusWorkspaces, tabs, and panes with id, kind, running, and busy state
workspace.createOpen a directory as a workspace (path, optional agent)
workspace.closeClose a workspace by name or path
pane.createAdd a pane (workspace name or path, kind, split: right, down, tab)
pane.send_textType into a pane (pane_id, text, optional enter)
pane.readThe pane's visible screen as plain text
pane.waitBlock until a pane's agent is idle or busy (until, optional timeout_ms)
pane.closeClose a pane by id
events.subscribeKeep the connection open and push events

Successful responses are {"id": "...", "result": {...}}; failures are {"id": "...", "error": {"code": "not_found", "message": "..."}} with codes not_found, invalid_params, unknown_method, timeout, and error.

After events.subscribe the connection stays open and hrdx pushes lines like {"event": "pane.busy_changed", "data": {"pane_id": 3, "busy": false}}. Events: workspace.created, workspace.closed, pane.created, pane.closed, and pane.busy_changed, so a script can react the moment an agent finishes instead of polling.

Every request is answered by the TUI's own update loop, so the API always sees exactly what is on screen. pane.wait plus pane.send_text is enough to build simple agent pipelines: prompt an agent, wait until it is idle, read the screen, move on.

Themes

hrdx themes are JSON files that override any subset of the built-in colors; missing values inherit the default look. Drop them into a themes/ directory next to the state file (~/Library/Application Support/hrdx/themes/ on macOS, $XDG_CONFIG_HOME/hrdx/themes/ on Linux, %AppData%\hrdx\themes\ on Windows) and pick them in the settings window's theme section. The change applies immediately and persists.

{
  "name": "neon",
  "description": "Pink accent, near-black bars.",
  "colors": {
    "accent": 201,
    "bar_bg": "#101010"
  }
}

Values are ANSI 256 color numbers or "#rrggbb" strings.

ColorUsed for
accentFocused pane frames, highlights, logo, selected items
altPrefix badge, behind-count in the sidebar
mutedSecondary text, hints, idle pane names
faintInactive pane borders, sidebar divider
goodRunning dots, input badge
busyBusy spinner and completed-work attention dot
badErrors, exited dots
bar_bg / bar_fgHeader and footer bars
inkText on accent backgrounds, tab bar strip

See examples/themes/ for a full example.

Notifications

The notification section of the settings window has two independent toggles for finished agent turns: play a sound (built-in ding and chime, or your own audio files) and a system notification, which rings the terminal bell so your platform's native attention indicator fires: dock badge and bounce on macOS, the window manager's urgency hint on Linux, the taskbar/window attention flash on Windows Terminal (depends on its bellStyle setting). No notification daemon or permission required. Add custom sounds with a sounds.json next to the state file; they appear as choices and are previewed when selected:

[
  { "name": "sheep", "file": "~/sounds/maehhh.wav" },
  { "name": "gong", "file": "/Users/me/sounds/gong.aiff" }
]

name is the label in settings (must not collide with built-ins), file any audio file your OS player understands (afplay on macOS, paplay/aplay on Linux, PowerShell's SoundPlayer on Windows — WAV only there). Missing files are reported in the footer and skipped.

Persistence

Quitting hrdx does not kill your sessions. Pane processes live in a small background process (the session holder) that hrdx starts on demand and talks to over a local socket. Close the TUI, reopen it, and every shell and agent reattaches exactly where it was: running commands keep running, scrollback and screen state are replayed, nothing restarts. The holder is the same hrdx binary, uses no resources worth mentioning, and goes away when you kill its sessions.

Workspaces, panes, split layout, ratios, selection, and holder session ids are saved automatically (default: ~/Library/Application Support/hrdx/state.json on macOS, $XDG_CONFIG_HOME/hrdx/state.json on Linux, %AppData%\hrdx\state.json on Windows). On the next launch the layout is restored and each pane reattaches to its held session. When a held session is gone (rebooted machine, killed holder), the pane starts fresh instead: shell panes get a new shell, and agent panes relaunch resuming their latest session for that directory via the agent's own session store.

--persist=false disables the holder (panes die with the TUI, like a plain terminal). --fresh skips restoring and cleans up now-unreferenced held sessions; --state "" disables persistence entirely.

Development

make check

Windows without make on PATH: go vet ./... && gofmt -l . && go test ./....

License

MIT

Contributors

patriceckhart

64 commits

mi-skam

9 commits

m1lian

5 commits

Languages

Go

97.9%

PowerShell

1.0%

Shell

1.0%