source dump of claude code
23
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 422 lines 14 kB view raw
1import chalk from 'chalk' 2import { logEvent } from 'src/services/analytics/index.js' 3import { 4 getLatestVersion, 5 type InstallStatus, 6 installGlobalPackage, 7} from 'src/utils/autoUpdater.js' 8import { regenerateCompletionCache } from 'src/utils/completionCache.js' 9import { 10 getGlobalConfig, 11 type InstallMethod, 12 saveGlobalConfig, 13} from 'src/utils/config.js' 14import { logForDebugging } from 'src/utils/debug.js' 15import { getDoctorDiagnostic } from 'src/utils/doctorDiagnostic.js' 16import { gracefulShutdown } from 'src/utils/gracefulShutdown.js' 17import { 18 installOrUpdateClaudePackage, 19 localInstallationExists, 20} from 'src/utils/localInstaller.js' 21import { 22 installLatest as installLatestNative, 23 removeInstalledSymlink, 24} from 'src/utils/nativeInstaller/index.js' 25import { getPackageManager } from 'src/utils/nativeInstaller/packageManagers.js' 26import { writeToStdout } from 'src/utils/process.js' 27import { gte } from 'src/utils/semver.js' 28import { getInitialSettings } from 'src/utils/settings/settings.js' 29 30export async function update() { 31 logEvent('tengu_update_check', {}) 32 writeToStdout(`Current version: ${MACRO.VERSION}\n`) 33 34 const channel = getInitialSettings()?.autoUpdatesChannel ?? 'latest' 35 writeToStdout(`Checking for updates to ${channel} version...\n`) 36 37 logForDebugging('update: Starting update check') 38 39 // Run diagnostic to detect potential issues 40 logForDebugging('update: Running diagnostic') 41 const diagnostic = await getDoctorDiagnostic() 42 logForDebugging(`update: Installation type: ${diagnostic.installationType}`) 43 logForDebugging( 44 `update: Config install method: ${diagnostic.configInstallMethod}`, 45 ) 46 47 // Check for multiple installations 48 if (diagnostic.multipleInstallations.length > 1) { 49 writeToStdout('\n') 50 writeToStdout(chalk.yellow('Warning: Multiple installations found') + '\n') 51 for (const install of diagnostic.multipleInstallations) { 52 const current = 53 diagnostic.installationType === install.type 54 ? ' (currently running)' 55 : '' 56 writeToStdout(`- ${install.type} at ${install.path}${current}\n`) 57 } 58 } 59 60 // Display warnings if any exist 61 if (diagnostic.warnings.length > 0) { 62 writeToStdout('\n') 63 for (const warning of diagnostic.warnings) { 64 logForDebugging(`update: Warning detected: ${warning.issue}`) 65 66 // Don't skip PATH warnings - they're always relevant 67 // The user needs to know that 'which claude' points elsewhere 68 logForDebugging(`update: Showing warning: ${warning.issue}`) 69 70 writeToStdout(chalk.yellow(`Warning: ${warning.issue}\n`)) 71 72 writeToStdout(chalk.bold(`Fix: ${warning.fix}\n`)) 73 } 74 } 75 76 // Update config if installMethod is not set (but skip for package managers) 77 const config = getGlobalConfig() 78 if ( 79 !config.installMethod && 80 diagnostic.installationType !== 'package-manager' 81 ) { 82 writeToStdout('\n') 83 writeToStdout('Updating configuration to track installation method...\n') 84 let detectedMethod: 'local' | 'native' | 'global' | 'unknown' = 'unknown' 85 86 // Map diagnostic installation type to config install method 87 switch (diagnostic.installationType) { 88 case 'npm-local': 89 detectedMethod = 'local' 90 break 91 case 'native': 92 detectedMethod = 'native' 93 break 94 case 'npm-global': 95 detectedMethod = 'global' 96 break 97 default: 98 detectedMethod = 'unknown' 99 } 100 101 saveGlobalConfig(current => ({ 102 ...current, 103 installMethod: detectedMethod, 104 })) 105 writeToStdout(`Installation method set to: ${detectedMethod}\n`) 106 } 107 108 // Check if running from development build 109 if (diagnostic.installationType === 'development') { 110 writeToStdout('\n') 111 writeToStdout( 112 chalk.yellow('Warning: Cannot update development build') + '\n', 113 ) 114 await gracefulShutdown(1) 115 } 116 117 // Check if running from a package manager 118 if (diagnostic.installationType === 'package-manager') { 119 const packageManager = await getPackageManager() 120 writeToStdout('\n') 121 122 if (packageManager === 'homebrew') { 123 writeToStdout('Claude is managed by Homebrew.\n') 124 const latest = await getLatestVersion(channel) 125 if (latest && !gte(MACRO.VERSION, latest)) { 126 writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`) 127 writeToStdout('\n') 128 writeToStdout('To update, run:\n') 129 writeToStdout(chalk.bold(' brew upgrade claude-code') + '\n') 130 } else { 131 writeToStdout('Claude is up to date!\n') 132 } 133 } else if (packageManager === 'winget') { 134 writeToStdout('Claude is managed by winget.\n') 135 const latest = await getLatestVersion(channel) 136 if (latest && !gte(MACRO.VERSION, latest)) { 137 writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`) 138 writeToStdout('\n') 139 writeToStdout('To update, run:\n') 140 writeToStdout( 141 chalk.bold(' winget upgrade Anthropic.ClaudeCode') + '\n', 142 ) 143 } else { 144 writeToStdout('Claude is up to date!\n') 145 } 146 } else if (packageManager === 'apk') { 147 writeToStdout('Claude is managed by apk.\n') 148 const latest = await getLatestVersion(channel) 149 if (latest && !gte(MACRO.VERSION, latest)) { 150 writeToStdout(`Update available: ${MACRO.VERSION}${latest}\n`) 151 writeToStdout('\n') 152 writeToStdout('To update, run:\n') 153 writeToStdout(chalk.bold(' apk upgrade claude-code') + '\n') 154 } else { 155 writeToStdout('Claude is up to date!\n') 156 } 157 } else { 158 // pacman, deb, and rpm don't get specific commands because they each have 159 // multiple frontends (pacman: yay/paru/makepkg, deb: apt/apt-get/aptitude/nala, 160 // rpm: dnf/yum/zypper) 161 writeToStdout('Claude is managed by a package manager.\n') 162 writeToStdout('Please use your package manager to update.\n') 163 } 164 165 await gracefulShutdown(0) 166 } 167 168 // Check for config/reality mismatch (skip for package-manager installs) 169 if ( 170 config.installMethod && 171 diagnostic.configInstallMethod !== 'not set' && 172 diagnostic.installationType !== 'package-manager' 173 ) { 174 const runningType = diagnostic.installationType 175 const configExpects = diagnostic.configInstallMethod 176 177 // Map installation types for comparison 178 const typeMapping: Record<string, string> = { 179 'npm-local': 'local', 180 'npm-global': 'global', 181 native: 'native', 182 development: 'development', 183 unknown: 'unknown', 184 } 185 186 const normalizedRunningType = typeMapping[runningType] || runningType 187 188 if ( 189 normalizedRunningType !== configExpects && 190 configExpects !== 'unknown' 191 ) { 192 writeToStdout('\n') 193 writeToStdout(chalk.yellow('Warning: Configuration mismatch') + '\n') 194 writeToStdout(`Config expects: ${configExpects} installation\n`) 195 writeToStdout(`Currently running: ${runningType}\n`) 196 writeToStdout( 197 chalk.yellow( 198 `Updating the ${runningType} installation you are currently using`, 199 ) + '\n', 200 ) 201 202 // Update config to match reality 203 saveGlobalConfig(current => ({ 204 ...current, 205 installMethod: normalizedRunningType as InstallMethod, 206 })) 207 writeToStdout( 208 `Config updated to reflect current installation method: ${normalizedRunningType}\n`, 209 ) 210 } 211 } 212 213 // Handle native installation updates first 214 if (diagnostic.installationType === 'native') { 215 logForDebugging( 216 'update: Detected native installation, using native updater', 217 ) 218 try { 219 const result = await installLatestNative(channel, true) 220 221 // Handle lock contention gracefully 222 if (result.lockFailed) { 223 const pidInfo = result.lockHolderPid 224 ? ` (PID ${result.lockHolderPid})` 225 : '' 226 writeToStdout( 227 chalk.yellow( 228 `Another Claude process${pidInfo} is currently running. Please try again in a moment.`, 229 ) + '\n', 230 ) 231 await gracefulShutdown(0) 232 } 233 234 if (!result.latestVersion) { 235 process.stderr.write('Failed to check for updates\n') 236 await gracefulShutdown(1) 237 } 238 239 if (result.latestVersion === MACRO.VERSION) { 240 writeToStdout( 241 chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', 242 ) 243 } else { 244 writeToStdout( 245 chalk.green( 246 `Successfully updated from ${MACRO.VERSION} to version ${result.latestVersion}`, 247 ) + '\n', 248 ) 249 await regenerateCompletionCache() 250 } 251 await gracefulShutdown(0) 252 } catch (error) { 253 process.stderr.write('Error: Failed to install native update\n') 254 process.stderr.write(String(error) + '\n') 255 process.stderr.write('Try running "claude doctor" for diagnostics\n') 256 await gracefulShutdown(1) 257 } 258 } 259 260 // Fallback to existing JS/npm-based update logic 261 // Remove native installer symlink since we're not using native installation 262 // But only if user hasn't migrated to native installation 263 if (config.installMethod !== 'native') { 264 await removeInstalledSymlink() 265 } 266 267 logForDebugging('update: Checking npm registry for latest version') 268 logForDebugging(`update: Package URL: ${MACRO.PACKAGE_URL}`) 269 const npmTag = channel === 'stable' ? 'stable' : 'latest' 270 const npmCommand = `npm view ${MACRO.PACKAGE_URL}@${npmTag} version` 271 logForDebugging(`update: Running: ${npmCommand}`) 272 const latestVersion = await getLatestVersion(channel) 273 logForDebugging( 274 `update: Latest version from npm: ${latestVersion || 'FAILED'}`, 275 ) 276 277 if (!latestVersion) { 278 logForDebugging('update: Failed to get latest version from npm registry') 279 process.stderr.write(chalk.red('Failed to check for updates') + '\n') 280 process.stderr.write('Unable to fetch latest version from npm registry\n') 281 process.stderr.write('\n') 282 process.stderr.write('Possible causes:\n') 283 process.stderr.write(' • Network connectivity issues\n') 284 process.stderr.write(' • npm registry is unreachable\n') 285 process.stderr.write(' • Corporate proxy/firewall blocking npm\n') 286 if (MACRO.PACKAGE_URL && !MACRO.PACKAGE_URL.startsWith('@anthropic')) { 287 process.stderr.write( 288 ' • Internal/development build not published to npm\n', 289 ) 290 } 291 process.stderr.write('\n') 292 process.stderr.write('Try:\n') 293 process.stderr.write(' • Check your internet connection\n') 294 process.stderr.write(' • Run with --debug flag for more details\n') 295 const packageName = 296 MACRO.PACKAGE_URL || 297 (process.env.USER_TYPE === 'ant' 298 ? '@anthropic-ai/claude-cli' 299 : '@anthropic-ai/claude-code') 300 process.stderr.write( 301 ` • Manually check: npm view ${packageName} version\n`, 302 ) 303 304 process.stderr.write(' • Check if you need to login: npm whoami\n') 305 await gracefulShutdown(1) 306 } 307 308 // Check if versions match exactly, including any build metadata (like SHA) 309 if (latestVersion === MACRO.VERSION) { 310 writeToStdout( 311 chalk.green(`Claude Code is up to date (${MACRO.VERSION})`) + '\n', 312 ) 313 await gracefulShutdown(0) 314 } 315 316 writeToStdout( 317 `New version available: ${latestVersion} (current: ${MACRO.VERSION})\n`, 318 ) 319 writeToStdout('Installing update...\n') 320 321 // Determine update method based on what's actually running 322 let useLocalUpdate = false 323 let updateMethodName = '' 324 325 switch (diagnostic.installationType) { 326 case 'npm-local': 327 useLocalUpdate = true 328 updateMethodName = 'local' 329 break 330 case 'npm-global': 331 useLocalUpdate = false 332 updateMethodName = 'global' 333 break 334 case 'unknown': { 335 // Fallback to detection if we can't determine installation type 336 const isLocal = await localInstallationExists() 337 useLocalUpdate = isLocal 338 updateMethodName = isLocal ? 'local' : 'global' 339 writeToStdout( 340 chalk.yellow('Warning: Could not determine installation type') + '\n', 341 ) 342 writeToStdout( 343 `Attempting ${updateMethodName} update based on file detection...\n`, 344 ) 345 break 346 } 347 default: 348 process.stderr.write( 349 `Error: Cannot update ${diagnostic.installationType} installation\n`, 350 ) 351 await gracefulShutdown(1) 352 } 353 354 writeToStdout(`Using ${updateMethodName} installation update method...\n`) 355 356 logForDebugging(`update: Update method determined: ${updateMethodName}`) 357 logForDebugging(`update: useLocalUpdate: ${useLocalUpdate}`) 358 359 let status: InstallStatus 360 361 if (useLocalUpdate) { 362 logForDebugging( 363 'update: Calling installOrUpdateClaudePackage() for local update', 364 ) 365 status = await installOrUpdateClaudePackage(channel) 366 } else { 367 logForDebugging('update: Calling installGlobalPackage() for global update') 368 status = await installGlobalPackage() 369 } 370 371 logForDebugging(`update: Installation status: ${status}`) 372 373 switch (status) { 374 case 'success': 375 writeToStdout( 376 chalk.green( 377 `Successfully updated from ${MACRO.VERSION} to version ${latestVersion}`, 378 ) + '\n', 379 ) 380 await regenerateCompletionCache() 381 break 382 case 'no_permissions': 383 process.stderr.write( 384 'Error: Insufficient permissions to install update\n', 385 ) 386 if (useLocalUpdate) { 387 process.stderr.write('Try manually updating with:\n') 388 process.stderr.write( 389 ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, 390 ) 391 } else { 392 process.stderr.write('Try running with sudo or fix npm permissions\n') 393 process.stderr.write( 394 'Or consider using native installation with: claude install\n', 395 ) 396 } 397 await gracefulShutdown(1) 398 break 399 case 'install_failed': 400 process.stderr.write('Error: Failed to install update\n') 401 if (useLocalUpdate) { 402 process.stderr.write('Try manually updating with:\n') 403 process.stderr.write( 404 ` cd ~/.claude/local && npm update ${MACRO.PACKAGE_URL}\n`, 405 ) 406 } else { 407 process.stderr.write( 408 'Or consider using native installation with: claude install\n', 409 ) 410 } 411 await gracefulShutdown(1) 412 break 413 case 'in_progress': 414 process.stderr.write( 415 'Error: Another instance is currently performing an update\n', 416 ) 417 process.stderr.write('Please wait and try again later\n') 418 await gracefulShutdown(1) 419 break 420 } 421 await gracefulShutdown(0) 422}