@@ -7,7 +7,6 @@ import (
77 "sort"
88 "strings"
99
10โ "charm.land/catwalk/pkg/catwalk"
1110 "charm.land/lipgloss/v2/tree"
1211 "github.com/charmbracelet/crush/internal/config"
1312 "github.com/mattn/go-isatty"
@@ -16,8 +15,8 @@ import (
1615
1716var modelsCmd = &cobra.Command{
1817 Use: "models",
19โ Short: "List all available models from configured providers",
20โ Long: `List all available models from configured providers. Shows provider name and model IDs.`,
18+ Short: "List all available models from known providers",
19+ Long: `List all available models from known providers. Shows provider name and model IDs. Unconfigured providers are marked with (not configured).`,
2120 Example: `# List all available models
2221crush models
2322
@@ -38,53 +37,94 @@ crush models gpt5`,
3837 return err
3938 }
4039
41โ if !cfg.Config().IsConfigured() {
42โ return fmt.Errorf("no providers configured - please run 'crush' to set up a provider interactively")
43โ }
44โ
4540 term := strings.ToLower(strings.Join(args, " "))
46โ filter := func(p config.ProviderConfig, m catwalk.Model) bool {
47โ for _, s := range []string{p.ID, p.Name, m.ID, m.Name} {
48โ if term == "" || strings.Contains(strings.ToLower(s), term) {
49โ return true
50โ }
51โ }
52โ return false
41+
42+ type providerEntry struct {
43+ name string
44+ models []string
45+ configured bool
5346 }
5447
55โ var providerIDs []string
56โ providerModels := make(map[string][]string)
48+ entries := make(map[string]*providerEntry)
5749
50+ // Add configured providers first.
5851 for providerID, provider := range cfg.Config().Providers.Seq2() {
5952 if provider.Disable {
6053 continue
6154 }
62โ var found bool
55+ entry := &providerEntry{
56+ name: provider.Name,
57+ configured: true,
58+ }
6359 for _, model := range provider.Models {
64โ if !filter(provider, model) {
65โ continue
60+ if term != "" {
61+ matched := false
62+ for _, s := range []string{provider.ID, provider.Name, model.ID, model.Name} {
63+ if strings.Contains(strings.ToLower(s), term) {
64+ matched = true
65+ break
66+ }
67+ }
68+ if !matched {
69+ continue
70+ }
6671 }
67โ providerModels[providerID] = append(providerModels[providerID], model.ID)
68โ found = true
72+ entry.models = append(entry.models, model.ID)
6973 }
70โ if !found {
74+ if len(entry.models) > 0 {
75+ slices.Sort(entry.models)
76+ entries[providerID] = entry
77+ }
78+ }
79+
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 {
7184 continue
7285 }
73โ slices.Sort(providerModels[providerID])
74โ providerIDs = append(providerIDs, providerID)
86+ entry := &providerEntry{
87+ name: kp.Name,
88+ configured: false,
89+ }
90+ for _, model := range kp.Models {
91+ if term != "" {
92+ matched := false
93+ for _, s := range []string{providerID, kp.Name, model.ID, model.Name} {
94+ if strings.Contains(strings.ToLower(s), term) {
95+ matched = true
96+ break
97+ }
98+ }
99+ if !matched {
100+ continue
101+ }
102+ }
103+ entry.models = append(entry.models, model.ID)
104+ }
105+ if len(entry.models) > 0 {
106+ slices.Sort(entry.models)
107+ entries[providerID] = entry
108+ }
109+ }
110+
111+ var providerIDs []string
112+ for id := range entries {
113+ providerIDs = append(providerIDs, id)
75114 }
76115 sort.Strings(providerIDs)
77116
78117 if len(providerIDs) == 0 && len(args) == 0 {
79โ return fmt.Errorf("no enabled providers found")
118+ return fmt.Errorf("no providers found")
80119 }
81120 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)
83122 }
84123
85124 if !isatty.IsTerminal(os.Stdout.Fd()) {
86125 for _, providerID := range providerIDs {
87โ for _, modelID := range providerModels[providerID] {
126+ entry := entries[providerID]
127+ for _, modelID := range entry.models {
88128 fmt.Println(providerID + "/" + modelID)
89129 }
90130 }
@@ -93,8 +133,13 @@ crush models gpt5`,
93133
94134 t := tree.New()
95135 for _, providerID := range providerIDs {
96โ providerNode := tree.Root(providerID)
97โ for _, modelID := range providerModels[providerID] {
136+ entry := entries[providerID]
137+ label := providerID
138+ if !entry.configured {
139+ label += " (not configured)"
140+ }
141+ providerNode := tree.Root(label)
142+ for _, modelID := range entry.models {
98143 providerNode.Child(modelID)
99144 }
100145 t.Child(providerNode)