""" Configuration management CLI commands. Provides commands for configuration validation, display, and troubleshooting. """ import os import sys from pathlib import Path from typing import Dict, Any, List, Tuple, Optional from config import ( get_unified_config, get_config_status, MarkitectConfig, ConfigurationError, ConfigValidationError, load_env_file ) from ..presenters.config import ConfigPresenter class ConfigCommands: """Configuration management command handlers.""" def __init__(self) -> None: self.presenter = ConfigPresenter() def show_config(self, show_sensitive: bool = False) -> None: """Display current configuration values.""" try: config = get_unified_config() status = get_config_status() self.presenter.show_configuration(config, status, show_sensitive) except ConfigurationError as e: self.presenter.show_error(f"Configuration error: {e}") sys.exit(1) except Exception as e: self.presenter.show_error(f"Unexpected error: {e}") sys.exit(1) def validate_config(self, verbose: bool = False) -> None: """Validate current configuration and show any issues.""" try: config = get_unified_config() validation_results = self._perform_validation_checks(config) self.presenter.show_validation_results(validation_results, verbose) # Exit with non-zero code if there are errors if any(result['status'] == 'error' for result in validation_results): sys.exit(1) except ConfigurationError as e: self.presenter.show_error(f"Configuration error: {e}") sys.exit(1) except Exception as e: self.presenter.show_error(f"Unexpected error: {e}") sys.exit(1) def troubleshoot_config(self) -> None: """Run comprehensive configuration troubleshooting.""" try: config = get_unified_config() status = get_config_status() # Perform all diagnostic checks diagnostics = self._run_diagnostics(config) self.presenter.show_troubleshooting_results(config, status, diagnostics) except Exception as e: # Even if config loading fails, we can still provide diagnostics diagnostics = self._run_basic_diagnostics() self.presenter.show_troubleshooting_results(None, None, diagnostics) def check_config_files(self) -> None: """Check for configuration files and their status.""" file_checks = self._check_configuration_files() self.presenter.show_config_file_status(file_checks) def _perform_validation_checks(self, config: MarkitectConfig) -> List[Dict[str, Any]]: """Perform comprehensive configuration validation.""" results = [] # Check required fields required_fields = [ ('gitea_url', 'Gitea/Git platform URL'), ('repo_owner', 'Repository owner'), ('repo_name', 'Repository name'), ] for field, description in required_fields: value = getattr(config, field, None) if not value or (isinstance(value, str) and not value.strip()): results.append({ 'check': f'Required field: {description}', 'status': 'error', 'message': f'{description} is required but not set', 'suggestion': f'Set {field.upper()} in environment or .env.tddai file' }) else: results.append({ 'check': f'Required field: {description}', 'status': 'success', 'message': f'{description} is properly configured' }) # Check URL format if config.gitea_url: if not (config.gitea_url.startswith('http://') or config.gitea_url.startswith('https://')): results.append({ 'check': 'URL format validation', 'status': 'error', 'message': 'Gitea URL must start with http:// or https://', 'suggestion': 'Update gitea_url to include protocol (e.g., https://github.com)' }) else: results.append({ 'check': 'URL format validation', 'status': 'success', 'message': 'Gitea URL format is valid' }) # Check workspace directory workspace_path = Path(config.workspace_dir) if workspace_path.exists() and not workspace_path.is_dir(): results.append({ 'check': 'Workspace directory', 'status': 'error', 'message': f'Workspace path exists but is not a directory: {workspace_path}', 'suggestion': 'Remove the file or choose a different workspace directory' }) else: results.append({ 'check': 'Workspace directory', 'status': 'success', 'message': f'Workspace directory is valid: {workspace_path}' }) # Check authentication token auth_token = os.getenv('GITEA_API_TOKEN') or os.getenv('GITHUB_TOKEN') if not auth_token: results.append({ 'check': 'Authentication token', 'status': 'warning', 'message': 'No authentication token found', 'suggestion': 'Set GITEA_API_TOKEN or GITHUB_TOKEN environment variable for API access' }) else: results.append({ 'check': 'Authentication token', 'status': 'success', 'message': 'Authentication token is configured' }) return results def _run_diagnostics(self, config: Optional[MarkitectConfig]) -> Dict[str, Any]: """Run comprehensive diagnostics.""" diagnostics = {} # Environment diagnostics diagnostics['environment'] = self._check_environment() # File system diagnostics diagnostics['filesystem'] = self._check_filesystem() # Configuration files diagnostics diagnostics['config_files'] = self._check_configuration_files() # Git repository diagnostics diagnostics['git_repository'] = self._check_git_repository() # Network diagnostics (if config available) if config: diagnostics['network'] = self._check_network_connectivity(config) return diagnostics def _run_basic_diagnostics(self) -> Dict[str, Any]: """Run basic diagnostics when config loading fails.""" return { 'environment': self._check_environment(), 'filesystem': self._check_filesystem(), 'config_files': self._check_configuration_files(), 'git_repository': self._check_git_repository(), } def _check_environment(self) -> Dict[str, Any]: """Check environment variables and settings.""" relevant_vars = [ 'TDDAI_GITEA_URL', 'TDDAI_REPO_OWNER', 'TDDAI_REPO_NAME', 'TDDAI_WORKSPACE_DIR', 'GITEA_API_TOKEN', 'GITHUB_TOKEN', 'PYTHONPATH', 'PATH' ] env_status = {} for var in relevant_vars: value = os.getenv(var) env_status[var] = { 'set': value is not None, 'value': '***HIDDEN***' if 'TOKEN' in var and value else value, 'length': len(value) if value else 0 } return { 'python_version': sys.version, 'python_executable': sys.executable, 'current_directory': str(Path.cwd()), 'environment_variables': env_status } def _check_filesystem(self) -> Dict[str, Any]: """Check file system permissions and paths.""" current_dir = Path.cwd() return { 'current_directory': { 'path': str(current_dir), 'exists': current_dir.exists(), 'readable': os.access(current_dir, os.R_OK), 'writable': os.access(current_dir, os.W_OK), }, 'home_directory': { 'path': str(Path.home()), 'exists': Path.home().exists(), 'readable': os.access(Path.home(), os.R_OK), 'writable': os.access(Path.home(), os.W_OK), } } def _check_configuration_files(self) -> Dict[str, Any]: """Check for configuration files and their status.""" config_files = { '.env.tddai': Path('.env.tddai'), '.env': Path('.env'), 'pyproject.toml': Path('pyproject.toml'), 'tddai-setup.sh': Path('tddai-setup.sh'), } file_status = {} for name, path in config_files.items(): file_status[name] = { 'path': str(path), 'exists': path.exists(), 'readable': path.exists() and os.access(path, os.R_OK), 'size': path.stat().st_size if path.exists() else 0, 'modified': path.stat().st_mtime if path.exists() else None } # Try to parse .env files if name.startswith('.env') and path.exists(): try: env_vars = load_env_file(path) file_status[name]['parsed_variables'] = len(env_vars) file_status[name]['parse_error'] = None except Exception as e: file_status[name]['parsed_variables'] = 0 file_status[name]['parse_error'] = str(e) return file_status def _check_git_repository(self) -> Dict[str, Any]: """Check git repository status.""" git_dir = Path('.git') status = { 'is_git_repository': git_dir.exists(), 'git_directory': str(git_dir), } if git_dir.exists(): try: import subprocess # Get remote origin URL result = subprocess.run( ['git', 'remote', 'get-url', 'origin'], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: status['remote_origin'] = result.stdout.strip() # Get current branch result = subprocess.run( ['git', 'branch', '--show-current'], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: status['current_branch'] = result.stdout.strip() except (subprocess.TimeoutExpired, FileNotFoundError): status['git_command_available'] = False return status def _check_network_connectivity(self, config: MarkitectConfig) -> Dict[str, Any]: """Check network connectivity to configured services.""" status = {} if config.gitea_url: try: import urllib.request import urllib.parse parsed_url = urllib.parse.urlparse(config.gitea_url) base_url = f"{parsed_url.scheme}://{parsed_url.netloc}" req = urllib.request.Request(base_url) req.add_header('User-Agent', 'tddai-config-check/1.0') with urllib.request.urlopen(req, timeout=10) as response: status['gitea_connectivity'] = { 'url': base_url, 'status_code': response.getcode(), 'reachable': True } except Exception as e: status['gitea_connectivity'] = { 'url': config.gitea_url, 'reachable': False, 'error': str(e) } return status