AI 任务完成、出错或权限弹窗时,自动发飞书或邮件通知
复制下面这句话,发给你的 AI 助手,它就会自动安装这个 Skill。
帮我安装wwcai-agent-notify技能,下载地址:https://wwcai.cn/skills/wwcai-agent-notify.zip你是飞鸽传书的交互式部署/卸载向导。有两种模式:
模式判断:如果用户说了「卸载」「清除」「删掉」「不要了」「remove」「uninstall」「cleanup」等词,进入 [卸载] 流程。否则默认进入 [安装] 流程。
核心原则:
装到哪个 AI Agent?
Claude Code — Hook 机制(事件 → 外部命令)→ ~/.claude/
OpenCode — Plugin 机制(JS 订阅事件)→ ~/.config/opencode/
两个都要 — 同时部署→ AskUserQuestion, header="🤖 目标 Agent"
后续所有部署步骤根据此选择分支。完成后先进入阶段 0.5 检测已有安装。
在进入配置前,根据阶段 0 选择的 Agent,检测已有安装状态。先检测 skill 本身,再检测 hook/插件。
检查 skill 是否已注册到对应 Agent 的 skills 目录:
| Agent | 全局 skills 目录 |
|---|---|
| Claude Code | ~/.claude/skills/ |
| OpenCode | ~/.config/opencode/skills/ |
检测方式:Exists 检查 <skills-dir>/agent-notify/SKILL.md。
如果 skill 未安装:自动安装——先装 skill 再装 hooks/插件。安装方式:复制当前 skill 目录(含 scripts/ + references/)到对应 Agent 的 skills 目录。
Read ~/.claude/settings.json(全局)或 .claude/settings.json(项目),检查:
env 中是否包含 HOOK_FEISHU_WEBHOOK_URL 或 HOOK_SMTP_HOSThooks 中是否有 command 含 send-webhook.py 或 send-mail.pyRead hooks 目录下是否存在 send-webhook.py / send-mail.py。
检查以下文件是否存在(只 Exists 检查,不 Read):
~/.config/opencode/hooks/send-webhook.py~/.config/opencode/hooks/send-mail.py~/.config/opencode/plugins/notify.ts~/.config/opencode/notify-config.json检测到 <Agent> 已有飞鸽传书安装:
📋 Skill 本身:<已安装 ✅ / 未安装 ❌>
📄 Hook/插件配置:<已安装 ✅ / 未安装 ❌>
📜 脚本文件:<send-webhook ✅/❌, send-mail ✅/❌>
要怎么做?
覆盖重装 — 保留已有配置,深度合并(只追加不删除);skill 如缺失则补装
跳过 — 不重复安装该 Agent
先卸载再重装 — 走 [卸载] 清理后全新安装→ AskUserQuestion, header="⚠️ 已有安装"
如果两个 Agent 都选了但只有一个已安装:只对已安装的 Agent 提示,另一个正常走安装流程。
如果没有任何安装残留:直接进入阶段 1,无需提示。
消息送到哪里?
飞书群机器人 — 不同事件不同颜色的卡片(推荐)
邮箱 — HTML 邮件
两个都要 — 双通道→ AskUserQuestion, header="📬 通知通道"
根据阶段 0 的选择展示事件列表。
Claude Code:
| 事件 | 解释 | 通知 |
|---|---|---|
TaskCompleted | 任务完成 | ✅ |
Stop | 回复结束 | ✅ |
PermissionRequest | 权限弹窗 | ✅ |
UserPromptSubmit | 暂存提问 | ❌ 缓存 |
OpenCode: (事件名不同,映射见 references/event-mapping.md)
| 事件 | 解释 | CC 对照 | 通知 |
|---|---|---|---|
session.idle | 会话空闲/任务完成 | ≈ Stop | ✅ |
session.error | 会话出错 | ≈ StopFailure | ✅ |
permission.asked | 权限弹窗 | ≈ PermissionRequest | ✅ |
OpenCode 用户输入由插件自动捕获(
message.part.updated),无需单独事件。
如果"两个都要",展示两个表并让用户按 OC 事件名选,内部映射到 CC 事件。
→ AskUserQuestion, multiSelect=true, header="🔔 通知时机"
CC 选了 Stop/TaskCompleted 但没选 UserPromptSubmit 时提醒:"不选「用户输入暂存」的话,通知里看不到你的提问,建议一起选。"
全局生效(所有项目)还是仅当前项目?| 范围 | Claude Code | OpenCode |
|---|---|---|
| 全局 | ~/.claude/ | ~/.config/opencode/ |
| 项目 | .claude/ | .opencode/ |
→ AskUserQuestion, header="📍 生效范围"
⚠️ 需要飞书企业账户。个人版点击「创建企业/团队」即可(名称随意,不用认证)。
获取步骤:
⚠️ 签名 base64 含
+/=字符,脚本已用urlencode正确处理,勿手动拼 URL。
依次问:Webhook 地址 → 签名密钥(可不填)
选服务商:QQ / 163 / Gmail / Outlook → AskUserQuestion
授权码获取:
依次问:地址 → 授权码 → 收件人(可同发件人)
执行 python -c "import sys; print(sys.executable)",拿到绝对路径。
按阶段 0(Agent)+ 阶段 3(范围)确定并创建目录:
| Agent | 范围 | hooks 目录 | plugins 目录 |
|---|---|---|---|
| Claude Code | 全局 | ~/.claude/hooks/ | — |
| Claude Code | 项目 | .claude/hooks/ | — |
| OpenCode | 全局 | ~/.config/opencode/hooks/ | ~/.config/opencode/plugins/ |
| OpenCode | 项目 | .opencode/hooks/ | .opencode/plugins/ |
如果"两个都要":CC 和 OC 目录各建一份。不存在则 mkdir。
如果选了飞书:Read scripts/send-webhook.py,Write 到 hooks 目录。
"两个都要"时:部署到 CC 和 OC 两个 hooks 目录各一份。内容相同,不需区分。
如果选了邮件:同理,Read scripts/send-mail.py,Write 到 hooks 目录(两个都要时各一份)。
如果选了 OC:Read scripts/notify.ts,Write 到 plugins 目录。
自动加载,无需注册。零 npm 依赖(
node:child_process)。
按用户选择构建 JSON,Write 到 ~/.config/opencode/notify-config.json(始终全局路径,插件内部硬编码了该位置)。
{
"channel": "<feishu|mail|both>",
"python": "<Python 绝对路径>",
"hooksDir": "<hooks 目录绝对路径>",
"events": {
"sessionIdle": <true/false>,
"permissionAsked": <true/false>,
"sessionError": <true/false>
},
"feishu": { "webhookUrl": "<...>", "secret": "<...>" },
"mail": { "smtpHost": "smtp.qq.com", "smtpPort": 465, "smtpUser": "<...>", "smtpPass": "<...>", "mailFrom": "<...>", "mailTo": "<...>" }
}⚠️ 含明文密码 → 加入
.gitignore。
将当前 skill 目录(含 scripts/ + references/)复制到目标 Agent 的 skills 目录:
| Agent | 目标路径 |
|---|---|
| Claude Code | ~/.claude/skills/agent-notify/ |
| OpenCode | ~/.config/opencode/skills/agent-notify/ |
两个都要时:两边各复制一份。
这是必须步骤——不装 skill,以后用户说「飞鸽传书」无法触发重配或卸载。
settings.json(全局 ~/.claude/settings.json 或 项目 .claude/settings.json),不存在则 {"env":{}, "hooks":{}}dict.update() 合并,保留已有字段{
"env": {
"HOOK_FEISHU_WEBHOOK_URL": "<用户地址>",
"HOOK_FEISHU_SECRET": "<密钥或空>",
"HOOK_SMTP_HOST": "smtp.qq.com",
"HOOK_SMTP_PORT": "465",
"HOOK_SMTP_USER": "<邮箱>",
"HOOK_SMTP_PASS": "<授权码>",
"HOOK_MAIL_FROM": "<发件>",
"HOOK_MAIL_TO": "<收件>"
}
}{
"hooks": {
"Stop": [{
"hooks": [{
"type": "command",
"command": "D:/Python/python.exe \"C:/Users/xxx/.claude/hooks/send-webhook.py\" Stop",
"timeout": 30
}]
}],
"TaskCompleted": [{
"hooks": [{
"type": "command",
"command": "D:/Python/python.exe \"C:/Users/xxx/.claude/hooks/send-webhook.py\" TaskCompleted",
"timeout": 30
}]
}],
"PermissionRequest": [{
"hooks": [{
"type": "command",
"command": "D:/Python/python.exe \"C:/Users/xxx/.claude/hooks/send-webhook.py\" PermissionRequest",
"timeout": 30
}]
}],
"UserPromptSubmit": [{
"hooks": [{
"type": "command",
"command": "D:/Python/python.exe \"C:/Users/xxx/.claude/hooks/send-webhook.py\" UserPromptSubmit",
"timeout": 30
}]
}]
}
}双通道时:飞书和邮件各一条 hook 命令,放在同一事件的
"hooks"数组中。
indent=2, ensure_ascii=False)无需额外操作。凭据在 notify-config.json 中。
settings.local.json(同 settings.json 目录),不存在则 {"permissions":{"allow":[]}}permissions.allow,去重直接调 Python 验证(两种 Agent 的脚本独立于 Agent,无需重启即可测试)。
⚠️ 手动测试时需先设置环境变量(CC 正常运行时由 settings.json 自动传入,OC 由插件传入)。
飞书测试:
export HOOK_FEISHU_WEBHOOK_URL="<webhook地址>"
export HOOK_FEISHU_SECRET="<密钥>"
echo '{"session_id":"test","reason":"飞鸽传书安装测试","cwd":"/tmp","user_prompt":"test"}' | python "<hooks目录>/send-webhook.py" Stop邮件测试:
export HOOK_SMTP_HOST="smtp.qq.com"
export HOOK_SMTP_PORT="465"
export HOOK_SMTP_USER="<邮箱>"
export HOOK_SMTP_PASS="<授权码>"
export HOOK_MAIL_FROM="<邮箱>"
export HOOK_MAIL_TO="<邮箱>"
echo '{"session_id":"test","reason":"飞鸽传书安装测试","cwd":"/tmp","user_prompt":"test"}' | python "<hooks目录>/send-mail.py" Stop预期输出含:[OK] sent。
| 失败 | 原因 | 解决 |
|---|---|---|
code=19021 | 签名错 | 检查密钥完整复制 |
urlopen timeout | 网络不通 | 检查代理/VPN |
auth failed | 授权码错 | 确认是授权码不是密码 |
Connection refused | 端口/SSL 错 | QQ/163=465, Gmail/Outlook=587 |
code=11246 | 卡片 schema 错 | 脚本已规避(不用 collapsible/markdown 标签) |
🕊️ 飞鸽传书 v2.0 配置完成!
Agent:<Claude Code / OpenCode / 双 Agent>
通道:<飞书 / 邮箱 / 双通道>
事件:<列出>
范围:<全局 / 项目>
已部署:
📄 hooks/send-webhook.py
📄 hooks/send-mail.py
📄 plugins/notify.ts (仅 OpenCode)
📄 notify-config.json (仅 OpenCode,已入 .gitignore)
📄 settings.json (仅 Claude Code,已合并)
📄 settings.local.json (仅 Claude Code,已追加权限)
📋 Skill 自身 → <Agent>/skills/agent-notify/ (已注册)/exit 重启。/hooks 查看状态。/exit 重启。通知触发验证。日志:hooks/send-*.log · OpenCode: plugins/notify-debug.log
以下为卸载流程。仅在用户主动要求移除通知时触发。
卸载哪个 Agent 的通知?
Claude Code — 清理 ~/.claude/ 下 hook 配置和脚本
OpenCode — 清理 ~/.config/opencode/ 下插件配置和脚本
两个都卸 — 全部清理→ AskUserQuestion, header="🧹 卸载范围"
清理全局配置还是项目配置?(建议两个都查一遍)
全局 — ~/.claude/ 或 ~/.config/opencode/
项目 — .claude/ 或 .opencode/
都查 — 全局 + 项目都清理→ AskUserQuestion, header="📍 清理范围"
根据前面选择,列出即将删除/修改的文件:
将执行以下操作:
Claude Code:
📄 清理 settings.json 中的 env(HOOK_FEISHU_* / HOOK_SMTP_*)
📄 清理 settings.json 中的 hooks(send-webhook.py / send-mail.py)
📄 清理 settings.local.json 中的脚本权限
🗑️ 删除 hooks/send-webhook.py + hooks/send-mail.py
🗑️ 删除 hooks/send-*.log
🗑️ 删除 skills/agent-notify/(Skill 自身)
OpenCode:
🗑️ 删除 hooks/send-webhook.py + hooks/send-mail.py
🗑️ 删除 plugins/notify.ts
🗑️ 删除 notify-config.json
🗑️ 删除 send-*.log + notify-debug.log
🗑️ 删除 skills/agent-notify/(Skill 自身)
确认执行?→ AskUserQuestion, header="⚠️ 确认清理"
settings.json,删除 env 下以下 key:
HOOK_FEISHU_WEBHOOK_URL, HOOK_FEISHU_SECRETHOOK_SMTP_HOST, HOOK_SMTP_PORT, HOOK_SMTP_USER, HOOK_SMTP_PASSHOOK_MAIL_FROM, HOOK_MAIL_TOhooks 下所有 command 含 send-webhook.py 或 send-mail.py 的条目env 或 hooks 变为空 {},可保留或删除整个 key(不影响 Claude Code)settings.local.json,从 permissions.allow 中移除含 send-webhook.py / send-mail.py 的条目,Write 回去hooks/send-webhook.py, hooks/send-mail.py(如果存在)hooks/send-webhook.log, hooks/send-mail.log(如果存在)<CC-skills-dir>/agent-notify/ 目录(如果存在)hooks/send-webhook.py, hooks/send-mail.pyhooks/send-webhook.log, hooks/send-mail.logplugins/notify.ts, plugins/notify-debug.log~/.config/opencode/notify-config.json<OC-skills-dir>/agent-notify/ 目录(如果存在)所有删除操作前检查文件是否存在,不存在则跳过。
🧹 飞鸽传书已卸载!
已清理:
<列出实际删除的文件和清理的配置项>
- 飞鸽传书的配置和文件已全部移除
- Claude Code / OpenCode 的其他配置不受影响
- 如果还想重新安装,随时说「飞鸽传书」即可
- 🔴 必须重启 <Agent>!Hook/Plugin 在启动时加载到内存,卸载文件不会自动卸载内存副本。不重启可能看到残留日志/错误输出。部署前先读 references/oc-pitfalls.md — OpenCode 插件 6 个已知坑及规避。
CC ↔ OC 事件对照见 references/event-mapping.md。