From 34adfa6b3e5f35839ea98de4327f1ef667bf3141 Mon Sep 17 00:00:00 2001 From: Julian lechner Date: Thu, 24 Sep 2026 16:25:59 +0200 Subject: [PATCH] fix(menu): check both clients in update selection --- TODO.md | 2 +- scripts/lib/plugins.ps1 | 26 +++++++++++++++++--------- scripts/lib/plugins.sh | 36 ++++++++++++++++++++---------------- 3 files changed, 38 insertions(+), 26 deletions(-) diff --git a/TODO.md b/TODO.md index ed5413c..c1e5f9a 100644 --- a/TODO.md +++ b/TODO.md @@ -13,7 +13,7 @@ ## Menus and reconciliation -- [ ] Build update-menu checks per client when `Both` is selected. Both menus inspect Claude's installed plugins for every entry, so a Codex resource can start unchecked and the combined sync can remove it. (`scripts/lib/plugins.ps1:98`, `scripts/lib/plugins.sh:108`) +- [x] Build update-menu checks per client when `Both` is selected. Both menus inspect Claude's installed plugins for every entry, so a Codex resource can start unchecked and the combined sync can remove it. (`scripts/lib/plugins.ps1:98`, `scripts/lib/plugins.sh:108`) - [ ] Keep shared MCPorter servers until neither client needs them. Reconciliation processes `shared` records even when only one client is selected; uninstalling that client with an empty selection removes servers still used by the other. (`scripts/lib/managed-extensions.py:463`, `scripts/commands/uninstall.ps1:59`, `scripts/commands/uninstall.sh:77`) - [ ] Manage global packages once across clients. With `--client both`, MCPorter is installed and recorded twice; QMD's global package can be removed during a Codex-only deselection while Claude still uses its plugin. (`scripts/lib/managed-extensions.py:342`, `scripts/lib/managed-extensions.py:378`, `scripts/lib/managed-extensions.py:463`) - [x] Fix Bash `auto` semantic retrieval selection. Both `test_semantic_retrieval_device()` and its caller read `$?` after a completed `if`, losing the benchmark's exit code; an unsuitable device produces an empty choice instead of `false`. (`scripts/lib/semantic-retrieval.sh:13`, `scripts/lib/install-options.sh:52`) diff --git a/scripts/lib/plugins.ps1 b/scripts/lib/plugins.ps1 index 63c0d55..9aa752f 100644 --- a/scripts/lib/plugins.ps1 +++ b/scripts/lib/plugins.ps1 @@ -94,18 +94,26 @@ function Select-ConfiguredPlugins { $fixed = @($entries | Where-Object { $toggleable -notcontains $_ }) if ($DryRun -or $toggleable.Count -eq 0) { return @{ Selected = $entries; Deselected = @() } } - $referenceClient = if ($Client -eq 'Codex') { 'Codex' } else { 'Claude' } - $installed = if ($Mode -eq 'Update') { @(Get-InstalledPlugins -Client $referenceClient -HomePath $HomePath) } else { @() } - $idField = if ($referenceClient -eq 'Claude') { 'id' } else { 'pluginId' } + $referenceClients = if ($Client -eq 'Both') { @('Claude','Codex') } else { @($Client) } + $installed = @{} + if ($Mode -eq 'Update') { foreach ($referenceClient in $referenceClients) { $installed[$referenceClient] = @(Get-InstalledPlugins -Client $referenceClient -HomePath $HomePath) } } $checked = @($toggleable | ForEach-Object { if ($Mode -eq 'Install') { $true; return } if ($_.codex_method -eq 'cli') { return $null -ne (Get-Command $_.codex_source -ErrorAction SilentlyContinue) } - $selector = if ($referenceClient -eq 'Claude') { $_.claude_plugin } else { $_.codex_plugin } - if ($referenceClient -eq 'Codex' -and $_.codex_method -eq 'qmd') { - $null -ne (Get-Command qmd -ErrorAction SilentlyContinue) - } elseif ($referenceClient -eq 'Codex' -and $_.codex_method -ne 'plugin') { - (Test-Path -LiteralPath (Join-Path $HomePath ('.agents/skills/' + $_.codex_skill))) -or (Test-Path -LiteralPath (Join-Path $HomePath ('.codex/skills/' + $_.codex_skill))) - } else { [bool](@($installed | Where-Object { $_.$idField -eq $selector }).Count) } + $entry = $_ + $found = $false + foreach ($referenceClient in $referenceClients) { + $selector = if ($referenceClient -eq 'Claude') { $entry.claude_plugin } else { $entry.codex_plugin } + if ($referenceClient -eq 'Codex' -and $entry.codex_method -eq 'qmd') { + $found = $found -or ($null -ne (Get-Command qmd -ErrorAction SilentlyContinue)) + } elseif ($referenceClient -eq 'Codex' -and $entry.codex_method -ne 'plugin') { + $found = $found -or (Test-Path -LiteralPath (Join-Path $HomePath ('.agents/skills/' + $entry.codex_skill))) -or (Test-Path -LiteralPath (Join-Path $HomePath ('.codex/skills/' + $entry.codex_skill))) + } else { + $idField = if ($referenceClient -eq 'Claude') { 'id' } else { 'pluginId' } + $found = $found -or [bool](@($installed[$referenceClient] | Where-Object { $_.$idField -eq $selector }).Count) + } + } + $found }) $names = @($toggleable | ForEach-Object { $_.name }) $checked = Read-PluginToggleSelection -Names $names -Checked $checked diff --git a/scripts/lib/plugins.sh b/scripts/lib/plugins.sh index 16ec9ed..b89aaf1 100755 --- a/scripts/lib/plugins.sh +++ b/scripts/lib/plugins.sh @@ -112,10 +112,11 @@ select_configured_plugins() { return 0 fi - local reference_client=claude - [ "$client" = codex ] && reference_client=codex + local reference_clients=(claude) + [ "$client" = codex ] && reference_clients=(codex) + [ "$client" = both ] && reference_clients=(claude codex) - local checked=() selector + local checked=() selector reference_client found for name in "${toggle_names[@]}"; do if [ "$mode" = Install ]; then checked+=(true) @@ -125,19 +126,22 @@ select_configured_plugins() { if command -v "$(plugin_field "$root" "$name" codex_source)" >/dev/null 2>&1; then checked+=(true); else checked+=(false); fi continue fi - if [ "$reference_client" = claude ]; then selector=$(plugin_field "$root" "$name" claude_plugin); else selector=$(plugin_field "$root" "$name" codex_plugin); fi - if [ "$reference_client" = codex ] && [ "$(plugin_field "$root" "$name" codex_method)" = qmd ]; then - if command -v qmd >/dev/null 2>&1; then checked+=(true); else checked+=(false); fi - elif [ "$reference_client" = codex ] && [ "$(plugin_field "$root" "$name" codex_method)" != plugin ]; then - local skill_name - skill_name=$(plugin_field "$root" "$name" codex_skill) - if [ -d "$HOME/.agents/skills/$skill_name" ] || [ -d "$HOME/.codex/skills/$skill_name" ]; then checked+=(true); else checked+=(false); fi - elif plugin_installed "$reference_client" "$selector"; then checked+=(true) - else - local status=$? - [ "$status" -eq 1 ] || return "$status" - checked+=(false) - fi + found=false + for reference_client in "${reference_clients[@]}"; do + if [ "$reference_client" = claude ]; then selector=$(plugin_field "$root" "$name" claude_plugin); else selector=$(plugin_field "$root" "$name" codex_plugin); fi + if [ "$reference_client" = codex ] && [ "$(plugin_field "$root" "$name" codex_method)" = qmd ]; then + command -v qmd >/dev/null 2>&1 && found=true + elif [ "$reference_client" = codex ] && [ "$(plugin_field "$root" "$name" codex_method)" != plugin ]; then + local skill_name + skill_name=$(plugin_field "$root" "$name" codex_skill) + { [ -d "$HOME/.agents/skills/$skill_name" ] || [ -d "$HOME/.codex/skills/$skill_name" ]; } && found=true + elif plugin_installed "$reference_client" "$selector"; then found=true + else + local status=$? + [ "$status" -eq 1 ] || return "$status" + fi + done + checked+=("$found") done read_plugin_toggle_selection toggle_names checked