Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/skills/catalog.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func Catalog(active []*Skill, skillPaths []string, workingDir string) []CatalogE
Description: skill.Description,
Label: label,
Source: source,
UserInvocable: skill.UserInvocable,
UserInvocable: skill.IsUserInvocable(),
})
}
return entries
Expand Down
9 changes: 8 additions & 1 deletion internal/skills/skills.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var (
type Skill struct {
Name string `yaml:"name" json:"name"`
Description string `yaml:"description" json:"description"`
UserInvocable bool `yaml:"user-invocable" json:"user_invocable"`
UserInvocable *bool `yaml:"user-invocable,omitempty" json:"user_invocable,omitempty"`
DisableModelInvocation bool `yaml:"disable-model-invocation" json:"disable_model_invocation"`
License string `yaml:"license,omitempty" json:"license,omitempty"`
Compatibility string `yaml:"compatibility,omitempty" json:"compatibility,omitempty"`
Expand All @@ -49,6 +49,13 @@ type Skill struct {
Builtin bool `yaml:"-" json:"builtin"`
}

// IsUserInvocable reports whether the skill should appear in the command
// palette. When the user-invocable frontmatter field is absent, skills
// default to user-invocable to make the feature opt-out.
func (s *Skill) IsUserInvocable() bool {
return s.UserInvocable == nil || *s.UserInvocable
}

// DiscoveryState represents the outcome of discovering a single skill file.
type DiscoveryState int

Expand Down
35 changes: 35 additions & 0 deletions internal/skills/skills_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,41 @@ func TestParseContent_NoFrontmatter(t *testing.T) {
require.Error(t, err)
}

func TestIsUserInvocable_DefaultTrue(t *testing.T) {
t.Parallel()

tests := []struct {
name string
yaml string
want bool
}{
{
name: "absent defaults to true",
yaml: "---\nname: a\ndescription: x.\n---\n",
want: true,
},
{
name: "explicit true",
yaml: "---\nname: a\ndescription: x.\nuser-invocable: true\n---\n",
want: true,
},
{
name: "explicit false",
yaml: "---\nname: a\ndescription: x.\nuser-invocable: false\n---\n",
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
skill, err := ParseContent([]byte(tt.yaml))
require.NoError(t, err)
require.Equal(t, tt.want, skill.IsUserInvocable())
})
}
}

func TestDiscoverBuiltin(t *testing.T) {
t.Parallel()

Expand Down
Loading