From 2afbb99660c58ab159f1e3e49c562839a097327e Mon Sep 17 00:00:00 2001 From: jackwener Date: Wed, 18 Mar 2026 19:40:08 +0800 Subject: [PATCH] feat: add ChatGPT Desktop native support + Cursor/Codex advanced commands MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ChatGPT macOS Desktop adapter (status, new, send, read) via AppleScript - Add Cursor composer, model, extract-code commands via CDP - Add Codex model command via CDP - Create adapter READMEs for ChatGPT (EN+ZH) and Cursor (EN+ZH) - Fix README.md duplicate table rows (6 sites were listed twice) - Update command count: 26 sites · 119 commands - Bump version to 0.9.5 --- README.md | 13 ++++-------- README.zh-CN.md | 5 +++-- package.json | 2 +- src/clis/chatgpt/README.md | 35 +++++++++++++++++++++++++++++++ src/clis/chatgpt/README.zh-CN.md | 35 +++++++++++++++++++++++++++++++ src/clis/chatgpt/new.ts | 24 +++++++++++++++++++++ src/clis/chatgpt/read.ts | 32 ++++++++++++++++++++++++++++ src/clis/chatgpt/send.ts | 36 ++++++++++++++++++++++++++++++++ src/clis/chatgpt/status.ts | 22 +++++++++++++++++++ src/clis/codex/README.md | 1 + src/clis/cursor/README.md | 33 +++++++++++++++++++++++++++++ src/clis/cursor/README.zh-CN.md | 33 +++++++++++++++++++++++++++++ 12 files changed, 259 insertions(+), 12 deletions(-) create mode 100644 src/clis/chatgpt/README.md create mode 100644 src/clis/chatgpt/README.zh-CN.md create mode 100644 src/clis/chatgpt/new.ts create mode 100644 src/clis/chatgpt/read.ts create mode 100644 src/clis/chatgpt/send.ts create mode 100644 src/clis/chatgpt/status.ts create mode 100644 src/clis/cursor/README.md create mode 100644 src/clis/cursor/README.zh-CN.md diff --git a/README.md b/README.md index 5dbb625..4dee590 100644 --- a/README.md +++ b/README.md @@ -143,25 +143,20 @@ npm install -g @jackwener/opencli@latest ## Built-in Commands -**19 sites · 80+ commands** — run `opencli list` for the live registry. +**26 sites · 119 commands** — run `opencli list` for the live registry. | Site | Commands | Count | Mode | |------|----------|:-----:|------| | **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` | 18 | 🔐 Browser | | **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 15 | 🔐 Browser | -| **antigravity** | `status` `send` `read` `new` `evaluate` | 5 | 🖥️ Desktop | -| **bbc** | `news` | 1 | 🌐 Public | | **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 11 | 🔐 Browser | -| **boss** | `search` `detail` | 2 | 🔐 Browser | +| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` | 8 | 🖥️ Desktop | | **codex** | `status` `send` `read` `new` `extract-diff` `model` | 6 | 🖥️ Desktop | -| **coupang** | `search` `add-to-cart` | 2 | 🔐 Browser | -| **ctrip** | `search` | 1 | 🔐 Browser | -| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` | 7 | 🖥️ Desktop | -| **github** | `search` | 1 | 🌐 Public | -| **hackernews** | `top` | 1 | 🌐 Public | | **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 6 | 🌐 / 🔐 | | **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 6 | 🔐 Browser | +| **antigravity** | `status` `send` `read` `new` `evaluate` | 5 | 🖥️ Desktop | | **xiaohongshu** | `search` `notifications` `feed` `me` `user` | 5 | 🔐 Browser | +| **chatgpt** | `status` `new` `send` `read` | 4 | 🖥️ Desktop | | **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | 3 | 🌐 Public | | **youtube** | `search` `video` `transcript` | 3 | 🔐 Browser | | **zhihu** | `hot` `search` `question` | 3 | 🔐 Browser | diff --git a/README.zh-CN.md b/README.zh-CN.md index 2efd805..8a8741f 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -144,15 +144,16 @@ npm install -g @jackwener/opencli@latest ## 内置命令 -**19 个站点 · 80+ 命令** — 运行 `opencli list` 查看完整注册表。 +**26 个站点 · 119 命令** — 运行 `opencli list` 查看完整注册表。 | 站点 | 命令 | 数量 | 模式 | |------|------|:----:|------| | **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` | 18 | 🔐 浏览器 | | **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 15 | 🔐 浏览器 | | **antigravity** | `status` `send` `read` `new` `evaluate` | 5 | 🖥️ 桌面端 | +| **chatgpt** | `status` `new` `send` `read` | 4 | 🖥️ 桌面端 | | **codex** | `status` `send` `read` `new` `extract-diff` `model` | 6 | 🖥️ 桌面端 | -| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` | 7 | 🖥️ 桌面端 | +| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` | 8 | 🖥️ 桌面端 | | **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` | 11 | 🔐 浏览器 | | **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 6 | 🌐 / 🔐 | | **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 6 | 🔐 浏览器 | diff --git a/package.json b/package.json index 0b7a2ba..a639302 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@jackwener/opencli", - "version": "0.9.4", + "version": "0.9.5", "publishConfig": { "access": "public" }, diff --git a/src/clis/chatgpt/README.md b/src/clis/chatgpt/README.md new file mode 100644 index 0000000..566094b --- /dev/null +++ b/src/clis/chatgpt/README.md @@ -0,0 +1,35 @@ +# ChatGPT Desktop Adapter for OpenCLI + +Control the **ChatGPT macOS Desktop App** directly from the terminal via native AppleScript automation. Unlike Electron-based apps (Antigravity, Codex, Cursor), ChatGPT Desktop is a native macOS application — OpenCLI drives it using `osascript` and System Events. + +## Prerequisites + +1. Install the official [ChatGPT Desktop App](https://openai.com/chatgpt/mac/) from OpenAI. +2. Grant **Accessibility permissions** to your terminal app (Terminal / iTerm / Warp) in **System Settings → Privacy & Security → Accessibility**. This is required for System Events keystroke simulation. + +## Setup + +No extra environment variables needed — the adapter uses `osascript` directly. + +## Commands + +### Diagnostics +- `opencli chatgpt status`: Check if the ChatGPT app is currently running. + +### Chat Manipulation +- `opencli chatgpt new`: Activate ChatGPT and press `Cmd+N` to start a new conversation. +- `opencli chatgpt send "message"`: Copy your message to clipboard, activate ChatGPT, paste, and submit. +- `opencli chatgpt read`: Copy the last AI response via `Cmd+Shift+C` and return it as text. + +## How It Works + +Unlike CDP-based adapters, this adapter: +- Uses `osascript` to send AppleScript commands to System Events +- Leverages `pbcopy`/`pbpaste` for clipboard-based text transfer +- Requires no remote debugging port — works with the stock app + +## Limitations + +- macOS only (AppleScript dependency) +- Requires Accessibility permissions for keystroke simulation +- `read` command copies the last response — earlier messages need manual scroll diff --git a/src/clis/chatgpt/README.zh-CN.md b/src/clis/chatgpt/README.zh-CN.md new file mode 100644 index 0000000..d6ff280 --- /dev/null +++ b/src/clis/chatgpt/README.zh-CN.md @@ -0,0 +1,35 @@ +# ChatGPT 桌面端适配器 + +通过原生 AppleScript 自动化,在终端中直接控制 **ChatGPT macOS 桌面应用**。与基于 Electron 的应用(Antigravity、Codex、Cursor)不同,ChatGPT Desktop 是原生 macOS 应用 — OpenCLI 使用 `osascript` 和 System Events 来驱动它。 + +## 前置条件 + +1. 安装官方 [ChatGPT Desktop App](https://openai.com/chatgpt/mac/)。 +2. 在 **系统设置 → 隐私与安全性 → 辅助功能** 中为终端应用(Terminal / iTerm / Warp)授予 **辅助功能权限**。这是 System Events 按键模拟所必需的。 + +## 配置 + +无需额外环境变量 — 适配器直接使用 `osascript`。 + +## 命令 + +### 诊断 +- `opencli chatgpt status`:检查 ChatGPT 应用是否在运行。 + +### 对话操作 +- `opencli chatgpt new`:激活 ChatGPT 并按 `Cmd+N` 开始新对话。 +- `opencli chatgpt send "消息"`:将消息复制到剪贴板,激活 ChatGPT,粘贴并提交。 +- `opencli chatgpt read`:通过 `Cmd+Shift+C` 复制最后一条 AI 回复并返回文本。 + +## 工作原理 + +与基于 CDP 的适配器不同,此适配器: +- 使用 `osascript` 向 System Events 发送 AppleScript 命令 +- 利用 `pbcopy`/`pbpaste` 进行基于剪贴板的文本传输 +- 无需远程调试端口 — 直接与原生应用交互 + +## 限制 + +- 仅支持 macOS(AppleScript 依赖) +- 需要辅助功能权限以模拟按键 +- `read` 命令复制最后一条回复 — 更早的消息需要手动滚动 diff --git a/src/clis/chatgpt/new.ts b/src/clis/chatgpt/new.ts new file mode 100644 index 0000000..fe065ab --- /dev/null +++ b/src/clis/chatgpt/new.ts @@ -0,0 +1,24 @@ +import { execSync } from 'node:child_process'; +import { cli, Strategy } from '../../registry.js'; +import type { IPage } from '../../types.js'; + +export const newCommand = cli({ + site: 'chatgpt', + name: 'new', + description: 'Open a new chat in ChatGPT Desktop App', + domain: 'localhost', + strategy: Strategy.PUBLIC, + browser: false, + args: [], + columns: ['Status'], + func: async (page: IPage | null) => { + try { + execSync("osascript -e 'tell application \"ChatGPT\" to activate'"); + execSync("osascript -e 'delay 0.5'"); + 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/chatgpt/read.ts b/src/clis/chatgpt/read.ts new file mode 100644 index 0000000..00237cd --- /dev/null +++ b/src/clis/chatgpt/read.ts @@ -0,0 +1,32 @@ +import { execSync } from 'node:child_process'; +import { cli, Strategy } from '../../registry.js'; +import type { IPage } from '../../types.js'; + +export const readCommand = cli({ + site: 'chatgpt', + name: 'read', + description: 'Copy the most recent ChatGPT Desktop App response to clipboard and read it', + domain: 'localhost', + strategy: Strategy.PUBLIC, + browser: false, + args: [], + columns: ['Role', 'Text'], + func: async (page: IPage | null) => { + try { + execSync("osascript -e 'tell application \"ChatGPT\" to activate'"); + execSync("osascript -e 'delay 0.5'"); + execSync("osascript -e 'tell application \"System Events\" to keystroke \"c\" using {command down, shift down}'"); + execSync("osascript -e 'delay 0.3'"); + + const result = execSync('pbpaste', { encoding: 'utf-8' }).trim(); + + if (!result) { + return [{ Role: 'System', Text: 'No text was copied. Is there a response in the chat?' }]; + } + + return [{ Role: 'Assistant', Text: result }]; + } catch (err: any) { + throw new Error("Failed to read from ChatGPT: " + err.message); + } + }, +}); diff --git a/src/clis/chatgpt/send.ts b/src/clis/chatgpt/send.ts new file mode 100644 index 0000000..baa3480 --- /dev/null +++ b/src/clis/chatgpt/send.ts @@ -0,0 +1,36 @@ +import { execSync, spawnSync } from 'node:child_process'; +import { cli, Strategy } from '../../registry.js'; +import type { IPage } from '../../types.js'; + +export const sendCommand = cli({ + site: 'chatgpt', + name: 'send', + description: 'Send a message to the active ChatGPT Desktop App 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 { + spawnSync('pbcopy', { input: text }); + + execSync("osascript -e 'tell application \"ChatGPT\" 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); + + return [{ Status: 'Success' }]; + } catch (err: any) { + return [{ Status: "Error: " + err.message }]; + } + }, +}); diff --git a/src/clis/chatgpt/status.ts b/src/clis/chatgpt/status.ts new file mode 100644 index 0000000..fdad539 --- /dev/null +++ b/src/clis/chatgpt/status.ts @@ -0,0 +1,22 @@ +import { execSync } from 'node:child_process'; +import { cli, Strategy } from '../../registry.js'; +import type { IPage } from '../../types.js'; + +export const statusCommand = cli({ + site: 'chatgpt', + name: 'status', + description: 'Check if ChatGPT Desktop App is running natively on macOS', + domain: 'localhost', + strategy: Strategy.PUBLIC, + browser: false, + args: [], + columns: ['Status'], + func: async (page: IPage | null) => { + try { + const output = execSync("osascript -e 'application \"ChatGPT\" is running'", { encoding: 'utf-8' }).trim(); + return [{ Status: output === 'true' ? 'Running' : 'Stopped' }]; + } catch { + return [{ Status: 'Error querying application state' }]; + } + }, +}); diff --git a/src/clis/codex/README.md b/src/clis/codex/README.md index 7ad3547..ef24abe 100644 --- a/src/clis/codex/README.md +++ b/src/clis/codex/README.md @@ -31,3 +31,4 @@ export OPENCLI_CODEX_CDP_ENDPOINT="http://127.0.0.1:9222" - *Pro-tip*: You can trigger internal shortcuts by sending them, e.g., `opencli codex send "/review"` or `opencli codex send "$imagegen draw a cat"`. - `opencli codex read`: Extracts the entire current thread history and AI reasoning logs into readable text. - `opencli codex extract-diff`: Automatically scrapes any visual Patch chunks and Code Diffs the AI generated inside the review UI. +- `opencli codex model`: Get the currently active AI model. diff --git a/src/clis/cursor/README.md b/src/clis/cursor/README.md new file mode 100644 index 0000000..a4eb5a4 --- /dev/null +++ b/src/clis/cursor/README.md @@ -0,0 +1,33 @@ +# Cursor Adapter for OpenCLI + +Control the **Cursor IDE** from the terminal via Chrome DevTools Protocol (CDP). Since Cursor is built on Electron (VS Code fork), OpenCLI can drive its internal UI, automate Composer interactions, and manipulate chat sessions. + +## Prerequisites + +1. Install [Cursor](https://cursor.sh/). +2. Launch it with the remote debugging port: + ```bash + /Applications/Cursor.app/Contents/MacOS/Cursor --remote-debugging-port=9226 + ``` + +## Setup + +```bash +export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9226" +``` + +## Commands + +### Diagnostics +- `opencli cursor status`: Check CDP connection status. +- `opencli cursor dump`: Dump the full DOM and Accessibility snapshot to `/tmp/cursor-dom.html` and `/tmp/cursor-snapshot.json`. + +### Chat Manipulation +- `opencli cursor new`: Press `Cmd+N` to start a new file/tab. +- `opencli cursor send "message"`: Inject text into the active Composer/Chat input and submit. +- `opencli cursor read`: Extract the full conversation history from the active chat panel. + +### AI Features +- `opencli cursor composer "prompt"`: Open the Composer panel (`Cmd+I`) and send a prompt for inline AI editing. +- `opencli cursor model`: Get the currently active AI model (e.g., `claude-4.5-sonnet`). +- `opencli cursor extract-code`: Extract all code blocks from the current conversation. diff --git a/src/clis/cursor/README.zh-CN.md b/src/clis/cursor/README.zh-CN.md new file mode 100644 index 0000000..41ca0d9 --- /dev/null +++ b/src/clis/cursor/README.zh-CN.md @@ -0,0 +1,33 @@ +# Cursor 适配器 + +通过 Chrome DevTools Protocol (CDP) 在终端中控制 **Cursor IDE**。由于 Cursor 基于 Electron(VS Code 分支),OpenCLI 可以驱动其内部 UI,自动化 Composer 交互,操控聊天会话。 + +## 前置条件 + +1. 安装 [Cursor](https://cursor.sh/)。 +2. 通过远程调试端口启动: + ```bash + /Applications/Cursor.app/Contents/MacOS/Cursor --remote-debugging-port=9226 + ``` + +## 配置 + +```bash +export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9226" +``` + +## 命令 + +### 诊断 +- `opencli cursor status`:检查 CDP 连接状态。 +- `opencli cursor dump`:导出完整 DOM 和 Accessibility 快照到 `/tmp/cursor-dom.html` 和 `/tmp/cursor-snapshot.json`。 + +### 对话操作 +- `opencli cursor new`:按 `Cmd+N` 创建新文件/标签。 +- `opencli cursor send "消息"`:将文本注入活跃的 Composer/Chat 输入框并提交。 +- `opencli cursor read`:提取当前聊天面板的完整对话历史。 + +### AI 功能 +- `opencli cursor composer "提示词"`:打开 Composer 面板(`Cmd+I`)并发送提示词进行内联 AI 编辑。 +- `opencli cursor model`:获取当前活跃的 AI 模型(如 `claude-4.5-sonnet`)。 +- `opencli cursor extract-code`:从当前对话中提取所有代码块。