package repo import ( "fmt" "os" "path/filepath" "skillmgr/internal/config" "skillmgr/internal/types" ) // ScanSkills 扫描仓库中的 skills func ScanSkills(repoPath string) ([]types.SkillMetadata, error) { skillsPath := filepath.Join(repoPath, "skills") entries, err := os.ReadDir(skillsPath) if err != nil { if os.IsNotExist(err) { return []types.SkillMetadata{}, nil } return nil, err } var skills []types.SkillMetadata for _, entry := range entries { if !entry.IsDir() { continue } // 检查是否有 SKILL.md skillFile := filepath.Join(skillsPath, entry.Name(), "SKILL.md") if _, err := os.Stat(skillFile); err == nil { skills = append(skills, types.SkillMetadata{ Name: entry.Name(), }) } } return skills, nil } // ScanCommands 扫描仓库中的 commands func ScanCommands(repoPath string) ([]types.CommandGroup, error) { commandsPath := filepath.Join(repoPath, "commands") entries, err := os.ReadDir(commandsPath) if err != nil { if os.IsNotExist(err) { return []types.CommandGroup{}, nil } return nil, err } var groups []types.CommandGroup for _, entry := range entries { if !entry.IsDir() { continue } // 列出目录下的 .md 文件 files, err := filepath.Glob(filepath.Join(commandsPath, entry.Name(), "*.md")) if err != nil { fmt.Fprintf(os.Stderr, "警告: 无法扫描 %s 下的 markdown 文件: %v\n", entry.Name(), err) continue } var fileNames []string for _, f := range files { fileNames = append(fileNames, filepath.Base(f)) } if len(fileNames) > 0 { groups = append(groups, types.CommandGroup{ Name: entry.Name(), Files: fileNames, }) } } return groups, nil } // FindSkill 在所有仓库中查找 skill func FindSkill(name string) (repoPath, skillPath string, repoName string, err error) { cfg, err := config.LoadRepositoryConfig() if err != nil { return "", "", "", err } cachePath, err := config.GetCachePath() if err != nil { return "", "", "", err } for _, repo := range cfg.Repositories { rp := filepath.Join(cachePath, URLToPathName(repo.URL)) sp := filepath.Join(rp, "skills", name) if _, err := os.Stat(filepath.Join(sp, "SKILL.md")); err == nil { return rp, sp, repo.Name, nil } } return "", "", "", fmt.Errorf("skill '%s' 未在任何仓库中找到", name) } // FindCommand 在所有仓库中查找 command func FindCommand(name string) (repoPath, commandPath string, repoName string, err error) { cfg, err := config.LoadRepositoryConfig() if err != nil { return "", "", "", err } cachePath, err := config.GetCachePath() if err != nil { return "", "", "", err } for _, repo := range cfg.Repositories { rp := filepath.Join(cachePath, URLToPathName(repo.URL)) cp := filepath.Join(rp, "commands", name) if info, err := os.Stat(cp); err == nil && info.IsDir() { // 检查目录是否包含 .md 文件 files, _ := filepath.Glob(filepath.Join(cp, "*.md")) if len(files) > 0 { return rp, cp, repo.Name, nil } // 目录存在但为空,返回特定错误 return "", "", "", fmt.Errorf("command group '%s' 不包含任何命令文件", name) } } return "", "", "", fmt.Errorf("command '%s' 未在任何仓库中找到", name) } // ListAvailableSkills 列出所有可用的 skills func ListAvailableSkills() ([]types.SkillMetadata, error) { cfg, err := config.LoadRepositoryConfig() if err != nil { return nil, err } cachePath, err := config.GetCachePath() if err != nil { return nil, err } var allSkills []types.SkillMetadata for _, repo := range cfg.Repositories { rp := filepath.Join(cachePath, URLToPathName(repo.URL)) skills, err := ScanSkills(rp) if err != nil { continue } for i := range skills { skills[i].SourceRepo = repo.Name } allSkills = append(allSkills, skills...) } return allSkills, nil } // ListAvailableCommands 列出所有可用的 commands func ListAvailableCommands() ([]types.CommandGroup, error) { cfg, err := config.LoadRepositoryConfig() if err != nil { return nil, err } cachePath, err := config.GetCachePath() if err != nil { return nil, err } var allCommands []types.CommandGroup for _, repo := range cfg.Repositories { rp := filepath.Join(cachePath, URLToPathName(repo.URL)) commands, err := ScanCommands(rp) if err != nil { continue } for i := range commands { commands[i].SourceRepo = repo.Name } allCommands = append(allCommands, commands...) } return allCommands, nil }