package cli import ( "encoding/json" "fmt" "net/http" "os" "os/exec" "path/filepath" "strings" "time" ) // GitHubRelease is the subset of the GitHub releases API response we need. type GitHubRelease struct { TagName string `json:"tag_name"` HTMLURL string `json:"html_url"` } // FetchLatestRelease fetches the latest release tag from the multica GitHub repo. func FetchLatestRelease() (*GitHubRelease, error) { client := &http.Client{Timeout: 10 * time.Second} req, err := http.NewRequest(http.MethodGet, "https://api.github.com/repos/multica-ai/multica/releases/latest", nil) if err != nil { return nil, err } req.Header.Set("Accept", "application/vnd.github+json") resp, err := client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("GitHub API returned %d", resp.StatusCode) } var release GitHubRelease if err := json.NewDecoder(resp.Body).Decode(&release); err != nil { return nil, err } return &release, nil } // IsBrewInstall checks whether the running multica binary was installed via Homebrew. func IsBrewInstall() bool { exePath, err := os.Executable() if err != nil { return false } resolved, err := filepath.EvalSymlinks(exePath) if err != nil { resolved = exePath } brewPrefix := GetBrewPrefix() if brewPrefix != "" && strings.HasPrefix(resolved, brewPrefix) { return true } for _, prefix := range []string{"/opt/homebrew", "/usr/local", "/home/linuxbrew/.linuxbrew"} { if strings.HasPrefix(resolved, prefix+"/Cellar/") { return true } } return false } // GetBrewPrefix returns the Homebrew prefix by running `brew --prefix`, or empty string. func GetBrewPrefix() string { out, err := exec.Command("brew", "--prefix").Output() if err != nil { return "" } return strings.TrimSpace(string(out)) } // UpdateViaBrew runs `brew upgrade multica-ai/tap/multica`. // Returns the combined output and any error. func UpdateViaBrew() (string, error) { cmd := exec.Command("brew", "upgrade", "multica-ai/tap/multica") out, err := cmd.CombinedOutput() if err != nil { return string(out), fmt.Errorf("brew upgrade failed: %w", err) } return string(out), nil } // DetectNewBinaryPath returns the path to the multica binary after an update. // It uses exec.LookPath to find the binary in PATH, which will resolve to the // updated version after a brew upgrade. func DetectNewBinaryPath() (string, error) { return exec.LookPath("multica") }