# VampCode remote installer (Windows) # Usage: # irm https://vampcode..workers.dev/install.ps1 | iex # irm https://YOUR_HOST/install.ps1 | iex # # Env overrides: # $env:VAMPCODE_BASE = install host (auto-detected when possible) # $env:VAMPCODE_SKIP_IDE = 1 to skip IDE registration # # VAMPCODE_INSTALL_BASE=https://vampcode.vampelium.workers.dev $ErrorActionPreference = "Stop" function Write-Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } function Write-Ok($msg) { Write-Host " $msg" -ForegroundColor Green } function Write-Warn($msg) { Write-Host " $msg" -ForegroundColor Yellow } # Resolve base URL of this installer CDN # Publish injects: # VAMPCODE_INSTALL_BASE=https://vampcode.vampelium.workers.dev $Base = $env:VAMPCODE_BASE if (-not $Base) { $raw = $MyInvocation.MyCommand.ScriptContents if (-not $raw) { $raw = $MyInvocation.MyCommand.Definition } if ($raw -match 'VAMPCODE_INSTALL_BASE=(https?://[^\s#]+)') { $Base = $Matches[1].Trim() } } if (-not $Base) { # Default production host (rewritten by scripts/publish-install.ps1 after deploy) $Base = "https://vampcode.vampelium.workers.dev" } $Base = $Base.TrimEnd("/") Write-Host "" Write-Host " VampCode installer" -ForegroundColor Magenta Write-Host " source: $Base" -ForegroundColor DarkGray Write-Host "" # Fetch manifest Write-Step "Fetching release metadata" $manifestUrl = "$Base/latest.json" try { $manifest = Invoke-RestMethod -Uri $manifestUrl -UseBasicParsing } catch { throw "Failed to fetch $manifestUrl : $($_.Exception.Message)" } $Version = $manifest.version if (-not $Version) { throw "latest.json missing version" } Write-Ok "version $Version" # Install directories $InstallRoot = Join-Path $env:USERPROFILE ".vampcode" $BinDir = Join-Path $InstallRoot "bin" $ShareDir = Join-Path $InstallRoot "share" New-Item -ItemType Directory -Force -Path $BinDir | Out-Null New-Item -ItemType Directory -Force -Path $ShareDir | Out-Null # Prefer cargo bin if it already exists (common for Rust users) $CargoBin = Join-Path $env:USERPROFILE ".cargo\bin" $UseCargoBin = Test-Path $CargoBin $TargetBin = if ($UseCargoBin) { $CargoBin } else { $BinDir } Write-Step "Downloading Windows x64 CLI" $relWin = $null if ($manifest.assets -and $manifest.assets.'windows-x64') { $relWin = $manifest.assets.'windows-x64' } else { $relWin = @{ vampcode = "/releases/$Version/windows-x64/vampcode.exe" vamp = "/releases/$Version/windows-x64/vamp.exe" } } function Get-AssetUrl([string]$pathOrUrl) { if ($pathOrUrl -match '^https?://') { return $pathOrUrl } if (-not $pathOrUrl.StartsWith("/")) { $pathOrUrl = "/$pathOrUrl" } return "$Base$pathOrUrl" } $exeUrl = Get-AssetUrl $(if ($relWin.vampcode) { $relWin.vampcode } else { $relWin }) $aliasUrl = if ($relWin.vamp) { Get-AssetUrl $relWin.vamp } else { $null } $tmp = Join-Path $env:TEMP ("vampcode-install-" + [guid]::NewGuid().ToString("n")) New-Item -ItemType Directory -Force -Path $tmp | Out-Null try { $tmpExe = Join-Path $tmp "vampcode.exe" Write-Ok "GET $exeUrl" Invoke-WebRequest -Uri $exeUrl -OutFile $tmpExe -UseBasicParsing if (-not (Test-Path $tmpExe) -or (Get-Item $tmpExe).Length -lt 10000) { throw "Download failed or file too small: $tmpExe" } $destExe = Join-Path $TargetBin "vampcode.exe" $destAlias = Join-Path $TargetBin "vamp.exe" Copy-Item $tmpExe $destExe -Force if ($aliasUrl) { $tmpAlias = Join-Path $tmp "vamp.exe" try { Invoke-WebRequest -Uri $aliasUrl -OutFile $tmpAlias -UseBasicParsing Copy-Item $tmpAlias $destAlias -Force } catch { Copy-Item $tmpExe $destAlias -Force } } else { Copy-Item $tmpExe $destAlias -Force } Write-Ok "installed $destExe" Write-Ok "alias $destAlias" } finally { Remove-Item $tmp -Recurse -Force -ErrorAction SilentlyContinue } # PATH Write-Step "Ensuring PATH" $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if (-not $userPath) { $userPath = "" } if ($userPath -notlike "*$TargetBin*") { [Environment]::SetEnvironmentVariable("Path", "$userPath;$TargetBin", "User") $env:Path = "$env:Path;$TargetBin" Write-Ok "added $TargetBin to user PATH" } else { Write-Ok "PATH already includes $TargetBin" if ($env:Path -notlike "*$TargetBin*") { $env:Path = "$env:Path;$TargetBin" } } # Download IDE pack $SkipIde = ($env:VAMPCODE_SKIP_IDE -eq "1") -or ($env:VAMPCODE_SKIP_IDE -eq "true") if (-not $SkipIde) { Write-Step "Installing IDE language support" $ideRel = if ($manifest.assets -and $manifest.assets.ide) { $manifest.assets.ide } else { $null } $vsixPath = $null $extZip = $null if ($ideRel -and $ideRel.vsix) { $vsixUrl = Get-AssetUrl $ideRel.vsix $vsixPath = Join-Path $ShareDir "vampcode-$Version.vsix" try { Invoke-WebRequest -Uri $vsixUrl -OutFile $vsixPath -UseBasicParsing Write-Ok "VSIX $vsixPath" } catch { Write-Warn "VSIX download failed: $($_.Exception.Message)" $vsixPath = $null } } if ($ideRel -and $ideRel.extension_zip) { $zipUrl = Get-AssetUrl $ideRel.extension_zip $extZip = Join-Path $env:TEMP "vampcode-ext-$Version.zip" $extDir = Join-Path $ShareDir "vscode-extension" try { Invoke-WebRequest -Uri $zipUrl -OutFile $extZip -UseBasicParsing if (Test-Path $extDir) { Remove-Item $extDir -Recurse -Force -ErrorAction SilentlyContinue } New-Item -ItemType Directory -Force -Path $extDir | Out-Null Expand-Archive -Path $extZip -DestinationPath $extDir -Force Write-Ok "extension cache $extDir" } catch { Write-Warn "extension zip failed: $($_.Exception.Message)" } finally { if ($extZip -and (Test-Path $extZip)) { Remove-Item $extZip -Force -ErrorAction SilentlyContinue } } } # Install VSIX via editor CLIs $cliInstalled = $false if ($vsixPath -and (Test-Path $vsixPath)) { foreach ($cmdName in @("code-insiders", "code", "cursor", "codium", "windsurf")) { $cli = Get-Command $cmdName -ErrorAction SilentlyContinue if (-not $cli) { continue } try { $p = Start-Process -FilePath $cli.Source -ArgumentList @( "--install-extension", $vsixPath, "--force" ) -Wait -PassThru -WindowStyle Hidden if ($p.ExitCode -eq 0) { Write-Ok "${cmdName}: extension installed" $cliInstalled = $true } else { Write-Warn "${cmdName}: exit $($p.ExitCode)" } } catch { Write-Warn "${cmdName}: $($_.Exception.Message)" } } } # Folder copy fallback into known extension dirs $extSrc = Join-Path $ShareDir "vscode-extension" if (Test-Path (Join-Path $extSrc "package.json")) { try { $pkg = Get-Content (Join-Path $extSrc "package.json") -Raw | ConvertFrom-Json $folder = "$($pkg.publisher).$($pkg.name)-$($pkg.version)" } catch { $folder = "vampcode.vampcode-$Version" } $userHome = $env:USERPROFILE $roots = @( (Join-Path $userHome ".vscode\extensions"), (Join-Path $userHome ".vscode-insiders\extensions"), (Join-Path $userHome ".cursor\extensions"), (Join-Path $userHome ".vscode-oss\extensions"), (Join-Path $userHome ".windsurf\extensions") ) foreach ($root in $roots) { $parent = Split-Path $root -Parent if (-not (Test-Path $parent) -and $root -notmatch '\\\.(vscode|cursor|windsurf)') { # still create primary vscode paths } try { New-Item -ItemType Directory -Force -Path $root | Out-Null $dest = Join-Path $root $folder if (Test-Path $dest) { Remove-Item $dest -Recurse -Force -ErrorAction SilentlyContinue } New-Item -ItemType Directory -Force -Path $dest | Out-Null Copy-Item (Join-Path $extSrc "*") $dest -Recurse -Force Write-Ok "+ $dest" } catch { # ignore missing editor profiles } } } elseif (-not $cliInstalled) { Write-Warn "No IDE pack available; run 'vampcode ide install' later from a full checkout" } # Windows .vamp file association (HKCU) Write-Step "Registering .vamp file type" $progId = "VampCode.File" $extKey = "HKCU:\Software\Classes\.vamp" $progKey = "HKCU:\Software\Classes\$progId" New-Item -Path $extKey -Force | Out-Null Set-ItemProperty -Path $extKey -Name "(Default)" -Value $progId New-ItemProperty -Path $extKey -Name "Content Type" -Value "text/x-vamp" -PropertyType String -Force | Out-Null New-ItemProperty -Path $extKey -Name "PerceivedType" -Value "text" -PropertyType String -Force | Out-Null New-Item -Path $progKey -Force | Out-Null Set-ItemProperty -Path $progKey -Name "(Default)" -Value "VampCode Source File" $iconKey = Join-Path $progKey "DefaultIcon" New-Item -Path $iconKey -Force | Out-Null Set-ItemProperty -Path $iconKey -Name "(Default)" -Value "`"$destExe`",0" $openCmd = $null foreach ($editor in @("code-insiders.cmd", "code.cmd", "cursor.cmd", "codium.cmd")) { $found = Get-Command $editor -ErrorAction SilentlyContinue if ($found) { $openCmd = "`"$($found.Source)`" `"%1`""; break } } if (-not $openCmd) { $openCmd = "`"$destExe`" run `"%1`"" } $cmdKey = Join-Path $progKey "shell\open\command" New-Item -Path $cmdKey -Force | Out-Null Set-ItemProperty -Path $cmdKey -Name "(Default)" -Value $openCmd $runKey = Join-Path $progKey "shell\Run with VampCode\command" New-Item -Path $runKey -Force | Out-Null Set-ItemProperty -Path $runKey -Name "(Default)" -Value "`"$destExe`" run `"%1`"" Set-ItemProperty -Path (Join-Path $progKey "shell\Run with VampCode") -Name "(Default)" -Value "Run with VampCode" Write-Ok "HKCU Classes\.vamp -> VampCode.File" } # State file $state = @{ version = $Version installed_at = (Get-Date).ToString("o") base = $Base bin = $destExe } | ConvertTo-Json [System.IO.File]::WriteAllText((Join-Path $InstallRoot "install.json"), $state) Write-Host "" Write-Host "VampCode $Version installed." -ForegroundColor Green Write-Host "" Write-Host "Try:" -ForegroundColor Cyan Write-Host " vampcode --version" Write-Host " vampcode create my-app" Write-Host " cd my-app" Write-Host " vampcode run ." Write-Host "" Write-Host "IDE: reload VS Code / Cursor (Developer: Reload Window)" Write-Host " open any .vamp file - language should be VampCode" Write-Host "" # Verify $vc = Get-Command vampcode -ErrorAction SilentlyContinue if (-not $vc) { # call absolute path for this session & $destExe --version Write-Warn "Open a new terminal if 'vampcode' is not found on PATH." } else { & vampcode --version }