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:
Andrew Yim
2026-02-03 16:48:06 +07:00
committed by GitHub
parent 0bc24d00b9
commit b6a478e1df
72 changed files with 4770 additions and 803 deletions

View File

@@ -0,0 +1,158 @@
#!/usr/bin/env python3
"""
Export OurDigital Skills for Claude.ai Projects
Generates simplified skill files suitable for uploading to Claude.ai.
Usage:
python export_for_claude_ai.py # Export all skills
python export_for_claude_ai.py --bundle # Generate bundle only
python export_for_claude_ai.py --individual # Generate individual files only
"""
import os
import re
import argparse
from pathlib import Path
from typing import Optional
SCRIPT_DIR = Path(__file__).parent.parent
SKILLS_DIR = SCRIPT_DIR.parent
EXPORT_DIR = SCRIPT_DIR / "claude-ai-export"
INDIVIDUAL_DIR = EXPORT_DIR / "individual"
# (dir, name, desc, triggers)
SKILLS = [
("01-ourdigital-brand-guide", "Brand Guide", "brand standards and style",
'"our brand guide", "our 브랜드 가이드", "our 톤앤매너"'),
("02-ourdigital-blog", "Blog", "Korean blog drafts",
'"our blog", "our 블로그", "our 한국어 포스트"'),
("03-ourdigital-journal", "Journal", "English essays",
'"our journal", "our English essay", "our 영문 에세이"'),
("04-ourdigital-research", "Research", "research prompts",
'"our research", "our 리서치", "our deep research"'),
("05-ourdigital-document", "Document", "presentations",
'"our document", "our 발표자료", "our presentation"'),
("06-ourdigital-designer", "Designer", "image prompts",
'"our design", "our 썸네일", "our image prompt"'),
("07-ourdigital-ad-manager", "Ad Manager", "ad copywriting",
'"our ad copy", "our 광고 카피", "our keyword"'),
("08-ourdigital-trainer", "Trainer", "training materials",
'"our training", "our 워크샵", "our 교육"'),
("09-ourdigital-backoffice", "Backoffice", "business documents",
'"our 견적서", "our proposal", "our quote"'),
("10-ourdigital-skill-creator", "Skill Creator", "creating new skills",
'"our skill create", "our 스킬 만들기"'),
]
def extract_skill_content(skill_dir: str) -> Optional[str]:
"""Extract and simplify content from a skill's SKILL.md."""
desktop_path = SKILLS_DIR / skill_dir / "desktop" / "SKILL.md"
if not desktop_path.exists():
print(f" Warning: {desktop_path} not found")
return None
content = desktop_path.read_text()
# Remove YAML frontmatter
if content.startswith("---"):
parts = content.split("---", 2)
if len(parts) >= 3:
content = parts[2].strip()
# Extract key sections (first 150 lines or so)
lines = content.split("\n")
# Find main heading and take content
simplified = []
in_content = False
line_count = 0
max_lines = 120
for line in lines:
if line.startswith("# "):
in_content = True
if in_content:
simplified.append(line)
line_count += 1
if line_count >= max_lines:
simplified.append("\n[... see full documentation for more ...]")
break
return "\n".join(simplified)
def generate_individual_files():
"""Generate individual skill files for Claude.ai."""
INDIVIDUAL_DIR.mkdir(parents=True, exist_ok=True)
print("Generating individual skill files...")
for skill_dir, name, desc, triggers in SKILLS:
content = extract_skill_content(skill_dir)
if content:
# Create simplified header with YAML frontmatter
num = skill_dir.split("-")[0]
skill_name = name.lower().replace(' ', '-')
filename = f"{num}-{skill_name}.md"
header = f"""---
name: ourdigital-{skill_name}
description: |
{desc.capitalize()}.
Triggers (ourdigital or our prefix):
- {triggers}
---
# OurDigital {name}
**Purpose**: {desc.capitalize()}
**Triggers**: Use "our" or "ourdigital" prefix
---
"""
output_path = INDIVIDUAL_DIR / filename
output_path.write_text(header + content)
print(f" Created: {filename}")
def generate_bundle():
"""Generate combined bundle file."""
print("Generating bundle file...")
# Read existing bundle (already created manually with full content)
bundle_path = EXPORT_DIR / "ourdigital-skills-bundle.md"
if bundle_path.exists():
print(f" Bundle exists: {bundle_path}")
else:
print(f" Warning: Bundle not found at {bundle_path}")
def main():
parser = argparse.ArgumentParser(description="Export skills for Claude.ai")
parser.add_argument("--bundle", action="store_true", help="Generate bundle only")
parser.add_argument("--individual", action="store_true", help="Generate individual files only")
args = parser.parse_args()
print(f"Export directory: {EXPORT_DIR}\n")
if args.bundle:
generate_bundle()
elif args.individual:
generate_individual_files()
else:
generate_bundle()
generate_individual_files()
print("\nDone! Files ready for upload to Claude.ai Projects.")
print(f" Bundle: {EXPORT_DIR / 'ourdigital-skills-bundle.md'}")
print(f" Individual: {INDIVIDUAL_DIR}/")
if __name__ == "__main__":
main()

View 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()