Initial commit
This commit is contained in:
406
skills/marketplace-update/marketplace_update.py
Executable file
406
skills/marketplace-update/marketplace_update.py
Executable 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()
|
||||
Reference in New Issue
Block a user