docs: add CLI-ELECTRON.md as an agent skill guide

This commit is contained in:
jackwener committed 2026-03-18 17:17:46 +08:00
1 parent cd63231b7e
commit 981cc1bc5e
1 file changed
+72
+72
View File
@@ -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/<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。
```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 阻断。