← Files RevylARCHIVED FILE
scripts/launch-runtime.ps1
16.2 KB · Oct 3, 2026 · 06:34 UTC
<#
.SYNOPSIS
Resolves the plugin-pinned Revyl runtime and runs the requested command.
.PARAMETER RevylArguments
Arguments forwarded unchanged to the selected Revyl executable.
#>
[CmdletBinding()]
param(
[Parameter(ValueFromRemainingArguments = $true)]
[string[]] $RevylArguments
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
[Net.ServicePointManager]::SecurityProtocol = (
[Net.ServicePointManager]::SecurityProtocol -bor
[Net.SecurityProtocolType]::Tls12
)
# Write-BootstrapError reports failures exclusively on stderr.
function Write-BootstrapError {
param(
[Parameter(Mandatory = $true)]
[string] $Message
)
[Console]::Error.WriteLine("Revyl plugin runtime error: $Message")
}
# Resolve-OverrideBinary returns an explicit runtime override as an absolute path.
function Resolve-OverrideBinary {
param(
[Parameter(Mandatory = $true)]
[string] $RequestedBinary
)
if (Test-Path -LiteralPath $RequestedBinary -PathType Leaf) {
return (Get-Item -LiteralPath $RequestedBinary).FullName
}
$command = Get-Command `
-Name $RequestedBinary `
-CommandType Application `
-ErrorAction SilentlyContinue |
Select-Object -First 1
if ($null -eq $command) {
throw "REVYL_BINARY is not executable: $RequestedBinary"
}
return [IO.Path]::GetFullPath($command.Source)
}
# Get-RuntimeArchitecture maps the native Windows architecture to release naming.
function Get-RuntimeArchitecture {
$detectedArchitecture = if (
-not [string]::IsNullOrWhiteSpace($env:PROCESSOR_ARCHITEW6432)
) {
$env:PROCESSOR_ARCHITEW6432
}
else {
$env:PROCESSOR_ARCHITECTURE
}
switch ($detectedArchitecture.ToUpperInvariant()) {
{ $_ -in @("AMD64", "X64", "X86_64") } { return "amd64" }
{ $_ -in @("ARM64", "AARCH64") } { return "arm64" }
default { throw "unsupported Windows architecture: $detectedArchitecture" }
}
}
# Test-RuntimeChecksum verifies one cached or downloaded executable.
function Test-RuntimeChecksum {
param(
[Parameter(Mandatory = $true)]
[string] $Path,
[Parameter(Mandatory = $true)]
[string] $ExpectedChecksum
)
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
return $false
}
$actualChecksum = (
Get-FileHash -LiteralPath $Path -Algorithm SHA256
).Hash.ToLowerInvariant()
return [string]::Equals(
$actualChecksum,
$ExpectedChecksum,
[StringComparison]::Ordinal
)
}
# Resolve-InstalledRuntime returns the first installed CLI byte-identical to the pin.
function Resolve-InstalledRuntime {
param(
[Parameter(Mandatory = $true)]
[string] $ExpectedChecksum
)
$candidates = [System.Collections.Generic.List[string]]::new()
Get-Command `
-Name "revyl" `
-CommandType Application `
-ErrorAction SilentlyContinue |
ForEach-Object { $candidates.Add($_.Source) }
if (-not [string]::IsNullOrWhiteSpace($env:USERPROFILE)) {
$candidates.Add((Join-Path $env:USERPROFILE ".revyl\bin\revyl.exe"))
}
if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
$candidates.Add((Join-Path $env:LOCALAPPDATA "Revyl\bin\revyl.exe"))
}
foreach ($candidate in $candidates) {
if (Test-RuntimeChecksum -Path $candidate -ExpectedChecksum $ExpectedChecksum) {
return (Get-Item -LiteralPath $candidate).FullName
}
}
return $null
}
# Invoke-RuntimeDownload retries one bounded HTTPS artifact with backoff.
function Invoke-RuntimeDownload {
param(
[Parameter(Mandatory = $true)]
[uri] $Uri,
[Parameter(Mandatory = $true)]
[string] $Destination
)
$delaySeconds = 1
for ($attempt = 1; $attempt -le $script:DownloadAttempts; $attempt++) {
try {
Invoke-WebRequest `
-Uri $Uri `
-OutFile $Destination `
-UseBasicParsing `
-UserAgent "revyl-cursor-plugin" `
-TimeoutSec 180 | Out-Null
break
}
catch {
Remove-Item -LiteralPath $Destination -Force -ErrorAction SilentlyContinue
if ($attempt -eq $script:DownloadAttempts) {
throw (
"could not download $Uri after $($script:DownloadAttempts) attempts; " +
"install the Revyl CLI or set REVYL_BINARY to an executable Revyl CLI path"
)
}
[Console]::Error.WriteLine(
"Revyl plugin runtime: download attempt $attempt of " +
"$($script:DownloadAttempts) failed; retrying in ${delaySeconds}s"
)
Start-Sleep -Seconds $delaySeconds
$delaySeconds = $delaySeconds * 2
}
}
if (
-not (Test-Path -LiteralPath $Destination -PathType Leaf) -or
(Get-Item -LiteralPath $Destination).Length -eq 0
) {
throw "the runtime download was empty"
}
}
# Install-RuntimeAtomically publishes a verified temporary executable.
function Install-RuntimeAtomically {
param(
[Parameter(Mandatory = $true)]
[string] $Source,
[Parameter(Mandatory = $true)]
[string] $Destination
)
if (Test-Path -LiteralPath $Destination -PathType Leaf) {
$backup = "$Destination.backup.$PID"
try {
[IO.File]::Replace($Source, $Destination, $backup, $true)
}
finally {
Remove-Item -LiteralPath $backup -Force -ErrorAction SilentlyContinue
}
return
}
try {
[IO.File]::Move($Source, $Destination)
}
catch {
if (-not (Test-Path -LiteralPath $Destination -PathType Leaf)) {
throw
}
Remove-Item -LiteralPath $Source -Force -ErrorAction SilentlyContinue
}
}
# Invoke-RevylRuntime forwards stdio and returns the child exit status.
function Invoke-RevylRuntime {
param(
[Parameter(Mandatory = $true)]
[string] $BinaryPath,
[string[]] $Arguments
)
$env:REVYL_MCP_EXECUTABLE = $BinaryPath
$env:REVYL_NO_UPDATE_NOTIFIER = "1"
$env:REVYL_CLIENT_SOURCE = "cursor_plugin"
if ($pluginHost -eq "codex") {
Remove-Item -Path Env:REVYL_CLIENT_SOURCE -ErrorAction SilentlyContinue
}
& $BinaryPath @Arguments
if ($null -eq $LASTEXITCODE) {
return 0
}
return $LASTEXITCODE
}
# Publish-UserRuntime copies a verified pin onto a user PATH location.
#
# Prefers %USERPROFILE%\.revyl\bin\revyl.exe, then
# %LOCALAPPDATA%\Revyl\bin\revyl.exe. A different existing CLI is left in
# place. Failures never block launching the verified runtime.
function Publish-UserRuntime {
param(
[Parameter(Mandatory = $true)]
[string] $SourceBinary,
[Parameter(Mandatory = $true)]
[string] $ExpectedChecksum
)
$destinations = [System.Collections.Generic.List[string]]::new()
if (-not [string]::IsNullOrWhiteSpace($env:USERPROFILE)) {
$destinations.Add((Join-Path $env:USERPROFILE ".revyl\bin\revyl.exe"))
}
if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
$destinations.Add((Join-Path $env:LOCALAPPDATA "Revyl\bin\revyl.exe"))
}
foreach ($destination in $destinations) {
if (Test-RuntimeChecksum -Path $destination -ExpectedChecksum $ExpectedChecksum) {
return
}
if (Test-Path -LiteralPath $destination) {
continue
}
$publishTemporary = $null
try {
$destinationDirectory = Split-Path -Parent $destination
New-Item -ItemType Directory -Path $destinationDirectory -Force | Out-Null
$publishTemporary = Join-Path `
-Path $destinationDirectory `
-ChildPath ".revyl.publish.$PID"
Copy-Item `
-LiteralPath $SourceBinary `
-Destination $publishTemporary `
-Force
if (Test-RuntimeChecksum -Path $publishTemporary -ExpectedChecksum $ExpectedChecksum) {
Install-RuntimeAtomically `
-Source $publishTemporary `
-Destination $destination
$publishTemporary = $null
if (Test-RuntimeChecksum -Path $destination -ExpectedChecksum $ExpectedChecksum) {
return
}
}
}
catch {
# Publishing is best effort; the verified runtime still runs.
}
if ($null -ne $publishTemporary) {
Remove-Item -LiteralPath $publishTemporary -Force -ErrorAction SilentlyContinue
}
}
}
# Invoke-VerifiedRuntime publishes a verified pin onto PATH, then starts it.
function Invoke-VerifiedRuntime {
param(
[Parameter(Mandatory = $true)]
[string] $BinaryPath,
[Parameter(Mandatory = $true)]
[string] $ExpectedChecksum,
[string[]] $Arguments
)
if ($pluginHost -eq "cursor") {
Publish-UserRuntime `
-SourceBinary $BinaryPath `
-ExpectedChecksum $ExpectedChecksum
}
return (
Invoke-RevylRuntime -BinaryPath $BinaryPath -Arguments $Arguments
)
}
$script:DownloadAttempts = 3
$temporaryPath = $null
try {
$pluginHost = if ([string]::IsNullOrWhiteSpace($env:REVYL_PLUGIN_HOST)) {
"cursor"
} else {
$env:REVYL_PLUGIN_HOST
}
if ($pluginHost -notin @("cursor", "codex")) {
throw "unsupported plugin host; expected cursor or codex"
}
if ($env:REVYL_API_KEY -eq '${env:REVYL_API_KEY}') {
Remove-Item -Path Env:REVYL_API_KEY -ErrorAction SilentlyContinue
}
if (
-not [string]::IsNullOrWhiteSpace($env:REVYL_BINARY) -and
$env:REVYL_BINARY -ne '${env:REVYL_BINARY}'
) {
$overrideBinary = Resolve-OverrideBinary -RequestedBinary $env:REVYL_BINARY
exit (Invoke-RevylRuntime -BinaryPath $overrideBinary -Arguments $RevylArguments)
}
$pluginDirectory = Split-Path -Parent $PSScriptRoot
$manifestPath = if (
-not [string]::IsNullOrWhiteSpace($env:REVYL_RUNTIME_MANIFEST)
) {
$env:REVYL_RUNTIME_MANIFEST
}
else {
Join-Path -Path $pluginDirectory -ChildPath "runtime-manifest.json"
}
if (-not (Test-Path -LiteralPath $manifestPath -PathType Leaf)) {
throw "runtime manifest not found at $manifestPath"
}
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
if ([int] $manifest.schema_version -ne 1) {
throw "unsupported runtime manifest schema: $($manifest.schema_version)"
}
if (-not [bool] $manifest.prepared) {
throw "this plugin release has no prepared runtime; reinstall or update the Revyl plugin"
}
$pluginVersion = [string] $manifest.plugin_version
$runtimeVersion = [string] $manifest.runtime_version
$releaseTag = [string] $manifest.release_tag
$releaseBaseUrl = [string] $manifest.release_base_url
$semanticVersionPattern = "^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z.-]+)?$"
if ($pluginVersion -notmatch $semanticVersionPattern) {
throw "invalid plugin version in runtime manifest"
}
if ($runtimeVersion -notmatch $semanticVersionPattern) {
throw "invalid runtime version in runtime manifest"
}
if ($releaseTag -ne "v$runtimeVersion") {
throw "runtime release tag does not match its version"
}
$expectedBaseUrl = "https://github.com/RevylAI/revyl-cli/releases/download/$releaseTag"
if ($releaseBaseUrl -ne $expectedBaseUrl) {
throw "runtime release URL is not immutable"
}
$architecture = Get-RuntimeArchitecture
$platform = "windows_$architecture"
$assetProperty = "${platform}_asset"
$checksumProperty = "${platform}_sha256"
$asset = [string] $manifest.$assetProperty
$expectedChecksum = ([string] $manifest.$checksumProperty).ToLowerInvariant()
if ($asset -notmatch "^revyl-windows-(amd64|arm64)\.exe$") {
throw "runtime manifest contains an invalid asset name for $platform"
}
if ($expectedChecksum -notmatch "^[0-9a-f]{64}$") {
throw "runtime manifest contains an invalid checksum for $platform"
}
$cacheRoot = if (
-not [string]::IsNullOrWhiteSpace($env:REVYL_PLUGIN_CACHE_DIR)
) {
$env:REVYL_PLUGIN_CACHE_DIR
}
else {
Join-Path `
-Path ([Environment]::GetFolderPath("LocalApplicationData")) `
-ChildPath "Revyl\$pluginHost-plugin"
}
$runtimeDirectory = Join-Path `
-Path $cacheRoot `
-ChildPath "$runtimeVersion\$platform"
$runtimeBinary = Join-Path -Path $runtimeDirectory -ChildPath "revyl.exe"
if (Test-RuntimeChecksum -Path $runtimeBinary -ExpectedChecksum $expectedChecksum) {
exit (
Invoke-VerifiedRuntime `
-BinaryPath $runtimeBinary `
-ExpectedChecksum $expectedChecksum `
-Arguments $RevylArguments
)
}
# An already-installed CLI is byte-identical to the pinned asset when its digest
# matches, so adopting it is equivalent to the download it replaces.
$installedBinary = Resolve-InstalledRuntime -ExpectedChecksum $expectedChecksum
if ($null -ne $installedBinary) {
try {
New-Item -ItemType Directory -Path $runtimeDirectory -Force | Out-Null
$temporaryPath = Join-Path `
-Path $runtimeDirectory `
-ChildPath ".revyl.adopt.$PID"
Copy-Item `
-LiteralPath $installedBinary `
-Destination $temporaryPath `
-Force
if (Test-RuntimeChecksum -Path $temporaryPath -ExpectedChecksum $expectedChecksum) {
Install-RuntimeAtomically -Source $temporaryPath -Destination $runtimeBinary
$temporaryPath = $null
if (Test-RuntimeChecksum -Path $runtimeBinary -ExpectedChecksum $expectedChecksum) {
exit (
Invoke-VerifiedRuntime `
-BinaryPath $runtimeBinary `
-ExpectedChecksum $expectedChecksum `
-Arguments $RevylArguments
)
}
}
}
catch {
# Adoption is best effort; the verified installed binary still runs below.
}
if ($null -ne $temporaryPath) {
Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue
$temporaryPath = $null
}
[Console]::Error.WriteLine(
"Revyl plugin runtime: could not populate the plugin cache; " +
"running verified $installedBinary"
)
exit (
Invoke-VerifiedRuntime `
-BinaryPath $installedBinary `
-ExpectedChecksum $expectedChecksum `
-Arguments $RevylArguments
)
}
# Callers on a short time budget opt out of the download so they fail fast
# instead of blocking on the network for a runtime a later run will cache.
if ($env:REVYL_RUNTIME_NO_DOWNLOAD -eq "1") {
throw "the pinned Revyl runtime is not cached yet and this invocation may not download it"
}
New-Item -ItemType Directory -Path $runtimeDirectory -Force | Out-Null
$temporaryPath = Join-Path `
-Path $runtimeDirectory `
-ChildPath ".revyl.download.$PID"
$downloadUri = [uri] "$releaseBaseUrl/$asset"
Invoke-RuntimeDownload -Uri $downloadUri -Destination $temporaryPath
if (-not (Test-RuntimeChecksum -Path $temporaryPath -ExpectedChecksum $expectedChecksum)) {
throw "checksum verification failed for $asset"
}
Install-RuntimeAtomically -Source $temporaryPath -Destination $runtimeBinary
$temporaryPath = $null
if (-not (Test-RuntimeChecksum -Path $runtimeBinary -ExpectedChecksum $expectedChecksum)) {
throw "cached runtime verification failed after installation"
}
exit (
Invoke-VerifiedRuntime `
-BinaryPath $runtimeBinary `
-ExpectedChecksum $expectedChecksum `
-Arguments $RevylArguments
)
}
catch {
Write-BootstrapError -Message $_.Exception.Message
exit 1
}
finally {
if ($null -ne $temporaryPath) {
Remove-Item -LiteralPath $temporaryPath -Force -ErrorAction SilentlyContinue
}
}
SHA-256: 13733f1eb9499093789f4ea09cd9dba269646073b3387c4bc865c549ca223477