Files
opencli/CLI-ELECTRON.md
T

4.2 KiB
Raw Blame History

description
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 应用,必须先要求用户使用以下参数注入调试端点:

/Applications/AppName.app/Contents/MacOS/AppName --remote-debugging-port=9222

标准适配模式:The 5-Command Pattern

适配一个新的 App,必须在 src/clis/<app_name>/ 下实现这 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。

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。
// 寻路机制:优先尝试寻找 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)。
const isMac = process.platform === 'darwin';
await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
await page.wait(1); // 等待重渲染

全局环境变量

为了让 Core Framework 挂载到我们指定的端口,必须在执行指令前(或在 README 中指导用户)注入目标环境端口:

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 阻断。