@@ -17,6 +17,27 @@ import (
1717 "github.com/spf13/cobra"
1818)
1919
20+ var imageFlags struct {
21+ model string
22+ width int
23+ height int
24+ steps int
25+ cfgScale float64
26+ scheduler string
27+ seed int64
28+ negative string
29+ count int
30+ outputDir string
31+ outputFormat string
32+ noDownload bool
33+ sourcePath string
34+ strength float64
35+ maskPath string
36+ preset string
37+ dryRun bool
38+ downloadTimeout time.Duration
39+ }
40+
2041var imageInferenceCmd = & cobra.Command {
2142 Use : "image [prompt]" ,
2243 Short : "Generate images from text or image input" ,
@@ -26,30 +47,31 @@ Examples:
2647 runware inference image "a cat riding a rocket"
2748 runware inference image "make it cinematic" --source ./input.png --strength 0.7
2849 runware inference image "replace with a dog" --source ./photo.png --mask ./mask.png` ,
29- Args : cobra .ExactArgs (1 ),
30- RunE : runImageInference ,
50+ Args : cobra .ExactArgs (1 ),
51+ PreRunE : preRunImageInference ,
52+ RunE : runImageInference ,
3153}
3254
3355func init () {
3456 f := imageInferenceCmd .Flags ()
35- f .String ( "model" , "" , "Model identifier" )
36- f .Int ( "width" , 0 , "Image width" )
37- f .Int ( "height" , 0 , "Image height" )
38- f .Int ( "steps" , 0 , "Number of inference steps" )
39- f .Float64 ( "cfg" , 0 , "CFG scale" )
40- f .String ( "scheduler" , "" , "Scheduler (e.g. euler, dpm++)" )
41- f .Int64 ( "seed" , 0 , "Seed for reproducibility" )
42- f .String ( "negative" , "" , "Negative prompt" )
43- f .Int ( "count" , 1 , "Number of images to generate" )
44- f .String ( "output" , "" , "Output directory" )
45- f .String ( "output-format" , "" , "Image format : png, jpg, webp" )
46- f .Bool ( "no-download" , false , "Print image URLs instead of downloading" )
47- f .String ( "source" , "" , "Source image path for img2img" )
48- f .Float64 ( "strength" , 0.7 , "img2img strength (0.0-1.0)" )
49- f .String ( "mask" , "" , "Mask image path for inpainting" )
50- f .String ( "preset" , "" , "Named preset to apply" )
51- f .Bool ( "dry-run" , false , "Print the API request without executing" )
52- f .Duration ( "download-timeout" , defaultImageDownloadTimeout , "timeout to use when downloading image inference results" )
57+ f .StringVarP ( & imageFlags . model , "model" , "m " , "" , "Model identifier" )
58+ f .IntVarP ( & imageFlags . width , "width" , "W " , 0 , "Image width" )
59+ f .IntVarP ( & imageFlags . height , "height" , "H " , 0 , "Image height" )
60+ f .IntVarP ( & imageFlags . steps , "steps" , "s " , 0 , "Number of inference steps" )
61+ f .Float64VarP ( & imageFlags . cfgScale , "cfg" , "c " , 0 , "CFG scale" )
62+ f .StringVarP ( & imageFlags . scheduler , "scheduler" , "S " , "" , "Scheduler (e.g. euler, dpm++)" )
63+ f .Int64VarP ( & imageFlags . seed , "seed" , "e " , 0 , "Seed for reproducibility" )
64+ f .StringVarP ( & imageFlags . negative , "negative" , "N " , "" , "Negative prompt" )
65+ f .IntVarP ( & imageFlags . count , "count" , "n " , 1 , "Number of images to generate" )
66+ f .StringVarP ( & imageFlags . outputDir , "output" , "o " , "" , "Output directory" )
67+ f .StringVarP ( & imageFlags . outputFormat , "output-format" , "f " , "" , "Format of generated images : png, jpg, webp" )
68+ f .BoolVarP ( & imageFlags . noDownload , "no-download" , "D " , false , "Print image URLs instead of downloading" )
69+ f .StringVarP ( & imageFlags . sourcePath , "source" , "i " , "" , "Source image path for img2img" )
70+ f .Float64VarP ( & imageFlags . strength , "strength" , "k " , 0.7 , "img2img strength (0.0-1.0)" )
71+ f .StringVarP ( & imageFlags . maskPath , "mask" , "M " , "" , "Mask image path for inpainting" )
72+ f .StringVarP ( & imageFlags . preset , "preset" , "p " , "" , "Named preset to apply" )
73+ f .BoolVarP ( & imageFlags . dryRun , "dry-run" , "X " , false , "Print the API request without executing" )
74+ f .DurationVarP ( & imageFlags . downloadTimeout , "download-timeout" , "T" , defaultImageDownloadTimeout , "Timeout for downloading image results" )
5375
5476 imageInferenceCmd .RegisterFlagCompletionFunc ("scheduler" , func (cmd * cobra.Command , args []string , toComplete string ) ([]cobra.Completion , cobra.ShellCompDirective ) { //nolint:errcheck,gosec
5577 return []cobra.Completion {
@@ -88,11 +110,17 @@ func init() {
88110
89111// completePresetNames provides dynamic completion for preset names from config.
90112func completePresetNames (cmd * cobra.Command , args []string , toComplete string ) ([]cobra.Completion , cobra.ShellCompDirective ) {
91- // Ensure config is loaded for completion context
92113 config .Init () //nolint:errcheck,gosec
93114 return config .ListPresets (), cobra .ShellCompDirectiveNoFileComp
94115}
95116
117+ func preRunImageInference (cmd * cobra.Command , _ []string ) error {
118+ if imageFlags .maskPath != "" && imageFlags .sourcePath == "" {
119+ return fmt .Errorf ("--mask requires --source to be set" )
120+ }
121+ return nil
122+ }
123+
96124func runImageInference (cmd * cobra.Command , args []string ) error {
97125 ctx := cmd .Context ()
98126 if ctx == nil {
@@ -108,7 +136,7 @@ func runImageInference(cmd *cobra.Command, args []string) error {
108136 cfg := config .Get ()
109137 prompt := args [0 ]
110138
111- // Start with config defaults for universal fields
139+ // Start with config defaults
112140 model := cfg .Defaults .Model
113141 width := cfg .Defaults .Width
114142 height := cfg .Defaults .Height
@@ -121,11 +149,10 @@ func runImageInference(cmd *cobra.Command, args []string) error {
121149 var scheduler string
122150
123151 // Apply preset if specified
124- presetName , _ := cmd .Flags ().GetString ("preset" )
125- if presetName != "" {
126- preset := config .GetPreset (presetName )
152+ if imageFlags .preset != "" {
153+ preset := config .GetPreset (imageFlags .preset )
127154 if preset == nil {
128- return fmt .Errorf ("preset '%s' not found" , presetName )
155+ return fmt .Errorf ("preset '%s' not found" , imageFlags . preset )
129156 }
130157 if preset .Model != "" {
131158 model = preset .Model
@@ -149,57 +176,41 @@ func runImageInference(cmd *cobra.Command, args []string) error {
149176
150177 // Override with explicit CLI flags
151178 if cmd .Flags ().Changed ("model" ) {
152- model , _ = cmd . Flags (). GetString ( " model" )
179+ model = imageFlags . model
153180 }
154181 if cmd .Flags ().Changed ("width" ) {
155- width , _ = cmd . Flags (). GetInt ( " width" )
182+ width = imageFlags . width
156183 }
157184 if cmd .Flags ().Changed ("height" ) {
158- height , _ = cmd . Flags (). GetInt ( " height" )
185+ height = imageFlags . height
159186 }
160187 if cmd .Flags ().Changed ("steps" ) {
161- steps , _ = cmd . Flags (). GetInt ( " steps" )
188+ steps = imageFlags . steps
162189 }
163190 if cmd .Flags ().Changed ("cfg" ) {
164- cfgScale , _ = cmd . Flags (). GetFloat64 ( "cfg" )
191+ cfgScale = imageFlags . cfgScale
165192 }
166193 if cmd .Flags ().Changed ("scheduler" ) {
167- scheduler , _ = cmd . Flags (). GetString ( " scheduler" )
194+ scheduler = imageFlags . scheduler
168195 }
169196 if cmd .Flags ().Changed ("output" ) {
170- outputDir , _ = cmd . Flags (). GetString ( "output" )
197+ outputDir = imageFlags . outputDir
171198 }
172199 if cmd .Flags ().Changed ("output-format" ) {
173- outputFormat , _ = cmd . Flags (). GetString ( "output-format" )
200+ outputFormat = imageFlags . outputFormat
174201 }
175202
176- count , _ := cmd .Flags ().GetInt ("count" )
177- seed , _ := cmd .Flags ().GetInt64 ("seed" )
178- negative , _ := cmd .Flags ().GetString ("negative" )
179- noDownload , _ := cmd .Flags ().GetBool ("no-download" )
180- dryRun , _ := cmd .Flags ().GetBool ("dry-run" )
181- sourcePath , _ := cmd .Flags ().GetString ("source" )
182- strength , _ := cmd .Flags ().GetFloat64 ("strength" )
183- maskPath , _ := cmd .Flags ().GetString ("mask" )
184- downloadTimeout , _ := cmd .Flags ().GetDuration ("download-timeout" )
185-
186- // Validation
187- if maskPath != "" && sourcePath == "" {
188- return fmt .Errorf ("--mask requires --source to be set" )
189- }
190-
191- // Build request — universal fields always included
203+ // Build request
192204 req := & api.ImageInferenceRequest {
193205 TaskUUID : api .NewUUID (),
194206 PositivePrompt : prompt ,
195207 Model : model ,
196208 Width : width ,
197209 Height : height ,
198- NumberResults : count ,
210+ NumberResults : imageFlags . count ,
199211 OutputFormat : api .OutputFormat (outputFormat ),
200212 }
201213
202- // Model-specific fields — only included when explicitly set
203214 if steps > 0 {
204215 req .Steps = steps
205216 }
@@ -209,34 +220,34 @@ func runImageInference(cmd *cobra.Command, args []string) error {
209220 if scheduler != "" {
210221 req .Scheduler = scheduler
211222 }
212- if negative != "" {
213- req .NegativePrompt = negative
223+ if imageFlags . negative != "" {
224+ req .NegativePrompt = imageFlags . negative
214225 }
215226 if cmd .Flags ().Changed ("seed" ) {
216- req .Seed = seed
227+ req .Seed = imageFlags . seed
217228 }
218229
219230 // Handle source image (img2img)
220- if sourcePath != "" {
221- encoded , err := encodeImageFile (sourcePath )
231+ if imageFlags . sourcePath != "" {
232+ encoded , err := encodeImageFile (imageFlags . sourcePath )
222233 if err != nil {
223234 return fmt .Errorf ("failed to read source image: %w" , err )
224235 }
225236 req .InputImage = encoded
226- req .Strength = strength
237+ req .Strength = imageFlags . strength
227238 }
228239
229240 // Handle mask image (inpainting)
230- if maskPath != "" {
231- encoded , err := encodeImageFile (maskPath )
241+ if imageFlags . maskPath != "" {
242+ encoded , err := encodeImageFile (imageFlags . maskPath )
232243 if err != nil {
233244 return fmt .Errorf ("failed to read mask image: %w" , err )
234245 }
235246 req .MaskImage = encoded
236247 }
237248
238- // Dry run — print request and exit
239- if dryRun {
249+ // Dry run
250+ if imageFlags . dryRun {
240251 data , _ := json .MarshalIndent ([]any {req }, "" , " " )
241252 fmt .Println (string (data ))
242253 return nil
@@ -247,7 +258,6 @@ func runImageInference(cmd *cobra.Command, args []string) error {
247258 s .Start ()
248259
249260 client := api .NewClient (key , config .GetBaseURL (), flagVerbose )
250-
251261 results , err := client .ImageInference (ctx , req )
252262 if err != nil {
253263 s .Stop ()
@@ -271,14 +281,13 @@ func runImageInference(cmd *cobra.Command, args []string) error {
271281 return output .Print (format , results , nil , nil )
272282 }
273283
274- // Table output — download or print URLs
275- if noDownload {
284+ // Table output — no- download: print URLs
285+ if imageFlags . noDownload {
276286 headers := []any {tableHeaderNum , tableHeaderURL , tableHeaderSeed }
277287 var rows [][]any
278288 for i , r := range results {
279289 rows = append (rows , []any {i + 1 , r .ImageURL , r .Seed })
280290 }
281-
282291 s .Stop ()
283292 return output .Print (format , results , headers , rows )
284293 }
@@ -301,7 +310,7 @@ func runImageInference(cmd *cobra.Command, args []string) error {
301310 filename := fmt .Sprintf ("runware_%s_%d.%s" , time .Now ().Format ("20060102_150405" ), i + 1 , ext )
302311 destPath := filepath .Join (outputDir , filename )
303312
304- if err := rhttp .Download (ctx , r .ImageURL , destPath , downloadTimeout ); err != nil {
313+ if err := rhttp .Download (ctx , r .ImageURL , destPath , imageFlags . downloadTimeout ); err != nil {
305314 output .Error (fmt .Sprintf ("Failed to download image %d: %s" , i + 1 , err ))
306315 continue
307316 }
0 commit comments