OpenCLI Grok 适配器实战:用浏览器会话在终端驱动 grok.com 聊天、图像生成与对话导出
【免费下载链接】OpenCLIMake Any Website into CLI & Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI
本指南以 OpenCLI 仓库中 grok 适配器文档 为主体,完整讲解如何通过opencli grok系列命令在终端驱动 grok.com 的聊天、历史阅读、对话导出、图像生成与会话管理。所有命令都复用你已登录的 Chrome 浏览器会话,无需 API Key;读完本文,你将掌握每个命令的参数、输出列、底层实现原理与异常处理策略,能够直接在 Agent 工作流中集成 Grok。
模式与原理:浏览器适配器(Browser Adapter)
Grok 适配器属于🔐 Browser 模式,作用于grok.com域。与需要 API Key 的云端适配器不同,浏览器适配器的核心思路是:CLI 通过 Browser Bridge 扩展 连接到你的 Chrome 实例,在已登录的 grok.com 页面上下文中执行 JavaScript,从而驱动真实页面完成交互。
整个链路为:opencli(Node.js)↔ WebSocket(localhost:19825)↔ micro-daemon(自动启动)↔ Chrome 扩展 ↔ grok.com 页面。浏览器扩展在网页上下文中执行脚本,天然携带登录 Cookie,因此不需要任何令牌或手动配置。
从源码结构看,适配器由 clis/grok 目录下的 13 个命令文件加上一个共享工具模块 clis/grok/utils.js 组成:
status、history、read、detail、export、export-all为只读操作(access:read);ask、send、new、image、pin、unpin、delete为写入操作(access:write);- 所有命令都声明
strategy: Strategy.COOKIE、browser: true、siteSession: 'persistent'(详见各命令源码,如 ask.js),意味着命令默认复用同一个持续会话的 Grok 页面,连续调用ask/read/detail会停留在同一页面上下文。
前置条件
在运行任何opencli grok命令前,请确认:
- Chrome 正在运行;
- 已登录 grok.com(在 Chrome 中完成登录);
- Browser Bridge 扩展 已安装。
Browser Bridge 的安装非常简单:在chrome://extensions开启开发者模式后,加载extension/目录(源码方式)或官方 Release 的 zip 包即可。装好后无需任何配置,daemon 会在首次执行浏览器命令时自动启动,可用opencli doctor检查扩展与 daemon 的连接状态(详见 docs/guide/browser-bridge.md)。
命令总览
下表是opencli grok的完整命令清单、功能与访问级别(写操作会改变 Grok 账户数据,只读操作仅读取页面):
| Command | Description | Access |
|---|---|---|
opencli grok status | Page availability, login state, current model and session | read |
opencli grok history | List recent conversations from the sidebar (requires login) | read |
opencli grok read | Read messages in the current conversation | read |
opencli grok detail <id> | Open a conversation by ID and read its messages | read |
opencli grok export | Export visible conversation history metadata from the history dialog | read |
opencli grok export-all | Export conversation metadata plus per-conversation transcript JSON | read |
opencli grok ask <prompt> | Send a prompt and wait for the assistant reply | write |
opencli grok send <prompt> | Fire-and-forget: send a prompt without waiting | write |
opencli grok new | Start a fresh conversation | write |
opencli grok image <prompt> | Generate images via Grok and return their URLs | write |
opencli grok pin <id> | Pin a conversation from the sidebar context menu | write |
opencli grok unpin <id> | Unpin a conversation from the sidebar context menu | write |
opencli grok delete <id> --yes | Delete a sidebar conversation after an explicit--yesconfirmation | write |
会话与状态检查:status / new / history
status:最快的健康检查
opencli grok status返回页面可用性、登录状态、当前模型与会话 ID,是所有 Grok 命令的"体检入口":
opencli grok status输出列:Status, Login, Model, SessionId, Url。
底层实现位于 clis/grok/status.js:先ensureOnGrok(page)确保停留在 grok.com,然后并行执行登录检测、会话 ID 提取、模型标签提取。其中登录检测逻辑(utils.js)以输入框(composer)是否存在作为登录标志——登出状态下 grok.com 渲染的是"Sign in"引导按钮而非 TipTap 编辑器;模型标签则优先使用稳定的#model-select-trigger选择器而非随浏览器语言变化的 aria-label(utils.js)。
需要注意:当模型或会话 ID 无法检测时(如页面仍在加载),status返回的Model/SessionId是null而不是字符串哨兵值(源码中明确Model: model ? model : null),Agent 代码中应针对null做分支判断。
new:开启新对话
opencli grok new输出列:Status。底层实现很简洁(new.js):Grok 当前 UI 没有独立的"新建对话"按钮,startNewChat的做法是直接导航回https://grok.com/首页(utils.js),因此该命令等价于"回到首页开始新会话"。
history:从侧边栏列出最近对话
opencli grok history --limit 10输出列:Index, Title, Url(Url 为https://grok.com/c/<id>形式)。--limit默认 20、最大 100。需要登录,未登录抛AuthRequiredError。
底层逻辑(history.js + utils.js)扫描侧边栏中所有a[href^="/c/"]可见链接,按 UUID 去重后提取标题;针对侧边栏中"标题链接"与"图标链接"(图标链接无文本)的 DOM 顺序问题,采用两遍扫描:先收集首个非空标题,再用第二轮为同 ID 的空标题回填。注意history读取的是当前侧边栏可见部分——如果 Grok 懒加载了更早的对话,需要先在浏览器里滚动侧边栏,或直接用detail <id>。
limit的校验是硬性的:非正整数或超过上限会直接抛出ArgumentError(must be a positive integer/must be <= 100),不会静默截断。
阅读对话:read 与 detail
read:读取当前对话
# 以 markdown 输出助手回复 opencli grok read --markdown true输出列:Role, Text。read读取当前标签页正在显示的对话,即使不传 ID 也能工作;配合status先拿到当前 SessionId 是推荐用法。若页面没有可见消息,返回一行Role: system, Text: No visible messages in the current conversation.(read.js)。
detail:按 ID 打开历史对话
opencli grok detail 7c4197f2-10a1-4ebb-a84a-fea89f4f1d06 opencli grok detail https://grok.com/c/7c4197f2-10a1-4ebb-a84a-fea89f4f1d06 --markdown true输出列:Role, Text。detail会导航到https://grok.com/c/<id>并轮询等待对话记录加载(最长约 20 秒,见 detail.js),避免页面壳先渲染而消息后加载导致的竞态;如果轮询结束仍无可见消息,抛出EmptyResultError,提示核对 ID 是否属于当前登录账户。
消息读取的底层实现
read/detail都基于getMessageBubbles(utils.js):
- 通过
[data-testid="user-message"]/[data-testid="assistant-message"]区分角色; - 向上查找最近的
id="response-<uuid>"祖先作为每条消息的稳定 ID(用于轮询去重); - 保留"仅含图片/非文本组件"的助手回合(text 为空但 html 非空也会保留),避免图回合被静默丢弃;
--markdown true时,助手回复的 HTML 通过bubbleHtmlToMarkdown(utils.js)转为 Markdown。
对应的单元测试见 clis/grok/utils.test.js,覆盖了会话 ID 解析、域名判断、布尔参数归一化、置顶状态识别等核心函数。
会话 ID 与 URL 解析规则
detail、pin、unpin、delete都接受会话 ID 参数,统一由parseGrokSessionId(utils.js)解析,规则如下:
- 裸 UUID(
7c4197f2-10a1-4ebb-a84a-fea89f4f1d06)直接接受; - 完整 URL(
https://grok.com/c/<id>或子域https://x.grok.com/c/<id>)提取其中的 UUID; - 只接受
https:协议与grok.com/*.grok.com主机名,路径必须精确匹配/c/<uuid>; - 校验失败抛出
ArgumentError,并附期望格式提示;URL 尾部拼接额外十六进制字符(如/c/<id>0)会被拒绝,防止"静默截断打开错误会话"。
utils.test.js 对此有非常完整的测试矩阵,包括大小写归一化、evil.com伪装域名、http:明文协议、路径多余片段等边界情形。
发送消息:ask 与 send
ask:发送并等待回复
opencli grok ask "Explain quantum computing in simple terms" opencli grok ask "Hello" --new true输出列:response(仅返回助手最新回复文本)。--timeout控制最长等待秒数(默认 120),--new指定是否先开启新会话(默认false)。
ask的完整调用链(ask.js):
- 若
--new,startNewChat导航回首页;否则ensureOnGrok确保停留在 grok.com; isLoggedIn校验登录,未登录抛AuthRequiredError;getBaselineLastAssistantId记录发送前最后一条助手消息 ID(防止把旧回复当新回复);sendMessage注入提示词并触发提交;waitForAnswer轮询等待流式回复稳定。
send:fire-and-forget
opencli grok send "continue the previous answer"输出列:Status, Prompt。send只负责把提示词发出去、确认已提交(返回Status: sent),不等待回复(send.js);若发送时发现 composer 缺失且登录态已失效,会抛AuthRequiredError而非笼统的执行失败,方便 Agent 触发重新认证。
发送与等待的底层原理
sendMessage(utils.js)优先使用 ProseMirror 编辑器的editor.commands.insertContent注入文本,提交按钮优先使用语言无关的button[data-testid="chat-submit"],回退到aria-label("Submit" / "提交" / "送信");若提交按钮始终未出现(某些地区部署无提交按钮,源码注释引用 issue #1782),则退化为对聚焦的编辑器派发 Enter 键(keydown/keypress/keyup 完整链)。提交后通过"出现新的匹配用户回合"来确认发送成功,而不是盲目信任点击。
waitForAnswer(utils.js)采用"稳定性判定"策略:每 2 秒轮询一次,跳过与发送前基线相同的旧助手回合,只有当回复文本连续两次轮询保持一致(且已等待至少 6 秒)才判定为稳定并返回ok;超时但已有部分内容时返回partial(保留最佳努力文本,不丢弃);完全无内容才抛TimeoutError。
导出对话:export 与 export-all
export:导出历史元数据
opencli grok export --limit 25 -f json > grok-history.json输出列:index, id, title, date, url。--limit默认 0(0 表示全部已加载的历史),--maxScrolls默认 80、最大 500。
底层实现(export.js)会打开完整的"全部历史"对话框(自动点击"查看全部 / Show all / View all"按钮),然后在对话框滚动容器内反复滚动到底并采集a[href^="/c/"]链接,直到连续多轮行数与滚动高度不再变化(判定稳定)或达到maxScrolls上限;每条记录提取title、date与真实的https://grok.com/c/<id>URL。若出现登录失效(检测到 Sign in CTA 且无历史入口)返回AUTH错误码,对话框未打开返回NO_DIALOG错误码;畸形行会被视为选择器漂移而报错,绝不静默丢弃(由normalizeConversationRows严格校验,见 export-utils.js)。
export-all:导出元数据 + 逐会话完整转录
opencli grok export-all --manifestPath grok-history.json --limit 25 -f json > grok-transcripts.json输出列:index, id, title, date, url, status, messageCount, error, messagesJson。
export-all在export的基础上,逐个访问每个会话的/c/<id>页面并导出完整消息转录,每行额外包含:
status:ok/empty/failed;messageCount:该会话可见消息数;error:失败原因(如"页面加载后无可见消息"、"读取器返回畸形行");messagesJson:转录的 JSON 字符串(每条含messageIndex、messageId、messageRole、messageText)。
关键参数(export-all.js):
| Option | Description |
|---|---|
--limit | Max conversations to export;0means all loaded history or all manifest rows after offset |
--offset | Skip this many conversations before exporting (default:0) |
--manifestPath | Optional JSON output fromgrok export; when present, skips history discovery and visits listed/c/<id>pages |
--maxScrolls | Max history-dialog scroll rounds when no manifest is provided (default:80, max500) |
--pageScrolls | Max per-conversation scroll-to-bottom rounds (default:30, max200) |
--pageTimeoutMs | Max wait for each conversation page to show messages (default:30000) |
--delayMinMs | Minimum polite delay after a conversation page loads (default:0) |
--delayMaxMs | Maximum polite delay after a conversation page loads (default:5000) |
工作方式:若提供--manifestPath,直接从grok export生成的 JSON 清单读取会话列表(跳过历史对话框发现过程),按offset/limit切片后逐个访问/c/<id>页面;否则先执行与export相同的历史对话框滚动采集。每个会话页面:等待消息气泡出现(pageTimeoutMs)、加载后随机礼貌延迟(delayMinMs~delayMaxMs)、滚动到底部采集全部消息(pageScrolls轮,连续 5 轮稳定即止),最后严格校验每行转录结构。页面无可见转录产出empty行,转录行畸形产出failed行,而畸形历史行或清单会在导出前直接失败(不会产生残缺导出)。参数校验同样硬性:delayMaxMs必须>= delayMinMs,各整数参数越界抛ArgumentError。
图像生成:image
opencli grok image "a cyberpunk mechanical owl, neon purple and blue" --new true输出列:url, width, height, path。额外参数:--timeout(默认 240 秒)、--new(默认false)、--count(返回前至少等待的图片数,默认 1)、--out(下载保存目录)。
完整流程(image.js):
- 记录发送前的图片气泡基线数,避免把旧图当新图;
- 通过 composer 发送提示词(优先 ProseMirror,回退 textarea);
- 每 3 秒轮询消息气泡中的
<img>元素(过滤小于 128px 的头像/UI 小图、按 src 去重),用"连续两次稳定签名(约 6 秒)"判定生成完成; - 超时但有部分图片时保留最佳努力结果;完全无图抛
TimeoutError。
下载机制值得注意:当传入--out时,图片是通过浏览器页面的fetch(携带credentials: 'include'与 grok.com referer)以 base64 拉取再写入本地文件的(image.js)。这是因为 assets.grok.com 由 Cloudflare 防护,直接 curl/node 下载会被拒绝;利用浏览器会话即可绕过。下载失败会响亮地抛出CommandExecutionError(而非输出[DOWNLOAD FAILED]哨兵路径),文件名形如grok-<timestamp>-<sha1前12位>.<ext>。
会话管理:pin / unpin / delete
pin / unpin
opencli grok pin 7c4197f2-10a1-4ebb-a84a-fea89f4f1d06 opencli grok unpin https://grok.com/c/7c4197f2-10a1-4ebb-a84a-fea89f4f1d06输出列:status, id。这两个命令(pin.js)通过共享的defineToggle工厂注册:Grok 上下文菜单根据当前置顶状态只显示"置顶"或"取消置顶"之一(不会同时出现),因此两个命令各自绑定匹配的本地化菜单项。执行逻辑为:先读取右键菜单标签判断当前状态,若已处于目标状态则幂等返回already-pinned/already-unpinned;否则右键打开上下文菜单点击目标项,再轮询验证菜单标签确实翻转(waitForConversationPinState),验证失败会抛出包含可用菜单标签的执行错误。
delete(带 dry-run 保护)
# 预览(dry-run) opencli grok delete 7c4197f2-10a1-4ebb-a84a-fea89f4f1d06 # 真正删除 opencli grok delete 7c4197f2-10a1-4ebb-a84a-fea89f4f1d06 --yes true输出列:status, id。删除操作是危险操作:Grok 的删除是立即生效、无确认弹窗的(源码注释明确记录这一点),因此delete默认只是 dry-run 预览(返回status: 'dry-run (pass --yes to actually delete)'),必须显式传入--yes才真正删除。真正删除后还会轮询确认侧边栏条目确实消失才返回deleted(waitForConversationToDisappear),否则抛执行错误。
右键菜单模拟的底层细节
pin/unpin/delete依赖clickConversationMenuItem(utils.js),其中有一个值得注意的实现细节:Grok 的上下文菜单由 radix 实现,只派发contextmenu事件会被忽略,因此代码完整派发了pointerdown → mousedown → contextmenu → pointerup → mouseup事件链(带button: 2右键状态),并在目标链接的边界矩形内选择命中坐标。菜单项匹配支持中英文等多语言标签("置顶/Pin"、"取消置顶/Unpin"、"删除/Delete"、"打开新标签页/Open in new tab"、"重命名/Rename")。
输出列速查表
| Command | Columns |
|---|---|
status | Status, Login, Model, SessionId, Url |
history | Index, Title, Url |
read | Role, Text |
detail | Role, Text |
export | index, id, title, date, url |
export-all | index, id, title, date, url, status, messageCount, error, messagesJson |
ask | response |
send | Status, Prompt |
new | Status |
pin | status, id |
unpin | status, id |
delete | status, id |
完整选项参考
ask/send
| Option | Description |
|---|---|
prompt | Prompt to send (required positional) |
--new | Start a new chat before sending (default:false) |
--timeout | (askonly) Max seconds to wait for the reply (default:120) |
read
| Option | Description |
|---|---|
--markdown | Emit assistant replies as markdown (default:false) |
detail
| Option | Description |
|---|---|
id | Session ID (UUID) or fullhttps://grok.com/c/<id>URL (required positional) |
--markdown | Emit assistant replies as markdown (default:false) |
history
| Option | Description |
|---|---|
--limit | Max conversations to list (default:20, max100) |
export
| Option | Description |
|---|---|
--limit | Max conversations to export;0means all loaded history (default:0) |
--maxScrolls | Max history-dialog scroll rounds (default:80, max500) |
export-all
| Option | Description |
|---|---|
--limit | Max conversations to export;0means all loaded history or all manifest rows after offset |
--offset | Skip this many conversations before exporting (default:0) |
--manifestPath | Optional JSON output fromgrok export; when present, skips history discovery and visits listed/c/<id>pages |
--maxScrolls | Max history-dialog scroll rounds when no manifest is provided (default:80, max500) |
--pageScrolls | Max per-conversation scroll-to-bottom rounds (default:30, max200) |
--pageTimeoutMs | Max wait for each conversation page to show messages (default:30000) |
--delayMinMs | Minimum polite delay after a conversation page loads (default:0) |
--delayMaxMs | Maximum polite delay after a conversation page loads (default:5000) |
pin/unpin/delete
| Option | Description |
|---|---|
id | Session ID (UUID) or fullhttps://grok.com/c/<id>URL (required positional) |
--yes | (deleteonly) Actually delete the conversation; without it the command returns a dry-run row |
实战注意事项与陷阱
read无 ID 也能用:它读取当前标签页的对话;组合用法是先用status获取 SessionId,再决定read还是detail。- 会话持续性:Grok 命令默认使用持久站点会话,连续
ask/read/detail会在同一个 Grok 页面内继续;需要一次性标签页时传--site-session ephemeral。 askvssend:ask等流式回复稳定后返回;send提交即返回。history受侧边栏可见性限制:Grok 懒加载旧对话时,先在浏览器里滚动侧边栏,或直接用detail <id>。export的严格性:畸形历史行按选择器漂移处理并报错,不静默丢弃,保证导出的 URL 全部真实可访问。export-all的行状态:无可见转录的会话产生empty行(带error),畸形转录行产生failed行,Agent 应消费status列做重试或跳过。pin/unpin/delete的验证闭环:三个命令都要求操作"可见于侧边栏"的会话,并在操作后验证上下文菜单状态(pin/unpin)或侧边栏条目消失(delete)才返回成功。null而非哨兵字符串:status的Model/SessionId检测不到时是null,Agent 代码必须对null分支。- DOM 漂移的应对:Grok 产品改版可能导致 composer 检测失败,
opencli grok status是最快的体检手段。 - 参数校验不静默:
limit等参数非法(非正数或超过文档上限,如history最大 100)会直接抛ArgumentError,不会悄悄截断。
延伸阅读
- 适配器权威文档:docs/adapters/browser/grok.md
- 核心共享实现:clis/grok/utils.js(会话 ID 解析、登录检测、消息气泡读取、发送与等待、右键菜单模拟)
- 各命令实现:ask.js、read.js、detail.js、history.js、export.js、export-all.js、image.js、pin.js、delete.js、send.js、new.js、status.js
- 导出行校验工具:clis/grok/export-utils.js
- 单元测试:clis/grok/utils.test.js、clis/grok/ask.test.js、clis/grok/export.test.js、clis/grok/image.test.ts
- 浏览器桥接与会话管理:docs/guide/browser-bridge.md
【免费下载链接】OpenCLIMake Any Website into CLI & Use your logged-in browser by AI agent.项目地址: https://gitcode.com/gh_mirrors/ope/OpenCLI
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考