convert_to_qoder.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. #!/usr/bin/env python3
  2. """
  3. Convert Agent Skills to Qoder Agent format
  4. This script converts Agent Skills to Qoder agent module format.
  5. Qoder is an agentic coding platform that supports agent-based automation.
  6. Usage:
  7. python convert_to_qoder.py <skill_path> [output_path]
  8. Example:
  9. python convert_to_qoder.py ../../skills/course-designer qoder-agents/
  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_steps(body):
  46. """Extract workflow steps from skill body"""
  47. steps = []
  48. # Look for numbered lists or workflow sections
  49. lines = body.split('\n')
  50. current_step = None
  51. for line in lines:
  52. # Match numbered list items
  53. match = re.match(r'^(\d+)\.\s+(.+)$', line.strip())
  54. if match:
  55. step_num = match.group(1)
  56. step_desc = match.group(2)
  57. steps.append({
  58. "step": f"step-{step_num}",
  59. "description": step_desc
  60. })
  61. # Match ## sections as workflow steps
  62. elif line.startswith('## ') and '流程' in line or 'workflow' in line.lower():
  63. current_step = line.replace('##', '').strip()
  64. return steps if steps else [
  65. {"step": "analyze", "description": "Analyze requirements"},
  66. {"step": "execute", "description": "Execute skill task"},
  67. {"step": "output", "description": "Generate output"}
  68. ]
  69. def convert_to_qoder_agent(skill_path, output_dir=None):
  70. """
  71. Convert Agent Skill to Qoder agent format
  72. Args:
  73. skill_path: Path to skill directory containing SKILL.md
  74. output_dir: Output directory for agent (optional)
  75. """
  76. skill_path = Path(skill_path)
  77. skill_file = skill_path / "SKILL.md"
  78. if not skill_file.exists():
  79. print(f"Error: SKILL.md not found in {skill_path}")
  80. return None
  81. # Read skill file
  82. with open(skill_file, "r", encoding="utf-8") as f:
  83. content = f.read()
  84. # Extract frontmatter and body
  85. frontmatter, body = extract_frontmatter(content)
  86. if not frontmatter:
  87. print(f"Warning: No frontmatter found in {skill_file}")
  88. frontmatter = {"name": skill_path.name, "description": ""}
  89. # Extract workflow steps
  90. workflow_steps = extract_workflow_steps(body)
  91. # Extract capabilities from description
  92. capabilities = []
  93. description = frontmatter.get("description", "")
  94. if "课程设计" in description or "course" in description.lower():
  95. capabilities.append("course-design")
  96. if "代码" in description or "code" in description.lower():
  97. capabilities.append("code-generation")
  98. if "测试" in description or "test" in description.lower():
  99. capabilities.append("testing")
  100. if not capabilities:
  101. capabilities = [frontmatter.get("name", skill_path.name)]
  102. # Create Qoder agent configuration
  103. agent_config = {
  104. "name": f"{frontmatter.get('name', skill_path.name)}-agent",
  105. "description": frontmatter.get("description", ""),
  106. "version": "1.0.0",
  107. "type": "skill-agent",
  108. "capabilities": capabilities,
  109. "workflow": workflow_steps,
  110. "instructions": "SKILL.md",
  111. "metadata": {
  112. "source": "teaching-ai-skills",
  113. "original_skill": frontmatter.get("name", skill_path.name)
  114. }
  115. }
  116. # Write output
  117. if output_dir:
  118. output_dir = Path(output_dir)
  119. output_dir.mkdir(parents=True, exist_ok=True)
  120. # Create agent directory
  121. agent_dir = output_dir / agent_config["name"]
  122. agent_dir.mkdir(parents=True, exist_ok=True)
  123. # Write agent config
  124. config_file = agent_dir / "qoder-agent-config.json"
  125. with open(config_file, "w", encoding="utf-8") as f:
  126. json.dump(agent_config, f, indent=2, ensure_ascii=False)
  127. # Copy SKILL.md
  128. import shutil
  129. shutil.copy2(skill_file, agent_dir / "SKILL.md")
  130. # Create Python agent module
  131. python_module = agent_dir / f"{frontmatter.get('name', skill_path.name).replace('-', '_')}_agent.py"
  132. create_python_agent_module(frontmatter, body, python_module)
  133. print(f"✅ Converted to Qoder agent: {agent_dir}")
  134. return agent_dir
  135. else:
  136. return agent_config
  137. def create_python_agent_module(frontmatter, body, output_file):
  138. """Create Python agent module for Qoder"""
  139. skill_name = frontmatter.get("name", "skill")
  140. class_name = "".join(word.capitalize() for word in skill_name.split("-")) + "Agent"
  141. module_content = f'''"""
  142. {frontmatter.get('description', 'Agent module')} for Qoder
  143. This agent module was automatically generated from Agent Skill.
  144. """
  145. class {class_name}:
  146. """
  147. Agent for {frontmatter.get('description', 'executing tasks')}
  148. """
  149. def __init__(self):
  150. self.name = "{skill_name}"
  151. self.description = "{frontmatter.get('description', '')}"
  152. def execute(self, task, context=None):
  153. """
  154. Execute the agent task
  155. Args:
  156. task: Task description or parameters
  157. context: Optional context information
  158. Returns:
  159. Task execution result
  160. """
  161. # Read instructions from SKILL.md
  162. # Execute skill workflow
  163. # Return result
  164. pass
  165. def get_capabilities(self):
  166. """Get agent capabilities"""
  167. return {{
  168. "name": self.name,
  169. "description": self.description,
  170. "capabilities": ["task-execution"]
  171. }}
  172. '''
  173. with open(output_file, "w", encoding="utf-8") as f:
  174. f.write(module_content)
  175. def convert_all_skills(skills_dir, output_dir):
  176. """Convert all skills in a directory"""
  177. skills_dir = Path(skills_dir)
  178. output_dir = Path(output_dir)
  179. output_dir.mkdir(parents=True, exist_ok=True)
  180. converted = []
  181. for skill_dir in skills_dir.iterdir():
  182. if skill_dir.is_dir() and (skill_dir / "SKILL.md").exists():
  183. try:
  184. result = convert_to_qoder_agent(skill_dir, output_dir)
  185. if result:
  186. converted.append(skill_dir.name)
  187. except Exception as e:
  188. print(f"Error converting {skill_dir.name}: {e}")
  189. print(f"\n✅ Converted {len(converted)} skills: {', '.join(converted)}")
  190. return converted
  191. if __name__ == "__main__":
  192. if len(sys.argv) < 2:
  193. print("Usage: python convert_to_qoder.py <skill_path> [output_path]")
  194. print(" or: python convert_to_qoder.py --all <skills_dir> <output_dir>")
  195. sys.exit(1)
  196. if sys.argv[1] == "--all":
  197. if len(sys.argv) < 4:
  198. print("Usage: python convert_to_qoder.py --all <skills_dir> <output_dir>")
  199. sys.exit(1)
  200. convert_all_skills(sys.argv[2], sys.argv[3])
  201. else:
  202. skill_path = sys.argv[1]
  203. output_path = sys.argv[2] if len(sys.argv) > 2 else None
  204. convert_to_qoder_agent(skill_path, output_path)