diff --git a/cmd/hyprvoice/main.go b/cmd/hyprvoice/main.go index 1032bd7..87f47d8 100644 --- a/cmd/hyprvoice/main.go +++ b/cmd/hyprvoice/main.go @@ -4,11 +4,14 @@ import ( "fmt" "os" "os/exec" + "sort" "strings" "github.com/leonardotrapani/hyprvoice/internal/bus" "github.com/leonardotrapani/hyprvoice/internal/config" "github.com/leonardotrapani/hyprvoice/internal/daemon" + "github.com/leonardotrapani/hyprvoice/internal/models/whisper" + "github.com/leonardotrapani/hyprvoice/internal/provider" "github.com/leonardotrapani/hyprvoice/internal/tui" "github.com/spf13/cobra" ) @@ -31,6 +34,7 @@ func init() { versionCmd(), stopCmd(), configureCmd(), + modelCmd(), ) } @@ -391,3 +395,135 @@ func hasCustomMessages(msgs config.MessagesConfig) bool { msgs.RecordingAborted.Body != "" || msgs.InjectionAborted.Body != "" } + +func modelCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "model", + Short: "Manage transcription models", + } + + cmd.AddCommand(modelListCmd()) + + return cmd +} + +func modelListCmd() *cobra.Command { + var providerFilter string + var typeFilter string + + cmd := &cobra.Command{ + Use: "list", + Short: "List available transcription and LLM models", + RunE: func(cmd *cobra.Command, args []string) error { + return runModelList(providerFilter, typeFilter) + }, + } + + cmd.Flags().StringVar(&providerFilter, "provider", "", "filter by provider name") + cmd.Flags().StringVar(&typeFilter, "type", "", "filter by type: transcription, llm") + + return cmd +} + +func runModelList(providerFilter, typeFilter string) error { + // parse type filter + var filterType *provider.ModelType + if typeFilter != "" { + switch strings.ToLower(typeFilter) { + case "transcription": + t := provider.Transcription + filterType = &t + case "llm": + t := provider.LLM + filterType = &t + default: + return fmt.Errorf("invalid type: %s (use 'transcription' or 'llm')", typeFilter) + } + } + + // get providers to iterate + providerNames := provider.ListProviders() + sort.Strings(providerNames) + + // filter by provider if specified + if providerFilter != "" { + found := false + for _, name := range providerNames { + if name == providerFilter { + providerNames = []string{name} + found = true + break + } + } + if !found { + return fmt.Errorf("unknown provider: %s", providerFilter) + } + } + + for _, providerName := range providerNames { + p := provider.GetProvider(providerName) + if p == nil { + continue + } + + models := p.Models() + if filterType != nil { + models = provider.ModelsOfType(p, *filterType) + } + + if len(models) == 0 { + continue + } + + // print provider header + fmt.Printf("\n%s:\n", providerName) + + for _, m := range models { + printModelLine(m) + } + } + + fmt.Println() + return nil +} + +func printModelLine(m provider.Model) { + // build prefix: checkmark for installed local models + prefix := " " + if m.Local { + if whisper.IsInstalled(m.ID) { + prefix = " [x]" + } else { + prefix = " [ ]" + } + } + + // build suffix parts + var parts []string + + // type indicator + if m.Type == provider.LLM { + parts = append(parts, "llm") + } + + // streaming indicator + if m.Streaming { + parts = append(parts, "streaming") + } + + // size for local models + if m.LocalInfo != nil && m.LocalInfo.Size != "" { + parts = append(parts, m.LocalInfo.Size) + } + + // build line + line := fmt.Sprintf("%s %s", prefix, m.ID) + if m.Description != "" { + line += fmt.Sprintf(" - %s", m.Description) + } + if len(parts) > 0 { + line += fmt.Sprintf(" [%s]", strings.Join(parts, ", ")) + } + + fmt.Println(line) +} diff --git a/progress.txt b/progress.txt index 869e798..ff3891e 100644 --- a/progress.txt +++ b/progress.txt @@ -234,4 +234,15 @@ Started: Sun Feb 1 12:22:47 AM CET 2026 - Validates language codes for whisper-cpp same as other providers - Note: Threads field, ToTranscriberConfig, and template were already done in Task 16 - Added comprehensive tests for whisper-cpp validation and threads auto-detection +- All tests passing, typecheck passes + +### Task 24: Add model list CLI command +- Created `modelCmd()` returning cobra.Command with Use: 'model' +- Created `modelListCmd()` subcommand with Use: 'list' +- Added `--provider` flag to filter by provider name +- Added `--type` flag to filter by 'transcription' or 'llm' +- Iterates all providers sorted alphabetically, gets Models(), filters by type +- For local models: shows [x] if installed via whisper.IsInstalled(), [ ] if not +- Shows: Model ID, Description, [streaming] tag if applicable, [size] for local models +- Groups output by provider with headers - All tests passing, typecheck passes \ No newline at end of file diff --git a/tasks/prd.jsonc b/tasks/prd.jsonc index e261e12..f90a51a 100644 --- a/tasks/prd.jsonc +++ b/tasks/prd.jsonc @@ -575,7 +575,7 @@ "Output includes size for local models", "Typecheck passes" ], - "passes": false + "passes": true }, { "title": "Add model download CLI command",