news 2026/11/6 3:28:28

terminal-mac Agent 技能实战:用 MCP 工具与 CLI 安全操控虚拟 Mac

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
terminal-mac Agent 技能实战:用 MCP 工具与 CLI 安全操控虚拟 Mac

【免费下载链接】terminal-mac

Entire MacOS running in your terminal.

项目地址:https://gitcode.com/gh_mirrors/te/terminal-mac
点击查看免费下载

本文以 terminal-mac 随包发布的 Agent 技能文档为主线,讲清楚一个 computer-use Agent 如何在一台独立的虚拟 macOS 上完成「观察 → 操作 → 复核」的闭环:优先通过 MCP 工具驱动,没有 MCP 时退回 CLI,并在遇到登录界面、系统权限弹窗等情形时把控制权交还给用户。读完后你将掌握各工具/命令的准确参数语义、坐标系统、失败判定方式,以及源码层面这些行为是如何被实现和约束的。

一、定位:为什么要一台"别人的 Mac"

terminal-mac在 Apple silicon 上通过 Apple Virtualization 运行一个真实的 macOS guest。辅助程序(helper)跑在 guest 内部,由它截取 guest 屏幕、投递输入事件,因此 Agent 的每一次点击、每一次键入都发生在虚拟机里,不会移动物理 Mac 的指针,也不会改写宿主机剪贴板。

这个设计直接决定了技能文档的三条基本假设:

  1. guest 是一台独立的 Mac,有自己的应用、文件、账户和权限,不继承宿主机正在运行的应用与已登录会话;
  2. 宿主机侧的屏幕与输入 API不参与Agent 操作,没有"回退到宿主机"的路径;
  3. 因此 guest 未登录、helper 未运行、权限未授予时,Agent 只能报告状态并请用户处理,而不能绕过。

技能文档的适用场景写得很明确:需要真实 macOS GUI 的任务——测试 Mac 应用、使用某个网站或桌面应用、以及任何希望被沙箱化的 computer-use 任务。运行前提(Apple silicon、macOS 14 或更高、首次运行需联网下载恢复镜像)在 README.md 中有完整说明。

二、第一优先级:使用 MCP 工具而不是拼命令

如果当前环境已经暴露了来自terminal-macMCP server 的screenshot、click、type、key等工具,技能文档要求优先使用它们。原因不只是调用更短:MCP 这一层还额外负责把 guest 返回的 PNG 压缩成更小的图片、在动作后追加一次截图、以及把"没有登录会话"这类超时翻译成可读的处置建议。

2.1 四步标准循环

技能文档给出的顺序是固定的:

  1. status:确认 VM 正在运行,且 guest 的两项权限都是true;
  2. screenshot:之后你传给其他工具的所有 x/y,都是这张图里的像素,原点在左上角;
  3. 执行动作:click(clicks: 2双击、button: "right"右键)、move、drag、scroll(负dy向下滚动)、type(字面文本)或key(return、tab、escape、cmd+space、cmd+c、cmd+shift+4等);
  4. 再看一次:给动作传screenshot: true,或单独调用screenshot,然后才决定下一步。

2.2 工具清单与参数边界

下表是技能文档提到的那组工具的完整参数边界,取值范围来自 Sources/TerminalMac/AgentServer.swift 中的 JSON Schema:

工具必填参数可选参数取值约束语义
screenshot无无只读截取 guest 屏幕,返回图片与"用它作为坐标"的提示
status无无只读VM 是否运行 + guest 两项隐私权限
clickx,ybutton,clicks,screenshotbutton仅left/right;clicks1–3单/双/三击
movex,yscreenshot坐标 ≥ 0移动指针,用于展开 hover 菜单或提示
dragx,y,toX,toYscreenshot坐标 ≥ 0按下左键、拖到目标、释放
scrollx,y,dydx,screenshotdy/dx在 -1000…1000负dy向下滚(相当于上滑)
typetextscreenshot字面文本向焦点输入框输入文本
keykeyscreenshot组合键字符串回车、Tab、方向键与快捷键

技能文档同时强调:type只用于字面文本,回车、Tab 和快捷键必须走key。这不是风格问题——源码里两者走完全不同的实现路径(见第四节)。

2.3 源码视角:坐标、双击与"再看一眼"

