@@ -36,15 +36,17 @@ var (
3636
3737// Skill represents a parsed SKILL.md file.
3838type Skill struct {
39− Name string `yaml:"name" json:"name"`
40− Description string `yaml:"description" json:"description"`
41− License string `yaml:"license,omitempty" json:"license,omitempty"`
42− Compatibility string `yaml:"compatibility,omitempty" json:"compatibility,omitempty"`
43− Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
44− Instructions string `yaml:"-" json:"instructions"`
45− Path string `yaml:"-" json:"path"`
46− SkillFilePath string `yaml:"-" json:"skill_file_path"`
47− Builtin bool `yaml:"-" json:"builtin"`
39+ Name string `yaml:"name" json:"name"`
40+ Description string `yaml:"description" json:"description"`
41+ UserInvocable bool `yaml:"user-invocable" json:"user_invocable"`
42+ DisableModelInvocation bool `yaml:"disable-model-invocation" json:"disable_model_invocation"`
43+ License string `yaml:"license,omitempty" json:"license,omitempty"`
44+ Compatibility string `yaml:"compatibility,omitempty" json:"compatibility,omitempty"`
45+ Metadata map[string]string `yaml:"metadata,omitempty" json:"metadata,omitempty"`
46+ Instructions string `yaml:"-" json:"instructions"`
47+ Path string `yaml:"-" json:"path"`
48+ SkillFilePath string `yaml:"-" json:"skill_file_path"`
49+ Builtin bool `yaml:"-" json:"builtin"`
4850}
4951
5052// DiscoveryState represents the outcome of discovering a single skill file.
@@ -293,6 +295,7 @@ func DiscoverWithStates(paths []string) ([]*Skill, []*SkillState) {
293295}
294296
295297// ToPromptXML generates XML for injection into the system prompt.
298+// Skills with DisableModelInvocation set to true are excluded.
296299func ToPromptXML(skills []*Skill) string {
297300 if len(skills) == 0 {
298301 return ""
@@ -301,6 +304,10 @@ func ToPromptXML(skills []*Skill) string {
301304 var sb strings.Builder
302305 sb.WriteString("<available_skills>\n")
303306 for _, s := range skills {
307+ // Skip skills that have disable-model-invocation set
308+ if s.DisableModelInvocation {
309+ continue
310+ }
304311 sb.WriteString(" <skill>\n")
305312 fmt.Fprintf(&sb, " <name>%s</name>\n", escape(s.Name))
306313 fmt.Fprintf(&sb, " <description>%s</description>\n", escape(s.Description))
@@ -314,6 +321,20 @@ func ToPromptXML(skills []*Skill) string {
314321 return sb.String()
315322}
316323
324+// FormatInvocation generates XML for a skill when invoked as a user command.
325+func (s *Skill) FormatInvocation() string {
326+ var sb strings.Builder
327+ sb.WriteString("<loaded_skill>\n")
328+ fmt.Fprintf(&sb, " <name>%s</name>\n", escape(s.Name))
329+ fmt.Fprintf(&sb, " <description>%s</description>\n", escape(s.Description))
330+ fmt.Fprintf(&sb, " <location>%s</location>\n", escape(s.SkillFilePath))
331+ sb.WriteString(" <instructions>\n")
332+ sb.WriteString(escape(s.Instructions))
333+ sb.WriteString("\n </instructions>\n")
334+ sb.WriteString("</loaded_skill>")
335+ return sb.String()
336+}
337+
317338func escape(s string) string {
318339 return promptReplacer.Replace(s)
319340}