convert_to_codebuddy.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #!/usr/bin/env python3
  2. """
  3. Convert Agent Skills to CodeBuddy Workflow format
  4. This script converts Agent Skills to CodeBuddy workflow format.
  5. CodeBuddy supports workflows and tips plugins.
  6. Usage:
  7. python convert_to_codebuddy.py <skill_path> [output_path]
  8. Example:
  9. python convert_to_codebuddy.py ../../skills/course-designer codebuddy-plugins/
  10. """
  11. import sys
  12. import json
  13. import re
  14. from pathlib import Path
  15. try:
  16. import yaml
  17. HAS_YAML = True
  18. except ImportError:
  19. HAS_YAML = False
  20. def parse_yaml_simple(text):
  21. """Simple YAML parser for frontmatter (handles basic key: value pairs)"""
  22. result = {}
  23. for line in text.split('\n'):
  24. line = line.strip()
  25. if ':' in line and not line.startswith('#'):
  26. key, value = line.split(':', 1)
  27. key = key.strip()
  28. value = value.strip().strip('"').strip("'")
  29. result[key] = value
  30. return result
  31. def extract_frontmatter(content):
  32. """Extract YAML frontmatter from SKILL.md"""
  33. frontmatter_pattern = r'^---\n(.*?)\n---\n'
  34. match = re.match(frontmatter_pattern, content, re.DOTALL)
  35. if match:
  36. frontmatter_text = match.group(1)
  37. if HAS_YAML:
  38. frontmatter = yaml.safe_load(frontmatter_text)
  39. else:
  40. frontmatter = parse_yaml_simple(frontmatter_text)
  41. body_start = match.end()
  42. body = content[body_start:].strip()
  43. return frontmatter, body
  44. return {}, content.strip()
  45. def extract_workflow_from_body(body):
  46. """Extract workflow steps from skill body"""
  47. workflow = {
  48. "name": "",
  49. "description": "",
  50. "steps": []
  51. }
  52. lines = body.split('\n')
  53. current_section = None
  54. for i, line in enumerate(lines):
  55. # Extract workflow name from title
  56. if line.startswith('# ') and not workflow["name"]:
  57. workflow["name"] = line.replace('#', '').strip()
  58. # Extract description
  59. elif line.startswith('## 概述') or line.startswith('## Overview'):
  60. if i + 1 < len(lines):
  61. workflow["description"] = lines[i + 1].strip()
  62. # Extract steps from numbered lists
  63. elif re.match(r'^\d+\.', line.strip()):
  64. step_text = re.sub(r'^\d+\.\s*', '', line.strip())
  65. workflow["steps"].append({
  66. "id": len(workflow["steps"]) + 1,
  67. "name": step_text.split(':')[0] if ':' in step_text else step_text,
  68. "description": step_text,
  69. "type": "instruction"
  70. })
  71. # If no steps found, create default workflow
  72. if not workflow["steps"]:
  73. workflow["steps"] = [
  74. {
  75. "id": 1,
  76. "name": "analyze",
  77. "description": "Analyze requirements",
  78. "type": "instruction"
  79. },
  80. {
  81. "id": 2,
  82. "name": "execute",
  83. "description": "Execute skill task",
  84. "type": "instruction"
  85. },
  86. {
  87. "id": 3,
  88. "name": "output",
  89. "description": "Generate output",
  90. "type": "instruction"
  91. }
  92. ]
  93. return workflow
  94. def convert_to_codebuddy_workflow(skill_path, output_dir=None):
  95. """
  96. Convert Agent Skill to CodeBuddy workflow format
  97. Args:
  98. skill_path: Path to skill directory containing SKILL.md
  99. output_dir: Output directory for plugin (optional)
  100. """
  101. skill_path = Path(skill_path)
  102. skill_file = skill_path / "SKILL.md"
  103. if not skill_file.exists():
  104. print(f"Error: SKILL.md not found in {skill_path}")
  105. return None
  106. # Read skill file
  107. with open(skill_file, "r", encoding="utf-8") as f:
  108. content = f.read()
  109. # Extract frontmatter and body
  110. frontmatter, body = extract_frontmatter(content)
  111. if not frontmatter:
  112. print(f"Warning: No frontmatter found in {skill_file}")
  113. frontmatter = {"name": skill_path.name, "description": ""}
  114. # Extract workflow
  115. workflow = extract_workflow_from_body(body)
  116. workflow["name"] = workflow["name"] or frontmatter.get("name", skill_path.name)
  117. workflow["description"] = workflow["description"] or frontmatter.get("description", "")
  118. # Create CodeBuddy plugin manifest
  119. plugin_manifest = {
  120. "name": frontmatter.get("name", skill_path.name),
  121. "version": "1.0.0",
  122. "description": frontmatter.get("description", ""),
  123. "author": "Teaching AI",
  124. "type": "workflow-plugin",
  125. "workflows": [
  126. {
  127. "id": frontmatter.get("name", skill_path.name),
  128. "name": workflow["name"],
  129. "description": workflow["description"],
  130. "steps": workflow["steps"],
  131. "inputs": [],
  132. "outputs": []
  133. }
  134. ]
  135. }
  136. # Write output
  137. if output_dir:
  138. output_dir = Path(output_dir)
  139. output_dir.mkdir(parents=True, exist_ok=True)
  140. # Create plugin directory
  141. plugin_dir = output_dir / frontmatter.get("name", skill_path.name)
  142. plugin_dir.mkdir(parents=True, exist_ok=True)
  143. # Create workflows directory
  144. workflows_dir = plugin_dir / "workflows"
  145. workflows_dir.mkdir(parents=True, exist_ok=True)
  146. # Write manifest
  147. manifest_file = plugin_dir / "plugin.json"
  148. with open(manifest_file, "w", encoding="utf-8") as f:
  149. json.dump(plugin_manifest, f, indent=2, ensure_ascii=False)
  150. # Write workflow file
  151. workflow_file = workflows_dir / f"{frontmatter.get('name', skill_path.name)}.json"
  152. with open(workflow_file, "w", encoding="utf-8") as f:
  153. json.dump(plugin_manifest["workflows"][0], f, indent=2, ensure_ascii=False)
  154. # Copy SKILL.md to skills directory
  155. skills_dir = plugin_dir / "skills" / frontmatter.get("name", skill_path.name)
  156. skills_dir.mkdir(parents=True, exist_ok=True)
  157. import shutil
  158. shutil.copy2(skill_file, skills_dir / "SKILL.md")
  159. # Copy resource directories
  160. resource_dirs = ["scripts", "references", "assets", "api", "templates", "examples"]
  161. for res_dir in resource_dirs:
  162. src_dir = skill_path / res_dir
  163. if src_dir.exists() and src_dir.is_dir():
  164. dest_dir = skills_dir / res_dir
  165. if dest_dir.exists():
  166. shutil.rmtree(dest_dir)
  167. shutil.copytree(src_dir, dest_dir)
  168. print(f"✅ Converted to CodeBuddy plugin: {plugin_dir}")
  169. return plugin_dir
  170. else:
  171. return plugin_manifest
  172. def convert_all_skills(skills_dir, output_dir):
  173. """Convert all skills in a directory"""
  174. skills_dir = Path(skills_dir)
  175. output_dir = Path(output_dir)
  176. output_dir.mkdir(parents=True, exist_ok=True)
  177. converted = []
  178. for skill_dir in skills_dir.iterdir():
  179. if skill_dir.is_dir() and (skill_dir / "SKILL.md").exists():
  180. try:
  181. result = convert_to_codebuddy_workflow(skill_dir, output_dir)
  182. if result:
  183. converted.append(skill_dir.name)
  184. except Exception as e:
  185. print(f"Error converting {skill_dir.name}: {e}")
  186. print(f"\n✅ Converted {len(converted)} skills: {', '.join(converted)}")
  187. return converted
  188. if __name__ == "__main__":
  189. if len(sys.argv) < 2:
  190. print("Usage: python convert_to_codebuddy.py <skill_path> [output_path]")
  191. print(" or: python convert_to_codebuddy.py --all <skills_dir> <output_dir>")
  192. sys.exit(1)
  193. if sys.argv[1] == "--all":
  194. if len(sys.argv) < 4:
  195. print("Usage: python convert_to_codebuddy.py --all <skills_dir> <output_dir>")
  196. sys.exit(1)
  197. convert_all_skills(sys.argv[2], sys.argv[3])
  198. else:
  199. skill_path = sys.argv[1]
  200. output_path = sys.argv[2] if len(sys.argv) > 2 else None
  201. convert_to_codebuddy_workflow(skill_path, output_path)