feat(plugins): YAML-driven plugin catalog + Claude/Codex marketplaces#91
Merged
Merged
Conversation
Introduces a build pipeline that turns plugins.d/<name>.yml definitions
into installable plugin trees, keeping the canonical ./skills/ catalog
as the single source of truth.
- plugins.d/_defaults.yml + plugins.d/nvidia-skills.yml: hand-edited
plugin spec (curated subset, branding, capabilities)
- .github/scripts/build-plugins.{py,sh}: regenerates plugins/<name>/
(.{claude,codex}-plugin/plugin.json, skills/) and updates both
top-level marketplace.json files
- .github/workflows/validate-plugins.yml: PR drift guard
- plugins/nvidia-skills/: generated catalog plugin tree
- .claude-plugin/marketplace.json + .agents/plugins/marketplace.json:
top-level marketplaces pointing at the generated subtree
The build script supports two materialization strategies via the
`materialize_skills:` field:
copy - rsync skill content into the plugin tree. Works for Codex
and Claude install but duplicates SKILL.md.
symlink - relative symlinks back into ./skills/. No duplication and
works for Claude + npx skills, but Codex 0.132 silently
drops symlinks during `codex plugin add`.
nvidia-skills uses `symlink` on this branch to demonstrate the
no-duplication design; flip to `copy` in plugins.d/nvidia-skills.yml
when shipping for Codex local-marketplace support.
Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Mirror the Codex side, which already declares "skills": "./skills/". Claude expects an array of paths, so we emit ["./skills/"] (container path; Claude scans immediate children for SKILL.md). Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Rename the per-plugin YAML option that selects how `include_skills` show
up under `plugins/<name>/skills/` from `materialize_skills` to
`skill_files`, with values `copy | symlink`. Both modes remain supported;
this branch's purpose is to provide that choice.
`copy` stays the default in plugins.d/_defaults.yml — Codex 0.132 silently
drops symlinks during `codex plugin add`, so real files are required for
local-marketplace install. Plugins that ship to Claude / `npx skills add`
only can opt into `symlink` per-plugin to avoid SKILL.md duplication.
Touched:
- .github/scripts/build-plugins.py: read spec.get("skill_files"),
updated error message
- plugins.d/_defaults.yml: skill_files: copy
- plugins.d/nvidia-skills.yml: comment now points at skill_files (no
override; inherits copy)
- plugins.d/README.md: heading + override example
Also folds in the marketplace identity rename from another branch
(`nvidia-skills` → `nvidia-official` in both marketplace.json files) and
regenerates plugins/nvidia-skills/skills/ as real directories under the
new copy default.
Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Carry over the trailing comment block from jasonnvidia/skills@feat/plugin-marketplace describing how to add upstream CLI validation as a follow-up: Claude Code CLI `claude plugin validate` for per-plugin manifests and .claude-plugin/marketplace.json (verified 2026-05-21), and three options for Codex-side coverage given that the codex CLI has no `plugin validate` subcommand. Documentation only; no behavior change. Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Set `skill_files: symlink` in plugins.d/nvidia-skills.yml. The on-disk plugins/nvidia-skills/skills/ tree is now three relative symlinks back into ./skills/<Product>/<skill>/ instead of rsync'd copies, eliminating the duplicated SKILL.md content from the previous commit. Compatible with `claude plugin install` and `npx skills add`. NOT compatible with `codex plugin add` (Codex 0.132 silently drops symlinks during install) — flip back to `copy` to test Codex. The default in plugins.d/_defaults.yml stays `copy`; this is a per-plugin override. Signed-off-by: Jason Dudash <jdudash@nvidia.com>
The module docstring still claimed skills/ was always materialized as relative symlinks. Update it to describe the `skill_files: copy | symlink` choice (default `copy`), and refresh the curated-plugin docstring to match. No behavior change. Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Upstream's daily skills sync (7946eaa) reorganized skills/cuopt/ and removed skills/NemoClaw/ entirely. The previous three curated entries in plugins.d/nvidia-skills.yml were all broken: - skills/cuopt/cuopt-install/ → removed upstream - skills/cuopt/cuopt-numerical-optimization-api-python/ → split into api-c / api-cli / server-api-python (no Python LP wrapper anymore) - skills/NemoClaw/nemoclaw-user-get-started/ → entire NemoClaw directory removed Repoint the plugin at a single still-present skill, skills/cuopt/cuopt-routing-api-python, so the build resolves and the plugin ships symlinks consumers can actually follow. Wider re-curation (adding more cuopt-* skills, restoring NemoClaw when it returns upstream) deferred to a follow-up. Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Append skills/cuopt/cuopt-user-rules to include_skills so the plugin ships base end-user guidance (covers install / routing / LP / MILP / QP / server) alongside the existing cuopt-routing-api-python skill. Trailing-slash also normalized for consistency. Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Drop the skill_files: symlink override from plugins.d/nvidia-skills.yml so the plugin inherits skill_files: copy from _defaults.yml. Rebuild materializes the two curated cuopt skills as real directory copies (including the new skill-card.md and skill.oms.sig sidecars now shipping from upstream cuopt) instead of relative symlinks. This is the layout Codex local-marketplace install requires (codex plugin add silently drops symlinks). Trade-off: plugins/<name>/skills/ now duplicates SKILL.md content, and copy-mode plugin trees go stale whenever the daily skills sync touches a curated source path. Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Two changes so copy-mode plugins (the default) coexist with the daily
skills sync without manually rebuilding the plugin tree:
1. .github/scripts/build-plugins.py — soft-fail include_skills entries
that the daily sync can legitimately invalidate:
* include_skills path missing on disk (rename / removal)
* path not a directory (rare upstream change)
* no SKILL.md under a curated path (compliance enforcement
dropped every skill)
* duplicate skill basename across entries (rename collision)
Each emits a loud "warning: ... skipping" log line and the build
continues. Plugin ships with whatever curated skills actually
resolved. Static config errors (bad yaml, invalid skill_files mode,
etc.) still hard-fail.
2. .github/workflows/sync-skills.yml — run build-plugins.sh after the
compliance-enforcement step and before regenerate-readme, so the
sync PR ships a refreshed plugin tree alongside the skills/ updates.
Mark "plugin catalog" as a changed component when the rebuild
produced a diff so the PR summary surfaces it and the has_changes
gate fires.
Net effect:
- Copy-mode plugins stay in sync with the canonical catalog
automatically; validate-plugins.yml --check no longer trips on the
daily sync.
- Renames / compliance drops in upstream skills do not block the
sync PR — they surface as warnings and as a curation-debt entry in
plugins.d/<name>.yml that a maintainer can reconcile in a follow-up.
Signed-off-by: Jason Dudash <jdudash@nvidia.com>
Convert six previously-fatal die() sites to error-or-warning logs that let the build continue past per-plugin misconfigurations. The principle matches the expand_skill_paths soft-fail introduced in e6335f0: one bad input no longer takes down the whole catalog. Helpers: error() non-exiting stderr logger (companion to die()) read_yaml_lenient() parse-or-None YAML reader for per-plugin files Sites converted (build_plugins.py): A. read_yaml(<plugins.d/x.yml>) malformed → log + skip file B. invalid skill_files=<value> → warning + fallback to "copy" C. plugin name not kebab-case → log + skip plugin (moved into discover so the bad entry never enters the catalog dict) D. curated .skills-manifest.yml empty → warning + materialize empty skills/ tree E. missing 'name' in plugins.d file → log + skip file F. duplicate plugin name across → log + first-alphabetically plugins.d/ wins, the rest are skipped (matches expand_skill_paths) Sites kept as die() (structural / guard / manual-invocation): - read_yaml(_defaults.yml) - missing .claude-plugin/marketplace.json - missing .agents/plugins/marketplace.json - --only NAME with no match - --check drift detection Smoke-tested all six locally: each malformed input emits a loud error/warning line and the build still completes with the rest of the catalog intact. Signed-off-by: Jason Dudash <jdudash@nvidia.com>
5 tasks
sayalinvidia
added a commit
to sayalinvidia/sayali-skills-test
that referenced
this pull request
May 27, 2026
Squash-merge of test PR #42 — validates plugin catalog build OOTB against this repo's skills/ tree. validate-plugins.yml ran green pre-merge. Source: NVIDIA/skills PR #91 (feat/plugin-build-modes by @jasonnvidia) Signed-off-by: Sayali Kandarkar <skandarkar@nvidia.com>
Collaborator
|
LGTM! approved workflows to run! |
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.
Summary
Adds an NVIDIA-branded plugin layer on top of the existing
skills/catalog so the same skills can be installed via Claude marketplace (claude plugin install) and Codex marketplace (codex plugin add), without duplicating skill content or fragmenting curation.The substantive change is infrastructure (~140 lines of Python in
.github/scripts/build-plugins.py, ~30 lines added tosync-skills.yml, a new 78-linevalidate-plugins.ymlworkflow); the rest of the diff is the first plugin definition (plugins.d/nvidia-skills.yml) and its generated artifacts underplugins/nvidia-skills/.The daily skills sync continues to be the only producer of
skills/. Plugin trees are derived fromskills/plus per-plugin YAML, regenerated atomically inside the same sync run, and validated on every PR.What this adds
1.
plugins.d/— plugin catalog YAMLOne file per plugin, plus
_defaults.ymlfor shared author/license/capability fields. The first plugin defined isnvidia-skillscurating two cuopt skills.Per-plugin YAML controls:
name,display_name,description,keywords)include_skills:— list of paths underskills/)skill_files: copy | symlink— see below)marketplace_enabled.{claude,codex}: false)2.
.github/scripts/build-plugins.py— derived-artifact generatorIdempotent build script that, from
plugins.d/andskills/, regenerates:plugins/<name>/skills/<skill>/— curated subset, materialized as either real-dir copies (rsync) or relative symlinksplugins/<name>/.claude-plugin/plugin.json— Anthropic plugin manifestplugins/<name>/.codex-plugin/plugin.json— OpenAI Codex plugin manifest withinterfaceblock.claude-plugin/marketplace.jsonand.agents/plugins/marketplace.json(top-level fields preserved;plugins:array rebuilt)Modes:
skill_files:plugins/<name>/skills/copy(default)codex plugin addsilently drops symlinks during install) — confirmed empirically against codex 0.132symlink../../../skills/<Product>/<skill>npx skills addconsumers; avoids duplicated SKILL.mdThe current default (
copy) keeps every consumer happy at the cost of duplicatedSKILL.mdcontent underplugins/. Plugins can opt intosymlinkper-file when Codex isn't a target.3.
.github/workflows/validate-plugins.yml— drift guardRuns on PRs touching
plugins.d/,skills/,plugins/, eithermarketplace.json, or the build script. Re-runsbuild-plugins.sh --check, which rebuilds into the working tree and exits non-zero if the committed plugin tree drifted from the YAML + skills sources.4.
.github/workflows/sync-skills.yml— one new stepInserted between the existing
Track dropped skillsandRegenerate README tablessteps:This regenerates copy-mode plugin trees atomically in the same PR as the upstream skills update, so
validate-plugins.yml --checkis never red on a sync PR.How it all fits together
skills/remains the only source of truth that gets written — the build only derivesplugins/,.claude-plugin/marketplace.json, and.agents/plugins/marketplace.jsonfrom it.Why this design
A few decisions worth surfacing for review:
skills/is canonical,plugins/is derived. The build script never editsskills/and never touchescomponents.d/. This keeps the existing daily-sync invariant intact — no new authoritative directory the team has to remember.Soft-fail policy. Curation references that the daily sync can legitimately invalidate (rename, removal, compliance-driven SKILL.md drop, basename collision) emit warnings and skip the entry instead of dying. The plugin ships with whatever resolved; the sync PR stays mergeable; a maintainer reconciles
plugins.d/<plugin>.ymlin a follow-up. We hit this empirically when upstream cuopt restructured its skill paths during testing.Two materialization modes, copy as default. Empirical testing showed
codex plugin add(codex 0.132) silently drops symlinks during install, leaving the cached plugin'sskills/empty. To keep Codex consumers working out of the box, copy is the default. Plugins that won't ship to Codex can opt into symlink mode and avoid duplicated SKILL.md content.Marketplace identity fields are preserved, not regenerated. The build only rewrites
plugins:arrays inmarketplace.jsonfiles. Top-levelname,displayName,metadata.description,owner, etc. are hand-edited and left alone — so the marketplace identity isn't accidentally clobbered by a build run.Curated plugins (
plugins/<name>/.skills-manifest.yml) coexist. A plugin directory with a hand-edited manifest skips the catalog-build path entirely; only itsskills/tree is regenerated, and itsmarketplace.jsonentry is preserved verbatim. This leaves the door open for a non-NVIDIA-managed plugin that still wants the build system to refresh its skills tree.Testing
Local testing exercised:
build-plugins.shproducesplugins/nvidia-skills/skills/cuopt-routing-api-python/andcuopt-user-rules/cleanly in copy mode (real directories withSKILL.md+skill-card.md+skill.oms.sig).build-plugins.sh --checkis green on a clean working tree, red on hand-edits underplugins/.include_skills:path on disk (rename/removal)include_skills:path is not a directory (rare upstream change)SKILL.mdunder a curated path (compliance enforcement dropped them all)include_skills:entriesplugins.d/<x>.ymlskill_files:with an invalid value (falls back tocopy)skills:list in a curated.skills-manifest.ymlname:field in plugins.d../../../skills/<Product>/<skill>and thatclaude plugin installfollows them; confirmedcodex plugin addsilently drops them (motivatingcopyas default).nvidia-skills.ymlbetweencopyandsymlinkand verified the build cleanly converts the on-disk tree both directions.CI integration tested:
validate-plugins.yml --checkon this branch → green.What's not yet tested in production CI:
Rebuild plugin catalogstep insidesync-skills.ymlrunning against a real product-repo clone. Recommend manually triggeringsync-skills.ymlviaworkflow_dispatchonce after merge to validate end-to-end before the next cron tick.Risk assessment
ubuntu-latestdue to PyYAML install--userthen--break-system-packagesfallback; same install pattern works invalidate-plugins.ymltodayworkflow_dispatchsmoke-test recommended after mergeWhat stays guaranteed-safe:
sync-skills.ymlwas modified or removed; one step was inserted.skills/is still the only catalog-source written by sync.dco,verify-authors,team-request,request-nvskills-ci) untouched._defaults.yml, missing marketplace.json, drift-check fail.Follow-up work (deliberately out of scope)
Captured as a TODO comment block in
validate-plugins.yml:claude plugin validateper generated plugin manifest +claude plugin validate .claude-plugin/marketplace.json(verified locally against@anthropic-ai/claude-code@2.1.145; deferred to keep this PR lean).codexCLI has noplugin validatesubcommand; options are an install-as-validator dry-run, a hand-rolled JSON schema check, or skipping (relying on the build's schema parity).Other follow-ups, not required for this PR:
plugins-curation-debttracking issue (mirroring the existingmissing-compliancepattern) when soft-fail warnings fire, so curation gaps don't get lost in workflow logs.Test plan
build-plugins.shproduces clean output in copy modebuild-plugins.shproduces clean output in symlink modebuild-plugins.sh --checkgreen on clean tree, red on hand-editsworkflow_dispatchrun ofsync-skills.ymlafter merge confirms the new step works end-to-end against real product repos