Claude Code实战9|250行代码打通飞书CLI与Claude的双向通信结构
本文介绍了一个通过Node.js脚本实现飞书与ClaudeCode自动交互的方案。该方案利用轮询机制,解决了LarkCLI和ClaudeCode两个独立工具间的通信问题,实现了消息自动传递和处理。文章详细解析了核心代码功能,包括消息轮询、Claude调用和结果返回等关键模块,并总结了开发中遇到的常见问题及解决方案。该方案特别适合个人和小团队使用,无需复杂配置即可实现移动端异步调用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 消息过滤——都可能让整个系统瘫痪。这也是为什么很多人"装好了工具但串不起来"的原因:不是工具不行,是粘合层的细节太多。

这个方案的价值
对于个人开发者来说,这套方案的价值在于:
-
移动端可用:在手机飞书上就能给 Claude 下达任务,不必守在电脑前
-
异步工作流:发出任务后可以去做别的事,Claude 处理完自动通知
-
团队协作基础:稍加扩展就能支持多人使用,让团队成员都能通过飞书调用 Claude
后续优化方向
当前版本是 MVP,还有几个值得优化的方向:
-
多轮上下文:目前每条消息独立处理,加上
claude --resume可以实现连续对话 -
Markdown 支持:用
--markdown发送富文本,代码块和表格显示更友好 -
任务队列:支持多条消息排队处理,而不是简单跳过
-
Webhook 模式:如果需要实时性,可以升级为事件订阅方案
需要完整代码请关注我,评论区留言“Claude Code实战”,我将发送可以直接使用的代码给你。
获取更多 AI 智能制造、飞书自动化、Claude Code 实战干货,欢迎关注我的公众号 「Rubin 智造社」
【关键词标签】#ClaudeCode #AI编程 #开发工作流 #代码重构 #飞书 #子代理 #实战教程 #代码分析 #飞书CLI #AI协作者
相关阅读:
Claude Code实战7:5分钟“吃透”陌生代码库的工程心法
更多推荐




所有评论(0)