diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-02 16:36:25 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-02 16:36:25 +0300 |
| commit | 6bd23a588bacee2e8c75f477150b7e2d345002ff (patch) | |
| tree | 67bfa78afe206e7560350bdd52c60e0e80ec3243 /internal/cli | |
| parent | 6ce9123de04ffff961cbf1da73648679216ff637 (diff) | |
Release v0.9.0v0.9.0
Diffstat (limited to 'internal/cli')
| -rw-r--r-- | internal/cli/command.go | 9 | ||||
| -rw-r--r-- | internal/cli/command_test.go | 26 | ||||
| -rw-r--r-- | internal/cli/flags.go | 6 | ||||
| -rw-r--r-- | internal/cli/flags_test.go | 5 |
4 files changed, 28 insertions, 18 deletions
diff --git a/internal/cli/command.go b/internal/cli/command.go index 621d43a..2c7ee04 100644 --- a/internal/cli/command.go +++ b/internal/cli/command.go @@ -22,7 +22,7 @@ func CreateRootCommand(flags *Flags) *cobra.Command { Long: `totalrecall generates Anki flashcard materials from Bulgarian words. It creates audio pronunciation files using Gemini TTS by default and downloads -representative images. Launching with no arguments opens the interactive GUI, which uses Nano Banana for images by default. Explicit CLI runs can use OpenAI or Nano Banana via --image-api, and audio can be switched between Gemini and OpenAI with --audio-provider. +representative images. Launching with no arguments opens the interactive GUI, which uses Nano Banana for images by default. Explicit CLI and batch runs also use Nano Banana by default, and can be switched to OpenAI via --image-api openai. Audio can be switched between Gemini and OpenAI with --audio-provider. Gemini audio model and voice flags are available for Gemini TTS generation. @@ -58,8 +58,8 @@ func setupFlags(cmd *cobra.Command, flags *Flags) { // Local flags cmd.Flags().StringVarP(&flags.OutputDir, "output", "o", defaultOutputDir, "Output directory") - cmd.Flags().StringVarP(&flags.AudioFormat, "format", "f", flags.AudioFormat, "Audio format (wav or mp3; Gemini TTS always writes wav)") - cmd.Flags().StringVar(&flags.ImageAPI, "image-api", flags.ImageAPI, "Image source for explicit CLI runs (OpenAI or Nano Banana; config file image.provider also applies when unset)") + cmd.Flags().StringVarP(&flags.AudioFormat, "format", "f", flags.AudioFormat, "Audio format (wav or mp3; Gemini TTS writes wav natively and auto-converts to mp3 with ffmpeg, which is now the default)") + cmd.Flags().StringVar(&flags.ImageAPI, "image-api", flags.ImageAPI, "Image source for explicit CLI runs (default: Nano Banana; use openai to switch, config file image.provider also applies when unset)") cmd.Flags().StringVar(&flags.BatchFile, "batch", "", "Process words from file (one per line)") cmd.Flags().BoolVar(&flags.SkipAudio, "skip-audio", false, "Skip audio generation") cmd.Flags().BoolVar(&flags.SkipImages, "skip-images", false, "Skip image download") @@ -100,6 +100,7 @@ func setupFlags(cmd *cobra.Command, flags *Flags) { // MarkExplicitFlagValues records which CLI flags were explicitly set by the user. func MarkExplicitFlagValues(cmd *cobra.Command, flags *Flags) { + flags.AudioFormatSpecified = cmd.Flags().Changed("format") flags.ImageAPISpecified = cmd.Flags().Changed("image-api") flags.NanoBananaModelSpecified = cmd.Flags().Changed("nanobanana-model") flags.NanoBananaTextModelSpecified = cmd.Flags().Changed("nanobanana-text-model") @@ -203,5 +204,5 @@ func openAIVoiceUsage() string { } func geminiVoiceUsage() string { - return "Gemini voice: " + strings.Join(audio.GeminiVoices, ", ") + " (default: model default)" + return "Gemini voice: " + strings.Join(audio.GeminiVoices, ", ") + " (default: random)" } diff --git a/internal/cli/command_test.go b/internal/cli/command_test.go index 212c549..afc1e18 100644 --- a/internal/cli/command_test.go +++ b/internal/cli/command_test.go @@ -28,8 +28,8 @@ func TestCreateRootCommand(t *testing.T) { if !strings.Contains(cmd.Long, "uses Nano Banana for images by default") { t.Errorf("Expected Long description to describe the Nano Banana GUI default") } - if !strings.Contains(cmd.Long, "Explicit CLI runs can use OpenAI or Nano Banana via --image-api") { - t.Errorf("Expected Long description to describe explicit CLI Nano Banana support") + if !strings.Contains(cmd.Long, "Explicit CLI and batch runs also use Nano Banana by default") { + t.Errorf("Expected Long description to describe the CLI and batch Nano Banana default") } // Test that flags are set up @@ -111,11 +111,11 @@ func TestSetupFlags(t *testing.T) { if imageAPIFlag == nil { t.Fatal("image-api flag not found") } - if imageAPIFlag.DefValue != "openai" { - t.Errorf("Expected default image-api to be openai, got %s", imageAPIFlag.DefValue) + if imageAPIFlag.DefValue != "nanobanana" { + t.Errorf("Expected default image-api to be nanobanana, got %s", imageAPIFlag.DefValue) } - if imageAPIFlag.Usage != "Image source for explicit CLI runs (OpenAI or Nano Banana; config file image.provider also applies when unset)" { - t.Errorf("Expected image-api help to describe CLI Nano Banana support and config fallback, got %q", imageAPIFlag.Usage) + if imageAPIFlag.Usage != "Image source for explicit CLI runs (default: Nano Banana; use openai to switch, config file image.provider also applies when unset)" { + t.Errorf("Expected image-api help to describe the CLI Nano Banana default and config fallback, got %q", imageAPIFlag.Usage) } openAIVoiceFlag := cmd.Flags().Lookup("openai-voice") @@ -150,8 +150,8 @@ func TestSetupFlags(t *testing.T) { if geminiVoiceFlag.DefValue != "" { t.Errorf("Expected default gemini-voice to be empty, got %q", geminiVoiceFlag.DefValue) } - if !strings.Contains(geminiVoiceFlag.Usage, "default: model default") { - t.Errorf("Expected gemini-voice help to describe the model default voice, got %q", geminiVoiceFlag.Usage) + if !strings.Contains(geminiVoiceFlag.Usage, "default: random") { + t.Errorf("Expected gemini-voice help to describe the random default voice, got %q", geminiVoiceFlag.Usage) } nanoBananaModelFlag := cmd.Flags().Lookup("nanobanana-model") @@ -490,8 +490,8 @@ func TestBindFlagsToViper(t *testing.T) { if viper.GetString("image.nanobanana_text_model") != "gemini-2.5-flash" { t.Errorf("Expected image.nanobanana_text_model to be gemini-2.5-flash, got %s", viper.GetString("image.nanobanana_text_model")) } - if viper.GetString("image.provider") != "openai" { - t.Errorf("Expected image.provider to be openai by default, got %s", viper.GetString("image.provider")) + if viper.GetString("image.provider") != "nanobanana" { + t.Errorf("Expected image.provider to be nanobanana by default, got %s", viper.GetString("image.provider")) } } @@ -503,6 +503,9 @@ func TestMarkExplicitFlagValues(t *testing.T) { if err := cmd.Flags().Set("image-api", "nanobanana"); err != nil { t.Fatalf("Failed to set image-api flag: %v", err) } + if err := cmd.Flags().Set("format", "mp3"); err != nil { + t.Fatalf("Failed to set format flag: %v", err) + } if err := cmd.Flags().Set("nanobanana-model", defaultNanoBananaModel); err != nil { t.Fatalf("Failed to set nanobanana-model flag: %v", err) } @@ -512,6 +515,9 @@ func TestMarkExplicitFlagValues(t *testing.T) { MarkExplicitFlagValues(cmd, flags) + if !flags.AudioFormatSpecified { + t.Error("Expected AudioFormatSpecified to be true") + } if !flags.ImageAPISpecified { t.Error("Expected ImageAPISpecified to be true") } diff --git a/internal/cli/flags.go b/internal/cli/flags.go index ac47628..0dad679 100644 --- a/internal/cli/flags.go +++ b/internal/cli/flags.go @@ -13,6 +13,8 @@ type Flags struct { CfgFile string OutputDir string AudioFormat string + // AudioFormatSpecified records whether the audio format was explicitly set on the CLI. + AudioFormatSpecified bool // AudioProvider selects the text-to-speech backend ("gemini" or "openai"). AudioProvider string ImageAPI string @@ -43,7 +45,7 @@ type Flags struct { // Gemini audio flags // GeminiTTSModel is the Gemini TTS model used when Gemini audio is selected. GeminiTTSModel string - // GeminiVoice selects a specific Gemini voice; empty uses the model default. + // GeminiVoice selects a specific Gemini voice; empty picks a random Gemini voice. GeminiVoice string // NanoBananaModel is the Gemini image model used for Nano Banana generation. @@ -63,7 +65,7 @@ func NewFlags() *Flags { return &Flags{ AudioFormat: defaults.OutputFormat, AudioProvider: defaults.Provider, - ImageAPI: "openai", + ImageAPI: "nanobanana", DeckName: "Bulgarian Vocabulary", OpenAIModel: "gpt-4o-mini-tts", OpenAISpeed: 0.9, diff --git a/internal/cli/flags_test.go b/internal/cli/flags_test.go index 79838c4..4b59bee 100644 --- a/internal/cli/flags_test.go +++ b/internal/cli/flags_test.go @@ -17,8 +17,9 @@ func TestNewFlags(t *testing.T) { expected interface{} }{ {"AudioFormat", flags.AudioFormat, audio.DefaultProviderConfig().OutputFormat}, + {"AudioFormatSpecified", flags.AudioFormatSpecified, false}, {"AudioProvider", flags.AudioProvider, audio.DefaultProviderConfig().Provider}, - {"ImageAPI", flags.ImageAPI, "openai"}, + {"ImageAPI", flags.ImageAPI, "nanobanana"}, {"ImageAPISpecified", flags.ImageAPISpecified, false}, {"NanoBananaModelSpecified", flags.NanoBananaModelSpecified, false}, {"NanoBananaTextModelSpecified", flags.NanoBananaTextModelSpecified, false}, @@ -92,7 +93,7 @@ func TestFlagsStructure(t *testing.T) { flagsType := reflect.TypeOf(*flags) expectedFields := []string{ - "CfgFile", "OutputDir", "AudioFormat", "AudioProvider", "ImageAPI", "ImageAPISpecified", "BatchFile", + "CfgFile", "OutputDir", "AudioFormat", "AudioFormatSpecified", "AudioProvider", "ImageAPI", "ImageAPISpecified", "BatchFile", "SkipAudio", "SkipImages", "GenerateAnki", "AnkiCSV", "DeckName", "ListModels", "AllVoices", "NoAutoPlay", "OpenAIModel", "OpenAIVoice", "OpenAISpeed", "OpenAIInstruction", |
