指南

用 iPhone 跑 Claude Code

这份指南讲的,就是这个 App 存在的理由。一共四步:让手机连上主机,在主机上装 herdr,起 Claude Code,最后在锁屏通知点进去的那个窗格里回答它的权限确认。页面里的每条命令、每句提示,都取自正式版 App,不是示意。

1 连上主机
填好 SSH 信息,核对一次主机指纹,把这条连接的 Multiplexer 选成herdr。
2 起智能体
在 herdr 的窗格里输入 claude,或者让 Moshpit 新开一个git worktree,把 Claude Code 起在里面。
3 把手机放下
Claude Code 需要你确认时,锁屏会告诉你。点一下,就到了提问的那个窗格。

开始前要准备的。一台能 SSH 登录的服务器,一台 iOS 18 或更新的 iPhone,服务器上装好的 Claude Code。Moshpit 不负责安装 Claude Code,也不和 Anthropic 通信。它操作的是一个真实的 shell,Claude Code 只是跑在里面的一个程序。

herdr 不是必需的。有了它,智能体的状态是读出来的,不是猜出来的,所以这份指南用它。走 tmux 也行,见用 tmux 和智能体通知的完整链路。

第一步

让手机连上主机

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

新建连接

在首页顶栏点 +。表单分三组:

  • CONNECTION:Name、Host、Port (默认 22)、Username。填了 Name 和 Host,Save 才会变成可点。
  • AUTHENTICATION:Password 或 SSH Key。密钥可以用Settings → SSH Keys 里生成的那把,也可以把 PEM 直接贴进表单。
  • ADVANCED:Multiplexer 选 herdr。自定义路径一栏留空,除非你的可执行文件放在特别的位置。填了路径,Moshpit 就原样信任它,不再做能力探测。

多路复用器是按连接选的,没有全局默认。每台机器装的工具不一样,全局默认只会让你纳闷「为什么这一台连不上」。

核对一次指纹

第一次连接时握手会停下来,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:两串指纹都印出来,默认按钮是 Disconnect,Trust New Key 标成危险操作。

有一条限制,现在就该知道。App 里没有任何界面能查看或删除已信任的主机密钥。删掉一条连接,它的指纹还留着。信任记录只存在这台设备上,不进备份,换手机后每台主机都会重新问一遍。主机正常更换密钥之后,App 内唯一的处理方式就是在那个 ⚠️ 对话框里接受新密钥。

另外,直接填在连接表单里的密码和 PEM,保存时不过 Face ID。只有在Add Key 里生成、并且开了 Require Face ID 的密钥,才会每次读取都要求验证。带口令保护的私钥目前完全不支持。

第二步

把 herdr 装到主机上

herdr 是一个为命令行编程智能体写的 Rust 单文件程序。它把agent_status 作为协议字段随每个窗格一起上报,所以 Moshpit 不需要你在主机上装别的东西,就知道 Claude Code 在等你。

安装

# macOS / Linuxbrew
brew install herdr

# 其他系统
curl -fsSL https://herdr.dev/install.sh | sh

这里给不出 apt-get 的写法:herdr 不在任何 Linux 发行版的软件仓库里,它自己的打包也只覆盖 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 command 和 Re-check。
  • 会话照常可用,只是变成普通 shell,而且不会悄悄换成 tmux。 tmux 和 herdr 各管各的会话,互不相干,替你连到另一个,等于把别人的工作摆到你面前说是你的。

这套集成对应的 herdr 版本。按 herdr main / v0.8.0,protocol 19 设计,在真机上用 0.7.3,protocol 16 验证过,后者是当时 brew install herdr 装到的版本。 snapshot 解码器的原则是:缺一个字段就少显示那一处,不让整次读取失败。

0.7.3 上看得到的代价:它不上报窗格里跑的命令,所以终端面包屑的第三段只能显示pane N,而不是程序名。这一段照样保留,因为它是进入 Select Pane 面板的唯一入口。

这份指南请用 SSH,不要用 Mosh。Mosh 传的是渲染后的屏幕差分,行的边界会被打乱,所以它承载不了 herdr 的帧协议,tmux -CC 走不了 Mosh 也是同一个原因。用 Mosh 时,herdr 跑的是它自己的全屏 TUI,Moshpit 只负责显示,在手机上 herdr 的侧栏大约要占掉三分之一的屏宽。控制面的各个面板仍然可用,它们走的是另一条 SSH 连接。

