@@ -9,7 +9,10 @@ import (
99 "sort"
1010 "strings"
1111
12+ "io"
13+ "net/http"
1214 "os/exec"
15+ "time"
1316
1417 "desktop/internal/auth"
1518 "desktop/internal/backup"
@@ -21,6 +24,8 @@ import (
2124 wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime"
2225)
2326
27+ const AppVersion = "0.1.0"
28+
2429type App struct {
2530 ctx context.Context
2631 database * db.DB
@@ -473,6 +478,156 @@ func (a *App) GetCurrentUser(token string) (map[string]string, error) {
473478 return map [string ]string {"username" : username }, nil
474479}
475480
481+ // ============== Environment & Install ==============
482+
483+ func (a * App ) GetAppVersion () string {
484+ return AppVersion
485+ }
486+
487+ func (a * App ) CheckEnvironment () map [string ]interface {} {
488+ result := map [string ]interface {}{
489+ "python_found" : false ,
490+ "python_path" : "" ,
491+ "avm_installed" : false ,
492+ "avm_version" : "" ,
493+ }
494+
495+ // Find Python (reuse candidate logic from embedding.DetectPython)
496+ pythonPath := a .findPython ()
497+ if pythonPath == "" {
498+ return result
499+ }
500+ result ["python_found" ] = true
501+ result ["python_path" ] = pythonPath
502+
503+ // Check aivectormemory installed + version
504+ out , err := exec .Command (pythonPath , "-c" ,
505+ "import aivectormemory; print(aivectormemory.__version__)" ).Output ()
506+ if err != nil {
507+ return result
508+ }
509+ version := strings .TrimSpace (string (out ))
510+ if version != "" {
511+ result ["avm_installed" ] = true
512+ result ["avm_version" ] = version
513+ }
514+ return result
515+ }
516+
517+ func (a * App ) CheckUpgrade (currentAvmVersion string ) map [string ]interface {} {
518+ result := map [string ]interface {}{
519+ "avm_latest" : "" ,
520+ "avm_update_available" : false ,
521+ "app_latest" : "" ,
522+ "app_update_available" : false ,
523+ "app_download_url" : "" ,
524+ }
525+
526+ // 1. Check PyPI latest version
527+ pythonPath := a .findPython ()
528+ if pythonPath != "" {
529+ // pip install aivectormemory== triggers error with available versions
530+ out , _ := exec .Command (pythonPath , "-m" , "pip" , "install" , "aivectormemory==___" ).CombinedOutput ()
531+ outStr := string (out )
532+ // Parse "from versions: 0.2.1, 0.2.2, ..., 0.2.6)"
533+ if idx := strings .LastIndex (outStr , "from versions:" ); idx >= 0 {
534+ tail := outStr [idx + len ("from versions:" ):]
535+ if end := strings .Index (tail , ")" ); end >= 0 {
536+ versions := strings .TrimSpace (tail [:end ])
537+ parts := strings .Split (versions , "," )
538+ if len (parts ) > 0 {
539+ latest := strings .TrimSpace (parts [len (parts )- 1 ])
540+ result ["avm_latest" ] = latest
541+ if latest != "" && latest != currentAvmVersion {
542+ result ["avm_update_available" ] = true
543+ }
544+ }
545+ }
546+ }
547+ }
548+
549+ // 2. Check GitHub Releases latest version
550+ client := & http.Client {Timeout : 10 * time .Second }
551+ resp , err := client .Get ("https://api.github.com/repos/Edlineas/aivectormemory/releases/latest" )
552+ if err == nil {
553+ defer resp .Body .Close ()
554+ if resp .StatusCode == 200 {
555+ body , _ := io .ReadAll (resp .Body )
556+ var release struct {
557+ TagName string `json:"tag_name"`
558+ HTMLURL string `json:"html_url"`
559+ }
560+ if json .Unmarshal (body , & release ) == nil && release .TagName != "" {
561+ appLatest := strings .TrimPrefix (release .TagName , "v" )
562+ result ["app_latest" ] = appLatest
563+ result ["app_download_url" ] = release .HTMLURL
564+ if appLatest != AppVersion {
565+ result ["app_update_available" ] = true
566+ }
567+ }
568+ }
569+ }
570+
571+ return result
572+ }
573+
574+ func (a * App ) InstallPackage (upgrade bool ) (string , error ) {
575+ pythonPath := a .findPython ()
576+ if pythonPath == "" {
577+ return "" , fmt .Errorf ("Python not found" )
578+ }
579+
580+ args := []string {"-m" , "pip" , "install" }
581+ if upgrade {
582+ args = append (args , "--upgrade" )
583+ }
584+ args = append (args , "aivectormemory" )
585+
586+ cmd := exec .Command (pythonPath , args ... )
587+ output , err := cmd .CombinedOutput ()
588+ if err != nil {
589+ return string (output ), fmt .Errorf ("install failed: %w\n %s" , err , string (output ))
590+ }
591+ return string (output ), nil
592+ }
593+
594+ func (a * App ) findPython () string {
595+ // If settings has a custom python path, try it first
596+ if a .settings != nil && a .settings .PythonPath != "" {
597+ if _ , err := os .Stat (a .settings .PythonPath ); err == nil {
598+ return a .settings .PythonPath
599+ }
600+ }
601+ // If engine already detected one, use it
602+ if a .engine != nil && a .engine .PythonPath != "" {
603+ return a .engine .PythonPath
604+ }
605+
606+ // Scan candidates (find Python, not necessarily with aivectormemory)
607+ home , _ := os .UserHomeDir ()
608+ candidates := []string {
609+ filepath .Join (home , "item" , "run-memory-mcp-server" , ".venv" , "bin" , "python3" ),
610+ "python3" , "python" ,
611+ "/usr/local/bin/python3" ,
612+ "/usr/bin/python3" ,
613+ "/opt/homebrew/bin/python3" ,
614+ }
615+ for _ , py := range candidates {
616+ path := py
617+ if ! filepath .IsAbs (path ) {
618+ found , err := exec .LookPath (path )
619+ if err != nil {
620+ continue
621+ }
622+ path = found
623+ }
624+ if _ , err := os .Stat (path ); err == nil {
625+ return path
626+ }
627+ }
628+ return ""
629+ }
630+
476631func expandHome (path string ) string {
477632 if strings .HasPrefix (path , "~" ) {
478633 home , _ := os .UserHomeDir ()
0 commit comments