Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 175 additions & 11 deletions src-tauri/src/claude_binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,16 @@ fn source_preference(installation: &ClaudeInstallation) -> u8 {
"system" => 3,
"nvm-active" => 4,
source if source.starts_with("nvm") => 5,
"local-bin" => 6,
"claude-local" => 7,
"npm-global" => 8,
"yarn" | "yarn-global" => 9,
"bun" => 10,
"node-modules" => 11,
"home-bin" => 12,
"PATH" => 13,
_ => 14,
"asdf" => 6,
"local-bin" => 7,
"claude-local" => 8,
"npm-global" => 9,
"yarn" | "yarn-global" => 10,
"bun" => 11,
"node-modules" => 12,
"home-bin" => 13,
"PATH" => 14,
_ => 15,
}
}

Expand All @@ -152,10 +153,13 @@ fn discover_system_installations() -> Vec<ClaudeInstallation> {
installations.push(installation);
}

// 2. Check NVM paths (includes current active NVM)
// 2. Check asdf shims first (before NVM)
installations.extend(find_asdf_installations());

// 3. Check NVM paths (includes current active NVM)
installations.extend(find_nvm_installations());

// 3. Check standard paths
// 4. Check standard paths
installations.extend(find_standard_installations());

// Remove duplicates by path
Expand Down Expand Up @@ -251,6 +255,120 @@ fn try_which_command() -> Option<ClaudeInstallation> {
}
}

/// Find Claude installations in asdf shims directories
#[cfg(unix)]
fn find_asdf_installations() -> Vec<ClaudeInstallation> {
let mut installations = Vec::new();
let mut checked_paths = std::collections::HashSet::new();

// Check ASDF_DIR environment variable first
if let Ok(asdf_dir) = std::env::var("ASDF_DIR") {
let claude_path = PathBuf::from(&asdf_dir).join("shims").join("claude");
if claude_path.exists() && claude_path.is_file() {
debug!("Found Claude via ASDF_DIR: {:?}", claude_path);
let path_str = claude_path.to_string_lossy().to_string();
checked_paths.insert(path_str.clone());

let version = get_claude_version(&path_str)
.ok()
.flatten();
installations.push(ClaudeInstallation {
path: path_str,
version,
source: "asdf".to_string(),
installation_type: InstallationType::System,
});
}
}

// Then check default ~/.asdf location (skip if already found via ASDF_DIR)
if let Ok(home) = std::env::var("HOME") {
let asdf_shims_path = PathBuf::from(&home)
.join(".asdf")
.join("shims")
.join("claude");

let path_str = asdf_shims_path.to_string_lossy().to_string();

// Skip if we already found this path via ASDF_DIR
if !checked_paths.contains(&path_str) {
debug!("Checking asdf shims directory: {:?}", asdf_shims_path);

if asdf_shims_path.exists() && asdf_shims_path.is_file() {
debug!("Found Claude in asdf shims: {}", path_str);

// Get Claude version
let version = get_claude_version(&path_str).ok().flatten();

installations.push(ClaudeInstallation {
path: path_str,
version,
source: "asdf".to_string(),
installation_type: InstallationType::System,
});
}
}
}

installations
}

#[cfg(windows)]
fn find_asdf_installations() -> Vec<ClaudeInstallation> {
let mut installations = Vec::new();
let mut checked_paths = std::collections::HashSet::new();

// Check ASDF_DIR environment variable first
if let Ok(asdf_dir) = std::env::var("ASDF_DIR") {
let claude_path = PathBuf::from(&asdf_dir).join("shims").join("claude.exe");
if claude_path.exists() && claude_path.is_file() {
debug!("Found Claude via ASDF_DIR: {:?}", claude_path);
let path_str = claude_path.to_string_lossy().to_string();
checked_paths.insert(path_str.clone());

let version = get_claude_version(&path_str)
.ok()
.flatten();
installations.push(ClaudeInstallation {
path: path_str,
version,
source: "asdf".to_string(),
installation_type: InstallationType::System,
});
}
}

// Then check default location (skip if already found via ASDF_DIR)
if let Ok(user_profile) = std::env::var("USERPROFILE") {
let asdf_shims_path = PathBuf::from(&user_profile)
.join(".asdf")
.join("shims")
.join("claude.exe");

let path_str = asdf_shims_path.to_string_lossy().to_string();

// Skip if we already found this path via ASDF_DIR
if !checked_paths.contains(&path_str) {
debug!("Checking asdf shims directory: {:?}", asdf_shims_path);

if asdf_shims_path.exists() && asdf_shims_path.is_file() {
debug!("Found Claude in asdf shims: {}", path_str);

let version = get_claude_version(&path_str).ok().flatten();

installations.push(ClaudeInstallation {
path: path_str,
version,
source: "asdf".to_string(),
installation_type: InstallationType::System,
});
}
}
}

installations
}

/// Find Claude installations in NVM directories
#[cfg(unix)]
fn find_nvm_installations() -> Vec<ClaudeInstallation> {
Expand Down Expand Up @@ -638,6 +756,10 @@ pub fn create_command_with_env(program: &str) -> Command {
|| key == "NVM_BIN"
|| key == "HOMEBREW_PREFIX"
|| key == "HOMEBREW_CELLAR"
// Add asdf environment variables
|| key == "ASDF_DIR"
|| key == "ASDF_DATA_DIR"
|| key == "ASDF_CONFIG_FILE"
// Add proxy environment variables (only uppercase)
|| key == "HTTP_PROXY"
|| key == "HTTPS_PROXY"
Expand Down Expand Up @@ -689,5 +811,47 @@ pub fn create_command_with_env(program: &str) -> Command {
}
}

// Add asdf support if the program is in an asdf shims directory
// Also check if the program is a symlink pointing to asdf shims
let is_asdf_program = program.contains("/.asdf/shims/")
|| std::fs::read_link(program)
.map(|target| target.to_string_lossy().contains("/.asdf/shims/"))
.unwrap_or(false);

if is_asdf_program {
if let Ok(home) = std::env::var("HOME") {
let asdf_bin_dir = format!("{}/.asdf/bin", home);
let asdf_shims_dir = format!("{}/.asdf/shims", home);
let current_path = std::env::var("PATH").unwrap_or_default();

let mut new_path = current_path.clone();

// Add asdf bin directory if not already in PATH
if !current_path.contains(&asdf_bin_dir) {
new_path = format!("{}:{}", asdf_bin_dir, new_path);
debug!("Adding asdf bin directory to PATH: {}", asdf_bin_dir);
}

// Add asdf shims directory if not already in PATH
if !current_path.contains(&asdf_shims_dir) {
new_path = format!("{}:{}", asdf_shims_dir, new_path);
debug!("Adding asdf shims directory to PATH: {}", asdf_shims_dir);
}

if new_path != current_path {
cmd.env("PATH", new_path);
}

// Set ASDF_DIR if not already set
if std::env::var("ASDF_DIR").is_err() {
let asdf_dir = format!("{}/.asdf", home);
if std::path::Path::new(&asdf_dir).exists() {
debug!("Setting ASDF_DIR to: {}", asdf_dir);
cmd.env("ASDF_DIR", asdf_dir);
}
}
}
}

cmd
}