()
| 203 | } |
| 204 | |
| 205 | async function detectMultipleInstallations(): Promise< |
| 206 | Array<{ type: string; path: string }> |
| 207 | > { |
| 208 | const fs = getFsImplementation() |
| 209 | const installations: Array<{ type: string; path: string }> = [] |
| 210 | |
| 211 | // Check for local installation |
| 212 | const localPath = join(homedir(), '.claude', 'local') |
| 213 | if (await localInstallationExists()) { |
| 214 | installations.push({ type: 'npm-local', path: localPath }) |
| 215 | } |
| 216 | |
| 217 | // Check for global npm installation |
| 218 | const packagesToCheck = ['@anthropic-ai/claude-code'] |
| 219 | if (MACRO.PACKAGE_URL && MACRO.PACKAGE_URL !== '@anthropic-ai/claude-code') { |
| 220 | packagesToCheck.push(MACRO.PACKAGE_URL) |
| 221 | } |
| 222 | const npmResult = await execFileNoThrow('npm', [ |
| 223 | '-g', |
| 224 | 'config', |
| 225 | 'get', |
| 226 | 'prefix', |
| 227 | ]) |
| 228 | if (npmResult.code === 0 && npmResult.stdout) { |
| 229 | const npmPrefix = npmResult.stdout.trim() |
| 230 | const isWindows = getPlatform() === 'windows' |
| 231 | |
| 232 | // First check for active installations via bin/claude |
| 233 | // Linux / macOS have prefix/bin/claude and prefix/lib/node_modules |
| 234 | // Windows has prefix/claude and prefix/node_modules |
| 235 | const globalBinPath = isWindows |
| 236 | ? join(npmPrefix, 'claude') |
| 237 | : join(npmPrefix, 'bin', 'claude') |
| 238 | |
| 239 | let globalBinExists = false |
| 240 | try { |
| 241 | await fs.stat(globalBinPath) |
| 242 | globalBinExists = true |
| 243 | } catch { |
| 244 | // Not found |
| 245 | } |
| 246 | |
| 247 | if (globalBinExists) { |
| 248 | // Check if this is actually a Homebrew cask installation, not npm-global |
| 249 | // When npm is installed via Homebrew, both can exist at /opt/homebrew/bin/claude |
| 250 | // We need to resolve the symlink to see where it actually points |
| 251 | let isCurrentHomebrewInstallation = false |
| 252 | |
| 253 | try { |
| 254 | // Resolve the symlink to get the actual target |
| 255 | const realPath = await realpath(globalBinPath) |
| 256 | |
| 257 | // If the symlink points to a Caskroom directory, it's a Homebrew cask |
| 258 | // Only skip it if it's the same Homebrew installation we're currently running from |
| 259 | if (realPath.includes('/Caskroom/')) { |
| 260 | isCurrentHomebrewInstallation = detectHomebrew() |
| 261 | } |
| 262 | } catch { |
no test coverage detected