几个容易踩坑的行为,在源码里都有明确落点:

  • 坐标语义:MCP 的screenshot()注释写明返回的是"point-sized screenshot",即图片像素等于 guest 的屏幕点,其他工具接收的 x/y 因此可以直接使用;返回的文本也会带上Screen: W×H. Use these pixel coordinates.这句提示(Sources/TerminalMac/AgentServer.swift)。
  • 图片被转成 JPEG:guest 交回的是 PNG,MCP 层用 0.85 的质量重新编码为 JPEG 以缩小消息体,因此模型看到的分辨率不变、体积更小。CLI 路径则原样落盘 PNG。
  • 双击不是一个"标志位":click的clicks: n会被展开成 n 个请求,第 k 个请求带count: k(从 1 开始逐个续接),guest 侧把这些事件写入mouseEventClickState,macOS 才会识别为双击/三击(Sources/TerminalMac/AgentServer.swift、Sources/TerminalMac/Guest.swift)。
  • screenshot: true有 400 毫秒延迟:注释说明是为了"给动画和重绘一点时间",随后才截图,避免拿到动作生效前的旧帧(Sources/TerminalMac/AgentServer.swift)。
  • 超时预算各自独立:click/move/key/scroll为 10 秒,drag15 秒,type30 秒,status5 秒。长文本输入因此比点一下更"贵"。

2.4 注册 MCP server

技能文档假设工具已经可用;把它接进客户端的方式在 README.md 的 "Use with agents" 一节:

claude mcp add terminal-mac -- terminal-mac mcp terminal-mac skills install # → ~/.claude/skills/terminal-mac/SKILL.md

其他 MCP 客户端(Cursor、VS Code、Codex、Windsurf 等)把terminal-mac mcp --config的输出粘进客户端的 MCP 设置即可;驱动非default的 VM 用terminal-mac mcp NAME。

--config的实现有一个值得注意的细节:它输出的是启动器路径而不是版本化 bundle 的路径,这样升级后配置不需要改。见 Sources/TerminalMac/CLI.swift。输出的 JSON 形如:

{ "mcpServers": { "terminal-mac": { "command": "/path/to/terminal-mac", "args": ["mcp"] } } }

handshake 阶段,server 会返回serverInfo(名称terminal-mac与当前版本)以及一段instructions,其中复述了同样的规则:先截图、坐标以截图为基准、动作后再截图、遇到登录或权限提示请用户处理(Sources/TerminalMac/AgentServer.swift、Sources/TerminalMac/AgentServer.swift)。

三、没有 MCP 时:CLI 的等价能力

技能文档给出的 CLI 形态非常克制,核心只有一句话:每条命令都打印 JSON,失败时以非零状态退出;NAME默认是default。

terminal-mac status NAME # stopped → terminal-mac start NAME terminal-mac action NAME status terminal-mac action NAME screenshot --output /tmp/guest.png # then view the PNG terminal-mac action NAME click X Y [--double] [--button right] terminal-mac action NAME move X Y terminal-mac action NAME drag X Y X2 Y2 terminal-mac action NAME type 'literal text' terminal-mac action NAME key cmd+space terminal-mac action NAME scroll --dy -300 --x X --y Y

3.1 命令名与 VM 名的解析顺序

action子命令的位置参数解析有点特殊:如果第一个位置参数本身就是一个动作名(status、screenshot、snapshot、click、move、drag、scroll、type、key之一),那么 VM 名取默认值default;否则第一个位置参数被当作 VM 名。这样action screenshot和action default screenshot都能工作(Sources/TerminalMac/CLI.swift)。

VM 名不是随便什么字符串都行:必须匹配^[a-zA-Z0-9][a-zA-Z0-9_-]{0,47}$,即 1–48 位字母、数字、下划线或连字符,且以字母或数字开头。这个校验同时用于防止路径穿越,测试用例里../host、/tmp、a/b、a b、49 个字符的名字都会被拒绝(Sources/TerminalMacCore/Models.swift、Tests/TerminalMacCoreTests/CoreTests.swift)。

3.2 参数细节与默认可值

选项适用动作说明
--output PATHscreenshot/snapshot落盘路径,默认terminal-mac-NAME.png;支持~展开
--doubleclick等价于双击
--button rightclick右键
--text 'value'typetype 'literal text'的另一种写法
--keykey显式指定组合键
--dx,--dyscroll像素级滚动量,--dy为负表示向下
--timeout全部动作1–120 秒,默认 15
--全部结束选项解析,用于文本以-开头的场景

