Marketplace: warn on imported global plugins; tidy import follow-ups

- The import preview counts global plugin installs and warns on them:
  a plugin can bring hooks and MCP servers into every container and an
  imported install skips the confirm step, like a hook.
- Item keys, hosts and branches in errors are quoted with {:?} and
  capped, since they can come from an import file.
- After an import, caches and snapshots of marketplaces the import
  dropped are removed (under the repo lock) and pins are refreshed for
  the imported installs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 09:46:56 -07:00
co-authored by Claude Opus 5.5
parent 89859b9a3c
commit 6cf9664dc8
7 changed files with 126 additions and 6 deletions
@@ -34,6 +34,7 @@ const samplePreview: SettingsImportPreview = {
custom_image_name: null,
marketplace_count: 0,
global_hook_install_count: 0,
global_plugin_install_count: 0,
marketplace_account_token_count: 0,
};
+10
View File
@@ -22,6 +22,7 @@ function preview(overrides: Partial<SettingsImportPreview> = {}): SettingsImport
custom_image_name: null,
marketplace_count: 0,
global_hook_install_count: 0,
global_plugin_install_count: 0,
marketplace_account_token_count: 0,
...overrides,
};
@@ -135,6 +136,15 @@ describe("describeImportWarnings", () => {
);
});
it("warns when the import installs plugins for every project", () => {
expect(describeImportWarnings(preview({ global_plugin_install_count: 1 }))).toEqual([
"Installs 1 marketplace plugin for all projects. Plugins can bring their own hooks and MCP servers into every project container, and these skip the confirmation an install from the Marketplace tab asks for.",
]);
expect(
describeImportWarnings(preview({ global_plugin_install_count: 2, global_hook_install_count: 1 })),
).toHaveLength(2);
});
it("warns about a custom Docker image every time, not only when it changes", () => {
expect(
describeImportWarnings(preview({ image_source: "custom", custom_image_name: "evil:latest" })),
+8 -1
View File
@@ -54,7 +54,8 @@ export function describeImport(preview: SettingsImportPreview): string[] {
*
* Global marketplace hooks get one too: a hook runs commands in every
* project container, and an imported install never passed the hook-confirm
* step an install from the Marketplace tab shows.
* step an install from the Marketplace tab shows. Global plugins likewise:
* a plugin can carry its own hooks and MCP servers.
*
* A custom Docker image gets a warning every time, not just on change: it's
* the image every project container is created from, so it's worth calling
@@ -75,6 +76,12 @@ export function describeImportWarnings(preview: SettingsImportPreview): string[]
`Installs ${n} marketplace hook${n === 1 ? "" : "s"} for all projects. Hooks run commands in every project container, and these skip the confirmation an install from the Marketplace tab asks for.`,
);
}
if (preview.global_plugin_install_count > 0) {
const n = preview.global_plugin_install_count;
warnings.push(
`Installs ${n} marketplace plugin${n === 1 ? "" : "s"} for all projects. Plugins can bring their own hooks and MCP servers into every project container, and these skip the confirmation an install from the Marketplace tab asks for.`,
);
}
if (preview.image_source === "custom") {
warnings.push(
`Runs every project container from a custom Docker image: ${preview.custom_image_name ?? "(no image name set)"}.`,
+3
View File
@@ -410,6 +410,9 @@ export interface SettingsImportPreview {
* every project container, without the confirm step a Marketplace-tab
* install shows, so the preview warns about them. */
global_hook_install_count: number;
/** Plugins the import installs for all projects — a plugin can bring its
* own hooks and MCP servers, and skips the same confirm step. */
global_plugin_install_count: number;
/** Marketplace account tokens the import restores to the keychain. */
marketplace_account_token_count: number;
}