--- description: How to CLI-ify and automate any Electron Desktop Application via CDP --- # CLI-ifying Electron Applications (Skill Guide) Based on the successful extraction and automation of **Antigravity** and **OpenAI Codex** desktop apps, this guide serves as the standard operating procedure (SOP) for adapting ANY Electron-based application into an OpenCLI adapter. ## 核心原理 (Core Concept) Electron 应用本质上是运行在本地的 Chromium 浏览器实例。只要在启动应用时暴露了调试端口(CDP,Chrome DevTools Protocol),我们就可以利用 Playwright MCP 直接穿透其 UI 层,获取并操控包括 React/Vue 组件、Shadow DOM 等在内的所有底层状态,实现“从应用外挂入自动化脚本”。 ### 启动 Target App 要在本地操作任何 Electron 应用,必须先要求用户使用以下参数注入调试端点: ```bash /Applications/AppName.app/Contents/MacOS/AppName --remote-debugging-port=9222 ``` ## 标准适配模式:The 5-Command Pattern 适配一个新的 App,必须在 `src/clis//` 下实现这 5 个标准化指令: ### 1. `status.ts` (连接测试) 负责确认应用监听正确。 - **机制**: 直接 `export const statusCommand = cli({...})` - **核心代码**: 获取 `window.location.href` 与 `document.title`。 - **注意**: 必须指明 `domain: 'localhost'` 和 `browser: true`。 ### 2. `dump.ts` (逆向工程核心) 很多现代 App DOM 极其庞大且混淆。**千万不要直接猜选择器**。 首先编写 dump 脚本,将当前页面的 DOM 与 Accessibility Tree 导出到 `/tmp/`,方便用 AI (或者 `grep`) 提取精确的容器名称和 Class。 ```typescript const dom = await page.evaluate('document.body.innerHTML'); fs.writeFileSync('/tmp/app-dom.html', dom); const snap = await page.snapshot({ interactive: false }); fs.writeFileSync('/tmp/app-snapshot.json', JSON.stringify(snap, null, 2)); ``` ### 3. `send.ts` (高级注入技巧) Electron 应用常常使用极端复杂的富文本编辑器(如 Monaco, Lexical, ProseMirror)。直接修改元素的 `value` 常常会被 React 状态机忽略。 - **最佳实践**: 使用 `document.execCommand('insertText')` 完美模拟真实的人类复制粘贴输入流,完全穿透 React state。 ```javascript // 寻路机制:优先尝试寻找 contenteditable let composer = document.querySelector('[contenteditable="true"]'); composer.focus(); document.execCommand('insertText', false, "你好"); ``` - **提交快捷键**: `await page.pressKey('Enter')`。 ### 4. `read.ts` (上下文解析) 不要提取整个页面的文本。应该利用 `dump.ts` 抓取出来的特征寻找真正的“对话容器”。 - **技巧**: 检查带有语义化结构的数据,例如 `[role="log"]`、`[data-testid="conversation"]` 或是 `[data-content-search-turn-key]`。 - **格式化**: 拼接抓取出的文本转粗暴渲染成 Markdown 返回,这样不仅你和人类能读懂,LLM 后续作为 Agent 也能精准切分。 ### 5. `new.ts` / Action Macros (底层事件模拟) 许多图形界面操作难以找到按钮实例,但它们通常响应原生快捷键。 - **最佳实践**: 模拟系统级快捷键直接驱动 `(Meta+N / Control+N)`。 ```typescript const isMac = process.platform === 'darwin'; await page.pressKey(isMac ? 'Meta+N' : 'Control+N'); await page.wait(1); // 等待重渲染 ``` ## 全局环境变量 为了让 Core Framework 挂载到我们指定的端口,必须在执行指令前(或在 README 中指导用户)注入目标环境端口: ```bash export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9222" ``` ## 踩坑避雷 (Pitfalls & Gotchas) 1. **端口占用 (EADDRINUSE)**: 确保同一时间只能有一个 App 占据一个端口。如果同时测试 Antigravity (9224) 且你要测试别的 App (9222),要将 CDP Endpoint 分配开来。 2. **TypeScript 抽象**: OpenCLI 内部封装了 `IPage` 类型(`src/types.ts`),不是原生的 Playwright Page。要用 `page.pressKey()` 和 `page.evaluate()`,而非 `page.keyboard.press()`。 3. **延时等待**: DOM 发生剧烈变化后,一定要加上 `await page.wait(0.5)` 到 `1.0` 给框架反应的时间。不要立刻 return 导致连接 prematurely 阻断。