Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
4.8 kB · 162 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163/** * Utilities for handling local installation */
import { access, chmod, writeFile } from 'fs/promises'import { join } from 'path'import { type ReleaseChannel, saveGlobalConfig } from './config.js'import { getClaudeConfigHomeDir } from './envUtils.js'import { getErrnoCode } from './errors.js'import { execFileNoThrowWithCwd } from './execFileNoThrow.js'import { getFsImplementation } from './fsOperations.js'import { logError } from './log.js'import { jsonStringify } from './slowOperations.js'
// Lazy getters: getClaudeConfigHomeDir() is memoized and reads process.env.// Evaluating at module scope would capture the value before entrypoints like// hfi.tsx get a chance to set CLAUDE_CONFIG_DIR in main(), and would also// populate the memoize cache with that stale value for all 150+ other callers.function getLocalInstallDir(): string { return join(getClaudeConfigHomeDir(), 'local')}export function getLocalClaudePath(): string { return join(getLocalInstallDir(), 'claude')}
/** * Check if we're running from our managed local installation */export function isRunningFromLocalInstallation(): boolean { const execPath = process.argv[1] || '' return execPath.includes('/.claude/local/node_modules/')}
/** * Write `content` to `path` only if the file does not already exist. * Uses O_EXCL ('wx') for atomic create-if-missing. */async function writeIfMissing( path: string, content: string, mode?: number,): Promise<boolean> { try { await writeFile(path, content, { encoding: 'utf8', flag: 'wx', mode }) return true } catch (e) { if (getErrnoCode(e) === 'EEXIST') return false throw e }}
/** * Ensure the local package environment is set up * Creates the directory, package.json, and wrapper script */export async function ensureLocalPackageEnvironment(): Promise<boolean> { try { const localInstallDir = getLocalInstallDir()
// Create installation directory (recursive, idempotent) await getFsImplementation().mkdir(localInstallDir)
// Create package.json if it doesn't exist await writeIfMissing( join(localInstallDir, 'package.json'), jsonStringify( { name: 'claude-local', version: '0.0.1', private: true }, null, 2, ), )
// Create the wrapper script if it doesn't exist const wrapperPath = join(localInstallDir, 'claude') const created = await writeIfMissing( wrapperPath, `#!/bin/sh\nexec "${localInstallDir}/node_modules/.bin/claude" "$@"`, 0o755, ) if (created) { // Mode in writeFile is masked by umask; chmod to ensure executable bit. await chmod(wrapperPath, 0o755) }
return true } catch (error) { logError(error) return false }}
/** * Install or update Claude CLI package in the local directory * @param channel - Release channel to use (latest or stable) * @param specificVersion - Optional specific version to install (overrides channel) */export async function installOrUpdateClaudePackage( channel: ReleaseChannel, specificVersion?: string | null,): Promise<'in_progress' | 'success' | 'install_failed'> { try { // First ensure the environment is set up if (!(await ensureLocalPackageEnvironment())) { return 'install_failed' }
// Use specific version if provided, otherwise use channel tag const versionSpec = specificVersion ? specificVersion : channel === 'stable' ? 'stable' : 'latest' const result = await execFileNoThrowWithCwd( 'npm', ['install', `${MACRO.PACKAGE_URL}@${versionSpec}`], { cwd: getLocalInstallDir(), maxBuffer: 1000000 }, )
if (result.code !== 0) { const error = new Error( `Failed to install Claude CLI package: ${result.stderr}`, ) logError(error) return result.code === 190 ? 'in_progress' : 'install_failed' }
// Set installMethod to 'local' to prevent npm permission warnings saveGlobalConfig(current => ({ ...current, installMethod: 'local', }))
return 'success' } catch (error) { logError(error) return 'install_failed' }}
/** * Check if local installation exists. * Pure existence probe — callers use this to choose update path / UI hints. */export async function localInstallationExists(): Promise<boolean> { try { await access(join(getLocalInstallDir(), 'node_modules', '.bin', 'claude')) return true } catch { return false }}
/** * Get shell type to determine appropriate path setup */export function getShellType(): string { const shellPath = process.env.SHELL || '' if (shellPath.includes('zsh')) return 'zsh' if (shellPath.includes('bash')) return 'bash' if (shellPath.includes('fish')) return 'fish' return 'unknown'}