From 1c12925c9e659f3c0e60c6bcfb47a26905aa791a Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sun, 28 Dec 2025 02:33:14 +0900 Subject: [PATCH] fix(plugin-loader): support installed_plugins.json v1 format for backward compatibility (#288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The installed_plugins.json file has two versions: - v1: plugins stored as direct objects - v2: plugins stored as arrays Use discriminated union types (InstalledPluginsDatabaseV1/V2) for proper type narrowing based on version field. 🤖 GENERATED WITH ASSISTANCE OF [OhMyOpenCode](https://github.com/code-yeongyu/oh-my-opencode) --- .../claude-code-plugin-loader/loader.ts | 15 +++++++++--- .../claude-code-plugin-loader/types.ts | 23 +++++++++++++++---- 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/features/claude-code-plugin-loader/loader.ts b/src/features/claude-code-plugin-loader/loader.ts index b21a839..72eacc3 100644 --- a/src/features/claude-code-plugin-loader/loader.ts +++ b/src/features/claude-code-plugin-loader/loader.ts @@ -14,6 +14,7 @@ import type { AgentFrontmatter } from "../claude-code-agent-loader/types" import type { ClaudeCodeMcpConfig, McpServerConfig } from "../claude-code-mcp-loader/types" import type { InstalledPluginsDatabase, + PluginInstallation, PluginManifest, LoadedPlugin, PluginLoadResult, @@ -134,6 +135,15 @@ function isPluginEnabled( return true } +function extractPluginEntries( + db: InstalledPluginsDatabase +): Array<[string, PluginInstallation | undefined]> { + if (db.version === 1) { + return Object.entries(db.plugins).map(([key, installation]) => [key, installation]) + } + return Object.entries(db.plugins).map(([key, installations]) => [key, installations[0]]) +} + export function discoverInstalledPlugins(options?: PluginLoaderOptions): PluginLoadResult { const db = loadInstalledPlugins() const settings = loadClaudeSettings() @@ -147,15 +157,14 @@ export function discoverInstalledPlugins(options?: PluginLoaderOptions): PluginL const settingsEnabledPlugins = settings?.enabledPlugins const overrideEnabledPlugins = options?.enabledPluginsOverride - for (const [pluginKey, installations] of Object.entries(db.plugins)) { - if (!installations || installations.length === 0) continue + for (const [pluginKey, installation] of extractPluginEntries(db)) { + if (!installation) continue if (!isPluginEnabled(pluginKey, settingsEnabledPlugins, overrideEnabledPlugins)) { log(`Plugin disabled: ${pluginKey}`) continue } - const installation = installations[0] const { installPath, scope, version } = installation if (!existsSync(installPath)) { diff --git a/src/features/claude-code-plugin-loader/types.ts b/src/features/claude-code-plugin-loader/types.ts index 522f2a7..34e0193 100644 --- a/src/features/claude-code-plugin-loader/types.ts +++ b/src/features/claude-code-plugin-loader/types.ts @@ -20,14 +20,29 @@ export interface PluginInstallation { isLocal?: boolean } +/** + * Installed plugins database v1 (legacy) + * plugins stored as direct objects + */ +export interface InstalledPluginsDatabaseV1 { + version: 1 + plugins: Record +} + +/** + * Installed plugins database v2 (current) + * plugins stored as arrays + */ +export interface InstalledPluginsDatabaseV2 { + version: 2 + plugins: Record +} + /** * Installed plugins database structure * Located at ~/.claude/plugins/installed_plugins.json */ -export interface InstalledPluginsDatabase { - version: number - plugins: Record -} +export type InstalledPluginsDatabase = InstalledPluginsDatabaseV1 | InstalledPluginsDatabaseV2 /** * Plugin author information