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 MaxThinkingTokens(u32),
143 McpConfig(Vec<String>),
145 McpDebug,
147 Model(String),
149 NoChrome,
151 NoSessionPersistence,
153 OutputFormat(OutputFormat),
155 PermissionMode(PermissionMode),
157 PermissionPromptTool(String),
159 PluginDir(Vec<PathBuf>),
161 Print,
163 ReplayUserMessages,
165 Resume(Option<String>),
167 SessionId(String),
169 SettingSources(String),
171 Settings(String),
173 StrictMcpConfig,
175 SystemPrompt(String),
177 Tools(Vec<String>),
179 Verbose,
181}
182
183impl CliFlag {
184 pub fn as_flag(&self) -> &'static str {
186 match self {
187 CliFlag::AddDir(_) => "--add-dir",
188 CliFlag::Agent(_) => "--agent",
189 CliFlag::Agents(_) => "--agents",
190 CliFlag::AllowDangerouslySkipPermissions => "--allow-dangerously-skip-permissions",
191 CliFlag::AllowedTools(_) => "--allowed-tools",
192 CliFlag::AppendSystemPrompt(_) => "--append-system-prompt",
193 CliFlag::Betas(_) => "--betas",
194 CliFlag::Chrome => "--chrome",
195 CliFlag::Continue => "--continue",
196 CliFlag::DangerouslySkipPermissions => "--dangerously-skip-permissions",
197 CliFlag::Debug(_) => "--debug",
198 CliFlag::DebugFile(_) => "--debug-file",
199 CliFlag::DisableSlashCommands => "--disable-slash-commands",
200 CliFlag::DisallowedTools(_) => "--disallowed-tools",
201 CliFlag::FallbackModel(_) => "--fallback-model",
202 CliFlag::File(_) => "--file",
203 CliFlag::ForkSession => "--fork-session",
204 CliFlag::FromPr(_) => "--from-pr",
205 CliFlag::IncludePartialMessages => "--include-partial-messages",
206 CliFlag::InputFormat(_) => "--input-format",
207 CliFlag::JsonSchema(_) => "--json-schema",
208 CliFlag::MaxBudgetUsd(_) => "--max-budget-usd",
209 CliFlag::MaxThinkingTokens(_) => "--max-thinking-tokens",
210 CliFlag::McpConfig(_) => "--mcp-config",
211 CliFlag::McpDebug => "--mcp-debug",
212 CliFlag::Model(_) => "--model",
213 CliFlag::NoChrome => "--no-chrome",
214 CliFlag::NoSessionPersistence => "--no-session-persistence",
215 CliFlag::OutputFormat(_) => "--output-format",
216 CliFlag::PermissionMode(_) => "--permission-mode",
217 CliFlag::PermissionPromptTool(_) => "--permission-prompt-tool",
218 CliFlag::PluginDir(_) => "--plugin-dir",
219 CliFlag::Print => "--print",
220 CliFlag::ReplayUserMessages => "--replay-user-messages",
221 CliFlag::Resume(_) => "--resume",
222 CliFlag::SessionId(_) => "--session-id",
223 CliFlag::SettingSources(_) => "--setting-sources",
224 CliFlag::Settings(_) => "--settings",
225 CliFlag::StrictMcpConfig => "--strict-mcp-config",
226 CliFlag::SystemPrompt(_) => "--system-prompt",
227 CliFlag::Tools(_) => "--tools",
228 CliFlag::Verbose => "--verbose",
229 }
230 }
231
232 pub fn to_args(&self) -> Vec<String> {
234 let flag = self.as_flag().to_string();
235 match self {
236 CliFlag::AllowDangerouslySkipPermissions
238 | CliFlag::Chrome
239 | CliFlag::Continue
240 | CliFlag::DangerouslySkipPermissions
241 | CliFlag::DisableSlashCommands
242 | CliFlag::ForkSession
243 | CliFlag::IncludePartialMessages
244 | CliFlag::McpDebug
245 | CliFlag::NoChrome
246 | CliFlag::NoSessionPersistence
247 | CliFlag::Print
248 | CliFlag::ReplayUserMessages
249 | CliFlag::StrictMcpConfig
250 | CliFlag::Verbose => vec![flag],
251
252 CliFlag::Debug(filter) => match filter {
254 Some(f) => vec![flag, f.clone()],
255 None => vec![flag],
256 },
257 CliFlag::FromPr(value) | CliFlag::Resume(value) => match value {
258 Some(v) => vec![flag, v.clone()],
259 None => vec![flag],
260 },
261
262 CliFlag::Agent(v)
264 | CliFlag::Agents(v)
265 | CliFlag::AppendSystemPrompt(v)
266 | CliFlag::FallbackModel(v)
267 | CliFlag::JsonSchema(v)
268 | CliFlag::Model(v)
269 | CliFlag::PermissionPromptTool(v)
270 | CliFlag::SessionId(v)
271 | CliFlag::SettingSources(v)
272 | CliFlag::Settings(v)
273 | CliFlag::SystemPrompt(v) => vec![flag, v.clone()],
274
275 CliFlag::InputFormat(f) => vec![flag, f.as_str().to_string()],
277 CliFlag::OutputFormat(f) => vec![flag, f.as_str().to_string()],
278 CliFlag::PermissionMode(m) => vec![flag, m.as_str().to_string()],
279
280 CliFlag::MaxBudgetUsd(amount) => vec![flag, amount.to_string()],
282 CliFlag::MaxThinkingTokens(tokens) => vec![flag, tokens.to_string()],
283
284 CliFlag::DebugFile(p) => vec![flag, p.to_string_lossy().to_string()],
286
287 CliFlag::AllowedTools(items)
289 | CliFlag::Betas(items)
290 | CliFlag::DisallowedTools(items)
291 | CliFlag::File(items)
292 | CliFlag::McpConfig(items)
293 | CliFlag::Tools(items) => {
294 let mut args = vec![flag];
295 args.extend(items.clone());
296 args
297 }
298
299 CliFlag::AddDir(paths) | CliFlag::PluginDir(paths) => {
301 let mut args = vec![flag];
302 args.extend(paths.iter().map(|p| p.to_string_lossy().to_string()));
303 args
304 }
305 }
306 }
307
308 pub fn all_flags() -> Vec<(&'static str, &'static str)> {
321 vec![
322 ("AddDir", "--add-dir"),
323 ("Agent", "--agent"),
324 ("Agents", "--agents"),
325 (
326 "AllowDangerouslySkipPermissions",
327 "--allow-dangerously-skip-permissions",
328 ),
329 ("AllowedTools", "--allowed-tools"),
330 ("AppendSystemPrompt", "--append-system-prompt"),
331 ("Betas", "--betas"),
332 ("Chrome", "--chrome"),
333 ("Continue", "--continue"),
334 (
335 "DangerouslySkipPermissions",
336 "--dangerously-skip-permissions",
337 ),
338 ("Debug", "--debug"),
339 ("DebugFile", "--debug-file"),
340 ("DisableSlashCommands", "--disable-slash-commands"),
341 ("DisallowedTools", "--disallowed-tools"),
342 ("FallbackModel", "--fallback-model"),
343 ("File", "--file"),
344 ("ForkSession", "--fork-session"),
345 ("FromPr", "--from-pr"),
346 ("IncludePartialMessages", "--include-partial-messages"),
347 ("InputFormat", "--input-format"),
348 ("JsonSchema", "--json-schema"),
349 ("MaxBudgetUsd", "--max-budget-usd"),
350 ("MaxThinkingTokens", "--max-thinking-tokens"),
351 ("McpConfig", "--mcp-config"),
352 ("McpDebug", "--mcp-debug"),
353 ("Model", "--model"),
354 ("NoChrome", "--no-chrome"),
355 ("NoSessionPersistence", "--no-session-persistence"),
356 ("OutputFormat", "--output-format"),
357 ("PermissionMode", "--permission-mode"),
358 ("PermissionPromptTool", "--permission-prompt-tool"),
359 ("PluginDir", "--plugin-dir"),
360 ("Print", "--print"),
361 ("ReplayUserMessages", "--replay-user-messages"),
362 ("Resume", "--resume"),
363 ("SessionId", "--session-id"),
364 ("SettingSources", "--setting-sources"),
365 ("Settings", "--settings"),
366 ("StrictMcpConfig", "--strict-mcp-config"),
367 ("SystemPrompt", "--system-prompt"),
368 ("Tools", "--tools"),
369 ("Verbose", "--verbose"),
370 ]
371 }
372}
373
374#[derive(Debug, Clone)]
382pub struct ClaudeCliBuilder {
383 command: PathBuf,
384 prompt: Option<String>,
385 debug: Option<String>,
386 verbose: bool,
387 dangerously_skip_permissions: bool,
388 allowed_tools: Vec<String>,
389 disallowed_tools: Vec<String>,
390 mcp_config: Vec<String>,
391 append_system_prompt: Option<String>,
392 permission_mode: Option<PermissionMode>,
393 continue_conversation: bool,
394 resume: Option<String>,
395 model: Option<String>,
396 fallback_model: Option<String>,
397 settings: Option<String>,
398 add_dir: Vec<PathBuf>,
399 ide: bool,
400 strict_mcp_config: bool,
401 session_id: Option<Uuid>,
402 oauth_token: Option<String>,
403 api_key: Option<String>,
404 permission_prompt_tool: Option<String>,
406 allow_recursion: bool,
408 max_thinking_tokens: Option<u32>,
410}
411
412impl Default for ClaudeCliBuilder {
413 fn default() -> Self {
414 Self::new()
415 }
416}
417
418impl ClaudeCliBuilder {
419 pub fn new() -> Self {
421 Self {
422 command: PathBuf::from("claude"),
423 prompt: None,
424 debug: None,
425 verbose: false,
426 dangerously_skip_permissions: false,
427 allowed_tools: Vec::new(),
428 disallowed_tools: Vec::new(),
429 mcp_config: Vec::new(),
430 append_system_prompt: None,
431 permission_mode: None,
432 continue_conversation: false,
433 resume: None,
434 model: None,
435 fallback_model: None,
436 settings: None,
437 add_dir: Vec::new(),
438 ide: false,
439 strict_mcp_config: false,
440 session_id: None,
441 oauth_token: None,
442 api_key: None,
443 permission_prompt_tool: None,
444 allow_recursion: false,
445 max_thinking_tokens: None,
446 }
447 }
448
449 pub fn command<P: Into<PathBuf>>(mut self, path: P) -> Self {
451 self.command = path.into();
452 self
453 }
454
455 pub fn prompt<S: Into<String>>(mut self, prompt: S) -> Self {
457 self.prompt = Some(prompt.into());
458 self
459 }
460
461 pub fn debug<S: Into<String>>(mut self, filter: Option<S>) -> Self {
463 self.debug = filter.map(|s| s.into());
464 self
465 }
466
467 pub fn verbose(mut self, verbose: bool) -> Self {
469 self.verbose = verbose;
470 self
471 }
472
473 pub fn dangerously_skip_permissions(mut self, skip: bool) -> Self {
475 self.dangerously_skip_permissions = skip;
476 self
477 }
478
479 pub fn allowed_tools<I, S>(mut self, tools: I) -> Self
481 where
482 I: IntoIterator<Item = S>,
483 S: Into<String>,
484 {
485 self.allowed_tools
486 .extend(tools.into_iter().map(|s| s.into()));
487 self
488 }
489
490 pub fn disallowed_tools<I, S>(mut self, tools: I) -> Self
492 where
493 I: IntoIterator<Item = S>,
494 S: Into<String>,
495 {
496 self.disallowed_tools
497 .extend(tools.into_iter().map(|s| s.into()));
498 self
499 }
500
501 pub fn mcp_config<I, S>(mut self, configs: I) -> Self
503 where
504 I: IntoIterator<Item = S>,
505 S: Into<String>,
506 {
507 self.mcp_config
508 .extend(configs.into_iter().map(|s| s.into()));
509 self
510 }
511
512 pub fn append_system_prompt<S: Into<String>>(mut self, prompt: S) -> Self {
514 self.append_system_prompt = Some(prompt.into());
515 self
516 }
517
518 pub fn permission_mode(mut self, mode: PermissionMode) -> Self {
520 self.permission_mode = Some(mode);
521 self
522 }
523
524 pub fn continue_conversation(mut self, continue_conv: bool) -> Self {
526 self.continue_conversation = continue_conv;
527 self
528 }
529
530 pub fn resume<S: Into<String>>(mut self, session_id: Option<S>) -> Self {
532 self.resume = session_id.map(|s| s.into());
533 self
534 }
535
536 pub fn model<S: Into<String>>(mut self, model: S) -> Self {
538 self.model = Some(model.into());
539 self
540 }
541
542 pub fn fallback_model<S: Into<String>>(mut self, model: S) -> Self {
544 self.fallback_model = Some(model.into());
545 self
546 }
547
548 pub fn max_thinking_tokens(mut self, tokens: u32) -> Self {
550 self.max_thinking_tokens = Some(tokens);
551 self
552 }
553
554 pub fn settings<S: Into<String>>(mut self, settings: S) -> Self {
556 self.settings = Some(settings.into());
557 self
558 }
559
560 pub fn add_directories<I, P>(mut self, dirs: I) -> Self
562 where
563 I: IntoIterator<Item = P>,
564 P: Into<PathBuf>,
565 {
566 self.add_dir.extend(dirs.into_iter().map(|p| p.into()));
567 self
568 }
569
570 pub fn ide(mut self, ide: bool) -> Self {
572 self.ide = ide;
573 self
574 }
575
576 pub fn strict_mcp_config(mut self, strict: bool) -> Self {
578 self.strict_mcp_config = strict;
579 self
580 }
581
582 pub fn session_id(mut self, id: Uuid) -> Self {
584 self.session_id = Some(id);
585 self
586 }
587
588 pub fn oauth_token<S: Into<String>>(mut self, token: S) -> Self {
590 let token_str = token.into();
591 if !token_str.starts_with("sk-ant-oat") {
592 eprintln!("Warning: OAuth token should start with 'sk-ant-oat'");
593 }
594 self.oauth_token = Some(token_str);
595 self
596 }
597
598 pub fn api_key<S: Into<String>>(mut self, key: S) -> Self {
600 let key_str = key.into();
601 if !key_str.starts_with("sk-ant-api") {
602 eprintln!("Warning: API key should start with 'sk-ant-api'");
603 }
604 self.api_key = Some(key_str);
605 self
606 }
607
608 pub fn permission_prompt_tool<S: Into<String>>(mut self, tool: S) -> Self {
623 self.permission_prompt_tool = Some(tool.into());
624 self
625 }
626
627 #[cfg(feature = "integration-tests")]
630 pub fn allow_recursion(mut self) -> Self {
631 self.allow_recursion = true;
632 self
633 }
634
635 fn resolve_command(&self) -> Result<PathBuf> {
637 if self.command.is_absolute() {
638 return Ok(self.command.clone());
639 }
640 which::which(&self.command).map_err(|_| Error::BinaryNotFound {
641 name: self.command.display().to_string(),
642 })
643 }
644
645 fn build_args(&self) -> Vec<String> {
647 let mut args = vec![
650 "--print".to_string(),
651 "--verbose".to_string(),
652 "--output-format".to_string(),
653 "stream-json".to_string(),
654 "--input-format".to_string(),
655 "stream-json".to_string(),
656 ];
657
658 if let Some(ref debug) = self.debug {
659 args.push("--debug".to_string());
660 if !debug.is_empty() {
661 args.push(debug.clone());
662 }
663 }
664
665 if self.dangerously_skip_permissions {
666 args.push("--dangerously-skip-permissions".to_string());
667 }
668
669 if !self.allowed_tools.is_empty() {
670 args.push("--allowed-tools".to_string());
671 args.extend(self.allowed_tools.clone());
672 }
673
674 if !self.disallowed_tools.is_empty() {
675 args.push("--disallowed-tools".to_string());
676 args.extend(self.disallowed_tools.clone());
677 }
678
679 if !self.mcp_config.is_empty() {
680 args.push("--mcp-config".to_string());
681 args.extend(self.mcp_config.clone());
682 }
683
684 if let Some(ref prompt) = self.append_system_prompt {
685 args.push("--append-system-prompt".to_string());
686 args.push(prompt.clone());
687 }
688
689 if let Some(ref mode) = self.permission_mode {
690 args.push("--permission-mode".to_string());
691 args.push(mode.as_str().to_string());
692 }
693
694 if self.continue_conversation {
695 args.push("--continue".to_string());
696 }
697
698 if let Some(ref session) = self.resume {
699 args.push("--resume".to_string());
700 args.push(session.clone());
701 }
702
703 if let Some(ref model) = self.model {
704 args.push("--model".to_string());
705 args.push(model.clone());
706 }
707
708 if let Some(ref model) = self.fallback_model {
709 args.push("--fallback-model".to_string());
710 args.push(model.clone());
711 }
712
713 if let Some(tokens) = self.max_thinking_tokens {
714 args.push("--max-thinking-tokens".to_string());
715 args.push(tokens.to_string());
716 }
717
718 if let Some(ref settings) = self.settings {
719 args.push("--settings".to_string());
720 args.push(settings.clone());
721 }
722
723 if !self.add_dir.is_empty() {
724 args.push("--add-dir".to_string());
725 for dir in &self.add_dir {
726 args.push(dir.to_string_lossy().to_string());
727 }
728 }
729
730 if self.ide {
731 args.push("--ide".to_string());
732 }
733
734 if self.strict_mcp_config {
735 args.push("--strict-mcp-config".to_string());
736 }
737
738 if let Some(ref tool) = self.permission_prompt_tool {
739 args.push("--permission-prompt-tool".to_string());
740 args.push(tool.clone());
741 }
742
743 if self.resume.is_none() && !self.continue_conversation {
747 args.push("--session-id".to_string());
748 let session_uuid = self.session_id.unwrap_or_else(|| {
749 let uuid = Uuid::new_v4();
750 debug!("[CLI] Generated session UUID: {}", uuid);
751 uuid
752 });
753 args.push(session_uuid.to_string());
754 }
755
756 if let Some(ref prompt) = self.prompt {
758 args.push(prompt.clone());
759 }
760
761 args
762 }
763
764 #[cfg(feature = "async-client")]
766 pub async fn spawn(self) -> Result<tokio::process::Child> {
767 let resolved = self.resolve_command()?;
768 let args = self.build_args();
769
770 debug!(
771 "[CLI] Executing command: {} {}",
772 resolved.display(),
773 args.join(" ")
774 );
775
776 let mut cmd = tokio::process::Command::new(&resolved);
777 cmd.args(&args)
778 .stdin(Stdio::piped())
779 .stdout(Stdio::piped())
780 .stderr(Stdio::piped());
781
782 if self.allow_recursion {
783 cmd.env_remove("CLAUDECODE");
784 }
785
786 if let Some(ref token) = self.oauth_token {
787 cmd.env("CLAUDE_CODE_OAUTH_TOKEN", token);
788 }
789
790 if let Some(ref key) = self.api_key {
791 cmd.env("ANTHROPIC_API_KEY", key);
792 }
793
794 let child = cmd.spawn().map_err(Error::Io)?;
795
796 Ok(child)
797 }
798
799 #[cfg(feature = "async-client")]
801 pub fn build_command(self) -> Result<tokio::process::Command> {
802 let resolved = self.resolve_command()?;
803 let args = self.build_args();
804 let mut cmd = tokio::process::Command::new(&resolved);
805 cmd.args(&args)
806 .stdin(Stdio::piped())
807 .stdout(Stdio::piped())
808 .stderr(Stdio::piped());
809
810 if self.allow_recursion {
811 cmd.env_remove("CLAUDECODE");
812 }
813
814 if let Some(ref token) = self.oauth_token {
815 cmd.env("CLAUDE_CODE_OAUTH_TOKEN", token);
816 }
817
818 if let Some(ref key) = self.api_key {
819 cmd.env("ANTHROPIC_API_KEY", key);
820 }
821
822 Ok(cmd)
823 }
824
825 pub fn spawn_sync(self) -> Result<std::process::Child> {
827 let resolved = self.resolve_command()?;
828 let args = self.build_args();
829
830 debug!(
831 "[CLI] Executing sync command: {} {}",
832 resolved.display(),
833 args.join(" ")
834 );
835
836 let mut cmd = std::process::Command::new(&resolved);
837 cmd.args(&args)
838 .stdin(Stdio::piped())
839 .stdout(Stdio::piped())
840 .stderr(Stdio::piped());
841
842 if self.allow_recursion {
843 cmd.env_remove("CLAUDECODE");
844 }
845
846 if let Some(ref token) = self.oauth_token {
847 cmd.env("CLAUDE_CODE_OAUTH_TOKEN", token);
848 }
849
850 if let Some(ref key) = self.api_key {
851 cmd.env("ANTHROPIC_API_KEY", key);
852 }
853
854 cmd.spawn().map_err(Error::Io)
855 }
856}
857
858#[cfg(test)]
859mod tests {
860 use super::*;
861
862 #[test]
863 fn test_streaming_flags_always_present() {
864 let builder = ClaudeCliBuilder::new();
865 let args = builder.build_args();
866
867 assert!(args.contains(&"--print".to_string()));
869 assert!(args.contains(&"--verbose".to_string())); assert!(args.contains(&"--output-format".to_string()));
871 assert!(args.contains(&"stream-json".to_string()));
872 assert!(args.contains(&"--input-format".to_string()));
873 }
874
875 #[test]
876 fn test_with_prompt() {
877 let builder = ClaudeCliBuilder::new().prompt("Hello, Claude!");
878 let args = builder.build_args();
879
880 assert_eq!(args.last().unwrap(), "Hello, Claude!");
881 }
882
883 #[test]
884 fn test_with_model() {
885 let builder = ClaudeCliBuilder::new()
886 .model("sonnet")
887 .fallback_model("opus");
888 let args = builder.build_args();
889
890 assert!(args.contains(&"--model".to_string()));
891 assert!(args.contains(&"sonnet".to_string()));
892 assert!(args.contains(&"--fallback-model".to_string()));
893 assert!(args.contains(&"opus".to_string()));
894 }
895
896 #[test]
897 fn test_with_debug() {
898 let builder = ClaudeCliBuilder::new().debug(Some("api"));
899 let args = builder.build_args();
900
901 assert!(args.contains(&"--debug".to_string()));
902 assert!(args.contains(&"api".to_string()));
903 }
904
905 #[test]
906 fn test_with_oauth_token() {
907 let valid_token = "sk-ant-oat-123456789";
908 let builder = ClaudeCliBuilder::new().oauth_token(valid_token);
909
910 let args = builder.clone().build_args();
912 assert!(!args.contains(&valid_token.to_string()));
913
914 assert_eq!(builder.oauth_token, Some(valid_token.to_string()));
916 }
917
918 #[test]
919 fn test_oauth_token_validation() {
920 let invalid_token = "invalid-token-123";
922 let builder = ClaudeCliBuilder::new().oauth_token(invalid_token);
923 assert_eq!(builder.oauth_token, Some(invalid_token.to_string()));
924 }
925
926 #[test]
927 fn test_with_api_key() {
928 let valid_key = "sk-ant-api-987654321";
929 let builder = ClaudeCliBuilder::new().api_key(valid_key);
930
931 let args = builder.clone().build_args();
933 assert!(!args.contains(&valid_key.to_string()));
934
935 assert_eq!(builder.api_key, Some(valid_key.to_string()));
937 }
938
939 #[test]
940 fn test_api_key_validation() {
941 let invalid_key = "invalid-api-key";
943 let builder = ClaudeCliBuilder::new().api_key(invalid_key);
944 assert_eq!(builder.api_key, Some(invalid_key.to_string()));
945 }
946
947 #[test]
948 fn test_both_auth_methods() {
949 let oauth = "sk-ant-oat-123";
950 let api_key = "sk-ant-api-456";
951 let builder = ClaudeCliBuilder::new().oauth_token(oauth).api_key(api_key);
952
953 assert_eq!(builder.oauth_token, Some(oauth.to_string()));
954 assert_eq!(builder.api_key, Some(api_key.to_string()));
955 }
956
957 #[test]
958 fn test_permission_prompt_tool() {
959 let builder = ClaudeCliBuilder::new().permission_prompt_tool("stdio");
960 let args = builder.build_args();
961
962 assert!(args.contains(&"--permission-prompt-tool".to_string()));
963 assert!(args.contains(&"stdio".to_string()));
964 }
965
966 #[test]
967 fn test_permission_prompt_tool_not_present_by_default() {
968 let builder = ClaudeCliBuilder::new();
969 let args = builder.build_args();
970
971 assert!(!args.contains(&"--permission-prompt-tool".to_string()));
972 }
973
974 #[test]
975 fn test_session_id_present_for_new_session() {
976 let builder = ClaudeCliBuilder::new();
977 let args = builder.build_args();
978
979 assert!(
980 args.contains(&"--session-id".to_string()),
981 "New sessions should have --session-id"
982 );
983 }
984
985 #[test]
986 fn test_session_id_not_present_with_resume() {
987 let builder = ClaudeCliBuilder::new().resume(Some("existing-uuid".to_string()));
990 let args = builder.build_args();
991
992 assert!(
993 args.contains(&"--resume".to_string()),
994 "Should have --resume flag"
995 );
996 assert!(
997 !args.contains(&"--session-id".to_string()),
998 "--session-id should NOT be present when resuming"
999 );
1000 }
1001
1002 #[test]
1003 fn test_session_id_not_present_with_continue() {
1004 let builder = ClaudeCliBuilder::new().continue_conversation(true);
1006 let args = builder.build_args();
1007
1008 assert!(
1009 args.contains(&"--continue".to_string()),
1010 "Should have --continue flag"
1011 );
1012 assert!(
1013 !args.contains(&"--session-id".to_string()),
1014 "--session-id should NOT be present when continuing"
1015 );
1016 }
1017}