use crate::error::RunnerError;
use crate::runner::CommandSpec;
use crate::types::RunnerMode;
use std::process::Stdio;
use super::exec::Runner;
impl Runner {
pub fn detect_auto() -> Result<RunnerMode, RunnerError> {
if !cfg!(target_os = "windows") {
return Ok(RunnerMode::Native);
}
if Self::test_native_claude().is_ok() {
return Ok(RunnerMode::Native);
}
match Self::test_wsl_claude() {
Ok(()) => Ok(RunnerMode::Wsl),
Err(_) => {
Err(RunnerError::DetectionFailed {
reason: "Claude CLI not found in Windows PATH and WSL is not available or doesn't have Claude installed".to_string(),
})
}
}
}
pub fn test_native_claude() -> Result<(), RunnerError> {
let output = CommandSpec::new("claude")
.arg("--version")
.to_command()
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.map_err(|e| RunnerError::NativeExecutionFailed {
reason: format!("Failed to execute 'claude --version': {e}"),
})?;
if output.status.success() {
Ok(())
} else {
Err(RunnerError::NativeExecutionFailed {
reason: format!(
"'claude --version' failed with exit code: {}",
output.status.code().unwrap_or(-1)
),
})
}
}
pub fn test_wsl_claude() -> Result<(), RunnerError> {
let output = CommandSpec::new("wsl")
.args(["-e", "claude", "--version"])
.to_command()
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.map_err(|e| RunnerError::WslNotAvailable {
reason: format!("Failed to execute 'wsl -e claude --version': {e}"),
})?;
if output.status.success() {
Ok(())
} else {
Err(RunnerError::WslExecutionFailed {
reason: format!(
"'wsl -e claude --version' failed with exit code: {}",
output.status.code().unwrap_or(-1)
),
})
}
}
pub fn validate(&self) -> Result<(), RunnerError> {
match self.mode {
RunnerMode::Auto => {
Self::detect_auto().map(|_| ())
}
RunnerMode::Native => self.test_native_claude_with_path(),
RunnerMode::Wsl => {
if cfg!(target_os = "windows") {
Self::test_wsl_claude()
} else {
Err(RunnerError::ConfigurationInvalid {
reason: "WSL runner mode is only supported on Windows".to_string(),
})
}
}
}
}
#[must_use]
#[allow(dead_code)] pub fn description(&self) -> String {
match self.mode {
RunnerMode::Auto => {
"Automatic detection (native first, then WSL on Windows)".to_string()
}
RunnerMode::Native => {
let mut desc = "Native execution (spawn claude directly)".to_string();
if let Some(claude_path) = &self.wsl_options.claude_path {
desc.push_str(&format!(" (claude path: {claude_path})"));
}
desc
}
RunnerMode::Wsl => {
let mut desc = "WSL execution".to_string();
if let Some(distro) = &self.wsl_options.distro {
desc.push_str(&format!(" (distro: {distro})"));
}
if let Some(claude_path) = &self.wsl_options.claude_path {
desc.push_str(&format!(" (claude path: {claude_path})"));
}
desc
}
}
}
fn test_native_claude_with_path(&self) -> Result<(), RunnerError> {
let output = self
.native_command_spec(&["--version".to_string()])
.to_command()
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.map_err(|e| RunnerError::NativeExecutionFailed {
reason: format!("Failed to execute 'claude --version': {e}"),
})?;
if output.status.success() {
Ok(())
} else {
Err(RunnerError::NativeExecutionFailed {
reason: format!(
"'claude --version' failed with exit code: {}",
output.status.code().unwrap_or(-1)
),
})
}
}
}
#[cfg(test)]
mod tests {
use super::Runner;
use crate::error::RunnerError;
use crate::runner::claude::WslOptions;
use crate::types::RunnerMode;
#[test]
fn test_runner_description() {
let runner = Runner::new(RunnerMode::Native, WslOptions::default());
assert_eq!(
runner.description(),
"Native execution (spawn claude directly)"
);
let wsl_options = WslOptions {
distro: Some("Ubuntu-22.04".to_string()),
claude_path: Some("/usr/local/bin/claude".to_string()),
};
let runner = Runner::new(RunnerMode::Wsl, wsl_options);
assert!(runner.description().contains("WSL execution"));
assert!(runner.description().contains("Ubuntu-22.04"));
assert!(runner.description().contains("/usr/local/bin/claude"));
}
#[cfg(not(target_os = "windows"))]
#[test]
fn test_auto_detection_non_windows() {
let result = Runner::detect_auto();
assert!(result.is_ok());
assert_eq!(result.unwrap(), RunnerMode::Native);
}
#[test]
fn test_wsl_validation_on_non_windows() {
if !cfg!(target_os = "windows") {
let runner = Runner::new(RunnerMode::Wsl, WslOptions::default());
let result = runner.validate();
assert!(result.is_err());
if let Err(RunnerError::ConfigurationInvalid { reason }) = result {
assert!(reason.contains("only supported on Windows"));
} else {
panic!("Expected ConfigurationInvalid error");
}
}
}
}