Files
2025-12-09 12:13:01 +01:00

214 lines
6.8 KiB
Python

"""
Base class for EdgarTools AI skills.
Provides the foundation for creating AI skills that integrate with
edgar.ai infrastructure. External packages can subclass BaseSkill to
create specialized skills (e.g., insider trading detection, fraud analysis).
"""
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, List, Optional, Callable
__all__ = ['BaseSkill']
class BaseSkill(ABC):
"""
Abstract base class for EdgarTools AI skills.
A skill packages:
- Documentation (markdown files with YAML frontmatter)
- Helper functions (workflow wrappers)
- Examples and patterns
External packages can subclass this to create specialized skills
that integrate seamlessly with edgar.ai infrastructure.
Example:
>>> from edgar.ai.skills.base import BaseSkill
>>> from pathlib import Path
>>>
>>> class InsiderTradingSkill(BaseSkill):
... @property
... def name(self) -> str:
... return "Insider Trading Detection"
...
... @property
... def description(self) -> str:
... return "Analyze Form 4 filings for insider trading patterns"
...
... @property
... def content_dir(self) -> Path:
... return Path(__file__).parent / "content"
...
... def get_helpers(self) -> Dict[str, Callable]:
... return {
... 'detect_unusual_trades': self.detect_unusual_trades,
... }
"""
@property
@abstractmethod
def name(self) -> str:
"""
Skill name for display and identification.
Should be descriptive and unique. Example: "SEC Filing Analysis"
Returns:
Human-readable skill name
"""
pass
@property
@abstractmethod
def description(self) -> str:
"""
Brief description of skill capabilities.
Used by AI agents to determine when to activate the skill.
Should clearly describe what problems the skill solves.
Returns:
One-sentence skill description
"""
pass
@property
@abstractmethod
def content_dir(self) -> Path:
"""
Directory containing skill documentation (markdown files).
This directory should contain:
- skill.md: Main skill documentation with YAML frontmatter
- objects.md: Object reference (optional)
- workflows.md: Workflow patterns (optional)
- readme.md: Installation/overview (optional)
Returns:
Path to skill content directory
"""
pass
@abstractmethod
def get_helpers(self) -> Dict[str, Callable]:
"""
Return dictionary of helper functions this skill provides.
Helper functions are convenience wrappers that simplify
common workflows for the skill's domain.
Returns:
Dict mapping function names to callable objects
Example:
>>> {
... 'get_revenue_trend': helpers.get_revenue_trend,
... 'compare_companies': helpers.compare_companies,
... }
"""
pass
# Non-abstract methods with default implementations
def get_object_docs(self) -> List[Path]:
"""
Return paths to centralized object documentation files to include in exports.
Override this method to specify which centralized API reference docs
should be included when exporting the skill. These docs are copied to
an 'api-reference/' subdirectory in the exported skill package.
Returns:
List of Path objects pointing to markdown documentation files
Example:
>>> def get_object_docs(self) -> List[Path]:
... from pathlib import Path
... root = Path(__file__).parent.parent.parent
... return [
... root / "entity/docs/Company.md",
... root / "xbrl/docs/XBRL.md",
... ]
"""
return [] # Default: no object docs
def get_documents(self) -> List[str]:
"""
List of markdown documents in this skill.
Returns:
List of document names (without .md extension)
"""
if not self.content_dir.exists():
return []
return [f.stem for f in self.content_dir.glob("*.md")]
def get_document_content(self, name: str) -> str:
"""
Get content of a specific markdown document.
Args:
name: Document name (with or without .md extension)
Returns:
Full markdown content as string
Raises:
FileNotFoundError: If document doesn't exist
"""
doc_name = name if name.endswith('.md') else f"{name}.md"
doc_path = self.content_dir / doc_name
if not doc_path.exists():
available = ", ".join(self.get_documents())
raise FileNotFoundError(
f"Document '{name}' not found in skill '{self.name}'. "
f"Available: {available}"
)
return doc_path.read_text()
def export(self, format: str = "claude-desktop", output_dir: Optional[Path] = None, **kwargs) -> Path:
"""
Export skill in specified format.
Args:
format: Export format (default: "claude-desktop")
- "claude-desktop": Claude Desktop Skills format (ZIP)
- "claude-skills": Official Claude Skills format (~/.claude/skills/)
output_dir: Where to create export (default: ./skills_export/)
**kwargs: Additional format-specific parameters
- create_zip (bool): For claude-desktop format (default: True)
- install (bool): For claude-skills format (default: True)
Returns:
Path to exported skill directory or archive
Example:
>>> skill = EdgarToolsSkill()
>>> # Export as ZIP for Claude Desktop upload
>>> path = skill.export(format="claude-desktop")
>>> # Export to ~/.claude/skills/ for automatic discovery
>>> path = skill.export(format="claude-skills")
"""
from edgar.ai.exporters import export_skill
return export_skill(self, format=format, output_dir=output_dir, **kwargs)
def __repr__(self) -> str:
"""String representation of the skill."""
return f"{self.__class__.__name__}(name='{self.name}')"
def __str__(self) -> str:
"""Human-readable skill description."""
docs_count = len(self.get_documents())
helpers_count = len(self.get_helpers())
return (
f"Skill: {self.name}\n"
f"Description: {self.description}\n"
f"Documents: {docs_count}\n"
f"Helper Functions: {helpers_count}"
)