screenshot与snapshot目前是同一个动作:snapshot只是别名,不是accessibility tree 快照——这一点技能文档与 README.md 都特意声明,避免 Agent 误以为能拿到控件树。

截图回传也有大小上限:CLI 侧读取上限 32 MiB,MCP 侧 64 MiB(Sources/TerminalMac/CLI.swift、Sources/TerminalMac/AgentServer.swift)。

3.3 打开应用:优先用键盘

技能文档给出的示例是:

terminal-mac action NAME key cmd+space terminal-mac action NAME type "Safari" terminal-mac action NAME key return

并在旁边补了一句"优先使用键盘快捷键,而不是去追小目标"。原因很实在:菜单栏、工具栏里的小图标在缩放开销与命中误差上都更脆弱,而 Spotlight 的输入路径稳定、可重复。

四、输入语义:为什么type与剪贴板无关

技能文档对type的描述是"Text is sent as Unicode keyboard events, not through the clipboard. No shell-execution action exists." 这两句话都很关键:前者保证不会污染 guest 的剪贴板(也不会反向影响宿主机),后者说明这套工具没有任何执行 shell 的入口——Agent 只能"像人一样"操作 GUI。

落到源码,type的约束更加具体:

  • 文本以 UTF-8 字节计数,上限32 KiB,超出即报错;
  • 文本按 UTF-16 code unit 切块,每块最多16 个单元就随一次按键按下/抬出发送,避免超出 CGEvent 的单次字符串容量;
  • 事件源为nil,通过keyboardSetUnicodeString携带字符,投递到.cghidEventTap——整条链路不触碰任何剪贴板 API(Sources/TerminalMac/Guest.swift)。

key则是虚拟键码加修饰键的组合:修饰键识别cmd/command、ctrl/control、alt/option、shift,键名覆盖字母、数字、常用符号、return/enter、tab、space、backspace、escape/esc、home、end、pageup、pagedown、delete、四个方向键与f1–f12。未知键或未知修饰键会返回明确错误,并给出cmd+space这样的示例(Sources/TerminalMac/Guest.swift)。

drag的实现不是"起点一次、终点一次",而是在两点之间按距离插值出 2–30 个中间点,每步间隔约 8 毫秒,最后才释放左键——这更接近真人拖动,也是文本选择、窗口移动能成功的原因(Sources/TerminalMac/Guest.swift)。scroll则构造像素单位的滚轮事件,dy与dx都在 -1000…1000 之间校验。

五、什么时候必须停下来问用户

技能文档把"停下来"的条件写成清单,这几条对应的是 Agent无法自行绕过的边界:

  • guest 停在登录界面,或 helper 没有运行(工具报告 "no logged-in session"):请用户登录,例如执行terminal-mac view NAME后按 Ctrl+O。让 guest 开启自动登录可以避免后续每次启动都卡在这里。
  • guest 内出现 macOS 权限弹窗、密码、双因素验证码或支付界面。
  • VM 根本不存在:terminal-mac up NAME会从 Apple 下载数 GB 的 macOS,并且首次设置必须由用户完成。

这三条在实现上都有对应判定:

超时的两种含义被区分开了。MailboxTimeout带一个pickedUp标记:请求文件从未被读取说明根本没有 helper 在轮询(典型情况是 guest 还在登录界面);请求被读取但没有回音,则说明 helper 在忙或卡住。MCP 层据此给出不同文案,第一种直接建议用户用terminal-mac view NAME加 Ctrl+O 登录(Sources/TerminalMacCore/Mailbox.swift、Sources/TerminalMac/AgentServer.swift)。

"VM 不存在"和"VM 已停止"是两条不同的错误。requireRunning()每次调用都会检查,因此 MCP server 可以在还没有任何 VM 时就启动并列出工具;真正调用时若找不到 VM,会提示用户运行terminal-mac up;若 VM 存在但未运行,则提示terminal-mac start NAME(Sources/TerminalMac/AgentServer.swift)。

就绪状态是"两项权限都真"才算真。GuestReadiness的判定逻辑是screenRecording == "true" && accessibility == "true"才为ready,任一为假都归为permissions;而在没有任何回复时是unavailable。测试用例覆盖了这四种组合(Sources/TerminalMacCore/Setup.swift、Tests/TerminalMacCoreTests/CoreTests.swift)。

