From 181548a45379d5c847031fe99ca8255f59571db0 Mon Sep 17 00:00:00 2001 From: Mohammed Date: Wed, 24 Sep 2025 10:33:52 +0200 Subject: [PATCH] fix(tauri): add windows support for claude binary detection This commit adds support for detecting the Claude binary on Windows. The previous implementation used Unix-specific commands and paths, which caused the application to fail to locate the Claude binary on Windows systems. This commit introduces conditional compilation for Windows and Unix-like systems to fix the issue. --- src-tauri/src/claude_binary.rs | 150 +++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) diff --git a/src-tauri/src/claude_binary.rs b/src-tauri/src/claude_binary.rs index 2d1c7e366..b2286e279 100644 --- a/src-tauri/src/claude_binary.rs +++ b/src-tauri/src/claude_binary.rs @@ -165,6 +165,7 @@ fn discover_system_installations() -> Vec { } /// Try using the 'which' command to find Claude +#[cfg(unix)] fn try_which_command() -> Option { debug!("Trying 'which claude' to find binary..."); @@ -208,7 +209,49 @@ fn try_which_command() -> Option { } } +#[cfg(windows)] +fn try_which_command() -> Option { + debug!("Trying 'where claude' to find binary..."); + + match Command::new("where").arg("claude").output() { + Ok(output) if output.status.success() => { + let output_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + if output_str.is_empty() { + return None; + } + + // On Windows, `where` can return multiple paths, newline-separated. We take the first one. + let path = output_str.lines().next().unwrap_or("").trim().to_string(); + + if path.is_empty() { + return None; + } + + debug!("'where' found claude at: {}", path); + + // Verify the path exists + if !PathBuf::from(&path).exists() { + warn!("Path from 'where' does not exist: {}", path); + return None; + } + + // Get version + let version = get_claude_version(&path).ok().flatten(); + + Some(ClaudeInstallation { + path, + version, + source: "where".to_string(), + installation_type: InstallationType::System, + }) + } + _ => None, + } +} + /// Find Claude installations in NVM directories +#[cfg(unix)] fn find_nvm_installations() -> Vec { let mut installations = Vec::new(); @@ -249,7 +292,44 @@ fn find_nvm_installations() -> Vec { installations } +#[cfg(windows)] +fn find_nvm_installations() -> Vec { + let mut installations = Vec::new(); + + if let Ok(nvm_home) = std::env::var("NVM_HOME") { + debug!("Checking NVM_HOME directory: {:?}", nvm_home); + + if let Ok(entries) = std::fs::read_dir(&nvm_home) { + for entry in entries.flatten() { + if entry.file_type().map(|t| t.is_dir()).unwrap_or(false) { + let claude_path = entry.path().join("claude.exe"); + + if claude_path.exists() && claude_path.is_file() { + let path_str = claude_path.to_string_lossy().to_string(); + let node_version = entry.file_name().to_string_lossy().to_string(); + + debug!("Found Claude in NVM node {}: {}", node_version, path_str); + + // Get Claude version + let version = get_claude_version(&path_str).ok().flatten(); + + installations.push(ClaudeInstallation { + path: path_str, + version, + source: format!("nvm ({})", node_version), + installation_type: InstallationType::System, + }); + } + } + } + } + } + + installations +} + /// Check standard installation paths +#[cfg(unix)] fn find_standard_installations() -> Vec { let mut installations = Vec::new(); @@ -330,6 +410,76 @@ fn find_standard_installations() -> Vec { installations } +#[cfg(windows)] +fn find_standard_installations() -> Vec { + let mut installations = Vec::new(); + + // Common installation paths for claude on Windows + let mut paths_to_check: Vec<(String, String)> = vec![]; + + // Check user-specific paths + if let Ok(user_profile) = std::env::var("USERPROFILE") { + paths_to_check.extend(vec![ + ( + format!("{}\\.claude\\local\\claude.exe", user_profile), + "claude-local".to_string(), + ), + ( + format!("{}\\.local\\bin\\claude.exe", user_profile), + "local-bin".to_string(), + ), + ( + format!("{}\\AppData\\Roaming\\npm\\claude.cmd", user_profile), + "npm-global".to_string(), + ), + ( + format!("{}\\.yarn\\bin\\claude.cmd", user_profile), + "yarn".to_string(), + ), + ( + format!("{}\\.bun\\bin\\claude.exe", user_profile), + "bun".to_string(), + ), + ]); + } + + // Check each path + for (path, source) in paths_to_check { + let path_buf = PathBuf::from(&path); + if path_buf.exists() && path_buf.is_file() { + debug!("Found claude at standard path: {} ({})", path, source); + + // Get version + let version = get_claude_version(&path).ok().flatten(); + + installations.push(ClaudeInstallation { + path, + version, + source, + installation_type: InstallationType::System, + }); + } + } + + // Also check if claude is available in PATH (without full path) + if let Ok(output) = Command::new("claude.exe").arg("--version").output() { + if output.status.success() { + debug!("claude.exe is available in PATH"); + let version = extract_version_from_output(&output.stdout); + + installations.push(ClaudeInstallation { + path: "claude.exe".to_string(), + version, + source: "PATH".to_string(), + installation_type: InstallationType::System, + }); + } + } + + installations +} + + /// Get Claude version by running --version command fn get_claude_version(path: &str) -> Result, String> { match Command::new(path).arg("--version").output() {