From da3848c5ded5c7813b33c321f640b606d846a781 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 29 Jan 2026 10:45:12 +0800 Subject: [PATCH] feat: add Higress community governance daily report skill for Claude (#3404) --- .claude/skills/higress-daily-report/README.md | 198 +++++++++++++ .claude/skills/higress-daily-report/SKILL.md | 257 +++++++++++++++++ .../scripts/generate-report.sh | 273 ++++++++++++++++++ 3 files changed, 728 insertions(+) create mode 100644 .claude/skills/higress-daily-report/README.md create mode 100644 .claude/skills/higress-daily-report/SKILL.md create mode 100755 .claude/skills/higress-daily-report/scripts/generate-report.sh diff --git a/.claude/skills/higress-daily-report/README.md b/.claude/skills/higress-daily-report/README.md new file mode 100644 index 000000000..f9caa193c --- /dev/null +++ b/.claude/skills/higress-daily-report/README.md @@ -0,0 +1,198 @@ +# Higress 社区治理日报 - Claude Skill + +这个 skill 让 AI 助手(如 Claude)能够自动追踪 Higress 项目的 GitHub 活动,并生成结构化的每日社区治理报告。 + +## 架构概览 + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Clawdbot │────▶│ Claude + Skill │────▶│ GitHub API │ +│ (Gateway) │ │ │ │ (gh CLI) │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ + │ │ + │ ▼ + │ ┌─────────────────┐ + │ │ 数据文件 │ + │ │ - tracking.json│ + │ │ - knowledge.md │ + │ └─────────────────┘ + │ │ + ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ +│ Discord/Slack │◀────│ 日报输出 │ +│ Channel │ │ │ +└─────────────────┘ └─────────────────┘ +``` + +## 什么是 Clawdbot? + +[Clawdbot](https://github.com/clawdbot/clawdbot) 是一个 AI Agent 网关,可以将 Claude 等 AI 模型连接到各种消息平台(Discord、Slack、Telegram 等)和工具(GitHub CLI、浏览器、文件系统等)。 + +通过 Clawdbot,Claude 可以: +- 接收来自 Discord 等平台的消息 +- 执行 shell 命令(如 `gh` CLI) +- 读写文件 +- 定时执行任务(cron) +- 将生成的内容发送回消息平台 + +## 工作流程 + +### 1. 定时触发 + +通过 Clawdbot 的 cron 功能,每天定时触发日报生成: + +``` +# Clawdbot 配置示例 +cron: + - schedule: "0 9 * * *" # 每天早上 9 点 + task: "生成 Higress 昨日日报并发送到 #issue-pr-notify 频道" +``` + +### 2. Skill 加载 + +当 Claude 收到生成日报的指令时,会自动加载此 skill(SKILL.md),获取: +- 数据获取方法(gh CLI 命令) +- 数据结构定义 +- 日报格式模板 +- 知识库维护规则 + +### 3. 数据获取 + +Claude 使用 GitHub CLI 获取数据: + +```bash +# 获取昨日新建的 issues +gh search issues --repo alibaba/higress --created yesterday --json number,title,author,url,body,state,labels + +# 获取昨日新建的 PRs +gh search prs --repo alibaba/higress --created yesterday --json number,title,author,url,body,state + +# 获取特定 issue 的评论 +gh api repos/alibaba/higress/issues/{number}/comments +``` + +### 4. 状态追踪 + +Claude 维护一个 JSON 文件追踪每个 issue 的状态: + +```json +{ + "issues": [ + { + "number": 3398, + "title": "浏览器发起的options请求报401", + "lastCommentCount": 13, + "status": "waiting_for_user", + "waitingFor": "用户验证解决方案" + } + ] +} +``` + +### 5. 知识沉淀 + +当 issue 被解决时,Claude 会将问题模式和解决方案记录到知识库: + +```markdown +## KB-001: OPTIONS 预检请求被认证拦截 + +**问题**: 浏览器 OPTIONS 请求返回 401 +**根因**: key-auth 在 AUTHN 阶段执行,先于 CORS +**解决方案**: 为 OPTIONS 请求创建单独路由,不启用认证插件 +**关联 Issue**: #3398 +``` + +### 6. 日报生成 + +最终生成结构化日报,包含: +- 📋 概览统计 +- 📌 新增 Issues +- 🔀 新增 PRs +- 🔔 Issue 动态(新评论、已解决) +- ⏰ 跟进提醒 +- 📚 知识沉淀 + +### 7. 消息推送 + +Claude 通过 Clawdbot 将日报发送到指定的 Discord 频道。 + +## 快速开始 + +### 前置要求 + +1. 安装并配置 [Clawdbot](https://github.com/clawdbot/clawdbot) +2. 配置 GitHub CLI (`gh`) 并登录 +3. 配置消息平台(如 Discord) + +### 配置 Skill + +将此 skill 目录复制到 Clawdbot 的 skills 目录: + +```bash +cp -r .claude/skills/higress-daily-report ~/.clawdbot/skills/ +``` + +### 使用方式 + +**手动触发:** +``` +@Claude 生成 Higress 昨日日报 +``` + +**定时触发(推荐):** +在 Clawdbot 配置中添加 cron 任务,每天自动生成并推送日报。 + +## 文件说明 + +``` +higress-daily-report/ +├── README.md # 本文件 +├── SKILL.md # Skill 定义(Claude 读取) +└── scripts/ + └── generate-report.sh # 辅助脚本(可选) +``` + +## 自定义 + +### 修改日报格式 + +编辑 `SKILL.md` 中的「日报格式」章节。 + +### 添加新的追踪维度 + +在 `SKILL.md` 的数据结构中添加新字段。 + +### 调整知识库规则 + +修改 `SKILL.md` 中的「知识沉淀」章节。 + +## 示例日报 + +```markdown +📊 Higress 项目每日报告 - 2026-01-29 + +📋 概览 +• 新增 Issues: 2 个 +• 新增 PRs: 3 个 +• 待跟进: 1 个 + +📌 新增 Issues +• #3399: 网关启动失败问题 + - 作者: user123 + - 标签: bug + +🔔 Issue 动态 +✅ 已解决 +• #3398: OPTIONS 请求 401 问题 + - 知识库: KB-001 + +⏰ 跟进提醒 +🟡 等待反馈 +• #3396: 等待用户提供配置信息(2天) +``` + +## 相关链接 + +- [Clawdbot 文档](https://docs.clawd.bot) +- [Higress 项目](https://github.com/alibaba/higress) +- [GitHub CLI 文档](https://cli.github.com/manual/) diff --git a/.claude/skills/higress-daily-report/SKILL.md b/.claude/skills/higress-daily-report/SKILL.md new file mode 100644 index 000000000..9b12c41cc --- /dev/null +++ b/.claude/skills/higress-daily-report/SKILL.md @@ -0,0 +1,257 @@ +--- +name: higress-daily-report +description: 生成 Higress 项目每日报告,追踪 issue/PR 动态,沉淀问题处理经验,驱动社区问题闭环。用于生成日报、跟进 issue、记录解决方案。 +--- + +# Higress Daily Report + +驱动 Higress 社区问题处理的智能工作流。 + +## 核心目标 + +1. **每日感知** - 追踪新 issues/PRs 和评论动态 +2. **进度跟踪** - 确保每个 issue 被持续跟进直到关闭 +3. **知识沉淀** - 积累问题分析和解决方案,提升处理能力 +4. **闭环驱动** - 通过日报推动问题解决,避免遗忘 + +## 数据文件 + +| 文件 | 用途 | +|------|------| +| `/root/clawd/memory/higress-issue-tracking.json` | Issue 追踪状态(评论数、跟进状态) | +| `/root/clawd/memory/higress-knowledge-base.md` | 知识库:问题模式、解决方案、经验教训 | +| `/root/clawd/reports/report_YYYY-MM-DD.md` | 每日报告存档 | + +## 工作流程 + +### 1. 获取每日数据 + +```bash +# 获取昨日 issues +gh search issues --repo alibaba/higress --created yesterday --json number,title,author,url,body,state,labels --limit 50 + +# 获取昨日 PRs +gh search prs --repo alibaba/higress --created yesterday --json number,title,author,url,body,state,additions,deletions,reviewDecision --limit 50 +``` + +### 2. Issue 追踪状态管理 + +**追踪数据结构** (`higress-issue-tracking.json`): + +```json +{ + "date": "2026-01-28", + "issues": [ + { + "number": 3398, + "title": "Issue 标题", + "state": "open", + "author": "username", + "url": "https://github.com/...", + "created_at": "2026-01-27", + "comment_count": 11, + "last_comment_by": "johnlanni", + "last_comment_at": "2026-01-28", + "follow_up_status": "waiting_user", + "follow_up_note": "等待用户提供请求日志", + "priority": "high", + "category": "cors", + "solution_ref": "KB-001" + } + ] +} +``` + +**跟进状态枚举**: +- `new` - 新 issue,待分析 +- `analyzing` - 正在分析中 +- `waiting_user` - 等待用户反馈 +- `waiting_review` - 等待 PR review +- `in_progress` - 修复进行中 +- `resolved` - 已解决(待关闭) +- `closed` - 已关闭 +- `wontfix` - 不予修复 +- `stale` - 超过 7 天无活动 + +### 3. 知识库结构 + +**知识库** (`higress-knowledge-base.md`) 用于沉淀经验: + +```markdown +# Higress 问题知识库 + +## 问题模式索引 + +### 认证与跨域类 +- KB-001: OPTIONS 预检请求被认证拦截 +- KB-002: CORS 配置不生效 + +### 路由配置类 +- KB-010: 路由状态 address 为空 +- KB-011: 服务发现失败 + +### 部署运维类 +- KB-020: Helm 安装问题 +- KB-021: 升级兼容性问题 + +--- + +## KB-001: OPTIONS 预检请求被认证拦截 + +**问题特征**: +- 浏览器 OPTIONS 请求返回 401 +- 已配置 CORS 和认证插件 + +**根因分析**: +Higress 插件执行阶段优先级:AUTHN (310) > AUTHZ (340) > STATS +- key-auth 在 AUTHN 阶段执行 +- CORS 在 AUTHZ 阶段执行 +- OPTIONS 请求先被 key-auth 拦截,CORS 无机会处理 + +**解决方案**: +1. **推荐**:修改 CORS 插件 stage 从 AUTHZ 改为 AUTHN +2. **Workaround**:创建 OPTIONS 专用路由,不启用认证 +3. **Workaround**:使用实例级 CORS 配置 + +**关联 Issue**:#3398 + +**学到的经验**: +- 排查跨域问题时,首先确认插件执行顺序 +- Higress 阶段优先级由 phase 决定,不是 priority 数值 +``` + +### 4. 日报生成规则 + +**报告结构**: + +```markdown +# 📊 Higress 项目每日报告 - YYYY-MM-DD + +## 📋 概览 +- 统计时间: YYYY-MM-DD +- 新增 Issues: X 个 +- 新增 PRs: X 个 +- 待跟进 Issues: X 个 +- 本周关闭: X 个 + +## 📌 新增 Issues +(按优先级排序,包含分类标签) + +## 🔀 新增 PRs +(包含代码变更量和 review 状态) + +## 🔔 Issue 动态 +(有新评论的 issues,标注最新进展) + +## ⏰ 跟进提醒 + +### 🔴 需要立即处理 +(等待我方回复超过 24h 的 issues) + +### 🟡 等待用户反馈 +(等待用户回复的 issues,标注等待天数) + +### 🟢 进行中 +(正在处理的 issues) + +### ⚪ 已过期 +(超过 7 天无活动的 issues,需决定是否关闭) + +## 📚 本周知识沉淀 +(新增的知识库条目摘要) +``` + +### 5. 智能分析能力 + +生成日报时,对每个新 issue 进行初步分析: + +1. **问题分类** - 根据标题和内容判断类别 +2. **知识库匹配** - 检索相似问题的解决方案 +3. **优先级评估** - 根据影响范围和紧急程度 +4. **建议回复** - 基于知识库生成初步回复建议 + +### 6. Issue 跟进触发 + +当用户在 Discord 中提到以下关键词时触发跟进记录: + +**完成跟进**: +- "已跟进 #xxx" +- "已回复 #xxx" +- "issue #xxx 已处理" + +**记录解决方案**: +- "issue #xxx 的问题是..." +- "#xxx 根因是..." +- "#xxx 解决方案..." + +触发后更新追踪状态和知识库。 + +## 执行检查清单 + +每次生成日报时: + +- [ ] 获取昨日新 issues 和 PRs +- [ ] 加载追踪数据,检查评论变化 +- [ ] 对比 `last_comment_by` 判断是等待用户还是等待我方 +- [ ] 超过 7 天无活动的 issue 标记为 stale +- [ ] 检索知识库,为新 issue 匹配相似问题 +- [ ] 生成报告并保存到 `/root/clawd/reports/` +- [ ] 更新追踪数据 +- [ ] 发送到 Discord channel:1465549185632702591 +- [ ] 格式:使用列表而非表格(Discord 不支持 Markdown 表格) + +## 知识库维护 + +### 新增条目时机 + +1. Issue 被成功解决后 +2. 发现新的问题模式 +3. 踩坑后的经验总结 + +### 条目模板 + +```markdown +## KB-XXX: 问题简述 + +**问题特征**: +- 症状1 +- 症状2 + +**根因分析**: +(技术原因说明) + +**解决方案**: +1. 推荐方案 +2. 备选方案 + +**关联 Issue**:#xxx + +**学到的经验**: +- 经验1 +- 经验2 +``` + +## 命令参考 + +```bash +# 查看 issue 详情和评论 +gh issue view --repo alibaba/higress --json number,title,state,comments,author,createdAt,labels,url + +# 查看 issue 评论 +gh issue view --repo alibaba/higress --comments + +# 发送 issue 评论 +gh issue comment --repo alibaba/higress --body "评论内容" + +# 关闭 issue +gh issue close --repo alibaba/higress --reason completed + +# 添加标签 +gh issue edit --repo alibaba/higress --add-label "bug" +``` + +## Discord 输出 + +- 频道: `channel:1465549185632702591` +- 格式: 纯文本 + emoji + 链接(用 `` 抑制预览) +- 长度: 单条消息不超过 2000 字符,超过则分多条发送 diff --git a/.claude/skills/higress-daily-report/scripts/generate-report.sh b/.claude/skills/higress-daily-report/scripts/generate-report.sh new file mode 100755 index 000000000..10649cb85 --- /dev/null +++ b/.claude/skills/higress-daily-report/scripts/generate-report.sh @@ -0,0 +1,273 @@ +#!/bin/bash +# Higress Daily Report Generator +# Generates daily report for alibaba/higress repository + +# set -e # 临时禁用以调试 + +REPO="alibaba/higress" +CHANNEL="1465549185632702591" +DATE=$(date +"%Y-%m-%d") +REPORT_DIR="/root/clawd/reports" +TRACKING_DIR="/root/clawd/memory" +RECORD_FILE="${TRACKING_DIR}/higress-issue-process-record.md" + +mkdir -p "$REPORT_DIR" "$TRACKING_DIR" + +echo "=== Higress Daily Report - $DATE ===" + +# Get yesterday's date +YESTERDAY=$(date -d "yesterday" +"%Y-%m-%d" 2>/dev/null || date -v-1d +"%Y-%m-%d") + +echo "Fetching issues created on $YESTERDAY..." + +# Fetch issues created yesterday +ISSUES=$(gh search issues --repo "${REPO}" --state open --created "${YESTERDAY}..${YESTERDAY}" --json number,title,labels,author,url,body,state --limit 50 2>/dev/null) + +if [ -z "$ISSUES" ]; then + ISSUES_COUNT=0 +else + ISSUES_COUNT=$(echo "$ISSUES" | jq 'length' 2>/dev/null || echo "0") +fi + +# Fetch PRs created yesterday +PRS=$(gh search prs --repo "${REPO}" --state open --created "${YESTERDAY}..${YESTERDAY}" --json number,title,labels,author,url,reviewDecision,additions,deletions,body,state --limit 50 2>/dev/null) + +if [ -z "$PRS" ]; then + PRS_COUNT=0 +else + PRS_COUNT=$(echo "$PRS" | jq 'length' 2>/dev/null || echo "0") +fi + +echo "Found: $ISSUES_COUNT issues, $PRS_COUNT PRs" + +# Build report +REPORT="📊 **Higress 项目每日报告 - ${DATE}** + +**📋 概览** +- 统计时间: ${YESTERDAY} 全天 +- 新增 Issues: **${ISSUES_COUNT}** 个 +- 新增 PRs: **${PRS_COUNT}** 个 + +--- + +" + +# Process issues +if [ "$ISSUES_COUNT" -gt 0 ]; then + REPORT="${REPORT}**📌 Issues 详情** + +" + + # Use a temporary file to avoid subshell variable scoping issues + ISSUE_DETAILS=$(mktemp) + + echo "$ISSUES" | jq -r '.[] | @json' | while IFS= read -r ISSUE; do + NUM=$(echo "$ISSUE" | jq -r '.number') + TITLE=$(echo "$ISSUE" | jq -r '.title') + URL=$(echo "$ISSUE" | jq -r '.url') + AUTHOR=$(echo "$ISSUE" | jq -r '.author.login') + BODY=$(echo "$ISSUE" | jq -r '.body // ""') + LABELS=$(echo "$ISSUE" | jq -r '.labels[]?.name // ""' | head -1) + + # Determine emoji + EMOJI="📝" + echo "$LABELS" | grep -q "priority/high" && EMOJI="🔴" + echo "$LABELS" | grep -q "type/bug" && EMOJI="🐛" + echo "$LABELS" | grep -q "type/enhancement" && EMOJI="✨" + + # Extract content + CONTENT=$(echo "$BODY" | head -n 8 | sed 's/```.*```//g' | sed 's/`//g' | tr '\n' ' ' | head -c 300) + + if [ -z "$CONTENT" ]; then + CONTENT="无详细描述" + fi + + if [ ${#CONTENT} -eq 300 ]; then + CONTENT="${CONTENT}..." + fi + + # Append to temporary file + echo "${EMOJI} **[#${NUM}](${URL})**: ${TITLE} + 👤 @${AUTHOR} + 📝 ${CONTENT} +" >> "$ISSUE_DETAILS" + done + + # Read from temp file and append to REPORT + REPORT="${REPORT}$(cat $ISSUE_DETAILS)" + + rm -f "$ISSUE_DETAILS" +fi + +REPORT="${REPORT} +--- + +" + +# Process PRs +if [ "$PRS_COUNT" -gt 0 ]; then + REPORT="${REPORT}**🔀 PRs 详情** + +" + + # Use a temporary file to avoid subshell variable scoping issues + PR_DETAILS=$(mktemp) + + echo "$PRS" | jq -r '.[] | @json' | while IFS= read -r PR; do + NUM=$(echo "$PR" | jq -r '.number') + TITLE=$(echo "$PR" | jq -r '.title') + URL=$(echo "$PR" | jq -r '.url') + AUTHOR=$(echo "$PR" | jq -r '.author.login') + ADDITIONS=$(echo "$PR" | jq -r '.additions') + DELETIONS=$(echo "$PR" | jq -r '.deletions') + REVIEW=$(echo "$PR" | jq -r '.reviewDecision // "pending"') + BODY=$(echo "$PR" | jq -r '.body // ""') + + # Determine status + STATUS="👀" + [ "$REVIEW" = "APPROVED" ] && STATUS="✅" + [ "$REVIEW" = "CHANGES_REQUESTED" ] && STATUS="🔄" + + # Calculate size + TOTAL=$((ADDITIONS + DELETIONS)) + SIZE="M" + [ $TOTAL -lt 100 ] && SIZE="XS" + [ $TOTAL -lt 500 ] && SIZE="S" + [ $TOTAL -lt 1000 ] && SIZE="M" + [ $TOTAL -lt 5000 ] && SIZE="L" + [ $TOTAL -ge 5000 ] && SIZE="XL" + + # Extract content + CONTENT=$(echo "$BODY" | head -n 8 | sed 's/```.*```//g' | sed 's/`//g' | tr '\n' ' ' | head -c 300) + + if [ -z "$CONTENT" ]; then + CONTENT="无详细描述" + fi + + if [ ${#CONTENT} -eq 300 ]; then + CONTENT="${CONTENT}..." + fi + + # Append to temporary file + echo "${STATUS} **[#${NUM}](${URL})**: ${TITLE} ${SIZE} + 👤 @${AUTHOR} | ${STATUS} | 变更: +${ADDITIONS}/-${DELETIONS} + 📝 ${CONTENT} +" >> "$PR_DETAILS" + done + + # Read from temp file and append to REPORT + REPORT="${REPORT}$(cat $PR_DETAILS)" + + rm -f "$PR_DETAILS" +fi + +# Check for new comments on tracked issues +TRACKING_FILE="${TRACKING_DIR}/higress-issue-tracking.json" + +echo "" +echo "Checking for new comments on tracked issues..." + +# Load previous tracking data +if [ -f "$TRACKING_FILE" ]; then + PREV_TRACKING=$(cat "$TRACKING_FILE") + PREV_ISSUES=$(echo "$PREV_TRACKING" | jq -r '.issues[]?.number // empty' 2>/dev/null) + + if [ -n "$PREV_ISSUES" ]; then + REPORT="${REPORT}**🔔 Issue跟进(新评论)**" + + HAS_NEW_COMMENTS=false + + for issue_num in $PREV_ISSUES; do + # Get current comment count + CURRENT_INFO=$(gh issue view "$issue_num" --repo "$REPO" --json number,title,state,comments,url 2>/dev/null) + if [ -n "$CURRENT_INFO" ]; then + CURRENT_COUNT=$(echo "$CURRENT_INFO" | jq '.comments | length') + CURRENT_TITLE=$(echo "$CURRENT_INFO" | jq -r '.title') + CURRENT_STATE=$(echo "$CURRENT_INFO" | jq -r '.state') + ISSUE_URL=$(echo "$CURRENT_INFO" | jq -r '.url') + PREV_COUNT=$(echo "$PREV_TRACKING" | jq -r ".issues[] | select(.number == $issue_num) | .comment_count // 0") + + if [ -z "$PREV_COUNT" ]; then + PREV_COUNT=0 + fi + + NEW_COMMENTS=$((CURRENT_COUNT - PREV_COUNT)) + + if [ "$NEW_COMMENTS" -gt 0 ]; then + HAS_NEW_COMMENTS=true + REPORT="${REPORT} + +• [#${issue_num}](${ISSUE_URL}) ${CURRENT_TITLE} + 📬 +${NEW_COMMENTS}条新评论(总计: ${CURRENT_COUNT}) | 状态: ${CURRENT_STATE}" + fi + fi + done + + if [ "$HAS_NEW_COMMENTS" = false ]; then + REPORT="${REPORT} + +• 暂无新评论" + fi + + REPORT="${REPORT} + +--- +" + fi +fi + +# Save current tracking data for tomorrow +echo "Saving issue tracking data for follow-up..." + +if [ -z "$ISSUES" ]; then + TRACKING_DATA='{"date":"'"$DATE"'","issues":[]}' +else + TRACKING_DATA=$(echo "$ISSUES" | jq '{ + date: "'"$DATE"'", + issues: [.[] | { + number: .number, + title: .title, + state: .state, + comment_count: 0, + url: .url + }] +}') +fi + +echo "$TRACKING_DATA" > "$TRACKING_FILE" +echo "Tracking data saved to $TRACKING_FILE" + +# Save report to file +REPORT_FILE="${REPORT_DIR}/report_${DATE}.md" +echo "$REPORT" > "$REPORT_FILE" +echo "Report saved to $REPORT_FILE" + +# Follow-up reminder +FOLLOWUP_ISSUES=$(echo "$PREV_TRACKING" | jq -r '[.issues[] | select(.comment_count > 0 or .state == "open")] | "#\(.number) [\(.title)]"' 2>/dev/null || echo "") + +if [ -n "$FOLLOWUP_ISSUES" ]; then + REPORT="${REPORT} + +**📌 需要跟进的Issues** + +以下Issues需要跟进处理: +${FOLLOWUP_ISSUES} + +--- + +" +fi + +# Footer +REPORT="${REPORT} +--- +📅 生成时间: $(date +"%Y-%m-%d %H:%M:%S %Z") +🔗 项目: https://github.com/${REPO} +🤖 本报告由 AI 辅助生成,所有链接均可点击跳转 +" + +# Send report +echo "Sending report to Discord..." +echo "$REPORT" | /root/.nvm/versions/node/v24.13.0/bin/clawdbot message send --channel discord -t "$CHANNEL" -m "$(cat -)" + +echo "Done!"