feat(scanner): add plugin marketplace skill discovery (#80)#149
Merged
feat(scanner): add plugin marketplace skill discovery (#80)#149
Conversation
Scan ~/.claude/plugins/marketplaces/ for skills installed via the Claude
plugin marketplace system, alongside traditionally installed skills.
- Add scanPluginMarketplaces() with recursive SKILL.md finder that handles
variable nesting depths (flat skills/ and nested plugins/.../skills/ layouts)
- Integrate into scanAllSkills() for global and both scopes
- Add optional marketplace field to SkillInfo for source attribution
- Skills appear with provider="plugin" and providerLabel="Plugin ({name})"
- Add 7 tests covering both path layouts, multi-marketplace, and scope filtering
- Rename misleading test title ("is included in scanAllSkills") to accurately
describe what it tests (scanPluginMarketplaces metadata)
- Add pluginBaseDir param to scanAllSkills so tests can inject a temp dir
instead of hitting the real ~/.claude/plugins/marketplaces path
- Add integration tests: scanAllSkills includes plugin skills (global scope),
excludes them (project scope), and deduplicates by realPath when a skill
appears in both a regular provider path and the plugin marketplace
- Parallelize provider scan and plugin marketplace scan in scanAllSkills
Use lstat() instead of stat() and skip symlinked entries to avoid infinite recursion from symlink cycles in plugin marketplace dirs.
- scanPluginMarketplaces: remove unreachable lstat/readlink block; findSkillDirs() guarantees non-symlink paths so isSymlink is always false — inline the constants directly - scanner.test.ts: rewrite dedup test to call scanAllSkills() end-to-end via customPaths + pluginBaseDir so the real dedup logic is exercised, not a manual reimplementation
) Cover previously untested paths: - symlink-skip safety (the cycle-prevention fix) - non-directory entries at marketplace level - multiple skills in a single marketplace - dirName fallback when SKILL.md has no name field - rich frontmatter fields (creator, license, compatibility, effort, allowed-tools) - isSymlink/symlinkTarget are always false/null for marketplace skills - empty marketplace directory - scanAllSkills with both scope includes plugin skills - path/originalPath/realPath values are set correctly
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #80
Summary
scanPluginMarketplaces()inscanner.tsthat recursively discovers skills installed via the Claude plugin marketplacescanAllSkills()for global and both scopesprovider: "plugin"andproviderLabel: "Plugin ({marketplace-name})"Approach
Claude's plugin marketplace installs skills under
~/.claude/plugins/marketplaces/with two known layout patterns:{marketplace}/skills/{skill}/SKILL.md{marketplace}/plugins/{plugin}/skills/{skill}/SKILL.mdRather than hard-coding path patterns, the implementation walks directories recursively until it finds a
SKILL.mdfile, which cleanly handles both layouts and any future depth variations.Changes
src/scanner.tsfindSkillDirs()recursive helper,scanPluginMarketplaces()(exported), integrate intoscanAllSkills()src/utils/types.tsmarketplace?: stringfield toSkillInfosrc/scanner.test.tsTest Results
44 scanner tests pass (37 existing + 7 new), 0 failures.
Note: pre-existing failures in
publisher.test.tsandcli.test.tsare unrelated to this change (verified on main branch before branching).Acceptance Criteria
~/.claude/plugins/marketplaces/directories to discover plugin-installed skillsasm listoutput, distinguished from traditionally installed skills (provider: "plugin",providerLabel: "Plugin ({marketplace})")asm info {skill}works for plugin-installed skills and shows the marketplace source (location: "global-plugin-{marketplace}"andmarketplacefield)~/.claude/settings.local.jsonhas nopluginSettingskey; flagged as future enhancement