Files
Kunthawat Greethong 7b5af16f6d 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
2026-07-02 10:01:53 +07:00

14 KiB
Raw Permalink Blame History

name, description
name description
mm-content-writer Content Stage: Takes a brief.md from Data Stage, writes an SEO-optimized article, optimizes for GEO, generates images (Nano Banana), waits for approval, then creates social posts (Facebook, X, IG) and saves everything to vault. Use when: "เขียนบทความ", "write article from brief", "content-writer-mm", "เริ่มเขียนบทความ", "สร้างบทความจาก brief".

mm-content-writer

The main content production skill. Takes a brief from Data Stage and produces a complete article + social posts + images, saved to vault.

When This Must Trigger

  • "เขียนบทความ", "write article from brief"
  • "content-writer-mm", "mm-content-writer"
  • "เริ่มเขียนบทความ", "สร้างบทความจาก brief"
  • "สร้าง content จาก brief"

Input

Read brief.md from ~/vault/60_Articles/<date>-<slug>/brief.md.

The brief contains: topic, audience, tone, angles, outline, research data, content requirements.

Process

Step 0: Determine client

Check if brief has related_client in frontmatter.

If not, ask which client:

บทความนี้สำหรับลูกค้าไหน?

1. [client-1] — [display_name]
2. [client-2] — [display_name]

Load client config from ~/vault/99_System/clients-config.json:

  • contact → used for CTA in ad copy only (not social posts)
  • website → used for categories

Step 1: Read the brief

# Find the most recent brief if not specified
ls -t ~/vault/60_Articles/*/brief.md 2>/dev/null | head -5
# Or search by client:
ls -t ~/vault/60_Articles/<client-id>/*/brief.md 2>/dev/null | head -5

Read the brief.md. Extract:

  • Topic, audience, tone, word count target
  • SEO keyword
  • Outline (H1, H2s, key points per section)
  • Research data (stats, quotes, examples)
  • Content requirements (images, FAQ, schema)

If multiple briefs exist, ask which one to work on.

Step 1.5: Load blog categories

Before writing, load the blog categories from the client's website to ensure the article uses the correct category:

  1. Load mm-blog-categories skill
  2. Call it with the related_client from the brief
  3. It will fetch categories (or use cache if fresh)
  4. Present categories to user or auto-select based on article topic

If user wants a new category:

  • WordPress: create via API using mm-blog-categories
  • Astro: add to convention, update cache

Use the exact category name/slug from the cache.

Step 2: Write the article

Write a complete SEO-optimized article following these rules:

Title (H1):

  • Hook-driven (number, contrarian claim, specific audience, curiosity gap)
  • Contains primary keyword (front-loaded)
  • Under 60 characters

Opening:

  • Hook paragraph — not throat-clearing ("In today's digital landscape...")
  • Directly addresses the search intent
  • First 100 words contain the primary keyword

