refactor(ux): consolidate BMAD skills, update design system, and clean up Prisma generated client
This commit is contained in:
190
.gemini/skills/bmad-module-builder/scripts/scaffold-standalone-module.py
Executable file
190
.gemini/skills/bmad-module-builder/scripts/scaffold-standalone-module.py
Executable file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# ///
|
||||
"""Scaffold standalone module infrastructure into an existing skill.
|
||||
|
||||
Copies template files (module-setup.md, merge scripts) into the skill directory
|
||||
and generates a .claude-plugin/marketplace.json for distribution. The LLM writes
|
||||
module.yaml and module-help.csv directly to the skill's assets/ folder before
|
||||
running this script.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Scaffold standalone module infrastructure into an existing skill"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skill-dir",
|
||||
required=True,
|
||||
help="Path to the existing skill directory (must contain SKILL.md)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--module-code",
|
||||
required=True,
|
||||
help="Module code (2-4 letter abbreviation, e.g. 'exc')",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--module-name",
|
||||
required=True,
|
||||
help="Module display name (e.g. 'Excalidraw Tools')",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--marketplace-dir",
|
||||
default=None,
|
||||
help="Directory to create .claude-plugin/ in (defaults to skill-dir parent)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--verbose", action="store_true", help="Print progress to stderr"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
template_dir = (
|
||||
Path(__file__).resolve().parent.parent
|
||||
/ "assets"
|
||||
/ "standalone-module-template"
|
||||
)
|
||||
skill_dir = Path(args.skill_dir).resolve()
|
||||
marketplace_dir = (
|
||||
Path(args.marketplace_dir).resolve() if args.marketplace_dir else skill_dir.parent
|
||||
)
|
||||
|
||||
# --- Validation ---
|
||||
|
||||
if not template_dir.is_dir():
|
||||
print(
|
||||
json.dumps({"status": "error", "message": f"Template not found: {template_dir}"}),
|
||||
file=sys.stdout,
|
||||
)
|
||||
return 2
|
||||
|
||||
if not skill_dir.is_dir():
|
||||
print(
|
||||
json.dumps({"status": "error", "message": f"Skill directory not found: {skill_dir}"}),
|
||||
file=sys.stdout,
|
||||
)
|
||||
return 2
|
||||
|
||||
if not (skill_dir / "SKILL.md").is_file():
|
||||
print(
|
||||
json.dumps({"status": "error", "message": f"No SKILL.md found in {skill_dir}"}),
|
||||
file=sys.stdout,
|
||||
)
|
||||
return 2
|
||||
|
||||
if not (skill_dir / "assets" / "module.yaml").is_file():
|
||||
print(
|
||||
json.dumps({
|
||||
"status": "error",
|
||||
"message": f"assets/module.yaml not found in {skill_dir} — the LLM must write it before running this script",
|
||||
}),
|
||||
file=sys.stdout,
|
||||
)
|
||||
return 2
|
||||
|
||||
# --- Copy template files ---
|
||||
|
||||
files_created: list[str] = []
|
||||
files_skipped: list[str] = []
|
||||
warnings: list[str] = []
|
||||
|
||||
# 1. Copy module-setup.md to assets/ (alongside module.yaml and module-help.csv)
|
||||
assets_dir = skill_dir / "assets"
|
||||
assets_dir.mkdir(exist_ok=True)
|
||||
src_setup = template_dir / "module-setup.md"
|
||||
dst_setup = assets_dir / "module-setup.md"
|
||||
if args.verbose:
|
||||
print(f"Copying module-setup.md to {dst_setup}", file=sys.stderr)
|
||||
dst_setup.write_bytes(src_setup.read_bytes())
|
||||
files_created.append("assets/module-setup.md")
|
||||
|
||||
# 2. Copy merge scripts to scripts/
|
||||
scripts_dir = skill_dir / "scripts"
|
||||
scripts_dir.mkdir(exist_ok=True)
|
||||
|
||||
for script_name in ("merge-config.py", "merge-help-csv.py"):
|
||||
src = template_dir / script_name
|
||||
dst = scripts_dir / script_name
|
||||
if dst.exists():
|
||||
msg = f"scripts/{script_name} already exists — skipped to avoid overwriting"
|
||||
files_skipped.append(f"scripts/{script_name}")
|
||||
warnings.append(msg)
|
||||
if args.verbose:
|
||||
print(f"SKIP: {msg}", file=sys.stderr)
|
||||
else:
|
||||
if args.verbose:
|
||||
print(f"Copying {script_name} to {dst}", file=sys.stderr)
|
||||
dst.write_bytes(src.read_bytes())
|
||||
dst.chmod(0o755)
|
||||
files_created.append(f"scripts/{script_name}")
|
||||
|
||||
# 3. Generate marketplace.json
|
||||
plugin_dir = marketplace_dir / ".claude-plugin"
|
||||
plugin_dir.mkdir(parents=True, exist_ok=True)
|
||||
marketplace_json = plugin_dir / "marketplace.json"
|
||||
|
||||
# Read module.yaml for description and version
|
||||
module_yaml_path = skill_dir / "assets" / "module.yaml"
|
||||
module_description = ""
|
||||
module_version = "1.0.0"
|
||||
try:
|
||||
yaml_text = module_yaml_path.read_text(encoding="utf-8")
|
||||
for line in yaml_text.splitlines():
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("description:"):
|
||||
module_description = stripped.split(":", 1)[1].strip().strip('"').strip("'")
|
||||
elif stripped.startswith("module_version:"):
|
||||
module_version = stripped.split(":", 1)[1].strip().strip('"').strip("'")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
skill_dir_name = skill_dir.name
|
||||
marketplace_data = {
|
||||
"name": args.module_code,
|
||||
"owner": {"name": ""},
|
||||
"license": "",
|
||||
"homepage": "",
|
||||
"repository": "",
|
||||
"keywords": ["bmad"],
|
||||
"plugins": [
|
||||
{
|
||||
"name": args.module_code,
|
||||
"source": "./",
|
||||
"description": module_description,
|
||||
"version": module_version,
|
||||
"author": {"name": ""},
|
||||
"skills": [f"./{skill_dir_name}"],
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
if args.verbose:
|
||||
print(f"Writing marketplace.json to {marketplace_json}", file=sys.stderr)
|
||||
marketplace_json.write_text(
|
||||
json.dumps(marketplace_data, indent=2) + "\n", encoding="utf-8"
|
||||
)
|
||||
files_created.append(".claude-plugin/marketplace.json")
|
||||
|
||||
# --- Result ---
|
||||
|
||||
result = {
|
||||
"status": "success",
|
||||
"skill_dir": str(skill_dir),
|
||||
"module_code": args.module_code,
|
||||
"files_created": files_created,
|
||||
"files_skipped": files_skipped,
|
||||
"warnings": warnings,
|
||||
"marketplace_json": str(marketplace_json),
|
||||
}
|
||||
print(json.dumps(result, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user