home / skills / gentleman-programming / gentleman.dots / gentleman-system

gentleman-system skill

/skills/gentleman-system

This skill guides adding OS support, refining detection, and orchestrating command execution across Termux, Linux, and macOS environments.

npx playbooks add skill gentleman-programming/gentleman.dots --skill gentleman-system

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.9 KB
---
name: gentleman-system
description: >
  System detection and command execution patterns for Gentleman.Dots.
  Trigger: When editing files in installer/internal/system/, adding OS support, or modifying command execution.
license: Apache-2.0
metadata:
  author: gentleman-programming
  version: "1.0"
---

## When to Use

Use this skill when:
- Adding support for new operating systems
- Modifying OS detection logic
- Working with command execution (sudo, brew, pkg)
- Adding new system checks
- Implementing backup/restore functionality

---

## Critical Patterns

### Pattern 1: OSType Enum

All OS types are defined in `detect.go`:

```go
type OSType int

const (
    OSMac OSType = iota
    OSLinux
    OSArch
    OSDebian    // Debian-based (Debian, Ubuntu)
    OSTermux    // Termux on Android
    OSUnknown
)
```

### Pattern 2: SystemInfo Structure

Detection results in `SystemInfo` struct:

```go
type SystemInfo struct {
    OS        OSType
    OSName    string
    IsWSL     bool
    IsARM     bool
    IsTermux  bool
    HomeDir   string
    HasBrew   bool
    HasPkg    bool    // Termux package manager
    HasXcode  bool
    UserShell string
    Prefix    string  // Termux $PREFIX or empty
}
```

### Pattern 3: OS Detection Priority

Termux is checked FIRST (runs on Linux but is special):

```go
func Detect() *SystemInfo {
    info := &SystemInfo{...}

    // Check Termux FIRST
    if isTermux() {
        info.OS = OSTermux
        info.IsTermux = true
        info.HasPkg = checkPkg()
        return info
    }

    // Then check standard OS
    switch runtime.GOOS {
    case "darwin":
        info.OS = OSMac
    case "linux":
        if isArchLinux() {
            info.OS = OSArch
        } else if isDebian() {
            info.OS = OSDebian
        }
    }
    return info
}
```

### Pattern 4: Command Execution Functions

Use the right function for each context:

```go
// Basic command (no sudo, no logs)
system.Run("git clone ...", nil)

// With real-time logs
system.RunWithLogs("git clone ...", nil, func(line string) {
    SendLog(stepID, line)
})

// Homebrew commands
system.RunBrewWithLogs("install fish", nil, logFunc)

// Sudo commands (password prompt)
system.RunSudo("apt-get install -y git", nil)
system.RunSudoWithLogs("pacman -S git", nil, logFunc)

// Termux pkg commands (no sudo needed)
system.RunPkgInstall("fish git", nil, logFunc)
system.RunPkgWithLogs("update", nil, logFunc)
```

---

## Decision Tree

```
Adding new OS support?
├── Add OSType constant in detect.go
├── Add detection function (isNewOS())
├── Update Detect() with priority order
├── Update SystemInfo if new fields needed
└── Add OS case in installer.go steps

Running a command?
├── Needs sudo? → RunSudo() or RunSudoWithLogs()
├── Needs brew? → RunBrewWithLogs()
├── On Termux? → RunPkgInstall() or RunPkgWithLogs()
├── Needs logs? → RunWithLogs()
└── Simple exec? → Run()

Checking if tool exists?
├── Use CommandExists("toolname")
└── Returns bool
```

---

## Code Examples

### Example 1: Termux Detection

```go
func isTermux() bool {
    // Check TERMUX_VERSION environment variable
    if os.Getenv("TERMUX_VERSION") != "" {
        return true
    }
    // Check PREFIX contains termux path
    prefix := os.Getenv("PREFIX")
    if strings.Contains(prefix, "com.termux") {
        return true
    }
    // Check for Termux-specific paths
    if _, err := os.Stat("/data/data/com.termux"); err == nil {
        return true
    }
    return false
}
```

### Example 2: Platform-Specific Execution

```go
func installTool(m *Model) error {
    var result *system.ExecResult

    switch {
    case m.SystemInfo.IsTermux:
        // Termux: use pkg (no sudo)
        result = system.RunPkgInstall("tool", nil, logFunc)

    case m.SystemInfo.OS == system.OSArch:
        // Arch: use pacman with sudo
        result = system.RunSudoWithLogs("pacman -S --noconfirm tool", nil, logFunc)

    case m.SystemInfo.OS == system.OSMac:
        // macOS: use Homebrew
        result = system.RunBrewWithLogs("install tool", nil, logFunc)

    case m.SystemInfo.OS == system.OSDebian:
        // Debian/Ubuntu: use Homebrew (installed by us)
        result = system.RunBrewWithLogs("install tool", nil, logFunc)

    default:
        return fmt.Errorf("unsupported OS: %v", m.SystemInfo.OS)
    }

    return result.Error
}
```

