@@ -166,6 +166,7 @@ fn discover_system_installations() -> Vec<ClaudeInstallation> {
166166}
167167
168168/// Try using the 'which' command to find Claude
169+ #[ cfg( unix) ]
169170fn try_which_command ( ) -> Option < ClaudeInstallation > {
170171 debug ! ( "Trying 'which claude' to find binary..." ) ;
171172
@@ -209,7 +210,49 @@ fn try_which_command() -> Option<ClaudeInstallation> {
209210 }
210211}
211212
213+ #[ cfg( windows) ]
214+ fn try_which_command ( ) -> Option < ClaudeInstallation > {
215+ debug ! ( "Trying 'where claude' to find binary..." ) ;
216+
217+ match Command :: new ( "where" ) . arg ( "claude" ) . output ( ) {
218+ Ok ( output) if output. status . success ( ) => {
219+ let output_str = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
220+
221+ if output_str. is_empty ( ) {
222+ return None ;
223+ }
224+
225+ // On Windows, `where` can return multiple paths, newline-separated. We take the first one.
226+ let path = output_str. lines ( ) . next ( ) . unwrap_or ( "" ) . trim ( ) . to_string ( ) ;
227+
228+ if path. is_empty ( ) {
229+ return None ;
230+ }
231+
232+ debug ! ( "'where' found claude at: {}" , path) ;
233+
234+ // Verify the path exists
235+ if !PathBuf :: from ( & path) . exists ( ) {
236+ warn ! ( "Path from 'where' does not exist: {}" , path) ;
237+ return None ;
238+ }
239+
240+ // Get version
241+ let version = get_claude_version ( & path) . ok ( ) . flatten ( ) ;
242+
243+ Some ( ClaudeInstallation {
244+ path,
245+ version,
246+ source : "where" . to_string ( ) ,
247+ installation_type : InstallationType :: System ,
248+ } )
249+ }
250+ _ => None ,
251+ }
252+ }
253+
212254/// Find Claude installations in NVM directories
255+ #[ cfg( unix) ]
213256fn find_nvm_installations ( ) -> Vec < ClaudeInstallation > {
214257 let mut installations = Vec :: new ( ) ;
215258
@@ -266,7 +309,44 @@ fn find_nvm_installations() -> Vec<ClaudeInstallation> {
266309 installations
267310}
268311
312+ #[ cfg( windows) ]
313+ fn find_nvm_installations ( ) -> Vec < ClaudeInstallation > {
314+ let mut installations = Vec :: new ( ) ;
315+
316+ if let Ok ( nvm_home) = std:: env:: var ( "NVM_HOME" ) {
317+ debug ! ( "Checking NVM_HOME directory: {:?}" , nvm_home) ;
318+
319+ if let Ok ( entries) = std:: fs:: read_dir ( & nvm_home) {
320+ for entry in entries. flatten ( ) {
321+ if entry. file_type ( ) . map ( |t| t. is_dir ( ) ) . unwrap_or ( false ) {
322+ let claude_path = entry. path ( ) . join ( "claude.exe" ) ;
323+
324+ if claude_path. exists ( ) && claude_path. is_file ( ) {
325+ let path_str = claude_path. to_string_lossy ( ) . to_string ( ) ;
326+ let node_version = entry. file_name ( ) . to_string_lossy ( ) . to_string ( ) ;
327+
328+ debug ! ( "Found Claude in NVM node {}: {}" , node_version, path_str) ;
329+
330+ // Get Claude version
331+ let version = get_claude_version ( & path_str) . ok ( ) . flatten ( ) ;
332+
333+ installations. push ( ClaudeInstallation {
334+ path : path_str,
335+ version,
336+ source : format ! ( "nvm ({})" , node_version) ,
337+ installation_type : InstallationType :: System ,
338+ } ) ;
339+ }
340+ }
341+ }
342+ }
343+ }
344+
345+ installations
346+ }
347+
269348/// Check standard installation paths
349+ #[ cfg( unix) ]
270350fn find_standard_installations ( ) -> Vec < ClaudeInstallation > {
271351 let mut installations = Vec :: new ( ) ;
272352
@@ -347,6 +427,76 @@ fn find_standard_installations() -> Vec<ClaudeInstallation> {
347427 installations
348428}
349429
430+ #[ cfg( windows) ]
431+ fn find_standard_installations ( ) -> Vec < ClaudeInstallation > {
432+ let mut installations = Vec :: new ( ) ;
433+
434+ // Common installation paths for claude on Windows
435+ let mut paths_to_check: Vec < ( String , String ) > = vec ! [ ] ;
436+
437+ // Check user-specific paths
438+ if let Ok ( user_profile) = std:: env:: var ( "USERPROFILE" ) {
439+ paths_to_check. extend ( vec ! [
440+ (
441+ format!( "{}\\ .claude\\ local\\ claude.exe" , user_profile) ,
442+ "claude-local" . to_string( ) ,
443+ ) ,
444+ (
445+ format!( "{}\\ .local\\ bin\\ claude.exe" , user_profile) ,
446+ "local-bin" . to_string( ) ,
447+ ) ,
448+ (
449+ format!( "{}\\ AppData\\ Roaming\\ npm\\ claude.cmd" , user_profile) ,
450+ "npm-global" . to_string( ) ,
451+ ) ,
452+ (
453+ format!( "{}\\ .yarn\\ bin\\ claude.cmd" , user_profile) ,
454+ "yarn" . to_string( ) ,
455+ ) ,
456+ (
457+ format!( "{}\\ .bun\\ bin\\ claude.exe" , user_profile) ,
458+ "bun" . to_string( ) ,
459+ ) ,
460+ ] ) ;
461+ }
462+
463+ // Check each path
464+ for ( path, source) in paths_to_check {
465+ let path_buf = PathBuf :: from ( & path) ;
466+ if path_buf. exists ( ) && path_buf. is_file ( ) {
467+ debug ! ( "Found claude at standard path: {} ({})" , path, source) ;
468+
469+ // Get version
470+ let version = get_claude_version ( & path) . ok ( ) . flatten ( ) ;
471+
472+ installations. push ( ClaudeInstallation {
473+ path,
474+ version,
475+ source,
476+ installation_type : InstallationType :: System ,
477+ } ) ;
478+ }
479+ }
480+
481+ // Also check if claude is available in PATH (without full path)
482+ if let Ok ( output) = Command :: new ( "claude.exe" ) . arg ( "--version" ) . output ( ) {
483+ if output. status . success ( ) {
484+ debug ! ( "claude.exe is available in PATH" ) ;
485+ let version = extract_version_from_output ( & output. stdout ) ;
486+
487+ installations. push ( ClaudeInstallation {
488+ path : "claude.exe" . to_string ( ) ,
489+ version,
490+ source : "PATH" . to_string ( ) ,
491+ installation_type : InstallationType :: System ,
492+ } ) ;
493+ }
494+ }
495+
496+ installations
497+ }
498+
499+
350500/// Get Claude version by running --version command
351501fn get_claude_version ( path : & str ) -> Result < Option < String > , String > {
352502 match Command :: new ( path) . arg ( "--version" ) . output ( ) {
0 commit comments