第三步

起 Claude Code

两种方式。在已有的窗格里直接跑,或者让 Moshpit 新开一个独立的 git worktree,把它起在里面。后一种才是在手机上真正值得用的。

在已有的窗格里跑

不用学新东西

  • 在 herdr 的窗格里输入 claude,这一步就完了
  • herdr 通过读自己窗格的屏幕内容认出它
  • 不用配置,也不用多记什么

New Agent Task

一个 worktree,一个工作区,一个智能体

  • 一张表单,入口在首页的 AGENTS 栏
  • 在主机上新开一个 git worktree,把 Claude Code 起在里面
  • 你原来的工作目录一点不动
  • 只有 herdr 有,tmux 没有对应功能

两种方式下,herdr 都是根据屏幕内容推断出这是个智能体。想让 Claude Code 主动上报而不是靠推断,在主机上跑一次 herdr integration install claude,之后它会自己汇报状态。这样更准确,但不是必需的,这份指南里的一切,不装它也成立。

表单的每一项

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

  • Repo:一个下拉菜单。Moshpit 从两处同时找候选仓库:每个已打开窗格的当前目录,用git rev-parse --show-toplevel 找到仓库根;再按修改时间扫一遍$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…。大仓库检出要几十秒,一个一直不变的按钮看起来像坏了。

New Agent Task 表单:仓库 payments-api,分支 fix-webhook-retry,智能体是 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、一个新的工作区、一个已经位于这个检出目录里并且获得焦点的窗格。第二步故意用 pane run 而不是 agent start:agent start --workspace 不继承 worktree 的目录,新窗格会落在错误的位置。pane run 输入的就是你自己会敲的那一行,而且你看得见它敲进去。

你首先会看到什么

真机验证那一轮:工作区出现了,当前目录是~/.herdr/worktrees/…/fix-scroll-jump,git worktree list 确认分支确实建好了,Claude Code 已经启动,停在信任目录的确认提示上。herdr 把这个窗格标成 agent: claude / status: blocked,首页的 Agents 栏随即显示AGENTS 1 · NEEDS YOU。这第一个提示,就是第四步里你要回答的那个。

用完之后怎么清理

在首页长按那个工作区 → Remove Worktree。只有真正是 linked worktree 的工作区才有这一项。第一条命令从不带 --force:

  • Remove the worktree for “…”?,说明是 “Deletes the branch checkout under ~/.herdr/worktrees. payments-api itself is untouched.”
  • 只有 herdr 因为 worktree 里有未提交的改动而拒绝时,才会弹出第二个对话框:“…” has uncommitted changes,说明是 “Those changes exist nowhere else. Removing the worktree throws them away.” 按钮是 Delete anyway 和 Keep it。只有你明确点了前者,命令才会加上 --force。

这一步的限制,直说。

worktree 建在 ~/.herdr/worktrees/<repo>/<branch> 下,不在仓库旁边,这一版也没有自定义路径的选项,那不是值得在手机上打字的东西。大仓库检出要几十秒。在有未提交改动的仓库上执行 git worktree add 应该没问题,但还没有验证过。

智能体的启动参数不替你选。manifest 里 claude 的默认命令是什么,跑的就是什么。--dangerously-skip-permissions 这类参数,得你自己在窗格里敲。

herdr 只看得到自己窗格里的智能体。你在同一台机器的 Terminal.app 里起的claude 不会出现,herdr 是运行时,不是进程扫描器。这条真的踩过:五个 herdr 窗格都空着,另一个终端里的 claude 跑得正忙,Agents 栏如实显示“Nothing running — start a task to isolate one”,而用户确信有东西在跑。

没在真机上验证的一处:长按弹出的菜单本身。自动化工具驱动不了 SwiftUI 的长按,所以「菜单 → 对话框 → 对话框」这一段只有编译和单元测试保证,没有真机录像。

第四步

人在哪里,都能回答权限确认

Claude Code 停下来,问能不能执行某条命令。琥珀色在这个网站上只有一个含义:有智能体在等人。它出现在这里,是一条能送到锁着的手机上的通知。

iPhone 锁屏:一条 Moshpit 的时效性通知,claude 请求在 m1-pro 上使用 Bash

锁屏上的问题 标题是智能体的名字,正文是它想做什么、在哪台机器上:Claude needs your permission to use Bash — m1-pro · pit。同一台主机上有几个智能体一起等,就合成一张卡:claude +2。

