feat: Add OurDigital custom skills package (10 skills)

Complete implementation of OurDigital skills with dual-platform support
(Claude Desktop + Claude Code) following standardized structure.

Skills created:
- 01-ourdigital-brand-guide: Brand reference & style guidelines
- 02-ourdigital-blog: Korean blog drafts (blog.ourdigital.org)
- 03-ourdigital-journal: English essays (journal.ourdigital.org)
- 04-ourdigital-research: Research prompts & workflows
- 05-ourdigital-document: Notion-to-presentation pipeline
- 06-ourdigital-designer: Visual/image prompt generation
- 07-ourdigital-ad-manager: Ad copywriting & keyword research
- 08-ourdigital-trainer: Training materials & workshop planning
- 09-ourdigital-backoffice: Quotes, proposals, cost analysis
- 10-ourdigital-skill-creator: Meta skill for creating new skills

Features:
- YAML frontmatter with "ourdigital" or "our" prefix triggers
- Standardized directory structure (code/, desktop/, shared/, docs/)
- Shared environment setup (_ourdigital-shared/)
- Comprehensive reference documentation
- Cross-skill integration support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-31 16:50:17 +07:00
parent 7d20abe811
commit 0bc24d00b9
169 changed files with 9970 additions and 741 deletions

View File

@@ -0,0 +1,203 @@
# OurDigital Skill Patterns
Common patterns and best practices for OurDigital Claude Skills.
## Directory Structure Pattern
```
XX-ourdigital-{name}/
├── code/
│ └── SKILL.md # Claude Code version
├── desktop/
│ └── SKILL.md # Claude Desktop version
├── shared/
│ ├── references/ # Documentation
│ ├── templates/ # Reusable templates
│ └── scripts/ # Utility scripts
├── docs/
│ ├── CHANGELOG.md # Version history
│ └── logs/ # Update logs
└── README.md # Overview
```
## YAML Frontmatter Pattern
```yaml
---
name: ourdigital-{skill-name}
description: |
[One-line purpose statement]
Activated with "ourdigital" keyword.
Triggers:
- "ourdigital {keyword1}", "ourdigital {keyword2}"
- "ourdigital-{skill-name} {action}"
Features:
- Feature 1 description
- Feature 2 description
version: "1.0"
author: OurDigital
environment: Desktop | Code | Both
dependencies:
python: ">=3.11"
packages: ["package1", "package2"]
---
```
## Workflow Patterns
### Linear Workflow
```
Phase 1 → Phase 2 → Phase 3 → Output
```
Example: ourdigital-blog
```
Topic Input → Research → Draft → SEO Meta → Export
```
### Interactive Q&A Workflow
```
Ask Q1 → Get A1 → Ask Q2 → Get A2 → Process → Output
```
Example: ourdigital-skill-creator
```
Purpose? → Triggers? → Tools? → Output? → Generate
```
### Multi-Output Workflow
```
Input → Process → Output A
└→ Output B
└→ Output C
```
Example: ourdigital-designer
```
Brief → Analyze → DALL-E prompt
└→ Midjourney prompt
└→ Figma spec
```
## Trigger Patterns
### Korean + English Pairs
```yaml
Triggers:
- "ourdigital 블로그", "ourdigital blog"
- "ourdigital 작성", "ourdigital write"
```
### Action-Based
```yaml
Triggers:
- "ourdigital create {type}"
- "ourdigital generate {type}"
- "ourdigital check {type}"
```
### Skill Name Direct
```yaml
Triggers:
- "ourdigital-blog 초안"
- "ourdigital-designer 프롬프트"
```
## Output Patterns
### File Export
```markdown
## Output
Export to:
- iCloud/Ulysses folder: `.md` files
- Google Drive: `.docx`, `.pptx`
- Local: `./output/`
```
### Notion Integration
```markdown
## Notion Save
Database: Working with AI
Properties:
- Name: [Output title]
- Status: Done
- AI used: Claude Code
```
### Artifact Generation
```markdown
## Artifact
Generate HTML artifact with:
- Structured sections
- Styled formatting
- Export options
```
## Reference Patterns
### Style Guide Reference
```markdown
See `shared/references/style-guide.md` for:
- Tone and voice
- Terminology
- Formatting rules
```
### API Config Reference
```markdown
See `shared/references/api-config.md` for:
- Endpoint URLs
- Authentication
- Rate limits
```
### Template Reference
```markdown
Use `shared/templates/{template}.md` as base structure.
```
## Version Numbering
```
Major.Minor.Patch
1.0.0 - Initial release
1.1.0 - New feature added
1.1.1 - Bug fix
2.0.0 - Breaking change
```
## Changelog Pattern
```markdown
## [1.1.0] - 2026-01-31
### Added
- New feature X
### Changed
- Updated behavior Y
### Fixed
- Bug in Z
### Notion Ref
- https://notion.so/page-id
```

