Herdr —— Rust 終端多工器 + AI agent 協調器(試用)¶
Terminology rule (zh-TW pages)
技術名詞首次出現以「中文 (English original)」格式呈現,例:依賴注入
(dependency injection)。不自創翻譯——若無公認譯名直接保留英文
(如 embedding、tokenizer)。代碼、API 名、CLI flag、套件名、檔名一律不翻。
herdrdev/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):
- 兩個平台都是 —— GitHub release 的單一靜態 binary(
herdr-{linux,macos}-{x86_64,aarch64})放到~/.local/bin/herdr,由dot_ansible/roles/devtools/tasks/main.yml中# --- herdr ... ---區塊管理。沒有 tarball,所以不需要解壓步驟。 - macOS 刻意不走 Homebrew,而且這是本 repo 唯一這樣做的工具。上游在 Homebrew/mise/Nix 安裝上停用
herdr update,因為 binary 歸套件管理器所有 —— 那等於拿掉了唯一能保住 pane 的升級路徑,只剩下重啟 server(也就是殺掉每一個 pane 內的行程)這一條。所以我們捨棄 formula 改用 release binary,讓 macOS 也拿得到--handoff。切換前就裝好的機器,由同一個 role 裡的一次性brew uninstall herdr遷移(兩份都留著會讓PATH上出現兩個 binary,最糟是自己對自己protocol_mismatch)。 - 驗證 (Verify):
herdr --version;用herdr server reload-config驗證設定檔 - Agent skill:每次
chezmoi apply都會從herdr --skill安裝官方 global skill 到~/.agents/skills/herdr/SKILL.md,並建立 Claude discovery symlink。binary 是版本 權威;Git vendored 的 catalog 副本刻意不作為 runtime 來源。 - 升級 (Upgrade):兩個平台都用
just upgrade-herdr(即先執行herdr update --handoff,再刷新與 binary 同版本的 skill)—— 必須在 herdr 外面跑,見下
herdr update在 herdr pane 裡面會拒絕執行。 handoff 要換掉的正是持有你當下這個 pane 的 server process,所以它 fail closed:$ herdr update --handoff # 在 herdr 裡面的 pane 執行 update failed: run `herdr update` outside herdr after detaching from the session正確流程是 detach(
prefix+q)→ 在一般終端機執行 → 用裸的herdr重新接上。detach 不會殺掉任何東西(關 client ≠ 掉 session),接著--handoff會在不結束 pane 行程的情況下換掉 server —— 2026-07 實測 0.7.1 → 0.7.5,pane 裡有一個 Claude Code session 全程存活。just upgrade-herdr把這件事寫進流程:偵測到HERDR_ENV/HERDR_PANE_ID時它會跳過並印出指示而不是失敗,所以在 herdr pane 裡跑just upgrade-all不會死在這一步。完整記錄:pitfalls/herdr-update-handoff-refuses-inside-pane.md。安裝不看版本,升級要自己跑。 Linux 的 ansible task 是用
herdr --version的 rc 當條件 —— 判斷的是「有沒有裝」而不是「是不是最新」—— 所以新機器拿到的是當天的 latest,之後chezmoi apply再也不會碰它。這是本 repo install-vs-upgrade 分離的設計,但 herdr 比多數工具更容易出事,因為它的 config 會跑在 binary 前面([[keys.command]]用了 0.7.4 才有的type = "popup",舊版 herdr 會拒絕整個 keys 區塊 ——reload-config回status: "partial"+keeping current keys settings,而且只有那一行 diagnostic 會告訴你)。Herdr app 版本與 integration schema 版本是兩件事。 目前 binary 是 0.8.0;
herdr integration status顯示的v7則是 Codex integration 的 schema 版本,不代表 binary 降版。每次 Herdr 升級後若看到outdated (v6 < v7),執行herdr integration install codex。腳本 sidecar 仍由 Herdr 擁有;~/.codex/hooks.json則透過 chezmoi 的 hook-awaremodify_merger 與 peon/其他工具共存。 - 設定 (Config):~/.config/herdr/config.toml—— chezmoimodify_覆蓋層 (overlay)(dot_config/herdr/modify_config.toml.tmpl+ 受管本體.chezmoitemplates/herdr/config.toml)。覆蓋層在每次chezmoi apply強制套用受管 tables,同時保留其餘所有 live/user-owned 頂層設定,包括 Herdr 目前的 runtime 寫入(見 設定管理)。為什麼套件管理器裝的 herdr 會擱淺自己的 server —— 這就是 macOS 離開 Homebrew 的原因。 herdr 的 socket API 有 protocol 版本號,而套件管理器的升級沒辦法重啟 server —— 所以
brew upgrade herdr之後,每一個 CLI 呼叫(連帶所有tv herdr-*channel、hvibe/hcode、以及各個[[keys.command]]helper)都會protocol_mismatch失敗,直到 server 重啟為止,而重啟會殺掉所有 pane 內的行程。herdr update --handoff——那個能保住 pane 的 live 路徑——在 Homebrew/mise/Nix 安裝上是停用的,所以 brew 裝的 herdr 沒辦法迴避這次重啟。本 repo 現在在 macOS 上也改裝自管的 release binary,正是為了補掉這個缺口;下面那篇 pitfall 仍然保留,因為它描述的是任何尚未遷移的機器上會發生的事(而且just upgrade-herdr偵測到套件管理器安裝時會跳過並印出指示)。要用herdr status(看compatible:/restart_needed:)判斷,不要看herdr --version。完整的復原對照表:pitfalls/herdr-brew-upgrade-strands-running-server.md。不受
enableVimMode控制。 該 flag 管的是 shell + tmux 的 modal 編輯;herdr 的 copy mode(prefix+[)天生就是 vi 風格(h/j/k/l、w/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。--json的socket_path才是權威值——純文字表格印出的是herdr.socket,但真正的 socket 是herdr.sock。herdr session attach NAME·herdr session stop NAME·herdr session delete NAME(stop 時default是合法的NAME)。
從 CLI 子指令鎖定某個 session:workspace/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 正是靠這個機制運作(見下文)。
用 herdr-grep 搜尋 pane 內容¶
Herdr 能讀取單一 pane,但沒有原生的跨 pane grep。本 repo 部署 herdr-grep,把 pane list → pane read → rg 包成一個指令,並印出每筆命中的完整 Session / Workspace / Tab / Pane 座標:
$ herdr-grep -F 'connection refused'
[session=default workspace=w1 tab=w1:t2 pane=w1:p4] 183:connection refused while opening socket
herdr-grep 'error|failed' # regex;當前/default session
herdr-grep -F -i 'connection refused' # 固定字串、不分大小寫
herdr-grep --visible 'ready' # 只搜尋目前可見畫面
herdr-grep --source recent-unwrapped 'url' # 搜尋保留歷史,不受硬換行切斷
herdr-grep --session work 'panic' # 一個正在執行的具名 session
herdr-grep --all-sessions 'rate limit' # 所有正在執行的本機 session
herdr-grep --all-sessions --json 'panic' # 結構化 matches + errors
herdr-grep --pick 'error|failed' # 先 grep,再以 fzf 選擇並跳轉/attach
herdr-grep --pick --all-sessions 'panic' # Herdr 外:選擇、預先聚焦、attach session
herdr-grep -F -- -leading-dash # 保護以 `-` 開頭的 pattern
| 面向 | 行為 |
|---|---|
| 預設範圍 | 有 HERDR_SOCKET_PATH(Herdr 內部)時搜尋 ambient session,否則搜尋 default。--session NAME 從 herdr session list --json 解析權威 socket;--all-sessions 只掃描正在執行的 session。ambient socket 無法唯一對應到 registry 時回傳 2,不會誤標成其他 session。 |
| 內容來源 | 預設 recent(完整保留的 scrollback);--visible 只看目前畫面;終端硬換行切斷片語時使用 --source recent-unwrapped。 |
| 輸出 | 人類可讀輸出在每一筆命中重複 session/workspace/tab/pane。--json 另含 socket、cwd、agent 狀態、byte-offset submatches、complete 與結構化 errors。行號是相對於這次 capture,不是永久 pane 座標。 |
| 互動 picker | --pick PATTERN 把已過濾 matches 交給 fzf。Enter 會精確聚焦 pane;在 Herdr 外還會 attach 所選 session。Alt+S 用 recent-unwrapped 重跑同一 pattern;Alt+V 回到 visible。 |
| Exit status | 0 = 有命中且掃描完整;1 = 完整掃描但無命中;2 = 用法錯誤/執行錯誤/掃描不完整。pane 在掃描途中消失時,成功的 matches 仍會保留、錯誤送到 stderr/JSON,並回傳 2。 |
在 Herdr 內,prefix+Alt+F 執行 herdr-grep --pick --visible:先輸入一次 grep pattern,再用 fzf refine 已過濾的 matches。Agent pane 使用 Herdr 的精確 agent focus;普通 split pane 則透過驗證過的 directional pane neighbor / pane focus 最短路徑抵達,最後再確認 active workspace/tab 與 pane layout.focused_pane_id。已 attach 時若選到其他 session,picker 會拒絕而不是巢狀啟動(請先 detach)。從一般 shell 執行 --pick --all-sessions,會先聚焦所選目標再 attach 該 session,與 hhere 的 focus-then-attach 模型一致。Preview 行號屬於先前 capture,pane 持續輸出時可能漂移。
搜尋範圍受限於 live Herdr server 仍保留的內容:已關閉的 pane、早於 history limit 的輸出,以及 alternate screen 已丟棄的內容都無法找回。命令必須在能存取 Unix socket 的環境執行;遠端 server 請透過 SSH 執行已部署的 CLI,例如 ssh server 'herdr-grep --all-sessions -F -- "connection refused"'。herdr --remote 是互動式 thin-client attach,不是 pane 子指令的 RPC 前綴。
tv herdr-panes 與 tv herdr-review 仍只 fuzzy-search metadata(pane/session identifiers、狀態、cwd)。可見 pane 文字只是 preview,不在 Television 的 searchable source 中。Television 在 live query 之前執行 source,且 source 收不到 query,因此 content selection 刻意採 herdr-grep --pick → fzf,而不是新增 tv herdr-grep channel。
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+Glazygit 位置都不跟著子 shell 走。這是 OSC 7 的固有特性,不是 herdr bug——預期行為。 - 新 tab 跟隨聚焦 pane 的即時 cwd(herdr ≥0.7.x)。 用
new_cwd = "follow"(見下)時,新 tab 繼承聚焦 pane 的即時 cwd——跟 split 一樣。herdr issue #912 改了follow,讓 tab 跟 split 行為一致;舊的「新 tab 開在 workspace 根(通常$HOME)」被當成 bug 移除了。沒有任何new_cwd值(也沒有 workspace 層級的 cwd——herdr workspace get完全沒有 cwd 欄位)能讓新 tab 開在 workspace 根;要那樣請用下方的prefix+C。 - workspace(space)label 自動跟隨 root/primary pane 的即時 cwd basename(例如 →
chezmoi、trading-journal)。在 tab 1 裡cd會改 space 名;在其他 tab 裡cd不會。沒有 config 旋鈕控制這個。 - 相對路徑的
--cwd是由 SERVER 解析的,而且解析失敗會靜默退回$HOME。herdr workspace create --cwd ../foo是把../foo接在herdr server當初啟動的目錄後面——不是接在你 shell 的$PWD後面;而當那個路徑不存在時,herdr 會回傳{"result":{"type":"ok"}},pane 卻開在$HOME。沒有 error,也沒有 warning。tmux 恰好相反:new-session -c ../foo是 client 端解析的——這正是為什麼shere/scode/svibe原版不需要防護、而 herdr 移植版需要。任何使用者給的路徑都必須在呼叫端的 shell 先絕對化再交給 CLI——24_herdr.sh用_herdr_abs_dir做這件事。見pitfalls/hhere-p-relative-path-opens-workspace-at-home.md。
new_cwd 值([terminal])—— 沒給明確 --cwd 時,新 pane/tab/workspace 的 CWD 政策:
| 值 | 意義 |
|---|---|
follow(預設) |
繼承來源 pane——split 與新 tab 都取聚焦 pane 的即時 cwd(herdr ≥0.7.x) |
home |
$HOME |
current |
herdr 自身行程的目錄(不是聚焦 pane) |
"~/path" |
固定路徑 |
沒有任何 new_cwd 值能讓新 tab 開在 workspace(space)根——follow 現在跟隨聚焦 pane,而 herdr 沒有 workspace 層級的 cwd 欄位。本 repo 把這個行為做成 keybind:prefix+C → ~/.config/herdr/new-tab-at-space-root.sh(dot_config/herdr/executable_new-tab-at-space-root.sh),它推導出 space 根——編號最小 tab 的 pane 即時 cwd,也就是 herdr 用來當 space label 的那個——然後執行 herdr tab create --workspace <wid> --cwd <root> --focus。原生 prefix+c 與滑鼠「+」按鈕維持跟隨聚焦 pane;keybind 無法攔截滑鼠按鈕。
[[keys.command]]
key = "prefix+C"
type = "pane"
command = "~/.config/herdr/new-tab-at-space-root.sh \"$HERDR_ACTIVE_PANE_ID\""
description = "new tab at the workspace (space) root dir"
可行性對照表 (Feasibility matrix)(現有 tmux 體驗 → herdr)¶
| 現有能力 | herdr 做法 | 這裡怎麼處理 |
|---|---|---|
| Catppuccin 主題 + 明暗 | 原生 [theme] + auto_switch |
受管設定固定 Mocha(auto_switch = false) |
| Splits / zoom / 開新 tab+workspace / pane 導覽 | 原生 [keys] actions |
重綁成 tmux 肌肉記憶 |
| Session 持久化(resurrect/continuum) | 原生 detach/reattach | 略過——原生 |
| 滑鼠 / 右鍵選單 | 原生 mouse-first | 略過——原生 |
| Agent 狀態 🤖/💬/✅(workmux,6 檔案) | 原生 側欄 agent-state 彙整 | 略過——原生(tmux 端 workmux 不動)。面板改用注意力佇列排序,而非依 space 分組:ui.agent_panel_sort = "priority"(herdr 預設是 "spaces") |
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 |
| dev / lazygit / scratch launcher | 自訂 command pane + popup | lazygit 等全螢幕 TUI 維持暫時 pane;短命 modal task 才用 popup |
URL 選單(prefix+u,tmux-fzf-url) |
自訂 command pane + 輔助腳本 | prefix+u → url-pick.sh(fzf → x open);--source recent 掃描 scrollback |
| 翻譯 | tmux 沒有對應物 | prefix+t 開啟互動式 translate TUI;prefix+y Quick Actions 擷取 pane 並進行雙語翻譯 |
檔案路徑選單(prefix+p;tmux 上為 extrakto prefix+Tab) |
自訂 command pane + 輔助腳本 | prefix+p → path-pick.sh——兩層(cwd 下存在的優先)→ x copy |
| 本機/遠端 attach 間的 Neovim clipboard | OSC 52 只寫入;不支援 clipboard query | yank 送到 attached client;普通 p 用 Neovim register;外部文字用 terminal paste 貼入 |
| 搜尋所有 pane 內容並跳轉 | CLI pipeline + fzf + 精確聚焦 helper | prefix+Alt+F → herdr-grep --pick --visible;Alt+S 搜尋 unwrapped scrollback |
無縫 Ctrl-hjkl nvim↔pane 導覽 |
沒有 herdr-aware smart-splits | 缺口——見下方 workaround |
OSC133 copy-mode(cpout/cpblock) |
tmux 專屬 | 缺口——cpcmd(zsh history)仍可用 |
| 每視窗狀態符號 + 書籤 ⭐📌 | 部分——report-metadata --token(逐 pane metadata token、與 agent 狀態正交) |
待 review 旗標(hmark/prefix+m + tv herdr-review 收件匣);純裝飾的狀態列符號仍是缺口(無 format-string 插值) |
| AI session-summary / agent-wakeup 擷取 | agent-wakeup 使用原生 agent list/get/read 與穩定的 agent-session ID |
已完成——tmux/Herdr 雙 backend 狀態、排程、預覽與安全送出 |
快捷鍵 (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=1、HERDR_PANE_ID、HERDR_TAB_ID、HERDR_WORKSPACE_ID、以及HERDR_SOCKET_PATH(當前 session 的 socket)。腳本用HERDR_ENV當作「我在不在 herdr 裡?」的判斷,並繼承HERDR_SOCKET_PATH來鎖定當前 session——這正是hvibe/hcode依賴的東西。Pane 環境在 herdr-server 啟動時凍結。 不像 tmux(
update-environment),herdr 不會在新 client 接上時更新SSH_CONNECTION/DISPLAY/WAYLAND_DISPLAY/SSH_AUTH_SOCK—— 每個 pane 繼承的是 daemon 首次啟動時的值。實際後果:透過 SSH 時 pane 的$SSH_*是空的,而$WAYLAND_DISPLAY還指著過期的本機 display,所以按這些變數挑後端的剪貼簿工具會複製到錯的機器。x與 Neovim 靠判斷HERDR_ENV(→ 走 pane TTY 的 OSC 52,它確實代理到真正的 client)繞過。Neovim 刻意只用這條路做 copy:Herdr 不會轉送 OSC 52 clipboard-query response,若把完整 provider 接到unnamedplus,普通p最多會卡十秒。Editor register 用普通p;外部剪貼簿文字用 terminal paste。見pitfalls/x-copy-over-ssh-writes-remote-clipboard-not-osc52.md、pitfalls/nvim-p-waits-for-osc52-response-in-herdr.md與 clipboard.md。透過SSH_AUTH_SOCK轉發的 agent,需要每個 pane 重新 export,或從有它的 session 啟動 server。
| Key | Action | 類型 |
|---|---|---|
prefix + c / prefix + 1..9 |
新 tab / 切 tab | built-in default |
prefix + C |
在 workspace(space)根目錄開新 tab——prefix+c 與滑鼠「+」維持跟隨聚焦 pane |
command pane |
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(popup 內按鍵) |
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(hjkl、w/b/e、{/}、v、y) | 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 + Alt + e| 只編輯 active runtime config、驗證並 reload(永不呼叫 chezmoi) | command pane | |prefix + shift + b| 新 git worktree(從prefix + shift + g移過來) | rebound | |prefix + d|devrepository/task/worktree dashboard | command pane | |prefix + G/Alt + g| lazygit 暫時 pane(近全螢幕 popup 會阻止切 tab/workspace,因此試用後移除) | command pane | |prefix + Alt + p/Alt + i/Alt + n| gh-dash 暫時 pane,分別開在 PR / Issue / 通知 view,預設只看當前 pane 的 repo(t切換) | command pane | |prefix + Y| 在聚焦 pane cwd 開啟 Yazi 檔案管理員;按q回到不變的 layout | 90% × 85% popup | |prefix + M| btop 系統監控器 | command pane | |prefix + N| nvtop GPU 監控器 | command pane | |prefix + U|tv tools(CLI launcher) | command pane | |prefix + u| URL 選單 — 從 pane fzf 挑一個 URL 並開啟(x open);tmux-fzf-url 對應物。--source recent= 完整 scrollback | command pane | |prefix + T|tv herdr-sesh(workspace/dir 切換) | command pane | |prefix + a|tv herdr-panes(非 shell / agent / process / shell) | command pane | |prefix + Alt + A|tv agent-wakeup(tmux/Herdr 雙 backend quota 等待與排程 continue) | command pane | |prefix + f|tv fleet-hosts(SSH picker) | command pane | |prefix + Alt + F| 輸入 pane-content pattern →herdr-grep --pick --visible→ fzf 精確跳轉;Alt+S = scrollback、Alt+V = visible | command pane | |prefix + m| 切換目前 pane 的待 review 旗標(⭐) | command pane | |prefix + i|tv herdr-review——待 review 收件匣(被標記的 pane) | command pane | |prefix + p| 複製檔案路徑 ——兩層 fzf(cwd 下存在的路徑在上),複製解析後的絕對路徑(x copy) | command pane | |prefix + `| scratch shell | command popup | |prefix + E| 執行任意指令(在該 pane 的 cwd)—— 用 fzf 從歷史挑、或直接打新的;指令結束後 popup 自己關掉(細節) | command popup | |prefix + O| herdr-plus Projects(layout launcher) | plugin action | |prefix + t| 互動式翻譯器 ——在translateTUI 輸入或貼上文字(詳細) | 80% × 80% popup | |prefix + Alt + c/Alt + x| claude / codex + SpecStory,往右分割(細節) | detached command | |prefix + Alt + t| claude + SpecStory 開在新 tab | detached command | |prefix + Alt + r| 重複上一次的 agent 開啟(快捷鍵或 Agent: Quick Actions 皆算) | detached command | |prefix + y| herdr-plus Quick Actions | plugin action |大寫字母會解析成
prefix+shift+<letter>;其中許多已是內建(shift+gworktree、shift+trename-tab、shift+h/j/k/lswap-pane)。prefix+G/prefix+T由上面的重綁釋放,prefix+Y則是 upstream 未使用的空位;herdr server reload-config會在diagnostics回報任何殘餘衝突。
lazygit 的直接快捷鍵使用小寫 alt+g(不需 prefix);alt+G 會要求 Alt+Shift+g。Ghostty/cmux 的 Meta 鍵設定使用左 Option(macos-option-as-alt = left)。
prefix+Y 刻意採用 session-modal popup,適合在閱讀 agent pane 時做快速檔案操作。
它從聚焦 pane 的 cwd 啟動,但子行程最後所在的目錄無法改變已在執行的來源 shell;
需要「離開後跟著 cd」時請使用 shell y helper。
Session navigator(prefix + g)——popup 內的按鍵¶
navigator 是 herdr 的 Cmd-K 對應物:一棵 space → tab → pane 樹,帶模糊搜尋與 agent 狀態過濾。它的 footer 只寫了 enter switch · / search · b/w/i/d/a states · j/k/↑↓ move · esc close,但還有幾個鍵是有效的、而且上游沒有文件(對照 herdr 0.7.5 src/app/input/modal.rs 驗證):
| 按鍵 | 作用 |
|---|---|
space |
展開/收起游標所在的 space ——只在 space 那一行有效;停在 tab/pane 行時靜默無反應 |
ctrl + d / ctrl + u |
下/上翻半頁 |
home / end(或 G) |
跳到第一/最後一列 |
backspace |
取消目前的狀態過濾(b/w/i/d),回到全部 |
a |
同時清掉查詢字串與狀態過濾 |
esc |
直接關閉 popup(≤ 0.7.2 是先清查詢+過濾,要按第二次才關) |
三個限制,讓 space 沒辦法當成 collapse-all 用(src/app/actions.rs):
- 每次開啟都重置成全部展開 ——
open_navigator_from()會清掉expanded_workspaces再把每個 workspace 塞回去。收起狀態不會跨越關閉 popup。 - 只要有搜尋字串或
b/w/i/d過濾就強制全展開(expanded = query_kind != Empty || …)。只有在a/ 空查詢下,收起才看得到效果。 - navigator 的展開集合與 sidebar 的
collapsed_space_keys是兩套獨立的 —— 後者才是~/.config/herdr/session.json持久化的那個,由 sidebar 右鍵選單的Expand/Close group驅動。在其中一邊收起某個 space,不會影響另一邊。
沒有 expand-all / collapse-all,也沒有深度上限。 上游對兩者的請求都因 issue 模板政策(issue 只收 bug)被直接關掉:#1256(ui.goto_depth = "workspace" | "tab" | "pane")與 #1255(vim h/l 收合展開)→ discussion #1248。
想要只看 space 層的總覽,改用
prefix + T(tv herdr-sesh)。它本質上就是一份扁平的 workspace 清單——等於永久的 collapse-all 視圖——而且還能從 zoxide frecency 目錄建立新 space,這是原生 navigator 做不到的。prefix + w是另一個 space 層的選項(原生 sidebar navigate mode),但沒有模糊搜尋。
Session 輔助函式:hvibe / hcode / hhere / hroot(svibe / 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)、再手動cd。hhere一步到位:herdr workspace create --cwd "$PWD"→ focus → 在外部就 attach。不需要 git;選用的指令以原始方式執行(不做 specstory/on-exit 包裝——那留給hcode/hvibe)。flag 見hhere -h/hroot -h。Label 注意事項:tab 1 裡cd之後,herdr 會把 workspace 重新命名為 root pane 的當前 cwd basename(見上方 cwd & workspace-naming model 一節),所以冪等聚焦是盡力而為——label 漂移後重跑會開一個新的 workspace。-p DIR會先在你的 shell 絕對化(透過_herdr_abs_dir,與hcode/hvibe共用)再交給 herdr,所以hhere -p ../sibling這類相對路徑可以正常運作,打錯的 DIR 也會直接報錯,而不是靜默開在$HOME——原因見上方 cwd 模型一節的--cwd那條。 - 每 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明確設定每次分割的--ratio(1/(N-m+1))以維持 agent pane 等寬;hcode用--ratio 0.75給 nvim 75%。 - 與 svibe/scode 相同的 flag:
--on-exit、--no-specstory、--no-attach、-p/--path,外加--session NAME;hvibe另有--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 在沒有 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(設定裡已備好)。
Quick Actions 同樣由 chezmoi 管理,位於 …/cloudmanic.herdr-plus/quick-actions/(每個 action 一個 TOML)。本 repo 出貨六個 copy action(見 複製 pane 與 space 的資訊),外加 plugin 的五個起手式範例(GitHub / Google / Search Google / Open Repo / Reveal Working Dir),已為 Linux 調整——macOS open → 本 repo 的跨平台 x open,repo 選單指向 daviddwlee84/*。這些 copy 操作刻意集中在此處,不再占用六個 prefix 直達鍵。因為這個受管目錄非空,herdr-plus 不會 自己 seed 那些範例(它只在空目錄時 seed),所以在此 vendored;不要的 TOML 刪掉即可。要新增就往這裡丟一個 TOML,或在 <repo>/.herdr-plus/quick-actions/ 出貨一組 repo 本地的。
Quick Actions 是給一次性、非互動式指令用的(內建範例全是
open <url>)。herdr-plus 用sh -c跑選中的 action,沒有 PTY、沒有互動 stdin,所以互動式 TUI 會出問題——btop 立刻退出、nvtop 收不到 F10。要跑 TUI 就用 dev/lazygit 那種浮動 command pane([[keys.command]] type="pane")——這就是為何 dev(prefix+d)、btop(prefix+M)與 nvtop(prefix+N)是快捷鍵而非 Quick Action。
開啟 coding agent(prefix + y → Agent: …、prefix + Alt + c/x/t/r)¶
在 focus 中的 pane 旁開啟 claude / codex / opencode / claude-copilot-once / codex-copilot-once,不必在新 shell 裡手打指令。
- 選單——
prefix + y,輸入agent:每個 agent 一個 Agent: … Quick Action(第一層),接著是以heading分組的 select——SpecStory (record) / Direct × → Split right / ↓ Split down / ⊞ New tab(第二層)。herdr-plus 沒有更深的巢狀(Option只有label/value/description/heading),所以 agent 是第一層、錄製 × 位置是第二層。OpenCode 只有 Direct(SpecStory 沒有該 provider);copilot 版本對應--specstory-mode run|off。 - 快捷鍵——
prefix + Alt + cclaude+SpecStory → 右、Alt + xcodex+SpecStory → 右、Alt + tclaude+SpecStory → 新 tab、Alt + r重複上一次(~/.cache/herdr/agent-open.last)。
為什麼比手動分割快。 herdr 只能用預設 shell 開 pane。~/.config/herdr/agent-open.sh 以 --env HERDR_AGENT_LAUNCH="<agent> <run|off>" 建立分割/tab;~/.zshrc / ~/.bashrc 最頂端(oh-my-zsh / ble.sh 之前)的 guard 以精簡環境、子行程方式執行 ~/.config/herdr/agent-run.sh,之後 rc 照常繼續——agent 結束後 pane 就變成正常、完整載入的 shell。實測:按鍵後約 1 秒偵測到 agent。
精簡環境 = 00_exports.sh(PATH)+ 09_editor.sh + cwd 的 mise env + ~/.shellrc.secrets + ~/.config/bash/secrets.sh,copilot 版本另載 43_copilot_proxy.sh。不載入:~/.config/zsh/secrets.zsh 與所有互動模組。若 agent 需要完整互動環境,對 launcher 設 HERDR_AGENT_FULL_RC=1(一般分割後 herdr pane run 進已載入的 shell——慢路徑)。兩支 script 都以 allowlist 檢查 agent 與模式,環境變數內容不會被 eval。測試:tests/unit/herdr_agent_open.bats。
Television 整合¶
大多數 tv channel(tools、fleet-hosts、mlflow、kill-process、ssh-config)的 action 不與 tmux 耦合,所以在 herdr command pane 裡原樣就能跑——綁一個鍵到 tv <channel> 即可。只有那些 action 會呼叫 tmux … 的 channel 才需要 herdr-aware 變體。這裡出貨三個:
herdr-sesh(dot_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。Ctrl+D關閉選中的 workspace;Alt+Y複製它的目錄(workspace 列與 zoxide 目錄列都適用——見dir與cwd的差別)。preview 會把推導出的目錄顯示在workspace get原始 JSON 之上,讓你先看清楚Alt+Y會給你什麼。herdr-panes(dot_config/television/cable/herdr-panes.toml)—— 通用 pane picker,由herdr-agent-panes更名。Ctrl+S切換 非 shell → agent → process → shell,Ctrl+R重掃目前 session。每列顯示類型、workspace/tab/pane、前景程式、agent 狀態及標題或 cwd。Enter / Alt+T 退出 picker,透過共用focus-pane.py精準跳到 pane(包含 split pane)。Alt+K 確認後關閉單一 pane 並退出;Ctrl+Y / Alt+P 複製 ID/目前目錄。詳見 TV pane picker。herdr-review(dot_config/television/cable/herdr-review.toml)—— 待 review 收件匣:只列出帶 ⭐ 旗標的 pane(tokens.review非空)。Enter 會 focus 該 pane 的 workspace/tab 並保留旗標;Alt+C則 focus 並清除旗標(「mark read」)。綁在prefix+i。見下方 待 review 旗標 一節。
原本綁 tmux 的 sesh / agent-panes channel 保持不變以利共存。
綁在 command pane 的 channel,Enter action 要用
mode = "execute"而不是"fork"。type = "pane"綁定只有在它的指令結束時才會消失,而 tv 在executeaction 之後會退出、在fork之後則會繼續活著——所以fork的 Enter 會讓 picker pane 懸在你剛 focus 的 workspace 上面。herdr-sesh的[actions.open]正是為此改用execute;它的ctrl-d(close_ws)仍保留fork,因為它與reload_source搭配,tv 必須活著才能重繪清單。herdr-panes的 Enter / Alt+T 與確認關閉也使用execute;herdr-review維持既有fork跳轉行為,方便持續檢視。
Agent 狀態(取代 6 檔案的 workmux 整合)¶
herdr 原生偵測 agent 狀態並彙整進側欄(一個 blocked 的 agent 會標記它的 pane/tab/workspace)。Claude Code 是靠 screen-manifest 啟發式 (heuristics)(終端標題 + OSC progress)偵測,而非生命週期 hook。若啟發式不夠用,可以明確推送狀態:
試用期間我們仰賴原生偵測——tmux 端的 workmux 🤖/💬/✅ 系統(Claude/OpenCode hook → @workmux_status)不動,且只在 tmux 下生效。
受管 UI 固定使用 Catppuccin Mocha 與 status_indicators = "symbols"。Agents 第一行顯示狀態/workspace/tab/review,第二行是 machine · agent,第三行是 terminal_title_stripped 任務標題。只有 Local 時 machine 會隱藏;沒有標題時第三行不顯示。Alt+g 與 prefix+G 都以暫時 pane 開啟 LazyGit。
可選的 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.tmpl merger 會保留它(跟它對待 CodeIsland 一樣)。chezmoi apply 是 no-op;不會把 herdr hook 拔掉。 |
| codex | ~/.codex/herdr-agent-state.sh + ~/.codex/hooks.json 的 SessionStart hook(0.8.0 integration v7) |
hooks.json 是 hook-aware modify_ 目標:Herdr 條目會保留,peon 條目會加法合併;config.toml 不再放 lifecycle hooks。 |
| 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 的另一個 writer。上游 v0.8.2 與目前 master 會持久化 onboarding、theme/UI、sound、toast、pane-label、agent-sort 設定、update channel 與 key reset。這些編輯會保留無關行與未知 sections,但最後仍是不加鎖的 whole-file write;外部修改之後仍需明確 reload。
這個檔案原本用 create_ 只植入一次。2026-07 轉換時,實測 source 與 live target 逐 byte 相同;並沒有發生過已觀察到的 onboarding clobber 事故。改成 modify_ 是在修正 split key 與 cwd 行為時做的預防性雙 writer 修正。create_ 雖不覆蓋 Herdr 寫入,卻也讓後續 repo 編輯永遠到不了已植入的主機。
現在改成 modify_ 覆蓋層——dot_config/herdr/modify_config.toml.tmpl,一個小腳本:
- 以
.chezmoitemplates/herdr/config.toml為受管 base,每次 apply 完整強制套用[theme]、[ui]、[terminal]、[keys];並 - 原樣拉回 (pull through) 其餘每個 live 頂層 key。
onboarding與update.channel是目前真正的 Herdr 寫入;其他[update]subkeys、[session]、[remote]、[experimental]、未知 sections 等則刻意以 live/user-owned 設定保留,不能誤稱為目前的 writeback。
TOML 沒有 jq,所以合併用 Python 透過 uv run --with tomlkit 跑(tomlkit 能 round-trip 註解與 [[keys.command]] array-of-tables;stdlib tomllib 唯讀,而 codex 的 modify_ emitter 無法輸出 AoT)。它退化到系統 python3,再退化到直接輸出 raw managed template,所以沒有 Python 的全新主機仍拿到完整設定。這是繼 ~/.codex/config.toml(dot_codex/modify_config.toml.tmpl)之後第二個 TOML-overlay 先例。
之後的 chezmoi apply 會刻意重新強制套用 canonical [theme]、[ui]、[terminal]、[keys] tables。因此要持久化某一項 runtime 實驗,必須另外手動、選擇性編輯 .chezmoitemplates/herdr/config.toml。這個 modify_ target 絕不能執行 chezmoi add 或 chezmoi re-add:它們可能取代/繞過 merger,或把 runtime-owned state 匯入 source。
編輯 runtime config、驗證並 reload(prefix + Alt + e)¶
prefix + Alt + e 會開一個暫時 command pane,執行 ~/.config/herdr/edit-config.sh。它只編輯 active runtime target:非空的 $HERDR_CONFIG_PATH 優先,否則是 ~/.config/herdr/config.toml。它永不呼叫 chezmoi。小寫 e 與 prefix + E 指令 popup、Herdr 內建的 prefix + e scrollback editor 都不同。
防護流程依序如下:
- 要求 target 是既有 regular file(Unix symlink 直接拒絕),解析單一 blocking
$EDITORexecutable/wrapper(fallbackvi),並在開 editor 前以cp -p建立唯一的同目錄 backup。 - 編輯 exact target,再用
HERDR_CONFIG_PATH="$target" herdr config check驗證同一路徑。 - Editor 或 validation 失敗時,把被拒內容保存成權限受限的 sibling
config.toml.invalid-*,再以同 filesystem move 原子還原先前有效 backup;不 reload。 - 驗證成功後才呼叫
herdr server reload-config;繼承的$HERDR_SOCKET_PATH讓 reload 留在目前 session。成功時移除 backup。
Preflight 或 backup 失敗會保持 target 不變且不開 editor。Reload 失敗不會丟棄有效編輯:edited target 與原始 backup 都保留。只有 cleanup 失敗時同樣保留/回報 backup,不 rollback。Rollback 失敗則回報並保留所有可恢復的 target/candidate/backup 路徑。互動 command pane 會等 Enter 讓錯誤留在畫面;非互動執行不等待。
持久化與還原(不小心關掉)¶
兩個原生層,不需要 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 通,就重試 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)。
三種解法,依這裡的偏好排序:
- 雙擊 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 肌肉記憶。 - 改內層 herdr 的 prefix。 設
keys.prefix(例如"ctrl+a"/"ctrl+space")讓內層 herdr 用不衝突的 prefix——一次按鍵而非雙擊。代價:即使單獨跑 herdr,prefix 也變了(不再與 tmux 的Ctrl+b一致)。 - 不巢狀——用 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 metadata token,它與原生 agent 偵測正交:
herdr pane report-metadata <pane> --source review --token review="⭐ REVIEW" # 設定(永久——不帶 --ttl-ms)
herdr pane report-metadata <pane> --source review --clear-token review # 清除
已驗證:一個 pane 可以同時帶 tokens.review = "⭐ REVIEW" 與 agent_status:"idle"——所以點進去不會清掉旗標(重點所在)。herdr pane get 會吐出 tokens map,herdr pane list(原生 JSON——不加 --json flag)讓收件匣列舉被標記的 pane,所以不需要 sidecar 檔;herdr 自己就是真相源。token 的存在與否就是旗標本身,所以字符文字可以自由替換。
只支援 herdr ≥ 0.7.4,而且 token 需要 row layout 才看得見。 0.7.4 把
--custom-status/ 扁平的custom_status欄位換成了這個帶命名空間的 token map——這是一次靜默移除,沒有被列進 breaking change。兩個後果:在更舊的 herdr 上這支 helper 會以unknown --custom-status死掉;而且不像custom_status,token 只會在 sidebar row layout 有指名它的地方渲染。這就是為什麼.chezmoitemplates/herdr/config.toml釘住了[ui.sidebar.agents] rows,在 herdr 預設的第一列後面補上"$review"——拿掉它旗標本身仍然運作(prefix+i照樣列得出來),但 sidebar 上的 ⭐ 會消失。見pitfalls/herdr-0.7.4-drops-custom-status.md。
各介面(共用同一支腳本 ~/.config/herdr/review-mark.sh = dot_config/herdr/executable_review-mark.sh,即 tmux toggle-bookmark.sh 的對應物):
| 介面 | 動作 |
|---|---|
hmark / hunmark(dot_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」) |
三個名字必須一起動:review-mark.sh 裡的 TOKEN、herdr-review.toml 裡的 .tokens.review 查詢,以及 sidebar row layout 裡的 "$review"。
在 popup 裡執行任意指令(prefix + E)¶
prefix + E 在適合 popup 的情境提供通用入口:用 fzf 從 shell 歷史挑一條指令(或直接打一條新的),在聚焦 pane 的 cwd 執行,結束後關閉 popup。 輔助腳本:~/.config/herdr/run-command.sh = dot_config/herdr/executable_run-command.sh。
為什麼用 type = "popup" 而不是其他做法 —— prefix+E 刻意浮在上方,而不是再開一個 command pane:
| 做法 | 問題 |
|---|---|
type = "pane"(prefix+d/G/M/N) |
執行期間會切開平鋪版面,整個重排 |
prefix + c → 打指令 → exit |
四個步驟,而且會弄亂 tab bar |
type = "popup" |
session-modal,浮在版面之上 —— 什麼都不重排,關掉就回到原位 |
type = "popup" 需要 herdr ≥ 0.7.4(#1125 加入,width/height 支援 cell 數或百分比)。它才是真正的 tmux display-popup -E 對應物。
prefix + `` `(scratch shell)基於同樣理由也是 popup —— 當它還是 command pane 時會佔滿版面,感覺像是把當前視窗 *zoom* 起來而不是開暫存空間。prefix+d/prefix+G/prefix+M/prefix+N(dev / lazygit / btop / nvtop)維持type = "pane"`。近全螢幕 lazygit popup 已在試用後移除:Popup 是 session-modal,開著時不能切到其他 tab/workspace。這個限制適合短命 modal task,不適合可能留著的全螢幕 TUI。
它沒辦法做成 herdr-plus Quick Action(prefix + y),雖然那是最直覺會去找的地方。兩個硬阻礙:Quick Actions 透過 sh -c 執行、沒有 PTY/stdin(這正是 btop/nvtop 是 command pane 而不是 Quick Action 的原因),而且每個 action 都是寫死的 command = "…" 字串,沒有自由輸入欄位。
行為:
| cwd | --cwd → $HERDR_ACTIVE_PANE_CWD → herdr pane get 的 foreground_cwd → $PWD。優先用環境變數的好處是:即使 CLI 與舊 server protocol 不相容,它照樣能運作 |
| 選取 | fzf 翻 $HISTFILE(預設 ~/.zsh_history),最新在上、已去重。Enter 執行反白的那筆歷史;Alt+Enter 執行你字面打的內容,即使歷史裡還有 match 也一樣;完全沒 match 時按 Enter 同樣會當作新指令執行;Esc 則什麼都不做。沒有 fzf 時退回純 read 提示 |
| shell | 預設 $SHELL -ic,所以這個 repo 的 alias 與 function 都能解析(gst、cas、x …)。--sh 改用 sh -c —— 快,但 alias 不存在 |
| 結束時 | 成功就關閉;失敗則印出 [exit N] 並等你按 Enter,讓錯誤訊息不會一閃而過。可用 HERDR_RUN_HOLD=always\|never 覆寫 |
有兩個可攜性陷阱已經在腳本裡處理掉了,如果你要改它值得先知道:
~/.zsh_history是 extended-history 格式(: <ts>:<elapsed>;<cmd>)而且含有非 UTF-8 位元組,所以除非在LC_ALL=C下解析,BSDsed會以sed: RE error: illegal byte sequence中止;另外「最新在上」的反轉用 POSIXawk實作,因為tail -r只有 BSD 有、tac只有 GNU 有。為什麼非要有
Alt+Enter。 只要 fzf 還有 match,單純的 Enter 就無法表達「執行我字面打的那串」—— 打ls -la而歷史裡有ls -la /tmp,Enter 會選走歷史那筆,於是整個 picker 感覺起來只能重播舊指令。--expect=alt-enter就是那個逃生口。要注意 fzf 的輸出行數不是固定的:在--print-query --expect下,沒有 match 的 Enter 只會印出 query(一行),而選中時才印出 query / key / selection 三行。所以腳本是從第 2 行讀 key(空的或不存在 ⇒ 純 Enter),而且只在 exit code 0 時才相信第 3 行。
複製 pane 與 space 的資訊到剪貼簿¶
六個低頻「把這個抓到剪貼簿」的操作都收在 prefix+y Quick Actions 清單中,並由同一支腳本驅動(~/.config/herdr/pane-copy.sh = dot_config/herdr/executable_pane-copy.sh)。它把一個 herdr CLI 呼叫萃取成人類可讀的文字,再導入本 repo 自己的 x copy(自動選 pbcopy / wl-copy / xclip / xsel / OSC 52;x 以絕對路徑 fallback 解析,因為 Quick Action 可能在沒有互動式 PATH 的情況下執行)。action 透過 $HERDR_PLUS_PANE_ID 傳入聚焦 pane;為空時 helper 會 fallback 到 herdr pane current。
| 操作 | Quick Action | 進剪貼簿的內容 |
|---|---|---|
process |
Copy pane: process info | 前景 process——cmdline + pid + cwd(來自 herdr pane process-info) |
coord |
Copy pane: coordinate | 可直接貼回 CLI 的 session / workspace / tab / pane id 區塊 + socket 路徑 + 一行 # herdr pane get <pane> |
content(可見) |
Copy pane: content (visible) | pane 目前螢幕上的文字(herdr pane read --source visible) |
content(scrollback) |
Copy pane: content (scrollback) | pane 完整保留的 scrollback(--source recent) |
dir |
Copy space: dir | workspace(space)的根目錄——見下方 |
cwd |
Copy pane: cwd | 聚焦 pane 的即時工作目錄(等同 pwd / abspath 的答案) |
座標回答「這是哪個 session > space > tab > pane?」,並以可餵回 CLI 的形式呈現。herdr 在 pane/tab/workspace 子命令上沒有 --session 旗標——session 只能經由 HERDR_SOCKET_PATH 指定——所以區塊裡納入 socket= 那行作為 session 選擇器(session 名稱 則是把該 socket 對 herdr session list --json 比對而得)。
所有 copy-*.toml Quick Actions 都共用這支 helper,並由 prefix+y 模糊啟動。原本的直達鍵(prefix+P/D/V/S/d/ctrl+d)已移除,讓 prefix namespace 優先留給互動式工具;prefix+d 現在啟動 dev dashboard。互動式檔案路徑 picker 仍保留在 prefix+p,因為 fzf 需要 PTY/stdin,無法在 Quick Action 裡執行。
dir 與 cwd 的差別,以及為什麼右鍵選單做不到¶
dir 回答的是「這個 space 是關於哪個目錄?」——也就是你會想從 sidebar workspace 列右鍵 Copy dir 拿到的東西。兩個入口:
| 介面 | 方式 | 範圍 |
|---|---|---|
prefix+y → Copy space: dir |
herdr-plus Quick Action | 聚焦中的 workspace |
prefix+T → Alt+Y |
herdr-sesh tv channel |
清單裡的任一 workspace——嚴格來說比右鍵能給的還多 |
herdr 的 context menu 無法擴充。 它是編進 binary 的固定 enum——整份清單就是一段打包字串(space:Rename · Close · New worktree · Open worktree... · Close group · Expand · Delete worktree checkout...;pane:New tab · Rename pane · Split right · Split down · Close pane · Swap with focused pane · Clear pane name)。在 0.7.5 上從三個方向驗證過:config.toml 沒有任何 menu 表(只有 [[keys.command]] 與 ui.right_click_passthrough_modifier,後者管的是右鍵要不要傳給內層 app);plugin manifest 的 [[actions]] 帶有 contexts = ["workspace"],看起來像選單放置位置但其實是死的;而 herdr api schema --json 裡完全沒有 "menu" 這個字,所以執行期也無法注入。
上游狀態(herdr 升級時重新確認)。 被提過四次——#1511(使用者自訂選單項目)、#1671、#1776、#1830——全部以
NOT_PLANNED關閉,其中三個是被kangal-bot以「feature request 不該開在只收 bug 的 tracker」自動關掉的。#1776 裡明白寫著contexts「目前只會出現在plugin action list的 API 回應裡」。被導去的討論串(#1609、#1672、#1722)到現在都還開著、也沒有維護者回應。
space 目錄怎麼推導出來的——以及唯一的但書。 herdr 在任何地方都沒有暴露 workspace 層級的 cwd:herdr workspace get 與 herdr api snapshot 回傳的都是同一個只有 label / number / tab_count / pane_count 的物件。所以它是算出來的,由 ~/.config/herdr/space-root.sh 負責——這是 pane-copy.sh dir、prefix+C 開新 tab 的 helper,以及 tv channel 的 preview + Alt+Y 共用的單一真實來源:
- space 根目錄 = 該 workspace 現存最舊 tab 的 cwd。 tab 的
.number是單調遞增的建立計數器,不是顯示索引——一個 space 可能持有編號10/13/14/15但顯示為1/2/3/4的 tab——所以sort_by(.number)[0]代表「最舊」,這才是「這個 space 當初從哪個 tab 開始」的正確概念。 - 但書:sidebar 的 label 在建立時就固定了,之後不會重算,所以一旦你在那個最舊的 tab 裡
cd,label 與推導出的目錄就會分岔,而且 API 裡沒有任何東西能還原原始路徑。實測遇過:一個標示為2026-05-14-grafana-provisioning-with-docker-otel-lgtm的 space,其最舊 tab 其實位於…/grafana/dashboards/Jingle.AI。dir回報的是後者——真正的當前目錄,而不是那個過時的名字。
cwd 之所以存在,是因為兩者在日常使用中真的會分岔:實測有個根目錄為 2026-07-24-unify-ashare-sdk 的 space,底下的 pane 分散在三個毫不相干的 Documents/Program/* 樹裡。dir 是專案,cwd 是這個 pane 實際待著的地方。
從 pane 開啟 URL(prefix+u)¶
tmux prefix + u(joshmedeski/tmux-fzf-url)的 herdr 對應物。prefix+u 開一個 fzf 彈窗,列出聚焦 pane 裡的每個 URL;挑一個(或多個——fzf 多選),每個都在瀏覽器開啟。小寫 u 刻意與大寫 U(tv tools)配對,延續 tmux 的肌肉記憶。
由一支腳本驅動:~/.config/herdr/url-pick.sh = dot_config/herdr/executable_url-pick.sh。它讀取 pane(herdr pane read),跑與 tmux-fzf-url 相同的萃取/改寫規則,再用本 repo 跨平台的 x open(wslview / open / xdg-open)開啟每個選項。它與 pane-copy.sh 完全同構:pane 預設為 $HERDR_ACTIVE_PANE_ID(keybind 變數),為空時 fallback 到 herdr pane current;x 以絕對路徑 fallback 解析,因為 command-pane 可能在沒有互動式 PATH 的情況下執行。
| 面向 | 行為 |
|---|---|
| 範圍 | 預設可見螢幕(與 tmux-fzf-url 一致)。url-pick.sh <pane> --source recent 掃描完整保留的 scrollback |
| 樣式 | http(s) / ftp / file、裸 www. → http://、IPv4[:port] → http://、git@… SSH remote → https://…、引號 "owner/repo" → github.com/owner/repo、import "pkg" → npmjs.com/package/pkg |
| 開啟 | 每個選項 x open <url>;多選則全部開啟 |
| 無 URL | 印出 no URLs found 並暫停約 1.5 秒(command pane 在腳本退出的瞬間就關閉——tmux 是用狀態列) |
以 .chezmoitemplates/herdr/config.toml 裡的 [[keys.command]] type="pane" 綁定——不需要 tv channel(fzf 才是忠實移植)。Copy-mode 的 URL 開啟(tmux tmux-open 的 o)沒有 herdr 對應物;請用 prefix+u 選單。
從 pane 複製檔案路徑(prefix+p)¶
URL 選單的複製路徑姊妹版。prefix+p 開一個 fzf 彈窗,列出聚焦 pane 裡的檔案路徑;挑一個(或多個),路徑就複製到剪貼簿。它保留直達鍵,因為互動式 fzf 無法在 prefix+y Quick Actions 裡執行。tmux 上的對應物是 extrakto plugin(prefix + Tab)——見 tmux 快捷鍵。
輔助腳本:~/.config/herdr/path-pick.sh = dot_config/herdr/executable_path-pick.sh,與 url-pick.sh/pane-copy.sh 完全同構(相同的 pane + x 解析 + herdr pane read 管線),用 x copy 複製。
noise 問題與處理方式。 與 URL(用 scheme:// 自我標識)不同,檔案路徑沒有標記,所以單靠 regex 會誤中日期(2024/01/02)、速率(10k/s)與分數(1/2)。兩道防線:
| 防線 | 行為 |
|---|---|
| 萃取 | extrakto 的路徑啟發式——含斜線的 token(/…、~/…、./、a/b/c)+ 裸 file.ext;去掉尾端 ",):;速率/分數 token 直接排除 |
| 存在檢查(noise 殺手) | 每個候選對 pane cwd($HERDR_ACTIVE_PANE_CWD,否則 herdr pane get 的 foreground_cwd,否則 process-info cwd)解析後 test -e |
| 兩層清單 | 存在的路徑排在最前(複製為解析後的絕對路徑);其餘——遠端 / 過期 / 假設的——放在 ── unverified ── 分隔線下方(仍可選),所以真實但無法解析的路徑不會遺失 |
path:line:col 後綴(grep -n / stack trace / 編譯器輸出)在存在檢查前先剝除,所以 pkg.py:42:5 會以 pkg.py 驗證。範圍預設為可見螢幕(--source recent 掃 scrollback);多選以換行接合複製;無結果時印出 no file paths found 並暫停約 1.5 秒。
翻譯:互動式 TUI 與 pane 擷取¶
prefix+t 在 80% × 80% popup 中直接開啟 translate 互動式 TUI,供你輸入或貼上待翻譯文字;這個快捷鍵不會擷取焦點 pane。
要翻譯 pane,請用 prefix+y → Translate pane。Helper 把焦點 pane 的內容送進 translate CLI 的雙語模式,結果顯示在新 pane 裡:原文逐行保留,每個區塊的譯文以 ↳ … 交錯在下方。tmux 沒有對應物——這是 herdr 專屬的,建立在 herdr pane read 之上。
Helper:~/.config/herdr/pane-translate.sh = dot_config/herdr/executable_pane-translate.sh,是 pane-copy.sh / url-pick.sh / path-pick.sh 的兄弟(相同的 pane 解析與 herdr pane read 管線)。
| 入口 | 範圍 | 結果去哪裡 |
|---|---|---|
prefix + t |
你輸入或貼上的文字 | popup 裡的互動式 TUI |
prefix + y → Translate pane |
select 清單:當前頁 / 最近 200 / 500 / 1000 行 | 往右分割出的 pane,並自動 focus |
prefix + y → Translate pane into… |
當前頁 | 同上,目標語言由你輸入 |
prefix + y → Translate pane: copy |
當前頁 | 剪貼簿(x copy)+一則通知 |
要取多少 scrollback——以及為什麼「當前頁」才是誠實的預設¶
最直覺的擔心是:固定視窗會切在句子中間,而 coding agent 的 plan 可能超過一整頁。三個事實決定了答案:
- 跑在 alternate screen 上的 agent pane 根本沒有 scrollback。 Claude Code 的 pane 回報
scroll.max_offset_from_bottom: 0,而herdr pane read --source recent --lines 1000剛好只回傳viewport_rows行——與--source visible逐字相同。離開 alternate screen 的行永遠不會進入 herdr 的 host scrollback,所以再大的--lines也救不回來。(隔壁分頁的 shell 或codexpane 則能回報數千行:這是逐應用程式的差異,不是全域限制。)也就是說,對於促成這個功能的情境,一頁就是全部——而且因為--source visible呈現的是你在 app 內部捲到的位置,「當前頁」是精確的,不是猜的。在 agent 裡捲動,再執行一次 pane 翻譯的 Quick Action 即可。 herdr pane read上限是 1000 行,而且沒有 offset/分頁參數,所以recent:1000是硬天花板——與pane-copy.sh記錄的限制相同。- 1000 行大約是 62 KB。
translate的成本大致是 10 KB ≈ 60 秒、20 KB ≈ 95 秒,而 provider 在遠低於 62 KB 時就會拒絕。因此真正的上限是字元預算(HERDR_TRANSLATE_MAX_CHARS,預設12000≈ 75 秒),不是行數。recent:N只是挑一個粗略視窗,再由預算裁切——每次裁切都會寫在 pane 標頭上,絕不無聲進行。
不切在內容中間¶
分兩個層次處理,而且第二個更重要:
| 機制 | 作用 |
|---|---|
上緣邊界對齊(僅 recent:N) |
掃描前 clamp(15%, 5, 40) 行找區塊邊界——空行、agent turn 標記(●⏺⎿•✻>❯$#)、水平分隔線,或看起來像標題的行——從那裡開始,並在前面加上 [… earlier output omitted …]。若在 margin 內找不到邊界,就完整保留並加上 [… continued from earlier output …]。visible 擷取永遠不會從上緣裁切:那正是你正在看的畫面,最多只會加標記。 |
--instructions |
擷取內容會附帶一段 system prompt,告訴模型這是終端機節錄、可能從句中開始或結束、必須逐字翻譯眼前的內容而不要自行補完,並且指令 / 路徑 / 旗標 / 識別字 / 程式碼 / JSON 一律原樣保留。LLM 翻譯器不需要乾淨的邊界,它需要的是「被告知邊界不乾淨」。 |
recent:N 使用 --source recent-unwrapped,會把 soft wrap 接回去,所以長行不會以斷在字中間的形式送進翻譯。
翻譯前的清理¶
三個階段,全部有邊界限制,不會吃掉正文:
- 只清底部裝飾。 從最後一行往上走,遇到第一行真正的內容就停:輸入框(
╭…╰)、footer 提示(? for shortcuts、⏵⏵ bypass permissions、-- INSERT --、單獨的❯)、狀態列(多個·/│分隔欄位)與 spinner 行。自訂狀態列是一整個區塊,改用錨點裁切:底部附近的整寬分隔線,且其下方有可辨識的裝飾行。出現在 transcript 中段的分隔線不受影響。 - 眾數 dedent。 這一步是關鍵:
translate的 bitext 只要區塊縮排 ≥ base + 2 就判定為 code,而 agent pane 會把正文整體推到一個固定左邊界。base 取的是眾數縮排而非最小值——transcript 會把 turn 標記放在第 0 欄、正文放在第 5 欄,用min()等於完全沒 dedent,整頁都會原封不動地回來。眾數縮排是這個 pane 的主要文字欄位,所以正文會落到 0,而真正巢狀的程式碼區塊保留相對縮排、仍然被判為 code。 - 折疊的 transcript 標記(
… +194 lines (ctrl + t to view transcript))換成單一[…];連續空行收斂;tab 展開。
不花任何 LLM 呼叫就能檢查上述行為:
它會把清理、修補後的擷取內容印到 stdout,並把 raw_lines=… out_chars=… trimmed=… 印到 stderr。
互動式 popup 與 pane 翻譯 Quick Actions¶
和 prefix + E 是同一個分工:Quick Actions 透過 sh -c 執行、沒有 PTY/stdin,因此無法自己承載 pager——它們改成先擷取,再 herdr pane split 開一個真正的 pane 並在裡面重新執行 helper(pane split 沒有 --focus 參數,所以由共用的 focus-pane.py 負責 focus)。prefix+t 的 popup 直接提供互動式翻譯器需要的 PTY。Pane 翻譯 Quick Actions 則一律在分割之前完成擷取,避免縮小來源 pane 時重新換行而改變擷取內容。
用小寫 t,因為 prefix + T 已經是 herdr-sesh session 選擇器。
Pane 翻譯 helper 的環境變數(不適用於直接開啟的 prefix+t TUI):HERDR_TRANSLATE_MAX_CHARS(12000)、HERDR_TRANSLATE_TO(預設目標語言;未設定時由 translate 自己的 [general] 設定決定)、HERDR_RUN_HOLD(fail|always|never,與 prefix+E 相同)、PAGER。
AI 用量 / 額度狀態¶
herdr 沒有原生的用量/額度/token 顯示(側欄只顯示 agent 狀態)。它確實有一個逐 pane 的 hook——herdr pane report-metadata <pane> --source ID --token usage="…" --ttl-ms N——driver 可以把 "Claude 62% • Codex 78%" 之類的標籤推進去(與上方 待 review 旗標 用的是同一個 hook,但 herdr 0.7.4 起 token 是帶命名空間的 map,所以 usage token 與 review token 可以並存,不再互搶單一欄位)。有一個 Codex-only 的社群 plugin(jerryfane/herdr-codex-usage-kit)已從 CodexBar 讀的同一份 ~/.codex 資料做到這件事;但沒有東西涵蓋 Claude/ChatGPT 額度。延後——CodexBar 的選單列仍是多供應商的檢視。設計與選項記在 backlog/herdr-usage-status-driver.md。
缺口(沒有乾淨的 herdr 對應)¶
- 無縫
Ctrl-hjklnvim↔pane 導覽。vim-tmux-navigator與 tmux 耦合(is_vim的ps/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-mode(
cpout/cpblock、prompt 跳轉、最後輸出 yank)是 tmux 專屬。herdr 的 copy mode(prefix+[)是 vi 風格但沒有 OSC133 的 prompt 邊界感知。cpcmd(zsh history,與多工器無關)仍可用。 - 裝飾用的狀態列符號(📌/🔖 當作自由浮動的視窗標籤):herdr 沒有
#{@option}的 format-string 插值,所以 tmux 風格的狀態列書籤不能移植。(但「mark-unread / 待 review ⭐」這個具體用途已解決——見上方 待 review 旗標 一節——靠逐 pane 的 metadata token;只剩純裝飾的狀態列符號仍是缺口。) - 逐鍵 pane 縮放。 tmux 把
prefix+H/J/K/L(與M-hjkl微調)直接綁成縮放;herdr 沒有逐鍵縮放——它用模態的resize_mode(prefix+r)再按h/j/k/l。不是精確對等,但是相近的對應物。
不是缺口: vi copy-mode 本身是原生(
prefix+[),且逐 pane 的 agent 狀態是原生偵測——我原本以為會缺的兩件事,結果都內建了。
延伸閱讀 (See also)¶
群組 SSH 設定與 dev 整合¶
新機採 create-only 的 SSH 入口,明列 config.d/git/github.conf、
config.d/git/gitlab.conf 與 config.d/common/defaults.conf,選項縮排四格。
優先順序由 Include 清單決定。種子不依賴 dev 已安裝;既有 flat/wildcard
設定保留舊種子,不會自動遷移。新群組種子依入口標記 gating,避免更新時
意外啟用新定義。搬移種子且移除原本精確 Include 後,apply 不會重建舊檔。
使用包含 SSH 管理功能的 dev build 時,可執行 dev ssh format 預覽縮排,
或明確選擇 dev ssh organize 搬移完整 Host 區塊及註解到群組。檢查計畫後
加 --apply 才套用,dev ssh restore <receipt> 預覽復原;新的 dev 寫入
使用 macOS/Linux 安全 backend。dev ssh manage 另外選擇 fleet/Herdr
註冊及原生 profile 動作;安裝 dotfiles 本身不註冊主機或啟動遠端 server。
Television SSH channel 優先使用 dev ssh list --format tsv 的 active aliases;
dev 不可用或版本尚無 machine-management command 時,退回 flat/群組檔案的顯示型掃描。共用 SSH setup helper 追蹤
巢狀 Include,為新選定檔案加入精確 Include,不會順便啟用相鄰的休眠檔案。
原有以目錄為單位的 helper 子命令仍保留供既有呼叫者使用。
Herdr UI(原生 GUI client)¶
Herdr UI 是既有 Herdr daemon 的獨立圖形介面。 TUI 與 GUI 可共用 session,關閉 GUI 不會結束終端 session。GUI 安裝器不會安裝或 升級 Herdr daemon。GUI 偏好設定由應用程式管理;apply 不啟動應用程式,也不開啟登入自啟。
- macOS:隨
installBrewApps安裝 Homebrew caskpenso/tap/herdr-gpui; 支援 Apple Silicon/Intel,需要 macOS 14.2+,舊版直接跳過。以open -a Herdr或herdr-gpui啟動。 - Ubuntu/Debian 桌面:需要
profile=ubuntu_desktop與installBrewApps=true、 glibc 2.39+(Ubuntu 24.04/Debian 13 起)、x86_64/ARM64 及 sudo。 Ansible 下載最新 stable.deb,核對上游 SHA-256 後交給 apt 安裝相依套件。 noRoot、舊 libc、不支援架構會說明並跳過。GPU 需有 Vulkan driver;從應用程式選單 或herdr-gpui啟動。 - apply 僅補裝,保留 PATH 上既有安裝。macOS 透過 Homebrew 更新
(
brew upgrade --cask penso/tap/herdr-gpui);Linux 使用 App Updates (需要pkexec與桌面 PolicyKit 認證代理),也可手動執行 官方安裝器 更新。 - Fedora/Arch/Nix 請依官方安裝指南 手動安裝;無桌面主機保留 daemon,由桌面 GUI 透過 SSH 連線。
just upgrade-herdr仍只負責 daemon 升級。