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,80 @@
# OurDigital Blog
Korean blog draft creation skill for blog.ourdigital.org.
## Purpose
Generate Korean blog posts with:
- OurDigital brand voice compliance
- SEO-optimized metadata
- Ghost CMS format output
- Ulysses export support
## Activation
Only activates with "ourdigital" keyword:
- "ourdigital 블로그 써줘"
- "ourdigital blog 초안"
- "ourdigital 한국어 포스트 [주제]"
## Channel Profile
| Field | Value |
|-------|-------|
| URL | blog.ourdigital.org |
| Language | Korean (전문용어 영문 병기) |
| Tone | Analytical & Personal |
| Length | 1,500-3,000자 |
## Structure
```
02-ourdigital-blog/
├── code/SKILL.md
├── desktop/SKILL.md
├── shared/
│ ├── references/
│ │ └── blog-style-guide.md
│ ├── templates/
│ │ └── blog-template.md
│ └── scripts/
│ └── ghost_publish.py
├── docs/
│ └── CHANGELOG.md
└── README.md
```
## Usage
### Draft a Blog Post
```
User: ourdigital 블로그 써줘 - AI 마케팅 자동화에 대해
Claude: [Asks clarifying questions, generates draft]
```
### Publish to Ghost
```bash
python shared/scripts/ghost_publish.py --file post.md --draft
```
## Writing Style
- **철학-기술 융합**: 기술 분석 + 인간적 함의
- **역설 활용**: 긴장/모순으로 논증
- **수사적 질문**: 독자 참여 유도
- **전문용어 병기**: 한글(영문)
## Environment
Requires `~/.env.ourdigital`:
```
GHOST_BLOG_URL=https://blog.ourdigital.org
GHOST_BLOG_ADMIN_KEY=<admin_key>
```
## Version
- Current: 1.0.0
- Author: OurDigital

View File

@@ -0,0 +1,147 @@
---
name: ourdigital-blog
description: |
Korean blog drafting for blog.ourdigital.org.
Activated with "ourdigital" keyword.
Triggers:
- "ourdigital 블로그", "ourdigital blog"
- "ourdigital 한국어 포스트"
Features:
- Korean blog draft generation
- SEO metadata creation
- Ghost CMS export
- Ulysses integration
version: "1.0"
author: OurDigital
environment: Code
---
# OurDigital Blog (Code)
Korean blog draft creation with Ghost CMS integration.
## Activation
Only with "ourdigital" keyword:
- "ourdigital 블로그 써줘"
- "ourdigital blog 초안"
## Quick Start
```bash
# Export to Ulysses
python shared/scripts/export_blog.py --output ulysses
# Push to Ghost (draft)
python shared/scripts/ghost_publish.py --draft
```
## Channel Profile
```yaml
URL: blog.ourdigital.org
Language: Korean (영문 전문용어 병기)
Tone: Analytical, Educational
Platform: Ghost CMS
Length: 1,500-3,000자
Frequency: 주 1-2회
```
## Workflow
### 1. Topic Clarification
Ask:
1. 주제가 무엇인가요?
2. 타겟 독자는? (마케터/개발자/일반)
3. 깊이 수준? (개요/심층/실무가이드)
### 2. Draft Structure
```
1. 도입부 (Hook + Context)
2. 본론 (3-5 핵심 포인트)
- 주장 → 근거 → 함의
3. 결론 (Summary + 열린 질문)
```
### 3. Writing Style
| Element | Rule |
|---------|------|
| Tone | 분석적 + 개인적 |
| Language | 한글 기본, 전문용어 영문 병기 |
| Structure | 관찰 → 분석 → 함의 |
| Questions | 수사적 질문으로 참여 유도 |
### 4. SEO Metadata
Generate:
```yaml
title: [60자 이내]
meta_description: [155자 이내]
slug: english-url-slug
tags: [tag1, tag2, tag3]
```
### 5. Output
**Markdown format:**
```markdown
---
title: "제목"
meta_description: "설명"
slug: "url-slug"
tags: ["tag1", "tag2"]
---
# 제목
[본문]
```
## Ghost Integration
```python
# Environment variables
GHOST_BLOG_URL=https://blog.ourdigital.org
GHOST_BLOG_ADMIN_KEY=<from .env.ourdigital>
```
## Export Options
1. **Ulysses**`$ULYSSES_EXPORT_PATH`
2. **Ghost Draft** → API push
3. **Local**`./output/blog/`
## Brand Compliance
Verify before export:
- ✓ 분석적 톤
- ✓ 기술 + 인간적 함의
- ✓ 전문용어 영문 병기
- ✓ 1,500-3,000자
## Scripts
| Script | Purpose |
|--------|---------|
| `export_blog.py` | Export to various targets |
| `ghost_publish.py` | Push to Ghost CMS |
## File Structure
```
02-ourdigital-blog/
├── code/SKILL.md
├── desktop/SKILL.md
├── shared/
│ ├── references/blog-style-guide.md
│ ├── templates/blog-template.md
│ └── scripts/
│ ├── export_blog.py
│ └── ghost_publish.py
└── docs/CHANGELOG.md
```

