llm-verify 0.5.1

Black-box authenticity, billing and performance verification for LLM API endpoints
Documentation
# SPDX-License-Identifier: Apache-2.0
<#
.SYNOPSIS
    llm-verify installer for Windows.

.DESCRIPTION
    Downloads the release archive matching this machine's architecture,
    verifies its SHA-256 against the published SHA256SUMS, and installs the
    binary into a per-user directory.

.EXAMPLE
    irm https://raw.githubusercontent.com/asale-ai/llm-verify/main/install.ps1 | iex

.NOTES
    Environment overrides:
      LLM_VERIFY_VERSION   install a specific tag (default: latest)
      LLM_VERIFY_BIN_DIR   install location (default: %LOCALAPPDATA%\Programs\llm-verify)
#>

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

$Repo = 'asale-ai/llm-verify'
$Bin  = 'llm-verify.exe'
$BinDir = if ($env:LLM_VERIFY_BIN_DIR) {
    $env:LLM_VERIFY_BIN_DIR
} else {
    Join-Path $env:LOCALAPPDATA 'Programs\llm-verify'
}

function Say  { param($m) Write-Host "  $m" }
function Ok   { param($m) Write-Host "  " -NoNewline; Write-Host "✓" -ForegroundColor Green -NoNewline; Write-Host " $m" }
function Warn { param($m) Write-Host "  " -NoNewline; Write-Host "!" -ForegroundColor Yellow -NoNewline; Write-Host " $m" }
function Die  {
    param($m)
    Write-Host ""
    Write-Host "  Install failed: " -ForegroundColor Red -NoNewline
    Write-Host $m
    Write-Host ""
    exit 1
}

Write-Host ""
Write-Host "  llm-verify installer" -ForegroundColor DarkGray
Write-Host ""

# ── platform ──────────────────────────────────────────────────────────────
$arch = $env:PROCESSOR_ARCHITECTURE
switch ($arch) {
    'AMD64' { $target = 'x86_64-pc-windows-msvc' }
    'ARM64' {
        Die "No prebuilt Windows ARM64 artefact yet.
       Run the x64 build under emulation, or build from source:
       cargo install --git https://github.com/$Repo"
    }
    default { Die "Unsupported CPU architecture: $arch. Only x86_64 is published." }
}
Say "Platform : $target"

