gentleman-installer

from gentleman-programming/gentleman.dots

My personal configuration for LazyVim !

1.3K stars197 forksUpdated Jan 24, 2026
npx skills add https://github.com/gentleman-programming/gentleman.dots --skill gentleman-installer

SKILL.md

When to Use

Use this skill when:

  • Adding new installation steps
  • Modifying existing tool installations
  • Working on backup/restore functionality
  • Implementing non-interactive mode support
  • Adding new OS/platform support

Critical Patterns

Pattern 1: InstallStep Structure

All steps follow this structure in model.go:

type InstallStep struct {
    ID          string      // Unique identifier: "terminal", "shell", etc.
    Name        string      // Display name: "Install Fish"
    Description string      // Short description
    Status      StepStatus  // Pending, Running, Done, Failed, Skipped
    Progress    float64     // 0.0 - 1.0
    Error       error       // Error if failed
    Interactive bool        // Needs terminal control (sudo, chsh)
}

Pattern 2: Step Registration in SetupInstallSteps

Steps MUST be registered in SetupInstallSteps() in model.go:

func (m *Model) SetupInstallSteps() {
    m.Steps = []InstallStep{}

    // Conditional step based on user choice
    if m.Choices.SomeChoice {
        m.Steps = append(m.Steps, InstallStep{
            ID:          "newstep",
            Name:        "Install Something",
            Description: "Description here",
            Status:      StatusPending,
            Interactive: false, // true if needs sudo/password
        })
    }
}

Pattern 3: Step Execution in executeStep

All step logic goes in installer.go:

func executeStep(stepID string, m *Model) error {
    switch stepID {
    case "newstep":
        return stepNewStep(m)
    // ... other cases
    default:
        return fmt.Errorf("unknown step: %s", stepID)
    }
}

func stepNewStep(m *Model) error {
    stepID := "newstep"

    SendLog(stepID, "Starting installation...")

    // Check if already installed
    if system.CommandExists("newtool") {
        SendLog(stepID, "Already installed, skipping...")
        return nil
    }

    // Install based on OS
    var result *system.ExecResult
    if m.SystemInfo.IsTermux {
        result = system.RunPkgInstall("newtool", nil, func(line string) {
            SendLog(stepID, line)
        })
    } else {
        result = system.RunBrewWithLogs("install newtool", nil, func(line string) {
            SendLog(stepID, line)
        })
    }

    if result.Error != nil {
        return wrapStepError("newstep", "Install NewTool",
            "Failed to install NewTool",
            result.Error)
    }

    SendLog(stepID, "✓ NewTool installed")
    return nil
}

Pattern 4: Interactive Steps (sudo/password required)

Mark step as Interactive and use runInteractiveStep:

// In SetupInstallSteps:
m.Steps = append(m.Steps, InstallStep{
    ID:          "interactive_step",
    Name:        "Configure System",
    Description: "Requires password",
    Status:      StatusPending,
    Interactive: true,  // KEY: marks as interactive
})

// In runNextStep (update.go):
if step.Interactive {
    return runInteractiveStep(step.ID, &m)
}

Decision Tree

Adding new tool installation?
├── Add step to SetupInstallSteps() with conditions
├── Add case in executeStep() switch
├── Create step{Name}() function in installer.go
├── Handle all OS variants (Mac, Linux, Arch, Debian, Termux)
├── Use SendLog() for progress updates
└── Return wrapStepError() on failure

Step needs password/sudo?
├── Set Interactive: true in InstallStep
├── Use system.RunSudo() or system.RunSudoWithLogs()
└── Use tea.ExecProcess for full terminal control

Step should be conditional?
├── Check m.Choices.{option} before appending
├── Check m.SystemInfo for OS-specific logic
└── Use StatusSkipped if conditions not met

Code Examples

Example 1: OS-Specific Installation

func stepInstallTool(m *Model) error {
    stepID := "tool"

    if !system.CommandExists("tool") {
        SendLog(stepID, "Installing tool...")

        var result *system.ExecResult
        switch {
        case m.SystemInfo.IsTermux:
            result = system.RunPkgInstall("tool", nil, logFunc(stepID))
        case m.SystemInfo.OS == system.OSArch:
            result = system.RunSudoWithLogs("pacman -S --noconfirm tool", nil, logFunc(stepID))
        case m.SystemInfo.OS == system.OSMac:
            result = system.RunBrewWithLogs("install tool", nil, logFunc(stepID))
        default: // Debian/Ubuntu
            result = system.RunBrewWithLogs("install tool", nil, logFunc(stepID))
        }

        if result.Error != nil {
            return wrapStepError("tool", "Install Tool",
                "Failed to install tool",
                result.Error)
        }
    }

    // Copy configuration
    SendLog(stepID, "Copying configuration...")
    homeDir := os.Getenv("HOME")
    if err := system.CopyDir(filepath.Join("Gentleman.Dots", "ToolConfig/*"),
        filepath.Join(homeDir, ".config/tool/")); err != nil {
        return wrapStepError("tool", "Install Tool",
            "Failed to copy configuration",
     

...
Read full content

Repository Stats

Stars1.3K
Forks197
LicenseApache License 2.0