本文作者:AYi(@AYi_AInotes)。版权归作者所有,未经授权禁止转载。
折腾了三天终于跑通了:现在 Codex /Claude code/Grok biuld/Cursor等agent 不用越狱就能直接控制我锁着的 iPhone,开 APP、点按钮、输文字、读屏幕全能干,手机刷推,刷朋友圈、发朋友圈都可以,真的牛逼,更狠的是我合盖睡觉的时候,Agent 自己在云端手机里跑任务,醒了邮箱里已经躺好了整理完的结果。
想让 AI 替你碰手机、跑重复活的,这一篇照着做就行,一篇文章把两套方案全写透、讲透:
1️⃣本地 Phone Harness 怎么装、权限坑怎么避、所有命令直接抄;
2️⃣云端异步任务怎么设、怎么锁死权限防 AI 乱点,包括我每天跑的岗位早报 Prompt 也全放出来了。

今早 8 点半我睁开眼,邮箱里已经躺着 12 个新出的 AI 岗位:公司、是否远程、推荐理由、申请链接排得整整齐齐。
我昨晚合电脑前只设了个定时任务,没定闹钟爬起来刷 LinkedIn,也没雇人帮我盯,甚至电脑一整夜都是合着的。
是 AI 自己控制手机,趁我睡觉的时候翻完了所有新岗位整理好发我的 —— 不用越狱,也不用装奇怪的插件,想让 AI 替你刷招聘、整理信息、跑重复任务的,直接照着做就行。
我昨晚合盖前只做了一件事:设了个每天早上八点的 Routine。

发这封早报的不是 Phone Harness,Phone Harness 发不出来——它只能在我 Mac 醒着、手机锁着、人坐在旁边的时候干活。而这封邮件发出来的时候,我还在睡觉。
Phone Harness 干的是另一半的活,它工作时的真实画面是:
Agent 在终端里敲命令,Mac 上那个手机大小的窗口自己在点,真机锁着躺在桌子另一头。你要是中途拿起来解锁,会话立刻断,窗口变成 "iPhone in Use"。
它把 iPhone 镜像窗口当成唯一的传输层:截图看屏幕,用 Apple Vision 做 OCR 拿到「文字 + 坐标」,再用 HID 级的 CGEvent 去点、拖、输入,点完再截一张确认。没有控件树,截图就是真相。
装它不用越狱,也不用 Xcode,甚至不用 WebDriverAgent。
我把安装、权限和 --doctor 在 Codex 里跑了一遍,安装花不了多少实践,最花时间的权限后面单独讲。
这篇分两部分,前半段用 Phone Harness 把 Agent 跑到能看见、能点、能写回手机;后半段就是开头那条早报是怎么来的,也说清 Airtap 不适合干什么。前一层是 Driver,后一层是 Runtime,缺的东西完全不一样。
命令、权限雷区、质检 Prompt 和 Routine 文案都在正文里,可以直接抄作业。只想看早报怎么设的,直接跳第六节。
换 Cursor 或 Claude Code 流程一样,只有一处要跟着改:权限勾在实际跑 Agent 的那个 App 上。
一、先过环境:门槛都在苹果那一侧
推荐:先过环境,下面任何一条不满足,后面全部无效,而且多半不是项目的问题。

- Mac 有 Apple Silicon 或 T2 芯片(2018 年后大部分 Mac 都行,更老的纯 Intel 不支持 iPhone Mirroring)
- macOS Sequoia 15+ / iOS 18+
- 两台设备同一个 Apple Account,开着双重认证
- 蓝牙和 Wi-Fi 都开,iPhone 在 Mac 十米内
- Python 3.12+
- 首次配对要先解锁、在手机上确认;配对完成后,会话期间保持锁定
sw_vers
python3 --version
最后那条容易被当成安装第一步,其实是会话保持的条件。
配对当天先解锁完成确认,之后把手机放到自己随手拿不到的地方。
不然你下意识拿起来看条消息,镜像断了,你还得反复折腾权限。
二、装:克隆、依赖、PATH,再注册成 skill

