Enhancement: Robust Logging, Legacy Fallback, CLI Help, and Typo Handling for SuperClaude CLI (#117)

* Update README.md

* Update README.md

* Update README.md

* Update SuperClaude.py
This commit is contained in:
Mithun Gowda B
2025-07-14 19:14:28 +05:30
committed by GitHub
parent 59d74b8af2
commit 8c5fad9875
2 changed files with 177 additions and 207 deletions

View File

@@ -1,4 +1,9 @@
# SuperClaude v3 🚀 # SuperClaude v3 🚀
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Version](https://img.shields.io/badge/version-3.0.0-blue.svg)](https://github.com/NomenAK/SuperClaude)
[![GitHub issues](https://img.shields.io/github/issues/NomenAK/SuperClaude)](https://github.com/NomenAK/SuperClaude/issues)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/NomenAK/SuperClaude/blob/master/CONTRIBUTING.md)
[![Contributors](https://img.shields.io/github/contributors/NomenAK/SuperClaude)](https://github.com/NomenAK/SuperClaude/graphs/contributors)
An enhancement framework for Claude Code that adds extra capabilities through specialized commands, personas, and MCP server integration. An enhancement framework for Claude Code that adds extra capabilities through specialized commands, personas, and MCP server integration.
@@ -160,10 +165,25 @@ A: Currently Claude Code only, but v4 will have broader compatibility.
**Q: Is this stable enough for daily use?** **Q: Is this stable enough for daily use?**
A: The core features work well, but expect some rough edges since it's a fresh release. A: The core features work well, but expect some rough edges since it's a fresh release.
## License 📄 ## SuperClaude Contributors
MIT - See LICENSE file for details. [![Contributors](https://contrib.rocks/image?repo=NomenAk/SuperClaude)](https://github.com/NomenAK/SuperClaude/graphs/contributors)
## License
MIT - [See LICENSE file for details](https://opensource.org/licenses/MIT)
## Star History
<a href="https://www.star-history.com/#NomenAK/SuperClaude&Date">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=NomenAK/SuperClaude&type=Date&theme=dark" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=NomenAK/SuperClaude&type=Date" />
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=NomenAK/SuperClaude&type=Date" />
</picture>
</a>
--- ---
*Built by developers, for developers. We hope you find it useful! 🙂* *Built by developers, for developers. We hope you find it useful! 🙂*
---

View File

@@ -3,289 +3,239 @@
SuperClaude Framework Management Hub SuperClaude Framework Management Hub
Unified entry point for all SuperClaude operations Unified entry point for all SuperClaude operations
This is the main command-line interface for the SuperClaude framework,
providing a unified hub for installation, updates, backups, and management.
Usage: Usage:
SuperClaude.py install [options] # Install framework SuperClaude.py install [options]
SuperClaude.py update [options] # Update framework SuperClaude.py update [options]
SuperClaude.py uninstall [options] # Uninstall framework SuperClaude.py uninstall [options]
SuperClaude.py backup [options] # Backup/restore operations SuperClaude.py backup [options]
SuperClaude.py --help # Show all available operations SuperClaude.py --help
""" """
import sys import sys
import argparse import argparse
import time import subprocess
import difflib
from pathlib import Path from pathlib import Path
from typing import Dict, Callable, Optional from typing import Dict, Callable
# Add setup directory to Python path # Add the 'setup' directory to the Python import path
setup_dir = Path(__file__).parent / "setup" setup_dir = Path(__file__).parent / "setup"
sys.path.insert(0, str(setup_dir)) sys.path.insert(0, str(setup_dir))
from setup.utils.ui import ( # Try to import utilities from the setup package
display_header, display_info, display_success, display_error, try:
display_warning, Colors from setup.utils.ui import (
) display_header, display_info, display_success, display_error,
from setup.utils.logger import setup_logging, get_logger, LogLevel display_warning, Colors
from setup import DEFAULT_INSTALL_DIR )
from setup.utils.logger import setup_logging, get_logger, LogLevel
from setup import DEFAULT_INSTALL_DIR
except ImportError:
# Provide minimal fallback functions and constants if imports fail
class Colors:
RED = YELLOW = GREEN = CYAN = RESET = ""
def display_error(msg): print(f"[ERROR] {msg}")
def display_warning(msg): print(f"[WARN] {msg}")
def display_success(msg): print(f"[OK] {msg}")
def display_info(msg): print(f"[INFO] {msg}")
def display_header(title, subtitle): print(f"{title} - {subtitle}")
def get_logger(): return None
def setup_logging(*args, **kwargs): pass
class LogLevel:
ERROR = 40
INFO = 20
DEBUG = 10
def create_global_parser() -> argparse.ArgumentParser: def create_global_parser() -> argparse.ArgumentParser:
"""Create parent parser with global arguments""" """Create shared parser for global flags used by all commands"""
global_parser = argparse.ArgumentParser(add_help=False) global_parser = argparse.ArgumentParser(add_help=False)
# Global options available to all operations global_parser.add_argument("--verbose", "-v", action="store_true",
global_parser.add_argument( help="Enable verbose logging")
"--verbose", "-v", global_parser.add_argument("--quiet", "-q", action="store_true",
action="store_true", help="Suppress all output except errors")
help="Enable verbose output (debug level logging)" global_parser.add_argument("--install-dir", type=Path, default=DEFAULT_INSTALL_DIR,
) help=f"Target installation directory (default: {DEFAULT_INSTALL_DIR})")
global_parser.add_argument("--dry-run", action="store_true",
global_parser.add_argument( help="Simulate operation without making changes")
"--quiet", "-q", global_parser.add_argument("--force", action="store_true",
action="store_true", help="Force execution, skipping checks")
help="Quiet mode - only show errors" global_parser.add_argument("--yes", "-y", action="store_true",
) help="Automatically answer yes to all prompts")
global_parser.add_argument(
"--install-dir",
type=Path,
default=DEFAULT_INSTALL_DIR,
help=f"Installation directory (default: {DEFAULT_INSTALL_DIR})"
)
global_parser.add_argument(
"--dry-run",
action="store_true",
help="Simulate operation without making changes"
)
global_parser.add_argument(
"--force",
action="store_true",
help="Force operation, skip confirmations and checks"
)
global_parser.add_argument(
"--yes", "-y",
action="store_true",
help="Automatically answer yes to all prompts"
)
return global_parser return global_parser
def create_parser() -> argparse.ArgumentParser: def create_parser():
"""Create the main argument parser with subcommands""" """Create the main CLI parser and attach subcommand parsers"""
# Create global parser for shared arguments
global_parser = create_global_parser() global_parser = create_global_parser()
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="SuperClaude", prog="SuperClaude",
description="SuperClaude Framework Management Hub - Unified CLI for all operations", description="SuperClaude Framework Management Hub - Unified CLI",
epilog=""" epilog="""
Examples: Examples:
SuperClaude.py install --quick --dry-run # Quick installation (dry-run) SuperClaude.py install --dry-run
SuperClaude.py install --profile developer # Developer profile SuperClaude.py update --verbose
SuperClaude.py backup --create # Create backup SuperClaude.py backup --create
SuperClaude.py update --verbose # Update with verbose output
SuperClaude.py uninstall --force # Force removal
Global options can be used with any operation:
--verbose, --quiet, --dry-run, --force, --yes, --install-dir
Use 'SuperClaude.py <operation> --help' for operation-specific options.
""", """,
formatter_class=argparse.RawDescriptionHelpFormatter, formatter_class=argparse.RawDescriptionHelpFormatter,
parents=[global_parser] parents=[global_parser]
) )
parser.add_argument( parser.add_argument("--version", action="version", version="SuperClaude v3.0.0")
"--version",
action="version",
version="SuperClaude v3.0.0"
)
# Create subparsers for operations
subparsers = parser.add_subparsers( subparsers = parser.add_subparsers(
dest="operation", dest="operation",
title="Available operations", title="Operations",
description="SuperClaude framework management operations", description="Framework operations to perform"
help="Operation to perform"
) )
# Register operation parsers (will be populated by operation modules)
# This design allows each operation to define its own CLI interface
return parser, subparsers, global_parser return parser, subparsers, global_parser
def setup_global_environment(args: argparse.Namespace) -> None: def setup_global_environment(args: argparse.Namespace):
"""Setup global environment configuration shared by all operations""" """Set up logging and shared runtime environment based on args"""
# Determine log level from global flags # Determine log level
if args.quiet: if args.quiet:
console_level = LogLevel.ERROR level = LogLevel.ERROR
elif args.verbose: elif args.verbose:
console_level = LogLevel.DEBUG level = LogLevel.DEBUG
else: else:
console_level = LogLevel.INFO level = LogLevel.INFO
# Setup logging system # Define log directory unless it's a dry run
log_dir = args.install_dir / "logs" if not args.dry_run else None log_dir = args.install_dir / "logs" if not args.dry_run else None
setup_logging( setup_logging("superclaude_hub", log_dir=log_dir, console_level=level)
name="superclaude_hub",
log_dir=log_dir, # Log startup context
console_level=console_level
)
# Log the operation being performed
logger = get_logger() logger = get_logger()
logger.debug(f"SuperClaude.py called with operation: {args.operation}") if logger:
logger.debug(f"Global options: verbose={args.verbose}, quiet={args.quiet}, " logger.debug(f"SuperClaude.py called with operation: {getattr(args, 'operation', 'None')}")
f"install_dir={args.install_dir}, dry_run={args.dry_run}, force={args.force}") logger.debug(f"Arguments: {vars(args)}")
def get_operation_modules() -> Dict[str, str]: def get_operation_modules() -> Dict[str, str]:
"""Get available operation modules and their descriptions""" """Return supported operations and their descriptions"""
return { return {
"install": "Install SuperClaude framework components", "install": "Install SuperClaude framework components",
"update": "Update existing SuperClaude installation", "update": "Update existing SuperClaude installation",
"uninstall": "Remove SuperClaude framework installation", "uninstall": "Remove SuperClaude installation",
"backup": "Backup and restore SuperClaude installations" "backup": "Backup and restore operations"
} }
def load_operation_module(operation_name: str): def load_operation_module(name: str):
"""Dynamically load an operation module""" """Try to dynamically import an operation module"""
try: try:
module_path = f"setup.operations.{operation_name}" return __import__(f"setup.operations.{name}", fromlist=[name])
module = __import__(module_path, fromlist=[operation_name])
return module
except ImportError as e: except ImportError as e:
logger = get_logger() logger = get_logger()
logger.error(f"Could not load operation module '{operation_name}': {e}") if logger:
logger.error(f"Module '{name}' failed to load: {e}")
return None return None
def register_operation_parsers(subparsers, global_parser) -> Dict[str, Callable]: def register_operation_parsers(subparsers, global_parser) -> Dict[str, Callable]:
"""Register all operation parsers and return operation functions""" """Register subcommand parsers and map operation names to their run functions"""
operations = {} operations = {}
operation_modules = get_operation_modules() for name, desc in get_operation_modules().items():
module = load_operation_module(name)
for operation_name, description in operation_modules.items(): if module and hasattr(module, 'register_parser') and hasattr(module, 'run'):
try: module.register_parser(subparsers, global_parser)
# Try to load the operation module operations[name] = module.run
module = load_operation_module(operation_name) else:
# If module doesn't exist, register a stub parser and fallback to legacy
if module and hasattr(module, 'register_parser') and hasattr(module, 'run'): parser = subparsers.add_parser(name, help=f"{desc} (legacy fallback)", parents=[global_parser])
# Register the parser for this operation with global parser inheritance parser.add_argument("--legacy", action="store_true", help="Use legacy script")
module.register_parser(subparsers, global_parser) operations[name] = None
operations[operation_name] = module.run
else:
# Create placeholder parser for operations not yet implemented
parser = subparsers.add_parser(
operation_name,
help=f"{description} (not yet implemented in unified CLI)",
parents=[global_parser]
)
parser.add_argument(
"--legacy",
action="store_true",
help=f"Use legacy {operation_name}.py script"
)
operations[operation_name] = None
except Exception as e:
logger = get_logger()
logger.warning(f"Could not register operation '{operation_name}': {e}")
return operations return operations
def handle_legacy_fallback(operation_name: str, args: argparse.Namespace) -> int: def handle_legacy_fallback(op: str, args: argparse.Namespace) -> int:
"""Handle fallback to legacy scripts when operation module not available""" """Run a legacy operation script if module is unavailable"""
legacy_script = Path(__file__).parent / f"{operation_name}.py" script_path = Path(__file__).parent / f"{op}.py"
if legacy_script.exists(): if not script_path.exists():
logger = get_logger() display_error(f"No module or legacy script found for operation '{op}'")
logger.info(f"Falling back to legacy script: {legacy_script}") return 1
# Build command to execute legacy script display_warning(f"Falling back to legacy script for '{op}'...")
import subprocess
cmd = [sys.executable, str(legacy_script)] cmd = [sys.executable, str(script_path)]
# Convert unified args back to legacy format # Convert args into CLI flags
for arg, value in vars(args).items(): for k, v in vars(args).items():
if arg in ['operation', 'install_dir'] or value in [False, None]: if k in ['operation', 'install_dir'] or v in [None, False]:
continue continue
if value is True: flag = f"--{k.replace('_', '-')}"
cmd.append(f"--{arg.replace('_', '-')}") if v is True:
else: cmd.append(flag)
cmd.extend([f"--{arg.replace('_', '-')}", str(value)]) else:
cmd.extend([flag, str(v)])
try:
return subprocess.call(cmd) try:
except Exception as e: return subprocess.call(cmd)
logger.error(f"Failed to execute legacy script: {e}") except Exception as e:
return 1 display_error(f"Legacy execution failed: {e}")
else:
logger = get_logger()
logger.error(f"Operation '{operation_name}' not implemented and no legacy script found")
return 1 return 1
def main() -> int: def main() -> int:
"""Main entry point for SuperClaude CLI hub""" """Main entry point"""
try: try:
# Create parser and subparsers
parser, subparsers, global_parser = create_parser() parser, subparsers, global_parser = create_parser()
# Register operation parsers
operations = register_operation_parsers(subparsers, global_parser) operations = register_operation_parsers(subparsers, global_parser)
# Parse arguments
args = parser.parse_args() args = parser.parse_args()
# Show help if no operation specified # No operation provided? Show help manually unless in quiet mode
if not args.operation: if not args.operation:
if not args.quiet: if not args.quiet:
display_header( display_header("SuperClaude Framework v3.0", "Unified CLI for all operations")
"SuperClaude Framework Management Hub v3.0", print(f"{Colors.CYAN}Available operations:{Colors.RESET}")
"Unified CLI for all SuperClaude operations" for op, desc in get_operation_modules().items():
) print(f" {op:<12} {desc}")
print(f"\n{Colors.CYAN}Available operations:{Colors.RESET}")
for op_name, op_desc in get_operation_modules().items():
print(f" {op_name:<12} {op_desc}")
print(f"\nUse 'SuperClaude.py <operation> --help' for operation-specific options.")
print(f"Use 'SuperClaude.py --help' for global options.")
return 0 return 0
# Setup global environment # Handle unknown operations and suggest corrections
if args.operation not in operations:
close = difflib.get_close_matches(args.operation, operations.keys(), n=1)
suggestion = f"Did you mean: {close[0]}?" if close else ""
display_error(f"Unknown operation: '{args.operation}'. {suggestion}")
return 1
# Setup global context (logging, install path, etc.)
setup_global_environment(args) setup_global_environment(args)
logger = get_logger() logger = get_logger()
# Execute the requested operation # Execute operation
if args.operation in operations and operations[args.operation]: run_func = operations.get(args.operation)
# Operation module is available if run_func:
logger.info(f"Executing operation: {args.operation}") if logger:
return operations[args.operation](args) logger.info(f"Executing operation: {args.operation}")
return run_func(args)
else: else:
# Fall back to legacy script # Fallback to legacy script
logger.warning(f"Operation module for '{args.operation}' not available, trying legacy fallback") if logger:
logger.warning(f"Module for '{args.operation}' missing, using legacy fallback")
return handle_legacy_fallback(args.operation, args) return handle_legacy_fallback(args.operation, args)
except KeyboardInterrupt: except KeyboardInterrupt:
print(f"\n{Colors.YELLOW}Operation cancelled by user{Colors.RESET}") print(f"\n{Colors.YELLOW}Operation cancelled by user{Colors.RESET}")
return 130 return 130
except Exception as e: except Exception as e:
try: try:
logger = get_logger() logger = get_logger()
logger.exception(f"Unexpected error in SuperClaude hub: {e}") if logger:
logger.exception(f"Unhandled error: {e}")
except: except:
print(f"{Colors.RED}[ERROR] Unexpected error: {e}{Colors.RESET}") print(f"{Colors.RED}[ERROR] {e}{Colors.RESET}")
return 1 return 1
# Entrypoint guard
if __name__ == "__main__": if __name__ == "__main__":
sys.exit(main()) sys.exit(main())