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