Development

gentleman-installer - Claude MCP Skill

Installation step patterns for Gentleman.Dots TUI installer. Trigger: When editing installer.go, adding installation steps, or modifying the installation flow.

SEO Guide: Enhance your AI agent with the gentleman-installer tool. This Model Context Protocol (MCP) server allows Claude Desktop and other LLMs to installation step patterns for gentleman.dots tui installer. trigger: when editing installer.go, add... Download and configure this skill to unlock new capabilities for your AI workflow.

🌟54 stars • 285 forks
šŸ“„0 downloads

Documentation

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`:

```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`:

```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`:

```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`:

```go
// 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

```go
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",
            err)
    }

    SendLog(stepID, "āœ“ Tool configured")
    return nil
}

func logFunc(stepID string) func(string) {
    return func(line string) {
        SendLog(stepID, line)
    }
}
```

### Example 2: Error Wrapping Pattern

```go
func wrapStepError(stepID, stepName, description string, cause error) error {
    return &StepError{
        StepID:      stepID,
        StepName:    stepName,
        Description: description,
        Cause:       cause,
    }
}

// Usage:
if result.Error != nil {
    return wrapStepError("terminal", "Install Alacritty",
        "Failed to install Alacritty. Check your internet connection.",
        result.Error)
}
```

### Example 3: Config Patching

```go
// Patch config based on user choices
func stepInstallShell(m *Model) error {
    // ... install shell ...

    // Patch config for window manager choice
    configPath := filepath.Join(homeDir, ".config/fish/config.fish")
    if err := system.PatchFishForWM(configPath, m.Choices.WindowMgr, m.Choices.InstallNvim); err != nil {
        return wrapStepError("shell", "Install Fish",
            "Failed to configure window manager in shell",
            err)
    }

    return nil
}
```

---

## Logging Pattern

Always use `SendLog` for step progress:

```go
SendLog(stepID, "Starting...")           // Start
SendLog(stepID, "Downloading...")        // Progress
SendLog(stepID, "  → file.txt")          // Sub-item
SendLog(stepID, "āœ“ Step completed")      // Success
```

---

## Commands

```bash
cd installer && go build ./cmd/gentleman-installer           # Build
./gentleman-installer --help                                  # Show help
./gentleman-installer --non-interactive --shell=fish         # Non-interactive
GENTLEMAN_VERBOSE=1 ./gentleman-installer --non-interactive  # Verbose logs
```

---

## Resources

- **Steps**: See `installer/internal/tui/installer.go` for step implementations
- **Model**: See `installer/internal/tui/model.go` for SetupInstallSteps
- **System**: See `installer/internal/system/exec.go` for command execution
- **Non-interactive**: See `installer/internal/tui/non_interactive.go` for CLI mode

Signals

Avg rating⭐ 0.0
Reviews0
Favorites0

Information

Repository
Gentleman-Programming/Gentleman.Dots
Author
Gentleman-Programming
Last Sync
9/5/2026
Repo Updated
9/4/2026
Created
1/13/2026

Reviews (0)

No reviews yet. Be the first to review this skill!

Related Skills

firecrawl-build-search

Integrate Firecrawl `/search` into product code and agent workflows. Use when an app needs discovery before extraction, when the feature starts with a query instead of a URL, or when the system should search the web and optionally hydrate result content.

⭐ 35092

firecrawl-build-onboarding

Get Firecrawl credentials and SDK setup into a project. Use when an application needs `FIRECRAWL_API_KEY`, when an agent should add Firecrawl to `.env`, when the user wants to authenticate Firecrawl for app code, or when choosing the first SDK and docs for a new Firecrawl integration. This skill includes its own browser auth flow, so it does not depend on the website onboarding skill.

⭐ 35092

firecrawl-build

Integrate Firecrawl into application code whenever a product, agent, or workflow needs web data inside the app — web search, live search results, page scraping, structured extraction, or browser interaction. Use when building any feature that needs data from the web in code, even if the user does not mention Firecrawl explicitly and only describes wanting web data, website content, search, scraping, or interaction in an application. Trigger for Firecrawl requests, "fire girl" shorthand, and generic app-level web-data needs that should map to `/scrape`, `/search`, or `/interact`. Do not use this skill for one-off terminal-only web tasks during the current session; use `firecrawl/cli` for those.

⭐ 35092

firecrawl-build-interact

Integrate Firecrawl `/interact` into product code for dynamic pages and browser actions after scraping. Use when a feature needs clicks, form fills, pagination, authentication-aware flows, or other multi-step interactions that plain `/scrape` cannot complete.

⭐ 35092

Related Guides