View File

@@ -0,0 +1,105 @@
# Skill Suitability Criteria
Evaluation criteria for determining if a need should become an OurDigital Claude Skill.
## Core Criteria
### 1. Clear Trigger (Required)
The skill must have an unambiguous activation condition.
**OurDigital Rule**: Must include "ourdigital" keyword to avoid conflicts with other skills.
| Good Triggers | Bad Triggers |
|---------------|--------------|
| "ourdigital 블로그 작성" | "블로그 써줘" |
| "ourdigital research prompt" | "research this" |
| "ourdigital 스킬 만들기" | "make a skill" |
### 2. Focused Scope
Each skill should do 1-3 things well (Unix philosophy).
**Word Limit**: SKILL.md body should be 800-1,200 words.
| Good Scope | Over-scoped |
|------------|-------------|
| Blog draft generation + SEO meta | Blog + Social + Email + Analytics |
| Visual prompt creation | Design + Development + Deployment |
### 3. Reusable Resources
Skill should bundle valuable resources worth maintaining.
| Resource Type | Examples |
|---------------|----------|
| Scripts | `export_to_ulysses.py`, `sync_notion.py` |
| Templates | `blog-template.md`, `research-plan.md` |
| References | `style-guide.md`, `api-config.md` |
| Assets | `brand-colors.json`, `prompt-library.md` |
### 4. Domain Knowledge
Skill should encode knowledge Claude doesn't have natively.
| Good Domain Knowledge | Not Skill-Worthy |
|-----------------------|------------------|
| OurDigital brand voice rules | Generic writing tips |
| Ghost CMS API specifics | Standard markdown |
| Jamie clinic terminology | Common Korean |
### 5. Clear Boundaries
Skill should not overlap with existing skills.
**Check Against**:
- Other ourdigital-* skills
- Existing custom-skills (SEO, GTM, Jamie, etc.)
- Built-in Claude capabilities
## Scoring Matrix
| Criterion | Weight | Score (0-2) |
|-----------|--------|-------------|
| Clear trigger | 25% | |
| Focused scope | 20% | |
| Reusable resources | 20% | |
| Domain knowledge | 20% | |
| Clear boundaries | 15% | |
**Threshold**: Score ≥ 1.2 (60%) to proceed with skill creation.
## Decision Flow
```
Is there a clear "ourdigital" trigger?
├── No → Suggest using generic Claude or other skill
└── Yes ↓
Is scope focused (1-3 functions)?
├── No → Split into multiple skills
└── Yes ↓
Are there reusable resources?
├── No → Consider if prompt is sufficient
└── Yes ↓
Does it encode domain knowledge?
├── No → May not need a skill
└── Yes ↓
No overlap with existing skills?
├── No → Merge with existing or differentiate
└── Yes → CREATE THE SKILL
```
## Alternatives to Skills
If criteria not met, consider:
| Alternative | When to Use |
|-------------|-------------|
| CLAUDE.md directive | Project-specific instructions |
| Prompt library | Reusable prompts without structure |
| MCP tool | API integration without workflow |
| Existing skill extension | Adding to current skill |

View File

