From cd95bf5d35e8152b80af751734dd9bc5378b4e52 Mon Sep 17 00:00:00 2001 From: Winner-Nick Date: Mon, 18 Aug 2025 16:57:01 +0800 Subject: [PATCH] fix: improve Windows support for Claude CLI detection - Add Windows-specific path detection for Claude CLI binary - Support NPM global installations (most common on Windows) - Use 'where' command instead of 'which' on Windows - Handle multiple paths returned by Windows 'where' command - Add PowerShell script for ICO file generation - Include proper ICO file for Windows builds - Maintain full compatibility with Linux and macOS Fixes issue with "Could not find claude binary" error on Windows when Claude CLI is installed via NPM globally. --- create_ico.ps1 | 26 ++++++++++++ src-tauri/icons/icon.ico | Bin 0 -> 766 bytes src-tauri/src/claude_binary.rs | 71 ++++++++++++++++++++++++++++----- 3 files changed, 87 insertions(+), 10 deletions(-) create mode 100644 create_ico.ps1 create mode 100644 src-tauri/icons/icon.ico diff --git a/create_ico.ps1 b/create_ico.ps1 new file mode 100644 index 00000000..fe046a42 --- /dev/null +++ b/create_ico.ps1 @@ -0,0 +1,26 @@ +# PowerShell script to convert PNG to ICO format +$pngPath = "src-tauri\icons\icon.png" +$icoPath = "src-tauri\icons\icon.ico" + +Add-Type -AssemblyName System.Drawing + +# Load the PNG image +$png = [System.Drawing.Image]::FromFile((Resolve-Path $pngPath)) + +# Create a new bitmap with 32x32 size +$bitmap = New-Object System.Drawing.Bitmap(32, 32) +$graphics = [System.Drawing.Graphics]::FromImage($bitmap) +$graphics.DrawImage($png, 0, 0, 32, 32) + +# Create ICO file +$icon = [System.Drawing.Icon]::FromHandle($bitmap.GetHicon()) +$fileStream = [System.IO.File]::Create((Resolve-Path ".").Path + "\" + $icoPath) +$icon.Save($fileStream) + +# Clean up +$fileStream.Close() +$graphics.Dispose() +$bitmap.Dispose() +$png.Dispose() + +Write-Host "ICO file created successfully at $icoPath" \ No newline at end of file diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..03b4b8d9474d8693d536a347992448779ff9aea0 GIT binary patch literal 766 zcmd5)F%rTc5M0z5Tho|QN=qFJT7N~$(|AfspI{evLoOqzl@=aZj=dubcLU%!yx&1~ zTLF9eBjJfF;jrNhB<9{*Vv?DZisQ^Bk&ts{Vw^sVqgDimLYn@=x~{rMi0<+f=wk%V zOTa~OR~p9XcH%hMhEVw0I4SqTxPBKm0ppkjKXKcSMh~Irnf}Xt+WVrf>j4$2dV!`N iR>|+Zp1~bB3~Xa&kA%eD81SW;j(nc=2cD%fb@l^%Q9bMc literal 0 HcmV?d00001 diff --git a/src-tauri/src/claude_binary.rs b/src-tauri/src/claude_binary.rs index 2d1c7e36..4edc943f 100644 --- a/src-tauri/src/claude_binary.rs +++ b/src-tauri/src/claude_binary.rs @@ -168,7 +168,14 @@ fn discover_system_installations() -> Vec { fn try_which_command() -> Option { debug!("Trying 'which claude' to find binary..."); - match Command::new("which").arg("claude").output() { + // Use 'where' on Windows, 'which' on Unix systems + let (cmd, args) = if cfg!(windows) { + ("where", vec!["claude"]) + } else { + ("which", vec!["claude"]) + }; + + match Command::new(cmd).args(&args).output() { Ok(output) if output.status.success() => { let output_str = String::from_utf8_lossy(&output.stdout).trim().to_string(); @@ -176,15 +183,22 @@ fn try_which_command() -> Option { return None; } - // Parse aliased output: "claude: aliased to /path/to/claude" - let path = if output_str.starts_with("claude:") && output_str.contains("aliased to") { - output_str - .split("aliased to") - .nth(1) - .map(|s| s.trim().to_string()) + // Handle Windows 'where' command which may return multiple paths + let path = if cfg!(windows) { + // Take the first line from Windows 'where' command output + output_str.lines().next().unwrap_or(&output_str).to_string() } else { - Some(output_str) - }?; + // Parse aliased output on Unix: "claude: aliased to /path/to/claude" + if output_str.starts_with("claude:") && output_str.contains("aliased to") { + output_str + .split("aliased to") + .nth(1) + .map(|s| s.trim().to_string()) + .unwrap_or(output_str) + } else { + output_str + } + }; debug!("'which' found claude at: {}", path); @@ -264,7 +278,44 @@ fn find_standard_installations() -> Vec { ("/bin/claude".to_string(), "system".to_string()), ]; - // Also check user-specific paths + // Check Windows-specific paths first + if cfg!(windows) { + if let Ok(userprofile) = std::env::var("USERPROFILE") { + paths_to_check.extend(vec![ + // NPM global installation (most common on Windows) + ( + format!("{}\\AppData\\Roaming\\npm\\claude.cmd", userprofile), + "npm-global".to_string(), + ), + ( + format!("{}\\AppData\\Roaming\\npm\\claude", userprofile), + "npm-global".to_string(), + ), + // Other Windows package managers + ( + format!("{}\\AppData\\Local\\Yarn\\bin\\claude.cmd", userprofile), + "yarn-global".to_string(), + ), + ( + format!("{}\\.bun\\bin\\claude.exe", userprofile), + "bun".to_string(), + ), + // Scoop installation + ( + format!("{}\\scoop\\apps\\claude\\current\\claude.exe", userprofile), + "scoop".to_string(), + ), + ]); + } + + // Check Program Files + paths_to_check.extend(vec![ + ("C:\\Program Files\\Claude\\claude.exe".to_string(), "system".to_string()), + ("C:\\Program Files (x86)\\Claude\\claude.exe".to_string(), "system".to_string()), + ]); + } + + // Also check user-specific paths (Unix-style) if let Ok(home) = std::env::var("HOME") { paths_to_check.extend(vec![ (