rustifymyclaw 0.1.1

Run Coding CLI Agents from Messaging Apps - single Rust binary, no server required.
# RustifyMyClaw installer for Windows
# Usage:
#   irm https://raw.githubusercontent.com/Escoto/RustifyMyClaw/main/scripts/install.ps1 | iex
#   .\install.ps1 -Version v0.1.0
#   $env:VERSION = "v0.1.0"; irm ... | iex

#Requires -Version 5.1

param(
    [string]$Version = ""
)

$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'  # Dramatically speeds up Invoke-WebRequest

$Repo = "Escoto/RustifyMyClaw"
$BinaryName = "rustifymyclaw.exe"
$InstallDir = Join-Path $env:APPDATA "RustifyMyClaw"
$ConfigFile = Join-Path $InstallDir "config.yaml"
$GitHubApi = "https://api.github.com/repos/$Repo"

# ---------------------------------------------------------------------------
# Output helpers
# ---------------------------------------------------------------------------
function Write-Info  { param([string]$Msg) Write-Host "[+] $Msg" -ForegroundColor Green }
function Write-Warn  { param([string]$Msg) Write-Host "[!] $Msg" -ForegroundColor Yellow }
function Write-Err   { param([string]$Msg) Write-Host "[-] $Msg" -ForegroundColor Red }

function Stop-WithError {
    param([string]$Msg)
    Write-Err $Msg
    exit 1
}

# ---------------------------------------------------------------------------
# Platform detection
# ---------------------------------------------------------------------------
function Get-Platform {
    $arch = $env:PROCESSOR_ARCHITECTURE
    switch ($arch) {
        "AMD64" { return "x86_64" }
        default { Stop-WithError "No pre-built binary for $arch Windows. You can build from source instead: https://github.com/$Repo/blob/main/docs/building-from-source.md" }
    }
}

# ---------------------------------------------------------------------------
# Version resolution
# ---------------------------------------------------------------------------
function Get-Version {
    # Priority: -Version param > $env:VERSION > GitHub API latest
    $ver = $Version
    if ([string]::IsNullOrEmpty($ver)) {
        $ver = $env:VERSION
    }

    if (-not [string]::IsNullOrEmpty($ver)) {
        if ($ver -notmatch '^v') { $ver = "v$ver" }
        return $ver
    }

    Write-Info "Fetching latest release..."
    try {
        $release = Invoke-RestMethod -Uri "$GitHubApi/releases/latest" -Headers @{ 'User-Agent' = 'RustifyMyClaw-Installer' }
        $ver = $release.tag_name
    }
    catch {
        $statusCode = $_.Exception.Response.StatusCode.value__
        if ($statusCode -eq 403) {
            Stop-WithError "GitHub API rate limit exceeded. Specify a version: `$env:VERSION = 'v0.1.0'"
        }
        Stop-WithError "Failed to fetch latest release: $_"
    }

    if ([string]::IsNullOrEmpty($ver)) {
        Stop-WithError "Could not determine latest version from GitHub API."
    }

    return $ver
}