View File

@@ -0,0 +1,145 @@
---
name: ourdigital-blog
description: |
Korean blog draft creation for blog.ourdigital.org.
Activated with "ourdigital" keyword for blog writing tasks.
Triggers (ourdigital or our prefix):
- "ourdigital blog", "our blog"
- "ourdigital 블로그", "our 블로그"
- "ourdigital 한국어 포스트", "our 한국어 포스트"
Features:
- Blog draft generation in Korean
- SEO metadata (title, description, slug)
- Ghost CMS format output
- Brand voice compliance
version: "1.0"
author: OurDigital
environment: Desktop
---
# OurDigital Blog
Korean blog draft creation skill for blog.ourdigital.org.
## Activation
Activate with "ourdigital" or "our" prefix:
- "ourdigital 블로그 써줘" / "our 블로그 써줘"
- "ourdigital blog draft" / "our blog draft"
- "our 한국어 포스트 [주제]"
## Channel Profile
| Field | Value |
|-------|-------|
| **URL** | blog.ourdigital.org |
| **Language** | Korean (전문용어 영문 병기) |
| **Tone** | Analytical & Personal, Educational |
| **Platform** | Ghost CMS |
| **Frequency** | 주 1-2회 |
| **Length** | 1,500-3,000자 |
## Workflow
### Phase 1: Topic Clarification
Ask clarifying questions (max 3):
1. **주제 확인**: 정확한 토픽이 무엇인가요?
2. **대상 독자**: 타겟 오디언스는? (마케터/개발자/경영진/일반)
3. **깊이 수준**: 개요 / 심층분석 / 실무가이드 중 어느 수준?
### Phase 2: Research (Optional)
If topic requires current information:
- Use `web_search` for latest trends/data
- Use `Notion:notion-search` for past research
- Reference internal documents if available
### Phase 3: Draft Generation
Generate blog draft following brand style:
**Structure:**
```
1. 도입부 (Hook + Context)
2. 본론 (3-5 핵심 포인트)
- 각 포인트: 주장 → 근거 → 함의
3. 결론 (Summary + 열린 질문)
```
**Writing Style:**
- 철학-기술 융합: 기술 분석 + 인간적 함의
- 역설 활용: 긴장/모순으로 논증 구조화
- 수사적 질문: 독자 참여 유도
- 우울한 낙관주의: 불안 인정, 절망 거부
**Language Rules:**
- 한글 기본, 전문용어는 영문 병기
- 예: "검색엔진최적화(SEO)"
- 문장: 복합문 허용, 상호연결된 개념 반영
- 단락: 관찰 → 분석 → 철학적 함의
### Phase 4: SEO Metadata
Generate metadata:
```yaml
title: [60자 이내, 키워드 포함]
meta_description: [155자 이내]
slug: [영문 URL slug]
tags: [3-5개 태그]
featured_image_prompt: [DALL-E/Midjourney 프롬프트]
```
### Phase 5: Output Format
**Markdown Output:**
```markdown
---
title: "포스트 제목"
meta_description: "메타 설명"
slug: "url-slug"
tags: ["tag1", "tag2"]
---
# 포스트 제목
[본문 내용]
---
*Originally drafted with Claude for OurDigital Blog*
```
## Ghost CMS Integration
Export options:
1. **Markdown file** → Ulysses → Ghost
2. **Direct API** → Ghost Admin API (if configured)
API endpoint: `GHOST_BLOG_URL` from environment
## Brand Compliance
Before finalizing, verify:
- [ ] 분석적 + 개인적 톤 유지
- [ ] 기술 내용에 인간적 함의 포함
- [ ] 수사적 질문으로 독자 참여
- [ ] 전문용어 영문 병기
- [ ] 1,500-3,000자 범위
## Quick Commands
| Command | Action |
|---------|--------|
| "ourdigital 블로그 [주제]" | Full workflow |
| "ourdigital blog SEO" | SEO metadata only |
| "ourdigital blog 편집" | Edit existing draft |
## References
- `shared/references/blog-style-guide.md` - Detailed writing guide
- `shared/templates/blog-template.md` - Post structure template
- `01-ourdigital-brand-guide` - Brand voice reference

