skills/ → skills-dev(9), skills-req(10), skills-ops(4), skills-integration(8), skills-biz(4), skills-workflow(7) generate-marketplace.py 改为自动扫描所有 skills-* 目录。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
163 lines
5.3 KiB
Python
163 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import yaml
|
|
HAS_YAML = True
|
|
except ImportError:
|
|
HAS_YAML = False
|
|
|
|
# Paths
|
|
script_dir = Path(__file__).parent.resolve()
|
|
config_file = script_dir / "claude-config.yaml"
|
|
marketplace_file = script_dir / ".claude-plugin" / "marketplace.json"
|
|
|
|
# Skill directories (label, directory name)
|
|
SKILL_DIRS = [
|
|
("dev", "skills-dev"),
|
|
("req", "skills-req"),
|
|
("ops", "skills-ops"),
|
|
("integration", "skills-integration"),
|
|
("biz", "skills-biz"),
|
|
("workflow", "skills-workflow"),
|
|
("projects", "skills-projects"),
|
|
("personal", "skills-personal"),
|
|
]
|
|
|
|
|
|
def load_config():
|
|
"""Load claude-config.yaml and return disabled list + personal_dir."""
|
|
disabled = []
|
|
personal = "skills-personal"
|
|
|
|
if config_file.exists() and HAS_YAML:
|
|
with open(config_file) as f:
|
|
cfg = yaml.safe_load(f) or {}
|
|
skills_cfg = cfg.get("skills", {})
|
|
disabled = skills_cfg.get("disabled", []) or []
|
|
personal = skills_cfg.get("personal_dir", personal)
|
|
elif config_file.exists():
|
|
# Fallback: parse disabled list without PyYAML
|
|
in_disabled = False
|
|
with open(config_file) as f:
|
|
for line in f:
|
|
stripped = line.strip()
|
|
if stripped.startswith("disabled:"):
|
|
rest = stripped[len("disabled:"):].strip()
|
|
if rest == "[]":
|
|
break
|
|
in_disabled = True
|
|
continue
|
|
if in_disabled:
|
|
if stripped.startswith("- "):
|
|
val = stripped[2:].strip().strip('"').strip("'")
|
|
disabled.append(val)
|
|
elif stripped and not stripped.startswith("#"):
|
|
break
|
|
if stripped.startswith("personal_dir:"):
|
|
personal = stripped.split(":", 1)[1].strip().strip('"').strip("'")
|
|
|
|
return disabled, personal
|
|
|
|
|
|
# Category mapping
|
|
def get_category_and_keywords(plugin_name):
|
|
if any(x in plugin_name for x in ['dev-', 'coding', 'frontend']):
|
|
return "development", ["development", "coding", "workflow"]
|
|
elif any(x in plugin_name for x in ['ops-', 'deploy', 'server']):
|
|
return "devops", ["devops", "deployment", "operations"]
|
|
elif any(x in plugin_name for x in ['ai-proj', 'req']):
|
|
return "productivity", ["project-management", "tasks", "requirements"]
|
|
elif any(x in plugin_name for x in ['feishu', 'wecom', 'siyuan']):
|
|
return "integration", ["integration", "automation", "productivity"]
|
|
elif 'biz-' in plugin_name:
|
|
return "business", ["business", "planning", "contracts"]
|
|
elif 'session' in plugin_name:
|
|
return "workflow", ["session", "workflow", "productivity"]
|
|
else:
|
|
return "utility", ["utility", "tools"]
|
|
|
|
|
|
def scan_plugins(directory, source_prefix, disabled):
|
|
"""Scan a directory for plugins, excluding disabled ones."""
|
|
plugins = []
|
|
if not directory.is_dir():
|
|
return plugins
|
|
|
|
for plugin_dir in sorted(directory.glob("*-plugin")):
|
|
if not plugin_dir.is_dir():
|
|
continue
|
|
|
|
plugin_name = plugin_dir.name
|
|
if plugin_name in disabled:
|
|
print(f" ⊘ {plugin_name} (disabled)")
|
|
continue
|
|
|
|
manifest_path = plugin_dir / ".claude-plugin" / "plugin.json"
|
|
if not manifest_path.exists():
|
|
continue
|
|
|
|
with open(manifest_path) as f:
|
|
manifest = json.load(f)
|
|
|
|
category, keywords = get_category_and_keywords(plugin_name)
|
|
|
|
plugins.append({
|
|
"name": plugin_name,
|
|
"source": f"{source_prefix}/{plugin_name}",
|
|
"description": manifest.get("description", f"Plugin for {plugin_name}"),
|
|
"version": manifest.get("version", "1.0.0"),
|
|
"category": category,
|
|
"keywords": keywords,
|
|
"strict": False
|
|
})
|
|
|
|
return plugins
|
|
|
|
|
|
# Load config
|
|
disabled_skills, personal_dir_name = load_config()
|
|
|
|
# Collect plugins from all skill directories
|
|
plugins = []
|
|
counts = {}
|
|
for label, dir_name in SKILL_DIRS:
|
|
# personal_dir may be overridden by config
|
|
if label == "personal":
|
|
dir_name = personal_dir_name
|
|
skill_path = script_dir / dir_name
|
|
if not skill_path.is_dir():
|
|
continue
|
|
print(f"Scanning {dir_name}/ ...")
|
|
found = scan_plugins(skill_path, f"./{dir_name}", disabled_skills)
|
|
counts[label] = len(found)
|
|
plugins.extend(found)
|
|
print(f" Found {counts[label]} {label} plugins")
|
|
|
|
# Create marketplace
|
|
marketplace = {
|
|
"name": "coolbuy-claude-plugins",
|
|
"owner": {
|
|
"name": "Donglin Lai (qiudl)",
|
|
"email": "qiudl@zhiyuncai.com"
|
|
},
|
|
"metadata": {
|
|
"description": "Custom Claude Code plugins for development workflows, DevOps, and business operations",
|
|
"version": "1.0.0",
|
|
"pluginRoot": "."
|
|
},
|
|
"plugins": plugins
|
|
}
|
|
|
|
# Ensure output directory exists
|
|
marketplace_file.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Write marketplace.json
|
|
with open(marketplace_file, 'w') as f:
|
|
json.dump(marketplace, f, indent=2, ensure_ascii=False)
|
|
|
|
summary = ", ".join(f"{v} {k}" for k, v in counts.items() if v > 0)
|
|
print(f"\n✓ Generated marketplace.json with {len(plugins)} plugins ({summary})")
|