指南

在 iPhone 上跑 Claude Code

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

1 连上主机
填 SSH 信息、核一次主机指纹、把这条连接的 Multiplexer 选成 herdr
2 把 agent 起起来
在 herdr 的 pane 里敲 claude;或者让 Moshpit 现开一个 git worktree,在里面起 Claude Code。
3 把手机放下
Claude Code 要人点头的时候,锁屏会告诉你——点一下, 就落在提问的那个 pane 里。

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

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

第一步

先把主机连上

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

新建连接

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

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

指纹核一次

第一次连的时候握手会停下来,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 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

这一步的边界,摊开讲。

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 锁屏:一条 time-sensitive 的 Moshpit 通知——claude 在 m1-pro 上请求运行 Bash

锁屏上的那个问题 标题是 agent,正文是它在问什么、在哪儿问—— Claude needs your permission to use Bash — m1-pro · pit。 同一台主机上几个 agent 一起等的时候,只有一张卡:claude +2

点一下 直接落在提问的那个 pane 里。你在一个真实的终端里读完提示, 用你在工位上会敲的那几个键回答它。

锁屏上刻意没有 Allow 按钮 这个 App 曾经有过的那排按钮,发的是盲打的按键。批准一个你没读过的请求, 恰恰违背了「你随时能读到它」这个 App 的全部价值——所以它们被拿掉了。

App 没在跑,它是怎么到你手机上的

被挂起的 iPhone 做不到的那部分,你的主机来做。给 pane 盖状态戳的那套 hook, 会把 attentiondone——从来不含 working—— 交给一个小小的发送器 ~/.moshpit/moshpit-push.sh:它用一把只存在于你手机上 的钥匙把提醒封好,把密文交给 Moshpit 的推送中转,再由 Apple 送达。两边都读不到一个字: agent 名字、命令、问题本身,都是在你手机上的通知扩展里、在锁屏上才解开的。 第一次给主机打开通知时自动完成配对;唯一留下的一次确认,是首次安装 hook 那一下。

适用范围,说在明处。发送器搭在 agent hook 上,而 hook 盖章的对象是 tmux 的 pane——所以「App 关着也能收到推送」这条路,需要你的 agent 跑在装了 hook 的 tmux 里。纯 herdr 的 pane 上,herdr 自带的 agent_status 会在 Moshpit 活着的时候驱动灵动岛和通知;App 一关,推送也就停了。 如果「关着 App 也被叫醒」正是你要的,现阶段请把 Claude Code 跑在 tmux 的 pane 里。

让它保持安静的四条规则

什么都通知,等于什么都没通知——所以每一次打扰都得挣来:

规则意味着什么
先站住 30 秒 在宽限窗口内、你在工位上顺手答掉的问题,永远不会惊动任何手机。
每台主机一张卡 所有在等的 agent 共用一张汇总卡——“claude +2”。只有 没人在等 → 有人在等的那个瞬间会响、可以穿透专注模式;之后都只是安静地更新。
跑满 3 分钟的完成才响 一个跑了三分钟以上的回合结束时值得一声提示音;二十秒的回答只进列表, 不点亮屏幕。
停着的 agent 保持沉默 你自己留在提示符上的 agent 发来的「空闲提醒」不会点亮任何东西—— 放了一天的陈旧「需要你」也会自愈成已完成。

细节处也诚实:在 App 里看过那个提示就算已读;重连或重启不会为一个 已经告诉过你的问题再响一次。

控制它的四个开关

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

NotificationsLive Activity 两个都关掉,就什么都不看了。 开关下面的 Set up this host 一行,列出当前连接的主机上都有什么:哪些 agent 装了 hook、推送发送器在不在、这台手机握着哪些配对——安装本身在连接时自动完成。

一处得说在前面的现状:锁屏通知的正文是 hook 从 agent 那里原样抓来的 ——Claude needs your permission to use Bash 这类句子通常是英文; “claude +2” 这种计数则与语言无关。App 内的界面是中文的,锁屏这一层跟着 agent 走。

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

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

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

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

诚实的那一段

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

在你打算依赖上面那一切之前,这一段才是该读的。现在补上这个缺口的是 密封推送——它补不上的部分,也一并写在这里。

iOS 会挂起连接,实时的那几层就停了

iOS 一挂起 App,那个 2 秒的轮询和 SSH 的拉取也跟着停了。能跨过这个缺口的, 是第四步讲的推送:主机上的 hook 自己注意到 attentiondone, 自己把密封的提醒发出来——不需要活着的会话,手机锁着、App 关着都行。

过去的是 Live Activity。它渲染的是 App 最后看到的状态,App 被挂起后 就没有东西再喂它了。真机实测:App 切到后台之后,把一个 pane 从 blocked 翻到 working, 灵动岛纹丝不动;只有回到前台、再过一个轮询周期,它才翻过来。

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

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

…… App 被挂起两分钟 ……

Moshpit            paused — open Moshpit to refresh

Live Activity 带着一个 120 秒的过期线——两个轮询周期没跟上—— 到点就渲染成诚实的「暂停」,而不是一个冻住的「working」。桌面小组件用的是 180 秒, 过期后把所有状态点都压成灰的。只要第四步的发送器在位,推送通知不受这些影响,照样送达。

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

两个 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 关掉之后还能叫醒我吗?

能——推送这条路就是为这个存在的。主机上的 hook 用一把只有你手机持有的钥匙把提醒封好, 经 Moshpit 的中转发出;手机上的通知扩展在锁屏上解开它——App 关着、手机锁着都行。 第四步的适用范围同样成立:发送器搭在 agent hook 上、hook 盖章的是 tmux 的 pane, 在乎这条路就把 agent 跑在 tmux 里。四条安静规则——30 秒宽限、每台主机一张卡、 3 分钟以上的完成才响、停着的 agent 沉默——见 在锁屏上回应 agent

那 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 运营的只有 推送中转这一件东西,而它承载的是自己解不开的密文——提醒在你的主机上、 用只有你手机持有的钥匙封好。唯一会离开 pane 的,是 hook 抓到的那一行短标题 ——agent 正在跑什么或问什么,截到 80 个字符——它全程密封,最终渲染在你自己的锁屏上。

接下来看哪?

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

接下来看 在任何地方连回自己的机器 · 回到指南列表