feat: Add installation tool, Claude.ai export, and skill standardization (#1)
## 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>
This commit is contained in:
284
custom-skills/_ourdigital-shared/scripts/validate_install.py
Executable file
284
custom-skills/_ourdigital-shared/scripts/validate_install.py
Executable file
@@ -0,0 +1,284 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user