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:
635
skills/mm-publish-content/SKILL.md
Normal file
635
skills/mm-publish-content/SKILL.md
Normal file
@@ -0,0 +1,635 @@
|
||||
---
|
||||
name: mm-publish-content
|
||||
description: >
|
||||
Publish Stage: Publishes approved articles and social posts to multiple platforms.
|
||||
Supports Astro (git push), WordPress (REST API), Facebook (Page API),
|
||||
X/Twitter (xurl CLI), and Instagram (Graph API).
|
||||
Reads clients-config.json for credentials. Marks published status in frontmatter.
|
||||
Use when: "publish article", "publish บทความ", "ส่งบทความ", "ลงบทความ",
|
||||
"publish to astro", "publish to wordpress", "post to social".
|
||||
---
|
||||
|
||||
# mm-publish-content
|
||||
|
||||
Publishes approved content to websites and social media platforms.
|
||||
|
||||
## When This Must Trigger
|
||||
|
||||
- "publish article", "publish บทความ", "ส่งบทความ", "ลงบทความ"
|
||||
- "publish to astro", "publish to wordpress"
|
||||
- "post to facebook", "post to x", "post to instagram"
|
||||
- "ลง social", "publish social"
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Article must have `status: approved` in frontmatter
|
||||
- Social posts must exist (created by mm-content-writer)
|
||||
- Credentials must be configured in `~/vault/99_System/clients-config.json`
|
||||
|
||||
## Process
|
||||
|
||||
### Step 1: Find content to publish
|
||||
|
||||
```bash
|
||||
# List articles ready to publish
|
||||
# Look for articles with status: approved in 60_Articles/<client-id>/
|
||||
VAULT="$HOME/vault"
|
||||
```
|
||||
|
||||
Search for articles with `status: approved` in per-client folders:
|
||||
|
||||
```python
|
||||
import os, re
|
||||
|
||||
vault = os.path.expanduser("~/vault/60_Articles")
|
||||
ready = []
|
||||
|
||||
for root, dirs, files in os.walk(vault):
|
||||
for f in files:
|
||||
if f != 'article.md':
|
||||
continue
|
||||
path = os.path.join(root, f)
|
||||
content = open(path).read()
|
||||
if 'status: approved' in content[:500]:
|
||||
# Extract title and slug
|
||||
title_match = re.search(r'^title:\s*["\']?(.+?)["\']?\s*$', content, re.MULTILINE)
|
||||
title = title_match.group(1) if title_match else f
|
||||
slug = os.path.basename(root)
|
||||
ready.append({'path': path, 'title': title, 'slug': slug, 'dir': root})
|
||||
```
|
||||
|
||||
If no approved articles found:
|
||||
```
|
||||
ไม่พบบทความที่พร้อม publish
|
||||
บทความต้องมี status: approved ก่อน
|
||||
ใช้ mm-content-writer skill เพื่อเขียนและอนุมัติบทความ
|
||||
```
|
||||
|
||||
If multiple found, ask which one to publish (or publish all).
|
||||
|
||||
### Step 2: Determine publish targets
|
||||
|
||||
Read the article's frontmatter:
|
||||
- `related_client` — look up in clients-config.json
|
||||
- `published_to` — already published platforms (skip these)
|
||||
|
||||
If `related_client` is set, load client config:
|
||||
|
||||
```python
|
||||
import json
|
||||
|
||||
config_path = os.path.expanduser("~/vault/99_System/clients-config.json")
|
||||
config = json.load(open(config_path))
|
||||
|
||||
client = None
|
||||
for c in config.get('clients', []):
|
||||
if c['id'] == related_client:
|
||||
client = c
|
||||
break
|
||||
```
|
||||
|
||||
If no `related_client` or client not found:
|
||||
|
||||
```
|
||||
บทความนี้ยังไม่ได้ระบุ client
|
||||
ต้องการ publish ไปที่ไหน?
|
||||
|
||||
1. Astro website (specify repo)
|
||||
2. WordPress website (specify URL)
|
||||
3. Social media only (Facebook, X, IG)
|
||||
4. ระบุ client จาก clients-config.json
|
||||
```
|
||||
|
||||
If client is found, show available platforms:
|
||||
|
||||
```
|
||||
📋 Client: [display_name]
|
||||
|
||||
🌐 Website:
|
||||
- [type]: [url/repo] ✅
|
||||
|
||||
📱 Social:
|
||||
- Facebook: [page_id] ✅/❌
|
||||
- Instagram: [user_id] ✅/❌
|
||||
- X: [username] ✅/❌
|
||||
|
||||
เผยแพร่ไปช่องทางไหนบ้าง? (เลือกหมายเลข หรือ "all"):
|
||||
1. Website ([type])
|
||||
2. Facebook
|
||||
3. Instagram
|
||||
4. X/Twitter
|
||||
5. All available
|
||||
```
|
||||
|
||||
### Step 3: Publish to Website
|
||||
|
||||
#### 3a. Astro (git push)
|
||||
|
||||
If website type is `astro`:
|
||||
|
||||
```bash
|
||||
# 1. Clone or pull the repo
|
||||
REPO_URL="<repo from config>"
|
||||
BRANCH="<branch from config, default main>"
|
||||
ARTICLE_DIR="<path to article>"
|
||||
|
||||
# 2. Convert article.md to Astro blog format
|
||||
# - Move to src/content/blog/<slug>/index.md
|
||||
# - Convert frontmatter to Astro format
|
||||
# - Copy images to same directory
|
||||
# - Update any wikilinks to regular links
|
||||
|
||||
# 3. Git commit and push
|
||||
git add .
|
||||
git commit -m "publish: <article title>"
|
||||
git push origin $BRANCH
|
||||
```
|
||||
|
||||
Conversion rules for Astro:
|
||||
- Keep `title`, `description`, `pubDate`, `slug` in frontmatter
|
||||
- Add `heroImage` pointing to featured image
|
||||
- Remove vault-specific fields (`type`, `status`, `related_client`)
|
||||
- Convert `published_to` to include `astro`
|
||||
- Copy images to the article directory
|
||||
|
||||
#### Capture published_url
|
||||
|
||||
After successful website publish, capture the URL:
|
||||
|
||||
**For Astro**: Construct from site config + slug
|
||||
```
|
||||
PUBLISHED_URL="<site_url>/blog/<slug>"
|
||||
```
|
||||
|
||||
**For WordPress**: Get from API response
|
||||
```
|
||||
PUBLISHED_URL="<response.link>"
|
||||
```
|
||||
|
||||
#### Save published_url to frontmatter
|
||||
|
||||
Update the article's frontmatter with the published URL:
|
||||
|
||||
```python
|
||||
import re
|
||||
|
||||
article_path = "<path to article.md>"
|
||||
content = open(article_path).read()
|
||||
|
||||
# Add published_url after status: published
|
||||
content = content.replace(
|
||||
"status: published",
|
||||
f"status: published\npublished_url: \"{published_url}\""
|
||||
)
|
||||
|
||||
open(article_path, 'w').write(content)
|
||||
```
|
||||
|
||||
Also update `published_to` to include the website platform:
|
||||
```python
|
||||
# Add to published_to array
|
||||
if "published_to: []" in content:
|
||||
content = content.replace("published_to: []", f"published_to: [astro]")
|
||||
```
|
||||
|
||||
Confirm to user:
|
||||
```
|
||||
✅ บทความ published:
|
||||
URL: [published_url]
|
||||
Frontmatter updated: published_url, published_to
|
||||
```
|
||||
|
||||
#### Update existing social posts with URL
|
||||
|
||||
If social posts already exist (from a previous run), update them with the published_url:
|
||||
|
||||
```python
|
||||
import os, re
|
||||
|
||||
article_dir = "<path to article dir>"
|
||||
published_url = "<url>"
|
||||
|
||||
for platform in ['facebook', 'x', 'ig']:
|
||||
post_path = os.path.join(article_dir, f"post-{platform}.md")
|
||||
if not os.path.exists(post_path):
|
||||
continue
|
||||
|
||||
content = open(post_path).read()
|
||||
|
||||
# Add published_url to frontmatter
|
||||
if 'published_url' not in content:
|
||||
content = re.sub(
|
||||
r'^(related_article:.*)$',
|
||||
f'\\1\npublished_url: "{published_url}"',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
else:
|
||||
content = re.sub(
|
||||
r'^(published_url:\s*)["\']?.*?["\']?\s*$',
|
||||
f'\\1"{published_url}"',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
# Replace URL placeholders in body
|
||||
content = content.replace('[ARTICLE_URL]', published_url)
|
||||
content = content.replace('[article_url]', published_url)
|
||||
|
||||
open(post_path, 'w').write(content)
|
||||
```
|
||||
|
||||
Or use the helper script:
|
||||
```bash
|
||||
python3 ~/Gitea/moreminimore-service-system/scripts/mm_update_url.py \
|
||||
--dir ~/vault/60_Articles/<client-id>/<slug> \
|
||||
--url [published_url]
|
||||
```
|
||||
| `meta_description` | `description` | First 160 chars of description or article |
|
||||
| `created` | `pubDate` | Use source date, NOT today |
|
||||
| — | `category` | Look up from client config or ask user. Must match one of client's blog categories exactly |
|
||||
| — | `heroImage` | `/images/blog/<slug>-<name>.png` — the ASTRO path, not the vault path |
|
||||
| — | `draft: false` | Always add for publication |
|
||||
| `type`, `status`, `related_client` | (omit) | Strip vault-internal fields |
|
||||
| `published_to` | (omit from frontmatter) | Track separately |
|
||||
|
||||
**Image relocation:**
|
||||
- Vault featured: `60_Articles/<slug>/images/featured.png`
|
||||
→ Astro: `public/images/blog/<slug>-featured.png`
|
||||
- Vault inline: `60_Articles/<slug>/attachments/<name>.png`
|
||||
→ Astro: `public/images/blog/<slug>-<name>.png`
|
||||
- Every image reference in markdown: must rewrite vault path to Astro path
|
||||
|
||||
**Example output:**
|
||||
```markdown
|
||||
---
|
||||
title: "Fintech มองไม่เห็นโดย AI Agents — แล้วจะเปลี่ยนยังไง?"
|
||||
description: "ธุรกิจการเงินพร้อมรับ AI Agents หรือยัง? วิเคราะห์ 3 แนวทางสร้าง visibility"
|
||||
pubDate: 2026-06-26
|
||||
category: "SEO"
|
||||
heroImage: "/images/blog/2026-06-26-fintech-invisible-to-ai-agents-featured.png"
|
||||
draft: false
|
||||
---
|
||||
|
||||
# Fintech มองไม่เห็นโดย AI Agents — แล้วจะเปลี่ยนยังไง?
|
||||
|
||||

|
||||
```
|
||||
|
||||
**Path conventions:**
|
||||
- Always use absolute paths from root: `/images/blog/<slug>-<name>.png`
|
||||
- Never use relative paths (`images/...` or `../images/...`)
|
||||
- Prefix inline image names with the article slug to avoid collisions across articles
|
||||
|
||||
**Post-conversion verification (MANDATORY):**
|
||||
```bash
|
||||
# 1. Verify all Astro blog files exist
|
||||
cd ~/Gitea/moreminimore-astroreal
|
||||
ls src/content/blog/2026-*.md | wc -l
|
||||
|
||||
# 2. Verify all image references exist in public/
|
||||
grep -oP '/images/blog/[^)\"\\s]+' src/content/blog/2026-*.md | sort -u | while IFS= read -r img; do
|
||||
[ -f "public$img" ] || echo "MISSING: public$img"
|
||||
done
|
||||
|
||||
# 3. Build check
|
||||
npm run build 2>&1 | tail -5
|
||||
# Must show "[N] page(s) built" without errors
|
||||
```
|
||||
|
||||
**Git commit + push:**
|
||||
```bash
|
||||
git add -A
|
||||
git commit -m "feat: publish [article title]"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
**UPDATED `clients-config.json`:**
|
||||
After publishing, update the client entry with:
|
||||
- `website.categories` — the full list of categories used (for content planning)
|
||||
- `website.last_published` — ISO date of latest publish batch
|
||||
- Keep `website.repo` pointing to the Astro repo path
|
||||
|
||||
#### 3b. WordPress (REST API)
|
||||
|
||||
If website type is `wordpress`:
|
||||
|
||||
```bash
|
||||
# WordPress REST API
|
||||
WP_URL="<url from config>"
|
||||
WP_USER="<user from config>"
|
||||
WP_APP_PASSWORD="<password from config>"
|
||||
|
||||
# Create post
|
||||
curl -X POST "$WP_URL/wp-json/wp/v2/posts" \
|
||||
-u "$WP_USER:$WP_APP_PASSWORD" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"title": "<title>",
|
||||
"content": "<html content>",
|
||||
"status": "draft",
|
||||
"slug": "<slug>",
|
||||
"categories": [<category_ids>],
|
||||
"featured_media": <media_id>
|
||||
}'
|
||||
```
|
||||
|
||||
Steps:
|
||||
1. Convert markdown to WordPress HTML (handle headings, lists, images)
|
||||
2. Upload featured image to WordPress Media Library → get media_id
|
||||
3. Upload inline images → get URLs
|
||||
4. Create post with content + featured_media
|
||||
5. Set status to "draft" first (user can review in WP before publishing)
|
||||
|
||||
WordPress image upload:
|
||||
```bash
|
||||
curl -X POST "$WP_URL/wp-json/wp/v2/media" \
|
||||
-u "$WP_USER:$WP_APP_PASSWORD" \
|
||||
-H "Content-Disposition: attachment; filename=\"featured.png\"" \
|
||||
-H "Content-Type: image/png" \
|
||||
--data-binary @featured.png
|
||||
```
|
||||
|
||||
### Step 4: Publish to Social Media
|
||||
|
||||
#### 4a. Facebook Page
|
||||
|
||||
Requires: `page_id` and `page_token` from client config.
|
||||
|
||||
```bash
|
||||
# Post to Facebook Page
|
||||
PAGE_ID="<from config>"
|
||||
PAGE_TOKEN="<from config>"
|
||||
|
||||
# Text post
|
||||
curl -X POST "https://graph.facebook.com/v21.0/$PAGE_ID/feed" \
|
||||
-d "message=<post content>" \
|
||||
-d "access_token=$PAGE_TOKEN"
|
||||
|
||||
# Post with image
|
||||
# 1. Upload photo first
|
||||
curl -X POST "https://graph.facebook.com/v21.0/$PAGE_ID/photos" \
|
||||
-F "source=@image.png" \
|
||||
-F "message=<caption>" \
|
||||
-F "access_token=$PAGE_TOKEN"
|
||||
```
|
||||
|
||||
Read `post-facebook.md` from the article directory.
|
||||
Post content = body of post-facebook.md.
|
||||
|
||||
#### 4b. X/Twitter (xurl CLI)
|
||||
|
||||
Requires: xurl configured with the client's app.
|
||||
|
||||
```bash
|
||||
# Check xurl auth
|
||||
xurl auth status
|
||||
|
||||
# Post single tweet
|
||||
xurl post "<tweet content>"
|
||||
|
||||
# Post with image
|
||||
xurl media upload images/featured.png
|
||||
xurl post "<tweet content>" --media-id <MEDIA_ID>
|
||||
|
||||
# Post thread (if post-x.md is a thread)
|
||||
xurl post "<tweet 1>"
|
||||
# Get tweet ID from response
|
||||
xurl reply <TWEET_ID> "<tweet 2>"
|
||||
xurl reply <TWEET_ID_2> "<tweet 3>"
|
||||
```
|
||||
|
||||
Read `post-x.md` from the article directory.
|
||||
Determine if single post or thread based on frontmatter `format` field.
|
||||
|
||||
#### 4c. Instagram (Graph API)
|
||||
|
||||
Requires: `user_id` and `token` from client config (Facebook Graph API).
|
||||
|
||||
```bash
|
||||
IG_USER_ID="<from config>"
|
||||
IG_TOKEN="<from config>"
|
||||
|
||||
# 1. Create media container
|
||||
CONTAINER_ID=$(curl -s -X POST "https://graph.facebook.com/v21.0/$IG_USER_ID/media" \
|
||||
-d "image_url=<public image url>" \
|
||||
-d "caption=<caption>" \
|
||||
-d "access_token=$IG_TOKEN" | jq -r '.id')
|
||||
|
||||
# 2. Publish container
|
||||
curl -X POST "https://graph.facebook.com/v21.0/$IG_USER_ID/media_publish" \
|
||||
-d "creation_id=$CONTAINER_ID" \
|
||||
-d "access_token=$IG_TOKEN"
|
||||
```
|
||||
|
||||
**Important**: Instagram requires images to be publicly accessible URLs.
|
||||
Options:
|
||||
1. Upload image to a public CDN first
|
||||
2. Use the WordPress media URL if already uploaded
|
||||
3. Use a temporary image hosting service
|
||||
|
||||
Read `post-ig.md` from the article directory.
|
||||
|
||||
### Step 5: Update frontmatter
|
||||
|
||||
After successful publishing, update the article's frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
...existing...
|
||||
status: published
|
||||
published_to: [astro, facebook, x, ig] # append new platforms
|
||||
published_date: YYYY-MM-DD
|
||||
---
|
||||
```
|
||||
|
||||
Also update each social post's frontmatter:
|
||||
|
||||
```yaml
|
||||
---
|
||||
...existing...
|
||||
status: published
|
||||
published_to: [facebook]
|
||||
published_date: YYYY-MM-DD
|
||||
post_id: "<platform post ID>"
|
||||
---
|
||||
```
|
||||
|
||||
Use `patch` to update frontmatter fields.
|
||||
|
||||
### Step 6: Error handling
|
||||
|
||||
For each platform, if publishing fails:
|
||||
|
||||
```
|
||||
❌ [Platform]: [error message]
|
||||
|
||||
สาเหตุที่เป็นไปได้:
|
||||
- Token หมดอายุ → ต้อง renew
|
||||
- credentials ไม่ถูกต้อง → ตรวจสอบ clients-config.json
|
||||
- API quota หมด → รอและลองใหม่
|
||||
- [platform-specific troubleshooting]
|
||||
|
||||
ต้องการ:
|
||||
1. ข้าม platform นี้ → publish ช่องทางอื่นต่อ
|
||||
2. หยุด → แก้ไขปัญหาก่อน
|
||||
3. ลองใหม่
|
||||
```
|
||||
|
||||
### Step 7: Summary
|
||||
|
||||
**CRITICAL: Full article verification.** The user expects a per-article status report (not a summary). Generate a table showing EVERY article's status:
|
||||
|
||||
```bash
|
||||
# Per-article verification
|
||||
for f in src/content/blog/2026-*.md; do
|
||||
slug=$(basename "$f" .md)
|
||||
title=$(head -5 "$f" | grep "title:" | sed 's/title: "//' | sed 's/"$//')
|
||||
cat=$(grep "category:" "$f" | head -1 | sed 's/category: "//' | sed 's/"$//')
|
||||
hero=$(grep "heroImage:" "$f" | head -1)
|
||||
# Check if hero image exists
|
||||
if [ -n "$hero" ]; then
|
||||
img_path=$(echo "$hero" | grep -oP '/images/blog/[^"'"'"']+')
|
||||
[ -f "public$img_path" ] && img="✅" || img="❌ MISSING"
|
||||
else
|
||||
img="⚠️ no heroImage"
|
||||
fi
|
||||
echo "$slug | $title | $cat | $img"
|
||||
done
|
||||
```
|
||||
|
||||
### Step 7: Summary
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Published: [Article Title]
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🌐 Website:
|
||||
✅ [Astro/WordPress]: [published_url]
|
||||
|
||||
📱 Social:
|
||||
✅ Facebook: [post url or "posted"]
|
||||
✅ X/Twitter: [tweet url or "posted"]
|
||||
✅ Instagram: [posted]
|
||||
❌ [Failed platform]: [reason]
|
||||
|
||||
📋 Status updated in vault frontmatter
|
||||
📋 published_url saved: [url]
|
||||
```
|
||||
|
||||
### Step 8: Archive check (after all publishing)
|
||||
|
||||
After publishing, check if ALL content in the article directory is fully published.
|
||||
Only archive when the entire bundle is complete.
|
||||
|
||||
```python
|
||||
def can_archive(article_dir):
|
||||
"""Check if ALL content in article dir is published"""
|
||||
import os
|
||||
|
||||
# Files that must be checked
|
||||
required_files = ['article.md']
|
||||
optional_posts = ['post-facebook.md', 'post-x.md', 'post-ig.md']
|
||||
|
||||
# Article must be published
|
||||
article_path = os.path.join(article_dir, 'article.md')
|
||||
if not os.path.exists(article_path):
|
||||
return False
|
||||
content = open(article_path).read()[:500]
|
||||
if 'status: published' not in content:
|
||||
return False
|
||||
|
||||
# All existing posts must also be published
|
||||
for f in optional_posts:
|
||||
path = os.path.join(article_dir, f)
|
||||
if not os.path.exists(path):
|
||||
continue # post doesn't exist = skip
|
||||
content = open(path).read()[:500]
|
||||
if 'status: published' not in content:
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
If `can_archive()` returns True:
|
||||
|
||||
```
|
||||
✅ ทั้ง article และ posts publish ครบแล้ว — ย้ายไป Archive
|
||||
|
||||
ย้าย folder ไป Archive/? (yes/no)
|
||||
```
|
||||
|
||||
If user confirms (or auto-confirm):
|
||||
|
||||
```bash
|
||||
ARTICLE_DIR="<path>"
|
||||
CLIENT_ID="<client-id>"
|
||||
ARCHIVE_DIR="$HOME/vault/60_Articles/$CLIENT_ID/Archive"
|
||||
|
||||
mkdir -p "$ARCHIVE_DIR"
|
||||
mv "$ARTICLE_DIR" "$ARCHIVE_DIR/$(basename $ARTICLE_DIR)"
|
||||
```
|
||||
|
||||
If `can_archive()` returns False:
|
||||
|
||||
```
|
||||
⚠️ ยังไม่ครบ — ไม่ย้ายไป Archive
|
||||
|
||||
สถานะ:
|
||||
✅ article.md: published
|
||||
✅ post-facebook.md: published
|
||||
❌ post-x.md: draft (ยังไม่ publish)
|
||||
✅ post-ig.md: published
|
||||
|
||||
ต้อง publish ทุกส่วนก่อนจึงจะย้ายไป Archive ได้
|
||||
```
|
||||
|
||||
**Important**: NEVER move to Archive unless ALL existing content files
|
||||
are published. The archive represents "fully complete" work only.
|
||||
|
||||
## Config Lookup
|
||||
|
||||
Credentials are read from `~/vault/99_System/clients-config.json`.
|
||||
|
||||
Structure:
|
||||
```json
|
||||
{
|
||||
"global": {
|
||||
"google": { ... },
|
||||
"meta": { ... }
|
||||
},
|
||||
"clients": [
|
||||
{
|
||||
"id": "client-id",
|
||||
"display_name": "Client Name",
|
||||
"website": {
|
||||
"type": "wordpress|astro",
|
||||
"url": "...",
|
||||
"wp_user": "...",
|
||||
"wp_app_password": "..."
|
||||
},
|
||||
"social": {
|
||||
"facebook": { "page_id": "...", "page_token": "..." },
|
||||
"instagram": { "user_id": "...", "token": "..." },
|
||||
"x": { "username": "...", "app": "..." }
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
If credentials are missing:
|
||||
1. Tell the user which credential is needed
|
||||
2. Explain how to obtain it
|
||||
3. Offer to save it to clients-config.json once provided
|
||||
|
||||
## Notes
|
||||
|
||||
- Always publish to website FIRST, then social (social posts should link to the article)
|
||||
- WordPress posts are created as "draft" by default — user reviews before publishing
|
||||
- Astro posts are pushed to git — may trigger CI/CD deployment
|
||||
- Instagram requires publicly accessible image URLs — plan accordingly
|
||||
- X/Twitter uses xurl CLI — must be installed and authenticated
|
||||
- Mark `status: published` only after ALL selected platforms succeed
|
||||
- If partial success (some platforms fail), mark only successful platforms in `published_to`
|
||||
Reference in New Issue
Block a user