cli-anything-iterm2 的 tmux -CC 自动化实战:让每个 tmux 窗口变身原生 iTerm2 Tab
【免费下载链接】CLI-Anything"CLI-Anything: Making ALL Software Agent-Native" -- CLI-Hub: https://clianything.cc/项目地址: https://gitcode.com/GitHub_Trending/cl/CLI-Anything
tmux -CC 控制模式会把 tmux 的每个窗口(window)渲染成 iTerm2 里一个完整的原生标签页(tab),让原本基于纯文本的 tmux 会话变成可见、可读、可控的图形化工作区。本文以cli-anything-iterm2的 tmux 命令组为核心,讲解从"引导连接"到"枚举窗格、读取内容、发送命令、调整布局"的完整 Agent 工作流,并深入核心源码揭示其底层实现原理。读完本文,你将掌握一套可脚本化、可被 LLM/Agent 直接驱动 iTerm2 内 tmux 会话的实战方案。
tmux -CC 模式:iTerm2 与 tmux 的桥接基础
iTerm2 对 tmux 提供了两层语义完全不同的支持方式:普通模式下,你只是在终端里手动运行tmux;而在Control Mode(tmux -CC)下,iTerm2 会主动与 tmux 服务器建立控制协议连接,将 tmux 的窗口树镜像为 iTerm2 的原生 UI 元素——每个 tmux 窗口对应一个 iTerm2 Tab,窗口里的每个 pane 对应 Tab 中的一个 split pane。
tmux-guide.md 开头对这一机制给出的描述非常精炼:tmux -CCrenders each tmux window as a native iTerm2 tab — fully visible, readable, and controllable。这正是整个自动化方案的基石:由于 tmux 窗口最终以 iTerm2 Tab 呈现,Agent 就可以绕过 tmux 纯文本输出的限制,利用 iTerm2 原生的会话读取能力(可见屏幕、回滚缓冲)去"看"每个 pane 的实时状态。
在进入命令操作之前,需要确认以下前置条件(对应 SKILL.md 中的 Prerequisites):
- 运行环境为 macOS,并已安装 iTerm2(
brew install --cask iterm2); - 在 iTerm2 → Preferences → General → Magic 中开启Python API;
- 安装 Python 包:
pip install cli-anything-iterm2(仓库源码方式可pip install -e .); - 目标机器上已安装 tmux,且存在一个以
tmux -CC启动的会话。
完整工作流总览
tmux-guide.md 给出了五个步骤的完整脚本骨架,这是整篇文章的"实战主干":
# 1. Set context to the target session BEFORE bootstrapping — otherwise bootstrap times out cli-anything-iterm2 app set-context --session-id <id> # 2. Bootstrap cli-anything-iterm2 --json tmux bootstrap # 2. Enumerate cli-anything-iterm2 --json tmux send "list-sessions" cli-anything-iterm2 --json tmux send "list-panes -a -F '#{session_name}:#{window_index}:#{pane_index} #{pane_current_command} #{pane_current_path}'" cli-anything-iterm2 --json tmux tabs # maps tmux windows → iTerm2 tab IDs # 3. Read any pane cli-anything-iterm2 --json session screen --session-id <pane-session-id> cli-anything-iterm2 --json session scrollback --session-id <pane-session-id> --tail 500 --strip # 4. Send to any pane cli-anything-iterm2 session send "git log --oneline -10" --session-id <pane-session-id> # 5. Manage layout cli-anything-iterm2 tmux send "new-window -n logs" cli-anything-iterm2 tmux send "split-window -h -t logs" cli-anything-iterm2 tmux send "select-layout -t logs even-horizontal" cli-anything-iterm2 tmux create-window --use-as-context注意原文档第 6–11 行存在一个编号笔误(出现了两个 "2."),正确的阶段划分是:设置上下文 → 引导连接 → 枚举窗格 → 读取/写入 pane → 管理布局。下面逐一展开。
第一步:先设上下文,再 Bootstrap(否则会超时)
工作流中最容易被忽略、却最影响成功率的一点是:必须先执行app set-context --session-id <id>把目标会话设成上下文,再去执行tmux bootstrap。原因在于 bootstrap 的实现逻辑:
在 core/tmux.py 的bootstrap()源码中,当未显式传入session_id时,它会回退到"当前窗口的第一个会话"(app.windows[0].current_tab.current_session);而引导的动作是向该会话发送一行tmux -CC(或tmux -CC attach)文本,随后以 0.5 秒为间隔轮询iterm2.async_get_tmux_connections(),直到出现新的连接或超过默认 15 秒超时。因此:
- 如果 Agent 所处的"当前会话"并不是真正要跑 tmux 的目标 shell,
tmux -CC可能被发错地方,或因目标 shell 处于异常状态而导致 15 秒内看不到新连接,最终抛出Timed out after 15s waiting for tmux -CC connection; - 提前用
set-context把目标会话 ID 固定下来,bootstrap 内部async_find_session()就能精确定位会话。
bootstrap 成功后返回的 JSON 里带有command(tmux -CC或tmux -CC attach)与elapsed_seconds,可供 Agent 判断连接建立耗时。更精细的控制参数还包括:
| 参数 | 作用 | 默认值 |
|---|---|---|
--attach | 发送tmux -CC attach以附加到既有会话,而非新建会话 | 不附加 |
--session-id <id> | 指定在哪个 iTerm2 会话中执行引导命令 | 当前窗口首个会话 |
--timeout <秒> | 等待 tmux 连接出现的轮询超时 | 15 |
相关 JSON 返回结构可参考 json-tmux-app.md:
{"connection_id": "user@host", "owning_session_id": "...", "command": "tmux -CC", "elapsed_seconds": 0.5}如果本机还没有任何 tmux 控制会话,可先用cli-anything-iterm2 tmux list检查;当连接列表为空时,工具会提示:No active tmux connections. Start one with: tmux -CC。
枚举阶段:弄清"tmux 世界里有什么"
引导连接建立后,第一步永远是枚举拓扑,即搞清楚 tmux 服务器上存在哪些会话、窗口、窗格。此时需要区分两种"查看视角":
tmux 协议命令(tmux send)
cli-anything-iterm2 tmux send "<command>"会把命令发送给tmux 服务器(而非某个 pane 的 shell),语义等同于你在 tmux 内按前缀键后输入命令。文档给出的三条核心枚举命令:
cli-anything-iterm2 --json tmux send "list-sessions" cli-anything-iterm2 --json tmux send "list-panes -a -F '#{session_name}:#{window_index}:#{pane_index} #{pane_current_command} #{pane_current_path}'" cli-anything-iterm2 --json tmux tabs其中第二条用 tmux 的-F格式化输出拼接了「会话名:窗口号:窗格号 + 前台命令 + 当前路径」,一条命令就能拿到定位 pane 所需的全部坐标信息,是 Agent 导航 tmux 拓扑的核心手段。底层上它对应 core/tmux.py 的send_command(),通过TmuxConnection.async_send_command()执行并原样返回输出:
{"connection_id": "user@host", "command": "list-sessions", "output": "0: 3 windows (created ...) (attached)"}tmux-commands.md 还补充了更多tmux send可用于枚举/管理的命令:list-windows -a、rename-session dev、new-window -n work、split-window -h、select-pane -t 0等。
iTerm2 侧映射(tmux tabs)
cli-anything-iterm2 tmux tabs与tmux send不同,它不经过 tmux 协议,而是直接调用 iTerm2 Python API 遍历 App 的窗口与标签页,只返回那些带有tmux_window_id(非空)的 Tab——即真正由 tmux 窗口镜像而来的 Tab。其实现位于 core/tmux.py,返回结构如下:
{"tmux_tabs": [{"tab_id": "47", "window_id": "pty-...", "tmux_window_id": "0", "tmux_connection_id": "user@host", "session_count": 1}]}tmux_window_id字段正是后续set-visible @1 off|on(隐藏/显示某个 tmux 窗口对应 Tab)所需的@N形式 ID,见set_window_visible()(core/tmux.py)中对tab.tmux_window_id(形如@1)的取用。
两种视角的取舍
关键区别(tmux-commands.md 末尾的 Key distinction 一句话点透):
tmux send= tmux协议命令,作用于 tmux 服务器(枚举会话、建窗、布局);session send= 向某个具体 pane 的shell 发送文本(跑命令、写输入)。
两者配合使用,才能覆盖"操控 tmux 拓扑 + 操控 pane 内部 shell"的完整闭环。
读取任意 pane:把 tmux pane 当作可读终端
tmux -CC 的价值在于:一旦 tmux 窗口被渲染为 iTerm2 Tab,每个 pane 就成了 iTerm2 的 session,于是可以复用session组的全部读取能力(详见 session-io.md):
# 读取可见屏幕(仅当前可视区域) cli-anything-iterm2 --json session screen --session-id <pane-session-id> cli-anything-iterm2 --json session screen --lines 20 # 读取回滚历史(整个 scrollback,原子性返回,旧→新) cli-anything-iterm2 --json session scrollback --session-id <pane-session-id> cli-anything-iterm2 --json session scrollback --session-id <pane-session-id> --tail 500 --strip工作流中推荐的--tail 500 --strip组合值得解释:--tail 500只取最后 500 行,避免在海量历史前浪费 token;--strip会剔除输出中的\x00空字节(屏幕/回滚原始数据中常见),保证文本可被 LLM 干净地消费。文档特别提醒:读取session screen必须加--json,否则输出会静默为空。
如果 pane 内配置了 iTerm2 shell integration,还能进一步做到"发送后等命令结束再读",形成可靠的 send → wait → read 模式(见 session-shell-integration.md):
cli-anything-iterm2 session send "make build" cli-anything-iterm2 session wait-command-end --timeout 120 cli-anything-iterm2 --json session scrollback --tail 50 --stripwait-command-end会返回{"session_id": "...", "exit_status": 0, "timed_out": false},Agent 据此即可判断远端命令是否成功退出,这是比"盲目 sleep 后读屏"可靠得多的执行确认手段。
写入任意 pane:定向发送 shell 文本
读取的逆操作是写入。cli-anything-iterm2 session send会把一行文本连同换行符送入指定 pane 的 shell:
cli-anything-iterm2 session send "git log --oneline -10" --session-id <pane-session-id>这也与 tmux-commands.md 中session run-tmux-cmd "rename-window mywork"的写法形成对照:后者把 tmux 命令从某个具体 session 的内部执行(对应 core/tmux.py 的run_session_tmux_command(),要求该 session 确实运行在 tmux -CC 之下),而session send是直接向 shell 键入文本。发送普通 shell 命令用send,需要操控 tmux 内部状态且你已持有目标 session ID 时用run-tmux-cmd。
布局管理:让 tmux 窗口像原生 Tab 一样组织
tmux 的优势之一是纯命令式的布局管理,而 tmux -CC 又让每个窗口以 Tab 形式存在。两者叠加即可用组合命令搭建多窗格工作区:
# 通过 tmux 协议新建窗口并横向拆分 cli-anything-iterm2 tmux send "new-window -n logs" cli-anything-iterm2 tmux send "split-window -h -t logs" cli-anything-iterm2 tmux send "select-layout -t logs even-horizontal" # 通过 iTerm2 侧创建 tmux 窗口(直接映射为新 Tab)并切换上下文 cli-anything-iterm2 tmux create-window --use-as-context注意这里new-window/split-window/select-layout是发给 tmux 服务器的协议命令,而tmux create-window则是 iTerm2 侧的桥接动作——create_window()(core/tmux.py)通过TmuxConnection.async_create_window()让 iTerm2 新建一个 tmux 窗口,并立刻把window_id、tab_id、session_id一并返回,--use-as-context表示新窗口随后成为后续命令的默认上下文。
若需要隐藏/恢复某个 tmux 窗口对应的 Tab(如临时收起日志窗格又不想关闭会话),使用cli-anything-iterm2 tmux set-visible @1 off|on,其中@1取自tmux tabs输出中的tmux_window_id。
关键映射:tmux pane → iTerm2 session ID 的对应关系
这是整个工作流中最容易卡住的环节:tmux pane 本身不暴露 iTerm2 session ID,两者分属两套 ID 体系——tmux 侧用session_name:window_index:pane_index坐标,iTerm2 侧用形如p1-...的 session ID。文档给出的交叉引用方案是:
cli-anything-iterm2 --json tmux tabs # → tab_id per tmux window cli-anything-iterm2 --json app status # → session_id per tab_id解读这条映射链:
tmux send "list-panes -a -F '#{session_name}:#{window_index}:#{pane_index} ...'"给出 tmux 坐标与 pane 的前台命令/路径;tmux tabs给出「tab_id ↔ tmux_window_id」的映射——注意一个 tmux 窗口(Tab)内含若干 pane;app status给出「tab_id ↔ session_id」的轻量清单(每个 tab 内按序排列的 session 即对应 tmux pane,session_count字段可辅助对齐数量)。
把三步结果按 tab_id 对齐,就能把一个 tmux 坐标唯一地落到session_id上,后续的session screen/scrollback/send才能定向到正确的 pane。实践中建议先用list-panes记录 pane 的排列顺序(如list-panes -t <window> -F '#{pane_index} #{pane_current_command}'),再结合app status中同一 Tab 下的 session 顺序完成坐标换算。
底层原理:一次 bootstrap 调用背后发生了什么
从 core/tmux.py 的源码结构可以梳理出 tmux 集成的完整调用链,理解它有助于排查问题:
- 连接发现:
_ensure_app_and_connections()(第 15–21 行)必须先iterm2.async_get_app()初始化 App——这一步会注册 DELEGATE_FACTORY,而async_get_tmux_connections()依赖该工厂才能感知tmux -CC连接。App 初始化顺序错误是常见坑; - 连接解析:
_resolve_connection()(第 24–41 行)支持按connection_id精确选择连接(如user@host),不指定则默认取第一个;找不到时会列出可用连接列表辅助诊断; - 引导轮询:
bootstrap()(第 154–220 行)先快照已有连接的connection_id集合,发送tmux -CC后以 0.5 秒间隔轮询,只有当检测到新增连接(ID 不在快照中)才算成功——这解释了为什么它适合在 Agent 会话中重复调用而不会误判为旧连接; - 窗口显隐:
set_window_visible()(第 111–130 行)直接调用async_set_tmux_window_visible(tmux_window_id, visible),把"收起 tmux 窗口"转化为"隐藏 iTerm2 Tab"的原生 UI 操作。
从错误处理看,bootstrap 超时信息("Make sure tmux is installed and no existing session conflicts")提示了一个现实约束:当存在既有tmux -CC会话冲突时,应改用--attach而非裸tmux -CC。
错误处理与 JSON 约定
所有 tmux 命令都建议配合--json使用,以获得稳定的机器可读输出(Skill 统一约定,参见 SKILL.md)。典型错误形态(json-tmux-app.md):
Error: No active tmux connections. Start one with: tmux bootstrap在--json模式下会以{"error": "No active tmux connections..."}返回,Agent 可以据此捕获异常分支并自动回退到tmux bootstrap先建立连接。连接 ID 不存在时则报Tmux connection '<id>' not found并附上可用连接,便于重试。
把它组装成 Agent 可复用的执行模式
综合 tmux-guide.md、tmux-commands.md 与 SKILL.md 中描述的典型 Agent 工作流,一个健壮的、可交给 LLM 循环执行的标准流程可以归纳为:
- 定向:
cli-anything-iterm2 --json app snapshot或app status确认现有会话布局,找出目标 shell 的 session ID; - 设上下文:
cli-anything-iterm2 app set-context --session-id <id>(若尚未连接任何 tmux,紧接 bootstrap 可能超时,务必先设上下文); - 引导:
cli-anything-iterm2 --json tmux bootstrap,必要时加--attach --timeout 30; - 枚举:
tmux send "list-panes -a -F ..."拿坐标 +tmux tabs+app status交叉换算 session ID; - 执行:
session send定向发命令 →session wait-command-end --timeout 120等退出 →session scrollback --tail 500 --strip读结果; - 布局:用
tmux send "split-window ..."、select-layout ...、new-window ...与tmux create-window --use-as-context维护多窗格工作区。
这套模式的最终效果是:Agent 不再需要"盲发命令再 sleep 碰运气",而是能够像操作一组原生终端 Tab 一样,精确地知道每个 pane 在哪、在跑什么、输出是什么,从而实现可观测、可确认、可恢复的 iTerm2 + tmux 会话自动化。
关联资源导航
- 本文主文档:tmux-guide.md
- tmux 命令全表:tmux-commands.md
- tmux/app JSON 输出结构:json-tmux-app.md
- 会话读写与 shell integration:session-io.md、session-shell-integration.md
- 上下文管理:app-context.md
- tmux 核心实现:core/tmux.py
- Skill 总览:SKILL.md
适用前提提示:以上全部命令依赖 iTerm2 Python API 运行在 macOS 环境,且
tmux -CC连接必须在 iTerm2 内建立;在无 iTerm2 的纯 Linux/CI 环境或未启用 Python API 时无法使用。请以本仓库当前版本为准。
【免费下载链接】CLI-Anything"CLI-Anything: Making ALL Software Agent-Native" -- CLI-Hub: https://clianything.cc/项目地址: https://gitcode.com/GitHub_Trending/cl/CLI-Anything
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考