scripts/specrew-host.ps1
|
# specrew host — multi-host inspection + selection command (F-043 / Proposal 104) # # Subcommands: # list — show available + currently-selected hosts (FR-005) # use — set last_selected_host without launching (FR-006) # status — per-host Crew-runtime install state (FR-007) # # Registry-driven: enumerates hosts/_registry.ps1 Get-RegisteredHostKinds. # Adding hosts/<new-kind>/ extends every subcommand's output automatically. [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateSet('list', 'use', 'status')] [string]$Subcommand, [Parameter(Position = 1)] [string]$HostKind, [string]$ProjectPath = (Get-Location).Path ) Set-StrictMode -Version Latest $ErrorActionPreference = 'Stop' $repoRoot = Split-Path -Parent $PSScriptRoot $registryPath = Join-Path $repoRoot 'hosts\_registry.ps1' if (-not (Test-Path -LiteralPath $registryPath -PathType Leaf)) { # Module-mode: hosts/ is sibling of scripts/, both under Specrew module root $registryPath = Join-Path (Split-Path -Parent $repoRoot) 'hosts\_registry.ps1' } if (-not (Test-Path -LiteralPath $registryPath -PathType Leaf)) { Write-Host "ERROR: Host registry not found. Searched: $registryPath" -ForegroundColor Red exit 1 } . $registryPath . (Join-Path $PSScriptRoot 'internal\host-history.ps1') . (Join-Path $PSScriptRoot 'internal\host-runtime-inventory.ps1') function Invoke-SpecrewHostList { param([string]$ProjectPath) $registered = @(Get-RegisteredHostKinds) $history = Get-SpecrewHostHistory -ProjectPath $ProjectPath $selected = if ($null -ne $history) { $root = if ($history.PSObject.Properties.Name -contains 'host_history') { $history.host_history } else { $history } [string]$root.last_selected_host } else { '' } Write-Host '' Write-Host 'Hosts' -ForegroundColor Cyan Write-Host '-----' -ForegroundColor Cyan $installedRows = @() $notInstalledRows = @() foreach ($kind in $registered) { $manifest = Get-HostManifest -Kind $kind $binary = [string]$manifest.Binary # Probe Binary + BinaryAliases (matches first-run probe semantics) $resolved = $null $candidates = @($binary) if ($manifest.ContainsKey('BinaryAliases') -and $null -ne $manifest.BinaryAliases) { foreach ($alias in @($manifest.BinaryAliases)) { if (-not [string]::IsNullOrWhiteSpace([string]$alias)) { $candidates += [string]$alias } } } foreach ($cand in $candidates) { if ($null -ne (Get-Command $cand -ErrorAction SilentlyContinue)) { $resolved = $cand break } } $available = ($null -ne $resolved) $effectiveBinary = if ($available) { $resolved } else { $binary } $status = [string]$manifest.Status $statusMark = switch ($status) { 'supported' { '' } 'deferred' { ' [deferred]' } 'experimental' { ' [experimental]' } default { " [$status]" } } $selectedMark = if ($kind -eq $selected) { ' *selected' } else { '' } $row = [pscustomobject]@{ Kind = $kind DisplayName = [string]$manifest.DisplayName Binary = $effectiveBinary Available = $available Status = $status Selected = ($kind -eq $selected) InstallUrl = if ($manifest.ContainsKey('InstallUrl')) { [string]$manifest.InstallUrl } else { '' } StatusMark = $statusMark SelectedMark = $selectedMark } if ($available) { $installedRows += $row } else { $notInstalledRows += $row } } if ($installedRows.Count -gt 0) { Write-Host 'Installed on this machine:' -ForegroundColor Green foreach ($row in $installedRows) { $line = " {0,-12} {1,-30} bin={2,-12} installed{3}{4}" -f $row.Kind, $row.DisplayName, $row.Binary, $row.StatusMark, $row.SelectedMark if ($row.Selected) { Write-Host $line -ForegroundColor Green } elseif ($row.Status -ne 'supported') { Write-Host $line -ForegroundColor DarkGray } else { Write-Host $line } } } if ($notInstalledRows.Count -gt 0) { Write-Host '' Write-Host 'Other supported hosts (not installed on this PATH):' -ForegroundColor DarkGray foreach ($row in $notInstalledRows) { $urlHint = if (-not [string]::IsNullOrWhiteSpace($row.InstallUrl)) { " (install: $($row.InstallUrl))" } else { '' } $line = " {0,-12} {1,-30} bin={2,-12} (not installed){3}{4}" -f $row.Kind, $row.DisplayName, $row.Binary, $row.StatusMark, $urlHint Write-Host $line -ForegroundColor DarkGray } } Write-Host '' if ([string]::IsNullOrWhiteSpace($selected)) { Write-Host "No host selected. Use 'specrew host use <kind>' to set one, or pass --host on 'specrew start'." -ForegroundColor Yellow } } function Invoke-SpecrewHostUse { param([string]$ProjectPath, [string]$HostKind) if ([string]::IsNullOrWhiteSpace($HostKind)) { Write-Host "ERROR: 'specrew host use' requires a <kind> argument." -ForegroundColor Red Write-Host "Usage: specrew host use <kind>" -ForegroundColor Yellow $registered = @(Get-RegisteredHostKinds | Where-Object { (Get-HostManifest -Kind $_).Status -eq 'supported' }) Write-Host "Supported: $($registered -join ', ')" -ForegroundColor Yellow exit 1 } $kindLower = $HostKind.ToLowerInvariant() # Validate kind exists and is supported try { $manifest = Get-HostManifest -Kind $kindLower } catch { Write-Host "ERROR: Unknown host kind '$HostKind'." -ForegroundColor Red $registered = @(Get-RegisteredHostKinds) Write-Host "Registered: $($registered -join ', ')" -ForegroundColor Yellow exit 1 } if ($manifest.Status -eq 'deferred') { $guidance = if ($manifest.PSObject.Properties.Name -contains 'DeferredGuidance') { [string]$manifest.DeferredGuidance } else { 'No guidance provided.' } Write-Host "ERROR: Host '$kindLower' is deferred. $guidance" -ForegroundColor Red exit 1 } # Probe Crew-runtime install state for the chosen host $inventory = Get-SpecrewHostRuntimeInventory -ProjectPath $ProjectPath $crewInstalled = [bool]$inventory[$kindLower].installed $crewPath = [string]$inventory[$kindLower].path $null = Update-SpecrewHostHistory ` -ProjectPath $ProjectPath ` -SelectedHost $kindLower ` -CrewRuntimeInstalled $crewInstalled ` -CrewRuntimePath $crewPath Write-Host "OK: Selected host '$kindLower' (persisted to .specrew/host-history.json)." -ForegroundColor Green Write-Host "Run 'specrew start' to launch this host." -ForegroundColor Cyan } function Invoke-SpecrewHostStatus { param([string]$ProjectPath) $inventory = Get-SpecrewHostRuntimeInventory -ProjectPath $ProjectPath $history = Get-SpecrewHostHistory -ProjectPath $ProjectPath $selected = if ($null -ne $history) { $root = if ($history.PSObject.Properties.Name -contains 'host_history') { $history.host_history } else { $history } [string]$root.last_selected_host } else { '' } Write-Host '' Write-Host 'Host runtime status' -ForegroundColor Cyan Write-Host '-------------------' -ForegroundColor Cyan foreach ($kind in (Get-RegisteredHostKinds)) { $manifest = Get-HostManifest -Kind $kind $binary = $manifest.Binary $available = ($null -ne (Get-Command $binary -ErrorAction SilentlyContinue)) $entry = $inventory[$kind] $crewInstalled = if ($entry) { [bool]$entry.installed } else { $false } $crewPath = if ($entry) { [string]$entry.path } else { '' } $selectedMark = if ($kind -eq $selected) { ' [SELECTED]' } else { '' } $color = if ($kind -eq $selected) { 'Green' } elseif ($manifest.Status -ne 'supported') { 'DarkGray' } else { 'White' } $line = "{0,-12} PATH={1,-13} CrewRuntime={2,-13} {3}{4}" -f $kind, $(if ($available) { 'available' } else { 'missing' }), $(if ($crewInstalled) { 'installed' } else { 'not-installed' }), $(if ($crewInstalled) { "path=$crewPath" } else { '' }), $selectedMark Write-Host $line -ForegroundColor $color } Write-Host '' } # Dispatch switch ($Subcommand) { 'list' { Invoke-SpecrewHostList -ProjectPath $ProjectPath } 'use' { Invoke-SpecrewHostUse -ProjectPath $ProjectPath -HostKind $HostKind } 'status' { Invoke-SpecrewHostStatus -ProjectPath $ProjectPath } } |