@@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""
Initialize a new OurDigital skill with standard directory structure.
Usage:
python init_skill.py {skill-name} --number XX
python init_skill.py blog --number 02
"""
import argparse
import os
from pathlib import Path
from datetime import datetime
SKILL_TEMPLATE = '''---
name: ourdigital-{name}
description: |
{description}
Activated with "ourdigital" keyword.
Triggers:
- "ourdigital {name}", "ourdigital {trigger}"
Features:
- {feature}
version: "1.0"
author: OurDigital
environment: {environment}
---
# OurDigital {title}
{description}
## Activation
Only activate when user includes "ourdigital" keyword:
- "ourdigital {trigger}"
## Workflow
### Phase 1: Input
Gather requirements from user.
### Phase 2: Process
Execute core functionality.
### Phase 3: Output
Deliver results.
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital {name}" | Main action |
'''
CHANGELOG_TEMPLATE = '''# Changelog
All notable changes to this skill will be documented in this file.
## [1.0.0] - {date}
### Added
- Initial skill creation
- Desktop and Code versions
- Basic workflow implementation
### Notion Ref
- (To be added after sync)
'''
README_TEMPLATE = '''# OurDigital {title}
{description}
## Installation
This skill is part of the OurDigital custom skills package.
## Usage
### Claude Desktop
The skill activates when you mention "ourdigital {name}".
### Claude Code
```bash
# The skill activates via SKILL.md directive
```
## Structure
```
{number}-ourdigital-{name}/
├── code/SKILL.md
├── desktop/SKILL.md
├── shared/
│ └── references/
├── docs/
│ └── CHANGELOG.md
└── README.md
```
## Version
- Current: 1.0.0
- Author: OurDigital
'''
def create_skill(name: str, number: str, description: str = "", environment: str = "Both"):
"""Create a new skill directory structure."""
base_path = Path(__file__).parent.parent.parent.parent
skill_dir = base_path / f"{number}-ourdigital-{name}"
if skill_dir.exists():
print(f"Skill directory already exists: {skill_dir}")
return False
# Create directories
dirs = [
skill_dir / "code",
skill_dir / "desktop",
skill_dir / "shared" / "references",
skill_dir / "shared" / "templates",
skill_dir / "shared" / "scripts",
skill_dir / "docs" / "logs",
]
for d in dirs:
d.mkdir(parents=True, exist_ok=True)
print(f"Created: {d}")
# Title case for display
title = name.replace("-", " ").title()
trigger = name.replace("-", " ")
feature = f"Core {title} functionality"
date = datetime.now().strftime("%Y-%m-%d")
if not description:
description = f"OurDigital {title} skill for Claude."
# Create SKILL.md for desktop
desktop_skill = SKILL_TEMPLATE.format(
name=name,
title=title,
description=description,
trigger=trigger,
feature=feature,
environment="Desktop"
)
(skill_dir / "desktop" / "SKILL.md").write_text(desktop_skill)
print(f"Created: desktop/SKILL.md")
# Create SKILL.md for code
code_skill = SKILL_TEMPLATE.format(
name=name,
title=title,
description=description,
trigger=trigger,
feature=feature,
environment="Code"
)
(skill_dir / "code" / "SKILL.md").write_text(code_skill)
print(f"Created: code/SKILL.md")
# Create CHANGELOG.md
changelog = CHANGELOG_TEMPLATE.format(date=date)
(skill_dir / "docs" / "CHANGELOG.md").write_text(changelog)
print(f"Created: docs/CHANGELOG.md")
# Create README.md
readme = README_TEMPLATE.format(
title=title,
name=name,
number=number,
description=description
)
(skill_dir / "README.md").write_text(readme)
print(f"Created: README.md")
print(f"\nSkill created successfully: {skill_dir}")
return True
def main():
parser = argparse.ArgumentParser(description="Initialize a new OurDigital skill")
parser.add_argument("name", help="Skill name (e.g., 'blog', 'designer')")
parser.add_argument("--number", required=True, help="Skill number (e.g., '02', '07')")
parser.add_argument("--description", default="", help="Skill description")
parser.add_argument("--environment", default="Both",
choices=["Desktop", "Code", "Both"],
help="Target environment")
args = parser.parse_args()
success = create_skill(
name=args.name,
number=args.number,
description=args.description,
environment=args.environment
)
return 0 if success else 1
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""
Validate an OurDigital skill structure and content.
Usage:
python validate_skill.py {skill-directory}
python validate_skill.py 02-ourdigital-blog
"""
import argparse
import re
from pathlib import Path
class SkillValidator:
def __init__(self, skill_path: Path):
self.skill_path = skill_path
self.errors = []
self.warnings = []
def validate(self) -> bool:
"""Run all validations."""
self._check_directory_structure()
self._check_skill_files()
self._check_frontmatter()
self._check_content_length()
self._check_ourdigital_triggers()
return len(self.errors) == 0
def _check_directory_structure(self):
"""Verify required directories exist."""
required_dirs = [
"code",
"desktop",
]
recommended_dirs = [
"shared",
"docs",
]
for d in required_dirs:
if not (self.skill_path / d).is_dir():
self.errors.append(f"Missing required directory: {d}/")
for d in recommended_dirs:
if not (self.skill_path / d).is_dir():
self.warnings.append(f"Missing recommended directory: {d}/")
def _check_skill_files(self):
"""Verify SKILL.md files exist."""
skill_files = [
"code/SKILL.md",
"desktop/SKILL.md",
]
for f in skill_files:
if not (self.skill_path / f).is_file():
self.errors.append(f"Missing required file: {f}")
def _check_frontmatter(self):
"""Verify YAML frontmatter in SKILL.md files."""
for env in ["code", "desktop"]:
skill_file = self.skill_path / env / "SKILL.md"
if not skill_file.is_file():
continue
content = skill_file.read_text()
# Check for YAML frontmatter
if not content.startswith("---"):
self.errors.append(f"{env}/SKILL.md: Missing YAML frontmatter")
continue
# Check required fields
required_fields = ["name", "description", "version", "author", "environment"]
for field in required_fields:
if f"{field}:" not in content.split("---")[1]:
self.warnings.append(f"{env}/SKILL.md: Missing frontmatter field '{field}'")
def _check_content_length(self):
"""Verify SKILL.md body is within word limit."""
for env in ["code", "desktop"]:
skill_file = self.skill_path / env / "SKILL.md"
if not skill_file.is_file():
continue
content = skill_file.read_text()
# Extract body (after frontmatter)
parts = content.split("---")
if len(parts) >= 3:
body = "---".join(parts[2:])
word_count = len(body.split())
if word_count < 200:
self.warnings.append(
f"{env}/SKILL.md: Body too short ({word_count} words, recommended 800-1200)"
)
elif word_count > 1500:
self.warnings.append(
f"{env}/SKILL.md: Body too long ({word_count} words, recommended 800-1200)"
)
def _check_ourdigital_triggers(self):
"""Verify 'ourdigital' keyword in triggers."""
for env in ["code", "desktop"]:
skill_file = self.skill_path / env / "SKILL.md"
if not skill_file.is_file():
continue
content = skill_file.read_text().lower()
# Check for ourdigital in description/triggers
if "ourdigital" not in content:
self.errors.append(
f"{env}/SKILL.md: Missing 'ourdigital' keyword in triggers"
)
def report(self):
"""Print validation report."""
print(f"\nValidation Report: {self.skill_path.name}")
print("=" * 50)
if self.errors:
print(f"\nERRORS ({len(self.errors)}):")
for e in self.errors:
print(f" [X] {e}")
if self.warnings:
print(f"\nWARNINGS ({len(self.warnings)}):")
for w in self.warnings:
print(f" [!] {w}")
if not self.errors and not self.warnings:
print("\n All checks passed!")
print("\n" + "=" * 50)
status = "PASS" if not self.errors else "FAIL"
print(f"Status: {status}")
return not self.errors
def main():
parser = argparse.ArgumentParser(description="Validate an OurDigital skill")
parser.add_argument("skill_dir", help="Skill directory name or path")
args = parser.parse_args()
# Handle relative or absolute path
skill_path = Path(args.skill_dir)
if not skill_path.is_absolute():
# Try relative to custom-skills directory
base_path = Path(__file__).parent.parent.parent.parent
skill_path = base_path / args.skill_dir
if not skill_path.is_dir():
print(f"Error: Skill directory not found: {skill_path}")
return 1
validator = SkillValidator(skill_path)
validator.validate()
success = validator.report()
return 0 if success else 1
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,56 @@
---
name: ourdigital-{skill-name}
description: |
{Brief description of the skill's purpose}
Activated with "ourdigital" keyword.
Triggers:
- "ourdigital {trigger1}", "ourdigital {trigger2}"
- "ourdigital-{skill-name} {action}"
Features:
- {Feature 1}
- {Feature 2}
version: "1.0"
author: OurDigital
environment: {Desktop | Code | Both}
---
# OurDigital {Skill Name}
{One-paragraph description of what this skill does.}
## Activation
Only activate when user includes "ourdigital" keyword:
- "ourdigital {example trigger 1}"
- "ourdigital {example trigger 2}"
## Workflow
### Phase 1: {Phase Name}
{Description of first phase}
### Phase 2: {Phase Name}
{Description of second phase}
### Phase 3: {Phase Name}
{Description of third phase}
## Output
{Describe the expected outputs}
## References
- `shared/references/{file}.md` - {Description}
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital {cmd1}" | {Action 1} |
| "ourdigital {cmd2}" | {Action 2} |