1use ralph_core::{CliConfig, HatBackend};
4use std::fmt;
5use std::io::Write;
6use tempfile::NamedTempFile;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
13pub enum OutputFormat {
14 #[default]
16 Text,
17 StreamJson,
19}
20
21#[derive(Debug, Clone)]
23pub struct CustomBackendError;
24
25impl fmt::Display for CustomBackendError {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "custom backend requires a command to be specified")
28 }
29}
30
31impl std::error::Error for CustomBackendError {}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub enum PromptMode {
36 Arg,
38 Stdin,
40}
41
42#[derive(Debug, Clone)]
44pub struct CliBackend {
45 pub command: String,
47 pub args: Vec<String>,
49 pub prompt_mode: PromptMode,
51 pub prompt_flag: Option<String>,
53 pub output_format: OutputFormat,
55}
56
57impl CliBackend {
58 pub fn from_config(config: &CliConfig) -> Result<Self, CustomBackendError> {
63 match config.backend.as_str() {
64 "claude" => Ok(Self::claude()),
65 "kiro" => Ok(Self::kiro()),
66 "gemini" => Ok(Self::gemini()),
67 "codex" => Ok(Self::codex()),
68 "amp" => Ok(Self::amp()),
69 "copilot" => Ok(Self::copilot()),
70 "opencode" => Ok(Self::opencode()),
71 "custom" => Self::custom(config),
72 _ => Ok(Self::claude()), }
74 }
75
76 pub fn claude() -> Self {
85 Self {
86 command: "claude".to_string(),
87 args: vec![
88 "--dangerously-skip-permissions".to_string(),
89 "--verbose".to_string(),
90 "--output-format".to_string(),
91 "stream-json".to_string(),
92 ],
93 prompt_mode: PromptMode::Arg,
94 prompt_flag: Some("-p".to_string()),
95 output_format: OutputFormat::StreamJson,
96 }
97 }
98
99 pub fn claude_interactive() -> Self {
107 Self {
108 command: "claude".to_string(),
109 args: vec!["--dangerously-skip-permissions".to_string()],
110 prompt_mode: PromptMode::Arg,
111 prompt_flag: None,
112 output_format: OutputFormat::Text,
113 }
114 }
115
116 pub fn kiro() -> Self {
120 Self {
121 command: "kiro-cli".to_string(),
122 args: vec![
123 "chat".to_string(),
124 "--no-interactive".to_string(),
125 "--trust-all-tools".to_string(),
126 ],
127 prompt_mode: PromptMode::Arg,
128 prompt_flag: None,
129 output_format: OutputFormat::Text,
130 }
131 }
132
133 pub fn kiro_with_agent(agent: String) -> Self {
137 Self {
138 command: "kiro-cli".to_string(),
139 args: vec![
140 "chat".to_string(),
141 "--no-interactive".to_string(),
142 "--trust-all-tools".to_string(),
143 "--agent".to_string(),
144 agent,
145 ],
146 prompt_mode: PromptMode::Arg,
147 prompt_flag: None,
148 output_format: OutputFormat::Text,
149 }
150 }
151
152 pub fn from_name(name: &str) -> Result<Self, CustomBackendError> {
157 match name {
158 "claude" => Ok(Self::claude()),
159 "kiro" => Ok(Self::kiro()),
160 "gemini" => Ok(Self::gemini()),
161 "codex" => Ok(Self::codex()),
162 "amp" => Ok(Self::amp()),
163 "copilot" => Ok(Self::copilot()),
164 "opencode" => Ok(Self::opencode()),
165 _ => Err(CustomBackendError),
166 }
167 }
168
169 pub fn from_hat_backend(hat_backend: &HatBackend) -> Result<Self, CustomBackendError> {
174 match hat_backend {
175 HatBackend::Named(name) => Self::from_name(name),
176 HatBackend::KiroAgent { agent, .. } => Ok(Self::kiro_with_agent(agent.clone())),
177 HatBackend::Custom { command, args } => Ok(Self {
178 command: command.clone(),
179 args: args.clone(),
180 prompt_mode: PromptMode::Arg,
181 prompt_flag: None,
182 output_format: OutputFormat::Text,
183 }),
184 }
185 }
186
187 pub fn gemini() -> Self {
189 Self {
190 command: "gemini".to_string(),
191 args: vec!["--yolo".to_string()],
192 prompt_mode: PromptMode::Arg,
193 prompt_flag: Some("-p".to_string()),
194 output_format: OutputFormat::Text,
195 }
196 }
197
198 pub fn codex() -> Self {
200 Self {
201 command: "codex".to_string(),
202 args: vec!["exec".to_string(), "--full-auto".to_string()],
203 prompt_mode: PromptMode::Arg,
204 prompt_flag: None, output_format: OutputFormat::Text,
206 }
207 }
208
209 pub fn amp() -> Self {
211 Self {
212 command: "amp".to_string(),
213 args: vec!["--dangerously-allow-all".to_string()],
214 prompt_mode: PromptMode::Arg,
215 prompt_flag: Some("-x".to_string()),
216 output_format: OutputFormat::Text,
217 }
218 }
219
220 pub fn copilot() -> Self {
225 Self {
226 command: "copilot".to_string(),
227 args: vec!["--allow-all-tools".to_string()],
228 prompt_mode: PromptMode::Arg,
229 prompt_flag: Some("-p".to_string()),
230 output_format: OutputFormat::Text,
231 }
232 }
233
234 pub fn copilot_tui() -> Self {
240 Self {
241 command: "copilot".to_string(),
242 args: vec![], prompt_mode: PromptMode::Arg,
244 prompt_flag: None, output_format: OutputFormat::Text,
246 }
247 }
248
249 pub fn for_interactive_prompt(backend_name: &str) -> Result<Self, CustomBackendError> {
268 match backend_name {
269 "claude" => Ok(Self::claude_interactive()),
270 "kiro" => Ok(Self::kiro_interactive()),
271 "gemini" => Ok(Self::gemini_interactive()),
272 "codex" => Ok(Self::codex_interactive()),
273 "amp" => Ok(Self::amp_interactive()),
274 "copilot" => Ok(Self::copilot_interactive()),
275 "opencode" => Ok(Self::opencode_interactive()),
276 _ => Err(CustomBackendError),
277 }
278 }
279
280 pub fn kiro_interactive() -> Self {
285 Self {
286 command: "kiro-cli".to_string(),
287 args: vec!["chat".to_string(), "--trust-all-tools".to_string()],
288 prompt_mode: PromptMode::Arg,
289 prompt_flag: None,
290 output_format: OutputFormat::Text,
291 }
292 }
293
294 pub fn gemini_interactive() -> Self {
299 Self {
300 command: "gemini".to_string(),
301 args: vec!["--yolo".to_string()],
302 prompt_mode: PromptMode::Arg,
303 prompt_flag: Some("-i".to_string()), output_format: OutputFormat::Text,
305 }
306 }
307
308 pub fn codex_interactive() -> Self {
313 Self {
314 command: "codex".to_string(),
315 args: vec![], prompt_mode: PromptMode::Arg,
317 prompt_flag: None, output_format: OutputFormat::Text,
319 }
320 }
321
322 pub fn amp_interactive() -> Self {
327 Self {
328 command: "amp".to_string(),
329 args: vec![],
330 prompt_mode: PromptMode::Arg,
331 prompt_flag: Some("-x".to_string()),
332 output_format: OutputFormat::Text,
333 }
334 }
335
336 pub fn copilot_interactive() -> Self {
341 Self {
342 command: "copilot".to_string(),
343 args: vec![],
344 prompt_mode: PromptMode::Arg,
345 prompt_flag: Some("-p".to_string()),
346 output_format: OutputFormat::Text,
347 }
348 }
349
350 pub fn opencode() -> Self {
360 Self {
361 command: "opencode".to_string(),
362 args: vec!["run".to_string()],
363 prompt_mode: PromptMode::Arg,
364 prompt_flag: None, output_format: OutputFormat::Text,
366 }
367 }
368
369 pub fn opencode_tui() -> Self {
377 Self {
378 command: "opencode".to_string(),
379 args: vec!["run".to_string()],
380 prompt_mode: PromptMode::Arg,
381 prompt_flag: None, output_format: OutputFormat::Text,
383 }
384 }
385
386 pub fn opencode_interactive() -> Self {
394 Self {
395 command: "opencode".to_string(),
396 args: vec!["run".to_string()],
397 prompt_mode: PromptMode::Arg,
398 prompt_flag: None, output_format: OutputFormat::Text,
400 }
401 }
402
403 pub fn custom(config: &CliConfig) -> Result<Self, CustomBackendError> {
408 let command = config.command.clone().ok_or(CustomBackendError)?;
409 let prompt_mode = if config.prompt_mode == "stdin" {
410 PromptMode::Stdin
411 } else {
412 PromptMode::Arg
413 };
414
415 Ok(Self {
416 command,
417 args: config.args.clone(),
418 prompt_mode,
419 prompt_flag: config.prompt_flag.clone(),
420 output_format: OutputFormat::Text,
421 })
422 }
423
424 pub fn build_command(
430 &self,
431 prompt: &str,
432 interactive: bool,
433 ) -> (String, Vec<String>, Option<String>, Option<NamedTempFile>) {
434 let mut args = self.args.clone();
435
436 if interactive {
438 args = self.filter_args_for_interactive(args);
439 }
440
441 let (stdin_input, temp_file) = match self.prompt_mode {
443 PromptMode::Arg => {
444 let (prompt_text, temp_file) = if self.command == "claude" && prompt.len() > 7000 {
445 match NamedTempFile::new() {
447 Ok(mut file) => {
448 if let Err(e) = file.write_all(prompt.as_bytes()) {
449 tracing::warn!("Failed to write prompt to temp file: {}", e);
450 (prompt.to_string(), None)
451 } else {
452 let path = file.path().display().to_string();
453 (
454 format!("Please read and execute the task in {}", path),
455 Some(file),
456 )
457 }
458 }
459 Err(e) => {
460 tracing::warn!("Failed to create temp file: {}", e);
461 (prompt.to_string(), None)
462 }
463 }
464 } else {
465 (prompt.to_string(), None)
466 };
467
468 if let Some(ref flag) = self.prompt_flag {
469 args.push(flag.clone());
470 }
471 args.push(prompt_text);
472 (None, temp_file)
473 }
474 PromptMode::Stdin => (Some(prompt.to_string()), None),
475 };
476
477 tracing::debug!(
479 command = %self.command,
480 args_count = args.len(),
481 prompt_len = prompt.len(),
482 interactive = interactive,
483 uses_stdin = stdin_input.is_some(),
484 uses_temp_file = temp_file.is_some(),
485 "Built CLI command"
486 );
487 tracing::trace!(prompt = %prompt, "Full prompt content");
489
490 (self.command.clone(), args, stdin_input, temp_file)
491 }
492
493 fn filter_args_for_interactive(&self, args: Vec<String>) -> Vec<String> {
495 match self.command.as_str() {
496 "kiro-cli" => args
497 .into_iter()
498 .filter(|a| a != "--no-interactive")
499 .collect(),
500 "codex" => args.into_iter().filter(|a| a != "--full-auto").collect(),
501 "amp" => args
502 .into_iter()
503 .filter(|a| a != "--dangerously-allow-all")
504 .collect(),
505 "copilot" => args
506 .into_iter()
507 .filter(|a| a != "--allow-all-tools")
508 .collect(),
509 _ => args, }
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517
518 #[test]
519 fn test_claude_backend() {
520 let backend = CliBackend::claude();
521 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
522
523 assert_eq!(cmd, "claude");
524 assert_eq!(
525 args,
526 vec![
527 "--dangerously-skip-permissions",
528 "--verbose",
529 "--output-format",
530 "stream-json",
531 "-p",
532 "test prompt"
533 ]
534 );
535 assert!(stdin.is_none()); assert_eq!(backend.output_format, OutputFormat::StreamJson);
537 }
538
539 #[test]
540 fn test_claude_interactive_backend() {
541 let backend = CliBackend::claude_interactive();
542 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
543
544 assert_eq!(cmd, "claude");
545 assert_eq!(args, vec!["--dangerously-skip-permissions", "test prompt"]);
548 assert!(stdin.is_none()); assert_eq!(backend.output_format, OutputFormat::Text);
550 assert_eq!(backend.prompt_flag, None);
551 }
552
553 #[test]
554 fn test_claude_large_prompt_uses_temp_file() {
555 let backend = CliBackend::claude();
557 let large_prompt = "x".repeat(7001);
558 let (cmd, args, _stdin, temp) = backend.build_command(&large_prompt, false);
559
560 assert_eq!(cmd, "claude");
561 assert!(temp.is_some());
563 assert!(args.iter().any(|a| a.contains("Please read and execute")));
565 }
566
567 #[test]
568 fn test_non_claude_large_prompt() {
569 let backend = CliBackend::kiro();
570 let large_prompt = "x".repeat(7001);
571 let (cmd, args, stdin, temp) = backend.build_command(&large_prompt, false);
572
573 assert_eq!(cmd, "kiro-cli");
574 assert_eq!(args[3], large_prompt);
575 assert!(stdin.is_none());
576 assert!(temp.is_none());
577 }
578
579 #[test]
580 fn test_kiro_backend() {
581 let backend = CliBackend::kiro();
582 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
583
584 assert_eq!(cmd, "kiro-cli");
585 assert_eq!(
586 args,
587 vec![
588 "chat",
589 "--no-interactive",
590 "--trust-all-tools",
591 "test prompt"
592 ]
593 );
594 assert!(stdin.is_none());
595 }
596
597 #[test]
598 fn test_gemini_backend() {
599 let backend = CliBackend::gemini();
600 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
601
602 assert_eq!(cmd, "gemini");
603 assert_eq!(args, vec!["--yolo", "-p", "test prompt"]);
604 assert!(stdin.is_none());
605 }
606
607 #[test]
608 fn test_codex_backend() {
609 let backend = CliBackend::codex();
610 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
611
612 assert_eq!(cmd, "codex");
613 assert_eq!(args, vec!["exec", "--full-auto", "test prompt"]);
614 assert!(stdin.is_none());
615 }
616
617 #[test]
618 fn test_amp_backend() {
619 let backend = CliBackend::amp();
620 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
621
622 assert_eq!(cmd, "amp");
623 assert_eq!(args, vec!["--dangerously-allow-all", "-x", "test prompt"]);
624 assert!(stdin.is_none());
625 }
626
627 #[test]
628 fn test_copilot_backend() {
629 let backend = CliBackend::copilot();
630 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
631
632 assert_eq!(cmd, "copilot");
633 assert_eq!(args, vec!["--allow-all-tools", "-p", "test prompt"]);
634 assert!(stdin.is_none());
635 assert_eq!(backend.output_format, OutputFormat::Text);
636 }
637
638 #[test]
639 fn test_copilot_tui_backend() {
640 let backend = CliBackend::copilot_tui();
641 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
642
643 assert_eq!(cmd, "copilot");
644 assert_eq!(args, vec!["test prompt"]);
646 assert!(stdin.is_none());
647 assert_eq!(backend.output_format, OutputFormat::Text);
648 assert_eq!(backend.prompt_flag, None);
649 }
650
651 #[test]
652 fn test_from_config() {
653 let config = CliConfig {
655 backend: "claude".to_string(),
656 command: None,
657 prompt_mode: "arg".to_string(),
658 ..Default::default()
659 };
660 let backend = CliBackend::from_config(&config).unwrap();
661
662 assert_eq!(backend.command, "claude");
663 assert_eq!(backend.prompt_mode, PromptMode::Arg);
664 assert_eq!(backend.prompt_flag, Some("-p".to_string()));
665 }
666
667 #[test]
668 fn test_kiro_interactive_mode_omits_no_interactive_flag() {
669 let backend = CliBackend::kiro();
670 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
671
672 assert_eq!(cmd, "kiro-cli");
673 assert_eq!(args, vec!["chat", "--trust-all-tools", "test prompt"]);
674 assert!(stdin.is_none());
675 assert!(!args.contains(&"--no-interactive".to_string()));
676 }
677
678 #[test]
679 fn test_codex_interactive_mode_omits_full_auto() {
680 let backend = CliBackend::codex();
681 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
682
683 assert_eq!(cmd, "codex");
684 assert_eq!(args, vec!["exec", "test prompt"]);
685 assert!(stdin.is_none());
686 assert!(!args.contains(&"--full-auto".to_string()));
687 }
688
689 #[test]
690 fn test_amp_interactive_mode_no_flags() {
691 let backend = CliBackend::amp();
692 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
693
694 assert_eq!(cmd, "amp");
695 assert_eq!(args, vec!["-x", "test prompt"]);
696 assert!(stdin.is_none());
697 assert!(!args.contains(&"--dangerously-allow-all".to_string()));
698 }
699
700 #[test]
701 fn test_copilot_interactive_mode_omits_allow_all_tools() {
702 let backend = CliBackend::copilot();
703 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
704
705 assert_eq!(cmd, "copilot");
706 assert_eq!(args, vec!["-p", "test prompt"]);
707 assert!(stdin.is_none());
708 assert!(!args.contains(&"--allow-all-tools".to_string()));
709 }
710
711 #[test]
712 fn test_claude_interactive_mode_unchanged() {
713 let backend = CliBackend::claude();
714 let (cmd, args_auto, stdin_auto, _) = backend.build_command("test prompt", false);
715 let (_, args_interactive, stdin_interactive, _) =
716 backend.build_command("test prompt", true);
717
718 assert_eq!(cmd, "claude");
719 assert_eq!(args_auto, args_interactive);
720 assert_eq!(
721 args_auto,
722 vec![
723 "--dangerously-skip-permissions",
724 "--verbose",
725 "--output-format",
726 "stream-json",
727 "-p",
728 "test prompt"
729 ]
730 );
731 assert!(stdin_auto.is_none());
733 assert!(stdin_interactive.is_none());
734 }
735
736 #[test]
737 fn test_gemini_interactive_mode_unchanged() {
738 let backend = CliBackend::gemini();
739 let (cmd, args_auto, stdin_auto, _) = backend.build_command("test prompt", false);
740 let (_, args_interactive, stdin_interactive, _) =
741 backend.build_command("test prompt", true);
742
743 assert_eq!(cmd, "gemini");
744 assert_eq!(args_auto, args_interactive);
745 assert_eq!(args_auto, vec!["--yolo", "-p", "test prompt"]);
746 assert_eq!(stdin_auto, stdin_interactive);
747 assert!(stdin_auto.is_none());
748 }
749
750 #[test]
751 fn test_custom_backend_with_prompt_flag_short() {
752 let config = CliConfig {
753 backend: "custom".to_string(),
754 command: Some("my-agent".to_string()),
755 prompt_mode: "arg".to_string(),
756 prompt_flag: Some("-p".to_string()),
757 ..Default::default()
758 };
759 let backend = CliBackend::from_config(&config).unwrap();
760 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
761
762 assert_eq!(cmd, "my-agent");
763 assert_eq!(args, vec!["-p", "test prompt"]);
764 assert!(stdin.is_none());
765 }
766
767 #[test]
768 fn test_custom_backend_with_prompt_flag_long() {
769 let config = CliConfig {
770 backend: "custom".to_string(),
771 command: Some("my-agent".to_string()),
772 prompt_mode: "arg".to_string(),
773 prompt_flag: Some("--prompt".to_string()),
774 ..Default::default()
775 };
776 let backend = CliBackend::from_config(&config).unwrap();
777 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
778
779 assert_eq!(cmd, "my-agent");
780 assert_eq!(args, vec!["--prompt", "test prompt"]);
781 assert!(stdin.is_none());
782 }
783
784 #[test]
785 fn test_custom_backend_without_prompt_flag_positional() {
786 let config = CliConfig {
787 backend: "custom".to_string(),
788 command: Some("my-agent".to_string()),
789 prompt_mode: "arg".to_string(),
790 prompt_flag: None,
791 ..Default::default()
792 };
793 let backend = CliBackend::from_config(&config).unwrap();
794 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
795
796 assert_eq!(cmd, "my-agent");
797 assert_eq!(args, vec!["test prompt"]);
798 assert!(stdin.is_none());
799 }
800
801 #[test]
802 fn test_custom_backend_without_command_returns_error() {
803 let config = CliConfig {
804 backend: "custom".to_string(),
805 command: None,
806 prompt_mode: "arg".to_string(),
807 ..Default::default()
808 };
809 let result = CliBackend::from_config(&config);
810
811 assert!(result.is_err());
812 let err = result.unwrap_err();
813 assert_eq!(
814 err.to_string(),
815 "custom backend requires a command to be specified"
816 );
817 }
818
819 #[test]
820 fn test_kiro_with_agent() {
821 let backend = CliBackend::kiro_with_agent("my-agent".to_string());
822 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
823
824 assert_eq!(cmd, "kiro-cli");
825 assert_eq!(
826 args,
827 vec![
828 "chat",
829 "--no-interactive",
830 "--trust-all-tools",
831 "--agent",
832 "my-agent",
833 "test prompt"
834 ]
835 );
836 assert!(stdin.is_none());
837 }
838
839 #[test]
840 fn test_from_name_claude() {
841 let backend = CliBackend::from_name("claude").unwrap();
842 assert_eq!(backend.command, "claude");
843 assert_eq!(backend.prompt_flag, Some("-p".to_string()));
844 }
845
846 #[test]
847 fn test_from_name_kiro() {
848 let backend = CliBackend::from_name("kiro").unwrap();
849 assert_eq!(backend.command, "kiro-cli");
850 }
851
852 #[test]
853 fn test_from_name_gemini() {
854 let backend = CliBackend::from_name("gemini").unwrap();
855 assert_eq!(backend.command, "gemini");
856 }
857
858 #[test]
859 fn test_from_name_codex() {
860 let backend = CliBackend::from_name("codex").unwrap();
861 assert_eq!(backend.command, "codex");
862 }
863
864 #[test]
865 fn test_from_name_amp() {
866 let backend = CliBackend::from_name("amp").unwrap();
867 assert_eq!(backend.command, "amp");
868 }
869
870 #[test]
871 fn test_from_name_copilot() {
872 let backend = CliBackend::from_name("copilot").unwrap();
873 assert_eq!(backend.command, "copilot");
874 assert_eq!(backend.prompt_flag, Some("-p".to_string()));
875 }
876
877 #[test]
878 fn test_from_name_invalid() {
879 let result = CliBackend::from_name("invalid");
880 assert!(result.is_err());
881 }
882
883 #[test]
884 fn test_from_hat_backend_named() {
885 let hat_backend = HatBackend::Named("claude".to_string());
886 let backend = CliBackend::from_hat_backend(&hat_backend).unwrap();
887 assert_eq!(backend.command, "claude");
888 }
889
890 #[test]
891 fn test_from_hat_backend_kiro_agent() {
892 let hat_backend = HatBackend::KiroAgent {
893 backend_type: "kiro".to_string(),
894 agent: "my-agent".to_string(),
895 };
896 let backend = CliBackend::from_hat_backend(&hat_backend).unwrap();
897 let (cmd, args, _, _) = backend.build_command("test", false);
898 assert_eq!(cmd, "kiro-cli");
899 assert!(args.contains(&"--agent".to_string()));
900 assert!(args.contains(&"my-agent".to_string()));
901 }
902
903 #[test]
904 fn test_from_hat_backend_custom() {
905 let hat_backend = HatBackend::Custom {
906 command: "my-cli".to_string(),
907 args: vec!["--flag".to_string()],
908 };
909 let backend = CliBackend::from_hat_backend(&hat_backend).unwrap();
910 assert_eq!(backend.command, "my-cli");
911 assert_eq!(backend.args, vec!["--flag"]);
912 }
913
914 #[test]
919 fn test_for_interactive_prompt_claude() {
920 let backend = CliBackend::for_interactive_prompt("claude").unwrap();
921 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
922
923 assert_eq!(cmd, "claude");
924 assert_eq!(args, vec!["--dangerously-skip-permissions", "test prompt"]);
926 assert!(stdin.is_none());
927 assert_eq!(backend.prompt_flag, None);
928 }
929
930 #[test]
931 fn test_for_interactive_prompt_kiro() {
932 let backend = CliBackend::for_interactive_prompt("kiro").unwrap();
933 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
934
935 assert_eq!(cmd, "kiro-cli");
936 assert_eq!(args, vec!["chat", "--trust-all-tools", "test prompt"]);
938 assert!(!args.contains(&"--no-interactive".to_string()));
939 assert!(stdin.is_none());
940 }
941
942 #[test]
943 fn test_for_interactive_prompt_gemini() {
944 let backend = CliBackend::for_interactive_prompt("gemini").unwrap();
945 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
946
947 assert_eq!(cmd, "gemini");
948 assert_eq!(args, vec!["--yolo", "-i", "test prompt"]);
950 assert_eq!(backend.prompt_flag, Some("-i".to_string()));
951 assert!(stdin.is_none());
952 }
953
954 #[test]
955 fn test_for_interactive_prompt_codex() {
956 let backend = CliBackend::for_interactive_prompt("codex").unwrap();
957 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
958
959 assert_eq!(cmd, "codex");
960 assert_eq!(args, vec!["test prompt"]);
962 assert!(!args.contains(&"exec".to_string()));
963 assert!(!args.contains(&"--full-auto".to_string()));
964 assert!(stdin.is_none());
965 }
966
967 #[test]
968 fn test_for_interactive_prompt_amp() {
969 let backend = CliBackend::for_interactive_prompt("amp").unwrap();
970 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
971
972 assert_eq!(cmd, "amp");
973 assert_eq!(args, vec!["-x", "test prompt"]);
975 assert!(!args.contains(&"--dangerously-allow-all".to_string()));
976 assert!(stdin.is_none());
977 }
978
979 #[test]
980 fn test_for_interactive_prompt_copilot() {
981 let backend = CliBackend::for_interactive_prompt("copilot").unwrap();
982 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
983
984 assert_eq!(cmd, "copilot");
985 assert_eq!(args, vec!["-p", "test prompt"]);
987 assert!(!args.contains(&"--allow-all-tools".to_string()));
988 assert!(stdin.is_none());
989 }
990
991 #[test]
992 fn test_for_interactive_prompt_invalid() {
993 let result = CliBackend::for_interactive_prompt("invalid_backend");
994 assert!(result.is_err());
995 }
996
997 #[test]
1002 fn test_opencode_backend() {
1003 let backend = CliBackend::opencode();
1004 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
1005
1006 assert_eq!(cmd, "opencode");
1007 assert_eq!(args, vec!["run", "test prompt"]);
1009 assert!(stdin.is_none());
1010 assert_eq!(backend.output_format, OutputFormat::Text);
1011 assert_eq!(backend.prompt_flag, None);
1012 }
1013
1014 #[test]
1015 fn test_opencode_tui_backend() {
1016 let backend = CliBackend::opencode_tui();
1017 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
1018
1019 assert_eq!(cmd, "opencode");
1020 assert_eq!(args, vec!["run", "test prompt"]);
1022 assert!(stdin.is_none());
1023 assert_eq!(backend.output_format, OutputFormat::Text);
1024 assert_eq!(backend.prompt_flag, None);
1025 }
1026
1027 #[test]
1028 fn test_opencode_interactive_mode_unchanged() {
1029 let backend = CliBackend::opencode();
1031 let (cmd, args_auto, stdin_auto, _) = backend.build_command("test prompt", false);
1032 let (_, args_interactive, stdin_interactive, _) =
1033 backend.build_command("test prompt", true);
1034
1035 assert_eq!(cmd, "opencode");
1036 assert_eq!(args_auto, args_interactive);
1038 assert_eq!(args_auto, vec!["run", "test prompt"]);
1039 assert!(stdin_auto.is_none());
1040 assert!(stdin_interactive.is_none());
1041 }
1042
1043 #[test]
1044 fn test_from_name_opencode() {
1045 let backend = CliBackend::from_name("opencode").unwrap();
1046 assert_eq!(backend.command, "opencode");
1047 assert_eq!(backend.prompt_flag, None); }
1049
1050 #[test]
1051 fn test_for_interactive_prompt_opencode() {
1052 let backend = CliBackend::for_interactive_prompt("opencode").unwrap();
1053 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
1054
1055 assert_eq!(cmd, "opencode");
1056 assert_eq!(args, vec!["run", "test prompt"]);
1058 assert!(stdin.is_none());
1059 assert_eq!(backend.prompt_flag, None);
1060 }
1061}