summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-01 20:14:03 +0300
committerPaul Buetow <paul@buetow.org>2026-04-01 20:14:03 +0300
commit4727a188102d8166e4acabc4fd62863245e6cdce (patch)
tree1e9edac79d1fb348dee86f43974ed729e03f9173
parent3ed1c1ba41709c0de75512643c50b064f7dce650 (diff)
z8: preserve Nano Banana CLI precedence
-rw-r--r--cmd/totalrecall/main.go3
-rw-r--r--internal/cli/command.go7
-rw-r--r--internal/cli/command_test.go28
-rw-r--r--internal/cli/flags.go4
-rw-r--r--internal/cli/flags_test.go4
-rw-r--r--internal/processor/processor.go4
-rw-r--r--internal/processor/processor_test.go49
7 files changed, 94 insertions, 5 deletions
diff --git a/cmd/totalrecall/main.go b/cmd/totalrecall/main.go
index c0bd142..d719be5 100644
--- a/cmd/totalrecall/main.go
+++ b/cmd/totalrecall/main.go
@@ -27,6 +27,7 @@ func main() {
// Set the run function
rootCmd.RunE = func(cmd *cobra.Command, args []string) error {
+ cli.MarkExplicitFlagValues(cmd, flags)
return runCommand(cmd, args, flags)
}
@@ -60,8 +61,6 @@ func runCommand(cmd *cobra.Command, args []string, flags *cli.Flags) error {
fmt.Printf("Note: Using image size 1024x1024 for DALL-E 3 (use --openai-image-size to override)\n")
}
- flags.ImageAPISpecified = cmd.Flags().Changed("image-api")
-
// Create processor
proc := processor.NewProcessor(flags)
diff --git a/internal/cli/command.go b/internal/cli/command.go
index 924740e..327c869 100644
--- a/internal/cli/command.go
+++ b/internal/cli/command.go
@@ -91,6 +91,13 @@ 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.ImageAPISpecified = cmd.Flags().Changed("image-api")
+ flags.NanoBananaModelSpecified = cmd.Flags().Changed("nanobanana-model")
+ flags.NanoBananaTextModelSpecified = cmd.Flags().Changed("nanobanana-text-model")
+}
+
func bindFlagsToViper(cmd *cobra.Command) error {
bindings := map[string]string{
"audio.format": "format",
diff --git a/internal/cli/command_test.go b/internal/cli/command_test.go
index 67ef6c4..37e2533 100644
--- a/internal/cli/command_test.go
+++ b/internal/cli/command_test.go
@@ -435,3 +435,31 @@ func TestBindFlagsToViper(t *testing.T) {
t.Errorf("Expected image.provider to be openai by default, got %s", viper.GetString("image.provider"))
}
}
+
+func TestMarkExplicitFlagValues(t *testing.T) {
+ flags := NewFlags()
+ cmd := &cobra.Command{}
+ setupFlags(cmd, flags)
+
+ 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("nanobanana-model", defaultNanoBananaModel); err != nil {
+ t.Fatalf("Failed to set nanobanana-model flag: %v", err)
+ }
+ if err := cmd.Flags().Set("nanobanana-text-model", defaultNanoBananaTextModel); err != nil {
+ t.Fatalf("Failed to set nanobanana-text-model flag: %v", err)
+ }
+
+ MarkExplicitFlagValues(cmd, flags)
+
+ if !flags.ImageAPISpecified {
+ t.Error("Expected ImageAPISpecified to be true")
+ }
+ if !flags.NanoBananaModelSpecified {
+ t.Error("Expected NanoBananaModelSpecified to be true")
+ }
+ if !flags.NanoBananaTextModelSpecified {
+ t.Error("Expected NanoBananaTextModelSpecified to be true")
+ }
+}
diff --git a/internal/cli/flags.go b/internal/cli/flags.go
index 1e18766..5fcab40 100644
--- a/internal/cli/flags.go
+++ b/internal/cli/flags.go
@@ -38,8 +38,12 @@ type Flags struct {
// NanoBananaModel is the Gemini image model used for Nano Banana generation.
NanoBananaModel string
+ // NanoBananaModelSpecified records whether the Nano Banana image model was explicitly set on the CLI.
+ NanoBananaModelSpecified bool
// NanoBananaTextModel is the Gemini text model used for Nano Banana prompt generation.
NanoBananaTextModel string
+ // NanoBananaTextModelSpecified records whether the Nano Banana text model was explicitly set on the CLI.
+ NanoBananaTextModelSpecified bool
}
// NewFlags creates a new Flags instance with default values
diff --git a/internal/cli/flags_test.go b/internal/cli/flags_test.go
index 2a63d8d..7ab6425 100644
--- a/internal/cli/flags_test.go
+++ b/internal/cli/flags_test.go
@@ -17,6 +17,8 @@ func TestNewFlags(t *testing.T) {
{"AudioFormat", flags.AudioFormat, "mp3"},
{"ImageAPI", flags.ImageAPI, "openai"},
{"ImageAPISpecified", flags.ImageAPISpecified, false},
+ {"NanoBananaModelSpecified", flags.NanoBananaModelSpecified, false},
+ {"NanoBananaTextModelSpecified", flags.NanoBananaTextModelSpecified, false},
{"DeckName", flags.DeckName, "Bulgarian Vocabulary"},
{"OpenAIModel", flags.OpenAIModel, "gpt-4o-mini-tts"},
{"OpenAISpeed", flags.OpenAISpeed, 0.9},
@@ -90,7 +92,7 @@ func TestFlagsStructure(t *testing.T) {
"ListModels", "AllVoices", "NoAutoPlay",
"OpenAIModel", "OpenAIVoice", "OpenAISpeed", "OpenAIInstruction",
"OpenAIImageModel", "OpenAIImageSize", "OpenAIImageQuality", "OpenAIImageStyle",
- "NanoBananaModel", "NanoBananaTextModel",
+ "NanoBananaModel", "NanoBananaModelSpecified", "NanoBananaTextModel", "NanoBananaTextModelSpecified",
}
for _, fieldName := range expectedFields {
diff --git a/internal/processor/processor.go b/internal/processor/processor.go
index aac9733..d7025a6 100644
--- a/internal/processor/processor.go
+++ b/internal/processor/processor.go
@@ -606,10 +606,10 @@ func (p *Processor) newNanoBananaImageSearcher() (image.ImageSearcher, error) {
TextModel: p.flags.NanoBananaTextModel,
}
- if p.flags.NanoBananaModel == image.DefaultNanoBananaModel && viper.IsSet("image.nanobanana_model") {
+ if !p.flags.NanoBananaModelSpecified && viper.IsSet("image.nanobanana_model") {
nanoBananaConfig.Model = viper.GetString("image.nanobanana_model")
}
- if p.flags.NanoBananaTextModel == image.DefaultNanoBananaTextModel && viper.IsSet("image.nanobanana_text_model") {
+ if !p.flags.NanoBananaTextModelSpecified && viper.IsSet("image.nanobanana_text_model") {
nanoBananaConfig.TextModel = viper.GetString("image.nanobanana_text_model")
}
diff --git a/internal/processor/processor_test.go b/internal/processor/processor_test.go
index ef9e655..8ab13e7 100644
--- a/internal/processor/processor_test.go
+++ b/internal/processor/processor_test.go
@@ -397,6 +397,55 @@ func TestNewImageSearcherConfiguredNanoBananaRequiresGoogleAPIKey(t *testing.T)
}
}
+func TestNewNanoBananaImageSearcherExplicitDefaultWinsOverConfig(t *testing.T) {
+ t.Setenv("OPENAI_API_KEY", "test-openai-key")
+ t.Setenv("GOOGLE_API_KEY", "test-google-key")
+
+ originalConfig := viper.New()
+ *originalConfig = *viper.GetViper()
+ defer func() {
+ *viper.GetViper() = *originalConfig
+ }()
+ viper.Reset()
+ viper.Set("image.nanobanana_model", "config-image-model")
+ viper.Set("image.nanobanana_text_model", "config-text-model")
+
+ originalConstructor := newNanoBananaImageClient
+ capturedConfig := new(image.NanoBananaConfig)
+ newNanoBananaImageClient = func(config *image.NanoBananaConfig) image.ImageSearcher {
+ *capturedConfig = *config
+ return &stubImageSearcher{}
+ }
+ t.Cleanup(func() {
+ newNanoBananaImageClient = originalConstructor
+ })
+
+ flags := cli.NewFlags()
+ flags.OutputDir = t.TempDir()
+ flags.ImageAPI = "nanobanana"
+ flags.ImageAPISpecified = true
+ flags.NanoBananaModel = image.DefaultNanoBananaModel
+ flags.NanoBananaModelSpecified = true
+ flags.NanoBananaTextModel = image.DefaultNanoBananaTextModel
+ flags.NanoBananaTextModelSpecified = true
+
+ p := NewProcessor(flags)
+ searcher, err := p.newNanoBananaImageSearcher()
+ if err != nil {
+ t.Fatalf("newNanoBananaImageSearcher() unexpected error: %v", err)
+ }
+ if searcher == nil {
+ t.Fatal("expected searcher")
+ }
+
+ if capturedConfig.Model != image.DefaultNanoBananaModel {
+ t.Fatalf("NanoBanana Model = %q, want explicit CLI default %q", capturedConfig.Model, image.DefaultNanoBananaModel)
+ }
+ if capturedConfig.TextModel != image.DefaultNanoBananaTextModel {
+ t.Fatalf("NanoBanana TextModel = %q, want explicit CLI default %q", capturedConfig.TextModel, image.DefaultNanoBananaTextModel)
+ }
+}
+
func TestProcessSingleWord_InvalidWord(t *testing.T) {
flags := cli.NewFlags()
flags.OutputDir = t.TempDir()