1package cmd
2
3import (
4 "fmt"
5 "os"
6 "slices"
7 "sort"
8 "strings"
9
10 "charm.land/lipgloss/v2/tree"
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/mattn/go-isatty"
13 "github.com/spf13/cobra"
14)
15
16var modelsCmd = &cobra.Command{
17 Use: "models",
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).`,
20 Example: `# List all available models
21crush models
22
23# Search models
24crush models gpt5`,
25 Args: cobra.ArbitraryArgs,
26 RunE: func(cmd *cobra.Command, args []string) error {
27 cwd, err := ResolveCwd(cmd)
28 if err != nil {
29 return err
30 }
31
32 dataDir, _ := cmd.Flags().GetString("data-dir")
33 debug, _ := cmd.Flags().GetBool("debug")
34
35 cfg, err := config.Init(cwd, dataDir, debug)
36 if err != nil {
37 return err
38 }
39
40 term := strings.ToLower(strings.Join(args, " "))
41
42 type providerEntry struct {
43 name string
44 models []string
45 configured bool
46 }
47
48 entries := make(map[string]*providerEntry)
49
50 // Add configured providers first.
51 for providerID, provider := range cfg.Config().Providers.Seq2() {
52 if provider.Disable {
53 continue
54 }
55 entry := &providerEntry{
56 name: provider.Name,
57 configured: true,
58 }
59 for _, model := range provider.Models {
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 }
71 }
72 entry.models = append(entry.models, model.ID)
73 }
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 {
84 continue
85 }
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)
114 }
115 sort.Strings(providerIDs)
116
117 if len(providerIDs) == 0 && len(args) == 0 {
118 return fmt.Errorf("no providers found")
119 }
120 if len(providerIDs) == 0 {
121 return fmt.Errorf("no providers found matching %q", term)
122 }
123
124 if !isatty.IsTerminal(os.Stdout.Fd()) {
125 for _, providerID := range providerIDs {
126 entry := entries[providerID]
127 for _, modelID := range entry.models {
128 fmt.Println(providerID + "/" + modelID)
129 }
130 }
131 return nil
132 }
133
134 t := tree.New()
135 for _, providerID := range providerIDs {
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 {
143 providerNode.Child(modelID)
144 }
145 t.Child(providerNode)
146 }
147
148 cmd.Println(t)
149 return nil
150 },
151}
152
153func init() {
154 rootCmd.AddCommand(modelsCmd)
155}