diff --git a/cmd/hyprvoice/main.go b/cmd/hyprvoice/main.go index 87f47d8..f017140 100644 --- a/cmd/hyprvoice/main.go +++ b/cmd/hyprvoice/main.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "os" "os/exec" @@ -403,6 +404,7 @@ func modelCmd() *cobra.Command { } cmd.AddCommand(modelListCmd()) + cmd.AddCommand(modelDownloadCmd()) return cmd } @@ -527,3 +529,60 @@ func printModelLine(m provider.Model) { fmt.Println(line) } + +func modelDownloadCmd() *cobra.Command { + return &cobra.Command{ + Use: "download ", + Short: "Download a local model (e.g. whisper models)", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return runModelDownload(cmd.Context(), args[0]) + }, + } +} + +func runModelDownload(ctx context.Context, modelName string) error { + // find the model across all providers + model, _, err := provider.FindModelByID(modelName) + if err != nil { + return fmt.Errorf("unknown model: %s", modelName) + } + + // check if it needs download (local model) + if !model.NeedsDownload() { + fmt.Printf("model '%s' is a cloud model and does not require download\n", modelName) + return nil + } + + // check if already installed + if whisper.IsInstalled(modelName) { + path := whisper.GetModelPath(modelName) + fmt.Printf("model '%s' is already installed at %s\n", modelName, path) + return nil + } + + // download with progress + fmt.Printf("downloading %s", modelName) + if model.LocalInfo != nil && model.LocalInfo.Size != "" { + fmt.Printf(" (%s)", model.LocalInfo.Size) + } + fmt.Println("...") + + var lastPercent int + err = whisper.Download(ctx, modelName, func(downloaded, total int64) { + if total > 0 { + percent := int(downloaded * 100 / total) + if percent >= lastPercent+10 { + fmt.Printf("%d%% ", percent) + lastPercent = percent + } + } + }) + if err != nil { + return fmt.Errorf("download failed: %w", err) + } + + path := whisper.GetModelPath(modelName) + fmt.Printf("\ndownload complete: %s\n", path) + return nil +} diff --git a/progress.txt b/progress.txt index ff3891e..66411c2 100644 --- a/progress.txt +++ b/progress.txt @@ -245,4 +245,14 @@ Started: Sun Feb 1 12:22:47 AM CET 2026 - 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 + +### Task 25: Add model download CLI command +- Created `modelDownloadCmd()` subcommand with Use: 'download ' +- Uses `provider.FindModelByID()` to search all providers for model +- Checks `model.NeedsDownload()` - if false, prints 'cloud model, does not require download' +- Checks `whisper.IsInstalled()` - if true, prints 'already installed at {path}' +- Downloads with progress callback showing percentage (10%, 20%, ...) +- Prints success message with full model path +- Tested: cloud model rejection, unknown model error, download with progress, already installed - All tests passing, typecheck passes \ No newline at end of file diff --git a/tasks/prd.jsonc b/tasks/prd.jsonc index f90a51a..5b507b3 100644 --- a/tasks/prd.jsonc +++ b/tasks/prd.jsonc @@ -596,7 +596,7 @@ "Shows error for cloud models that don't need download", "Typecheck passes" ], - "passes": false + "passes": true }, { "title": "Add model remove CLI command",