@@ -48,6 +48,11 @@ interface PluginResolvedSource {
4848 version ?: string ;
4949}
5050
51+ interface PluginPackageManifestEntry {
52+ name ?: string ;
53+ source ?: string ;
54+ }
55+
5156interface LegacyMarkerFile {
5257 pluginId : string ;
5358 pluginVersion ?: string ;
@@ -191,6 +196,74 @@ async function resolvePluginMarketplaceRoot(
191196 return null ;
192197}
193198
199+ async function hasPluginContent ( root : string ) : Promise < boolean > {
200+ const candidates = [
201+ path . join ( root , 'skills' ) ,
202+ path . join ( root , 'commands' ) ,
203+ path . join ( root , 'agents' ) ,
204+ ] ;
205+
206+ for ( const candidate of candidates ) {
207+ if ( await dirExists ( candidate ) ) return true ;
208+ }
209+
210+ return false ;
211+ }
212+
213+ async function readPluginPackageManifestEntries (
214+ installPath : string ,
215+ ) : Promise < PluginPackageManifestEntry [ ] > {
216+ const packageJsonPath = path . join ( installPath , 'package.json' ) ;
217+
218+ try {
219+ const raw = JSON . parse ( await fs . readFile ( packageJsonPath , 'utf8' ) ) as {
220+ plugins ?: unknown ;
221+ } ;
222+ if ( ! Array . isArray ( raw . plugins ) ) return [ ] ;
223+
224+ return raw . plugins . filter (
225+ ( entry ) : entry is PluginPackageManifestEntry =>
226+ Boolean ( entry ) && typeof entry === 'object' ,
227+ ) ;
228+ } catch {
229+ return [ ] ;
230+ }
231+ }
232+
233+ async function resolveInstalledPluginSourceRoot (
234+ pluginId : string ,
235+ installPath : string ,
236+ ) : Promise < string | null > {
237+ const manifestEntries = await readPluginPackageManifestEntries ( installPath ) ;
238+ const parsedPluginId = parsePluginId ( pluginId ) ;
239+ const matchingEntry =
240+ manifestEntries . find (
241+ ( entry ) => entry . name === parsedPluginId ?. pluginName ,
242+ ) ?? ( manifestEntries . length === 1 ? manifestEntries [ 0 ] : null ) ;
243+
244+ const rawSource =
245+ typeof matchingEntry ?. source === 'string' &&
246+ matchingEntry . source . trim ( ) !== ''
247+ ? matchingEntry . source
248+ : '.' ;
249+
250+ const sourceRoot = path . resolve ( installPath , rawSource ) ;
251+ const normalizedInstallPath = path . resolve ( installPath ) ;
252+ const isWithinInstallPath =
253+ sourceRoot === normalizedInstallPath ||
254+ sourceRoot . startsWith ( normalizedInstallPath + path . sep ) ;
255+
256+ if ( isWithinInstallPath && ( await hasPluginContent ( sourceRoot ) ) ) {
257+ return sourceRoot ;
258+ }
259+
260+ if ( await hasPluginContent ( installPath ) ) {
261+ return installPath ;
262+ }
263+
264+ return null ;
265+ }
266+
194267export function resolvePluginInstall (
195268 pluginId : string ,
196269 projectRoot : string ,
@@ -772,13 +845,27 @@ export async function syncClaudePluginsToSkillsDirs(
772845 const unresolvedEnabled = new Set < string > ( ) ;
773846
774847 for ( const pluginId of enabledPlugins ) {
775- const pluginRoot = await resolvePluginMarketplaceRoot ( pluginId , claudeDir ) ;
848+ const resolved = index
849+ ? resolvePluginInstall ( pluginId , projectRoot , index )
850+ : null ;
851+ const marketplaceRoot = await resolvePluginMarketplaceRoot (
852+ pluginId ,
853+ claudeDir ,
854+ ) ;
855+ const pluginRoot =
856+ ( marketplaceRoot && ( await hasPluginContent ( marketplaceRoot ) )
857+ ? marketplaceRoot
858+ : null ) ??
859+ ( resolved
860+ ? await resolveInstalledPluginSourceRoot ( pluginId , resolved . installPath )
861+ : null ) ;
862+
776863 if ( ! pluginRoot ) {
777864 unresolvedEnabled . add ( pluginId ) ;
778865 const hasIndexEntry = Boolean ( index ?. plugins ?. [ pluginId ] ?. length ) ;
779866 if ( hasIndexEntry ) {
780867 logVerboseInfo (
781- `[plugins] Enabled plugin has no marketplace content, skipping: ${ pluginId } ` ,
868+ `[plugins] Enabled plugin has no syncable content, skipping: ${ pluginId } ` ,
782869 verbose ,
783870 dryRun ,
784871 ) ;
@@ -788,10 +875,6 @@ export async function syncClaudePluginsToSkillsDirs(
788875 continue ;
789876 }
790877
791- const resolved = index
792- ? resolvePluginInstall ( pluginId , projectRoot , index )
793- : null ;
794-
795878 resolvedSources . push ( {
796879 pluginId,
797880 pluginRoot,
0 commit comments