@@ -24,10 +24,13 @@ pub struct ClaudeInstallation {
2424 pub path : String ,
2525 /// Version string if available
2626 pub version : Option < String > ,
27- /// Source of discovery (e.g., "nvm", "system", "homebrew", "which")
27+ /// Source of discovery (e.g., "nvm", "system", "homebrew", "which", "wsl" )
2828 pub source : String ,
2929 /// Type of installation
3030 pub installation_type : InstallationType ,
31+ /// WSL distribution name (if this is a WSL installation)
32+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
33+ pub wsl_distro : Option < String > ,
3134}
3235
3336/// Main function to find the Claude binary
@@ -204,6 +207,7 @@ fn try_which_command() -> Option<ClaudeInstallation> {
204207 version,
205208 source : "which" . to_string ( ) ,
206209 installation_type : InstallationType :: System ,
210+ wsl_distro : None ,
207211 } )
208212 }
209213 _ => None ,
@@ -245,6 +249,7 @@ fn try_which_command() -> Option<ClaudeInstallation> {
245249 version,
246250 source : "where" . to_string ( ) ,
247251 installation_type : InstallationType :: System ,
252+ wsl_distro : None ,
248253 } )
249254 }
250255 _ => None ,
@@ -269,6 +274,7 @@ fn find_nvm_installations() -> Vec<ClaudeInstallation> {
269274 version,
270275 source : "nvm-active" . to_string ( ) ,
271276 installation_type : InstallationType :: System ,
277+ wsl_distro : None ,
272278 } ) ;
273279 }
274280 }
@@ -337,6 +343,7 @@ fn find_nvm_installations() -> Vec<ClaudeInstallation> {
337343 version,
338344 source : format ! ( "nvm ({})" , node_version) ,
339345 installation_type : InstallationType :: System ,
346+ wsl_distro : None ,
340347 } ) ;
341348 }
342349 }
@@ -407,6 +414,7 @@ fn find_standard_installations() -> Vec<ClaudeInstallation> {
407414 version,
408415 source,
409416 installation_type : InstallationType :: System ,
417+ wsl_distro : None ,
410418 } ) ;
411419 }
412420 }
@@ -422,6 +430,7 @@ fn find_standard_installations() -> Vec<ClaudeInstallation> {
422430 version,
423431 source : "PATH" . to_string ( ) ,
424432 installation_type : InstallationType :: System ,
433+ wsl_distro : None ,
425434 } ) ;
426435 }
427436 }
@@ -476,6 +485,7 @@ fn find_standard_installations() -> Vec<ClaudeInstallation> {
476485 version,
477486 source,
478487 installation_type : InstallationType :: System ,
488+ wsl_distro : None ,
479489 } ) ;
480490 }
481491 }
@@ -491,13 +501,207 @@ fn find_standard_installations() -> Vec<ClaudeInstallation> {
491501 version,
492502 source : "PATH" . to_string ( ) ,
493503 installation_type : InstallationType :: System ,
504+ wsl_distro : None ,
505+ } ) ;
506+ }
507+ }
508+
509+ // Also check WSL installations
510+ installations. extend ( find_wsl_installations ( ) ) ;
511+
512+ installations
513+ }
514+
515+ /// Find Claude installations in WSL distributions (Windows only)
516+ #[ cfg( windows) ]
517+ fn find_wsl_installations ( ) -> Vec < ClaudeInstallation > {
518+ let mut installations = Vec :: new ( ) ;
519+
520+ debug ! ( "Checking for Claude installations in WSL..." ) ;
521+
522+ // Get list of WSL distributions
523+ let distros = match get_wsl_distributions ( ) {
524+ Ok ( d) => d,
525+ Err ( e) => {
526+ debug ! ( "Failed to get WSL distributions: {}" , e) ;
527+ return installations;
528+ }
529+ } ;
530+
531+ for distro in distros {
532+ debug ! ( "Checking WSL distribution: {}" , distro) ;
533+
534+ // Try to find claude in this distribution
535+ if let Some ( claude_path) = find_claude_in_wsl ( & distro) {
536+ debug ! ( "Found Claude in WSL {}: {}" , distro, claude_path) ;
537+
538+ // Get version
539+ let version = get_claude_version_in_wsl ( & distro, & claude_path) ;
540+
541+ installations. push ( ClaudeInstallation {
542+ path : claude_path,
543+ version,
544+ source : format ! ( "wsl ({})" , distro) ,
545+ installation_type : InstallationType :: System ,
546+ wsl_distro : Some ( distro) ,
494547 } ) ;
495548 }
496549 }
497550
498551 installations
499552}
500553
554+ /// Get list of WSL distributions
555+ #[ cfg( windows) ]
556+ fn get_wsl_distributions ( ) -> Result < Vec < String > , String > {
557+ // Run: wsl -l -q (quiet mode, just names)
558+ let output = Command :: new ( "wsl" )
559+ . args ( [ "-l" , "-q" ] )
560+ . output ( )
561+ . map_err ( |e| format ! ( "Failed to run wsl -l -q: {}" , e) ) ?;
562+
563+ if !output. status . success ( ) {
564+ return Err ( "wsl -l -q failed" . to_string ( ) ) ;
565+ }
566+
567+ // WSL output is UTF-16 LE encoded on Windows
568+ let output_str = String :: from_utf16_lossy (
569+ & output
570+ . stdout
571+ . chunks ( 2 )
572+ . filter_map ( |chunk| {
573+ if chunk. len ( ) == 2 {
574+ Some ( u16:: from_le_bytes ( [ chunk[ 0 ] , chunk[ 1 ] ] ) )
575+ } else {
576+ None
577+ }
578+ } )
579+ . collect :: < Vec < u16 > > ( ) ,
580+ ) ;
581+
582+ let distros: Vec < String > = output_str
583+ . lines ( )
584+ . map ( |s| s. trim ( ) . trim_matches ( '\0' ) . to_string ( ) )
585+ . filter ( |s| !s. is_empty ( ) )
586+ . collect ( ) ;
587+
588+ debug ! ( "Found WSL distributions: {:?}" , distros) ;
589+ Ok ( distros)
590+ }
591+
592+ /// Find Claude binary path in a WSL distribution
593+ #[ cfg( windows) ]
594+ fn find_claude_in_wsl ( distro : & str ) -> Option < String > {
595+ // Try 'which claude' in the WSL distribution
596+ let output = Command :: new ( "wsl" )
597+ . args ( [ "-d" , distro, "--" , "which" , "claude" ] )
598+ . output ( )
599+ . ok ( ) ?;
600+
601+ if output. status . success ( ) {
602+ let path = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
603+ if !path. is_empty ( ) && !path. contains ( "not found" ) {
604+ return Some ( path) ;
605+ }
606+ }
607+
608+ // Try common paths if 'which' doesn't work
609+ let common_paths = [ "/usr/local/bin/claude" , "/usr/bin/claude" ] ;
610+
611+ // Also check NVM paths - first get home directory
612+ if let Some ( home) = get_wsl_home_dir ( distro) {
613+ // Check if there's an NVM installation
614+ let nvm_base = format ! ( "{}/.nvm/versions/node" , home) ;
615+
616+ // List node versions and check for claude
617+ let output = Command :: new ( "wsl" )
618+ . args ( [ "-d" , distro, "--" , "ls" , "-1" , & nvm_base] )
619+ . output ( ) ;
620+
621+ if let Ok ( output) = output {
622+ if output. status . success ( ) {
623+ let versions = String :: from_utf8_lossy ( & output. stdout ) ;
624+ for version in versions. lines ( ) {
625+ let version = version. trim ( ) ;
626+ if !version. is_empty ( ) {
627+ let claude_path = format ! ( "{}/{}/bin/claude" , nvm_base, version) ;
628+ // Check if claude exists at this path
629+ let check = Command :: new ( "wsl" )
630+ . args ( [ "-d" , distro, "--" , "test" , "-f" , & claude_path] )
631+ . output ( ) ;
632+
633+ if let Ok ( check) = check {
634+ if check. status . success ( ) {
635+ return Some ( claude_path) ;
636+ }
637+ }
638+ }
639+ }
640+ }
641+ }
642+
643+ // Check ~/.local/bin/claude
644+ let local_claude = format ! ( "{}/.local/bin/claude" , home) ;
645+ let check = Command :: new ( "wsl" )
646+ . args ( [ "-d" , distro, "--" , "test" , "-f" , & local_claude] )
647+ . output ( ) ;
648+
649+ if let Ok ( check) = check {
650+ if check. status . success ( ) {
651+ return Some ( local_claude) ;
652+ }
653+ }
654+ }
655+
656+ // Check common system paths
657+ for path in common_paths {
658+ let check = Command :: new ( "wsl" )
659+ . args ( [ "-d" , distro, "--" , "test" , "-f" , path] )
660+ . output ( ) ;
661+
662+ if let Ok ( check) = check {
663+ if check. status . success ( ) {
664+ return Some ( path. to_string ( ) ) ;
665+ }
666+ }
667+ }
668+
669+ None
670+ }
671+
672+ /// Get home directory in WSL
673+ #[ cfg( windows) ]
674+ fn get_wsl_home_dir ( distro : & str ) -> Option < String > {
675+ let output = Command :: new ( "wsl" )
676+ . args ( [ "-d" , distro, "--" , "echo" , "$HOME" ] )
677+ . output ( )
678+ . ok ( ) ?;
679+
680+ if output. status . success ( ) {
681+ let home = String :: from_utf8_lossy ( & output. stdout ) . trim ( ) . to_string ( ) ;
682+ if !home. is_empty ( ) {
683+ return Some ( home) ;
684+ }
685+ }
686+
687+ None
688+ }
689+
690+ /// Get Claude version in WSL
691+ #[ cfg( windows) ]
692+ fn get_claude_version_in_wsl ( distro : & str , claude_path : & str ) -> Option < String > {
693+ let output = Command :: new ( "wsl" )
694+ . args ( [ "-d" , distro, "--" , claude_path, "--version" ] )
695+ . output ( )
696+ . ok ( ) ?;
697+
698+ if output. status . success ( ) {
699+ extract_version_from_output ( & output. stdout )
700+ } else {
701+ None
702+ }
703+ }
704+
501705/// Get Claude version by running --version command
502706fn get_claude_version ( path : & str ) -> Result < Option < String > , String > {
503707 match Command :: new ( path) . arg ( "--version" ) . output ( ) {
0 commit comments