scaffold-setup-skill.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #!/usr/bin/env python3
  2. # /// script
  3. # requires-python = ">=3.10"
  4. # ///
  5. """Scaffold a BMad module setup skill from template.
  6. Copies the setup-skill-template into the target directory as {code}-setup/,
  7. then writes the generated module.yaml and module-help.csv into the assets folder
  8. and updates the SKILL.md frontmatter with the module's identity.
  9. """
  10. import argparse
  11. import json
  12. import shutil
  13. import sys
  14. from pathlib import Path
  15. def main() -> int:
  16. parser = argparse.ArgumentParser(
  17. description="Scaffold a BMad module setup skill from template"
  18. )
  19. parser.add_argument(
  20. "--target-dir",
  21. required=True,
  22. help="Directory to create the setup skill in (the user's skills folder)",
  23. )
  24. parser.add_argument(
  25. "--module-code",
  26. required=True,
  27. help="Module code (2-4 letter abbreviation, e.g. 'cis')",
  28. )
  29. parser.add_argument(
  30. "--module-name",
  31. required=True,
  32. help="Module display name (e.g. 'Creative Intelligence Suite')",
  33. )
  34. parser.add_argument(
  35. "--module-yaml",
  36. required=True,
  37. help="Path to the generated module.yaml content file",
  38. )
  39. parser.add_argument(
  40. "--module-csv",
  41. required=True,
  42. help="Path to the generated module-help.csv content file",
  43. )
  44. parser.add_argument(
  45. "--verbose", action="store_true", help="Print progress to stderr"
  46. )
  47. args = parser.parse_args()
  48. template_dir = Path(__file__).resolve().parent.parent / "assets" / "setup-skill-template"
  49. setup_skill_name = f"{args.module_code}-setup"
  50. target = Path(args.target_dir) / setup_skill_name
  51. if not template_dir.is_dir():
  52. print(
  53. json.dumps({"status": "error", "message": f"Template not found: {template_dir}"}),
  54. file=sys.stdout,
  55. )
  56. return 2
  57. for source_path in [args.module_yaml, args.module_csv]:
  58. if not Path(source_path).is_file():
  59. print(
  60. json.dumps({"status": "error", "message": f"Source file not found: {source_path}"}),
  61. file=sys.stdout,
  62. )
  63. return 2
  64. target_dir = Path(args.target_dir)
  65. if not target_dir.is_dir():
  66. print(
  67. json.dumps({"status": "error", "message": f"Target directory not found: {target_dir}"}),
  68. file=sys.stdout,
  69. )
  70. return 2
  71. # Remove existing setup skill if present (anti-zombie)
  72. if target.exists():
  73. if args.verbose:
  74. print(f"Removing existing {setup_skill_name}/", file=sys.stderr)
  75. shutil.rmtree(target)
  76. # Copy template
  77. if args.verbose:
  78. print(f"Copying template to {target}", file=sys.stderr)
  79. shutil.copytree(template_dir, target)
  80. # Update SKILL.md frontmatter placeholders
  81. skill_md = target / "SKILL.md"
  82. content = skill_md.read_text(encoding="utf-8")
  83. content = content.replace("{setup-skill-name}", setup_skill_name)
  84. content = content.replace("{module-name}", args.module_name)
  85. content = content.replace("{module-code}", args.module_code)
  86. skill_md.write_text(content, encoding="utf-8")
  87. # Write generated module.yaml
  88. yaml_content = Path(args.module_yaml).read_text(encoding="utf-8")
  89. (target / "assets" / "module.yaml").write_text(yaml_content, encoding="utf-8")
  90. # Write generated module-help.csv
  91. csv_content = Path(args.module_csv).read_text(encoding="utf-8")
  92. (target / "assets" / "module-help.csv").write_text(csv_content, encoding="utf-8")
  93. # Collect file list
  94. files_created = sorted(
  95. str(p.relative_to(target)) for p in target.rglob("*") if p.is_file()
  96. )
  97. result = {
  98. "status": "success",
  99. "setup_skill": setup_skill_name,
  100. "location": str(target),
  101. "files_created": files_created,
  102. "files_count": len(files_created),
  103. }
  104. print(json.dumps(result, indent=2))
  105. return 0
  106. if __name__ == "__main__":
  107. sys.exit(main())