diff --git a/CLI-ELECTRON.md b/CLI-ELECTRON.md new file mode 100644 index 0000000..69732e2 --- /dev/null +++ b/CLI-ELECTRON.md @@ -0,0 +1,72 @@ +--- +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 阻断。