点一下 直接进入提问的那个窗格。在真实的终端里读完提示,用你在电脑前会按的那几个键回答。

锁屏上没有 Allow 按钮,是故意的 这个 App 以前有过锁屏按钮,作用是往窗格里盲发一个按键。没读过就批准,和「随时能读到」这个 App 的全部价值相悖,所以拿掉了。

App 没在运行,通知怎么到手机上

被系统挂起的 iPhone 做不到的事,由你的主机来做。给窗格盖状态戳的那套 hooks,会把 attention 和 done 两种状态(从不包括 working)交给一个小发送脚本 ~/.moshpit/moshpit-push.sh。脚本用一把只有你手机才有 的密钥加密通知,把密文交给 Moshpit 的中转服务器,再由 Apple 送达。两边都读不到一个字: 智能体名字、命令、问题本身,都在你手机的通知扩展里解密,锁屏上也一样。第一次给某台主机开通知时自动完成配对,需要你确认的只有第一次安装 hooks。

适用范围,说清楚。发送脚本挂在智能体的 hooks 上,而 hooks 盖章的对象是tmux 窗格。所以「App 关着也能收到推送」这条路,需要智能体跑在装了 hooks 的 tmux 里。纯 herdr 窗格上,herdr 自带的 agent_status 只在 Moshpit 运行时驱动灵动岛和通知,App 一关,推送就停了。如果你要的正是关着 App 也能被叫醒,目前请把 Claude Code 跑在 tmux 窗格里。

让它安静的四条规则

事事通知等于没有通知,所以每次打扰都得有理由:

规则意思
先等 30 秒 问题出现后 30 秒内你在电脑前顺手答掉了,任何手机都不会收到通知。
每台主机一张卡 所有等待中的智能体合成一张汇总卡,比如“claude +2”。只有从没人等到有人等的那一刻会响铃,并且可以穿透专注模式。之后的变化只是静默更新这张卡。
跑满 3 分钟才有完成提示音 一轮跑了三分钟以上,结束时才会响一声。几十秒的短回合静默进列表,不亮屏。
停着的智能体保持安静 你自己留在提示符上的智能体,它的空闲提醒不会点亮任何东西。放了一天的陈旧「需要你」也会自动变成已完成。

小地方也一样诚实:在 App 里看过的提示就算已读。重连或重新打开 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 这一行。

Notifications 和 Live Activity 都关掉,App 就什么都不监视。开关下面的 Set up this host 一行显示当前主机的状态:哪些智能体装了 hooks,推送脚本在不在,这台手机持有哪些配对。安装本身在连接时自动完成。

一点现状要说在前面。锁屏通知的正文是 hooks 从智能体那里原样取来的,所以 Claude needs your permission to use Bash 这类句子通常是英文。 “claude +2” 这种计数和语言无关。这一层跟着智能体走,不跟手机的语言设置走。

灵动岛显示琥珀色圆点和感叹号:有智能体在等你

解锁时 · 需要你 一个智能体卡住时是琥珀色感叹号,多个时显示数量。灵动岛里只放得下一个,展开后的 Switch 按钮可以切到其他智能体。

灵动岛显示青色圆点和走动的计时器:智能体正在工作

解锁时 · 工作中 Claude Code 运行时是青色加一个走动的计时器。正在思考的智能体和卡住的智能体,一眼就能分开。

该说清楚的部分

App 退到后台之后会怎样

依赖上面这些之前,先读这一段。现在补上这个缺口的是端到端加密推送,它补不上的部分也写在这里。

iOS 会挂起连接,实时显示随之暂停

iOS 挂起 App 之后,两秒一次的轮询和 SSH 拉取都停了。能跨过这个缺口的,是第四步讲的推送:主机上的 hooks 自己发现 attention 和 done,自己把加密后的通知发出来。不需要会话在线,手机锁着、App 关着都行。

跨不过去的是实时活动。它显示的是 App 最后一次看到的状态,App 被挂起后没有东西再更新它。真机实测:Moshpit 退到后台后,把一个窗格从 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

实时活动有一条 120 秒的过期线,也就是漏掉两次轮询。到时它会如实显示「已暂停」,而不是一个冻住的「working」。主屏幕小组件用 180 秒,过期后把所有状态点变灰。只要第四步的发送脚本装好了,推送通知不受这些影响,照常送达。

