Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:28:02 +08:00
commit 4ea8c8d0b0
15 changed files with 4003 additions and 0 deletions

View File

@@ -0,0 +1,406 @@
#!/usr/bin/env python3
"""
Marketplace Update Script
Updates the .claude-plugin/marketplace.json file when plugins are added,
modified, or removed.
"""
import json
import os
import sys
from pathlib import Path
from typing import Dict, List, Optional, Any
import argparse
class MarketplaceUpdater:
"""Handles marketplace.json updates"""
def __init__(self, marketplace_path: str = ".claude-plugin/marketplace.json"):
self.marketplace_path = Path(marketplace_path)
self.marketplace_data: Dict[str, Any] = {}
def load(self) -> None:
"""Load marketplace.json file"""
if not self.marketplace_path.exists():
raise FileNotFoundError(f"Marketplace file not found: {self.marketplace_path}")
try:
with open(self.marketplace_path, 'r') as f:
self.marketplace_data = json.load(f)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in marketplace file: {e}")
def save(self) -> None:
"""Save marketplace.json file"""
with open(self.marketplace_path, 'w') as f:
json.dump(self.marketplace_data, f, indent=2)
f.write('\n') # Add trailing newline
def add_plugin(
self,
name: str,
description: str,
version: str,
category: str = "general",
agents: Optional[List[str]] = None,
commands: Optional[List[str]] = None,
skills: Optional[List[str]] = None,
keywords: Optional[List[str]] = None,
license: str = "MIT",
strict: bool = False,
author_name: Optional[str] = None,
author_url: Optional[str] = None,
) -> None:
"""Add a new plugin to the marketplace"""
self.load()
# Check if plugin already exists
if "plugins" not in self.marketplace_data:
self.marketplace_data["plugins"] = []
existing_plugin = self._find_plugin(name)
if existing_plugin:
raise ValueError(f"Plugin '{name}' already exists in marketplace")
# Build plugin entry
plugin_entry: Dict[str, Any] = {
"name": name,
"source": f"./plugins/{name}",
"description": description,
"version": version,
"category": category,
"license": license,
"strict": strict,
}
# Add author if provided, otherwise use marketplace owner
if author_name or author_url:
plugin_entry["author"] = {}
if author_name:
plugin_entry["author"]["name"] = author_name
if author_url:
plugin_entry["author"]["url"] = author_url
elif "owner" in self.marketplace_data:
plugin_entry["author"] = {
"name": self.marketplace_data["owner"].get("name", ""),
"url": self.marketplace_data["owner"].get("url", ""),
}
# Add homepage and repository from owner if available
if "owner" in self.marketplace_data and "url" in self.marketplace_data["owner"]:
base_url = self.marketplace_data["owner"]["url"]
# Extract repo name from URL if it's a GitHub URL
if "github.com" in base_url:
plugin_entry["homepage"] = base_url
plugin_entry["repository"] = base_url
# Add optional fields
if keywords:
plugin_entry["keywords"] = keywords
if agents:
plugin_entry["agents"] = [f"./agents/{agent}" for agent in agents]
if commands:
plugin_entry["commands"] = [f"./commands/{cmd}" for cmd in commands]
if skills:
plugin_entry["skills"] = [f"./skills/{skill}" for skill in skills]
# Add plugin to marketplace
self.marketplace_data["plugins"].append(plugin_entry)
self.save()
print(f"✓ Added plugin '{name}' to marketplace")
def update_plugin(
self,
name: str,
description: Optional[str] = None,
version: Optional[str] = None,
category: Optional[str] = None,
keywords: Optional[List[str]] = None,
add_agent: Optional[str] = None,
remove_agent: Optional[str] = None,
add_command: Optional[str] = None,
remove_command: Optional[str] = None,
add_skill: Optional[str] = None,
remove_skill: Optional[str] = None,
) -> None:
"""Update an existing plugin"""
self.load()
plugin = self._find_plugin(name)
if not plugin:
raise ValueError(f"Plugin '{name}' not found in marketplace")
# Update basic fields
if description:
plugin["description"] = description
if version:
plugin["version"] = version
if category:
plugin["category"] = category
if keywords:
plugin["keywords"] = keywords
# Update agents
if add_agent:
if "agents" not in plugin:
plugin["agents"] = []
agent_path = f"./agents/{add_agent}"
if agent_path not in plugin["agents"]:
plugin["agents"].append(agent_path)
if remove_agent:
if "agents" in plugin:
agent_path = f"./agents/{remove_agent}"
if agent_path in plugin["agents"]:
plugin["agents"].remove(agent_path)
# Update commands
if add_command:
if "commands" not in plugin:
plugin["commands"] = []
cmd_path = f"./commands/{add_command}"
if cmd_path not in plugin["commands"]:
plugin["commands"].append(cmd_path)
if remove_command:
if "commands" in plugin:
cmd_path = f"./commands/{remove_command}"
if cmd_path in plugin["commands"]:
plugin["commands"].remove(cmd_path)
# Update skills
if add_skill:
if "skills" not in plugin:
plugin["skills"] = []
skill_path = f"./skills/{add_skill}"
if skill_path not in plugin["skills"]:
plugin["skills"].append(skill_path)
if remove_skill:
if "skills" in plugin:
skill_path = f"./skills/{remove_skill}"
if skill_path in plugin["skills"]:
plugin["skills"].remove(skill_path)
self.save()
print(f"✓ Updated plugin '{name}' in marketplace")
def remove_plugin(self, name: str) -> None:
"""Remove a plugin from the marketplace"""
self.load()
plugin = self._find_plugin(name)
if not plugin:
raise ValueError(f"Plugin '{name}' not found in marketplace")
self.marketplace_data["plugins"].remove(plugin)
self.save()
print(f"✓ Removed plugin '{name}' from marketplace")
def validate(self) -> bool:
"""Validate marketplace structure"""
self.load()
errors = []
warnings = []
# Check required top-level fields
required_fields = ["name", "owner", "metadata", "plugins"]
for field in required_fields:
if field not in self.marketplace_data:
errors.append(f"Missing required field: {field}")
# Validate plugins
if "plugins" in self.marketplace_data:
plugin_names = set()
for i, plugin in enumerate(self.marketplace_data["plugins"]):
# Check required plugin fields
plugin_required = ["name", "source", "description", "version"]
for field in plugin_required:
if field not in plugin:
errors.append(f"Plugin {i}: Missing required field '{field}'")
# Check for duplicate names
if "name" in plugin:
if plugin["name"] in plugin_names:
errors.append(f"Duplicate plugin name: {plugin['name']}")
plugin_names.add(plugin["name"])
# Validate component file paths
plugin_dir = Path(f"plugins/{plugin['name']}")
if "agents" in plugin:
for agent in plugin["agents"]:
agent_path = plugin_dir / agent
if not agent_path.exists():
warnings.append(
f"Plugin '{plugin['name']}': Agent file not found: {agent_path}"
)
if "commands" in plugin:
for command in plugin["commands"]:
cmd_path = plugin_dir / command
if not cmd_path.exists():
warnings.append(
f"Plugin '{plugin['name']}': Command file not found: {cmd_path}"
)
if "skills" in plugin:
for skill in plugin["skills"]:
skill_path = plugin_dir / skill / "SKILL.md"
if not skill_path.exists():
warnings.append(
f"Plugin '{plugin['name']}': Skill file not found: {skill_path}"
)
# Report results
if errors:
print("❌ Validation failed with errors:")
for error in errors:
print(f" - {error}")
else:
print("✓ Validation passed with no errors")
if warnings:
print("\n⚠️ Warnings:")
for warning in warnings:
print(f" - {warning}")
return len(errors) == 0
def _find_plugin(self, name: str) -> Optional[Dict[str, Any]]:
"""Find a plugin by name"""
if "plugins" not in self.marketplace_data:
return None
for plugin in self.marketplace_data["plugins"]:
if plugin.get("name") == name:
return plugin
return None
def main():
"""Main entry point"""
parser = argparse.ArgumentParser(description="Update marketplace.json")
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
# Add command
add_parser = subparsers.add_parser("add", help="Add a new plugin")
add_parser.add_argument("--name", required=True, help="Plugin name")
add_parser.add_argument("--description", required=True, help="Plugin description")
add_parser.add_argument("--version", required=True, help="Plugin version")
add_parser.add_argument("--category", default="general", help="Plugin category")
add_parser.add_argument("--agents", help="Comma-separated list of agent files")
add_parser.add_argument("--commands", help="Comma-separated list of command files")
add_parser.add_argument("--skills", help="Comma-separated list of skill directories")
add_parser.add_argument("--keywords", help="Comma-separated list of keywords")
add_parser.add_argument("--license", default="MIT", help="License type")
add_parser.add_argument("--strict", action="store_true", help="Enable strict mode")
add_parser.add_argument("--author-name", help="Author name")
add_parser.add_argument("--author-url", help="Author URL")
add_parser.add_argument(
"--marketplace",
default=".claude-plugin/marketplace.json",
help="Path to marketplace.json",
)
# Update command
update_parser = subparsers.add_parser("update", help="Update an existing plugin")
update_parser.add_argument("--name", required=True, help="Plugin name")
update_parser.add_argument("--description", help="Updated description")
update_parser.add_argument("--version", help="Updated version")
update_parser.add_argument("--category", help="Updated category")
update_parser.add_argument("--keywords", help="Updated keywords (comma-separated)")
update_parser.add_argument("--add-agent", help="Agent file to add")
update_parser.add_argument("--remove-agent", help="Agent file to remove")
update_parser.add_argument("--add-command", help="Command file to add")
update_parser.add_argument("--remove-command", help="Command file to remove")
update_parser.add_argument("--add-skill", help="Skill directory to add")
update_parser.add_argument("--remove-skill", help="Skill directory to remove")
update_parser.add_argument(
"--marketplace",
default=".claude-plugin/marketplace.json",
help="Path to marketplace.json",
)
# Remove command
remove_parser = subparsers.add_parser("remove", help="Remove a plugin")
remove_parser.add_argument("--name", required=True, help="Plugin name")
remove_parser.add_argument(
"--marketplace",
default=".claude-plugin/marketplace.json",
help="Path to marketplace.json",
)
# Validate command
validate_parser = subparsers.add_parser("validate", help="Validate marketplace.json")
validate_parser.add_argument(
"--marketplace",
default=".claude-plugin/marketplace.json",
help="Path to marketplace.json",
)
args = parser.parse_args()
if not args.command:
parser.print_help()
sys.exit(1)
try:
updater = MarketplaceUpdater(
getattr(args, "marketplace", ".claude-plugin/marketplace.json")
)
if args.command == "add":
updater.add_plugin(
name=args.name,
description=args.description,
version=args.version,
category=args.category,
agents=args.agents.split(",") if args.agents else None,
commands=args.commands.split(",") if args.commands else None,
skills=args.skills.split(",") if args.skills else None,
keywords=args.keywords.split(",") if args.keywords else None,
license=args.license,
strict=args.strict,
author_name=args.author_name,
author_url=args.author_url,
)
elif args.command == "update":
updater.update_plugin(
name=args.name,
description=args.description,
version=args.version,
category=args.category,
keywords=args.keywords.split(",") if args.keywords else None,
add_agent=args.add_agent,
remove_agent=args.remove_agent,
add_command=args.add_command,
remove_command=args.remove_command,
add_skill=args.add_skill,
remove_skill=args.remove_skill,
)
elif args.command == "remove":
updater.remove_plugin(name=args.name)
elif args.command == "validate":
if not updater.validate():
sys.exit(1)
except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()