安装是否完成的判定同样来自命令路径。start会先检查machine.json中的installed字段,未完成安装时给出明确失败提示,而不是尝试启动一台半成品 VM(Sources/TerminalMac/CLI.swift)。

六、安全边界:三条"不要"与它们的实现依据

技能文档的安全条款简短但强硬:

  • 不要在宿主机上运行terminal-mac guest,也绝不回退到宿主机的截图或输入工具;
  • VM 里的应用、文件、账户与用户的 Mac 相互独立,除非用户要求,不要把密钥材料移进去;
  • stop是请求关机;stop --force可能丢失 guest 内未保存的工作,只在正常关机无法完成时使用。

第一条不是约定,而是代码强制的:GuestGuard.requireVirtualMac()读取hw.model,要求其以VirtualMac开头,否则直接抛错。guest子命令、GuestRuntime初始化、以及每个请求的处理入口都会调用它(Sources/TerminalMac/Guest.swift、Sources/TerminalMac/Guest.swift)。入口层还会在启动阶段先做一次检查,失败即退出并返回非零状态(Sources/TerminalMac/Entry.swift)。需要说明的是,这个检查的目的是防止"误在宿主机上执行",官方文档也明确它不是硬件证明,也不构成对已被攻陷宿主机的防御。

第三条对应的是两段不同的实现:stop调用vm.requestStop()并设置 60 秒期限,超时后提示可使用--force;stop --force走vm.stop()并立即退出进程(Sources/TerminalMac/VirtualMachine.swift)。

宿主与 guest 之间只有两条显式通道:可读写的control(请求/应答 JSON 与截图)与只读的tools(helper 应用)。从源码结构看,共享目录就是在配置阶段按这两个名字注册的,宿主机 home、项目目录、剪贴板、麦克风与摄像头都不在共享列表中(Sources/TerminalMac/VirtualMachine.swift)。

控制通道本身也没有网络监听:它是一组平铺的 JSON 文件,目录句柄用O_NOFOLLOW打开并被"钉住",读取时校验必须是普通文件、硬链接数为 1、大小不超过上限,文件名禁止/、..与空字符,应答 ID 必须与请求匹配,过期请求直接丢弃并在遍历时删除。这些行为都有对应测试(Sources/TerminalMacCore/Mailbox.swift、Tests/TerminalMacCoreTests/CoreTests.swift)。token 与凭据不会经过这条通道,宿主也从不执行 guest 提供的内容。

七、把技能装进 Agent 的几种方式

技能文档本身就是"可安装的资产"。随包发布的构建里,SKILL.md位于应用 bundle 的Resources/skills/terminal-mac/SKILL.md;开发构建则从可执行文件所在目录向上最多 6 层查找skills/terminal-mac/SKILL.md(Sources/TerminalMac/CLI.swift)。

terminal-mac skills get # 打印与本版本匹配的技能全文 terminal-mac skills path # 打印技能文件所在路径 terminal-mac skills install # 默认安装到 ~/.claude/skills/terminal-mac/SKILL.md terminal-mac skills install --dir PATH # 安装到任意 skills 目录

install带一个保护:如果目标位置已存在一份不是terminal-mac 的技能(判定方式是内容里没有\nname: terminal-mac\n),会拒绝覆盖并报错,避免误删别的技能(Sources/TerminalMac/CLI.swift)。

对使用AGENTS.md的 Agent,README.md 建议加一行"在使用虚拟 Mac 之前先运行terminal-mac skills get",这样 Agent 拿到的指令永远与已安装的版本一致,而不是散落在提示词里的一段过期文本。docs/AGENT.md 也说明完整指南就是这份技能文件,并保留了 CLI 参考。

八、排错速查

现象可能原因处置
工具报 "no logged-in session"guest 在登录界面,或 helper 未运行请用户terminal-mac view NAME后按 Ctrl+O 登录
工具报权限相关错误guest 内未授予 Screen Recording / Accessibility请用户在 guest 内点 helper 的两个 Allow 按钮
status显示 VM 停止被stop或未启动terminal-mac start NAME
找不到 VM尚未创建请用户执行terminal-mac up NAME(需下载 macOS 并完成首次设置)
坐标点偏用了旧截图的坐标或忽略了 HiDPI重新screenshot,严格使用新图左上角原点
输入没反应只授予了屏幕录制,没有辅助功能检查status中accessibility是否为true