Body:

  • Follow the outline from the brief
  • Minimum 1000 words (use brief's target if higher)
  • Short paragraphs (2-4 sentences)
  • Bullet lists for scannability
  • Bold key phrases
  • One idea per paragraph
  • Include specific examples, data, and quotes from the brief
  • Internal links to related vault notes (as wikilinks for now)

FAQ Section:

  • 3-5 questions targeting People Also Ask
  • Direct, concise answers

Structure:

# [Hook-driven title]

[Hook paragraph — 2-3 sentences that grab attention and contain primary keyword]

## Table of Contents
- [Section 1](#section-1)
- [Section 2](#section-2)
...

## [H2 — Section 1]
[Content with data from brief]

## [H2 — Section 2]
[Content with data from brief]

...

## FAQ
### [Question 1]
[Answer]

### [Question 2]
[Answer]

---
*Last updated: YYYY-MM-DD*

Step 3: GEO Optimize

Apply GEO optimization to the article:

  1. Front-load the answer — first 150 words directly answer the core question
  2. Evidence density:
    • ≥5 specific numbers with units
    • ≥1 external citation per 500 words
    • ≥2 direct quotes from named experts (from brief's research data)
    • ≥3 named entities (people, orgs, products)
  3. Structure for extraction:
    • TL;DR or Key Takeaways box near top
    • Comparison data → tables
    • Sequential steps → numbered lists
  4. Strip anti-patterns:
    • No keyword stuffing
    • No filler ("In today's digital landscape...")
    • No unsupported superlatives
    • No vague entities ("experts say")

Add a TL;DR section after the hook paragraph:

> **TL;DR:** [2-3 sentence summary of the article's main point and takeaway]

Step 4: Generate images

CRITICAL RULE: NEVER delegate image generation to subagents. image_generate is sequential and slow (15-30s per call). Subagents timeout before completing. Always generate images from the main agent, one image per tool call.

If a subagent DOES generate images and then times out, the URLs can still be recovered via the FAL API history endpoint (see references/image-generation-pitfalls.md section 1b), but this recovery process is slower than generating fresh. Prevent the problem by never delegating image generation.

CRITICAL RULE: Check FAL.ai billing balance BEFORE starting image generation. If balance is exhausted, tell the user and stop — don't generate partial sets.

Image cost budget: Each article needs 1 featured + 3 inline minimum = 4 images minimum. For N articles, expect 4N image_generate calls. Batch of 14 articles = 56+ calls.

Pre-generation checklist (MANDATORY before ANY image work)

  1. Check FAL API history FIRST — run curl -s --request GET --url 'https://api.fal.ai/v1/models/requests/by-endpoint?limit=100&sort_by=ended_at&expand=payloads' --header 'Authorization: Key <FAL_KEY>' | jq -r '.requests[] | select(.status == "COMPLETED") | .request.payload.json_output.images[].url' to see if previously-generated images can be reused before spending new credits
  2. Check disk — run find ~/vault/60_Articles -name "*.png" to inventory what already exists
  3. Count total images needed: N_articles × (1 featured + inline_count) minus what exists on disk
  4. Estimate credits: ~1 credit/image for Nano Banana Pro
  5. Check FAL.ai balance: trigger image_generate once with a simple prompt. If it fails with Exhausted balance, tell the user exactly how many credits are needed and stop
  6. Surface the estimate: "Need ~{N} images = ~{N} FAL.ai credits. Current balance unknown — let me check."
  7. Warn for large batches: "14 articles × 4 images = 56 credits. This will consume significant balance. Proceed?"

Post-generation reconciliation (MANDATORY after batch)

After ALL image generation is done, reconcile expected vs. actual:

cd ~/vault/60_Articles
echo "=== Total images on disk ==="
find . -name "*.png" | wc -l

echo "=== Per-article inventory ==="
for dir in */; do
  featured=$(ls "$dir/images/" 2>/dev/null | wc -l)
  attach=$(ls "$dir/attachments/" 2>/dev/null | wc -l)
  echo "$dir → featured: $featured | inline: $attach"
done

echo "=== Remaining placeholders ==="
grep -rn "PLACEHOLDER\|TODO_IMAGE\|<!-- IMAGE" . --include="*.md" | grep -v "brief.md" || echo "None found"

If images are missing (expected > actual), report the exact shortfall and which articles are affected. Do not silently declare completion if not all images exist.

featured_prompt = f"""
Professional blog featured image for article about {topic}.
Style: clean, modern, {tone} aesthetic.
Subject: [specific visual concept from article]
Composition: centered, balanced, with negative space for text overlay.
Colors: [palette based on brand or topic]
No text in image. No stock photo clichés.
"""

Save to: ~/vault/60_Articles/<date>-<slug>/images/featured.png

4b. Generate inline images (batch mode = SKIP, single mode = REQUIRED)

Batch mode: Skip inline images entirely — generate ONLY the featured image. Inline images are a separate post-processing step the user must explicitly request. If they do request inline images for a batch, warn them about the cost (3× article count) and FAL.ai credit burn.

Single mode: Generate inline images per the brief's content requirements:

  • Diagram or infographic for complex concepts
  • Screenshot or example illustration
  • Data visualization or comparison visual

Save each to: ~/vault/60_Articles/<date>-<slug>/attachments/<descriptive-name>.png

4c. Download and verify images (CRITICAL — do not skip)

For every generated image:

  1. The image_generate tool returns a URL — download it immediately with curl -sL "<url>" -o <path>
  2. Verify the file exists and is non-empty (file <path>)
  3. If the download fails, retry once. If it still fails, report the failure

4d. Replace all placeholders in article.md

Before writing article.md, ensure every inline image reference is a real path (not a <!-- PLACEHOLDER --> comment). Images referenced in the article MUST exist on disk already.

Verification step after all images are saved:

# Check every placeholder was replaced
grep -r "PLACEHOLDER" 60_Articles/<slug>/article.md
# Should return no matches

# Check every image file referenced in article.md exists on disk
# Extract all image paths and stat them
grep -oP '!\\[.*?\\]\\((.*?)\\)' article.md | while read -r line; do
  path=$(echo "$line" | grep -oP '\\(.*?\\)' | tr -d '()')
  full_path=$(dirname article.md)/$path
  if [ ! -f "$full_path" ]; then
    echo "MISSING: $full_path"
  else
    echo "OK: $full_path ($(wc -c < "$full_path") bytes)"
  fi
done

4e. Deduplicate image prompts

Track every image prompt you've already sent in the current session. If a new article needs a similar visual concept, reuse the existing image rather than generating another one. Common patterns that can share a single image:

  • "architecture diagram" type visuals
  • Flow/infographic layout images
  • Generic "concept illustration" images

4f. FAL.ai credit management

Track FAL.ai balance proactively:

  • Don't assume balance is unlimited
  • If balance runs out mid-session, stop and tell the user exactly how many images were generated vs. still needed
  • When resuming after top-up, don't regenerate existing images — skip to the missing ones only

Step 5: Assemble and present for approval

Create the complete article file:

~/vault/60_Articles/<client-id>/<date>-<slug>/article.md

Frontmatter:

---
type: article
status: draft
created: YYYY-MM-DD  # IMPORTANT: use source date from brief, NOT today
seo_keyword: "[primary keyword]"
title: "[article title]"
meta_description: "[120-160 chars]"
slug: "[url-slug]"
featured_image: "images/featured.png"
related_client: "[client-id or empty]"
published_to: []
related_website: ""
source_brief: "brief.md"
word_count: [N]
---

Present to user:

📝 บทความพร้อมตรวจ:

📄 [Article Title]
📊 [word count] words
🖼️ [N] images generated
🔍 SEO keyword: [keyword]
📋 GEO optimized: ✅

โครงสร้าง:
- H1: [title]
- H2: [section 1]
- H2: [section 2]
- ...
- FAQ: [N] questions

ตรวจบทความแล้วเป็นยังไงบ้าง?
- ✅ "approve" — อนุมัติ → สร้าง social posts
- ✏️ "แก้ไข [specify]" — ปรับแก้ตามที่ต้องการ
- ❌ "reject" — เริ่มใหม่

Wait for user response. If edits requested, apply and re-present.

Step 5: Present for approval

Once the article is approved, the flow continues to mm-publish-content to publish the article and get the published URL, then mm-social-writer creates social posts.

Step 7: Final summary

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Article Ready: [Article Title]
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📂 ~/vault/60_Articles/<client-id>/<date>-<slug>/

✅ article.md              ← [word count] words, GEO optimized
✅ images/featured.png     ← Featured image
✅ images/inline-01.png    ← [description]
✅ images/inline-02.png    ← [description]
✅ images/inline-03.png    ← [description]
✅ brief.md                ← Source brief

📋 NEXT STEPS:
   1. Load mm-publish-content → publish article → get URL
   2. Load mm-social-writer → create social posts (with URL)
   3. Load mm-publish-content → publish social posts

Output

All files in ~/vault/60_Articles/<client-id>/<date>-<slug>/:

  • article.md — Complete article with frontmatter
  • brief.md — Source brief (from Data Stage)
  • images/featured.png — Featured image
  • images/inline-*.png — Inline article images

Image Generation — Pitfalls Reference

See references/image-generation-pitfalls.md for detailed error transcripts, retry strategies, and FAL.ai balance troubleshooting from real sessions.

Important Notes

  • Article approval is MANDATORY before creating social posts (single-article mode)
  • In batch mode, intermediate approval is skipped — the user implicitly approved by saying "ทั้งหมด"
  • Date convention: article created date = source research's date: frontmatter, NOT today. Extract from the brief's source_research field
  • Images are generated from the MAIN AGENT only — NEVER delegate image generation to subagents (see references/image-generation-pitfalls.md)
  • After all images are generated AND saved, run verification: grep -rn "PLACEHOLDER" article.md to catch any missed replacements
  • Article approval is MANDATORY before proceeding to publish
  • Images are generated during the writing process, not after
  • All frontmatter must include type, status, created
  • Use Thai for article content unless the brief specifies otherwise
  • GEO optimization is applied automatically — don't skip it
  • After approval, use mm-publish-content to publish, then mm-social-writer for posts
  • Batch mode: featured images only (no inline images). Generate exactly ONE image per article. Inline images are a separate pass with its own FAL.ai budget warning
  • Image verification: After every image generation round (featured or inline), verify ALL image references in article.md resolve to real files on disk. A broken image link in the vault means a broken image when published