官方推荐装在 ~/.phone-harness,和 ~/.nvm 一样当固定家目录,Agent 才找得到。
系统 Python 受 PEP 668 限制的话(用 Homebrew 的很常见),走 venv 更干净:
下面这一段要在同一个终端里连续跑,中间不要新开窗口——source 只对当前这个壳有效:
打开终端,复制以下命令行,粘贴,回车
git clone https://github.com/ShawnPana/phone-harness.git ~/.phone-harness
cd ~/.phone-harness
python3 -m venv .venv
source .venv/bin/activate
pip install pyobjc-framework-Cocoa pyobjc-framework-Quartz \
pyobjc-framework-Vision pyobjc-framework-ApplicationServices
pip install -e . --no-depsAppKit 在较新 pyobjc 里随 Cocoa 提供;ApplicationServices 是 --doctor 需要的。
装上了还不够,source .venv/bin/activate 只对当前这个壳有效,Cursor 或 Codex 新开的终端经常不在 venv 里,于是报 phone-harness: command not found。你会以为没装上,其实是装上了但那个会话找不到。再跑这一段,把命令链到 PATH:
mkdir -p ~/.local/bin
ln -sf ~/.phone-harness/.venv/bin/phone-harness ~/.local/bin/phone-harness装完先看说明和入口:
cat install.md
phone-harness --help
# 或在仓库里:./phone-harness --help
grep "^def " src/phone_harness/helpers.py最后那两行值得单独说,source .venv/bin/activate 只对当前这个壳有效,而 Cursor 或 Codex 新开的终端经常不在 venv 里,于是报 phone-harness: command not found。你会以为没装上,其实是装上了但那个会话找不到,软链就是为这个准备的。
装完先看说明和入口:
cat install.md
phone-harness --help
# 或在仓库里:./phone-harness --help
grep "^def " src/phone_harness/helpers.py
本文用到的 screen_info()、ocr()、open_app()、tap_text()、type_text()、wait_stable()、connection_state(),都以你机器上 helpers.py 的实际输出为准。
想让 Agent 自己会捡起这套能力,就把 skill 注册进去。官方 Fast Path 只写了 Claude Code 和 Codex,用 Cursor 的自己补一份:
mkdir -p "${CODEX_HOME:-$HOME/.codex}/skills/phone-harness"
phone-harness skill > "${CODEX_HOME:-$HOME/.codex}/skills/phone-harness/SKILL.md"
mkdir -p ~/.claude/skills/phone-harness
phone-harness skill > ~/.claude/skills/phone-harness/SKILL.md
mkdir -p ~/.cursor/skills/phone-harness
phone-harness skill > ~/.cursor/skills/phone-harness/SKILL.md拉过仓库更新之后,这几行再跑一次,否则 Agent 手里还是旧版说明。
三、交给 Codex:它能做什么,不能做什么

Phone Harness 自带 install.md 和 SKILL.md,等于把操作说明写给 Agent 看的,所以在 Codex 里可以直接派活:
读 ~/.phone-harness/install.md 和 SKILL.md,
帮我完成安装和自检:装依赖、配好 PATH、注册 phone-harness skill、
跑 phone-harness --doctor。
遇到需要我在系统设置里手动授权的步骤,停下来告诉我具体点哪里,不要跳过。
任何需要点 Connect / 解锁 / 锁屏手机的步骤,也停下来等我。它适合接手的是这些:读文档、装依赖、配路径、跑自检、读报错、改脚本、把能力注册成 skill。都是重复劳动,交出去很值。
替不了你的有两步:
第一步,打开「iPhone 镜像」完成配对。首次在手机上确认,之后锁屏放下。Harness 不会替你点 Connect,也不该轮询傻等——官方写得很死:重连是物理动作,只有人能做。
第二步,到「系统设置 → 隐私与安全性」,把跑 Codex 的那个 App 在「辅助功能」和「屏幕录制」两处都勾上。
open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
open "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture"第二步特别容易勾错,我单独说清楚。你要勾的不是「某个叫 Terminal 的东西」,是实际跑 Codex 的那个进程所在的 App。
在 Cursor 集成终端里起,就勾 Cursor;在 iTerm 里起,就勾 iTerm;
用独立终端 App 起,就勾那个 App。勾了 Terminal 却在别的 App 里跑,一律无效。
屏幕录制改完通常要完全退出并重开这个 App 才生效,辅助功能一般立刻生效。
勾错的症状很有误导性:截图全黑,或者点击毫无反应。
看起来像镜像没连上、像 OCR 坏了、像项目有 bug,实际只是权限勾在了另一个 App 上。
Agent 自己也判断不出来——它只能看到「截图是空的」。
官方还提醒一句:--doctor 目前只查已知的两项权限。新机器第一次点、打字、截图时,系统可能再弹别的授权。doctor 过了但动作静默失败,先看有没有新弹窗。

