setup-notifications-via-wecom
Set up and send technical status notifications through WeCom (Enterprise WeChat) webhooks. Use this skill whenever the user needs to send notifications, alerts, backup completion reports, or status updates via WeCom; when they mention 企业微信, 企微机器人, webhook, or alerting; or when a message needs to be clear, unambiguous, and technically precise rather than vague or condescending.
1实现原理 · 为什么它能做到
分成两层:一次性的「装好发信器」(存 webhook、绑定收件人分类、试发验证)+ 每次的「把话说清楚」(消息写作规范与模板)。发信本身是脚本 POST 到企业微信群机器人 webhook。
1. **One-time setup**: store the webhook URL, test connectivity, and create a reusable sender script. 2. **Send messages**: craft messages that distinguish state from delta, define every number, and avoid the misleading patterns that made earlier backup-sync notifications confusing.
腾讯系服务必须绕过本地代理,脚本在发起请求前主动清理六个代理环境变量——这是针对中国大陆网络环境的必要适配,不是可选优化。
def clear_proxy_env(): """Remove proxy env vars so Tencent endpoints are reached directly.""" for name in ( "http_proxy", "https_proxy", "all_proxy", "HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", ): os.environ.pop(name, None)
收件人身份被提升为强制配置项而非可推断项:config 必须有 recipient_scope(self/others)与 recipient_label,缺失即硬失败退出,不允许 agent 猜。
recipient_scope = config.get("recipient_scope") if recipient_scope not in VALID_RECIPIENT_SCOPES: print( f"Missing or invalid 'recipient_scope' in {CONFIG_PATH}; " "run scripts/set_recipient.py with self or others.", file=sys.stderr, ) sys.exit(1)
防替换链条:set_recipient.py 把发信脚本的绝对路径与 sha256 写进 config,发信前 send_wecom.py 重新计算并比对,任何一侧不匹配都拒绝发送。这是把「发信器自身被换掉」当成威胁模型来防。
sender_path = Path(sender_path_value).expanduser().resolve() running_sender = Path(__file__).resolve() if sender_path != running_sender or not sender_path.is_file(): print( f"Configured sender does not match this executable: {running_sender}", file=sys.stderr, ) sys.exit(1) if not isinstance(sender_sha256, str) or sha256_path(sender_path) != sender_sha256: print( "Configured sender digest is stale; rerun scripts/set_recipient.py.", file=sys.stderr, ) sys.exit(1)
自动发送(outbox 路径)走更严的一次性授权:必须由 runaway-guard 给出 payload 摘要,脚本核对 schema、scope、webhook 摘要、sender 指纹全部一致才发,且只尝试一次 HTTP。
"status": "pending_auto_self", "sender_skill": "notify-wecom", "recipient_scope": "self", "recipient_label": config["recipient_label"], "webhook_sha256": webhook_sha256,
配置写入是原子的:临时文件 + fsync + os.replace + 目录 fsync,且保留原文件权限位(默认 0600),避免半写坏配置或权限放宽。
mode = stat.S_IMODE(path.stat().st_mode) or 0o600 tmp = path.with_name(f".tinkle_{path.name}.{os.getpid()}.tmp") try: fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL, mode) with os.fdopen(fd, "w", encoding="utf-8") as handle: json.dump(config, handle, ensure_ascii=False, indent=2) handle.write("\n") handle.flush() os.fsync(handle.fileno()) os.chmod(tmp, mode) os.replace(tmp, path)
2核心能力
3外部依赖
| Kind | Dependency / Endpoint |
|---|---|
| api | 企业微信群机器人 webhook(腾讯官方) |
| cli | uv(以 --no-project 方式运行脚本,避免依赖项目环境) |
| cli | python3(脚本运行时;标准库 urllib 发请求) |
| cli | curl(文档提供的等价内联发送方式) |
| cli | env(清代理的 shell 内建) |
4管控建议与判定依据
- scripts/: 执行本地脚本或命令: uv run --no-project python scripts/set_recipient.py --scope self|others --l
- scripts/ 或 SKILL.md: 声明动态安装外部语言包依赖
- 网络端点: 声明外部网络访问: POST https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=<KEY>(JSON 体 {"ms
- 本地工作目录: 文件读写与工件输出: 创建 ~/.config 下的配置目录与 config.json(由用户或 agent 用 shell 完成,SKILL.md 附带 chmod 60
5第二遍独立确认
- [ok] 每条外部依赖的调用点是否真实存在 — webhook 端点(SKILL.md Quick Start 第 2 步 + 脚本 docstring + 内联 curl 三处一致)、uv --no-project(SKILL.md 命令块,脚本以 uv run 方式被调用)、python3/urllib(脚本 import 段与 urllib.request.Request/urlopen)、curl(SKILL.md 内联等价示例)、env -u / env|grep(SKILL.md 两处)。无推测项。
- [ok] 凭证/密钥处理是否有反例(例如把 webhook 打印出去、写入日志、发往第三方) — 反例检索失败:全目录 grep webhook 的每一处均不打印其值——SKILL.md 的 set_recipient 说明写 'without exposing or rewriting the webhook value';脚本只把它用于 urllib.request.Request(webhook_url, …),出错时的报错文案(load_config 的四条 sys.exit 提示)不含 URL 本身;唯一的 webhook 值落点是 config.json 与用户自行执行的 echo(SKILL.md 明示 chmod 600)。发信目标与密钥归属同一服务。第一遍结论成立。
- [ok] 「self 可自动发、others 需人工确认」是否只是文档口号(脚本是否真的挡) — 脚本层面真实生效:recipient_scope 不在 {self, others} 即 sys.exit(1);outbox 自动路径额外要求 payload.delivery 的 recipient_scope 必须为 'self'、recipient_label 与 webhook_sha256 必须与当前 config 完全一致,任一不符即拒绝('Outbox item is not bound to this explicit self target')。不是纯口号。
- [ok] 发信器指纹绑定能否被绕过(是否只在文档里说、代码里没验) — 代码里确实验:send_wecom.py 同时比对 sender_path != running_sender 与 sha256_path(sender_path) != sender_sha256,两处不符都 sys.exit(1)。相应地,config 本身被篡改时这两个字段可被一起改写——这是该机制的边界,已在 security.injection_surface 第 ③ 点写明,不算被遗漏。
- [ok] 写盘是否可能损坏配置或放宽权限 — set_recipient.py 用 O_EXCL 建临时文件、fsync 文件、os.chmod(tmp, mode) 后 os.replace,并对父目录 fsync;mode 取自原文件(stat.S_IMODE(path.stat().st_mode) or 0o600),不会放宽。tests 里 test_setter_preserves_webhook_and_mode 对 webhook 与权限有直接断言。结论成立。
- [ok] references 与 SKILL.md 的规范条数/内容是否一致 — SKILL.md 的 Message Best Practices 8 条(标题先行/状态vs增量/数字定义/地点用词/去噪音/精确不居高临下/验证指标/下一步)在 references/message_best_practices.md 中为 12 节(多出告警 vs 状态更新、告警额外规则、常见错误模板、发送前自检),属扩充而非冲突。
- [ok] 同一 pin 下本 skill 是否曾产出过旧侦查记录 — data/analysis 目录中不存在 setup-notifications-via-wecom.json 的旧记录(本次为首次产出)。同 repo 其他 slug 有旧记录的情况见各自文件。
- [ok] pin commit 与 skill.path 是否与任务书一致 — git rev-parse HEAD = d5c4678cb5d4fd6acc9c922690df035dbd33d247;目录位于仓库根,相对路径 setup-notifications-via-wecom;本目录最后提交 878f947(2026-09-07)。GitHub API 复核 MIT / stars 1392 / pushed 2026-09-15T09:30:04Z。
6结论
75c3e6008604ce81…