models.go

  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}