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_tui() -> Self {
110 Self {
111 command: "claude".to_string(),
112 args: vec!["--dangerously-skip-permissions".to_string()],
113 prompt_mode: PromptMode::Arg,
114 prompt_flag: None, output_format: OutputFormat::Text, }
117 }
118
119 pub fn kiro() -> Self {
123 Self {
124 command: "kiro-cli".to_string(),
125 args: vec![
126 "chat".to_string(),
127 "--no-interactive".to_string(),
128 "--trust-all-tools".to_string(),
129 ],
130 prompt_mode: PromptMode::Arg,
131 prompt_flag: None,
132 output_format: OutputFormat::Text,
133 }
134 }
135
136 pub fn kiro_with_agent(agent: String) -> Self {
140 Self {
141 command: "kiro-cli".to_string(),
142 args: vec![
143 "chat".to_string(),
144 "--no-interactive".to_string(),
145 "--trust-all-tools".to_string(),
146 "--agent".to_string(),
147 agent,
148 ],
149 prompt_mode: PromptMode::Arg,
150 prompt_flag: None,
151 output_format: OutputFormat::Text,
152 }
153 }
154
155 pub fn from_name(name: &str) -> Result<Self, CustomBackendError> {
160 match name {
161 "claude" => Ok(Self::claude()),
162 "kiro" => Ok(Self::kiro()),
163 "gemini" => Ok(Self::gemini()),
164 "codex" => Ok(Self::codex()),
165 "amp" => Ok(Self::amp()),
166 "copilot" => Ok(Self::copilot()),
167 "opencode" => Ok(Self::opencode()),
168 _ => Err(CustomBackendError),
169 }
170 }
171
172 pub fn from_hat_backend(hat_backend: &HatBackend) -> Result<Self, CustomBackendError> {
177 match hat_backend {
178 HatBackend::Named(name) => Self::from_name(name),
179 HatBackend::KiroAgent { agent, .. } => Ok(Self::kiro_with_agent(agent.clone())),
180 HatBackend::Custom { command, args } => Ok(Self {
181 command: command.clone(),
182 args: args.clone(),
183 prompt_mode: PromptMode::Arg,
184 prompt_flag: None,
185 output_format: OutputFormat::Text,
186 }),
187 }
188 }
189
190 pub fn gemini() -> Self {
192 Self {
193 command: "gemini".to_string(),
194 args: vec!["--yolo".to_string()],
195 prompt_mode: PromptMode::Arg,
196 prompt_flag: Some("-p".to_string()),
197 output_format: OutputFormat::Text,
198 }
199 }
200
201 pub fn codex() -> Self {
203 Self {
204 command: "codex".to_string(),
205 args: vec!["exec".to_string(), "--full-auto".to_string()],
206 prompt_mode: PromptMode::Arg,
207 prompt_flag: None, output_format: OutputFormat::Text,
209 }
210 }
211
212 pub fn amp() -> Self {
214 Self {
215 command: "amp".to_string(),
216 args: vec!["--dangerously-allow-all".to_string()],
217 prompt_mode: PromptMode::Arg,
218 prompt_flag: Some("-x".to_string()),
219 output_format: OutputFormat::Text,
220 }
221 }
222
223 pub fn copilot() -> Self {
228 Self {
229 command: "copilot".to_string(),
230 args: vec!["--allow-all-tools".to_string()],
231 prompt_mode: PromptMode::Arg,
232 prompt_flag: Some("-p".to_string()),
233 output_format: OutputFormat::Text,
234 }
235 }
236
237 pub fn copilot_tui() -> Self {
243 Self {
244 command: "copilot".to_string(),
245 args: vec![], prompt_mode: PromptMode::Arg,
247 prompt_flag: None, output_format: OutputFormat::Text,
249 }
250 }
251
252 pub fn for_interactive_prompt(backend_name: &str) -> Result<Self, CustomBackendError> {
271 match backend_name {
272 "claude" => Ok(Self::claude_tui()),
273 "kiro" => Ok(Self::kiro_interactive()),
274 "gemini" => Ok(Self::gemini_interactive()),
275 "codex" => Ok(Self::codex_interactive()),
276 "amp" => Ok(Self::amp_interactive()),
277 "copilot" => Ok(Self::copilot_interactive()),
278 "opencode" => Ok(Self::opencode_interactive()),
279 _ => Err(CustomBackendError),
280 }
281 }
282
283 pub fn kiro_interactive() -> Self {
288 Self {
289 command: "kiro-cli".to_string(),
290 args: vec!["chat".to_string(), "--trust-all-tools".to_string()],
291 prompt_mode: PromptMode::Arg,
292 prompt_flag: None,
293 output_format: OutputFormat::Text,
294 }
295 }
296
297 pub fn gemini_interactive() -> Self {
302 Self {
303 command: "gemini".to_string(),
304 args: vec!["--yolo".to_string()],
305 prompt_mode: PromptMode::Arg,
306 prompt_flag: Some("-i".to_string()), output_format: OutputFormat::Text,
308 }
309 }
310
311 pub fn codex_interactive() -> Self {
316 Self {
317 command: "codex".to_string(),
318 args: vec![], prompt_mode: PromptMode::Arg,
320 prompt_flag: None, output_format: OutputFormat::Text,
322 }
323 }
324
325 pub fn amp_interactive() -> Self {
330 Self {
331 command: "amp".to_string(),
332 args: vec![],
333 prompt_mode: PromptMode::Arg,
334 prompt_flag: Some("-x".to_string()),
335 output_format: OutputFormat::Text,
336 }
337 }
338
339 pub fn copilot_interactive() -> Self {
344 Self {
345 command: "copilot".to_string(),
346 args: vec![],
347 prompt_mode: PromptMode::Arg,
348 prompt_flag: Some("-p".to_string()),
349 output_format: OutputFormat::Text,
350 }
351 }
352
353 pub fn opencode() -> Self {
363 Self {
364 command: "opencode".to_string(),
365 args: vec!["run".to_string()],
366 prompt_mode: PromptMode::Arg,
367 prompt_flag: None, output_format: OutputFormat::Text,
369 }
370 }
371
372 pub fn opencode_tui() -> Self {
380 Self {
381 command: "opencode".to_string(),
382 args: vec!["run".to_string()],
383 prompt_mode: PromptMode::Arg,
384 prompt_flag: None, output_format: OutputFormat::Text,
386 }
387 }
388
389 pub fn opencode_interactive() -> Self {
397 Self {
398 command: "opencode".to_string(),
399 args: vec!["run".to_string()],
400 prompt_mode: PromptMode::Arg,
401 prompt_flag: None, output_format: OutputFormat::Text,
403 }
404 }
405
406 pub fn custom(config: &CliConfig) -> Result<Self, CustomBackendError> {
411 let command = config.command.clone().ok_or(CustomBackendError)?;
412 let prompt_mode = if config.prompt_mode == "stdin" {
413 PromptMode::Stdin
414 } else {
415 PromptMode::Arg
416 };
417
418 Ok(Self {
419 command,
420 args: config.args.clone(),
421 prompt_mode,
422 prompt_flag: config.prompt_flag.clone(),
423 output_format: OutputFormat::Text,
424 })
425 }
426
427 pub fn build_command(
433 &self,
434 prompt: &str,
435 interactive: bool,
436 ) -> (String, Vec<String>, Option<String>, Option<NamedTempFile>) {
437 let mut args = self.args.clone();
438
439 if interactive {
441 args = self.filter_args_for_interactive(args);
442 }
443
444 let (stdin_input, temp_file) = match self.prompt_mode {
446 PromptMode::Arg => {
447 let (prompt_text, temp_file) = if self.command == "claude" && prompt.len() > 7000 {
448 match NamedTempFile::new() {
450 Ok(mut file) => {
451 if let Err(e) = file.write_all(prompt.as_bytes()) {
452 tracing::warn!("Failed to write prompt to temp file: {}", e);
453 (prompt.to_string(), None)
454 } else {
455 let path = file.path().display().to_string();
456 (
457 format!("Please read and execute the task in {}", path),
458 Some(file),
459 )
460 }
461 }
462 Err(e) => {
463 tracing::warn!("Failed to create temp file: {}", e);
464 (prompt.to_string(), None)
465 }
466 }
467 } else {
468 (prompt.to_string(), None)
469 };
470
471 if let Some(ref flag) = self.prompt_flag {
472 args.push(flag.clone());
473 }
474 args.push(prompt_text);
475 (None, temp_file)
476 }
477 PromptMode::Stdin => (Some(prompt.to_string()), None),
478 };
479
480 tracing::debug!(
482 command = %self.command,
483 args_count = args.len(),
484 prompt_len = prompt.len(),
485 interactive = interactive,
486 uses_stdin = stdin_input.is_some(),
487 uses_temp_file = temp_file.is_some(),
488 "Built CLI command"
489 );
490 tracing::trace!(prompt = %prompt, "Full prompt content");
492
493 (self.command.clone(), args, stdin_input, temp_file)
494 }
495
496 fn filter_args_for_interactive(&self, args: Vec<String>) -> Vec<String> {
498 match self.command.as_str() {
499 "kiro-cli" => args
500 .into_iter()
501 .filter(|a| a != "--no-interactive")
502 .collect(),
503 "codex" => args.into_iter().filter(|a| a != "--full-auto").collect(),
504 "amp" => args
505 .into_iter()
506 .filter(|a| a != "--dangerously-allow-all")
507 .collect(),
508 "copilot" => args
509 .into_iter()
510 .filter(|a| a != "--allow-all-tools")
511 .collect(),
512 _ => args, }
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 #[test]
522 fn test_claude_backend() {
523 let backend = CliBackend::claude();
524 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
525
526 assert_eq!(cmd, "claude");
527 assert_eq!(
528 args,
529 vec![
530 "--dangerously-skip-permissions",
531 "--verbose",
532 "--output-format",
533 "stream-json",
534 "-p",
535 "test prompt"
536 ]
537 );
538 assert!(stdin.is_none()); assert_eq!(backend.output_format, OutputFormat::StreamJson);
540 }
541
542 #[test]
543 fn test_claude_tui_backend() {
544 let backend = CliBackend::claude_tui();
545 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
546
547 assert_eq!(cmd, "claude");
548 assert_eq!(args, vec!["--dangerously-skip-permissions", "test prompt"]);
551 assert!(stdin.is_none()); assert_eq!(backend.output_format, OutputFormat::Text);
553 assert_eq!(backend.prompt_flag, None);
554 }
555
556 #[test]
557 fn test_claude_large_prompt_uses_temp_file() {
558 let backend = CliBackend::claude();
560 let large_prompt = "x".repeat(7001);
561 let (cmd, args, _stdin, temp) = backend.build_command(&large_prompt, false);
562
563 assert_eq!(cmd, "claude");
564 assert!(temp.is_some());
566 assert!(args.iter().any(|a| a.contains("Please read and execute")));
568 }
569
570 #[test]
571 fn test_non_claude_large_prompt() {
572 let backend = CliBackend::kiro();
573 let large_prompt = "x".repeat(7001);
574 let (cmd, args, stdin, temp) = backend.build_command(&large_prompt, false);
575
576 assert_eq!(cmd, "kiro-cli");
577 assert_eq!(args[3], large_prompt);
578 assert!(stdin.is_none());
579 assert!(temp.is_none());
580 }
581
582 #[test]
583 fn test_kiro_backend() {
584 let backend = CliBackend::kiro();
585 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
586
587 assert_eq!(cmd, "kiro-cli");
588 assert_eq!(
589 args,
590 vec![
591 "chat",
592 "--no-interactive",
593 "--trust-all-tools",
594 "test prompt"
595 ]
596 );
597 assert!(stdin.is_none());
598 }
599
600 #[test]
601 fn test_gemini_backend() {
602 let backend = CliBackend::gemini();
603 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
604
605 assert_eq!(cmd, "gemini");
606 assert_eq!(args, vec!["--yolo", "-p", "test prompt"]);
607 assert!(stdin.is_none());
608 }
609
610 #[test]
611 fn test_codex_backend() {
612 let backend = CliBackend::codex();
613 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
614
615 assert_eq!(cmd, "codex");
616 assert_eq!(args, vec!["exec", "--full-auto", "test prompt"]);
617 assert!(stdin.is_none());
618 }
619
620 #[test]
621 fn test_amp_backend() {
622 let backend = CliBackend::amp();
623 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
624
625 assert_eq!(cmd, "amp");
626 assert_eq!(args, vec!["--dangerously-allow-all", "-x", "test prompt"]);
627 assert!(stdin.is_none());
628 }
629
630 #[test]
631 fn test_copilot_backend() {
632 let backend = CliBackend::copilot();
633 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
634
635 assert_eq!(cmd, "copilot");
636 assert_eq!(args, vec!["--allow-all-tools", "-p", "test prompt"]);
637 assert!(stdin.is_none());
638 assert_eq!(backend.output_format, OutputFormat::Text);
639 }
640
641 #[test]
642 fn test_copilot_tui_backend() {
643 let backend = CliBackend::copilot_tui();
644 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
645
646 assert_eq!(cmd, "copilot");
647 assert_eq!(args, vec!["test prompt"]);
649 assert!(stdin.is_none());
650 assert_eq!(backend.output_format, OutputFormat::Text);
651 assert_eq!(backend.prompt_flag, None);
652 }
653
654 #[test]
655 fn test_from_config() {
656 let config = CliConfig {
658 backend: "claude".to_string(),
659 command: None,
660 prompt_mode: "arg".to_string(),
661 ..Default::default()
662 };
663 let backend = CliBackend::from_config(&config).unwrap();
664
665 assert_eq!(backend.command, "claude");
666 assert_eq!(backend.prompt_mode, PromptMode::Arg);
667 assert_eq!(backend.prompt_flag, Some("-p".to_string()));
668 }
669
670 #[test]
671 fn test_kiro_interactive_mode_omits_no_interactive_flag() {
672 let backend = CliBackend::kiro();
673 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
674
675 assert_eq!(cmd, "kiro-cli");
676 assert_eq!(args, vec!["chat", "--trust-all-tools", "test prompt"]);
677 assert!(stdin.is_none());
678 assert!(!args.contains(&"--no-interactive".to_string()));
679 }
680
681 #[test]
682 fn test_codex_interactive_mode_omits_full_auto() {
683 let backend = CliBackend::codex();
684 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
685
686 assert_eq!(cmd, "codex");
687 assert_eq!(args, vec!["exec", "test prompt"]);
688 assert!(stdin.is_none());
689 assert!(!args.contains(&"--full-auto".to_string()));
690 }
691
692 #[test]
693 fn test_amp_interactive_mode_no_flags() {
694 let backend = CliBackend::amp();
695 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
696
697 assert_eq!(cmd, "amp");
698 assert_eq!(args, vec!["-x", "test prompt"]);
699 assert!(stdin.is_none());
700 assert!(!args.contains(&"--dangerously-allow-all".to_string()));
701 }
702
703 #[test]
704 fn test_copilot_interactive_mode_omits_allow_all_tools() {
705 let backend = CliBackend::copilot();
706 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", true);
707
708 assert_eq!(cmd, "copilot");
709 assert_eq!(args, vec!["-p", "test prompt"]);
710 assert!(stdin.is_none());
711 assert!(!args.contains(&"--allow-all-tools".to_string()));
712 }
713
714 #[test]
715 fn test_claude_interactive_mode_unchanged() {
716 let backend = CliBackend::claude();
717 let (cmd, args_auto, stdin_auto, _) = backend.build_command("test prompt", false);
718 let (_, args_interactive, stdin_interactive, _) =
719 backend.build_command("test prompt", true);
720
721 assert_eq!(cmd, "claude");
722 assert_eq!(args_auto, args_interactive);
723 assert_eq!(
724 args_auto,
725 vec![
726 "--dangerously-skip-permissions",
727 "--verbose",
728 "--output-format",
729 "stream-json",
730 "-p",
731 "test prompt"
732 ]
733 );
734 assert!(stdin_auto.is_none());
736 assert!(stdin_interactive.is_none());
737 }
738
739 #[test]
740 fn test_gemini_interactive_mode_unchanged() {
741 let backend = CliBackend::gemini();
742 let (cmd, args_auto, stdin_auto, _) = backend.build_command("test prompt", false);
743 let (_, args_interactive, stdin_interactive, _) =
744 backend.build_command("test prompt", true);
745
746 assert_eq!(cmd, "gemini");
747 assert_eq!(args_auto, args_interactive);
748 assert_eq!(args_auto, vec!["--yolo", "-p", "test prompt"]);
749 assert_eq!(stdin_auto, stdin_interactive);
750 assert!(stdin_auto.is_none());
751 }
752
753 #[test]
754 fn test_custom_backend_with_prompt_flag_short() {
755 let config = CliConfig {
756 backend: "custom".to_string(),
757 command: Some("my-agent".to_string()),
758 prompt_mode: "arg".to_string(),
759 prompt_flag: Some("-p".to_string()),
760 ..Default::default()
761 };
762 let backend = CliBackend::from_config(&config).unwrap();
763 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
764
765 assert_eq!(cmd, "my-agent");
766 assert_eq!(args, vec!["-p", "test prompt"]);
767 assert!(stdin.is_none());
768 }
769
770 #[test]
771 fn test_custom_backend_with_prompt_flag_long() {
772 let config = CliConfig {
773 backend: "custom".to_string(),
774 command: Some("my-agent".to_string()),
775 prompt_mode: "arg".to_string(),
776 prompt_flag: Some("--prompt".to_string()),
777 ..Default::default()
778 };
779 let backend = CliBackend::from_config(&config).unwrap();
780 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
781
782 assert_eq!(cmd, "my-agent");
783 assert_eq!(args, vec!["--prompt", "test prompt"]);
784 assert!(stdin.is_none());
785 }
786
787 #[test]
788 fn test_custom_backend_without_prompt_flag_positional() {
789 let config = CliConfig {
790 backend: "custom".to_string(),
791 command: Some("my-agent".to_string()),
792 prompt_mode: "arg".to_string(),
793 prompt_flag: None,
794 ..Default::default()
795 };
796 let backend = CliBackend::from_config(&config).unwrap();
797 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
798
799 assert_eq!(cmd, "my-agent");
800 assert_eq!(args, vec!["test prompt"]);
801 assert!(stdin.is_none());
802 }
803
804 #[test]
805 fn test_custom_backend_without_command_returns_error() {
806 let config = CliConfig {
807 backend: "custom".to_string(),
808 command: None,
809 prompt_mode: "arg".to_string(),
810 ..Default::default()
811 };
812 let result = CliBackend::from_config(&config);
813
814 assert!(result.is_err());
815 let err = result.unwrap_err();
816 assert_eq!(
817 err.to_string(),
818 "custom backend requires a command to be specified"
819 );
820 }
821
822 #[test]
823 fn test_kiro_with_agent() {
824 let backend = CliBackend::kiro_with_agent("my-agent".to_string());
825 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
826
827 assert_eq!(cmd, "kiro-cli");
828 assert_eq!(
829 args,
830 vec![
831 "chat",
832 "--no-interactive",
833 "--trust-all-tools",
834 "--agent",
835 "my-agent",
836 "test prompt"
837 ]
838 );
839 assert!(stdin.is_none());
840 }
841
842 #[test]
843 fn test_from_name_claude() {
844 let backend = CliBackend::from_name("claude").unwrap();
845 assert_eq!(backend.command, "claude");
846 assert_eq!(backend.prompt_flag, Some("-p".to_string()));
847 }
848
849 #[test]
850 fn test_from_name_kiro() {
851 let backend = CliBackend::from_name("kiro").unwrap();
852 assert_eq!(backend.command, "kiro-cli");
853 }
854
855 #[test]
856 fn test_from_name_gemini() {
857 let backend = CliBackend::from_name("gemini").unwrap();
858 assert_eq!(backend.command, "gemini");
859 }
860
861 #[test]
862 fn test_from_name_codex() {
863 let backend = CliBackend::from_name("codex").unwrap();
864 assert_eq!(backend.command, "codex");
865 }
866
867 #[test]
868 fn test_from_name_amp() {
869 let backend = CliBackend::from_name("amp").unwrap();
870 assert_eq!(backend.command, "amp");
871 }
872
873 #[test]
874 fn test_from_name_copilot() {
875 let backend = CliBackend::from_name("copilot").unwrap();
876 assert_eq!(backend.command, "copilot");
877 assert_eq!(backend.prompt_flag, Some("-p".to_string()));
878 }
879
880 #[test]
881 fn test_from_name_invalid() {
882 let result = CliBackend::from_name("invalid");
883 assert!(result.is_err());
884 }
885
886 #[test]
887 fn test_from_hat_backend_named() {
888 let hat_backend = HatBackend::Named("claude".to_string());
889 let backend = CliBackend::from_hat_backend(&hat_backend).unwrap();
890 assert_eq!(backend.command, "claude");
891 }
892
893 #[test]
894 fn test_from_hat_backend_kiro_agent() {
895 let hat_backend = HatBackend::KiroAgent {
896 backend_type: "kiro".to_string(),
897 agent: "my-agent".to_string(),
898 };
899 let backend = CliBackend::from_hat_backend(&hat_backend).unwrap();
900 let (cmd, args, _, _) = backend.build_command("test", false);
901 assert_eq!(cmd, "kiro-cli");
902 assert!(args.contains(&"--agent".to_string()));
903 assert!(args.contains(&"my-agent".to_string()));
904 }
905
906 #[test]
907 fn test_from_hat_backend_custom() {
908 let hat_backend = HatBackend::Custom {
909 command: "my-cli".to_string(),
910 args: vec!["--flag".to_string()],
911 };
912 let backend = CliBackend::from_hat_backend(&hat_backend).unwrap();
913 assert_eq!(backend.command, "my-cli");
914 assert_eq!(backend.args, vec!["--flag"]);
915 }
916
917 #[test]
922 fn test_for_interactive_prompt_claude() {
923 let backend = CliBackend::for_interactive_prompt("claude").unwrap();
924 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
925
926 assert_eq!(cmd, "claude");
927 assert_eq!(args, vec!["--dangerously-skip-permissions", "test prompt"]);
929 assert!(stdin.is_none());
930 assert_eq!(backend.prompt_flag, None);
931 }
932
933 #[test]
934 fn test_for_interactive_prompt_kiro() {
935 let backend = CliBackend::for_interactive_prompt("kiro").unwrap();
936 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
937
938 assert_eq!(cmd, "kiro-cli");
939 assert_eq!(args, vec!["chat", "--trust-all-tools", "test prompt"]);
941 assert!(!args.contains(&"--no-interactive".to_string()));
942 assert!(stdin.is_none());
943 }
944
945 #[test]
946 fn test_for_interactive_prompt_gemini() {
947 let backend = CliBackend::for_interactive_prompt("gemini").unwrap();
948 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
949
950 assert_eq!(cmd, "gemini");
951 assert_eq!(args, vec!["--yolo", "-i", "test prompt"]);
953 assert_eq!(backend.prompt_flag, Some("-i".to_string()));
954 assert!(stdin.is_none());
955 }
956
957 #[test]
958 fn test_for_interactive_prompt_codex() {
959 let backend = CliBackend::for_interactive_prompt("codex").unwrap();
960 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
961
962 assert_eq!(cmd, "codex");
963 assert_eq!(args, vec!["test prompt"]);
965 assert!(!args.contains(&"exec".to_string()));
966 assert!(!args.contains(&"--full-auto".to_string()));
967 assert!(stdin.is_none());
968 }
969
970 #[test]
971 fn test_for_interactive_prompt_amp() {
972 let backend = CliBackend::for_interactive_prompt("amp").unwrap();
973 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
974
975 assert_eq!(cmd, "amp");
976 assert_eq!(args, vec!["-x", "test prompt"]);
978 assert!(!args.contains(&"--dangerously-allow-all".to_string()));
979 assert!(stdin.is_none());
980 }
981
982 #[test]
983 fn test_for_interactive_prompt_copilot() {
984 let backend = CliBackend::for_interactive_prompt("copilot").unwrap();
985 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
986
987 assert_eq!(cmd, "copilot");
988 assert_eq!(args, vec!["-p", "test prompt"]);
990 assert!(!args.contains(&"--allow-all-tools".to_string()));
991 assert!(stdin.is_none());
992 }
993
994 #[test]
995 fn test_for_interactive_prompt_invalid() {
996 let result = CliBackend::for_interactive_prompt("invalid_backend");
997 assert!(result.is_err());
998 }
999
1000 #[test]
1005 fn test_opencode_backend() {
1006 let backend = CliBackend::opencode();
1007 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
1008
1009 assert_eq!(cmd, "opencode");
1010 assert_eq!(args, vec!["run", "test prompt"]);
1012 assert!(stdin.is_none());
1013 assert_eq!(backend.output_format, OutputFormat::Text);
1014 assert_eq!(backend.prompt_flag, None);
1015 }
1016
1017 #[test]
1018 fn test_opencode_tui_backend() {
1019 let backend = CliBackend::opencode_tui();
1020 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
1021
1022 assert_eq!(cmd, "opencode");
1023 assert_eq!(args, vec!["run", "test prompt"]);
1025 assert!(stdin.is_none());
1026 assert_eq!(backend.output_format, OutputFormat::Text);
1027 assert_eq!(backend.prompt_flag, None);
1028 }
1029
1030 #[test]
1031 fn test_opencode_interactive_mode_unchanged() {
1032 let backend = CliBackend::opencode();
1034 let (cmd, args_auto, stdin_auto, _) = backend.build_command("test prompt", false);
1035 let (_, args_interactive, stdin_interactive, _) =
1036 backend.build_command("test prompt", true);
1037
1038 assert_eq!(cmd, "opencode");
1039 assert_eq!(args_auto, args_interactive);
1041 assert_eq!(args_auto, vec!["run", "test prompt"]);
1042 assert!(stdin_auto.is_none());
1043 assert!(stdin_interactive.is_none());
1044 }
1045
1046 #[test]
1047 fn test_from_name_opencode() {
1048 let backend = CliBackend::from_name("opencode").unwrap();
1049 assert_eq!(backend.command, "opencode");
1050 assert_eq!(backend.prompt_flag, None); }
1052
1053 #[test]
1054 fn test_for_interactive_prompt_opencode() {
1055 let backend = CliBackend::for_interactive_prompt("opencode").unwrap();
1056 let (cmd, args, stdin, _temp) = backend.build_command("test prompt", false);
1057
1058 assert_eq!(cmd, "opencode");
1059 assert_eq!(args, vec!["run", "test prompt"]);
1061 assert!(stdin.is_none());
1062 assert_eq!(backend.prompt_flag, None);
1063 }
1064}