diff --git a/tui/component_command_runtime.go b/tui/component_command_runtime.go index 328a336d..36c7dc67 100644 --- a/tui/component_command_runtime.go +++ b/tui/component_command_runtime.go @@ -117,7 +117,7 @@ func (m *model) activateSkillCommand(input, name string, args map[string]string) if err != nil { return err } - response := fmt.Sprintf("Activated skill `%s` (%s).\nTool policy: %s\nEntry: %s", skill.Name, skill.Scope, skill.ToolPolicy.Policy, skill.Entry.Slash) + response := fmt.Sprintf("Activated skill `%s` (%s).\nTool policy: %s", skill.Name, skill.Scope, skill.ToolPolicy.Policy) if len(args) > 0 { argParts := make([]string, 0, len(args)) keys := make([]string, 0, len(args)) @@ -145,5 +145,5 @@ func (m *model) activateSelectedSkill() error { } index := clamp(m.commandCursor, 0, len(items)-1) selected := items[index] - return m.activateSkillCommand(selected.Usage, selected.Usage, nil) + return m.activateSkillCommand("Skill select: "+selected.Name, selected.Name, nil) } diff --git a/tui/component_command_utils.go b/tui/component_command_utils.go index df8ce6e6..5820153a 100644 --- a/tui/component_command_utils.go +++ b/tui/component_command_utils.go @@ -17,13 +17,13 @@ func (m model) helpText() string { "- `/agents [name]`: list available subagents or show one definition.", "- `/review`: run the builtin review subagent with a bounded task.", "- `/explorer`: run the builtin explorer subagent with a bounded task.", + "- `/skills-select`: open the loaded skills picker.", "- `/skills`: list discovered skills and diagnostics.", "- `/mcp list`: list configured MCP servers and runtime status.", "- `/mcp help`: show MCP command help.", "- `/mcp show `: show one MCP server config and runtime status.", "- Add MCP servers by writing `.bytemind/mcp.json` in the workspace.", "- `/model`: open the configured provider/model picker and switch with Up/Down + Enter.", - "- `/ [k=v...]`: activate a skill for this session.", "- `/skill clear`: clear the active skill in this session.", "- `/skill delete `: delete the specified project skill.", "- `/new`: start a fresh session.", diff --git a/tui/component_palette_runtime.go b/tui/component_palette_runtime.go index f573a7fe..79f64aab 100644 --- a/tui/component_palette_runtime.go +++ b/tui/component_palette_runtime.go @@ -252,7 +252,7 @@ func (m model) skillPickerItems() []commandItem { } items = append(items, commandItem{ Name: name, - Usage: "/" + name, + Usage: name, Description: description, Kind: "skill", }) diff --git a/tui/model_test.go b/tui/model_test.go index 27417715..43b32a97 100644 --- a/tui/model_test.go +++ b/tui/model_test.go @@ -3742,6 +3742,7 @@ func TestHelpTextOnlyMentionsSupportedEntryPoints(t *testing.T) { "/plan", "/skill use", "/skill show", + "/", } { if strings.Contains(text, unwanted) { t.Fatalf("help text should not mention %q", unwanted) @@ -3752,6 +3753,7 @@ func TestHelpTextOnlyMentionsSupportedEntryPoints(t *testing.T) { "go run ./cmd/bytemind chat", "go run ./cmd/bytemind run -prompt", "/session", + "/skills-select", "TUI does not expose `/resume`", "CLI keeps `/resume `", "/skill clear", @@ -4496,26 +4498,27 @@ func TestFilteredCommandsExcludeSkillSlashCommands(t *testing.T) { items := m.filteredCommands() for _, item := range items { - if item.Name == "review" && item.Kind == "skill" { - t.Fatalf("skill commands should NOT appear in filtered commands, found %q", item.Name) + if item.Kind == "skill" { + t.Fatalf("command palette should not expose skill slash items, got %+v in %+v", item, items) } } - m.syncCommandPalette() skills := m.skillPickerItems() found := false - for _, s := range skills { - if s.Name == "review" && s.Usage == "/review" && s.Kind == "skill" { + for _, item := range skills { + if item.Name == "review" && item.Usage == "review" && item.Kind == "skill" { found = true - break + } + if strings.HasPrefix(item.Usage, "/") { + t.Fatalf("skill picker should show skill names without slash, got %+v", item) } } if !found { - t.Fatal("expected /review skill in skillPickerItems") + t.Fatalf("expected review skill in picker items, got %+v", skills) } } -func TestFilteredCommandsExcludeProjectSkillSlashCommands(t *testing.T) { +func TestSkillPickerActivatesProjectSkillWithoutShowingSlash(t *testing.T) { workspace := t.TempDir() skillDir := filepath.Join(workspace, ".bytemind", "skills", "review-plus") if err := os.MkdirAll(skillDir, 0o755); err != nil { @@ -4558,21 +4561,40 @@ func TestFilteredCommandsExcludeProjectSkillSlashCommands(t *testing.T) { items := m.filteredCommands() for _, item := range items { - if item.Name == "review-plus" && item.Kind == "skill" { - t.Fatalf("skill commands should NOT appear in filtered commands, found %q", item.Name) + if item.Kind == "skill" { + t.Fatalf("command palette should not expose project skill slash items, got %+v in %+v", item, items) } } - skills := m.skillPickerItems() - found := false - for _, s := range skills { - if s.Name == "review-plus" && s.Usage == "/review-plus" && s.Kind == "skill" { - found = true + pickerItems := m.skillPickerItems() + pickerIndex := -1 + for i, item := range pickerItems { + if item.Name == "review-plus" { + pickerIndex = i + if item.Usage != "review-plus" { + t.Fatalf("expected skill picker usage without slash, got %+v", item) + } break } } - if !found { - t.Fatal("expected /review-plus project skill in skillPickerItems") + if pickerIndex < 0 { + t.Fatalf("expected review-plus in skill picker items, got %+v", pickerItems) + } + + m.commandCursor = pickerIndex + if err := m.activateSelectedSkill(); err != nil { + t.Fatalf("expected skill picker activation to succeed, got %v", err) + } + if m.sess.ActiveSkill == nil || m.sess.ActiveSkill.Name != "review-plus" { + t.Fatalf("expected review-plus active after picker activation, got %#v", m.sess.ActiveSkill) + } + if len(m.chatItems) < 2 { + t.Fatalf("expected picker activation command exchange, got %#v", m.chatItems) + } + userBody := m.chatItems[len(m.chatItems)-2].Body + responseBody := m.chatItems[len(m.chatItems)-1].Body + if strings.Contains(userBody, "/review-plus") || strings.Contains(responseBody, "/review-plus") || strings.Contains(responseBody, "Entry:") { + t.Fatalf("skill picker activation should not render slash entry, user=%q response=%q", userBody, responseBody) } }