package service import ( "log" "os" "path/filepath" "strings" "x-agents/server/internal/model" "x-agents/server/internal/repository" "gopkg.in/yaml.v3" ) type SkillService struct { skillRepo *repository.SkillRepository } func NewSkillService(skillRepo *repository.SkillRepository) *SkillService { return &SkillService{skillRepo: skillRepo} } func (s *SkillService) GetAllSkills() ([]model.Skill, error) { return s.skillRepo.FindAll() } func (s *SkillService) GetSkillByID(id string) (*model.Skill, error) { return s.skillRepo.FindByID(id) } func (s *SkillService) GetSkillsByType(skillType string) ([]model.Skill, error) { return s.skillRepo.FindByType(skillType) } func (s *SkillService) CreateSkill(skill *model.Skill) error { return s.skillRepo.Create(skill) } func (s *SkillService) UpdateSkill(skill *model.Skill) error { return s.skillRepo.Update(skill) } func (s *SkillService) DeleteSkill(id string) error { // 先获取 skill 信息,以便删除本地文件 skill, err := s.skillRepo.FindByID(id) if err != nil { return err } // 删除数据库记录 if err := s.skillRepo.Delete(id); err != nil { return err } // 删除本地文件(skill 目录) if skill.Path != "" { // 获取 skill 所在目录(SKILL.md 的父目录) skillDir := filepath.Dir(skill.Path) if err := os.RemoveAll(skillDir); err != nil { log.Printf("[SkillService] Warning: failed to delete skill directory %s: %v", skillDir, err) // 数据库记录已删除,不返回错误 } else { log.Printf("[SkillService] Deleted skill directory: %s", skillDir) } } return nil } // InitSkills 初始化扫描所有 skills 目录 func (s *SkillService) InitSkills() error { // 获取项目根目录 projectRoot := s.getProjectRoot() if projectRoot == "" { return nil } var totalCount int // 扫描 system skills: core/agents/skills/system systemSkillsPath := filepath.Join(projectRoot, "core", "agents", "skills", "system") if _, err := os.Stat(systemSkillsPath); err == nil { systemSkills, err := s.scanSkillsDirectory(systemSkillsPath, "system") if err == nil && len(systemSkills) > 0 { s.skillRepo.DeleteByType("system") s.skillRepo.UpsertBatch(systemSkills) totalCount += len(systemSkills) } } // 扫描 user skills: core/agents/skills/user userSkillsPath := filepath.Join(projectRoot, "core", "agents", "skills", "user") if _, err := os.Stat(userSkillsPath); err == nil { userSkills, err := s.scanSkillsDirectory(userSkillsPath, "user") if err == nil && len(userSkills) > 0 { s.skillRepo.DeleteByType("user") s.skillRepo.UpsertBatch(userSkills) totalCount += len(userSkills) } } if totalCount > 0 { log.Printf("[SkillService] Loaded %d skills", totalCount) } return nil } // scanSkillsDirectory 扫描指定目录下的所有 skill func (s *SkillService) scanSkillsDirectory(basePath string, skillType string) ([]model.Skill, error) { var skills []model.Skill entries, err := os.ReadDir(basePath) if err != nil { return nil, err } for _, entry := range entries { if !entry.IsDir() { continue } skillDir := filepath.Join(basePath, entry.Name()) skillPath := filepath.Join(skillDir, "SKILL.md") // 尝试 skill.md(大小写不敏感) if _, err := os.Stat(skillPath); os.IsNotExist(err) { skillPath = filepath.Join(skillDir, "skill.md") } skillInfo, err := s.parseSkillFile(skillPath) if err != nil { log.Printf("[SkillService] Error parsing skill file %s: %v", skillPath, err) continue } // 如果没有从文件解析到名称,使用目录名 if skillInfo.SkillName == "" { skillInfo.SkillName = entry.Name() } skill := model.Skill{ SkillName: skillInfo.SkillName, SkillType: skillType, SkillDesc: skillInfo.SkillDesc, Path: skillPath, Status: 1, } skills = append(skills, skill) } return skills, nil } // parseSkillFile 解析 SKILL.md 文件,提取 YAML front matter func (s *SkillService) parseSkillFile(skillPath string) (*model.Skill, error) { content, err := os.ReadFile(skillPath) if err != nil { return nil, err } // 检查是否有 YAML front matter contentStr := string(content) if !strings.HasPrefix(contentStr, "---") { // 没有 front matter,返回空 return &model.Skill{}, nil } // 找到结束标记 lines := strings.Split(contentStr, "\n") var endIdx int for i := 1; i < len(lines); i++ { if strings.TrimSpace(lines[i]) == "---" { endIdx = i break } } if endIdx == 0 { return &model.Skill{}, nil } // 提取 YAML 内容 yamlContent := strings.Join(lines[1:endIdx], "\n") // 解析 YAML var frontMatter map[string]string if err := yaml.Unmarshal([]byte(yamlContent), &frontMatter); err != nil { return nil, err } skill := &model.Skill{ SkillName: frontMatter["name"], SkillDesc: frontMatter["description"], } return skill, nil } // getProjectRoot 获取项目根目录 func (s *SkillService) getProjectRoot() string { execPath, _ := os.Getwd() projectRoot := execPath // 如果当前目录名为 server,向上找一级 baseName := filepath.Base(execPath) if baseName == "server" { projectRoot = filepath.Dir(execPath) } // 尝试向上查找包含 .git 的目录 if _, err := os.Stat(filepath.Join(projectRoot, ".git")); os.IsNotExist(err) { for i := 0; i < 3; i++ { parent := filepath.Dir(projectRoot) if parent == projectRoot { break } if _, err := os.Stat(filepath.Join(parent, ".git")); err == nil { projectRoot = parent break } projectRoot = parent } } return projectRoot }