授权完成后跑自检:
phone-harness --doctor--doctor 是按梯子往上爬的,先修第一个 FAIL,后面的检查往往依赖前面的结果:
- pyobjc(Quartz / Vision / AppKit)
- 辅助功能
- 屏幕录制
- iPhone Mirroring 是否安装
- App 是否在跑(未开不一定致命,首次使用可能自启)
- 是否找到镜像窗口
- 窗口截图是否有内容(空白多半是屏幕录制没重启)
- Vision OCR 是否能认出文字框
跑不通时的对应关系,我按撞到的频率排了一下:
| 现象 | 优先查 |
| :--- | :--- |
| 截图全黑 / capture 很小 | 屏幕录制权限;改完是否完全重开过那个 App |
| window not found,但镜像窗口明明在前台 | 中文系统窗口名是「iPhone镜像」(无空格);新版 helpers 已兼容中英文,旧脚本只认英文。也查屏幕录制权限 |
| window not found,窗口也不见 | 镜像没配对、手机不在范围、还停在连接页;或运行进程没有屏幕录制权限 |
| taps 没反应 | 辅助功能没给对 App;或镜像窗口丢了前台焦点 |
| doctor 全过,动作仍静默失败 | 系统另有权限弹窗;或会话其实已经是 "iPhone in Use" |装过的人里,卡权限比卡 Python 常见得多,这一步磨清楚,后面少浪费一小时。
四、三个 Demo:从只读到写入

这个顺序是刻意放的的,要先确认能看见,再确认能操作,最后确认能闭环。
三步都可以让 Agent 执行,你只看结果就行。
命令可以照抄,图以你自己跑出来的为准。
看窗口信息
以下命令行复制到终端:
phone-harness <<'PY'
print(screen_info())
PY正常会看到窗口 bounds、是否前台、截图像素尺寸。screen_info() 有输出,说明镜像窗口至少被找到并截到了。

OCR 读屏
phone-harness <<'PY'
items = ocr()
print([(o["text"], o.get("x"), o.get("y"), round(o.get("confidence", 0), 2)) for o in items][:20])
PY没有控件树的时候,OCR 坐标就是穷人版控件树,Codex 后面所有「点哪里」,多半是从这份列表里挑出来的。
读结果时留意三件事:
中文界面会不会拆词认错;
小字号、深色背景下 confidence 会不会掉;
同一个按钮有没有被拆成两段,tap_text 失败的时候,先跑一次 ocr(),照抄它实际读出来的那串字,不要照你以为的文案点。


写入一条备忘录
中文系统先试系统显示名,不行再试英文:
phone-harness <<'PY'
ensure_mirroring()
open_app("备忘录") # 不行就试 "Notes"
wait_stable()
tap_text("新建备忘录") # 或 "New Note" / OCR 实际读到的字
wait_stable()
type_text("hello from phone-harness")
wait_stable()
print([o["text"] for o in ocr()][:12])
PY这一步跑通,本地这半边就算闭环了:终端里敲的字,出现在那台锁着的手机上。
但它能做到的极限,也就到不休眠 Mac 为止。
顺便分享一个真实用法:
我让 Agent 接管 iPhone 去理备忘录——截图里是 ChatGPT Work,同一套镜像能力换个 Agent 照样使。它数出 iCloud 里一共 878 条,没一上来就大改,先建议从置顶那几条动手。光是把「到底有多少、该从哪下手」理清楚,我自己手动至少得搭进去两个星期。