# ---------------------------------------------------------------------------
# Download and verify
# ---------------------------------------------------------------------------
function Install-RustifyMyClaw {
    $arch = Get-Platform
    $resolvedVersion = Get-Version

    $artifact = "rustifymyclaw-${resolvedVersion}+${arch}-windows.zip"
    $baseUrl = "https://github.com/$Repo/releases/download/$resolvedVersion"
    $downloadUrl = "$baseUrl/$artifact"
    $checksumUrl = "$downloadUrl.sha256"

    $tmpDir = Join-Path ([System.IO.Path]::GetTempPath()) "rustifymyclaw-install-$([System.Guid]::NewGuid().ToString('N').Substring(0,8))"

    try {
        New-Item -ItemType Directory -Force -Path $tmpDir | Out-Null
        $artifactPath = Join-Path $tmpDir $artifact
        $checksumPath = Join-Path $tmpDir "$artifact.sha256"

        # Download artifact
        Write-Info "Downloading $artifact..."
        try {
            Invoke-WebRequest -Uri $downloadUrl -OutFile $artifactPath -UseBasicParsing
        }
        catch {
            Stop-WithError "Download failed. Check that version $resolvedVersion exists at https://github.com/$Repo/releases"
        }

        # Download checksum
        Write-Info "Downloading checksum..."
        try {
            Invoke-WebRequest -Uri $checksumUrl -OutFile $checksumPath -UseBasicParsing
        }
        catch {
            Stop-WithError "Checksum download failed."
        }

        # Verify checksum
        Write-Info "Verifying SHA256 checksum..."
        $expectedHash = (Get-Content $checksumPath -Raw).Trim().Split(' ')[0].ToLower()
        $actualHash = (Get-FileHash -Path $artifactPath -Algorithm SHA256).Hash.ToLower()
        if ($expectedHash -ne $actualHash) {
            Stop-WithError "Checksum mismatch! Expected: $expectedHash, Got: $actualHash"
        }
        Write-Info "Checksum verified."

        # Create install directory
        New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null

        # Extract — the zip may contain nested paths from the release workflow,
        # so extract to temp and find the exe.
        $extractDir = Join-Path $tmpDir "extract"
        Expand-Archive -Path $artifactPath -DestinationPath $extractDir -Force

        $exeFile = Get-ChildItem -Path $extractDir -Recurse -Filter $BinaryName | Select-Object -First 1
        if (-not $exeFile) {
            Stop-WithError "Could not find $BinaryName in downloaded archive."
        }

        Copy-Item -Path $exeFile.FullName -Destination (Join-Path $InstallDir $BinaryName) -Force
        Write-Info "Binary installed at $(Join-Path $InstallDir $BinaryName)"

        # Config scaffold
        if (Test-Path $ConfigFile) {
            Write-Info "Existing config preserved at $ConfigFile"
        }
        else {
            $configUrl = "https://raw.githubusercontent.com/$Repo/main/examples/config.yaml"
            Write-Info "Downloading example config..."
            try {
                Invoke-WebRequest -Uri $configUrl -OutFile $ConfigFile -UseBasicParsing
            }
            catch {
                Stop-WithError "Failed to download example config from $configUrl"
            }
            Write-Info "Starter config created at $ConfigFile"
        }

        # PATH modification (user-level, no admin required)
        $currentUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
        if ([string]::IsNullOrEmpty($currentUserPath)) {
            [Environment]::SetEnvironmentVariable('Path', $InstallDir, 'User')
            Write-Info "PATH set to $InstallDir"
        }
        elseif ($currentUserPath -notlike "*$InstallDir*") {
            [Environment]::SetEnvironmentVariable('Path', "$InstallDir;$currentUserPath", 'User')
            Write-Info "PATH updated — added $InstallDir"
        }
        else {
            Write-Info "PATH already configured."
        }

        # Update current session
        if ($env:Path -notlike "*$InstallDir*") {
            $env:Path = "$InstallDir;$env:Path"
        }

        # Warn about shadowing
        $existing = Get-Command $BinaryName -ErrorAction SilentlyContinue | Select-Object -First 1
        if ($existing -and $existing.Source -ne (Join-Path $InstallDir $BinaryName)) {
            Write-Warn "Another $BinaryName found at $($existing.Source) — it may shadow this install."
        }

        # Success
        Write-Host ""
        Write-Info "RustifyMyClaw $resolvedVersion installed successfully!"
        Write-Host ""
        Write-Host "  Binary:  $(Join-Path $InstallDir $BinaryName)"
        Write-Host "  Config:  $ConfigFile"
        Write-Host ""
        Write-Host "  Next steps:"
        Write-Host "    1. Edit $ConfigFile"
        Write-Host "       - Set your workspace directory"
        Write-Host "       - Configure your channel (Telegram / WhatsApp / Slack)"
        Write-Host "       - Set allowed_users"
        Write-Host "    2. Set required environment variables:"
        Write-Host '       $env:TELEGRAM_BOT_TOKEN = "your_token_here"'
        Write-Host "    3. Start the daemon:"
        Write-Host "       rustifymyclaw"
        Write-Host "    4. Restart your terminal for PATH changes to take effect"
        Write-Host ""
        Write-Host "  Full config reference:"
        Write-Host "  https://github.com/$Repo/blob/main/docs/configuration.md"
        Write-Host ""
    }
    finally {
        if (Test-Path $tmpDir) {
            Remove-Item -Recurse -Force $tmpDir -ErrorAction SilentlyContinue
        }
    }
}

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
Write-Host ""
Write-Host "  RustifyMyClaw Installer" -ForegroundColor Cyan
Write-Host ""

Install-RustifyMyClaw