This commit is contained in:
Pablo Estevez
2026-01-17 17:29:21 +00:00
parent c89f059712
commit 5ed767ff9a
144 changed files with 14142 additions and 16488 deletions

View File

@@ -33,17 +33,17 @@ except ImportError:
# Registry of available adaptors
ADAPTORS: Dict[str, Type[SkillAdaptor]] = {}
ADAPTORS: dict[str, type[SkillAdaptor]] = {}
# Register adaptors that are implemented
if ClaudeAdaptor:
ADAPTORS['claude'] = ClaudeAdaptor
ADAPTORS["claude"] = ClaudeAdaptor
if GeminiAdaptor:
ADAPTORS['gemini'] = GeminiAdaptor
ADAPTORS["gemini"] = GeminiAdaptor
if OpenAIAdaptor:
ADAPTORS['openai'] = OpenAIAdaptor
ADAPTORS["openai"] = OpenAIAdaptor
if MarkdownAdaptor:
ADAPTORS['markdown'] = MarkdownAdaptor
ADAPTORS["markdown"] = MarkdownAdaptor
def get_adaptor(platform: str, config: dict = None) -> SkillAdaptor:
@@ -65,15 +65,11 @@ def get_adaptor(platform: str, config: dict = None) -> SkillAdaptor:
>>> adaptor = get_adaptor('gemini', {'api_version': 'v1beta'})
"""
if platform not in ADAPTORS:
available = ', '.join(ADAPTORS.keys())
available = ", ".join(ADAPTORS.keys())
if not ADAPTORS:
raise ValueError(
f"No adaptors are currently implemented. "
f"Platform '{platform}' is not available."
)
raise ValueError(f"No adaptors are currently implemented. Platform '{platform}' is not available.")
raise ValueError(
f"Platform '{platform}' is not supported or not yet implemented. "
f"Available platforms: {available}"
f"Platform '{platform}' is not supported or not yet implemented. Available platforms: {available}"
)
adaptor_class = ADAPTORS[platform]
@@ -115,10 +111,10 @@ def is_platform_available(platform: str) -> bool:
# Export public interface
__all__ = [
'SkillAdaptor',
'SkillMetadata',
'get_adaptor',
'list_platforms',
'is_platform_available',
'ADAPTORS',
"SkillAdaptor",
"SkillMetadata",
"get_adaptor",
"list_platforms",
"is_platform_available",
"ADAPTORS",
]

View File

@@ -7,18 +7,19 @@ This enables Skill Seekers to generate skills for multiple LLM platforms (Claude
"""
from abc import ABC, abstractmethod
from pathlib import Path
from typing import Dict, Any, Optional
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
@dataclass
class SkillMetadata:
"""Universal skill metadata used across all platforms"""
name: str
description: str
version: str = "1.0.0"
author: Optional[str] = None
author: str | None = None
tags: list[str] = field(default_factory=list)
@@ -34,11 +35,11 @@ class SkillAdaptor(ABC):
"""
# Platform identifiers (override in subclasses)
PLATFORM: str = "unknown" # e.g., "claude", "gemini", "openai"
PLATFORM_NAME: str = "Unknown" # e.g., "Claude AI (Anthropic)"
DEFAULT_API_ENDPOINT: Optional[str] = None
PLATFORM: str = "unknown" # e.g., "claude", "gemini", "openai"
PLATFORM_NAME: str = "Unknown" # e.g., "Claude AI (Anthropic)"
DEFAULT_API_ENDPOINT: str | None = None
def __init__(self, config: Optional[Dict[str, Any]] = None):
def __init__(self, config: dict[str, Any] | None = None):
"""
Initialize adaptor with optional configuration.
@@ -86,7 +87,7 @@ class SkillAdaptor(ABC):
pass
@abstractmethod
def upload(self, package_path: Path, api_key: str, **kwargs) -> Dict[str, Any]:
def upload(self, package_path: Path, api_key: str, **kwargs) -> dict[str, Any]:
"""
Upload packaged skill to platform.
@@ -168,11 +169,11 @@ class SkillAdaptor(ABC):
if not skill_md_path.exists():
return ""
content = skill_md_path.read_text(encoding='utf-8')
content = skill_md_path.read_text(encoding="utf-8")
# Strip YAML frontmatter if present
if content.startswith('---'):
parts = content.split('---', 2)
if content.startswith("---"):
parts = content.split("---", 2)
if len(parts) >= 3:
return parts[2].strip()
@@ -193,7 +194,7 @@ class SkillAdaptor(ABC):
return "See references/ directory for documentation."
# Read index and extract relevant sections
content = index_path.read_text(encoding='utf-8')
content = index_path.read_text(encoding="utf-8")
return content[:500] + "..." if len(content) > 500 else content
def _generate_toc(self, skill_dir: Path) -> str:
@@ -214,7 +215,7 @@ class SkillAdaptor(ABC):
for ref_file in sorted(refs_dir.glob("*.md")):
if ref_file.name == "index.md":
continue
title = ref_file.stem.replace('_', ' ').title()
title = ref_file.stem.replace("_", " ").title()
toc_lines.append(f"- [{title}](references/{ref_file.name})")
return "\n".join(toc_lines)

View File

@@ -6,10 +6,9 @@ Implements platform-specific handling for Claude AI (Anthropic) skills.
Refactored from upload_skill.py and enhance_skill.py.
"""
import os
import zipfile
from pathlib import Path
from typing import Dict, Any
from typing import Any
from .base import SkillAdaptor, SkillMetadata
@@ -101,16 +100,16 @@ version: {metadata.version}
skill_dir = Path(skill_dir)
# Determine output filename
if output_path.is_dir() or str(output_path).endswith('/'):
if output_path.is_dir() or str(output_path).endswith("/"):
output_path = Path(output_path) / f"{skill_dir.name}.zip"
elif not str(output_path).endswith('.zip'):
output_path = Path(str(output_path) + '.zip')
elif not str(output_path).endswith(".zip"):
output_path = Path(str(output_path) + ".zip")
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Create ZIP file
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zf:
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
# Add SKILL.md (required)
skill_md = skill_dir / "SKILL.md"
if skill_md.exists():
@@ -120,7 +119,7 @@ version: {metadata.version}
refs_dir = skill_dir / "references"
if refs_dir.exists():
for ref_file in refs_dir.rglob("*"):
if ref_file.is_file() and not ref_file.name.startswith('.'):
if ref_file.is_file() and not ref_file.name.startswith("."):
arcname = ref_file.relative_to(skill_dir)
zf.write(ref_file, str(arcname))
@@ -128,7 +127,7 @@ version: {metadata.version}
scripts_dir = skill_dir / "scripts"
if scripts_dir.exists():
for script_file in scripts_dir.rglob("*"):
if script_file.is_file() and not script_file.name.startswith('.'):
if script_file.is_file() and not script_file.name.startswith("."):
arcname = script_file.relative_to(skill_dir)
zf.write(script_file, str(arcname))
@@ -136,13 +135,13 @@ version: {metadata.version}
assets_dir = skill_dir / "assets"
if assets_dir.exists():
for asset_file in assets_dir.rglob("*"):
if asset_file.is_file() and not asset_file.name.startswith('.'):
if asset_file.is_file() and not asset_file.name.startswith("."):
arcname = asset_file.relative_to(skill_dir)
zf.write(asset_file, str(arcname))
return output_path
def upload(self, package_path: Path, api_key: str, **kwargs) -> Dict[str, Any]:
def upload(self, package_path: Path, api_key: str, **kwargs) -> dict[str, Any]:
"""
Upload skill ZIP to Anthropic Skills API.
@@ -159,130 +158,99 @@ version: {metadata.version}
import requests
except ImportError:
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'requests library not installed. Run: pip install requests'
"success": False,
"skill_id": None,
"url": None,
"message": "requests library not installed. Run: pip install requests",
}
# Validate ZIP file
package_path = Path(package_path)
if not package_path.exists():
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'File not found: {package_path}'
}
return {"success": False, "skill_id": None, "url": None, "message": f"File not found: {package_path}"}
if not package_path.suffix == '.zip':
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Not a ZIP file: {package_path}'
}
if not package_path.suffix == ".zip":
return {"success": False, "skill_id": None, "url": None, "message": f"Not a ZIP file: {package_path}"}
# Prepare API request
api_url = self.DEFAULT_API_ENDPOINT
headers = {
"x-api-key": api_key,
"anthropic-version": "2023-06-01",
"anthropic-beta": "skills-2025-10-02"
}
headers = {"x-api-key": api_key, "anthropic-version": "2023-06-01", "anthropic-beta": "skills-2025-10-02"}
timeout = kwargs.get('timeout', 60)
timeout = kwargs.get("timeout", 60)
try:
# Read ZIP file
with open(package_path, 'rb') as f:
with open(package_path, "rb") as f:
zip_data = f.read()
# Upload skill
files = {
'files[]': (package_path.name, zip_data, 'application/zip')
}
files = {"files[]": (package_path.name, zip_data, "application/zip")}
response = requests.post(
api_url,
headers=headers,
files=files,
timeout=timeout
)
response = requests.post(api_url, headers=headers, files=files, timeout=timeout)
# Check response
if response.status_code == 200:
# Extract skill ID if available
try:
response_data = response.json()
skill_id = response_data.get('id')
skill_id = response_data.get("id")
except:
skill_id = None
return {
'success': True,
'skill_id': skill_id,
'url': 'https://claude.ai/skills',
'message': 'Skill uploaded successfully to Claude AI'
"success": True,
"skill_id": skill_id,
"url": "https://claude.ai/skills",
"message": "Skill uploaded successfully to Claude AI",
}
elif response.status_code == 401:
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'Authentication failed. Check your ANTHROPIC_API_KEY'
"success": False,
"skill_id": None,
"url": None,
"message": "Authentication failed. Check your ANTHROPIC_API_KEY",
}
elif response.status_code == 400:
try:
error_msg = response.json().get('error', {}).get('message', 'Unknown error')
error_msg = response.json().get("error", {}).get("message", "Unknown error")
except:
error_msg = 'Invalid skill format'
error_msg = "Invalid skill format"
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Invalid skill format: {error_msg}'
"success": False,
"skill_id": None,
"url": None,
"message": f"Invalid skill format: {error_msg}",
}
else:
try:
error_msg = response.json().get('error', {}).get('message', 'Unknown error')
error_msg = response.json().get("error", {}).get("message", "Unknown error")
except:
error_msg = f'HTTP {response.status_code}'
error_msg = f"HTTP {response.status_code}"
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Upload failed: {error_msg}'
}
return {"success": False, "skill_id": None, "url": None, "message": f"Upload failed: {error_msg}"}
except requests.exceptions.Timeout:
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'Upload timed out. Try again or use manual upload'
"success": False,
"skill_id": None,
"url": None,
"message": "Upload timed out. Try again or use manual upload",
}
except requests.exceptions.ConnectionError:
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'Connection error. Check your internet connection'
"success": False,
"skill_id": None,
"url": None,
"message": "Connection error. Check your internet connection",
}
except Exception as e:
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Unexpected error: {str(e)}'
}
return {"success": False, "skill_id": None, "url": None, "message": f"Unexpected error: {str(e)}"}
def validate_api_key(self, api_key: str) -> bool:
"""
@@ -294,7 +262,7 @@ version: {metadata.version}
Returns:
True if key starts with 'sk-ant-'
"""
return api_key.strip().startswith('sk-ant-')
return api_key.strip().startswith("sk-ant-")
def get_env_var_name(self) -> str:
"""
@@ -355,17 +323,13 @@ version: {metadata.version}
# Read current SKILL.md
current_skill_md = None
if skill_md_path.exists():
current_skill_md = skill_md_path.read_text(encoding='utf-8')
current_skill_md = skill_md_path.read_text(encoding="utf-8")
print(f" Found existing SKILL.md ({len(current_skill_md)} chars)")
else:
print(f" No existing SKILL.md, will create new one")
print(" No existing SKILL.md, will create new one")
# Build enhancement prompt
prompt = self._build_enhancement_prompt(
skill_dir.name,
references,
current_skill_md
)
prompt = self._build_enhancement_prompt(skill_dir.name, references, current_skill_md)
print("\n🤖 Asking Claude to enhance SKILL.md...")
print(f" Input: {len(prompt):,} characters")
@@ -377,10 +341,7 @@ version: {metadata.version}
model="claude-sonnet-4-20250514",
max_tokens=4096,
temperature=0.3,
messages=[{
"role": "user",
"content": prompt
}]
messages=[{"role": "user", "content": prompt}],
)
enhanced_content = message.content[0].text
@@ -388,13 +349,13 @@ version: {metadata.version}
# Backup original
if skill_md_path.exists():
backup_path = skill_md_path.with_suffix('.md.backup')
backup_path = skill_md_path.with_suffix(".md.backup")
skill_md_path.rename(backup_path)
print(f" 💾 Backed up original to: {backup_path.name}")
# Save enhanced version
skill_md_path.write_text(enhanced_content, encoding='utf-8')
print(f" ✅ Saved enhanced SKILL.md")
skill_md_path.write_text(enhanced_content, encoding="utf-8")
print(" ✅ Saved enhanced SKILL.md")
return True
@@ -402,7 +363,7 @@ version: {metadata.version}
print(f"❌ Error calling Claude API: {e}")
return False
def _read_reference_files(self, references_dir: Path, max_chars: int = 200000) -> Dict[str, str]:
def _read_reference_files(self, references_dir: Path, max_chars: int = 200000) -> dict[str, str]:
"""
Read reference markdown files from skill directory.
@@ -425,7 +386,7 @@ version: {metadata.version}
break
try:
content = ref_file.read_text(encoding='utf-8')
content = ref_file.read_text(encoding="utf-8")
# Limit individual file size
if len(content) > 30000:
content = content[:30000] + "\n\n...(truncated)"
@@ -439,10 +400,7 @@ version: {metadata.version}
return references
def _build_enhancement_prompt(
self,
skill_name: str,
references: Dict[str, str],
current_skill_md: str = None
self, skill_name: str, references: dict[str, str], current_skill_md: str = None
) -> str:
"""
Build Claude API prompt for enhancement.
@@ -460,9 +418,9 @@ version: {metadata.version}
I've scraped documentation and organized it into reference files. Your job is to create an EXCELLENT SKILL.md that will help Claude use this documentation effectively.
CURRENT SKILL.MD:
{'```markdown' if current_skill_md else '(none - create from scratch)'}
{current_skill_md or 'No existing SKILL.md'}
{'```' if current_skill_md else ''}
{"```markdown" if current_skill_md else "(none - create from scratch)"}
{current_skill_md or "No existing SKILL.md"}
{"```" if current_skill_md else ""}
REFERENCE DOCUMENTATION:
"""

View File

@@ -6,11 +6,11 @@ Implements platform-specific handling for Google Gemini skills.
Uses Gemini Files API for grounding and Gemini 2.0 Flash for enhancement.
"""
import json
import os
import tarfile
import json
from pathlib import Path
from typing import Dict, Any
from typing import Any
from .base import SkillAdaptor, SkillMetadata
@@ -105,20 +105,20 @@ See the references directory for complete documentation with examples and best p
skill_dir = Path(skill_dir)
# Determine output filename
if output_path.is_dir() or str(output_path).endswith('/'):
if output_path.is_dir() or str(output_path).endswith("/"):
output_path = Path(output_path) / f"{skill_dir.name}-gemini.tar.gz"
elif not str(output_path).endswith('.tar.gz'):
elif not str(output_path).endswith(".tar.gz"):
# Replace .zip with .tar.gz if needed
output_str = str(output_path).replace('.zip', '.tar.gz')
if not output_str.endswith('.tar.gz'):
output_str += '.tar.gz'
output_str = str(output_path).replace(".zip", ".tar.gz")
if not output_str.endswith(".tar.gz"):
output_str += ".tar.gz"
output_path = Path(output_str)
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Create tar.gz file
with tarfile.open(output_path, 'w:gz') as tar:
with tarfile.open(output_path, "w:gz") as tar:
# Add SKILL.md as system_instructions.md
skill_md = skill_dir / "SKILL.md"
if skill_md.exists():
@@ -128,21 +128,22 @@ See the references directory for complete documentation with examples and best p
refs_dir = skill_dir / "references"
if refs_dir.exists():
for ref_file in refs_dir.rglob("*"):
if ref_file.is_file() and not ref_file.name.startswith('.'):
if ref_file.is_file() and not ref_file.name.startswith("."):
arcname = ref_file.relative_to(skill_dir)
tar.add(ref_file, arcname=str(arcname))
# Create and add metadata file
metadata = {
'platform': 'gemini',
'name': skill_dir.name,
'version': '1.0.0',
'created_with': 'skill-seekers'
"platform": "gemini",
"name": skill_dir.name,
"version": "1.0.0",
"created_with": "skill-seekers",
}
# Write metadata to temp file and add to archive
import tempfile
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tmp:
with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
json.dump(metadata, tmp, indent=2)
tmp_path = tmp.name
@@ -153,7 +154,7 @@ See the references directory for complete documentation with examples and best p
return output_path
def upload(self, package_path: Path, api_key: str, **kwargs) -> Dict[str, Any]:
def upload(self, package_path: Path, api_key: str, **kwargs) -> dict[str, Any]:
"""
Upload skill tar.gz to Gemini Files API.
@@ -168,30 +169,20 @@ See the references directory for complete documentation with examples and best p
# Validate package file FIRST
package_path = Path(package_path)
if not package_path.exists():
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'File not found: {package_path}'
}
return {"success": False, "skill_id": None, "url": None, "message": f"File not found: {package_path}"}
if not package_path.suffix == '.gz':
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Not a tar.gz file: {package_path}'
}
if not package_path.suffix == ".gz":
return {"success": False, "skill_id": None, "url": None, "message": f"Not a tar.gz file: {package_path}"}
# Check for google-generativeai library
try:
import google.generativeai as genai
except ImportError:
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'google-generativeai library not installed. Run: pip install google-generativeai'
"success": False,
"skill_id": None,
"url": None,
"message": "google-generativeai library not installed. Run: pip install google-generativeai",
}
# Configure Gemini
@@ -200,11 +191,10 @@ See the references directory for complete documentation with examples and best p
# Extract tar.gz to temp directory
import tempfile
import shutil
with tempfile.TemporaryDirectory() as temp_dir:
# Extract archive
with tarfile.open(package_path, 'r:gz') as tar:
with tarfile.open(package_path, "r:gz") as tar:
tar.extractall(temp_dir)
temp_path = Path(temp_dir)
@@ -213,17 +203,14 @@ See the references directory for complete documentation with examples and best p
main_file = temp_path / "system_instructions.md"
if not main_file.exists():
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'Invalid package: system_instructions.md not found'
"success": False,
"skill_id": None,
"url": None,
"message": "Invalid package: system_instructions.md not found",
}
# Upload to Files API
uploaded_file = genai.upload_file(
path=str(main_file),
display_name=f"{package_path.stem}_instructions"
)
uploaded_file = genai.upload_file(path=str(main_file), display_name=f"{package_path.stem}_instructions")
# Upload reference files (if any)
refs_dir = temp_path / "references"
@@ -231,25 +218,19 @@ See the references directory for complete documentation with examples and best p
if refs_dir.exists():
for ref_file in refs_dir.glob("*.md"):
ref_uploaded = genai.upload_file(
path=str(ref_file),
display_name=f"{package_path.stem}_{ref_file.stem}"
path=str(ref_file), display_name=f"{package_path.stem}_{ref_file.stem}"
)
uploaded_refs.append(ref_uploaded.name)
return {
'success': True,
'skill_id': uploaded_file.name,
'url': f"https://aistudio.google.com/app/files/{uploaded_file.name}",
'message': f'Skill uploaded to Google AI Studio ({len(uploaded_refs) + 1} files)'
"success": True,
"skill_id": uploaded_file.name,
"url": f"https://aistudio.google.com/app/files/{uploaded_file.name}",
"message": f"Skill uploaded to Google AI Studio ({len(uploaded_refs) + 1} files)",
}
except Exception as e:
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Upload failed: {str(e)}'
}
return {"success": False, "skill_id": None, "url": None, "message": f"Upload failed: {str(e)}"}
def validate_api_key(self, api_key: str) -> bool:
"""
@@ -261,7 +242,7 @@ See the references directory for complete documentation with examples and best p
Returns:
True if key starts with 'AIza'
"""
return api_key.strip().startswith('AIza')
return api_key.strip().startswith("AIza")
def get_env_var_name(self) -> str:
"""
@@ -319,17 +300,13 @@ See the references directory for complete documentation with examples and best p
# Read current SKILL.md
current_skill_md = None
if skill_md_path.exists():
current_skill_md = skill_md_path.read_text(encoding='utf-8')
current_skill_md = skill_md_path.read_text(encoding="utf-8")
print(f" Found existing SKILL.md ({len(current_skill_md)} chars)")
else:
print(f" No existing SKILL.md, will create new one")
print(" No existing SKILL.md, will create new one")
# Build enhancement prompt
prompt = self._build_enhancement_prompt(
skill_dir.name,
references,
current_skill_md
)
prompt = self._build_enhancement_prompt(skill_dir.name, references, current_skill_md)
print("\n🤖 Asking Gemini to enhance SKILL.md...")
print(f" Input: {len(prompt):,} characters")
@@ -337,7 +314,7 @@ See the references directory for complete documentation with examples and best p
try:
genai.configure(api_key=api_key)
model = genai.GenerativeModel('gemini-2.0-flash-exp')
model = genai.GenerativeModel("gemini-2.0-flash-exp")
response = model.generate_content(prompt)
@@ -346,13 +323,13 @@ See the references directory for complete documentation with examples and best p
# Backup original
if skill_md_path.exists():
backup_path = skill_md_path.with_suffix('.md.backup')
backup_path = skill_md_path.with_suffix(".md.backup")
skill_md_path.rename(backup_path)
print(f" 💾 Backed up original to: {backup_path.name}")
# Save enhanced version
skill_md_path.write_text(enhanced_content, encoding='utf-8')
print(f" ✅ Saved enhanced SKILL.md")
skill_md_path.write_text(enhanced_content, encoding="utf-8")
print(" ✅ Saved enhanced SKILL.md")
return True
@@ -360,7 +337,7 @@ See the references directory for complete documentation with examples and best p
print(f"❌ Error calling Gemini API: {e}")
return False
def _read_reference_files(self, references_dir: Path, max_chars: int = 200000) -> Dict[str, str]:
def _read_reference_files(self, references_dir: Path, max_chars: int = 200000) -> dict[str, str]:
"""
Read reference markdown files from skill directory.
@@ -383,7 +360,7 @@ See the references directory for complete documentation with examples and best p
break
try:
content = ref_file.read_text(encoding='utf-8')
content = ref_file.read_text(encoding="utf-8")
# Limit individual file size
if len(content) > 30000:
content = content[:30000] + "\n\n...(truncated)"
@@ -397,10 +374,7 @@ See the references directory for complete documentation with examples and best p
return references
def _build_enhancement_prompt(
self,
skill_name: str,
references: Dict[str, str],
current_skill_md: str = None
self, skill_name: str, references: dict[str, str], current_skill_md: str = None
) -> str:
"""
Build Gemini API prompt for enhancement.
@@ -418,9 +392,9 @@ See the references directory for complete documentation with examples and best p
I've scraped documentation and organized it into reference files. Your job is to create an EXCELLENT markdown documentation file that will help Gemini use this documentation effectively.
CURRENT DOCUMENTATION:
{'```markdown' if current_skill_md else '(none - create from scratch)'}
{current_skill_md or 'No existing documentation'}
{'```' if current_skill_md else ''}
{"```markdown" if current_skill_md else "(none - create from scratch)"}
{current_skill_md or "No existing documentation"}
{"```" if current_skill_md else ""}
REFERENCE DOCUMENTATION:
"""

View File

@@ -8,7 +8,7 @@ No platform-specific features, just clean markdown documentation.
import zipfile
from pathlib import Path
from typing import Dict, Any
from typing import Any
from .base import SkillAdaptor, SkillMetadata
@@ -100,33 +100,33 @@ Browse the reference files for detailed information on each topic. All files are
skill_dir = Path(skill_dir)
# Determine output filename
if output_path.is_dir() or str(output_path).endswith('/'):
if output_path.is_dir() or str(output_path).endswith("/"):
output_path = Path(output_path) / f"{skill_dir.name}-markdown.zip"
elif not str(output_path).endswith('.zip'):
elif not str(output_path).endswith(".zip"):
# Replace extension if needed
output_str = str(output_path).replace('.tar.gz', '.zip')
if not output_str.endswith('-markdown.zip'):
output_str = output_str.replace('.zip', '-markdown.zip')
if not output_str.endswith('.zip'):
output_str += '.zip'
output_str = str(output_path).replace(".tar.gz", ".zip")
if not output_str.endswith("-markdown.zip"):
output_str = output_str.replace(".zip", "-markdown.zip")
if not output_str.endswith(".zip"):
output_str += ".zip"
output_path = Path(output_str)
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Create ZIP file
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zf:
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
# Add SKILL.md as README.md
skill_md = skill_dir / "SKILL.md"
if skill_md.exists():
content = skill_md.read_text(encoding='utf-8')
content = skill_md.read_text(encoding="utf-8")
zf.writestr("README.md", content)
# Add individual reference files
refs_dir = skill_dir / "references"
if refs_dir.exists():
for ref_file in refs_dir.rglob("*.md"):
if ref_file.is_file() and not ref_file.name.startswith('.'):
if ref_file.is_file() and not ref_file.name.startswith("."):
# Preserve directory structure under references/
arcname = ref_file.relative_to(skill_dir)
zf.write(ref_file, str(arcname))
@@ -138,20 +138,21 @@ Browse the reference files for detailed information on each topic. All files are
# Add metadata file
import json
metadata = {
'platform': 'markdown',
'name': skill_dir.name,
'version': '1.0.0',
'created_with': 'skill-seekers',
'format': 'universal_markdown',
'usage': 'Use with any LLM or documentation system'
"platform": "markdown",
"name": skill_dir.name,
"version": "1.0.0",
"created_with": "skill-seekers",
"format": "universal_markdown",
"usage": "Use with any LLM or documentation system",
}
zf.writestr("metadata.json", json.dumps(metadata, indent=2))
return output_path
def upload(self, package_path: Path, api_key: str, **kwargs) -> Dict[str, Any]:
def upload(self, package_path: Path, api_key: str, **kwargs) -> dict[str, Any]:
"""
Generic markdown export does not support upload.
@@ -166,13 +167,13 @@ Browse the reference files for detailed information on each topic. All files are
Result indicating no upload capability
"""
return {
'success': False,
'skill_id': None,
'url': str(package_path.absolute()),
'message': (
'Generic markdown export does not support automatic upload. '
f'Your documentation is packaged at: {package_path.absolute()}'
)
"success": False,
"skill_id": None,
"url": str(package_path.absolute()),
"message": (
"Generic markdown export does not support automatic upload. "
f"Your documentation is packaged at: {package_path.absolute()}"
),
}
def validate_api_key(self, api_key: str) -> bool:
@@ -237,10 +238,10 @@ Browse the reference files for detailed information on each topic. All files are
# Add main content
if skill_md.exists():
content = skill_md.read_text(encoding='utf-8')
content = skill_md.read_text(encoding="utf-8")
# Strip YAML frontmatter if present
if content.startswith('---'):
parts = content.split('---', 2)
if content.startswith("---"):
parts = content.split("---", 2)
if len(parts) >= 3:
content = parts[2].strip()
combined_parts.append(content)
@@ -258,7 +259,7 @@ Browse the reference files for detailed information on each topic. All files are
continue # Skip index
try:
ref_content = ref_file.read_text(encoding='utf-8')
ref_content = ref_file.read_text(encoding="utf-8")
combined_parts.append(f"# {ref_file.stem.replace('_', ' ').title()}\n\n")
combined_parts.append(ref_content)
combined_parts.append("\n\n---\n\n")

View File

@@ -6,11 +6,10 @@ Implements platform-specific handling for OpenAI ChatGPT Assistants.
Uses Assistants API with Vector Store for file search.
"""
import os
import zipfile
import json
import zipfile
from pathlib import Path
from typing import Dict, Any
from typing import Any
from .base import SkillAdaptor, SkillMetadata
@@ -123,51 +122,51 @@ Always prioritize accuracy by consulting the attached documentation files before
skill_dir = Path(skill_dir)
# Determine output filename
if output_path.is_dir() or str(output_path).endswith('/'):
if output_path.is_dir() or str(output_path).endswith("/"):
output_path = Path(output_path) / f"{skill_dir.name}-openai.zip"
elif not str(output_path).endswith('.zip'):
elif not str(output_path).endswith(".zip"):
# Keep .zip extension
if not str(output_path).endswith('-openai.zip'):
output_str = str(output_path).replace('.zip', '-openai.zip')
if not output_str.endswith('.zip'):
output_str += '.zip'
if not str(output_path).endswith("-openai.zip"):
output_str = str(output_path).replace(".zip", "-openai.zip")
if not output_str.endswith(".zip"):
output_str += ".zip"
output_path = Path(output_str)
output_path = Path(output_path)
output_path.parent.mkdir(parents=True, exist_ok=True)
# Create ZIP file
with zipfile.ZipFile(output_path, 'w', zipfile.ZIP_DEFLATED) as zf:
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
# Add SKILL.md as assistant_instructions.txt
skill_md = skill_dir / "SKILL.md"
if skill_md.exists():
instructions = skill_md.read_text(encoding='utf-8')
instructions = skill_md.read_text(encoding="utf-8")
zf.writestr("assistant_instructions.txt", instructions)
# Add references directory as vector_store_files/
refs_dir = skill_dir / "references"
if refs_dir.exists():
for ref_file in refs_dir.rglob("*.md"):
if ref_file.is_file() and not ref_file.name.startswith('.'):
if ref_file.is_file() and not ref_file.name.startswith("."):
# Place all reference files in vector_store_files/
arcname = f"vector_store_files/{ref_file.name}"
zf.write(ref_file, arcname)
# Create and add metadata file
metadata = {
'platform': 'openai',
'name': skill_dir.name,
'version': '1.0.0',
'created_with': 'skill-seekers',
'model': 'gpt-4o',
'tools': ['file_search']
"platform": "openai",
"name": skill_dir.name,
"version": "1.0.0",
"created_with": "skill-seekers",
"model": "gpt-4o",
"tools": ["file_search"],
}
zf.writestr("openai_metadata.json", json.dumps(metadata, indent=2))
return output_path
def upload(self, package_path: Path, api_key: str, **kwargs) -> Dict[str, Any]:
def upload(self, package_path: Path, api_key: str, **kwargs) -> dict[str, Any]:
"""
Upload skill ZIP to OpenAI Assistants API.
@@ -186,30 +185,20 @@ Always prioritize accuracy by consulting the attached documentation files before
# Validate package file FIRST
package_path = Path(package_path)
if not package_path.exists():
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'File not found: {package_path}'
}
return {"success": False, "skill_id": None, "url": None, "message": f"File not found: {package_path}"}
if not package_path.suffix == '.zip':
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Not a ZIP file: {package_path}'
}
if not package_path.suffix == ".zip":
return {"success": False, "skill_id": None, "url": None, "message": f"Not a ZIP file: {package_path}"}
# Check for openai library
try:
from openai import OpenAI
except ImportError:
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'openai library not installed. Run: pip install openai'
"success": False,
"skill_id": None,
"url": None,
"message": "openai library not installed. Run: pip install openai",
}
# Configure OpenAI client
@@ -218,11 +207,10 @@ Always prioritize accuracy by consulting the attached documentation files before
# Extract package to temp directory
import tempfile
import shutil
with tempfile.TemporaryDirectory() as temp_dir:
# Extract ZIP
with zipfile.ZipFile(package_path, 'r') as zf:
with zipfile.ZipFile(package_path, "r") as zf:
zf.extractall(temp_dir)
temp_path = Path(temp_dir)
@@ -231,29 +219,27 @@ Always prioritize accuracy by consulting the attached documentation files before
instructions_file = temp_path / "assistant_instructions.txt"
if not instructions_file.exists():
return {
'success': False,
'skill_id': None,
'url': None,
'message': 'Invalid package: assistant_instructions.txt not found'
"success": False,
"skill_id": None,
"url": None,
"message": "Invalid package: assistant_instructions.txt not found",
}
instructions = instructions_file.read_text(encoding='utf-8')
instructions = instructions_file.read_text(encoding="utf-8")
# Read metadata
metadata_file = temp_path / "openai_metadata.json"
skill_name = package_path.stem
model = kwargs.get('model', 'gpt-4o')
model = kwargs.get("model", "gpt-4o")
if metadata_file.exists():
with open(metadata_file, 'r') as f:
with open(metadata_file) as f:
metadata = json.load(f)
skill_name = metadata.get('name', skill_name)
model = metadata.get('model', model)
skill_name = metadata.get("name", skill_name)
model = metadata.get("model", model)
# Create vector store
vector_store = client.beta.vector_stores.create(
name=f"{skill_name} Documentation"
)
vector_store = client.beta.vector_stores.create(name=f"{skill_name} Documentation")
# Upload reference files to vector store
vector_files_dir = temp_path / "vector_store_files"
@@ -262,19 +248,13 @@ Always prioritize accuracy by consulting the attached documentation files before
if vector_files_dir.exists():
for ref_file in vector_files_dir.glob("*.md"):
# Upload file
with open(ref_file, 'rb') as f:
uploaded_file = client.files.create(
file=f,
purpose='assistants'
)
with open(ref_file, "rb") as f:
uploaded_file = client.files.create(file=f, purpose="assistants")
file_ids.append(uploaded_file.id)
# Attach files to vector store
if file_ids:
client.beta.vector_stores.files.create_batch(
vector_store_id=vector_store.id,
file_ids=file_ids
)
client.beta.vector_stores.files.create_batch(vector_store_id=vector_store.id, file_ids=file_ids)
# Create assistant
assistant = client.beta.assistants.create(
@@ -282,27 +262,18 @@ Always prioritize accuracy by consulting the attached documentation files before
instructions=instructions,
model=model,
tools=[{"type": "file_search"}],
tool_resources={
"file_search": {
"vector_store_ids": [vector_store.id]
}
}
tool_resources={"file_search": {"vector_store_ids": [vector_store.id]}},
)
return {
'success': True,
'skill_id': assistant.id,
'url': f"https://platform.openai.com/assistants/{assistant.id}",
'message': f'Assistant created with {len(file_ids)} knowledge files'
"success": True,
"skill_id": assistant.id,
"url": f"https://platform.openai.com/assistants/{assistant.id}",
"message": f"Assistant created with {len(file_ids)} knowledge files",
}
except Exception as e:
return {
'success': False,
'skill_id': None,
'url': None,
'message': f'Upload failed: {str(e)}'
}
return {"success": False, "skill_id": None, "url": None, "message": f"Upload failed: {str(e)}"}
def validate_api_key(self, api_key: str) -> bool:
"""
@@ -314,7 +285,7 @@ Always prioritize accuracy by consulting the attached documentation files before
Returns:
True if key starts with 'sk-'
"""
return api_key.strip().startswith('sk-')
return api_key.strip().startswith("sk-")
def get_env_var_name(self) -> str:
"""
@@ -372,17 +343,13 @@ Always prioritize accuracy by consulting the attached documentation files before
# Read current SKILL.md
current_skill_md = None
if skill_md_path.exists():
current_skill_md = skill_md_path.read_text(encoding='utf-8')
current_skill_md = skill_md_path.read_text(encoding="utf-8")
print(f" Found existing SKILL.md ({len(current_skill_md)} chars)")
else:
print(f" No existing SKILL.md, will create new one")
print(" No existing SKILL.md, will create new one")
# Build enhancement prompt
prompt = self._build_enhancement_prompt(
skill_dir.name,
references,
current_skill_md
)
prompt = self._build_enhancement_prompt(skill_dir.name, references, current_skill_md)
print("\n🤖 Asking GPT-4o to enhance SKILL.md...")
print(f" Input: {len(prompt):,} characters")
@@ -395,15 +362,12 @@ Always prioritize accuracy by consulting the attached documentation files before
messages=[
{
"role": "system",
"content": "You are an expert technical writer creating Assistant instructions for OpenAI ChatGPT."
"content": "You are an expert technical writer creating Assistant instructions for OpenAI ChatGPT.",
},
{
"role": "user",
"content": prompt
}
{"role": "user", "content": prompt},
],
temperature=0.3,
max_tokens=4096
max_tokens=4096,
)
enhanced_content = response.choices[0].message.content
@@ -411,13 +375,13 @@ Always prioritize accuracy by consulting the attached documentation files before
# Backup original
if skill_md_path.exists():
backup_path = skill_md_path.with_suffix('.md.backup')
backup_path = skill_md_path.with_suffix(".md.backup")
skill_md_path.rename(backup_path)
print(f" 💾 Backed up original to: {backup_path.name}")
# Save enhanced version
skill_md_path.write_text(enhanced_content, encoding='utf-8')
print(f" ✅ Saved enhanced SKILL.md")
skill_md_path.write_text(enhanced_content, encoding="utf-8")
print(" ✅ Saved enhanced SKILL.md")
return True
@@ -425,7 +389,7 @@ Always prioritize accuracy by consulting the attached documentation files before
print(f"❌ Error calling OpenAI API: {e}")
return False
def _read_reference_files(self, references_dir: Path, max_chars: int = 200000) -> Dict[str, str]:
def _read_reference_files(self, references_dir: Path, max_chars: int = 200000) -> dict[str, str]:
"""
Read reference markdown files from skill directory.
@@ -448,7 +412,7 @@ Always prioritize accuracy by consulting the attached documentation files before
break
try:
content = ref_file.read_text(encoding='utf-8')
content = ref_file.read_text(encoding="utf-8")
# Limit individual file size
if len(content) > 30000:
content = content[:30000] + "\n\n...(truncated)"
@@ -462,10 +426,7 @@ Always prioritize accuracy by consulting the attached documentation files before
return references
def _build_enhancement_prompt(
self,
skill_name: str,
references: Dict[str, str],
current_skill_md: str = None
self, skill_name: str, references: dict[str, str], current_skill_md: str = None
) -> str:
"""
Build OpenAI API prompt for enhancement.
@@ -483,9 +444,9 @@ Always prioritize accuracy by consulting the attached documentation files before
I've scraped documentation and organized it into reference files. Your job is to create EXCELLENT Assistant instructions that will help the Assistant use this documentation effectively.
CURRENT INSTRUCTIONS:
{'```' if current_skill_md else '(none - create from scratch)'}
{current_skill_md or 'No existing instructions'}
{'```' if current_skill_md else ''}
{"```" if current_skill_md else "(none - create from scratch)"}
{current_skill_md or "No existing instructions"}
{"```" if current_skill_md else ""}
REFERENCE DOCUMENTATION:
"""