Background
The installer's interactive plugin picker (promptPluginMultiselect in bin/install.js) runs once per marketplace source — the base infinum/ai marketplace, plus any private overlays added via --extend. Each run shows a @clack/prompts multiselect of that source's plugins so you tick the ones you want.
Re-running the installer is the intended way to pick up updates (bundled rules have no auto-update; the stack-essentials hook explicitly nudges you to re-run — see #6). So this picker is something you hit repeatedly, not just once.
Problem
The plugin multiselect starts from an empty selection every time. On each re-run you have to re-tick every plugin you previously installed, from scratch, or you risk losing them. This makes updating tedious and error-prone.
Notably, the installer already does the right thing for rule bundles: selectBundles() reads the previously-selected bundles and passes them as initialValues, so bundle checkboxes come pre-checked with your prior choices (bin/install.js, the multiselect({ ..., initialValues }) call around line 550). Plugins just never got the same treatment — promptPluginMultiselect() builds its multiselect({ ... }) with no initialValues (around line 761).
Proposed improvement
Pre-select, per marketplace, the plugins that are already installed for that marketplace, so the checkboxes reflect my current setup and I only toggle deltas.
The clean part: the installer already knows how to query this, and doesn't need to start tracking plugin state to do it.
liveInstalledPluginIds() (bin/install.js ~line 861) runs claude plugin list --json and returns ids as <plugin>@<marketplace>. --remove already uses it, filtering by the @<marketplace> suffix.
promptPluginMultiselect(source, plugins) could compute the currently-installed plugin names for source.marketplaceName (filter live ids by @${source.marketplaceName}, strip the suffix) and pass them as initialValues, mirroring what selectBundles does for bundles.
This respects the deliberate design decision that the manifest does not track which plugins are installed — "that's Claude Code's state, read live when needed" (header comment, bin/install.js lines ~25-28, echoed at liveInstalledPluginIds). Live query = no new state to keep in sync, and it stays correct even if the user installs/uninstalls plugins directly via /plugin between runs.
Pointers
- Empty-
initialValues plugin picker: promptPluginMultiselect() in bin/install.js (~line 761)
- The pattern to copy — bundles already pre-select:
selectBundles() in bin/install.js (~line 517, initialValues at ~550)
- Live installed-plugin query (
<plugin>@<marketplace> ids): liveInstalledPluginIds() in bin/install.js (~line 861)
- Per-source picker caller:
installSelectedPlugins() in bin/install.js (~line 774)
Considerations
- Interactive path only. This affects the TTY multiselect. Non-interactive runs already require an explicit
--plugins <list> and are unaffected.
- Installed vs. enabled. Decide whether pre-selection keys off "installed" or "installed and enabled" — a plugin the user installed but then disabled via
/plugin probably shouldn't come back pre-checked. Check what claude plugin list --json exposes.
- Graceful when the query fails. If
claude plugin list --json errors or returns nothing (fresh machine, CLI hiccup), fall back to the current behaviour (empty selection) rather than blocking the picker.
- Auto-installed plugins.
stack-essentials (in AUTO_INSTALL_PLUGINS) is already filtered out of the base source's picker — make sure pre-selection doesn't reintroduce it into the list.
Contributions welcome — this is up for grabs.
Background
The installer's interactive plugin picker (
promptPluginMultiselectinbin/install.js) runs once per marketplace source — the baseinfinum/aimarketplace, plus any private overlays added via--extend. Each run shows a@clack/promptsmultiselect of that source's plugins so you tick the ones you want.Re-running the installer is the intended way to pick up updates (bundled rules have no auto-update; the
stack-essentialshook explicitly nudges you to re-run — see #6). So this picker is something you hit repeatedly, not just once.Problem
The plugin multiselect starts from an empty selection every time. On each re-run you have to re-tick every plugin you previously installed, from scratch, or you risk losing them. This makes updating tedious and error-prone.
Notably, the installer already does the right thing for rule bundles:
selectBundles()reads the previously-selected bundles and passes them asinitialValues, so bundle checkboxes come pre-checked with your prior choices (bin/install.js, themultiselect({ ..., initialValues })call around line 550). Plugins just never got the same treatment —promptPluginMultiselect()builds itsmultiselect({ ... })with noinitialValues(around line 761).Proposed improvement
Pre-select, per marketplace, the plugins that are already installed for that marketplace, so the checkboxes reflect my current setup and I only toggle deltas.
The clean part: the installer already knows how to query this, and doesn't need to start tracking plugin state to do it.
liveInstalledPluginIds()(bin/install.js~line 861) runsclaude plugin list --jsonand returns ids as<plugin>@<marketplace>.--removealready uses it, filtering by the@<marketplace>suffix.promptPluginMultiselect(source, plugins)could compute the currently-installed plugin names forsource.marketplaceName(filter live ids by@${source.marketplaceName}, strip the suffix) and pass them asinitialValues, mirroring whatselectBundlesdoes for bundles.This respects the deliberate design decision that the manifest does not track which plugins are installed — "that's Claude Code's state, read live when needed" (header comment,
bin/install.jslines ~25-28, echoed atliveInstalledPluginIds). Live query = no new state to keep in sync, and it stays correct even if the user installs/uninstalls plugins directly via/pluginbetween runs.Pointers
initialValuesplugin picker:promptPluginMultiselect()inbin/install.js(~line 761)selectBundles()inbin/install.js(~line 517,initialValuesat ~550)<plugin>@<marketplace>ids):liveInstalledPluginIds()inbin/install.js(~line 861)installSelectedPlugins()inbin/install.js(~line 774)Considerations
--plugins <list>and are unaffected./pluginprobably shouldn't come back pre-checked. Check whatclaude plugin list --jsonexposes.claude plugin list --jsonerrors or returns nothing (fresh machine, CLI hiccup), fall back to the current behaviour (empty selection) rather than blocking the picker.stack-essentials(inAUTO_INSTALL_PLUGINS) is already filtered out of the base source's picker — make sure pre-selection doesn't reintroduce it into the list.Contributions welcome — this is up for grabs.