Agent Skills Framework Extension
Native Installer Patterns Skill
When to Use This Skill
Use this skill when implementing native installer patterns patterns in your codebase.
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
Universal install scripts for macOS, Linux, and Windows with platform detection, PATH setup, checksum verification, and self-update capabilities.
Core Capabilities
- Universal Installer - Single script for all platforms
- Platform Detection - Automatic OS/arch identification
- PATH Management - Shell profile configuration
- Checksum Verification - SHA256 validation
- Self-Update - Built-in update mechanism
Universal Install Script (Bash)
#!/usr/bin/env bash
# install.sh - Universal installer for mytool
# Usage: curl -fsSL https://example.com/install.sh | bash
# Or: wget -qO- https://example.com/install.sh | bash
set -euo pipefail
# Configuration
REPO="org/mytool"
INSTALL_DIR="${MYTOOL_INSTALL_DIR:-$HOME/.mytool}"
BIN_DIR="$INSTALL_DIR/bin"
VERSION="${MYTOOL_VERSION:-latest}"
GITHUB_API="https://api.github.com"
GITHUB_DOWNLOAD="https://github.com/${REPO}/releases/download"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
log_step() { echo -e "${BLUE}==>${NC} $1"; }
# Detect platform
detect_platform() {
local os arch
case "$(uname -s)" in
Linux*)
os="linux"
;;
Darwin*)
os="darwin"
;;
MINGW*|MSYS*|CYGWIN*)
os="windows"
;;
*)
log_error "Unsupported OS: $(uname -s)"
exit 1
;;
esac
case "$(uname -m)" in
x86_64|amd64)
arch="x64"
;;
arm64|aarch64)
arch="arm64"
;;
i386|i686)
arch="x86"
;;
*)
log_error "Unsupported architecture: $(uname -m)"
exit 1
;;
esac
echo "${os}-${arch}"
}
# Get latest version from GitHub
get_latest_version() {
local url="${GITHUB_API}/repos/${REPO}/releases/latest"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/'
elif command -v wget >/dev/null 2>&1; then
wget -qO- "$url" | grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/'
else
log_error "Neither curl nor wget found. Please install one of them."
exit 1
fi
}
# Download file with progress
download_file() {
local url="$1"
local output="$2"
log_step "Downloading: $(basename "$output")"
if command -v curl >/dev/null 2>&1; then
curl -fsSL --progress-bar -o "$output" "$url"
elif command -v wget >/dev/null 2>&1; then
wget -q --show-progress -O "$output" "$url"
else
log_error "Neither curl nor wget found"
exit 1
fi
}
# Verify checksum
verify_checksum() {
local file="$1"
local checksum_file="$2"
log_step "Verifying checksum..."
if command -v sha256sum >/dev/null 2>&1; then
(cd "$(dirname "$file")" && sha256sum -c "$(basename "$checksum_file")")
elif command -v shasum >/dev/null 2>&1; then
(cd "$(dirname "$file")" && shasum -a 256 -c "$(basename "$checksum_file")")
else
log_warn "No SHA256 tool found, skipping verification"
return 0
fi
}
# Extract archive
extract_archive() {
local archive="$1"
local dest="$2"
log_step "Extracting archive..."
case "$archive" in
*.tar.gz|*.tgz)
tar -xzf "$archive" -C "$dest"
;;
*.zip)
unzip -q "$archive" -d "$dest"
;;
*)
log_error "Unsupported archive format: $archive"
exit 1
;;
esac
}
# Update PATH in shell profile
update_path() {
local shell_profile=""
case "$SHELL" in
*/bash)
shell_profile="$HOME/.bashrc"
[[ -f "$HOME/.bash_profile" ]] && shell_profile="$HOME/.bash_profile"
;;
*/zsh)
shell_profile="$HOME/.zshrc"
;;
*/fish)
shell_profile="$HOME/.config/fish/config.fish"
;;
*)
log_warn "Unknown shell: $SHELL. Please manually add $BIN_DIR to your PATH"
return
;;
esac
# Check if already in PATH
if grep -q "$BIN_DIR" "$shell_profile" 2>/dev/null; then
log_info "PATH already configured in $shell_profile"
return
fi
log_step "Adding to PATH in $shell_profile"
# Create profile if doesn't exist
touch "$shell_profile"
# Add to PATH
if [[ "$SHELL" == */fish ]]; then
echo "" >> "$shell_profile"
echo "# mytool" >> "$shell_profile"
echo "set -gx PATH \$PATH $BIN_DIR" >> "$shell_profile"
else
echo "" >> "$shell_profile"
echo "# mytool" >> "$shell_profile"
echo "export PATH=\"\$PATH:$BIN_DIR\"" >> "$shell_profile"
fi
log_info "Added $BIN_DIR to PATH"
log_warn "Restart your shell or run: source $shell_profile"
}
# Main installation
install_mytool() {
log_info "Installing mytool..."
# Detect platform
local platform
platform=$(detect_platform)
log_info "Detected platform: $platform"
# Get version
if [[ "$VERSION" == "latest" ]]; then
VERSION=$(get_latest_version)
fi
log_info "Installing version: $VERSION"
# Determine archive format
local ext="tar.gz"
[[ "$platform" == windows-* ]] && ext="zip"
# Download URLs
local filename="mytool-${platform}.${ext}"
local download_url="${GITHUB_DOWNLOAD}/v${VERSION}/${filename}"
local checksum_url="${download_url}.sha256"
# Create temp directory
local tmpdir
tmpdir=$(mktemp -d)
trap 'rm -rf "$tmpdir"' EXIT
# Download binary and checksum
download_file "$download_url" "${tmpdir}/${filename}"
download_file "$checksum_url" "${tmpdir}/${filename}.sha256"
# Verify checksum
verify_checksum "${tmpdir}/${filename}" "${tmpdir}/${filename}.sha256"
# Create install directory
mkdir -p "$BIN_DIR"
# Extract archive
extract_archive "${tmpdir}/${filename}" "$tmpdir"
# Find binary in extracted files
local binary_name="mytool"
[[ "$platform" == windows-* ]] && binary_name="mytool.exe"
local binary_path
binary_path=$(find "$tmpdir" -name "$binary_name" -type f | head -1)
if [[ -z "$binary_path" ]]; then
log_error "Binary not found in archive"
exit 1
fi
# Install binary
cp "$binary_path" "${BIN_DIR}/${binary_name}"
chmod +x "${BIN_DIR}/${binary_name}"
log_info "Installed to: ${BIN_DIR}/${binary_name}"
# Update PATH
update_path
# Verify installation
if command -v mytool >/dev/null 2>&1 || [[ -x "${BIN_DIR}/${binary_name}" ]]; then
log_info "Installation successful!"
log_info "Run 'mytool --version' to verify"
else
log_warn "Installation complete but mytool not in PATH yet"
log_warn "Restart your shell or add manually: export PATH=\"\$PATH:$BIN_DIR\""
fi
}
# Uninstall
uninstall_mytool() {
log_info "Uninstalling mytool..."
if [[ -d "$INSTALL_DIR" ]]; then
rm -rf "$INSTALL_DIR"
log_info "Removed: $INSTALL_DIR"
fi
log_warn "Please manually remove PATH entries from your shell profile"
log_info "Uninstall complete"
}
# Update
update_mytool() {
log_info "Updating mytool..."
VERSION="latest"
install_mytool
}
# CLI
main() {
case "${1:-install}" in
install)
install_mytool
;;
uninstall)
uninstall_mytool
;;
update)
update_mytool
;;
--version)
echo "mytool installer v1.0.0"
;;
--help)
cat <<EOF
mytool Installer
Usage:
curl -fsSL https://example.com/install.sh | bash
curl -fsSL https://example.com/install.sh | bash -s -- [command]
Commands:
install Install mytool (default)
uninstall Uninstall mytool
update Update to latest version
--version Show installer version
--help Show this help
Environment variables:
MYTOOL_VERSION Version to install (default: latest)
MYTOOL_INSTALL_DIR Installation directory (default: ~/.mytool)
Examples:
# Install latest version
curl -fsSL https://example.com/install.sh | bash
# Install specific version
curl -fsSL https://example.com/install.sh | MYTOOL_VERSION=1.2.3 bash
# Uninstall
curl -fsSL https://example.com/install.sh | bash -s -- uninstall
EOF
;;
*)
log_error "Unknown command: $1"
log_error "Run with --help for usage"
exit 1
;;
esac
}
main "$@"
Windows PowerShell Installer
# install.ps1 - Windows installer for mytool
# Usage: iwr -useb https://example.com/install.ps1 | iex
param(
[string]$Version = "latest",
[string]$InstallDir = "$env:USERPROFILE\.mytool"
)
$ErrorActionPreference = "Stop"
# Configuration
$Repo = "org/mytool"
$GithubAPI = "https://api.github.com"
$GithubDownload = "https://github.com/$Repo/releases/download"
$BinDir = Join-Path $InstallDir "bin"
# Logging functions
function Write-ColorOutput {
param([string]$Message, [string]$Color = "White")
Write-Host $Message -ForegroundColor $Color
}
function Log-Info { Write-ColorOutput "[INFO] $args" -Color Green }
function Log-Warn { Write-ColorOutput "[WARN] $args" -Color Yellow }
function Log-Error { Write-ColorOutput "[ERROR] $args" -Color Red }
function Log-Step { Write-ColorOutput "==> $args" -Color Blue }
# Get latest version
function Get-LatestVersion {
$url = "$GithubAPI/repos/$Repo/releases/latest"
try {
$response = Invoke-RestMethod -Uri $url
return $response.tag_name.TrimStart('v')
} catch {
Log-Error "Failed to fetch latest version: $_"
exit 1
}
}
# Download file
function Download-File {
param([string]$Url, [string]$Output)
Log-Step "Downloading: $(Split-Path $Output -Leaf)"
try {
# Use BITS transfer for progress indication
Start-BitsTransfer -Source $Url -Destination $Output
} catch {
# Fallback to Invoke-WebRequest
Invoke-WebRequest -Uri $Url -OutFile $Output
}
}
# Verify checksum
function Verify-Checksum {
param([string]$File, [string]$ChecksumFile)
Log-Step "Verifying checksum..."
$expectedHash = (Get-Content $ChecksumFile).Split()[0]
$actualHash = (Get-FileHash $File -Algorithm SHA256).Hash.ToLower()
if ($expectedHash -ne $actualHash) {
Log-Error "Checksum mismatch!"
Log-Error "Expected: $expectedHash"
Log-Error "Actual: $actualHash"
exit 1
}
Log-Info "Checksum verified"
}
# Update PATH
function Update-Path {
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
if ($currentPath -like "*$BinDir*") {
Log-Info "PATH already configured"
return
}
Log-Step "Adding to PATH..."
$newPath = $currentPath + ";$BinDir"
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
# Update current session PATH
$env:Path += ";$BinDir"
Log-Info "Added $BinDir to PATH"
Log-Warn "Restart your terminal for PATH changes to take effect"
}
# Main installation
function Install-MyTool {
Log-Info "Installing mytool for Windows..."
# Get version
if ($Version -eq "latest") {
$Version = Get-LatestVersion
}
Log-Info "Installing version: $Version"
# Download URLs
$filename = "mytool-windows-x64.zip"
$downloadUrl = "$GithubDownload/v$Version/$filename"
$checksumUrl = "$downloadUrl.sha256"
# Create temp directory
$tempDir = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ([System.Guid]::NewGuid()))
try {
# Download binary and checksum
$archivePath = Join-Path $tempDir $filename
$checksumPath = Join-Path $tempDir "$filename.sha256"
Download-File -Url $downloadUrl -Output $archivePath
Download-File -Url $checksumUrl -Output $checksumPath
# Verify checksum
Verify-Checksum -File $archivePath -ChecksumFile $checksumPath
# Extract archive
Log-Step "Extracting archive..."
Expand-Archive -Path $archivePath -DestinationPath $tempDir -Force
# Create install directory
New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
# Find and copy binary
$binaryPath = Get-ChildItem -Path $tempDir -Filter "mytool.exe" -Recurse | Select-Object -First 1
if (-not $binaryPath) {
Log-Error "Binary not found in archive"
exit 1
}
Copy-Item -Path $binaryPath.FullName -Destination (Join-Path $BinDir "mytool.exe") -Force
Log-Info "Installed to: $(Join-Path $BinDir 'mytool.exe')"
# Update PATH
Update-Path
# Verify installation
try {
& (Join-Path $BinDir "mytool.exe") --version | Out-Null
Log-Info "Installation successful!"
Log-Info "Run 'mytool --version' to verify"
} catch {
Log-Warn "Installation complete but mytool not in PATH yet"
Log-Warn "Restart your terminal or add manually to PATH: $BinDir"
}
} finally {
# Cleanup
Remove-Item -Path $tempDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
# Uninstall
function Uninstall-MyTool {
Log-Info "Uninstalling mytool..."
if (Test-Path $InstallDir) {
Remove-Item -Path $InstallDir -Recurse -Force
Log-Info "Removed: $InstallDir"
}
# Remove from PATH
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
$newPath = ($currentPath.Split(';') | Where-Object { $_ -ne $BinDir }) -join ';'
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
Log-Info "Uninstall complete"
}
# Run installation
Install-MyTool
Self-Update Mechanism
#!/usr/bin/env bash
# Built into mytool binary - self-update command
mytool_self_update() {
local current_version
current_version=$(mytool --version | awk '{print $NF}')
local latest_version
latest_version=$(curl -fsSL "https://api.github.com/repos/org/mytool/releases/latest" | \
grep '"tag_name"' | sed -E 's/.*"v?([^"]+)".*/\1/')
if [[ "$current_version" == "$latest_version" ]]; then
echo "✅ mytool is already up to date ($current_version)"
return 0
fi
echo "📦 Updating mytool: $current_version → $latest_version"
# Re-run installer with latest version
export MYTOOL_VERSION="$latest_version"
curl -fsSL https://example.com/install.sh | bash
echo "✅ Updated to version $latest_version"
}
Usage Examples
Create Universal Installer
Apply native-installer-patterns skill to create bash installer with platform detection and PATH configuration
Windows PowerShell Installer
Apply native-installer-patterns skill to generate PowerShell installer for Windows with PATH updates
Self-Update Command
Apply native-installer-patterns skill to implement self-update mechanism with version checking
Integration Points
- binary-distribution-patterns - Download and extract binaries
- npm-packaging-patterns - npm postinstall integration
- security-audit-patterns - Checksum verification and validation
Success Output
When successful, this skill MUST output:
✅ SKILL COMPLETE: native-installer-patterns
Completed:
- [x] Universal installer script created (Bash for macOS/Linux)
- [x] Windows PowerShell installer implemented
- [x] Platform detection and PATH management configured
- [x] Checksum verification integrated (SHA256)
- [x] Self-update mechanism implemented
Outputs:
- install.sh (universal Bash installer)
- install.ps1 (Windows PowerShell installer)
- Platform detection functions tested on macOS/Linux/Windows
- PATH update verified in shell profiles (bash/zsh/fish)
- Installation tested with version verification
Completion Checklist
Before marking this skill as complete, verify:
- Universal installer script (install.sh) created and executable
- Windows installer script (install.ps1) created
- Platform detection working for macOS/Linux/Windows
- Architecture detection working (x64/arm64/x86)
- Download mechanisms tested (curl and wget fallback)
- SHA256 checksum verification implemented
- Archive extraction working (tar.gz, zip)
- PATH update working for bash/zsh/fish shells
- Self-update command functional
- Installation verified with --version command
- Uninstall mechanism tested
Failure Indicators
This skill has FAILED if:
- ❌ Installer script fails on target platform (macOS, Linux, or Windows)
- ❌ Platform/architecture detection returns "unsupported" for common platforms
- ❌ Download fails without trying fallback method (curl → wget)
- ❌ Checksum verification skipped or always passes
- ❌ Binary not found in archive after extraction
- ❌ PATH not updated in shell profile
- ❌ Installation succeeds but binary not executable
- ❌ Self-update mechanism fails to detect new versions
When NOT to Use
Do NOT use this skill when:
- Application is distributed via package managers only (apt, brew, npm, pip)
- Binary releases are not published (GitHub releases, CDN)
- Application requires complex installation (database setup, config generation)
- Installation is containerized only (Docker, Kubernetes)
- Cross-platform support not needed (single platform binary)
- User base is technical and prefers manual installation
- Platform-specific installers already exist (.deb, .rpm, .pkg, .msi)
- Application is web-based only (no local installation)
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Skipping checksum verification | Security vulnerability | Always verify SHA256 before execution |
| Platform-specific scripts only | Users on other platforms can't install | Create universal installer with detection |
| Hardcoded version | Can't update easily | Fetch latest version from API |
| No PATH update | Users must configure manually | Auto-update shell profiles |
| Missing error messages | Silent failures confuse users | Log clear errors with troubleshooting steps |
| No cleanup on failure | Leaves temp files | Use trap for cleanup in Bash |
| Overwriting without backup | Users lose custom configs | Backup existing installations |
| No uninstall mechanism | Users stuck with tool | Provide uninstall command |
Principles
This skill embodies:
- #1 First Principles - Understand platform differences before creating installer
- #2 Self-Provisioning - Installer handles all dependencies and configuration
- #5 Eliminate Ambiguity - Clear error messages and installation status
- #6 Clear, Understandable, Explainable - Installation steps logged to user
- #8 No Assumptions - Detect platform/shell, don't assume environment
- #9 Keep It Simple - Single script handles all platforms when possible
Full Standard: CODITECT-STANDARD-AUTOMATION.md