@@ -131,15 +131,16 @@ fn source_preference(installation: &ClaudeInstallation) -> u8 {
131131 "system" => 3 ,
132132 "nvm-active" => 4 ,
133133 source if source. starts_with ( "nvm" ) => 5 ,
134- "local-bin" => 6 ,
135- "claude-local" => 7 ,
136- "npm-global" => 8 ,
137- "yarn" | "yarn-global" => 9 ,
138- "bun" => 10 ,
139- "node-modules" => 11 ,
140- "home-bin" => 12 ,
141- "PATH" => 13 ,
142- _ => 14 ,
134+ "asdf" => 6 ,
135+ "local-bin" => 7 ,
136+ "claude-local" => 8 ,
137+ "npm-global" => 9 ,
138+ "yarn" | "yarn-global" => 10 ,
139+ "bun" => 11 ,
140+ "node-modules" => 12 ,
141+ "home-bin" => 13 ,
142+ "PATH" => 14 ,
143+ _ => 15 ,
143144 }
144145}
145146
@@ -152,10 +153,13 @@ fn discover_system_installations() -> Vec<ClaudeInstallation> {
152153 installations. push ( installation) ;
153154 }
154155
155- // 2. Check NVM paths (includes current active NVM)
156+ // 2. Check asdf shims first (before NVM)
157+ installations. extend ( find_asdf_installations ( ) ) ;
158+
159+ // 3. Check NVM paths (includes current active NVM)
156160 installations. extend ( find_nvm_installations ( ) ) ;
157161
158- // 3 . Check standard paths
162+ // 4 . Check standard paths
159163 installations. extend ( find_standard_installations ( ) ) ;
160164
161165 // Remove duplicates by path
@@ -251,6 +255,120 @@ fn try_which_command() -> Option<ClaudeInstallation> {
251255 }
252256}
253257
258+ /// Find Claude installations in asdf shims directories
259+ #[ cfg( unix) ]
260+ fn find_asdf_installations ( ) -> Vec < ClaudeInstallation > {
261+ let mut installations = Vec :: new ( ) ;
262+ let mut checked_paths = std:: collections:: HashSet :: new ( ) ;
263+
264+ // Check ASDF_DIR environment variable first
265+ if let Ok ( asdf_dir) = std:: env:: var ( "ASDF_DIR" ) {
266+ let claude_path = PathBuf :: from ( & asdf_dir) . join ( "shims" ) . join ( "claude" ) ;
267+ if claude_path. exists ( ) && claude_path. is_file ( ) {
268+ debug ! ( "Found Claude via ASDF_DIR: {:?}" , claude_path) ;
269+ let path_str = claude_path. to_string_lossy ( ) . to_string ( ) ;
270+ checked_paths. insert ( path_str. clone ( ) ) ;
271+
272+ let version = get_claude_version ( & path_str)
273+ . ok ( )
274+ . flatten ( ) ;
275+ installations. push ( ClaudeInstallation {
276+ path : path_str,
277+ version,
278+ source : "asdf" . to_string ( ) ,
279+ installation_type : InstallationType :: System ,
280+ } ) ;
281+ }
282+ }
283+
284+ // Then check default ~/.asdf location (skip if already found via ASDF_DIR)
285+ if let Ok ( home) = std:: env:: var ( "HOME" ) {
286+ let asdf_shims_path = PathBuf :: from ( & home)
287+ . join ( ".asdf" )
288+ . join ( "shims" )
289+ . join ( "claude" ) ;
290+
291+ let path_str = asdf_shims_path. to_string_lossy ( ) . to_string ( ) ;
292+
293+ // Skip if we already found this path via ASDF_DIR
294+ if !checked_paths. contains ( & path_str) {
295+ debug ! ( "Checking asdf shims directory: {:?}" , asdf_shims_path) ;
296+
297+ if asdf_shims_path. exists ( ) && asdf_shims_path. is_file ( ) {
298+ debug ! ( "Found Claude in asdf shims: {}" , path_str) ;
299+
300+ // Get Claude version
301+ let version = get_claude_version ( & path_str) . ok ( ) . flatten ( ) ;
302+
303+ installations. push ( ClaudeInstallation {
304+ path : path_str,
305+ version,
306+ source : "asdf" . to_string ( ) ,
307+ installation_type : InstallationType :: System ,
308+ } ) ;
309+ }
310+ }
311+ }
312+
313+ installations
314+ }
315+
316+ #[ cfg( windows) ]
317+ fn find_asdf_installations ( ) -> Vec < ClaudeInstallation > {
318+ let mut installations = Vec :: new ( ) ;
319+ let mut checked_paths = std:: collections:: HashSet :: new ( ) ;
320+
321+ // Check ASDF_DIR environment variable first
322+ if let Ok ( asdf_dir) = std:: env:: var ( "ASDF_DIR" ) {
323+ let claude_path = PathBuf :: from ( & asdf_dir) . join ( "shims" ) . join ( "claude.exe" ) ;
324+ if claude_path. exists ( ) && claude_path. is_file ( ) {
325+ debug ! ( "Found Claude via ASDF_DIR: {:?}" , claude_path) ;
326+ let path_str = claude_path. to_string_lossy ( ) . to_string ( ) ;
327+ checked_paths. insert ( path_str. clone ( ) ) ;
328+
329+ let version = get_claude_version ( & path_str)
330+ . ok ( )
331+ . flatten ( ) ;
332+ installations. push ( ClaudeInstallation {
333+ path : path_str,
334+ version,
335+ source : "asdf" . to_string ( ) ,
336+ installation_type : InstallationType :: System ,
337+ } ) ;
338+ }
339+ }
340+
341+ // Then check default location (skip if already found via ASDF_DIR)
342+ if let Ok ( user_profile) = std:: env:: var ( "USERPROFILE" ) {
343+ let asdf_shims_path = PathBuf :: from ( & user_profile)
344+ . join ( ".asdf" )
345+ . join ( "shims" )
346+ . join ( "claude.exe" ) ;
347+
348+ let path_str = asdf_shims_path. to_string_lossy ( ) . to_string ( ) ;
349+
350+ // Skip if we already found this path via ASDF_DIR
351+ if !checked_paths. contains ( & path_str) {
352+ debug ! ( "Checking asdf shims directory: {:?}" , asdf_shims_path) ;
353+
354+ if asdf_shims_path. exists ( ) && asdf_shims_path. is_file ( ) {
355+ debug ! ( "Found Claude in asdf shims: {}" , path_str) ;
356+
357+ let version = get_claude_version ( & path_str) . ok ( ) . flatten ( ) ;
358+
359+ installations. push ( ClaudeInstallation {
360+ path : path_str,
361+ version,
362+ source : "asdf" . to_string ( ) ,
363+ installation_type : InstallationType :: System ,
364+ } ) ;
365+ }
366+ }
367+ }
368+
369+ installations
370+ }
371+
254372/// Find Claude installations in NVM directories
255373#[ cfg( unix) ]
256374fn find_nvm_installations ( ) -> Vec < ClaudeInstallation > {
@@ -638,6 +756,10 @@ pub fn create_command_with_env(program: &str) -> Command {
638756 || key == "NVM_BIN"
639757 || key == "HOMEBREW_PREFIX"
640758 || key == "HOMEBREW_CELLAR"
759+ // Add asdf environment variables
760+ || key == "ASDF_DIR"
761+ || key == "ASDF_DATA_DIR"
762+ || key == "ASDF_CONFIG_FILE"
641763 // Add proxy environment variables (only uppercase)
642764 || key == "HTTP_PROXY"
643765 || key == "HTTPS_PROXY"
@@ -689,5 +811,47 @@ pub fn create_command_with_env(program: &str) -> Command {
689811 }
690812 }
691813
814+ // Add asdf support if the program is in an asdf shims directory
815+ // Also check if the program is a symlink pointing to asdf shims
816+ let is_asdf_program = program. contains ( "/.asdf/shims/" )
817+ || std:: fs:: read_link ( program)
818+ . map ( |target| target. to_string_lossy ( ) . contains ( "/.asdf/shims/" ) )
819+ . unwrap_or ( false ) ;
820+
821+ if is_asdf_program {
822+ if let Ok ( home) = std:: env:: var ( "HOME" ) {
823+ let asdf_bin_dir = format ! ( "{}/.asdf/bin" , home) ;
824+ let asdf_shims_dir = format ! ( "{}/.asdf/shims" , home) ;
825+ let current_path = std:: env:: var ( "PATH" ) . unwrap_or_default ( ) ;
826+
827+ let mut new_path = current_path. clone ( ) ;
828+
829+ // Add asdf bin directory if not already in PATH
830+ if !current_path. contains ( & asdf_bin_dir) {
831+ new_path = format ! ( "{}:{}" , asdf_bin_dir, new_path) ;
832+ debug ! ( "Adding asdf bin directory to PATH: {}" , asdf_bin_dir) ;
833+ }
834+
835+ // Add asdf shims directory if not already in PATH
836+ if !current_path. contains ( & asdf_shims_dir) {
837+ new_path = format ! ( "{}:{}" , asdf_shims_dir, new_path) ;
838+ debug ! ( "Adding asdf shims directory to PATH: {}" , asdf_shims_dir) ;
839+ }
840+
841+ if new_path != current_path {
842+ cmd. env ( "PATH" , new_path) ;
843+ }
844+
845+ // Set ASDF_DIR if not already set
846+ if std:: env:: var ( "ASDF_DIR" ) . is_err ( ) {
847+ let asdf_dir = format ! ( "{}/.asdf" , home) ;
848+ if std:: path:: Path :: new ( & asdf_dir) . exists ( ) {
849+ debug ! ( "Setting ASDF_DIR to: {}" , asdf_dir) ;
850+ cmd. env ( "ASDF_DIR" , asdf_dir) ;
851+ }
852+ }
853+ }
854+ }
855+
692856 cmd
693857}
0 commit comments