其中最后一条在终端查看器里有更直观的提示:一旦有帧但输入权限缺失,头部会显示 "⚠ input needs Accessibility";而 helper 的状态应答同时提供screenRecording与accessibility两个布尔值,正是status工具的判断依据(Sources/TerminalMac/Viewer.swift、Sources/TerminalMac/Guest.swift)。

九、验证边界:哪些结论可以当真

技能文档描述的是"应该怎么用",不等于"全部已在真机验证"。仓库的验证记录明确区分了两类结论:

  • 已完成的自动化检查:Debug 构建与 14 个 XCTest 协议/输入/设置测试全部通过;Release 构建通过;打包应用通过严格递归签名校验;宿主 entitlements 含com.apple.security.virtualization而 guest 不含;guest --check在物理宿主机上拒绝运行并以非零状态退出。
  • 尚未完成的真机验收:下载恢复镜像并创建 VM、在原生窗口完成 Setup Assistant、在 guest 内授予隐私权限、验证 Retina/非 Retina 下的坐标对齐、在 Ghostty/Kitty/iTerm2 中实测查看器、暂停/恢复与强制关机等。

因此 docs/VALIDATION.md 特别声明:在这些检查完成之前,内联终端渲染、guest 隐私授权、guest 截图与 guest 输入行为都不应被描述为"已实测通过"。自动化测试覆盖的是邮箱往返、过期与清理、文件边界防御、名称与选项校验、分片 UTF-8 与终端输入序列——它们是协议级证据,不是"VM 已成功安装、启动并被操控"的证据。把这条边界带上,Agent 在真实环境中才不会被过度承诺误导。

【免费下载链接】terminal-mac

Entire MacOS running in your terminal.

项目地址:https://gitcode.com/gh_mirrors/te/terminal-mac
点击查看免费下载
上一篇:Security-101 安全运营入门:SecOps 关键概念与 NIST 事件响应工作流全解析
下一篇:Security-101 课程精讲:IAM 零信任架构——以身份作为现代 IT 环境的新边界

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/6 3:24:06

ADC+DWA

1.5V2.GND3. PA0 4.PD11使用DWA之前先看STM32的参考手册-DMA映像147,防止冲突你的现状:完全够用,互不干扰STM32F103 系列有两个 DMA 控制器,资源其实是挺丰富的:DMA1:有 7 个通道。DMA2:…

作者头像 李华
网站建设 2026/11/6 3:22:12

【芳芯科技】图像识别教室无人关灯系统

实物效果图:实现功能:1.采用ARM内核STM32单片机作为控制核心。2.采用OV7670摄像头实现图像获取。3.采用TFT2.8显示配合图像调试显示。4.通过按键进行布放,布放后采用图像算法进行检测,检测到有人体后通过电开关进行教室灯光的开启…

作者头像 李华
网站建设 2026/11/6 3:20:57

互联网的数据请求的架构演变

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/11/6 3:12:26

Spyglass:官方Hands-on Training(一)

相关阅读 Spyglasshttps://blog.csdn.net/weixin_45791458/category_12828934.html?spm1001.2014.3001.5482 本文是对Spyglass Hands-on Training中第一个实验的翻译(有删改),Lab文件可以从以下链接获取。Spyglass Hands-on Traininghttps:…

作者头像 李华
网站建设 2026/11/6 3:11:11

用 mpv 换掉浏览器的播放芯:Jellyfin 桌面客户端快速上手

用 mpv 换掉浏览器的播放芯:Jellyfin 桌面客户端快速上手 【免费下载链接】jellyfin-desktop Jellyfin Desktop Client 项目地址: https://gitcode.com/GitHub_Trending/je/jellyfin-desktop 周五晚上你打开浏览器看那部 4K 电影,进度条拖两秒才动…

作者头像 李华
网站建设 2026/11/6 3:08:51

高通学习9--烧写

1 QFIL WIndows 来自:高通系列烧写操作指南_Telpo开发者平台 https://www.cnblogs.com/blogs-of-lxl/p/10620662.html 这个看起来简单,实际也有很多门门道道,而且有时候这个是bringup的第一步,有可能在这里烧写会失败&#xff0…

作者头像 李华