## Summary - Add portable installation tool (`install.sh`) for cross-machine setup - Add Claude.ai export files with proper YAML frontmatter - Add multi-agent-guide v2.0 with consolidated framework template - Rename `00-claude-code-setting` → `00-our-settings-audit` (avoid reserved word) - Add YAML frontmatter to 25+ SKILL.md files for Claude Desktop compatibility ## Commits Included - `93f604a` feat: Add portable installation tool for cross-machine setup - `9b84104` feat: Add Claude.ai export for portable skill installation - `f7ab973` fix: Add YAML frontmatter to Claude.ai export files - `3fed49a` feat(multi-agent-guide): Add v2.0 with consolidated framework - `3be26ef` refactor: Rename settings-audit skill and add YAML frontmatter Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
285 lines
9.0 KiB
Python
Executable File
285 lines
9.0 KiB
Python
Executable File
#!/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()
|