1package skills
2
3import (
4 "embed"
5 "io/fs"
6 "log/slog"
7 "path/filepath"
8)
9
10// BuiltinPrefix is the path prefix for builtin skill files. It is used by
11// the View tool to distinguish embedded files from disk files.
12const BuiltinPrefix = "crush://skills/"
13
14//go:embed builtin/*
15var builtinFS embed.FS
16
17// BuiltinFS returns the embedded filesystem containing builtin skills.
18func BuiltinFS() embed.FS {
19 return builtinFS
20}
21
22// DiscoverBuiltin finds all valid skills embedded in the binary.
23func DiscoverBuiltin() []*Skill {
24 skills, _ := DiscoverBuiltinWithStates()
25 return skills
26}
27
28// DiscoverBuiltinWithStates is like DiscoverBuiltin but additionally returns
29// a per-file state slice describing parse/validation outcomes. Useful for
30// diagnostics.
31func DiscoverBuiltinWithStates() ([]*Skill, []*SkillState) {
32 var discovered []*Skill
33 var states []*SkillState
34
35 fs.WalkDir(builtinFS, "builtin", func(path string, d fs.DirEntry, err error) error {
36 if err != nil {
37 return nil
38 }
39 if d.IsDir() || d.Name() != SkillFileName {
40 return nil
41 }
42
43 content, err := builtinFS.ReadFile(path)
44 if err != nil {
45 slog.Warn("Failed to read builtin skill file", "path", path, "error", err)
46 states = append(states, &SkillState{Path: path, State: StateError, Err: err})
47 return nil
48 }
49
50 skill, err := ParseContent(content)
51 if err != nil {
52 slog.Warn("Failed to parse builtin skill file", "path", path, "error", err)
53 states = append(states, &SkillState{Path: path, State: StateError, Err: err})
54 return nil
55 }
56
57 // Set paths using the crush prefix. Strip the leading "builtin/"
58 // so the path is relative to the embedded root
59 // (e.g., "crush://skills/crush-config/SKILL.md").
60 relPath, _ := filepath.Rel("builtin", path)
61 relPath = filepath.ToSlash(relPath)
62 skill.SkillFilePath = BuiltinPrefix + relPath
63 skill.Path = BuiltinPrefix + filepath.Dir(relPath)
64 skill.Builtin = true
65
66 if err := skill.Validate(); err != nil {
67 slog.Warn("Builtin skill validation failed", "path", path, "error", err)
68 states = append(states, &SkillState{Name: skill.Name, Path: path, State: StateError, Err: err})
69 return nil
70 }
71
72 slog.Debug("Successfully loaded builtin skill", "name", skill.Name, "path", skill.SkillFilePath)
73 discovered = append(discovered, skill)
74 states = append(states, &SkillState{Name: skill.Name, Path: skill.SkillFilePath, State: StateNormal})
75 return nil
76 })
77
78 return discovered, states
79}