Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
9.0 kB · 336 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337/** * Package manager detection for Claude CLI */
import { readFile } from 'fs/promises'import memoize from 'lodash-es/memoize.js'import { logForDebugging } from '../debug.js'import { execFileNoThrow } from '../execFileNoThrow.js'import { getPlatform } from '../platform.js'
export type PackageManager = | 'homebrew' | 'winget' | 'pacman' | 'deb' | 'rpm' | 'apk' | 'mise' | 'asdf' | 'unknown'
/** * Parses /etc/os-release to extract the distro ID and ID_LIKE fields. * ID_LIKE identifies the distro family (e.g. Ubuntu has ID_LIKE=debian), * letting us skip package manager execs on distros that can't have them. * Returns null if the file is unreadable (pre-systemd or non-standard systems); * callers fall through to the exec in that case as a conservative fallback. */export const getOsRelease = memoize( async (): Promise<{ id: string; idLike: string[] } | null> => { try { const content = await readFile('/etc/os-release', 'utf8') const idMatch = content.match(/^ID=["']?(\S+?)["']?\s*$/m) const idLikeMatch = content.match(/^ID_LIKE=["']?(.+?)["']?\s*$/m) return { id: idMatch?.[1] ?? '', idLike: idLikeMatch?.[1]?.split(' ') ?? [], } } catch { return null } },)
function isDistroFamily( osRelease: { id: string; idLike: string[] }, families: string[],): boolean { return ( families.includes(osRelease.id) || osRelease.idLike.some(like => families.includes(like)) )}
/** * Detects if the currently running Claude instance was installed via mise * (a polyglot tool version manager) by checking if the executable path * is within a mise installs directory. * * mise installs to: ~/.local/share/mise/installs/<tool>/<version>/ */export function detectMise(): boolean { const execPath = process.execPath || process.argv[0] || ''
// Check if the executable is within a mise installs directory if (/[/\\]mise[/\\]installs[/\\]/i.test(execPath)) { logForDebugging(`Detected mise installation: ${execPath}`) return true }
return false}
/** * Detects if the currently running Claude instance was installed via asdf * (another polyglot tool version manager) by checking if the executable path * is within an asdf installs directory. * * asdf installs to: ~/.asdf/installs/<tool>/<version>/ */export function detectAsdf(): boolean { const execPath = process.execPath || process.argv[0] || ''
// Check if the executable is within an asdf installs directory if (/[/\\]\.?asdf[/\\]installs[/\\]/i.test(execPath)) { logForDebugging(`Detected asdf installation: ${execPath}`) return true }
return false}
/** * Detects if the currently running Claude instance was installed via Homebrew * by checking if the executable path is within a Homebrew Caskroom directory. * * Note: We specifically check for Caskroom because npm can also be installed via * Homebrew, which would place npm global packages under the same Homebrew prefix * (e.g., /opt/homebrew/lib/node_modules). We need to distinguish between: * - Homebrew cask: /opt/homebrew/Caskroom/claude-code/... * - npm-global (via Homebrew's npm): /opt/homebrew/lib/node_modules/@anthropic-ai/... */export function detectHomebrew(): boolean { const platform = getPlatform()
// Homebrew is only for macOS and Linux if (platform !== 'macos' && platform !== 'linux' && platform !== 'wsl') { return false }
// Get the path of the currently running executable const execPath = process.execPath || process.argv[0] || ''
// Check if the executable is within a Homebrew Caskroom directory // This is specific to Homebrew cask installations if (execPath.includes('/Caskroom/')) { logForDebugging(`Detected Homebrew cask installation: ${execPath}`) return true }
return false}
/** * Detects if the currently running Claude instance was installed via winget * by checking if the executable path is within a WinGet directory. * * Winget installs to: * - User: %LOCALAPPDATA%\Microsoft\WinGet\Packages * - System: C:\Program Files\WinGet\Packages * And creates links at: %LOCALAPPDATA%\Microsoft\WinGet\Links\ */export function detectWinget(): boolean { const platform = getPlatform()
// Winget is only for Windows if (platform !== 'windows') { return false }
const execPath = process.execPath || process.argv[0] || ''
// Check for WinGet paths (handles both forward and backslashes) const wingetPatterns = [ /Microsoft[/\\]WinGet[/\\]Packages/i, /Microsoft[/\\]WinGet[/\\]Links/i, ]
for (const pattern of wingetPatterns) { if (pattern.test(execPath)) { logForDebugging(`Detected winget installation: ${execPath}`) return true } }
return false}
/** * Detects if the currently running Claude instance was installed via pacman * by querying pacman's database for file ownership. * * We gate on the Arch distro family before invoking pacman. On other distros * like Ubuntu/Debian, 'pacman' in PATH may resolve to the pacman game * (/usr/games/pacman) rather than the Arch package manager. */export const detectPacman = memoize(async (): Promise<boolean> => { const platform = getPlatform()
if (platform !== 'linux') { return false }
const osRelease = await getOsRelease() if (osRelease && !isDistroFamily(osRelease, ['arch'])) { return false }
const execPath = process.execPath || process.argv[0] || ''
const result = await execFileNoThrow('pacman', ['-Qo', execPath], { timeout: 5000, useCwd: false, })
if (result.code === 0 && result.stdout) { logForDebugging(`Detected pacman installation: ${result.stdout.trim()}`) return true }
return false})
/** * Detects if the currently running Claude instance was installed via a .deb package * by querying dpkg's database for file ownership. * * We use `dpkg -S <execPath>` to check if the executable is owned by a dpkg-managed package. */export const detectDeb = memoize(async (): Promise<boolean> => { const platform = getPlatform()
if (platform !== 'linux') { return false }
const osRelease = await getOsRelease() if (osRelease && !isDistroFamily(osRelease, ['debian'])) { return false }
const execPath = process.execPath || process.argv[0] || ''
const result = await execFileNoThrow('dpkg', ['-S', execPath], { timeout: 5000, useCwd: false, })
if (result.code === 0 && result.stdout) { logForDebugging(`Detected deb installation: ${result.stdout.trim()}`) return true }
return false})
/** * Detects if the currently running Claude instance was installed via an RPM package * by querying the RPM database for file ownership. * * We use `rpm -qf <execPath>` to check if the executable is owned by an RPM package. */export const detectRpm = memoize(async (): Promise<boolean> => { const platform = getPlatform()
if (platform !== 'linux') { return false }
const osRelease = await getOsRelease() if (osRelease && !isDistroFamily(osRelease, ['fedora', 'rhel', 'suse'])) { return false }
const execPath = process.execPath || process.argv[0] || ''
const result = await execFileNoThrow('rpm', ['-qf', execPath], { timeout: 5000, useCwd: false, })
if (result.code === 0 && result.stdout) { logForDebugging(`Detected rpm installation: ${result.stdout.trim()}`) return true }
return false})
/** * Detects if the currently running Claude instance was installed via Alpine APK * by querying apk's database for file ownership. * * We use `apk info --who-owns <execPath>` to check if the executable is owned * by an apk-managed package. */export const detectApk = memoize(async (): Promise<boolean> => { const platform = getPlatform()
if (platform !== 'linux') { return false }
const osRelease = await getOsRelease() if (osRelease && !isDistroFamily(osRelease, ['alpine'])) { return false }
const execPath = process.execPath || process.argv[0] || ''
const result = await execFileNoThrow( 'apk', ['info', '--who-owns', execPath], { timeout: 5000, useCwd: false, }, )
if (result.code === 0 && result.stdout) { logForDebugging(`Detected apk installation: ${result.stdout.trim()}`) return true }
return false})
/** * Memoized function to detect which package manager installed Claude * Returns 'unknown' if no package manager is detected */export const getPackageManager = memoize(async (): Promise<PackageManager> => { if (detectHomebrew()) { return 'homebrew' }
if (detectWinget()) { return 'winget' }
if (detectMise()) { return 'mise' }
if (detectAsdf()) { return 'asdf' }
if (await detectPacman()) { return 'pacman' }
if (await detectApk()) { return 'apk' }
if (await detectDeb()) { return 'deb' }
if (await detectRpm()) { return 'rpm' }
return 'unknown'})