# TLS 1.2 is not the default on older Windows PowerShell hosts.
try {
    [Net.ServicePointManager]::SecurityProtocol =
        [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12
} catch { }

# ── version ───────────────────────────────────────────────────────────────
$version = $env:LLM_VERIFY_VERSION
if (-not $version) {
    Say "Resolving the latest version…"
    try {
        $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest" `
                                 -Headers @{ 'User-Agent' = 'llm-verify-installer' }
        $version = $rel.tag_name
    } catch {
        Die "Could not resolve the latest version: $($_.Exception.Message)
       The network may be down or the GitHub API rate-limited.
       Pin one instead: `$env:LLM_VERIFY_VERSION='v0.2.0'; irm ... | iex"
    }
}
if (-not $version) { Die "GitHub returned no usable version tag." }
$num = $version.TrimStart('v')
Say "Version  : $version"

# ── download ──────────────────────────────────────────────────────────────
$name  = "llm-verify-$num-$target"
$asset = "$name.zip"
$url   = "https://github.com/$Repo/releases/download/$version/$asset"

$tmp = Join-Path ([IO.Path]::GetTempPath()) ("llm-verify-" + [Guid]::NewGuid().ToString('N').Substring(0, 8))
New-Item -ItemType Directory -Path $tmp -Force | Out-Null

try {
    Say "Download : $asset"
    $zip = Join-Path $tmp $asset
    try {
        Invoke-WebRequest -Uri $url -OutFile $zip -UseBasicParsing `
                          -Headers @{ 'User-Agent' = 'llm-verify-installer' }
    } catch {
        Die "Download failed: $url
       $($_.Exception.Message)
       That release may have no $target artefact.
       See https://github.com/$Repo/releases/tag/$version for what is available."
    }
    if (-not (Test-Path $zip) -or (Get-Item $zip).Length -eq 0) {
        Die "The downloaded file was empty: $url"
    }

    # ── checksum ──────────────────────────────────────────────────────────
    $sumsUrl = "https://github.com/$Repo/releases/download/$version/SHA256SUMS"
    $sums = Join-Path $tmp 'SHA256SUMS'
    $verified = $false
    try {
        Invoke-WebRequest -Uri $sumsUrl -OutFile $sums -UseBasicParsing `
                          -Headers @{ 'User-Agent' = 'llm-verify-installer' }
        $line = Get-Content $sums | Where-Object { $_ -match "\s\*?$([regex]::Escape($asset))$" } | Select-Object -First 1
        if ($line) {
            $expected = ($line -split '\s+')[0].ToLower()
            $actual = (Get-FileHash -Path $zip -Algorithm SHA256).Hash.ToLower()
            if ($expected -ne $actual) {
                Die "Checksum mismatch.
       expected: $expected
       actual:   $actual
       The file was corrupted in transit, or came from somewhere untrusted.
       Install aborted."
            }
            Ok "Checksum verified (sha256)"
            $verified = $true
        }
    } catch { }
    if (-not $verified) { Warn "Cannot verify the checksum (file unavailable, or no entry for this asset)" }

    # ── unpack ────────────────────────────────────────────────────────────
    try {
        Expand-Archive -Path $zip -DestinationPath $tmp -Force
    } catch {
        Die "Extraction failed; the archive may be corrupt: $($_.Exception.Message)"
    }
    $src = Join-Path $tmp "$name\$Bin"
    if (-not (Test-Path $src)) { Die "Unexpected archive layout: $name\$Bin not found." }

    # ── install ───────────────────────────────────────────────────────────
    try {
        New-Item -ItemType Directory -Path $BinDir -Force | Out-Null
        Copy-Item -Path $src -Destination (Join-Path $BinDir $Bin) -Force
    } catch {
        Die "Could not write to $BinDir: $($_.Exception.Message)
       Permissions, or the file is currently running.
       Set `$env:LLM_VERIFY_BIN_DIR to another location."
    }

    $exe = Join-Path $BinDir $Bin
    try {
        $ver = & $exe --version 2>$null
    } catch {
        Die "Installed to $exe, but it will not run: $($_.Exception.Message)"
    }
    Ok "Installed $ver → $exe"
}
finally {
    Remove-Item -Path $tmp -Recurse -Force -ErrorAction SilentlyContinue
}

# ── PATH ──────────────────────────────────────────────────────────────────
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($userPath -and ($userPath -split ';' | Where-Object { $_.TrimEnd('\') -ieq $BinDir.TrimEnd('\') })) {
    Write-Host ""
    Ok "$BinDir is already on your PATH — open a new terminal and run llm-verify"
} else {
    try {
        $newPath = if ($userPath) { "$userPath;$BinDir" } else { $BinDir }
        [Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
        $env:Path = "$env:Path;$BinDir"
        Write-Host ""
        Ok "Added $BinDir to your user PATH (takes effect in new terminals)"
    } catch {
        Write-Host ""
        Warn "Could not update PATH automatically: $($_.Exception.Message)"
        Write-Host "      Add this directory to your PATH manually:"
        Write-Host "      $BinDir" -ForegroundColor Green
    }
}

Write-Host ""
Write-Host "  Get started:"
Write-Host "      llm-verify --base-url <URL> --api-key <KEY> --model <MODEL>" -ForegroundColor DarkGray
Write-Host "      npx skills add asale-ai/llm-verify   # the skill, for your AI coding tool" -ForegroundColor DarkGray
Write-Host ""