From 63489fb596ec24fa7646edd2f1acd43c16aa0a16 Mon Sep 17 00:00:00 2001 From: jackwener Date: Wed, 18 Mar 2026 22:28:53 +0800 Subject: [PATCH] chore: remove untested feishu/wechat adapters, polish CLI-ELECTRON.md - Remove feishu and wechat adapters (not tested yet, will re-add later) - Remove their rows from README.md and README.zh-CN.md - Significantly polish CLI-ELECTRON.md skill guide: - Add Electron detection guide (check for Electron Framework) - Add Non-Electron AppleScript pattern section - Add port assignment table for all CDP adapters - Improve code examples with real working TypeScript --- CLI-ELECTRON.md | 125 +++++++++++++++++++++++--------- README.md | 2 - README.zh-CN.md | 2 - src/clis/feishu/README.md | 21 ------ src/clis/feishu/README.zh-CN.md | 21 ------ src/clis/feishu/new.ts | 24 ------ src/clis/feishu/read.ts | 46 ------------ src/clis/feishu/search.ts | 32 -------- src/clis/feishu/send.ts | 40 ---------- src/clis/feishu/status.ts | 22 ------ src/clis/wechat/README.md | 21 ------ src/clis/wechat/README.zh-CN.md | 21 ------ src/clis/wechat/new.ts | 24 ------ src/clis/wechat/read.ts | 50 ------------- src/clis/wechat/search.ts | 33 --------- src/clis/wechat/send.ts | 44 ----------- src/clis/wechat/status.ts | 22 ------ 17 files changed, 89 insertions(+), 461 deletions(-) delete mode 100644 src/clis/feishu/README.md delete mode 100644 src/clis/feishu/README.zh-CN.md delete mode 100644 src/clis/feishu/new.ts delete mode 100644 src/clis/feishu/read.ts delete mode 100644 src/clis/feishu/search.ts delete mode 100644 src/clis/feishu/send.ts delete mode 100644 src/clis/feishu/status.ts delete mode 100644 src/clis/wechat/README.md delete mode 100644 src/clis/wechat/README.zh-CN.md delete mode 100644 src/clis/wechat/new.ts delete mode 100644 src/clis/wechat/read.ts delete mode 100644 src/clis/wechat/search.ts delete mode 100644 src/clis/wechat/send.ts delete mode 100644 src/clis/wechat/status.ts diff --git a/CLI-ELECTRON.md b/CLI-ELECTRON.md index 69732e2..0ac42f1 100644 --- a/CLI-ELECTRON.md +++ b/CLI-ELECTRON.md @@ -4,30 +4,51 @@ description: How to CLI-ify and automate any Electron Desktop Application via CD # 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. +Based on the successful automation of **Cursor**, **Codex**, **Antigravity**, **ChatWise**, **Notion**, and **Discord** 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 等在内的所有底层状态,实现“从应用外挂入自动化脚本”。 +## Core Concept -### 启动 Target App -要在本地操作任何 Electron 应用,必须先要求用户使用以下参数注入调试端点: +Electron apps are essentially local Chromium browser instances. By exposing a debugging port (CDP — Chrome DevTools Protocol) at launch time, we can use Playwright to pierce through the UI layer, accessing and controlling all underlying state including React/Vue components and Shadow DOM. + +> **Note:** Not all desktop apps are Electron. WeChat (native Cocoa) and Feishu/Lark (custom Lark Framework) embed Chromium but do NOT expose CDP. For those apps, use the AppleScript + clipboard approach instead (see [Non-Electron Pattern](#non-electron-pattern-applescript)). + +### Launching the Target App ```bash /Applications/AppName.app/Contents/MacOS/AppName --remote-debugging-port=9222 ``` -## 标准适配模式:The 5-Command Pattern +### Verifying Electron +```bash +# Check for Electron Framework in the app bundle +ls /Applications/AppName.app/Contents/Frameworks/Electron\ Framework.framework +# If this directory exists → Electron → CDP works +# If not → check for libEGL.dylib (embedded Chromium/CEF, CDP may not work) +``` -适配一个新的 App,必须在 `src/clis//` 下实现这 5 个标准化指令: +## The 5-Command Pattern (CDP / Electron) -### 1. `status.ts` (连接测试) -负责确认应用监听正确。 -- **机制**: 直接 `export const statusCommand = cli({...})` -- **核心代码**: 获取 `window.location.href` 与 `document.title`。 -- **注意**: 必须指明 `domain: 'localhost'` 和 `browser: true`。 +Every new Electron adapter should implement these 5 commands in `src/clis//`: -### 2. `dump.ts` (逆向工程核心) -很多现代 App DOM 极其庞大且混淆。**千万不要直接猜选择器**。 -首先编写 dump 脚本,将当前页面的 DOM 与 Accessibility Tree 导出到 `/tmp/`,方便用 AI (或者 `grep`) 提取精确的容器名称和 Class。 +### 1. `status.ts` — Connection Test +```typescript +export const statusCommand = cli({ + site: 'myapp', + name: 'status', + domain: 'localhost', + strategy: Strategy.UI, + browser: true, // Requires CDP connection + args: [], + columns: ['Status', 'Url', 'Title'], + func: async (page: IPage) => { + const url = await page.evaluate('window.location.href'); + const title = await page.evaluate('document.title'); + return [{ Status: 'Connected', Url: url, Title: title }]; + }, +}); +``` + +### 2. `dump.ts` — Reverse Engineering Core +Modern app DOMs are huge and obfuscated. **Never guess selectors.** Dump first, then extract precise class names with AI or `grep`: ```typescript const dom = await page.evaluate('document.body.innerHTML'); fs.writeFileSync('/tmp/app-dom.html', dom); @@ -35,38 +56,70 @@ 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。 +### 3. `send.ts` — Advanced Text Injection +Electron apps often use complex rich-text editors (Monaco, Lexical, ProseMirror). Setting `.value` directly is ignored by React state. + +**Best practice:** Use `document.execCommand('insertText')` to perfectly simulate real user input, fully piercing React state: ```javascript -// 寻路机制:优先尝试寻找 contenteditable -let composer = document.querySelector('[contenteditable="true"]'); +const composer = document.querySelector('[contenteditable="true"]'); composer.focus(); -document.execCommand('insertText', false, "你好"); +document.execCommand('insertText', false, 'Hello'); ``` -- **提交快捷键**: `await page.pressKey('Enter')`。 +Then submit with `await page.pressKey('Enter')`. -### 4. `read.ts` (上下文解析) -不要提取整个页面的文本。应该利用 `dump.ts` 抓取出来的特征寻找真正的“对话容器”。 -- **技巧**: 检查带有语义化结构的数据,例如 `[role="log"]`、`[data-testid="conversation"]` 或是 `[data-content-search-turn-key]`。 -- **格式化**: 拼接抓取出的文本转粗暴渲染成 Markdown 返回,这样不仅你和人类能读懂,LLM 后续作为 Agent 也能精准切分。 +### 4. `read.ts` — Context Extraction +Don't extract the entire page text. Use `dump.ts` output to find the real "conversation container": +- Look for semantic selectors: `[role="log"]`, `[data-testid="conversation"]`, `[data-content-search-turn-key]` +- Format output as Markdown — readable by both humans and LLMs -### 5. `new.ts` / Action Macros (底层事件模拟) -许多图形界面操作难以找到按钮实例,但它们通常响应原生快捷键。 -- **最佳实践**: 模拟系统级快捷键直接驱动 `(Meta+N / Control+N)`。 +### 5. `new.ts` — Keyboard Shortcuts +Many GUI actions respond to native shortcuts rather than button clicks: ```typescript const isMac = process.platform === 'darwin'; await page.pressKey(isMac ? 'Meta+N' : 'Control+N'); -await page.wait(1); // 等待重渲染 +await page.wait(1); // Wait for re-render ``` -## 全局环境变量 -为了让 Core Framework 挂载到我们指定的端口,必须在执行指令前(或在 README 中指导用户)注入目标环境端口: +## Environment Variable ```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 阻断。 +## Non-Electron Pattern (AppleScript) + +For native macOS apps (WeChat, Feishu) that don't expose CDP: +```typescript +export const statusCommand = cli({ + site: 'myapp', + strategy: Strategy.PUBLIC, + browser: false, // No browser needed + func: async (page: IPage | null) => { + const output = execSync("osascript -e 'application \"MyApp\" is running'", { encoding: 'utf-8' }).trim(); + return [{ Status: output === 'true' ? 'Running' : 'Stopped' }]; + }, +}); +``` + +Core techniques: +- **status**: `osascript -e 'application "AppName" is running'` +- **send**: `pbcopy` → activate window → `Cmd+V` → `Enter` +- **read**: `Cmd+A` → `Cmd+C` → `pbpaste` +- **search**: Activate → `Cmd+F`/`Cmd+K` → `keystroke "query"` + +## Pitfalls & Gotchas + +1. **Port conflicts (EADDRINUSE)**: Only one app per port. Use unique ports: Codex=9222, ChatGPT=9224, Cursor=9226, ChatWise=9228, Notion=9230, Discord=9232 +2. **IPage abstraction**: OpenCLI wraps Playwright Page as `IPage` (`src/types.ts`). Use `page.pressKey()` and `page.evaluate()`, NOT `page.keyboard.press()` +3. **Timing**: Always add `await page.wait(0.5)` to `1.0` after DOM mutations. Returning too early disconnects prematurely +4. **AppleScript requires Accessibility**: Terminal app must be granted permission in System Settings → Privacy & Security → Accessibility + +## Port Assignment Table + +| App | Port | Mode | +|-----|------|------| +| Codex | 9222 | CDP | +| ChatGPT | 9224 | CDP / AppleScript | +| Cursor | 9226 | CDP | +| ChatWise | 9228 | CDP | +| Notion | 9230 | CDP | +| Discord | 9232 | CDP | diff --git a/README.md b/README.md index bdb01bf..0023e12 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,6 @@ Run `opencli list` for the live registry. | **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 🔐 Browser | | **antigravity** | `status` `send` `read` `new` `evaluate` | 🖥️ Desktop | | **chatgpt** | `status` `new` `send` `read` `ask` | 🖥️ Desktop | -| **wechat** | `status` `send` `new` `search` `read` | 🖥️ Desktop | -| **feishu** | `status` `send` `new` `search` `read` | 🖥️ Desktop | | **xiaohongshu** | `search` `notifications` `feed` `me` `user` `download` | 🔐 Browser | | **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | 🌐 Public | | **zhihu** | `hot` `search` `question` `download` | 🔐 Browser | diff --git a/README.zh-CN.md b/README.zh-CN.md index c741bbc..f5dbe32 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -161,8 +161,6 @@ npm install -g @jackwener/opencli@latest | **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 🔐 浏览器 | | **antigravity** | `status` `send` `read` `new` `evaluate` | 🖥️ 桌面端 | | **chatgpt** | `status` `new` `send` `read` `ask` | 🖥️ 桌面端 | -| **wechat** | `status` `send` `new` `search` `read` | 🖥️ 桌面端 | -| **feishu** | `status` `send` `new` `search` `read` | 🖥️ 桌面端 | | **xiaohongshu** | `search` `notifications` `feed` `me` `user` `download` | 🔐 浏览器 | | **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | 🌐 公开 | | **zhihu** | `hot` `search` `question` `download` | 🔐 浏览器 | diff --git a/src/clis/feishu/README.md b/src/clis/feishu/README.md deleted file mode 100644 index a0ba95c..0000000 --- a/src/clis/feishu/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Feishu (飞书/Lark) Desktop Adapter - -Control the **Feishu Desktop App** from the terminal via macOS AppleScript automation. - -> **Note:** Feishu/Lark uses a custom `Lark Framework` (Chromium v131 embedded) but does **not** expose CDP. This adapter uses AppleScript + clipboard instead. - -## Commands - -| Command | Description | -|---------|-------------| -| `feishu status` | Check if Feishu is running | -| `feishu send "message"` | Send a message in the active conversation | -| `feishu new` | Start a new chat (Cmd+N) | -| `feishu search "query"` | Open global search (Cmd+K) | -| `feishu read` | Copy recent messages from the active chat | - -## Requirements - -- macOS only (uses AppleScript) -- **Accessibility permissions** must be granted to your terminal app -- Feishu must be running with a conversation window open diff --git a/src/clis/feishu/README.zh-CN.md b/src/clis/feishu/README.zh-CN.md deleted file mode 100644 index 284a140..0000000 --- a/src/clis/feishu/README.zh-CN.md +++ /dev/null @@ -1,21 +0,0 @@ -# 飞书桌面端适配器 - -通过 macOS AppleScript 在终端中控制 **飞书桌面应用**。 - -> **注意:** 飞书使用自研 `Lark Framework`(内嵌 Chromium v131),但不暴露 CDP。因此使用 AppleScript + 剪贴板方式。 - -## 命令 - -| 命令 | 说明 | -|------|------| -| `feishu status` | 检查飞书是否在运行 | -| `feishu send "消息"` | 在当前对话窗口发送消息 | -| `feishu new` | 开始新对话(Cmd+N) | -| `feishu search "关键词"` | 打开全局搜索(Cmd+K) | -| `feishu read` | 复制当前对话的聊天记录 | - -## 前置条件 - -- 仅 macOS(使用 AppleScript) -- 需要为终端应用开启**辅助功能权限** -- 飞书必须打开且有对话窗口 diff --git a/src/clis/feishu/new.ts b/src/clis/feishu/new.ts deleted file mode 100644 index 6f426f2..0000000 --- a/src/clis/feishu/new.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { execSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const newCommand = cli({ - site: 'feishu', - name: 'new', - description: 'Start a new chat in Feishu (Cmd+N)', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [], - columns: ['Status'], - func: async (page: IPage | null) => { - try { - execSync("osascript -e 'tell application \"Lark\" to activate'"); - execSync("osascript -e 'delay 0.3'"); - execSync("osascript -e 'tell application \"System Events\" to keystroke \"n\" using command down'"); - return [{ Status: 'Success' }]; - } catch (err: any) { - return [{ Status: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/feishu/read.ts b/src/clis/feishu/read.ts deleted file mode 100644 index 4f71061..0000000 --- a/src/clis/feishu/read.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { execSync, spawnSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const readCommand = cli({ - site: 'feishu', - name: 'read', - description: 'Copy recent messages from the active Feishu conversation (Cmd+A → Cmd+C)', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [], - columns: ['Content'], - func: async (page: IPage | null) => { - try { - let clipBackup = ''; - try { clipBackup = execSync('pbpaste', { encoding: 'utf-8' }); } catch {} - - spawnSync('pbcopy', { input: '__OPENCLI_EMPTY__' }); - - execSync("osascript -e 'tell application \"Lark\" to activate'"); - execSync("osascript -e 'delay 0.3'"); - - const cmd = "osascript " + - "-e 'tell application \"System Events\"' " + - "-e 'keystroke \"a\" using command down' " + - "-e 'delay 0.2' " + - "-e 'keystroke \"c\" using command down' " + - "-e 'end tell'"; - execSync(cmd); - execSync("osascript -e 'delay 0.3'"); - - const result = execSync('pbpaste', { encoding: 'utf-8' }).trim(); - - if (clipBackup) spawnSync('pbcopy', { input: clipBackup }); - - if (!result || result === '__OPENCLI_EMPTY__') { - return [{ Content: 'Could not copy chat content. Try clicking in the message area first.' }]; - } - - return [{ Content: result }]; - } catch (err: any) { - return [{ Content: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/feishu/search.ts b/src/clis/feishu/search.ts deleted file mode 100644 index 5f13dd1..0000000 --- a/src/clis/feishu/search.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { execSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const searchCommand = cli({ - site: 'feishu', - name: 'search', - description: 'Open Feishu search and type a query (Cmd+K)', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [{ name: 'query', required: true, positional: true, help: 'Search query' }], - columns: ['Status'], - func: async (page: IPage | null, kwargs: any) => { - const query = kwargs.query as string; - try { - execSync("osascript -e 'tell application \"Lark\" to activate'"); - execSync("osascript -e 'delay 0.3'"); - - // Feishu uses Cmd+K for global search (similar to Slack/Notion) - execSync("osascript -e 'tell application \"System Events\" to keystroke \"k\" using command down'"); - execSync("osascript -e 'delay 0.5'"); - - const escaped = query.replace(/'/g, "'\\''"); - execSync(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`); - - return [{ Status: `Searched for: ${query}` }]; - } catch (err: any) { - return [{ Status: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/feishu/send.ts b/src/clis/feishu/send.ts deleted file mode 100644 index 94df63b..0000000 --- a/src/clis/feishu/send.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { execSync, spawnSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const sendCommand = cli({ - site: 'feishu', - name: 'send', - description: 'Send a message in the active Feishu conversation window', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }], - columns: ['Status'], - func: async (page: IPage | null, kwargs: any) => { - const text = kwargs.text as string; - try { - let clipBackup = ''; - try { clipBackup = execSync('pbpaste', { encoding: 'utf-8' }); } catch {} - - spawnSync('pbcopy', { input: text }); - - execSync("osascript -e 'tell application \"Lark\" to activate'"); - execSync("osascript -e 'delay 0.5'"); - - const cmd = "osascript " + - "-e 'tell application \"System Events\"' " + - "-e 'keystroke \"v\" using command down' " + - "-e 'delay 0.2' " + - "-e 'keystroke return' " + - "-e 'end tell'"; - execSync(cmd); - - if (clipBackup) spawnSync('pbcopy', { input: clipBackup }); - - return [{ Status: 'Success' }]; - } catch (err: any) { - return [{ Status: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/feishu/status.ts b/src/clis/feishu/status.ts deleted file mode 100644 index a33d97f..0000000 --- a/src/clis/feishu/status.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { execSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const statusCommand = cli({ - site: 'feishu', - name: 'status', - description: 'Check if Feishu (Lark) Desktop App is running on macOS', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [], - columns: ['Status'], - func: async (page: IPage | null) => { - try { - const output = execSync("osascript -e 'application \"Lark\" is running'", { encoding: 'utf-8' }).trim(); - return [{ Status: output === 'true' ? 'Running' : 'Stopped' }]; - } catch { - return [{ Status: 'Error querying application state' }]; - } - }, -}); diff --git a/src/clis/wechat/README.md b/src/clis/wechat/README.md deleted file mode 100644 index de6940e..0000000 --- a/src/clis/wechat/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# WeChat (微信) Desktop Adapter - -Control the **WeChat Desktop App** from the terminal via macOS AppleScript automation. - -> **Note:** WeChat Mac is a native Cocoa app (not Electron), so this adapter uses AppleScript + clipboard instead of CDP. Requires macOS Accessibility permissions for your terminal app. - -## Commands - -| Command | Description | -|---------|-------------| -| `wechat status` | Check if WeChat is running | -| `wechat send "message"` | Send a message in the active conversation | -| `wechat new` | Start a new chat (Cmd+N) | -| `wechat search "query"` | Open search and type a query (Cmd+F) | -| `wechat read` | Copy recent messages from the active chat | - -## Requirements - -- macOS only (uses AppleScript) -- **Accessibility permissions** must be granted to your terminal app (System Settings → Privacy & Security → Accessibility) -- WeChat must be running with a conversation window open diff --git a/src/clis/wechat/README.zh-CN.md b/src/clis/wechat/README.zh-CN.md deleted file mode 100644 index 63d0418..0000000 --- a/src/clis/wechat/README.zh-CN.md +++ /dev/null @@ -1,21 +0,0 @@ -# 微信桌面端适配器 - -通过 macOS AppleScript 在终端中控制 **微信桌面应用**。 - -> **注意:** 微信 Mac 版是原生 Cocoa 应用(非 Electron),因此使用 AppleScript + 剪贴板方式,而非 CDP。需要为终端开启 macOS 辅助功能权限。 - -## 命令 - -| 命令 | 说明 | -|------|------| -| `wechat status` | 检查微信是否在运行 | -| `wechat send "消息"` | 在当前对话窗口发送消息 | -| `wechat new` | 开始新对话(Cmd+N) | -| `wechat search "关键词"` | 打开搜索并输入(Cmd+F) | -| `wechat read` | 复制当前对话的聊天记录 | - -## 前置条件 - -- 仅 macOS(使用 AppleScript) -- 需要为终端应用开启**辅助功能权限**(系统设置 → 隐私与安全 → 辅助功能) -- 微信必须打开且有对话窗口 diff --git a/src/clis/wechat/new.ts b/src/clis/wechat/new.ts deleted file mode 100644 index 61c4d24..0000000 --- a/src/clis/wechat/new.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { execSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const newCommand = cli({ - site: 'wechat', - name: 'new', - description: 'Start a new chat in WeChat (Cmd+N)', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [], - columns: ['Status'], - func: async (page: IPage | null) => { - try { - execSync("osascript -e 'tell application \"WeChat\" to activate'"); - execSync("osascript -e 'delay 0.3'"); - execSync("osascript -e 'tell application \"System Events\" to keystroke \"n\" using command down'"); - return [{ Status: 'Success' }]; - } catch (err: any) { - return [{ Status: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/wechat/read.ts b/src/clis/wechat/read.ts deleted file mode 100644 index 81a53a0..0000000 --- a/src/clis/wechat/read.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { execSync, spawnSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const readCommand = cli({ - site: 'wechat', - name: 'read', - description: 'Copy recent messages from the active WeChat conversation (Cmd+A → Cmd+C)', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [], - columns: ['Content'], - func: async (page: IPage | null) => { - try { - // Backup clipboard - let clipBackup = ''; - try { clipBackup = execSync('pbpaste', { encoding: 'utf-8' }); } catch {} - - // Clear clipboard marker - spawnSync('pbcopy', { input: '__OPENCLI_EMPTY__' }); - - execSync("osascript -e 'tell application \"WeChat\" to activate'"); - execSync("osascript -e 'delay 0.3'"); - - // Try Cmd+A then Cmd+C to select and copy chat content - const cmd = "osascript " + - "-e 'tell application \"System Events\"' " + - "-e 'keystroke \"a\" using command down' " + - "-e 'delay 0.2' " + - "-e 'keystroke \"c\" using command down' " + - "-e 'end tell'"; - execSync(cmd); - execSync("osascript -e 'delay 0.3'"); - - const result = execSync('pbpaste', { encoding: 'utf-8' }).trim(); - - // Restore clipboard - if (clipBackup) spawnSync('pbcopy', { input: clipBackup }); - - if (!result || result === '__OPENCLI_EMPTY__') { - return [{ Content: 'Could not copy chat content. WeChat may not support Cmd+A in the message area.' }]; - } - - return [{ Content: result }]; - } catch (err: any) { - return [{ Content: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/wechat/search.ts b/src/clis/wechat/search.ts deleted file mode 100644 index 2abf15b..0000000 --- a/src/clis/wechat/search.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { execSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const searchCommand = cli({ - site: 'wechat', - name: 'search', - description: 'Open WeChat search and type a query (Cmd+F)', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [{ name: 'query', required: true, positional: true, help: 'Search query (contact name, group, or message)' }], - columns: ['Status'], - func: async (page: IPage | null, kwargs: any) => { - const query = kwargs.query as string; - try { - execSync("osascript -e 'tell application \"WeChat\" to activate'"); - execSync("osascript -e 'delay 0.3'"); - - // Cmd+F opens search in WeChat Mac - execSync("osascript -e 'tell application \"System Events\" to keystroke \"f\" using command down'"); - execSync("osascript -e 'delay 0.5'"); - - // Type the search query - const escaped = query.replace(/'/g, "'\\''"); - execSync(`osascript -e 'tell application "System Events" to keystroke "${escaped}"'`); - - return [{ Status: `Searched for: ${query}` }]; - } catch (err: any) { - return [{ Status: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/wechat/send.ts b/src/clis/wechat/send.ts deleted file mode 100644 index 10dd42e..0000000 --- a/src/clis/wechat/send.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { execSync, spawnSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const sendCommand = cli({ - site: 'wechat', - name: 'send', - description: 'Send a message in the active WeChat conversation window', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [{ name: 'text', required: true, positional: true, help: 'Message to send' }], - columns: ['Status'], - func: async (page: IPage | null, kwargs: any) => { - const text = kwargs.text as string; - try { - // Backup clipboard - let clipBackup = ''; - try { clipBackup = execSync('pbpaste', { encoding: 'utf-8' }); } catch {} - - // Copy text to clipboard - spawnSync('pbcopy', { input: text }); - - // Activate WeChat and paste + send - execSync("osascript -e 'tell application \"WeChat\" to activate'"); - execSync("osascript -e 'delay 0.5'"); - - const cmd = "osascript " + - "-e 'tell application \"System Events\"' " + - "-e 'keystroke \"v\" using command down' " + - "-e 'delay 0.2' " + - "-e 'keystroke return' " + - "-e 'end tell'"; - execSync(cmd); - - // Restore clipboard - if (clipBackup) spawnSync('pbcopy', { input: clipBackup }); - - return [{ Status: 'Success' }]; - } catch (err: any) { - return [{ Status: 'Error: ' + err.message }]; - } - }, -}); diff --git a/src/clis/wechat/status.ts b/src/clis/wechat/status.ts deleted file mode 100644 index 3669617..0000000 --- a/src/clis/wechat/status.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { execSync } from 'node:child_process'; -import { cli, Strategy } from '../../registry.js'; -import type { IPage } from '../../types.js'; - -export const statusCommand = cli({ - site: 'wechat', - name: 'status', - description: 'Check if WeChat Desktop App is running on macOS', - domain: 'localhost', - strategy: Strategy.PUBLIC, - browser: false, - args: [], - columns: ['Status'], - func: async (page: IPage | null) => { - try { - const output = execSync("osascript -e 'application \"WeChat\" is running'", { encoding: 'utf-8' }).trim(); - return [{ Status: output === 'true' ? 'Running' : 'Stopped' }]; - } catch { - return [{ Status: 'Error querying application state' }]; - } - }, -});