diff --git a/scripts/lib/plugins.ps1 b/scripts/lib/plugins.ps1 index da7f31d..c761809 100644 --- a/scripts/lib/plugins.ps1 +++ b/scripts/lib/plugins.ps1 @@ -2,12 +2,25 @@ function Invoke-PluginCommand { param( [Parameter(Mandatory=$true)][string]$Command, [Parameter(Mandatory=$true)][string[]]$Arguments, - [switch]$DryRun + [switch]$DryRun, + [switch]$Summary ) $display = "$Command $($Arguments -join ' ')" - if ($DryRun) { Write-Output "DRYRUN $display"; return } - & $Command @Arguments - if ($LASTEXITCODE -ne 0) { throw "Plugin command failed: $display" } + if ($DryRun) { if (-not $Summary) { Write-Output "DRYRUN $display" }; return } + $resolvedCommand = Get-Command $Command -ErrorAction Stop + if (-not $Summary) { + & $resolvedCommand @Arguments + if ($LASTEXITCODE -ne 0) { throw "Plugin command failed: $display" } + return + } + $previousPreference = $ErrorActionPreference + try { + $ErrorActionPreference = 'Continue' + $output = @(& $resolvedCommand @Arguments 2>&1) + $exitCode = $LASTEXITCODE + } finally { $ErrorActionPreference = $previousPreference } + $output | Where-Object { -not $Summary -or $exitCode -ne 0 -or $_ -is [System.Management.Automation.ErrorRecord] -or "$_" -match '(?i)warn|error|fail|deprecat' } + if ($exitCode -ne 0) { throw "Plugin command failed: $display" } } function Get-ConfiguredPluginEntries { @@ -37,10 +50,11 @@ function Ensure-CodexMarketplace { param( [Parameter(Mandatory=$true)][string]$Source, [Parameter(Mandatory=$true)][string]$MarketplaceName, - [switch]$DryRun + [switch]$DryRun, + [switch]$Summary ) if ($DryRun) { - Write-Output "DRYRUN codex plugin marketplace add $Source" + if (-not $Summary) { Write-Output "DRYRUN codex plugin marketplace add $Source" } return } $previousErrorActionPreference = $ErrorActionPreference @@ -54,14 +68,14 @@ function Ensure-CodexMarketplace { } finally { $ErrorActionPreference = $previousErrorActionPreference } - $output | ForEach-Object { Write-Output $_ } + $output | Where-Object { -not $Summary -or $exitCode -ne 0 -or $_ -is [System.Management.Automation.ErrorRecord] -or "$_" -match '(?i)warn|error|fail|deprecat' } if ($exitCode -eq 0) { return } $text = $output -join [Environment]::NewLine if ($text -match 'marketplace .* already added from a different source') { Write-Output "WARN Codex marketplace '$MarketplaceName' exists from another source; replacing it with: $Source" - Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','marketplace','remove',$MarketplaceName) - Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','marketplace','add',$Source) + Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','marketplace','remove',$MarketplaceName) -Summary:$Summary + Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','marketplace','add',$Source) -Summary:$Summary return } throw "Plugin command failed: codex plugin marketplace add $Source" @@ -80,7 +94,7 @@ function Read-PluginToggleSelection { Write-Host (" {0}) [{1}] {2}" -f ($i + 1), $mark, $Names[$i]) } $answer = Read-Host 'Toggle number or "done"' - if ($answer -eq 'done') { return $state } + if ([string]::IsNullOrWhiteSpace($answer) -or $answer -eq 'done') { return $state } $index = 0 if ([int]::TryParse($answer, [ref]$index) -and $index -ge 1 -and $index -le $Names.Count) { $state[$index - 1] = -not $state[$index - 1] @@ -108,7 +122,7 @@ function Select-ConfiguredPlugins { if ($DryRun -or $toggleable.Count -eq 0) { return @{ Selected = $entries; Deselected = @() } } $referenceClient = if ($Client -eq 'Codex') { 'Codex' } else { 'Claude' } - $installed = @(Get-InstalledPlugins -Client $referenceClient) + $installed = if ($Mode -eq 'Update') { @(Get-InstalledPlugins -Client $referenceClient) } else { @() } $idField = if ($referenceClient -eq 'Claude') { 'id' } else { 'pluginId' } $checked = @($toggleable | ForEach-Object { if ($Mode -eq 'Install') { $true; return } @@ -128,11 +142,12 @@ function Select-ConfiguredPlugins { function Uninstall-DeselectedPlugins { param( - [Parameter(Mandatory=$true)][object[]]$Entries, + [Parameter(Mandatory=$true)][AllowEmptyCollection()][object[]]$Entries, [ValidateSet('Codex','Claude','Both')][Parameter(Mandatory=$true)][string]$Client, [switch]$DryRun, [switch]$Summary ) + if ($Entries.Count -eq 0 -and $PSBoundParameters.ContainsKey('Entries')) { return } $clients = if ($Client -eq 'Both') { @('Claude','Codex') } else { @($Client) } foreach ($selectedClient in $clients) { $installed = if ($DryRun) { @() } else { @(Get-InstalledPlugins -Client $selectedClient) } @@ -144,9 +159,9 @@ function Uninstall-DeselectedPlugins { $isInstalled = [bool](@($installed | Where-Object { $_.$idField -eq $plugin }).Count) if (-not $isInstalled) { continue } if ($selectedClient -eq 'Claude') { - Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','uninstall',$plugin,'--scope','user') -DryRun:$DryRun + Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','uninstall',$plugin,'--scope','user') -DryRun:$DryRun -Summary:$Summary } else { - Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','remove',$plugin) -DryRun:$DryRun + Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','remove',$plugin) -DryRun:$DryRun -Summary:$Summary } if (-not $Summary) { Write-Output "PASS $selectedClient plugin removed: $plugin" } $removedCount++ @@ -164,7 +179,8 @@ function Install-ConfiguredPlugins { [switch]$Summary, [object[]]$Entries ) - $entries = if ($Entries) { $Entries } else { @(Get-ConfiguredPluginEntries -RepositoryRoot $RepositoryRoot) } + $entries = if ($PSBoundParameters.ContainsKey('Entries')) { $Entries } else { @(Get-ConfiguredPluginEntries -RepositoryRoot $RepositoryRoot) } + if ($Entries.Count -eq 0 -and $PSBoundParameters.ContainsKey('Entries')) { return } $clients = if ($Client -eq 'Both') { @('Claude','Codex') } else { @($Client) } foreach ($selectedClient in $clients) { @@ -185,7 +201,7 @@ function Install-ConfiguredPlugins { } else { throw "Unknown Codex install method '$($entry.codex_method)' for $($entry.name)." } - Invoke-PluginCommand -Command 'npx' -Arguments $arguments -DryRun:$DryRun + Invoke-PluginCommand -Command 'npx' -Arguments $arguments -DryRun:$DryRun -Summary:$Summary if (-not $Summary) { Write-Output "PASS Codex skill ensured: $($entry.name)" } $tally.Ensured++ continue @@ -193,16 +209,16 @@ function Install-ConfiguredPlugins { $installedItem = if ($selectedClient -eq 'Claude') { $installed | Where-Object { $_.id -eq $plugin } | Select-Object -First 1 } else { $installed | Where-Object { $_.pluginId -eq $plugin } | Select-Object -First 1 } if ($Update -and $selectedClient -eq 'Claude' -and $null -ne $installedItem) { - Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','update',$plugin) -DryRun:$DryRun + Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','update',$plugin) -DryRun:$DryRun -Summary:$Summary if (-not $installedItem.enabled) { - Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','enable',$plugin) -DryRun:$DryRun + Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','enable',$plugin) -DryRun:$DryRun -Summary:$Summary } $tally.Updated++ continue } if (-not $Update -and $null -ne $installedItem) { if ($selectedClient -eq 'Claude' -and -not $installedItem.enabled) { - Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','enable',$plugin) -DryRun:$DryRun + Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','enable',$plugin) -DryRun:$DryRun -Summary:$Summary } if (-not $Summary) { Write-Output "PASS $selectedClient plugin already installed: $plugin" } $tally.AlreadyInstalled++ @@ -211,18 +227,18 @@ function Install-ConfiguredPlugins { if ($selectedClient -eq 'Claude') { if (-not [string]::IsNullOrWhiteSpace($marketplace)) { - Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','marketplace','add',$marketplace) -DryRun:$DryRun + Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','marketplace','add',$marketplace) -DryRun:$DryRun -Summary:$Summary } - Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','install',$plugin,'--scope','user') -DryRun:$DryRun + Invoke-PluginCommand -Command 'claude' -Arguments @('plugin','install',$plugin,'--scope','user') -DryRun:$DryRun -Summary:$Summary } else { if (-not [string]::IsNullOrWhiteSpace($marketplace) -and $marketplace -ne 'openai-curated-remote') { $marketplaceName = ($plugin -split '@')[-1] - Ensure-CodexMarketplace -Source $marketplace -MarketplaceName $marketplaceName -DryRun:$DryRun + Ensure-CodexMarketplace -Source $marketplace -MarketplaceName $marketplaceName -DryRun:$DryRun -Summary:$Summary } - Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','add',$plugin) -DryRun:$DryRun + Invoke-PluginCommand -Command 'codex' -Arguments @('plugin','add',$plugin) -DryRun:$DryRun -Summary:$Summary } if (-not $Summary) { Write-Output "PASS $selectedClient plugin ensured: $plugin" } - $tally.Ensured++ + if ($Update -and $null -ne $installedItem) { $tally.Updated++ } else { $tally.Ensured++ } } if ($Summary) { Write-Output ("PLUGINS {0}: {1} ensured, {2} already installed, {3} updated" -f $selectedClient, $tally.Ensured, $tally.AlreadyInstalled, $tally.Updated) } } diff --git a/scripts/lib/plugins.sh b/scripts/lib/plugins.sh index 19dbf63..9b9d694 100755 --- a/scripts/lib/plugins.sh +++ b/scripts/lib/plugins.sh @@ -14,7 +14,17 @@ run_plugin_command() { local dry_run=$1 shift if [ "$dry_run" = true ]; then - printf 'DRYRUN %s\n' "$*" + [ "${summary:-false}" = true ] || printf 'DRYRUN %s\n' "$*" + return 0 + elif [ "${summary:-false}" = true ]; then + local output status + if output=$("$@"); then + printf '%s\n' "$output" | grep -Ei 'warn|error|fail|deprecat' || true + else + status=$? + printf '%s\n' "$output" >&2 + return "$status" + fi else "$@" fi @@ -23,7 +33,7 @@ run_plugin_command() { ensure_codex_marketplace() { local dry_run=$1 source=$2 marketplace_name=$3 output if [ "$dry_run" = true ]; then - printf 'DRYRUN codex plugin marketplace add %s\n' "$source" + [ "${summary:-false}" = true ] || printf 'DRYRUN codex plugin marketplace add %s\n' "$source" return 0 fi if output=$(codex plugin marketplace add "$source" 2>&1); then @@ -33,8 +43,8 @@ ensure_codex_marketplace() { printf '%s\n' "$output" >&2 if printf '%s\n' "$output" | grep -Eiq 'marketplace .* already added from a different source'; then printf "WARN Codex marketplace '%s' exists from another source; replacing it with: %s\n" "$marketplace_name" "$source" - codex plugin marketplace remove "$marketplace_name" - codex plugin marketplace add "$source" + run_plugin_command false codex plugin marketplace remove "$marketplace_name" + run_plugin_command false codex plugin marketplace add "$source" return 0 fi return 1 @@ -79,10 +89,10 @@ read_plugin_toggle_selection() { [ "${checked_ref[$i]}" = true ] && mark=x printf ' %d) [%s] %s\n' "$((i + 1))" "$mark" "${names_ref[$i]}" done - read -r -p 'Toggle number or "done" ' answer - [ "$answer" = done ] && return 0 + read -r -p 'Toggle number or "done" ' answer || { printf 'Plugin selection cancelled: input closed.\n' >&2; return 1; } + { [ -z "$answer" ] || [ "$answer" = done ]; } && return 0 if [[ "$answer" =~ ^[0-9]+$ ]] && [ "$answer" -ge 1 ] && [ "$answer" -le "${#names_ref[@]}" ]; then - i=$((answer - 1)) + i=$((10#$answer - 1)) if [ "${checked_ref[$i]}" = true ]; then checked_ref[$i]=false; else checked_ref[$i]=true; fi fi done