본문으로 건너뛰기

Herdr

에이전트 세션을 전부 띄워 두는 herdr — 저를 기다리는 에이전트만 눈에 띄게 한 사이드바, /team 워커 pane, 브랜치 토큰, 계정 전환.

herdr 는 에이전트 멀티플렉서예요. tmux 처럼 탭과 pane 을 나누지만, 사이드바가 pane 마다 어떤 에이전트가 돌고 있는지, 지금 일하는 중인지 제 답을 기다리는지를 보여 줘요. 저는 에이전트 세션을 전부 그 안에서 굴려요.

화면 아래 자막은 Cmd 키를 앞에, 같은 동작의 prefix 키를 괄호에 적었어요.

동작 원리

사이드바는 제가 봐야 할 pane 만 표시해요

에이전트를 여럿 띄우면 문제는 "지금 누가 저를 기다리나"예요. herdr 사이드바는 pane 마다 에이전트 종류와 상태를 보여 주는데, 저는 상태 글자를 blocked(제 답을 기다림)와 done 일 때만 보이게 해 뒀어요. 일하는 중인 pane 은 조용하고, 제가 손대야 하는 pane 만 글자가 떠요. 브랜치도 같은 생각이에요. 기본 브랜치(main·master·develop)는 흐리게, 작업 브랜치는 접두어(feat·fix·hotfix)마다 다른 색으로 보여요.

/team 워커도 이 안에 떠요

/team 을 치면 팀장 세션이 이 herdr 안에 워커 pane 을 직접 띄워요(herdr agent start). 워커는 첫 프롬프트부터 팀장과 동료의 이름을 알고, herdr 탭을 닫으면 Claude 워커도 같이 끝나요. 팀장은 워커 화면을 스크립트로 읽어(team.sh peek) 30분 넘게 busy 인 워커를 걸린 것으로 보고 들여다봐요.

사이드바 브랜치는 타이머 스크립트가 채워요

에이전트마다 worktree 가 달라서, 어느 pane 이 어느 브랜치에서 일하는지가 사이드바에 보여야 해요. 에이전트 행 두 번째 줄에 그 브랜치를 보여 주려고 $branch 라는 사용자 토큰을 써요. herdr 는 이 토큰을 스스로 채우지 않아요. 그래서 탭 바 오른쪽에 아무것도 출력하지 않는 command 항목을 하나 두고, 이 항목이 5초마다 pane-branch.sh 를 돌려요. 스크립트는 에이전트 pane 마다 브랜치를 읽어 herdr pane report-metadata 로 보고해요. 토큰에는 30초 TTL 이 있어서, 스크립트가 멈추면 브랜치 표시도 저절로 사라져요.

사이드바의 브랜치는 5초마다 도는 스크립트가 보고해요탭 바 오른쪽의 보이지 않는 command 항목이 5초마다 pane-branch.sh 를 돌려요. 스크립트는 에이전트 pane 마다 git 브랜치를 읽어 herdr pane report-metadata 로 보고하고, 사이드바가 그 값을 $branch 토큰으로 보여 줘요. 토큰은 30초 TTL 이라 스크립트가 멈추면 표시도 사라져요.command 항목탭 바 · 5초 간격pane-branch.sh에이전트 pane 의 브랜치report-metadataTTL 30초사이드바 $branch멈추면 저절로 사라져요

계정은 키 하나로 바꿔요

Cmd+Shift+A 는 제가 만든 herdr 플러그인 tak-bro.account-switch 를 불러요. Claude Code·Codex 로그인 프로필을 키 하나로 바꾸고, 이미 돌고 있는 세션도 30초 안에 새 계정을 집어요. 한 계정의 사용량 창이 차면 브라우저 재로그인 없이 다른 계정으로 넘어가요. pane 마다 $acct 배지가 지금 어느 계정을 쓰는지 보여 줘요.

내 설정

herdr 설정

사이드바는 두 패널이에요. 위의 spaces 패널이 프로젝트와 브랜치를 보여 주고, 아래 agents 패널은 누가 돌고 있는지를 앞에 둬요. 에이전트 이름은 종류마다 색을 달리해요.

~/.config/herdr/config.toml
onboarding = false
[theme]
name = "dracula"
auto_switch = false

# Sidebar sits one step darker than the Ghostty background (#1D1E27) so it
# reads as its own panel. Dracula's neon state colours are swapped for pastels
# so state icons and the sidebar row colours below share one palette.
[theme.custom]
accent = "#c6a0f6"
green = "#a6da95"
yellow = "#eed49f"
red = "#ed8796"
blue = "#8aadf4"
teal = "#8bd5ca"
peach = "#f5a97f"
mauve = "#c6a0f6"
sidebar_bg = "#17181f"
active_row_bg = "#2a2c3a"
selection_bg = "#44475a"

[ui]
agent_panel_sort = "spaces"
status_indicators = "symbols"
prompt_new_tab_name = false
# The command entry prints nothing and so never shows: it is the timer that
# keeps the sidebar's $branch token fresh (see pane-branch.sh).
tab_bar_right = [
  { type = "zoom" },
  { type = "datetime", format = "%H:%M" },
  { type = "command", command = "~/.config/herdr/pane-branch.sh", interval_seconds = 5, timeout_seconds = 3 },
]
tab_bar_right_separator = " · "