View File

@@ -0,0 +1,24 @@
# Changelog
All notable changes to ourdigital-blog will be documented here.
## [1.0.0] - 2026-01-31
### Added
- Initial skill creation
- Desktop and Code versions
- Korean blog drafting workflow
- SEO metadata generation
- Ghost CMS integration script
- Blog style guide reference
- Post template
### Files
- `desktop/SKILL.md` - Desktop version
- `code/SKILL.md` - Code version
- `shared/references/blog-style-guide.md` - Writing guide
- `shared/templates/blog-template.md` - Post structure
- `shared/scripts/ghost_publish.py` - Ghost API integration
### Notion Ref
- (To be synced)

View File

@@ -0,0 +1,149 @@
# OurDigital Blog Style Guide
Detailed writing guidelines for blog.ourdigital.org.
## Channel Identity
| Field | Value |
|-------|-------|
| **Domain** | blog.ourdigital.org |
| **Tagline** | 사람, 디지털 그리고 문화 |
| **Language** | Korean (전문용어 영문 병기) |
| **Tone** | Analytical & Personal, Educational |
| **Target** | 교양 있는 일반 독자 - 기술의 문화적 영향에 호기심 있는 독자 |
## Writing Characteristics
### 1. 철학-기술 융합체
기술 분석과 실존적 질문을 자연스럽게 결합한다.
**Good Example:**
> AI가 우리의 업무를 대체할 수 있다는 사실은 분명하다. 그러나 더 중요한 질문은 "AI가 대체할 수 없는 것은 무엇인가?"이다.
**Bad Example:**
> AI는 업무 효율성을 높여준다. 다양한 분야에서 활용되고 있다.
### 2. 역설(Paradox) 활용
논증을 긴장과 모순 구조로 전개한다.
**Paradox Patterns:**
- "~하면서 동시에 ~하다"
- "~인 것 같지만 실은 ~이다"
- "~를 얻었지만 ~를 잃었다"
### 3. 수사적 질문
선언적 권위보다 질문을 통한 참여를 선호한다.
**Good:**
> 우리는 정말 데이터를 이해하고 있는 것일까?
**Bad:**
> 데이터를 이해하는 것이 중요하다.
### 4. 우울한 낙관주의
불안과 상실을 인정하되 절망하지 않는다.
**Tone Spectrum:**
```
비관 ←――――――――――――――――――→ 낙관
우울한 낙관주의
(여기에 위치)
```
## 문장 구조
| Element | Pattern |
|---------|---------|
| 문장 길이 | 긴 복합문 허용 - 상호연결된 개념 반영 |
| 단락 구조 | 관찰 → 분석 → 철학적 함의 |
| 근거 제시 | 역사적 사례 + 기술 명세 + 문화적 참조 |
| 결론 | 열린 결말, 답보다 질문 |
## 언어 규칙
### 전문용어 병기
```
✓ 검색엔진최적화(SEO)
✓ 핵심성과지표(KPI)
✓ 고객관계관리(CRM)
✗ SEO (첫 등장 시 한글 없이)
✗ 서치엔진옵티마이제이션
```
### 외래어 표기
- 영문 브랜드/제품명: 원어 유지 (Google, ChatGPT)
- 일반 외래어: 한글화 (데이터, 마케팅, 콘텐츠)
## 포스트 구조
### 도입부 (10-15%)
1. **Hook**: 독자의 관심을 끄는 질문/통계/역설
2. **Context**: 주제의 배경 설명
3. **Preview**: 글에서 다룰 내용 암시
### 본론 (70-80%)
3-5개의 핵심 포인트, 각각:
1. **주장**: 명확한 포인트 제시
2. **근거**: 데이터, 사례, 전문가 의견
3. **함의**: 이것이 의미하는 바
### 결론 (10-15%)
1. **Summary**: 핵심 내용 요약
2. **Reflection**: 더 넓은 맥락에서의 의미
3. **Open Question**: 독자가 생각할 질문
## SEO Guidelines
### Title (제목)
- 60자 이내
- 핵심 키워드 포함
- 호기심 유발 또는 가치 제안
**Patterns:**
- "[주제]의 역설: ~하면서 ~하는 시대"
- "[주제]를 다시 생각한다"
- "왜 [주제]가 중요한가"
### Meta Description
- 155자 이내
- 글의 핵심 가치 요약
- 클릭 유도 문구
### URL Slug
- 영문 소문자
- 하이픈으로 구분
- 3-5 단어
## Content Calendar
| Category | Frequency | Example Topics |
|----------|-----------|----------------|
| SEO/마케팅 | 주 1회 | Technical SEO, AEO, 콘텐츠 전략 |
| 데이터 분석 | 격주 | GA4, BigQuery, 대시보드 |
| AI/기술 트렌드 | 월 2회 | LLM, 자동화, 마케팅 AI |
| 인사이트/에세이 | 월 1회 | 디지털 문화, 세대론, 직업의 미래 |
## Quality Checklist
Before publishing:
- [ ] 제목이 60자 이내인가?
- [ ] 메타 설명이 155자 이내인가?
- [ ] 전문용어에 영문이 병기되었는가?
- [ ] 수사적 질문이 포함되었는가?
- [ ] 기술 내용에 인간적 함의가 있는가?
- [ ] 결론이 열린 질문으로 끝나는가?
- [ ] 1,500-3,000자 범위인가?

View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python3
"""
Ghost CMS Publisher for OurDigital Blog
Publishes markdown content to Ghost CMS via Admin API.
Usage:
python ghost_publish.py --file post.md --draft
python ghost_publish.py --file post.md --publish
"""
import argparse
import os
import re
import jwt
import time
import requests
from datetime import datetime
from pathlib import Path
from dotenv import load_dotenv
# Load environment variables
load_dotenv(os.path.expanduser("~/.env.ourdigital"))
def create_ghost_token(admin_key: str) -> str:
"""Create JWT token for Ghost Admin API."""
key_id, secret = admin_key.split(":")
iat = int(time.time())
header = {"alg": "HS256", "typ": "JWT", "kid": key_id}
payload = {
"iat": iat,
"exp": iat + 5 * 60, # 5 minutes
"aud": "/admin/"
}
token = jwt.encode(payload, bytes.fromhex(secret), algorithm="HS256", headers=header)
return token
def parse_frontmatter(content: str) -> tuple[dict, str]:
"""Parse YAML frontmatter from markdown content."""
import yaml
pattern = r'^---\s*\n(.*?)\n---\s*\n(.*)$'
match = re.match(pattern, content, re.DOTALL)
if match:
frontmatter = yaml.safe_load(match.group(1))
body = match.group(2)
return frontmatter, body
return {}, content
def publish_to_ghost(
file_path: str,
ghost_url: str,
admin_key: str,
draft: bool = True
) -> dict:
"""Publish markdown file to Ghost CMS."""
# Read and parse file
content = Path(file_path).read_text(encoding="utf-8")
frontmatter, body = parse_frontmatter(content)
# Create JWT token
token = create_ghost_token(admin_key)
# Prepare post data
post_data = {
"posts": [{
"title": frontmatter.get("title", "Untitled"),
"slug": frontmatter.get("slug"),
"html": markdown_to_html(body),
"meta_description": frontmatter.get("meta_description", ""),
"tags": [{"name": tag} for tag in frontmatter.get("tags", [])],
"status": "draft" if draft else "published",
"authors": [{"email": "andrew.yim@ourdigital.org"}]
}]
}
# Add feature image if provided
if frontmatter.get("featured_image"):
post_data["posts"][0]["feature_image"] = frontmatter["featured_image"]
# Make API request
api_url = f"{ghost_url}/ghost/api/admin/posts/"
headers = {
"Authorization": f"Ghost {token}",
"Content-Type": "application/json"
}
response = requests.post(api_url, json=post_data, headers=headers)
response.raise_for_status()
return response.json()
def markdown_to_html(markdown_text: str) -> str:
"""Convert markdown to HTML."""
try:
import markdown
return markdown.markdown(
markdown_text,
extensions=["tables", "fenced_code", "codehilite"]
)
except ImportError:
# Basic conversion if markdown library not available
html = markdown_text.replace("\n\n", "</p><p>")
html = f"<p>{html}</p>"
return html
def main():
parser = argparse.ArgumentParser(description="Publish to Ghost CMS")
parser.add_argument("--file", required=True, help="Markdown file to publish")
parser.add_argument("--draft", action="store_true", help="Publish as draft")
parser.add_argument("--publish", action="store_true", help="Publish immediately")
parser.add_argument("--channel", default="blog",
choices=["blog", "journal", "ourstory"],
help="Ghost channel to publish to")
args = parser.parse_args()
# Get credentials based on channel
channel_map = {
"blog": ("GHOST_BLOG_URL", "GHOST_BLOG_ADMIN_KEY"),
"journal": ("GHOST_JOURNAL_URL", "GHOST_JOURNAL_ADMIN_KEY"),
"ourstory": ("GHOST_OURSTORY_URL", "GHOST_OURSTORY_ADMIN_KEY")
}
url_var, key_var = channel_map[args.channel]
ghost_url = os.getenv(url_var)
admin_key = os.getenv(key_var)
if not ghost_url or not admin_key:
print(f"Error: Missing credentials for {args.channel}")
print(f"Set {url_var} and {key_var} in ~/.env.ourdigital")
return 1
try:
result = publish_to_ghost(
args.file,
ghost_url,
admin_key,
draft=not args.publish
)
post = result["posts"][0]
status = "Draft" if not args.publish else "Published"
print(f"{status}: {post['title']}")
print(f"URL: {ghost_url}/{post['slug']}/")
print(f"Edit: {ghost_url}/ghost/#/editor/post/{post['id']}")
return 0
except Exception as e:
print(f"Error: {e}")
return 1
if __name__ == "__main__":
exit(main())

