Auto-sync from website-creator
This commit is contained in:
501
skills/seo-context/scripts/context_manager.py
Normal file
501
skills/seo-context/scripts/context_manager.py
Normal file
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Context Manager
|
||||
|
||||
Create, update, and manage per-project context files.
|
||||
Each website has its own context/ folder with brand voice, keywords, and guidelines.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
|
||||
class ContextManager:
|
||||
"""Manage per-project context files"""
|
||||
|
||||
def __init__(self, project_path: str):
|
||||
self.project_path = project_path
|
||||
self.context_path = os.path.join(project_path, 'context')
|
||||
|
||||
# Ensure context directory exists
|
||||
os.makedirs(self.context_path, exist_ok=True)
|
||||
|
||||
def create_context(self, industry: str = 'general', audience: str = 'Thai audience',
|
||||
formality: str = 'normal') -> Dict[str, str]:
|
||||
"""Create complete context structure for new project"""
|
||||
created_files = {}
|
||||
|
||||
# 1. brand-voice.md
|
||||
brand_voice_content = self._generate_brand_voice(industry, audience, formality)
|
||||
brand_voice_path = os.path.join(self.context_path, 'brand-voice.md')
|
||||
with open(brand_voice_path, 'w', encoding='utf-8') as f:
|
||||
f.write(brand_voice_content)
|
||||
created_files['brand-voice.md'] = brand_voice_path
|
||||
|
||||
# 2. target-keywords.md
|
||||
keywords_content = self._generate_target_keywords(industry)
|
||||
keywords_path = os.path.join(self.context_path, 'target-keywords.md')
|
||||
with open(keywords_path, 'w', encoding='utf-8') as f:
|
||||
f.write(keywords_content)
|
||||
created_files['target-keywords.md'] = keywords_path
|
||||
|
||||
# 3. seo-guidelines.md
|
||||
seo_guidelines = self._generate_seo_guidelines()
|
||||
seo_guidelines_path = os.path.join(self.context_path, 'seo-guidelines.md')
|
||||
with open(seo_guidelines_path, 'w', encoding='utf-8') as f:
|
||||
f.write(seo_guidelines)
|
||||
created_files['seo-guidelines.md'] = seo_guidelines_path
|
||||
|
||||
# 4. internal-links-map.md
|
||||
links_map = "# Internal Links Map\n\nAdd your priority pages here:\n\n## Homepage\n- URL: /\n- Priority: High\n\n## Key Pages\n- Add your key pages here...\n"
|
||||
links_map_path = os.path.join(self.context_path, 'internal-links-map.md')
|
||||
with open(links_map_path, 'w', encoding='utf-8') as f:
|
||||
f.write(links_map)
|
||||
created_files['internal-links-map.md'] = links_map_path
|
||||
|
||||
# 5. data-services.json
|
||||
data_services = {
|
||||
'ga4': {'enabled': False, 'property_id': '', 'credentials_path': ''},
|
||||
'gsc': {'enabled': False, 'site_url': '', 'credentials_path': ''},
|
||||
'dataforseo': {'enabled': False, 'login': '', 'password': ''},
|
||||
'umami': {'enabled': False, 'api_url': '', 'api_key': ''}
|
||||
}
|
||||
data_services_path = os.path.join(self.context_path, 'data-services.json')
|
||||
with open(data_services_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data_services, f, indent=2)
|
||||
created_files['data-services.json'] = data_services_path
|
||||
|
||||
# 6. style-guide.md
|
||||
style_guide = self._generate_style_guide()
|
||||
style_guide_path = os.path.join(self.context_path, 'style-guide.md')
|
||||
with open(style_guide_path, 'w', encoding='utf-8') as f:
|
||||
f.write(style_guide)
|
||||
created_files['style-guide.md'] = style_guide_path
|
||||
|
||||
return created_files
|
||||
|
||||
def _generate_brand_voice(self, industry: str, audience: str, formality: str) -> str:
|
||||
"""Generate brand-voice.md template"""
|
||||
formality_th = {
|
||||
'casual': 'กันเอง (Casual)',
|
||||
'normal': 'ปกติ (Normal)',
|
||||
'formal': 'เป็นทางการ (Formal)'
|
||||
}.get(formality, 'ปกติ (Normal)')
|
||||
|
||||
return f"""# Brand Voice & Messaging
|
||||
|
||||
**Industry:** {industry}
|
||||
**Target Audience:** {audience}
|
||||
**Default Formality:** {formality_th}
|
||||
**Created:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
|
||||
---
|
||||
|
||||
## Voice Pillars
|
||||
|
||||
### 1. เป็นกันเอง (Friendly)
|
||||
- **What it means**: พูดเหมือนเพื่อนช่วยเพื่อน ไม่ทางการเกินไป
|
||||
- **Example**: "มาเริ่มกันเลย! ไม่ต้องรอให้พร้อม 100%"
|
||||
- **Avoid**: ภาษาทางการแบบเอกสารราชการ
|
||||
|
||||
### 2. น่าเชื่อถือ (Trustworthy)
|
||||
- **What it means**: ให้ข้อมูลที่ถูกต้อง มีหลักฐานรองรับ
|
||||
- **Example**: "จากการทดสอบ เราพบว่า..."
|
||||
- **Avoid**: อ้างอิงไม่มีแหล่งที่มา
|
||||
|
||||
### 3. มีประโยชน์ (Helpful)
|
||||
- **What it means**: มุ่งให้ค่ากับผู้อ่าน ช่วยแก้ปัญหา
|
||||
- **Example**: "ทำตามขั้นตอนนี้ คุณจะได้..."
|
||||
- **Avoid**: ขายของเกินไปโดยไม่ให้คุณค่า
|
||||
|
||||
---
|
||||
|
||||
## Tone Guidelines
|
||||
|
||||
### General Tone
|
||||
|
||||
พูดแบบเพื่อนที่หวังดี อธิบายเรื่องยากให้ง่าย
|
||||
|
||||
### By Content Type
|
||||
|
||||
**How-To Guides**:
|
||||
- ใช้ภาษาง่ายๆ
|
||||
- เป็นขั้นตอน
|
||||
- มีตัวอย่างประกอบ
|
||||
|
||||
**Review Content**:
|
||||
- เปรียบเทียบตรงไปตรงมา
|
||||
- มีข้อมูลสนับสนุน
|
||||
- บอกข้อดีข้อเสีย
|
||||
|
||||
**News/Updates**:
|
||||
- กระชับ ได้ใจความ
|
||||
- เน้นข้อมูลสำคัญ
|
||||
- อัปเดตทันทีที่มีข้อมูลใหม่
|
||||
|
||||
---
|
||||
|
||||
## Formality Level
|
||||
|
||||
**Default**: {formality_th}
|
||||
|
||||
**Social Media**: กันเอง (Casual) - ใช้คำฟุ่มเฟือยได้บ้าง
|
||||
|
||||
**Blog**: ปกติ (Normal) - อ่านง่ายแต่ยังคงความน่าเชื่อถือ
|
||||
|
||||
**Product Pages**: ปกติถึงเป็นทางการเล็กน้อย - ให้ความน่าเชื่อถือ
|
||||
|
||||
---
|
||||
|
||||
## Messaging Framework
|
||||
|
||||
### Core Messages
|
||||
|
||||
1. **แก้ปัญหาจริง**: เน้นแก้ปัญหาที่ลูกค้าเจอจริง
|
||||
2. **ไม่ซับซ้อน**: อธิบายเรื่องยากให้ง่าย
|
||||
3. **น่าเชื่อถือ**: มีหลักฐาน ข้อมูลรองรับ
|
||||
|
||||
### Value Propositions
|
||||
|
||||
**For Beginners**: เริ่มต้นง่าย ไม่ต้องมีพื้นฐานก็ทำได้
|
||||
|
||||
**For Professionals**: เครื่องมือครบ จบในที่เดียว
|
||||
|
||||
---
|
||||
|
||||
## Writing Examples
|
||||
|
||||
### Excellent Voice ✅
|
||||
|
||||
"มาเริ่ม podcast กันเลย! ไม่ต้องรอให้พร้อม 100% แค่มีไอเดียดีๆ กับไมค์หนึ่งอัน คุณก็เริ่มต้นได้แล้ว ส่วนเรื่องเทคนิคที่เหลือ เราช่วยคุณเอง"
|
||||
|
||||
**Why this works**:
|
||||
- เป็นกันเอง
|
||||
- ให้กำลังใจ
|
||||
- ไม่ข่มขู่ด้วยความยาก
|
||||
|
||||
### Not Our Voice ❌
|
||||
|
||||
"การดำเนินการสร้าง podcast จำเป็นต้องมีการเตรียมการอย่างรอบคอบและใช้อุปกรณ์ที่มีคุณภาพสูง"
|
||||
|
||||
**Why this fails**:
|
||||
- เป็นทางการเกินไป
|
||||
- ดูน่ากลัว
|
||||
- ไม่เป็นมิตร
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
"""
|
||||
|
||||
def _generate_target_keywords(self, industry: str) -> str:
|
||||
"""Generate target-keywords.md template"""
|
||||
return f"""# Target Keywords
|
||||
|
||||
**Industry:** {industry}
|
||||
**Created:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
|
||||
---
|
||||
|
||||
## Primary Keyword Clusters
|
||||
|
||||
### Cluster 1: [Main Topic]
|
||||
|
||||
**Intent:** Commercial Investigation
|
||||
|
||||
**Keywords (Thai)**:
|
||||
- [Keyword 1]
|
||||
- [Keyword 2]
|
||||
- [Keyword 3]
|
||||
|
||||
**Keywords (English)**:
|
||||
- [Keyword 1]
|
||||
- [Keyword 2]
|
||||
- [Keyword 3]
|
||||
|
||||
**Search Volume:** TBD (research needed)
|
||||
|
||||
**Difficulty:** Medium
|
||||
|
||||
---
|
||||
|
||||
### Cluster 2: [Secondary Topic]
|
||||
|
||||
[Same structure]
|
||||
|
||||
---
|
||||
|
||||
## Keyword Mapping
|
||||
|
||||
| Keyword | Intent | Priority | Target URL |
|
||||
|---------|--------|----------|------------|
|
||||
| [keyword] | Commercial | High | /page |
|
||||
| [keyword] | Informational | Medium | /blog |
|
||||
|
||||
---
|
||||
|
||||
**Notes:**
|
||||
- Update keyword data from GSC monthly
|
||||
- Add new clusters as business expands
|
||||
- Track ranking performance
|
||||
|
||||
**Last Updated:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
"""
|
||||
|
||||
def _generate_seo_guidelines(self) -> str:
|
||||
"""Generate seo-guidelines.md"""
|
||||
return f"""# SEO Guidelines (Thai-Specific)
|
||||
|
||||
**Created:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
|
||||
---
|
||||
|
||||
## Content Requirements
|
||||
|
||||
### Word Count
|
||||
- **Thai:** 1,500-3,000 words
|
||||
- **English:** 2,000-3,000 words
|
||||
|
||||
### Keyword Density
|
||||
- **Thai:** 1.0-1.5%
|
||||
- **English:** 1.5-2.0%
|
||||
|
||||
### Readability
|
||||
- **Thai Grade Level:** ม.6-ม.12
|
||||
- **Avg Sentence Length:** 15-25 words (Thai)
|
||||
- **Formality:** Auto-detect from brand-voice.md
|
||||
|
||||
---
|
||||
|
||||
## Meta Elements
|
||||
|
||||
### Title Tag
|
||||
- **Length:** 50-60 characters
|
||||
- **Must include:** Primary keyword
|
||||
- **Format:** [Keyword]: [Benefit] | [Brand]
|
||||
|
||||
### Meta Description
|
||||
- **Length:** 150-160 characters
|
||||
- **Must include:** Keyword + CTA
|
||||
- **Format:** [Problem]? [Solution]. [CTA].
|
||||
|
||||
### URL Slug
|
||||
- **Format:** lowercase-with-hyphens
|
||||
- **Thai:** Keep Thai or use transliteration
|
||||
- **Max:** 5 words
|
||||
|
||||
---
|
||||
|
||||
## Content Structure
|
||||
|
||||
### Headings
|
||||
- **H1:** 1 per page, includes keyword
|
||||
- **H2:** 4-7 per article
|
||||
- **H3:** As needed for subsections
|
||||
|
||||
### Internal Links
|
||||
- **Minimum:** 3 per article
|
||||
- **Maximum:** 7 per article
|
||||
- **Anchor text:** Descriptive with keywords
|
||||
|
||||
### External Links
|
||||
- **Minimum:** 2 per article
|
||||
- **Authority sources only**
|
||||
- **No competitor links**
|
||||
|
||||
---
|
||||
|
||||
## Images
|
||||
|
||||
### Requirements
|
||||
- **Alt text:** Descriptive with keywords
|
||||
- **File names:** descriptive-name.jpg
|
||||
- **Compression:** WebP preferred
|
||||
- **Size:** Optimized for web
|
||||
|
||||
---
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
Before publishing:
|
||||
- [ ] Keyword in H1
|
||||
- [ ] Keyword in first 100 words
|
||||
- [ ] Keyword in 2+ H2s
|
||||
- [ ] Keyword density 1.0-1.5% (Thai)
|
||||
- [ ] 3-5 internal links
|
||||
- [ ] 2-3 external authority links
|
||||
- [ ] Meta title 50-60 chars
|
||||
- [ ] Meta description 150-160 chars
|
||||
- [ ] Images have alt text
|
||||
- [ ] Readability checked
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
"""
|
||||
|
||||
def _generate_style_guide(self) -> str:
|
||||
"""Generate style-guide.md"""
|
||||
return f"""# Writing Style Guide
|
||||
|
||||
**Created:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
|
||||
---
|
||||
|
||||
## General Principles
|
||||
|
||||
1. **Clear over clever** - ความชัดเจนสำคัญกว่าการเล่นคำ
|
||||
2. **Helpful over promotional** - ให้ค่ามากกว่าขาย
|
||||
3. **Conversational over formal** - พูดคุยมากกว่าทางการ
|
||||
|
||||
---
|
||||
|
||||
## Sentence Structure
|
||||
|
||||
### Thai Sentences
|
||||
- **Average:** 15-25 words
|
||||
- **Active voice:** 80%+
|
||||
- **Short paragraphs:** 2-4 sentences
|
||||
|
||||
### Formatting
|
||||
- **Use bullets:** For lists of 3+ items
|
||||
- **Use bold:** For key concepts
|
||||
- **Use white space:** Generously
|
||||
|
||||
---
|
||||
|
||||
## Word Choice
|
||||
|
||||
### Use This, Not That
|
||||
|
||||
| Say This | Not That |
|
||||
|----------|----------|
|
||||
| เริ่มเลย | ดำเนินการเริ่มต้น |
|
||||
| ง่ายมาก | ไม่มีความซับซ้อน whatsoever |
|
||||
| ช่วยคุณ | ให้ความช่วยเหลือแก่ท่าน |
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Good Introduction
|
||||
|
||||
"คุณกำลังมองหาวิธีเริ่มต้น podcast ใช่ไหม? บทความนี้จะบอกทุกอย่างที่ต้องรู้ ตั้งแต่การเลือกอุปกรณ์จนถึงการเผยแพร่"
|
||||
|
||||
**Why it works:**
|
||||
- ตรงประเด็น
|
||||
- บอกสิ่งที่ผู้อ่านจะได้
|
||||
- อ่านเข้าใจง่าย
|
||||
|
||||
---
|
||||
|
||||
## Thai-Specific Guidelines
|
||||
|
||||
### Particles
|
||||
- Use ครับ/ค่ะ appropriately
|
||||
- Don't overuse นะ, จ้ะ in formal content
|
||||
- Match formality level to content type
|
||||
|
||||
### Transliteration
|
||||
- Use consistent Thai spelling for English terms
|
||||
- Example: "podcast" = "พ็อดคาสท์" (not พอดแคสต์, พ็อดคาสต์)
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** {datetime.now().strftime('%Y-%m-%d')}
|
||||
"""
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point"""
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Manage per-project context files'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--action',
|
||||
choices=['create', 'analyze', 'update-keywords'],
|
||||
default='create',
|
||||
help='Action to perform'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--create',
|
||||
action='store_true',
|
||||
help='Create context files (shortcut for --action create)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--project', '-p',
|
||||
required=True,
|
||||
help='Path to project folder'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--industry', '-i',
|
||||
default='general',
|
||||
help='Industry (for create action)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--audience', '-a',
|
||||
default='Thai audience',
|
||||
help='Target audience (for create action)'
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
'--formality', '-f',
|
||||
choices=['casual', 'normal', 'formal'],
|
||||
default='normal',
|
||||
help='Formality level (for create action)'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Handle --create shortcut
|
||||
if args.create:
|
||||
args.action = 'create'
|
||||
|
||||
# Initialize manager
|
||||
print(f"\n📝 Context Manager")
|
||||
print(f"Project: {args.project}\n")
|
||||
|
||||
manager = ContextManager(args.project)
|
||||
|
||||
if args.action == 'create':
|
||||
print(f"Creating context files...")
|
||||
print(f"Industry: {args.industry}")
|
||||
print(f"Audience: {args.audience}")
|
||||
print(f"Formality: {args.formality}\n")
|
||||
|
||||
created = manager.create_context(args.industry, args.audience, args.formality)
|
||||
|
||||
print(f"\n✅ Context created successfully!")
|
||||
print(f"\n📁 Created files:")
|
||||
for filename, path in created.items():
|
||||
print(f" ✓ {filename}")
|
||||
|
||||
print(f"\n📍 Location: {manager.context_path}")
|
||||
print(f"\nNext steps:")
|
||||
print(f" 1. Customize brand-voice.md with your actual voice")
|
||||
print(f" 2. Add target keywords based on your research")
|
||||
print(f" 3. Configure analytics in data-services.json")
|
||||
print()
|
||||
|
||||
elif args.action == 'analyze':
|
||||
print("Content analysis not yet implemented.")
|
||||
print("This will analyze existing content and update context files.")
|
||||
print()
|
||||
|
||||
elif args.action == 'update-keywords':
|
||||
print("Keyword update not yet implemented.")
|
||||
print("This will update keywords from GSC data.")
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user