CLI-Anything:把任意GUI软件变成Agent可调用的命令行工具

📅 发布时间:2026/9/8 14:07:31
CLI-Anything:把任意GUI软件变成Agent可调用的命令行工具 做 Agent 开发这两年我有个很深的感受真正卡住落地的往往不是模型能力而是工具链。现在大模型已经能写代码、能看懂截图、能调用 API但办公室那堆老旧业务系统十有七八只有 GUI 没有 API。你总不能指望 Agent 对着屏幕自己找按钮吧所以当我看到CLI-Anything这个项目冲到 4.8 万星的时候第一反应是这个痛点终于有人做成了通用方案。简单说它做的事情非常直接——把任意 GUI 软件包装成 Agent 可以直接调用的命令行工具让 AI 像执行函数一样去操作 GUI而不是靠视觉一个像素一个像素地猜。这篇文章我会围绕这个项目展开先聊聊我为什么觉得它是 Agent 落地的关键拼图再拆一拆核心设计的几个层次最后会把我在 Windows 上把一个记事本变成 Agent 工具的完整过程复现一遍。所有方案都是我实际跑过的不是只写在文档里的演示代码。不管你是做 RPA、做企业自动化、还是单纯想给个人项目接上 Agent 能力这篇应该都能帮上忙。1. 为什么 GUI 自动化是 Agent 落地的最后一块拼图1.1 所谓“Agent 原生工具”到底标准是什么先别急着看代码得把“原生工具”这四个字掰开。Agent 生态里我们经常提到 Function Calling本质上是给模型一份 JSON Schema模型根据用户意图生成参数系统再把参数传给真正的执行函数。一个能被 Agent 顺畅调用的工具至少要满足四个条件可发现、可传参、可验证、可组合。可发现意味着模型能从工具描述里知道这个工具能干什么可传参意味着输入输出都有明确的类型定义可验证意味着执行之后有办法判断成没成功可组合则是说多个工具可以串成一个复杂任务。很多 GUI 自动化脚本之所以没法搬到 Agent 场景就是只满足了执行根本没有任何 schema 和验证。CLI-Anything在做的其实就是把 GUI 动作包装成这一层标准接口让原本“只可意会”的鼠标键盘操作变成一个稳定的调用单元。你可以把 GUI 软件想象成一个不会说英文的老外Agent 是一个只会说英文的客服。直接让客服手舞足蹈去沟通效率低且没法标准化但如果中间有一个翻译器把客户的需求转成老外能听懂的指令再把老外的反馈转回英文一切就通顺了。CLI-Anything就是那个翻译器它翻译的不是语言而是“用户意图”到“GUI 操作”的映射。1.2 GUI 软件为什么让 Agent 无从下手很多没做过 GUI 自动化的人会觉得能截图就能操作。实际完全不是这样。GUI 程序本质上是两个东西的组合一张不断重绘的像素画布加上一套内部状态机。按钮的位置会变、主题会换、缩放率会改、弹窗可能出现也可能不出现这些对人类来说很自然但对程序来说全是不可控变量。传统 RPA 工具的脆弱大家都有体会昨天还能跑通的脚本今天因为系统更新弹了个广告窗就废了。为什么因为它把所有操作都固化成了坐标。而 GUI 软件本身没有公开 API控件树也不一定稳定甚至同一个按钮在不同机器上可能叫不同的名字。这种情况下Agent 直接操作 GUI 会遇到三重困难一是不知道现在屏幕上是什么状态二是不知道某个元素到底在哪三是不知道操作之后系统会怎么响应。纯视觉模型可以解决第一部分但后两部分是工程问题。CLI-Anything的做法很聪明它不跟像素较劲而是先给 GUI 建立一个“可编程的边界”在这个边界之外Agent 看到的是熟悉的命令行或函数接口边界之内才是各种 GUI 自动化细节。1.3 为什么纯视觉 GUI Agent 不能完全替代 CLI 包装这两年 GUI Agent 很火不少团队在做纯视觉路线把截图丢给模型让模型输出点击坐标。这个方向确实惊艳但投入产出比在工程上很尴尬。大模型看一张高分辨率截图可能要几千 token执行一次操作要两三次推理成本和延迟都扛不住。更麻烦的是非常难测——你没法断言一个坐标点击到底触发了什么只能靠人肉盯屏幕。还有一点纯视觉 Agent 天然不够确定。同一个界面换一个显示器分辨率模型给出的坐标可能全偏了。这对 demo 没问题但在生产环境里是不可接受的。所以我认为工程化落地阶段一定需要一层“稳定的操作抽象”。把 GUI 动作封装成 CLI 工具本质上就是把这层抽象暴露给 Agent。它有几个纯视觉路线没有的优势第一一次写好处处可测第二执行单元可以纳入 CI/CD第三多个 Agent 任务可以共享一套工具库。CLI-Anything的火爆正是踩在了这个时间点上。2. CLI-Anything 的核心设计拆解把点击变成调用2.1 一条命令背后的三层结构我在源码里翻了一圈发现它的核心抽象非常干净拆成三层来看就很好理解。最上层是声明层。每个工具都用一份配置文件描述里面写明工具名、描述、参数类型、要执行的步骤以及执行后的预期结果。这层是给 Agent 看的也是给开发者看的。你不需要关心底层是用什么方式点击的只需要关心这个工具“做什么、要什么参数、怎么验证”。中间是执行层。这层负责把声明里的每一步转换成真实的 GUI 操作。底层驱动可以接多种方案Windows 上常用 pywinauto、WinAppDriver、FlaUI跨平台可以用 Playwright 的桌面模式再加上 OCR 和图像识别做兜底。CLI-Anything没有把底层驱动锁死而是抽象成一套动作接口launch、click、type、hotkey、wait_window、read_text 等等。最下面是资源层负责启动应用、管理窗口句柄、处理等待和重试。这层解决的是“GUI 操作天然不稳定”的问题比如窗口没起来、焦点不对、控件没加载完都需要在这里统一处理。这三层好处是更换底层驱动时上面声明文件几乎不用改。我后来在 Linux 上试发现只需要替换执行层的适配器YAML 里的逻辑基本原样复用。这就是抽象的价值。2.2 映射配置怎么写一个可运行的示例以 Windows 记事本为例我想让 Agent 能执行“打开记事本、写入内容、另存为文件”这个操作映射配置大概是下面这样tools: - name: notepad_write description: 在记事本中写入指定内容并保存到目标路径 parameters: text: type: string description: 要写入的文本内容 path: type: string description: 目标保存路径例如 C:\\temp\\output.txt steps: - action: launch app: notepad.exe - action: wait_window title: 无标题 - 记事本 timeout: 5 - action: type text: {text} - action: hotkey keys: [ctrl, s] - action: wait_window title: 另存为 timeout: 5 - action: type text: {path} - action: hotkey keys: [enter] - action: wait_window title: *{basename(path)}* timeout: 10 expect: - window_title: {basename(path)} - 记事本这段配置里有几个关键点。steps是按顺序执行的动作队列每个动作都有一个明确的action类型。{text}和{path}是从参数里替换进来的模板变量这样工具就是通用的Agent 每次调用传不同内容就行。wait_window后面带 timeout防止 GUI 卡死导致整个任务挂住。expect段是我觉得最有价值的部分。它定义了“成功”的衡量标准执行完所有步骤后框架会去检查匹配的窗口标题是否存在。只有通过了验证工具才会向 Agent 返回成功状态。这比单纯“点击了保存按钮”要可靠得多因为 GUI 自动化经常会“操作完成了但结果没成功”比如保存对话框根本没弹出来脚本却以为已经保存了。2.3 定位策略为什么会默认用“语义锚点”而不是坐标配置里没有出现任何 x、y 坐标这是我特别喜欢的一点。CLI-Anything默认不推荐用绝对坐标定位而是优先用窗口标题、控件 ID、文本内容这些语义锚点。原因很简单坐标是 GUI 里最容易变化的东西。窗口位置移动、分辨率变化、DPI 缩放、主题换肤都会让坐标失效。但一个按钮的AutomationId或者一个窗口的标题在正常情况下是稳定的。就好比你约人见面说“站在黄鹤楼门口”比说“在东经 114.3 度、北纬 30.5 度”靠谱得多因为后者在地图上可能精确但真实环境下一挪就偏。如果语义锚点拿不到它也会退回去用 OCR 识别屏幕上特定文本的位置再退一级用图像模板匹配。我在多台机器上测试过这三种策略组合起来成功率比单独用坐标高非常多。坐标应该只是最后的手段不是默认选项。3. 实操给 Windows 记事本配上 CLI 接口3.1 环境准备与安装我是在 Windows 11 上操作的Python 版本 3.10。安装很简单直接用 pippip install cli-anything这个包会带上 pywinauto、Pillow、pyperclip 这些核心依赖。如果你要用 OCR 兜底还需要单独安装 tesseract并把tesseract.exe加到 PATH 里。我当时没提前装结果配置 OCR 识别的那一步直接报找不到模型后来补装才算跑通。装完之后可以先跑一下版本验证cli-anything --version能输出版本号就说明环境没问题。需要注意的一点是GUI 自动化非常依赖当前桌面会话状态。建议不要在锁屏状态或者通过 SSH 远程会话里跑否则应用可能不会真正显示到桌面上操作全落在虚拟屏幕上。我第一次测试时就是在远程桌面断开的会话里跑的记事本窗口死活找不到。3.2 录制 GUI 动作并导出为工具定义配置写起来虽然有套路但对着手写很容易漏。CLI-Anything提供了一个录制模式可以帮我们生成初始配置。具体流程是cli-anything record --name notepad_write然后跟着提示自己手动打开记事本、输入文字、按 CtrlS、弹出保存框、输入路径、回车保存。整个操作过程会被记录成一份初始 YAML。这相当于让程序看一遍人是怎么办事的然后模仿着生成步骤。不过这里有个坑录制得到的步骤里很多动作是绝对坐标。比如点击“保存”按钮录下来的可能是一个坐标点而不是控件属性。我建议录完之后一定要手动编辑配置把坐标替换成title或automation_id锚点。另外保存对话框是一个独立窗口录制时要注意wait_window的标题是不是“另存为”不同系统语言可能不一样需要在配置里调整。录制不是万能的它只适合快速搭框架。真正稳定可靠的配置还得靠手工打磨。3.3 把工具交给 AgentFunction Calling 接入示例配置写好后下一步是让 Agent 能调用它。我用的是 OpenAI 的 Function Calling 接口做演示但换成任何支持 tools 的模型都行。先把配置读到 Python 里生成 OpenAI 的 function schemafrom cli_anything import AgentTool tool AgentTool.from_yaml(notepad.yaml) openai_schema { type: function, function: tool.openai_schema(), }接着和普通 Function Calling 一样处理from openai import OpenAI client OpenAI(api_key你的key) messages [ {role: user, content: 帮我写一份请假说明内容写3月5日请假一天保存到 C:/temp/leave.txt} ] response client.chat.completions.create( modelgpt-4o, messagesmessages, tools[openai_schema], )模型看到工具描述后会返回一个函数调用请求里面带有符合 schema 的参数。我们直接把参数交给tool.execute()就行tool_call response.choices[0].message.tool_calls[0] import json arguments json.loads(tool_call.function.arguments) result tool.execute(arguments) print(result.output)这里tool.execute()会严格按照配置文件里的 steps 执行 GUI 操作执行完再做expect验证。整个过程对模型来说就像一个普通的工具回调完全不需要关心记事本窗口长什么样。需要提醒一下tool.openai_schema()这个方法名可能在不同版本里有变化但思路都是一样的把 YAML 里的 name、description、parameters 转成 OpenAI 认识的 JSON Schema。3.4 验证与反馈回路执行之后如何确定成功GUI 自动化和普通 API 调用最大的区别是命令发出去了不代表事情办成了。典型的例子就是点击“保存”后弹出一个覆盖式错误提示框但我们只看到了“保存”动作执行成功根本没发现后续对话。所以验证环节一定要做。在 notepad_write 的配置里我把expect设置成“保存后的窗口标题包含文件名”。如果另存为对话框没有正确关闭或者保存失败窗口标题就还是“无标题 - 记事本”验证直接失败。这个反馈会被当作错误信息返回给模型模型可以自己决定要不要换个方式重试。更复杂的场景我建议在执行后的验证里读取文件内容或窗口文本。比如写完一个文档可以重新打开文件并断言里面有预期片段。CLI-Anything的文本读取动作就派上用场了。没有验证的工具就跟没写测试的代码一样迟早要出事。4. 常见问题与排查技巧实录4.1 定位不到控件别急着调坐标我最早遇到的问题是录制后回放某一步点击完全无效。打开日志一看控件没找到。解决方案不是去改 x/y而是先把控件属性挖出来。Windows 上可以用系统自带的 Inspect.exe 或者开源工具 Accessibility Insights 查看控件树。只要把鼠标悬停在目标按钮上就能看到它的Name、AutomationId、ControlType等属性。然后回到配置里把定位锚点从坐标改成这个 ID。- action: click automation_id: 1001注意不是所有控件都有稳定的 AutomationId。有些老式软件是自绘界面控件树里只有一张画布。这种情况我会退回到read_text配合 OCR 定位或者干脆用图像模板匹配在屏幕上找那张“确定”按钮的小截图。优先级永远是控件树 OCR 文本定位 图像模板 坐标。4.2 多显示器和 DPI 缩放让点击跑偏这个问题非常隐蔽。Windows 默认会对高 DPI 显示器的应用做缩放但不同应用对这个策略的处理不同。如果你在 4K 屏幕上以 150% 缩放运行某个程序pywinauto 拿到的坐标和实际鼠标事件落点可能对不上点击就会偏到按钮旁边。我的解决办法是在执行层里强制设置 DPI 感知from ctypes import windll windll.user32.SetProcessDPIAware()这样程序拿到的坐标和实际像素就是一一对应的。另外如果接了多个显示器还得注意逻辑坐标和物理坐标转换。配置里我一般尽量少用坐标实在躲不开就先用一个测试脚本把所有定位点跑一遍确认当前环境坐标没有偏差再接入 Agent。4.3 UAC、杀毒软件和最小权限问题GUI 自动化最容易被忽略的是权限边界。有些操作会触发 UAC 弹窗而这个弹窗运行在安全桌面上任何自动化工具都无法直接操作。我的经验是第一步先把目标软件和自动化进程放在同一权限级别否则光是“是否允许此应用更改设备”的确认框就能卡死整个任务。杀毒软件也会干扰。尤其是模拟键盘和鼠标的操作很容易被判断成异常行为。我遇到过某安全软件在后台拦截了SendKeys日志里看着是已经发送了但屏幕上一个字都没打进去。后来是把开发机加入了信任区才恢复正常。这引出一个更重要的话题给 Agent 操作 GUI 时一定要遵守最小权限原则。不要让 Agent 在没有人工确认的情况下执行删除、覆盖保存、修改系统设置等高风险操作。我自己的做法是在这些工具的参数里加一个confirmed字段只有 Agent 拿到 true 才真正执行破坏性步骤。4.4 常见问题速查表现象可能原因解决办法窗口找不到应用启动过慢或窗口标题不匹配调大 wait_window 的 timeout用通配符匹配标题点击无效DPI 缩放坐标偏移设置 SetProcessDPIAware尽量用控件属性定位文字没输入键盘焦点不在目标窗口在 type 前先执行 click 聚焦窗口保存无反应另存为窗口是独立进程单独等待“另存为”窗口标题再执行输入验证失败expect 条件写得太严先手动确认真实标题再用通配符杀毒软件拦截模拟输入被识别为异常加入信任区或改用后台消息注入这张表是我自己在项目里总结的不一定覆盖所有情况但对新手排查已经够用了。5. 一点实战经验与后续扩展用CLI-Anything跑通记事本只是开胃菜真正有价值的是把它沉淀成一套内部工具库。我现在会把公司里所有需要人工点击的报表系统一个个封装成 CLI 工具然后让 Agent 按部门、按日期把报表拉下来整理成 Excel。这个过程中我发现配置文件本身也是可以模板化的启动、等待、输入、保存几乎每个工具都是这几个动作的排列组合只要把参数抠出来剩下全是重复劳动。另外一个实用技巧是把工具执行的历史记录接回 Agent 的记忆系统。每次调用都记录参数、耗时、成功与否下次遇到类似任务Agent 可以优先参考成功过的参数组合。尤其是 GUI 操作经常涉及一些不稳定的窗口时序问题历史记忆能帮忙少踩很多坑。最后想说的是这类项目给我的最大启发不是技术本身而是思路上的转换。让模型看懂 GUI 固然酷但把 GUI 变成模型本来就会的东西才是工程上更稳的路。不给模型一双眼睛而是给模型一只手。这大概就是CLI-Anything能在 GitHub 拿到 4.8 万星的原因。如果你手头也有“只能手点”的软件不妨试试这个方案把那些重复劳动真正交给 Agent。