起因

Claude Code 是个 TUI,输入输出绑在终端里。想让别的程序操控一个正在跑的会话,官方没给 SDK,得自己想办法。

翻了两个项目,都是拿 tmux 做地基:

一个是让 agent 遥控 Claude,一个是让人从聊天软件遥控 Claude。角度很不一样,但底下都是 tmux。记一下为什么。

为什么先想到 tmux

遥控 TUI 有三个硬约束:

  1. 全屏 TUI,不能 echo prompt | claude 管道喂
  2. claude -p 跑完就退,交互模式又没 API
  3. 信任对话框、权限弹窗这些只出现在屏幕上,裸 PTY 不好稳定识别

tmux 一把解决,而且顺手多给了几样:

能力用来干什么
new-session -d后台起 pty 容器跑 claude,不占前台
send-keys打字、按方向键、发 Enter
capture-pane抓屏幕文本(可带 ANSI 色码)
daemonsession 脱离父进程,脚本挂了 claude 还在
tmux attach人工随时接管
window/session多路复用,N 个 claude 就是 N 个 window

前三条是"遥控"需要的,后三条是附送的——但后三条恰恰是 ccbot 那种"从手机切回桌面"场景的关键。

hermes skill:最小用法

这个 skill 只用到前四个原语,就让一个 agent 能起一个下属 Claude 干活:

# 起后台 session
tmux new-session -d -s claude-work -x 140 -y 40

# 跑 claude
tmux send-keys -t claude-work 'cd /path/to/project && claude' Enter

# 等 TUI 初始化,发第一个 prompt
sleep 5 && tmux send-keys -t claude-work 'Refactor the auth module' Enter

# 看它在干嘛
sleep 15 && tmux capture-pane -t claude-work -p -S -50

# 追问
tmux send-keys -t claude-work 'Now add unit tests' Enter

# 收工
tmux send-keys -t claude-work '/exit' Enter
tmux kill-session -t claude-work

就这一套命令,拼出"起进程、发命令、读结果、清理"的闭环,没写一行状态管理代码。

局限也明显:信息全从屏幕文本抠。终端滚动会丢内容,ANSI 色码和框线得当噪音过滤,想知道 tool 调了啥参数只能看 TUI 怎么渲染。糙,但跑得通。

ccbot:加一条 JSONL 旁路

ccbot 的目的是让你人不在电脑前也能继续操作本地 Claude——手机上发消息,bot 转成 send-keys 打进本地 tmux;claude 的回复再从 tmux 那头读出来推到手机。

作者自己在 README 里点破了和别家 bot 的区别:

It operates on tmux, not the Claude Code SDK. Your Claude Code process stays exactly where it is, in a tmux window on your machine.

其他 bot 走 SDK 起独立 API 会话,那会话跟你本地终端是两回事,手机聊完回不去。ccbot 走 tmux,手机聊完回家 tmux attach 接着敲,scrollback 都在。这一点完全依赖 tmux 的 daemon 架构,没这条没得玩。

三条通道,各干各的

ccbot 实际用到 tmux 三种能力,对应三个问题。

1. send-keys:输入通道

手机里发的消息最终都是一次键码注入打进 tmux window。期间还要处理 TUI 的小脾气:发文本和 Enter 之间得插几百毫秒,否则 Enter 会被当换行而不是提交;! 开头的 bash 模式也要先发 ! 等 TUI 切换好再发剩下。都是跟 TUI 输入缓冲斗争的脏活。

2. capture-pane + JSONL:获取输出

Claude Code 会把对话内容写到 JSONL 文件,ccbot 直接轮询文件拿对话记录,不经过 tmux。

但权限请求、计划确认、选项菜单、首次启动的信任提示这些东西只活在屏幕上,进不了 JSONL。原因也简单:这些弹窗是 TUI 在等你按键,人没按之前模型根本没动,自然没东西可记。光靠 JSONL 会卡在"claude 说要跑 bash"之后突然没动静。

所以 ccbot 另起一条 1 秒一次的轮询,用 capture-pane 扫屏识别弹窗,扫到就在手机上用可点按钮把选项发给你,你点一下 bot 再 send-keys 发对应数字回去。tmux 在这里既当观察端也当控制端,JSONL 做不到这件事。

3. daemon + attach:持久化

tmux session 生命周期独立于 ccbot 进程。bot 挂了、重启了,tmux 里 claude 还在跑,回家 tmux attach 看到的是完整的会话。“从手机切回桌面不丢上下文"就是这么来的。

一条核心映射:1 个会话入口 = 1 个 tmux window = 1 个 Claude session。

两个项目对 tmux 的使用对比

需求tmux 原语hermes skillccbot
起进程new-session / new-window每任务一个 session每 topic 一个 window
注入输入send-keys发 prompt、方向键转发聊天消息、弹窗选项
读输出capture-pane唯一信息源只用来识别弹窗和状态行
持久化daemon任务可后台长跑核心功能
人工接管tmux attach调试用核心功能
多路复用window / session多任务并发多项目并行

hermes skill 只用前三项,ccbot 六项全用。但抽象是一样的:把 tmux 当"带 API 的终端 + 带持久化的进程管理”。