Implements native Weaviate integration for RAG pipelines as part of Week 2 vector store integrations. ## Features - **Auto-generated schema** - Creates Weaviate class definition from metadata - **Deterministic UUIDs** - Stable IDs for consistent re-imports - **Rich metadata** - All properties indexed for filtering - **Batch-ready format** - Optimized for batch import - **Example code** - Complete usage examples in upload() ## Output Format JSON file containing: - `schema`: Weaviate class definition with properties - `objects`: Array of objects ready for batch import - `class_name`: Derived from skill name ## Properties - content (text, searchable) - source (filterable, searchable) - category (filterable, searchable) - file (filterable) - type (filterable) - version (filterable) ## CLI Integration ```bash skill-seekers package output/django --target weaviate # → output/django-weaviate.json ``` ## Files Added - src/skill_seekers/cli/adaptors/weaviate.py (428 lines) * Complete Weaviate adaptor implementation * Schema auto-generation * UUID generation from content hash * Example code for import/query ## Files Modified - src/skill_seekers/cli/adaptors/__init__.py * Import WeaviateAdaptor * Register "weaviate" in ADAPTORS - src/skill_seekers/cli/package_skill.py * Add "weaviate" to --target choices - src/skill_seekers/cli/main.py * Add "weaviate" to --target choices ## Testing Tested with ansible skill: - ✅ Schema generation works - ✅ Object format correct - ✅ UUID generation deterministic - ✅ Metadata preserved - ✅ CLI integration working Output: output/ansible-weaviate.json (10.7 KB, 1 object) ## Week 2 Progress - ✅ Task #10: Weaviate adaptor (Complete) - ⏳ Task #11: Chroma adaptor (Next) - ⏳ Task #12: FAISS helpers - ⏳ Task #13: Qdrant adaptor Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
242 lines
7.3 KiB
Python
242 lines
7.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Simple Skill Packager
|
|
Packages a skill directory into a .zip file for Claude.
|
|
|
|
Usage:
|
|
skill-seekers package output/steam-inventory/
|
|
skill-seekers package output/react/
|
|
skill-seekers package output/react/ --no-open # Don't open folder
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# Import utilities
|
|
try:
|
|
from quality_checker import SkillQualityChecker, print_report
|
|
from utils import (
|
|
format_file_size,
|
|
open_folder,
|
|
print_upload_instructions,
|
|
validate_skill_directory,
|
|
)
|
|
except ImportError:
|
|
# If running from different directory, add cli to path
|
|
sys.path.insert(0, str(Path(__file__).parent))
|
|
from quality_checker import SkillQualityChecker, print_report
|
|
from utils import (
|
|
format_file_size,
|
|
open_folder,
|
|
print_upload_instructions,
|
|
validate_skill_directory,
|
|
)
|
|
|
|
|
|
def package_skill(skill_dir, open_folder_after=True, skip_quality_check=False, target="claude"):
|
|
"""
|
|
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, package_path) where success is bool and package_path is Path or None
|
|
"""
|
|
skill_path = Path(skill_dir)
|
|
|
|
# Validate skill directory
|
|
is_valid, error_msg = validate_skill_directory(skill_path)
|
|
if not is_valid:
|
|
print(f"❌ Error: {error_msg}")
|
|
return False, None
|
|
|
|
# Run quality checks (unless skipped)
|
|
if not skip_quality_check:
|
|
print("\n" + "=" * 60)
|
|
print("QUALITY CHECK")
|
|
print("=" * 60)
|
|
|
|
checker = SkillQualityChecker(skill_path)
|
|
report = checker.check_all()
|
|
|
|
# Print report
|
|
print_report(report, verbose=False)
|
|
|
|
# If there are errors or warnings, ask user to confirm
|
|
if report.has_errors or report.has_warnings:
|
|
print("=" * 60)
|
|
response = input("\nContinue with packaging? (y/n): ").strip().lower()
|
|
if response != "y":
|
|
print("\n❌ Packaging cancelled by user")
|
|
return False, None
|
|
print()
|
|
else:
|
|
print("=" * 60)
|
|
print()
|
|
|
|
# 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
|
|
output_dir = skill_path.parent
|
|
|
|
print(f"📦 Packaging skill: {skill_name}")
|
|
print(f" Target: {adaptor.PLATFORM_NAME}")
|
|
print(f" Source: {skill_path}")
|
|
|
|
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
|
|
|
|
# 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: {package_path.parent}")
|
|
open_folder(package_path.parent)
|
|
|
|
# Print upload instructions
|
|
print_upload_instructions(package_path)
|
|
|
|
return True, package_path
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Package a skill directory into a .zip file for Claude",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Package skill with quality checks (recommended)
|
|
skill-seekers package output/react/
|
|
|
|
# Package skill without opening folder
|
|
skill-seekers package output/react/ --no-open
|
|
|
|
# Skip quality checks (faster, but not recommended)
|
|
skill-seekers package output/react/ --skip-quality-check
|
|
|
|
# Package and auto-upload to Claude
|
|
skill-seekers package output/react/ --upload
|
|
|
|
# Get help
|
|
skill-seekers package --help
|
|
""",
|
|
)
|
|
|
|
parser.add_argument("skill_dir", help="Path to skill directory (e.g., output/react/)")
|
|
|
|
parser.add_argument(
|
|
"--no-open", action="store_true", help="Do not open the output folder after packaging"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--skip-quality-check", action="store_true", help="Skip quality checks before packaging"
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--target",
|
|
choices=["claude", "gemini", "openai", "markdown", "langchain", "llama-index", "weaviate"],
|
|
default="claude",
|
|
help="Target LLM platform (default: claude)",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--upload",
|
|
action="store_true",
|
|
help="Automatically upload after packaging (requires platform API key)",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
success, package_path = package_skill(
|
|
args.skill_dir,
|
|
open_folder_after=not args.no_open,
|
|
skip_quality_check=args.skip_quality_check,
|
|
target=args.target,
|
|
)
|
|
|
|
if not success:
|
|
sys.exit(1)
|
|
|
|
# Auto-upload if requested
|
|
if args.upload:
|
|
try:
|
|
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(" 1. Get API key from the platform")
|
|
print(f" 2. Set: export {adaptor.get_env_var_name()}=...")
|
|
print(" 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)
|
|
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)
|
|
|
|
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)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|