使用 OpenCLI 通过 CDP 控制 Qoder IDE 桌面端:完整命令手册与源码原理解析
【免费下载链接】OpenCLIMake Any Website into CLI & Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI
导读
本文围绕 OpenCLI 仓库中的 Qoder 桌面适配器(docs/adapters/desktop/qoder.md)展开,讲解如何让 AI Agent 通过 Chrome DevTools Protocol(CDP)直接操作本机已登录的 Qoder IDE:从带调试端口启动应用、设置环境变量,到用 20 条命令完成新建会话、收发消息、读取回复、切换侧边栏与 Composer 增强等完整操作。读完后,你将掌握一套可直接复制的桌面 AI IDE 自动化方案,并理解其"以可见 UI 为唯一事实来源"的底层设计。
一、背景:为什么需要为 Qoder 单独做一个桌面适配器
Qoder 是阿里巴巴出品的、基于 Electron / VS Code 技术栈衍生的 AI IDE(bundleId: com.qoder.ide)。它没有开放的 HTTP API 供外部程序调用聊天能力,但作为 Electron 应用,它的渲染进程本身就是"一个浏览器页面",天然可以通过Chrome DevTools Protocol(CDP)被外部驱动。
OpenCLI 正是利用这一点,把 Qoder 当作一个"本地网站"来操控:Agent 不需要任何 API Key,直接复用你已在 Qoder 中登录的账号、工作区与知识库。相关的端口与进程元数据注册在 src/electron-apps.ts:
qoder: { port: 9237, processName: 'Qoder', executableNames: ['Electron'], bundleId: 'com.qoder.ide', displayName: 'Qoder', },从源码结构看,OpenCLI 对 Qoder、ChatGPT、Trae Solo 等桌面应用采用了同一套 Electron 适配模式,Qoder 的专用端口是9237。
二、前置条件:以调试模式启动 Qoder
1. 安装 Qoder
首先安装 Qoder 桌面应用(macOS / Windows / Linux 均可,下文命令以 macOS 路径为例)。
2. 带 CDP 启动
Qoder 必须先以远程调试模式启动,OpenCLI 才能连接。官方文档给出的启动命令为:
/Applications/Qoder.app/Contents/MacOS/Electron \ --remote-debugging-port=9237 \ --remote-allow-origins='*'两点说明:
--remote-debugging-port=9237必须与 src/electron-apps.ts 中注册的port: 9237保持一致,这是 OpenCLI 发现端口的依据;--remote-allow-origins='*'用于放开 CDP 的跨源限制,否则来自非 DevTools 来源的连接请求会被 Chromium 拒绝。
仓库中同样存在配套的启动脚本约定(qoder-launch-with-cdp.sh,见 clis/qoder/_utils.js 注释),目的都是把上述命令固化下来。
三、环境配置:告诉 OpenCLI 去哪里连
启动 Qoder 后,在终端导出 CDP 端点环境变量:
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:9237"该变量是所有桌面适配器(Qoder、ChatGPT App、Trae 等)通用的连接配置。设置后,opencli qoder ...系列命令就会自动连接到当前 Qoder 渲染进程。
四、命令全景
适配器共注册 20 条命令,按功能分为四组。下表来自命令注册清单(见 clis/qoder/qoder.test.js 的断言),标注了每条命令的读/写访问级别:
| 分组 | 命令 | 访问级别 | 作用 |
|---|---|---|---|
| 诊断 | qoder status | read | 检查 CDP 连接,输出渲染进程 URL 与标题 |
| Quest 生命周期 | qoder new | write | 新建一个 Quest(会话) |
qoder history [--limit] | read | 列出侧边栏可见的 Quest | |
qoder read [--limit] | read | 读取当前 Quest 中可见的对话轮次 | |
qoder send "message" | write | 向当前 Quest 发送消息(即发即走) | |
qoder ask "prompt" [--timeout] | write | 发送提示词并等待可见回复 | |
| 侧边栏与视图 | qoder sidebar-toggle | write | 折叠/展开 Quest 侧边栏 |
qoder open-panel | write | 开关底部面板 | |
qoder search "query" | read | 打开搜索面板并列出结果 | |
qoder settings | write | 打开设置 | |
qoder knowledge | write | 打开知识库视图 | |
qoder marketplace | write | 打开插件市场 | |
qoder credits | read | 打开 Credits 用量并读取弹层 | |
qoder view-all | write | 点击 Quest 列表中的 View all | |
qoder add-workspace | write | 打开添加工作区目录选择器 | |
qoder account [--username] | read | 打开账号菜单并列出条目 | |
qoder more-actions | read | 打开 More Actions 并列出菜单项 | |
| Composer | qoder prompt-enhance | write | 对当前草稿执行 Prompt Enhance |
qoder open-editor | write | 在编辑器视图中打开当前草稿 |
下面按分组逐条展开用法与实现细节。
五、诊断命令
opencli qoder status
检查当前与 Qoder 渲染进程的 CDP 连接是否正常,并报告两个关键信息:当前页面 URL 与页面标题。输出列为Status / Url / Title。
其实现(clis/qoder/status.js)非常直白——通过page.evaluate读取window.location.href与document.title:
Url: await evaluateQoder(page, 'window.location.href'), Title: await evaluateQoder(page, 'document.title'),建议在任何自动化流程开始时先执行status,确认 Qoder 已带调试端口启动、且当前处于预期的视图(例如已打开某个 Quest),再做后续操作。
六、Quest 生命周期命令
Qoder 中一次对话被称为Quest(对应 VS Code 系的聊天线程),围绕它有一组核心命令,实现集中在 clis/qoder/quest.js。
opencli qoder new
新建一个 Quest。实现上不是发快捷键,而是通过文本匹配点击侧边栏的New Quest按钮(clis/qoder/quest.js):
const res = await evaluateQoder(page, clickByTextScript(['New Quest'])); if (!res?.ok) throw new CommandExecutionError(res?.reason || 'New Quest button not found', '');若按钮当前不可见,命令会抛出带原因的CommandExecutionError(typed error),而不会静默失败。
opencli qoder history --limit 20
列出侧边栏中可见的 Quest,返回Index / Title两列。--limit为可选参数,默认50,必须是正整数(非法值抛出ArgumentError,见 clis/qoder/_utils.js 的parsePositiveInt)。
实现要点(clis/qoder/history.js):
- 在侧边栏区域(
[class*="sidebar"], [class*="quest-list"], [class*="quest"])内查找可见的可点击行; - 通过正则过滤掉
New Quest / Search / Settings / View all / Knowledge / Marketplace / Credits Usage等固定控件与包含⌘的快捷键标签; - 对标题去重、限制长度(2~200 字符)后返回;
- 若侧边栏太窄导致没有任何 Quest 可见,会抛出
EmptyResultError,错误信息提示"尝试拉宽侧边栏或先选择工作区"。
opencli qoder read --limit 30
读取当前 Quest 中可见的所有对话轮次,返回Index / Role / Text。--limit默认30。
角色判定由 clis/qoder/_utils.js 中的QODER_TURNS_JS完成:它先在聊天面板中按高度选出最大的可见面板,再遍历其中的消息候选元素,按 CSS 类名启发式识别User(user/me-/right模式)与Assistant(assistant/ai/bot/response模式),其余归为Turn;同时过滤掉过短(<5 字符)、过长(>4000 字符)与嵌套过深(children ≥ 20)的干扰元素,并做文本去重。每条消息文本截取前 1200 字符返回。
opencli qoder read --limit 30opencli qoder send "message"
向当前 Quest 发送一条消息(fire-and-forget),返回Status / Length。它的执行链路体现了"以可见 UI 为事实来源"的核心设计(clis/qoder/quest.js):
- 先统计发送前的可见消息条数
beforeCount; - 用
buildQoderInjectTextScript(text)在高置信度的 Composer 输入框中注入文本(见下文"Composer 识别"); - 依次尝试
button[aria-label="Send message"]、button[title="Send message"],兜底再用文本匹配Send message / Send / 发送; - 轮询等待消息条数增长(
waitForMessageCountGrowth,最多 5 秒)——只有确实出现了新的可见消息行,命令才返回成功,否则抛出CommandExecutionError。
这一点也被测试显式覆盖(clis/qoder/qoder.test.js):"点击发送后没有新增可见消息行"会被断言为失败。
opencli qoder ask "prompt" --timeout 120
ask是 Agent 最常用的"提问并等待回答"命令,返回两行结果:User提示词与Assistant回复(各含WaitedSeconds等待秒数)。参数:
text:位置参数,提示词正文,必填;--timeout:可选整数,最大等待秒数,默认120。
实现上先完整复用send的注入+发送逻辑,然后进入轮询循环(clis/qoder/quest.js):
- 每 1.5 秒统计一次可见消息条数;
- 以消息条数是否变化作为"是否仍在生成"的判据:条数变化则重置稳定计数,连续 6 次无变化(约 9 秒静止)且消息数确有增长时判定回复稳定;
- 稳定后调用
qoderResponseAfterScript取出发送时间点之后的最新回复; - 若超时仍未拿到回复,抛出带指引的
TimeoutError(错误信息建议确认 Qoder 已发出提示词并完成生成,再调大--timeout重试)。
opencli qoder ask "请帮我 review 当前工作区的代码结构" --timeout 180七、侧边栏与视图命令
这组命令全部位于 clis/qoder/ui.js,大部分是"点击某个可见按钮 + 读取结果",非常轻量。
侧边栏与面板开关
opencli qoder sidebar-toggle # 折叠/展开 Quest 列表(等价 ⌘B) opencli qoder open-panel # 开关底部面板(Output/Terminal/Debug Console,等价 ⌥⌘B)两者均按可见文本匹配Collapse Quest List / Expand Quest List与Open Panel / Close Panel,并返回实际命中的文本。
搜索
opencli qoder search "query" --limit 20search是少数"读"性质的交互命令,流程分三步(clis/qoder/ui.js):
- 点击
Search(⌘P)打开搜索面板; - 向最近挂载的可见输入框注入查询词,并派发
input/change事件驱动前端过滤; - 收集
[role="option"] / [role="menuitem"]中的可见选项文本(截取 200 字符、去重),最后按 Escape 关闭面板。
--limit默认 20;无匹配结果时抛出EmptyResultError。
视图导航类
以下命令都是"点击对应按钮":
opencli qoder settings # 打开设置(文本匹配失败时兜底按 aria-label 匹配) opencli qoder knowledge # 打开知识库视图(个人/团队知识库) opencli qoder marketplace # 打开插件/技能市场 opencli qoder view-all # 点击 Quest 列表中的 View all opencli qoder add-workspace # 打开添加工作区目录选择器需要特别注意的是add-workspace:它只会点开系统的目录选择对话框,实际的文件夹选择必须由用户在 Qoder 界面中手动完成(系统文件选择器不受 CDP 控制),命令返回状态为clicked — folder picker opened (manual selection required)。
信息读取类
opencli qoder credits # 点击 Credits Usage,读取弹层文本 opencli qoder account [--username name] # 打开账号菜单并列出菜单项 opencli qoder more-actions # 打开 More Actions 并列出菜单项credits:点击后从最近出现的可见弹层([role="dialog"] / popover / popup)提取最多 600 字符的用量文本,随后按 Escape 关闭;account:账号按钮的标签就是当前用户名。优先使用--username显式指定;未指定时通过启发式在可见按钮中排除已知控件(New Quest / Search / Settings / Knowledge / Marketplace等)与New/Open/Add/...开头动词,反推出用户名按钮;点击后读取[role="menu"] / dropdown弹层中的菜单项,最多列 20 条;more-actions:点击后读取[role="menuitem"] / button中的菜单项并列出;菜单已打开但无条目时抛EmptyResultError。
八、Composer 增强命令
位于 clis/qoder/composer.js:
opencli qoder prompt-enhance # 点击 Prompt Enhance,Qoder 会重写当前草稿以适配 LLM opencli qoder open-editor # 点击 Open Editor,把聊天草稿在完整编辑器窗格中打开两者均为简单的文本匹配点击,prompt-enhance成功后会提示enhanced (check composer),建议回到read确认重写后的内容。
九、底层原理:这些命令是如何操作真实 UI 的
1. 可见性判定
所有点击与读取都先经过IS_VISIBLE_JS(clis/qoder/_utils.js)过滤,它从三个维度判定元素可见:
const r = el.getBoundingClientRect(); if (r.width < 1 || r.height < 1) return false; if (cs.visibility === 'hidden' || cs.display === 'none' || cs.opacity === '0') return false;因此"侧边栏折叠导致按钮不可见"、"弹层未弹出"等情况都会导致命令以 typed error 失败,而不是点错元素——这正是文档 Notes 部分"以可见 UI 为唯一事实来源"的落地实现。
2. 两种点击策略
- 选择器点击
clickFirstScript(selectors):遍历候选 CSS 选择器,点击第一个可见匹配元素; - 文本点击
clickByTextScript(patterns):在button / [role="button"] / a / [role="tab"]中按可见文本(子串或全等匹配,文本长度上限默认 60)定位,适合 Qoder 中大量缺少aria-label的按钮。
两者都会派发完整的指针事件链(pointerdown → mousedown → pointerup → mouseup → click),以兼容 radix 等头部菜单库对事件序列的严格要求(见 clis/qoder/_utils.js 注释)。
3. Composer 高置信度识别
buildQoderInjectTextScript(clis/qoder/_utils.js)给每个可见的contenteditable="true"编辑器打分:
role="textbox"+25;包含message/prompt/ask关键词 +120;包含composer/input+60;- 包含
optional description / user context document / knowledge则-240(明确排除"可选描述"等干扰输入框); - 距视口底部越近加分越多,模拟"输入框在屏幕下方"的直觉;
- 只接受总分 ≥ 40 的最高分候选,找不到时返回
No high-confidence Qoder composer found.
注入文本时优先用document.execCommand('insertText')(走真实输入链路),失败则直接写textContent并派发InputEvent。对应的选择器测试见 clis/qoder/qoder.test.js——测试明确验证"高置信度主输入框被注入,而 'Optional description' 干扰框保持不变"。
4. 回复获取与稳定性判定
QODER_TURNS_JS负责提取对话轮次;ask用"消息条数增长 + 连续约 9 秒无变化"双重条件判断回复生成完成,再通过qoderResponseAfterScript(previousCount, userText)精确取出发送时间点之后、且不等于用户原文的最新回复(clis/qoder/_utils.js)。
5. 桌面命令的公共底座
status/new等命令与 ChatGPT App、Cursor 等适配器共享同一套工厂函数(clis/_shared/desktop-commands.js),例如makeStatusCommand与makeNewCommand(后者按平台发送Meta+N/Control+N),而 Qoder 因 UI 特殊性改用文本匹配点击,这从侧面说明各桌面适配器在统一框架下保留了各自 UI 的定制逻辑。
十、测试验证与可靠性保障
适配器自带完整的单元测试(clis/qoder/qoder.test.js),覆盖四类关键行为:
- 命令注册面:断言 20 条
qoder/*命令全部注册,且读写访问级别与预期一致(read8 条 /write12 条,见上文表格); - 工具函数:
unwrapEvaluateResult能解包 Browser Bridge 的{ session, data }信封;parsePositiveInt对 0、非数字抛ArgumentError; - Composer 注入:验证高置信度选择逻辑,确保注入到正确输入框;
- send 的证据要求:只有消息条数确实增长才返回成功,否则 typed-fail。
这套测试与"命令失败必须是 typed error"的约定共同保证了:Agent 调用qoder/*命令时,能可靠地把失败原因反馈给上层决策,而不是拿到模棱两可的返回。
十一、注意事项与使用限制
- 必须先启动后连接:Qoder 未以
--remote-debugging-port=9237启动时,status会失败;请先完成第二节的启动步骤,再设置OPENCLI_CDP_ENDPOINT; - 可见性是硬约束:侧边栏折叠、面板未打开、按钮在滚动区之外时,点击类命令会抛 typed error。执行
view-all、sidebar-toggle等命令前,建议先status确认当前视图; send/ask要求可见证据:若 Qoder 处于生成中且界面未渲染出新消息行,send可能报"未出现新消息行",ask则可能超时——此时应调大--timeout或确认 Quest 已打开;add-workspace需要人工配合:系统目录选择器无法被 CDP 驱动;- 平台差异:启动路径以 macOS 为例(
/Applications/Qoder.app/...),Windows / Linux 请替换为对应安装路径与可执行文件名。
十二、进一步阅读
- 官方适配器文档:docs/adapters/desktop/qoder.md
- Qoder 应用元数据(端口 / bundleId):src/electron-apps.ts
- 核心工具函数与页面脚本:clis/qoder/_utils.js
- Quest 生命周期实现(new/send/ask):clis/qoder/quest.js
- 侧边栏与视图命令:clis/qoder/ui.js
- Composer 命令:clis/qoder/composer.js
- 适配器测试:clis/qoder/qoder.test.js
- 桌面应用公共命令底座:clis/_shared/desktop-commands.js
【免费下载链接】OpenCLIMake Any Website into CLI & Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考