跳轉到

Herdr —— Rust 終端多工器 + AI agent 協調器(試用)

Terminology rule (zh-TW pages)

技術名詞首次出現以「中文 (English original)」格式呈現,例:依賴注入 (dependency injection)。不自創翻譯——若無公認譯名直接保留英文 (如 embeddingtokenizer)。代碼、API 名、CLI flag、套件名、檔名一律不翻。

ogulcancelik/herdr 是一個用 Rust 寫的終端多工器 (terminal multiplexer),內建 coding-agent 感知——它會追蹤每個 pane 的 agent 狀態(idle / working / blocked / done)。它跟 tmux/zellij 屬於同一類工具,但更偏滑鼠優先 (mouse-first)、更 agent-native。官方文件:https://herdr.dev/docs/

本 repo 把 herdr 當作一個與 tmux 共存的試用工具 (trial tool)——你只會執行 herdr tmux,永不巢狀 (nested)。既有的 tmux / sesh / tmuxp / workmux 設定完全不動;herdr 純粹是加法,讓你能在不失去日常工具的前提下評估它。

  • 安裝 (Install)
  • macOS —— Homebrew(herdr 在 homebrew-core;由 dot_ansible/roles/devtools/tasks/main.yml 的 macOS 清單管理)
  • Linux —— GitHub release 的單一靜態 binaryherdr-linux-{x86_64,aarch64})放到 ~/.local/bin/herdr(由同一 role 中 # --- herdr ... --- 區塊管理)。沒有 tarball,所以不需要解壓步驟。
  • 驗證 (Verify)herdr --version;用 herdr server reload-config 驗證設定檔
  • 升級 (Upgrade):macOS 用 brew;自管的 Linux binary 用 herdr update
  • 設定 (Config)~/.config/herdr/config.toml —— chezmoi modify_ 覆蓋層 (overlay)dot_config/herdr/modify_config.toml.tmpl + 受管本體 .chezmoitemplates/herdr/config.toml)。覆蓋層在每次 chezmoi apply 強制套用我們受管的表 (tables),同時保留 herdr 在執行期寫回的東西(見 設定管理)。

不受 enableVimMode 控制。 該 flag 管的是 shell + tmux 的 modal 編輯;herdr 的 copy mode(prefix+[)天生就是 vi 風格(h/j/k/lw/b/e{/}v/Space 選取、y/Enter 複製、q/Esc 離開),沒有東西需要 gate。


與 tmux 的模型差異 (Model differences vs tmux)

herdr 的階層是 Session → Workspace → Tab → Pane——比 tmux(Session → Window → Pane)多一層。「Workspace」是專案層級的容器;「Tab」把多個 pane 分組。CLI(herdr session|workspace|tab|pane|agent …,大多支援 --json)是取代 tmux switch-client / list-sessions 等指令的腳本介面。

跟 tmux 最關鍵的差異:單一 herdr server 會託管多個具名 session (named sessions),每個 session 各自是一棵持久化的樹、各自有自己的 socket。最上層的 Session 這一層,比較接近「多個 tmux server / socket」,而不是 tmux 的 session——你在 tmux 會叫做 session 的東西(vibe/<repo>)在 herdr 對應的是 Workspace,而不是 herdr Session。

具名 session (Named sessions)

  • herdr —— 啟動或 attach default session(socket ~/.config/herdr/herdr.sock)。
  • herdr --session NAME —— 啟動/attach 一個具名 session(socket ~/.config/herdr/sessions/<NAME>/herdr.sock)。
  • herdr session list [--json] —— 列出所有 session 及其 running + socket_path--jsonsocket_path 才是權威值——純文字表格印出的是 herdr.socket,但真正的 socket 是 herdr.sock
  • herdr session attach NAME · herdr session stop NAME · herdr session delete NAME(stop 時 default 是合法的 NAME)。

從 CLI 子指令鎖定某個 sessionworkspace/tab/pane/agent 沒有 --session/--socket flag。唯一的槓桿是 HERDR_SOCKET_PATH 這個環境變數 (env var)——把它設成某 session 的 socket_path,所有子指令就會導向該 session。在 herdr pane 內部它已經被 export 成當前 session 的 socket,所以在 herdr 內跑的腳本天生就鎖定當前 session。hvibe --session NAME 正是靠這個機制運作(見下文)。

cwd 與 workspace 命名模型

herdr 追蹤 cwd 的方式跟 tmux 不同,會顛覆兩個常見預期(皆用 herdr pane list 驗證):

  • 每個 pane 有兩個 cwd。 cwd = shell 的啟動目錄(spawn 時固定);foreground_cwd = 即時 cwd,透過 OSC 7 shell 整合追蹤。shell 裡 cd 會更新 foreground_cwd;啟動 cwd 永不變。
  • 子行程 / 子 shell 裡的 cd 不會傳上來。 因為追蹤是 OSC 7-based,一個沒有再送 OSC 7 的子 shell 裡的 cd——例如 chezmoi cd,它會在 source 目錄 spawn 一個shell——對 herdr 是隱形的。foreground_cwd 不動,所以 space 的 git-repo 偵測與 prefix+G lazygit 位置都不跟著子 shell 走。這是 OSC 7 的固有特性,不是 herdr bug——預期行為。
  • 新 tab 開在 workspace 根目錄,不是聚焦 pane 的 cwd。new_cwd = "follow"(見下)時,split 繼承聚焦 pane 的 cwd,但新 tab 退回 workspace 的 cwd(通常是 $HOME)。已驗證:每個非首 tab 的 root pane cwd = $HOME
  • workspace(space)label 自動跟隨 root/primary pane 的即時 cwd basename(例如 → chezmoitrading-journal)。在 tab 1cd 會改 space 名;在其他 tab 裡 cd 不會。沒有 config 旋鈕控制這個。

new_cwd[terminal])—— 沒給明確 --cwd 時,新 pane/tab/workspace 的 CWD 政策:

