摘要:本机已安装 Cursor,并且 Cursor CLI 可用 若还没安装 Cursor CLI,按下面步骤操作: Windows(PowerShell):
irm 'https://cursor.com/install?win32=true' | iex
📌 提示:如果遇到执行策略报错,先运行: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
然后输入 Y 确认,再重新执行安装命令。macOS / Linux:
curl -fsSL https://cursor.com/install | sh
⚠️ 实际踩坑:Cursor CLI 依赖 ripgrep 执行 agent 命令时如果报错:Could not find ripgrep (rg) binary.on("close", (code) => resolve({ success: code === 0, exitCode: code, output }) ); }); }
写一个简单的「调度函数」来更新任务状态 对应第八节错误处理和第七节结果收集
// 调度封装:围绕 runCursorCliTask 更新任务状态(极简版) async function dispatchToCursorCli(task, updateStatus) { await updateStatus({ status: "running" }); try { const result = await runCursorCliTask(task); await updateStatus({ status: result.
🚀 在飞书里 @ 机器人发一句话,让 Cursor 在你指定的项目目录里写代码、改代码、跑测试,执行结果自动回推到飞书。 本文按「手把手」顺序,带你打通这条链路。
在之前的文章中,我已经写过:
前几篇分别解决了:本地部署 OpenClaw、自建 APP 控制 OpenClaw、以及把飞书接到 OpenClaw、在飞书里对话本地 AI。
那在飞书里和 OpenClaw 聊上天之后,很多同学会想:能不能直接让 Cursor 在某个项目里干活,比如改代码、加函数、跑测试,然后把执行结果自动推回飞书?这样不用切到 Cursor 界面,在飞书里就能「下任务 → 等结果」。
这篇就带你实现这条链路:飞书机器人下发任务 → OpenClaw 编排 → 调用 Cursor CLI Agent → OpenClaw 收集执行结果 → 通过飞书回推结果。
整篇文章的目标只有一个:
让你看完后,能在飞书里发一条消息,让 Cursor 在你指定的项目目录里执行开发任务,并在同一会话里收到执行结果。
先把整条链路看清楚,后面按这个顺序一步步做。
关键点:
agent
动手之前,先确认这几样已经就绪。
OpenClaw 已安装并初始化,且飞书已经接进去、能收发消息。 若还没做,请先按《手把手教你安装 OpenClaw 并接入飞书》完成。
飞书侧:已在开放平台创建企业自建应用 + 机器人,并配置好事件订阅 / 消息回调,消息能推到 OpenClaw。
本机已安装 Cursor,并且 Cursor CLI 可用 若还没安装 Cursor CLI,按下面步骤操作:
Windows(PowerShell):
# 安装 Cursor CLI irm 'https://cursor.com/install?win32=true' | iex
📌 提示:如果遇到执行策略报错,先运行: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser 然后输入 Y 确认,再重新执行安装命令。
📌 提示:如果遇到执行策略报错,先运行:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
然后输入 Y 确认,再重新执行安装命令。
macOS / Linux:
# 安装 Cursor CLI curl -fsSL https://cursor.com/install | sh
⚠️ 实际踩坑:Cursor CLI 依赖 ripgrep 执行 agent 命令时如果报错:Could not find ripgrep (rg) binary. Please install ripgrep. Error: rg is not installed 这是因为 Cursor Agent CLI 依赖 ripgrep(简称 rg) 来进行高效的代码搜索和上下文理解,但你的系统中尚未安装它。
Could not find ripgrep (rg) binary. Please install ripgrep. Error: rg is not installed
解决方案:安装 ripgrep
Windows(手动下载):
.zip
ripgrep-14.1.1-x86_64-pc-windows-msvc.zip
rg.exe
rg --version
如果显示版本号,说明安装成功。
在终端里运行:
# 启动交互式会话(首次运行会提示登录) agent
如果看到 Cursor 的交互界面或提示登录,说明 CLI 已安装成功。完成登录后,后续 OpenClaw 调用时就能复用认证信息。
用户在飞书里向「机器人」发一条消息,就是一次「任务描述」。飞书会把这条消息通过事件订阅 / 消息回调 POST 到你配置的 OpenClaw HTTP 接口。
OpenClaw 这边要做的:
任务描述怎么写: 建议包含「项目路径 + 你想让 Cursor 做的事」,例如:「在 F:\cursor_work\ 里,写一个冒泡排序算法,写入py文件中。」这样方便你在 OpenClaw 里拼成给 Cursor 的 prompt。
飞书的消息回调是 JSON,但 content 可能是「JSON 字符串」而不是直接的对象;另外富文本(卡片)和普通文本的格式不一样,解析时要区分。 建议:优先支持纯文本消息(msg_type: text),先跑通链路,再考虑卡片等复杂格式。
msg_type: text
不管用户从飞书发来的是哪一句话,在 OpenClaw 里都先转成同一套任务对象。这样后面无论是调 Cursor CLI 还是以后扩展别的执行端,逻辑都一致。
任务对象里至少要有:
D:\projects\my-app
一个最小可用的任务对象大概长这样:
{ "task_id": "task_1739356123456", "from_chat": { "chat_id": "oc_1234567890", "message_id": "om_abcdefg123456", "user_id": "ou_999999999" }, "project": { "root": "D:\\\\projects\\\\my-app" }, "payload": { "natural_language": "在 README 里新增一段 100 字以内的项目简介。", "files": ["README.md"], "constraints": { "language": "markdown" } } }
飞书只是入口,真正驱动 Cursor 的是这份统一模型。
核心思路:不依赖 Cursor 桌面端界面,只通过 Cursor CLI 的 agent 命令(非交互、带 -p 的 prompt)在指定目录执行。
-p
流程:
agent -p "<由任务生成的 Prompt>" --mode agent
Prompt 从哪来: 用任务里的 payload(自然语言、文件、约束)拼成一段清晰指令;如果需要,可以把任务 JSON 的关键字段也塞进 prompt,方便 Agent 理解上下文。
代码层面: 在 OpenClaw 里用类似 spawn('agent', args, { cwd: projectRoot }) 的方式调用,并收集 stdout / stderr;用 exit code === 0 判断成功或失败。后文会给一个 Node.js 伪代码示例,演示如何在项目目录中拉起 Cursor CLI 并收集结果。
spawn('agent', args, { cwd: projectRoot })
agent 可能不在系统 PATH 里,OpenClaw 子进程找不到。 解决:在配置里写 完整路径,例如 Windows 下 C:\Users\你的用户名\.cursor\agent.exe,Linux/Mac 下 ~/.cursor/agent;或把 Cursor CLI 所在目录加到 OpenClaw 进程的 PATH 环境变量里。
C:\Users\你的用户名\.cursor\agent.exe
~/.cursor/agent
Cursor CLI 执行前会做登录校验,没登录会报错。 解决:在本机至少用终端跑过一次 agent 并完成登录,认证信息会持久化,之后 OpenClaw 调用的子进程就能复用。
简单任务几秒,复杂任务可能几分钟,超时设太短会把任务误杀。 解决:建议超时设得偏长(例如 30~60 分钟),或做成可配置项,方便按场景调整。
什么时候算「完成」: 不需要轮询 Cursor 服务,Cursor CLI 子进程退出就表示这次执行结束。
结果内容: 把 stdout + stderr 当作「执行日志 / 结果摘要」;退出码 0 视为成功,非 0 视为失败。OpenClaw 把结果和状态(done / failed)写入任务记录,供下一步回推飞书。
存储时注意: 输出可能很长,建议做长度截断(例如最多保留 2 万字符),避免占满存储;回推飞书时也要考虑单条消息长度限制(见下文踩坑 7)。
若将来需要更结构化的结果,可以约定:让 Cursor 在输出里带一段特定标记(如 RESULT_JSON: {...}),OpenClaw 再解析。
RESULT_JSON: {...}
Agent 的输出可能非常长(尤其是 diff),全存会占空间,全推飞书也会超长。 解决:做智能截断(例如保留前 1000 字 + 后 500 字),或把完整日志存成文件,在飞书里只推摘要 + 「完整日志见 xxx 路径」。
Windows 下子进程的 stdout/stderr 可能是 GBK,直接读会中文乱码。 解决:子进程里指定 encoding: 'utf-8',或在调用前用 chcp 65001 把控制台切到 UTF-8。
encoding: 'utf-8'
chcp 65001
任务状态更新后,根据 task_id 找到对应的飞书上下文(群 ID / 私聊 ID、原消息 ID、发送人),调用飞书开放平台的「发消息」接口,把执行结果推回去。
推什么: 简明摘要(成功 / 失败)、关键日志或「详细日志见附件/文件路径」。这样用户在同一会话里就能看到「任务已完成 + 结果」。
体验闭环: 用户在飞书发一条「让 Cursor 干活」的消息 → 过一段时间在同一会话收到机器人回复「任务已完成 + 结果」。
建议在任务一开始就先发一条「已接收任务,正在执行中」的快速回复(见踩坑 9),避免用户以为没收到。
单条消息有长度上限(通常约 2 万字符),过长会被拒绝。 解决:分多条发送(先发摘要,再分段发日志),或用飞书富文本卡片做折叠。
飞书支持的 Markdown 有限,部分语法或特殊字符会显示异常。 解决:用简化的 Markdown,代码块用标准 ``` 语法,避免飞书不支持的特性。
timeout
可选:对网络抖动等临时异常做有限次数重试(如 1~2 次)。
Cursor Agent 会访问远程 API,网络不稳定可能导致任务失败。 解决:识别常见错误(如 ECONNRESET、ETIMEDOUT),在飞书里给友好提示,并可选做重试。
用户填的项目路径可能不存在,或 OpenClaw 运行用户没有读写权限。 解决:在执行前检查路径存在性 + 权限,不通过则直接返回明确错误,不发起 Cursor 子进程。
feishu-cursor-cli
我在本地写了一个简单的 OpenClaw 插件 feishu-cursor-cli,基本就是把上面这几节的思路串成了一段代码,方便你参考(这里只保留关键片段,完整实现可以联系作者获取):
// cursor_cli_agent.js 中的核心结构(只示意关键字段) const task = { id: taskId, task_type: taskType, project: { root: projectRoot }, payload: { natural_language: text.trim(), // files / constraints 等可按需补充 }, };
// 根据任务模型构造给 Cursor CLI 的 Prompt(核心思路) function buildPromptFromTask(task) { return [ "你是一个代码开发 Agent,请在当前仓库完成下面任务。", "", `任务 ID: ${task.id}`, `任务类型: ${task.task_type}`, "", "自然语言需求:", task.payload.natural_language, // 如需,可在此追加 files / constraints 等信息 ].join("\n"); }
cmd.exe
shell
/bin/bash
// 在指定项目目录下调用 Cursor CLI(只保留核心结构) async function runCursorCliTask(task, options = {}) { const cwd = path.resolve(task.project.root); const prompt = buildPromptFromTask(task); return new Promise((resolve, reject) => { const child = spawn("agent", [], { cwd, shell: "cmd.exe", // Windows 示例;在 macOS / Linux 上可改为 "/bin/bash" 或直接省略 env: { ...process.env, ...(options.env || {}) }, }); let output = ""; child.stdin.write(prompt); child.stdin.end(); child.stdout.on("data", (data) => (output += data.toString())); child.stderr.on("data", (data) => (output += data.toString())); child.on("error", (err) => reject(err)); child.on("close", (code) => resolve({ success: code === 0, exitCode: code, output }) ); }); }
// 调度封装:围绕 runCursorCliTask 更新任务状态(极简版) async function dispatchToCursorCli(task, updateStatus) { await updateStatus({ status: "running" }); try { const result = await runCursorCliTask(task); await updateStatus({ status: result.success ? "done" : "failed", result_summary: result.success ? "执行成功" : "执行失败", result_output: (result.output || "").slice(0, 20000), }); } catch (err) { await updateStatus({ status: "failed", result_summary: "调用 Cursor CLI 出错", result_output: String(err), }); } }
// 简单 Feishu 文本解析器(只保留思路示意) function buildTaskFromFeishuText(text, { taskId, defaultProjectRoot }) { const projectRoot = text.match(/项目[::]\s*([A-Za-z]:[^\s,。;;]*)/)?.[1]?.trim() || defaultProjectRoot; const payload = { natural_language: text.trim() }; // 这里可按需解析文件名 / 语言约束 / 任务类型等 return { id: taskId, task_type: "code_edit", project: { root: projectRoot }, payload, }; }
// index.js(入口极简示意) const { buildTaskFromFeishuText, dispatchToCursorCli } = require("./cursor_cli_agent.js"); module.exports = { id: "feishu-cursor-cli", async handleFeishuMessage({ text, chatId, messageId, userId, sendReply, saveTask, updateTask }) { const taskId = `task_${Date.now()}`; const task = buildTaskFromFeishuText(text, { taskId, defaultProjectRoot: "F:\\cursor_work", }); await saveTask(taskId, { from_chat: { chatId, messageId, userId }, task }); await dispatchToCursorCli(task, async (partial) => { await updateTask(taskId, partial); if (partial.status === "done" || partial.status === "failed") { await sendReply(chatId, messageId, partial.result_summary); } }); }, };
可以看到,这个插件本质上就是把本文讲的几步——「解析 Feishu 文本 → 构造任务对象 → 在项目目录中调用 Cursor CLI → 更新任务状态并截断输出 → 通过 Feishu 回推结果」用代码完整串起来,方便你参考或直接改成自己的版本。
下面用一个完整例子,把整个流程串起来。
示例需求:请使用 Cursor CLI,在项目 F:\cursor_work\ 里,帮我写一个冒泡排序算法,使用 Python 编写,写入到 .py 文件中。
F:\cursor_work\
.py
步骤 1:在飞书里发出任务指令
此时飞书会把这条消息通过事件订阅 / 消息回调推送到你的 OpenClaw HTTP 接口。
步骤 2:OpenClaw 收到消息并生成内部任务
OpenClaw 的 Feishu 插件会解析这条文本,提取出:
bubble_sort.py
然后包装成统一的任务对象,例如(简化示意):
{ "id": "task_xxx", "task_type": "code_edit", "project": { "root": "F:\\\\cursor_work" }, "payload": { "natural_language": "在项目 F:\\cursor_work 里,用 Python 写一个冒泡排序算法,写入到 bubble_sort.py 中。" } }
步骤 3:OpenClaw 在 F:\cursor_work 目录里调用 Cursor CLI
F:\cursor_work
OpenClaw 根据任务里的 project.root,在该目录下通过子进程执行 agent 命令,并把上一步构造好的 Prompt 写入到标准输入中,大致等价于:
project.root
cd F:\cursor_work agent
然后由插件代码把「冒泡排序需求」作为一整段 Prompt 喂给 Cursor CLI。
步骤 4:Cursor CLI 在仓库中创建 / 更新 bubble_sort.py
Cursor CLI 接收到 Prompt 后,会自动在当前仓库中:
步骤 5:OpenClaw 收集 Cursor CLI 的执行结果
当这次 agent 调用结束、子进程退出时,OpenClaw 会拿到:
插件会把这些信息写回任务记录中,例如:
status: "done"
"failed"
result_summary: "执行成功,已在 bubble_sort.py 中写入冒泡排序示例"
result_output
步骤 6:通过飞书把结果回推给用户
最后,OpenClaw 根据任务里保存的 chat_id / message_id,调用飞书开放平台的发送消息接口,在同一会话里给用户回一条结果消息,例如:
chat_id
message_id
到这里,一个完整闭环就跑通了:飞书发指令 → OpenClaw 编排 → Cursor CLI 落地写码 → OpenClaw 收集结果 → 飞书回推摘要。
在前面的冒泡排序例子基础上,你还可以:
task_type: "run_tests"
这样,一个非常贴近真实工作的「写功能 → 补测试 → 看结果」闭环,就完全建立在飞书 + OpenClaw + Cursor CLI 之上了。
通过以上步骤,你已经完成了:
接下来可以在此基础上继续扩展,例如:多项目配置、权限控制、结果结构化(RESULT_JSON)、任务取消等。若以后 Cursor 提供更强的集成方式,当前以 CLI 为核心的设计也便于平滑升级。
🌟 飞书只是你发任务、收结果的入口; 真正的编排中枢是你本地的 OpenClaw,执行体是 Cursor CLI。 现在,就在飞书里发一条消息,让 Cursor 在你项目里干一票吧。🦞💬
安装 Cursor CLI
irm 'https://cursor.com/install?win32=true' | iex
📌 提示:如果遇到执行策略报错,先运行: Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
然后输入 Y 确认,再重新执行安装命令。macOS / Linux:
安装 Cursor CLI
curl -fsSL https://cursor.com/install | sh
⚠️ 实际踩坑:Cursor CLI 依赖 ripgrep 执行 agent 命令时如果报错:Could not find ripgrep (rg) binary.on("close", (code) => resolve({ success: code === 0, exitCode: code, output }) ); }); }
写一个简单的「调度函数」来更新任务状态 对应第八节错误处理和第七节结果收集
// 调度封装:围绕 runCursorCliTask 更新任务状态(极简版) async function dispatchToCursorCli(task, updateStatus) { await updateStatus({ status: "running" }); try { const result = await runCursorCliTask(task); await updateStatus({ status: result.
在之前的文章中,我已经写过:
前几篇分别解决了:本地部署 OpenClaw、自建 APP 控制 OpenClaw、以及把飞书接到 OpenClaw、在飞书里对话本地 AI。
那在飞书里和 OpenClaw 聊上天之后,很多同学会想:能不能直接让 Cursor 在某个项目里干活,比如改代码、加函数、跑测试,然后把执行结果自动推回飞书?这样不用切到 Cursor 界面,在飞书里就能「下任务 → 等结果」。
这篇就带你实现这条链路:飞书机器人下发任务 → OpenClaw 编排 → 调用 Cursor CLI Agent → OpenClaw 收集执行结果 → 通过飞书回推结果。
整篇文章的目标只有一个:
一、整体架构:一张图看懂整条链路
先把整条链路看清楚,后面按这个顺序一步步做。
关键点:
agent命令),不需要额外再搭 MCP/HTTP 服务。二、开始前你需要准备好的东西
动手之前,先确认这几样已经就绪。
OpenClaw 已安装并初始化,且飞书已经接进去、能收发消息。
若还没做,请先按《手把手教你安装 OpenClaw 并接入飞书》完成。
飞书侧:已在开放平台创建企业自建应用 + 机器人,并配置好事件订阅 / 消息回调,消息能推到 OpenClaw。
本机已安装 Cursor,并且 Cursor CLI 可用 若还没安装 Cursor CLI,按下面步骤操作:
Windows(PowerShell):
macOS / Linux:
# 安装 Cursor CLI curl -fsSL https://cursor.com/install | sh解决方案:安装 ripgrep
Windows(手动下载):
.zip文件,例如:ripgrep-14.1.1-x86_64-pc-windows-msvc.ziprg.exe所在目录添加到系统 PATH 环境变量如果显示版本号,说明安装成功。
在终端里运行:
# 启动交互式会话(首次运行会提示登录) agent如果看到 Cursor 的交互界面或提示登录,说明 CLI 已安装成功。完成登录后,后续 OpenClaw 调用时就能复用认证信息。
三、飞书 → OpenClaw:任务怎么进来
用户在飞书里向「机器人」发一条消息,就是一次「任务描述」。飞书会把这条消息通过事件订阅 / 消息回调 POST 到你配置的 OpenClaw HTTP 接口。
OpenClaw 这边要做的:
任务描述怎么写:
建议包含「项目路径 + 你想让 Cursor 做的事」,例如:「在 F:\cursor_work\ 里,写一个冒泡排序算法,写入py文件中。」这样方便你在 OpenClaw 里拼成给 Cursor 的 prompt。
⚠️ 实际踩坑 1:消息格式问题
飞书的消息回调是 JSON,但 content 可能是「JSON 字符串」而不是直接的对象;另外富文本(卡片)和普通文本的格式不一样,解析时要区分。
建议:优先支持纯文本消息(
msg_type: text),先跑通链路,再考虑卡片等复杂格式。四、OpenClaw 内部:统一任务模型
不管用户从飞书发来的是哪一句话,在 OpenClaw 里都先转成同一套任务对象。这样后面无论是调 Cursor CLI 还是以后扩展别的执行端,逻辑都一致。
任务对象里至少要有:
D:\projects\my-app一个最小可用的任务对象大概长这样:
{ "task_id": "task_1739356123456", "from_chat": { "chat_id": "oc_1234567890", "message_id": "om_abcdefg123456", "user_id": "ou_999999999" }, "project": { "root": "D:\\\\projects\\\\my-app" }, "payload": { "natural_language": "在 README 里新增一段 100 字以内的项目简介。", "files": ["README.md"], "constraints": { "language": "markdown" } } }飞书只是入口,真正驱动 Cursor 的是这份统一模型。
五、OpenClaw → Cursor Agent:在项目目录里调用 CLI
核心思路:不依赖 Cursor 桌面端界面,只通过 Cursor CLI 的
agent命令(非交互、带-p的 prompt)在指定目录执行。流程:
agent -p "<由任务生成的 Prompt>" --mode agentPrompt 从哪来:
用任务里的 payload(自然语言、文件、约束)拼成一段清晰指令;如果需要,可以把任务 JSON 的关键字段也塞进 prompt,方便 Agent 理解上下文。
代码层面:
在 OpenClaw 里用类似
spawn('agent', args, { cwd: projectRoot })的方式调用,并收集 stdout / stderr;用 exit code === 0 判断成功或失败。后文会给一个 Node.js 伪代码示例,演示如何在项目目录中拉起 Cursor CLI 并收集结果。⚠️ 实际踩坑 2:Cursor CLI 路径问题
agent可能不在系统 PATH 里,OpenClaw 子进程找不到。解决:在配置里写 完整路径,例如 Windows 下
C:\Users\你的用户名\.cursor\agent.exe,Linux/Mac 下~/.cursor/agent;或把 Cursor CLI 所在目录加到 OpenClaw 进程的 PATH 环境变量里。⚠️ 实际踩坑 3:Cursor 需要登录
Cursor CLI 执行前会做登录校验,没登录会报错。
解决:在本机至少用终端跑过一次
agent并完成登录,认证信息会持久化,之后 OpenClaw 调用的子进程就能复用。⚠️ 实际踩坑 4:子进程超时
简单任务几秒,复杂任务可能几分钟,超时设太短会把任务误杀。
解决:建议超时设得偏长(例如 30~60 分钟),或做成可配置项,方便按场景调整。
六、任务完成信号与结果收集
什么时候算「完成」:
不需要轮询 Cursor 服务,Cursor CLI 子进程退出就表示这次执行结束。
结果内容:
把 stdout + stderr 当作「执行日志 / 结果摘要」;退出码 0 视为成功,非 0 视为失败。OpenClaw 把结果和状态(done / failed)写入任务记录,供下一步回推飞书。
存储时注意:
输出可能很长,建议做长度截断(例如最多保留 2 万字符),避免占满存储;回推飞书时也要考虑单条消息长度限制(见下文踩坑 7)。
若将来需要更结构化的结果,可以约定:让 Cursor 在输出里带一段特定标记(如
RESULT_JSON: {...}),OpenClaw 再解析。⚠️ 实际踩坑 5:输出截断
Agent 的输出可能非常长(尤其是 diff),全存会占空间,全推飞书也会超长。
解决:做智能截断(例如保留前 1000 字 + 后 500 字),或把完整日志存成文件,在飞书里只推摘要 + 「完整日志见 xxx 路径」。
⚠️ 实际踩坑 6:编码问题
Windows 下子进程的 stdout/stderr 可能是 GBK,直接读会中文乱码。
解决:子进程里指定
encoding: 'utf-8',或在调用前用chcp 65001把控制台切到 UTF-8。七、OpenClaw → 飞书:把结果推回去
任务状态更新后,根据 task_id 找到对应的飞书上下文(群 ID / 私聊 ID、原消息 ID、发送人),调用飞书开放平台的「发消息」接口,把执行结果推回去。
推什么:
简明摘要(成功 / 失败)、关键日志或「详细日志见附件/文件路径」。这样用户在同一会话里就能看到「任务已完成 + 结果」。
体验闭环:
用户在飞书发一条「让 Cursor 干活」的消息 → 过一段时间在同一会话收到机器人回复「任务已完成 + 结果」。
建议在任务一开始就先发一条「已接收任务,正在执行中」的快速回复(见踩坑 9),避免用户以为没收到。
⚠️ 实际踩坑 7:飞书消息长度限制
单条消息有长度上限(通常约 2 万字符),过长会被拒绝。
解决:分多条发送(先发摘要,再分段发日志),或用飞书富文本卡片做折叠。
⚠️ 实际踩坑 8:Markdown 格式
飞书支持的 Markdown 有限,部分语法或特殊字符会显示异常。
解决:用简化的 Markdown,代码块用标准 ``` 语法,避免飞书不支持的特性。
八、错误处理与可落地的细节
timeout。可选:对网络抖动等临时异常做有限次数重试(如 1~2 次)。
⚠️ 实际踩坑 10:网络问题
Cursor Agent 会访问远程 API,网络不稳定可能导致任务失败。
解决:识别常见错误(如 ECONNRESET、ETIMEDOUT),在飞书里给友好提示,并可选做重试。
⚠️ 实际踩坑 11:项目路径不存在或无权限
用户填的项目路径可能不存在,或 OpenClaw 运行用户没有读写权限。
解决:在执行前检查路径存在性 + 权限,不通过则直接返回明确错误,不发起 Cursor 子进程。
九、实战插件:
feishu-cursor-cli是怎么把这些步骤拼在一起的我在本地写了一个简单的 OpenClaw 插件
feishu-cursor-cli,基本就是把上面这几节的思路串成了一段代码,方便你参考(这里只保留关键片段,完整实现可以联系作者获取):// cursor_cli_agent.js 中的核心结构(只示意关键字段) const task = { id: taskId, task_type: taskType, project: { root: projectRoot }, payload: { natural_language: text.trim(), // files / constraints 等可按需补充 }, };对应第五节里的「Prompt 从哪来」
// 根据任务模型构造给 Cursor CLI 的 Prompt(核心思路) function buildPromptFromTask(task) { return [ "你是一个代码开发 Agent,请在当前仓库完成下面任务。", "", `任务 ID: ${task.id}`, `任务类型: ${task.task_type}`, "", "自然语言需求:", task.payload.natural_language, // 如需,可在此追加 files / constraints 等信息 ].join("\n"); }对应第五、六节里的子进程调用 + 结果收集(省略了一些样板代码,只保留关键逻辑)。下面示例以 Windows 环境为例,使用
cmd.exe作为 shell,在 macOS / Linux 上可以去掉shell参数或改用/bin/bash。// 在指定项目目录下调用 Cursor CLI(只保留核心结构) async function runCursorCliTask(task, options = {}) { const cwd = path.resolve(task.project.root); const prompt = buildPromptFromTask(task); return new Promise((resolve, reject) => { const child = spawn("agent", [], { cwd, shell: "cmd.exe", // Windows 示例;在 macOS / Linux 上可改为 "/bin/bash" 或直接省略 env: { ...process.env, ...(options.env || {}) }, }); let output = ""; child.stdin.write(prompt); child.stdin.end(); child.stdout.on("data", (data) => (output += data.toString())); child.stderr.on("data", (data) => (output += data.toString())); child.on("error", (err) => reject(err)); child.on("close", (code) => resolve({ success: code === 0, exitCode: code, output }) ); }); }对应第八节错误处理和第七节结果收集
// 调度封装:围绕 runCursorCliTask 更新任务状态(极简版) async function dispatchToCursorCli(task, updateStatus) { await updateStatus({ status: "running" }); try { const result = await runCursorCliTask(task); await updateStatus({ status: result.success ? "done" : "failed", result_summary: result.success ? "执行成功" : "执行失败", result_output: (result.output || "").slice(0, 20000), }); } catch (err) { await updateStatus({ status: "failed", result_summary: "调用 Cursor CLI 出错", result_output: String(err), }); } }对应第三、四节「飞书 → OpenClaw」+「统一任务模型」(这里给简化版示例)
// 简单 Feishu 文本解析器(只保留思路示意) function buildTaskFromFeishuText(text, { taskId, defaultProjectRoot }) { const projectRoot = text.match(/项目[::]\s*([A-Za-z]:[^\s,。;;]*)/)?.[1]?.trim() || defaultProjectRoot; const payload = { natural_language: text.trim() }; // 这里可按需解析文件名 / 语言约束 / 任务类型等 return { id: taskId, task_type: "code_edit", project: { root: projectRoot }, payload, }; }// index.js(入口极简示意) const { buildTaskFromFeishuText, dispatchToCursorCli } = require("./cursor_cli_agent.js"); module.exports = { id: "feishu-cursor-cli", async handleFeishuMessage({ text, chatId, messageId, userId, sendReply, saveTask, updateTask }) { const taskId = `task_${Date.now()}`; const task = buildTaskFromFeishuText(text, { taskId, defaultProjectRoot: "F:\\cursor_work", }); await saveTask(taskId, { from_chat: { chatId, messageId, userId }, task }); await dispatchToCursorCli(task, async (partial) => { await updateTask(taskId, partial); if (partial.status === "done" || partial.status === "failed") { await sendReply(chatId, messageId, partial.result_summary); } }); }, };可以看到,这个插件本质上就是把本文讲的几步——「解析 Feishu 文本 → 构造任务对象 → 在项目目录中调用 Cursor CLI → 更新任务状态并截断输出 → 通过 Feishu 回推结果」用代码完整串起来,方便你参考或直接改成自己的版本。
十、手把手实操:一条消息让 Cursor CLI 写出冒泡排序
下面用一个完整例子,把整个流程串起来。
示例需求:请使用 Cursor CLI,在项目
F:\cursor_work\里,帮我写一个冒泡排序算法,使用 Python 编写,写入到.py文件中。步骤 1:在飞书里发出任务指令
此时飞书会把这条消息通过事件订阅 / 消息回调推送到你的 OpenClaw HTTP 接口。
步骤 2:OpenClaw 收到消息并生成内部任务
OpenClaw 的 Feishu 插件会解析这条文本,提取出:
F:\cursor_work\bubble_sort.py然后包装成统一的任务对象,例如(简化示意):
{ "id": "task_xxx", "task_type": "code_edit", "project": { "root": "F:\\\\cursor_work" }, "payload": { "natural_language": "在项目 F:\\cursor_work 里,用 Python 写一个冒泡排序算法,写入到 bubble_sort.py 中。" } }步骤 3:OpenClaw 在
F:\cursor_work目录里调用 Cursor CLIOpenClaw 根据任务里的
project.root,在该目录下通过子进程执行agent命令,并把上一步构造好的 Prompt 写入到标准输入中,大致等价于:cd F:\cursor_work agent然后由插件代码把「冒泡排序需求」作为一整段 Prompt 喂给 Cursor CLI。
步骤 4:Cursor CLI 在仓库中创建 / 更新
bubble_sort.pyCursor CLI 接收到 Prompt 后,会自动在当前仓库中:
bubble_sort.py;步骤 5:OpenClaw 收集 Cursor CLI 的执行结果
当这次
agent调用结束、子进程退出时,OpenClaw 会拿到:插件会把这些信息写回任务记录中,例如:
status: "done"或"failed"result_summary: "执行成功,已在 bubble_sort.py 中写入冒泡排序示例"result_output: 截断后的部分 CLI 输出内容步骤 6:通过飞书把结果回推给用户
最后,OpenClaw 根据任务里保存的
chat_id/message_id,调用飞书开放平台的发送消息接口,在同一会话里给用户回一条结果消息,例如:到这里,一个完整闭环就跑通了:飞书发指令 → OpenClaw 编排 → Cursor CLI 落地写码 → OpenClaw 收集结果 → 飞书回推摘要。
十一、(可选)扩展这个示例:再让 Cursor CLI 跑一遍测试
在前面的冒泡排序例子基础上,你还可以:
bubble_sort.py补充简单的单元测试,并运行一次。」task_type: "run_tests",在F:\cursor_work中调用 Cursor CLI,让它自动补充测试文件并执行。这样,一个非常贴近真实工作的「写功能 → 补测试 → 看结果」闭环,就完全建立在飞书 + OpenClaw + Cursor CLI 之上了。
十二、总结:飞书是入口,OpenClaw 是编排,Cursor CLI 是执行体
通过以上步骤,你已经完成了:
接下来可以在此基础上继续扩展,例如:多项目配置、权限控制、结果结构化(RESULT_JSON)、任务取消等。若以后 Cursor 提供更强的集成方式,当前以 CLI 为核心的设计也便于平滑升级。