| import chalk from 'chalk' |
| import { logEvent } from 'src/services/analytics/index.js' |
| import { |
| getLatestVersion, |
| type InstallStatus, |
| installGlobalPackage, |
| } from 'src/utils/autoUpdater.js' |
| import { regenerateCompletionCache } from 'src/utils/completionCache.js' |
| import { |
| getGlobalConfig, |
| type InstallMethod, |
| saveGlobalConfig, |
| } from 'src/utils/config.js' |
| import { logForDebugging } from 'src/utils/debug.js' |
| import { getDoctorDiagnostic } from 'src/utils/doctorDiagnostic.js' |
| import { gracefulShutdown } from 'src/utils/gracefulShutdown.js' |
| import { |
| installOrUpdateClaudePackage, |
| localInstallationExists, |
| } from 'src/utils/localInstaller.js' |
| import { |
| installLatest as installLatestNative, |
| removeInstalledSymlink, |
| } from 'src/utils/nativeInstaller/index.js' |
| import { getPackageManager } from 'src/utils/nativeInstaller/packageManagers.js' |
| import { writeToStdout } from 'src/utils/process.js' |
| import { gte } from 'src/utils/semver.js' |
| import { getInitialSettings } from 'src/utils/settings/settings.js' |
|
|
| export async function update() { |
| logEvent('tengu_update_check', {}) |
| writeToStdout(`Current version: ${MACRO.VERSION}\n`) |
|
|
| const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest' |
| writeToStdout(`Checking for updates to ${channel} version...\n`) |
|
|
| logForDebugging('update: Starting update check') |
|
|
| |
| logForDebugging('update: Running diagnostic') |
| const diagnostic = await getDoctorDiagnostic() |
| logForDebugging(`update: Installation type: ${diagnostic.installationType}`) |
| logForDebugging( |
| `update: Config install method: ${diagnostic.configInstallMethod}`, |
| ) |
|
|
| |
| if (diagnostic.multipleInstallations.length > 1) { |
| writeToStdout('\n') |
| writeToStdout(chalk.yellow('Warning: Multiple installations found') + '\n') |
| for (const install of diagnostic.multipleInstallations) { |
| const current = |
| diagnostic.installationType === install.type |
| ? ' (currently running)' |
| : '' |
| writeToStdout(`- ${install.type} at ${install.path}${current}\n`) |
| } |
| } |
|
|
| |
| if (diagnostic.warnings.length > 0) { |
| writeToStdout('\n') |
| for (const warning of diagnostic.warnings) { |
| logForDebugging(`update: Warning detected: ${warning.issue}`) |
|
|
| |
| |
| logForDebugging(`update: Showing warning: ${warning.issue}`) |
|
|
| writeToStdout(chalk.yellow(`Warning: ${warning.issue}\n`)) |
|
|
| writeToStdout(chalk.bold(`Fix: ${warning.fix}\n`)) |
| } |
| } |
|
|
| |
| const config = getGlobalConfig() |
| if ( |
| !config.installMethod && |
| diagnostic.installationType !== 'package-manager' |
| ) { |
| writeToStdout('\n') |
| writeToStdout('Updating configuration to track installation method...\n') |
| let detectedMethod: 'local' | 'native' | 'global' | 'unknown' = 'unknown' |
|
|
| |
| switch (diagnostic.installationType) { |
| case 'npm-local': |
| detectedMethod = 'local' |
| break |
| case 'native': |
| detectedMethod = 'native' |
| break |
| case 'npm-global': |
| detectedMethod = 'global' |
| break |
| default: |
| detectedMethod = 'unknown' |
| } |
|
|
| saveGlobalConfig(current => ({ |
| ...current, |
| installMethod: detectedMethod, |
| })) |
| writeToStdout(`Installation method set to: ${detectedMethod}\n`) |
| } |
|
|
| |
| if (diagnostic.installationType === 'development') { |
| writeToStdout('\n') |
| writeToStdout( |
| chalk.yellow('Warning: Cannot update development build') + '\n', |
| ) |
| await gracefulShutdown(1) |
| } |
|
|
| |
| if (diagnostic.installationType === 'package-manager') { |
| const packageManager = await getPackageManager() |
| writeToStdout('\n') |
|
|
| if (packageManager === 'homebrew') { |
| writeToStdout('Claude is managed by Homebrew.\n') |
| const latest = await getLatestVersion(channel) |
| if (latest && !gte(MACRO.VERSION, latest)) { |
| writeToStdout(`Update available: ${MACRO.VERSION} → ${latest}\n`) |
| writeToStdout('\n') |
| writeToStdout('To update, run:\n') |
| writeToStdout(chalk.bold(' brew upgrade claude-code') + '\n') |
| } else { |
| writeToStdout('Claude is up to date!\n') |
| } |
| } else if (packageManager === 'winget') { |
| writeToStdout('Claude is managed by winget.\n') |
| const latest = await getLatestVersion(channel) |
| if (latest && !gte(MACRO.VERSION, latest)) { |
| writeToStdout(`Update available: ${MACRO.VERSION} → ${latest}\n`) |
| writeToStdout('\n') |
| writeToStdout('To update, run:\n') |
| writeToStdout( |
| chalk.bold(' winget upgrade Anthropic.ClaudeCode') + '\n', |
| ) |
| } else { |
| writeToStdout('Claude is up to date!\n') |
| } |
| } else if (packageManager === 'apk') { |
| writeToStdout('Claude is managed by apk.\n') |
| const latest = await getLatestVersion(channel) |
| if (latest && !gte(MACRO.VERSION, latest)) { |
| writeToStdout(`Update available: ${MACRO.VERSION} → ${latest}\n`) |
| writeToStdout('\n') |
| writeToStdout('To update, run:\n') |
| writeToStdout(chalk.bold(' apk upgrade claude-code') + '\n') |
| } else { |
| writeToStdout('Claude is up to date!\n') |
| } |
| } else { |
| |
| |
| |
| writeToStdout('Claude is managed by a package manager.\n') |
| writeToStdout('Please use your package manager to update.\n') |
| } |
|
|
| await gracefulShutdown(0) |
| } |
|
|
| |
| if ( |
| config.installMethod && |
| diagnostic.configInstallMethod !== 'not set' && |
| diagnostic.installationType !== 'package-manager' |
| ) { |
| const runningType = diagnostic.installationType |
| const configExpects = diagnostic.configInstallMethod |
|
|
| |
| const typeMapping: Record<string, string> = { |
| 'npm-local': 'local', |
| 'npm-global': 'global', |
| native: 'native', |
| development: 'development', |
| unknown: 'unknown', |
| } |
|
|
| const normalizedRunningType = typeMapping[runningType] || runningType |
|
|
| if ( |
| normalizedRunningType !== configExpects && |
| configExpects !== 'unknown' |
| ) { |
| writeToStdout('\n') |
| writeToStdout(chalk.yellow('Warning: Configuration mismatch') + '\n') |
| writeToStdout(`Config expects: ${configExpects} installation\n`) |
| writeToStdout(`Currently running: ${runningType}\n`) |
| writeToStdout( |
| chalk.yellow( |
| `Updating the ${runningType} installation you are currently using`, |
| ) + '\n', |
| ) |
|
|
| |
| saveGlobalConfig(current => ({ |
| ...current, |
| installMethod: normalizedRunningType as InstallMethod, |
| })) |
| writeToStdout( |
| `Config updated to reflect current installation method: ${normalizedRunningType}\n`, |
| ) |
| } |
| } |
|
|
| |
| if (diagnostic.installationType === 'native') { |
| logForDebugging( |
| 'update: Detected native installation, using native updater', |
| ) |
| try { |
| const result = await installLatestNative(channel, true) |
|
|
| |
| if (result.lockFailed) { |
| const pidInfo = result.lockHolderPid |
| ? ` (PID ${result.lockHolderPid})` |
| : '' |
| writeToStdout( |
| chalk.yellow( |
| `Another Claude process${pidInfo} is currently running. Please try again in a moment.`, |
| ) + '\n', |
| ) |
| await gracefulShutdown(0) |
| } |
|
|
| if (!result.latestVersion) { |
| process.stderr.write('Failed to check for updates\n') |
| await gracefulShutdown(1) |
| } |
|
|
| if (result.latestVersion === MACRO.VERSION) { |
| writeToStdout( |
| chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', |
| ) |
| } else { |
| writeToStdout( |
| chalk.green( |
| `Successfully updated from ${MACRO.VERSION} to version ${result.latestVersion}`, |
| ) + '\n', |
| ) |
| await regenerateCompletionCache() |
| } |
| await gracefulShutdown(0) |
| } catch (error) { |
| process.stderr.write('Error: Failed to install native update\n') |
| process.stderr.write(String(error) + '\n') |
| process.stderr.write('Try running "claude doctor" for diagnostics\n') |
| await gracefulShutdown(1) |
| } |
| } |
|
|
| |
| |
| |
| if (config.installMethod !== 'native') { |
| await removeInstalledSymlink() |
| } |
|
|
| logForDebugging('update: Checking npm registry for latest version') |
| logForDebugging(`update: Package URL: ${MACRO.PACKAGE_URL}`) |
| const npmTag = channel === 'stable' ? 'stable' : 'latest' |
| const npmCommand = `npm view ${MACRO.PACKAGE_URL}@${npmTag} version` |
| logForDebugging(`update: Running: ${npmCommand}`) |
| const latestVersion = await getLatestVersion(channel) |
| logForDebugging( |
| `update: Latest version from npm: ${latestVersion || 'FAILED'}`, |
| ) |
|
|
| if (!latestVersion) { |
| logForDebugging('update: Failed to get latest version from npm registry') |
| process.stderr.write(chalk.red('Failed to check for updates') + '\n') |
| process.stderr.write('Unable to fetch latest version from npm registry\n') |
| process.stderr.write('\n') |
| process.stderr.write('Possible causes:\n') |
| process.stderr.write(' • Network connectivity issues\n') |
| process.stderr.write(' • npm registry is unreachable\n') |
| process.stderr.write(' • Corporate proxy/firewall blocking npm\n') |
| if (MACRO.PACKAGE_URL && !MACRO.PACKAGE_URL.startsWith('@anthropic')) { |
| process.stderr.write( |
| ' • Internal/development build not published to npm\n', |
| ) |
| } |
| process.stderr.write('\n') |
| process.stderr.write('Try:\n') |
| process.stderr.write(' • Check your internet connection\n') |
| process.stderr.write(' • Run with --debug flag for more details\n') |
| const packageName = |
| MACRO.PACKAGE_URL || |
| (process.env.USER_TYPE === 'ant' |
| ? '@anthropic-ai/claude-cli' |
| : '@anthropic-ai/claude-code') |
| process.stderr.write( |
| ` • Manually check: npm view ${packageName} version\n`, |
| ) |
|
|
| process.stderr.write(' • Check if you need to login: npm whoami\n') |
| await gracefulShutdown(1) |
| } |
|
|
| |
| if (latestVersion === MACRO.VERSION) { |
| writeToStdout( |
| chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', |
| ) |
| await gracefulShutdown(0) |
| } |
|
|
| writeToStdout( |
| `New version available: ${latestVersion} (current: ${MACRO.VERSION})\n`, |
| ) |
| writeToStdout('Installing update...\n') |
|
|
| |
| let useLocalUpdate = false |
| let updateMethodName = '' |
|
|
| switch (diagnostic.installationType) { |
| case 'npm-local': |
| useLocalUpdate = true |
| updateMethodName = 'local' |
| break |
| case 'npm-global': |
| useLocalUpdate = false |
| updateMethodName = 'global' |
| break |
| case 'unknown': { |
| |
| const isLocal = await localInstallationExists() |
| useLocalUpdate = isLocal |
| updateMethodName = isLocal ? 'local' : 'global' |
| writeToStdout( |
| chalk.yellow('Warning: Could not determine installation type') + '\n', |
| ) |
| writeToStdout( |
| `Attempting ${updateMethodName} update based on file detection...\n`, |
| ) |
| break |
| } |
| default: |
| process.stderr.write( |
| `Error: Cannot update ${diagnostic.installationType} installation\n`, |
| ) |
| await gracefulShutdown(1) |
| } |
|
|
| writeToStdout(`Using ${updateMethodName} installation update method...\n`) |
|
|
| logForDebugging(`update: Update method determined: ${updateMethodName}`) |
| logForDebugging(`update: useLocalUpdate: ${useLocalUpdate}`) |
|
|
| let status: InstallStatus |
|
|
| if (useLocalUpdate) { |
| logForDebugging( |
| 'update: Calling installOrUpdateClaudePackage() for local update', |
| ) |
| status = await installOrUpdateClaudePackage(channel) |
| } else { |
| logForDebugging('update: Calling installGlobalPackage() for global update') |
| status = await installGlobalPackage() |
| } |
|
|
| logForDebugging(`update: Installation status: ${status}`) |
|
|
| switch (status) { |
| case 'success': |
| writeToStdout( |
| chalk.green( |
| `Successfully updated from ${MACRO.VERSION} to version ${latestVersion}`, |
| ) + '\n', |
| ) |
| await regenerateCompletionCache() |
| break |
| case 'no_permissions': |
| process.stderr.write( |
| 'Error: Insufficient permissions to install update\n', |
| ) |
| if (useLocalUpdate) { |
| process.stderr.write('Try manually updating with:\n') |
| process.stderr.write( |
| ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, |
| ) |
| } else { |
| process.stderr.write('Try running with sudo or fix npm permissions\n') |
| process.stderr.write( |
| 'Or consider using native installation with: claude install\n', |
| ) |
| } |
| await gracefulShutdown(1) |
| break |
| case 'install_failed': |
| process.stderr.write('Error: Failed to install update\n') |
| if (useLocalUpdate) { |
| process.stderr.write('Try manually updating with:\n') |
| process.stderr.write( |
| ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, |
| ) |
| } else { |
| process.stderr.write( |
| 'Or consider using native installation with: claude install\n', |
| ) |
| } |
| await gracefulShutdown(1) |
| break |
| case 'in_progress': |
| process.stderr.write( |
| 'Error: Another instance is currently performing an update\n', |
| ) |
| process.stderr.write('Please wait and try again later\n') |
| await gracefulShutdown(1) |
| break |
| } |
| await gracefulShutdown(0) |
| } |
|
|