几条能省时间的经验,一部分来自官方说明,一部分是这类 harness 几乎都会踩的:
- AppleScript 的 click at 对镜像窗口无效——里面没有可访问性树,只有视频流
- 输入时镜像窗口必须在最前台,执行中途别抢焦点,否则字会被吞一半
- type_text 之前先点进输入框,等键盘起来再打
- 长列表用滚轮或 scroll_* 系列,比慢拖靠谱;慢拖在 iOS 列表上经常弹回去
- 主屏图标不要用 tap_text("天气"),点中的是标签不是图标,看起来像没点中。主屏用 tap_icon(agent helper),App 内的按钮和列表行才用 tap_text
- 坐标不要跨调用缓存,窗口位置会变,每次 ocr() / swipe() 都会重取 bounds
- 没有多指,捏合缩放做不了;需要 Face ID 或相机流程的场景也不在这套能力里
- DRM 视频区域截出来可能是黑的
会话中途断了,先查状态,别让 Agent 对着 Connect 按钮狂点:
phone-harness <<'PY'
print(connection_state()) # ready / blocked / no-window / not-running
PY本机这次 connection_state() 返回 ready,但窗口 UI 仍是「连接暂停」——状态字和你看见的画面可能不一致,以窗口画面为准。
blocked 通常是 "iPhone in Use" 或 "Lock your iPhone",把真机重新锁上,回镜像 App 确认恢复,再让 Agent 重试一次。
no-window 和 not-running 得人去打开并连上镜像。
Agent 侧的正确做法是停下来告诉你,轮询 Connect 只会烧时间。
日常更稳的循环不是把它当 GUI 连点器,而是:先读 SKILL.md 和 helpers.py 知道现成能力;用一小段 Python 描述任务;
每步走「操作 → wait_stable() → OCR 或截图验证」;
缺能力就写到 agent-workspace/agent_helpers.py,它会自动载入。
最后这条我觉得是这个项目设计上最漂亮的地方:不是人写好 SDK、Agent 来调,而是 Agent 边跑边往自己的工具箱里补件,下次调用直接可用。
SKILL.md 里还写了一条边界,我很赞同:能在 Mac 或网页上做完的事就别上手机。只有 iOS 独占 App、绑手机号或 2FA、或者你就是要看真机表现的时候,才值得动 Phone Harness。
五、这层 Driver 到哪为止

整套东西挂在一串前提上:Mac 醒着,Codex 会话还开着,手机锁着放在旁边。它没有内建调度器,不负责定时触发,也不会在会话断掉之后自动重连。状态放哪、几点跑、断了谁拉起来、结果怎么送回你——这些都不是它的工作。
所以我不会写「本地方案过时了」之类的,本地 Harness 把「控制一台真机有多难」摊开给你看了,这个认知很值。但如果你还要人离开现场任务继续,缺的不是更狠的点击,是一台能托管状态、能定时、能异步交付的 Runtime。
六、另一侧:让它在我不在的时候干活

开头那条早报,就是这一节用Airtap这个agent跑出来的。
Airtap 的边界和限额我写在第七节,大家可以对着判断。
Airtap 给的是一台独立、可持久的云端 Android。
App 由你自己在云机里登录,任务可以异步跑很多分钟,结果直接发到你的邮箱,还能设成 Routine 每天定时跑。
我选的长期任务是 LinkedIn 上「AI Agent / FDE / AI 应用岗」的每日早报。
标题里我只写了 FDE,因为它最能说明我想找什么样的活;实际搜的是四个词,FDE 只是其中一个。
选它其实有点功利哈哈,因为产出是一张能收藏的结构化岗位表,好坏我一眼能判断;它得在固定时间跑,而那个点我通常在睡觉;
早报迟到一天就贬值——它必须真的自动发生,不是我想起来才去跑一次。
这几条正好是本地 Harness 默认不提供的那一层。
先确认主要的入口,用 iPhone 的「信息」给 Airtap 发一句话,发这个美国号码就行,+1 (650) 248-0054,注意必须是下图这种紫色亮起,才说明是接通了,这一步不确认,后面所有交付失败你都会先误判成任务失败。

