AI 帮我盯飞书、修 Bug,我负责喝咖啡:DM-Watch 搭建全拆解
同事在飞书私信问我问题,我人不在电脑前,AI 自动帮我读消息、改代码、回复对方。TAPD 派了 Bug 给我,AI 自动去看描述、找到出问题的代码、修完提交。
我知道这听起来像吹牛。但这套东西我确实每天在用,而且踩了一堆坑才跑稳。
这套东西长什么样
两个后台监听,一键启动:
| 监听 | 干什么 |
|---|---|
| DM-Watch(飞书私信) | 盯着指定同事发给我的私信,Claude 自动分诊处理,能答的答,能改的改,以我的名义回复对方 |
| TAPD-Watch(Bug 派单) | 盯着派给我的 new/reopened Bug,Claude 拉详情、定位代码、修、提交、标已解决 |
脚本负责"盯"和"喊",Claude 负责干活。脚本每隔几秒轮询飞书 API 或 TAPD API,发现新消息就往终端吐一行事件,比如 DM-NEW name=李四 count=1。Claude Code 的 Monitor 接住这行事件,开始分析消息、决定怎么处理。
我可以完全不看电脑。
搭建过程
需要装什么
| 组件 | 干什么 | 怎么装 |
|---|---|---|
| Claude Code | AI 大脑 | Anthropic 官方 CLI |
| lark-cli | 飞书命令行,拉消息发消息 | 装好后 lark-cli auth login 登你的飞书号 |
| jq | JSON 解析,脚本里大量用 | brew install jq |
| TAPD Access Token | 调 TAPD API | TAPD 个人设置里生成 |
| GitLab PAT(可选) | 查 CI 流水线 | GitLab 个人设置 → Access Tokens |
文件就一个文件夹
放在 ~/.claude/skills/dm-watch/,里面这些东西:
├── SKILL.md ← 告诉 Claude 怎么处理事件的"操作手册"
├── config.json ← 你的配置(监听谁、TAPD 项目等)
├── config.example.json ← 配置模板
├── monitor-loop.sh ← 飞书私信轮询脚本
├── tapd-watch.sh ← TAPD Bug 轮询脚本
├── ci-watch.sh ← CI 失败监听(可选)
├── .seen-ou_xxx.txt ← 每个监听对象的"已读水位线"
├── .tapd-notified.txt ← 已通知过的 Bug ID
└── pending-ou_xxx.json ← 待处理的新消息
填配置
把 config.example.json 复制成 config.json,填四样东西:
{
"selfOpenId": "ou_你自己的飞书open_id",
"watched": [
{ "name": "张三", "openId": "ou_张三的open_id" },
{ "name": "李四", "openId": "ou_李四的open_id" }
],
"botChat": {
"chatId": "xxx",
"name": "my-bug-agent"
},
"tapd": {
"workspaceId": "44192340",
"ownerName": "你在TAPD的显示名"
},
"gitlab": {
"host": "gitlab.your-company.cn",
"projectPaths": [
"your-org/app/mobile-app",
"your-org/web/admin-panel",
"your-org/system/backend-service"
],
"projectDescriptions": {
"your-org/app/mobile-app": "移动端前端项目",
"your-org/web/admin-panel": "管理后台前端项目",
"your-org/system/backend-service": "后端服务项目"
},
"projectLocalPaths": {
"your-org/app/mobile-app": "/Users/你的用户名/projects/mobile-app",
"your-org/web/admin-panel": "/Users/你的用户名/projects/admin-panel",
"your-org/system/backend-service": "/Users/你的用户名/projects/backend-service"
}
},
"quietHours": { "start": 23, "end": 8 },
"pollSeconds": { "dm": 10, "tapd": 180 }
}
逐个说一下:
-
watched是你要监听谁的私信。open_id 用这个命令查:lark-cli contact +search-user --as user --queries '张三' --has-chatted。 -
tapd.workspaceId是 TAPD 项目 ID。打开 TAPD 任意项目页面看地址栏,https://www.tapd.cn/44192340/...里的那串数字就是。ownerName填你在 TAPD 上的显示名,脚本按这个名字筛"派给我的 Bug"。 -
botChat是需要监听的飞书机器人。通过:飞书机器人 创建一个机器人,填chatId和name到config.json。 -
gitlab支持多项目。projectPaths是 GitLab 项目路径数组,projectDescriptions给每个项目加一句中文描述方便 Claude 判断消息该归哪个仓库,projectLocalPaths是对应的本地目录。收到同事消息时,如果 Claude 拿不准该改哪个项目,会先飞书列出选项问你再动手。 -
quietHours是深夜不轮询的时段。pollSeconds.dm设 10 秒是因为聊天要快,pollSeconds.tapd设 3 分钟是因为 Bug 不急这几分钟。 -
TAPD 的 Access Token 不放 config.json 里,放在
~/.claude.json的 MCP 配置中:
{
"mcpServers": {
"tapd": {
"env": {
"TAPD_ACCESS_TOKEN": "你的token"
}
}
}
}
Token 在这里生成:https://www.tapd.cn/personal_settings/index?tab=personal_token 。tapd-watch.sh 启动时从 ~/.claude.json 读这个值,读不到直接报错退出。
两个脚本在干什么
monitor-loop.sh
伪代码:
死循环:
如果深夜 → 睡 5 分钟,跳过
遍历每个被监听的人:
调 lark-cli 拉最近 50 条私信
过滤掉自己发的
和 .seen 文件比对
有新消息 → 写 pending json + 吐事件行 + 更新 seen
睡 N 秒
首次启动时,脚本会把当前所有消息 ID 直接写进 .seen 文件,不处理任何历史消息。这个必须有,否则一启动就回放过去所有聊天记录,Claude 试图回复几十条历史消息,场面很难看。
已读水位线用 message_id 集合(平面文本,一行一个 ID),不用时间戳。消息乱序到达也不会漏。文件超 200 行自动裁剪。
tapd-watch.sh
启动先鉴权探测(limit=1 请求)
token 无效 → 报错退出
死循环:
查 TAPD API "派给我的 new/reopened Bug"
遍历每个 Bug:
bug_id:status 没通知过 → 吐事件行 + 记录
睡 3 分钟
去重用 bug_id:status 做 key。同一个 Bug 从 new 变成 reopened 会再次触发(key 变了),同状态不重复。
SKILL.md:Claude 的操作手册
脚本只管发现事件。怎么处理,写在 SKILL.md 里。这个文件是给 Claude 看的,定义了收到不同事件后的处理流程:
收到 DM-NEW:读 pending json → 分诊(问题?Bug?需求?)→ 执行 → 飞书回复对方 → 飞书通知我。
收到 TAPD-BUG:调 TAPD API 拉 Bug 详情 → 分析定位 → 修代码 → 提交 → 更新 Bug 状态 → 飞书通知我。
SKILL.md 里写得最醒目的一条规则:
用户不看终端,飞书是唯一通道。
这条是被我反复纠正后才定下来的。Claude 一开始总把确认问题写在终端里,我根本看不到。后来加了一句自检口诀:"写终端之前先问自己——这条飞书发了没有?"
一键启动
Claude Code 里输入 /dm-watch,Claude 检查配置和凭证,启动两个 Monitor 后台任务,报告状态。之后去忙别的就行。
实际在用的几个场景
同事发消息说改接口
后端同事在飞书私聊发了一张截图加一段话:"原来批量接口现在改单个接口,改下对应的前端调用"。
我当时在开会。10 秒后 monitor-loop.sh 发现新消息,Claude 读了 pending json,判断这是接口变更需求,找到前端调用点,改了 API 调用方式,飞书回复对方"已改完,批量接口调用改为单个调用",飞书通知我"后端同事说要改接口,已处理"。
等我开完会打开飞书,事情已经办完了。
TAPD 派了 Bug
有人提了个 Bug:"订单详情页,标签显示异常,每个标签独占一整行"。
tapd-watch.sh 3 分钟内发现,Claude 拉 Bug 描述,分析出标签组件用了错误的布局方式导致排版异常,改成正确的换行布局,提交代码,等 CI 过了,更新 TAPD 状态为已解决并加评论,飞书通知我。
同事问技术问题
有时候同事不是报 Bug,就是问"那个接口入参格式是什么"或者"那个页面路由路径是多少"。Claude 在项目代码里搜到答案后,以我的飞书身份回复对方,同时通知我"xxx 问了这个问题,我这样回复的"。
lark-cli im +messages-send --as user 是以我的身份发消息,对方看到的就是"我"在回复。
需要我拍板的事
不是所有事 Claude 都能自己定。涉及后端改动要我确认,修复方案有多种要我选,需求描述不清楚要不要问回去。
Claude 会飞书问我。然后通过 DM-Watch 监听我的回复,我在飞书里回一句"用方案一",Claude 收到后继续干。
这是个闭环:监听别人消息 → Claude 处理 → 不确定的飞书问我 → 监听我的回复 → 继续。
整理需求
后端同事有时在飞书里丢一大段需求描述,比如"列表接口联调改动,请求体新增 xxx 字段"洋洋洒洒几百字。Claude 读完之后飞书给我发一份精炼的分析:需要改哪些文件、涉及哪些接口。我回一句"改吧"就直接开始。
踩过的坑
终端写了一堆,飞书没发。 早期最大的问题。Claude 处理完事情在终端写一大段总结,我根本不看终端。纠正了两次之后把"飞书铁律"写进 SKILL.md 最醒目的位置。
首次启动回放历史消息。 第一版脚本没做水位线种子,启动后把过去所有聊天记录全当新消息,Claude 一口气试图回复几十条。加了首次种子逻辑才消停。
自己发的消息触发了自己。 没过滤发送者。我回复别人的消息也被当成"新消息"反复触发。加了 selfOpenId 和 sender 过滤。
Claude 直接改了后端代码。 收到同事说后端有 Bug,Claude 改了代码直接提交了。后端改动影响面太大,从此定了铁律:后端必须先飞书问我确认再动手,前端不限制。
lark-cli token 过期没人知道。 飞书 user token 大约一周过期,过期后脚本默默地什么都拉不到。加了静默失败检测:连续 3 轮全部请求失败就吐 DM-WATCH-AUTH-LOST,Claude 提醒我重新登录。
整体怎么串起来的
┌───────────────────────────────────────────┐
│ Claude Code 会话 │
│ │
│ monitor-loop.sh tapd-watch.sh │
│ (10秒轮询飞书) (3分钟轮询TAPD) │
│ │ │ │
│ DM-NEW ... TAPD-BUG ... │
│ │ │ │
│ └──────────┬────────────┘ │
│ ▼ │
│ Claude 接住事件 │
│ 读消息 / 拉Bug详情 │
│ 分诊 → 执行 → 回复 │
│ │ │
│ lark-cli 发飞书 │
│ git commit/push │
└──────────────────┼─────────────────────────┘
▼
飞书(回复+通知)
GitLab(代码仓库)
一句话:脚本轮询 → 吐事件 → Claude 接住 → 分诊执行 → 飞书闭环。
给同事用
这个文件夹可以直接拷给别人。装 lark-cli 登自己飞书号,复制 config.example.json 填自己的值,在 Claude Code 里敲 /dm-watch 就能跑。
想加监听的人也不用手动改 config.json,直接在 Claude Code 里说"帮我监听张三"就行,Claude 会自动查 open_id、更新配置、重启监听。
不需要飞书机器人,不需要服务器,不需要数据库。
有一个限制:Claude Code 会话开着才活。电脑睡了或者会话关了,监听就停了。7x24 无人值守要做成独立服务,目前工作时间段用够了。
为什么搞这个
飞书一会儿弹一个消息,TAPD 一会儿派一个 Bug,每次被打断都要几分钟恢复上下文。同事问的很多问题查一下代码就知道答案,不需要我亲自处理。
DM-Watch 把"被打断"变成"被代理"。同事照样得到及时回复,Bug 照样被修,但我的时间没有被切碎。遇到真正需要我判断的事,飞书通知加回环确保不会漏掉。
从第一行脚本到跑稳,迭代了十几个版本。去重、水位线、静默期、auth 过期检测全是踩坑后补的。如果你也想搞一套,先跑最小版本再说——一个 watched 对象加一个轮询脚本,能跑了再往上加。

