main
py 265 lines 8.03 KB
Raw
1 #!/usr/bin/env python3
2 """
3 Skills CLI - Easy skill management for Agent Zero
4
5 Usage:
6 python -m helpers.skills_cli list List all skills
7 python -m helpers.skills_cli create <name> Create a new skill
8 python -m helpers.skills_cli show <name> Show skill details
9 python -m helpers.skills_cli validate <name> Validate a skill
10 python -m helpers.skills_cli search <query> Search skills
11 """
12
13 import argparse
14 import sys
15 from pathlib import Path
16
17 from helpers import files, skills as skills_runtime
18
19
20 Skill = skills_runtime.Skill
21
22
23 def get_skills_dirs() -> list[Path]:
24 """Get all skill directories"""
25 return [Path(root) for root in skills_runtime.get_skill_roots()]
26
27
28 def parse_skill_file(skill_path: Path) -> Skill | None:
29 """Parse a SKILL.md file and return a Skill object"""
30 return skills_runtime.skill_from_markdown(
31 skill_path,
32 include_content=True,
33 validate=False,
34 )
35
36
37 def list_skills() -> list[Skill]:
38 """List all available skills"""
39 return skills_runtime.list_skills(include_content=True)
40
41
42 def find_skill(name: str) -> Skill | None:
43 """Find a skill by name"""
44 return skills_runtime.find_skill(name, include_content=True, validate=False)
45
46
47 def search_skills(query: str) -> list[Skill]:
48 """Search skills by name, description, or tags"""
49 return skills_runtime.search_skills(query)
50
51
52 def validate_skill(skill: Skill) -> list[str]:
53 """Validate a skill and return list of issues"""
54 return skills_runtime.validate_skill(skill)
55
56
57 def create_skill(name: str, description: str = "", author: str = "") -> Path:
58 """Create a new skill from template"""
59 # Use custom directory for user-created skills
60 custom_dir = Path(files.get_abs_path("usr", "skills", "custom"))
61 custom_dir.mkdir(parents=True, exist_ok=True)
62
63 skill_dir = custom_dir / name
64 if skill_dir.exists():
65 raise ValueError(f"Skill '{name}' already exists at {skill_dir}")
66
67 # Create directory structure
68 skill_dir.mkdir(parents=True)
69 (skill_dir / "scripts").mkdir()
70 (skill_dir / "docs").mkdir()
71
72 # Create SKILL.md from template
73 skill_content = f'''---
74 name: "{name}"
75 description: "{description or 'Description of what this skill does and when to use it'}"
76 version: "1.0.0"
77 author: "{author or 'Your Name'}"
78 tags: ["custom"]
79 triggers:
80 - "{name}"
81 ---
82
83 # {name.replace("-", " ").replace("_", " ").title()}
84
85 ## When to Use
86
87 Describe when this skill should be activated.
88
89 ## Instructions
90
91 Provide detailed instructions for the agent to follow.
92
93 ### Step 1: First Step
94
95 Description of what to do first.
96
97 ### Step 2: Second Step
98
99 Description of what to do next.
100
101 ## Examples
102
103 **User**: "Example prompt that triggers this skill"
104
105 **Agent Response**:
106 > Example of how the agent should respond
107
108 ## Tips
109
110 - Tip 1: Helpful guidance
111 - Tip 2: More helpful guidance
112
113 ## Anti-Patterns
114
115 - Don't do this
116 - Avoid that
117 '''
118
119 skill_file = skill_dir / "SKILL.md"
120 skill_file.write_text(skill_content, encoding="utf-8")
121
122 # Create placeholder README in docs
123 readme = skill_dir / "docs" / "README.md"
124 readme.write_text(f"# {name}\n\nAdditional documentation for the {name} skill.\n")
125
126 return skill_dir
127
128
129 def print_skill_table(skills: list[Skill]):
130 """Print skills in a formatted table"""
131 if not skills:
132 print("No skills found.")
133 return
134
135 # Calculate column widths
136 name_width = max(len(s.name) for s in skills) + 2
137 desc_width = 50
138
139 # Print header
140 print(f"\n{'Name':<{name_width}} {'Version':<10} {'Tags':<20} Description")
141 print("-" * (name_width + 80))
142
143 # Print skills
144 for skill in skills:
145 tags = ", ".join(skill.tags[:3])
146 if len(skill.tags) > 3:
147 tags += "..."
148 desc = skill.description[:desc_width]
149 if len(skill.description) > desc_width:
150 desc += "..."
151 print(f"{skill.name:<{name_width}} {skill.version:<10} {tags:<20} {desc}")
152
153 print(f"\nTotal: {len(skills)} skills")
154
155
156 def main():
157 parser = argparse.ArgumentParser(
158 description="Agent Zero Skills CLI",
159 formatter_class=argparse.RawDescriptionHelpFormatter,
160 epilog="""
161 Examples:
162 %(prog)s list List all skills
163 %(prog)s create my-skill Create a new skill
164 %(prog)s show brainstorming Show skill details
165 %(prog)s validate my-skill Validate a skill
166 %(prog)s search python Search for skills
167 """
168 )
169
170 subparsers = parser.add_subparsers(dest="command", help="Available commands")
171
172 # List command
173 list_parser = subparsers.add_parser("list", help="List all skills")
174 list_parser.add_argument("--tags", help="Filter by tags (comma-separated)")
175
176 # Create command
177 create_parser = subparsers.add_parser("create", help="Create a new skill")
178 create_parser.add_argument("name", help="Skill name (lowercase, use hyphens)")
179 create_parser.add_argument("-d", "--description", help="Skill description")
180 create_parser.add_argument("-a", "--author", help="Author name")
181
182 # Show command
183 show_parser = subparsers.add_parser("show", help="Show skill details")
184 show_parser.add_argument("name", help="Skill name")
185
186 # Validate command
187 validate_parser = subparsers.add_parser("validate", help="Validate a skill")
188 validate_parser.add_argument("name", help="Skill name")
189
190 # Search command
191 search_parser = subparsers.add_parser("search", help="Search skills")
192 search_parser.add_argument("query", help="Search query")
193
194 args = parser.parse_args()
195
196 if args.command == "list":
197 skills = list_skills()
198 if args.tags:
199 filter_tags = [t.strip().lower() for t in args.tags.split(",")]
200 skills = [s for s in skills if any(t in [tag.lower() for tag in s.tags] for t in filter_tags)]
201 print_skill_table(skills)
202
203 elif args.command == "create":
204 try:
205 skill_dir = create_skill(args.name, args.description, args.author)
206 print(f"\n✅ Created skill at: {skill_dir}")
207 print(f"\nNext steps:")
208 print(f" 1. Edit {skill_dir / 'SKILL.md'} to add your instructions")
209 print(f" 2. Add any helper scripts to {skill_dir / 'scripts'}/")
210 print(f" 3. Run: python -m helpers.skills_cli validate {args.name}")
211 except ValueError as e:
212 print(f"\n❌ Error: {e}")
213 sys.exit(1)
214
215 elif args.command == "show":
216 skill = find_skill(args.name)
217 if skill:
218 print(f"\n{'=' * 60}")
219 print(f"Skill: {skill.name}")
220 print(f"{'=' * 60}")
221 print(f"Version: {skill.version}")
222 print(f"Author: {skill.author or 'Unknown'}")
223 print(f"Path: {skill.path}")
224 print(f"Tags: {', '.join(skill.tags) if skill.tags else 'None'}")
225 print(f"Triggers: {', '.join(skill.triggers) if skill.triggers else 'None'}")
226 print(f"\nDescription:")
227 print(f" {skill.description}")
228 print(f"\nContent Preview (first 500 chars):")
229 print("-" * 60)
230 print(skill.content[:500])
231 if len(skill.content) > 500:
232 print("...")
233 print("-" * 60)
234 else:
235 print(f"\n❌ Skill '{args.name}' not found")
236 sys.exit(1)
237
238 elif args.command == "validate":
239 skill = find_skill(args.name)
240 if skill:
241 issues = validate_skill(skill)
242 if issues:
243 print(f"\n⚠️ Validation issues for '{args.name}':")
244 for issue in issues:
245 print(f" - {issue}")
246 else:
247 print(f"\n✅ Skill '{args.name}' is valid!")
248 else:
249 print(f"\n❌ Skill '{args.name}' not found")
250 sys.exit(1)
251
252 elif args.command == "search":
253 results = search_skills(args.query)
254 if results:
255 print(f"\nSearch results for '{args.query}':")
256 print_skill_table(results)
257 else:
258 print(f"\nNo skills found matching '{args.query}'")
259
260 else:
261 parser.print_help()
262
263
264 if __name__ == "__main__":
265 main()