a1-ai 2.8.0

A1 — The cryptographic identity and authorization layer that turns anonymous AI agents into accountable, verifiable entities. One Identity. Full Provenance.
Documentation
# A1 — Know Your Agent  v2.8.0
# https://github.com/dyologician/a1
#
# Usage:
#   .\setup.ps1           Start A1
#   .\setup.ps1 stop      Stop A1
#   .\setup.ps1 status    Check if running
#   .\setup.ps1 restart   Restart A1
#
# Run as: Right-click → "Run with PowerShell" — or — .\setup.ps1

param([string]$Command = "start")

$ErrorActionPreference = "Continue"

$Version        = "2.8.0"
$StudioUrl      = "http://localhost:8080/studio"
$QuickstartUrl  = "http://localhost:8080/studio?tab=wizard"
$HealthUrl      = "http://localhost:8080/healthz"
$A1Dir          = "$env:USERPROFILE\.a1"
$BinDir         = "$A1Dir\bin"
$BinPath        = "$BinDir\a1-gateway.exe"
$LogFile        = "$A1Dir\logs\gateway.log"
$PidFile        = "$A1Dir\gateway.pid"
$AutostartFlag  = "$A1Dir\autostart-enabled"
$GhBase         = "https://github.com/dyologician/a1/releases/download/v$Version"

New-Item -ItemType Directory -Force -Path "$A1Dir\logs" | Out-Null

function Write-Ok   { param($msg) Write-Host "  [OK]  $msg" -ForegroundColor Green }
function Write-Warn { param($msg) Write-Host "  [!]   $msg" -ForegroundColor Yellow }
function Write-Err  { param($msg) Write-Host "  [X]   $msg" -ForegroundColor Red }

function Test-A1Running {
    try { $r = Invoke-WebRequest $HealthUrl -TimeoutSec 2 -UseBasicParsing -ErrorAction Stop; return $r.StatusCode -eq 200 }
    catch { return $false }
}

function Wait-ForHealth {
    Write-Host "  Starting" -NoNewline
    for ($i = 0; $i -lt 45; $i++) {
        Start-Sleep 1; Write-Host "." -NoNewline
        if (Test-A1Running) { Write-Host ""; return $true }
    }
    Write-Host ""; return $false
}

function Open-Browser([string]$Url = $QuickstartUrl) { Start-Process $Url }

function Enable-GitIgnore {
    $block = "`n# A1 - keep passport keys out of Git`npassport.json`n*-key.hex`n*.passport.json`n.a1/`n"
    if (Test-Path ".gitignore") {
        $c = Get-Content ".gitignore" -Raw -ErrorAction SilentlyContinue
        if ($c -notmatch "passport\.json") { Add-Content ".gitignore" $block }
    } elseif (& git rev-parse --is-inside-work-tree 2>$null) {
        Set-Content ".gitignore" $block.TrimStart()
        Write-Host "  Created .gitignore — passport keys protected from Git" -ForegroundColor DarkGray
    }
}

function Enable-Autostart {
    if (Test-Path $AutostartFlag) { return }
    try {
        $action   = New-ScheduledTaskAction -Execute $BinPath
        $trigger  = New-ScheduledTaskTrigger -AtLogOn
        $settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 0) -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
        $principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -RunLevel Highest
        Register-ScheduledTask -TaskName "A1 Gateway" -Action $action -Trigger $trigger -Settings $settings -Principal $principal -Force | Out-Null
        New-Item -ItemType File -Force -Path $AutostartFlag | Out-Null
        Write-Ok "Auto-start enabled — A1 will run after every login"
    } catch {
        Write-Warn "Auto-start setup skipped (may need admin rights)"
    }
}

