Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:08 +08:00
commit 8f22ddf339
295 changed files with 59710 additions and 0 deletions

View File

@@ -0,0 +1,522 @@
#!/usr/bin/env python3
"""
generate_marketplace.py - Implementation of the generate.marketplace Skill
Generates marketplace JSON files from registry entries:
- marketplace/skills.json
- marketplace/agents.json
- marketplace/commands.json
- marketplace/hooks.json
Filters by status: active and certified: true (if present).
Adds last_updated ISO timestamp to all marketplace files.
"""
import os
import sys
import json
from typing import Dict, Any, List, Optional
from datetime import datetime, timezone
from pathlib import Path
from betty.config import BASE_DIR
from betty.logging_utils import setup_logger
logger = setup_logger(__name__)
def load_registry_file(registry_path: str) -> Dict[str, Any]:
"""
Load a JSON registry file.
Args:
registry_path: Path to the registry JSON file
Returns:
Parsed registry data
Raises:
FileNotFoundError: If registry file doesn't exist
json.JSONDecodeError: If JSON is invalid
"""
try:
with open(registry_path) as f:
return json.load(f)
except FileNotFoundError:
logger.error(f"Registry file not found: {registry_path}")
raise
except json.JSONDecodeError as e:
logger.error(f"Failed to parse JSON from {registry_path}: {e}")
raise
def is_certified(item: Dict[str, Any]) -> bool:
"""
Check if an item is certified for marketplace inclusion.
Args:
item: Skill or agent entry
Returns:
True if item should be included in marketplace
"""
# Filter by status: active
if item.get("status") != "active":
return False
# If certified field exists, check it
if "certified" in item:
return item.get("certified") is True
# If no certified field, consider active items as certified
return True
def convert_skill_to_marketplace(skill: Dict[str, Any]) -> Dict[str, Any]:
"""
Convert a registry skill entry to marketplace format.
Args:
skill: Skill entry from registry
Returns:
Skill entry in marketplace format
"""
skill_name = skill.get("name")
marketplace_skill = {
"name": skill_name,
"version": skill.get("version"),
"description": skill.get("description"),
"status": "certified", # Transform active -> certified for marketplace
"tags": skill.get("tags", []),
"manifest_path": f"skills/{skill_name}/skill.yaml",
"maintainer": skill.get("maintainer", "Betty Core Team"),
"usage_examples": skill.get("usage_examples", []),
"documentation_url": f"https://betty-framework.dev/docs/skills/{skill_name}",
"dependencies": skill.get("dependencies", []),
"entrypoints": skill.get("entrypoints", []),
"inputs": skill.get("inputs", []),
"outputs": skill.get("outputs", [])
}
# Generate usage examples if not present
if not marketplace_skill["usage_examples"] and marketplace_skill["entrypoints"]:
examples = []
for entrypoint in marketplace_skill["entrypoints"]:
command = entrypoint.get("command", "")
desc = entrypoint.get("description", skill.get("description", ""))
if command:
# Create a simple example from the command
example = f"Run {skill.get('name')}: {command}"
examples.append(example.strip())
marketplace_skill["usage_examples"] = examples[:2] # Limit to 2 examples
return marketplace_skill
def convert_agent_to_marketplace(agent: Dict[str, Any]) -> Dict[str, Any]:
"""
Convert a registry agent entry to marketplace format.
Args:
agent: Agent entry from registry
Returns:
Agent entry in marketplace format
"""
agent_name = agent.get("name")
marketplace_agent = {
"name": agent_name,
"version": agent.get("version"),
"description": agent.get("description"),
"status": "certified", # Transform active -> certified for marketplace
"reasoning_mode": agent.get("reasoning_mode", "oneshot"),
"skills_available": agent.get("skills_available", []),
"capabilities": agent.get("capabilities", []),
"tags": agent.get("tags", []),
"manifest_path": f"agents/{agent_name}/agent.yaml",
"maintainer": agent.get("maintainer", "Betty Core Team"),
"documentation_url": f"https://betty-framework.dev/docs/agents/{agent_name}",
"dependencies": agent.get("dependencies", [])
}
return marketplace_agent
def convert_command_to_marketplace(command: Dict[str, Any]) -> Dict[str, Any]:
"""
Convert a registry command entry to marketplace format.
Args:
command: Command entry from registry
Returns:
Command entry in marketplace format
"""
marketplace_command = {
"name": command.get("name"),
"version": command.get("version"),
"description": command.get("description"),
"status": "certified", # Transform active -> certified for marketplace
"tags": command.get("tags", []),
"execution": command.get("execution", {}),
"parameters": command.get("parameters", []),
"maintainer": command.get("maintainer", "Betty Core Team")
}
return marketplace_command
def convert_hook_to_marketplace(hook: Dict[str, Any]) -> Dict[str, Any]:
"""
Convert a registry hook entry to marketplace format.
Args:
hook: Hook entry from registry
Returns:
Hook entry in marketplace format
"""
marketplace_hook = {
"name": hook.get("name"),
"version": hook.get("version"),
"description": hook.get("description"),
"status": "certified", # Transform active -> certified for marketplace
"tags": hook.get("tags", []),
"event": hook.get("event"),
"command": hook.get("command"),
"blocking": hook.get("blocking", False),
"when": hook.get("when", {}),
"timeout": hook.get("timeout"),
"on_failure": hook.get("on_failure"),
"maintainer": hook.get("maintainer", "Betty Core Team")
}
return marketplace_hook
def generate_skills_marketplace(registry_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate marketplace skills catalog from registry.
Args:
registry_data: Parsed skills.json from registry
Returns:
Marketplace-formatted skills catalog
"""
skills = registry_data.get("skills", [])
# Filter and convert active/certified skills
certified_skills = []
for skill in skills:
if is_certified(skill):
marketplace_skill = convert_skill_to_marketplace(skill)
certified_skills.append(marketplace_skill)
logger.info(f"Added certified skill: {skill.get('name')}")
else:
logger.debug(f"Skipped non-certified skill: {skill.get('name')} (status: {skill.get('status')})")
# Build marketplace catalog
marketplace = {
"marketplace_version": "1.0.0",
"generated_at": datetime.now(timezone.utc).isoformat(),
"last_updated": datetime.now(timezone.utc).isoformat(),
"description": "Betty Framework Certified Skills Marketplace",
"total_skills": len(skills),
"certified_count": len(certified_skills),
"draft_count": len(skills) - len(certified_skills),
"catalog": certified_skills
}
return marketplace
def generate_agents_marketplace(registry_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate marketplace agents catalog from registry.
Args:
registry_data: Parsed agents.json from registry
Returns:
Marketplace-formatted agents catalog
"""
agents = registry_data.get("agents", [])
# Filter and convert active/certified agents
certified_agents = []
for agent in agents:
if is_certified(agent):
marketplace_agent = convert_agent_to_marketplace(agent)
certified_agents.append(marketplace_agent)
logger.info(f"Added certified agent: {agent.get('name')}")
else:
logger.debug(f"Skipped non-certified agent: {agent.get('name')} (status: {agent.get('status')})")
# Build marketplace catalog
marketplace = {
"marketplace_version": "1.0.0",
"generated_at": datetime.now(timezone.utc).isoformat(),
"last_updated": datetime.now(timezone.utc).isoformat(),
"description": "Betty Framework Certified Agents Marketplace",
"total_agents": len(agents),
"certified_count": len(certified_agents),
"draft_count": len(agents) - len(certified_agents),
"catalog": certified_agents
}
return marketplace
def generate_commands_marketplace(registry_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate marketplace commands catalog from registry.
Args:
registry_data: Parsed commands.json from registry
Returns:
Marketplace-formatted commands catalog
"""
commands = registry_data.get("commands", [])
# Filter and convert active/certified commands
certified_commands = []
for command in commands:
if is_certified(command):
marketplace_command = convert_command_to_marketplace(command)
certified_commands.append(marketplace_command)
logger.info(f"Added certified command: {command.get('name')}")
else:
logger.debug(f"Skipped non-certified command: {command.get('name')} (status: {command.get('status')})")
# Build marketplace catalog
marketplace = {
"marketplace_version": "1.0.0",
"generated_at": datetime.now(timezone.utc).isoformat(),
"last_updated": datetime.now(timezone.utc).isoformat(),
"description": "Betty Framework Certified Commands Marketplace",
"total_commands": len(commands),
"certified_count": len(certified_commands),
"draft_count": len(commands) - len(certified_commands),
"catalog": certified_commands
}
return marketplace
def generate_hooks_marketplace(registry_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate marketplace hooks catalog from registry.
Args:
registry_data: Parsed hooks.json from registry
Returns:
Marketplace-formatted hooks catalog
"""
hooks = registry_data.get("hooks", [])
# Filter and convert active/certified hooks
certified_hooks = []
for hook in hooks:
if is_certified(hook):
marketplace_hook = convert_hook_to_marketplace(hook)
certified_hooks.append(marketplace_hook)
logger.info(f"Added certified hook: {hook.get('name')}")
else:
logger.debug(f"Skipped non-certified hook: {hook.get('name')} (status: {hook.get('status')})")
# Build marketplace catalog
marketplace = {
"marketplace_version": "1.0.0",
"generated_at": datetime.now(timezone.utc).isoformat(),
"last_updated": datetime.now(timezone.utc).isoformat(),
"description": "Betty Framework Certified Hooks Marketplace",
"total_hooks": len(hooks),
"certified_count": len(certified_hooks),
"draft_count": len(hooks) - len(certified_hooks),
"catalog": certified_hooks
}
return marketplace
def write_marketplace_file(data: Dict[str, Any], output_path: str):
"""
Write marketplace JSON file with proper formatting.
Args:
data: Marketplace data dictionary
output_path: Path where to write the file
"""
os.makedirs(os.path.dirname(output_path), exist_ok=True)
with open(output_path, 'w') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
logger.info(f"✅ Written marketplace file to {output_path}")
def generate_claude_code_marketplace() -> Dict[str, Any]:
"""
Generate Claude Code marketplace.json file.
This file lists Betty Framework as an installable plugin in Claude Code's
marketplace format, as specified in Claude Code's plugin marketplace documentation.
Returns:
Claude Code marketplace JSON structure
"""
# Load plugin.yaml to get current metadata
plugin_yaml_path = os.path.join(BASE_DIR, "plugin.yaml")
try:
import yaml
with open(plugin_yaml_path, 'r') as f:
plugin_data = yaml.safe_load(f)
except Exception as e:
logger.warning(f"Could not load plugin.yaml: {e}. Using defaults.")
plugin_data = {}
# Build Claude Code marketplace structure
marketplace = {
"name": "betty-marketplace",
"version": plugin_data.get("version", "1.0.0"),
"description": "Betty Framework Plugin Marketplace - Enterprise-grade AI-assisted engineering framework",
"owner": {
"name": plugin_data.get("author", {}).get("name", "RiskExec"),
"email": plugin_data.get("author", {}).get("email", "platform@riskexec.com"),
"url": plugin_data.get("author", {}).get("url", "https://github.com/epieczko/betty")
},
"metadata": {
"homepage": plugin_data.get("metadata", {}).get("homepage", "https://github.com/epieczko/betty"),
"repository": plugin_data.get("metadata", {}).get("repository", "https://github.com/epieczko/betty"),
"documentation": plugin_data.get("metadata", {}).get("documentation", "https://github.com/epieczko/betty/tree/main/docs"),
"generated_at": datetime.now(timezone.utc).isoformat(),
"generated_by": "generate.marketplace skill"
},
"plugins": [
{
"name": plugin_data.get("name", "betty-framework"),
"source": ".",
"version": plugin_data.get("version", "1.0.0"),
"description": plugin_data.get("description", "Betty Framework for structured AI-assisted engineering"),
"author": {
"name": plugin_data.get("author", {}).get("name", "RiskExec"),
"email": plugin_data.get("author", {}).get("email", "platform@riskexec.com"),
"url": plugin_data.get("author", {}).get("url", "https://github.com/epieczko/betty")
},
"license": plugin_data.get("license", "MIT"),
"tags": plugin_data.get("metadata", {}).get("tags", [
"framework",
"api-development",
"workflow",
"governance",
"enterprise"
]),
"requirements": plugin_data.get("requirements", {
"python": ">=3.11",
"packages": ["pyyaml"]
}),
"strict": True # Requires plugin.yaml manifest
}
]
}
return marketplace
def main():
"""Main CLI entry point."""
logger.info("Starting marketplace catalog generation from registries...")
# Define registry and output paths
skills_registry_path = os.path.join(BASE_DIR, "registry", "skills.json")
agents_registry_path = os.path.join(BASE_DIR, "registry", "agents.json")
commands_registry_path = os.path.join(BASE_DIR, "registry", "commands.json")
hooks_registry_path = os.path.join(BASE_DIR, "registry", "hooks.json")
marketplace_dir = os.path.join(BASE_DIR, "marketplace")
skills_output_path = os.path.join(marketplace_dir, "skills.json")
agents_output_path = os.path.join(marketplace_dir, "agents.json")
commands_output_path = os.path.join(marketplace_dir, "commands.json")
hooks_output_path = os.path.join(marketplace_dir, "hooks.json")
try:
# Load registry files
logger.info("Loading registry files...")
skills_registry = load_registry_file(skills_registry_path)
agents_registry = load_registry_file(agents_registry_path)
commands_registry = load_registry_file(commands_registry_path)
hooks_registry = load_registry_file(hooks_registry_path)
# Generate marketplace catalogs
logger.info("Generating marketplace catalogs...")
skills_marketplace = generate_skills_marketplace(skills_registry)
agents_marketplace = generate_agents_marketplace(agents_registry)
commands_marketplace = generate_commands_marketplace(commands_registry)
hooks_marketplace = generate_hooks_marketplace(hooks_registry)
# Write Betty marketplace catalogs to files
logger.info("Writing Betty marketplace files...")
write_marketplace_file(skills_marketplace, skills_output_path)
write_marketplace_file(agents_marketplace, agents_output_path)
write_marketplace_file(commands_marketplace, commands_output_path)
write_marketplace_file(hooks_marketplace, hooks_output_path)
# Generate and write Claude Code marketplace.json
logger.info("Generating Claude Code marketplace.json...")
claude_marketplace = generate_claude_code_marketplace()
claude_marketplace_path = os.path.join(BASE_DIR, ".claude-plugin", "marketplace.json")
write_marketplace_file(claude_marketplace, claude_marketplace_path)
# Report results
result = {
"ok": True,
"status": "success",
"skills_output": skills_output_path,
"agents_output": agents_output_path,
"commands_output": commands_output_path,
"hooks_output": hooks_output_path,
"claude_marketplace_output": claude_marketplace_path,
"skills_certified": skills_marketplace["certified_count"],
"skills_total": skills_marketplace["total_skills"],
"agents_certified": agents_marketplace["certified_count"],
"agents_total": agents_marketplace["total_agents"],
"commands_certified": commands_marketplace["certified_count"],
"commands_total": commands_marketplace["total_commands"],
"hooks_certified": hooks_marketplace["certified_count"],
"hooks_total": hooks_marketplace["total_hooks"]
}
# Print summary
logger.info(f"✅ Generated marketplace catalogs:")
logger.info(f" Skills: {result['skills_certified']}/{result['skills_total']} certified")
logger.info(f" Agents: {result['agents_certified']}/{result['agents_total']} certified")
logger.info(f" Commands: {result['commands_certified']}/{result['commands_total']} certified")
logger.info(f" Hooks: {result['hooks_certified']}/{result['hooks_total']} certified")
logger.info(f"📄 Outputs:")
logger.info(f" - {skills_output_path}")
logger.info(f" - {agents_output_path}")
logger.info(f" - {commands_output_path}")
logger.info(f" - {hooks_output_path}")
logger.info(f" - {claude_marketplace_path} (Claude Code format)")
print(json.dumps(result, indent=2))
sys.exit(0)
except Exception as e:
logger.error(f"Failed to generate marketplace catalogs: {e}")
result = {
"ok": False,
"status": "failed",
"error": str(e)
}
print(json.dumps(result, indent=2))
sys.exit(1)
if __name__ == "__main__":
main()