diff --git a/src/features/claude-code-plugin-loader/discovery.test.ts b/src/features/claude-code-plugin-loader/discovery.test.ts index dab25cd22..63e2340a6 100644 --- a/src/features/claude-code-plugin-loader/discovery.test.ts +++ b/src/features/claude-code-plugin-loader/discovery.test.ts @@ -32,6 +32,41 @@ describe("discoverInstalledPlugins", () => { } }) + it("preserves scoped package name from npm plugin keys", () => { + //#given + const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string + const installPath = join(createTemporaryDirectory("omo-plugin-install-"), "@myorg", "my-plugin") + mkdirSync(installPath, { recursive: true }) + + const databasePath = join(pluginsHome, "installed_plugins.json") + writeFileSync( + databasePath, + JSON.stringify({ + version: 2, + plugins: { + "@myorg/my-plugin@1.0.0": [ + { + scope: "user", + installPath, + version: "1.0.0", + installedAt: "2026-03-25T00:00:00Z", + lastUpdated: "2026-03-25T00:00:00Z", + }, + ], + }, + }), + "utf-8", + ) + + //#when + const discovered = discoverInstalledPlugins() + + //#then + expect(discovered.errors).toHaveLength(0) + expect(discovered.plugins).toHaveLength(1) + expect(discovered.plugins[0]?.name).toBe("@myorg/my-plugin") + }) + it("derives package name from file URL plugin keys", () => { //#given const pluginsHome = process.env.CLAUDE_PLUGINS_HOME as string diff --git a/src/features/claude-code-plugin-loader/discovery.ts b/src/features/claude-code-plugin-loader/discovery.ts index 217e7211e..d4d9bb577 100644 --- a/src/features/claude-code-plugin-loader/discovery.ts +++ b/src/features/claude-code-plugin-loader/discovery.ts @@ -81,7 +81,14 @@ function loadPluginManifest(installPath: string): PluginManifest | null { function derivePluginNameFromKey(pluginKey: string): string { const keyWithoutSource = pluginKey.startsWith("npm:") ? pluginKey.slice(4) : pluginKey - const versionSeparator = keyWithoutSource.lastIndexOf("@") + + let versionSeparator: number + if (keyWithoutSource.startsWith("@")) { + const scopeEnd = keyWithoutSource.indexOf("/") + versionSeparator = scopeEnd > 0 ? keyWithoutSource.indexOf("@", scopeEnd) : -1 + } else { + versionSeparator = keyWithoutSource.lastIndexOf("@") + } const keyWithoutVersion = versionSeparator > 0 ? keyWithoutSource.slice(0, versionSeparator) : keyWithoutSource if (keyWithoutVersion.startsWith("file://")) { @@ -92,6 +99,10 @@ function derivePluginNameFromKey(pluginKey: string): string { } } + if (keyWithoutVersion.startsWith("@") && keyWithoutVersion.includes("/")) { + return keyWithoutVersion + } + if (keyWithoutVersion.includes("/") || keyWithoutVersion.includes("\\")) { return basename(keyWithoutVersion) }