Initial commit: moreminimore-service content pipeline

- 9 mm* skills (orchestrator, article, social, publish, analytics)
- 6 dependency skills (content-writer, geo-optimizer, etc.)
- 3 analytics scripts (GSC, Google Ads, Meta Ads)
- Config template + setup guide
- SOUL-MM.md persona extension
- OrbitOS integration reference
This commit is contained in:
Kunthawat Greethong
2026-07-02 10:01:53 +07:00
commit 7b5af16f6d
36 changed files with 7803 additions and 0 deletions

View File

@@ -0,0 +1,275 @@
---
name: mm-blog-categories
description: >
Fetch, cache, and manage blog categories from Astro or WordPress websites.
Caches categories in clients-config.json to avoid repeated API calls.
Used by mm-content-writer to ensure articles use existing categories.
Use when: "ดึง categories", "blog categories", "fetch categories",
"refresh categories", "categories ของเว็บ", "update category cache".
---
# mm-blog-categories
Manages blog categories for moreminimore content pipeline.
Fetches from website (Astro or WordPress), caches in clients-config.json.
## When This Must Trigger
- "ดึง categories", "fetch categories", "blog categories"
- "refresh categories", "update category cache"
- "categories ของเว็บ", "มี category อะไรบ้าง"
- Called internally by mm-content-writer before assigning categories
## Config Structure
Categories are cached per-client in `~/vault/99_System/clients-config.json`:
```json
{
"id": "client-id",
"website": {
"type": "wordpress",
"url": "https://example.com",
"categories": [
{ "id": 1, "name": "Technology", "slug": "technology", "count": 12 },
{ "id": 2, "name": "Marketing", "slug": "marketing", "count": 8 }
],
"categories_cached_at": "2026-06-30T10:00:00Z"
}
}
```
For Astro:
```json
{
"website": {
"type": "astro",
"repo": "git.moreminimore.com/kunthawat/site",
"categories": [
{ "name": "Technology", "slug": "technology" },
{ "name": "Marketing", "slug": "marketing" }
],
"categories_cached_at": "2026-06-30T10:00:00Z"
}
}
```
## Process
### Step 1: Determine client
If called directly, ask which client:
```
ดึง categories ของเว็บไหน?
1. [client-1] — [website type]: [url]
2. [client-2] — [website type]: [url]
...
```
If called by mm-content-writer, use the `related_client` from the article.
### Step 2: Check cache
Read `clients-config.json`. Check if `categories` and `categories_cached_at` exist.
**Cache is fresh** if `categories_cached_at` is less than 7 days old:
```python
from datetime import datetime, timezone
cached_at = client['website'].get('categories_cached_at')
if cached_at:
cached_dt = datetime.fromisoformat(cached_at.replace('Z', '+00:00'))
age_days = (datetime.now(timezone.utc) - cached_dt).days
if age_days < 7:
# Cache is fresh, use it
return client['website']['categories']
```
**Cache is stale or missing** → fetch from website.
### Step 3a: Fetch from WordPress
```bash
WP_URL="<url from config>"
```
```bash
# Fetch all categories
curl -s "$WP_URL/wp-json/wp/v2/categories?per_page=100&_fields=id,name,slug,count" | python3 -c "
import json, sys
cats = json.load(sys.stdin)
for c in cats:
print(json.dumps({
'id': c['id'],
'name': c['name'],
'slug': c['slug'],
'count': c.get('count', 0)
}))
"
```
Handle pagination if > 100 categories:
```bash
# Check total pages from response headers
curl -sI "$WP_URL/wp-json/wp/v2/categories?per_page=100" | grep -i 'x-wp-totalpages'
```
### Step 3b: Fetch from Astro
Astro categories are defined in content frontmatter. Need to scan the repo:
```bash
REPO="<repo from config>"
CLONE_DIR="/tmp/astro-categories-$(date +%s)"
```
1. Clone or pull the repo:
```bash
git clone --depth 1 "https://$REPO" "$CLONE_DIR" 2>/dev/null || \
git clone --depth 1 "git@$REPO" "$CLONE_DIR"
```
2. Scan blog posts for unique categories:
```python
import os, re, yaml
blog_dir = os.path.join(CLONE_DIR, "src/content/blog")
categories = set()
for root, dirs, files in os.walk(blog_dir):
for f in files:
if not f.endswith(('.md', '.mdx')):
continue
path = os.path.join(root, f)
content = open(path).read()
# Extract frontmatter
match = re.match(r'^---\s*\n(.*?)\n---', content, re.DOTALL)
if match:
try:
fm = yaml.safe_load(match.group(1))
if fm and 'categories' in fm:
cats = fm['categories']
if isinstance(cats, list):
categories.update(cats)
elif isinstance(cats, str):
categories.add(cats)
if fm and 'category' in fm:
categories.add(fm['category'])
except:
continue
# Convert to list
result = [{'name': c, 'slug': c.lower().replace(' ', '-')} for c in sorted(categories)]
```
3. Clean up:
```bash
rm -rf "$CLONE_DIR"
```
### Step 4: Update cache
Write fetched categories back to `clients-config.json`:
```python
import json
from datetime import datetime, timezone
config_path = os.path.expanduser("~/vault/99_System/clients-config.json")
config = json.load(open(config_path))
for client in config['clients']:
if client['id'] == client_id:
client['website']['categories'] = fetched_categories
client['website']['categories_cached_at'] = datetime.now(timezone.utc).isoformat() + 'Z'
break
json.dump(config, open(config_path, 'w'), indent=2, ensure_ascii=False)
```
### Step 5: Present
```
📂 Categories สำหรับ [client_name] ([website_type]):
| # | Category | Slug | Posts |
|---|----------|------|-------|
| 1 | Technology | technology | 12 |
| 2 | Marketing | marketing | 8 |
| 3 | Business | business | 5 |
✅ Cache updated: categories_cached_at = [timestamp]
```
## Commands
### Show categories (from cache or fetch)
```
mm categories [client-id]
```
### Force refresh
```
mm categories refresh [client-id]
```
### Add new category (to website)
```
mm categories add [client-id] "[category name]"
```
- WordPress: POST to `/wp-json/wp/v2/categories`
- Astro: manual (add to frontmatter convention doc)
## Integration with mm-content-writer
When mm-content-writer assigns categories to an article:
1. Call mm-blog-categories to get categories (uses cache if fresh)
2. Present categories to user or auto-select based on article topic
3. If user wants a new category:
- WordPress: create via API, update cache
- Astro: add to convention, update cache
4. Use exact category name/slug from cache (no guessing)
## Error Handling
If WordPress API fails:
```
❌ ไม่สามารถดึง categories จาก WordPress ได้
สาเหตุที่เป็นไปได้:
- REST API ถูกปิดใช้งาน → ตรวจสอบ wp-admin → Settings → Permalinks
- URL ไม่ถูกต้อง → ตรวจสอบ clients-config.json
- Authentication required → บางเว็บต้องใช้ Application Password
ต้องการ:
1. ลองใหม่
2. เพิ่ม categories ด้วยตนเอง
3. ข้าม
```
If Astro clone fails:
```
❌ ไม่สามารถ clone Astro repo ได้
สาเหตุที่เป็นไปได้:
- Git credentials ไม่ถูกต้อง
- Repo URL ไม่ถูกต้อง → ตรวจสอบ clients-config.json
- Network error
ต้องการ:
1. ลองใหม่
2. เพิ่ม categories ด้วยตนเอง
3. ข้าม
```
## Notes
- Cache expires after 7 days (configurable)
- WordPress categories include post count; Astro categories don't
- For Astro, categories are inferred from frontmatter — there's no central registry
- The skill handles both website types transparently based on `website.type`
- mm-content-writer should call this skill before assigning categories