@guiho/runx 0.2.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/DOCS.md +127 -128
- package/README.md +56 -75
- package/devops/build-binaries.ts +44 -62
- package/devops/devops.xdocs.md +8 -5
- package/devops/install.ps1 +182 -152
- package/devops/install.sh +197 -262
- package/devops/installers.spec.ts +31 -0
- package/devops/verify-release-assets.ts +17 -0
- package/docs/docs.xdocs.md +3 -1
- package/docs/plans/plans.xdocs.md +7 -0
- package/docs/plans/rfc-0034-cli-compliance-migration.md +586 -0
- package/docs/plans/upgrade-reliability-implementation.md +95 -0
- package/docs/reviews/implementation/implementation.xdocs.md +2 -0
- package/docs/reviews/implementation/rfc-0034-cli-compliance-migration-review.md +59 -0
- package/docs/reviews/plans/plans.xdocs.md +6 -0
- package/docs/reviews/plans/rfc-0034-cli-compliance-migration-review.md +80 -0
- package/docs/reviews/plans/upgrade-reliability-implementation-review.md +65 -0
- package/docs/superpowers/specs/2026-07-15-upgrade-reliability-design.md +662 -0
- package/docs/superpowers/specs/specs.xdocs.md +24 -0
- package/docs/superpowers/superpowers.xdocs.md +21 -0
- package/docs/todo/rfc-0034-cli-compliance-migration-implementation.md +58 -0
- package/docs/todo/rfc-0034-cli-compliance-migration.md +151 -0
- package/docs/todo/todo.xdocs.md +6 -2
- package/docs/validation/rfc-0034-cli-compliance-migration.md +67 -0
- package/docs/validation/upgrade-reliability.md +124 -0
- package/docs/validation/validation.xdocs.md +5 -0
- package/library/agents.d.ts +28 -4
- package/library/agents.d.ts.map +1 -1
- package/library/agents.js +143 -41
- package/library/cli.d.ts.map +1 -1
- package/library/cli.js +296 -334
- package/library/configuration.d.ts +57 -0
- package/library/configuration.d.ts.map +1 -0
- package/library/configuration.js +111 -0
- package/library/embedded-resources.d.ts +6 -1
- package/library/embedded-resources.d.ts.map +1 -1
- package/library/embedded-resources.js +10 -4
- package/library/executor.d.ts +5 -1
- package/library/executor.d.ts.map +1 -1
- package/library/executor.js +10 -10
- package/library/help.d.ts +8 -4
- package/library/help.d.ts.map +1 -1
- package/library/help.js +70 -46
- package/library/init.d.ts +8 -27
- package/library/init.d.ts.map +1 -1
- package/library/init.js +37 -159
- package/library/manifest.d.ts +4 -43
- package/library/manifest.d.ts.map +1 -1
- package/library/manifest.js +4 -125
- package/library/path-utils.d.ts +13 -0
- package/library/path-utils.d.ts.map +1 -0
- package/library/path-utils.js +82 -0
- package/library/recovery.d.ts +7 -0
- package/library/recovery.d.ts.map +1 -0
- package/library/recovery.js +23 -0
- package/library/release-catalog.d.ts +32 -0
- package/library/release-catalog.d.ts.map +1 -0
- package/library/release-catalog.js +124 -0
- package/library/self-management.d.ts +24 -4
- package/library/self-management.d.ts.map +1 -1
- package/library/self-management.js +279 -99
- package/library/storage.d.ts +13 -0
- package/library/storage.d.ts.map +1 -0
- package/library/storage.js +38 -0
- package/library/types.d.ts +11 -16
- package/library/types.d.ts.map +1 -1
- package/library/types.js +3 -0
- package/library/update-cache.d.ts +21 -0
- package/library/update-cache.d.ts.map +1 -0
- package/library/update-cache.js +68 -0
- package/library/upgrade-reporting.d.ts +11 -0
- package/library/upgrade-reporting.d.ts.map +1 -0
- package/library/upgrade-reporting.js +67 -0
- package/library/upgrade-types.d.ts +72 -0
- package/library/upgrade-types.d.ts.map +1 -0
- package/library/upgrade-types.js +3 -0
- package/package.json +6 -3
- package/scripts/runx-bin.mjs +49 -0
- package/scripts/runx-bin.spec.ts +62 -0
- package/scripts/scripts.xdocs.md +3 -3
- package/skills/guiho-s-runx/SKILL.md +64 -59
- package/skills/guiho-s-runx/guiho-s-runx.xdocs.md +7 -4
- package/skills/skills.xdocs.md +1 -1
- package/docs/todo/implement-runx-alpha.md +0 -36
- package/scripts/runx-bin.ts +0 -24
package/devops/install.ps1
CHANGED
|
@@ -9,208 +9,238 @@ param(
|
|
|
9
9
|
$ErrorActionPreference = 'Stop'
|
|
10
10
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
if ([string]::IsNullOrWhiteSpace($Version)) {
|
|
14
|
-
$Version = if ($env:RUNX_VERSION) { $env:RUNX_VERSION } else { 'latest' }
|
|
15
|
-
}
|
|
12
|
+
if ([string]::IsNullOrWhiteSpace($Version)) { $Version = if ($env:RUNX_VERSION) { $env:RUNX_VERSION } else { 'latest' } }
|
|
16
13
|
$Repo = if ($env:RUNX_REPO) { $env:RUNX_REPO } else { 'CGuiho/runx' }
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
}
|
|
14
|
+
$DownloadBaseUrl = if ($env:RUNX_DOWNLOAD_BASE_URL) { $env:RUNX_DOWNLOAD_BASE_URL.TrimEnd('/') } else { $null }
|
|
15
|
+
if ([string]::IsNullOrWhiteSpace($InstallDir)) { $InstallDir = if ($env:RUNX_INSTALL_DIR) { $env:RUNX_INSTALL_DIR } else { Join-Path $HOME '.local\bin' } }
|
|
20
16
|
|
|
21
|
-
# === Show help ===
|
|
22
17
|
if ($Help -or $Version -eq '--help' -or $Version -eq '-h') {
|
|
23
18
|
@"
|
|
24
|
-
Install GUIHO RunX as a native CLI binary from GitHub Releases.
|
|
19
|
+
Install GUIHO RunX as a verified native CLI binary from GitHub Releases.
|
|
25
20
|
|
|
26
21
|
Usage: install.ps1 [-Version VERSION] [-Arch ARCH] [-Variant VARIANT] [-InstallDir DIR]
|
|
27
22
|
|
|
28
23
|
Parameters:
|
|
29
|
-
-Version
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-Help Show this help
|
|
35
|
-
|
|
36
|
-
Environment variables:
|
|
37
|
-
RUNX_VERSION, RUNX_REPO, RUNX_INSTALL_DIR
|
|
24
|
+
-Version Exact stable or prerelease version (default: latest stable).
|
|
25
|
+
-Arch Force architecture: x64 | arm64 (default: auto-detect).
|
|
26
|
+
-Variant Force x64 variant: baseline | default | modern (default: baseline).
|
|
27
|
+
-InstallDir Install directory (default: `$HOME\.local\bin).
|
|
28
|
+
-Help Show this help.
|
|
38
29
|
"@
|
|
39
30
|
return
|
|
40
31
|
}
|
|
41
32
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
$
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
default { throw "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE. Must be AMD64 or ARM64." }
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if ($detectedArch -notin @('x64', 'arm64')) {
|
|
54
|
-
throw "Invalid architecture: $detectedArch. Must be x64 or arm64."
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (-not [Environment]::Is64BitOperatingSystem) {
|
|
58
|
-
throw 'Unsupported platform: Windows 32-bit is not supported.'
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
# === Build asset candidates (baseline-first for x64) ===
|
|
62
|
-
$variant = if ($Variant) { $Variant } else { 'baseline' }
|
|
63
|
-
|
|
64
|
-
$assetCandidates = if ($detectedArch -eq 'x64') {
|
|
65
|
-
switch ($variant) {
|
|
66
|
-
'baseline' { @(
|
|
67
|
-
"runx-windows-x64-baseline.exe",
|
|
68
|
-
"runx-windows-x64.exe",
|
|
69
|
-
"runx-windows-x64-modern.exe"
|
|
70
|
-
)}
|
|
71
|
-
'default' { @(
|
|
72
|
-
"runx-windows-x64.exe",
|
|
73
|
-
"runx-windows-x64-baseline.exe",
|
|
74
|
-
"runx-windows-x64-modern.exe"
|
|
75
|
-
)}
|
|
76
|
-
'modern' { @(
|
|
77
|
-
"runx-windows-x64-modern.exe",
|
|
78
|
-
"runx-windows-x64.exe",
|
|
79
|
-
"runx-windows-x64-baseline.exe"
|
|
80
|
-
)}
|
|
81
|
-
default { throw "Invalid variant: $variant. Must be baseline, default, or modern." }
|
|
33
|
+
function Resolve-TargetVersion {
|
|
34
|
+
param([string]$RequestedVersion)
|
|
35
|
+
$tag = $RequestedVersion
|
|
36
|
+
if ($RequestedVersion -eq 'latest') {
|
|
37
|
+
Write-Host 'Resolving latest stable RunX release...'
|
|
38
|
+
$release = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest" -Headers @{ Accept = 'application/vnd.github+json' }
|
|
39
|
+
$tag = [string]$release.tag_name
|
|
82
40
|
}
|
|
83
|
-
|
|
84
|
-
if ($
|
|
85
|
-
throw
|
|
41
|
+
$normalized = $tag -replace '^@guiho/runx@', '' -replace '^v', ''
|
|
42
|
+
if ($normalized -notmatch '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$') {
|
|
43
|
+
throw "Invalid RunX version: $RequestedVersion"
|
|
86
44
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
param([string]$Asset)
|
|
93
|
-
|
|
94
|
-
if ($Version -eq 'latest') {
|
|
95
|
-
return "https://github.com/$Repo/releases/latest/download/$Asset"
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
$tag = if ($Version.StartsWith('@guiho/runx@')) { $Version }
|
|
99
|
-
elseif ($Version.StartsWith('@')) { $Version }
|
|
100
|
-
else { "@guiho/runx@$Version" }
|
|
101
|
-
|
|
102
|
-
$encodedTag = [Uri]::EscapeDataString($tag)
|
|
103
|
-
return "https://github.com/$Repo/releases/download/$encodedTag/$Asset"
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function Get-PathEntries {
|
|
107
|
-
param([string]$PathValue)
|
|
108
|
-
|
|
109
|
-
if ([string]::IsNullOrWhiteSpace($PathValue)) {
|
|
110
|
-
return @()
|
|
45
|
+
$prerelease = (($normalized -split '\+', 2)[0] -split '-', 2)
|
|
46
|
+
if ($prerelease.Count -eq 2) {
|
|
47
|
+
foreach ($identifier in $prerelease[1] -split '\.') {
|
|
48
|
+
if ($identifier -match '^0\d+$') { throw "Invalid RunX version: $RequestedVersion" }
|
|
49
|
+
}
|
|
111
50
|
}
|
|
112
|
-
|
|
113
|
-
return @($PathValue -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) })
|
|
51
|
+
return $normalized
|
|
114
52
|
}
|
|
115
53
|
|
|
54
|
+
function Get-PathEntries { param([string]$PathValue); if ([string]::IsNullOrWhiteSpace($PathValue)) { return @() }; return @($PathValue -split ';' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) }
|
|
116
55
|
function Test-PathContains {
|
|
117
|
-
param(
|
|
118
|
-
[string]$PathValue,
|
|
119
|
-
[string]$Directory
|
|
120
|
-
)
|
|
121
|
-
|
|
56
|
+
param([string]$PathValue, [string]$Directory)
|
|
122
57
|
$normalizedDirectory = $Directory.TrimEnd('\')
|
|
123
58
|
foreach ($entry in Get-PathEntries -PathValue $PathValue) {
|
|
124
|
-
if ($entry.TrimEnd('\').Equals($normalizedDirectory, [StringComparison]::OrdinalIgnoreCase)) {
|
|
125
|
-
return $true
|
|
126
|
-
}
|
|
59
|
+
if ($entry.TrimEnd('\').Equals($normalizedDirectory, [StringComparison]::OrdinalIgnoreCase)) { return $true }
|
|
127
60
|
}
|
|
128
|
-
|
|
129
61
|
return $false
|
|
130
62
|
}
|
|
131
|
-
|
|
132
63
|
function Add-InstallDirToPath {
|
|
133
64
|
param([string]$Directory)
|
|
134
|
-
|
|
135
65
|
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
136
66
|
if (-not (Test-PathContains -PathValue $userPath -Directory $Directory)) {
|
|
137
|
-
$
|
|
138
|
-
$newUserPath = (@($Directory) + $entries) -join ';'
|
|
67
|
+
$newUserPath = (@($Directory) + @(Get-PathEntries -PathValue $userPath)) -join ';'
|
|
139
68
|
[Environment]::SetEnvironmentVariable('Path', $newUserPath.TrimEnd(';'), 'User')
|
|
140
69
|
Write-Host "Added $Directory to user PATH. Restart your terminal to use runx globally."
|
|
141
|
-
} else {
|
|
142
|
-
Write-Host "$Directory is already configured in user PATH."
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (-not (Test-PathContains -PathValue $env:Path -Directory $Directory)) {
|
|
146
|
-
$env:Path = "$Directory;$env:Path"
|
|
147
70
|
}
|
|
71
|
+
if (-not (Test-PathContains -PathValue $env:Path -Directory $Directory)) { $env:Path = "$Directory;$env:Path" }
|
|
148
72
|
}
|
|
149
73
|
|
|
150
|
-
$temporaryFile = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
|
|
151
|
-
|
|
152
74
|
function Test-NativeBinary {
|
|
153
75
|
param([string]$Path)
|
|
76
|
+
$stream = [System.IO.File]::OpenRead($Path)
|
|
77
|
+
try { return $stream.Length -ge 2 -and $stream.ReadByte() -eq 0x4D -and $stream.ReadByte() -eq 0x5A }
|
|
78
|
+
finally { $stream.Dispose() }
|
|
79
|
+
}
|
|
154
80
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
81
|
+
function Test-InstalledVersion {
|
|
82
|
+
param([string]$Path, [string]$ExpectedVersion)
|
|
83
|
+
$startInfo = New-Object System.Diagnostics.ProcessStartInfo
|
|
84
|
+
$startInfo.FileName = $Path
|
|
85
|
+
$startInfo.Arguments = '--version'
|
|
86
|
+
$startInfo.UseShellExecute = $false
|
|
87
|
+
$startInfo.CreateNoWindow = $true
|
|
88
|
+
$startInfo.RedirectStandardOutput = $true
|
|
89
|
+
$startInfo.RedirectStandardError = $true
|
|
90
|
+
$process = New-Object System.Diagnostics.Process
|
|
91
|
+
$process.StartInfo = $startInfo
|
|
92
|
+
try {
|
|
93
|
+
if (-not $process.Start()) { throw 'Could not start installed RunX for version verification' }
|
|
94
|
+
if (-not $process.WaitForExit(10000)) {
|
|
95
|
+
$process.Kill()
|
|
96
|
+
$process.WaitForExit()
|
|
97
|
+
throw 'Installed RunX version check timed out after 10 seconds'
|
|
98
|
+
}
|
|
99
|
+
$stdout = $process.StandardOutput.ReadToEnd().Trim()
|
|
100
|
+
$stderr = $process.StandardError.ReadToEnd().Trim()
|
|
101
|
+
if ($process.ExitCode -ne 0) { throw "Installed RunX exited with code $($process.ExitCode) during verification: $stderr" }
|
|
102
|
+
if ($stdout -ne $ExpectedVersion) { throw "Installed RunX reported $stdout; expected $ExpectedVersion" }
|
|
103
|
+
} finally {
|
|
104
|
+
$process.Dispose()
|
|
158
105
|
}
|
|
106
|
+
}
|
|
159
107
|
|
|
160
|
-
|
|
108
|
+
function Start-BackupCleanup {
|
|
109
|
+
param([string]$BackupPath)
|
|
110
|
+
$script = 'for ($attempt = 0; $attempt -lt 300; $attempt += 1) { try { Remove-Item -LiteralPath $env:RUNX_BACKUP_PATH -Force -ErrorAction Stop; exit 0 } catch { Start-Sleep -Milliseconds 100 } }; exit 1'
|
|
111
|
+
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($script))
|
|
112
|
+
$previousBackupPath = $env:RUNX_BACKUP_PATH
|
|
113
|
+
try {
|
|
114
|
+
$env:RUNX_BACKUP_PATH = $BackupPath
|
|
115
|
+
Start-Process powershell.exe -ArgumentList @('-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', $encoded) -WindowStyle Hidden
|
|
116
|
+
} finally {
|
|
117
|
+
$env:RUNX_BACKUP_PATH = $previousBackupPath
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function Install-Transactional {
|
|
122
|
+
param([string]$DownloadedPath, [string]$Destination, [string]$ExpectedVersion)
|
|
123
|
+
$backupPath = "$Destination.old-$PID-$([Guid]::NewGuid().ToString('N'))"
|
|
124
|
+
$originalMoved = $false
|
|
125
|
+
try {
|
|
126
|
+
if (Test-Path -LiteralPath $Destination) { Move-Item -LiteralPath $Destination -Destination $backupPath; $originalMoved = $true }
|
|
127
|
+
Move-Item -LiteralPath $DownloadedPath -Destination $Destination
|
|
128
|
+
Write-Host 'Verifying...'
|
|
129
|
+
Test-InstalledVersion -Path $Destination -ExpectedVersion $ExpectedVersion
|
|
130
|
+
if ($originalMoved) {
|
|
131
|
+
try { Remove-Item -LiteralPath $backupPath -Force }
|
|
132
|
+
catch { Start-BackupCleanup -BackupPath $backupPath; Write-Host 'Old executable cleanup will finish after the running process exits.' }
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
$failure = $_.Exception.Message
|
|
136
|
+
try {
|
|
137
|
+
if (Test-Path -LiteralPath $Destination) { Remove-Item -LiteralPath $Destination -Force -ErrorAction Stop }
|
|
138
|
+
if ($originalMoved) {
|
|
139
|
+
if (-not (Test-Path -LiteralPath $backupPath)) { throw "Backup is missing: $backupPath" }
|
|
140
|
+
Move-Item -LiteralPath $backupPath -Destination $Destination -ErrorAction Stop
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
throw "RunX installation failed: $failure. Automatic rollback also failed: $($_.Exception.Message). Backup remains at $backupPath"
|
|
144
|
+
}
|
|
145
|
+
if ($originalMoved) { throw "RunX installation failed and the previous executable was restored: $failure" }
|
|
146
|
+
throw "RunX installation failed before a previous executable could be restored: $failure"
|
|
147
|
+
}
|
|
161
148
|
}
|
|
162
149
|
|
|
163
150
|
function Test-Shadowing {
|
|
164
151
|
param([string]$ExpectedPath)
|
|
165
|
-
|
|
166
152
|
$command = Get-Command runx -ErrorAction SilentlyContinue
|
|
167
|
-
if (-not $command) {
|
|
168
|
-
return
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (-not $command.Source.Equals($ExpectedPath, [StringComparison]::OrdinalIgnoreCase)) {
|
|
153
|
+
if ($command -and -not $command.Source.Equals($ExpectedPath, [StringComparison]::OrdinalIgnoreCase)) {
|
|
172
154
|
Write-Warning "Another runx appears earlier in PATH: $($command.Source)"
|
|
173
155
|
Write-Warning "The newly installed binary is at: $ExpectedPath"
|
|
174
156
|
}
|
|
175
157
|
}
|
|
176
158
|
|
|
177
|
-
|
|
178
|
-
$
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
$
|
|
183
|
-
|
|
159
|
+
$detectedArch = if ($Arch) { $Arch } else { switch ($env:PROCESSOR_ARCHITECTURE) { 'AMD64' { 'x64' } 'ARM64' { 'arm64' } default { throw "Unsupported architecture: $env:PROCESSOR_ARCHITECTURE" } } }
|
|
160
|
+
if ($detectedArch -notin @('x64', 'arm64')) { throw "Invalid architecture: $detectedArch" }
|
|
161
|
+
if (-not [Environment]::Is64BitOperatingSystem) { throw 'Unsupported platform: Windows 32-bit is not supported.' }
|
|
162
|
+
$variant = if ($Variant) { $Variant } else { 'baseline' }
|
|
163
|
+
$assetCandidates = if ($detectedArch -eq 'arm64') {
|
|
164
|
+
if ($Variant) { throw '-Variant is only valid for x64 installs.' }
|
|
165
|
+
@('runx-windows-arm64.exe')
|
|
166
|
+
} else {
|
|
167
|
+
switch ($variant) {
|
|
168
|
+
'baseline' { @('runx-windows-x64-baseline.exe', 'runx-windows-x64.exe', 'runx-windows-x64-modern.exe') }
|
|
169
|
+
'default' { @('runx-windows-x64.exe', 'runx-windows-x64-baseline.exe', 'runx-windows-x64-modern.exe') }
|
|
170
|
+
'modern' { @('runx-windows-x64-modern.exe', 'runx-windows-x64.exe', 'runx-windows-x64-baseline.exe') }
|
|
171
|
+
default { throw "Invalid variant: $variant" }
|
|
172
|
+
}
|
|
173
|
+
}
|
|
184
174
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
175
|
+
$targetVersion = Resolve-TargetVersion -RequestedVersion $Version
|
|
176
|
+
$encodedTag = [Uri]::EscapeDataString("@guiho/runx@$targetVersion")
|
|
177
|
+
$temporaryDirectory = Join-Path ([System.IO.Path]::GetTempPath()) ("runx-install-$PID-$([Guid]::NewGuid().ToString('N'))")
|
|
178
|
+
New-Item -ItemType Directory -Force -Path $temporaryDirectory | Out-Null
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
|
|
182
|
+
$InstallDir = (Resolve-Path -LiteralPath $InstallDir).Path
|
|
183
|
+
$destination = Join-Path $InstallDir 'runx.exe'
|
|
184
|
+
$firstAsset = $assetCandidates[0]
|
|
185
|
+
$sourceUrl = if ($DownloadBaseUrl) { "$DownloadBaseUrl/$encodedTag/$firstAsset" } else { "https://github.com/$Repo/releases/download/$encodedTag/$firstAsset" }
|
|
186
|
+
Write-Host 'Initiating GUIHO CLI Upgrade / Installation Sequence...'
|
|
187
|
+
Write-Host "Target Version: v$targetVersion"
|
|
188
|
+
Write-Host "Architecture: $detectedArch"
|
|
189
|
+
Write-Host "Variant: $variant"
|
|
190
|
+
Write-Host "Source URL: $sourceUrl"
|
|
191
|
+
$downloadedPath = $null
|
|
192
|
+
foreach ($asset in $assetCandidates) {
|
|
193
|
+
$url = if ($DownloadBaseUrl) { "$DownloadBaseUrl/$encodedTag/$asset" } else { "https://github.com/$Repo/releases/download/$encodedTag/$asset" }
|
|
194
|
+
$candidatePath = Join-Path $temporaryDirectory $asset
|
|
195
|
+
Write-Host "Downloading $url"
|
|
196
|
+
try {
|
|
197
|
+
Invoke-WebRequest -Uri $url -OutFile $candidatePath -UseBasicParsing
|
|
198
|
+
} catch {
|
|
199
|
+
$statusCode = if ($_.Exception.Response -and $_.Exception.Response.StatusCode) { [int]$_.Exception.Response.StatusCode } else { 0 }
|
|
200
|
+
if ($statusCode -eq 404) { Write-Host " $asset is not available; trying the next compatible candidate."; continue }
|
|
201
|
+
throw "Failed to download $url`: $($_.Exception.Message)"
|
|
193
202
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
203
|
+
if (-not (Test-NativeBinary $candidatePath)) { throw "Downloaded asset $asset is not a native Windows executable." }
|
|
204
|
+
Unblock-File -LiteralPath $candidatePath -ErrorAction SilentlyContinue
|
|
205
|
+
$downloadedPath = $candidatePath
|
|
206
|
+
break
|
|
207
|
+
}
|
|
208
|
+
if (-not $downloadedPath) { throw "No compatible RunX $targetVersion binary found at https://github.com/$Repo/releases" }
|
|
209
|
+
Write-Host 'Replacing...'
|
|
210
|
+
Install-Transactional -DownloadedPath $downloadedPath -Destination $destination -ExpectedVersion $targetVersion
|
|
211
|
+
$skillAsset = Join-Path $temporaryDirectory 'guiho-s-runx'
|
|
212
|
+
$promptAsset = Join-Path $temporaryDirectory 'guiho-i-runx'
|
|
213
|
+
$assetBase = if ($DownloadBaseUrl) { "$DownloadBaseUrl/$encodedTag" } else { "https://github.com/$Repo/releases/download/$encodedTag" }
|
|
214
|
+
Write-Host "Downloading skill asset: $assetBase/guiho-s-runx"
|
|
215
|
+
Invoke-WebRequest -Uri "$assetBase/guiho-s-runx" -OutFile $skillAsset -UseBasicParsing
|
|
216
|
+
Write-Host "Downloading instruction asset: $assetBase/guiho-i-runx"
|
|
217
|
+
Invoke-WebRequest -Uri "$assetBase/guiho-i-runx" -OutFile $promptAsset -UseBasicParsing
|
|
218
|
+
foreach ($skillRoot in @((Join-Path $HOME '.agents\skills\guiho-s-runx'), (Join-Path $HOME '.claude\skills\guiho-s-runx'))) {
|
|
219
|
+
New-Item -ItemType Directory -Force -Path $skillRoot | Out-Null
|
|
220
|
+
Copy-Item -LiteralPath $skillAsset -Destination (Join-Path $skillRoot 'SKILL.md') -Force
|
|
221
|
+
Write-Host "Installed skill: $(Join-Path $skillRoot 'SKILL.md')"
|
|
222
|
+
}
|
|
223
|
+
$instructionTargets = @()
|
|
224
|
+
if (Test-Path -LiteralPath (Join-Path (Get-Location) 'AGENTS.md')) { $instructionTargets += (Join-Path (Get-Location) 'AGENTS.md') }
|
|
225
|
+
if (Test-Path -LiteralPath (Join-Path (Get-Location) 'CLAUDE.md')) { $instructionTargets += (Join-Path (Get-Location) 'CLAUDE.md') }
|
|
226
|
+
if ($instructionTargets.Count -eq 0) { $instructionTargets += (Join-Path (Get-Location) 'AGENTS.md') }
|
|
227
|
+
$startMarker = '<!-- BEGIN RUNX — DO NOT EDIT THIS SECTION -->'
|
|
228
|
+
$endMarker = '<!-- END RUNX -->'
|
|
229
|
+
$prompt = Get-Content -Raw -LiteralPath $promptAsset
|
|
230
|
+
foreach ($instructionPath in $instructionTargets) {
|
|
231
|
+
Write-Host "Reconciling instruction file: $instructionPath"
|
|
232
|
+
$existing = if (Test-Path -LiteralPath $instructionPath) { Get-Content -Raw -LiteralPath $instructionPath } else { '' }
|
|
233
|
+
$pattern = [Regex]::Escape($startMarker) + '[\s\S]*?' + [Regex]::Escape($endMarker) + '\s*'
|
|
234
|
+
$clean = ([Regex]::Replace($existing, $pattern, '')).TrimEnd()
|
|
235
|
+
$prefix = if ($clean) { "$clean`r`n`r`n" } else { '' }
|
|
236
|
+
Set-Content -LiteralPath $instructionPath -Value "$prefix$startMarker`r`n$($prompt.Trim())`r`n$endMarker`r`n" -Encoding utf8
|
|
237
|
+
}
|
|
238
|
+
if ($env:RUNX_SKIP_PATH_UPDATE -ne '1') {
|
|
198
239
|
Add-InstallDirToPath -Directory $InstallDir
|
|
199
240
|
Test-Shadowing -ExpectedPath $destination
|
|
200
|
-
|
|
201
|
-
if (Test-Path -LiteralPath $temporaryFile) {
|
|
202
|
-
Remove-Item -LiteralPath $temporaryFile -Force
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
Write-Host 'Run: runx --help'
|
|
206
|
-
return
|
|
207
|
-
} catch {
|
|
208
|
-
Write-Host " not available, trying next..."
|
|
209
241
|
}
|
|
242
|
+
Write-Host "Final verification: $destination --version"
|
|
243
|
+
Write-Host "Installed and verified RunX $targetVersion at $destination"
|
|
244
|
+
} finally {
|
|
245
|
+
Remove-Item -LiteralPath $temporaryDirectory -Recurse -Force -ErrorAction SilentlyContinue
|
|
210
246
|
}
|
|
211
|
-
|
|
212
|
-
if (Test-Path -LiteralPath $temporaryFile) {
|
|
213
|
-
Remove-Item -LiteralPath $temporaryFile -Force
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
throw "No compatible runx binary found. Check available assets at: https://github.com/$Repo/releases"
|