@@ -72,11 +72,7 @@ pub(crate) fn cmd_install() -> Result<()> {
7272 if configured. is_empty ( ) {
7373 println ! ( "No agents were configured." ) ;
7474 } else {
75- println ! (
76- "\n Installed infigraph MCP server for {} agent(s): {}" ,
77- configured. len( ) ,
78- configured. join( ", " )
79- ) ;
75+ print_capabilities_summary ( & configured) ;
8076 }
8177
8278 // Write primary search instructions to ~/.claude/CLAUDE.md
@@ -396,6 +392,163 @@ pub(crate) fn cmd_uninstall() -> Result<()> {
396392 Ok ( ( ) )
397393}
398394
395+ pub ( crate ) fn platform_triple ( ) -> Result < ( String , String , String ) > {
396+ let os = std:: env:: consts:: OS ;
397+ let arch = std:: env:: consts:: ARCH ;
398+ let os_tag = match os {
399+ "macos" => "apple-darwin" ,
400+ "linux" => "unknown-linux-gnu" ,
401+ "windows" => "pc-windows-msvc" ,
402+ _ => anyhow:: bail!( "unsupported OS: {os}" ) ,
403+ } ;
404+ let arch_tag = match arch {
405+ "x86_64" => "x86_64" ,
406+ "aarch64" => "aarch64" ,
407+ _ => anyhow:: bail!( "unsupported architecture: {arch}" ) ,
408+ } ;
409+ let target = format ! ( "{arch_tag}-{os_tag}" ) ;
410+ Ok ( ( os_tag. to_string ( ) , arch_tag. to_string ( ) , target) )
411+ }
412+
413+ pub ( crate ) fn self_update ( version : & str ) -> Result < ( ) > {
414+ let os = std:: env:: consts:: OS ;
415+ let ( _, _, target) = platform_triple ( ) ?;
416+ let archive_ext = if os == "windows" { "zip" } else { "tar.gz" } ;
417+ let asset_name = format ! ( "infigraph-{target}.{archive_ext}" ) ;
418+ let tag = format ! ( "v{version}" ) ;
419+
420+ let gh_host =
421+ std:: env:: var ( "INFIGRAPH_GH_HOST" ) . unwrap_or_else ( |_| "github.com" . to_string ( ) ) ;
422+ let gh_owner =
423+ std:: env:: var ( "INFIGRAPH_GH_OWNER" ) . unwrap_or_else ( |_| "intuit" . to_string ( ) ) ;
424+ let gh_repo = "infigraph" ;
425+ let full_repo = format ! ( "{gh_host}/{gh_owner}/{gh_repo}" ) ;
426+
427+ println ! ( "Downloading {asset_name} from release {tag}..." ) ;
428+
429+ let install_dir = std:: env:: var ( "INFIGRAPH_INSTALL_DIR" )
430+ . map ( PathBuf :: from)
431+ . unwrap_or_else ( |_| {
432+ dirs:: home_dir ( ) . unwrap_or_else ( || PathBuf :: from ( "." ) ) . join ( ".local" ) . join ( "bin" )
433+ } ) ;
434+
435+ let tmp_dir = std:: env:: temp_dir ( ) ;
436+ let download_path = tmp_dir. join ( & asset_name) ;
437+
438+ let mut gh_args = vec ! [
439+ "release" . to_string( ) , "download" . to_string( ) , tag. clone( ) ,
440+ "--repo" . to_string( ) , format!( "{gh_owner}/{gh_repo}" ) ,
441+ "--pattern" . to_string( ) , asset_name. clone( ) ,
442+ "--dir" . to_string( ) , tmp_dir. to_string_lossy( ) . to_string( ) ,
443+ "--clobber" . to_string( ) ,
444+ ] ;
445+ if gh_host != "github.com" {
446+ gh_args. push ( "--hostname" . to_string ( ) ) ;
447+ gh_args. push ( gh_host. clone ( ) ) ;
448+ }
449+
450+ let status = std:: process:: Command :: new ( "gh" )
451+ . args ( & gh_args)
452+ . status ( )
453+ . context ( "failed to run `gh release download`" ) ?;
454+
455+ if !status. success ( ) {
456+ anyhow:: bail!( "download failed for {asset_name} in release {tag} from {full_repo}" ) ;
457+ }
458+
459+ std:: fs:: create_dir_all ( & install_dir) ?;
460+
461+ let bin_suffix = if os == "windows" { ".exe" } else { "" } ;
462+ for bin in & [ "infigraph" , "infigraph-mcp" , "lsp-to-scip" ] {
463+ let bin_path = install_dir. join ( format ! ( "{bin}{bin_suffix}" ) ) ;
464+ let old_path = install_dir. join ( format ! ( "{bin}{bin_suffix}.old" ) ) ;
465+ if bin_path. exists ( ) {
466+ let _ = std:: fs:: remove_file ( & old_path) ;
467+ let _ = std:: fs:: rename ( & bin_path, & old_path) ;
468+ }
469+ }
470+
471+ if archive_ext == "zip" {
472+ let status = std:: process:: Command :: new ( "unzip" )
473+ . args ( [ "-o" , & download_path. to_string_lossy ( ) , "-d" , & install_dir. to_string_lossy ( ) ] )
474+ . status ( ) ?;
475+ if !status. success ( ) {
476+ anyhow:: bail!( "failed to extract zip" ) ;
477+ }
478+ } else {
479+ let status = std:: process:: Command :: new ( "tar" )
480+ . args ( [ "-xzf" , & download_path. to_string_lossy ( ) , "-C" , & install_dir. to_string_lossy ( ) ] )
481+ . status ( ) ?;
482+ if !status. success ( ) {
483+ anyhow:: bail!( "failed to extract tar.gz" ) ;
484+ }
485+ }
486+
487+ let _ = std:: fs:: remove_file ( & download_path) ;
488+
489+ for bin in & [ "infigraph" , "infigraph-mcp" , "lsp-to-scip" ] {
490+ let _ = std:: fs:: remove_file ( install_dir. join ( format ! ( "{bin}{bin_suffix}.old" ) ) ) ;
491+ }
492+
493+ if os == "macos" {
494+ for bin in & [ "infigraph" , "infigraph-mcp" , "lsp-to-scip" ] {
495+ let _ = std:: process:: Command :: new ( "xattr" )
496+ . args ( [ "-dr" , "com.apple.quarantine" , & install_dir. join ( bin) . to_string_lossy ( ) ] )
497+ . status ( ) ;
498+ }
499+ }
500+
501+ if let Some ( cache_path) = update_cache_path ( ) {
502+ let _ = std:: fs:: remove_file ( & cache_path) ;
503+ }
504+
505+ println ! ( "Installed v{version} to {}" , install_dir. display( ) ) ;
506+ Ok ( ( ) )
507+ }
508+
509+ pub ( crate ) fn print_capabilities_summary ( configured : & [ & str ] ) {
510+ let version = env ! ( "CARGO_PKG_VERSION" ) ;
511+ let count = configured. len ( ) ;
512+ let agents = configured. join ( ", " ) ;
513+
514+ println ! ( ) ;
515+ println ! ( "Infigraph v{version} installed for {count} agent(s): {agents}" ) ;
516+ println ! ( ) ;
517+ println ! ( "What you can do now:" ) ;
518+ println ! ( ) ;
519+ println ! ( " Index & Search" ) ;
520+ println ! ( " infigraph index Index your codebase (code + docs)" ) ;
521+ println ! ( " infigraph search \" query\" Hybrid BM25 + semantic search" ) ;
522+ println ! ( " infigraph search-docs \" q\" Search indexed documents" ) ;
523+ println ! ( ) ;
524+ println ! ( " Analysis" ) ;
525+ println ! ( " infigraph dead-code Find unreachable functions" ) ;
526+ println ! ( " infigraph security Scan for vulnerabilities (30+ patterns)" ) ;
527+ println ! ( " infigraph complexity Cyclomatic complexity hotspots" ) ;
528+ println ! ( " infigraph check CI quality gate (exit non-zero on violations)" ) ;
529+ println ! ( " infigraph review AI-powered PR review" ) ;
530+ println ! ( " infigraph vulns OSV vulnerability scanning" ) ;
531+ println ! ( ) ;
532+ println ! ( " Code Navigation" ) ;
533+ println ! ( " infigraph impact <symbol> Blast radius of a change" ) ;
534+ println ! ( " infigraph routes Detect HTTP/gRPC endpoints" ) ;
535+ println ! ( " infigraph cluster Detect functional modules" ) ;
536+ println ! ( " infigraph architecture Codebase overview" ) ;
537+ println ! ( " infigraph refs <symbol> Find all references" ) ;
538+ println ! ( ) ;
539+ println ! ( " Visualization" ) ;
540+ println ! ( " infigraph visualize Interactive graph in browser" ) ;
541+ println ! ( " infigraph viz-sym <symbol> Focused subgraph for one symbol" ) ;
542+ println ! ( ) ;
543+ println ! ( " Multi-Repo" ) ;
544+ println ! ( " infigraph group create <name> Create a service group" ) ;
545+ println ! ( " infigraph group link Link cross-service calls" ) ;
546+ println ! ( ) ;
547+ println ! ( " Get started:" ) ;
548+ println ! ( " cd your-project && infigraph init" ) ;
549+ println ! ( ) ;
550+ }
551+
399552pub ( crate ) fn update_cache_path ( ) -> Option < PathBuf > {
400553 dirs:: home_dir ( ) . map ( |h| h. join ( ".infigraph" ) . join ( "update_check.json" ) )
401554}
@@ -492,9 +645,26 @@ pub(crate) fn print_update_hint(handle: Option<std::thread::JoinHandle<()>>) {
492645}
493646
494647pub ( crate ) fn cmd_update ( ) -> Result < ( ) > {
495- println ! ( "Updating infigraph..." ) ;
496- println ! ( "Downloading latest install script and running it." ) ;
497- println ! ( "This will fetch the latest binary and re-register MCP configs.\n " ) ;
648+ let current = env ! ( "CARGO_PKG_VERSION" ) ;
649+
650+ // Try direct binary download via gh release if a new version is available
651+ if let Some ( latest) = fetch_latest_version ( ) {
652+ if version_newer ( & latest, current) {
653+ println ! ( "Updating infigraph: v{current} → v{latest}" ) ;
654+ match self_update ( & latest) {
655+ Ok ( ( ) ) => return Ok ( ( ) ) ,
656+ Err ( e) => {
657+ eprintln ! ( "Binary update failed ({e}), falling back to install script..." ) ;
658+ }
659+ }
660+ } else {
661+ println ! ( "Already at latest version v{current}." ) ;
662+ return Ok ( ( ) ) ;
663+ }
664+ }
665+
666+ // Fallback: install script
667+ println ! ( "Downloading latest install script and running it.\n " ) ;
498668
499669 let gh_host =
500670 std:: env:: var ( "INFIGRAPH_GH_HOST" ) . unwrap_or_else ( |_| "github.com" . to_string ( ) ) ;
0 commit comments