当前位置: 首页 > news >正文

Claude Code实战9|250行代码打通飞书CLI与Claude的双向通信结构

装好了 Lark CLI,也配好了 Claude Code,然后呢?

很多人卡在这一步:两个工具都能用,但就是串不起来。在飞书对话框里发了消息,Claude 那边毫无反应;Claude 处理完了,又不知道怎么把结果送回飞书。

核心原因很简单——Lark CLI 和 Claude Code 是两个独立的命令行工具,它们之间没有现成的桥梁。

今天这篇文章,我用一个 Node.js 脚本(约250行),实现了:

  • 在飞书对话框发消息 → 自动触发 Claude Code 处理

  • Claude Code 响应 → 自动发回飞书对话框

  • 全程自动化,无需切换窗口

环境要求:

  • macOS / Linux

  • Node.js ≥ 16

  • lark-cli已安装并登录(npm i -g @anthropic/lark-cli

  • claudeCLI 已安装(Claude Code 2.x)

  • 飞书机器人已创建(需要im:message相关权限)

方案设计:轮询 + 管道调用

架构总览

飞书用户 → 发消息给机器人 ↓ Bridge (Node.js 轮询) ├── lark-cli 拉取新消息 ├── echo "消息" | claude -p 处理 └── lark-cli 发送响应 ↓ 飞书用户 ← 收到 Claude 回复

为什么选轮询而不是 Webhook?

轮询模式

Webhook 模式

配置复杂度

低,无需改飞书后台

高,需配置事件订阅

本地端口

不需要暴露

需要公网可达

延迟

约5秒

实时

适用场景

个人/小团队

生产环境

对个人使用来说,5秒延迟完全够用,省掉的配置工作量是值得的。

核心代码解析

完整代码约250行,这里拆解最核心的三个函数。

1. 轮询飞书消息

function fetchLatestMessages() { const cmd = `lark-cli im +chat-messages-list \ --as bot \ --user-id ${CONFIG.userOpenId} \ --format json`; const output = execSync(cmd, { encoding: 'utf-8', timeout: 15000 }); const data = JSON.parse(output); if (data.ok && data.data && data.data.messages) { return data.data.messages; } return []; }

关键点:

  • --as bot:以机器人身份获取消息

  • --user-id:指定用户的open_id,获取与该用户的 P2P 对话

  • --format json:返回 JSON 格式,方便解析

  • 返回的消息数组中,第一条是最新消息

2. 调用 Claude Code

function callClaude(userMessage) { return new Promise((resolve, reject) => { sendToLark('⏳ 正在思考中...'); const cmd = `echo "${userMessage.replace(/"/g, '\\"')}" | claude -p 2>&1`; exec(cmd, { encoding: 'utf-8', timeout: 120000, // 2分钟超时 maxBuffer: 1024 * 1024, }, (error, stdout) => { if (error) { resolve(error.killed ? '抱歉,处理超时了。请尝试简化你的问题。' : '处理出错,请稍后再试。'); return; } resolve(stdout.trim() || '抱歉,没有生成有效响应。'); }); }); }

关键点:

  • claude -p是 print 模式,非交互式,直接输出结果后退出

  • 用管道echo "消息" | claude -p传递用户输入

  • 设置 2 分钟超时,防止复杂任务长时间阻塞

  • 2>&1合并错误输出,确保能捕获所有信息

3. 发送消息回飞书

function sendToLark(text) { const tmpFile = path.join(CONFIG.workDir, '.tmp_msg.txt'); fs.writeFileSync(tmpFile, text); const cmd = `lark-cli im +messages-send \ --as bot \ --user-id ${CONFIG.userOpenId} \ --text "$(cat '${tmpFile}')"`; execSync(cmd, { encoding: 'utf-8', timeout: 15000 }); fs.unlinkSync(tmpFile); }

关键点:

  • 用临时文件中转消息内容,避免特殊字符转义问题

  • --text参数直接发送纯文本,无需手动构造 JSON

  • 发完即删临时文件,保持工作目录整洁

4. 主循环:串联一切

setInterval(async () => { if (isProcessing) return; // 防止并发 const messages = fetchLatestMessages(); if (messages.length === 0) return; const latestMessage = messages[0]; if (latestMessage.message_id === lastMessageId) return; // 关键:过滤 bot 自己发的消息 const isFromUser = latestMessage.sender && latestMessage.sender.sender_type === 'user'; if (isFromUser) { saveLastMessageId(latestMessage.message_id); lastMessageId = latestMessage.message_id; await handleMessage(latestMessage); } else { // bot 消息只更新 ID,不处理 saveLastMessageId(latestMessage.message_id); lastMessageId = latestMessage.message_id; } }, 5000);

实战中的坑点 + 避坑指南

坑1:JSON 字段名不是items,是messages

lark-cli im +chat-messages-list返回的 JSON 中,消息数组的字段名是data.messages,不是很多人以为的data.items

// ❌ 错误 data.data.items // ✅ 正确 data.data.messages

避坑:先用--format json手动执行一次,确认实际字段结构。

坑2:Bot 消息回环(死循环)

这是最危险的坑。Bot 发出的响应消息,会出现在chat-messages-list的结果中。如果不过滤,Bridge 会把 Bot 自己的消息当成新消息处理,再次调用 Claude,再次发送响应……无限循环。

// ❌ 不过滤 → 死循环 if (latestMessage.message_id !== lastMessageId) { handleMessage(latestMessage); } // ✅ 必须检查 sender_type const isFromUser = latestMessage.sender && latestMessage.sender.sender_type === 'user'; if (isFromUser) { handleMessage(latestMessage); }

避坑:务必通过sender.sender_type === 'user'过滤,只处理真实用户消息。

坑3:claude -p的正确调用方式

Claude Code CLI 的-p(print)模式有几个容易踩的坑:

# ❌ --no-input 不是有效参数 claude -p '你好' --no-input # ❌ 单引号在 Node.js exec 中容易出转义问题 claude -p '包含"引号"的消息' # ✅ 用管道传入最稳定 echo "你好" | claude -p

避坑:统一用echo "消息" | claude -p 2>&1管道方式调用。

坑4:发送消息的参数是--text,不是--data

lark-cli im +messages-send支持多种参数,很多人习惯性地用--data传 JSON,但实际上纯文本消息用--text更简洁可靠。

# ❌ --data 需要构造嵌套 JSON,容易出错 lark-cli im +messages-send --data '{"msg_type":"text","content":"{\"text\":\"你好\"}"}' # ✅ --text 自动处理格式 lark-cli im +messages-send --text "你好"

避坑:优先用--text发送纯文本,用--markdown发送富文本。

坑5:特殊字符导致命令执行失败

用户消息可能包含引号、反斜杠、换行等特殊字符,直接拼接到命令中会导致 shell 解析失败。

// ❌ 直接拼接 → 特殊字符炸裂 const cmd = `echo "${userMessage}" | claude -p`; // ✅ 先转义引号 const cmd = `echo "${userMessage.replace(/"/g, '\\"')}" | claude -p`; // ✅✅ 更稳妥:用临时文件中转 fs.writeFileSync(tmpFile, text); const cmd = `lark-cli im +messages-send --text "$(cat '${tmpFile}')"`;

避坑:涉及用户输入的地方,统一用临时文件中转,彻底规避转义问题。

快速启动

步骤1:获取你的 open_id

lark-cli auth status

找到userOpenId字段,类似ou_xxxxxxxxxxxx

步骤2:修改配置

编辑bridge.js,替换CONFIG.userOpenId为你自己的 open_id。

步骤3:启动服务

cd ~/lark-claude-bridge chmod +x start.sh stop.sh ./start.sh

步骤4:测试

在飞书给你的机器人发一条消息,几秒后应该收到 Claude 的回复。

停止服务

./stop.sh

心得体会 + 总结

做这个项目最大的感受

"简单的需求,不简单的细节。"

表面上看,这个项目就是"读消息 → 处理 → 发回去",三步搞定。但实际开发中,光是消息回环这一个坑,就让我的飞书对话框被刷屏了一轮。

每一个看似 trivial 的细节——JSON 字段名、CLI 参数格式、特殊字符转义、bot 消息过滤——都可能让整个系统瘫痪。这也是为什么很多人"装好了工具但串不起来"的原因:不是工具不行,是粘合层的细节太多。

这个方案的价值

对于个人开发者来说,这套方案的价值在于:

  1. 移动端可用:在手机飞书上就能给 Claude 下达任务,不必守在电脑前

  2. 异步工作流:发出任务后可以去做别的事,Claude 处理完自动通知

  3. 团队协作基础:稍加扩展就能支持多人使用,让团队成员都能通过飞书调用 Claude

后续优化方向

当前版本是 MVP,还有几个值得优化的方向:

  • 多轮上下文:目前每条消息独立处理,加上claude --resume可以实现连续对话

  • Markdown 支持:用--markdown发送富文本,代码块和表格显示更友好

  • 任务队列:支持多条消息排队处理,而不是简单跳过

  • Webhook 模式:如果需要实时性,可以升级为事件订阅方案

需要完整代码请关注我,评论区留言“Claude Code实战”,我将发送可以直接使用的代码给你。

获取更多 AI 智能制造、飞书自动化、Claude Code 实战干货,欢迎关注我的公众号「Rubin 智造社」

【关键词标签】#ClaudeCode #AI编程 #开发工作流 #代码重构 #飞书 #子代理 #实战教程 #代码分析 #飞书CLI #AI协作者

相关阅读:

Claude Code实战8: 高效排错修复问题实战手记

Claude Code实战7:5分钟“吃透”陌生代码库的工程心法

Claude Code实战6: 告别黑框,安装可视化界面这才是AI编程该有的面子!

Claude code实战5: Claude 4.5升级介绍,让AI工程化落地快了不止一倍

http://www.jsqmd.com/news/576684/

相关文章:

  • 基于NGAFID航空数据集的InceptionTime时序分类研究
  • Linux平台微信开发者工具终极指南:快速搭建免费小程序开发环境
  • 如何用AI图像分层工具layerdivider:3分钟完成复杂插画分层
  • 2026预算有限怎么学雅思口语?高性价比线上课程推荐 - 品牌2025
  • 终极视频修复指南:如何使用Untrunc轻松恢复损坏的MP4/MOV文件
  • semi-utils:摄影师的EXIF水印自动化工具——从效率提升到专业呈现
  • 【Proteus 仿真实战】基于51单片机的智能测距与自适应报警系统设计
  • Fitgirl-Repack-Launcher:重新定义游戏资源管理的开源解决方案
  • Android StrictMode实战:线程与虚拟机策略的深度检测与优化
  • 颠覆式智能剪辑:用Python重新定义视频创作流程
  • OpenClaw——windows保姆级安装教程【零基础小白也能操作】
  • 如何突破网盘限速?2025年开源直链解析工具深度解析与应用指南
  • 独立完成了一道题
  • 5个维度解析libiec61850:电力系统通信的开源解决方案
  • Lombok实战避坑指南:内部类为何必须标注@Data注解?
  • JS 入门通关手册(35):执行上下文、调用栈与作用域链深度解析
  • 3步打造复古时间美学:给设计师的FlipIt屏保配置指南
  • C++ 位运算从入门到精通(全知识点+面试题+实战应用)
  • 2026襄阳做农产品品牌线上推广的公司多少钱,价格大揭秘 - 工业品牌热点
  • 效率革命:QuickLookVideo如何重构macOS视频工作流
  • SCH16T-K01 | SCH16T-K10 | SCH16T-K20村田 6DoF陀螺仪加速度传感器
  • ROG笔记本色彩管理与配置优化指南:从问题诊断到专业校准
  • LFM2.5-1.2B-Thinking-GGUF保姆级教程:低资源VPS部署LLM Web服务
  • ai赋能:快马平台智能生成个性化wsl安装ubuntu方案,打造专属开发环境
  • JavaSE从0到1-DAY7-内部类(i)
  • B03 SpringMVC拦截器
  • 效率倍增:基于快马AI生成web版批量服务器管理工具,告别重复终端操作
  • 怎样轻松下载网页视频:3个实用技巧与猫抓浏览器扩展指南
  • VMware虚拟机Ubuntu 22.04与Windows共享剪贴板终极指南(附中文输入法切换技巧)
  • Linux内核核心机制与开发实践详解