Moshpit

指南

在 iPhone 上跑 Claude Code

这个 App 就是为这条路存在的。四步:连上主机、把 herdr 装上、起 Claude Code、 在锁屏上回它的权限询问。这一页里出现的每一条命令和每一句提示,都是 App 里真实的那一份, 不是示意。

连上主机

填 SSH 信息、核一次主机指纹、把这条连接的 Multiplexer 选成 herdr

把 agent 起起来

在 herdr 的 pane 里敲 claude;或者让 Moshpit 现开一个 git worktree,在里面起 Claude Code。

把手机放下

Claude Code 要人点头的时候,锁屏会告诉你,并且直接给你回它的按钮。

开始之前你得有什么。一台你本来就能 SSH 上去的服务器、一台 iOS 18 以上的 iPhone、以及服务器上已经装好的 Claude Code。Moshpit 不负责装 Claude Code,也不和 Anthropic 通信——它驱动的是一个真实的 shell,Claude Code 只是跑在里面的一个程序。

herdr 不是必需的。它的价值在于把「agent 现在什么状态」从猜变成读,所以这份 指南用它。走 tmux 那条路一样能成,见 用 tmux在锁屏上回应 agent

第一步

先把主机连上

四个字段加一次指纹核对。如果你在电脑上本来就能 ssh 上这台机器, 这一步一分钟就完了。

新建连接

首页右上角点 。表单是分组的:

  • CONNECTION——NameHostPort (默认 22)、Username。Name 和 Host 填了,Save 才会亮。
  • AUTHENTICATION——PasswordSSH Key。密钥可以是你在 Settings → SSH Keys 里生成的,也可以直接把 PEM 贴进表单。
  • ADVANCED——Multiplexerherdr。下面那个自定义路径除非 你的二进制放在很偏的地方,否则留空:填了路径就等于你替它担保,能力探测会被整个跳过。

多路复用器是按连接选的,不做全局默认。你手上的机器本来装的东西就不一样, 一个全局默认只会换来「怎么就这台连不上」。

指纹核一次

第一次连的时候握手会停下来,Moshpit 把对方给出的主机密钥摆给你看:

New Host

First connection to your-host:22.

Key fingerprint:
SHA256:

Verify it matches the server (e.g. `ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub`).

          [ Cancel ]   [ Trust ]

Moshpit 没法替你验这串指纹,它能做的是把该敲的命令一并写给你。 如果以后某台主机的密钥变了,这个对话框会换成 ⚠️ Host Key Changed,两串指纹都印出来,默认按钮是 DisconnectTrust New Key 被标成破坏性操作。

这里有条限制,现在就该知道。App 里没有任何一个地方能列出或删掉已信任的主机密钥。 删掉一条连接,它的指纹还留着;信任是按设备存的,而且排除在备份之外——换台手机,每台主机都会 重新问你一遍。主机正当换了密钥之后,唯一的 App 内路径就是在那个 ⚠️ 对话框里接受变更。

还有:直接填在连接表单里的密码和 PEM,保存时没有加 Face ID 门禁;只有在 Add Key 里建、并且开了 Require Face ID 的密钥才会每次读取都验人。 带密码短语加密的私钥则完全用不了。

第二步

把 herdr 装到主机上

herdr 是专门为 CLI coding agent 写的 Rust 单二进制。它把 agent_status 做成了每个 pane 上的协议字段——这就是为什么 Moshpit 不用你在主机上装任何东西,也知道 Claude Code 正等着你。

装它

# macOS / Linuxbrew
brew install herdr

# 其它情况
curl -fsSL https://herdr.dev/install.sh | sh

这里给不出 apt-get 那一行:herdr 不在任何 Linux 发行版仓库里, 它自己的 packaging 只覆盖 Windows。Moshpit 也拒绝硬凑一条 sudo apt-get install -y herdr——一条以「找不到软件包」收场的命令, 用户只会当成是 App 的 bug。

安装脚本把二进制放进 ~/.local/bin,并且刻意不动你任何 rc 文件。 Moshpit 在探测和拉起 herdr 时都会把这个目录加进 PATH,所以主机那边你什么都不用改。

没装的话,App 会直说

