添加 weekly-report 技能:生成项目周报并发送邮件
- SKILL.md 定义完整执行流程(拉取代码、统计commits/PR、生成报告、发邮件) - send_email.py 从环境变量读取 SMTP 凭证,收件人从项目 report-config.json 读取 - 更新 README 技能列表 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0cce3a074f
commit
d139ff3398
@ -81,9 +81,9 @@ skills/my-skill/
|
||||
|
||||
## 技能列表
|
||||
|
||||
| 技能名 | 描述 |
|
||||
|--------|------|
|
||||
| — | — |
|
||||
| 技能名 | 调用方式 | 描述 |
|
||||
|--------|----------|------|
|
||||
| [weekly-report](./skills/weekly-report/) | `/weekly-report` | 统计过去7天的 commit 和 PR,生成协作者个人周报与项目整体报告,通过 SMTP 发送邮件 |
|
||||
|
||||
## 更新技能
|
||||
|
||||
|
||||
131
skills/weekly-report/SKILL.md
Normal file
131
skills/weekly-report/SKILL.md
Normal file
@ -0,0 +1,131 @@
|
||||
---
|
||||
name: weekly-report
|
||||
description: 生成项目过去7天的周报,统计每位协作者的 commit 和 PR,生成个人工作日志与项目整体报告,通过 SMTP 发送到配置邮箱
|
||||
argument-hint: ""
|
||||
---
|
||||
|
||||
生成当前项目过去 7 天的周报并发送邮件。按以下步骤执行,每步完成后告知用户进度。
|
||||
|
||||
## 步骤 1:读取配置
|
||||
|
||||
读取当前项目根目录的 `report-config.json`,格式如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"recipients": ["boss@company.com", "team@company.com"],
|
||||
"project_name": "项目名称"
|
||||
}
|
||||
```
|
||||
|
||||
如果文件不存在,**停止执行**,提示用户在项目根目录创建该文件后重试。
|
||||
|
||||
## 步骤 2:计算日期范围
|
||||
|
||||
用 Python 计算日期,避免平台差异:
|
||||
|
||||
```bash
|
||||
python -c "from datetime import date, timedelta; d=date.today()-timedelta(days=7); print(d)"
|
||||
```
|
||||
|
||||
## 步骤 3:拉取最新代码
|
||||
|
||||
```bash
|
||||
git pull origin main
|
||||
```
|
||||
|
||||
如果当前不在 git 仓库内,停止并提示用户。
|
||||
|
||||
## 步骤 4:获取过去 7 天的提交记录
|
||||
|
||||
```bash
|
||||
git log origin/main --since="7 days ago" --format="%an|||%ae|||%ad|||%s" --date=short --no-merges
|
||||
```
|
||||
|
||||
将输出按 `|||` 分割,整理为结构化数据(作者姓名、邮箱、日期、提交说明)。
|
||||
|
||||
## 步骤 5:获取过去 7 天合并的 PR
|
||||
|
||||
```bash
|
||||
gh pr list --state merged --base main --limit 100 --json number,title,author,mergedAt,additions,deletions
|
||||
```
|
||||
|
||||
过滤 `mergedAt` 在过去 7 天内的 PR,按作者分组。
|
||||
|
||||
如果 `gh` 命令不可用,跳过此步并在报告中注明。
|
||||
|
||||
## 步骤 6:生成报告内容
|
||||
|
||||
### 个人周报(每位协作者一份)
|
||||
|
||||
格式要求:
|
||||
- 姓名与提交统计(commit 数、PR 数、增删行数)
|
||||
- 合并 PR 列表(编号、标题)
|
||||
- 关键 commit 摘要(按日期列出,去除 merge commit)
|
||||
- 工作总结(2-3 句话,用中文概括本周主要工作)
|
||||
|
||||
### 项目整体进度报告
|
||||
|
||||
格式要求:
|
||||
- 统计摘要:参与人数、总 commit 数、合并 PR 总数
|
||||
- 主要完成内容(按功能或模块归类,中文)
|
||||
- 整体进度评估(根据提交内容客观描述)
|
||||
- 如有未合并的 open PR,列出数量作为下周关注点
|
||||
|
||||
### 拼装 HTML 报告
|
||||
|
||||
将个人周报和整体报告拼装成一份 HTML 邮件,写入临时文件:
|
||||
|
||||
```bash
|
||||
python -c "
|
||||
from datetime import date
|
||||
filename = f'/tmp/weekly_report_{date.today().strftime(\"%Y%m%d\")}.html'
|
||||
print(filename)
|
||||
"
|
||||
```
|
||||
|
||||
HTML 结构建议:
|
||||
1. 标题:`【周报】<project_name> - <日期范围>`
|
||||
2. 项目整体报告(放在最前)
|
||||
3. 分割线
|
||||
4. 各协作者个人报告(每人一节)
|
||||
|
||||
## 步骤 7:检查 SMTP 环境变量
|
||||
|
||||
在发送前,检查以下环境变量是否全部存在:
|
||||
|
||||
```bash
|
||||
python -c "
|
||||
import os, sys
|
||||
required = ['SMTP_HOST', 'SMTP_PORT', 'SMTP_USER', 'SMTP_PASSWORD']
|
||||
missing = [k for k in required if not os.environ.get(k)]
|
||||
if missing:
|
||||
print('缺少环境变量:' + ', '.join(missing))
|
||||
sys.exit(1)
|
||||
else:
|
||||
print('环境变量检查通过')
|
||||
"
|
||||
```
|
||||
|
||||
如果有缺失,**停止执行**,提示用户配置对应环境变量:
|
||||
|
||||
| 变量 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `SMTP_HOST` | SMTP 服务器地址 | `smtp.qq.com` |
|
||||
| `SMTP_PORT` | 端口(SSL用465,STARTTLS用587) | `465` |
|
||||
| `SMTP_USER` | 发件人邮箱 | `noreply@company.com` |
|
||||
| `SMTP_PASSWORD` | SMTP 密码或授权码 | `your-auth-code` |
|
||||
|
||||
**绝对不要将这些值硬编码在任何文件中。**
|
||||
|
||||
## 步骤 8:发送邮件
|
||||
|
||||
使用 Glob 工具找到与本 SKILL.md 同目录的 `send_email.py` 的绝对路径,然后执行:
|
||||
|
||||
```bash
|
||||
python <send_email.py的绝对路径> \
|
||||
--config report-config.json \
|
||||
--subject "【周报】<project_name> <YYYY-MM-DD>" \
|
||||
--body-file <步骤6生成的HTML文件路径>
|
||||
```
|
||||
|
||||
发送成功后,告知用户已发送至哪些邮箱,并清理临时文件。
|
||||
121
skills/weekly-report/send_email.py
Normal file
121
skills/weekly-report/send_email.py
Normal file
@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
周报邮件发送脚本
|
||||
SMTP 凭证从环境变量读取,不接受命令行参数传入凭证。
|
||||
|
||||
必须设置的环境变量:
|
||||
SMTP_HOST - SMTP 服务器地址
|
||||
SMTP_PORT - SMTP 端口(SSL用465,STARTTLS用587)
|
||||
SMTP_USER - 发件人邮箱
|
||||
SMTP_PASSWORD - SMTP 密码或授权码
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import smtplib
|
||||
import sys
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
|
||||
def load_config(config_path: str) -> dict:
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"错误:找不到配置文件 {config_path}", file=sys.stderr)
|
||||
print("请在项目根目录创建 report-config.json,格式:", file=sys.stderr)
|
||||
print('{"recipients": ["email@example.com"], "project_name": "项目名"}', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"错误:report-config.json 格式错误 - {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_smtp_config() -> tuple:
|
||||
host = os.environ.get("SMTP_HOST")
|
||||
port = os.environ.get("SMTP_PORT", "465")
|
||||
user = os.environ.get("SMTP_USER")
|
||||
password = os.environ.get("SMTP_PASSWORD")
|
||||
|
||||
missing = [k for k, v in {
|
||||
"SMTP_HOST": host,
|
||||
"SMTP_USER": user,
|
||||
"SMTP_PASSWORD": password,
|
||||
}.items() if not v]
|
||||
|
||||
if missing:
|
||||
print(f"错误:缺少以下环境变量:{', '.join(missing)}", file=sys.stderr)
|
||||
print("请在 shell 中设置(不要写入任何文件):", file=sys.stderr)
|
||||
for k in missing:
|
||||
print(f" export {k}=your_value", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
port = int(port)
|
||||
except ValueError:
|
||||
print(f"错误:SMTP_PORT 必须是数字,当前值为 '{port}'", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
return host, port, user, password
|
||||
|
||||
|
||||
def send_email(recipients: list, subject: str, body_html: str, sender: str):
|
||||
host, port, user, password = get_smtp_config()
|
||||
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = sender or user
|
||||
msg["To"] = ", ".join(recipients)
|
||||
msg.attach(MIMEText(body_html, "html", "utf-8"))
|
||||
|
||||
try:
|
||||
if port == 465:
|
||||
with smtplib.SMTP_SSL(host, port, timeout=30) as smtp:
|
||||
smtp.login(user, password)
|
||||
smtp.sendmail(user, recipients, msg.as_string())
|
||||
else:
|
||||
with smtplib.SMTP(host, port, timeout=30) as smtp:
|
||||
smtp.ehlo()
|
||||
smtp.starttls()
|
||||
smtp.login(user, password)
|
||||
smtp.sendmail(user, recipients, msg.as_string())
|
||||
except smtplib.SMTPAuthenticationError:
|
||||
print("错误:SMTP 认证失败,请检查 SMTP_USER 和 SMTP_PASSWORD", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except smtplib.SMTPConnectError:
|
||||
print(f"错误:无法连接到 {host}:{port},请检查 SMTP_HOST 和 SMTP_PORT", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"错误:发送失败 - {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(f"邮件已发送至:{', '.join(recipients)}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="发送周报邮件")
|
||||
parser.add_argument("--config", required=True, help="report-config.json 路径")
|
||||
parser.add_argument("--subject", required=True, help="邮件主题")
|
||||
parser.add_argument("--body-file", required=True, help="HTML 正文文件路径")
|
||||
args = parser.parse_args()
|
||||
|
||||
config = load_config(args.config)
|
||||
recipients = config.get("recipients", [])
|
||||
if not recipients:
|
||||
print("错误:report-config.json 中 recipients 为空", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
with open(args.body_file, "r", encoding="utf-8") as f:
|
||||
body_html = f.read()
|
||||
except FileNotFoundError:
|
||||
print(f"错误:找不到邮件正文文件 {args.body_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
send_email(recipients, args.subject, body_html, sender=config.get("sender", ""))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
x
Reference in New Issue
Block a user