Files
english/.opencode/scripts/scan_skills.py
2026-04-12 01:06:31 +07:00

264 lines
8.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Scan .opencode/skills directory and extract skill metadata.
"""
import re
from pathlib import Path
from typing import Dict, List
try:
import yaml
except ModuleNotFoundError:
raise SystemExit(
"PyYAML is required. Install with: python3 -m pip install -r .opencode/scripts/requirements.txt"
)
# Exact mappings for high-signal CK skills to avoid falling into "other".
EXACT_CATEGORY_MAP = {
# Utilities & Helpers
"ask": "utilities",
"bootstrap": "utilities",
"brainstorm": "utilities",
"ck-autoresearch": "utilities",
"ck-debug": "utilities",
"ck-loop": "utilities",
"ck-predict": "utilities",
"ck-scenario": "utilities",
"code-review": "utilities",
"coding-level": "utilities",
"context-engineering": "utilities",
"cook": "utilities",
"copywriting": "utilities",
"debug": "utilities",
"docs": "utilities",
"fix": "utilities",
"journal": "utilities",
"markdown-novel-viewer": "utilities",
"mermaidjs-v11": "utilities",
"plan": "utilities",
"ck-plan": "utilities",
"preview": "utilities",
"problem-solving": "utilities",
"project-management": "utilities",
"project-organization": "utilities",
"research": "utilities",
"retro": "utilities",
"sequential-thinking": "utilities",
"test": "utilities",
"watzup": "utilities",
# Development Tools
"find-skills": "dev-tools",
"git": "dev-tools",
"gkg": "dev-tools",
"kanban": "dev-tools",
"llms": "dev-tools",
"mintlify": "dev-tools",
"plans-kanban": "dev-tools",
"scout": "dev-tools",
"ship": "dev-tools",
"team": "dev-tools",
"use-mcp": "dev-tools",
"worktree": "dev-tools",
# Frontend & Design
"react-best-practices": "frontend",
"remotion": "frontend",
"shader": "frontend",
"stitch": "frontend",
"web-design-guidelines": "frontend",
# Frameworks & Platforms
"tanstack": "frameworks",
# Infrastructure & DevOps
"deploy": "infrastructure",
# Multimedia & Processing
"agent-browser": "multimedia",
"web-testing": "multimedia",
# Security (mapped to utilities)
"ck-security": "utilities",
"security-scan": "utilities",
}
def extract_frontmatter(content: str) -> Dict:
"""Extract YAML frontmatter from markdown content."""
match = re.match(r'^---\s*\n(.*?)\n---\s*\n', content, re.DOTALL)
if match:
try:
return yaml.safe_load(match.group(1))
except:
return {}
return {}
def extract_first_paragraph(content: str) -> str:
"""Extract first meaningful paragraph after frontmatter."""
# Remove frontmatter
content = re.sub(r'^---\s*\n.*?\n---\s*\n', '', content, flags=re.DOTALL)
# Find first paragraph (after headings)
lines = content.split('\n')
paragraph = []
for line in lines:
line = line.strip()
# Skip headings and empty lines
if line.startswith('#') or not line:
if paragraph: # If we've started collecting, stop
break
continue
paragraph.append(line)
# Stop after first paragraph
if line.endswith('.') and len(' '.join(paragraph)) > 50:
break
return ' '.join(paragraph)[:200]
def scan_skills(base_path: Path) -> List[Dict]:
"""Scan all skill files and extract metadata."""
skills = []
for skill_file in sorted(base_path.rglob('SKILL.md')):
# Get skill directory name
skill_dir = skill_file.parent
skill_name = skill_dir.name
# Skip template
if skill_name == 'template-skill':
continue
# Handle nested skills (like document-skills/*)
if skill_dir.parent.name != 'skills':
parent_name = skill_dir.parent.name
skill_name = f"{parent_name}/{skill_name}"
try:
content = skill_file.read_text()
frontmatter = extract_frontmatter(content)
description = frontmatter.get('description', '')
if not description:
description = extract_first_paragraph(content)
# Categorize based on content/name
category = categorize_skill(skill_name, description, content)
skill_entry = {
'name': skill_name,
'path': str(skill_file.relative_to(Path('.opencode/skills'))),
'description': description,
'category': category,
'has_scripts': (skill_dir / 'scripts').exists(),
'has_references': (skill_dir / 'references').exists()
}
# Include argument-hint if present in frontmatter
argument_hint = frontmatter.get('argument-hint', '')
if argument_hint:
skill_entry['argument_hint'] = str(argument_hint)
skills.append(skill_entry)
except Exception as e:
print(f"Error processing {skill_file}: {e}")
return skills
def categorize_skill(name: str, description: str, content: str) -> str:
"""Categorize skill based on name and content."""
lower_name = name.lower()
if lower_name in EXACT_CATEGORY_MAP:
return EXACT_CATEGORY_MAP[lower_name]
# AI/ML
if any(x in lower_name for x in ['ai-', 'gemini', 'multimodal', 'adk']):
return 'ai-ml'
# Frontend
if any(x in lower_name for x in ['frontend', 'ui', 'design', 'aesthetic', 'threejs']):
return 'frontend'
# Backend
if any(x in lower_name for x in ['backend', 'auth', 'payment']):
return 'backend'
# Infrastructure
if any(x in lower_name for x in ['devops', 'docker', 'cloudflare', 'gcloud']):
return 'infrastructure'
# Database
if any(x in lower_name for x in ['database', 'mongodb', 'postgresql', 'sql']):
return 'database'
# Development Tools
if any(x in lower_name for x in ['mcp', 'skill-creator', 'repomix', 'docs-seeker']):
return 'dev-tools'
# Multimedia
if any(x in lower_name for x in ['media', 'chrome-devtools', 'document-skills']):
return 'multimedia'
# Frameworks
if any(x in lower_name for x in ['web-frameworks', 'mobile', 'shopify']):
return 'frameworks'
# Utilities
if any(x in lower_name for x in ['debug', 'problem', 'code-review', 'planning', 'research', 'sequential']):
return 'utilities'
return 'other'
def group_by_category(skills: List[Dict]) -> Dict[str, List[Dict]]:
"""Group skills by category."""
categories = {}
for skill in skills:
category = skill['category']
if category not in categories:
categories[category] = []
categories[category].append(skill)
return categories
def main():
"""Main execution."""
base_path = Path('.opencode/skills')
if not base_path.exists():
print(f"Error: {base_path} not found")
return
print("Scanning skills...")
skills = scan_skills(base_path)
print(f"\nFound {len(skills)} skills\n")
# Group by category
categories = group_by_category(skills)
category_names = {
'ai-ml': 'AI & Machine Learning',
'frontend': 'Frontend & Design',
'backend': 'Backend Development',
'infrastructure': 'Infrastructure & DevOps',
'database': 'Database & Storage',
'dev-tools': 'Development Tools',
'multimedia': 'Multimedia & Processing',
'frameworks': 'Frameworks & Platforms',
'utilities': 'Utilities & Helpers',
'other': 'Other'
}
for category, skills_list in sorted(categories.items()):
print(f"\n{category_names.get(category, category.upper())}:")
for skill in skills_list:
scripts = '📦' if skill['has_scripts'] else ' '
refs = '📚' if skill['has_references'] else ' '
print(f" {scripts}{refs} {skill['name']:30} {skill['description'][:80]}")
# Output YAML to scripts directory
output_path = Path('.opencode/scripts/skills_data.yaml')
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(yaml.dump(skills, allow_unicode=True, default_flow_style=False))
print(f"\n✓ Saved metadata to {output_path}")
if __name__ == '__main__':
main()