Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: pages
# Publishes site/ (the catalogue at https://stylusnexus.github.io/agent-plugins/)
# to GitHub Pages whenever it changes on main. ci.yml's check-readme.py keeps the
# catalogue's packs, skills, and counts in step with plugins/ on disk.
on:
push:
branches: [main]
paths: ["site/**", ".github/workflows/pages.yml"]
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: false

jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/configure-pages@v6
- uses: actions/upload-pages-artifact@v5
with:
path: site
- id: deployment
uses: actions/deploy-pages@v5
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Windsurf, Zed, opencode, Cline, Continue, Hermes and others) through the Skills
Every skill is repo-agnostic: it detects your repository's conventions rather than assuming its own,
and defers to a repo-local version of itself when your project defines one.

**[Browse the catalogue →](https://claude.ai/code/artifact/667c2158-4833-4535-a5c5-a5597dea8fea)** — every pack, what it's for, and how to install it on your agent.
**[Browse the catalogue →](https://stylusnexus.github.io/agent-plugins/)** — every pack, what it's for, and how to install it on your agent.

> ⚠️ **Trust before you install.** Plugins run code on your machine. Review a plugin's source before
> installing it. Everything here is open-source — links are in the table below.
Expand Down Expand Up @@ -484,6 +484,7 @@ agent-plugins/
│ ├── gen-catalog.py # writes the skill catalogue and skills.sh.json
│ ├── check-links.py # relative links and anchors resolve
│ └── check-private-terms.sh # nothing private ships in a skill
├── site/ # the catalogue page, published to GitHub Pages
├── skills.sh.json # skills.sh groupings, one per pack
├── LICENSE
└── README.md
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"github-copilot",
"developer-tools"
],
"homepage": "https://github.com/stylusnexus/agent-plugins#readme",
"homepage": "https://stylusnexus.github.io/agent-plugins/",
"bugs": {
"url": "https://github.com/stylusnexus/agent-plugins/issues"
},
Expand Down
35 changes: 34 additions & 1 deletion scripts/check-private-terms.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# has no business in this PUBLIC MIT marketplace's shipped text.
#
# Scope is plugins/*/skills/**, plugins/*/agents/**, and plugins/*/README.md.
# The root README.md and site/ (the GitHub Pages catalogue) are public too, but
# they name this org's repos and publisher on purpose, so they're checked
# against the private overlay only (failures and warnings), like docs/ below.
# plugin.json and LICENSE are never scanned -- the publisher name there is
# legitimate and the whole point of the LICENSE file.
#
Expand Down Expand Up @@ -153,6 +156,36 @@ elif [ -d docs ]; then
echo "no private-terms overlay available -- skipping docs/ check" >&2
fi

# Public pages outside plugins/: the root README and the Pages site. Private
# overlay terms fail; warn: vocabulary warns. Public-list terms don't apply here
# (these pages link this org's repos by design).
PAGES=""
[ -f README.md ] && PAGES="README.md"
[ -d site ] && PAGES="$PAGES site"
if [ -n "$PAGES" ] && [ -s "$OVERLAY_FILE" ]; then
while IFS= read -r pattern; do
case "$pattern" in
''|'#'*) continue ;;
warn:*)
# shellcheck disable=SC2086
hits=$(grep -rniIE "${pattern#warn:}" $PAGES 2>/dev/null || true)
if [ -n "$hits" ]; then
echo "WARNING: private-product vocabulary (from the private list) found; check these lines:" >&2
report_warning "$hits"
warned=1
fi
continue ;;
esac
# shellcheck disable=SC2086
hits=$(grep -rniIE "$pattern" $PAGES 2>/dev/null || true)
if [ -n "$hits" ]; then
echo "PRIVATE TERM (from the private list) found in README.md or site/ at:" >&2
echo "$hits" | cut -d: -f1,2 | sed 's/^/ /' >&2
fail=1
fi
done < "$OVERLAY_FILE"
fi

[ "$warned" = 1 ] && echo "warnings above are not failures: confirm each line is generic, or rewrite it" >&2
[ "$fail" = 0 ] && echo "no private terms found in plugins/*/skills, plugins/*/agents, plugins/*/README.md, or docs/"
[ "$fail" = 0 ] && echo "no private terms found in plugins/*/skills, plugins/*/agents, plugins/*/README.md, docs/, README.md, or site/"
exit "$fail"
48 changes: 47 additions & 1 deletion scripts/check-readme.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
3. Each plugins/<pack>/README.md must mention every skill under that pack's
skills/ directory (found as a table cell that is exactly one backticked
name), and mention no skill that doesn't exist.
4. The GitHub Pages catalogue (site/index.html) must have a card for every
pack with the right skill count and one chip per skill, and its headline
plugin and skill counts must match the README's.

The table format is read from the README rather than hardcoded (look for the
header row whose first cell is "Pack" / "Plugin"), so formatting can move
Expand Down Expand Up @@ -219,15 +222,58 @@ def check_plugin_readmes():
errors.append(f"{readme}: table(s) mention skill(s) not in this pack: {sorted(extra)}")


SITE_INDEX = os.path.join("site", "index.html")


def check_site():
if not os.path.isfile(SITE_INDEX):
return
with open(SITE_INDEX, encoding="utf-8") as fh:
html = fh.read()
cards = {}
for art in re.findall(r'<article class="plugin">(.*?)</article>', html, re.S):
m = re.search(r'class="plugin-name">(?:<a [^>]*>)?([a-z0-9-]+)', art)
if m:
cards[m.group(1)] = art
total = 0
for pack in sorted(os.listdir(PLUGINS_DIR)):
real = actual_skills(pack)
if not real:
continue
total += len(real)
art = cards.get(pack)
if art is None:
errors.append(f"{SITE_INDEX}: no card for pack '{pack}'")
continue
n = re.search(r'<span class="plugin-n">(\d+) skills', art)
if not n or int(n.group(1)) != len(real):
errors.append(f"{SITE_INDEX}: card '{pack}' should say {len(real)} skills")
chips = set(re.findall(r'<span class="chip">([a-z0-9-]+)</span>', art))
if chips != set(real):
errors.append(
f"{SITE_INDEX}: card '{pack}' chips differ from plugins/{pack}/skills/: "
f"missing {sorted(set(real) - chips)}, extra {sorted(chips - set(real))}"
)
with open(ROOT_README, encoding="utf-8") as fh:
headline = re.search(r'\*\*(\d+)\s+plugins?,\s*(\d+)\s+skills?\*\*', fh.read())
site_plugins = re.search(r'<b>(\d+)</b><span>plugins</span>', html)
site_skills = re.search(r'<b>(\d+)</b><span>skills</span>', html)
if not site_skills or int(site_skills.group(1)) != total:
errors.append(f"{SITE_INDEX}: headline skill count should be {total}")
if headline and (not site_plugins or site_plugins.group(1) != headline.group(1)):
errors.append(f"{SITE_INDEX}: headline plugin count should be {headline.group(1)}")


def main():
check_root_readme()
check_plugin_readmes()
check_site()
if errors:
print(f"{len(errors)} README/skill-set mismatch(es) found:\n", file=sys.stderr)
for e in errors:
print(f" {e}", file=sys.stderr)
return 1
print("README pack tables, plugin READMEs, and headline counts all match plugins/ on disk")
print("README pack tables, plugin READMEs, headline counts, and the Pages catalogue all match plugins/ on disk")
return 0


Expand Down
Empty file added site/.nojekyll
Empty file.
Binary file added site/img/social.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading