refactor: 合并 claude-marketplace,重构目录结构为单一仓库
- 重命名 plugins/ → skills/,个人插件迁移到 skills-personal/(gitignore) - 更新 generate-marketplace.py 支持 config 读取和 skills-personal 扫描 - 新增 claude-config.yaml(技能启用/禁用 + MCP 配置) - 新增 init.sh(交互式 MCP 初始化,支持 stdio/SSE 模式) - 新增 CLAUDE.md 项目说明 - 重写 README.md 反映新结构 - 删除过时脚本:PUSH.sh、generate-marketplace.sh、convert-skills.sh Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,57 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import yaml
|
||||
HAS_YAML = True
|
||||
except ImportError:
|
||||
HAS_YAML = False
|
||||
|
||||
# Paths
|
||||
script_dir = Path(__file__).parent.resolve()
|
||||
plugins_dir = script_dir / "plugins"
|
||||
skills_dir = script_dir / "skills"
|
||||
personal_dir = script_dir / "skills-personal"
|
||||
config_file = script_dir / "claude-config.yaml"
|
||||
marketplace_file = script_dir / ".claude-plugin" / "marketplace.json"
|
||||
|
||||
|
||||
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']):
|
||||
@@ -26,31 +69,61 @@ def get_category_and_keywords(plugin_name):
|
||||
else:
|
||||
return "utility", ["utility", "tools"]
|
||||
|
||||
# Collect plugins
|
||||
|
||||
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()
|
||||
personal_skills_dir = script_dir / personal_dir_name
|
||||
|
||||
# Collect plugins from skills/ and skills-personal/
|
||||
plugins = []
|
||||
for plugin_dir in sorted(plugins_dir.glob("*-plugin")):
|
||||
if not plugin_dir.is_dir():
|
||||
continue
|
||||
print("Scanning skills/ ...")
|
||||
plugins.extend(scan_plugins(skills_dir, "./skills", disabled_skills))
|
||||
print(f" Found {len(plugins)} public plugins")
|
||||
|
||||
manifest_path = plugin_dir / ".claude-plugin" / "plugin.json"
|
||||
if not manifest_path.exists():
|
||||
continue
|
||||
|
||||
with open(manifest_path) as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
plugin_name = plugin_dir.name
|
||||
category, keywords = get_category_and_keywords(plugin_name)
|
||||
|
||||
plugins.append({
|
||||
"name": plugin_name,
|
||||
"source": f"./plugins/{plugin_name}",
|
||||
"description": manifest.get("description", f"Plugin for {plugin_name}"),
|
||||
"version": manifest.get("version", "1.0.0"),
|
||||
"category": category,
|
||||
"keywords": keywords,
|
||||
"strict": False
|
||||
})
|
||||
personal_count = 0
|
||||
if personal_skills_dir.is_dir():
|
||||
print(f"Scanning {personal_dir_name}/ ...")
|
||||
personal_plugins = scan_plugins(personal_skills_dir, f"./{personal_dir_name}", disabled_skills)
|
||||
personal_count = len(personal_plugins)
|
||||
plugins.extend(personal_plugins)
|
||||
print(f" Found {personal_count} personal plugins")
|
||||
|
||||
# Create marketplace
|
||||
marketplace = {
|
||||
@@ -62,13 +135,16 @@ marketplace = {
|
||||
"metadata": {
|
||||
"description": "Custom Claude Code plugins for development workflows, DevOps, and business operations",
|
||||
"version": "1.0.0",
|
||||
"pluginRoot": "./plugins"
|
||||
"pluginRoot": "./skills"
|
||||
},
|
||||
"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)
|
||||
|
||||
print(f"✓ Generated marketplace.json with {len(plugins)} plugins")
|
||||
print(f"\n✓ Generated marketplace.json with {len(plugins)} plugins ({personal_count} personal)")
|
||||
|
||||
Reference in New Issue
Block a user