import * as React from 'react' import { useState } from 'react' import { useInterval } from 'usehooks-ts' import { Text } from '@anthropic/ink' import { type AutoUpdaterResult, getLatestVersionFromGcs, getMaxVersion, shouldSkipVersion, } from '../utils/autoUpdater.js' import { isAutoUpdaterDisabled } from '../utils/config.js' import { logForDebugging } from '../utils/debug.js' import { getPackageManager, type PackageManager, } from '../utils/nativeInstaller/packageManagers.js' import { gt, gte } from '../utils/semver.js' import { getInitialSettings } from '../utils/settings/settings.js' type Props = { isUpdating: boolean onChangeIsUpdating: (isUpdating: boolean) => void onAutoUpdaterResult: (autoUpdaterResult: AutoUpdaterResult) => void autoUpdaterResult: AutoUpdaterResult | null showSuccessMessage: boolean verbose: boolean } export function PackageManagerAutoUpdater({ verbose }: Props): React.ReactNode { const [updateAvailable, setUpdateAvailable] = useState(false) const [packageManager, setPackageManager] = useState('unknown') const checkForUpdates = React.useCallback(async () => { if ( process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'development' ) { return } if (isAutoUpdaterDisabled()) { return } const [channel, pm] = await Promise.all([ Promise.resolve(getInitialSettings()?.autoUpdatesChannel ?? 'latest'), getPackageManager(), ]) setPackageManager(pm) let latest = await getLatestVersionFromGcs(channel) // Check if max version is set (server-side kill switch for auto-updates) const maxVersion = await getMaxVersion() if (maxVersion && latest && gt(latest, maxVersion)) { logForDebugging( `PackageManagerAutoUpdater: maxVersion ${maxVersion} is set, capping update from ${latest} to ${maxVersion}`, ) if (gte(MACRO.VERSION, maxVersion)) { logForDebugging( `PackageManagerAutoUpdater: current version ${MACRO.VERSION} is already at or above maxVersion ${maxVersion}, skipping update`, ) setUpdateAvailable(false) return } latest = maxVersion } const hasUpdate = latest && !gte(MACRO.VERSION, latest) && !shouldSkipVersion(latest) setUpdateAvailable(!!hasUpdate) if (hasUpdate) { logForDebugging( `PackageManagerAutoUpdater: Update available ${MACRO.VERSION} -> ${latest}`, ) } }, []) // Initial check React.useEffect(() => { void checkForUpdates() }, [checkForUpdates]) // Check every 30 minutes useInterval(checkForUpdates, 30 * 60 * 1000) if (!updateAvailable) { return null } // pacman, deb, and rpm don't get specific commands because they each have // multiple frontends (pacman: yay/paru/makepkg, deb: apt/apt-get/aptitude/nala, // rpm: dnf/yum/zypper) const updateCommand = packageManager === 'homebrew' ? 'brew upgrade claude-code' : packageManager === 'winget' ? 'winget upgrade Anthropic.ClaudeCode' : packageManager === 'apk' ? 'apk upgrade claude-code' : 'your package manager update command' return ( <> {verbose && ( currentVersion: {MACRO.VERSION} )} Update available! Run: {updateCommand} ) }