forked from Crockan/MercuryToolbox
chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,557 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[ValidateNotNullOrEmpty()]
|
||||
[string]$InstallRoot = (Join-Path $env:LOCALAPPDATA 'MercuryToolbox'),
|
||||
[string]$CodexHome,
|
||||
[switch]$NoCodexSkillInstall,
|
||||
[switch]$NoPathUpdate
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
. (Join-Path $PSScriptRoot 'toolbox-commands.ps1')
|
||||
|
||||
function Normalize-PathValue {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$PathValue
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($PathValue)) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return $PathValue.Replace('/', '\').Trim().TrimEnd('\').ToLowerInvariant()
|
||||
}
|
||||
|
||||
function Split-PathEntries {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$PathValue
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($PathValue)) {
|
||||
return @()
|
||||
}
|
||||
|
||||
return $PathValue.Split(';', [System.StringSplitOptions]::RemoveEmptyEntries)
|
||||
}
|
||||
|
||||
function Add-PathEntry {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$ExistingPath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Entry
|
||||
)
|
||||
|
||||
$normalizedEntry = Normalize-PathValue -PathValue $Entry
|
||||
$entries = Split-PathEntries -PathValue $ExistingPath
|
||||
foreach ($existing in $entries) {
|
||||
if ((Normalize-PathValue -PathValue $existing) -eq $normalizedEntry) {
|
||||
return @{
|
||||
Changed = $false
|
||||
Value = $ExistingPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$newEntries = @($entries + $Entry)
|
||||
return @{
|
||||
Changed = $true
|
||||
Value = ($newEntries -join ';')
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-PathEntry {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$ExistingPath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Entry
|
||||
)
|
||||
|
||||
$normalizedEntry = Normalize-PathValue -PathValue $Entry
|
||||
$remaining = [System.Collections.Generic.List[string]]::new()
|
||||
$changed = $false
|
||||
|
||||
foreach ($existing in (Split-PathEntries -PathValue $ExistingPath)) {
|
||||
if ((Normalize-PathValue -PathValue $existing) -eq $normalizedEntry) {
|
||||
$changed = $true
|
||||
continue
|
||||
}
|
||||
$remaining.Add($existing)
|
||||
}
|
||||
|
||||
return @{
|
||||
Changed = $changed
|
||||
Value = ($remaining -join ';')
|
||||
}
|
||||
}
|
||||
|
||||
function Copy-OptionalFile {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$SourcePath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$DestinationPath
|
||||
)
|
||||
|
||||
if (Test-Path -LiteralPath $SourcePath) {
|
||||
$destinationDirectory = Split-Path -Parent $DestinationPath
|
||||
if (-not [string]::IsNullOrWhiteSpace($destinationDirectory)) {
|
||||
New-Item -ItemType Directory -Force -Path $destinationDirectory | Out-Null
|
||||
}
|
||||
Copy-Item -LiteralPath $SourcePath -Destination $DestinationPath -Force
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-ExistingPathNotReparsePoint {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Description
|
||||
)
|
||||
|
||||
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
|
||||
if (-not (Test-Path -LiteralPath $resolvedPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
$item = Get-Item -LiteralPath $resolvedPath -Force
|
||||
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
|
||||
throw "$Description must not be a reparse point: $resolvedPath"
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-ExistingTreeHasNoReparsePoints {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Description
|
||||
)
|
||||
|
||||
$resolvedPath = [System.IO.Path]::GetFullPath($Path)
|
||||
if (-not (Test-Path -LiteralPath $resolvedPath)) {
|
||||
return
|
||||
}
|
||||
|
||||
$reparsePoints = @(Get-ChildItem -LiteralPath $resolvedPath -Force -Recurse -Attributes ReparsePoint)
|
||||
if ($reparsePoints.Count -gt 0) {
|
||||
throw "$Description must not contain reparse points: $($reparsePoints[0].FullName)"
|
||||
}
|
||||
}
|
||||
|
||||
function Copy-DirectoryTree {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$SourcePath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$DestinationPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $SourcePath)) {
|
||||
throw "Required directory not found: $SourcePath"
|
||||
}
|
||||
|
||||
$sourceRoot = Get-Item -LiteralPath $SourcePath -Force
|
||||
if (($sourceRoot.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
|
||||
throw "Refusing to copy reparse-point directory: $SourcePath"
|
||||
}
|
||||
|
||||
$reparsePoints = @(Get-ChildItem -LiteralPath $SourcePath -Force -Recurse -Attributes ReparsePoint)
|
||||
if ($reparsePoints.Count -gt 0) {
|
||||
throw "Refusing to copy directory tree containing reparse points: $($reparsePoints[0].FullName)"
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $DestinationPath) {
|
||||
Remove-Item -LiteralPath $DestinationPath -Recurse -Force
|
||||
}
|
||||
|
||||
$destinationParent = Split-Path -Parent $DestinationPath
|
||||
if (-not [string]::IsNullOrWhiteSpace($destinationParent)) {
|
||||
New-Item -ItemType Directory -Force -Path $destinationParent | Out-Null
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath $SourcePath -Destination $DestinationPath -Recurse -Force
|
||||
}
|
||||
|
||||
function Get-PathWithTrailingSeparator {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$trimmed = $Path.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar)
|
||||
return $trimmed + [System.IO.Path]::DirectorySeparatorChar
|
||||
}
|
||||
|
||||
function Assert-PackageSha256Sums {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$PackageRoot,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$HashPath
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $HashPath)) {
|
||||
throw "Package checksum file not found: $HashPath"
|
||||
}
|
||||
|
||||
$packageRootItem = Get-Item -LiteralPath $PackageRoot -Force
|
||||
if (($packageRootItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
|
||||
throw "Package root must not be a reparse point: $PackageRoot"
|
||||
}
|
||||
|
||||
$packageReparsePoints = @(Get-ChildItem -LiteralPath $PackageRoot -Force -Recurse -Attributes ReparsePoint)
|
||||
if ($packageReparsePoints.Count -gt 0) {
|
||||
throw "Package tree contains reparse points: $($packageReparsePoints[0].FullName)"
|
||||
}
|
||||
|
||||
$resolvedPackageRoot = [System.IO.Path]::GetFullPath($PackageRoot)
|
||||
$packageRootPrefix = Get-PathWithTrailingSeparator -Path $resolvedPackageRoot
|
||||
$listedFiles = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
|
||||
$lineNumber = 0
|
||||
$entryCount = 0
|
||||
foreach ($line in Get-Content -LiteralPath $HashPath) {
|
||||
$lineNumber++
|
||||
if ([string]::IsNullOrWhiteSpace($line)) {
|
||||
continue
|
||||
}
|
||||
if ($line -notmatch '^(?<hash>[A-Fa-f0-9]{64})\s+\*?(?<path>.+)$') {
|
||||
throw "Invalid checksum entry at ${HashPath}:$lineNumber"
|
||||
}
|
||||
|
||||
$relativePath = $Matches.path.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($relativePath) -or [System.IO.Path]::IsPathRooted($relativePath)) {
|
||||
throw "Invalid checksum path at ${HashPath}:$lineNumber"
|
||||
}
|
||||
|
||||
$filePath = [System.IO.Path]::GetFullPath((Join-Path $resolvedPackageRoot $relativePath))
|
||||
if (-not $filePath.StartsWith($packageRootPrefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
throw "Checksum path escapes package root at ${HashPath}:$lineNumber"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $filePath -PathType Leaf)) {
|
||||
throw "Package checksum listed file is missing: $relativePath"
|
||||
}
|
||||
$normalizedRelativePath = [System.IO.Path]::GetRelativePath($resolvedPackageRoot, $filePath)
|
||||
if (-not $listedFiles.Add($normalizedRelativePath)) {
|
||||
throw "Duplicate checksum entry for package file: $normalizedRelativePath"
|
||||
}
|
||||
|
||||
$expectedHash = $Matches.hash.ToLowerInvariant()
|
||||
$actualHash = (Get-FileHash -LiteralPath $filePath -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
if ($actualHash -ne $expectedHash) {
|
||||
throw "Package checksum mismatch for ${relativePath}: expected $expectedHash, got $actualHash"
|
||||
}
|
||||
$entryCount++
|
||||
}
|
||||
|
||||
if ($entryCount -eq 0) {
|
||||
throw "Package checksum file contains no entries: $HashPath"
|
||||
}
|
||||
|
||||
foreach ($file in Get-ChildItem -LiteralPath $PackageRoot -Force -Recurse -File) {
|
||||
if ($file.FullName -eq $HashPath) {
|
||||
continue
|
||||
}
|
||||
$relativePath = [System.IO.Path]::GetRelativePath($resolvedPackageRoot, $file.FullName)
|
||||
if (-not $listedFiles.Contains($relativePath)) {
|
||||
throw "Package contains file missing from SHA256SUMS: $relativePath"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-PathItem {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
return
|
||||
}
|
||||
|
||||
$item = Get-Item -LiteralPath $Path -Force
|
||||
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
|
||||
Remove-Item -LiteralPath $Path -Force
|
||||
return
|
||||
}
|
||||
|
||||
if ($item.PSIsContainer) {
|
||||
Remove-Item -LiteralPath $Path -Recurse -Force
|
||||
return
|
||||
}
|
||||
|
||||
Remove-Item -LiteralPath $Path -Force
|
||||
}
|
||||
|
||||
function New-VersionDirectoryName {
|
||||
$stamp = Get-Date -Format 'yyyyMMdd-HHmmss-fff'
|
||||
$suffix = [System.Guid]::NewGuid().ToString('N').Substring(0, 8)
|
||||
return "$stamp-package-$suffix"
|
||||
}
|
||||
|
||||
function Set-CurrentInstallLink {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$CurrentLinkPath,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$TargetPath
|
||||
)
|
||||
|
||||
$currentParent = Split-Path -Parent $CurrentLinkPath
|
||||
if (-not [string]::IsNullOrWhiteSpace($currentParent)) {
|
||||
New-Item -ItemType Directory -Force -Path $currentParent | Out-Null
|
||||
}
|
||||
|
||||
if (Test-Path -LiteralPath $CurrentLinkPath) {
|
||||
Remove-PathItem -Path $CurrentLinkPath
|
||||
}
|
||||
|
||||
try {
|
||||
New-Item -ItemType Junction -Path $CurrentLinkPath -Target $TargetPath | Out-Null
|
||||
}
|
||||
catch {
|
||||
throw "Failed to create current install junction '$CurrentLinkPath' -> '$TargetPath'. Ensure this is running on Windows with a local NTFS-compatible install root. $($_.Exception.Message)"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $CurrentLinkPath)) {
|
||||
throw "Current install junction was not created: $CurrentLinkPath"
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-ObsoleteVersionDirectories {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$VersionsRoot,
|
||||
[AllowNull()]
|
||||
[string]$CurrentVersionRoot
|
||||
)
|
||||
|
||||
if (-not (Test-Path -LiteralPath $VersionsRoot)) {
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($directory in Get-ChildItem -LiteralPath $VersionsRoot -Directory -Force) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($CurrentVersionRoot) -and $directory.FullName -eq $CurrentVersionRoot) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
Remove-Item -LiteralPath $directory.FullName -Recurse -Force
|
||||
}
|
||||
catch {
|
||||
Write-Warning "Leaving stale toolbox version in place because it is still in use: $($directory.FullName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Resolve-CodexHomePath {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$ExplicitCodexHome
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($ExplicitCodexHome)) {
|
||||
return $ExplicitCodexHome
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($env:CODEX_HOME)) {
|
||||
return $env:CODEX_HOME
|
||||
}
|
||||
|
||||
return (Join-Path $env:USERPROFILE '.codex')
|
||||
}
|
||||
|
||||
function Write-CodexSkillOwnershipMarker {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$SkillRoot,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$InstallKind
|
||||
)
|
||||
|
||||
$markerPath = Join-Path $SkillRoot '.mercury-toolbox-owner.json'
|
||||
$payload = [ordered]@{
|
||||
owner = 'mercury-toolbox'
|
||||
install_kind = $InstallKind
|
||||
installed_utc = (Get-Date).ToUniversalTime().ToString('o')
|
||||
} | ConvertTo-Json -Compress
|
||||
Set-Content -LiteralPath $markerPath -Value $payload -Encoding utf8NoBOM
|
||||
}
|
||||
|
||||
function Assert-CodexSkillInstallAllowed {
|
||||
param(
|
||||
[AllowNull()]
|
||||
[string]$SkillRoot
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SkillRoot) -or -not (Test-Path -LiteralPath $SkillRoot)) {
|
||||
return
|
||||
}
|
||||
|
||||
$markerPath = Join-Path $SkillRoot '.mercury-toolbox-owner.json'
|
||||
if (-not (Test-Path -LiteralPath $markerPath -PathType Leaf)) {
|
||||
throw "Refusing to overwrite unmanaged Codex skill directory: $SkillRoot"
|
||||
}
|
||||
|
||||
try {
|
||||
$marker = Get-Content -LiteralPath $markerPath -Raw | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
throw "Refusing to overwrite Codex skill directory with invalid ownership marker: $SkillRoot"
|
||||
}
|
||||
if ($marker.owner -ne 'mercury-toolbox') {
|
||||
throw "Refusing to overwrite Codex skill directory owned by '$($marker.owner)': $SkillRoot"
|
||||
}
|
||||
}
|
||||
|
||||
$packageRoot = Split-Path -Parent $PSScriptRoot
|
||||
$binSource = Join-Path $packageRoot 'bin'
|
||||
$docsSource = Join-Path $packageRoot 'docs'
|
||||
$skillsSource = Join-Path $packageRoot 'skills'
|
||||
$supportSource = Join-Path $packageRoot 'support'
|
||||
$manifestSource = Join-Path $packageRoot 'mercury-toolbox-package.json'
|
||||
$hashSource = Join-Path $packageRoot 'SHA256SUMS.txt'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $binSource)) {
|
||||
throw "Package bin directory not found: $binSource"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $docsSource)) {
|
||||
throw "Package docs directory not found: $docsSource"
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $skillsSource)) {
|
||||
throw "Package skills directory not found: $skillsSource"
|
||||
}
|
||||
|
||||
$legacyBinDir = Join-Path $InstallRoot 'bin'
|
||||
$versionsRoot = Join-Path $InstallRoot 'versions'
|
||||
$currentRoot = Join-Path $InstallRoot 'current'
|
||||
$activeBinDir = Join-Path $currentRoot 'bin'
|
||||
$versionRoot = Join-Path $versionsRoot (New-VersionDirectoryName)
|
||||
$stagingBinDir = Join-Path $versionRoot 'bin'
|
||||
$shareRoot = Join-Path $InstallRoot 'share\mercury-toolbox'
|
||||
$codexHomeRoot = if ($NoCodexSkillInstall) { $null } else { Resolve-CodexHomePath -ExplicitCodexHome $CodexHome }
|
||||
$codexSkillRoot = if ([string]::IsNullOrWhiteSpace([string]$codexHomeRoot)) { $null } else { Join-Path $codexHomeRoot 'skills\mercury-toolbox' }
|
||||
$legacyCommandNames = @('context', 'waitfor')
|
||||
|
||||
Assert-ExistingPathNotReparsePoint -Path $InstallRoot -Description 'InstallRoot'
|
||||
Assert-ExistingPathNotReparsePoint -Path $versionsRoot -Description 'versions root'
|
||||
Assert-ExistingPathNotReparsePoint -Path $shareRoot -Description 'share root'
|
||||
Assert-ExistingTreeHasNoReparsePoints -Path $shareRoot -Description 'share root'
|
||||
if ($null -ne $codexSkillRoot) {
|
||||
Assert-ExistingPathNotReparsePoint -Path $codexSkillRoot -Description 'Codex skill root'
|
||||
Assert-ExistingTreeHasNoReparsePoints -Path $codexSkillRoot -Description 'Codex skill root'
|
||||
}
|
||||
Assert-PackageSha256Sums -PackageRoot $packageRoot -HashPath $hashSource
|
||||
Assert-CodexSkillInstallAllowed -SkillRoot $codexSkillRoot
|
||||
|
||||
Write-Host "Mercury Toolbox package installer"
|
||||
Write-Host "package root: $packageRoot"
|
||||
Write-Host "install root: $InstallRoot"
|
||||
Write-Host "active bin dir: $activeBinDir"
|
||||
Write-Host "versions root: $versionsRoot"
|
||||
Write-Host "share dir: $shareRoot"
|
||||
if ($null -ne $codexSkillRoot) {
|
||||
Write-Host "Codex skill dir: $codexSkillRoot"
|
||||
}
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $stagingBinDir, $shareRoot | Out-Null
|
||||
foreach ($legacyName in $legacyCommandNames) {
|
||||
$legacyPath = Join-Path $legacyBinDir "$legacyName.exe"
|
||||
if (Test-Path -LiteralPath $legacyPath) {
|
||||
Remove-Item -LiteralPath $legacyPath -Force
|
||||
}
|
||||
}
|
||||
|
||||
$installed = [System.Collections.Generic.List[string]]::new()
|
||||
foreach ($commandName in Get-ToolboxCommandNames) {
|
||||
$sourcePath = Join-Path $binSource "$commandName.exe"
|
||||
if (-not (Test-Path -LiteralPath $sourcePath)) {
|
||||
throw "Expected packaged binary not found: $sourcePath"
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath $sourcePath -Destination (Join-Path $stagingBinDir "$commandName.exe") -Force
|
||||
$installed.Add($commandName)
|
||||
}
|
||||
|
||||
foreach ($runtimeFile in Get-ChildItem -LiteralPath $binSource -Filter '*.dll' -File -ErrorAction SilentlyContinue) {
|
||||
Copy-Item -LiteralPath $runtimeFile.FullName -Destination (Join-Path $stagingBinDir $runtimeFile.Name) -Force
|
||||
}
|
||||
|
||||
Copy-DirectoryTree -SourcePath $docsSource -DestinationPath (Join-Path $shareRoot 'docs')
|
||||
Copy-DirectoryTree -SourcePath $skillsSource -DestinationPath (Join-Path $shareRoot 'skills')
|
||||
if (Test-Path -LiteralPath $supportSource) {
|
||||
Copy-DirectoryTree -SourcePath $supportSource -DestinationPath (Join-Path $shareRoot 'support')
|
||||
}
|
||||
Copy-OptionalFile -SourcePath $manifestSource -DestinationPath (Join-Path $shareRoot 'mercury-toolbox-package.json')
|
||||
Copy-OptionalFile -SourcePath $hashSource -DestinationPath (Join-Path $shareRoot 'SHA256SUMS.txt')
|
||||
|
||||
if ($null -ne $codexSkillRoot) {
|
||||
$packagedSkillRoot = Join-Path $skillsSource 'mercury-toolbox'
|
||||
if (Test-Path -LiteralPath $packagedSkillRoot) {
|
||||
Copy-DirectoryTree -SourcePath $packagedSkillRoot -DestinationPath $codexSkillRoot
|
||||
Write-CodexSkillOwnershipMarker -SkillRoot $codexSkillRoot -InstallKind 'package'
|
||||
}
|
||||
}
|
||||
|
||||
Set-CurrentInstallLink -CurrentLinkPath $currentRoot -TargetPath $versionRoot
|
||||
Remove-ObsoleteVersionDirectories -VersionsRoot $versionsRoot -CurrentVersionRoot $versionRoot
|
||||
|
||||
$pathStatus = 'skipped'
|
||||
if (-not $NoPathUpdate) {
|
||||
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||
$userPathChanged = $false
|
||||
$userPathRemoval = Remove-PathEntry -ExistingPath $userPath -Entry $legacyBinDir
|
||||
if ($userPathRemoval.Changed) {
|
||||
$userPath = $userPathRemoval.Value
|
||||
$userPathChanged = $true
|
||||
}
|
||||
$userPathAdd = Add-PathEntry -ExistingPath $userPath -Entry $activeBinDir
|
||||
if ($userPathAdd.Changed) {
|
||||
$userPath = $userPathAdd.Value
|
||||
$userPathChanged = $true
|
||||
}
|
||||
if ($userPathChanged) {
|
||||
[Environment]::SetEnvironmentVariable('Path', $userPath, 'User')
|
||||
$pathStatus = 'updated'
|
||||
}
|
||||
else {
|
||||
$pathStatus = 'already_present'
|
||||
}
|
||||
|
||||
$sessionPath = $env:Path
|
||||
$sessionRemoval = Remove-PathEntry -ExistingPath $sessionPath -Entry $legacyBinDir
|
||||
if ($sessionRemoval.Changed) {
|
||||
$sessionPath = $sessionRemoval.Value
|
||||
}
|
||||
$sessionAdd = Add-PathEntry -ExistingPath $sessionPath -Entry $activeBinDir
|
||||
if ($sessionAdd.Changed) {
|
||||
$sessionPath = $sessionAdd.Value
|
||||
}
|
||||
$env:Path = $sessionPath
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "Installed commands ($($installed.Count)):"
|
||||
foreach ($commandName in $installed) {
|
||||
Write-Host " - $commandName"
|
||||
}
|
||||
Write-Host "PATH status: $pathStatus"
|
||||
Write-Host "AI prompt: $(Join-Path $shareRoot 'docs\ai\mercury-toolbox-ai-prompt.md')"
|
||||
Write-Host "Shared skill copy: $(Join-Path $shareRoot 'skills\mercury-toolbox\SKILL.md')"
|
||||
Write-Host "Command catalog: $(Join-Path $shareRoot 'skills\mercury-toolbox\references\command-catalog.md')"
|
||||
if ($null -ne $codexSkillRoot) {
|
||||
Write-Host "Codex skill: $(Join-Path $codexSkillRoot 'SKILL.md')"
|
||||
}
|
||||
if (-not $NoPathUpdate) {
|
||||
Write-Host "Shell note: future shells pick up the new PATH automatically."
|
||||
Write-Host "Shell note: if you launched install via 'pwsh -File', reopen this shell or run:"
|
||||
Write-Host " `$env:Path = [Environment]::GetEnvironmentVariable('Path','User') + ';' + [Environment]::GetEnvironmentVariable('Path','Machine')"
|
||||
}
|
||||
Write-Host 'Verification: pathshadow msudo'
|
||||
Write-Host 'Verification: msudo status --json'
|
||||
Write-Host 'Verification: msudo --help'
|
||||
Reference in New Issue
Block a user