767 lines
22 KiB
Python
Executable File
767 lines
22 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Cross-Platform Export Utilities for Agent-Skill-Creator
|
|
|
|
Packages Claude Code skills for Desktop/Web/API use with versioning and validation.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import zipfile
|
|
import json
|
|
import subprocess
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Dict, List, Tuple, Optional
|
|
|
|
# Directories and files to exclude from exports
|
|
EXCLUDE_DIRS = {
|
|
'.git', '__pycache__', 'node_modules', '.claude-plugin',
|
|
'venv', 'env', '.venv', '.pytest_cache', '.mypy_cache',
|
|
'dist', 'build', '*.egg-info'
|
|
}
|
|
|
|
EXCLUDE_FILES = {
|
|
'.DS_Store', '.gitignore', 'Thumbs.db', '*.pyc', '*.pyo',
|
|
'.env', 'credentials.json', '*.log', '.python-version'
|
|
}
|
|
|
|
# API package size limit (8MB per Claude API requirements)
|
|
MAX_API_SIZE_MB = 8
|
|
MAX_API_SIZE_BYTES = MAX_API_SIZE_MB * 1024 * 1024
|
|
|
|
# SKILL.md validation limits
|
|
MAX_NAME_LENGTH = 64
|
|
MAX_DESCRIPTION_LENGTH = 1024
|
|
|
|
|
|
def get_skill_version(skill_path: str, override_version: str = None) -> str:
|
|
"""
|
|
Determine skill version from git tags, SKILL.md, or use default.
|
|
|
|
Args:
|
|
skill_path: Path to skill directory
|
|
override_version: User-specified version (takes precedence)
|
|
|
|
Returns:
|
|
Version string in format "vX.Y.Z"
|
|
"""
|
|
if override_version:
|
|
return override_version if override_version.startswith('v') else f'v{override_version}'
|
|
|
|
# Try git tags first
|
|
try:
|
|
os.chdir(skill_path)
|
|
result = subprocess.run(
|
|
['git', 'describe', '--tags', '--abbrev=0'],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5
|
|
)
|
|
if result.returncode == 0:
|
|
version = result.stdout.strip()
|
|
return version if version.startswith('v') else f'v{version}'
|
|
except (subprocess.TimeoutExpired, FileNotFoundError, subprocess.SubprocessError):
|
|
pass
|
|
|
|
# Try SKILL.md frontmatter
|
|
skill_md_path = os.path.join(skill_path, 'SKILL.md')
|
|
if os.path.exists(skill_md_path):
|
|
try:
|
|
with open(skill_md_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
# Look for version: in frontmatter
|
|
if content.startswith('---'):
|
|
frontmatter_end = content.find('---', 3)
|
|
if frontmatter_end > 0:
|
|
frontmatter = content[3:frontmatter_end]
|
|
for line in frontmatter.split('\n'):
|
|
if line.strip().startswith('version:'):
|
|
version = line.split(':', 1)[1].strip()
|
|
return version if version.startswith('v') else f'v{version}'
|
|
except Exception:
|
|
pass
|
|
|
|
# Default version
|
|
return 'v1.0.0'
|
|
|
|
|
|
def validate_skill_structure(skill_path: str) -> Tuple[bool, List[str]]:
|
|
"""
|
|
Validate that skill has required structure for export.
|
|
|
|
Args:
|
|
skill_path: Path to skill directory
|
|
|
|
Returns:
|
|
Tuple of (is_valid, list_of_issues)
|
|
"""
|
|
issues = []
|
|
|
|
# Check if path exists and is directory
|
|
if not os.path.exists(skill_path):
|
|
issues.append(f"Path does not exist: {skill_path}")
|
|
return False, issues
|
|
|
|
if not os.path.isdir(skill_path):
|
|
issues.append(f"Path is not a directory: {skill_path}")
|
|
return False, issues
|
|
|
|
# Check for SKILL.md
|
|
skill_md_path = os.path.join(skill_path, 'SKILL.md')
|
|
if not os.path.exists(skill_md_path):
|
|
issues.append("SKILL.md not found (required)")
|
|
return False, issues
|
|
|
|
# Validate SKILL.md frontmatter
|
|
try:
|
|
with open(skill_md_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
if not content.startswith('---'):
|
|
issues.append("SKILL.md missing frontmatter (must start with ---)")
|
|
else:
|
|
# Extract frontmatter
|
|
frontmatter_end = content.find('---', 3)
|
|
if frontmatter_end == -1:
|
|
issues.append("SKILL.md frontmatter not closed (missing second ---)")
|
|
else:
|
|
frontmatter = content[3:frontmatter_end]
|
|
|
|
# Check for required fields
|
|
has_name = False
|
|
has_description = False
|
|
name_length = 0
|
|
desc_length = 0
|
|
|
|
for line in frontmatter.split('\n'):
|
|
line = line.strip()
|
|
if line.startswith('name:'):
|
|
has_name = True
|
|
name = line.split(':', 1)[1].strip()
|
|
name_length = len(name)
|
|
if name_length > MAX_NAME_LENGTH:
|
|
issues.append(f"name too long: {name_length} chars (max {MAX_NAME_LENGTH})")
|
|
elif line.startswith('description:'):
|
|
has_description = True
|
|
desc = line.split(':', 1)[1].strip()
|
|
desc_length = len(desc)
|
|
if desc_length > MAX_DESCRIPTION_LENGTH:
|
|
issues.append(f"description too long: {desc_length} chars (max {MAX_DESCRIPTION_LENGTH})")
|
|
|
|
if not has_name:
|
|
issues.append("SKILL.md missing 'name:' field in frontmatter")
|
|
if not has_description:
|
|
issues.append("SKILL.md missing 'description:' field in frontmatter")
|
|
|
|
except Exception as e:
|
|
issues.append(f"Error reading SKILL.md: {str(e)}")
|
|
|
|
return len(issues) == 0, issues
|
|
|
|
|
|
def should_include_file(file_path: str, filename: str) -> bool:
|
|
"""
|
|
Determine if a file should be included in export.
|
|
|
|
Args:
|
|
file_path: Full path to file
|
|
filename: Just the filename
|
|
|
|
Returns:
|
|
True if file should be included
|
|
"""
|
|
# Check excluded filenames
|
|
if filename in EXCLUDE_FILES:
|
|
return False
|
|
|
|
# Check excluded patterns
|
|
for pattern in EXCLUDE_FILES:
|
|
if '*' in pattern:
|
|
extension = pattern.replace('*', '')
|
|
if filename.endswith(extension):
|
|
return False
|
|
|
|
# Check for sensitive files
|
|
if filename in {'.env', 'credentials.json', 'secrets.json', 'api_keys.json'}:
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def get_directory_size(path: str) -> int:
|
|
"""
|
|
Calculate total size of directory in bytes.
|
|
|
|
Args:
|
|
path: Directory path
|
|
|
|
Returns:
|
|
Total size in bytes
|
|
"""
|
|
total = 0
|
|
for root, dirs, files in os.walk(path):
|
|
# Filter excluded directories
|
|
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
|
|
|
|
for file in files:
|
|
if should_include_file(os.path.join(root, file), file):
|
|
try:
|
|
total += os.path.getsize(os.path.join(root, file))
|
|
except OSError:
|
|
pass
|
|
return total
|
|
|
|
|
|
def create_export_package(
|
|
skill_path: str,
|
|
output_dir: str,
|
|
variant: str = 'desktop',
|
|
version: str = 'v1.0.0',
|
|
skill_name: str = None
|
|
) -> Dict:
|
|
"""
|
|
Create optimized export package for specified variant.
|
|
|
|
Args:
|
|
skill_path: Path to skill directory
|
|
output_dir: Where to save the .zip file
|
|
variant: 'desktop' or 'api'
|
|
version: Version string (e.g., 'v1.0.0')
|
|
skill_name: Override skill name (default: directory name)
|
|
|
|
Returns:
|
|
Dict with 'success', 'zip_path', 'size_mb', 'files_included', 'message'
|
|
"""
|
|
if skill_name is None:
|
|
skill_name = os.path.basename(os.path.abspath(skill_path))
|
|
|
|
# Create output filename
|
|
zip_filename = f"{skill_name}-{variant}-{version}.zip"
|
|
zip_path = os.path.join(output_dir, zip_filename)
|
|
|
|
files_included = []
|
|
total_size = 0
|
|
|
|
try:
|
|
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=9) as zipf:
|
|
for root, dirs, files in os.walk(skill_path):
|
|
# Filter excluded directories
|
|
dirs[:] = [d for d in dirs if d not in EXCLUDE_DIRS]
|
|
|
|
# For API variant, exclude .claude-plugin
|
|
if variant == 'api' and '.claude-plugin' in dirs:
|
|
dirs.remove('.claude-plugin')
|
|
|
|
for file in files:
|
|
if not should_include_file(os.path.join(root, file), file):
|
|
continue
|
|
|
|
file_path = os.path.join(root, file)
|
|
arcname = os.path.relpath(file_path, skill_path)
|
|
|
|
# For API variant, apply additional filtering
|
|
if variant == 'api':
|
|
# Skip large documentation files
|
|
if file.endswith('.md') and file not in {'SKILL.md', 'README.md'}:
|
|
continue
|
|
# Skip example files
|
|
if 'examples' in arcname.lower():
|
|
continue
|
|
|
|
try:
|
|
zipf.write(file_path, arcname)
|
|
files_included.append(arcname)
|
|
total_size += os.path.getsize(file_path)
|
|
except Exception as e:
|
|
print(f"Warning: Could not add {arcname}: {e}", file=sys.stderr)
|
|
|
|
# Check final size
|
|
final_size = os.path.getsize(zip_path)
|
|
size_mb = final_size / (1024 * 1024)
|
|
|
|
# Warn if API package is too large
|
|
if variant == 'api' and final_size > MAX_API_SIZE_BYTES:
|
|
return {
|
|
'success': False,
|
|
'zip_path': zip_path,
|
|
'size_mb': size_mb,
|
|
'files_included': files_included,
|
|
'message': f"API package too large: {size_mb:.2f} MB (max {MAX_API_SIZE_MB} MB)"
|
|
}
|
|
|
|
return {
|
|
'success': True,
|
|
'zip_path': zip_path,
|
|
'size_mb': size_mb,
|
|
'files_included': files_included,
|
|
'message': f"Package created successfully: {len(files_included)} files, {size_mb:.2f} MB"
|
|
}
|
|
|
|
except Exception as e:
|
|
return {
|
|
'success': False,
|
|
'zip_path': None,
|
|
'size_mb': 0,
|
|
'files_included': [],
|
|
'message': f"Error creating package: {str(e)}"
|
|
}
|
|
|
|
|
|
def generate_installation_guide(
|
|
skill_name: str,
|
|
version: str,
|
|
desktop_package: Dict = None,
|
|
api_package: Dict = None,
|
|
output_dir: str = None
|
|
) -> str:
|
|
"""
|
|
Generate platform-specific installation guide.
|
|
|
|
Args:
|
|
skill_name: Name of the skill
|
|
version: Version string
|
|
desktop_package: Desktop package info dict (optional)
|
|
api_package: API package info dict (optional)
|
|
output_dir: Where to save the guide
|
|
|
|
Returns:
|
|
Path to generated installation guide
|
|
"""
|
|
guide_filename = f"{skill_name}-{version}_INSTALL.md"
|
|
guide_path = os.path.join(output_dir, guide_filename)
|
|
|
|
# Build guide content
|
|
content = f"""# {skill_name} - Installation Guide
|
|
|
|
**Version:** {version}
|
|
**Generated:** {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
|
|
---
|
|
|
|
## 📦 Export Packages
|
|
|
|
"""
|
|
|
|
if desktop_package and desktop_package['success']:
|
|
content += f"""### Desktop/Web Package
|
|
|
|
**File:** `{os.path.basename(desktop_package['zip_path'])}`
|
|
**Size:** {desktop_package['size_mb']:.2f} MB
|
|
**Files:** {len(desktop_package['files_included'])} files included
|
|
|
|
✅ Optimized for Claude Desktop and claude.ai manual upload
|
|
|
|
"""
|
|
|
|
if api_package and api_package['success']:
|
|
content += f"""### API Package
|
|
|
|
**File:** `{os.path.basename(api_package['zip_path'])}`
|
|
**Size:** {api_package['size_mb']:.2f} MB
|
|
**Files:** {len(api_package['files_included'])} files included
|
|
|
|
✅ Optimized for programmatic Claude API integration
|
|
|
|
"""
|
|
|
|
content += """---
|
|
|
|
## 🚀 Installation Instructions
|
|
|
|
### For Claude Desktop
|
|
|
|
1. **Locate the Desktop package**
|
|
- File: `{skill}-desktop-{version}.zip`
|
|
|
|
2. **Open Claude Desktop**
|
|
- Launch the Claude Desktop application
|
|
|
|
3. **Navigate to Skills settings**
|
|
- Go to: **Settings → Capabilities → Skills**
|
|
|
|
4. **Upload the skill**
|
|
- Click: **Upload skill**
|
|
- Select the desktop package .zip file
|
|
- Wait for upload confirmation
|
|
|
|
5. **Verify installation**
|
|
- The skill should now appear in your Skills list
|
|
- Try using it with a relevant query
|
|
|
|
✅ **Your skill is now available in Claude Desktop!**
|
|
|
|
---
|
|
|
|
### For claude.ai (Web Interface)
|
|
|
|
1. **Locate the Desktop package**
|
|
- File: `{skill}-desktop-{version}.zip`
|
|
- (Same package as Desktop - optimized for both)
|
|
|
|
2. **Visit claude.ai**
|
|
- Open https://claude.ai in your browser
|
|
- Log in to your account
|
|
|
|
3. **Open Settings**
|
|
- Click your profile icon
|
|
- Select **Settings**
|
|
|
|
4. **Navigate to Skills**
|
|
- Click on the **Skills** section
|
|
|
|
5. **Upload the skill**
|
|
- Click: **Upload skill**
|
|
- Select the desktop package .zip file
|
|
- Confirm the upload
|
|
|
|
6. **Start using**
|
|
- Create a new conversation
|
|
- The skill will activate automatically when relevant
|
|
|
|
✅ **Your skill is now available at claude.ai!**
|
|
|
|
---
|
|
|
|
### For Claude API (Programmatic Integration)
|
|
|
|
1. **Locate the API package**
|
|
- File: `{skill}-api-{version}.zip`
|
|
- Optimized for API use (smaller, execution-focused)
|
|
|
|
2. **Install required packages**
|
|
```bash
|
|
pip install anthropic
|
|
```
|
|
|
|
3. **Upload skill programmatically**
|
|
```python
|
|
import anthropic
|
|
|
|
client = anthropic.Anthropic(api_key="your-api-key")
|
|
|
|
# Upload the skill
|
|
with open('{skill}-api-{version}.zip', 'rb') as f:
|
|
skill = client.skills.create(
|
|
file=f,
|
|
name="{skill}"
|
|
)
|
|
|
|
print(f"Skill uploaded! ID: {{skill.id}}")
|
|
```
|
|
|
|
4. **Use in API requests**
|
|
```python
|
|
response = client.messages.create(
|
|
model="claude-sonnet-4",
|
|
messages=[
|
|
{{"role": "user", "content": "Your query here"}}
|
|
],
|
|
container={{
|
|
"type": "custom_skill",
|
|
"skill_id": skill.id
|
|
}},
|
|
betas=[
|
|
"code-execution-2025-08-25",
|
|
"skills-2025-10-02"
|
|
]
|
|
)
|
|
|
|
print(response.content)
|
|
```
|
|
|
|
5. **Important API requirements**
|
|
- Must include beta headers: `code-execution-2025-08-25` and `skills-2025-10-02`
|
|
- Maximum 8 skills per request
|
|
- Skills run in isolated containers (no network access, no pip install)
|
|
|
|
✅ **Your skill is now integrated with the Claude API!**
|
|
|
|
---
|
|
|
|
## 📋 Platform Comparison
|
|
|
|
| Feature | Claude Code | Desktop/Web | Claude API |
|
|
|---------|-------------|-------------|------------|
|
|
| **Installation** | Plugin command | Manual upload | Programmatic |
|
|
| **Updates** | Git pull | Re-upload .zip | New upload |
|
|
| **Version Control** | ✅ Native | ⚠️ Manual | ✅ Versioned |
|
|
| **Team Sharing** | ✅ Via plugins | ❌ Individual | ✅ Via API |
|
|
| **marketplace.json** | ✅ Used | ❌ Ignored | ❌ Not used |
|
|
|
|
---
|
|
|
|
## ⚙️ Technical Details
|
|
|
|
### What's Included
|
|
|
|
"""
|
|
|
|
if desktop_package and desktop_package['success']:
|
|
content += f"""**Desktop Package:**
|
|
- SKILL.md (core functionality)
|
|
- Complete scripts/ directory
|
|
- Full references/ documentation
|
|
- All assets/ and templates
|
|
- README.md and requirements.txt
|
|
|
|
"""
|
|
|
|
if api_package and api_package['success']:
|
|
content += f"""**API Package:**
|
|
- SKILL.md (required)
|
|
- Essential scripts only
|
|
- Minimal documentation (execution-focused)
|
|
- Size-optimized (< 8MB)
|
|
|
|
"""
|
|
|
|
content += """### What's Excluded (Security)
|
|
|
|
For both packages:
|
|
- `.git/` (version control history)
|
|
- `__pycache__/` (compiled Python)
|
|
- `.env` files (environment variables)
|
|
- `credentials.json` (API keys/secrets)
|
|
- `.DS_Store` (system metadata)
|
|
|
|
For API package additionally:
|
|
- `.claude-plugin/` (Claude Code specific)
|
|
- Large documentation files
|
|
- Example files (size optimization)
|
|
|
|
---
|
|
|
|
## 🔧 Troubleshooting
|
|
|
|
### Upload fails with "File too large"
|
|
|
|
**Desktop/Web:**
|
|
- Maximum size varies by platform
|
|
- Try the API package instead (smaller)
|
|
- Contact support if needed
|
|
|
|
**API:**
|
|
- Maximum: 8MB
|
|
- The API package is already optimized
|
|
- May need to reduce documentation or scripts
|
|
|
|
### Skill doesn't activate
|
|
|
|
**Check:**
|
|
1. SKILL.md has valid frontmatter
|
|
2. `name:` field is present and ≤ 64 characters
|
|
3. `description:` field is present and ≤ 1024 characters
|
|
4. Description clearly explains when to use the skill
|
|
|
|
### API errors
|
|
|
|
**Common issues:**
|
|
- Missing beta headers (required!)
|
|
- Skill ID incorrect (check `skill.id` after upload)
|
|
- Network/pip install attempted (not allowed in API environment)
|
|
|
|
---
|
|
|
|
## 📚 Additional Resources
|
|
|
|
- **Export Guide:** See `references/export-guide.md` in the main repository
|
|
- **Cross-Platform Guide:** See `references/cross-platform-guide.md`
|
|
- **Main Documentation:** See the main README.md
|
|
|
|
---
|
|
|
|
## ✅ Verification Checklist
|
|
|
|
After installation, verify:
|
|
|
|
- [ ] Skill appears in Skills list
|
|
- [ ] Skill activates with relevant queries
|
|
- [ ] Scripts execute correctly
|
|
- [ ] Documentation is accessible
|
|
- [ ] No error messages on activation
|
|
|
|
---
|
|
|
|
**Need help?** Refer to the platform-specific documentation or the main repository guides.
|
|
|
|
**Generated by:** agent-skill-creator v3.2 cross-platform export system
|
|
"""
|
|
|
|
# Write guide to file
|
|
with open(guide_path, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
|
|
return guide_path
|
|
|
|
|
|
def export_skill(
|
|
skill_path: str,
|
|
variants: List[str] = ['desktop', 'api'],
|
|
version_override: str = None,
|
|
output_dir: str = None
|
|
) -> Dict:
|
|
"""
|
|
Main export function - validates, packages, and generates guides.
|
|
|
|
Args:
|
|
skill_path: Path to skill directory
|
|
variants: List of variants to create ('desktop', 'api', or both)
|
|
version_override: User-specified version (optional)
|
|
output_dir: Where to save exports (default: exports/ in parent dir)
|
|
|
|
Returns:
|
|
Dict with export results
|
|
"""
|
|
# Normalize path
|
|
skill_path = os.path.abspath(skill_path)
|
|
skill_name = os.path.basename(skill_path)
|
|
|
|
# Determine output directory
|
|
if output_dir is None:
|
|
# Use exports/ in parent directory
|
|
parent_dir = os.path.dirname(skill_path)
|
|
output_dir = os.path.join(parent_dir, 'exports')
|
|
|
|
# Create output directory if needed
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
# Validate skill structure
|
|
print("🔍 Validating skill structure...")
|
|
valid, issues = validate_skill_structure(skill_path)
|
|
if not valid:
|
|
return {
|
|
'success': False,
|
|
'message': 'Skill validation failed',
|
|
'issues': issues
|
|
}
|
|
print("✅ Skill structure valid")
|
|
|
|
# Determine version
|
|
version = get_skill_version(skill_path, version_override)
|
|
print(f"📌 Version: {version}")
|
|
|
|
# Create packages
|
|
results = {
|
|
'success': True,
|
|
'version': version,
|
|
'packages': {}
|
|
}
|
|
|
|
if 'desktop' in variants:
|
|
print(f"\n🔨 Creating Desktop/Web package...")
|
|
desktop_result = create_export_package(
|
|
skill_path, output_dir, 'desktop', version, skill_name
|
|
)
|
|
results['packages']['desktop'] = desktop_result
|
|
if desktop_result['success']:
|
|
print(f"✅ Desktop package: {os.path.basename(desktop_result['zip_path'])} ({desktop_result['size_mb']:.2f} MB)")
|
|
else:
|
|
print(f"❌ Desktop package failed: {desktop_result['message']}")
|
|
results['success'] = False
|
|
|
|
if 'api' in variants:
|
|
print(f"\n🔨 Creating API package...")
|
|
api_result = create_export_package(
|
|
skill_path, output_dir, 'api', version, skill_name
|
|
)
|
|
results['packages']['api'] = api_result
|
|
if api_result['success']:
|
|
print(f"✅ API package: {os.path.basename(api_result['zip_path'])} ({api_result['size_mb']:.2f} MB)")
|
|
else:
|
|
print(f"❌ API package failed: {api_result['message']}")
|
|
results['success'] = False
|
|
|
|
# Generate installation guide
|
|
if results['success']:
|
|
print(f"\n📄 Generating installation guide...")
|
|
guide_path = generate_installation_guide(
|
|
skill_name,
|
|
version,
|
|
desktop_package=results['packages'].get('desktop'),
|
|
api_package=results['packages'].get('api'),
|
|
output_dir=output_dir
|
|
)
|
|
results['guide_path'] = guide_path
|
|
print(f"✅ Installation guide: {os.path.basename(guide_path)}")
|
|
|
|
return results
|
|
|
|
|
|
def main():
|
|
"""CLI interface for export_utils.py"""
|
|
if len(sys.argv) < 2:
|
|
print("""
|
|
Usage: python export_utils.py <skill-path> [options]
|
|
|
|
Arguments:
|
|
skill-path Path to skill directory
|
|
|
|
Options:
|
|
--variant VARIANT Export variant: desktop, api, or both (default: both)
|
|
--version VERSION Override version (default: auto-detect)
|
|
--output-dir DIR Output directory (default: exports/)
|
|
|
|
Examples:
|
|
python export_utils.py ./my-skill-cskill
|
|
python export_utils.py ./my-skill-cskill --variant desktop
|
|
python export_utils.py ./my-skill-cskill --version 2.0.1
|
|
python export_utils.py ./my-skill-cskill --variant api --output-dir ./dist
|
|
""")
|
|
sys.exit(1)
|
|
|
|
skill_path = sys.argv[1]
|
|
|
|
# Parse options
|
|
variants = ['desktop', 'api'] # default: both
|
|
version_override = None
|
|
output_dir = None
|
|
|
|
i = 2
|
|
while i < len(sys.argv):
|
|
if sys.argv[i] == '--variant':
|
|
variant_arg = sys.argv[i + 1]
|
|
if variant_arg == 'both':
|
|
variants = ['desktop', 'api']
|
|
else:
|
|
variants = [variant_arg]
|
|
i += 2
|
|
elif sys.argv[i] == '--version':
|
|
version_override = sys.argv[i + 1]
|
|
i += 2
|
|
elif sys.argv[i] == '--output-dir':
|
|
output_dir = sys.argv[i + 1]
|
|
i += 2
|
|
else:
|
|
print(f"Unknown option: {sys.argv[i]}")
|
|
sys.exit(1)
|
|
|
|
# Run export
|
|
print(f"\n🚀 Exporting skill: {os.path.basename(skill_path)}\n")
|
|
results = export_skill(skill_path, variants, version_override, output_dir)
|
|
|
|
# Print summary
|
|
print(f"\n{'='*60}")
|
|
if results['success']:
|
|
print("✅ Export completed successfully!")
|
|
print(f"\n📦 Packages created:")
|
|
for variant, package in results['packages'].items():
|
|
if package['success']:
|
|
print(f" - {variant.capitalize()}: {os.path.basename(package['zip_path'])}")
|
|
if 'guide_path' in results:
|
|
print(f"\n📄 Installation guide: {os.path.basename(results['guide_path'])}")
|
|
print(f"\n🎯 All files saved to: {output_dir or 'exports/'}")
|
|
else:
|
|
print("❌ Export failed!")
|
|
if 'issues' in results:
|
|
print("\nIssues found:")
|
|
for issue in results['issues']:
|
|
print(f" - {issue}")
|
|
print(f"{'='*60}\n")
|
|
|
|
sys.exit(0 if results['success'] else 1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|