@@ -9,7 +9,7 @@ import type { Config } from '../../config/schema';
99import type { GlobalFlags } from '../../types/flags' ;
1010import type { ImageRequest , ImageResponse } from '../../types/api' ;
1111import { mkdirSync , existsSync , readFileSync } from 'fs' ;
12- import { join , resolve , extname } from 'path' ;
12+ import { dirname , join , resolve , extname } from 'path' ;
1313
1414const MIME_TYPES : Record < string , string > = {
1515 '.jpg' : 'image/jpeg' , '.jpeg' : 'image/jpeg' ,
@@ -33,6 +33,7 @@ export default defineCommand({
3333 { flag : '--prompt-optimizer' , description : 'Automatically optimize the prompt before generation for better results.' } ,
3434 { flag : '--aigc-watermark' , description : 'Embed AI-generated content watermark in the output image.' } ,
3535 { flag : '--subject-ref <params>' , description : 'Subject reference for character consistency. Format: type=character,image=path-or-url' } ,
36+ { flag : '--out <path>' , description : 'Save image to exact file path (single image only)' } ,
3637 { flag : '--out-dir <dir>' , description : 'Download images to directory' } ,
3738 { flag : '--out-prefix <prefix>' , description : 'Filename prefix (default: image)' } ,
3839 ] ,
@@ -46,6 +47,8 @@ export default defineCommand({
4647 'mmx image generate --prompt "Wide landscape" --width 1920 --height 1080' ,
4748 '# Optimized prompt with watermark' ,
4849 'mmx image generate --prompt "sunset" --prompt-optimizer --aigc-watermark' ,
50+ '# Save to exact path' ,
51+ 'mmx image generate --prompt "A cat" --out /tmp/cat.jpg' ,
4952 ] ,
5053 async run ( config : Config , flags : GlobalFlags ) {
5154 let prompt = ( flags . prompt ?? ( flags . _positional as string [ ] | undefined ) ?. [ 0 ] ) as string | undefined ;
@@ -88,6 +91,11 @@ export default defineCommand({
8891 validateSize ( 'height' , height ) ;
8992 }
9093
94+ const outPath = flags . out as string | undefined ;
95+ if ( outPath && ( flags . n as number ) > 1 ) {
96+ throw new CLIError ( '--out cannot be used with --n > 1. Use --out-dir instead.' , ExitCode . USAGE ) ;
97+ }
98+
9199 const body : ImageRequest = {
92100 model : 'image-01' ,
93101 prompt,
@@ -149,23 +157,31 @@ export default defineCommand({
149157 process . stderr . write ( '[Model: image-01]\n' ) ;
150158 }
151159
152- const outDir = ( flags . outDir as string | undefined ) ?? '.' ;
153- if ( ! existsSync ( outDir ) ) mkdirSync ( outDir , { recursive : true } ) ;
154-
155- const prefix = ( flags . outPrefix as string ) || 'image' ;
156160 const saved : string [ ] = [ ] ;
157161
158- for ( let i = 0 ; i < imageUrls . length ; i ++ ) {
159- const filename = `${ prefix } _${ String ( i + 1 ) . padStart ( 3 , '0' ) } .jpg` ;
160- const destPath = join ( outDir , filename ) ;
161-
162- // Warn if overwriting existing file (but don't block)
162+ if ( outPath ) {
163+ const dir = dirname ( resolve ( outPath ) ) ;
164+ if ( ! existsSync ( dir ) ) mkdirSync ( dir , { recursive : true } ) ;
165+ const destPath = resolve ( outPath ) ;
163166 if ( existsSync ( destPath ) ) {
164167 process . stderr . write ( `Warning: overwriting existing file: ${ destPath } \n` ) ;
165168 }
166-
167- await downloadFile ( imageUrls [ i ] ! , destPath , { quiet : config . quiet } ) ;
169+ await downloadFile ( imageUrls [ 0 ] ! , destPath , { quiet : config . quiet } ) ;
168170 saved . push ( destPath ) ;
171+ } else {
172+ const outDir = ( flags . outDir as string | undefined ) ?? '.' ;
173+ if ( ! existsSync ( outDir ) ) mkdirSync ( outDir , { recursive : true } ) ;
174+ const prefix = ( flags . outPrefix as string ) || 'image' ;
175+
176+ for ( let i = 0 ; i < imageUrls . length ; i ++ ) {
177+ const filename = `${ prefix } _${ String ( i + 1 ) . padStart ( 3 , '0' ) } .jpg` ;
178+ const destPath = join ( outDir , filename ) ;
179+ if ( existsSync ( destPath ) ) {
180+ process . stderr . write ( `Warning: overwriting existing file: ${ destPath } \n` ) ;
181+ }
182+ await downloadFile ( imageUrls [ i ] ! , destPath , { quiet : config . quiet } ) ;
183+ saved . push ( destPath ) ;
184+ }
169185 }
170186
171187 // --output json is respected even in --quiet mode (JSON is the actual output, not progress)
0 commit comments