### Example 3: Homebrew Prefix Detection

```go
func GetBrewPrefix() string {
    if runtime.GOOS == "darwin" {
        // Apple Silicon uses /opt/homebrew
        // Intel uses /usr/local
        if runtime.GOARCH == "arm64" {
            return "/opt/homebrew"
        }
        return "/usr/local"
    }
    return "/home/linuxbrew/.linuxbrew"
}
```

### Example 4: File Operations

```go
// Ensure directory exists
if err := system.EnsureDir(filepath.Join(homeDir, ".config/tool")); err != nil {
    return err
}

// Copy single file
if err := system.CopyFile(src, dst); err != nil {
    return err
}

// Copy directory contents
if err := system.CopyDir("Gentleman.Dots/Config/*", destDir+"/"); err != nil {
    return err
}
```

---

## ExecResult Structure

```go
type ExecResult struct {
    Output   string  // stdout
    Stderr   string  // stderr
    ExitCode int     // exit code
    Error    error   // error if any
}

// Usage
result := system.Run("command", nil)
if result.Error != nil {
    // Handle error
}
if result.ExitCode != 0 {
    // Non-zero exit
}
```

---

## Commands

```bash
cd installer && go test ./internal/system/...   # Run system tests
cd installer && go test -run TestDetect         # Test OS detection
cd installer && go test -run TestExec           # Test command execution
```

---

## Resources

- **Detection**: See `installer/internal/system/detect.go` for OS detection
- **Execution**: See `installer/internal/system/exec.go` for command running
- **Backup**: See `installer/internal/system/backup.go` for backup/restore
- **Tests**: See `installer/internal/system/*_test.go` for patterns

Overview

This skill describes system detection and command execution patterns used by Gentleman.Dots. It documents the OS type enum, SystemInfo shape, detection priority (Termux first), and the correct command execution helpers for different environments. The goal is to make adding OS support, modifying detection logic, and running platform-specific commands consistent and safe.

How this skill works

Detection builds a SystemInfo struct by checking Termux first, then runtime.GOOS and additional checks for Arch or Debian families. SystemInfo exposes flags like IsTermux, IsARM, HasBrew, and Prefix to drive behavior. Command execution helpers choose proper behavior: plain Run for simple exec, RunWithLogs for streaming output, RunSudo* for privileged ops, RunBrew* for Homebrew, and RunPkg* for Termux pkg manager.

When to use it

  • Adding support for a new OS or variant (add OSType, detection, and installer case).
  • Changing OS detection logic or adding new detection heuristics (Termux, Arch, Debian).
  • Running commands that require platform-specific handling (sudo, brew, pkg).
  • Implementing backup/restore or file operations that depend on HomeDir/Prefix.
  • Choosing the correct exec function when logs, sudo, or package managers are needed.

Best practices

  • Always check Termux first; it runs on Linux but needs special handling.
  • Populate SystemInfo completely so downstream code can branch safely (HasBrew, HasPkg, IsARM, Prefix).
  • Use RunWithLogs or Run*WithLogs when you need real-time output for progress or telemetry.
  • Use CommandExists("tool") before attempting installs to avoid unnecessary operations.
  • When adding a new OS, update Detect(), add an isNewOS() helper, and add an installer case.

Example use cases

  • Add support for a new BSD: add OSType constant, implement isBSD(), update Detect(), and add installer steps.
  • Install a tool: use RunBrewWithLogs on macOS, RunSudoWithLogs on Arch, RunPkgInstall on Termux.
  • Detect Homebrew prefix for Apple Silicon vs Intel using runtime.GOARCH to choose paths.
  • Implement backup: use EnsureDir, CopyFile, and CopyDir with homeDir from SystemInfo.
  • Write tests: run Go tests under installer/internal/system for Detect and Exec behavior.

FAQ

Why check Termux first?

Termux reports as Linux but has different package management and paths; checking it first prevents misclassification.

Which exec helper should I use for installs?

Pick based on platform: RunBrew* for Homebrew, RunSudo* for system package managers on Linux, RunPkg* for Termux; add logging variants when you need streaming logs.