选了 herdr 却连到一台没装它的主机上,你得到的是一条可关闭的横幅, 而不是一次失败:

  • 横幅写 “herdr not found on this host — plain shell session.”,右边一个 Install herdr
  • 点开 Install Assist,命令已经填好,上面那句是 “Moshpit never installs anything silently. Run the command below in your shell — sudo and its output stay fully visible.”
  • Run in terminal 把命令粘进当前这个 shell 执行,sudo 的交互和输出全程可见; 旁边还有 Copy commandRe-check
  • 会话照样能用,只是退成裸 shell,而且绝不会被悄悄换成 tmux—— tmux 和 herdr 是两个独立服务器、装的是毫不相干的会话,替你 attach 另一个, 等于把别人的活儿端到你面前说这是你的。

这套集成是对着哪个版本写的。设计基准是 herdr main / v0.8.0,protocol 19; 真机验证跑的是 0.7.3,protocol 16——当时 brew install herdr 装到的就是它。 snapshot 解码器按「字段缺了就降级这一处细节,绝不让整份读取失败」写。

0.7.3 上看得见的代价:它不上报 pane 的命令,所以终端面包屑的第三段会回落成 pane N,而不是告诉你里面跑的是什么。这一段仍然保留,因为它是进 Select Pane sheet 的唯一入口。

这份指南请走 SSH,不要走 Mosh。Mosh 传的是渲染后的屏幕差分,会毁掉按行分帧, 所以它扛不动 herdr 的 frame 协议(tmux -CC 走不了 mosh 是同一个原因)。 走 Mosh 时 herdr 跑的是它自己的全屏 TUI,Moshpit 只是个渲染器——在手机上, herdr 的侧栏大概要吃掉三分之一屏宽。控制面的那些 sheet 仍然可用,走的是另一条 SSH 旁路。

第三步

起 Claude Code

两条路。在你已有的 pane 里跑,或者让 Moshpit 开一个隔离的 git worktree 在里面跑。后一条才是在手机上真正值得用的那条。

在已有的 pane 里跑

没有新东西要学

  • 在 herdr 的 pane 里敲 claude,这一步就完了
  • herdr 靠观察自己 pane 的屏幕内容认出它
  • 不用配置,也不用多记什么

New Agent Task

一个 worktree、一个 workspace、一个 agent

  • 一张表单,入口在首页 AGENTS 那一栏
  • 在主机上现开一个 git worktree,并在里面起 Claude Code
  • 你自己那份工作区一动不动
  • 只有 herdr 有——tmux 给不了这个

两条路上 herdr 都是靠屏幕内容推断出这是个 agent。想要它被上报 而不是被推断,在主机上跑一次 herdr integration install claude, 之后 Claude Code 会自己汇报状态。这更权威,但不是必需的——这一页里的一切, 不装它照样成立。

这张表单,一栏一栏说

