feat(multi-llm): Phase 1 - Foundation adaptor architecture
Implement base adaptor pattern for multi-LLM support (Issue #179) **Architecture:** - Created adaptors/ package with base SkillAdaptor class - Implemented factory pattern with get_adaptor() registry - Refactored Claude-specific code into ClaudeAdaptor **Changes:** - New: src/skill_seekers/cli/adaptors/base.py (SkillAdaptor + SkillMetadata) - New: src/skill_seekers/cli/adaptors/__init__.py (registry + factory) - New: src/skill_seekers/cli/adaptors/claude.py (refactored upload + enhance logic) - Modified: package_skill.py (added --target flag, uses adaptor.package()) - Modified: upload_skill.py (added --target flag, uses adaptor.upload()) - Modified: enhance_skill.py (added --target flag, uses adaptor.enhance()) **Tests:** - New: tests/test_adaptors/test_base.py (10 tests passing) - All existing tests still pass (backward compatible) **Backward Compatibility:** - Default --target=claude maintains existing behavior - All CLI tools work exactly as before without --target flag - No breaking changes **Next:** Phase 2 - Implement Gemini, OpenAI, Markdown adaptors
This commit is contained in:
@@ -36,17 +36,18 @@ except ImportError:
|
||||
from quality_checker import SkillQualityChecker, print_report
|
||||
|
||||
|
||||
def package_skill(skill_dir, open_folder_after=True, skip_quality_check=False):
|
||||
def package_skill(skill_dir, open_folder_after=True, skip_quality_check=False, target='claude'):
|
||||
"""
|
||||
Package a skill directory into a .zip file
|
||||
Package a skill directory into platform-specific format
|
||||
|
||||
Args:
|
||||
skill_dir: Path to skill directory
|
||||
open_folder_after: Whether to open the output folder after packaging
|
||||
skip_quality_check: Skip quality checks before packaging
|
||||
target: Target LLM platform ('claude', 'gemini', 'openai', 'markdown')
|
||||
|
||||
Returns:
|
||||
tuple: (success, zip_path) where success is bool and zip_path is Path or None
|
||||
tuple: (success, package_path) where success is bool and package_path is Path or None
|
||||
"""
|
||||
skill_path = Path(skill_dir)
|
||||
|
||||
@@ -80,40 +81,43 @@ def package_skill(skill_dir, open_folder_after=True, skip_quality_check=False):
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Create zip filename
|
||||
# Get platform-specific adaptor
|
||||
try:
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
adaptor = get_adaptor(target)
|
||||
except (ImportError, ValueError) as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return False, None
|
||||
|
||||
# Create package using adaptor
|
||||
skill_name = skill_path.name
|
||||
zip_path = skill_path.parent / f"{skill_name}.zip"
|
||||
output_dir = skill_path.parent
|
||||
|
||||
print(f"📦 Packaging skill: {skill_name}")
|
||||
print(f" Target: {adaptor.PLATFORM_NAME}")
|
||||
print(f" Source: {skill_path}")
|
||||
print(f" Output: {zip_path}")
|
||||
|
||||
# Create zip file
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for root, dirs, files in os.walk(skill_path):
|
||||
# Skip backup files
|
||||
files = [f for f in files if not f.endswith('.backup')]
|
||||
try:
|
||||
package_path = adaptor.package(skill_path, output_dir)
|
||||
print(f" Output: {package_path}")
|
||||
except Exception as e:
|
||||
print(f"❌ Error creating package: {e}")
|
||||
return False, None
|
||||
|
||||
for file in files:
|
||||
file_path = Path(root) / file
|
||||
arcname = file_path.relative_to(skill_path)
|
||||
zf.write(file_path, arcname)
|
||||
print(f" + {arcname}")
|
||||
|
||||
# Get zip size
|
||||
zip_size = zip_path.stat().st_size
|
||||
print(f"\n✅ Package created: {zip_path}")
|
||||
print(f" Size: {zip_size:,} bytes ({format_file_size(zip_size)})")
|
||||
# Get package size
|
||||
package_size = package_path.stat().st_size
|
||||
print(f"\n✅ Package created: {package_path}")
|
||||
print(f" Size: {package_size:,} bytes ({format_file_size(package_size)})")
|
||||
|
||||
# Open folder in file browser
|
||||
if open_folder_after:
|
||||
print(f"\n📂 Opening folder: {zip_path.parent}")
|
||||
open_folder(zip_path.parent)
|
||||
print(f"\n📂 Opening folder: {package_path.parent}")
|
||||
open_folder(package_path.parent)
|
||||
|
||||
# Print upload instructions
|
||||
print_upload_instructions(zip_path)
|
||||
print_upload_instructions(package_path)
|
||||
|
||||
return True, zip_path
|
||||
return True, package_path
|
||||
|
||||
|
||||
def main():
|
||||
@@ -156,18 +160,26 @@ Examples:
|
||||
help='Skip quality checks before packaging'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--target',
|
||||
choices=['claude', 'gemini', 'openai', 'markdown'],
|
||||
default='claude',
|
||||
help='Target LLM platform (default: claude)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--upload',
|
||||
action='store_true',
|
||||
help='Automatically upload to Claude after packaging (requires ANTHROPIC_API_KEY)'
|
||||
help='Automatically upload after packaging (requires platform API key)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
success, zip_path = package_skill(
|
||||
success, package_path = package_skill(
|
||||
args.skill_dir,
|
||||
open_folder_after=not args.no_open,
|
||||
skip_quality_check=args.skip_quality_check
|
||||
skip_quality_check=args.skip_quality_check,
|
||||
target=args.target
|
||||
)
|
||||
|
||||
if not success:
|
||||
@@ -175,42 +187,58 @@ Examples:
|
||||
|
||||
# Auto-upload if requested
|
||||
if args.upload:
|
||||
# Check if API key is set BEFORE attempting upload
|
||||
api_key = os.environ.get('ANTHROPIC_API_KEY', '').strip()
|
||||
|
||||
if not api_key:
|
||||
# No API key - show helpful message but DON'T fail
|
||||
print("\n" + "="*60)
|
||||
print("💡 Automatic Upload")
|
||||
print("="*60)
|
||||
print()
|
||||
print("To enable automatic upload:")
|
||||
print(" 1. Get API key from https://console.anthropic.com/")
|
||||
print(" 2. Set: export ANTHROPIC_API_KEY=sk-ant-...")
|
||||
print(" 3. Run package_skill.py with --upload flag")
|
||||
print()
|
||||
print("For now, use manual upload (instructions above) ☝️")
|
||||
print("="*60)
|
||||
# Exit successfully - packaging worked!
|
||||
sys.exit(0)
|
||||
|
||||
# API key exists - try upload
|
||||
try:
|
||||
from upload_skill import upload_skill_api
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
|
||||
# Get adaptor for target platform
|
||||
adaptor = get_adaptor(args.target)
|
||||
|
||||
# Get API key from environment
|
||||
api_key = os.environ.get(adaptor.get_env_var_name(), '').strip()
|
||||
|
||||
if not api_key:
|
||||
# No API key - show helpful message but DON'T fail
|
||||
print("\n" + "="*60)
|
||||
print("💡 Automatic Upload")
|
||||
print("="*60)
|
||||
print()
|
||||
print(f"To enable automatic upload to {adaptor.PLATFORM_NAME}:")
|
||||
print(f" 1. Get API key from the platform")
|
||||
print(f" 2. Set: export {adaptor.get_env_var_name()}=...")
|
||||
print(f" 3. Run package command with --upload flag")
|
||||
print()
|
||||
print("For now, use manual upload (instructions above) ☝️")
|
||||
print("="*60)
|
||||
# Exit successfully - packaging worked!
|
||||
sys.exit(0)
|
||||
|
||||
# API key exists - try upload
|
||||
print("\n" + "="*60)
|
||||
upload_success, message = upload_skill_api(zip_path)
|
||||
if not upload_success:
|
||||
print(f"❌ Upload failed: {message}")
|
||||
print(f"📤 Uploading to {adaptor.PLATFORM_NAME}...")
|
||||
print("="*60)
|
||||
|
||||
result = adaptor.upload(package_path, api_key)
|
||||
|
||||
if result['success']:
|
||||
print(f"\n✅ {result['message']}")
|
||||
if result['url']:
|
||||
print(f" View at: {result['url']}")
|
||||
print("="*60)
|
||||
sys.exit(0)
|
||||
else:
|
||||
print(f"\n❌ Upload failed: {result['message']}")
|
||||
print()
|
||||
print("💡 Try manual upload instead (instructions above) ☝️")
|
||||
print("="*60)
|
||||
# Exit successfully - packaging worked even if upload failed
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("="*60)
|
||||
sys.exit(0)
|
||||
except ImportError:
|
||||
print("\n❌ Error: upload_skill.py not found")
|
||||
|
||||
except ImportError as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
print("Install required dependencies for this platform")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ Upload error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
Reference in New Issue
Block a user