即使在前台,外部的变化也会有延迟

  • herdr 控制面的轮询在有变化时每 2 秒一次,连续三次读到相同结果后放缓到8 秒。任何变化,或者你下拉刷新一次,都会立刻拉回 2 秒。
  • 你在 App 里做的操作会立刻刷新,每次修改后都跟着一次重读。在别处发生的事,比如你的电脑、另一个客户端、智能体自己改了状态,最多要8 秒才会显示出来。
  • 这也是为什么持续时间只显示到分钟,now、2m、1h 12m,从不显示秒。数据本身最多有 8 秒的误差,显示到秒就是在宣称一个不存在的精度。
  • herdr 的 socket API 有事件订阅,但它的命令行没有提供 subscribe 子命令,手机也打不开你主机上的 Unix socket。所以目前只能轮询。

两个 Moshpit 连同一个窗格会互相抢

herdr 的直接连接是按窗格独占的。Moshpit 必须带--takeover 去连,因为每次重连都会撞上自己残留的旧连接。于是两台手机连同一个窗格时,会每两秒左右互相抢一次。这是独占式直连的固有行为,客户端改不了。

Moshpit 的应对是:30 秒内被莫名断开 3 次,就暂停 30 秒,并显示一条提示“Another client is using this pane — retrying shortly”,画面重新刷出来后自动消失。电脑上跑 herdr 自己的 TUI 不占连接名额,所以电脑加手机应该没问题,但这个组合还没有测过。

零碎问题

读完这份指南常见的几个问题

必须用 herdr 吗?

不必。用 tmux 的话,在 Settings → NOTIFICATIONS → Install agent hooks 装一次 hooks。这是一条命令,把 Claude Code 的 UserPromptSubmit、PreToolUse、Notification 和 Stop 四个 hook 注册好,让它们把状态盖到 tmux 窗格上。它会先备份~/.claude/settings.json,永远以 0 退出,所以不会挡住智能体,重复运行也会自动去重。不装 hooks,Moshpit 就退回去读窗格输出和终端响铃,那只是猜测,文档里也这么写。首页的 Agents 栏只有 herdr 才有。

Moshpit 关着的时候能叫醒我吗?

能,推送就是为这个做的。主机上的 hooks 用一把只有你手机才有的密钥加密通知,经 Moshpit 的中转服务器发出,手机上的通知扩展在锁屏上解密。App 关着、手机锁着都行。第四步里的适用范围同样成立:发送脚本挂在智能体的 hooks 上,hooks 盖章的是 tmux 窗格,在乎这一点就把智能体跑在 tmux 里。四条安静规则(等 30 秒、每台主机一张卡、跑满三分钟才有完成提示音、停着的智能体不响)见智能体通知的完整链路。

Mosh 呢?

想让 shell 撑过 Wi-Fi 切到 5G,Mosh 是对的选择,但它承载不了 herdr 的帧协议,也承载不了 tmux -CC。用 Mosh 时 herdr 在终端里跑自己的 TUI,侧栏要占掉手机大约三分之一的宽度。控制面的各个面板仍然可用,它们走另一条 SSH 连接。这份指南请用 SSH。详见 Mosh 与漫游。

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

几乎可以肯定它不在 herdr 的窗格里。herdr 靠观察自己的窗格识别智能体,在 Terminal.app 或另一个 SSH 会话里起的 claude 它看不见。用 New Agent Task 起,或者在 herdr 的窗格里跑。herdr 0.7.3 上还有一层原因:它不上报智能体名字,所以空闲的智能体根本不会出现在列表里。

Moshpit 会读我的代码、prompt 或别的东西吗?

没有 Moshpit 账号,没有数据统计,终端流量只去你自己添加的服务器。Moshpit 运营的只有推送中转服务器这一样东西,它传的是自己解不开的密文,通知在你的主机上用只有你手机才有的密钥加密。唯一离开窗格的内容,是 hooks 取到的一行短标题: 智能体正在跑什么或问什么,最多 80 个字符。它全程加密,最后显示在你自己的锁屏上。

接下来看什么?

连接与密钥讲完整的表单和主机密钥的处理方式,用 herdr 讲术语和快捷键,智能体通知的完整链路 把通知和实时活动这条链讲透。连不上或者行为异常,从排障开始。

接着看在任何地方连上你的机器 · 回到指南