Skip to content

Pre-select already-installed plugins in the installer TUI so re-runs don't start from scratch #11

Description

@fvoska

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions