feat: Complete multi-platform feature parity implementation
This commit implements full feature parity across all platforms (Claude, Gemini, OpenAI, Markdown) and all skill modes (Docs, GitHub, PDF, Unified, Local Repo). ## Core Changes ### Phase 1: MCP Package Tool Multi-Platform Support - Added `target` parameter to `package_skill_tool()` in packaging_tools.py - Updated MCP server definition to expose `target` parameter - Platform-specific packaging: ZIP for Claude/OpenAI/Markdown, tar.gz for Gemini - Platform-specific output messages and instructions ### Phase 2: MCP Upload Tool Multi-Platform Support - Added `target` parameter to `upload_skill_tool()` in packaging_tools.py - Added optional `api_key` parameter for API key override - Updated MCP server definition with platform selection - Platform-specific API key validation (ANTHROPIC_API_KEY, GOOGLE_API_KEY, OPENAI_API_KEY) - Graceful handling of Markdown (upload not supported) ### Phase 3: Standalone MCP Enhancement Tool - Created new `enhance_skill_tool()` function (140+ lines) - Supports both 'local' mode (Claude Code Max) and 'api' mode (platform APIs) - Added MCP server definition for `enhance_skill` - Works with Claude, Gemini, and OpenAI - Integrated into MCP tools exports ### Phase 4: Unified Config Splitting Support - Added `is_unified_config()` method to detect multi-source configs - Implemented `split_by_source()` method to split by source type (docs, github, pdf) - Updated auto-detection to recommend 'source' strategy for unified configs - Added 'source' to valid CLI strategy choices - Updated MCP tool documentation for unified support ### Phase 5: Comprehensive Feature Matrix Documentation - Created `docs/FEATURE_MATRIX.md` (~400 lines) - Complete platform comparison tables - Skill mode support matrix - CLI and MCP tool coverage matrices - Platform-specific notes and FAQs - Workflow examples for each combination - Updated README.md with feature matrix section ## Files Modified **Core Implementation:** - src/skill_seekers/mcp/tools/packaging_tools.py - src/skill_seekers/mcp/server_fastmcp.py - src/skill_seekers/mcp/tools/__init__.py - src/skill_seekers/cli/split_config.py - src/skill_seekers/mcp/tools/splitting_tools.py **Documentation:** - docs/FEATURE_MATRIX.md (NEW) - README.md **Tests:** - tests/test_install_multiplatform.py (already existed) ## Test Results - ✅ 699 tests passing - ✅ All multiplatform install tests passing (6/6) - ✅ No regressions introduced - ✅ All syntax checks passed - ✅ Import tests successful ## Breaking Changes None - all changes are backward compatible with default `target='claude'` ## Migration Guide Existing MCP calls without `target` parameter will continue to work (defaults to 'claude'). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,17 +60,24 @@ Examples:
|
||||
# Preview workflow (dry run)
|
||||
skill-seekers install --config react --dry-run
|
||||
|
||||
# Install for Gemini instead of Claude
|
||||
skill-seekers install --config react --target gemini
|
||||
|
||||
# Install for OpenAI ChatGPT
|
||||
skill-seekers install --config fastapi --target openai
|
||||
|
||||
Important:
|
||||
- Enhancement is MANDATORY (30-60 sec) for quality (3/10→9/10)
|
||||
- Total time: 20-45 minutes (mostly scraping)
|
||||
- Auto-uploads to Claude if ANTHROPIC_API_KEY is set
|
||||
- Multi-platform support: claude (default), gemini, openai, markdown
|
||||
- Auto-uploads if API key is set (ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY)
|
||||
|
||||
Phases:
|
||||
1. Fetch config (if config name provided)
|
||||
2. Scrape documentation
|
||||
3. AI Enhancement (MANDATORY - no skip option)
|
||||
4. Package to .zip
|
||||
5. Upload to Claude (optional)
|
||||
4. Package for target platform (ZIP or tar.gz)
|
||||
5. Upload to target platform (optional)
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -104,6 +111,13 @@ Phases:
|
||||
help="Preview workflow without executing"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--target",
|
||||
choices=['claude', 'gemini', 'openai', 'markdown'],
|
||||
default='claude',
|
||||
help="Target LLM platform (default: claude)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Determine if config is a name or path
|
||||
@@ -124,7 +138,8 @@ Phases:
|
||||
"destination": args.destination,
|
||||
"auto_upload": not args.no_upload,
|
||||
"unlimited": args.unlimited,
|
||||
"dry_run": args.dry_run
|
||||
"dry_run": args.dry_run,
|
||||
"target": args.target
|
||||
}
|
||||
|
||||
# Run async tool
|
||||
|
||||
@@ -36,15 +36,37 @@ class ConfigSplitter:
|
||||
print(f"❌ Error: Invalid JSON in config file: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def is_unified_config(self) -> bool:
|
||||
"""Check if this is a unified multi-source config"""
|
||||
return 'sources' in self.config
|
||||
|
||||
def get_split_strategy(self) -> str:
|
||||
"""Determine split strategy"""
|
||||
# Check if strategy is defined in config
|
||||
# For unified configs, default to source-based splitting
|
||||
if self.is_unified_config():
|
||||
if self.strategy == "auto":
|
||||
num_sources = len(self.config.get('sources', []))
|
||||
if num_sources <= 1:
|
||||
print(f"ℹ️ Single source unified config - no splitting needed")
|
||||
return "none"
|
||||
else:
|
||||
print(f"ℹ️ Multi-source unified config ({num_sources} sources) - source split recommended")
|
||||
return "source"
|
||||
# For unified configs, only 'source' and 'none' strategies are valid
|
||||
elif self.strategy in ['source', 'none']:
|
||||
return self.strategy
|
||||
else:
|
||||
print(f"⚠️ Warning: Strategy '{self.strategy}' not supported for unified configs")
|
||||
print(f"ℹ️ Using 'source' strategy instead")
|
||||
return "source"
|
||||
|
||||
# Check if strategy is defined in config (documentation configs)
|
||||
if 'split_strategy' in self.config:
|
||||
config_strategy = self.config['split_strategy']
|
||||
if config_strategy != "none":
|
||||
return config_strategy
|
||||
|
||||
# Use provided strategy or auto-detect
|
||||
# Use provided strategy or auto-detect (documentation configs)
|
||||
if self.strategy == "auto":
|
||||
max_pages = self.config.get('max_pages', 500)
|
||||
|
||||
@@ -147,6 +169,46 @@ class ConfigSplitter:
|
||||
print(f"✅ Created {len(configs)} size-based configs ({self.target_pages} pages each)")
|
||||
return configs
|
||||
|
||||
def split_by_source(self) -> List[Dict[str, Any]]:
|
||||
"""Split unified config by source type"""
|
||||
if not self.is_unified_config():
|
||||
print("❌ Error: Config is not a unified config (missing 'sources' key)")
|
||||
sys.exit(1)
|
||||
|
||||
sources = self.config.get('sources', [])
|
||||
if not sources:
|
||||
print("❌ Error: No sources defined in unified config")
|
||||
sys.exit(1)
|
||||
|
||||
configs = []
|
||||
source_type_counts = defaultdict(int)
|
||||
|
||||
for source in sources:
|
||||
source_type = source.get('type', 'unknown')
|
||||
source_type_counts[source_type] += 1
|
||||
count = source_type_counts[source_type]
|
||||
|
||||
# Create new config for this source
|
||||
new_config = {
|
||||
'name': f"{self.base_name}-{source_type}" + (f"-{count}" if count > 1 else ""),
|
||||
'description': f"{self.base_name.capitalize()} - {source_type.title()} source. {self.config.get('description', '')}",
|
||||
'sources': [source] # Single source per config
|
||||
}
|
||||
|
||||
# Copy merge_mode if it exists
|
||||
if 'merge_mode' in self.config:
|
||||
new_config['merge_mode'] = self.config['merge_mode']
|
||||
|
||||
configs.append(new_config)
|
||||
|
||||
print(f"✅ Created {len(configs)} source-based configs")
|
||||
|
||||
# Show breakdown by source type
|
||||
for source_type, count in source_type_counts.items():
|
||||
print(f" 📄 {count}x {source_type}")
|
||||
|
||||
return configs
|
||||
|
||||
def create_router_config(self, sub_configs: List[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""Create a router config that references sub-skills"""
|
||||
router_name = self.config.get('split_config', {}).get('router_name', self.base_name)
|
||||
@@ -173,17 +235,22 @@ class ConfigSplitter:
|
||||
"""Execute split based on strategy"""
|
||||
strategy = self.get_split_strategy()
|
||||
|
||||
config_type = "UNIFIED" if self.is_unified_config() else "DOCUMENTATION"
|
||||
print(f"\n{'='*60}")
|
||||
print(f"CONFIG SPLITTER: {self.base_name}")
|
||||
print(f"CONFIG SPLITTER: {self.base_name} ({config_type})")
|
||||
print(f"{'='*60}")
|
||||
print(f"Strategy: {strategy}")
|
||||
print(f"Target pages per skill: {self.target_pages}")
|
||||
if not self.is_unified_config():
|
||||
print(f"Target pages per skill: {self.target_pages}")
|
||||
print("")
|
||||
|
||||
if strategy == "none":
|
||||
print("ℹ️ No splitting required")
|
||||
return [self.config]
|
||||
|
||||
elif strategy == "source":
|
||||
return self.split_by_source()
|
||||
|
||||
elif strategy == "category":
|
||||
return self.split_by_category(create_router=False)
|
||||
|
||||
@@ -245,9 +312,14 @@ Examples:
|
||||
Split Strategies:
|
||||
none - No splitting (single skill)
|
||||
auto - Automatically choose best strategy
|
||||
source - Split unified configs by source type (docs, github, pdf)
|
||||
category - Split by categories defined in config
|
||||
router - Create router + category-based sub-skills
|
||||
size - Split by page count
|
||||
|
||||
Config Types:
|
||||
Documentation - Single base_url config (supports: category, router, size)
|
||||
Unified - Multi-source config (supports: source)
|
||||
"""
|
||||
)
|
||||
|
||||
@@ -258,7 +330,7 @@ Split Strategies:
|
||||
|
||||
parser.add_argument(
|
||||
'--strategy',
|
||||
choices=['auto', 'none', 'category', 'router', 'size'],
|
||||
choices=['auto', 'none', 'source', 'category', 'router', 'size'],
|
||||
default='auto',
|
||||
help='Splitting strategy (default: auto)'
|
||||
)
|
||||
|
||||
@@ -84,6 +84,7 @@ try:
|
||||
# Packaging tools
|
||||
package_skill_impl,
|
||||
upload_skill_impl,
|
||||
enhance_skill_impl,
|
||||
install_skill_impl,
|
||||
# Splitting tools
|
||||
split_config_impl,
|
||||
@@ -109,6 +110,7 @@ except ImportError:
|
||||
scrape_pdf_impl,
|
||||
package_skill_impl,
|
||||
upload_skill_impl,
|
||||
enhance_skill_impl,
|
||||
install_skill_impl,
|
||||
split_config_impl,
|
||||
generate_router_impl,
|
||||
@@ -397,24 +399,27 @@ async def scrape_pdf(
|
||||
|
||||
|
||||
@safe_tool_decorator(
|
||||
description="Package a skill directory into a .zip file ready for Claude upload. Automatically uploads if ANTHROPIC_API_KEY is set."
|
||||
description="Package skill directory into platform-specific format (ZIP for Claude/OpenAI/Markdown, tar.gz for Gemini). Supports all platforms: claude, gemini, openai, markdown. Automatically uploads if platform API key is set."
|
||||
)
|
||||
async def package_skill(
|
||||
skill_dir: str,
|
||||
target: str = "claude",
|
||||
auto_upload: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
Package a skill directory into a .zip file.
|
||||
Package skill directory for target LLM platform.
|
||||
|
||||
Args:
|
||||
skill_dir: Path to skill directory (e.g., output/react/)
|
||||
auto_upload: Try to upload automatically if API key is available (default: true). If false, only package without upload attempt.
|
||||
skill_dir: Path to skill directory to package (e.g., output/react/)
|
||||
target: Target platform (default: 'claude'). Options: claude, gemini, openai, markdown
|
||||
auto_upload: Auto-upload after packaging if API key is available (default: true). Requires platform-specific API key: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY.
|
||||
|
||||
Returns:
|
||||
Packaging results with .zip file path and upload status.
|
||||
Packaging results with file path and platform info.
|
||||
"""
|
||||
args = {
|
||||
"skill_dir": skill_dir,
|
||||
"target": target,
|
||||
"auto_upload": auto_upload,
|
||||
}
|
||||
result = await package_skill_impl(args)
|
||||
@@ -424,26 +429,74 @@ async def package_skill(
|
||||
|
||||
|
||||
@safe_tool_decorator(
|
||||
description="Upload a skill .zip file to Claude automatically (requires ANTHROPIC_API_KEY)"
|
||||
description="Upload skill package to target LLM platform API. Requires platform-specific API key. Supports: claude (Anthropic Skills API), gemini (Google Files API), openai (Assistants API). Does NOT support markdown."
|
||||
)
|
||||
async def upload_skill(skill_zip: str) -> str:
|
||||
async def upload_skill(
|
||||
skill_zip: str,
|
||||
target: str = "claude",
|
||||
api_key: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Upload a skill .zip file to Claude.
|
||||
Upload skill package to target platform.
|
||||
|
||||
Args:
|
||||
skill_zip: Path to skill .zip file (e.g., output/react.zip)
|
||||
skill_zip: Path to skill package (.zip or .tar.gz, e.g., output/react.zip)
|
||||
target: Target platform (default: 'claude'). Options: claude, gemini, openai
|
||||
api_key: Optional API key (uses env var if not provided: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY)
|
||||
|
||||
Returns:
|
||||
Upload results with success/error message.
|
||||
Upload results with skill ID and platform URL.
|
||||
"""
|
||||
result = await upload_skill_impl({"skill_zip": skill_zip})
|
||||
args = {
|
||||
"skill_zip": skill_zip,
|
||||
"target": target,
|
||||
}
|
||||
if api_key:
|
||||
args["api_key"] = api_key
|
||||
|
||||
result = await upload_skill_impl(args)
|
||||
if isinstance(result, list) and result:
|
||||
return result[0].text if hasattr(result[0], "text") else str(result[0])
|
||||
return str(result)
|
||||
|
||||
|
||||
@safe_tool_decorator(
|
||||
description="Complete one-command workflow: fetch config → scrape docs → AI enhance (MANDATORY) → package → upload. Enhancement required for quality (3/10→9/10). Takes 20-45 min depending on config size. Automatically uploads to Claude if ANTHROPIC_API_KEY is set."
|
||||
description="Enhance SKILL.md with AI using target platform's model. Local mode uses Claude Code Max (no API key). API mode uses platform API (requires key). Transforms basic templates into comprehensive 500+ line guides with examples."
|
||||
)
|
||||
async def enhance_skill(
|
||||
skill_dir: str,
|
||||
target: str = "claude",
|
||||
mode: str = "local",
|
||||
api_key: str | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Enhance SKILL.md with AI.
|
||||
|
||||
Args:
|
||||
skill_dir: Path to skill directory containing SKILL.md (e.g., output/react/)
|
||||
target: Target platform (default: 'claude'). Options: claude, gemini, openai
|
||||
mode: Enhancement mode (default: 'local'). Options: local (Claude Code, no API), api (uses platform API)
|
||||
api_key: Optional API key for 'api' mode (uses env var if not provided: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY)
|
||||
|
||||
Returns:
|
||||
Enhancement results with backup location.
|
||||
"""
|
||||
args = {
|
||||
"skill_dir": skill_dir,
|
||||
"target": target,
|
||||
"mode": mode,
|
||||
}
|
||||
if api_key:
|
||||
args["api_key"] = api_key
|
||||
|
||||
result = await enhance_skill_impl(args)
|
||||
if isinstance(result, list) and result:
|
||||
return result[0].text if hasattr(result[0], "text") else str(result[0])
|
||||
return str(result)
|
||||
|
||||
|
||||
@safe_tool_decorator(
|
||||
description="Complete one-command workflow: fetch config → scrape docs → AI enhance (MANDATORY) → package → upload. Enhancement required for quality (3/10→9/10). Takes 20-45 min depending on config size. Supports multiple LLM platforms: claude (default), gemini, openai, markdown. Auto-uploads if platform API key is set."
|
||||
)
|
||||
async def install_skill(
|
||||
config_name: str | None = None,
|
||||
@@ -452,6 +505,7 @@ async def install_skill(
|
||||
auto_upload: bool = True,
|
||||
unlimited: bool = False,
|
||||
dry_run: bool = False,
|
||||
target: str = "claude",
|
||||
) -> str:
|
||||
"""
|
||||
Complete one-command workflow to install a skill.
|
||||
@@ -460,9 +514,10 @@ async def install_skill(
|
||||
config_name: Config name from API (e.g., 'react', 'django'). Mutually exclusive with config_path. Tool will fetch this config from the official API before scraping.
|
||||
config_path: Path to existing config JSON file (e.g., 'configs/custom.json'). Mutually exclusive with config_name. Use this if you already have a config file.
|
||||
destination: Output directory for skill files (default: 'output')
|
||||
auto_upload: Auto-upload to Claude after packaging (requires ANTHROPIC_API_KEY). Default: true. Set to false to skip upload.
|
||||
auto_upload: Auto-upload after packaging (requires platform API key). Default: true. Set to false to skip upload.
|
||||
unlimited: Remove page limits during scraping (default: false). WARNING: Can take hours for large sites.
|
||||
dry_run: Preview workflow without executing (default: false). Shows all phases that would run.
|
||||
target: Target LLM platform (default: 'claude'). Options: claude, gemini, openai, markdown. Requires corresponding API key: ANTHROPIC_API_KEY, GOOGLE_API_KEY, or OPENAI_API_KEY.
|
||||
|
||||
Returns:
|
||||
Workflow results with all phase statuses.
|
||||
@@ -472,6 +527,7 @@ async def install_skill(
|
||||
"auto_upload": auto_upload,
|
||||
"unlimited": unlimited,
|
||||
"dry_run": dry_run,
|
||||
"target": target,
|
||||
}
|
||||
if config_name:
|
||||
args["config_name"] = config_name
|
||||
@@ -490,7 +546,7 @@ async def install_skill(
|
||||
|
||||
|
||||
@safe_tool_decorator(
|
||||
description="Split large documentation config into multiple focused skills. For 10K+ page documentation."
|
||||
description="Split large configs into multiple focused skills. Supports documentation (10K+ pages) and unified multi-source configs. Auto-detects config type and recommends best strategy."
|
||||
)
|
||||
async def split_config(
|
||||
config_path: str,
|
||||
@@ -499,12 +555,16 @@ async def split_config(
|
||||
dry_run: bool = False,
|
||||
) -> str:
|
||||
"""
|
||||
Split large documentation config into multiple skills.
|
||||
Split large configs into multiple skills.
|
||||
|
||||
Supports:
|
||||
- Documentation configs: Split by categories, size, or create router skills
|
||||
- Unified configs: Split by source type (documentation, github, pdf)
|
||||
|
||||
Args:
|
||||
config_path: Path to config JSON file (e.g., configs/godot.json)
|
||||
strategy: Split strategy: auto, none, category, router, size (default: auto)
|
||||
target_pages: Target pages per skill (default: 5000)
|
||||
config_path: Path to config JSON file (e.g., configs/godot.json or configs/react_unified.json)
|
||||
strategy: Split strategy: auto, none, source, category, router, size (default: auto). 'source' is for unified configs.
|
||||
target_pages: Target pages per skill for doc configs (default: 5000)
|
||||
dry_run: Preview without saving files (default: false)
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -29,6 +29,7 @@ from .scraping_tools import (
|
||||
from .packaging_tools import (
|
||||
package_skill_tool as package_skill_impl,
|
||||
upload_skill_tool as upload_skill_impl,
|
||||
enhance_skill_tool as enhance_skill_impl,
|
||||
install_skill_tool as install_skill_impl,
|
||||
)
|
||||
|
||||
@@ -58,6 +59,7 @@ __all__ = [
|
||||
# Packaging tools
|
||||
"package_skill_impl",
|
||||
"upload_skill_impl",
|
||||
"enhance_skill_impl",
|
||||
"install_skill_impl",
|
||||
# Splitting tools
|
||||
"split_config_impl",
|
||||
|
||||
@@ -102,30 +102,46 @@ def run_subprocess_with_streaming(cmd: List[str], timeout: int = None) -> Tuple[
|
||||
|
||||
async def package_skill_tool(args: dict) -> List[TextContent]:
|
||||
"""
|
||||
Package skill to .zip and optionally auto-upload.
|
||||
Package skill for target LLM platform and optionally auto-upload.
|
||||
|
||||
Args:
|
||||
args: Dictionary with:
|
||||
- skill_dir (str): Path to skill directory (e.g., output/react/)
|
||||
- auto_upload (bool): Try to upload automatically if API key is available (default: True)
|
||||
- target (str): Target platform (default: 'claude')
|
||||
Options: 'claude', 'gemini', 'openai', 'markdown'
|
||||
|
||||
Returns:
|
||||
List of TextContent with packaging results
|
||||
"""
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
|
||||
skill_dir = args["skill_dir"]
|
||||
auto_upload = args.get("auto_upload", True)
|
||||
target = args.get("target", "claude")
|
||||
|
||||
# Check if API key exists - only upload if available
|
||||
has_api_key = os.environ.get('ANTHROPIC_API_KEY', '').strip()
|
||||
# Get platform adaptor
|
||||
try:
|
||||
adaptor = get_adaptor(target)
|
||||
except ValueError as e:
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ Invalid platform: {str(e)}\n\nSupported platforms: claude, gemini, openai, markdown"
|
||||
)]
|
||||
|
||||
# Check if platform-specific API key exists - only upload if available
|
||||
env_var_name = adaptor.get_env_var_name()
|
||||
has_api_key = os.environ.get(env_var_name, '').strip() if env_var_name else False
|
||||
should_upload = auto_upload and has_api_key
|
||||
|
||||
# Run package_skill.py
|
||||
# Run package_skill.py with target parameter
|
||||
cmd = [
|
||||
sys.executable,
|
||||
str(CLI_DIR / "package_skill.py"),
|
||||
skill_dir,
|
||||
"--no-open", # Don't open folder in MCP context
|
||||
"--skip-quality-check" # Skip interactive quality checks in MCP context
|
||||
"--skip-quality-check", # Skip interactive quality checks in MCP context
|
||||
"--target", target # Add target platform
|
||||
]
|
||||
|
||||
# Add upload flag only if we have API key
|
||||
@@ -135,9 +151,9 @@ async def package_skill_tool(args: dict) -> List[TextContent]:
|
||||
# Timeout: 5 minutes for packaging + upload
|
||||
timeout = 300
|
||||
|
||||
progress_msg = "📦 Packaging skill...\n"
|
||||
progress_msg = f"📦 Packaging skill for {adaptor.PLATFORM_NAME}...\n"
|
||||
if should_upload:
|
||||
progress_msg += "📤 Will auto-upload if successful\n"
|
||||
progress_msg += f"📤 Will auto-upload to {adaptor.PLATFORM_NAME} if successful\n"
|
||||
progress_msg += f"⏱️ Maximum time: {timeout // 60} minutes\n\n"
|
||||
|
||||
stdout, stderr, returncode = run_subprocess_with_streaming(cmd, timeout=timeout)
|
||||
@@ -147,24 +163,54 @@ async def package_skill_tool(args: dict) -> List[TextContent]:
|
||||
if returncode == 0:
|
||||
if should_upload:
|
||||
# Upload succeeded
|
||||
output += "\n\n✅ Skill packaged and uploaded automatically!"
|
||||
output += "\n Your skill is now available in Claude!"
|
||||
output += f"\n\n✅ Skill packaged and uploaded to {adaptor.PLATFORM_NAME}!"
|
||||
if target == 'claude':
|
||||
output += "\n Your skill is now available in Claude!"
|
||||
output += "\n Go to https://claude.ai/skills to use it"
|
||||
elif target == 'gemini':
|
||||
output += "\n Your skill is now available in Gemini!"
|
||||
output += "\n Go to https://aistudio.google.com/ to use it"
|
||||
elif target == 'openai':
|
||||
output += "\n Your assistant is now available in OpenAI!"
|
||||
output += "\n Go to https://platform.openai.com/assistants/ to use it"
|
||||
elif auto_upload and not has_api_key:
|
||||
# User wanted upload but no API key
|
||||
output += "\n\n📝 Skill packaged successfully!"
|
||||
output += f"\n\n📝 Skill packaged successfully for {adaptor.PLATFORM_NAME}!"
|
||||
output += "\n"
|
||||
output += "\n💡 To enable automatic upload:"
|
||||
output += "\n 1. Get API key from https://console.anthropic.com/"
|
||||
output += "\n 2. Set: export ANTHROPIC_API_KEY=sk-ant-..."
|
||||
output += "\n"
|
||||
output += "\n📤 Manual upload:"
|
||||
output += "\n 1. Find the .zip file in your output/ folder"
|
||||
output += "\n 2. Go to https://claude.ai/skills"
|
||||
output += "\n 3. Click 'Upload Skill' and select the .zip file"
|
||||
if target == 'claude':
|
||||
output += "\n 1. Get API key from https://console.anthropic.com/"
|
||||
output += "\n 2. Set: export ANTHROPIC_API_KEY=sk-ant-..."
|
||||
output += "\n\n📤 Manual upload:"
|
||||
output += "\n 1. Find the .zip file in your output/ folder"
|
||||
output += "\n 2. Go to https://claude.ai/skills"
|
||||
output += "\n 3. Click 'Upload Skill' and select the .zip file"
|
||||
elif target == 'gemini':
|
||||
output += "\n 1. Get API key from https://aistudio.google.com/"
|
||||
output += "\n 2. Set: export GOOGLE_API_KEY=AIza..."
|
||||
output += "\n\n📤 Manual upload:"
|
||||
output += "\n 1. Go to https://aistudio.google.com/"
|
||||
output += "\n 2. Upload the .tar.gz file from your output/ folder"
|
||||
elif target == 'openai':
|
||||
output += "\n 1. Get API key from https://platform.openai.com/"
|
||||
output += "\n 2. Set: export OPENAI_API_KEY=sk-proj-..."
|
||||
output += "\n\n📤 Manual upload:"
|
||||
output += "\n 1. Use OpenAI Assistants API"
|
||||
output += "\n 2. Upload the .zip file from your output/ folder"
|
||||
elif target == 'markdown':
|
||||
output += "\n (No API key needed - markdown is export only)"
|
||||
output += "\n Package created for manual distribution"
|
||||
else:
|
||||
# auto_upload=False, just packaged
|
||||
output += "\n\n✅ Skill packaged successfully!"
|
||||
output += "\n Upload manually to https://claude.ai/skills"
|
||||
output += f"\n\n✅ Skill packaged successfully for {adaptor.PLATFORM_NAME}!"
|
||||
if target == 'claude':
|
||||
output += "\n Upload manually to https://claude.ai/skills"
|
||||
elif target == 'gemini':
|
||||
output += "\n Upload manually to https://aistudio.google.com/"
|
||||
elif target == 'openai':
|
||||
output += "\n Upload manually via OpenAI Assistants API"
|
||||
elif target == 'markdown':
|
||||
output += "\n Package ready for manual distribution"
|
||||
|
||||
return [TextContent(type="text", text=output)]
|
||||
else:
|
||||
@@ -173,28 +219,57 @@ async def package_skill_tool(args: dict) -> List[TextContent]:
|
||||
|
||||
async def upload_skill_tool(args: dict) -> List[TextContent]:
|
||||
"""
|
||||
Upload skill .zip to Claude.
|
||||
Upload skill package to target LLM platform.
|
||||
|
||||
Args:
|
||||
args: Dictionary with:
|
||||
- skill_zip (str): Path to skill .zip file (e.g., output/react.zip)
|
||||
- skill_zip (str): Path to skill package (.zip or .tar.gz)
|
||||
- target (str): Target platform (default: 'claude')
|
||||
Options: 'claude', 'gemini', 'openai'
|
||||
Note: 'markdown' does not support upload
|
||||
- api_key (str, optional): API key (uses env var if not provided)
|
||||
|
||||
Returns:
|
||||
List of TextContent with upload results
|
||||
"""
|
||||
skill_zip = args["skill_zip"]
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
|
||||
# Run upload_skill.py
|
||||
skill_zip = args["skill_zip"]
|
||||
target = args.get("target", "claude")
|
||||
api_key = args.get("api_key")
|
||||
|
||||
# Get platform adaptor
|
||||
try:
|
||||
adaptor = get_adaptor(target)
|
||||
except ValueError as e:
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ Invalid platform: {str(e)}\n\nSupported platforms: claude, gemini, openai"
|
||||
)]
|
||||
|
||||
# Check if upload is supported
|
||||
if target == 'markdown':
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text="❌ Markdown export does not support upload. Use the packaged file manually."
|
||||
)]
|
||||
|
||||
# Run upload_skill.py with target parameter
|
||||
cmd = [
|
||||
sys.executable,
|
||||
str(CLI_DIR / "upload_skill.py"),
|
||||
skill_zip
|
||||
skill_zip,
|
||||
"--target", target
|
||||
]
|
||||
|
||||
# Add API key if provided
|
||||
if api_key:
|
||||
cmd.extend(["--api-key", api_key])
|
||||
|
||||
# Timeout: 5 minutes for upload
|
||||
timeout = 300
|
||||
|
||||
progress_msg = "📤 Uploading skill to Claude...\n"
|
||||
progress_msg = f"📤 Uploading skill to {adaptor.PLATFORM_NAME}...\n"
|
||||
progress_msg += f"⏱️ Maximum time: {timeout // 60} minutes\n\n"
|
||||
|
||||
stdout, stderr, returncode = run_subprocess_with_streaming(cmd, timeout=timeout)
|
||||
@@ -207,6 +282,142 @@ async def upload_skill_tool(args: dict) -> List[TextContent]:
|
||||
return [TextContent(type="text", text=f"{output}\n\n❌ Error:\n{stderr}")]
|
||||
|
||||
|
||||
async def enhance_skill_tool(args: dict) -> List[TextContent]:
|
||||
"""
|
||||
Enhance SKILL.md with AI using target platform's model.
|
||||
|
||||
Args:
|
||||
args: Dictionary with:
|
||||
- skill_dir (str): Path to skill directory
|
||||
- target (str): Target platform (default: 'claude')
|
||||
Options: 'claude', 'gemini', 'openai'
|
||||
Note: 'markdown' does not support enhancement
|
||||
- mode (str): Enhancement mode (default: 'local')
|
||||
'local': Uses Claude Code Max (no API key)
|
||||
'api': Uses platform API (requires API key)
|
||||
- api_key (str, optional): API key for 'api' mode
|
||||
|
||||
Returns:
|
||||
List of TextContent with enhancement results
|
||||
"""
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
|
||||
skill_dir = Path(args.get("skill_dir"))
|
||||
target = args.get("target", "claude")
|
||||
mode = args.get("mode", "local")
|
||||
api_key = args.get("api_key")
|
||||
|
||||
# Validate skill directory
|
||||
if not skill_dir.exists():
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ Skill directory not found: {skill_dir}"
|
||||
)]
|
||||
|
||||
if not (skill_dir / "SKILL.md").exists():
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ SKILL.md not found in {skill_dir}"
|
||||
)]
|
||||
|
||||
# Get platform adaptor
|
||||
try:
|
||||
adaptor = get_adaptor(target)
|
||||
except ValueError as e:
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ Invalid platform: {str(e)}\n\nSupported platforms: claude, gemini, openai"
|
||||
)]
|
||||
|
||||
# Check if enhancement is supported
|
||||
if not adaptor.supports_enhancement():
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ {adaptor.PLATFORM_NAME} does not support AI enhancement"
|
||||
)]
|
||||
|
||||
output_lines = []
|
||||
output_lines.append(f"🚀 Enhancing skill with {adaptor.PLATFORM_NAME}")
|
||||
output_lines.append("-" * 70)
|
||||
output_lines.append(f"Skill directory: {skill_dir}")
|
||||
output_lines.append(f"Mode: {mode}")
|
||||
output_lines.append("")
|
||||
|
||||
if mode == 'local':
|
||||
# Use local enhancement (Claude Code)
|
||||
output_lines.append("Using Claude Code Max (local, no API key required)")
|
||||
output_lines.append("Running enhancement in headless mode...")
|
||||
output_lines.append("")
|
||||
|
||||
cmd = [
|
||||
sys.executable,
|
||||
str(CLI_DIR / "enhance_skill_local.py"),
|
||||
str(skill_dir)
|
||||
]
|
||||
|
||||
try:
|
||||
stdout, stderr, returncode = run_subprocess_with_streaming(cmd, timeout=900)
|
||||
|
||||
if returncode == 0:
|
||||
output_lines.append(stdout)
|
||||
output_lines.append("")
|
||||
output_lines.append("✅ Enhancement complete!")
|
||||
output_lines.append(f"Enhanced SKILL.md: {skill_dir / 'SKILL.md'}")
|
||||
output_lines.append(f"Backup: {skill_dir / 'SKILL.md.backup'}")
|
||||
else:
|
||||
output_lines.append(f"❌ Enhancement failed (exit code {returncode})")
|
||||
output_lines.append(stderr if stderr else stdout)
|
||||
|
||||
except Exception as e:
|
||||
output_lines.append(f"❌ Error: {str(e)}")
|
||||
|
||||
elif mode == 'api':
|
||||
# Use API enhancement
|
||||
output_lines.append(f"Using {adaptor.PLATFORM_NAME} API")
|
||||
|
||||
# Get API key
|
||||
if not api_key:
|
||||
env_var = adaptor.get_env_var_name()
|
||||
api_key = os.environ.get(env_var)
|
||||
|
||||
if not api_key:
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ {env_var} not set. Set API key or pass via api_key parameter."
|
||||
)]
|
||||
|
||||
# Validate API key
|
||||
if not adaptor.validate_api_key(api_key):
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ Invalid API key format for {adaptor.PLATFORM_NAME}"
|
||||
)]
|
||||
|
||||
output_lines.append("Calling API for enhancement...")
|
||||
output_lines.append("")
|
||||
|
||||
try:
|
||||
success = adaptor.enhance(skill_dir, api_key)
|
||||
|
||||
if success:
|
||||
output_lines.append("✅ Enhancement complete!")
|
||||
output_lines.append(f"Enhanced SKILL.md: {skill_dir / 'SKILL.md'}")
|
||||
output_lines.append(f"Backup: {skill_dir / 'SKILL.md.backup'}")
|
||||
else:
|
||||
output_lines.append("❌ Enhancement failed")
|
||||
|
||||
except Exception as e:
|
||||
output_lines.append(f"❌ Error: {str(e)}")
|
||||
|
||||
else:
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ Invalid mode: {mode}. Use 'local' or 'api'"
|
||||
)]
|
||||
|
||||
return [TextContent(type="text", text="\n".join(output_lines))]
|
||||
|
||||
|
||||
async def install_skill_tool(args: dict) -> List[TextContent]:
|
||||
"""
|
||||
Complete skill installation workflow.
|
||||
@@ -215,8 +426,8 @@ async def install_skill_tool(args: dict) -> List[TextContent]:
|
||||
1. Fetch config (if config_name provided)
|
||||
2. Scrape documentation
|
||||
3. AI Enhancement (MANDATORY - no skip option)
|
||||
4. Package to .zip
|
||||
5. Upload to Claude (optional)
|
||||
4. Package for target platform (ZIP or tar.gz)
|
||||
5. Upload to target platform (optional)
|
||||
|
||||
Args:
|
||||
args: Dictionary with:
|
||||
@@ -226,13 +437,15 @@ async def install_skill_tool(args: dict) -> List[TextContent]:
|
||||
- auto_upload (bool): Upload after packaging (default: True)
|
||||
- unlimited (bool): Remove page limits (default: False)
|
||||
- dry_run (bool): Preview only (default: False)
|
||||
- target (str): Target LLM platform (default: "claude")
|
||||
|
||||
Returns:
|
||||
List of TextContent with workflow progress and results
|
||||
"""
|
||||
# Import these here to avoid circular imports
|
||||
from .scraping_tools import scrape_docs_tool
|
||||
from .config_tools import fetch_config_tool
|
||||
from .source_tools import fetch_config_tool
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
|
||||
# Extract and validate inputs
|
||||
config_name = args.get("config_name")
|
||||
@@ -241,6 +454,16 @@ async def install_skill_tool(args: dict) -> List[TextContent]:
|
||||
auto_upload = args.get("auto_upload", True)
|
||||
unlimited = args.get("unlimited", False)
|
||||
dry_run = args.get("dry_run", False)
|
||||
target = args.get("target", "claude")
|
||||
|
||||
# Get platform adaptor
|
||||
try:
|
||||
adaptor = get_adaptor(target)
|
||||
except ValueError as e:
|
||||
return [TextContent(
|
||||
type="text",
|
||||
text=f"❌ Error: {str(e)}\n\nSupported platforms: claude, gemini, openai, markdown"
|
||||
)]
|
||||
|
||||
# Validation: Must provide exactly one of config_name or config_path
|
||||
if not config_name and not config_path:
|
||||
@@ -397,73 +620,118 @@ async def install_skill_tool(args: dict) -> List[TextContent]:
|
||||
|
||||
# ===== PHASE 4: Package Skill =====
|
||||
phase_num = "4/5" if config_name else "3/4"
|
||||
output_lines.append(f"📦 PHASE {phase_num}: Package Skill")
|
||||
output_lines.append(f"📦 PHASE {phase_num}: Package Skill for {adaptor.PLATFORM_NAME}")
|
||||
output_lines.append("-" * 70)
|
||||
output_lines.append(f"Skill directory: {workflow_state['skill_dir']}")
|
||||
output_lines.append(f"Target platform: {adaptor.PLATFORM_NAME}")
|
||||
output_lines.append("")
|
||||
|
||||
if not dry_run:
|
||||
# Call package_skill_tool (auto_upload=False, we handle upload separately)
|
||||
# Call package_skill_tool with target
|
||||
package_result = await package_skill_tool({
|
||||
"skill_dir": workflow_state['skill_dir'],
|
||||
"auto_upload": False # We handle upload in next phase
|
||||
"auto_upload": False, # We handle upload in next phase
|
||||
"target": target
|
||||
})
|
||||
|
||||
package_output = package_result[0].text
|
||||
output_lines.append(package_output)
|
||||
output_lines.append("")
|
||||
|
||||
# Extract zip path from output
|
||||
# Expected format: "Saved to: output/react.zip"
|
||||
match = re.search(r"Saved to:\s*(.+\.zip)", package_output)
|
||||
# Extract package path from output (supports .zip and .tar.gz)
|
||||
# Expected format: "Saved to: output/react.zip" or "Saved to: output/react-gemini.tar.gz"
|
||||
match = re.search(r"Saved to:\s*(.+\.(?:zip|tar\.gz))", package_output)
|
||||
if match:
|
||||
workflow_state['zip_path'] = match.group(1).strip()
|
||||
else:
|
||||
# Fallback: construct zip path
|
||||
workflow_state['zip_path'] = f"{destination}/{workflow_state['skill_name']}.zip"
|
||||
# Fallback: construct package path based on platform
|
||||
if target == 'gemini':
|
||||
workflow_state['zip_path'] = f"{destination}/{workflow_state['skill_name']}-gemini.tar.gz"
|
||||
elif target == 'openai':
|
||||
workflow_state['zip_path'] = f"{destination}/{workflow_state['skill_name']}-openai.zip"
|
||||
else:
|
||||
workflow_state['zip_path'] = f"{destination}/{workflow_state['skill_name']}.zip"
|
||||
|
||||
workflow_state['phases_completed'].append('package_skill')
|
||||
else:
|
||||
output_lines.append(" [DRY RUN] Would package to .zip file")
|
||||
workflow_state['zip_path'] = f"{destination}/{workflow_state['skill_name']}.zip"
|
||||
# Dry run - show expected package format
|
||||
if target == 'gemini':
|
||||
pkg_ext = "tar.gz"
|
||||
pkg_file = f"{destination}/{workflow_state['skill_name']}-gemini.tar.gz"
|
||||
elif target == 'openai':
|
||||
pkg_ext = "zip"
|
||||
pkg_file = f"{destination}/{workflow_state['skill_name']}-openai.zip"
|
||||
else:
|
||||
pkg_ext = "zip"
|
||||
pkg_file = f"{destination}/{workflow_state['skill_name']}.zip"
|
||||
|
||||
output_lines.append(f" [DRY RUN] Would package to {pkg_ext} file for {adaptor.PLATFORM_NAME}")
|
||||
workflow_state['zip_path'] = pkg_file
|
||||
|
||||
output_lines.append("")
|
||||
|
||||
# ===== PHASE 5: Upload (Optional) =====
|
||||
if auto_upload:
|
||||
phase_num = "5/5" if config_name else "4/4"
|
||||
output_lines.append(f"📤 PHASE {phase_num}: Upload to Claude")
|
||||
output_lines.append(f"📤 PHASE {phase_num}: Upload to {adaptor.PLATFORM_NAME}")
|
||||
output_lines.append("-" * 70)
|
||||
output_lines.append(f"Zip file: {workflow_state['zip_path']}")
|
||||
output_lines.append(f"Package file: {workflow_state['zip_path']}")
|
||||
output_lines.append("")
|
||||
|
||||
# Check for API key
|
||||
has_api_key = os.environ.get('ANTHROPIC_API_KEY', '').strip()
|
||||
# Check for platform-specific API key
|
||||
env_var_name = adaptor.get_env_var_name()
|
||||
has_api_key = os.environ.get(env_var_name, '').strip()
|
||||
|
||||
if not dry_run:
|
||||
if has_api_key:
|
||||
# Call upload_skill_tool
|
||||
upload_result = await upload_skill_tool({
|
||||
"skill_zip": workflow_state['zip_path']
|
||||
})
|
||||
# Upload not supported for markdown platform
|
||||
if target == 'markdown':
|
||||
output_lines.append("⚠️ Markdown export does not support upload")
|
||||
output_lines.append(" Package has been created - use manually")
|
||||
else:
|
||||
# Call upload_skill_tool with target
|
||||
upload_result = await upload_skill_tool({
|
||||
"skill_zip": workflow_state['zip_path'],
|
||||
"target": target
|
||||
})
|
||||
|
||||
upload_output = upload_result[0].text
|
||||
output_lines.append(upload_output)
|
||||
upload_output = upload_result[0].text
|
||||
output_lines.append(upload_output)
|
||||
|
||||
workflow_state['phases_completed'].append('upload_skill')
|
||||
workflow_state['phases_completed'].append('upload_skill')
|
||||
else:
|
||||
output_lines.append("⚠️ ANTHROPIC_API_KEY not set - skipping upload")
|
||||
# Platform-specific instructions for missing API key
|
||||
output_lines.append(f"⚠️ {env_var_name} not set - skipping upload")
|
||||
output_lines.append("")
|
||||
output_lines.append("To enable automatic upload:")
|
||||
output_lines.append(" 1. Get API key from https://console.anthropic.com/")
|
||||
output_lines.append(" 2. Set: export ANTHROPIC_API_KEY=sk-ant-...")
|
||||
output_lines.append("")
|
||||
output_lines.append("📤 Manual upload:")
|
||||
output_lines.append(" 1. Go to https://claude.ai/skills")
|
||||
output_lines.append(" 2. Click 'Upload Skill'")
|
||||
output_lines.append(f" 3. Select: {workflow_state['zip_path']}")
|
||||
|
||||
if target == 'claude':
|
||||
output_lines.append(" 1. Get API key from https://console.anthropic.com/")
|
||||
output_lines.append(" 2. Set: export ANTHROPIC_API_KEY=sk-ant-...")
|
||||
output_lines.append("")
|
||||
output_lines.append("📤 Manual upload:")
|
||||
output_lines.append(" 1. Go to https://claude.ai/skills")
|
||||
output_lines.append(" 2. Click 'Upload Skill'")
|
||||
output_lines.append(f" 3. Select: {workflow_state['zip_path']}")
|
||||
elif target == 'gemini':
|
||||
output_lines.append(" 1. Get API key from https://aistudio.google.com/")
|
||||
output_lines.append(" 2. Set: export GOOGLE_API_KEY=AIza...")
|
||||
output_lines.append("")
|
||||
output_lines.append("📤 Manual upload:")
|
||||
output_lines.append(" 1. Go to https://aistudio.google.com/")
|
||||
output_lines.append(f" 2. Upload package: {workflow_state['zip_path']}")
|
||||
elif target == 'openai':
|
||||
output_lines.append(" 1. Get API key from https://platform.openai.com/")
|
||||
output_lines.append(" 2. Set: export OPENAI_API_KEY=sk-proj-...")
|
||||
output_lines.append("")
|
||||
output_lines.append("📤 Manual upload:")
|
||||
output_lines.append(" 1. Use OpenAI Assistants API")
|
||||
output_lines.append(f" 2. Upload package: {workflow_state['zip_path']}")
|
||||
elif target == 'markdown':
|
||||
output_lines.append(" (No API key needed - markdown is export only)")
|
||||
output_lines.append(f" Package created: {workflow_state['zip_path']}")
|
||||
else:
|
||||
output_lines.append(" [DRY RUN] Would upload to Claude (if API key set)")
|
||||
output_lines.append(f" [DRY RUN] Would upload to {adaptor.PLATFORM_NAME} (if API key set)")
|
||||
|
||||
output_lines.append("")
|
||||
|
||||
@@ -485,14 +753,22 @@ async def install_skill_tool(args: dict) -> List[TextContent]:
|
||||
output_lines.append(f" Skill package: {workflow_state['zip_path']}")
|
||||
output_lines.append("")
|
||||
|
||||
if auto_upload and has_api_key:
|
||||
output_lines.append("🎉 Your skill is now available in Claude!")
|
||||
output_lines.append(" Go to https://claude.ai/skills to use it")
|
||||
if auto_upload and has_api_key and target != 'markdown':
|
||||
# Platform-specific success message
|
||||
if target == 'claude':
|
||||
output_lines.append("🎉 Your skill is now available in Claude!")
|
||||
output_lines.append(" Go to https://claude.ai/skills to use it")
|
||||
elif target == 'gemini':
|
||||
output_lines.append("🎉 Your skill is now available in Gemini!")
|
||||
output_lines.append(" Go to https://aistudio.google.com/ to use it")
|
||||
elif target == 'openai':
|
||||
output_lines.append("🎉 Your assistant is now available in OpenAI!")
|
||||
output_lines.append(" Go to https://platform.openai.com/assistants/ to use it")
|
||||
elif auto_upload:
|
||||
output_lines.append("📝 Manual upload required (see instructions above)")
|
||||
else:
|
||||
output_lines.append("📤 To upload:")
|
||||
output_lines.append(" skill-seekers upload " + workflow_state['zip_path'])
|
||||
output_lines.append(f" skill-seekers upload {workflow_state['zip_path']} --target {target}")
|
||||
else:
|
||||
output_lines.append("This was a dry run. No actions were taken.")
|
||||
output_lines.append("")
|
||||
|
||||
@@ -94,17 +94,22 @@ def run_subprocess_with_streaming(cmd, timeout=None):
|
||||
|
||||
async def split_config(args: dict) -> List[TextContent]:
|
||||
"""
|
||||
Split large documentation config into multiple focused skills.
|
||||
Split large configs into multiple focused skills.
|
||||
|
||||
Supports both documentation and unified (multi-source) configs:
|
||||
- Documentation configs: Split by categories, size, or create router skills
|
||||
- Unified configs: Split by source type (documentation, github, pdf)
|
||||
|
||||
For large documentation sites (10K+ pages), this tool splits the config into
|
||||
multiple smaller configs based on categories, size, or custom strategy. This
|
||||
improves performance and makes individual skills more focused.
|
||||
multiple smaller configs. For unified configs with multiple sources, splits
|
||||
into separate configs per source type.
|
||||
|
||||
Args:
|
||||
args: Dictionary containing:
|
||||
- config_path (str): Path to config JSON file (e.g., configs/godot.json)
|
||||
- strategy (str, optional): Split strategy: auto, none, category, router, size (default: auto)
|
||||
- target_pages (int, optional): Target pages per skill (default: 5000)
|
||||
- config_path (str): Path to config JSON file (e.g., configs/godot.json or configs/react_unified.json)
|
||||
- strategy (str, optional): Split strategy: auto, none, source, category, router, size (default: auto)
|
||||
'source' strategy is for unified configs only
|
||||
- target_pages (int, optional): Target pages per skill for doc configs (default: 5000)
|
||||
- dry_run (bool, optional): Preview without saving files (default: False)
|
||||
|
||||
Returns:
|
||||
|
||||
Reference in New Issue
Block a user