意義
follow(預設) 繼承來源 pane/workspace(split → 聚焦 pane 的即時 cwd;新 tab → workspace 根)
home $HOME
current herdr 自身行程的目錄(不是聚焦 pane)
"~/path" 固定路徑

沒有任何 new_cwd 值能讓新 tab 開在聚焦 pane 的即時 cwd——只有明確的 herdr tab create --cwd … 可以(已驗證:tab create --cwd PATH 會把新 tab 的 shell spawn 在 PATH)。要一個「在此開新 tab」的鍵,綁一個 command pane 到 herdr tab create --cwd "$HERDR_ACTIVE_PANE_CWD" --focus(例如綁 prefix+C;原生 prefix+c 保留 = workspace 根):

[[keys.command]]
key = "prefix+C"
type = "pane"
command = "herdr tab create --cwd \"$HERDR_ACTIVE_PANE_CWD\" --focus"
description = "在目前 pane 的 cwd 開新 tab"

可行性對照表 (Feasibility matrix)(現有 tmux 體驗 → herdr)

現有能力 herdr 做法 這裡怎麼處理
Catppuccin 主題 + 明暗 原生 [theme] + auto_switch config.toml 設定
Splits / zoom / 開新 tab+workspace / pane 導覽 原生 [keys] actions 重綁成 tmux 肌肉記憶
Session 持久化(resurrect/continuum) 原生 detach/reattach 略過——原生
滑鼠 / 右鍵選單 原生 mouse-first 略過——原生
Agent 狀態 🤖/💬/✅(workmux,6 檔案) 原生 側欄 agent-state 彙整 略過——原生(tmux 端 workmux 不動)
sesh 模糊切換 + tmuxp layout Plugin herdr-plus Projects + Quick Actions Plugin + Projects 範本
tv channel 彈窗(prefix+T/U/a 自訂 command pane[[keys.command]] type="pane" Key bindings + 2 個 herdr-aware channel
lazygit / scratch 彈窗 自訂 command pane Key bindings
無縫 Ctrl-hjkl nvim↔pane 導覽 沒有 herdr-aware smart-splits 缺口——見下方 workaround
OSC133 copy-mode(cpout/cpblock tmux 專屬 缺口——cpcmd(zsh history)仍可用
每視窗狀態符號 + 書籤 ⭐📌 部分——report-metadata --custom-status(逐 pane、與 agent 狀態正交) 待 review 旗標hmark/prefix+m + tv herdr-review 收件匣);純裝飾的狀態列符號仍是缺口(無 format-string 插值)
AI session-summary / agent-wakeup 擷取 可改用 herdr pane read / pane list --json 重寫 延後——超出試用範圍

快捷鍵 (Keybindings)

Prefix 是 ctrl+b(跟 tmux 一樣)。內建 action 只能重綁 (rebind)(herdr 的 action 集合是固定的);其餘一切都是 [[keys.command]] 自訂指令。自訂指令會收到 $HERDR_SOCKET_PATH$HERDR_ACTIVE_PANE_ID$HERDR_ACTIVE_PANE_CWD,並在聚焦 pane 的 cwd 下執行。

兩組 herdr 環境變數。 上面那組 HERDR_ACTIVE_PANE_* 會被注入到 [[keys.command]] 的呼叫裡。而每個跑在 herdr pane 內的 shell 還會拿到一組環境值 (ambient)HERDR_ENV=1HERDR_PANE_IDHERDR_TAB_IDHERDR_WORKSPACE_ID、以及 HERDR_SOCKET_PATH(當前 session 的 socket)。腳本用 HERDR_ENV 當作「我在不在 herdr 裡?」的判斷,並繼承 HERDR_SOCKET_PATH 來鎖定當前 session——這正是 hvibe/hcode 依賴的東西。

Key Action 類型
prefix + c / prefix + 1..9 新 tab / 切 tab built-in default
prefix + h/j/k/l 聚焦 pane built-in default
prefix + \| / prefix + % · prefix + minus / prefix + " 左右分割 / 上下分割(tmux 肌肉記憶——直覺鍵與 tmux 預設鍵都綁) rebound(陣列 arrays)
prefix + z / prefix + x zoom / 關 pane built-in default
prefix + w / prefix + g workspace 導覽(navigate-mode:j/k 或方向鍵移動,Enter 選定) / session navigator built-in;navigate_workspace_* 重綁成 j/k + 方向鍵
prefix + ctrl + 1..9 直接跳到 workspace N(switch_workspace) rebound (indexed)
prefix + alt + 1..9 直接跳到 agent N 的 pane(focus_agent) rebound (indexed)

索引跳轉為何用 ctrl/alt 而非 shift:herdr 使用 kitty keyboard protocol,shift+1 仍帶著可列印的 !,所以 herdr 比對到的是符號而非 shift+1——prefix+shift+1..9 會靜默失效。ctrl+digit / alt+digit 沒有可列印形式,能乾淨地區分。 | prefix + [ | vi copy mode(hjklw/b/e{/}vy) | built-in default | | prefix + q | detach | built-in default | | prefix + ? | 快捷鍵說明覆蓋層——列出每個當前綁定與標籤(herdr 原生 which-key;手動觸發,非逾時自動提示) | built-in default | | prefix + , | 重新命名 tab | rebound(tmux 肌肉記憶) | | prefix + shift + r | reload config(prefix + r 保留給 resize mode) | rebound | | prefix + shift + b | 新 git worktree(從 prefix + shift + g 移過來) | rebound | | prefix + G | lazygit | command pane | | prefix + U | tv tools(CLI launcher) | command pane | | prefix + T | tv herdr-sesh(workspace/dir 切換) | command pane | | prefix + a | tv herdr-agent-panes(即時 agent panes) | command pane | | prefix + f | tv fleet-hosts(SSH picker) | command pane | | prefix + m | 切換目前 pane 的待 review 旗標(⭐) | command pane | | prefix + i | tv herdr-review——待 review 收件匣(被標記的 pane) | command pane | | prefix + ` | scratch shell | command pane | | prefix + O | herdr-plus Projects(layout launcher) | plugin action | | prefix + y | herdr-plus Quick Actions | plugin action |

大寫字母會解析成 prefix+shift+<letter>,herdr 保留給內建(shift+g worktree、shift+t rename-tab、shift+h/j/k/l swap-pane)。prefix+G/prefix+T 由上面的重綁釋放出來;herdr server reload-config 會在它的 diagnostics 回報任何殘餘衝突。

Session 輔助函式:hvibe / hcode / hhere / hrootsvibe / scode / shere / sroot 的 herdr 版)

dot_config/shell/24_herdr.sh 裡有四個 shell 函式,就是 tmux 的 svibe / scode / shere / sroot 在 herdr 端的對應物。兩個重量級的(hvibe / hcode)能一氣呵成拉起整個 coding workspace;兩個輕量級的(hhere / hroot)只是在某個目錄開一個純 workspace 並 attach——不需要 git repo,也沒有 agent layout。它們呼叫原生的 herdr workspace|tab|pane CLI,並原封不動重用純邏輯(specstory 包裝、--on-exit shell|kill|restart、git-root 解析、agent-CLI 偵測),這些來自 22_sesh.sh;只有 layout 呼叫不同。需要 herdr server 正在跑 + jq

指令 建立的 workspace Layout
hvibe [N] [CLI] / hvibe --agents claude,codex,opencode vibe/<repo> tab agents(N 個等寬 agent pane)+ tab git(lazygit)+ tab edit(nvim)
hvibe --tab-per-agent … vibe/<repo> 每個 agent 一個 tab + git + edit tab
hcode [CLI] coding-agent/<repo> tab editor(nvim 75% | agent 25%)+ tab monitor(btop)
hhere [CMD...] <basename $PWD> 單一 tab,$PWD(或 -p DIR)的純 shell;CMD 選用,會在 root pane 執行
hroot [CMD...] <basename git-root> hhere,但落在當前 git root(不在 repo 時退回 $PWD
  • 純開 pair(hhere / hroot 補上了「其他每個 herdr 入口都強制要 git repo + 完整 agent layout」的缺口。tmux 的 tmux new-session 會直接把你丟進 $PWD;herdr 多了一層 Workspace,所以少了這對指令,你得先啟動 herdr、建一個 space(會依 new_cwd 開在 $HOME)、再手動 cdhhere 一步到位:herdr workspace create --cwd "$PWD" → focus → 在外部就 attach。不需要 git;選用的指令以原始方式執行(不做 specstory/on-exit 包裝——那留給 hcode/hvibe)。flag 見 hhere -h / hroot -hLabel 注意事項:tab 1 裡 cd 之後,herdr 會把 workspace 重新命名為 root pane 的當前 cwd basename(見上方 cwd & workspace-naming model 一節),所以冪等聚焦是盡力而為——label 漂移後重跑會開一個新的 workspace。
  • 每 repo 冪等 (idempotent):重跑會聚焦既有的 vibe/<repo> / coding-agent/<repo> workspace,而不是重複建立(對應 svibe 的「存在就 attach」)。
  • Attach 感知(像 svibe 的 $TMUX 分支):從 herdr 內部跑 → workspace/tab 的 focus 呼叫會切換活著的 client(不開新 client)。從 herdr 外部的一般終端跑 → 輔助函式會拉起一個 attach 到該 session 的 client,讓新 workspace 真的看得到(herdr 的 workspace focus 只會移動已經 attach 的 client,所以少了這步,整包會被建在看不見的地方)。--no-attach 兩種情況都以 detach 方式建立。判斷依據是環境值 HERDR_ENV
  • --session NAME 鎖定某個正在跑的 herdr session(見 具名 session)。預設值:在 herdr 內部時用當前 session(透過繼承的 HERDR_SOCKET_PATH),否則用 default session。這個覆寫用 local -x HERDR_SOCKET_PATH 限縮在函式範圍,所以永遠不會外洩到你的 shell。指定一個沒在跑的 --session 會報錯,並提示用 herdr --session NAME 先啟動(hvibe 不會自己 spawn server)。
  • Agent 可見性:herdr 逐 pane 追蹤 agent 狀態,所以在預設的 splits layout 下,每個 agent 仍會在 herdr 的 agent 追蹤裡各自現身(已驗證:同一 tab 裡兩個 agent 會在 herdr agent list 顯示為兩筆)。緊湊的左側欄會把一個 tab 的狀態彙整成一個點——若你要每個 agent 各擁有一個 tab 層級的狀態點,用 --tab-per-agent
  • 等寬欄位:herdr 沒有 select-layout even-horizontal,所以 hvibe 明確設定每次分割的 --ratio1/(N-m+1))以維持 agent pane 等寬;hcode--ratio 0.75 給 nvim 75%。
  • 與 svibe/scode 相同的 flag:--on-exit--no-specstory--no-attach-p/--path,外加 --session NAMEhvibe 另有 --min-width / --tab-per-agent,並吃 $HVIBE_MIN_WIDTH / $HVIBE_LAUNCH_STAGGER。完整 flag 說明:hvibe -h / hcode -h

tmux 的 svibe / scode 仍是 tmux 端的對應物(見 sesh);兩個家族共存——hvibe/hcode 只碰 herdr,svibe/scode 只碰 tmux。

herdr-plus plugin(sesh + tmuxp + menu 的對應物)

herdr-plus 加了 Projects(宣告式的多 tab/多 pane workspace 範本 + 模糊選擇器——tmuxp/tmuxinator 的對應物)與 Quick Actions(模糊指令 launcher——彈出選單的對應物)。

安裝已自動化,由 devtools ansible role(# --- herdr-plus plugin --- 區塊)處理——冪等、每次 chezmoi apply 都會跑、裝好後就跳過。它呼叫:

herdr plugin install cloudmanic/herdr-plus   # 手動 fallback / role 實際跑的東西

herdr plugin install 在沒有 Go toolchain 時會下載預編譯 release binary,所以有沒有 Go 都能用。唯一的陷阱:若 PATH 上有一個過期的 Go(例如舊的 /usr/local/go 遮蔽了較新的),herdr 會嘗試從原始碼建置並失敗(invalid go version … must match format 1.23),而不是 fallback。ansible task 透過在可用時把 mise 的 Go(mise which go → 它的 bin 目錄)前置到 PATH 來規避;手動修法:把現代 Go 放前面:PATH="$(dirname "$(mise which go)"):$PATH" herdr plugin install cloudmanic/herdr-plus。(Go 現在由 mise 管理,受 installExtraRuntimes gate。)

Projects 範本由 chezmoi 管理於 dot_config/herdr/plugins/config/cloudmanic.herdr-plus/projects/~/.config/ 下的同一路徑。內建的 chezmoi.toml 對應本 repo 的 tmuxinator chezmoi session(editor/git/shell tab)。把 prefix+O 綁到 Projects、prefix+y 綁到 Quick Actions(設定裡已備好)。

Television 整合

大多數 tv channel(toolsfleet-hostsmlflowkill-processssh-config)的 action 不與 tmux 耦合,所以在 herdr command pane 裡原樣就能跑——綁一個鍵到 tv <channel> 即可。只有那些 action 會呼叫 tmux … 的 channel 才需要 herdr-aware 變體。這裡出貨三個:

  • herdr-seshdot_config/television/cable/herdr-sesh.toml)—— 列出 herdr session/workspace + zoxide 目錄;Enter 會分派 herdr session attach / herdr workspace focus / herdr workspace create --cwd,而不是 sesh connect / tmux switch-client
  • herdr-agent-panesdot_config/television/cable/herdr-agent-panes.toml)—— 與 agent-panes 同來源,但切換/kill 改用 herdr pane focus / herdr pane close
  • herdr-reviewdot_config/television/cable/herdr-review.toml)—— 待 review 收件匣:只列出帶 ⭐ 旗標的 pane(custom_status ~ REVIEW)。Enter 會 focus 該 pane 的 workspace/tab 並保留旗標;Alt+C 則 focus 清除旗標(「mark read」)。綁在 prefix+i。見下方 待 review 旗標 一節。

原本綁 tmux 的 sesh / agent-panes channel 保持不變以利共存。

Agent 狀態(取代 6 檔案的 workmux 整合)

herdr 原生偵測 agent 狀態並彙整進側欄(一個 blocked 的 agent 會標記它的 pane/tab/workspace)。Claude Code 是靠 screen-manifest 啟發式 (heuristics)(終端標題 + OSC progress)偵測,而非生命週期 hook。若啟發式不夠用,可以明確推送狀態:

herdr pane report-agent w1:p1 --agent claude --state working

試用期間我們仰賴原生偵測——tmux 端的 workmux 🤖/💬/✅ 系統(Claude/OpenCode hook → @workmux_status)不動,且只在 tmux 下生效。

可選的 agent 整合(onboarding 的「install」按鈕)

herdr 首次執行的 onboarding 會提議安裝可選的 agent 整合herdr integration install <agent>),讓 agent 直接回報狀態,而不必依賴 screen 啟發式。按下 install 會為每個偵測到的 agent 都設好。它會寫入什麼(本機已驗證):

Agent herdr integration install 建立什麼 會碰到 repo 管理的檔案嗎?
claude ~/.claude/hooks/herdr-agent-state.sh + ~/.claude/settings.json 裡一個 hook 項目 會——但 repo 的 hook-aware modify_settings.json merger 會保留它(跟它對待 CodeIsland 一樣)。chezmoi apply 是 no-op;不會把 herdr hook 拔掉。
codex 只有 ~/.codex/herdr-agent-state.sh 不會——~/.codex/config.toml 不動(與 chezmoi 計算出的目標相同)。
opencode ~/.config/opencode/plugins/herdr-agent-state.js(獨立 plugin) 不會——只有 workmux-status.ts 受管;herdr 的 plugin 共存。
cursor ~/.cursor/herdr-agent-state.sh + hook 腳本在 chezmoi 之外;共存。

這些整合檔案被 vendored 進 repo,所以不會在其他機器上重現(在那些機器再按一次 install,或跳過 onboarding)。它們用 herdr 自己的 socket,不會干擾 tmux/workmux(不同機制)。移除方式:herdr integration uninstall <agent>——而對 claude,之後要再跑一次 chezmoi apply,讓 merger 從 settings.json 丟掉那個已移除的 hook。

設定管理(為何用 modify_

herdr 會在執行期改寫 ~/.config/herdr/config.toml——完成 onboarding 會在檔首插入 onboarding = false,而 app 內的設定彈窗(theme / sound / toasts / pane labels)在套用時也會持久化到那裡。它就地編輯並保留既有註解,但執行期它擁有這個檔案。

這個檔案原本用 create_ 前綴只植入一次,避免 chezmoi apply 蓋掉那些寫回。代價是:repo 的編輯永遠傳不到已植入的機器——你在 source 改的分割鍵重綁、註解修正,都得手動 cp … source-path 刷新才會抵達,否則悄悄不見。這正是為何一台機器在 chezmoi diff 顯示乾淨時仍會感覺「沒同步」(diff 之所以乾淨,正是因為 create_ 從不再碰該檔)。這與跨機器無關:source 對每台 live 檔的 diff 可以逐字節相同,但 repo 編輯在你刷新前仍不會落地。

現在改成 modify_ 覆蓋層——dot_config/herdr/modify_config.toml.tmpl,一個小腳本:

  • 以受管本體 .chezmoitemplates/herdr/config.toml 為 base(帶註解 + tmux-parity 說明),每次 apply 強制套用 [theme] / [ui] / [terminal] / [keys] 表,並
  • 原樣拉回 (pull through) live 檔的其他每個頂層 key(onboarding[session][remote][update][experimental]…),讓 herdr 的執行期寫回存活。

TOML 沒有 jq,所以合併用 Python 透過 uv run --with tomlkit 跑(tomlkit 能來回保留註解 [[keys.command]] 的 array-of-tables;stdlib tomllib 唯讀,而 codex 的 modify_ emitter 無法輸出 AoT)。它退化到系統 python3,再退化到直接輸出原始受管範本,所以沒有 Python 的全新主機仍拿到完整設定。這是繼 ~/.codex/config.tomldot_codex/modify_config.toml.tmpl)之後第二個 TOML-overlay 先例。

要改 herdr 的受管設定,編輯 .chezmoitemplates/herdr/config.tomlchezmoi apply——它現在會抵達每台主機。用 herdr server reload-config 驗證(會在 diagnostics 回報快捷鍵衝突;空的 diagnostics 陣列 + "status":"applied" 代表設定——含陣列鍵綁定——解析乾淨)。

持久化與還原(不小心關掉)

兩個原生層,不需要 plugin:

  • 關 client/終端 ≠ 丟失 session。 herdr 跑一個持久化的 server(具名 session default,socket ~/.config/herdr/herdr.sock)。關終端視窗只是 detach——workspace/tab/pane 及其執行中的行程都還活著。用裸 herdr(或 herdr session attach default)重新 attach;herdr session list 會顯示它 running。這就是 tmux 的 detach/reattach,內建。
  • 完整 server 重啟 / 重開機 / crash。 設定裡的 [session]resume_agents_on_restore = true——在 server 重啟後把支援的 AI-agent pane 恢復到它們原生的對話 session(需要官方 herdr integration install <agent> hook)——外加一個選項可跨重啟保存最近的 pane 畫面歷史。這原生涵蓋了 tmux-resurrect/continuum 的情境,受那些 key + 整合 gate。

遠端 session (herdr --remote)

herdr 能跑一個透過 SSH attach 到另一台主機上 herdr server 的本機 thin client

herdr --remote <ssh-target> [--session NAME] [--handoff]
herdr --remote local_ubuntu          # <ssh-target> 是 ~/.ssh/config 裡任何主機/別名

它做什麼(依 https://herdr.dev/docs/remote):

  • SSH 到 <ssh-target>,偵測遠端平台,並在那裡自動安裝對應的 herdr server binary——遠端不需要預先裝好 herdr(但需要 SSH 存取權 + 安裝/執行 binary 的權限)。
  • 把你的本機剪貼簿(含貼圖)橋接到遠端 server,並沿用你的本機快捷鍵--remote-keybindings server 可改用 server 的)。
  • --session NAME 在遠端挑選/建立一個具名 session;--handoff 選擇加入 live handoff。

設定——config.toml[remote] 區塊:

  • manage_ssh_config = true(預設)—— herdr 透過一份產生的設定跑橋接 SSH,該設定先 include 你的 ~/.ssh/config,再補上 ServerAliveInterval/ServerAliveCountMax,讓閒置的 NAT/網路逾時不會斷掉 session。設成 false 則用原封不動的你自己的設定跑純 SSH。
  • remote_image_paste[keys])—— 原始按鍵的貼圖綁定,只在 --remote 下生效。

排解 remote platform detection failed: Connection closed by <host> port 22:這是暫時性的 SSH 層級斷線,不是 herdr 設定問題。先確認 herdr 用的 SSH 路徑真的通,然後重試即可:

ssh -o BatchMode=yes -o ConnectTimeout=5 <ssh-target> 'uname -sm'   # 應印出例如 "Linux x86_64"

若純 SSH 通,就重試 herdr --remote <ssh-target>;若一直失敗,在失敗當下看 ~/.config/herdr/herdr-client.log / herdr.log。常見的真正原因:SSH 認證需要橋接無法回答的互動提示,或 sshd 對快速連線做速率限制(MaxStartups)。

在 tmux 內巢狀執行 herdr(多 remote)

本 repo 的預設立場是 herdr tmux,不巢狀。但你可以刻意巢狀——tmux 當外層視窗管理器,內含數個 herdr client,有些本地、有些 herdr --remote <host> 連到不同 server。癥結是 prefix 衝突:兩者都預設 Ctrl+b,所以外層 tmux 吃掉它,內層 herdr 永遠收不到自己的 prefix(herdr #759)。

三種解法,依這裡的偏好排序:

  1. 雙擊 prefix 轉發(推薦,零設定)。 tmux 保有預設的 bind -T prefix C-b send-prefix(已確認本 repo 未覆寫),所以 Ctrl+b Ctrl+b 會把一個字面 Ctrl+b 轉發給內層 herdr;之後它的 prefix 綁定正常運作(Ctrl+b Ctrl+b c = 內層開新 tab)。herdr 與 tmux 都不用改——經典的 tmux-in-tmux 肌肉記憶。
  2. 改內層 herdr 的 prefix。keys.prefix(例如 "ctrl+a" / "ctrl+space")讓內層 herdr 用不衝突的 prefix——一次按鍵而非雙擊。代價:即使單獨跑 herdr,prefix 也變了(不再與 tmux 的 Ctrl+b 一致)。
  3. 不巢狀——用 herdr 原生遠端。 herdr --remote <target> [--remote-keybindings local|server] 跑一個本機 thin client 連到遠端 herdr server,中間沒有 tmux,而 herdr 自己就託管多個具名 session。對純粹「多個 remote」的需求,這通常比巢狀乾淨;--remote-keybindings local(預設)沿用你的本機 keymap,server 用遠端的。

注意:本 repo 的 tmux 用了很多 root-table bind -n C-* 綁定會遮蔽內層 app 的 Ctrl 鍵,所以 herdr 的 prefix-free direct 終端捷徑在 tmux 下不可靠——透過它的 prefix 觸及 herdr action(用 1 或 2)。herdr 自己的 allow_nested(config,預設 false)只管 herdr-inside-herdr(靠 HERDR_ENV 偵測),不是 herdr-inside-tmux。

待 review 旗標(mark-unread / ⭐)

tmux 那邊有逐視窗書籤(@bookmark_status + toggle-bookmark.sh,靠 #{?@bookmark_status,…} 渲染)。herdr 沒有 #{@option} 狀態列插值,所以那套機制不能直接移植——但這裡真正重要的用途可以:「agent 跑完了(done)但我還沒 review」。herdr 一旦你點進去瞄一眼就把 done pane 塌成 idle,唯一還需要注意的訊號就此消失。

解法用 herdr 的逐 pane custom-status metadata,它與原生 agent 偵測正交:

herdr pane report-metadata <pane> --source review --custom-status "⭐ REVIEW"   # 設定(永久——不帶 --ttl-ms)
herdr pane report-metadata <pane> --source review --clear-custom-status         # 清除

已驗證:一個 pane 可以同時帶 custom_status:"⭐ REVIEW"agent_status:"idle"——所以點進去不會清掉旗標(重點所在)。herdr pane get 會吐出 custom_status 欄位,herdr pane list(原生 JSON——--json flag)讓收件匣列舉被標記的 pane,所以不需要 sidecar 檔;herdr 自己就是真相源。

各介面(共用同一支腳本 ~/.config/herdr/review-mark.sh = dot_config/herdr/executable_review-mark.sh,即 tmux toggle-bookmark.sh 的對應物):

介面 動作
hmark / hunmarkdot_config/shell/24_herdr.sh 的 alias) 對目前 pane(環境值 $HERDR_PANE_ID)set / clear,或傳入 pane id
prefix+m 切換 focus pane 的旗標(用 $HERDR_ACTIVE_PANE_ID
tv herdr-review / prefix+i 收件匣:只列出被標記的 pane。Enter focus 過去並保留 ⭐(你可能還在 review 中);Alt+C focus 清除(「mark read」)

Caveat——custom_status 每 pane 單值。 延後中的用量狀態 driver(下方)也會推 --custom-status;雖然用不同 --source,herdr pane get 只吐一個扁平的 custom_status,兩者會爭同一個可見標籤。目前無實際衝突(driver 未建、review 旗標短暫),但見 backlog/herdr-usage-status-driver.md

AI 用量 / 額度狀態

herdr 沒有原生的用量/額度/token 顯示(側欄只顯示 agent 狀態)。它確實有一個逐 pane 的 hook——herdr pane report-metadata <pane> --source ID --custom-status "…" --ttl-ms N——driver 可以把 "Claude 62% • Codex 78%" 之類的標籤推進去(與上方 待 review 旗標 用的是同一個 hook——注意兩者都寫入單一的逐 pane custom_status)。有一個 Codex-only 的社群 plugin(jerryfane/herdr-codex-usage-kit)已從 CodexBar 讀的同一份 ~/.codex 資料做到這件事;但沒有東西涵蓋 Claude/ChatGPT 額度。延後——CodexBar 的選單列仍是多供應商的檢視。設計與選項記在 backlog/herdr-usage-status-driver.md

缺口(沒有乾淨的 herdr 對應)

  • 無縫 Ctrl-hjkl nvim↔pane 導覽。 vim-tmux-navigator 與 tmux 耦合(is_vimps/pane_tty 啟發式 + nvim plugin)。herdr 沒有 smart-splits 對應物——它的 pane focus 是 prefix+h/j/k/l,在邊界不會穿透進 nvim 的 splits。Workaround:在 nvim 裡用它自己的 <C-w>hjkl。這是相對 tmux 最大的 UX 退步。
  • OSC133 copy-modecpout / cpblock、prompt 跳轉、最後輸出 yank)是 tmux 專屬。herdr 的 copy mode(prefix+[)是 vi 風格但沒有 OSC133 的 prompt 邊界感知。cpcmd(zsh history,與多工器無關)仍可用。
  • 裝飾用的狀態列符號(📌/🔖 當作自由浮動的視窗標籤):herdr 沒有 #{@option} 的 format-string 插值,所以 tmux 風格的狀態列書籤不能移植。(但「mark-unread / 待 review ⭐」這個具體用途已解決——見上方 待 review 旗標 一節——靠逐 pane 的 custom_status metadata;只剩純裝飾的狀態列符號仍是缺口。)
  • 逐鍵 pane 縮放。 tmux 把 prefix+H/J/K/L(與 M-hjkl 微調)直接綁成縮放;herdr 沒有逐鍵縮放——它用模態的 resize_modeprefix+r)再按 h/j/k/l。不是精確對等,但是相近的對應物。

不是缺口: vi copy-mode 本身原生(prefix+[),且逐 pane 的 agent 狀態是原生偵測——我原本以為會缺的兩件事,結果都內建了。

延伸閱讀 (See also)