用 peekaboo + System Events 可靠地操控 macOS 原生应用与手机 App(搜索、点击、播放、调音量、点外卖、点咖啡、下单、支付等)。核心是「归一化窗口几何 + 优先键盘/菜单、坐标只兜底、点前自检」。覆盖:网易云音乐等桌面软件;以及经 iPhone 镜像操控 iOS App——点外卖/点咖啡/美团外卖下单付款(profile: meituan-waimai-ios-mirror)、微信收发消息(profile: wechat-ios-mirror)等防截屏/反自动化的手机 App。当任务是操控某个本地 App 或手机 App 界面(点外卖、点咖啡、美团、微信、各种桌面/手机软件)时【必须先调用本技能并读对应 profile,按归一化+grab.sh截图+cliclick 那套来,别直接抓 peekaboo MCP 自由发挥、别用 peekaboo see/image(本机 bridge error)】。不适用于浏览器内的事(走 Playwright MCP,且别去开美团网页版 waimai.meituan.com——要登录、付不了款)。
Resources
8Install
npx skillscat add bettersilvanus849/inner-coordinates Install via the SkillsCat registry.
screen-pilot — 桌面界面操控
目标:少截图、不靠会漂移的绝对坐标、不点错按钮。把一个 App「学一次」,之后照 profile 精准复用。
我们其实在替这些不暴露可访问性树的 App 从外面反向重建一张交互地图:第一次睁着眼学(慢),之后照这张「内心的坐标」图走(快)。
坐标是消耗品会过期,真正的资产是这套会学、会验、会自愈的回路。库(profiles/)越用越大。
术语:内心链路(inner chain)。 指「学好之后,凭已记/已算的坐标,胸有成竹地一口气把一串动作执行完,中途不截图」。
本文里凡说「跑内心链路 / 直点 / 直发 / 直做 / 闭眼快做」都是这个意思——不是"盲"、不是冒险,是照内心那张图走。只在卡点(算不出的内容相关步、或链路断了)才睁眼截图。
渠道选择(step 0:先判断,再让用户定)
本 skill 有两条渠道,动手前先定走哪条:
- (A) 直接操控 Mac 原生窗口(如网易云音乐、各种桌面软件)。更快、无镜像延迟。
- (B) 经 iPhone 镜像操控 iOS App(如微信、美团外卖)。动作发生在手机上。
判断规则:
- 该 App 有能用的 Mac 版、内容能
screencapture、不反自动化 → 走 A。 - Mac 版防截屏/反自动化(微信),或只有 iOS 版/小程序(美团),或用户账号数据在手机上 → 走 B。
- 两条都可行时:给出判断+推荐,然后一句话让用户拍板走哪条(如「这个我能用 Mac 版,也能用你手机镜像,走哪个?」)。别替用户默选,也别为这个反复纠结——问一句即可。
生命周期(每次要操控某个 App 都走这条)
- 认出:查
profiles/_index.json有没有这个 App。 - 没学过 → 问用户「要不要花 ~30s 学一次?学完以后就秒级」。同意 → 进「睁眼学」。不同意 → 当场临时操作不入库。
- 学过 → 跑
lib/normalize.sh <进程> [W H]拿指纹:- 几何对齐(窗口被挪/拉也被它弹回固定矩形,缓存坐标复活)。
- 核版本:和 profile 的
learnedVersion一致 → 放心闭眼飞快做;不一致 = 可能改版 → 点前更要自检,错了就把那个元素重学、更新 profile。
- 闭眼快做(exploit):normalize → 优先快捷键 → 菜单 → 缓存坐标(相对偏移+窗口position) → 每个动作后验证。
- 自愈:任一验证失败 = 地图过期 → 退回「睁眼学」补那一处 → 写回 profile + 更新
_index.json的 version。
「睁眼学」= 截图定位元素 → 记成相对窗口原点的偏移 + 记下达成它的最稳方式(快捷键/菜单/坐标) → 写进该 App 的 profile。
「内心 figma」原则(顺手记全屏):你为了点一个键,已经截图看清了整屏。那就别只记那一个键——把这一屏所有可见功能的位置都顺手记进 profile,包括这次没点的(标题、tab、分类项、其它按钮、列表结构)。代价几乎为零(图已在眼前),收益是下次到同一屏不必再裁图找,脑内直接定位任意功能。
- 边界:记你确实路过的每一屏的全部可见键;但别为没用上的功能专门去翻新页面(增量长,按需访问新屏)。两者不矛盾:访问到的屏记满,不刻意扩张访问范围。
- 坐标随滚动/内容浮动的(列表项、分类项),记下来同时标注「随滚动变,用前截图重定位」;不动的骨架(搜索框、tab、底栏、键盘)记成稳定坐标。
- 从录制/截图学时,逐帧仔细看,别扫缩略图把看不清的当「噪声」跳过。 每一屏都是一个 state、每个跳转都是一条边,全画进图——没有噪声帧。(2026-06-21 实测教训:我把 montage 一扫,把「付款成功后怎么回首页」那几帧当噪声跳过、漏画了那条边,被用户抓到。)
学完就落脚本(最重要的产物)。 光在 profile 里记坐标 ≠ 能一次跑完——那等于把「重组链路 + 逐个点」留给模型,它一组装就触发「做一步看一眼」、一张张截图。真正的可执行产物是脚本:一段学好的稳定链路,落成一条 lib/<app>-<flow>.sh(含所有点击+等待,中文用 pbcopy 设剪贴板再 cliclick 真实 Cmd+V),参数化(店名/商品/规格…)。agent 跑这一条命令就走完整段,物理上没有「中间」可截,逐步观察的反射被绕开。
- 秘密边界:脚本跑到密码/付款这一步必须【停】,密码绝不写进脚本。 脚本只含公开的【路径】(点击坐标+等待);到支付密码这种只有用户能给的秘密,脚本停下,由 agent 问用户、把口令实时映射到密码键盘坐标逐位点入——键位在 profile(哪个数字在哪),数字本身不进任何脚本/文件。脚本=可共享的路径,密码=用完即弃的实时口令。
- 错了修脚本,不重学。 某个坐标漂了 → 改脚本里那一个数;这是脚本级自愈,比重跑一遍学习快得多。
- profile 仍要(记屏图/坑/哪稳哪变),但它是给脚本当说明书和坐标来源;脚本才是跑起来的东西。
- 见
lib/meituan-order.sh(参数化全链路)、lib/meituan-search.sh(搜索段)为范例。
两条铁律(实测踩过的坑,必须遵守)
- 任何键盘输入前,先
activate目标 App。 坐标点击(peekaboo click)不管焦点都落在目标窗口上,
但键盘事件(⌘A / ⌘V / 回车 / type)只发给当前最前台 App。不先聚焦,你的文字会打进终端/别处,
搜索框纹丝不动还以为成功了。每段键盘操作前都补一句:osascript -e 'tell application "PROC" to activate'(sleep 0.4)。 - 每一步动作后给页面渲染时间,再做下一步;关键步点前先自检。 搜索回车后结果要 ~2s 才渲染好,
太快点「单曲」标签会点在没加载完的页面上、落空。搜索→等 2s→切 tab→等→双击行→验证。
页面跳转/加载要等更久(实测:搜索结果 ~2s;进店/结算/收银台等大跳转 ~2.5-3.5s;支付结果页 ~3.5-4s)。
宁可慢半拍:跳转类操作后多等一会儿,再截图确认目标已出现,没出现就再等、别赌时机、别点空。
(用户明确反馈:这类流程跳转/等待很常见,等待纪律比手快更重要。)
自动执行原则(用户要自动化时,默认这样)
用户明确叫你「自动/帮我做完/一路做到X」时,他要的是自动化结果,不是被反复追问。默认姿势:
- 最快 = 一次性推理整条链路 + 只在卡点截图学习。 开始前可以(也最好)跟用户对齐一句「我要做什么、到哪一步停」;一旦跑起来,只在真正的卡点才停(算不出的内容相关步、或链路断了)。
- 别中途反复问。 能用已记/已算的坐标做的,绝不停下来问、绝不再学一遍。链路一旦确认,就默认一口气直达目标,多往前走几步,别一步一确认(那是胆小,不是稳)。
- 隐私/安全的停与不停:只有当用户可能忽略了某个隐私或安全后果时才停下提醒(如「这会真扣款/把隐私发出去,你确定?」);如果用户已经明确要这个结果,就别问——他知道自己在干嘛。花钱/不可逆的临门一脚仍可截一张图自检(看,不是问)。
- 唯一例外是支付密码这类必须由用户给的秘密:到那一步问用户要,不存、不猜。
定位一次 → 一口气跑内心链路(localize-then-batch,profile 已存在时的正确姿势)
核心执行模型:每一次都「进行到底」。 不是一步一停、一张张截图——那是第一次睁眼学的节奏。学过一遍、profile/脚本在手后,模型的默认行为应当是:
- 固定节点理想只有一个:开局定位——看镜像状态 + App 停在哪屏。这是唯一算不出来的(App 停在上次离开的地方)。定位完、复位到锚点(如首页),之后整条链路全是常量/可算的,一路跑到底、不再截图。
- 每次尝试都一路推理内心链路跑到底,中间不回头逐个确认。
- 某步还会漂 = 它还没被换成常量、或脚本还没修好,不是"该多截一张图"的理由。复盘修掉它:从当前屏反推「断在哪一步、为什么」→ 把那步换成常量 / 改脚本里那个坐标 → 从断点一次性再跑到底。绝不退回「逐步点」。
具体怎么走:
学过一遍、profile 在手后,别再「点一下截一下」——那是第一次睁眼学的节奏,慢且没必要。换成:
- 入口定位(截一张):App 常停在上次离开的那屏(不会复位)。进来先截一张,对照 profile 认出「现在停在哪屏」。
- 复位到锚点 或 就地接链路:要么先导航回规范起点(如美团点底部「首页」tab、或连点返回退到已知屏),从锚点起跑最稳;要么从当前屏直接接。
- 跑内心链路一串:链路里稳定骨架的坐标(搜索框、tab、底栏、固定按钮、密码键盘、规格弹窗里位置固定的选项)直接按 profile 坐标连续点,中途不截图。
核心判据(按置信度走):能算出来的链路就不截图;截图只在两种意外时跑——①操作疑似失误(点完该变没变、焦点可能丢了、价格/件数对不上预期)②碰到完全的新东西(profile 没记过的屏/弹窗/字段)。
- 即:下一步状态你能预测 → 直点、连发一批,不看;不能预测或结果不符预期 → 截一张再说。
- 内容相关的坐标(列表第几行、滚动后的分类、随行变的「选规格」按钮)本质就是「算不出来」,归入「新东西/需确认」,看一眼。
- 唯一即便有把握也值得截一张的例外:花钱/不可逆的临门一脚(确认支付、提交订单、删项)——一张图的保险,便宜。
原则:常量永远胜过变量。 能把「内容相关、会漂移」的步骤换成「固定锚点」就一定换——如美团店内用搜索框搜商品名(搜索框坐标固定、结果第一条稳定),别去赌随滚动浮动的分类/列表坐标。该处的「大胆」= 走确定路径,不是盲点一个记忆里的位置;盲点变量必漂。(2026-06-21 实测教训:进店没走店内搜索、去盲点分类栏,分类落到「冰爽冷饮」、选规格点中「玫瑰香椰冷萃」,整条链漂掉。)
边界处理(实测)
- 窗口被拉伸/挪动 → 一句 set bounds 复位。 normalize 不只开局用,每次复用缓存坐标前都先跑一遍,
几何就回到指纹。注意有些 App 有最小窗口尺寸(网易云 ~1056x752),所以归一化后回读实际几何。 - 冷启动可能吞输入。
open返回不等于能交互;先轮询到窗口出现(count windows ≥ 1),再小等一下。
但不要赌时机——动作后一律验证:搜索后确认结果渲染出来了,没有就说明还没就绪 → 等一下重发。
这条「验证+重试」自愈,不管机器快慢都稳。 - 快捷键优先于点坐标(前提仍是先 activate):能用 ⌘→/空格 这种就别去点按钮。
终极绕法:iPhone 镜像驱动 iOS App(对付防截屏/反自动化的 App,如微信)
某些 App 在 Mac 上既防截屏(截到空白)又反自动化(会风控)。绕法:在 iPhone 镜像窗口里操控手机上的那个 App——
动作发生在 iOS(App 只看到一次正常触摸,不触发 Mac 风控),而镜像窗口是普通 macOS 窗口,grab.sh 能截到内容、peekaboo 能点。两道防线一起绕开,已对微信全自动跑通。要点:
- 前提:iPhone 镜像配好;接力/Handoff 必开(Universal Clipboard 靠它把 Mac 剪贴板同步到 iOS,不开则镜像里粘贴是空的);
brew install cliclick。 - 截图/点击:
grab.sh "iPhone Mirroring"截图(手机屏可见)、peekaboo click点 —— 跟普通窗口一样。 - 任何输入前先清空输入框。 框里可能残留上次的字(搜过的词、历史),不清空就粘会变成「皮爷咖啡皮爷咖啡」。聚焦后先
cliclick kd:cmd t:a ku:cmd(Cmd+A 全选)→cliclick kp:delete(删),再粘。空框也无害。 - 输入中文:剪贴板 +
cliclick kd:cmd t:v ku:cmd(真实 Cmd+V)。peekaboo/osascript 发 Cmd+V 会被镜像把 Cmd 吃掉只剩 v —— 必须 cliclick 这种真实 CGEvent 才带得动修饰键。中文用pbcopy设剪贴板(避开 osascript 引号坑)。 - 发送:
cliclick kp:return(普通 Return)。绝不用 Cmd+Return —— 那会把输入框顶进多行模式,Return 变换行发不出。 - ★代聊就必挂轮询(铁律):只要还在代someone聊天,发完当前回复就立刻挂轮询盯新消息——别发完一条就停手等用户来催。 推荐形态是「列表扫描 + 白名单 + 退避」的自管循环(用定时器每轮唤醒自己):
- 列表扫描:每轮回到微信会话列表页(不死守一个会话),
grab.sh截一张看哪行有红色未读角标,命中就点进去回、回完退回列表。坐标见wechat-flowmap.json列表页节点(会话行 y、返回键)。点前必新截一张——公众号推送会把列表行序顶乱,凭上一张的行号点会点错(实测点成被顶上来的公众号)。 - 行位/对象拿不准时,别赌行坐标——搜全名。 点搜索框→粘联系人/群全名→点第一个联系人结果(固定坐标)必命中:微信极少重名,「最常使用/联系人」分区第一条就是本人。这是把「会漂的行坐标」换成「固定锚点(搜索框+第一结果)」,比数行点行稳得多(合「常量永远胜过变量」)。
- 白名单:只回【用户配置的名单】(联系人/群名存本地、不入库),名单外有红点也只看不回。
- 退避节奏:1 分钟一轮起;同档连续 3 轮没新消息就退一档(1m→5m→10m 封顶);任何时候有新消息→立刻打回 1 分钟(对话热就跟得紧)。
- 永不自动撤:只有用户【明确】说撤才停;别因为「像收尾了/几轮没消息」就自己判断着撤,也别每轮催问要不要撤。
- 每轮截图用之即弃(读完 rm,不留盘)。状态(档位/空轮数/定时器 id/上轮列表快照)存本地一个 state 文件,跨轮不丢。
- 列表扫描:每轮回到微信会话列表页(不死守一个会话),
- 详见
profiles/wechat-ios-mirror.json。
决策顺序(从稳到不稳,能用上面就别用下面)
- 键盘快捷键 —— 最稳,零坐标,布局变了也不怕。
- 菜单按名字点(System Events)—— 播放/暂停/音量/上一首下一首这类几乎都在菜单里。
菜单项的名字本身还能当状态验证(如「控制」菜单首项是「播放」=已暂停,是「暂停」=正在播放)。 - a11y 树(
peekaboo see --app X)—— 能读到就按元素 role/label 拿实时坐标,免缓存。 - 缓存坐标(本 profile)—— 只在上面都不行(a11y 读不到的烂 App)时兜底,且只缓存不动的骨架
(搜索框、标签、列表第 N 行的位置),绝不缓存会变的内容(具体某首歌、某条消息)。点之前先自检。
归一化窗口(坐标可复现的前提)
不要用绿灯/全屏(全屏会进独立 Space + 隐藏菜单栏,坑)。用 System Events 把窗口摆到固定矩形,再回读实际几何当指纹:
osascript -e 'tell application "System Events" to tell process "PROC"
set frontmost to true
set position of front window to {0, 25}
set size of front window to {1440, 900}
end tell'
# 回读(有些 App 有最大/最小限制,记实际生效的)
osascript -e 'tell application "System Events" to tell process "PROC" to get {position, size} of front window'指纹 = {bundleId, 进程名, 实际 position, 实际 size, 屏幕缩放}。复用缓存前先核指纹,任一不符就重学(换分辨率/App 更新会让坐标整体偏,闭眼点很危险)。
缓存坐标怎么存怎么用
- 存相对窗口左上角的偏移,不存绝对屏幕坐标:
dx = 元素x - 窗口x,dy = 元素y - 窗口y。 - 用时读实时窗口 position,
点击点 = (窗口x + dx, 窗口y + dy)。窗口被挪了也不错位。
学一个新元素坐标(贴左上角 + 只截窗口那块,别全屏+裁剪)
把窗口 normalize 到贴左上角的固定矩形(set position {0,25} → 回读实际 y,菜单栏高度被回读自动吸收,这台是 30)。
然后只截窗口那块,不全屏(小 3 倍、快、还省掉裁剪):
lib/grab.sh NeteaseMusic /tmp/w.png # = screencapture -x -R <窗口实际几何>坐标换算变得极简:截图像素 (px,py) ÷ 缩放(2) = 窗口相对坐标(截图原点就是窗口原点,不用算全屏偏移)。
存进 profile 的就是这个相对偏移;用时 绝对 = 窗口position + 偏移。
peekaboo 的
image在本机 beta 版常报 bridge error,所以用系统screencapture -R。
防截屏的 App(微信)用 grab.sh 照样空白 —— 那种靠用户全屏截图(用户自己截能看到内容),我按固定窗口几何反算坐标。
点前自检 + 点后验证
- 点列表项前:先截一张确认「第 1 行确实是我要的那个」(标题文字对不对),再双击。
- 动作后:用菜单项名 / 一张小截图确认状态真的变了(如播放→暂停)。不对就重来,别假设成功。
本机注意事项(实测)
peekaboo image/see/menu list在这台机的 3.0.0-beta3 上对部分 App 报PeekabooBridge ... error 1—— 改用系统screencapture截图、用 System Events 读/点菜单。peekaboo click --coords "x,y"(加--double双击)工作正常,坐标是逻辑点(和 AppleScript 一致)。- 中文输入走剪贴板,别直接 type(避开输入法):
osascript -e 'set the clipboard to "中文"'然后peekaboo hotkey --keys cmd,v。
profiles/
每个 App 一个 JSON,见 profiles/neteasemusic.json(已实测跑通:搜索→单曲 tab→双击第一首播放,
播放/暂停/音量全走「控制」菜单)。新 App 照它的结构学一份。