mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-17 09:46:06 +00:00
Add missing install.sh script (#483)
* feat: add missing install.sh script referenced in README\n\n- Create comprehensive installation script with POSIX compatibility\n- Add interactive and non-interactive installation modes\n- Include prerequisites checking and MCP server setup guidance\n- Replace echo -e with printf for better POSIX compliance * fix: resolve linting errors in install_mcp.py and clean_command_names.py Fix multiple ruff linting errors to ensure CI/CD pipeline passes: - install_mcp.py: Remove unused pathlib.Path import, replace bare except with specific exception types (ValueError, IndexError), remove extraneous f-string prefixes on lines without placeholders - clean_command_names.py: Remove unused os import, convert f-strings without placeholders to regular strings - pyproject.toml: Exclude docs/ directory from ruff checks to avoid N999 module naming violations in documentation templates All linting checks now pass successfully. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * style: apply ruff format to Python source files Apply ruff formatting rules to CLI and scripts modules to ensure consistent code style across the codebase. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix(ci): remove incompatible pip cache from quick-check workflow ## Problem GitHub Actions was failing with error: "Cache folder path is retrieved for pip but doesn't exist on disk: /home/runner/.cache/pip. This likely indicates that there are no dependencies to cache." ## Root Cause The quick-check.yml workflow specified `cache: 'pip'` in the Python setup step, but the workflow uses UV (not pip) for package management via `uv pip install --system -e ".[dev]"`. UV uses its own cache directory (~/.cache/uv), so the pip cache path was never created, causing the error. This was a migration oversight: - When UV was adopted as the project standard (commit 00706f0), the CLAUDE.md established "CRITICAL: Never use pip directly" rule - The test.yml workflow was created correctly without pip cache - The quick-check.yml workflow incorrectly included pip cache from initial creation (commit 8c0559c) and was not updated during migration ## Solution Remove `cache: 'pip'` line to align with: - Project's UV-first architecture (CLAUDE.md) - test.yml workflow (which runs successfully without pip cache) - readme-quality-check.yml workflow (no cache needed) Note: publish-pypi.yml intentionally uses pip cache as it directly runs `python -m pip install` commands, which is correct for that workflow. ## Impact - ✅ Eliminates GitHub Actions cache warning - ✅ Aligns all UV-based workflows consistently - ✅ Follows project standards documented in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1b14d06537
commit
18c0e4e127
1
.github/workflows/quick-check.yml
vendored
1
.github/workflows/quick-check.yml
vendored
@ -18,7 +18,6 @@ jobs:
|
|||||||
uses: actions/setup-python@v5
|
uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
cache: 'pip'
|
|
||||||
|
|
||||||
- name: Install UV
|
- name: Install UV
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
363
install.sh
Executable file
363
install.sh
Executable file
@ -0,0 +1,363 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
################################################################################
|
||||||
|
# SuperClaude Framework Installation Script
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# This script installs SuperClaude Framework directly from the Git repository.
|
||||||
|
# It performs the following steps:
|
||||||
|
# 1. Checks prerequisites (Python 3.10+, UV package manager)
|
||||||
|
# 2. Installs SuperClaude package in editable mode
|
||||||
|
# 3. Installs 30 slash commands to ~/.claude/commands/
|
||||||
|
# 4. Verifies installation
|
||||||
|
# 5. Provides next steps guidance
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./install.sh # Interactive installation
|
||||||
|
# ./install.sh --yes # Non-interactive (auto-yes to prompts)
|
||||||
|
# ./install.sh --help # Show help message
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
set -e # Exit on error
|
||||||
|
|
||||||
|
# Color codes for output
|
||||||
|
readonly RED='\033[0;31m'
|
||||||
|
readonly GREEN='\033[0;32m'
|
||||||
|
readonly YELLOW='\033[1;33m'
|
||||||
|
readonly BLUE='\033[0;34m'
|
||||||
|
readonly CYAN='\033[0;36m'
|
||||||
|
readonly NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# Script directory
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
PROJECT_ROOT="$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Installation options
|
||||||
|
AUTO_YES=false
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Helper Functions
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
print_header() {
|
||||||
|
printf "%b\n" "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
printf "%b\n" "${CYAN}$1${NC}"
|
||||||
|
printf "%b\n" "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_success() {
|
||||||
|
printf "%b\n" "${GREEN}✅ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_error() {
|
||||||
|
printf "%b\n" "${RED}❌ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_warning() {
|
||||||
|
printf "%b\n" "${YELLOW}⚠️ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_info() {
|
||||||
|
printf "%b\n" "${BLUE}ℹ️ $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_step() {
|
||||||
|
printf "%b\n" "${CYAN}🔹 $1${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
if [ "$AUTO_YES" = true ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local prompt="$1"
|
||||||
|
local default="${2:-y}"
|
||||||
|
|
||||||
|
if [ "$default" = "y" ]; then
|
||||||
|
prompt="$prompt [Y/n]: "
|
||||||
|
else
|
||||||
|
prompt="$prompt [y/N]: "
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -p "$prompt" -r response
|
||||||
|
response=${response:-$default}
|
||||||
|
|
||||||
|
if [[ "$response" =~ ^[Yy]$ ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Prerequisite Checks
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
check_python() {
|
||||||
|
print_step "Checking Python installation..."
|
||||||
|
|
||||||
|
if ! command -v python3 &> /dev/null; then
|
||||||
|
print_error "Python 3 is not installed"
|
||||||
|
print_info "Please install Python 3.10 or higher from https://www.python.org/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local python_version=$(python3 --version 2>&1 | awk '{print $2}')
|
||||||
|
local major_version=$(echo "$python_version" | cut -d. -f1)
|
||||||
|
local minor_version=$(echo "$python_version" | cut -d. -f2)
|
||||||
|
|
||||||
|
if [ "$major_version" -lt 3 ] || ([ "$major_version" -eq 3 ] && [ "$minor_version" -lt 10 ]); then
|
||||||
|
print_error "Python $python_version found, but Python 3.10+ is required"
|
||||||
|
print_info "Please upgrade Python from https://www.python.org/"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_success "Python $python_version found"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_git() {
|
||||||
|
print_step "Checking Git installation..."
|
||||||
|
|
||||||
|
if ! command -v git &> /dev/null; then
|
||||||
|
print_warning "Git is not installed"
|
||||||
|
print_info "Git is recommended for development. Install from https://git-scm.com/"
|
||||||
|
else
|
||||||
|
local git_version=$(git --version 2>&1 | awk '{print $3}')
|
||||||
|
print_success "Git $git_version found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
check_uv() {
|
||||||
|
print_step "Checking UV package manager..."
|
||||||
|
|
||||||
|
if ! command -v uv &> /dev/null; then
|
||||||
|
print_warning "UV package manager is not installed"
|
||||||
|
return 1
|
||||||
|
else
|
||||||
|
local uv_version=$(uv --version 2>&1 | awk '{print $2}')
|
||||||
|
print_success "UV $uv_version found"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_uv() {
|
||||||
|
print_step "Installing UV package manager..."
|
||||||
|
|
||||||
|
if ! confirm "Would you like to install UV now?"; then
|
||||||
|
print_error "UV is required for SuperClaude installation"
|
||||||
|
print_info "You can install UV manually: curl -LsSf https://astral.sh/uv/install.sh | sh"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "Installing UV (this may take a moment)..."
|
||||||
|
if curl -LsSf https://astral.sh/uv/install.sh | sh; then
|
||||||
|
print_success "UV installed successfully"
|
||||||
|
|
||||||
|
# Add UV to PATH for current session
|
||||||
|
export PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
|
||||||
|
# Verify UV is now available
|
||||||
|
if ! command -v uv &> /dev/null; then
|
||||||
|
print_warning "UV installed but not in PATH"
|
||||||
|
print_info "Please restart your terminal or run: source ~/.bashrc (or ~/.zshrc)"
|
||||||
|
print_info "Then run this script again"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
print_error "Failed to install UV"
|
||||||
|
print_info "Please install UV manually: https://github.com/astral-sh/uv"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Installation Functions
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
install_package() {
|
||||||
|
print_step "Installing SuperClaude package..."
|
||||||
|
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Check if pyproject.toml exists
|
||||||
|
if [ ! -f "pyproject.toml" ]; then
|
||||||
|
print_error "pyproject.toml not found in $PROJECT_ROOT"
|
||||||
|
print_info "Are you running this script from the SuperClaude repository root?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install in editable mode with dev dependencies
|
||||||
|
print_info "Running: uv pip install -e \".[dev]\""
|
||||||
|
if uv pip install -e ".[dev]"; then
|
||||||
|
print_success "SuperClaude package installed successfully"
|
||||||
|
else
|
||||||
|
print_error "Failed to install SuperClaude package"
|
||||||
|
print_info "Try running manually: uv pip install -e \".[dev]\""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
install_commands() {
|
||||||
|
print_step "Installing slash commands..."
|
||||||
|
|
||||||
|
# Check if superclaude command is available
|
||||||
|
if ! command -v superclaude &> /dev/null; then
|
||||||
|
print_error "superclaude command not found"
|
||||||
|
print_info "Package installation may have failed"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
print_info "Installing 30 slash commands to ~/.claude/commands/sc/"
|
||||||
|
if superclaude install; then
|
||||||
|
print_success "Slash commands installed successfully"
|
||||||
|
else
|
||||||
|
print_error "Failed to install slash commands"
|
||||||
|
print_info "Try running manually: superclaude install"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
verify_installation() {
|
||||||
|
print_step "Verifying installation..."
|
||||||
|
|
||||||
|
# Check package version
|
||||||
|
local version=$(superclaude --version 2>&1)
|
||||||
|
print_info "Installed version: $version"
|
||||||
|
|
||||||
|
# Run doctor command
|
||||||
|
print_info "Running health check..."
|
||||||
|
if superclaude doctor; then
|
||||||
|
print_success "Installation verified successfully"
|
||||||
|
else
|
||||||
|
print_warning "Health check completed with warnings"
|
||||||
|
print_info "You can run 'superclaude doctor' anytime to check status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# List installed commands
|
||||||
|
print_info "Installed commands:"
|
||||||
|
superclaude install --list | head -n 10
|
||||||
|
echo " ... and more (30 commands total)"
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Main Installation Flow
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
cat << EOF
|
||||||
|
SuperClaude Framework Installation Script
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
./install.sh [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
--yes Non-interactive mode (auto-yes to all prompts)
|
||||||
|
--help Show this help message
|
||||||
|
|
||||||
|
Description:
|
||||||
|
Installs SuperClaude Framework directly from the Git repository.
|
||||||
|
Performs installation in editable/development mode with all features.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Python 3.10 or higher
|
||||||
|
- UV package manager (will be installed if missing)
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
./install.sh # Interactive installation
|
||||||
|
./install.sh --yes # Non-interactive installation
|
||||||
|
|
||||||
|
For more information:
|
||||||
|
https://github.com/SuperClaude-Org/SuperClaude_Framework
|
||||||
|
EOF
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_args() {
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
--yes|-y)
|
||||||
|
AUTO_YES=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--help|-h)
|
||||||
|
show_help
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
print_error "Unknown option: $1"
|
||||||
|
echo "Run './install.sh --help' for usage information"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
# Parse command line arguments
|
||||||
|
parse_args "$@"
|
||||||
|
|
||||||
|
# Print header
|
||||||
|
clear
|
||||||
|
print_header "🚀 SuperClaude Framework Installation"
|
||||||
|
echo ""
|
||||||
|
print_info "This script will install SuperClaude Framework in development mode"
|
||||||
|
print_info "Installation location: $PROJECT_ROOT"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ "$AUTO_YES" != true ]; then
|
||||||
|
if ! confirm "Continue with installation?"; then
|
||||||
|
print_info "Installation cancelled"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Phase 1: Check prerequisites
|
||||||
|
print_header "📋 Phase 1: Checking Prerequisites"
|
||||||
|
check_python
|
||||||
|
check_git
|
||||||
|
if ! check_uv; then
|
||||||
|
install_uv
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Phase 2: Install package
|
||||||
|
print_header "📦 Phase 2: Installing SuperClaude Package"
|
||||||
|
install_package
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Phase 3: Install commands
|
||||||
|
print_header "⚙️ Phase 3: Installing Slash Commands"
|
||||||
|
install_commands
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Phase 4: Verify installation
|
||||||
|
print_header "✅ Phase 4: Verifying Installation"
|
||||||
|
verify_installation
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Phase 5: Next steps
|
||||||
|
print_header "🎉 Installation Complete!"
|
||||||
|
echo ""
|
||||||
|
print_success "SuperClaude Framework is now installed!"
|
||||||
|
echo ""
|
||||||
|
print_info "Next Steps:"
|
||||||
|
echo " 1. Run health check: superclaude doctor"
|
||||||
|
echo " 2. View all commands: superclaude install --list"
|
||||||
|
echo " 3. Try a command: /sc:help"
|
||||||
|
echo ""
|
||||||
|
print_info "Optional - Install MCP Servers for enhanced features:"
|
||||||
|
echo " • List available servers: superclaude mcp --list"
|
||||||
|
echo " • Interactive installation: superclaude mcp"
|
||||||
|
echo " • Specific servers: superclaude mcp --servers tavily context7"
|
||||||
|
echo ""
|
||||||
|
print_info "Documentation:"
|
||||||
|
echo " • Quick Start: docs/getting-started/quick-start.md"
|
||||||
|
echo " • User Guide: docs/user-guide/"
|
||||||
|
echo " • Commands: docs/reference/commands-list.md"
|
||||||
|
echo ""
|
||||||
|
print_success "Happy coding with SuperClaude! 🚀"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run main function
|
||||||
|
main "$@"
|
||||||
@ -14,7 +14,6 @@ Exit Codes:
|
|||||||
1 - Error (directory not found or processing failed)
|
1 - Error (directory not found or processing failed)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -117,8 +116,8 @@ def process_commands_directory(commands_dir: Path) -> int:
|
|||||||
error_count += 1
|
error_count += 1
|
||||||
print(f"❌ Error: {md_file.name} - {e}", file=sys.stderr)
|
print(f"❌ Error: {md_file.name} - {e}", file=sys.stderr)
|
||||||
|
|
||||||
print(f"{'='*60}")
|
print("="*60)
|
||||||
print(f"📊 Summary:")
|
print("📊 Summary:")
|
||||||
print(f" • Processed: {processed_count} files")
|
print(f" • Processed: {processed_count} files")
|
||||||
print(f" • Modified: {modified_count} files")
|
print(f" • Modified: {modified_count} files")
|
||||||
print(f" • Errors: {error_count} files")
|
print(f" • Errors: {error_count} files")
|
||||||
@ -151,7 +150,7 @@ def main() -> int:
|
|||||||
print("\n❌ Cleanup failed with errors", file=sys.stderr)
|
print("\n❌ Cleanup failed with errors", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print(f"\n✅ Cleanup completed successfully")
|
print("\n✅ Cleanup completed successfully")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
|
|||||||
@ -165,6 +165,7 @@ extend-exclude = '''
|
|||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
target-version = "py310"
|
target-version = "py310"
|
||||||
|
exclude = ["docs/"]
|
||||||
|
|
||||||
[tool.ruff.lint]
|
[tool.ruff.lint]
|
||||||
select = ["E", "F", "I", "N", "W"]
|
select = ["E", "F", "I", "N", "W"]
|
||||||
|
|||||||
@ -11,11 +11,12 @@ Usage:
|
|||||||
--metric tokens_used
|
--metric tokens_used
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
|
import statistics
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Tuple
|
from typing import Dict, List, Tuple
|
||||||
import statistics
|
|
||||||
from scipy import stats
|
from scipy import stats
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -10,13 +10,13 @@ Usage:
|
|||||||
python scripts/analyze_workflow_metrics.py --task-type bug_fix
|
python scripts/analyze_workflow_metrics.py --task-type bug_fix
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
import argparse
|
import argparse
|
||||||
from pathlib import Path
|
import json
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from typing import Dict, List, Optional
|
|
||||||
from collections import defaultdict
|
|
||||||
import statistics
|
import statistics
|
||||||
|
from collections import defaultdict
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
|
||||||
class WorkflowMetricsAnalyzer:
|
class WorkflowMetricsAnalyzer:
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import json
|
|||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
PLUGIN_SRC = ROOT / "plugins" / "superclaude"
|
PLUGIN_SRC = ROOT / "plugins" / "superclaude"
|
||||||
DIST_ROOT = ROOT / "dist" / "plugins" / "superclaude"
|
DIST_ROOT = ROOT / "dist" / "plugins" / "superclaude"
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
|
||||||
from typing import Dict, List, Optional, Tuple
|
from typing import Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import click
|
import click
|
||||||
@ -137,7 +136,7 @@ def check_prerequisites() -> Tuple[bool, List[str]]:
|
|||||||
errors.append(
|
errors.append(
|
||||||
f"Node.js version {version} found, but version 18+ required"
|
f"Node.js version {version} found, but version 18+ required"
|
||||||
)
|
)
|
||||||
except:
|
except (ValueError, IndexError):
|
||||||
pass
|
pass
|
||||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||||
errors.append("Node.js not found - required for npm-based MCP servers")
|
errors.append("Node.js not found - required for npm-based MCP servers")
|
||||||
@ -173,7 +172,9 @@ def check_mcp_server_installed(server_name: str) -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def prompt_for_api_key(server_name: str, env_var: str, description: str) -> Optional[str]:
|
def prompt_for_api_key(
|
||||||
|
server_name: str, env_var: str, description: str
|
||||||
|
) -> Optional[str]:
|
||||||
"""Prompt user for API key if needed."""
|
"""Prompt user for API key if needed."""
|
||||||
click.echo(f"\n🔑 MCP server '{server_name}' requires an API key")
|
click.echo(f"\n🔑 MCP server '{server_name}' requires an API key")
|
||||||
click.echo(f" Environment variable: {env_var}")
|
click.echo(f" Environment variable: {env_var}")
|
||||||
@ -189,14 +190,14 @@ def prompt_for_api_key(server_name: str, env_var: str, description: str) -> Opti
|
|||||||
api_key = click.prompt(f" Enter {env_var}", hide_input=True)
|
api_key = click.prompt(f" Enter {env_var}", hide_input=True)
|
||||||
return api_key
|
return api_key
|
||||||
else:
|
else:
|
||||||
click.echo(f" ⚠️ Proceeding without {env_var} - server may not function properly")
|
click.echo(
|
||||||
|
f" ⚠️ Proceeding without {env_var} - server may not function properly"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def install_mcp_server(
|
def install_mcp_server(
|
||||||
server_info: Dict,
|
server_info: Dict, scope: str = "user", dry_run: bool = False
|
||||||
scope: str = "user",
|
|
||||||
dry_run: bool = False
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Install a single MCP server using modern Claude Code API.
|
Install a single MCP server using modern Claude Code API.
|
||||||
@ -227,7 +228,7 @@ def install_mcp_server(
|
|||||||
api_key = prompt_for_api_key(
|
api_key = prompt_for_api_key(
|
||||||
server_name,
|
server_name,
|
||||||
api_key_env,
|
api_key_env,
|
||||||
server_info.get("api_key_description", f"API key for {server_name}")
|
server_info.get("api_key_description", f"API key for {server_name}"),
|
||||||
)
|
)
|
||||||
|
|
||||||
if api_key:
|
if api_key:
|
||||||
@ -260,13 +261,10 @@ def install_mcp_server(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
try:
|
try:
|
||||||
click.echo(f" Running: claude mcp add --transport {transport} {server_name} -- {command}")
|
click.echo(
|
||||||
result = _run_command(
|
f" Running: claude mcp add --transport {transport} {server_name} -- {command}"
|
||||||
cmd,
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
timeout=120
|
|
||||||
)
|
)
|
||||||
|
result = _run_command(cmd, capture_output=True, text=True, timeout=120)
|
||||||
|
|
||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
click.echo(f" ✅ Successfully installed: {server_name}")
|
click.echo(f" ✅ Successfully installed: {server_name}")
|
||||||
@ -310,7 +308,7 @@ def list_available_servers():
|
|||||||
def install_mcp_servers(
|
def install_mcp_servers(
|
||||||
selected_servers: Optional[List[str]] = None,
|
selected_servers: Optional[List[str]] = None,
|
||||||
scope: str = "user",
|
scope: str = "user",
|
||||||
dry_run: bool = False
|
dry_run: bool = False,
|
||||||
) -> Tuple[bool, str]:
|
) -> Tuple[bool, str]:
|
||||||
"""
|
"""
|
||||||
Install MCP servers for Claude Code.
|
Install MCP servers for Claude Code.
|
||||||
@ -347,8 +345,12 @@ def install_mcp_servers(
|
|||||||
|
|
||||||
server_options = []
|
server_options = []
|
||||||
for key, info in MCP_SERVERS.items():
|
for key, info in MCP_SERVERS.items():
|
||||||
api_note = f" (requires {info['api_key_env']})" if "api_key_env" in info else ""
|
api_note = (
|
||||||
server_options.append(f"{info['name']:25} - {info['description']}{api_note}")
|
f" (requires {info['api_key_env']})" if "api_key_env" in info else ""
|
||||||
|
)
|
||||||
|
server_options.append(
|
||||||
|
f"{info['name']:25} - {info['description']}{api_note}"
|
||||||
|
)
|
||||||
|
|
||||||
for i, option in enumerate(server_options, 1):
|
for i, option in enumerate(server_options, 1):
|
||||||
click.echo(f" {i}. {option}")
|
click.echo(f" {i}. {option}")
|
||||||
@ -358,7 +360,7 @@ def install_mcp_servers(
|
|||||||
|
|
||||||
selection = click.prompt(
|
selection = click.prompt(
|
||||||
"Select servers to install (comma-separated numbers, or 0 for all)",
|
"Select servers to install (comma-separated numbers, or 0 for all)",
|
||||||
default="0"
|
default="0",
|
||||||
)
|
)
|
||||||
|
|
||||||
if selection.strip() == "0":
|
if selection.strip() == "0":
|
||||||
@ -367,7 +369,9 @@ def install_mcp_servers(
|
|||||||
try:
|
try:
|
||||||
indices = [int(x.strip()) for x in selection.split(",")]
|
indices = [int(x.strip()) for x in selection.split(",")]
|
||||||
server_list = list(MCP_SERVERS.keys())
|
server_list = list(MCP_SERVERS.keys())
|
||||||
servers_to_install = [server_list[i-1] for i in indices if 0 < i <= len(server_list)]
|
servers_to_install = [
|
||||||
|
server_list[i - 1] for i in indices if 0 < i <= len(server_list)
|
||||||
|
]
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
return False, "Invalid selection"
|
return False, "Invalid selection"
|
||||||
|
|
||||||
@ -394,6 +398,6 @@ def install_mcp_servers(
|
|||||||
return False, message
|
return False, message
|
||||||
else:
|
else:
|
||||||
message = f"\n✅ Successfully installed {installed_count} MCP server(s)!\n"
|
message = f"\n✅ Successfully installed {installed_count} MCP server(s)!\n"
|
||||||
message += f"\nℹ️ Use 'claude mcp list' to see all installed servers"
|
message += "\nℹ️ Use 'claude mcp list' to see all installed servers"
|
||||||
message += f"\nℹ️ Use '/mcp' in Claude Code to check server status"
|
message += "\nℹ️ Use '/mcp' in Claude Code to check server status"
|
||||||
return True, message
|
return True, message
|
||||||
|
|||||||
@ -91,11 +91,18 @@ def install(target: str, force: bool, list_only: bool):
|
|||||||
|
|
||||||
@main.command()
|
@main.command()
|
||||||
@click.option("--servers", "-s", multiple=True, help="Specific MCP servers to install")
|
@click.option("--servers", "-s", multiple=True, help="Specific MCP servers to install")
|
||||||
|
@click.option("--list", "list_only", is_flag=True, help="List available MCP servers")
|
||||||
@click.option(
|
@click.option(
|
||||||
"--list", "list_only", is_flag=True, help="List available MCP servers"
|
"--scope",
|
||||||
|
default="user",
|
||||||
|
type=click.Choice(["local", "project", "user"]),
|
||||||
|
help="Installation scope",
|
||||||
|
)
|
||||||
|
@click.option(
|
||||||
|
"--dry-run",
|
||||||
|
is_flag=True,
|
||||||
|
help="Show what would be installed without actually installing",
|
||||||
)
|
)
|
||||||
@click.option("--scope", default="user", type=click.Choice(["local", "project", "user"]), help="Installation scope")
|
|
||||||
@click.option("--dry-run", is_flag=True, help="Show what would be installed without actually installing")
|
|
||||||
def mcp(servers, list_only, scope, dry_run):
|
def mcp(servers, list_only, scope, dry_run):
|
||||||
"""
|
"""
|
||||||
Install and manage MCP servers for Claude Code
|
Install and manage MCP servers for Claude Code
|
||||||
@ -118,7 +125,7 @@ def mcp(servers, list_only, scope, dry_run):
|
|||||||
success, message = install_mcp_servers(
|
success, message = install_mcp_servers(
|
||||||
selected_servers=list(servers) if servers else None,
|
selected_servers=list(servers) if servers else None,
|
||||||
scope=scope,
|
scope=scope,
|
||||||
dry_run=dry_run
|
dry_run=dry_run,
|
||||||
)
|
)
|
||||||
|
|
||||||
click.echo(message)
|
click.echo(message)
|
||||||
|
|||||||
@ -14,7 +14,6 @@ Exit Codes:
|
|||||||
1 - Error (directory not found or processing failed)
|
1 - Error (directory not found or processing failed)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@ -55,20 +54,20 @@ def clean_name_attributes(content: str) -> Tuple[str, bool]:
|
|||||||
"""
|
"""
|
||||||
# Pattern to match 'name: value' in frontmatter
|
# Pattern to match 'name: value' in frontmatter
|
||||||
# Matches: name: value, name : value, NAME: value (case-insensitive)
|
# Matches: name: value, name : value, NAME: value (case-insensitive)
|
||||||
name_pattern = re.compile(r'^\s*name\s*:\s*.*$', re.MULTILINE | re.IGNORECASE)
|
name_pattern = re.compile(r"^\s*name\s*:\s*.*$", re.MULTILINE | re.IGNORECASE)
|
||||||
|
|
||||||
# Check if modification is needed
|
# Check if modification is needed
|
||||||
if not name_pattern.search(content):
|
if not name_pattern.search(content):
|
||||||
return content, False
|
return content, False
|
||||||
|
|
||||||
# Remove name attributes
|
# Remove name attributes
|
||||||
cleaned = name_pattern.sub('', content)
|
cleaned = name_pattern.sub("", content)
|
||||||
|
|
||||||
# Clean up multiple consecutive newlines (max 2)
|
# Clean up multiple consecutive newlines (max 2)
|
||||||
cleaned = re.sub(r'\n{3,}', '\n\n', cleaned)
|
cleaned = re.sub(r"\n{3,}", "\n\n", cleaned)
|
||||||
|
|
||||||
# Remove trailing whitespace while preserving final newline
|
# Remove trailing whitespace while preserving final newline
|
||||||
cleaned = cleaned.rstrip() + '\n' if cleaned.strip() else ''
|
cleaned = cleaned.rstrip() + "\n" if cleaned.strip() else ""
|
||||||
|
|
||||||
return cleaned, True
|
return cleaned, True
|
||||||
|
|
||||||
@ -92,7 +91,7 @@ def process_commands_directory(commands_dir: Path) -> int:
|
|||||||
error_count = 0
|
error_count = 0
|
||||||
|
|
||||||
print(f"🔍 Scanning: {commands_dir}")
|
print(f"🔍 Scanning: {commands_dir}")
|
||||||
print(f"{'='*60}")
|
print(f"{'=' * 60}")
|
||||||
|
|
||||||
# Process all .md files
|
# Process all .md files
|
||||||
for md_file in sorted(commands_dir.glob("*.md")):
|
for md_file in sorted(commands_dir.glob("*.md")):
|
||||||
@ -100,14 +99,14 @@ def process_commands_directory(commands_dir: Path) -> int:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Read file content
|
# Read file content
|
||||||
content = md_file.read_text(encoding='utf-8')
|
content = md_file.read_text(encoding="utf-8")
|
||||||
|
|
||||||
# Clean name attributes
|
# Clean name attributes
|
||||||
cleaned_content, was_modified = clean_name_attributes(content)
|
cleaned_content, was_modified = clean_name_attributes(content)
|
||||||
|
|
||||||
if was_modified:
|
if was_modified:
|
||||||
# Write cleaned content back
|
# Write cleaned content back
|
||||||
md_file.write_text(cleaned_content, encoding='utf-8')
|
md_file.write_text(cleaned_content, encoding="utf-8")
|
||||||
modified_count += 1
|
modified_count += 1
|
||||||
print(f"✅ Modified: {md_file.name}")
|
print(f"✅ Modified: {md_file.name}")
|
||||||
else:
|
else:
|
||||||
@ -117,8 +116,8 @@ def process_commands_directory(commands_dir: Path) -> int:
|
|||||||
error_count += 1
|
error_count += 1
|
||||||
print(f"❌ Error: {md_file.name} - {e}", file=sys.stderr)
|
print(f"❌ Error: {md_file.name} - {e}", file=sys.stderr)
|
||||||
|
|
||||||
print(f"{'='*60}")
|
print("=" * 60)
|
||||||
print(f"📊 Summary:")
|
print("📊 Summary:")
|
||||||
print(f" • Processed: {processed_count} files")
|
print(f" • Processed: {processed_count} files")
|
||||||
print(f" • Modified: {modified_count} files")
|
print(f" • Modified: {modified_count} files")
|
||||||
print(f" • Errors: {error_count} files")
|
print(f" • Errors: {error_count} files")
|
||||||
@ -151,7 +150,7 @@ def main() -> int:
|
|||||||
print("\n❌ Cleanup failed with errors", file=sys.stderr)
|
print("\n❌ Cleanup failed with errors", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
print(f"\n✅ Cleanup completed successfully")
|
print("\n✅ Cleanup completed successfully")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
@ -160,6 +159,7 @@ def main() -> int:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Unexpected error: {e}", file=sys.stderr)
|
print(f"❌ Unexpected error: {e}", file=sys.stderr)
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user