@@ -143,11 +143,30 @@ func installPlugin(pluginArg string, scope settings.Scope, projectPath string) e
143143 // Check if plugin is installable via plum
144144 if ! pluginInfo .Installable {
145145 fmt .Printf ("Cannot install %s: %s\n \n " , fullName , pluginInfo .InstallabilityReason )
146- fmt .Println ("This plugin requires a different installation method." )
147- fmt .Println ("Check the plugin's homepage for installation instructions." )
146+ if pluginInfo .IsIncomplete {
147+ fmt .Println ("This plugin doesn't have a standard plugin manifest. You can try:" )
148+ fmt .Println ()
149+ fmt .Println (" 1. Refresh your marketplace in case it was recently updated:" )
150+ fmt .Println (" plum marketplace refresh" )
151+ fmt .Println ()
152+ fmt .Println (" 2. Use the plugin directly from the marketplace directory" )
153+ fmt .Println (" (Claude Code can access skills/commands without installation)" )
154+ } else {
155+ fmt .Println ("This plugin requires a different installation method." )
156+ fmt .Println ("Check the plugin's homepage for installation instructions." )
157+ }
148158 return fmt .Errorf ("plugin not installable via plum" )
149159 }
150160
161+ // Check if already installed in the requested scope
162+ scopeSettings , err := settings .LoadSettings (scope , projectPath )
163+ if err == nil {
164+ if _ , exists := scopeSettings .EnabledPlugins [fullName ]; exists {
165+ fmt .Printf ("%s is already installed in %s scope\n " , fullName , scope )
166+ return nil
167+ }
168+ }
169+
151170 fmt .Printf ("Installing %s...\n " , fullName )
152171
153172 // Get cache directory
@@ -156,9 +175,17 @@ func installPlugin(pluginArg string, scope settings.Scope, projectPath string) e
156175 return fmt .Errorf ("failed to get cache directory: %w" , err )
157176 }
158177
159- // Download plugin files to cache
160- if err := downloadPluginToCache (pluginInfo , cacheDir ); err != nil {
161- return fmt .Errorf ("failed to download plugin: %w" , err )
178+ // Check if cache already exists with valid plugin.json
179+ // This allows installation to succeed even if remote download fails
180+ cacheValid := isValidPluginCache (cacheDir )
181+
182+ // Try to download plugin files to cache (skip if cache is valid)
183+ if ! cacheValid {
184+ if err := downloadPluginToCache (pluginInfo , cacheDir ); err != nil {
185+ return fmt .Errorf ("failed to download plugin: %w" , err )
186+ }
187+ } else {
188+ fmt .Println ("Using cached plugin files" )
162189 }
163190
164191 // Register in installed_plugins_v2.json
@@ -184,6 +211,7 @@ type pluginSearchResult struct {
184211 Source string // Path within marketplace
185212 Installable bool // Whether plum can install this plugin
186213 InstallabilityReason string // Human-readable reason if not installable
214+ IsIncomplete bool // True if plugin is missing required files
187215}
188216
189217// findPluginInMarketplaces searches for a plugin across all known marketplaces
@@ -209,6 +237,7 @@ func findPluginInMarketplaces(pluginName, marketplaceFilter string) (*pluginSear
209237 Source : p .Source ,
210238 Installable : p .Installable (),
211239 InstallabilityReason : p .InstallabilityReason (),
240+ IsIncomplete : p .IsIncomplete ,
212241 })
213242 }
214243 }
@@ -229,6 +258,17 @@ func findPluginInMarketplaces(pluginName, marketplaceFilter string) (*pluginSear
229258 return matches [0 ], nil
230259}
231260
261+ // isValidPluginCache checks if a cache directory contains a valid plugin.json
262+ func isValidPluginCache (cacheDir string ) bool {
263+ pluginJSONPath := filepath .Join (cacheDir , ".claude-plugin" , "plugin.json" )
264+ info , err := os .Stat (pluginJSONPath )
265+ if err != nil {
266+ return false
267+ }
268+ // Ensure it's a file with non-zero size
269+ return ! info .IsDir () && info .Size () > 0
270+ }
271+
232272// pluginCacheDir returns the path to cache a plugin
233273// Path: ~/.claude/plugins/cache/<marketplace>/<plugin>/
234274func pluginCacheDir (marketplaceName , pluginName string ) (string , error ) {
0 commit comments