Cherry Studio 飞书 Webhook 通知 CLI:scripts/feishu-notify.ts全指南
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
本篇技术指南围绕 CherryHQ/cherry-studio 仓库中的 scripts/feishu-notify.ts 展开,讲解如何通过一个基于 TypeScript 与 Commander 的子命令式 CLI 向飞书(Lark)Webhook 发送交互式卡片通知,并重点剖析其在 .github/workflows/github-issue-tracker.yml 中的自动化落地实践。读完本文,你将掌握send与issue两个命令的完整用法、环境变量鉴权机制、HMAC-SHA256 签名原理、GitHub Actions 集成方式,以及如何扩展新的通知命令。
一、工具定位与核心特性
scripts/feishu-notify.ts是 Cherry Studio 仓库中一个独立、可脱离主程序运行的 CLI 工具,用于向飞书群机器人 Webhook 发送通知。它不依赖 Electron 主进程或渲染进程的任何代码,仅依赖 Node.js 标准库与少量 npm 包,因此可以在 CI(如 GitHub Actions)或任何安装了 Node.js 的环境中直接运行。
从源码(scripts/feishu-notify.ts)可见其设计要点:
- 子命令式 CLI 结构:基于
commander实现,通过send与issue两个子命令区分通用通知与 GitHub Issue 通知,便于按业务扩展(program.command('send')、program.command('issue'))。 - HMAC-SHA256 签名校验:使用
crypto.createHmac('sha256', ...)按飞书官方规则生成签名,保证 Webhook 请求来源可信。 - 交互式卡片消息:发送
msg_type: 'interactive'的飞书卡片(含头部颜色模板、lark_md 富文本、分隔线与跳转按钮)。 - 完整 TypeScript 类型支持:所有卡片元素、载荷、命令选项均定义了明确的 interface,配合
zod枚举(FeishuHeaderTemplateSchema)在运行时校验颜色等枚举参数。 - 凭据走环境变量:
FEISHU_WEBHOOK_URL与FEISHU_WEBHOOK_SECRET从环境读取,避免把密钥硬编码进命令或仓库。
依赖版本(见 package.json)为:commander ^14.0.2、dotenv 16.6.1、tsx ^4.20.3、zod ^4.1.5、@types/node 24.10.4,其中tsx用于直接执行 TypeScript 脚本,无需预编译。
二、环境准备与运行方式
2.1 前置条件
pnpm install仓库使用 pnpm 管理依赖(根目录存在pnpm-lock.yaml与pnpm-workspace.yaml)。由于脚本以#!/usr/bin/env npx tsx开头,并通过pnpm tsx调用,因此依赖tsx这一 TypeScript 执行器。
2.2 CLI 通用结构
pnpm tsx scripts/feishu-notify.ts [command] [options]脚本启动时通过dotenv.config()自动加载.env文件(若存在),这意味着除了在 CI 中通过环境变量注入凭据外,本地调试时也可以把凭据写入仓库根目录的.env文件。
2.3 必需环境变量
| 变量 | 说明 |
|---|---|
FEISHU_WEBHOOK_URL | 飞书群自定义机器人 Webhook 地址 |
FEISHU_WEBHOOK_SECRET | 飞书 Webhook 签名密钥(安全设置中开启"签名校验"后获得) |
这两个变量缺一不可:getCredentials()(scripts/feishu-notify.ts中)会在缺失时打印Error: FEISHU_WEBHOOK_URL environment variable is required并以退出码 1 结束进程,避免向错误目标发送请求。
三、命令详解与实战示例
3.1send—— 发送通用通知
send用于发送不带业务逻辑的通用通知,适合部署完成、构建失败、告警等场景:
pnpm tsx scripts/feishu-notify.ts send [options]| 选项 | 短参 | 说明 | 是否必填 |
|---|---|---|---|
--title | -t | 卡片标题 | 是 |
--description | -d | 卡片描述内容(支持 Markdown) | 是 |
--color | -c | 头部颜色模板 | 否(默认turquoise) |
可选颜色:blue、wathet、turquoise、green、yellow、orange、red、carmine、violet、purple、indigo、grey、default。该枚举由FeishuHeaderTemplateSchema(zod enum)在运行时校验,若传入非法值,脚本会输出合法颜色列表并以退出码 1 终止(见handleSendCommand)。
示例一:部署完成通知(绿色)
# 使用 $'...' 语法保证换行正确 pnpm tsx scripts/feishu-notify.ts send \ -t "Deployment Completed" \ -d $'**Status:** Success\n\n**Environment:** Production\n\n**Version:** v1.2.3' \ -c green示例二:错误告警(红色)
pnpm tsx scripts/feishu-notify.ts send \ -t "Error Alert" \ -d $'**Error Type:** Connection failed\n\n**Severity:** High\n\nPlease check the system status' \ -c red换行注意事项:描述中的换行必须使用 bash 的$'...'语法(如$'**Status:** Success\n\n...')。不要直接在双引号内写字面量\n——它会被当作普通字符原样显示在飞书卡片中。createSimpleCard会把整个 description 作为lark_md文本元素写入卡片正文,因此 Markdown 语法(**加粗**、空行分段)都会在飞书端渲染。
3.2issue—— 发送 GitHub Issue 通知
issue专为 GitHub Issue 通知设计,自动组装作者、标签、摘要与跳转按钮:
pnpm tsx scripts/feishu-notify.ts issue [options]| 选项 | 短参 | 说明 | 是否必填 |
|---|---|---|---|
--url | -u | GitHub Issue 地址 | 是 |
--number | -n | Issue 编号 | 是 |
--title | -t | Issue 标题 | 是 |
--summary | -m | Issue 摘要 | 是 |
--author | -a | Issue 作者 | 否(默认Unknown) |
--labels | -l | Issue 标签(逗号分隔) | 否 |
示例:
pnpm tsx scripts/feishu-notify.ts issue \ -u "https://github.com/owner/repo/issues/123" \ -n "123" \ -t "Bug: Something is broken" \ -m "This is a bug report about a feature" \ -a "username" \ -l "bug,high-priority"handleIssueCommand中会校验url、number、title、summary四项必填,缺失即报错退出;labels字符串经split(',').map(trim).filter(Boolean)清洗成数组,只有非空标签才会出现在卡片上。
四、GitHub Actions 自动化集成
该脚本的主要用途即是在 CI 工作流中实现"Issue 一创建即通知飞书"的自动化。.github/workflows/github-issue-tracker.yml 提供了完整的生产级示例:
- name: Install dependencies run: pnpm install - name: Send notification run: | pnpm tsx scripts/feishu-notify.ts issue \ -u "${{ github.event.issue.html_url }}" \ -n "${{ github.event.issue.number }}" \ -t "${{ github.event.issue.title }}" \ -a "${{ github.event.issue.user.login }}" \ -l "${{ join(github.event.issue.labels.*.name, ',') }}" \ -m "Issue summary content" env: FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }} FEISHU_WEBHOOK_SECRET: ${{ secrets.FEISHU_WEBHOOK_SECRET }}4.1 静默期延迟通知机制
该工作流还实现了"北京时间 00:00–08:30 静默期延迟通知"的工程细节,值得借鉴:
process-new-issuejob 监听issues: [opened]事件,先用TZ='Asia/Shanghai'计算北京当前时间;- 若处于静默期,则为 Issue 打上
pending-feishu-notification标签,暂不发送通知; process-pending-issuesjob 由schedule(每天 00:30 UTC,即北京时间 08:30)触发,通过gh api拉取带该标签的 Issue 列表,逐个调用feishu-notify.ts issue补发通知,成功后用gh api -X DELETE移除标签,并在多个 Issue 之间间隔 2–3 秒避免 API 限流。
4.2 与 Claude Code Agent 的组合
值得注意的细节是:工作流中飞书通知并非由 YAML 直接拼接参数发送,而是通过anthropics/claude-code-action@v1让 Claude Code Agent 读取 Issue 正文、用简体中文生成 2–3 句摘要后,再调用pnpm tsx scripts/feishu-notify.ts issue发送(claude_args中通过--allowed-tools白名单了该 CLI 的调用权限)。这体现了issue命令"摘要内容由上层逻辑生成"的设计意图——-m参数只负责承载已经处理好的文本,命令本身保持职责单一。
五、飞书卡片消息格式
issue命令发送的交互式卡片结构(由createIssueCard生成)如下:
- Header 标题:
#<issue_number> - <issue_title>,颜色模板固定为blue; - 作者行:
**Author:** <author>,使用lark_md标签渲染; - 标签行:
**Labels:** label1, label2(仅在存在标签时出现); - 摘要区:以
hr分隔线隔开的**Summary:**\n<summary>; - 操作按钮:
action元素内的primary按钮"View Issue",url指向 Issue 页面。
send命令的卡片则更简单(createSimpleCard):仅包含一个lark_md正文元素与一个指定颜色模板的 header,默认颜色为turquoise。
5.1 底层消息载荷
两种卡片最终都由sendToFeishu统一打包成飞书 Webhook 期望的载荷结构(FeishuPayload):
{ timestamp: string // 当前 Unix 秒级时间戳 sign: string // HMAC-SHA256 签名 msg_type: 'interactive', card: FeishuCard // 卡片内容 }请求通过 Node.js 原生https.request以POST方式发送到FEISHU_WEBHOOK_URL(解析hostname与pathname + search),Content-Type为application/json。响应为 2xx 时打印飞书返回的 JSON 并成功 resolve,否则抛出Feishu API error: <statusCode> - <data>错误。
5.2 HMAC-SHA256 签名原理
签名实现位于generateSignature:
const stringToSign = `${timestamp}\n${secret}` const hmac = crypto.createHmac('sha256', stringToSign) return hmac.digest('base64')即:以timestamp + '\n' + secret作为 HMAC-SHA256 的密钥,对空内容求摘要后取 Base64。这正是飞书官方"签名校验"的算法约定(时间戳必须与请求体中的timestamp字段一致,飞书端用同一密钥复算比对)。在飞书群设置中开启安全设置 → 签名校验后即可获得secret。
六、配置飞书 Webhook
- 进入目标飞书群 → 设置 → 群机器人 → 添加机器人 → 选择"自定义机器人";
- 记录生成的 Webhook 地址,并在"安全设置"中开启签名校验,复制签名密钥;
- 将两者配置为 GitHub 仓库的 Secrets(Settings → Secrets and variables → Actions):
FEISHU_WEBHOOK_URL:Webhook 地址FEISHU_WEBHOOK_SECRET:签名密钥
本地调试时也可以直接以环境变量前缀方式运行:
FEISHU_WEBHOOK_URL="https://open.feishu.cn/open-apis/bot/v2/hook/xxx" \ FEISHU_WEBHOOK_SECRET="your-secret" \ pnpm tsx scripts/feishu-notify.ts send -t "Test" -d "Hello Feishu"七、错误处理行为
脚本在以下场景会以非零退出码结束(便于 CI 感知失败):
- 缺失必需环境变量(
FEISHU_WEBHOOK_URL、FEISHU_WEBHOOK_SECRET),由getCredentials()检测并process.exit(1); - 缺失必需命令选项(
issue缺少--url/--number/--title/--summary,send缺少--title/--description),由commander的requiredOption或handleIssueCommand的显式校验拦截; send传入非法颜色,由 zod 枚举校验拦截并列出合法值;- 飞书 API 返回非 2xx 状态码,抛
Feishu API error; - 网络请求失败(DNS、连接被拒、超时等),
req.on('error')将错误透传给命令处理函数,统一打印Error: <message>后退出。
两个子命令的action回调都包在try/catch中,因此任何异步错误都会转换为清晰的错误输出与退出码 1,不会出现静默失败。
八、扩展新的通知命令
CLI 通过program.command()注册子命令,扩展新通知类型只需四步(以源码结构为参照):
- 定义命令选项接口:仿照
IssueOptions/SendOptions声明选项类型; - 编写卡片构建函数:仿照
createIssueCard/createSimpleCard,返回符合FeishuCard类型的卡片(elements数组 +header),元素类型可为div(lark_md 文本)、hr(分隔线)、action(按钮)三种,见FeishuCardElement联合类型; - 添加命令处理器:仿照
handleIssueCommand,先getCredentials()校验环境变量,再组装卡片并调用sendToFeishu; - 注册命令:在
program.parse()之前调用program.command('your-command').requiredOption(...).action(...)注册。
由于sendToFeishu、generateSignature、getCredentials均为通用函数,新命令可零成本复用签名、发送与鉴权逻辑,这也是该脚本"以子命令为扩展单元"的架构收益。
九、总结
scripts/feishu-notify.ts是一个小而完整的工程化示例:Commander 子命令结构、zod 运行时校验、HMAC-SHA256 安全签名、原生 HTTPS 请求、环境变量凭据管理,以及 GitHub Actions 中的静默期调度与 Agent 协作——这些设计使其既能作为 Cherry Studio 仓库的 CI 通知组件,也能被其他项目直接借鉴或复刻。更多细节可对照源码 scripts/feishu-notify.ts 与工作流 .github/workflows/github-issue-tracker.yml 查阅,本文档的原始出处为 docs/contrib/feishu-notify.md。
【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考