@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/DOCS.md +127 -128
  3. package/README.md +56 -75
  4. package/devops/build-binaries.ts +44 -62
  5. package/devops/devops.xdocs.md +8 -5
  6. package/devops/install.ps1 +182 -152
  7. package/devops/install.sh +197 -262
  8. package/devops/installers.spec.ts +31 -0
  9. package/devops/verify-release-assets.ts +17 -0
  10. package/docs/docs.xdocs.md +3 -1
  11. package/docs/plans/plans.xdocs.md +7 -0
  12. package/docs/plans/rfc-0034-cli-compliance-migration.md +586 -0
  13. package/docs/plans/upgrade-reliability-implementation.md +95 -0
  14. package/docs/reviews/implementation/implementation.xdocs.md +2 -0
  15. package/docs/reviews/implementation/rfc-0034-cli-compliance-migration-review.md +59 -0
  16. package/docs/reviews/plans/plans.xdocs.md +6 -0
  17. package/docs/reviews/plans/rfc-0034-cli-compliance-migration-review.md +80 -0
  18. package/docs/reviews/plans/upgrade-reliability-implementation-review.md +65 -0
  19. package/docs/superpowers/specs/2026-07-15-upgrade-reliability-design.md +662 -0
  20. package/docs/superpowers/specs/specs.xdocs.md +24 -0
  21. package/docs/superpowers/superpowers.xdocs.md +21 -0
  22. package/docs/todo/rfc-0034-cli-compliance-migration-implementation.md +58 -0
  23. package/docs/todo/rfc-0034-cli-compliance-migration.md +151 -0
  24. package/docs/todo/todo.xdocs.md +6 -2
  25. package/docs/validation/rfc-0034-cli-compliance-migration.md +67 -0
  26. package/docs/validation/upgrade-reliability.md +124 -0
  27. package/docs/validation/validation.xdocs.md +5 -0
  28. package/library/agents.d.ts +28 -4
  29. package/library/agents.d.ts.map +1 -1
  30. package/library/agents.js +143 -41
  31. package/library/cli.d.ts.map +1 -1
  32. package/library/cli.js +296 -334
  33. package/library/configuration.d.ts +57 -0
  34. package/library/configuration.d.ts.map +1 -0
  35. package/library/configuration.js +111 -0
  36. package/library/embedded-resources.d.ts +6 -1
  37. package/library/embedded-resources.d.ts.map +1 -1
  38. package/library/embedded-resources.js +10 -4
  39. package/library/executor.d.ts +5 -1
  40. package/library/executor.d.ts.map +1 -1
  41. package/library/executor.js +10 -10
  42. package/library/help.d.ts +8 -4
  43. package/library/help.d.ts.map +1 -1
  44. package/library/help.js +70 -46
  45. package/library/init.d.ts +8 -27
  46. package/library/init.d.ts.map +1 -1
  47. package/library/init.js +37 -159
  48. package/library/manifest.d.ts +4 -43
  49. package/library/manifest.d.ts.map +1 -1
  50. package/library/manifest.js +4 -125
  51. package/library/path-utils.d.ts +13 -0
  52. package/library/path-utils.d.ts.map +1 -0
  53. package/library/path-utils.js +82 -0
  54. package/library/recovery.d.ts +7 -0
  55. package/library/recovery.d.ts.map +1 -0
  56. package/library/recovery.js +23 -0
  57. package/library/release-catalog.d.ts +32 -0
  58. package/library/release-catalog.d.ts.map +1 -0
  59. package/library/release-catalog.js +124 -0
  60. package/library/self-management.d.ts +24 -4
  61. package/library/self-management.d.ts.map +1 -1
  62. package/library/self-management.js +279 -99
  63. package/library/storage.d.ts +13 -0
  64. package/library/storage.d.ts.map +1 -0
  65. package/library/storage.js +38 -0
  66. package/library/types.d.ts +11 -16
  67. package/library/types.d.ts.map +1 -1
  68. package/library/types.js +3 -0
  69. package/library/update-cache.d.ts +21 -0
  70. package/library/update-cache.d.ts.map +1 -0
  71. package/library/update-cache.js +68 -0
  72. package/library/upgrade-reporting.d.ts +11 -0
  73. package/library/upgrade-reporting.d.ts.map +1 -0
  74. package/library/upgrade-reporting.js +67 -0
  75. package/library/upgrade-types.d.ts +72 -0
  76. package/library/upgrade-types.d.ts.map +1 -0
  77. package/library/upgrade-types.js +3 -0
  78. package/package.json +6 -3
  79. package/scripts/runx-bin.mjs +49 -0
  80. package/scripts/runx-bin.spec.ts +62 -0
  81. package/scripts/scripts.xdocs.md +3 -3
  82. package/skills/guiho-s-runx/SKILL.md +64 -59
  83. package/skills/guiho-s-runx/guiho-s-runx.xdocs.md +7 -4
  84. package/skills/skills.xdocs.md +1 -1
  85. package/docs/todo/implement-runx-alpha.md +0 -36
  86. package/scripts/runx-bin.ts +0 -24
