Skip to content

feat: detect Scoop install on Windows for auto-upgrade #541

Description

@samuellambeam-hue

Problem

When gentle-ai is installed via Scoop on Windows, the TUI upgrade flow always shows:

gentle-ai  (skipped)
     upgrade "gentle-ai" on Windows requires manual update: irm https://raw.githubusercontent.com/Gentleman-Programming/...

This forces users to manually run scoop update gentle-ai instead of pressing Enter in the TUI.

Root Cause

Two files need changes:

1. internal/update/instructions.go

The gentleAIHint function always returns irm ... for Windows without detecting Scoop:

func gentleAIHint(profile system.PlatformProfile) string {
    switch profile.OS {
    case "windows":
        return "irm https://...install.ps1 | iex"  // Always irm, no Scoop detection
    }
}

Fix: Add Scoop detection:

func gentleAIHint(profile system.PlatformProfile) string {
    switch profile.OS {
    case "windows":
        if isScoopInstall() {
            return "scoop update gentle-ai"
        }
        return "irm https://...install.ps1 | iex"
    }
}

func isScoopInstall() bool {
    exe, err := os.Executable()
    if err != nil {
        return false
    }
    resolved, err := filepath.EvalSymlinks(exe)
    if err != nil {
        return false
    }
    return strings.Contains(strings.ToLower(resolved), filepath.Join("scoop", "apps", "gentle-ai"))
}

2. internal/update/upgrade/strategy.go

The binaryUpgrade function returns ManualFallbackError on Windows for all binary tools:

func binaryUpgrade(ctx context.Context, r update.UpdateResult, profile system.PlatformProfile) error {
    if profile.OS == "windows" {
        return &ManualFallbackError{
            Hint: fmt.Sprintf("upgrade %q on Windows requires manual update: %s", r.Tool.Name, hint),
        }
    }
}

Fix: Add Scoop upgrade strategy before the manual fallback:

func binaryUpgrade(ctx context.Context, r update.UpdateResult, profile system.PlatformProfile) error {
    if r.Tool.Name == "engram" {
        return engramBinaryUpgrade(profile)
    }
    if profile.OS == "windows" {
        if isScoopInstall() {
            return scoopUpgrade(ctx, r.Tool.Name)
        }
        return &ManualFallbackError{...}
    }
    return downloadAndReplace(ctx, r, profile)
}

func scoopUpgrade(ctx context.Context, toolName string) error {
    cmd := execCommand("scoop", "update", toolName)
    cmd.Stdin = nil
    if out, err := cmd.CombinedOutput(); err != nil {
        return fmt.Errorf("scoop update %s: %w (output: %s)", toolName, err, string(out))
    }
    return nil
}

3. internal/update/types.go (optional)

Add InstallScoop constant for explicit method tracking:

const (
    InstallBrew      InstallMethod = "brew"
    InstallScoop     InstallMethod = "scoop"  // NEW
    InstallBinary    InstallMethod = "binary"
    // ...
)

Expected Behavior

When installed via Scoop on Windows:

  • TUI shows scoop update gentle-ai as the update command
  • Pressing Enter auto-upgrades via scoop update gentle-ai (no manual intervention)
  • Falls back to irm only when NOT installed via Scoop

Detection Logic

Scoop installs to ~/scoop/apps/gentle-ai/current/ with symlinks. Detect by checking if the resolved executable path contains scoop/apps/gentle-ai.

Environment

  • OS: Windows
  • Install method: Scoop (scoop bucket add gentleman ... && scoop install gentle-ai)
  • Version: 1.28.3 -> 1.29.1

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions