diff --git a/README.md b/README.md index 37adc137c..cffffcd3a 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,33 @@ if task.status == "completed": ## Installation and Usage -Install the SDK using pip or uv: +Install the SDK using pip, pipx, or uv: ```bash pip install codegen # or -uv pip install codegen +pipx install codegen +# or +uv tool install codegen ``` +### Keeping Up to Date + +The Codegen CLI includes a built-in self-update system: + +```bash +# Update to the latest version +codegen update + +# Check for available updates +codegen update --check + +# Update to a specific version +codegen update --version 1.2.3 +``` + +The CLI automatically checks for updates daily and notifies you when a new version is available. + Get started at [codegen.com](https://codegen.com) and get your API token at [codegen.com/token](https://codegen.com/token). You can interact with your AI engineer via API, or chat with it in Slack, Linear, Github, or on our website. diff --git a/docs/introduction/cli.mdx b/docs/introduction/cli.mdx index 30da2482f..36c41be62 100644 --- a/docs/introduction/cli.mdx +++ b/docs/introduction/cli.mdx @@ -48,6 +48,24 @@ codegen login codegen login --token YOUR_API_TOKEN ``` +### `codegen update` + +Keep your CLI up to date with the latest features and improvements. The CLI automatically checks for updates daily and notifies you when new versions are available. + +```bash +# Update to latest version +codegen update + +# Check for updates without installing +codegen update --check + +# Update to a specific version +codegen update --version 1.2.3 + +# Preview changes without updating +codegen update --dry-run +``` + ## What You Can Do - **View and manage agents** - List agent runs, check status, and see detailed execution logs @@ -55,6 +73,7 @@ codegen login --token YOUR_API_TOKEN - **Create new agents** - Trigger agent runs from the command line with custom prompts - **Run Claude Code** - Execute Claude Code with OpenTelemetry monitoring and comprehensive logging - **Manage organizations** - Switch between organizations and configure repositories +- **Stay up to date** - Built-in self-update system with automatic update notifications The CLI provides the same capabilities as the web UI and API, optimized for diff --git a/docs/introduction/sdk.mdx b/docs/introduction/sdk.mdx index 833439ac3..3d5f60331 100644 --- a/docs/introduction/sdk.mdx +++ b/docs/introduction/sdk.mdx @@ -31,10 +31,31 @@ if task.status == "completed": ## Installation -Install the [codegen](https://pypi.org/project/codegen/) package from PyPI: +Install the [codegen](https://pypi.org/project/codegen/) package from PyPI using your preferred package manager: ```bash +# Using pip +pip install codegen + +# Using pipx (for CLI usage) +pipx install codegen + +# Using uv uv pip install codegen +# or +uv tool install codegen +``` + +### Keeping Up to Date + +The CLI includes a built-in self-update system that checks for updates daily: + +```bash +# Update to latest version +codegen update + +# Check for updates +codegen update --check ``` ## Get Started diff --git a/docs/self-update.md b/docs/self-update.md new file mode 100644 index 000000000..fa346450a --- /dev/null +++ b/docs/self-update.md @@ -0,0 +1,293 @@ +# Self-Update System for Codegen CLI + +The Codegen CLI includes a simplified self-update system that allows users to easily update to the latest version. + +## Features + +### 🚀 Smart Update Detection +- Automatically detects installation method (pip, pipx, uv tool, homebrew, development) +- Checks for updates periodically (once per day by default) +- Shows update notifications when new versions are available +- Fetches stable releases from PyPI + +### 🔄 Update Management +- Update to latest stable version +- Update to specific versions +- Dry-run mode to preview changes +- Legacy mode for simple pip upgrades + +### đŸ›Ąī¸ Safety Features +- Pre-update validation checks +- Major version update warnings +- Post-update tips and guidance + +### 📊 Version Information +- List available versions +- Check for updates without installing +- View current and latest versions + +## Usage + +### Basic Commands + +```bash +# Update to latest stable version +codegen update + +# Check for available updates without installing +codegen update --check + +# List available versions +codegen update --list + +# Show version history +codegen update --history +``` + +### Advanced Options + +```bash +# Update to specific version +codegen update --version 1.2.3 + +# Preview update without making changes +codegen update --dry-run + +# Force update check (bypass cache) +codegen update --force + +# Use legacy pip upgrade method +codegen update --legacy +``` + +## Release Information + +The update system fetches stable releases from PyPI. Pre-release versions are automatically filtered out to ensure stability. Only production-ready versions are shown and available for installation through the update command. + +## Installation Methods + +The update system automatically detects how Codegen was installed and uses the appropriate update method: + +### pip Installation +```bash +pip install codegen +# Updates via: pip install --upgrade codegen +``` + +### pipx Installation +```bash +pipx install codegen +# Updates via: pipx upgrade codegen +``` + +### UV Tool Installation +```bash +uv tool install codegen +# Updates via: uv tool install --upgrade codegen +``` + +### Homebrew Installation (macOS) +```bash +brew install codegen +# Updates via: brew upgrade codegen +``` + +### Development Installation +For development/editable installs, the update system will notify you to update via git: +```bash +git pull origin main +pip install -e . +``` + +## Update Process + +The update system performs a streamlined update process: + +### How Updates Work + +1. **Version Check**: Fetches available versions from PyPI +2. **Comparison**: Compares current version with available versions +3. **Confirmation**: Asks for user confirmation before proceeding +4. **Installation**: Uses the appropriate package manager to update +5. **Verification**: Displays success message and restart instructions + +### Major Version Updates + +When updating to a new major version: +- The system warns about potential breaking changes +- Post-update tips are displayed +- Users are encouraged to check the changelog + +## Update Notifications + +The CLI checks for updates periodically and shows notifications when new versions are available: + +``` +â„šī¸ A new version of Codegen CLI is available: 1.2.3 +Run 'codegen update' to upgrade +``` + +### Disable Update Checks + +To disable automatic update checks, set the environment variable: + +```bash +export CODEGEN_DISABLE_UPDATE_CHECK=1 +``` + +## Downgrading + +If an update causes issues, you can downgrade to a previous version: + +```bash +# Downgrade to a specific version +codegen update --version 1.2.2 + +# Or use your package manager directly +pip install codegen==1.2.2 +pipx install codegen==1.2.2 --force +uv tool install codegen==1.2.2 --upgrade +``` + +## Configuration + +Update settings are stored in `~/.codegen/`: + +- `update_check.json` - Last update check timestamp and cache + +## Troubleshooting + +### Update Fails + +1. Try using the legacy update method: + ```bash + codegen update --legacy + ``` + +2. Manually update via pip: + ```bash + pip install --upgrade codegen + ``` + +3. Check installation method: + ```bash + which codegen + pip show codegen + ``` + +### Permission Errors + +If you get permission errors, you may need to use sudo (not recommended) or update your user installation: + +```bash +# For user installation +pip install --user --upgrade codegen + +# For pipx +pipx upgrade codegen +``` + +### Downgrade Issues + +If you need to downgrade: + +1. Use the update command with a specific version: + ```bash + codegen update --version 1.2.2 + ``` + +2. Or manually install the desired version: + ```bash + pip install codegen==1.2.2 + pipx install codegen==1.2.2 --force + uv tool install codegen==1.2.2 --upgrade + ``` + +## Development + +### Testing Updates + +Test the update system in development: + +```bash +# Check current version +codegen --version + +# Test update check +codegen update --check --force + +# Test dry-run update +codegen update --dry-run + +# Test specific version +codegen update --version 1.2.3 --dry-run +``` + +### Adding New Features + +To add new update features: + +1. Modify `updater.py` for core functionality +2. Update command options in `main.py` +3. Add tests for new functionality + +## API Reference + +### UpdateManager Class + +```python +from codegen.cli.commands.update import UpdateManager + +manager = UpdateManager() + +# Check for updates +result = manager.check_for_updates(force=True) + +# Perform update +success = manager.perform_update(target_version="1.2.3", dry_run=False) + +# Check installation method +print(manager.install_method) +``` + +### Installation Methods + +```python +from codegen.cli.commands.update.updater import InstallMethod + +methods = [ + InstallMethod.PIP, + InstallMethod.PIPX, + InstallMethod.UV_TOOL, + InstallMethod.HOMEBREW, + InstallMethod.DEVELOPMENT, + InstallMethod.UNKNOWN +] +``` + +## Best Practices + +1. **Regular Updates**: Keep your CLI updated for latest features and security fixes +2. **Check Changelog**: Review breaking changes before major version updates +3. **Test in Dev**: Test updates in development environment first +4. **Use Dry Run**: Preview updates with `--dry-run` before applying +5. **Report Issues**: Report update issues to help improve the system + +## Security + +- Updates are fetched over HTTPS from PyPI +- Package signatures are verified by pip/pipx/uv +- Pre-release versions are filtered out automatically +- Major version updates require confirmation + +## Future Enhancements + +- [ ] Automatic rollback on update failure +- [ ] Configuration migration system +- [ ] Release notes integration +- [ ] Beta and nightly release channels +- [ ] Binary distribution for faster updates +- [ ] Automatic security update installation +- [ ] Update progress with detailed logging +- [ ] Network proxy support +- [ ] Offline update packages diff --git a/src/codegen/cli/cli.py b/src/codegen/cli/cli.py index ab19f73ae..7de7fc85b 100644 --- a/src/codegen/cli/cli.py +++ b/src/codegen/cli/cli.py @@ -59,6 +59,18 @@ def version_callback(value: bool): # Create the main Typer app main = typer.Typer(name="codegen", help="Codegen - the Operating System for Code Agents.", rich_markup_mode="rich") +# Check for updates on startup (non-blocking) +try: + from codegen.cli.commands.update import check_for_updates_on_startup + + # Only check on actual command runs, not completions + import sys + + if not any(arg in sys.argv for arg in ["--help", "-h", "completion", "--version"]): + check_for_updates_on_startup() +except ImportError: + pass # Update check dependencies not available + # Add individual commands to the main app (logging now handled within each command) main.command("agent", help="Create a new agent run with a prompt.")(agent) main.command("claude", help="Run Claude Code with OpenTelemetry monitoring and logging.")(claude) diff --git a/src/codegen/cli/commands/update/__init__.py b/src/codegen/cli/commands/update/__init__.py new file mode 100644 index 000000000..4e2613cc1 --- /dev/null +++ b/src/codegen/cli/commands/update/__init__.py @@ -0,0 +1,6 @@ +"""Update command module for Codegen CLI.""" + +from .main import update +from .updater import UpdateManager, check_for_updates_on_startup + +__all__ = ["update", "UpdateManager", "check_for_updates_on_startup"] diff --git a/src/codegen/cli/commands/update/main.py b/src/codegen/cli/commands/update/main.py index f5a22b5e8..1cdd5d349 100644 --- a/src/codegen/cli/commands/update/main.py +++ b/src/codegen/cli/commands/update/main.py @@ -6,8 +6,12 @@ import rich import typer from packaging.version import Version +from rich.console import Console import codegen +from .updater import UpdateManager, check_for_updates_on_startup + +console = Console() def fetch_pypi_releases(package: str) -> list[str]: @@ -31,18 +35,52 @@ def install_package(package: str, *args: str) -> None: def update( - list_: bool = typer.Option(False, "--list", "-l", help="List all supported versions of the codegen"), - version: str | None = typer.Option(None, "--version", "-v", help="Update to a specific version of the codegen"), + list_: bool = typer.Option(False, "--list", "-l", help="List all supported versions"), + version: str | None = typer.Option(None, "--version", "-v", help="Update to a specific version"), + check: bool = typer.Option(False, "--check", help="Check for available updates without installing"), + dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be updated without making changes"), + force: bool = typer.Option(False, "--force", "-f", help="Force update check even if recently checked"), + legacy: bool = typer.Option(False, "--legacy", help="Use legacy update method (simple pip upgrade)"), ): - """Update Codegen to the latest or specified version + """Update Codegen CLI to the latest or specified version. - --list: List all supported versions of the codegen - --version: Update to a specific version of the codegen + Examples: + codegen update # Update to latest version + codegen update --check # Check for updates + codegen update --version 1.2.3 # Update to specific version + codegen update --dry-run # Preview update without making changes """ - if list_ and version: - rich.print("[red]Error:[/red] Cannot specify both --list and --version") - raise typer.Exit(1) + # Handle legacy mode + if legacy: + _legacy_update(list_, version) + return + + # Use new update manager + manager = UpdateManager() + + # Handle different actions + if check or list_: + result = manager.check_for_updates(force=force) + + if result.update_available: + console.print(f"\n[cyan]Update available: {result.current_version} → {result.latest_version}[/cyan]") + console.print("[dim]Run 'codegen update' to upgrade[/dim]\n") + else: + console.print("[green]You're on the latest version![/green]") + + if list_ and result.versions: + console.print("\n[bold]Available versions:[/bold]") + for ver_info in result.versions[:10]: + marker = " (current)" if ver_info.version == result.current_version else "" + console.print(f" {ver_info.version}{marker}") + else: + # Perform update + if not manager.perform_update(target_version=version, dry_run=dry_run): + raise typer.Exit(1) + +def _legacy_update(list_: bool, version: str | None): + """Legacy update method using simple pip upgrade.""" package_name = codegen.__package__ or "codegen" package_info = distribution(package_name) current_version = Version(package_info.version) diff --git a/src/codegen/cli/commands/update/updater.py b/src/codegen/cli/commands/update/updater.py new file mode 100644 index 000000000..2987d9023 --- /dev/null +++ b/src/codegen/cli/commands/update/updater.py @@ -0,0 +1,396 @@ +"""Simplified self-update system for the Codegen CLI.""" + +import json +import os +import platform +import shutil +import subprocess +import sys +from dataclasses import dataclass +from datetime import datetime, timedelta +from enum import Enum +from pathlib import Path +from typing import List, Optional + +import requests +from packaging.version import Version +from rich.console import Console +from rich.panel import Panel +from rich.progress import Progress, SpinnerColumn, TextColumn +from rich.table import Table + +console = Console() + +# Update configuration +UPDATE_CHECK_FILE = Path.home() / ".codegen" / "update_check.json" +UPDATE_CHECK_INTERVAL = timedelta(hours=24) # Check for updates once per day + + +class InstallMethod(Enum): + """Installation methods for the CLI.""" + + PIP = "pip" + PIPX = "pipx" + UV_TOOL = "uv_tool" + HOMEBREW = "homebrew" + GITHUB_RELEASE = "github" + DEVELOPMENT = "development" + UNKNOWN = "unknown" + + +@dataclass +class VersionInfo: + """Information about a version.""" + + version: Version + release_date: datetime + download_url: Optional[str] = None + release_notes: Optional[str] = None + size: Optional[int] = None + + +@dataclass +class UpdateCheckResult: + """Result of checking for updates.""" + + current_version: Version + latest_version: Optional[Version] = None + update_available: bool = False + versions: List[VersionInfo] = None + last_check: Optional[datetime] = None + + +class UpdateManager: + """Manages self-updates for the CLI.""" + + def __init__(self, package_name: str = "codegen"): + self.package_name = package_name + self.console = console + self.install_method = self._detect_install_method() + + def _detect_install_method(self) -> InstallMethod: + """Detect how the CLI was installed.""" + # Check for UV tool FIRST (before development check) + # UV tools are installed in ~/.local/share/uv/tools/ + if ".local/share/uv/tools/" in sys.executable: + return InstallMethod.UV_TOOL + + # Also check if the codegen command is in the UV managed bin directory + try: + import shutil + + codegen_path = shutil.which("codegen") + if codegen_path and ".local/bin/codegen" in codegen_path: + # Check if this links to a UV tool installation + real_path = os.path.realpath(codegen_path) + if ".local/share/uv/tools/" in real_path: + return InstallMethod.UV_TOOL + except Exception: + pass + + # Check for pipx + if "pipx" in sys.executable or os.environ.get("PIPX_HOME"): + return InstallMethod.PIPX + + # Check for Homebrew + if platform.system() == "Darwin" and "/homebrew/" in sys.executable: + return InstallMethod.HOMEBREW + + # Check if running from development environment + # This check should come AFTER UV tool check since UV tool installations + # may have direct_url.json files + if "site-packages" not in sys.executable and "dist-packages" not in sys.executable: + # Check if we're in an editable install + try: + import importlib.metadata + + dist = importlib.metadata.distribution(self.package_name) + if dist.read_text("direct_url.json"): + return InstallMethod.DEVELOPMENT + except Exception: + pass + + # Check for pip + if "site-packages" in sys.executable or "dist-packages" in sys.executable: + return InstallMethod.PIP + + return InstallMethod.UNKNOWN + + def check_for_updates(self, force: bool = False) -> UpdateCheckResult: + """Check for available updates.""" + # Load last check time + last_check = self._load_last_check_time() + + # Skip check if recently checked (unless forced) + if not force and last_check: + if datetime.now() - last_check < UPDATE_CHECK_INTERVAL: + return UpdateCheckResult(current_version=self._get_current_version(), last_check=last_check) + + current_version = self._get_current_version() + + try: + # Fetch available versions + versions = self._fetch_available_versions() + + # Find latest stable version + latest = self._find_latest_version(versions) + + # Save check time + self._save_last_check_time() + + return UpdateCheckResult( + current_version=current_version, + latest_version=latest.version if latest else None, + update_available=latest and latest.version > current_version, + versions=versions, + last_check=datetime.now(), + ) + except Exception as e: + self.console.print(f"[yellow]Warning: Could not check for updates: {e}[/yellow]") + return UpdateCheckResult(current_version=current_version) + + def _get_current_version(self) -> Version: + """Get the current installed version.""" + import importlib.metadata + + dist = importlib.metadata.distribution(self.package_name) + return Version(dist.version) + + def _fetch_available_versions(self) -> List[VersionInfo]: + """Fetch available versions from PyPI.""" + versions = [] + + # Fetch from PyPI + try: + response = requests.get(f"https://pypi.org/pypi/{self.package_name}/json", timeout=10) + response.raise_for_status() + data = response.json() + + for version_str, releases in data["releases"].items(): + try: + version = Version(version_str) + + # Skip pre-releases + if version.is_prerelease: + continue + + # Get release info + release_date = None + if releases: + upload_time = releases[0].get("upload_time_iso_8601") + if upload_time: + release_date = datetime.fromisoformat(upload_time.replace("Z", "+00:00")) + + versions.append( + VersionInfo( + version=version, + release_date=release_date or datetime.now(), + download_url=f"https://pypi.org/project/{self.package_name}/{version}/", + ) + ) + except Exception: + continue # Skip invalid versions + except Exception as e: + self.console.print(f"[yellow]Could not fetch PyPI versions: {e}[/yellow]") + + return sorted(versions, key=lambda v: v.version, reverse=True) + + def _find_latest_version(self, versions: List[VersionInfo]) -> Optional[VersionInfo]: + """Find the latest stable version.""" + for version_info in versions: + # Return the first (highest) version since list is sorted + return version_info + return None + + def _load_last_check_time(self) -> Optional[datetime]: + """Load the last update check time.""" + if UPDATE_CHECK_FILE.exists(): + try: + with open(UPDATE_CHECK_FILE) as f: + data = json.load(f) + return datetime.fromisoformat(data.get("last_check")) + except Exception: + pass + return None + + def _save_last_check_time(self) -> None: + """Save the last update check time.""" + UPDATE_CHECK_FILE.parent.mkdir(parents=True, exist_ok=True) + with open(UPDATE_CHECK_FILE, "w") as f: + json.dump({"last_check": datetime.now().isoformat()}, f) + + def perform_update(self, target_version: Optional[str] = None, dry_run: bool = False) -> bool: + """Perform the update to a specific version or latest.""" + current_version = self._get_current_version() + + # Determine target version + if target_version: + try: + target = Version(target_version) + except Exception: + self.console.print(f"[red]Invalid version: {target_version}[/red]") + return False + else: + # Get latest stable version + check_result = self.check_for_updates(force=True) + if not check_result.update_available: + self.console.print("[green]Already on the latest version![/green]") + return True + target = check_result.latest_version + + if target <= current_version: + self.console.print(f"[yellow]Target version {target} is not newer than current {current_version}[/yellow]") + return False + + # Show update plan + self._show_update_plan(current_version, target, dry_run) + + if dry_run: + return True + + # Confirm update + if not self._confirm_update(): + self.console.print("[yellow]Update cancelled[/yellow]") + return False + + # Run pre-update hooks + if not self._run_pre_update_hooks(current_version, target): + self.console.print("[red]Pre-update checks failed[/red]") + return False + + # Perform the update based on installation method + success = self._perform_update_for_method(target) + + if success: + # Run post-update hooks + self._run_post_update_hooks(current_version, target) + + self.console.print(f"[green]✅ Successfully updated from {current_version} to {target}![/green]") + self.console.print("\n[cyan]Please restart your terminal or run:[/cyan]") + self.console.print("[bold]hash -r[/bold] # For bash/zsh") + self.console.print("[bold]rehash[/bold] # For zsh") + else: + self.console.print("[red]Update failed.[/red]") + + return success + + def _show_update_plan(self, current: Version, target: Version, dry_run: bool) -> None: + """Show the update plan.""" + panel_content = f""" +[cyan]Current Version:[/cyan] {current} +[cyan]Target Version:[/cyan] {target} +[cyan]Install Method:[/cyan] {self.install_method.value} +[cyan]Dry Run:[/cyan] {dry_run} +""" + + self.console.print(Panel(panel_content.strip(), title="Update Plan", border_style="blue")) + + def _confirm_update(self) -> bool: + """Confirm the update with the user.""" + from rich.prompt import Confirm + + return Confirm.ask("Do you want to proceed with the update?", default=True) + + def _run_pre_update_hooks(self, current: Version, target: Version) -> bool: + """Run pre-update hooks.""" + # Check for breaking changes + if target.major > current.major: + self.console.print("[yellow]âš ī¸ Major version update detected - breaking changes possible[/yellow]") + + # No migrations needed for now + return True + + def _run_post_update_hooks(self, previous: Version, current: Version) -> None: + """Run post-update hooks.""" + # Show post-update tips + if current.major > previous.major: + self.console.print("\n[cyan]📚 Major version update completed![/cyan]") + self.console.print("[dim]Check the changelog for breaking changes and new features.[/dim]") + + def _perform_update_for_method(self, target: Version) -> bool: + """Perform update based on installation method.""" + if self.install_method == InstallMethod.PIP: + return self._update_via_pip(target) + elif self.install_method == InstallMethod.PIPX: + return self._update_via_pipx(target) + elif self.install_method == InstallMethod.UV_TOOL: + return self._update_via_uv_tool(target) + elif self.install_method == InstallMethod.HOMEBREW: + return self._update_via_homebrew(target) + elif self.install_method == InstallMethod.DEVELOPMENT: + self.console.print("[yellow]Development installation detected - please update via git[/yellow]") + return False + else: + self.console.print("[yellow]Unknown installation method - trying pip[/yellow]") + return self._update_via_pip(target) + + def _update_via_pip(self, target: Version) -> bool: + """Update using pip.""" + try: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + transient=True, + ) as progress: + progress.add_task(f"Updating via pip to {target}...", total=None) + + subprocess.check_call([sys.executable, "-m", "pip", "install", f"{self.package_name}=={target}", "--upgrade"]) + return True + except subprocess.CalledProcessError as e: + self.console.print(f"[red]pip update failed: {e}[/red]") + return False + + def _update_via_pipx(self, target: Version) -> bool: + """Update using pipx.""" + try: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + transient=True, + ) as progress: + progress.add_task(f"Updating via pipx to {target}...", total=None) + + subprocess.check_call(["pipx", "upgrade", self.package_name, "--pip-args", f"{self.package_name}=={target}"]) + return True + except subprocess.CalledProcessError as e: + self.console.print(f"[red]pipx update failed: {e}[/red]") + return False + + def _update_via_uv_tool(self, target: Version) -> bool: + """Update using uv tool.""" + try: + with Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + transient=True, + ) as progress: + progress.add_task(f"Updating via uv tool to {target}...", total=None) + + # UV tool requires different syntax: uv tool install --upgrade package==version + subprocess.check_call(["uv", "tool", "install", "--upgrade", f"{self.package_name}=={target}"]) + return True + except subprocess.CalledProcessError as e: + self.console.print(f"[red]uv tool update failed: {e}[/red]") + self.console.print("[yellow]You may need to manually update using:[/yellow]") + self.console.print(f"[bold]uv tool install --upgrade {self.package_name}=={target}[/bold]") + return False + + def _update_via_homebrew(self, target: Version) -> bool: + """Update using Homebrew.""" + self.console.print("[yellow]Homebrew update not yet implemented - please use 'brew upgrade codegen'[/yellow]") + return False + + +def check_for_updates_on_startup() -> None: + """Check for updates on CLI startup (non-blocking).""" + try: + # Only check if we haven't checked recently + manager = UpdateManager() + result = manager.check_for_updates(force=False) + + if result.update_available: + console.print(f"\n[cyan]â„šī¸ A new version of Codegen CLI is available: {result.latest_version}[/cyan]") + console.print("[dim]Run 'codegen update' to upgrade[/dim]\n") + except Exception: + # Silently ignore update check failures on startup + pass