装好了 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

  • claude CLI 已安装(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工程化落地快了不止一倍

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