Merge branch 'development' into ruff-and-mypy
This commit is contained in:
104
CHANGELOG.md
104
CHANGELOG.md
@@ -126,6 +126,51 @@ This **minor feature release** introduces intelligent GitHub rate limit handling
|
||||
- **All tests passing** ✅ (16/16)
|
||||
- **Test utilities**: Mock responses, config isolation, tmp directories
|
||||
|
||||
- **🎯 Bootstrap Skill Feature** - Self-hosting capability (PR #249)
|
||||
- **Self-Bootstrap**: Generate skill-seekers as a Claude Code skill
|
||||
- `./scripts/bootstrap_skill.sh` - One-command bootstrap
|
||||
- Combines manual header with auto-generated codebase analysis
|
||||
- Output: `output/skill-seekers/` ready for Claude Code
|
||||
- Install: `cp -r output/skill-seekers ~/.claude/skills/`
|
||||
- **Robust Frontmatter Detection**:
|
||||
- Dynamic YAML frontmatter boundary detection (not hardcoded line counts)
|
||||
- Fallback to line 6 if frontmatter not found
|
||||
- Future-proof against frontmatter field additions
|
||||
- **SKILL.md Validation**:
|
||||
- File existence and non-empty checks
|
||||
- Frontmatter delimiter presence
|
||||
- Required fields validation (name, description)
|
||||
- Exit with clear error messages on validation failures
|
||||
- **Comprehensive Error Handling**:
|
||||
- UV dependency check with install instructions
|
||||
- Permission checks for output directory
|
||||
- Graceful degradation on missing header file
|
||||
|
||||
- **🔧 MCP Now Optional** - User choice for installation profile
|
||||
- **CLI Only**: `pip install skill-seekers` - No MCP dependencies
|
||||
- **MCP Integration**: `pip install skill-seekers[mcp]` - Full MCP support
|
||||
- **All Features**: `pip install skill-seekers[all]` - Everything enabled
|
||||
- **Lazy Loading**: Graceful failure with helpful error messages when MCP not installed
|
||||
- **Interactive Setup Wizard**:
|
||||
- Shows all installation options on first run
|
||||
- Stored at `~/.config/skill-seekers/.setup_shown`
|
||||
- Accessible via `skill-seekers-setup` command
|
||||
- **Entry Point**: `skill-seekers-setup` for manual access
|
||||
|
||||
- **🧪 E2E Testing for Bootstrap** - Comprehensive end-to-end tests
|
||||
- **6 core tests** verifying bootstrap workflow:
|
||||
- Output structure creation
|
||||
- Header prepending
|
||||
- YAML frontmatter validation
|
||||
- Line count sanity checks
|
||||
- Virtual environment installability
|
||||
- Platform adaptor compatibility
|
||||
- **Pytest markers**: @pytest.mark.e2e, @pytest.mark.venv, @pytest.mark.slow
|
||||
- **Execution modes**:
|
||||
- Fast tests: `pytest -k "not venv"` (~2-3 min)
|
||||
- Full suite: `pytest -m "e2e"` (~5-10 min)
|
||||
- **Test utilities**: Fixtures for project root, bootstrap runner, output directory
|
||||
|
||||
### Changed
|
||||
|
||||
- **GitHub Fetcher** - Integrated rate limit handler
|
||||
@@ -149,11 +194,20 @@ This **minor feature release** introduces intelligent GitHub rate limit handling
|
||||
- Updated command documentation strings
|
||||
- Version bumped to 2.7.0
|
||||
|
||||
- **pyproject.toml** - New entry points
|
||||
- **pyproject.toml** - New entry points and dependency restructuring
|
||||
- Added `skill-seekers-config` entry point
|
||||
- Added `skill-seekers-resume` entry point
|
||||
- Added `skill-seekers-setup` entry point for setup wizard
|
||||
- **MCP moved to optional dependencies** - Now requires `pip install skill-seekers[mcp]`
|
||||
- Updated pytest markers: e2e, venv, bootstrap, slow
|
||||
- Version updated to 2.7.0
|
||||
|
||||
- **install_skill.py** - Lazy MCP loading
|
||||
- Try/except ImportError for MCP imports
|
||||
- Graceful failure with helpful error message when MCP not installed
|
||||
- Suggests alternatives: scrape + package workflow
|
||||
- Maintains backward compatibility for existing MCP users
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Rate limit indefinite wait** - No more infinite waiting
|
||||
@@ -174,19 +228,53 @@ This **minor feature release** introduces intelligent GitHub rate limit handling
|
||||
- Clear error messages for automation logs
|
||||
- Exit codes for pipeline integration
|
||||
|
||||
- **AttributeError in codebase_scraper.py** - Fixed incorrect flag check (PR #249)
|
||||
- Changed `if args.build_api_reference:` to `if not args.skip_api_reference:`
|
||||
- Aligns with v2.5.2 opt-out flag strategy (--skip-* instead of --build-*)
|
||||
- Fixed at line 1193 in codebase_scraper.py
|
||||
|
||||
### Technical Details
|
||||
|
||||
- **Architecture**: Strategy pattern for rate limit handling, singleton for config manager
|
||||
- **Files Modified**: 3 (github_fetcher.py, github_scraper.py, main.py)
|
||||
- **New Files**: 4 (config_manager.py ~490 lines, config_command.py ~400 lines, rate_limit_handler.py ~450 lines, resume_command.py ~150 lines)
|
||||
- **Tests**: 16 tests added, all passing
|
||||
- **Dependencies**: No new dependencies required
|
||||
- **Backward Compatibility**: Fully backward compatible, new features are opt-in
|
||||
- **Files Modified**: 6 (github_fetcher.py, github_scraper.py, main.py, pyproject.toml, install_skill.py, codebase_scraper.py)
|
||||
- **New Files**: 6 (config_manager.py ~490 lines, config_command.py ~400 lines, rate_limit_handler.py ~450 lines, resume_command.py ~150 lines, setup_wizard.py ~95 lines, test_bootstrap_skill_e2e.py ~169 lines)
|
||||
- **Bootstrap Scripts**: 2 (bootstrap_skill.sh enhanced, skill_header.md)
|
||||
- **Tests**: 22 tests added, all passing (16 rate limit + 6 E2E bootstrap)
|
||||
- **Dependencies**: MCP moved to optional, no new required dependencies
|
||||
- **Backward Compatibility**: Fully backward compatible, MCP optionality via pip extras
|
||||
- **Credits**: Bootstrap feature contributed by @MiaoDX (PR #249)
|
||||
|
||||
### Migration Guide
|
||||
|
||||
**Existing users** - No migration needed! Everything works as before.
|
||||
|
||||
**MCP users** - If you use MCP integration features:
|
||||
```bash
|
||||
# Reinstall with MCP support
|
||||
pip install -U skill-seekers[mcp]
|
||||
|
||||
# Or install everything
|
||||
pip install -U skill-seekers[all]
|
||||
```
|
||||
|
||||
**New installation profiles**:
|
||||
```bash
|
||||
# CLI only (no MCP)
|
||||
pip install skill-seekers
|
||||
|
||||
# With MCP integration
|
||||
pip install skill-seekers[mcp]
|
||||
|
||||
# With multi-LLM support (Gemini, OpenAI)
|
||||
pip install skill-seekers[all-llms]
|
||||
|
||||
# Everything
|
||||
pip install skill-seekers[all]
|
||||
|
||||
# See all options
|
||||
skill-seekers-setup
|
||||
```
|
||||
|
||||
**To use new features**:
|
||||
```bash
|
||||
# Set up GitHub token (one-time)
|
||||
@@ -205,6 +293,10 @@ skill-seekers github --repo owner/repo --non-interactive
|
||||
|
||||
# View configuration
|
||||
skill-seekers config --show
|
||||
|
||||
# Bootstrap skill-seekers as a Claude Code skill
|
||||
./scripts/bootstrap_skill.sh
|
||||
cp -r output/skill-seekers ~/.claude/skills/
|
||||
```
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
78
README.md
78
README.md
@@ -220,6 +220,37 @@ skill-seekers resume github_react_20260117_143022
|
||||
|
||||
**See complete documentation**: [Configuration Guide](docs/guides/CONFIGURATION.md) (coming soon)
|
||||
|
||||
### 🎯 Bootstrap Skill - Self-Hosting (**NEW - v2.7.0**)
|
||||
|
||||
Generate skill-seekers as a Claude Code skill to use within Claude:
|
||||
|
||||
```bash
|
||||
# Generate the skill
|
||||
./scripts/bootstrap_skill.sh
|
||||
|
||||
# Install to Claude Code
|
||||
cp -r output/skill-seekers ~/.claude/skills/
|
||||
|
||||
# Verify
|
||||
ls ~/.claude/skills/skill-seekers/SKILL.md
|
||||
```
|
||||
|
||||
**What you get:**
|
||||
- ✅ **Complete skill documentation** - All CLI commands and usage patterns
|
||||
- ✅ **CLI command reference** - Every tool and its options documented
|
||||
- ✅ **Quick start examples** - Common workflows and best practices
|
||||
- ✅ **Auto-generated API docs** - Code analysis, patterns, and examples
|
||||
- ✅ **Robust validation** - YAML frontmatter and required fields checked
|
||||
- ✅ **One-command bootstrap** - Combines manual header with auto-generated analysis
|
||||
|
||||
**How it works:**
|
||||
1. Runs codebase analysis on skill-seekers itself (dogfooding!)
|
||||
2. Combines handcrafted header (prerequisites, commands) with auto-generated content
|
||||
3. Validates SKILL.md structure (frontmatter, required fields)
|
||||
4. Outputs ready-to-use skill directory
|
||||
|
||||
**Result:** Use skill-seekers to create skills, from within Claude Code!
|
||||
|
||||
### 🔐 Private Config Repositories (**NEW - v2.2.0**)
|
||||
- ✅ **Git-Based Config Sources** - Fetch configs from private/team git repositories
|
||||
- ✅ **Multi-Source Management** - Register unlimited GitHub, GitLab, Bitbucket repos
|
||||
@@ -297,6 +328,53 @@ skill-seekers-codebase tests/ --build-how-to-guides --ai-mode none
|
||||
pip install skill-seekers
|
||||
```
|
||||
|
||||
### Installation Options
|
||||
|
||||
Choose your installation profile based on which features you need:
|
||||
|
||||
```bash
|
||||
# 1️⃣ CLI Only (Skill Generation)
|
||||
pip install skill-seekers
|
||||
|
||||
# Features:
|
||||
# • Scrape documentation websites
|
||||
# • Analyze GitHub repositories
|
||||
# • Extract from PDFs
|
||||
# • Package skills for all platforms
|
||||
|
||||
# 2️⃣ MCP Integration (Claude Code, Cursor, Windsurf)
|
||||
pip install skill-seekers[mcp]
|
||||
|
||||
# Features:
|
||||
# • Everything from CLI Only
|
||||
# • MCP server for Claude Code
|
||||
# • One-command skill installation
|
||||
# • HTTP/stdio transport modes
|
||||
|
||||
# 3️⃣ Multi-LLM Support (Gemini, OpenAI)
|
||||
pip install skill-seekers[all-llms]
|
||||
|
||||
# Features:
|
||||
# • Everything from CLI Only
|
||||
# • Google Gemini support
|
||||
# • OpenAI ChatGPT support
|
||||
# • Enhanced AI features
|
||||
|
||||
# 4️⃣ Everything
|
||||
pip install skill-seekers[all]
|
||||
|
||||
# Features:
|
||||
# • All features enabled
|
||||
# • Maximum flexibility
|
||||
```
|
||||
|
||||
**Need help choosing?** Run the setup wizard:
|
||||
```bash
|
||||
skill-seekers-setup
|
||||
```
|
||||
|
||||
The wizard shows all options with detailed feature lists and guides you through configuration.
|
||||
|
||||
Get started in seconds. No cloning, no setup - just install and run. See installation options below.
|
||||
|
||||
---
|
||||
|
||||
@@ -43,9 +43,8 @@ dependencies = [
|
||||
"beautifulsoup4>=4.14.2",
|
||||
"PyGithub>=2.5.0",
|
||||
"GitPython>=3.1.40",
|
||||
"mcp>=1.25,<2",
|
||||
"httpx>=0.28.1",
|
||||
"httpx-sse>=0.4.3",
|
||||
"httpx>=0.28.1", # Required for async scraping (core feature)
|
||||
"anthropic>=0.76.0", # Required for AI enhancement (core feature)
|
||||
"PyMuPDF>=1.24.14",
|
||||
"Pillow>=11.0.0",
|
||||
"pytesseract>=0.3.13",
|
||||
@@ -60,7 +59,7 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
# MCP server dependencies (included by default, but optional)
|
||||
# MCP server dependencies (NOW TRULY OPTIONAL)
|
||||
mcp = [
|
||||
"mcp>=1.25,<2",
|
||||
"httpx>=0.28.1",
|
||||
@@ -126,6 +125,7 @@ skill-seekers-install-agent = "skill_seekers.cli.install_agent:main"
|
||||
skill-seekers-codebase = "skill_seekers.cli.codebase_scraper:main"
|
||||
skill-seekers-patterns = "skill_seekers.cli.pattern_recognizer:main"
|
||||
skill-seekers-how-to-guides = "skill_seekers.cli.how_to_guide_builder:main"
|
||||
skill-seekers-setup = "skill_seekers.cli.setup_wizard:main"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
@@ -146,8 +146,11 @@ python_functions = ["test_*"]
|
||||
addopts = "-v --tb=short --strict-markers"
|
||||
markers = [
|
||||
"asyncio: mark test as an async test",
|
||||
"slow: mark test as slow running",
|
||||
"slow: mark test as slow running (>5 seconds)",
|
||||
"integration: mark test as integration test (requires external services)",
|
||||
"e2e: mark test as end-to-end (resource-intensive, may create files)",
|
||||
"venv: mark test as requiring virtual environment setup",
|
||||
"bootstrap: mark test as bootstrap feature specific",
|
||||
]
|
||||
asyncio_mode = "auto"
|
||||
asyncio_default_fixture_loop_scope = "function"
|
||||
|
||||
115
scripts/bootstrap_skill.sh
Executable file
115
scripts/bootstrap_skill.sh
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# Bootstrap Skill Seekers into an Operational Skill for Claude Code
|
||||
#
|
||||
# Usage: ./scripts/bootstrap_skill.sh
|
||||
# Output: output/skill-seekers/ (skill directory)
|
||||
#
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SKILL_NAME="skill-seekers"
|
||||
OUTPUT_DIR="$PROJECT_ROOT/output/$SKILL_NAME"
|
||||
HEADER_FILE="$SCRIPT_DIR/skill_header.md"
|
||||
|
||||
echo "============================================"
|
||||
echo " Skill Seekers Bootstrap"
|
||||
echo "============================================"
|
||||
|
||||
# Step 1: Sync dependencies
|
||||
echo "Step 1: uv sync..."
|
||||
if ! command -v uv &> /dev/null; then
|
||||
echo "❌ Error: 'uv' is not installed"
|
||||
echo ""
|
||||
echo "Install uv:"
|
||||
echo " curl -LsSf https://astral.sh/uv/install.sh | sh"
|
||||
echo " # or"
|
||||
echo " pip install uv"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
cd "$PROJECT_ROOT"
|
||||
uv sync --quiet
|
||||
echo "✓ Done"
|
||||
|
||||
# Step 2: Run codebase analysis
|
||||
echo "Step 2: Analyzing codebase..."
|
||||
rm -rf "$OUTPUT_DIR" 2>/dev/null || true
|
||||
uv run skill-seekers-codebase \
|
||||
--directory "$PROJECT_ROOT" \
|
||||
--output "$OUTPUT_DIR" \
|
||||
--depth deep \
|
||||
--ai-mode none 2>&1 | grep -E "^(INFO|✅)" || true
|
||||
echo "✓ Done"
|
||||
|
||||
# Step 3: Prepend header to SKILL.md
|
||||
echo "Step 3: Adding operational header..."
|
||||
if [[ -f "$HEADER_FILE" ]]; then
|
||||
# Detect end of frontmatter dynamically
|
||||
# Look for second occurrence of '---'
|
||||
FRONTMATTER_END=$(grep -n '^---$' "$OUTPUT_DIR/SKILL.md" | sed -n '2p' | cut -d: -f1)
|
||||
|
||||
if [[ -n "$FRONTMATTER_END" ]]; then
|
||||
# Skip frontmatter + blank line
|
||||
AUTO_CONTENT=$(tail -n +$((FRONTMATTER_END + 2)) "$OUTPUT_DIR/SKILL.md")
|
||||
else
|
||||
# Fallback to line 6 if no frontmatter found
|
||||
AUTO_CONTENT=$(tail -n +6 "$OUTPUT_DIR/SKILL.md")
|
||||
fi
|
||||
|
||||
# Combine: header + auto-generated
|
||||
cat "$HEADER_FILE" > "$OUTPUT_DIR/SKILL.md"
|
||||
echo "$AUTO_CONTENT" >> "$OUTPUT_DIR/SKILL.md"
|
||||
echo "✓ Done ($(wc -l < "$OUTPUT_DIR/SKILL.md") lines)"
|
||||
else
|
||||
echo "Warning: $HEADER_FILE not found, using auto-generated only"
|
||||
fi
|
||||
|
||||
# Step 4: Validate merged SKILL.md
|
||||
echo "Step 4: Validating SKILL.md..."
|
||||
if [[ -f "$OUTPUT_DIR/SKILL.md" ]]; then
|
||||
# Check file not empty
|
||||
if [[ ! -s "$OUTPUT_DIR/SKILL.md" ]]; then
|
||||
echo "❌ Error: SKILL.md is empty"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check frontmatter exists
|
||||
if ! head -1 "$OUTPUT_DIR/SKILL.md" | grep -q '^---$'; then
|
||||
echo "⚠️ Warning: SKILL.md missing frontmatter delimiter"
|
||||
fi
|
||||
|
||||
# Check required fields
|
||||
if ! grep -q '^name:' "$OUTPUT_DIR/SKILL.md"; then
|
||||
echo "❌ Error: SKILL.md missing 'name:' field"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! grep -q '^description:' "$OUTPUT_DIR/SKILL.md"; then
|
||||
echo "❌ Error: SKILL.md missing 'description:' field"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Validation passed"
|
||||
else
|
||||
echo "❌ Error: SKILL.md not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo " Bootstrap Complete!"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo "Output: $OUTPUT_DIR/"
|
||||
echo " - SKILL.md ($(wc -l < "$OUTPUT_DIR/SKILL.md") lines)"
|
||||
echo " - references/ (API docs, patterns, examples)"
|
||||
echo ""
|
||||
echo "Install to Claude Code:"
|
||||
echo " cp -r output/$SKILL_NAME ~/.claude/skills/"
|
||||
echo ""
|
||||
echo "Verify:"
|
||||
echo " ls ~/.claude/skills/$SKILL_NAME/SKILL.md"
|
||||
echo ""
|
||||
44
scripts/skill_header.md
Normal file
44
scripts/skill_header.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
name: skill-seekers
|
||||
description: Generate LLM skills from documentation, codebases, and GitHub repositories
|
||||
---
|
||||
|
||||
# Skill Seekers
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
pip install skill-seekers
|
||||
# Or: uv pip install skill-seekers
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Source | Command |
|
||||
|--------|---------|
|
||||
| Local code | `skill-seekers-codebase --directory ./path` |
|
||||
| Docs URL | `skill-seekers scrape --url https://...` |
|
||||
| GitHub | `skill-seekers github --repo owner/repo` |
|
||||
| PDF | `skill-seekers pdf --file doc.pdf` |
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Analyze local codebase
|
||||
skill-seekers-codebase --directory /path/to/project --output output/my-skill/
|
||||
|
||||
# Package for Claude
|
||||
yes | skill-seekers package output/my-skill/ --no-open
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Flag | Description |
|
||||
|------|-------------|
|
||||
| `--depth surface/deep/full` | Analysis depth |
|
||||
| `--skip-patterns` | Skip pattern detection |
|
||||
| `--skip-test-examples` | Skip test extraction |
|
||||
| `--ai-mode none/api/local` | AI enhancement |
|
||||
|
||||
---
|
||||
|
||||
@@ -1177,7 +1177,7 @@ Examples:
|
||||
print(f"{'=' * 60}")
|
||||
print(f"Files analyzed: {len(results['files'])}")
|
||||
print(f"Output directory: {args.output}")
|
||||
if args.build_api_reference:
|
||||
if not args.skip_api_reference:
|
||||
print(f"API reference: {Path(args.output) / 'api_reference'}")
|
||||
print(f"{'=' * 60}\n")
|
||||
|
||||
|
||||
@@ -34,12 +34,29 @@ from pathlib import Path
|
||||
# Add parent directory to path to import MCP server
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
# Import the MCP tool function
|
||||
from skill_seekers.mcp.server import install_skill_tool
|
||||
# Import the MCP tool function (with lazy loading)
|
||||
try:
|
||||
from skill_seekers.mcp.server import install_skill_tool
|
||||
MCP_AVAILABLE = True
|
||||
except ImportError:
|
||||
MCP_AVAILABLE = False
|
||||
install_skill_tool = None
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point for CLI"""
|
||||
# Check MCP availability first
|
||||
if not MCP_AVAILABLE:
|
||||
print("\n❌ Error: MCP package not installed")
|
||||
print("\nThe 'install' command requires MCP support.")
|
||||
print("Install with:")
|
||||
print(" pip install skill-seekers[mcp]")
|
||||
print("\nOr use these alternatives:")
|
||||
print(" skill-seekers scrape --config react")
|
||||
print(" skill-seekers package output/react/")
|
||||
print()
|
||||
sys.exit(1)
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Complete skill installation workflow (fetch → scrape → enhance → package → upload)",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
|
||||
94
src/skill_seekers/cli/setup_wizard.py
Normal file
94
src/skill_seekers/cli/setup_wizard.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""
|
||||
Interactive Setup Wizard for Skill Seekers
|
||||
|
||||
Guides users through installation options on first run.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def show_installation_guide():
|
||||
"""Show installation options"""
|
||||
print("""
|
||||
╔═══════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ Skill Seekers Setup Guide ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════╝
|
||||
|
||||
Choose your installation profile:
|
||||
|
||||
1️⃣ CLI Only (Skill Generation)
|
||||
pip install skill-seekers
|
||||
|
||||
Features:
|
||||
• Scrape documentation websites
|
||||
• Analyze GitHub repositories
|
||||
• Extract from PDFs
|
||||
• Package skills for all platforms
|
||||
|
||||
2️⃣ MCP Integration (Claude Code, Cursor, Windsurf)
|
||||
pip install skill-seekers[mcp]
|
||||
|
||||
Features:
|
||||
• Everything from CLI Only
|
||||
• MCP server for Claude Code
|
||||
• One-command skill installation
|
||||
• HTTP/stdio transport modes
|
||||
|
||||
3️⃣ Multi-LLM Support (Gemini, OpenAI)
|
||||
pip install skill-seekers[all-llms]
|
||||
|
||||
Features:
|
||||
• Everything from CLI Only
|
||||
• Google Gemini support
|
||||
• OpenAI ChatGPT support
|
||||
• Enhanced AI features
|
||||
|
||||
4️⃣ Everything
|
||||
pip install skill-seekers[all]
|
||||
|
||||
Features:
|
||||
• All features enabled
|
||||
• Maximum flexibility
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Current installation: pip install skill-seekers
|
||||
Upgrade with: pip install -U skill-seekers[mcp]
|
||||
|
||||
For configuration wizard:
|
||||
skill-seekers config
|
||||
|
||||
For help:
|
||||
skill-seekers --help
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
""")
|
||||
|
||||
|
||||
def check_first_run():
|
||||
"""Check if this is first run"""
|
||||
flag_file = Path.home() / ".config" / "skill-seekers" / ".setup_shown"
|
||||
|
||||
if not flag_file.exists():
|
||||
show_installation_guide()
|
||||
|
||||
# Create flag to not show again
|
||||
flag_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
flag_file.touch()
|
||||
|
||||
response = input("\nPress Enter to continue...")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Show wizard"""
|
||||
show_installation_guide()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
83
tests/test_bootstrap_skill.py
Normal file
83
tests/test_bootstrap_skill.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Tests for the bootstrap skill script."""
|
||||
|
||||
import subprocess
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project_root():
|
||||
"""Get project root directory."""
|
||||
return Path(__file__).parent.parent
|
||||
|
||||
|
||||
class TestBootstrapSkillScript:
|
||||
"""Tests for scripts/bootstrap_skill.sh"""
|
||||
|
||||
def test_script_exists(self, project_root):
|
||||
"""Test that bootstrap script exists and is executable."""
|
||||
script = project_root / "scripts" / "bootstrap_skill.sh"
|
||||
assert script.exists(), "bootstrap_skill.sh should exist"
|
||||
assert script.stat().st_mode & 0o111, "bootstrap_skill.sh should be executable"
|
||||
|
||||
def test_header_template_exists(self, project_root):
|
||||
"""Test that skill header template exists."""
|
||||
header = project_root / "scripts" / "skill_header.md"
|
||||
assert header.exists(), "skill_header.md should exist"
|
||||
|
||||
def test_header_has_required_sections(self, project_root):
|
||||
"""Test that header template has required operational sections."""
|
||||
header = project_root / "scripts" / "skill_header.md"
|
||||
content = header.read_text()
|
||||
|
||||
# Must have prerequisites
|
||||
assert "## Prerequisites" in content, "Header must have Prerequisites section"
|
||||
assert "pip install skill-seekers" in content, "Header must have pip install instruction"
|
||||
|
||||
# Must have commands table
|
||||
assert "## Commands" in content, "Header must have Commands section"
|
||||
assert "skill-seekers-codebase" in content, "Header must mention codebase command"
|
||||
assert "skill-seekers scrape" in content, "Header must mention scrape command"
|
||||
assert "skill-seekers github" in content, "Header must mention github command"
|
||||
|
||||
def test_header_has_yaml_frontmatter(self, project_root):
|
||||
"""Test that header has valid YAML frontmatter."""
|
||||
header = project_root / "scripts" / "skill_header.md"
|
||||
content = header.read_text()
|
||||
|
||||
assert content.startswith("---"), "Header must start with YAML frontmatter"
|
||||
assert "name: skill-seekers" in content, "Header must have skill name"
|
||||
assert "description:" in content, "Header must have description"
|
||||
|
||||
@pytest.mark.slow
|
||||
def test_bootstrap_script_runs(self, project_root, tmp_path):
|
||||
"""Test that bootstrap script runs successfully.
|
||||
|
||||
Note: This test is slow as it runs full codebase analysis.
|
||||
Run with: pytest -m slow
|
||||
"""
|
||||
script = project_root / "scripts" / "bootstrap_skill.sh"
|
||||
|
||||
# Run script (skip if uv not available)
|
||||
result = subprocess.run(
|
||||
["bash", str(script)],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=600, # 10 minute timeout
|
||||
)
|
||||
|
||||
# Check script completed
|
||||
assert result.returncode == 0, f"Script failed: {result.stderr}"
|
||||
|
||||
# Check outputs exist (directory named 'skill-seekers' for Claude Code)
|
||||
output_dir = project_root / "output" / "skill-seekers"
|
||||
assert output_dir.exists(), "Output directory should be created"
|
||||
|
||||
skill_md = output_dir / "SKILL.md"
|
||||
assert skill_md.exists(), "SKILL.md should be created"
|
||||
|
||||
# Check SKILL.md has header prepended
|
||||
content = skill_md.read_text()
|
||||
assert "## Prerequisites" in content, "SKILL.md should have header prepended"
|
||||
assert "pip install skill-seekers" in content, "SKILL.md should have install instructions"
|
||||
171
tests/test_bootstrap_skill_e2e.py
Normal file
171
tests/test_bootstrap_skill_e2e.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
End-to-end tests for bootstrap skill feature (PR #249)
|
||||
|
||||
Tests verify:
|
||||
1. Bootstrap script creates proper skill structure
|
||||
2. Generated SKILL.md is valid and usable
|
||||
3. Skill is installable in isolated virtual environment
|
||||
4. Output works with all platform adaptors
|
||||
5. Error cases handled gracefully
|
||||
|
||||
Coverage: 8-12 tests
|
||||
Execution time: Fast tests ~2-3 min, Full tests ~5-10 min
|
||||
Requires: Python 3.10+, bash, uv
|
||||
|
||||
Run fast tests:
|
||||
pytest tests/test_bootstrap_skill_e2e.py -v -k "not venv"
|
||||
|
||||
Run full suite:
|
||||
pytest tests/test_bootstrap_skill_e2e.py -v -m "e2e"
|
||||
|
||||
Run with venv tests:
|
||||
pytest tests/test_bootstrap_skill_e2e.py -v -m "venv"
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def project_root():
|
||||
"""Get project root directory."""
|
||||
return Path(__file__).parent.parent
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run_bootstrap(project_root):
|
||||
"""Execute bootstrap script and return result"""
|
||||
def _run(timeout=600):
|
||||
script = project_root / "scripts" / "bootstrap_skill.sh"
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", str(script)],
|
||||
cwd=project_root,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
return result
|
||||
return _run
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def output_skill_dir(project_root):
|
||||
"""Get path to bootstrap output directory"""
|
||||
return project_root / "output" / "skill-seekers"
|
||||
|
||||
|
||||
@pytest.mark.e2e
|
||||
class TestBootstrapSkillE2E:
|
||||
"""End-to-end tests for bootstrap skill"""
|
||||
|
||||
def test_bootstrap_creates_output_structure(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify bootstrap creates correct directory structure"""
|
||||
result = run_bootstrap()
|
||||
|
||||
assert result.returncode == 0, f"Bootstrap failed: {result.stderr}"
|
||||
assert output_skill_dir.exists(), "Output directory not created"
|
||||
assert (output_skill_dir / "SKILL.md").exists(), "SKILL.md not created"
|
||||
assert (output_skill_dir / "SKILL.md").stat().st_size > 0, "SKILL.md is empty"
|
||||
|
||||
def test_bootstrap_prepends_header(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify header template prepended to SKILL.md"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
content = (output_skill_dir / "SKILL.md").read_text()
|
||||
|
||||
# Check header sections present
|
||||
assert "## Prerequisites" in content, "Missing Prerequisites section"
|
||||
assert "pip install skill-seekers" in content, "Missing install instruction"
|
||||
assert "## Commands" in content, "Missing Commands section"
|
||||
|
||||
def test_bootstrap_validates_yaml_frontmatter(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify generated SKILL.md has valid YAML frontmatter"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
content = (output_skill_dir / "SKILL.md").read_text()
|
||||
|
||||
# Check frontmatter structure
|
||||
assert content.startswith("---"), "Missing frontmatter start"
|
||||
|
||||
# Find closing delimiter
|
||||
lines = content.split('\n')
|
||||
closing_found = False
|
||||
for i, line in enumerate(lines[1:], 1):
|
||||
if line.strip() == "---":
|
||||
closing_found = True
|
||||
break
|
||||
|
||||
assert closing_found, "Missing frontmatter closing delimiter"
|
||||
|
||||
# Check required fields
|
||||
assert "name:" in content[:500], "Missing name field"
|
||||
assert "description:" in content[:500], "Missing description field"
|
||||
|
||||
def test_bootstrap_output_line_count(self, run_bootstrap, output_skill_dir):
|
||||
"""Verify output SKILL.md has reasonable line count"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
line_count = len((output_skill_dir / "SKILL.md").read_text().splitlines())
|
||||
|
||||
# Should be substantial (header ~44 + auto-generated ~200+)
|
||||
assert line_count > 100, f"SKILL.md too short: {line_count} lines"
|
||||
assert line_count < 2000, f"SKILL.md suspiciously long: {line_count} lines"
|
||||
|
||||
@pytest.mark.slow
|
||||
@pytest.mark.venv
|
||||
def test_skill_installable_in_venv(self, run_bootstrap, output_skill_dir, tmp_path):
|
||||
"""Test skill is installable in clean virtual environment"""
|
||||
# First run bootstrap
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
# Create venv
|
||||
venv_path = tmp_path / "test_venv"
|
||||
subprocess.run(
|
||||
[sys.executable, "-m", "venv", str(venv_path)],
|
||||
check=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
# Install skill in venv
|
||||
pip_path = venv_path / "bin" / "pip"
|
||||
result = subprocess.run(
|
||||
[str(pip_path), "install", "-e", "."],
|
||||
cwd=output_skill_dir.parent.parent,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
)
|
||||
|
||||
# Should install successfully
|
||||
assert result.returncode == 0, f"Install failed: {result.stderr}"
|
||||
|
||||
def test_skill_packageable_with_adaptors(self, run_bootstrap, output_skill_dir, tmp_path):
|
||||
"""Verify bootstrap output works with all platform adaptors"""
|
||||
result = run_bootstrap()
|
||||
assert result.returncode == 0
|
||||
|
||||
# Try to package with claude adaptor (simplest)
|
||||
from skill_seekers.cli.adaptors import get_adaptor
|
||||
|
||||
adaptor = get_adaptor('claude')
|
||||
|
||||
# Should be able to package without errors
|
||||
try:
|
||||
package_path = adaptor.package(
|
||||
skill_dir=output_skill_dir, # Path object, not str
|
||||
output_path=tmp_path # Path object, not str
|
||||
)
|
||||
|
||||
assert Path(package_path).exists(), "Package not created"
|
||||
assert Path(package_path).stat().st_size > 0, "Package is empty"
|
||||
except Exception as e:
|
||||
pytest.fail(f"Packaging failed: {e}")
|
||||
@@ -114,7 +114,7 @@ class TestUnifiedCLIEntryPoints(unittest.TestCase):
|
||||
|
||||
# Should show version
|
||||
output = result.stdout + result.stderr
|
||||
self.assertIn("2.5.1", output)
|
||||
self.assertIn('2.7.0', output)
|
||||
|
||||
except FileNotFoundError:
|
||||
# If skill-seekers is not installed, skip this test
|
||||
|
||||
34
uv.lock
generated
34
uv.lock
generated
@@ -17,6 +17,25 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anthropic"
|
||||
version = "0.76.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "distro" },
|
||||
{ name = "docstring-parser" },
|
||||
{ name = "httpx" },
|
||||
{ name = "jiter" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "sniffio" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/6e/be/d11abafaa15d6304826438170f7574d750218f49a106c54424a40cef4494/anthropic-0.76.0.tar.gz", hash = "sha256:e0cae6a368986d5cf6df743dfbb1b9519e6a9eee9c6c942ad8121c0b34416ffe", size = 495483, upload-time = "2026-01-13T18:41:14.908Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/70/7b0fd9c1a738f59d3babe2b4212031c34ab7d0fda4ffef15b58a55c5bcea/anthropic-0.76.0-py3-none-any.whl", hash = "sha256:81efa3113901192af2f0fe977d3ec73fdadb1e691586306c4256cd6d5ccc331c", size = 390309, upload-time = "2026-01-13T18:41:13.483Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "4.12.0"
|
||||
@@ -450,6 +469,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docstring-parser"
|
||||
version = "0.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/9d/c3b43da9515bd270df0f80548d9944e389870713cc1fe2b8fb35fe2bcefd/docstring_parser-0.17.0.tar.gz", hash = "sha256:583de4a309722b3315439bb31d64ba3eebada841f2e2cee23b99df001434c912", size = 27442, upload-time = "2025-07-21T07:35:01.868Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896, upload-time = "2025-07-21T07:35:00.684Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.3.1"
|
||||
@@ -1821,13 +1849,12 @@ name = "skill-seekers"
|
||||
version = "2.7.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "anthropic" },
|
||||
{ name = "beautifulsoup4" },
|
||||
{ name = "click" },
|
||||
{ name = "gitpython" },
|
||||
{ name = "httpx" },
|
||||
{ name = "httpx-sse" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "mcp" },
|
||||
{ name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" },
|
||||
{ name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
|
||||
{ name = "pathspec" },
|
||||
@@ -1884,6 +1911,7 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "anthropic", specifier = ">=0.76.0" },
|
||||
{ name = "beautifulsoup4", specifier = ">=4.14.2" },
|
||||
{ name = "click", specifier = ">=8.3.0" },
|
||||
{ name = "gitpython", specifier = ">=3.1.40" },
|
||||
@@ -1893,11 +1921,9 @@ requires-dist = [
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "httpx", marker = "extra == 'all'", specifier = ">=0.28.1" },
|
||||
{ name = "httpx", marker = "extra == 'mcp'", specifier = ">=0.28.1" },
|
||||
{ name = "httpx-sse", specifier = ">=0.4.3" },
|
||||
{ name = "httpx-sse", marker = "extra == 'all'", specifier = ">=0.4.3" },
|
||||
{ name = "httpx-sse", marker = "extra == 'mcp'", specifier = ">=0.4.3" },
|
||||
{ name = "jsonschema", specifier = ">=4.25.1" },
|
||||
{ name = "mcp", specifier = ">=1.25,<2" },
|
||||
{ name = "mcp", marker = "extra == 'all'", specifier = ">=1.25,<2" },
|
||||
{ name = "mcp", marker = "extra == 'mcp'", specifier = ">=1.25,<2" },
|
||||
{ name = "networkx", specifier = ">=3.0" },
|
||||
|
||||
Reference in New Issue
Block a user