TASK 组,脚注:“Creates a git worktree on the host, then starts the agent inside it. Your working tree is untouched.”

  • Repo——一个菜单。Moshpit 同时从两处找候选:每个已开 pane 的 cwd,用 git rev-parse --show-toplevel 反查 git 根;以及按修改时间扫一遍 $HOME。找的过程中菜单写 “Looking for repositories…”;一个都没找到就写 “None found — no panes in repos, and nothing under ~”。最后一项永远是 Other…,点开露出一个 Repository path 输入框。
  • Branch——在手机上先校验,不合法就根本不发请求。空名字、空格、 开头的 -/、结尾的 /..、 结尾的 .lock,以及 ~ ^ : ? * [ \ 这些字符,都会被顶回来, 并给一句人话,比如 “No spaces in a branch name”
  • Agent——从 herdr server agent-manifests --json 读。默认优先 claude,然后 codex,再回落到第一个。(按字母序排的时候, 真机上默认值填进去的是 agy,所以现在不按字母序了。)

FIRST MESSAGE 组,脚注:“Optional. Sent to the agent once it's running — leave blank to type it yourself.”Start 之后按钮会变成 Starting…——大仓库 checkout 要几十秒,一个从不变化的按钮读起来就像坏了。

New Agent Task 表单:repo 是 payments-api、branch 是 fix-webhook-retry、agent 是 claude,下面是第一条消息

点 Start 之后实际跑的是什么

三条命令,按顺序走 SSH。没有任何一步是藏着的;你填的每个标签在靠近 shell 之前都会被单引号包好。

herdr worktree create --cwd '~/code/payments-api' --branch 'fix-webhook-retry' \
     --label 'fix-webhook-retry' --focus --json
→ {"type":"worktree_created",
    "workspace":{"workspace_id":"w4"},
    "root_pane":{"pane_id":"w4:p1",
                 "cwd":"~/.herdr/worktrees/payments-api/fix-webhook-retry"}}

herdr pane run 'w4:p1' 'claude'          <- 把命令敲进去并回车

herdr agent send 'w4:p1' '<第一条消息>'    <- 2 秒后,只在你填了的时候

一个新的 git worktree、一个新的 workspace、一个 cwd 已经在里面的 pane, 并且焦点已经过去了。第二步刻意用 pane run 而不是 agent start: 实测 agent start --workspace 不继承 worktree 的目录,新 pane 会落在 发起命令的那个目录里。pane run 敲进去的就是你自己会敲的那一行,而且你看得见。

你会先看到什么

真机验证那一轮:workspace 起来了,cwd 是 ~/.herdr/worktrees/…/fix-scroll-jumpgit worktree list 确认分支是真建了,Claude Code 真的起来了,停在它的信任目录提示上。herdr 把这个 pane 标成 agent: claude / status: blocked,首页的 Agents 区随即显示 AGENTS 1 · NEEDS YOU。那第一个提示,就是第四步里你要回的那个。

用完之后怎么收

首页长按那条 workspace → Remove Worktree。这个菜单项只在真的是 linked worktree 的 workspace 上出现。第一条命令绝不带 --force

  • Remove the worktree for “…”?——“Deletes the branch checkout under ~/.herdr/worktrees. payments-api itself is untouched.”
  • 只有当 herdr 因为工作区是脏的而拒绝时,才会弹第二个对话框: “…” has uncommitted changes——“Those changes exist nowhere else. Removing the worktree throws them away.”按钮是 Delete anyway / Keep it。 只有这一次明确点击才会加上 --force。这是 herdr 自己的安全网, 不是我们发明的——顺着它比替你做决定好。

这一步的边界,摊开讲。

worktree 落在 ~/.herdr/worktrees/<repo>/<branch>,不在仓库旁边, 这一版也没有自定义路径的选项——那不是个值得在手机上打字的东西。大仓库 checkout 要几十秒。 对着一个脏仓库跑 git worktree add 预期没问题(它本来就是独立 checkout), 但还没验证过

agent 的启动参数不替你选。manifest 里 claude 的默认命令是什么, 跑的就是什么。像 --dangerously-skip-permissions 这种,得你自己在 pane 里敲。

herdr 只看得见自己 pane 里的 agent。你在 Terminal.app 里起的那个 claude 不会出现——herdr 是 runtime,不是进程扫描器。这条实测踩过: 五个 herdr pane 全在空转、另一个终端里 claude 跑得正欢,而 Agents 区诚实地写着 “Nothing running — start a task to isolate one”,用户则确信有东西在跑。

没在设备上验到的一处:长按出来的那个上下文菜单本身。自动化驱动不了 SwiftUI 的长按, 所以「菜单 → 对话框 → 对话框」这段只有编译和单测保证,没有设备录像。

第四步

在锁屏上回它的权限询问

Claude Code 停下来问你能不能跑某条命令。琥珀色在这个站上只有一个意思—— 「有 agent 在等人」——它出现的地方就是这里。

iPhone 锁屏上的 Moshpit Live Activity:Claude Code 带着 Allow 和 Deny 按钮,下面是工作中的 codex

正卡着的那一条 状态点、等宽字体的命令名、位置写成 主机 · workspace · tab,右边一个在走的计时器。 最紧急的那个排最前,也只有它带按钮。

其余的 headline 以下全部收成一行状态。这是故意的:iOS 会把过高的 Live Activity 裁掉, 而当初掉出屏幕的,正好就是第二个 agent 的 Allow 和 Deny。

按钮跑在哪 跑在 Moshpit 自己的进程里,不在 widget 里。你点一下,iOS 会把 App 拉到后台执行。 屏幕不用解锁。

每个按钮到底敲进去什么

就是往 Claude Code 那个 pane 里发原始按键。没有别的,也没有什么聪明操作。

按钮发进 pane 的字节什么状态下出现
Allow 0x0d——一个回车,选中当前高亮的那一项 需要你
Deny 0x1b——Esc,取消 需要你
yes / continue 这段文字,然后回车 需要你
Stop 0x03——Ctrl-C 工作中
Reply 你输入的文字,然后回车 通知上的操作

这套映射是对着 Claude Code 的权限提示调的——肯定项默认高亮、Esc 取消, 这个形状也吻合绝大多数 TUI 的 yes/no 菜单。但它们是盲发的按键:Moshpit 不解析提示内容, 不会去确认此刻高亮的到底是哪一项。yescontinue 是快捷回复, 它们是往 pane 里打字,不是往什么聊天框里发消息。

点了却送不到的时候

  • 在 herdr 上,帧通道是打进「它当前 attach 的那个 pane」的,所以一次点击会先给目标 pane 发 agent focus,等切换真的完成了再写入。这条是被一个真 bug 逼出来的: 两个 agent 并发时,锁屏上的 Allow 可能变成别人的回车。
  • 如果焦点拿不到,投递会返回失败,并且明确告诉你——通知标题 “Not delivered”, 正文 “Your tap didn't reach the agent — open Moshpit and answer there.” 锁屏上点了 Allow 却悄无声息地失败,是最坏的结果,所以它宁可吵你一下。
  • 如果 Moshpit 已经知道那个 pane 早就不在「需要你」状态了,Allow / Deny 干脆不发: “Prompt already gone”“That request was already answered or has changed — nothing was sent.” 状态一变,之前那条「需要你」的通知也会被从锁屏上撤掉—— 免得你回的是一个已经被换成更危险的新提示。
  • 已经彻底断开的会话,没法从后台的一次点击里救回来。打开 App 在里面回。

控制它的四个开关

Settings → NOTIFICATIONS,四个默认都是开的。

  • Notifications——“Alert when an agent needs you”
  • Live Activity——“Show agent session status in the Dynamic Island”
  • Alert sound——“Play a sound when the agent needs you”
  • Show detail on lock screen——“Display what the agent is running/asking — off keeps it private”。关掉之后,工作中 / 需要你 / 已完成照样显示,只是不再露出 Bash: npm install 那一行。

NotificationsLive Activity 两个都关掉,就什么都不看了。 herdr 连接下,Install agent hooks 那一行是隐藏的——没东西要装。

一处得说在前面的现状:Live Activity 上的按钮和通知里的操作名,目前只有英文 ——AllowDenyReplyStop,以及 “Not delivered”、 “Prompt already gone” 这两条提示都还没有中文。App 内的界面是中文的,锁屏这一层还不是。

灵动岛显示琥珀色圆点和一个感叹号:有 agent 在等你

解锁时 · 需要你 一个 agent 卡住就是琥珀色感叹号,多个就是数量。胶囊里只放得下一个—— 展开后的 Switch 按钮用来轮到别的。

灵动岛显示青色圆点和一个正在走的计时器

解锁时 · 工作中 Claude Code 在跑的时候是青色加一个走着的计时器。在想的 agent 和卡住的 agent 长得不一样。

诚实的那一段

你把 App 切到后台之后会怎样

在你打算依赖上面那一切之前,这一段才是该读的。它是「中间不放服务器」这个决定的 必然代价,也不是任何一个设置能关掉的。

iOS 会挂起连接,状态就停在那了

Moshpit 里没有任何推送服务器。你手机上的每一条提醒,都是正在跑的 App 从活着的那条会话里就地生成的本地通知。iOS 一挂起 App,那个 2 秒的轮询和 SSH 的拉取 也跟着停了。

真机实测:App 切到后台之后,把一个 pane 从 blocked 翻到 working,灵动岛 纹丝不动;只有回到前台、再过一个轮询周期,它才翻过来。tmux 那条路同理—— 这是「不做远程推送」这个产品决定的结果,不是 herdr 的问题。

所以它选择说「暂停了」,而不是骗你

Moshpit                      2 working · 1 needs you
●  claude       needs you   2m
   mac-studio · ~ · Tab 1
   Bash: npm install

        [ Deny ]  [ Allow ]

…… App 被挂起两分钟 ……

Moshpit            paused — open Moshpit to refresh

Live Activity 带着一个 120 秒的过期线——两个轮询周期没跟上—— 到点就渲染成诚实的「暂停」,而不是一个冻住的「working」。桌面小组件用的是 180 秒, 过期后把所有状态点都压成灰的。

就算在前台,外面的改动也会滞后

  • herdr 的控制面轮询在有变化时是 2 秒一次,连着读到三次一模一样就退到 8 秒。任何变化、或者你手动刷新一下,都会立刻把它拉回 2 秒。
  • 在 App 里做的任何操作都是立即刷新的——每次改动后面都跟一次重读。 但在别处发生的事(你的电脑、另一个客户端、agent 自己变了状态)最多要 8 秒才出现。
  • 这也是为什么已持续时间只印到分钟——now2m1h 12m,从不印秒。背后的数据本身就有最多 8 秒的粗糙度, 印出秒来等于宣称一个它没有的精度。
  • herdr 的 socket API 其实是有事件订阅的,但它的 CLI 没有暴露 subscribe 子命令, 而手机也打不开你主机上的 Unix socket。所以今天能用的只有轮询。

两个 Moshpit 连同一个 pane 会打架

herdr 的直连是按 pane 独占的。Moshpit 必须带 --takeover 去 attach,因为每次重连都会和自己那条还没死透的旧通道撞上。 于是两台手机连同一个 pane,就会每两秒左右互相抢一次。这是独占式直连的固有行为, 在客户端消不掉

Moshpit 能做的是:30 秒内被非预期地踢掉 3 次,就暂停 30 秒并给一条看得见的提示 ——“Another client is using this pane — retrying shortly”—— 等画面重新出帧就自动消失。你电脑上跑 herdr 自己的 TUI 不占 attach 名额, 所以「电脑 TUI + 手机」理论上是安全的,但这个组合没有被验证过

零碎问题

这份指南通常会引出的几个问题

非得用 herdr 吗?

不用。走 tmux 的话,去 Settings → NOTIFICATIONS → Install agent hooks 装一次 hook:一条命令,把 Claude Code 的 UserPromptSubmitPreToolUseNotificationStop 注册上, 让它们把状态盖到 tmux 的 pane 上。它会先备份 ~/.claude/settings.json,永远 exit 0 所以挡不住 agent, 重复跑会自己去重。不装 hook 的话,Moshpit 退回去读 pane 输出和终端响铃—— 那是猜,文档里也是当猜来写的。首页的 Agents 区块则是 herdr 独有的。

Moshpit 关掉之后还能叫醒我吗?

不能。没有推送服务器,所以提醒只在会话还活在你设备上的时候存在。iOS 会挂起连接, 状态会停更,直到你回来。见上面那一段

那 Mosh 呢?

想让一个 shell 扛过 Wi-Fi 切 5G,Mosh 是对的选择,但它扛不动 herdr 的 frame 协议, 也扛不动 tmux -CC。走 Mosh 时 herdr 在终端里跑自己的 TUI,侧栏要占掉手机 大约三分之一的宽度;控制面的 sheet 仍然通过另一条 SSH 旁路工作。这份指南请用 SSH。 细节见 Mosh 与漫游

Claude Code 明明在跑,Agents 区却是空的。

那基本可以肯定它不在 herdr 的 pane 里。herdr 靠观察自己的 pane 认 agent, 所以在 Terminal.app 或另一条 SSH 里起的 claude 对它是不可见的。 用 New Agent Task 起,或者手动在 herdr 的 pane 里跑。herdr 0.7.3 上还有第二层原因: 它不上报 agent 名字,所以空闲状态的 agent 根本长不出那一行。

Moshpit 会读我的代码、我的 prompt,或者别的什么吗?

没有 Moshpit 账号、没有中转、也根本没有 Moshpit 的服务器。App 只连你自己添加的机器。 唯一会离开 pane 的,是 hook 抓到的那一行短标题——agent 正在跑什么或问什么,截到 80 个字符 ——它渲染在你自己的锁屏上,并且可以用 Show detail on lock screen 关掉。

接下来看哪?

连接与密钥讲完整的表单和主机密钥行为, 用 herdr 讲术语和键位, 在锁屏上回应 agent 把通知和 Live Activity 那条链讲透。连不上或者行为奇怪,看 排查

上一页用 git worktree 开隔离任务下一页键盘与快捷键