DiscoverLocalSkillsWithOptions finds skills in a local directory using the same conventions as remote discovery, with configurable discovery behavior.
(dir string, opts DiscoverOptions)
| 922 | // DiscoverLocalSkillsWithOptions finds skills in a local directory using the |
| 923 | // same conventions as remote discovery, with configurable discovery behavior. |
| 924 | func DiscoverLocalSkillsWithOptions(dir string, opts DiscoverOptions) ([]Skill, error) { |
| 925 | absDir, err := filepath.Abs(dir) |
| 926 | if err != nil { |
| 927 | return nil, fmt.Errorf("could not resolve path: %w", err) |
| 928 | } |
| 929 | |
| 930 | info, err := os.Stat(absDir) |
| 931 | if err != nil { |
| 932 | return nil, fmt.Errorf("could not access %s: %w", dir, err) |
| 933 | } |
| 934 | if !info.IsDir() { |
| 935 | return nil, fmt.Errorf("%s is not a directory", dir) |
| 936 | } |
| 937 | |
| 938 | if _, err := os.Stat(filepath.Join(absDir, "SKILL.md")); err == nil { |
| 939 | skill, err := localSkillFromDir(absDir) |
| 940 | if err != nil { |
| 941 | return nil, err |
| 942 | } |
| 943 | skill.Path = "." |
| 944 | return []Skill{*skill}, nil |
| 945 | } |
| 946 | |
| 947 | var skills []Skill |
| 948 | seen := make(map[string]bool) |
| 949 | |
| 950 | err = filepath.Walk(absDir, func(p string, info os.FileInfo, walkErr error) error { |
| 951 | if walkErr != nil { |
| 952 | return walkErr |
| 953 | } |
| 954 | // Skip symlinks to avoid following links outside the source tree. |
| 955 | if info.Mode()&os.ModeSymlink != 0 { |
| 956 | return nil |
| 957 | } |
| 958 | if info.IsDir() || info.Name() != "SKILL.md" { |
| 959 | return nil |
| 960 | } |
| 961 | |
| 962 | relPath, relErr := filepath.Rel(absDir, p) |
| 963 | if relErr != nil { |
| 964 | return relErr |
| 965 | } |
| 966 | relPath = filepath.ToSlash(relPath) |
| 967 | |
| 968 | entry := treeEntry{Path: relPath, Type: "blob"} |
| 969 | m := matchSkillConventions(entry) |
| 970 | if m == nil { |
| 971 | m = matchHiddenDirConventions(entry) |
| 972 | } |
| 973 | if m == nil { |
| 974 | return nil |
| 975 | } |
| 976 | if seen[m.skillDir] { |
| 977 | return nil |
| 978 | } |
| 979 | seen[m.skillDir] = true |
| 980 | |
| 981 | skill, skillErr := localSkillFromDir(filepath.Join(absDir, filepath.FromSlash(m.skillDir))) |