#!/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 = [ ("core", "skills-core"), ("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})")