run ruff
This commit is contained in:
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user