mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-29 16:16:08 +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 (commit00706f0), 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 (commit8c0559c) 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:
@@ -9,7 +9,6 @@ import os
|
||||
import platform
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
import click
|
||||
@@ -137,7 +136,7 @@ def check_prerequisites() -> Tuple[bool, List[str]]:
|
||||
errors.append(
|
||||
f"Node.js version {version} found, but version 18+ required"
|
||||
)
|
||||
except:
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
except (subprocess.TimeoutExpired, FileNotFoundError):
|
||||
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
|
||||
|
||||
|
||||
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."""
|
||||
click.echo(f"\n🔑 MCP server '{server_name}' requires an API key")
|
||||
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)
|
||||
return api_key
|
||||
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
|
||||
|
||||
|
||||
def install_mcp_server(
|
||||
server_info: Dict,
|
||||
scope: str = "user",
|
||||
dry_run: bool = False
|
||||
server_info: Dict, scope: str = "user", dry_run: bool = False
|
||||
) -> bool:
|
||||
"""
|
||||
Install a single MCP server using modern Claude Code API.
|
||||
@@ -227,7 +228,7 @@ def install_mcp_server(
|
||||
api_key = prompt_for_api_key(
|
||||
server_name,
|
||||
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:
|
||||
@@ -260,13 +261,10 @@ def install_mcp_server(
|
||||
return True
|
||||
|
||||
try:
|
||||
click.echo(f" Running: claude mcp add --transport {transport} {server_name} -- {command}")
|
||||
result = _run_command(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=120
|
||||
click.echo(
|
||||
f" Running: claude mcp add --transport {transport} {server_name} -- {command}"
|
||||
)
|
||||
result = _run_command(cmd, capture_output=True, text=True, timeout=120)
|
||||
|
||||
if result.returncode == 0:
|
||||
click.echo(f" ✅ Successfully installed: {server_name}")
|
||||
@@ -310,7 +308,7 @@ def list_available_servers():
|
||||
def install_mcp_servers(
|
||||
selected_servers: Optional[List[str]] = None,
|
||||
scope: str = "user",
|
||||
dry_run: bool = False
|
||||
dry_run: bool = False,
|
||||
) -> Tuple[bool, str]:
|
||||
"""
|
||||
Install MCP servers for Claude Code.
|
||||
@@ -347,8 +345,12 @@ def install_mcp_servers(
|
||||
|
||||
server_options = []
|
||||
for key, info in MCP_SERVERS.items():
|
||||
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}")
|
||||
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):
|
||||
click.echo(f" {i}. {option}")
|
||||
@@ -358,7 +360,7 @@ def install_mcp_servers(
|
||||
|
||||
selection = click.prompt(
|
||||
"Select servers to install (comma-separated numbers, or 0 for all)",
|
||||
default="0"
|
||||
default="0",
|
||||
)
|
||||
|
||||
if selection.strip() == "0":
|
||||
@@ -367,7 +369,9 @@ def install_mcp_servers(
|
||||
try:
|
||||
indices = [int(x.strip()) for x in selection.split(",")]
|
||||
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):
|
||||
return False, "Invalid selection"
|
||||
|
||||
@@ -394,6 +398,6 @@ def install_mcp_servers(
|
||||
return False, message
|
||||
else:
|
||||
message = f"\n✅ Successfully installed {installed_count} MCP server(s)!\n"
|
||||
message += f"\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 'claude mcp list' to see all installed servers"
|
||||
message += "\nℹ️ Use '/mcp' in Claude Code to check server status"
|
||||
return True, message
|
||||
|
||||
@@ -91,11 +91,18 @@ def install(target: str, force: bool, list_only: bool):
|
||||
|
||||
@main.command()
|
||||
@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(
|
||||
"--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):
|
||||
"""
|
||||
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(
|
||||
selected_servers=list(servers) if servers else None,
|
||||
scope=scope,
|
||||
dry_run=dry_run
|
||||
dry_run=dry_run,
|
||||
)
|
||||
|
||||
click.echo(message)
|
||||
|
||||
@@ -14,7 +14,6 @@ Exit Codes:
|
||||
1 - Error (directory not found or processing failed)
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -55,20 +54,20 @@ def clean_name_attributes(content: str) -> Tuple[str, bool]:
|
||||
"""
|
||||
# Pattern to match 'name: value' in frontmatter
|
||||
# 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
|
||||
if not name_pattern.search(content):
|
||||
return content, False
|
||||
|
||||
# Remove name attributes
|
||||
cleaned = name_pattern.sub('', content)
|
||||
cleaned = name_pattern.sub("", content)
|
||||
|
||||
# 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
|
||||
cleaned = cleaned.rstrip() + '\n' if cleaned.strip() else ''
|
||||
cleaned = cleaned.rstrip() + "\n" if cleaned.strip() else ""
|
||||
|
||||
return cleaned, True
|
||||
|
||||
@@ -92,7 +91,7 @@ def process_commands_directory(commands_dir: Path) -> int:
|
||||
error_count = 0
|
||||
|
||||
print(f"🔍 Scanning: {commands_dir}")
|
||||
print(f"{'='*60}")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
# Process all .md files
|
||||
for md_file in sorted(commands_dir.glob("*.md")):
|
||||
@@ -100,14 +99,14 @@ def process_commands_directory(commands_dir: Path) -> int:
|
||||
|
||||
try:
|
||||
# Read file content
|
||||
content = md_file.read_text(encoding='utf-8')
|
||||
content = md_file.read_text(encoding="utf-8")
|
||||
|
||||
# Clean name attributes
|
||||
cleaned_content, was_modified = clean_name_attributes(content)
|
||||
|
||||
if was_modified:
|
||||
# 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
|
||||
print(f"✅ Modified: {md_file.name}")
|
||||
else:
|
||||
@@ -117,8 +116,8 @@ def process_commands_directory(commands_dir: Path) -> int:
|
||||
error_count += 1
|
||||
print(f"❌ Error: {md_file.name} - {e}", file=sys.stderr)
|
||||
|
||||
print(f"{'='*60}")
|
||||
print(f"📊 Summary:")
|
||||
print("=" * 60)
|
||||
print("📊 Summary:")
|
||||
print(f" • Processed: {processed_count} files")
|
||||
print(f" • Modified: {modified_count} files")
|
||||
print(f" • Errors: {error_count} files")
|
||||
@@ -151,7 +150,7 @@ def main() -> int:
|
||||
print("\n❌ Cleanup failed with errors", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
print(f"\n✅ Cleanup completed successfully")
|
||||
print("\n✅ Cleanup completed successfully")
|
||||
return 0
|
||||
|
||||
except FileNotFoundError as e:
|
||||
@@ -160,6 +159,7 @@ def main() -> int:
|
||||
except Exception as e:
|
||||
print(f"❌ Unexpected error: {e}", file=sys.stderr)
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user