View File

@@ -0,0 +1,70 @@
---
title: "{제목}"
meta_description: "{155자 이내 메타 설명}"
slug: "{english-url-slug}"
tags: ["{tag1}", "{tag2}", "{tag3}"]
author: "Andrew Yim"
date: "{YYYY-MM-DD}"
featured_image: "{이미지 URL 또는 프롬프트}"
---
# {제목}
{도입부: Hook으로 시작. 독자의 관심을 끄는 질문, 통계, 또는 역설로 시작한다.}
{Context: 주제의 배경을 설명하고, 왜 지금 이 주제가 중요한지 언급한다.}
{Preview: 이 글에서 다룰 핵심 내용을 암시한다.}
---
## {첫 번째 핵심 포인트 제목}
{주장: 명확한 포인트를 제시한다.}
{근거: 데이터, 사례, 또는 전문가 의견으로 뒷받침한다.}
> {인용문이나 통계가 있다면 여기에}
{함의: 이것이 독자에게 의미하는 바를 설명한다.}
## {두 번째 핵심 포인트 제목}
{주장}
{근거}
{함의}
## {세 번째 핵심 포인트 제목}
{주장}
{근거}
{함의}
---
## 마치며
{Summary: 핵심 내용을 1-2문장으로 요약한다.}
{Reflection: 더 넓은 맥락에서 이 주제가 갖는 의미를 성찰한다.}
{Open Question: 독자가 계속 생각할 수 있는 열린 질문으로 마무리한다.}
> {마지막 수사적 질문}
---
*이 글은 [OurDigital Blog](https://blog.ourdigital.org)에 게재된 콘텐츠입니다.*
<!--
SEO Checklist:
- [ ] 제목 60자 이내
- [ ] 메타 설명 155자 이내
- [ ] 전문용어 영문 병기
- [ ] 수사적 질문 포함
- [ ] 1,500-3,000자
-->