@@ -7,7 +7,6 @@ import (
10− "charm.land/catwalk/pkg/catwalk"
11 "charm.land/lipgloss/v2/tree"
10 "charm.land/lipgloss/v2/tree"
12 "github.com/charmbracelet/crush/internal/config"
11 "github.com/charmbracelet/crush/internal/config"
13 "github.com/mattn/go-isatty"
12 "github.com/mattn/go-isatty"
@@ -16,8 +15,8 @@ import (
17var modelsCmd = &cobra.Command{
16var modelsCmd = &cobra.Command{
18 Use: "models",
17 Use: "models",
19− Short: "List all available models from configured providers",
18+ Short: "List all available models from known providers",
20− Long: `List all available models from configured providers. Shows provider name and model IDs.`,
19+ Long: `List all available models from known providers. Shows provider name and model IDs. Unconfigured providers are marked with (not configured).`,
21 Example: `# List all available models
20 Example: `# List all available models
22crush models
21crush models
@@ -38,53 +37,94 @@ crush models gpt5`,
38 return err
37 return err
41− if !cfg.Config().IsConfigured() {
42− return fmt.Errorf("no providers configured - please run 'crush' to set up a provider interactively")
45 term := strings.ToLower(strings.Join(args, " "))
40 term := strings.ToLower(strings.Join(args, " "))
46− filter := func(p config.ProviderConfig, m catwalk.Model) bool {
41+
47− for _, s := range []string{p.ID, p.Name, m.ID, m.Name} {
42+ type providerEntry struct {
48− if term == "" || strings.Contains(strings.ToLower(s), term) {
43+ name string
49− return true
44+ models []string
55− var providerIDs []string
48+ entries := make(map[string]*providerEntry)
56− providerModels := make(map[string][]string)
50+ // Add configured providers first.
58 for providerID, provider := range cfg.Config().Providers.Seq2() {
51 for providerID, provider := range cfg.Config().Providers.Seq2() {
59 if provider.Disable {
52 if provider.Disable {
62− var found bool
55+ entry := &providerEntry{
63 for _, model := range provider.Models {
59 for _, model := range provider.Models {
64− if !filter(provider, model) {
60+ if term != "" {
62+ for _, s := range []string{provider.ID, provider.Name, model.ID, model.Name} {
63+ if strings.Contains(strings.ToLower(s), term) {
67− providerModels[providerID] = append(providerModels[providerID], model.ID)
72+ entry.models = append(entry.models, model.ID)
70− if !found {
74+ if len(entry.models) > 0 {
75+ slices.Sort(entry.models)
76+ entries[providerID] = entry
80+ // Add known but unconfigured providers from catwalk.
81+ for _, kp := range cfg.KnownProviders() {
82+ providerID := string(kp.ID)
83+ if _, exists := entries[providerID]; exists {
73− slices.Sort(providerModels[providerID])
86+ entry := &providerEntry{
90+ for _, model := range kp.Models {
93+ for _, s := range []string{providerID, kp.Name, model.ID, model.Name} {
94+ if strings.Contains(strings.ToLower(s), term) {
103+ entry.models = append(entry.models, model.ID)
105+ if len(entry.models) > 0 {
106+ slices.Sort(entry.models)
107+ entries[providerID] = entry
111+ var providerIDs []string
112+ for id := range entries {
74− providerIDs = append(providerIDs, providerID)
113+ providerIDs = append(providerIDs, id)
76 sort.Strings(providerIDs)
115 sort.Strings(providerIDs)
78 if len(providerIDs) == 0 && len(args) == 0 {
117 if len(providerIDs) == 0 && len(args) == 0 {
79− return fmt.Errorf("no enabled providers found")
118+ return fmt.Errorf("no providers found")
81 if len(providerIDs) == 0 {
120 if len(providerIDs) == 0 {
82− return fmt.Errorf("no enabled providers found matching %q", term)
121+ return fmt.Errorf("no providers found matching %q", term)
85 if !isatty.IsTerminal(os.Stdout.Fd()) {
124 if !isatty.IsTerminal(os.Stdout.Fd()) {
86 for _, providerID := range providerIDs {
125 for _, providerID := range providerIDs {
126+ entry := entries[providerID]
87− for _, modelID := range providerModels[providerID] {
127+ for _, modelID := range entry.models {
88 fmt.Println(providerID + "/" + modelID)
128 fmt.Println(providerID + "/" + modelID)
@@ -93,8 +133,13 @@ crush models gpt5`,
94 t := tree.New()
134 t := tree.New()
95 for _, providerID := range providerIDs {
135 for _, providerID := range providerIDs {
136+ entry := entries[providerID]
138+ if !entry.configured {
139+ label += " (not configured)"
96− providerNode := tree.Root(providerID)
141+ providerNode := tree.Root(label)
97− for _, modelID := range providerModels[providerID] {
142+ for _, modelID := range entry.models {
98 providerNode.Child(modelID)
143 providerNode.Child(modelID)
100 t.Child(providerNode)
145 t.Child(providerNode)