Files
herdr-session-switch/README.md
T
2026-09-18 14:32:58 +00:00

16 KiB
Raw Blame History

herdr-hub

herdr-hub 是一个 Linux 下的 Herdr session 切换器。它让 tmux 做最外层路由, 把每个本地或 herdr --remote attach 放进独立 tmux window,并在 Herdr 画面顶部 显示可点击的 Sessions 条。

它不是 Herdr 插件,也不会修改 Herdr core。Herdr 0.8.2 的插件 API 不能向原生 sidebar 插入 UI,也不能让 thin client 从一个 server 原地改连另一个 server; 所以顶部状态栏横跨整个终端,而不是只嵌在 Spaces 上方。

功能

  • 自动读取 herdr session list --json,导入本机正在运行的 sessions。
  • 记住使用过的远端,并通过 SSH 实时读取运行中及已停止的 Herdr sessions。
  • 远端查询兼容 Windows OpenSSH 与 Linux/macOS shell;Windows target 自动改用并持久记住 SSH TTY 模式。
  • 在 Linux /proc/*/cmdline 中查找仍在运行的 herdr --remote … clients,并自动记住目标。
  • 首次接管已有 remote client 时安全地再 attach,不结束原进程。
  • 一个 endpoint 对应一个 tmux window,隐藏 window 中的连接继续在线。
  • 顶部 Sessions 条在窄屏下也为每个 Tab 显示主机优先的短名称,并支持鼠标点击、数字切换和无需 fzf 的动态 popup 选择。
  • 可隐藏不再需要的 Tab,而不停止底层本地或远端 Herdr Session。
  • 可在 Sessions 管理弹窗中为 Tab 设置持久化显示名称,或恢复默认名称。
  • dead window 留在顶栏,可原地重连。
  • 不修改 ~/.tmux.conf,不保存 SSH 凭据或远端 Session 缓存。

依赖

  • Linux
  • Python 3.11+
  • tmux 3.4+
  • Herdr 0.9+(本地和需要 Fork 的远端目标均需支持 0.9 的控制 API)
  • OpenSSH client

检查当前环境:

./bin/herdr-hub doctor

安装

直接从仓库运行:

./bin/herdr-hub

或把启动器链接到 ~/.local/bin:

make install
herdr-hub doctor

安装脚本只创建一个指向本仓库的符号链接。移动或删除仓库后需重新安装。

第一次接管

如果本地和 remote Herdr clients 已经分别开在其他终端中:

  1. 保持这些 clients 运行。
  2. 从一个新的、未嵌套在 tmux 中的终端执行 herdr-hub。
  3. Hub 扫描现有 clients,并为每个 endpoint 创建一个新的 attach window。
  4. 确认 Hub 中可以正常操作后,手动退出原来的 clients。

tmux 不能把另一个终端里已经运行的进程搬进 window,所以首次接管会短暂存在两个 clients;它们连接的是同一个 Herdr server,不会复制 server 或 panes。

只有一个终端时,本地 Herdr 可以先 detach,因为 herdr session list 仍能发现后台 server。remote target 在 client 退出后没有本地记录,因此直接使用:

herdr-hub open --remote orangepi
herdr-hub open --remote orangepi --session work
herdr-hub open --remote ssh://me@server:2222 --remote-keybindings server
herdr-hub open --remote windows-host --transport ssh

打开本地 named session:

herdr-hub open --session work

open 会把 endpoint 保存在当前 tmux hub 的 window metadata 中。远端 target 还会 同时加入 ~/.config/herdr-hub/remotes.json,以后可直接从 Sessions 弹窗浏览这台机器上的其他 Session。

日常操作

Hub 使用独立 tmux socket herdr-hub,不会接管普通 tmux server。外层前缀固定为 Ctrl+A,Herdr 原来的 Ctrl+B 可以直接使用。

操作 快捷键 / 命令
打开快捷键帮助 Ctrl+A ?
点击切换 鼠标点击顶部 session
打开 Herdr 命令菜单 Ctrl+A h
打开 Session 弹窗 点击左上角 Sessions 或 Ctrl+A g
上一个/下一个 window Ctrl+A p / Ctrl+A n
按编号切换 Ctrl+A 0..9
扫描新增 sessions Ctrl+A r 或 herdr-hub refresh
强制重连当前 window Ctrl+A R
关闭/隐藏当前 Tab Ctrl+A q,确认 y;也可在 Sessions 弹窗中选择
关闭并停止远端 Session Sessions 弹窗 → 选中 Session → Space / [⋯] → 关闭并停止 Session
修改 Tab 显示名称 Sessions 弹窗 → 选中 Session → Space / [⋯] → 修改显示名称
detach Hub Ctrl+A d
重新进入 Hub herdr-hub
停止 Hub clients herdr-hub stop

Fork 当前 Agent 到新 Tab

按 Ctrl+A h 打开独立的 Herdr 命令菜单,选择“Fork 当前会话到新 Tab”, 按 Enter 或单击执行,Esc 关闭。菜单只显示当前 Agent、Workspace、主操作和必要的状态信息。 操作正在执行时需等待结果后再关闭,以免将关闭弹窗误认为取消已经发送的请求。 Sessions 弹窗仍使用 Ctrl+A g;两个菜单都使用 Hub 外层的 Ctrl+A 前缀。 快捷键集中定义在 herdr_hub/control.py 的 CONTROL_KEY 常量中。

Fork 复制的是 Codex / Claude conversation,并保留正在运行的原 Agent 和原 Tab。 它在同一个 Herdr Workspace、相同 cwd 新建 Tab,使用当前 pane 已报告的 conversation ID: Codex 执行 codex fork UUID;Claude 执行 claude --resume SESSION_ID --fork-session。 新 Agent 被 Herdr 确认为就绪后才切换焦点。它不会新建外层 Herdr named session, 也不会创建 Git worktree;两个 Agent 会编辑同一工作目录,需自行协调并发修改。

支持本地 default / named session,以及通过 SSH 控制的远端 native / Windows SSH endpoint。 控制目标取自触发菜单的 Hub window metadata,不使用 popup 继承的 Herdr socket 或 pane 环境变量。 目标必须运行 Herdr 0.9+,并安装对应 Agent;当前 pane 必须已报告可用的 Agent session ID。 远端控制使用非交互 SSH(需要已信任的主机密钥和可用认证);Windows 需要 PowerShell。 启动 blocked、失败、超时或响应不完整时,菜单保留错误信息,不会自动关闭任何 Tab。 超时并不代表远端没有创建或启动成功,请先检查提示中的新 Tab,再重新打开菜单重试。

CLI 入口是 herdr-hub control。弹窗打开时会锁定 Hub 当前激活的 managed window, 再从其 Endpoint 对应的 Herdr server 读取当前页面的 Agent conversation。 更新后运行 herdr-hub refresh 配置现有 Hub 的快捷键。herdr-hub doctor 会显示入口和版本要求。

远端 Session 弹窗

弹窗由 tmux display-popup 和 Hub 内置选择器组成,不依赖 fzf。它会立即显示已打开的 window,同时在后台并行查询所有已登记远端;每台机器的结果就绪后原地更新。

主页面只显示 Session 和两个全局入口。大多数主机只有 default Session,因此默认只显示 主机名;有命名 Session 时显示为 主机 / Session。本机同样显示为“本机”或 “本机 / Session”。状态与主操作如下:

状态 选择后的行为
▶ 当前 / ● 已打开 跳转到已有 window
× 已断线 自动重连并跳转
○ 远端运行 新建 attach window 并跳转
■ 远端已停止 Enter/单击启动并打开
↻ Windows 旧窗口 原地切换为 SSH TTY 模式并重连

Enter 或单击一行始终执行它的主操作。管理操作统一放在上下文菜单中:键盘选中一行后按 Space,或在手机端点击行尾 [⋯];右上角 [操作] 也会打开当前行的菜单。菜单会按 Session 当前状态提供修改显示名称、关闭 Hub Tab、关闭并停止 Session、永久删除已停止的 命名 Session、在此远端新建 Session、重试连接和移除远端记录等可用操作。Esc 逐层返回; 危险操作会二次确认并默认选中“取消”。关闭 Hub Tab 只结束该 Tab 中的 client,底层 Session 和工作现场保持运行;“关闭并停止”则会中断远端任务。若它是 Hub 中最后一个 Tab, 该 Tab 会保留为断线状态,避免销毁承载弹窗的最后一个 window。

主页面只有一个“新建 Session”入口,选择远端后再输入 Session 名;“添加远端”会先让你 选择“自动检测(Linux/macOS)”或“Windows/SSH”。修改显示名称支持最多 48 个字符,留空 保存可恢复默认名称,且不会修改底层 Session ID。移除远端记录只删除本机记忆,不关闭已有 Tab,也不停止远端 Herdr server。

搜索必须按 / 显式进入,因此普通字母不会误触命令;搜索页输入文字即可筛选,Esc 清空并 返回。键盘还支持上下键、Page Up/Down、Enter、Ctrl+R 刷新和 Esc 返回/关闭。窄屏会优先 显示主机和 Session 名,状态详情可在 [⋯] 菜单中查看。

永久删除只会出现在已停止的命名 Session 的菜单中,无论是否仍有关联 Hub Tab 都可执行。 确认后会在后台通过 SSH 执行 herdr session delete,永久删除工作现场和历史数据;关联 Tab 也会一并关闭。运行中的 Session 需先“关闭并停止”,Herdr 永久保留的 default Session 不能删除。

Herdr 0.8.2 不支持 Windows 作为原生 herdr --remote 的目标主机。Hub 查询到 Windows Session 后会标记为“Windows · SSH 模式”,并自动改为 ssh -tt target herdr;命名 Session 会在远端运行 herdr --session NAME。这种模式仍支持 Tab 切换、detach、断线 重连和远端工作现场持久化,但使用远端 Herdr 的 keybindings,也没有原生 thin client 提供的本地图片剪贴板桥接。空 Windows 主机或平台检测失败时,可在“添加远端”中选择 “Windows / SSH”,也可手动执行 herdr-hub open --remote TARGET --transport ssh。

rootless 容器或 UID 映射环境可能把 /etc/ssh/ssh_config 显示为 nobody 所有,导致原生 herdr --remote 报 Bad owner or permissions,即使系统 ssh 本身仍可使用。不要放宽该 文件的写权限;可在本机 ~/.config/herdr/config.toml 中让 Herdr 直接使用系统 SSH:

[remote]
manage_ssh_config = false

这会停用 Herdr 自动生成的 keepalive 和连接复用配置,但不会禁用用户自己的 SSH 配置。 执行 herdr config check 验证后重新打开 Session 即可。若 Session 进程启动后立即退出, Sessions 弹窗会保留在该 Session 的操作页,显示 pane 返回的错误并允许直接重试。

也可通过 CLI 管理远端:

herdr-hub remote list
herdr-hub remote add orangepi
herdr-hub remote add ssh://me@server:2222 --remote-keybindings server
herdr-hub remote add windows-host --transport ssh
herdr-hub remote remove orangepi

注册表默认位于 ~/.config/herdr-hub/remotes.json(遵循 $XDG_CONFIG_HOME),权限为 0600。它只保存 SSH target、remote keybinding 模式和首选 transport,不保存凭据。

Session 显示名称

Herdr 0.8.2 没有底层 Session rename 命令,因此 Hub 的“重命名”是安全的显示名称: 顶部 Tab 和 Sessions 弹窗显示新名称,attach、重连和工作现场仍绑定原来的 Session ID。 名称会跨终端断线、Hub detach 和 herdr-hub stop 保留,并在重新打开 endpoint 时自动 应用。配置保存在 ~/.config/herdr-hub/aliases.json(遵循 $XDG_CONFIG_HOME), 权限为 0600;留空保存会删除该 endpoint 的显示名称并恢复 local/work、 host/work 等默认格式。

复制与剪贴板

Hub 启用 tmux 的 OSC 52 剪贴板透传。Herdr 触发的自动复制,以及 tmux 接管后的 鼠标拖选,都会尝试写入当前 Hub 访问端的本地剪贴板。远程 session 中复制的 文本也回到访问端,不是远程机器的剪贴板。

访问端的终端需要支持并允许 OSC 52。如果终端或 Web terminal 禁止了这项能力, 可按住 Shift 再拖选,绕过 tmux 并使用终端原生复制。

Termux / 手机端复制

Termux 的原生长按选区只认识整个终端画面,不知道 Herdr pane 和左侧 sidebar 的边界。 跨行拖选时,中间行因此可能混入 sidebar 文本。优先使用 Herdr 自带的 Copy Mode:按 Ctrl+B [ 进入,用方向键或 h/j/k/l 移动,按 v 或空格开始选择,再按 y 或 Enter 复制。它只读取当前 pane,不会包含 sidebar;复制大段历史输出也可按 Ctrl+B e 在编辑器 中打开当前 pane 的 scrollback。这里的 Ctrl+B 是 Herdr 前缀,Hub 外层 tmux 前缀仍为 Ctrl+A。

如果更习惯 Termux 长按拖选,可在 ~/.config/herdr/config.toml 已有的 [ui] 段中加入:

[ui]
pane_scrollbars = false
sidebar_collapsed_mode = "hidden"
# sidebar_start_collapsed = true  # 可选:下次启动时默认收起

不要重复创建 [ui] 段。执行 herdr config check 和 herdr server reload-config 后,按 Ctrl+B b 可在完整 sidebar 和零宽度隐藏之间切换;隐藏后再使用 Termux 原生选区即可。 pane_scrollbars = false 还会移除 pane 右侧滚动条并腾出一列空间。

当访问端宽度小于 80 列时,Hub 会自动进入窄屏模式: 所有 Tab 都显示状态和最多 12 列的短名称,并优先显示主机:默认 Session 的本地与 远端分别显示 local、host,非默认 Session 才显示 local/work、host/work。 顶部右侧只保留 C-a ? 帮助;宽度小于 50 列时缩成 C-a ?。左上角标题也会缩成 S,继续给 Tab 留空间。点击 Sessions/S 可打开占 90% 屏幕的完整选择器。

refresh 是 add-only:它添加新发现的 endpoint,不自动删除已有 window,也不会改变 当前焦点。通过 Ctrl+A q 或弹窗关闭的 endpoint 会在当前 Hub 生命周期内记为已隐藏, 刷新和断开终端后重新进入都不会让它自动回来。要恢复它,打开 Sessions 弹窗并重新选择; 也可以执行对应的 herdr-hub open 命令。远端通过 SSH 目录重新选择,本地隐藏的运行中 Session 会列在“本机”下。Hub 至少保留一个 Tab;要全部退出请使用 herdr-hub stop。

当 endpoint 连接进程退出时,window 会保留:顶栏将它标记为 × [断开], 当前 pane 底部会显示退出状态和 Ctrl+A R 重连提示。这与正常 detach Hub 不同;仅关闭外层终端后,直接再次执行 herdr-hub 即可。

herdr-hub stop 只结束 tmux 中的 client processes。本地及远端 Herdr servers 和 它们的 panes 会继续运行。

CLI

herdr-hub
herdr-hub start
herdr-hub refresh
herdr-hub open [--session NAME]
herdr-hub open --remote TARGET [--session NAME]
               [--remote-keybindings local|server] [--transport native|ssh]
herdr-hub choose
herdr-hub remote list
herdr-hub remote add TARGET [--remote-keybindings local|server]
                            [--transport native|ssh]
herdr-hub remote remove TARGET
herdr-hub reconnect [--window ID]
herdr-hub close [--window ID]
herdr-hub doctor
herdr-hub stop

自动发现与远端查询的边界

零配置的首次发现只覆盖:

  • 当前用户正在运行的本地 Herdr servers;
  • 当前用户、同一 PID namespace 中仍然存活的 herdr --remote processes。

发现或手动添加一次后,remote target 会被记住,不再要求本地 client 持续存活。弹窗先 通过非交互 SSH 执行跨平台的 herdr session list --json;Linux/macOS 的非交互 PATH 找不到 Herdr 时,再自动尝试 $HOME/.local/bin/herdr。Windows OpenSSH 返回的本地代码页 错误文本也会被容错解码。SSH key/passphrase 应已由 ssh-agent 处理;未知 host key、需要 密码、远端不可达或命令失败时,该主机显示“查询失败 · 单击重试”,不会关闭或卡住整个 弹窗。首次安装或 host-key 确认仍可在实际新建的 Herdr window 中交互完成。

首次使用且尚未登记时,以下情况仍无法自动找回 remote target:

  • remote client 已经退出,但远端 server 仍在后台运行;
  • client 位于另一个 container/PID namespace;
  • /proc 使用了阻止读取同用户 argv 的安全配置;
  • 可执行文件不叫 herdr。

这些场景可在弹窗中选择“添加远端”,或使用 herdr-hub open --remote …。为了避免嵌套 tmux 与前缀冲突, herdr-hub 应从普通终端启动;若当前已经在另一个 tmux server 中,它会拒绝再次 attach。

开发与测试

make test

测试使用伪造的 /proc 和 Herdr executable;tmux 集成测试使用唯一 socket 和 /dev/null 配置,不接触真实 Herdr sessions 或现有 tmux server。