@@ -151,8 +151,27 @@ pub fn build_command_args(
151151 . unwrap_or_else ( || "llama3.2" . to_string ( ) ) ;
152152 args. push ( model) ;
153153 args. push ( prompt. to_string ( ) ) ;
154+ } else if backend. name == "codex" {
155+ // codex exec [OPTIONS] [PROMPT] — non-interactive mode with positional prompt
156+ args. push ( "exec" . to_string ( ) ) ;
157+ if auto_approve {
158+ if let Some ( flag) = backend. auto_approve_flag {
159+ args. push ( flag. to_string ( ) ) ;
160+ }
161+ }
162+ if let Some ( m) = model_override. or ( backend. model . as_deref ( ) ) {
163+ args. push ( "--model" . to_string ( ) ) ;
164+ args. push ( m. to_string ( ) ) ;
165+ }
166+ // System prompt prepended (codex has no native --system-prompt)
167+ if let Some ( sp) = system_prompt {
168+ args. push ( format ! ( "SYSTEM: {}\n \n TASK: {}" , sp, prompt) ) ;
169+ } else {
170+ args. push ( prompt. to_string ( ) ) ;
171+ }
154172 } else {
155- // Standard pattern: [binary] [auto_approve_flag] [-p prompt]
173+ // Standard pattern: [binary] [auto_approve_flag] -p <prompt>
174+ // Used by gemini, claude, grok.
156175 if auto_approve {
157176 if let Some ( flag) = backend. auto_approve_flag {
158177 args. push ( flag. to_string ( ) ) ;
@@ -171,15 +190,22 @@ pub fn build_command_args(
171190 args. push ( m. to_string ( ) ) ;
172191 }
173192 args. push ( "-p" . to_string ( ) ) ;
174- // For backends without native system prompt support, prepend it
175- if let Some ( sp) = system_prompt {
193+ // For backends without native system prompt support, prepend it.
194+ // Prefix with a space if the prompt starts with '-' to prevent
195+ // yargs-based CLIs (gemini) from misinterpreting it as a flag.
196+ let final_prompt = if let Some ( sp) = system_prompt {
176197 if backend. name != "claude" {
177- args . push ( format ! ( "SYSTEM: {}\n \n TASK: {}" , sp, prompt) ) ;
198+ format ! ( "SYSTEM: {}\n \n TASK: {}" , sp, prompt)
178199 } else {
179- args . push ( prompt. to_string ( ) ) ;
200+ prompt. to_string ( )
180201 }
181202 } else {
182- args. push ( prompt. to_string ( ) ) ;
203+ prompt. to_string ( )
204+ } ;
205+ if final_prompt. starts_with ( '-' ) {
206+ args. push ( format ! ( " {}" , final_prompt) ) ;
207+ } else {
208+ args. push ( final_prompt) ;
183209 }
184210 }
185211
@@ -280,6 +306,54 @@ mod tests {
280306 assert ! ( args. contains( & "hello" . to_string( ) ) ) ;
281307 }
282308
309+ #[ test]
310+ fn test_build_command_args_codex ( ) {
311+ let backend = BackendConfig {
312+ name : "codex" ,
313+ display_name : "Codex CLI" ,
314+ binary : "/usr/bin/codex" . to_string ( ) ,
315+ model : None ,
316+ auto_approve_flag : Some ( "--full-auto" ) ,
317+ api_key_env : Some ( "OPENAI_API_KEY" ) ,
318+ } ;
319+ let ( bin, args) = build_command_args ( & backend, "hello" , None , None , true ) ;
320+ assert_eq ! ( bin, "/usr/bin/codex" ) ;
321+ assert_eq ! ( args, vec![ "exec" , "--full-auto" , "hello" ] ) ;
322+ }
323+
324+ #[ test]
325+ fn test_build_command_args_codex_with_system_prompt ( ) {
326+ let backend = BackendConfig {
327+ name : "codex" ,
328+ display_name : "Codex CLI" ,
329+ binary : "/usr/bin/codex" . to_string ( ) ,
330+ model : None ,
331+ auto_approve_flag : Some ( "--full-auto" ) ,
332+ api_key_env : Some ( "OPENAI_API_KEY" ) ,
333+ } ;
334+ let ( _, args) = build_command_args ( & backend, "task" , None , Some ( "You are helpful" ) , true ) ;
335+ assert_eq ! ( args[ 0 ] , "exec" ) ;
336+ assert ! ( args. last( ) . unwrap( ) . contains( "SYSTEM: You are helpful" ) ) ;
337+ assert ! ( args. last( ) . unwrap( ) . contains( "TASK: task" ) ) ;
338+ }
339+
340+ #[ test]
341+ fn test_build_command_args_dash_prefix_escaped ( ) {
342+ let backend = BackendConfig {
343+ name : "gemini" ,
344+ display_name : "Gemini CLI" ,
345+ binary : "/usr/bin/gemini" . to_string ( ) ,
346+ model : None ,
347+ auto_approve_flag : Some ( "-y" ) ,
348+ api_key_env : Some ( "GEMINI_API_KEY" ) ,
349+ } ;
350+ let ( _, args) = build_command_args ( & backend, "--- FILE: test ---\n content" , None , None , true ) ;
351+ // Prompt should be space-prefixed to avoid yargs misinterpreting leading dashes
352+ let prompt_arg = args. last ( ) . unwrap ( ) ;
353+ assert ! ( prompt_arg. starts_with( ' ' ) ) ;
354+ assert ! ( prompt_arg. contains( "--- FILE: test ---" ) ) ;
355+ }
356+
283357 #[ test]
284358 fn test_all_backend_specs_returns_all ( ) {
285359 let specs = all_backend_specs ( ) ;
0 commit comments