@@ -336,8 +336,8 @@ def run_extension(name: str):
336336 console .print (f"[red]Error running extension: { e } [/red]" )
337337
338338@app .command ("install" )
339- def install_extension (name : str ):
340- """Build/Install the extension (runs build.sh)."""
339+ def install_extension (name : str , version : str = typer . Option ( None , "--version" , "-v" ) ):
340+ """Build/Install the extension (runs build.sh). Use -v to specify version. """
341341 ext_path = find_extension_path (name )
342342 if not ext_path :
343343 console .print (f"[red]Extension '{ name } ' not found.[/red]" )
@@ -346,10 +346,17 @@ def install_extension(name: str):
346346 build_script = (ext_path / "build.sh" ).resolve ()
347347 if build_script .exists ():
348348 console .print (f"[green]Building { name } ... from { ext_path } [/green]" )
349+
350+ # Pass version override if specified
351+ env = os .environ .copy ()
352+ if version :
353+ console .print (f"[bold]Target Version: { version } [/bold]" )
354+ env ["QUANUX_EXT_VERSION" ] = version
355+
349356 try :
350357 # Ensure executable
351358 os .chmod (build_script , 0o755 )
352- subprocess .run ([str (build_script )], cwd = ext_path .resolve (), check = True )
359+ subprocess .run ([str (build_script )], cwd = ext_path .resolve (), env = env , check = True )
353360 console .print (f"[bold green]✓ Build successful[/bold green]" )
354361 except subprocess .CalledProcessError :
355362 console .print (f"[red]Build failed for { name } [/red]" )
@@ -366,6 +373,80 @@ def install_extension(name: str):
366373 except subprocess .CalledProcessError :
367374 console .print (f"[red]Failed to install requirements[/red]" )
368375
376+ @app .command ("upgradeable" )
377+ def upgradeable (name : str ):
378+ """Check for available updates (compares installed version vs upstream tags)."""
379+ manifest = load_manifest (name )
380+ if not manifest :
381+ console .print (f"[red]Extension '{ name } ' not found.[/red]" )
382+ return
383+
384+ upstream = manifest .get ("upstream_repo" )
385+ current_version = manifest .get ("version" , "unknown" )
386+
387+ if not upstream :
388+ console .print (f"[yellow]No upstream_repo defined for { name } . Cannot check for updates.[/yellow]" )
389+ return
390+
391+ console .print (f"Current Version: [cyan]{ current_version } [/cyan]" )
392+ console .print (f"Fetching tags from { upstream } ..." )
393+
394+ try :
395+ # Fetch tags via git ls-remote, sort by version
396+ result = subprocess .run (
397+ ["git" , "ls-remote" , "--tags" , "--refs" , "--sort=-v:refname" , upstream ],
398+ capture_output = True , text = True , check = True
399+ )
400+ # Parse tags (refs/tags/dest/v1.2.3 -> v1.2.3)
401+ tags = [line .split ("/" )[- 1 ] for line in result .stdout .splitlines () if line .strip ()]
402+
403+ # Simple heuristic: filter tags that look like versions (vX.Y.Z or X.Y.Z)
404+ # and take the top 5
405+ versions = [t for t in tags if "v" in t or "." in t ][:5 ]
406+
407+ console .print ("\n [bold]Available Versions (Top 5):[/bold]" )
408+ for v in versions :
409+ if v == current_version or v == f"v{ current_version } " :
410+ console .print (f" [green]{ v } (Installed)[/green]" )
411+ else :
412+ console .print (f" { v } " )
413+
414+ except subprocess .CalledProcessError as e :
415+ console .print (f"[red]Failed to fetch tags: { e } [/red]" )
416+
417+ @app .command ("upgrade" )
418+ def upgrade (name : str ):
419+ """Auto-upgrade to the latest version found upstream."""
420+ manifest = load_manifest (name )
421+ if not manifest :
422+ console .print (f"[red]Extension '{ name } ' not found.[/red]" )
423+ raise typer .Exit (1 )
424+
425+ upstream = manifest .get ("upstream_repo" )
426+ if not upstream :
427+ console .print (f"[yellow]No upstream_repo defined. Cannot upgrade.[/yellow]" )
428+ raise typer .Exit (1 )
429+
430+ try :
431+ # Fetch status to get latest
432+ result = subprocess .run (
433+ ["git" , "ls-remote" , "--tags" , "--refs" , "--sort=-v:refname" , upstream ],
434+ capture_output = True , text = True , check = True
435+ )
436+ lines = result .stdout .splitlines ()
437+ if not lines :
438+ console .print ("[red]No tags found upstream.[/red]" )
439+ return
440+
441+ # Latest is the first one due to sort=-v:refname
442+ latest_tag = lines [0 ].split ("/" )[- 1 ]
443+
444+ console .print (f"[bold green]Upgrading { name } -> { latest_tag } [/bold green]" )
445+ install_extension (name , version = latest_tag )
446+
447+ except subprocess .CalledProcessError as e :
448+ console .print (f"[red]Upgrade failed: { e } [/red]" )
449+
369450@app .command ("uninstall" )
370451def uninstall_extension (name : str , force : bool = typer .Option (False , "--force" , "-f" )):
371452 """Clean up build artifacts (removes 'build' directory)."""
@@ -387,3 +468,8 @@ def uninstall_extension(name: str, force: bool = typer.Option(False, "--force",
387468 else :
388469 console .print (f"[yellow]No build directory found for { name } .[/yellow]" )
389470
471+ @app .command ("remove" )
472+ def remove_extension (name : str , force : bool = typer .Option (False , "--force" , "-f" )):
473+ """Alias for uninstall."""
474+ uninstall_extension (name , force )
475+
0 commit comments