#!/usr/bin/env python3 """ OurDigital Skills Installation Validator Checks that all components are properly installed and configured. Usage: python validate_install.py python validate_install.py --verbose python validate_install.py --fix # Attempt to fix issues """ import os import sys import json import argparse from pathlib import Path from typing import List, Tuple, Dict # Colors for terminal output class Colors: GREEN = '\033[92m' YELLOW = '\033[93m' RED = '\033[91m' CYAN = '\033[96m' BOLD = '\033[1m' END = '\033[0m' def ok(msg: str) -> None: print(f"{Colors.GREEN}[✓]{Colors.END} {msg}") def warn(msg: str) -> None: print(f"{Colors.YELLOW}[!]{Colors.END} {msg}") def fail(msg: str) -> None: print(f"{Colors.RED}[✗]{Colors.END} {msg}") def info(msg: str) -> None: print(f"{Colors.CYAN}[i]{Colors.END} {msg}") class InstallationValidator: def __init__(self, verbose: bool = False): self.verbose = verbose self.home = Path.home() self.script_dir = Path(__file__).parent.parent self.skills_dir = self.script_dir.parent self.env_file = self.home / ".env.ourdigital" self.config_dir = self.home / ".ourdigital" self.venv_dir = self.skills_dir / ".venv-ourdigital" self.results: List[Tuple[str, bool, str]] = [] def check(self, name: str, condition: bool, fix_hint: str = "") -> bool: """Record a check result.""" self.results.append((name, condition, fix_hint)) return condition def validate_directories(self) -> int: """Check required directories exist.""" info("Checking directories...") passed = 0 checks = [ ("Config directory", self.config_dir.exists(), f"mkdir -p {self.config_dir}"), ("Credentials directory", (self.config_dir / "credentials").exists(), f"mkdir -p {self.config_dir}/credentials"), ("Logs directory", (self.config_dir / "logs").exists(), f"mkdir -p {self.config_dir}/logs"), ] for name, condition, fix in checks: if self.check(name, condition, fix): ok(name) passed += 1 else: fail(name) if self.verbose: print(f" Fix: {fix}") return passed def validate_files(self) -> int: """Check required files exist.""" info("Checking files...") passed = 0 checks = [ ("Environment file (.env.ourdigital)", self.env_file.exists(), "cp _ourdigital-shared/.env.ourdigital.template ~/.env.ourdigital"), ("Config file (config.yaml)", (self.config_dir / "config.yaml").exists(), "cp _ourdigital-shared/config/ourdigital.yaml ~/.ourdigital/config.yaml"), ] for name, condition, fix in checks: if self.check(name, condition, fix): ok(name) passed += 1 else: fail(name) if self.verbose: print(f" Fix: {fix}") return passed def validate_env_variables(self) -> int: """Check environment variables are set.""" info("Checking environment variables...") passed = 0 required_vars = [ "NOTION_API_TOKEN", "GHOST_BLOG_ADMIN_KEY", "GHOST_JOURNAL_ADMIN_KEY", ] optional_vars = [ "FIGMA_ACCESS_TOKEN", "N8N_API_KEY", ] if self.env_file.exists(): env_content = self.env_file.read_text() for var in required_vars: has_value = f"{var}=" in env_content and \ f"{var}=<" not in env_content and \ f"{var}=$" not in env_content if self.check(f"Required: {var}", has_value, f"Set {var} in {self.env_file}"): ok(f"{var}") passed += 1 else: fail(f"{var} (required)") for var in optional_vars: has_value = f"{var}=" in env_content and \ f"{var}=<" not in env_content if has_value: ok(f"{var} (optional)") passed += 1 else: warn(f"{var} (optional, not set)") else: fail("Environment file not found") return passed def validate_python_env(self) -> int: """Check Python virtual environment.""" info("Checking Python environment...") passed = 0 if self.check("Virtual environment", self.venv_dir.exists(), f"python3 -m venv {self.venv_dir}"): ok("Virtual environment exists") passed += 1 # Check for key packages pip_path = self.venv_dir / "bin" / "pip" if pip_path.exists(): ok("pip available") passed += 1 else: fail("Virtual environment not found") return passed def validate_skills(self) -> int: """Check skills are properly structured.""" info("Checking skills...") passed = 0 expected_skills = [ "01-ourdigital-brand-guide", "02-ourdigital-blog", "03-ourdigital-journal", "04-ourdigital-research", "05-ourdigital-document", "06-ourdigital-designer", "07-ourdigital-ad-manager", "08-ourdigital-trainer", "09-ourdigital-backoffice", "10-ourdigital-skill-creator", ] for skill in expected_skills: skill_dir = self.skills_dir / skill has_desktop = (skill_dir / "desktop" / "SKILL.md").exists() has_code = (skill_dir / "code" / "SKILL.md").exists() if has_desktop and has_code: ok(f"{skill}") passed += 1 elif has_desktop or has_code: warn(f"{skill} (incomplete)") else: fail(f"{skill} (missing)") return passed def validate_permissions(self) -> int: """Check file permissions are secure.""" info("Checking permissions...") passed = 0 if self.env_file.exists(): mode = oct(self.env_file.stat().st_mode)[-3:] if mode in ('600', '400'): ok(f".env.ourdigital permissions ({mode})") passed += 1 else: warn(f".env.ourdigital permissions ({mode}, should be 600)") creds_dir = self.config_dir / "credentials" if creds_dir.exists(): mode = oct(creds_dir.stat().st_mode)[-3:] if mode in ('700', '500'): ok(f"credentials/ permissions ({mode})") passed += 1 else: warn(f"credentials/ permissions ({mode}, should be 700)") return passed def run(self) -> Dict[str, int]: """Run all validation checks.""" print(f"\n{Colors.CYAN}{Colors.BOLD}OurDigital Skills Installation Validator{Colors.END}\n") results = { "directories": self.validate_directories(), "files": self.validate_files(), "env_variables": self.validate_env_variables(), "python_env": self.validate_python_env(), "skills": self.validate_skills(), "permissions": self.validate_permissions(), } # Summary total_passed = sum(results.values()) total_checks = len(self.results) print(f"\n{Colors.CYAN}{'='*50}{Colors.END}") print(f"{Colors.BOLD}Summary:{Colors.END} {total_passed}/{total_checks} checks passed") if total_passed == total_checks: print(f"{Colors.GREEN}All checks passed! Installation is complete.{Colors.END}") else: failed = [r for r in self.results if not r[1]] print(f"{Colors.YELLOW}Some checks failed. Review above for details.{Colors.END}") if self.verbose: print(f"\n{Colors.BOLD}Failed checks:{Colors.END}") for name, _, fix in failed: print(f" - {name}") if fix: print(f" Fix: {fix}") return results def main(): parser = argparse.ArgumentParser(description="Validate OurDigital Skills installation") parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed output with fix suggestions") parser.add_argument("--json", action="store_true", help="Output results as JSON") args = parser.parse_args() validator = InstallationValidator(verbose=args.verbose) results = validator.run() if args.json: print(json.dumps(results, indent=2)) # Exit with error if any checks failed total_passed = sum(results.values()) total_checks = len(validator.results) sys.exit(0 if total_passed == total_checks else 1) if __name__ == "__main__": main()