顺便说个我发现的小反差,它自己的 FAQ 里,第二条能力就是「用 Easy Apply 帮你投递」。我这次特意把它关掉了——能证明 Runtime,不需要动到我的求职资料。
然后在云机里自己登录 LinkedIn,先跑一次手工任务,成功了再设定时。质检 Prompt 我原样贴:
任务:在已登录的 LinkedIn 里,找「过去 24 小时」新出现或明显更新的岗位。
搜索关键词(依次执行,去重):
1) AI Agent
2) Forward Deployed Engineer OR FDE
3) AI Engineer
4) Applied AI
筛选偏好:
- Remote 或允许 remote 优先
- 英文岗位可
- 跳过明显的实习灌水帖(不确定就保留并标注)
输出格式(用一条 iMessage 或邮件发我):
- 日期:YYYY-MM-DD
- 字段:Job Title | Company | Location/Remote | Why relevant(≤15字) | Link
- 最多 8 条
- 没有新岗位就写「今日无新增」,并列出你检查过的搜索词
安全约束:
- 不要点击 Easy Apply
- 不要修改我的资料
- 不要给任何人发消息
- 遇到验证码 / 登录墙 / 安全验证:立刻停止,告诉我卡在哪一步约束比任务本身更重要。只搜索和汇总,不点 Easy Apply,不改资料,不代发私信,遇到验证码就停下来告诉你卡在哪。能证明 Runtime 就够了,不必为了更炸去承担误点风险。
手工质检只看四项:跑了多久才回(异步等几分钟是常态);返回几条,链接能不能点开;有没有误点、有没有撞验证码;「今日无新增」也算合格,前提是它列出了检查过的搜索词。

通过后再设成每天早上八点的 Routine,交付走邮箱:
请设为每日 Routine:
- 时间:每天早上 8:00(时区:Asia/Shanghai)
- 任务:同上「LinkedIn AI Agent/FDE 岗位早报」
- 交付:通过 iMessage 或邮件发摘要表
- 约束:不 Easy Apply、不改资料、不私信;遇验证码停止并通知我
- 若连续 2 天零结果:第 3 天额外扩大关键词「LLM Ops」「AI Platform」这是今天早上8点准时给我发的岗位早报,一共给了我12条岗位信息:



「人不在场」这个维度,验收咱们只看三件事:
1️⃣Mac 合着、人不在电脑前时它是否仍然触发;
2️⃣邮件时间戳是否落在你设定的窗口里;
3️⃣内容和手工那次是不是同一套字段和约束。
三件里有一件不稳,那就还是调度问题,不是「点得不够快」。
这就是我想对照的那一点,Codex、Claude Code 这类 Agent 加上 Phone Harness,让我在 Mac 前把 iPhone 变成可脚本化的设备,而「我睡着时 Airtap 在巡岗,早上把表发进邮箱」,是另一层的活。
七、谁该用哪个

| | Phone Harness | Airtap |
| :--- | :--- | :--- |
| 设备 | 你的 iPhone + Mac 镜像 | 云端 Android(你自己登录 App) |
| 人在不在场 | Mac 醒着、会话开着 | 合盖也能跑,可设每日 Routine |
| 交付 | 你盯着终端 | 邮件送到你邮箱(首次用 iMessage 开通) |
| 不适合 | 定时、托管、断线自愈 | 同一秒、零失误、硬闯验证码和登录墙 |Airtap 的边界一并写清楚:
适合注意力密集、容错尚可的任务;
异步等几分钟是常态;免费档有日限额;遇到登录墙和风控,它应该停下来通知你,这比硬闯重要。
Phone Harness 那侧更简单,它负责驱动不负责调度——Mac 合盖、会话关掉、手机被拿起来解锁,链路就停。
选型时问三个问题就够了:人在不在场;状态存在哪,是本机进程还是可持久环境;结果怎么交付,是你盯着终端,还是它自己发进你的邮箱。
全量安装说明、失败排查和更新日志看仓库的 install.md;这篇留下的是能直接抄的命令、权限雷区、会话恢复方式,以及一份可复用的长期任务 Prompt。
想自己试 Airtap,用 iMessage 发到 +1 (650) 248-0054,发前先确认是蓝气泡;号码和免费额度是否还有效,以官网当期说明为准。
来源
- Phone Harness:github.com/ShawnPana/phone-harness(install.md、SKILL.md、helpers.py、--doctor 阶梯与已知限制)
- Airtap 接入方式与免费档限额,以官网当期说明为准:airtap.ai/message-airtap