272 lines
8.6 KiB
Bash
272 lines
8.6 KiB
Bash
#!/usr/bin/env bash
|
|
# ================================================================
|
|
# Skill Manager — Agent Skills 管理工具
|
|
# 用法: bash scripts/skill-manager.sh <command> [args]
|
|
# ================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
SKILLS_DIR=".cursor/skills"
|
|
AGENTS_DIR=".cursor/agents"
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
RED='\033[0;31m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m'
|
|
|
|
# ================================================================
|
|
# Commands
|
|
# ================================================================
|
|
|
|
cmd_list() {
|
|
echo -e "${BLUE}📦 Agent Skills${NC}"
|
|
echo "─────────────────────────────────────────────"
|
|
|
|
if [ ! -d "$SKILLS_DIR" ]; then
|
|
echo -e "${RED} ✗ Skills 目录不存在${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local count=0
|
|
for skill_dir in "$SKILLS_DIR"/*/; do
|
|
[ -d "$skill_dir" ] || continue
|
|
local skill_file="$skill_dir/SKILL.md"
|
|
if [ -f "$skill_file" ]; then
|
|
local name=$(grep -m1 '^name:' "$skill_file" | sed 's/name:\s*//')
|
|
local desc=$(grep -A2 '^description:' "$skill_file" | tail -1 | sed 's/^\s*//')
|
|
local lines=$(wc -l < "$skill_file")
|
|
local has_refs=$( [ -d "${skill_dir}references" ] && echo "📚" || echo " " )
|
|
local has_scripts=$( [ -d "${skill_dir}scripts" ] && echo "⚡" || echo " " )
|
|
|
|
printf " ${GREEN}%-22s${NC} ${has_refs}${has_scripts} (%3d lines) %s\n" \
|
|
"$name" "$lines" "$desc"
|
|
count=$((count + 1))
|
|
fi
|
|
done
|
|
|
|
echo "─────────────────────────────────────────────"
|
|
echo -e " 共 ${GREEN}${count}${NC} 个技能"
|
|
|
|
echo ""
|
|
echo -e "${BLUE}🤖 Subagents${NC}"
|
|
echo "─────────────────────────────────────────────"
|
|
|
|
if [ -d "$AGENTS_DIR" ]; then
|
|
for agent_file in "$AGENTS_DIR"/*.md; do
|
|
[ -f "$agent_file" ] || continue
|
|
local name=$(grep -m1 '^name:' "$agent_file" | sed 's/name:\s*//')
|
|
local desc=$(grep -A1 '^description:' "$agent_file" | tail -1 | sed 's/^\s*//')
|
|
local readonly_flag=$(grep -m1 '^readonly:' "$agent_file" | sed 's/readonly:\s*//')
|
|
local mode=$( [ "$readonly_flag" = "true" ] && echo "🔒只读" || echo "✏️读写" )
|
|
printf " ${GREEN}%-22s${NC} ${mode} %s\n" "$name" "$desc"
|
|
done
|
|
else
|
|
echo -e " ${YELLOW}⚠ Agents 目录不存在${NC}"
|
|
fi
|
|
}
|
|
|
|
cmd_validate() {
|
|
echo -e "${BLUE}🔍 验证 Agent Skills 规范合规性${NC}"
|
|
echo "─────────────────────────────────────────────"
|
|
|
|
local errors=0
|
|
local warnings=0
|
|
|
|
for skill_dir in "$SKILLS_DIR"/*/; do
|
|
[ -d "$skill_dir" ] || continue
|
|
local skill_name=$(basename "$skill_dir")
|
|
local skill_file="$skill_dir/SKILL.md"
|
|
|
|
# Check SKILL.md exists
|
|
if [ ! -f "$skill_file" ]; then
|
|
echo -e " ${RED}✗${NC} ${skill_name}: 缺少 SKILL.md"
|
|
errors=$((errors + 1))
|
|
continue
|
|
fi
|
|
|
|
# Check name field
|
|
if ! grep -q '^name:' "$skill_file"; then
|
|
echo -e " ${RED}✗${NC} ${skill_name}: 缺少 name 字段"
|
|
errors=$((errors + 1))
|
|
fi
|
|
|
|
# Check description field
|
|
if ! grep -q '^description:' "$skill_file"; then
|
|
echo -e " ${RED}✗${NC} ${skill_name}: 缺少 description 字段"
|
|
errors=$((errors + 1))
|
|
fi
|
|
|
|
# Check name format (kebab-case)
|
|
local name_val=$(grep -m1 '^name:' "$skill_file" | sed 's/name:[[:space:]]*//')
|
|
if ! echo "$name_val" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
|
|
echo -e " ${RED}✗${NC} ${skill_name}: name 不符合 kebab-case 格式: '${name_val}'"
|
|
errors=$((errors + 1))
|
|
fi
|
|
|
|
# Check description length
|
|
local desc_len=$(sed -n '/^description:/,/^---/{/^description:/d;/^---/d;p;}' "$skill_file" | wc -c)
|
|
if [ "$desc_len" -gt 1024 ]; then
|
|
echo -e " ${YELLOW}⚠${NC} ${skill_name}: description 超过 1024 字符 (${desc_len})"
|
|
warnings=$((warnings + 1))
|
|
fi
|
|
|
|
# Check file length
|
|
local lines=$(wc -l < "$skill_file")
|
|
if [ "$lines" -gt 300 ]; then
|
|
echo -e " ${YELLOW}⚠${NC} ${skill_name}: SKILL.md 超过 300 行 (${lines}),建议拆分到 references/"
|
|
warnings=$((warnings + 1))
|
|
fi
|
|
|
|
# Check YAML frontmatter
|
|
local fm_count=$(grep -c '^---$' "$skill_file")
|
|
if [ "$fm_count" -lt 2 ]; then
|
|
echo -e " ${RED}✗${NC} ${skill_name}: YAML frontmatter 格式不完整"
|
|
errors=$((errors + 1))
|
|
fi
|
|
|
|
# All checks passed
|
|
if [ "$errors" -eq 0 ] && [ "$warnings" -eq 0 ]; then
|
|
echo -e " ${GREEN}✓${NC} ${skill_name}"
|
|
fi
|
|
done
|
|
|
|
echo "─────────────────────────────────────────────"
|
|
if [ "$errors" -gt 0 ]; then
|
|
echo -e " ${RED}✗ ${errors} 个错误${NC}, ${YELLOW}${warnings} 个警告${NC}"
|
|
return 1
|
|
elif [ "$warnings" -gt 0 ]; then
|
|
echo -e " ${GREEN}✓ 全部通过${NC}, ${YELLOW}${warnings} 个警告${NC}"
|
|
else
|
|
echo -e " ${GREEN}✓ 全部验证通过${NC}"
|
|
fi
|
|
}
|
|
|
|
cmd_stats() {
|
|
echo -e "${BLUE}📊 Skills 统计${NC}"
|
|
echo "─────────────────────────────────────────────"
|
|
|
|
local total_skills=0
|
|
local total_lines=0
|
|
local total_refs=0
|
|
local total_scripts=0
|
|
|
|
for skill_dir in "$SKILLS_DIR"/*/; do
|
|
[ -d "$skill_dir" ] || continue
|
|
local skill_file="$skill_dir/SKILL.md"
|
|
[ -f "$skill_file" ] || continue
|
|
|
|
total_skills=$((total_skills + 1))
|
|
total_lines=$((total_lines + $(wc -l < "$skill_file")))
|
|
|
|
if [ -d "${skill_dir}references" ]; then
|
|
total_refs=$((total_refs + $(find "${skill_dir}references" -name "*.md" | wc -l)))
|
|
fi
|
|
if [ -d "${skill_dir}scripts" ]; then
|
|
total_scripts=$((total_scripts + $(find "${skill_dir}scripts" -type f | wc -l)))
|
|
fi
|
|
done
|
|
|
|
local agents=0
|
|
if [ -d "$AGENTS_DIR" ]; then
|
|
agents=$(find "$AGENTS_DIR" -name "*.md" | wc -l)
|
|
fi
|
|
|
|
echo " Skills: ${total_skills}"
|
|
echo " Subagents: ${agents}"
|
|
echo " 总行数: ${total_lines} lines"
|
|
echo " 平均行数: $((total_lines / (total_skills > 0 ? total_skills : 1))) lines/skill"
|
|
echo " 参考文档: ${total_refs} files"
|
|
echo " 脚本: ${total_scripts} files"
|
|
echo ""
|
|
echo " 预估 Token (Tier 1 发现): ~$((total_skills * 40)) tokens"
|
|
echo " 预估 Token (全量加载): ~$((total_lines * 2)) tokens"
|
|
}
|
|
|
|
cmd_create() {
|
|
local name="${1:-}"
|
|
if [ -z "$name" ]; then
|
|
echo -e "${RED}用法: skill-manager.sh create <skill-name>${NC}"
|
|
echo " 名称必须是 kebab-case 格式"
|
|
return 1
|
|
fi
|
|
|
|
# Validate name format
|
|
if ! echo "$name" | grep -qE '^[a-z0-9]+(-[a-z0-9]+)*$'; then
|
|
echo -e "${RED}✗ 名称必须是 kebab-case: a-z, 0-9, 连字符${NC}"
|
|
return 1
|
|
fi
|
|
|
|
local dir="$SKILLS_DIR/$name"
|
|
if [ -d "$dir" ]; then
|
|
echo -e "${RED}✗ 技能已存在: $dir${NC}"
|
|
return 1
|
|
fi
|
|
|
|
mkdir -p "$dir"
|
|
cat > "$dir/SKILL.md" << 'TEMPLATE'
|
|
---
|
|
name: SKILL_NAME
|
|
description: >
|
|
Describe what this skill does and when to use it.
|
|
Include trigger keywords in both English and Chinese.
|
|
---
|
|
|
|
# Skill Title
|
|
|
|
## 触发条件
|
|
|
|
Describe when this skill should be activated.
|
|
|
|
## 执行流程
|
|
|
|
### 1. Step One
|
|
|
|
Specific, actionable instructions.
|
|
|
|
### 2. Step Two
|
|
|
|
More instructions.
|
|
|
|
## 验证
|
|
|
|
1. [ ] Check item 1
|
|
2. [ ] Check item 2
|
|
TEMPLATE
|
|
|
|
sed "s/SKILL_NAME/$name/g" "$dir/SKILL.md" > "$dir/SKILL.md.tmp" && mv "$dir/SKILL.md.tmp" "$dir/SKILL.md"
|
|
|
|
echo -e "${GREEN}✓ 技能已创建: $dir${NC}"
|
|
echo " 编辑 $dir/SKILL.md 完善技能内容"
|
|
}
|
|
|
|
# ================================================================
|
|
# Main
|
|
# ================================================================
|
|
|
|
cmd="${1:-help}"
|
|
shift || true
|
|
|
|
case "$cmd" in
|
|
list|ls) cmd_list ;;
|
|
validate|v) cmd_validate ;;
|
|
stats|s) cmd_stats ;;
|
|
create|new) cmd_create "$@" ;;
|
|
help|--help|-h)
|
|
echo "Skill Manager — Agent Skills 管理工具"
|
|
echo ""
|
|
echo "用法: bash scripts/skill-manager.sh <command>"
|
|
echo ""
|
|
echo "命令:"
|
|
echo " list 列出所有 Skills 和 Subagents"
|
|
echo " validate 验证 SKILL.md 规范合规性"
|
|
echo " stats 显示 Skills 统计信息"
|
|
echo " create 创建新技能脚手架"
|
|
echo " help 显示此帮助信息"
|
|
;;
|
|
*)
|
|
echo -e "${RED}未知命令: $cmd${NC}"
|
|
echo "运行 'bash scripts/skill-manager.sh help' 查看帮助"
|
|
exit 1
|
|
;;
|
|
esac
|