@@ -9,208 +9,238 @@ param(
9
9
  $ErrorActionPreference = 'Stop'
10
10
  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
11
11
 
12
- # === Defaults from env vars or sensible defaults ===
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 ([string]::IsNullOrWhiteSpace($InstallDir)) {
18
- $InstallDir = if ($env:RUNX_INSTALL_DIR) { $env:RUNX_INSTALL_DIR } else { Join-Path $HOME '.local\bin' }
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 Version to install (default: latest).
30
- Examples: latest, 0.1.1, @guiho/runx@0.1.1
31
- -Arch Force architecture: x64 | arm64 (default: auto-detect)
32
- -Variant Force x64 variant: baseline | default | modern (default: baseline)
33
- -InstallDir Install directory (default: `$HOME\.local\bin)
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
- # === Detect architecture (compatible with PowerShell 5.1+) ===
43
- $detectedArch = if ($Arch) {
44
- $Arch
45
- } else {
46
- switch ($env:PROCESSOR_ARCHITECTURE) {
47
- 'AMD64' { 'x64' }
48
- 'ARM64' { 'arm64' }
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
- } else {
84
- if ($Variant) {
85
- throw '-Variant is only valid for x64 installs.'
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
- @("runx-windows-arm64.exe")
88
- }
89
-
90
- # === Build download URL ===
91
- function Get-DownloadUrl {
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
- $entries = @(Get-PathEntries -PathValue $userPath)
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
- $bytes = [System.IO.File]::ReadAllBytes($Path)
156
- if ($bytes.Length -lt 2) {
157
- return $false
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
- return $bytes[0] -eq 0x4D -and $bytes[1] -eq 0x5A
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
- # === Main ===
178
- $variantLabel = if ($Variant) { " variant=$Variant" } else { "" }
179
- Write-Host "runx: $Version os=windows arch=$detectedArch$variantLabel"
180
-
181
- New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null
182
- $InstallDir = (Resolve-Path -LiteralPath $InstallDir).Path
183
- $destination = Join-Path $InstallDir 'runx.exe'
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
- foreach ($asset in $assetCandidates) {
186
- $url = Get-DownloadUrl -Asset $asset
187
- Write-Host " Trying $url"
188
- try {
189
- Invoke-WebRequest -Uri $url -OutFile $temporaryFile -UseBasicParsing -ErrorAction Stop
190
- if (-not (Test-NativeBinary -Path $temporaryFile)) {
191
- Write-Host " $asset was not a native Windows binary, trying next..."
192
- continue
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
- Move-Item -Force -Path $temporaryFile -Destination $destination
196
- Write-Host "Installed runx to $destination"
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"