function New-DesktopShortcut {
    $shortcut = "$env:USERPROFILE\Desktop\A1 Gateway.lnk"
    if (Test-Path $shortcut) { return }
    try {
        $wsh = New-Object -ComObject WScript.Shell
        $lnk = $wsh.CreateShortcut($shortcut)
        $lnk.TargetPath       = "powershell.exe"
        $lnk.Arguments        = "-WindowStyle Hidden -Command `"Start-Process '$BinPath'; Start-Sleep 3; Start-Process '$StudioUrl'`""
        $lnk.WorkingDirectory = $A1Dir
        $lnk.Description      = "Open A1 Studio"
        $lnk.Save()
        Write-Host "  Desktop shortcut created — double-click 'A1 Gateway' to open Studio" -ForegroundColor DarkGray
    } catch {}
}

function Install-Docker {
    Write-Warn "Docker not found — installing Docker Desktop automatically..."
    $url = "https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe"
    $tmp = "$env:TEMP\DockerInstaller-A1.exe"
    try {
        Invoke-WebRequest $url -OutFile $tmp -UseBasicParsing -ErrorAction Stop
    } catch {
        Write-Err "Docker download failed. Install manually: https://docs.docker.com/get-docker/"
        return $false
    }
    Write-Host "  Installing Docker Desktop..." -ForegroundColor DarkGray
    Start-Process $tmp -ArgumentList "install --quiet" -Wait
    Remove-Item $tmp -Force -ErrorAction SilentlyContinue
    $dockerExe = "C:\Program Files\Docker\Docker\Docker Desktop.exe"
    if (Test-Path $dockerExe) {
        Start-Process $dockerExe
        Write-Host "  Waiting for Docker Desktop..." -ForegroundColor DarkGray
        for ($i = 0; $i -lt 30; $i++) {
            Start-Sleep 2
            try { docker info 2>$null | Out-Null; if ($LASTEXITCODE -eq 0) { Write-Ok "Docker is ready!"; return $true } } catch {}
        }
    }
    Write-Warn "Docker installed but still starting. Open Docker Desktop, wait for the green icon, then re-run .\setup.ps1"
    return $false
}

function Start-Binary {
    if (Test-A1Running) { return $true }
    New-Item -ItemType Directory -Force -Path $BinDir | Out-Null
    if (-not (Test-Path $BinPath)) {
        Write-Host "  Downloading A1 (one-time, ~10 MB)..." -ForegroundColor DarkGray
        $url = "$GhBase/a1-gateway-$Version-x86_64-pc-windows-msvc.exe"
        try { Invoke-WebRequest $url -OutFile $BinPath -UseBasicParsing -ErrorAction Stop }
        catch { Write-Warn "Download failed: $_"; return $false }
    }
    $proc = Start-Process -FilePath $BinPath -RedirectStandardOutput $LogFile -WindowStyle Hidden -PassThru
    $proc.Id | Out-File $PidFile
    return (Wait-ForHealth)
}

function Start-ViaDocker {
    if (Test-A1Running) { return $true }
    $dockerOk = $false
    try { docker info 2>$null | Out-Null; $dockerOk = ($LASTEXITCODE -eq 0) } catch {}
    if (-not $dockerOk) { $dockerOk = Install-Docker }
    if (-not $dockerOk) { return $false }

    $cf = if (Test-Path "docker\docker-compose.yml") { "docker\docker-compose.yml" }
           elseif (Test-Path "docker-compose.yml")   { "docker-compose.yml" }
           else { return $false }

    Write-Host "  Starting via Docker Compose..." -ForegroundColor DarkGray
    docker compose -f $cf up -d --quiet-pull 2>$null
    return (Wait-ForHealth)
}

function Invoke-PostStart([string]$Method) {
    Write-Host ""
    Write-Ok "A1 is running! (via $Method)"
    Write-Host ""
    Enable-GitIgnore
    Enable-Autostart
    New-DesktopShortcut
    Write-Host ""
    Write-Host "  Opening A1 Studio..." -ForegroundColor Cyan
    Open-Browser $QuickstartUrl
    Write-Host ""
    Write-Host "  Stop:    .\setup.ps1 stop" -ForegroundColor DarkGray
    Write-Host "  Status:  .\setup.ps1 status" -ForegroundColor DarkGray
    Write-Host ""
}

switch ($Command) {
    "stop" {
        Write-Host ""
        if (Test-Path $PidFile) {
            $p = Get-Content $PidFile -ErrorAction SilentlyContinue
            if ($p) { Stop-Process -Id $p -Force -ErrorAction SilentlyContinue; Write-Ok "A1 stopped" }
            Remove-Item $PidFile -Force -ErrorAction SilentlyContinue
        }
        $cf = if (Test-Path "docker\docker-compose.yml") { "docker\docker-compose.yml" }
               elseif (Test-Path "docker-compose.yml")   { "docker-compose.yml" }
               else { $null }
        if ($cf) { try { docker compose -f $cf down 2>$null | Out-Null } catch {} }
        Write-Host ""
        exit 0
    }
    "status" {
        Write-Host ""
        if (Test-A1Running) { Write-Ok "A1 is running  -> $StudioUrl" }
        else { Write-Err "A1 is not running — run .\setup.ps1 to start" }
        Write-Host ""
        exit 0
    }
    "restart" {
        Write-Host ""; Write-Host "  Restarting A1..." -ForegroundColor DarkGray
        & $PSCommandPath stop 2>$null; Start-Sleep 1; & $PSCommandPath start
        exit 0
    }
}

Write-Host ""
Write-Host "  A1 — Know Your Agent  v$Version" -ForegroundColor White
Write-Host ""

if (Test-A1Running) {
    Write-Ok "A1 is already running"
    Open-Browser $QuickstartUrl
    exit 0
}

Write-Host "  Trying pre-built binary..." -ForegroundColor DarkGray
$ok = Start-Binary

if (-not $ok) {
    Write-Warn "Binary unavailable — trying Docker (auto-install if needed)..."
    Write-Host ""
    $ok = Start-ViaDocker
}

if ($ok) { Invoke-PostStart (if ($ok -and (Test-Path $BinPath)) { "pre-built binary" } else { "Docker" }); exit 0 }

Write-Host ""
Write-Err "Could not start A1 automatically."
Write-Host ""
Write-Host "  Option A — Install Docker Desktop (free, 2 min):" -ForegroundColor White
Write-Host "    https://docs.docker.com/get-docker/" -ForegroundColor Cyan
Write-Host "    Then run: .\setup.ps1"
Write-Host ""
Write-Host "  Option B — Port 8080 in use?" -ForegroundColor White
Write-Host "    netstat -ano | findstr :8080"
Write-Host ""
Write-Host "  Option C — Get help:" -ForegroundColor White
Write-Host "    https://github.com/dyologician/a1/issues"
Write-Host ""
exit 1