This commit includes two major improvements:
## 1. Unified Create Command (v3.0.0 feature)
- Auto-detects source type (web, GitHub, local, PDF, config)
- Three-tier argument organization (universal, source-specific, advanced)
- Routes to existing scrapers (100% backward compatible)
- Progressive disclosure: 15 universal flags in default help
**New files:**
- src/skill_seekers/cli/source_detector.py - Auto-detection logic
- src/skill_seekers/cli/arguments/create.py - Argument definitions
- src/skill_seekers/cli/create_command.py - Main orchestrator
- src/skill_seekers/cli/parsers/create_parser.py - Parser integration
**Tests:**
- tests/test_source_detector.py (35 tests)
- tests/test_create_arguments.py (30 tests)
- tests/test_create_integration_basic.py (10 tests)
## 2. Enhanced Flag Consolidation (Phase 1)
- Consolidated 3 flags (--enhance, --enhance-local, --enhance-level) → 1 flag
- --enhance-level 0-3 with auto-detection of API vs LOCAL mode
- Default: --enhance-level 2 (balanced enhancement)
**Modified files:**
- arguments/{common,create,scrape,github,analyze}.py - Added enhance_level
- {doc_scraper,github_scraper,config_extractor,main}.py - Updated logic
- create_command.py - Uses consolidated flag
**Auto-detection:**
- If ANTHROPIC_API_KEY set → API mode
- Else → LOCAL mode (Claude Code)
## 3. PresetManager Bug Fix
- Fixed module naming conflict (presets.py vs presets/ directory)
- Moved presets.py → presets/manager.py
- Updated __init__.py exports
**Test Results:**
- All 160+ tests passing
- Zero regressions
- 100% backward compatible
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
326 lines
10 KiB
Python
326 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Skill Seekers - Unified CLI Entry Point
|
|
|
|
Provides a git-style unified command-line interface for all Skill Seekers tools.
|
|
|
|
Usage:
|
|
skill-seekers <command> [options]
|
|
|
|
Commands:
|
|
config Configure GitHub tokens, API keys, and settings
|
|
scrape Scrape documentation website
|
|
github Scrape GitHub repository
|
|
pdf Extract from PDF file
|
|
unified Multi-source scraping (docs + GitHub + PDF)
|
|
analyze Analyze local codebase and extract code knowledge
|
|
enhance AI-powered enhancement (local, no API key)
|
|
enhance-status Check enhancement status (for background/daemon modes)
|
|
package Package skill into .zip file
|
|
upload Upload skill to Claude
|
|
estimate Estimate page count before scraping
|
|
extract-test-examples Extract usage examples from test files
|
|
install-agent Install skill to AI agent directories
|
|
resume Resume interrupted scraping job
|
|
|
|
Examples:
|
|
skill-seekers scrape --config configs/react.json
|
|
skill-seekers github --repo microsoft/TypeScript
|
|
skill-seekers unified --config configs/react_unified.json
|
|
skill-seekers extract-test-examples tests/ --language python
|
|
skill-seekers package output/react/
|
|
skill-seekers install-agent output/react/ --agent cursor
|
|
"""
|
|
|
|
import argparse
|
|
import importlib
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
from skill_seekers.cli import __version__
|
|
|
|
|
|
# Command module mapping (command name -> module path)
|
|
COMMAND_MODULES = {
|
|
"create": "skill_seekers.cli.create_command", # NEW: Unified create command
|
|
"config": "skill_seekers.cli.config_command",
|
|
"scrape": "skill_seekers.cli.doc_scraper",
|
|
"github": "skill_seekers.cli.github_scraper",
|
|
"pdf": "skill_seekers.cli.pdf_scraper",
|
|
"unified": "skill_seekers.cli.unified_scraper",
|
|
"enhance": "skill_seekers.cli.enhance_skill_local",
|
|
"enhance-status": "skill_seekers.cli.enhance_status",
|
|
"package": "skill_seekers.cli.package_skill",
|
|
"upload": "skill_seekers.cli.upload_skill",
|
|
"estimate": "skill_seekers.cli.estimate_pages",
|
|
"extract-test-examples": "skill_seekers.cli.test_example_extractor",
|
|
"install-agent": "skill_seekers.cli.install_agent",
|
|
"analyze": "skill_seekers.cli.codebase_scraper",
|
|
"install": "skill_seekers.cli.install_skill",
|
|
"resume": "skill_seekers.cli.resume_command",
|
|
"stream": "skill_seekers.cli.streaming_ingest",
|
|
"update": "skill_seekers.cli.incremental_updater",
|
|
"multilang": "skill_seekers.cli.multilang_support",
|
|
"quality": "skill_seekers.cli.quality_metrics",
|
|
}
|
|
|
|
|
|
def create_parser() -> argparse.ArgumentParser:
|
|
"""Create the main argument parser with subcommands."""
|
|
from skill_seekers.cli.parsers import register_parsers
|
|
|
|
parser = argparse.ArgumentParser(
|
|
prog="skill-seekers",
|
|
description="Convert documentation, GitHub repos, and PDFs into Claude AI skills",
|
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
epilog="""
|
|
Examples:
|
|
# Scrape documentation
|
|
skill-seekers scrape --config configs/react.json
|
|
|
|
# Scrape GitHub repository
|
|
skill-seekers github --repo microsoft/TypeScript --name typescript
|
|
|
|
# Multi-source scraping (unified)
|
|
skill-seekers unified --config configs/react_unified.json
|
|
|
|
# AI-powered enhancement
|
|
skill-seekers enhance output/react/
|
|
|
|
# Package and upload
|
|
skill-seekers package output/react/
|
|
skill-seekers upload output/react.zip
|
|
|
|
For more information: https://github.com/yusufkaraaslan/Skill_Seekers
|
|
""",
|
|
)
|
|
|
|
parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
|
|
|
|
# Create subparsers
|
|
subparsers = parser.add_subparsers(
|
|
dest="command",
|
|
title="commands",
|
|
description="Available Skill Seekers commands",
|
|
help="Command to run",
|
|
)
|
|
|
|
# Register all subcommand parsers
|
|
register_parsers(subparsers)
|
|
|
|
return parser
|
|
|
|
|
|
def _reconstruct_argv(command: str, args: argparse.Namespace) -> list[str]:
|
|
"""Reconstruct sys.argv from args namespace for command module.
|
|
|
|
Args:
|
|
command: Command name
|
|
args: Parsed arguments namespace
|
|
|
|
Returns:
|
|
List of command-line arguments for the command module
|
|
"""
|
|
argv = [f"{command}_command.py"]
|
|
|
|
# Convert args to sys.argv format
|
|
for key, value in vars(args).items():
|
|
if key == "command":
|
|
continue
|
|
|
|
# Handle positional arguments (no -- prefix)
|
|
if key in [
|
|
"url",
|
|
"directory",
|
|
"file",
|
|
"job_id",
|
|
"skill_directory",
|
|
"zip_file",
|
|
"config",
|
|
"input_file",
|
|
]:
|
|
if value is not None and value != "":
|
|
argv.append(str(value))
|
|
continue
|
|
|
|
# Handle flags and options
|
|
arg_name = f"--{key.replace('_', '-')}"
|
|
|
|
if isinstance(value, bool):
|
|
if value:
|
|
argv.append(arg_name)
|
|
elif isinstance(value, list):
|
|
for item in value:
|
|
argv.extend([arg_name, str(item)])
|
|
elif value is not None:
|
|
argv.extend([arg_name, str(value)])
|
|
|
|
return argv
|
|
|
|
|
|
def main(argv: list[str] | None = None) -> int:
|
|
"""Main entry point for the unified CLI.
|
|
|
|
Args:
|
|
argv: Command-line arguments (defaults to sys.argv)
|
|
|
|
Returns:
|
|
Exit code (0 for success, non-zero for error)
|
|
"""
|
|
parser = create_parser()
|
|
args = parser.parse_args(argv)
|
|
|
|
if not args.command:
|
|
parser.print_help()
|
|
return 1
|
|
|
|
# Get command module
|
|
module_name = COMMAND_MODULES.get(args.command)
|
|
if not module_name:
|
|
print(f"Error: Unknown command '{args.command}'", file=sys.stderr)
|
|
parser.print_help()
|
|
return 1
|
|
|
|
# Special handling for 'analyze' command (has post-processing)
|
|
if args.command == "analyze":
|
|
return _handle_analyze_command(args)
|
|
|
|
# Standard delegation for all other commands
|
|
try:
|
|
# Import and execute command module
|
|
module = importlib.import_module(module_name)
|
|
|
|
# Reconstruct sys.argv for command module
|
|
original_argv = sys.argv.copy()
|
|
sys.argv = _reconstruct_argv(args.command, args)
|
|
|
|
# Execute command
|
|
try:
|
|
result = module.main()
|
|
return result if result is not None else 0
|
|
finally:
|
|
sys.argv = original_argv
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\nInterrupted by user", file=sys.stderr)
|
|
return 130
|
|
except Exception as e:
|
|
error_msg = str(e) if str(e) else f"{type(e).__name__} occurred"
|
|
print(f"Error: {error_msg}", file=sys.stderr)
|
|
|
|
# Show traceback in verbose mode
|
|
import traceback
|
|
|
|
if hasattr(args, "verbose") and getattr(args, "verbose", False):
|
|
traceback.print_exc()
|
|
|
|
return 1
|
|
|
|
|
|
def _handle_analyze_command(args: argparse.Namespace) -> int:
|
|
"""Handle analyze command with special post-processing logic.
|
|
|
|
Args:
|
|
args: Parsed arguments
|
|
|
|
Returns:
|
|
Exit code
|
|
"""
|
|
from skill_seekers.cli.codebase_scraper import main as analyze_main
|
|
|
|
# Reconstruct sys.argv for analyze command
|
|
original_argv = sys.argv.copy()
|
|
sys.argv = ["codebase_scraper.py", "--directory", args.directory]
|
|
|
|
if args.output:
|
|
sys.argv.extend(["--output", args.output])
|
|
|
|
# Handle preset flags (depth and features)
|
|
if args.quick:
|
|
sys.argv.extend(
|
|
[
|
|
"--depth",
|
|
"surface",
|
|
"--skip-patterns",
|
|
"--skip-test-examples",
|
|
"--skip-how-to-guides",
|
|
"--skip-config-patterns",
|
|
]
|
|
)
|
|
elif args.comprehensive:
|
|
sys.argv.extend(["--depth", "full"])
|
|
elif args.depth:
|
|
sys.argv.extend(["--depth", args.depth])
|
|
|
|
# Determine enhance_level (simplified - use default or override)
|
|
enhance_level = getattr(args, 'enhance_level', 2) # Default is 2
|
|
if getattr(args, 'quick', False):
|
|
enhance_level = 0 # Quick mode disables enhancement
|
|
|
|
sys.argv.extend(["--enhance-level", str(enhance_level)])
|
|
|
|
# Pass through remaining arguments
|
|
if args.languages:
|
|
sys.argv.extend(["--languages", args.languages])
|
|
if args.file_patterns:
|
|
sys.argv.extend(["--file-patterns", args.file_patterns])
|
|
if args.skip_api_reference:
|
|
sys.argv.append("--skip-api-reference")
|
|
if args.skip_dependency_graph:
|
|
sys.argv.append("--skip-dependency-graph")
|
|
if args.skip_patterns:
|
|
sys.argv.append("--skip-patterns")
|
|
if args.skip_test_examples:
|
|
sys.argv.append("--skip-test-examples")
|
|
if args.skip_how_to_guides:
|
|
sys.argv.append("--skip-how-to-guides")
|
|
if args.skip_config_patterns:
|
|
sys.argv.append("--skip-config-patterns")
|
|
if args.skip_docs:
|
|
sys.argv.append("--skip-docs")
|
|
if args.no_comments:
|
|
sys.argv.append("--no-comments")
|
|
if args.verbose:
|
|
sys.argv.append("--verbose")
|
|
|
|
try:
|
|
result = analyze_main() or 0
|
|
|
|
# Enhance SKILL.md if enhance_level >= 1
|
|
if result == 0 and enhance_level >= 1:
|
|
skill_dir = Path(args.output)
|
|
skill_md = skill_dir / "SKILL.md"
|
|
|
|
if skill_md.exists():
|
|
print("\n" + "=" * 60)
|
|
print(f"ENHANCING SKILL.MD WITH AI (Level {enhance_level})")
|
|
print("=" * 60 + "\n")
|
|
|
|
try:
|
|
from skill_seekers.cli.enhance_skill_local import LocalSkillEnhancer
|
|
|
|
enhancer = LocalSkillEnhancer(str(skill_dir), force=True)
|
|
success = enhancer.run(headless=True, timeout=600)
|
|
|
|
if success:
|
|
print("\n✅ SKILL.md enhancement complete!")
|
|
with open(skill_md) as f:
|
|
lines = len(f.readlines())
|
|
print(f" Enhanced SKILL.md: {lines} lines")
|
|
else:
|
|
print("\n⚠️ SKILL.md enhancement did not complete")
|
|
print(" You can retry with: skill-seekers enhance " + str(skill_dir))
|
|
except Exception as e:
|
|
print(f"\n⚠️ SKILL.md enhancement failed: {e}")
|
|
print(" You can retry with: skill-seekers enhance " + str(skill_dir))
|
|
else:
|
|
print(f"\n⚠️ SKILL.md not found at {skill_md}, skipping enhancement")
|
|
|
|
return result
|
|
finally:
|
|
sys.argv = original_argv
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|