# beacon.host installer for Windows: the publish-website agent skill, then the beacon CLI. # # iwr https://beacon.host/install.ps1 -useb | iex # # Installs per user; never needs an administrator prompt. Environment overrides: # BEACON_INSTALL_DIR where beacon.exe goes (default %LOCALAPPDATA%\Programs\beacon) # BEACON_SKIP_SKILL=1 don't install the agent skill # BEACON_SKIP_CLI=1 install only the agent skill $ErrorActionPreference = "Stop" $BaseUrl = if ($env:BEACON_BASE_URL) { $env:BEACON_BASE_URL.TrimEnd("/") } else { "https://beacon.host" } $InstallDir = if ($env:BEACON_INSTALL_DIR) { $env:BEACON_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Programs\beacon" } $SkillName = "publish-website" $TempDir = New-Item -ItemType Directory -Path (Join-Path ([System.IO.Path]::GetTempPath()) ("beacon-" + [System.Guid]::NewGuid())) -Force try { # 1. The agent skill. Claude Code reads ~\.claude\skills; Codex, Cursor and # most other agents read ~\.agents\skills. if ($env:BEACON_SKIP_SKILL -ne "1") { $SkillPath = Join-Path $TempDir "SKILL.md" Invoke-WebRequest "$BaseUrl/skill.md?ref=install-ps1" -OutFile $SkillPath -UseBasicParsing $SkillText = Get-Content $SkillPath -Raw if (-not $SkillText.StartsWith("---") -or $SkillText -notmatch "(?m)^name: $SkillName\s*$") { throw "$BaseUrl/skill.md is not a valid skill file; not installing it" } foreach ($Root in @(".claude", ".agents")) { $Dir = Join-Path $HOME (Join-Path $Root (Join-Path "skills" $SkillName)) New-Item -ItemType Directory -Path $Dir -Force | Out-Null Copy-Item $SkillPath (Join-Path $Dir "SKILL.md") -Force } Write-Host 'Skill installed: future agent sessions will use beacon.host when you say "publish this" or "deploy an API"' } if ($env:BEACON_SKIP_CLI -eq "1") { return } # 2. The beacon CLI. $Arch = switch ((Get-CimInstance Win32_OperatingSystem).OSArchitecture) { { $_ -match "ARM64" } { "arm64"; break } default { "amd64" } } $LatestPath = Join-Path $TempDir "latest.json" Invoke-WebRequest "$BaseUrl/downloads/beacon/latest.json" -OutFile $LatestPath -UseBasicParsing $Latest = Get-Content $LatestPath -Raw | ConvertFrom-Json $Version = $Latest.version if (-not $Version) { throw "Could not read version from latest.json" } $Name = "beacon-windows-$Arch.zip" $Archive = Join-Path $TempDir $Name Invoke-WebRequest "$BaseUrl/downloads/beacon/$Version/$Name" -OutFile $Archive -UseBasicParsing Invoke-WebRequest "$BaseUrl/downloads/beacon/$Version/checksums.txt" -OutFile (Join-Path $TempDir "checksums.txt") -UseBasicParsing $Expected = (Select-String -Path (Join-Path $TempDir "checksums.txt") -Pattern " $([Regex]::Escape($Name))$").Line.Split(" ")[0] $Actual = (Get-FileHash -Algorithm SHA256 $Archive).Hash.ToLowerInvariant() if ($Expected -and $Actual -ne $Expected.ToLowerInvariant()) { throw "Checksum mismatch for $Name" } Expand-Archive -Path $Archive -DestinationPath $TempDir -Force New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null Copy-Item (Join-Path $TempDir "beacon.exe") (Join-Path $InstallDir "beacon.exe") -Force Write-Host "Installed beacon $Version to $(Join-Path $InstallDir 'beacon.exe')" $UserPath = [Environment]::GetEnvironmentVariable("Path", "User") if ($UserPath -notlike "*$InstallDir*") { [Environment]::SetEnvironmentVariable("Path", "$UserPath;$InstallDir", "User") Write-Host "Added $InstallDir to your user PATH. Open a new terminal before running beacon." } } finally { Remove-Item $TempDir -Recurse -Force -ErrorAction SilentlyContinue }