1use crate::error::{Error, Result};
13use log::debug;
14use std::path::PathBuf;
15use std::process::Stdio;
16use uuid::Uuid;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PermissionMode {
21 AcceptEdits,
22 BypassPermissions,
23 Default,
24 Delegate,
25 DontAsk,
26 Plan,
27}
28
29impl PermissionMode {
30 pub fn as_str(&self) -> &'static str {
32 match self {
33 PermissionMode::AcceptEdits => "acceptEdits",
34 PermissionMode::BypassPermissions => "bypassPermissions",
35 PermissionMode::Default => "default",
36 PermissionMode::Delegate => "delegate",
37 PermissionMode::DontAsk => "dontAsk",
38 PermissionMode::Plan => "plan",
39 }
40 }
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum InputFormat {
46 Text,
47 StreamJson,
48}
49
50impl InputFormat {
51 pub fn as_str(&self) -> &'static str {
53 match self {
54 InputFormat::Text => "text",
55 InputFormat::StreamJson => "stream-json",
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum OutputFormat {
63 Text,
64 Json,
65 StreamJson,
66}
67
68impl OutputFormat {
69 pub fn as_str(&self) -> &'static str {
71 match self {
72 OutputFormat::Text => "text",
73 OutputFormat::Json => "json",
74 OutputFormat::StreamJson => "stream-json",
75 }
76 }
77}
78
79#[derive(Debug, Clone)]
96pub enum CliFlag {
97 AddDir(Vec<PathBuf>),
99 Agent(String),
101 Agents(String),
103 AllowDangerouslySkipPermissions,
105 AllowedTools(Vec<String>),
107 AppendSystemPrompt(String),
109 Betas(Vec<String>),
111 Chrome,
113 Continue,
115 DangerouslySkipPermissions,
117 Debug(Option<String>),
119 DebugFile(PathBuf),
121 DisableSlashCommands,
123 DisallowedTools(Vec<String>),
125 FallbackModel(String),
127 File(Vec<String>),
129 ForkSession,
131 FromPr(Option<String>),
133 IncludePartialMessages,
135 InputFormat(InputFormat),
137 JsonSchema(String),
139 MaxBudgetUsd(f64),
141 McpConfig(Vec<String>),
143 McpDebug,
145 Model(String),
147 NoChrome,
149 NoSessionPersistence,
151 OutputFormat(OutputFormat),
153 PermissionMode(PermissionMode),
155 PermissionPromptTool(String),
157 PluginDir(Vec<PathBuf>),
159 Print,
161 ReplayUserMessages,
163 Resume(Option<String>),
165 SessionId(String),
167 SettingSources(String),
169 Settings(String),
171 StrictMcpConfig,
173 SystemPrompt(String),
175 Tools(Vec<String>),
177 Verbose,
179}
180
181impl CliFlag {
182 pub fn as_flag(&self) -> &'static str {
184 match self {
185 CliFlag::AddDir(_) => "--add-dir",
186 CliFlag::Agent(_) => "--agent",
187 CliFlag::Agents(_) => "--agents",
188 CliFlag::AllowDangerouslySkipPermissions => "--allow-dangerously-skip-permissions",
189 CliFlag::AllowedTools(_) => "--allowed-tools",
190 CliFlag::AppendSystemPrompt(_) => "--append-system-prompt",
191 CliFlag::Betas(_) => "--betas",
192 CliFlag::Chrome => "--chrome",
193 CliFlag::Continue => "--continue",
194 CliFlag::DangerouslySkipPermissions => "--dangerously-skip-permissions",
195 CliFlag::Debug(_) => "--debug",
196 CliFlag::DebugFile(_) => "--debug-file",
197 CliFlag::DisableSlashCommands => "--disable-slash-commands",
198 CliFlag::DisallowedTools(_) => "--disallowed-tools",
199 CliFlag::FallbackModel(_) => "--fallback-model",
200 CliFlag::File(_) => "--file",
201 CliFlag::ForkSession => "--fork-session",
202 CliFlag::FromPr(_) => "--from-pr",
203 CliFlag::IncludePartialMessages => "--include-partial-messages",
204 CliFlag::InputFormat(_) => "--input-format",
205 CliFlag::JsonSchema(_) => "--json-schema",
206 CliFlag::MaxBudgetUsd(_) => "--max-budget-usd",
207 CliFlag::McpConfig(_) => "--mcp-config",
208 CliFlag::McpDebug => "--mcp-debug",
209 CliFlag::Model(_) => "--model",
210 CliFlag::NoChrome => "--no-chrome",
211 CliFlag::NoSessionPersistence => "--no-session-persistence",
212 CliFlag::OutputFormat(_) => "--output-format",
213 CliFlag::PermissionMode(_) => "--permission-mode",
214 CliFlag::PermissionPromptTool(_) => "--permission-prompt-tool",
215 CliFlag::PluginDir(_) => "--plugin-dir",
216 CliFlag::Print => "--print",
217 CliFlag::ReplayUserMessages => "--replay-user-messages",
218 CliFlag::Resume(_) => "--resume",
219 CliFlag::SessionId(_) => "--session-id",
220 CliFlag::SettingSources(_) => "--setting-sources",
221 CliFlag::Settings(_) => "--settings",
222 CliFlag::StrictMcpConfig => "--strict-mcp-config",
223 CliFlag::SystemPrompt(_) => "--system-prompt",
224 CliFlag::Tools(_) => "--tools",
225 CliFlag::Verbose => "--verbose",
226 }
227 }
228
229 pub fn to_args(&self) -> Vec<String> {
231 let flag = self.as_flag().to_string();
232 match self {
233 CliFlag::AllowDangerouslySkipPermissions
235 | CliFlag::Chrome
236 | CliFlag::Continue
237 | CliFlag::DangerouslySkipPermissions
238 | CliFlag::DisableSlashCommands
239 | CliFlag::ForkSession
240 | CliFlag::IncludePartialMessages
241 | CliFlag::McpDebug
242 | CliFlag::NoChrome
243 | CliFlag::NoSessionPersistence
244 | CliFlag::Print
245 | CliFlag::ReplayUserMessages
246 | CliFlag::StrictMcpConfig
247 | CliFlag::Verbose => vec![flag],
248
249 CliFlag::Debug(filter) => match filter {
251 Some(f) => vec![flag, f.clone()],
252 None => vec![flag],
253 },
254 CliFlag::FromPr(value) | CliFlag::Resume(value) => match value {
255 Some(v) => vec![flag, v.clone()],
256 None => vec![flag],
257 },
258
259 CliFlag::Agent(v)
261 | CliFlag::Agents(v)
262 | CliFlag::AppendSystemPrompt(v)
263 | CliFlag::FallbackModel(v)
264 | CliFlag::JsonSchema(v)
265 | CliFlag::Model(v)
266 | CliFlag::PermissionPromptTool(v)
267 | CliFlag::SessionId(v)
268 | CliFlag::SettingSources(v)
269 | CliFlag::Settings(v)
270 | CliFlag::SystemPrompt(v) => vec![flag, v.clone()],
271
272 CliFlag::InputFormat(f) => vec![flag, f.as_str().to_string()],
274 CliFlag::OutputFormat(f) => vec![flag, f.as_str().to_string()],
275 CliFlag::PermissionMode(m) => vec![flag, m.as_str().to_string()],
276
277 CliFlag::MaxBudgetUsd(amount) => vec![flag, amount.to_string()],
279
280 CliFlag::DebugFile(p) => vec![flag, p.to_string_lossy().to_string()],
282
283 CliFlag::AllowedTools(items)
285 | CliFlag::Betas(items)
286 | CliFlag::DisallowedTools(items)
287 | CliFlag::File(items)
288 | CliFlag::McpConfig(items)
289 | CliFlag::Tools(items) => {
290 let mut args = vec![flag];
291 args.extend(items.clone());
292 args
293 }
294
295 CliFlag::AddDir(paths) | CliFlag::PluginDir(paths) => {
297 let mut args = vec![flag];
298 args.extend(paths.iter().map(|p| p.to_string_lossy().to_string()));
299 args
300 }
301 }
302 }
303
304 pub fn all_flags() -> Vec<(&'static str, &'static str)> {
317 vec![
318 ("AddDir", "--add-dir"),
319 ("Agent", "--agent"),
320 ("Agents", "--agents"),
321 (
322 "AllowDangerouslySkipPermissions",
323 "--allow-dangerously-skip-permissions",
324 ),
325 ("AllowedTools", "--allowed-tools"),
326 ("AppendSystemPrompt", "--append-system-prompt"),
327 ("Betas", "--betas"),
328 ("Chrome", "--chrome"),
329 ("Continue", "--continue"),
330 (
331 "DangerouslySkipPermissions",
332 "--dangerously-skip-permissions",
333 ),
334 ("Debug", "--debug"),
335 ("DebugFile", "--debug-file"),
336 ("DisableSlashCommands", "--disable-slash-commands"),
337 ("DisallowedTools", "--disallowed-tools"),
338 ("FallbackModel", "--fallback-model"),
339 ("File", "--file"),
340 ("ForkSession", "--fork-session"),
341 ("FromPr", "--from-pr"),
342 ("IncludePartialMessages", "--include-partial-messages"),
343 ("InputFormat", "--input-format"),
344 ("JsonSchema", "--json-schema"),
345 ("MaxBudgetUsd", "--max-budget-usd"),
346 ("McpConfig", "--mcp-config"),
347 ("McpDebug", "--mcp-debug"),
348 ("Model", "--model"),
349 ("NoChrome", "--no-chrome"),
350 ("NoSessionPersistence", "--no-session-persistence"),
351 ("OutputFormat", "--output-format"),
352 ("PermissionMode", "--permission-mode"),
353 ("PermissionPromptTool", "--permission-prompt-tool"),
354 ("PluginDir", "--plugin-dir"),
355 ("Print", "--print"),
356 ("ReplayUserMessages", "--replay-user-messages"),
357 ("Resume", "--resume"),
358 ("SessionId", "--session-id"),
359 ("SettingSources", "--setting-sources"),
360 ("Settings", "--settings"),
361 ("StrictMcpConfig", "--strict-mcp-config"),
362 ("SystemPrompt", "--system-prompt"),
363 ("Tools", "--tools"),
364 ("Verbose", "--verbose"),
365 ]
366 }
367}
368
369#[derive(Debug, Clone)]
377pub struct ClaudeCliBuilder {
378 command: PathBuf,
379 prompt: Option<String>,
380 debug: Option<String>,
381 verbose: bool,
382 dangerously_skip_permissions: bool,
383 allowed_tools: Vec<String>,
384 disallowed_tools: Vec<String>,
385 mcp_config: Vec<String>,
386 append_system_prompt: Option<String>,
387 permission_mode: Option<PermissionMode>,
388 continue_conversation: bool,
389 resume: Option<String>,
390 model: Option<String>,
391 fallback_model: Option<String>,
392 settings: Option<String>,
393 add_dir: Vec<PathBuf>,
394 ide: bool,
395 strict_mcp_config: bool,
396 session_id: Option<Uuid>,
397 oauth_token: Option<String>,
398 api_key: Option<String>,
399 permission_prompt_tool: Option<String>,
401 allow_recursion: bool,
403}
404
405impl Default for ClaudeCliBuilder {
406 fn default() -> Self {
407 Self::new()
408 }
409}
410
411impl ClaudeCliBuilder {
412 pub fn new() -> Self {
414 Self {
415 command: PathBuf::from("claude"),
416 prompt: None,
417 debug: None,
418 verbose: false,
419 dangerously_skip_permissions: false,
420 allowed_tools: Vec::new(),
421 disallowed_tools: Vec::new(),
422 mcp_config: Vec::new(),
423 append_system_prompt: None,
424 permission_mode: None,
425 continue_conversation: false,
426 resume: None,
427 model: None,
428 fallback_model: None,
429 settings: None,
430 add_dir: Vec::new(),
431 ide: false,
432 strict_mcp_config: false,
433 session_id: None,
434 oauth_token: None,
435 api_key: None,
436 permission_prompt_tool: None,
437 allow_recursion: false,
438 }
439 }
440
441 pub fn command<P: Into<PathBuf>>(mut self, path: P) -> Self {
443 self.command = path.into();
444 self
445 }
446
447 pub fn prompt<S: Into<String>>(mut self, prompt: S) -> Self {
449 self.prompt = Some(prompt.into());
450 self
451 }
452
453 pub fn debug<S: Into<String>>(mut self, filter: Option<S>) -> Self {
455 self.debug = filter.map(|s| s.into());
456 self
457 }
458
459 pub fn verbose(mut self, verbose: bool) -> Self {
461 self.verbose = verbose;
462 self
463 }
464
465 pub fn dangerously_skip_permissions(mut self, skip: bool) -> Self {
467 self.dangerously_skip_permissions = skip;
468 self
469 }
470
471 pub fn allowed_tools<I, S>(mut self, tools: I) -> Self
473 where
474 I: IntoIterator<Item = S>,
475 S: Into<String>,
476 {
477 self.allowed_tools
478 .extend(tools.into_iter().map(|s| s.into()));
479 self
480 }
481
482 pub fn disallowed_tools<I, S>(mut self, tools: I) -> Self
484 where
485 I: IntoIterator<Item = S>,
486 S: Into<String>,
487 {
488 self.disallowed_tools
489 .extend(tools.into_iter().map(|s| s.into()));
490 self
491 }
492
493 pub fn mcp_config<I, S>(mut self, configs: I) -> Self
495 where
496 I: IntoIterator<Item = S>,
497 S: Into<String>,
498 {
499 self.mcp_config
500 .extend(configs.into_iter().map(|s| s.into()));
501 self
502 }
503
504 pub fn append_system_prompt<S: Into<String>>(mut self, prompt: S) -> Self {
506 self.append_system_prompt = Some(prompt.into());
507 self
508 }
509
510 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
512 self.permission_mode = Some(mode);
513 self
514 }
515
516 pub fn continue_conversation(mut self, continue_conv: bool) -> Self {
518 self.continue_conversation = continue_conv;
519 self
520 }
521
522 pub fn resume<S: Into<String>>(mut self, session_id: Option<S>) -> Self {
524 self.resume = session_id.map(|s| s.into());
525 self
526 }
527
528 pub fn model<S: Into<String>>(mut self, model: S) -> Self {
530 self.model = Some(model.into());
531 self
532 }
533
534 pub fn fallback_model<S: Into<String>>(mut self, model: S) -> Self {
536 self.fallback_model = Some(model.into());
537 self
538 }
539
540 pub fn settings<S: Into<String>>(mut self, settings: S) -> Self {
542 self.settings = Some(settings.into());
543 self
544 }
545
546 pub fn add_directories<I, P>(mut self, dirs: I) -> Self
548 where
549 I: IntoIterator<Item = P>,
550 P: Into<PathBuf>,
551 {
552 self.add_dir.extend(dirs.into_iter().map(|p| p.into()));
553 self
554 }
555
556 pub fn ide(mut self, ide: bool) -> Self {
558 self.ide = ide;
559 self
560 }
561
562 pub fn strict_mcp_config(mut self, strict: bool) -> Self {
564 self.strict_mcp_config = strict;
565 self
566 }
567
568 pub fn session_id(mut self, id: Uuid) -> Self {
570 self.session_id = Some(id);
571 self
572 }
573
574 pub fn oauth_token<S: Into<String>>(mut self, token: S) -> Self {
576 let token_str = token.into();
577 if !token_str.starts_with("sk-ant-oat") {
578 eprintln!("Warning: OAuth token should start with 'sk-ant-oat'");
579 }
580 self.oauth_token = Some(token_str);
581 self
582 }
583
584 pub fn api_key<S: Into<String>>(mut self, key: S) -> Self {
586 let key_str = key.into();
587 if !key_str.starts_with("sk-ant-api") {
588 eprintln!("Warning: API key should start with 'sk-ant-api'");
589 }
590 self.api_key = Some(key_str);
591 self
592 }
593
594 pub fn permission_prompt_tool<S: Into<String>>(mut self, tool: S) -> Self {
609 self.permission_prompt_tool = Some(tool.into());
610 self
611 }
612
613 #[cfg(feature = "integration-tests")]
616 pub fn allow_recursion(mut self) -> Self {
617 self.allow_recursion = true;
618 self
619 }
620
621 fn resolve_command(&self) -> Result<PathBuf> {
623 if self.command.is_absolute() {
624 return Ok(self.command.clone());
625 }
626 which::which(&self.command).map_err(|_| Error::BinaryNotFound {
627 name: self.command.display().to_string(),
628 })
629 }
630
631 fn build_args(&self) -> Vec<String> {
633 let mut args = vec![
636 "--print".to_string(),
637 "--verbose".to_string(),
638 "--output-format".to_string(),
639 "stream-json".to_string(),
640 "--input-format".to_string(),
641 "stream-json".to_string(),
642 ];
643
644 if let Some(ref debug) = self.debug {
645 args.push("--debug".to_string());
646 if !debug.is_empty() {
647 args.push(debug.clone());
648 }
649 }
650
651 if self.dangerously_skip_permissions {
652 args.push("--dangerously-skip-permissions".to_string());
653 }
654
655 if !self.allowed_tools.is_empty() {
656 args.push("--allowed-tools".to_string());
657 args.extend(self.allowed_tools.clone());
658 }
659
660 if !self.disallowed_tools.is_empty() {
661 args.push("--disallowed-tools".to_string());
662 args.extend(self.disallowed_tools.clone());
663 }
664
665 if !self.mcp_config.is_empty() {
666 args.push("--mcp-config".to_string());
667 args.extend(self.mcp_config.clone());
668 }
669
670 if let Some(ref prompt) = self.append_system_prompt {
671 args.push("--append-system-prompt".to_string());
672 args.push(prompt.clone());
673 }
674
675 if let Some(ref mode) = self.permission_mode {
676 args.push("--permission-mode".to_string());
677 args.push(mode.as_str().to_string());
678 }
679
680 if self.continue_conversation {
681 args.push("--continue".to_string());
682 }
683
684 if let Some(ref session) = self.resume {
685 args.push("--resume".to_string());
686 args.push(session.clone());
687 }
688
689 if let Some(ref model) = self.model {
690 args.push("--model".to_string());
691 args.push(model.clone());
692 }
693
694 if let Some(ref model) = self.fallback_model {
695 args.push("--fallback-model".to_string());
696 args.push(model.clone());
697 }
698
699 if let Some(ref settings) = self.settings {
700 args.push("--settings".to_string());
701 args.push(settings.clone());
702 }
703
704 if !self.add_dir.is_empty() {
705 args.push("--add-dir".to_string());
706 for dir in &self.add_dir {
707 args.push(dir.to_string_lossy().to_string());
708 }
709 }
710
711 if self.ide {
712 args.push("--ide".to_string());
713 }
714
715 if self.strict_mcp_config {
716 args.push("--strict-mcp-config".to_string());
717 }
718
719 if let Some(ref tool) = self.permission_prompt_tool {
720 args.push("--permission-prompt-tool".to_string());
721 args.push(tool.clone());
722 }
723
724 if self.resume.is_none() && !self.continue_conversation {
728 args.push("--session-id".to_string());
729 let session_uuid = self.session_id.unwrap_or_else(|| {
730 let uuid = Uuid::new_v4();
731 debug!("[CLI] Generated session UUID: {}", uuid);
732 uuid
733 });
734 args.push(session_uuid.to_string());
735 }
736
737 if let Some(ref prompt) = self.prompt {
739 args.push(prompt.clone());
740 }
741
742 args
743 }
744
745 #[cfg(feature = "async-client")]
747 pub async fn spawn(self) -> Result<tokio::process::Child> {
748 let resolved = self.resolve_command()?;
749 let args = self.build_args();
750
751 debug!(
752 "[CLI] Executing command: {} {}",
753 resolved.display(),
754 args.join(" ")
755 );
756
757 let mut cmd = tokio::process::Command::new(&resolved);
758 cmd.args(&args)
759 .stdin(Stdio::piped())
760 .stdout(Stdio::piped())
761 .stderr(Stdio::piped());
762
763 if self.allow_recursion {
764 cmd.env_remove("CLAUDECODE");
765 }
766
767 if let Some(ref token) = self.oauth_token {
768 cmd.env("CLAUDE_CODE_OAUTH_TOKEN", token);
769 }
770
771 if let Some(ref key) = self.api_key {
772 cmd.env("ANTHROPIC_API_KEY", key);
773 }
774
775 let child = cmd.spawn().map_err(Error::Io)?;
776
777 Ok(child)
778 }
779
780 #[cfg(feature = "async-client")]
782 pub fn build_command(self) -> Result<tokio::process::Command> {
783 let resolved = self.resolve_command()?;
784 let args = self.build_args();
785 let mut cmd = tokio::process::Command::new(&resolved);
786 cmd.args(&args)
787 .stdin(Stdio::piped())
788 .stdout(Stdio::piped())
789 .stderr(Stdio::piped());
790
791 if self.allow_recursion {
792 cmd.env_remove("CLAUDECODE");
793 }
794
795 if let Some(ref token) = self.oauth_token {
796 cmd.env("CLAUDE_CODE_OAUTH_TOKEN", token);
797 }
798
799 if let Some(ref key) = self.api_key {
800 cmd.env("ANTHROPIC_API_KEY", key);
801 }
802
803 Ok(cmd)
804 }
805
806 pub fn spawn_sync(self) -> Result<std::process::Child> {
808 let resolved = self.resolve_command()?;
809 let args = self.build_args();
810
811 debug!(
812 "[CLI] Executing sync command: {} {}",
813 resolved.display(),
814 args.join(" ")
815 );
816
817 let mut cmd = std::process::Command::new(&resolved);
818 cmd.args(&args)
819 .stdin(Stdio::piped())
820 .stdout(Stdio::piped())
821 .stderr(Stdio::piped());
822
823 if self.allow_recursion {
824 cmd.env_remove("CLAUDECODE");
825 }
826
827 if let Some(ref token) = self.oauth_token {
828 cmd.env("CLAUDE_CODE_OAUTH_TOKEN", token);
829 }
830
831 if let Some(ref key) = self.api_key {
832 cmd.env("ANTHROPIC_API_KEY", key);
833 }
834
835 cmd.spawn().map_err(Error::Io)
836 }
837}
838
839#[cfg(test)]
840mod tests {
841 use super::*;
842
843 #[test]
844 fn test_streaming_flags_always_present() {
845 let builder = ClaudeCliBuilder::new();
846 let args = builder.build_args();
847
848 assert!(args.contains(&"--print".to_string()));
850 assert!(args.contains(&"--verbose".to_string())); assert!(args.contains(&"--output-format".to_string()));
852 assert!(args.contains(&"stream-json".to_string()));
853 assert!(args.contains(&"--input-format".to_string()));
854 }
855
856 #[test]
857 fn test_with_prompt() {
858 let builder = ClaudeCliBuilder::new().prompt("Hello, Claude!");
859 let args = builder.build_args();
860
861 assert_eq!(args.last().unwrap(), "Hello, Claude!");
862 }
863
864 #[test]
865 fn test_with_model() {
866 let builder = ClaudeCliBuilder::new()
867 .model("sonnet")
868 .fallback_model("opus");
869 let args = builder.build_args();
870
871 assert!(args.contains(&"--model".to_string()));
872 assert!(args.contains(&"sonnet".to_string()));
873 assert!(args.contains(&"--fallback-model".to_string()));
874 assert!(args.contains(&"opus".to_string()));
875 }
876
877 #[test]
878 fn test_with_debug() {
879 let builder = ClaudeCliBuilder::new().debug(Some("api"));
880 let args = builder.build_args();
881
882 assert!(args.contains(&"--debug".to_string()));
883 assert!(args.contains(&"api".to_string()));
884 }
885
886 #[test]
887 fn test_with_oauth_token() {
888 let valid_token = "sk-ant-oat-123456789";
889 let builder = ClaudeCliBuilder::new().oauth_token(valid_token);
890
891 let args = builder.clone().build_args();
893 assert!(!args.contains(&valid_token.to_string()));
894
895 assert_eq!(builder.oauth_token, Some(valid_token.to_string()));
897 }
898
899 #[test]
900 fn test_oauth_token_validation() {
901 let invalid_token = "invalid-token-123";
903 let builder = ClaudeCliBuilder::new().oauth_token(invalid_token);
904 assert_eq!(builder.oauth_token, Some(invalid_token.to_string()));
905 }
906
907 #[test]
908 fn test_with_api_key() {
909 let valid_key = "sk-ant-api-987654321";
910 let builder = ClaudeCliBuilder::new().api_key(valid_key);
911
912 let args = builder.clone().build_args();
914 assert!(!args.contains(&valid_key.to_string()));
915
916 assert_eq!(builder.api_key, Some(valid_key.to_string()));
918 }
919
920 #[test]
921 fn test_api_key_validation() {
922 let invalid_key = "invalid-api-key";
924 let builder = ClaudeCliBuilder::new().api_key(invalid_key);
925 assert_eq!(builder.api_key, Some(invalid_key.to_string()));
926 }
927
928 #[test]
929 fn test_both_auth_methods() {
930 let oauth = "sk-ant-oat-123";
931 let api_key = "sk-ant-api-456";
932 let builder = ClaudeCliBuilder::new().oauth_token(oauth).api_key(api_key);
933
934 assert_eq!(builder.oauth_token, Some(oauth.to_string()));
935 assert_eq!(builder.api_key, Some(api_key.to_string()));
936 }
937
938 #[test]
939 fn test_permission_prompt_tool() {
940 let builder = ClaudeCliBuilder::new().permission_prompt_tool("stdio");
941 let args = builder.build_args();
942
943 assert!(args.contains(&"--permission-prompt-tool".to_string()));
944 assert!(args.contains(&"stdio".to_string()));
945 }
946
947 #[test]
948 fn test_permission_prompt_tool_not_present_by_default() {
949 let builder = ClaudeCliBuilder::new();
950 let args = builder.build_args();
951
952 assert!(!args.contains(&"--permission-prompt-tool".to_string()));
953 }
954
955 #[test]
956 fn test_session_id_present_for_new_session() {
957 let builder = ClaudeCliBuilder::new();
958 let args = builder.build_args();
959
960 assert!(
961 args.contains(&"--session-id".to_string()),
962 "New sessions should have --session-id"
963 );
964 }
965
966 #[test]
967 fn test_session_id_not_present_with_resume() {
968 let builder = ClaudeCliBuilder::new().resume(Some("existing-uuid".to_string()));
971 let args = builder.build_args();
972
973 assert!(
974 args.contains(&"--resume".to_string()),
975 "Should have --resume flag"
976 );
977 assert!(
978 !args.contains(&"--session-id".to_string()),
979 "--session-id should NOT be present when resuming"
980 );
981 }
982
983 #[test]
984 fn test_session_id_not_present_with_continue() {
985 let builder = ClaudeCliBuilder::new().continue_conversation(true);
987 let args = builder.build_args();
988
989 assert!(
990 args.contains(&"--continue".to_string()),
991 "Should have --continue flag"
992 );
993 assert!(
994 !args.contains(&"--session-id".to_string()),
995 "--session-id should NOT be present when continuing"
996 );
997 }
998}