
Marketplace Sync
- 2 installs
- 1 repo stars
- Updated July 11, 2026
- simonlee2/claude-plugins
Updates a GitHub Pages plugin marketplace (docs/plugins.json) by scanning plugin metadata and generating icons, taglines, and categories.
About
Syncs a GitHub Pages marketplace by scanning plugin directories for metadata via a Python script, then generating marketing copy like icons, taglines, and categories. A developer uses it to keep docs/plugins.json in sync after adding or updating plugins.
- Scans plugins/* for commands, skills, and agents metadata
- Generates icons, taglines, and categories while preserving unchanged copy
Marketplace Sync by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,839 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/simonlee2/claude-plugins --skill marketplace-syncAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 1 |
| Last updated | July 11, 2026 |
| Repository | simonlee2/claude-plugins ↗ |
What it does
Updates a GitHub Pages plugin marketplace (docs/plugins.json) by scanning plugin metadata and generating icons, taglines, and categories.
Files
Marketplace Sync
Overview
This skill updates the GitHub Pages marketplace (docs/plugins.json) by intelligently combining automated scanning with AI-generated marketing copy. It extracts technical metadata from plugin files, then generates compelling icons, taglines, and categories based on the plugin's purpose and capabilities.
When to Use This Skill
Use this skill when:
- User asks to "update the marketplace"
- User asks to "refresh the github page" or "sync the website"
- User asks to "sync plugins.json" or "update docs/plugins.json"
- After making changes to plugins that should be reflected on the website
- After adding new plugins to the repository
- After updating plugin versions or components
How It Works
Phase 1: Automated Scanning
Run scripts/sync-marketplace.py to extract technical metadata:
1. Scan plugin directories (plugins/*/) for all installed plugins 2. Read metadata from each plugin's .claude-plugin/plugin.json 3. Extract component information:
- Commands from
commands/*.mdfiles - Skills from
skills/*/SKILL.mdfiles - Agents from
agents/*/AGENT.mdfiles
4. Preserve existing marketing copy for unchanged plugins 5. Report changes: Show what was added, updated, or removed
Phase 2: Intelligent Marketing Copy Generation
After running the script, analyze the results and generate compelling marketing copy:
For new plugins: 1. Read the plugin's description and SKILL.md files to understand its purpose 2. Generate an appropriate icon emoji that visually represents the plugin's function 3. Write a compelling tagline (under 60 characters) that captures the value proposition 4. Assign relevant categories based on the plugin's domain and use cases 5. Document any requirements (API keys, environment variables)
For updated plugins: 1. Review what changed (version bumps, new components, updated descriptions) 2. Consider if the existing marketing copy is still accurate 3. Update taglines or categories if the plugin's scope has expanded 4. Preserve marketing copy if changes are minor (version bumps, small tweaks)
Phase 3: Update and Commit
Write the updated docs/plugins.json with both technical metadata and polished marketing copy.
Usage
Intelligent Update Workflow
When the user asks to "update the marketplace", follow this workflow:
1. Run the sync script:
python3 skills/marketplace-sync/scripts/sync-marketplace.py2. Analyze the output:
➕ New plugin: data-analyzer
➖ Removed plugin: old-plugin
📝 git-workflow: skills changed from 1 to 23. For new plugins:
- Read the plugin's files to understand its purpose
- Generate appropriate icon, tagline, and categories
- Update
docs/plugins.jsonwith the marketing copy
4. For removed plugins:
- Confirm the plugin directory was intentionally deleted
- Note that it will be removed from the marketplace automatically
- No action needed (script handles removal)
5. For updated plugins:
- Review the changes (new components, version bumps)
- Assess if marketing copy needs updating
- Update if scope/purpose has changed significantly
6. Show the user what you generated:
- Explain the icon choice for new plugins
- Share the tagline and categories
- Confirm removals were intentional
- Ask for feedback if unsure
Example: New Plugin Detected
$ python3 skills/marketplace-sync/scripts/sync-marketplace.py
➕ New plugin: data-analyzerYour response: "I've detected a new plugin called 'data-analyzer'. Let me read its documentation and generate marketing copy..."
[Read plugin.json and SKILL.md files]
"Based on the plugin's purpose (analyzing data patterns), I've generated:
- Icon: 📊 (represents data/analytics)
- Tagline: 'Discover patterns and insights in your data'
- Categories: ['data', 'analytics', 'productivity']
Updating the marketplace now..."
[Update docs/plugins.json with generated copy]
What Gets Updated Automatically
From plugin files (auto-extracted):
- Plugin name, version, author
- Plugin description
- Component names and descriptions
- Component counts (commands, skills, agents)
- Installation command
Preserved from existing data (manual marketing copy):
- Plugin icons (emoji)
- Plugin taglines
- Plugin categories
- Requirements (API keys, etc.)
- Skill features (bullet points)
- Skill examples
Generating Marketing Copy for New Plugins
When the script detects a new plugin with default values (📦 icon, generic tagline), intelligently generate better marketing copy:
Step 1: Understand the Plugin Read the plugin's materials to understand its purpose:
- Plugin description from
plugin.json - SKILL.md files to understand capabilities
- Command descriptions to see functionality
- Examples and use cases from documentation
Step 2: Choose an Appropriate Icon Select an emoji that visually represents the plugin's function:
Examples:
- Git/workflow tools: 🔧 (wrench), 🔀 (shuffle), 📋 (clipboard)
- Creative/AI tools: 🎨 (palette), ✨ (sparkles), 🖼️ (framed picture)
- Data/analytics: 📊 (chart), 🔍 (magnifying glass), 📈 (trending up)
- Security/validation: 🔒 (lock), ✅ (checkmark), 🛡️ (shield)
- Documentation: 📚 (books), 📝 (memo), 📖 (open book)
- DevOps/automation: 🤖 (robot), ⚙️ (gear), 🚀 (rocket)
Step 3: Write a Compelling Tagline Create a benefit-focused tagline under 60 characters:
Good taglines:
- Focus on the benefit, not just what it is
- Use active, engaging language
- Capture the unique value proposition
- "Streamline your git operations with intelligent automation" ✅
- "AI-powered image generation and transformation" ✅
Avoid:
- Generic descriptions that could apply to anything
- "Git workflow automation tools" ❌ (too dry)
- "Tools for images" ❌ (too vague)
Step 4: Assign Relevant Categories Choose 2-4 categories that help users find the plugin:
Common categories:
productivity,workflow,automationgit,version-control,githubai,creative,images,generationtesting,quality,validationdocumentation,writing,contentsecurity,privacy,compliancedata,analytics,reporting
Step 5: Document Requirements List any setup requirements:
- Environment variables (API keys, tokens)
- External services or accounts needed
- System dependencies
- Configuration prerequisites
Step 6: Update the JSON After generating marketing copy, read the current docs/plugins.json, update the specific plugin entry with your generated content, and write the file back.
Understanding the Output
The script reports changes in this format:
🔍 Scanning plugins...
📊 Detected 3 change(s):
➕ New plugin: My Plugin
🔄 git-workflow: 1.0.0 → 1.1.0
📝 creative-tools: skills changed from 1 to 2
💾 Writing to /path/to/docs/plugins.json...
✅ Marketplace data updated successfully!Change indicators:
➕ New plugin- A new plugin was discovered inplugins/➖ Removed plugin- A plugin directory was deleted (will be removed from marketplace)🔄 Version change- Plugin version was updated inplugin.json📝 Component change- Number of commands/skills/agents changed
Handling Removals: When a plugin directory is deleted from plugins/, the script: 1. Detects it's missing from the new scan 2. Reports ➖ Removed plugin: PluginName 3. Removes it from docs/plugins.json automatically 4. The marketplace site will no longer show the removed plugin
Workflow Integration
Before Pushing to GitHub
After making plugin changes, update the marketplace before committing:
# 1. Make plugin changes
# 2. Update the marketplace
python skills/marketplace-sync/scripts/sync-marketplace.py
# 3. Review changes
git diff docs/plugins.json
# 4. Commit everything together
git add .
git commit -m "Add new feature and update marketplace"
git pushAfter Adding a New Plugin
When adding a new plugin to the repository:
# 1. Create plugin structure
mkdir -p plugins/my-plugin/{.claude-plugin,skills,commands}
# 2. Add plugin.json and components
# ... create your plugin files ...
# 3. Sync marketplace (auto-discovers new plugin)
python skills/marketplace-sync/scripts/sync-marketplace.py
# 4. Customize marketing copy in docs/plugins.json
# Edit: icon, tagline, categories
# 5. Sync again to verify preservation
python skills/marketplace-sync/scripts/sync-marketplace.py
# 6. Commit
git add .
git commit -m "Add my-plugin to marketplace"Script Details
Location
scripts/sync-marketplace.py
How It Scans Plugins
Plugin metadata from .claude-plugin/plugin.json:
{
"name": "plugin-name",
"description": "Plugin description",
"version": "1.0.0",
"author": {"name": "Simon"}
}Commands from commands/*.md frontmatter:
---
description: Command description here
---Skills from skills/*/SKILL.md frontmatter:
---
name: skill-name
description: Skill description here
---Agents from agents/*/AGENT.md frontmatter:
---
name: agent-name
description: Agent description here
---Data Merging Strategy
1. Load existing docs/plugins.json to preserve marketing copy 2. Scan plugins to extract fresh technical metadata 3. Merge by plugin ID:
- Use scanned data for: name, version, description, components
- Preserve existing data for: icon, tagline, categories, requirements
4. Write updated docs/plugins.json
This ensures marketing copy survives updates while technical metadata stays current.
Best Practices
Regular Syncing
Run the sync after any plugin changes:
- ✅ After updating plugin versions
- ✅ After adding/removing components
- ✅ After changing descriptions
- ✅ Before creating commits
Marketing Copy Maintenance
Keep marketing copy fresh:
- Choose distinctive emojis that represent each plugin
- Write compelling taglines (under 60 characters)
- Add relevant categories for filtering
- Document requirements (API keys, environment setup)
Version Management
When bumping plugin versions: 1. Update plugin.json version field 2. Run sync script (auto-detects version change) 3. Review changes in git diff 4. Commit together with code changes
Troubleshooting
Script shows no changes but I made updates:
- Verify you edited the correct files (plugin.json, SKILL.md frontmatter)
- Check that frontmatter uses correct YAML format (
---delimiters) - Ensure files are in expected directories
New plugin not detected:
- Verify
.claude-plugin/plugin.jsonexists - Check that plugin directory is in
plugins/ - Ensure plugin.json has valid JSON syntax
Marketing copy was overwritten:
- The script preserves: icon, tagline, categories, requirements
- If overwritten, check if plugin ID changed (treated as new plugin)
- Manually restore from git history and re-run sync
Component counts incorrect:
- Verify SKILL.md files are in
skills/*/SKILL.mdformat - Check command files are in
commands/*.mdformat - Ensure frontmatter has
---delimiters
Implementation Notes
The sync script is written in Python 3 with no external dependencies. It uses:
pathlibfor cross-platform path handlingjsonfor data serializationrefor pattern matching (extracting features/examples)
The script is designed to be:
- Idempotent: Running multiple times with no changes shows "no changes detected"
- Non-destructive: Always preserves marketing copy
- Informative: Reports exactly what changed
- Standalone: No external dependencies required
#!/usr/bin/env python3
"""
Marketplace Sync Script
Scans plugin directories and generates/updates docs/plugins.json with the latest
metadata while preserving marketing copy (icons, taglines, categories).
"""
import json
import os
import re
from pathlib import Path
from typing import Dict, List, Any
def parse_frontmatter(content: str) -> Dict[str, str]:
"""Extract YAML frontmatter from markdown content."""
frontmatter = {}
if content.startswith('---'):
parts = content.split('---', 2)
if len(parts) >= 3:
frontmatter_text = parts[1].strip()
for line in frontmatter_text.split('\n'):
if ':' in line:
key, value = line.split(':', 1)
frontmatter[key.strip()] = value.strip()
return frontmatter
def read_plugin_metadata(plugin_path: Path) -> Dict[str, Any]:
"""Read metadata from plugin.json."""
plugin_json = plugin_path / '.claude-plugin' / 'plugin.json'
if not plugin_json.exists():
return None
with open(plugin_json, 'r') as f:
return json.load(f)
def scan_commands(plugin_path: Path) -> List[Dict[str, Any]]:
"""Scan and extract command metadata."""
commands = []
commands_dir = plugin_path / 'commands'
if not commands_dir.exists():
return commands
for cmd_file in commands_dir.glob('*.md'):
with open(cmd_file, 'r') as f:
content = f.read()
frontmatter = parse_frontmatter(content)
if frontmatter:
command_name = cmd_file.stem
commands.append({
'name': command_name,
'fullName': f'/{plugin_path.name}:{command_name}',
'description': frontmatter.get('description', ''),
'usage': f'Run the command and Claude will handle the rest',
'example': f'/{plugin_path.name}:{command_name}'
})
return commands
def scan_skills(plugin_path: Path) -> List[Dict[str, Any]]:
"""Scan and extract skill metadata."""
skills = []
skills_dir = plugin_path / 'skills'
if not skills_dir.exists():
return skills
for skill_dir in skills_dir.iterdir():
if not skill_dir.is_dir():
continue
skill_md = skill_dir / 'SKILL.md'
if not skill_md.exists():
continue
with open(skill_md, 'r') as f:
content = f.read()
frontmatter = parse_frontmatter(content)
if frontmatter:
skill_name = frontmatter.get('name', skill_dir.name)
description = frontmatter.get('description', '')
# Extract usage hint from description
usage = "Activates automatically based on your request"
if 'user asks' in description.lower():
usage = "Just ask naturally and the skill activates automatically"
skill_data = {
'name': skill_name,
'fullName': f'{plugin_path.name}:{skill_name}',
'description': description,
'usage': usage
}
# Extract features from the markdown body
features = extract_features_from_content(content)
if features:
skill_data['features'] = features
# Extract examples from the markdown body
examples = extract_examples_from_content(content)
if examples:
skill_data['examples'] = examples
skills.append(skill_data)
return skills
def scan_agents(plugin_path: Path) -> List[Dict[str, Any]]:
"""Scan and extract agent metadata."""
agents = []
agents_dir = plugin_path / 'agents'
if not agents_dir.exists():
return agents
for agent_dir in agents_dir.iterdir():
if not agent_dir.is_dir():
continue
agent_md = agent_dir / 'AGENT.md'
if not agent_md.exists():
continue
with open(agent_md, 'r') as f:
content = f.read()
frontmatter = parse_frontmatter(content)
if frontmatter:
agents.append({
'name': frontmatter.get('name', agent_dir.name),
'fullName': f'{plugin_path.name}:{agent_dir.name}',
'description': frontmatter.get('description', ''),
'usage': 'Triggered automatically by the Task tool when appropriate'
})
return agents
def extract_features_from_content(content: str) -> List[str]:
"""Extract bullet point features from markdown content."""
features = []
# Look for sections that might contain features
lines = content.split('\n')
in_features_section = False
for line in lines:
# Detect features sections
if any(keyword in line.lower() for keyword in ['## capabilities', '## features', '## core capabilities']):
in_features_section = True
continue
# Stop at next major section
if line.startswith('## ') and in_features_section:
in_features_section = False
# Extract bullet points in features section
if in_features_section and line.strip().startswith(('-', '*', '•')):
feature = line.strip().lstrip('-*• ').strip()
if feature and len(feature) < 200: # Reasonable feature length
features.append(feature)
if len(features) >= 5: # Limit to 5 features
break
return features
def extract_examples_from_content(content: str) -> List[str]:
"""Extract example usage from markdown content."""
examples = []
# Look for example sections
lines = content.split('\n')
in_examples_section = False
for line in lines:
# Detect examples sections
if any(keyword in line.lower() for keyword in ['example request', '**example', 'examples:']):
in_examples_section = True
continue
# Stop at next major section
if line.startswith('##') and in_examples_section:
in_examples_section = False
# Extract quoted examples or bullet points
if in_examples_section:
# Look for quoted text
quoted = re.findall(r'"([^"]+)"', line)
for quote in quoted:
if 10 < len(quote) < 200: # Reasonable example length
examples.append(quote)
if len(examples) >= 3: # Limit to 3 examples
return examples
# Or bullet points
if line.strip().startswith(('-', '*', '•')):
example = line.strip().lstrip('-*• ').strip()
if example and 10 < len(example) < 200:
examples.append(example)
if len(examples) >= 3:
return examples
return examples
def load_existing_marketplace(docs_path: Path) -> Dict[str, Any]:
"""Load existing marketplace data to preserve marketing copy."""
plugins_json = docs_path / 'plugins.json'
if plugins_json.exists():
with open(plugins_json, 'r') as f:
return json.load(f)
return {'marketplace': {}, 'plugins': []}
def merge_plugin_data(existing: Dict[str, Any], scanned: Dict[str, Any]) -> Dict[str, Any]:
"""Merge scanned data with existing marketing copy."""
# Start with scanned data
merged = scanned.copy()
# Preserve marketing fields from existing data if they exist
if existing:
marketing_fields = ['icon', 'tagline', 'categories', 'requirements']
for field in marketing_fields:
if field in existing:
merged[field] = existing[field]
return merged
def generate_marketplace_data(repo_root: Path) -> Dict[str, Any]:
"""Generate complete marketplace data by scanning plugins."""
plugins_dir = repo_root / 'plugins'
docs_dir = repo_root / 'docs'
# Load existing data to preserve marketing copy
existing_data = load_existing_marketplace(docs_dir)
existing_plugins = {p['id']: p for p in existing_data.get('plugins', [])}
# Scan all plugins
plugins = []
for plugin_path in plugins_dir.iterdir():
if not plugin_path.is_dir():
continue
# Read plugin metadata
metadata = read_plugin_metadata(plugin_path)
if not metadata:
continue
# Scan components
commands = scan_commands(plugin_path)
skills = scan_skills(plugin_path)
agents = scan_agents(plugin_path)
# Build plugin data
plugin_data = {
'id': metadata.get('name', plugin_path.name),
'name': metadata.get('name', plugin_path.name).replace('-', ' ').title(),
'namespace': 'simon-plugins',
'description': metadata.get('description', ''),
'version': metadata.get('version', '1.0.0'),
'author': metadata.get('author', {}).get('name', 'Simon'),
'installation': f"/plugin install {metadata.get('name')}@simon-plugins",
'components': {}
}
# Add components
if commands:
plugin_data['components']['commands'] = commands
if skills:
plugin_data['components']['skills'] = skills
if agents:
plugin_data['components']['agents'] = agents
# Merge with existing data to preserve marketing copy
plugin_id = plugin_data['id']
if plugin_id in existing_plugins:
plugin_data = merge_plugin_data(existing_plugins[plugin_id], plugin_data)
else:
# New plugin - set defaults for marketing fields
plugin_data['icon'] = '📦'
plugin_data['tagline'] = plugin_data['description'][:60] + '...' if len(plugin_data['description']) > 60 else plugin_data['description']
plugin_data['categories'] = ['productivity']
plugins.append(plugin_data)
# Build complete marketplace data
marketplace_data = {
'marketplace': existing_data.get('marketplace', {
'name': "Simon's Claude Plugins",
'namespace': 'simon-plugins',
'repository': 'simonlee2/claude-plugins',
'description': 'Professional Claude Code plugins for development workflow automation and creative tools'
}),
'plugins': plugins
}
return marketplace_data
def detect_changes(old_data: Dict[str, Any], new_data: Dict[str, Any]) -> List[str]:
"""Detect and summarize changes between old and new marketplace data."""
changes = []
old_plugins = {p['id']: p for p in old_data.get('plugins', [])}
new_plugins = {p['id']: p for p in new_data.get('plugins', [])}
# Check for new plugins
for plugin_id in new_plugins:
if plugin_id not in old_plugins:
changes.append(f"➕ New plugin: {new_plugins[plugin_id]['name']}")
# Check for removed plugins
for plugin_id in old_plugins:
if plugin_id not in new_plugins:
changes.append(f"➖ Removed plugin: {old_plugins[plugin_id]['name']}")
# Check for version changes and component changes
for plugin_id in new_plugins:
if plugin_id in old_plugins:
old_plugin = old_plugins[plugin_id]
new_plugin = new_plugins[plugin_id]
# Version change
if old_plugin.get('version') != new_plugin.get('version'):
changes.append(f"🔄 {new_plugin['name']}: {old_plugin.get('version')} → {new_plugin.get('version')}")
# Component changes
old_components = old_plugin.get('components', {})
new_components = new_plugin.get('components', {})
for comp_type in ['commands', 'skills', 'agents']:
old_count = len(old_components.get(comp_type, []))
new_count = len(new_components.get(comp_type, []))
if old_count != new_count:
changes.append(f" 📝 {new_plugin['name']}: {comp_type} changed from {old_count} to {new_count}")
return changes
def main():
"""Main execution function."""
# Determine repository root
# Path: sync-marketplace.py -> scripts/ -> marketplace-sync/ -> skills/ -> .claude/ -> repo_root
repo_root = Path(__file__).parent.parent.parent.parent.parent
docs_dir = repo_root / 'docs'
plugins_json = docs_dir / 'plugins.json'
print("🔍 Scanning plugins...")
# Load existing data
existing_data = load_existing_marketplace(docs_dir)
# Generate new marketplace data
new_data = generate_marketplace_data(repo_root)
# Detect changes
changes = detect_changes(existing_data, new_data)
if not changes:
print("✅ No changes detected - marketplace is up to date")
return
print(f"\n📊 Detected {len(changes)} change(s):")
for change in changes:
print(f" {change}")
# Write updated data
print(f"\n💾 Writing to {plugins_json}...")
with open(plugins_json, 'w') as f:
json.dump(new_data, f, indent=2)
print("✅ Marketplace data updated successfully!")
print(f"\n📍 Updated: {plugins_json}")
if __name__ == '__main__':
main()