Files
Daniel Avila 11a0c93715 feat: add support for agent subcategories with dynamic GitHub discovery (#56)
- Support agent installation from subcategories (e.g., deep-research-team/academic-researcher)
- Add dynamic GitHub API integration to discover available agents in real-time
- Automatically organize agents by category folders
- Preserve folder structure when installing categorized agents
- Add comprehensive agent teams from merged PR #46:
  - Deep Research Team (9 specialized research agents)
  - FFmpeg Clip Team (6 media processing agents)
  - MCP Development Team (5 MCP server agents)
  - Obsidian Operations Team (5 knowledge management agents)
  - OCR Extraction Team (5 text processing agents)
  - Podcast Creator Team (9 content creation agents)
  - Plus individual specialized agents

Technical improvements:
- Remove hardcoded agent lists in favor of GitHub repository scanning
- Support both 'agent-name' and 'category/agent-name' formats
- Fallback mechanism when GitHub API is unavailable
- Maintains backward compatibility with existing CLI usage
- Real-time agent discovery without requiring CLI updates

Usage examples:
- cct --agent api-security-audit (existing agents)
- cct --agent deep-research-team/academic-researcher (new categorized agents)
- cct --agent help (shows all available agents dynamically)



🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-01 12:09:18 -04:00

296 lines
11 KiB
Python
Executable File

#!/usr/bin/env python3
"""
MOC (Map of Content) Generator for Obsidian Vault
Automatically generates MOCs for directories and topics.
"""
import os
import re
from pathlib import Path
from datetime import datetime
from collections import defaultdict
import argparse
class MOCGenerator:
def __init__(self, vault_path):
self.vault_path = Path(vault_path)
self.directory_stats = {}
self.topic_clusters = defaultdict(list)
def analyze_directory(self, directory_path):
"""Analyze a directory and its contents."""
stats = {
'total_files': 0,
'md_files': 0,
'subdirectories': [],
'file_types': defaultdict(int),
'common_topics': defaultdict(int)
}
try:
for item in directory_path.iterdir():
if item.is_file():
stats['total_files'] += 1
if item.suffix == '.md':
stats['md_files'] += 1
# Extract topics from filename
topics = self.extract_topics_from_filename(item.name)
for topic in topics:
stats['common_topics'][topic] += 1
stats['file_types'][item.suffix] += 1
elif item.is_dir() and not item.name.startswith('.'):
stats['subdirectories'].append(item.name)
except PermissionError:
print(f"Permission denied: {directory_path}")
return stats
def extract_topics_from_filename(self, filename):
"""Extract potential topics from filename."""
# Remove extension and common prefixes
name = filename.replace('.md', '')
name = re.sub(r'^\d{4}-\d{2}-\d{2}[_-]', '', name) # Remove date prefix
name = re.sub(r'^MOC[_-]', '', name) # Remove MOC prefix
# Split on common separators and filter
words = re.split(r'[_\-\s]+', name)
topics = []
# Filter out common words and keep meaningful terms
stop_words = {'and', 'the', 'for', 'with', 'to', 'of', 'in', 'on', 'at', 'by'}
for word in words:
if len(word) > 2 and word.lower() not in stop_words:
topics.append(word.lower())
return topics
def generate_moc_content(self, directory_path, title, description=""):
"""Generate MOC content for a directory."""
stats = self.analyze_directory(directory_path)
# Create frontmatter
frontmatter = f"""---
tags: [MOC, {directory_path.name.lower().replace(' ', '-')}]
type: map-of-content
created: {datetime.now().strftime('%Y-%m-%d')}
modified: {datetime.now().strftime('%Y-%m-%d')}
status: active
cssclass: moc
aliases: [{title} Hub, {title} Overview]
hub_for: [{directory_path.name}]
related_mocs: []
---
"""
# Create content
content = f"""# {title} Map of Content
## Overview
{description if description else f"This MOC organizes all content related to {title.lower()}."}
**Directory**: `{directory_path.name}/`
**Total Files**: {stats['md_files']} markdown files
**Last Updated**: {datetime.now().strftime('%Y-%m-%d')}
"""
# Add subdirectories if any
if stats['subdirectories']:
content += "## Subdirectories\n\n"
for subdir in sorted(stats['subdirectories']):
content += f"### {subdir}\n"
content += f"- [[MOC - {subdir}|{subdir} Overview]]\n\n"
# Organize files by topic
content += self.organize_files_by_topic(directory_path, stats)
# Add common topics section
if stats['common_topics']:
content += "## Key Topics\n\n"
sorted_topics = sorted(stats['common_topics'].items(), key=lambda x: x[1], reverse=True)
for topic, count in sorted_topics[:10]: # Top 10 topics
content += f"- **{topic.title()}** ({count} files)\n"
content += "\n"
# Add templates and resources
content += """## Related MOCs
- [[MOC - AI Development|AI Development]]
- [[MOC - Learning Resources|Learning Resources]]
- [[Master Index|🗂️ Master Index]]
## Status & Progress
- [ ] Organize files by topic
- [ ] Add cross-references
- [ ] Review and update links
- [ ] Add examples and tutorials
## Next Steps
- Review all files in this directory
- Create sub-MOCs for large topic clusters
- Add relevant tags to all files
- Connect to related MOCs
---
*This MOC was auto-generated. Please review and customize as needed.*
"""
return frontmatter + content
def organize_files_by_topic(self, directory_path, stats):
"""Organize files by topic clusters."""
content = "## Content Organization\n\n"
# Group files by topic
topic_files = defaultdict(list)
ungrouped_files = []
try:
for file_path in directory_path.glob('*.md'):
if file_path.name.startswith('MOC'):
continue # Skip existing MOCs
topics = self.extract_topics_from_filename(file_path.name)
if topics:
# Use first topic as primary grouping
primary_topic = topics[0]
topic_files[primary_topic].append(file_path)
else:
ungrouped_files.append(file_path)
except Exception as e:
print(f"Error organizing files: {e}")
return content
# Sort topics by number of files
sorted_topics = sorted(topic_files.items(), key=lambda x: len(x[1]), reverse=True)
for topic, files in sorted_topics:
if len(files) > 1: # Only create sections for topics with multiple files
content += f"### {topic.title()}\n"
for file_path in sorted(files):
title = file_path.stem.replace('_', ' ').replace('-', ' ')
content += f"- [[{title}]]\n"
content += "\n"
# Add ungrouped files
if ungrouped_files:
content += "### Other Files\n"
for file_path in sorted(ungrouped_files):
title = file_path.stem.replace('_', ' ').replace('-', ' ')
content += f"- [[{title}]]\n"
content += "\n"
return content
def create_moc_file(self, directory_path, title, description="", output_path=None):
"""Create a MOC file for a directory."""
content = self.generate_moc_content(directory_path, title, description)
if output_path is None:
# Create MOCs in the centralized map-of-content directory
moc_dir = self.vault_path / "map-of-content"
moc_dir.mkdir(exist_ok=True)
output_path = moc_dir / f"MOC - {title}.md"
# Check if file already exists
if output_path.exists():
print(f"MOC already exists: {output_path}")
return False
try:
with open(output_path, 'w', encoding='utf-8') as f:
f.write(content)
print(f"Created MOC: {output_path}")
return True
except Exception as e:
print(f"Error creating MOC: {e}")
return False
def suggest_mocs(self):
"""Suggest MOCs for directories that don't have them."""
suggestions = []
# Skip these directories
skip_dirs = {'.obsidian', '.trash', 'System_Files', '.git'}
for directory in self.vault_path.iterdir():
if not directory.is_dir() or directory.name in skip_dirs:
continue
stats = self.analyze_directory(directory)
# Check if directory has enough content to warrant a MOC
if stats['md_files'] >= 3:
# Check if MOC already exists
existing_mocs = list(directory.glob('MOC*.md'))
if not existing_mocs:
suggestions.append({
'directory': directory,
'title': directory.name,
'file_count': stats['md_files'],
'subdirs': len(stats['subdirectories']),
'top_topics': sorted(stats['common_topics'].items(),
key=lambda x: x[1], reverse=True)[:5]
})
return suggestions
def main():
parser = argparse.ArgumentParser(description='Generate MOCs for Obsidian vault')
parser.add_argument('--vault', default='/Users/cam/VAULT01',
help='Path to Obsidian vault')
parser.add_argument('--directory',
help='Specific directory to create MOC for')
parser.add_argument('--title',
help='Title for the MOC')
parser.add_argument('--description',
help='Description for the MOC')
parser.add_argument('--suggest', action='store_true',
help='Suggest directories that need MOCs')
parser.add_argument('--create-all', action='store_true',
help='Create MOCs for all suggested directories')
args = parser.parse_args()
generator = MOCGenerator(args.vault)
if args.suggest or args.create_all:
suggestions = generator.suggest_mocs()
if args.suggest:
print("MOC Suggestions:")
print("="*50)
for suggestion in suggestions:
print(f"Directory: {suggestion['directory'].name}")
print(f" Files: {suggestion['file_count']}")
print(f" Subdirs: {suggestion['subdirs']}")
if suggestion['top_topics']:
topics = [f"{topic} ({count})" for topic, count in suggestion['top_topics']]
print(f" Topics: {', '.join(topics)}")
print()
if args.create_all:
for suggestion in suggestions:
generator.create_moc_file(
suggestion['directory'],
suggestion['title']
)
elif args.directory:
directory_path = Path(args.vault) / args.directory
if not directory_path.exists():
print(f"Directory not found: {directory_path}")
return
title = args.title or directory_path.name
description = args.description or ""
generator.create_moc_file(directory_path, title, description)
else:
parser.print_help()
if __name__ == '__main__':
main()