# The spaces panel above already lists projects, so agent rows lead with who:
# line 1 is the agent, coloured per kind, plus the state in words only when it
# needs me (blocked, done) — the icon already carries idle and working.
# Line 2 is the workspace and, off the default branch, the branch without its
# type prefix (reported by pane-branch.sh), both faded.
[ui.sidebar.agents]
row_gap = 1
rows = [
  [
    "state_icon",
    { token = "agent", fg = "#8aadf4", bold = true, rules = [
      { equals = "hwip cc", fg = "#f5a97f" },
      { contains = "claude", ignore_case = true, fg = "#f5a97f" },
      { equals = "hwip pi", fg = "#c6a0f6" },
      { equals = "pi", fg = "#c6a0f6" },
      { contains = "codex", ignore_case = true, fg = "#8bd5ca" },
    ] },
    { token = "state_text", rules = [
      { starts_with = "blocked", fg = "#ed8796", bold = true },
      { starts_with = "done", fg = "#a6da95" },
      { starts_with = "", hide = true },
    ] },
  ],
  [
    { token = "workspace", fg = "#6e738d", bold = false },
    { token = "$branch", fg = "#6e738d" },
  ],
]

# Default branches fade out; working branches take a colour by prefix.
[ui.sidebar.spaces]
row_gap = 1
rows = [
  ["state_icon", { token = "workspace", fg = "#e4e6f5", bold = true }],
  [
    { token = "branch", fg = "#8aadf4", rules = [
      { equals = "main", fg = "#6e738d" },
      { equals = "master", fg = "#6e738d" },
      { equals = "develop", fg = "#6e738d" },
      { starts_with = "feat", fg = "#a6da95" },
      { starts_with = "fix", fg = "#f5bde6" },
      { starts_with = "hotfix", fg = "#ed8796" },
    ] },
    "git_status",
  ],
]

[ui.sound]
enabled = true

[keys]
prefix = "ctrl+space"

# tabs
new_tab = ["prefix+c", "cmd+t"]
close_tab = ["prefix+shift+x", "cmd+shift+w"]
previous_tab = ["prefix+p", "cmd+shift+["]
next_tab = ["prefix+n", "cmd+shift+]"]
switch_tab = ["prefix+1..9", "cmd+1..9"]

# panes
split_vertical = ["prefix+v", "cmd+d"]
split_horizontal = ["prefix+minus", "cmd+shift+d"]
close_pane = ["prefix+x", "cmd+w"]
focus_pane_left = ["prefix+h", "cmd+alt+left"]
focus_pane_down = ["prefix+j", "cmd+alt+down"]
focus_pane_up = ["prefix+k", "cmd+alt+up"]
focus_pane_right = ["prefix+l", "cmd+alt+right"]
cycle_pane_next = ["prefix+tab", "cmd+]"]
cycle_pane_previous = ["prefix+shift+tab", "cmd+["]
zoom = ["prefix+z", "cmd+shift+enter"]
# Rectangle defaults bind cmd+ctrl+left/right to topLeft/topRight; macos-defaults.sh clears them.
resize_pane_left = "cmd+ctrl+left"
resize_pane_down = "cmd+ctrl+down"
resize_pane_up = "cmd+ctrl+up"
resize_pane_right = "cmd+ctrl+right"

# workspaces
new_workspace = ["prefix+shift+n", "cmd+shift+n"]
goto = ["prefix+g", "cmd+k"]
previous_workspace = "cmd+shift+up"
next_workspace = "cmd+shift+down"
switch_workspace = ["prefix+shift+1..9", "alt+1..9"]
toggle_sidebar = ["prefix+b", "cmd+b"]

[[keys.command]]
key = "cmd+shift+g"
type = "popup"
command = "lazygit"
description = "lazygit"
width = "90%"
height = "90%"

# Account picker (plugin tak-bro.account-switch): swaps the Claude Code / Codex login.
[[keys.command]]
key = "cmd+shift+a"
type = "shell"
command = "herdr plugin action invoke open --plugin tak-bro.account-switch"
description = "accounts"

브랜치 토큰 스크립트

~/.config/herdr/pane-branch.sh
#!/bin/sh
# Reports each agent pane's git branch as its `branch` metadata token, which
# the sidebar agent rows in config.toml show as `$branch`.
#
# herdr runs this from a `tab_bar_right` command entry purely as a timer: it
# prints nothing, so the entry itself stays invisible. The token carries a TTL,
# so it disappears on its own if this stops running.
#
# The type prefix is dropped (feat/herdr-sidebar -> herdr-sidebar) and
# the default branches config.toml fades (main, master, develop) report nothing:
# the spaces panel already shows the full branch.

PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
herdr=${HERDR_BIN_PATH:-herdr}
tab=$(printf '\t')

"$herdr" pane list |
  jq -r '.result.panes[] | select(.agent != null) | [.pane_id, .foreground_cwd // .cwd] | @tsv' |
  while IFS=$tab read -r pane cwd; do
    branch=$(git -C "$cwd" symbolic-ref --quiet --short HEAD 2>/dev/null)
    case $branch in
      main | master | develop) branch= ;;
    esac
    # An empty value clears the token.
    "$herdr" pane report-metadata "$pane" --source custom:pane-branch \
      --token "branch=${branch#*/}" --ttl-ms 30000 >/dev/null
  done

확인

  1. 작업 브랜치에 있는 레포에서 에이전트를 띄워요. 5초 안에 사이드바 에이전트 행 둘째 줄에 접두어를 뺀 브랜치 이름이 나와요.
  2. 브랜치 보고가 안 되면 스크립트가 읽는 값을 직접 봐요.
herdr pane list | jq '.result.panes[] | select(.agent != null) | {pane_id, cwd: (.foreground_cwd // .cwd)}'