xchecker_runner/claude/
detect.rs1use crate::command_spec::CommandSpec;
2use crate::error::RunnerError;
3use crate::types::RunnerMode;
4use std::process::Stdio;
5
6use super::exec::Runner;
7
8impl Runner {
9 pub fn detect_auto() -> Result<RunnerMode, RunnerError> {
18 if !cfg!(target_os = "windows") {
20 return Ok(RunnerMode::Native);
21 }
22
23 if Self::test_native_claude().is_ok() {
25 return Ok(RunnerMode::Native);
26 }
27
28 match Self::test_wsl_claude() {
30 Ok(()) => Ok(RunnerMode::Wsl),
31 Err(_) => {
32 Err(RunnerError::DetectionFailed {
34 reason: "Claude CLI not found in Windows PATH and WSL is not available or doesn't have Claude installed".to_string(),
35 })
36 }
37 }
38 }
39
40 pub fn test_native_claude() -> Result<(), RunnerError> {
42 let output = CommandSpec::new("claude")
44 .arg("--version")
45 .to_command()
46 .stdout(Stdio::piped())
47 .stderr(Stdio::piped())
48 .output()
49 .map_err(|e| RunnerError::NativeExecutionFailed {
50 reason: format!("Failed to execute 'claude --version': {e}"),
51 })?;
52
53 if output.status.success() {
54 Ok(())
55 } else {
56 Err(RunnerError::NativeExecutionFailed {
57 reason: format!(
58 "'claude --version' failed with exit code: {}",
59 output.status.code().unwrap_or(-1)
60 ),
61 })
62 }
63 }
64
65 pub fn test_wsl_claude() -> Result<(), RunnerError> {
67 let output = CommandSpec::new("wsl")
69 .args(["-e", "claude", "--version"])
70 .to_command()
71 .stdout(Stdio::piped())
72 .stderr(Stdio::piped())
73 .output()
74 .map_err(|e| RunnerError::WslNotAvailable {
75 reason: format!("Failed to execute 'wsl -e claude --version': {e}"),
76 })?;
77
78 if output.status.success() {
79 Ok(())
80 } else {
81 Err(RunnerError::WslExecutionFailed {
82 reason: format!(
83 "'wsl -e claude --version' failed with exit code: {}",
84 output.status.code().unwrap_or(-1)
85 ),
86 })
87 }
88 }
89
90 pub fn validate(&self) -> Result<(), RunnerError> {
92 match self.mode {
93 RunnerMode::Auto => {
94 Self::detect_auto().map(|_| ())
96 }
97 RunnerMode::Native => self.test_native_claude_with_path(),
98 RunnerMode::Wsl => {
99 if cfg!(target_os = "windows") {
101 Self::test_wsl_claude()
102 } else {
103 Err(RunnerError::ConfigurationInvalid {
104 reason: "WSL runner mode is only supported on Windows".to_string(),
105 })
106 }
107 }
108 }
109 }
110
111 #[must_use]
113 #[allow(dead_code)] pub fn description(&self) -> String {
115 match self.mode {
116 RunnerMode::Auto => {
117 "Automatic detection (native first, then WSL on Windows)".to_string()
118 }
119 RunnerMode::Native => {
120 let mut desc = "Native execution (spawn claude directly)".to_string();
121 if let Some(claude_path) = &self.wsl_options.claude_path {
122 desc.push_str(&format!(" (claude path: {claude_path})"));
123 }
124 desc
125 }
126 RunnerMode::Wsl => {
127 let mut desc = "WSL execution".to_string();
128 if let Some(distro) = &self.wsl_options.distro {
129 desc.push_str(&format!(" (distro: {distro})"));
130 }
131 if let Some(claude_path) = &self.wsl_options.claude_path {
132 desc.push_str(&format!(" (claude path: {claude_path})"));
133 }
134 desc
135 }
136 }
137 }
138
139 fn test_native_claude_with_path(&self) -> Result<(), RunnerError> {
140 let output = self
141 .native_command_spec(&["--version".to_string()])
142 .to_command()
143 .stdout(Stdio::piped())
144 .stderr(Stdio::piped())
145 .output()
146 .map_err(|e| RunnerError::NativeExecutionFailed {
147 reason: format!("Failed to execute 'claude --version': {e}"),
148 })?;
149
150 if output.status.success() {
151 Ok(())
152 } else {
153 Err(RunnerError::NativeExecutionFailed {
154 reason: format!(
155 "'claude --version' failed with exit code: {}",
156 output.status.code().unwrap_or(-1)
157 ),
158 })
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::Runner;
166 use crate::claude::WslOptions;
167 use crate::error::RunnerError;
168 use crate::types::RunnerMode;
169
170 #[test]
171 fn test_runner_description() {
172 let runner = Runner::new(RunnerMode::Native, WslOptions::default());
173 assert_eq!(
174 runner.description(),
175 "Native execution (spawn claude directly)"
176 );
177
178 let wsl_options = WslOptions {
179 distro: Some("Ubuntu-22.04".to_string()),
180 claude_path: Some("/usr/local/bin/claude".to_string()),
181 };
182 let runner = Runner::new(RunnerMode::Wsl, wsl_options);
183 assert!(runner.description().contains("WSL execution"));
184 assert!(runner.description().contains("Ubuntu-22.04"));
185 assert!(runner.description().contains("/usr/local/bin/claude"));
186 }
187
188 #[cfg(not(target_os = "windows"))]
189 #[test]
190 fn test_auto_detection_non_windows() {
191 let result = Runner::detect_auto();
193 assert!(result.is_ok());
194 assert_eq!(result.unwrap(), RunnerMode::Native);
195 }
196
197 #[test]
198 fn test_wsl_validation_on_non_windows() {
199 if !cfg!(target_os = "windows") {
200 let runner = Runner::new(RunnerMode::Wsl, WslOptions::default());
201 let result = runner.validate();
202 assert!(result.is_err());
203 if let Err(RunnerError::ConfigurationInvalid { reason }) = result {
204 assert!(reason.contains("only supported on Windows"));
205 } else {
206 panic!("Expected ConfigurationInvalid error");
207 }
208 }
209 }
210}