From yux-blog
Analyze a blog article's structure to plan optimal image placement, then generate and insert AI images. This is the blog-specific image workflow — it reads a markdown article, identifies where images should go, creates a plan, and generates images that fit each section. Use when the user wants images added to an existing article — e.g., "analyze article images", "suggest images for this post", "generate article images", "分析文章配图", "生成文章配图", "插入文章配图". Do NOT use for standalone image generation (use yux-nano-banana instead). After generating, use yux-blog-oss to upload images to CDN.
npx claudepluginhub wuyuxiangx/yux-claude-hub --plugin yux-blogThis skill is limited to using the following tools:
Analyze blog articles for optimal image placement, then generate AI images via OpenRouter API and insert them into the article.
Generates and edits AI images for blog content (hero, inline illustrations, social/OG cards) via Gemini MCP using 6-component prompts and 6 domain modes (Editorial, Product, etc.).
Analyzes article structure, identifies positions for visual aids, generates illustrations using Type × Style × Palette approach. Useful for requests to illustrate articles or add images.
Generates full SEO-optimized blog posts from keywords or topics. Includes keyword research via SemRush API if available, SERP analysis, detailed outlines, and E-E-A-T framework.
Share bugs, ideas, or general feedback.
Analyze blog articles for optimal image placement, then generate AI images via OpenRouter API and insert them into the article.
Read references/image-data-safety.md before any API call. It contains safety rules, the fixed API response structure, and common mistakes to avoid.
Save generated images to the current working directory by default. If the user specifies a custom output path, use that instead (expand ~ to home directory; create directory if it doesn't exist using mkdir -p).
Run which curl jq base64 to confirm all tools are available. If any are missing, inform the user and stop.
Check if $OPENROUTER_API_KEY is set:
echo "${OPENROUTER_API_KEY:+set}"
If empty, inform the user: "Please set OPENROUTER_API_KEY in your environment to use image generation." and stop immediately.
All plans and images are organized per article using a slug derived from the article filename:
article filename: my-awesome-article.md → slug: my-awesome-article
{output_dir}/
└── {article-slug}/
├── blog-image-1.png
├── blog-image-2.png
└── ...
.claude/
└── blog-image-plans/
└── {article-slug}.json # one plan per article
.claude/blog-image-plans/{slug}.json — each article has its own plan file{output_dir}/{slug}/ — each article's images are in a dedicated subdirectoryoutput_dir defaults to the current working directory; user can specify a custom pathStrip the .md extension and use the filename (not path) as the slug:
/path/to/my-article.md → my-article/path/to/2026-01-15-deep-dive.md → 2026-01-15-deep-diveParse the user's request to determine the operation phase:
| Phase | Trigger keywords (EN) | Trigger keywords (ZH) |
|---|---|---|
| Analyze | "analyze article images", "suggest images", "article image plan" | "分析文章配图", "文章插图分析" |
| Generate | "generate article images", "insert article images" | "生成文章配图", "插入文章配图" |
| List | "list image plans", "show plans" | "查看配图计划", "列出计划" |
Glob .claude/blog-image-plans/*.json and display a summary table:
| Article | Created | AI Images | Completed | Pending |
|---------|---------|-----------|-----------|---------|
| my-article | 2026-02-09 | 3 | 2 | 1 |
| deep-dive | 2026-02-08 | 5 | 5 | 0 |
If the user then selects an article, load that plan and go to Step 5 (generate) for any pending images.
If the user did not provide an article path, ask for it using AskUserQuestion. The article must be a markdown file.
Derive the slug from the article filename. Check if .claude/blog-image-plans/{slug}.json already exists:
completed images: inform user, ask whether to keep completed images and re-analyze pending or start freshRead the article file and analyze its structure:
Determine optimal positions for images based on:
For each insertion point, determine:
ai-generated (can be created by Nano Banana) or real (user should provide a real photo/screenshot)ai-generated, draft a detailed Nano Banana prompt optimized for the model (descriptive, specific about style and content)Create directory .claude/blog-image-plans/ if not exists. Save the plan to .claude/blog-image-plans/{slug}.json:
{
"article_path": "/absolute/path/to/my-article.md",
"slug": "my-article",
"created_at": "2026-02-09T...",
"updated_at": "2026-02-09T...",
"images": [
{
"id": 1,
"insert_after_line": 15,
"section": "Introduction",
"type": "ai-generated",
"description": "A diagram showing the system architecture",
"prompt": "A clean, minimalist system architecture diagram showing...",
"status": "pending",
"file_path": null
},
{
"id": 2,
"insert_after_line": 42,
"section": "Results",
"type": "real",
"description": "Screenshot of the dashboard metrics",
"prompt": "",
"status": "pending",
"file_path": null
}
]
}
Present the plan in a clear table format:
Article: my-article.md
Plan: .claude/blog-image-plans/my-article.json
Images dir: {output_dir}/my-article/
| # | Line | Section | Type | Description | Status |
|---|------|---------|------|-------------|--------|
| 1 | 15 | Intro | AI | System arch | pending |
| 2 | 42 | Results | Real | Dashboard | pending |
Use AskUserQuestion to ask the user to:
Apply any changes the user requests, update updated_at, and save.
After the analyze phase is complete, inform the user they can run the generate phase when ready by saying "generate article images" or "生成文章配图".
Determine which plan to use:
.claude/blog-image-plans/{slug}.jsonIf no plan file found, inform the user to run the analyze phase first and stop.
# OUTPUT_DIR defaults to "." (current directory). If user specified a custom path, use that.
# ARTICLE_DIR="$OUTPUT_DIR/{slug}"
mkdir -p "$ARTICLE_DIR"
Collect all images where:
type is ai-generatedstatus is pendingIf none are found, inform the user (all images already generated or only real type) and stop.
Display the count of images to generate and ask the user to confirm:
uname -s
Use -D for base64 decode on macOS (Darwin), -d on Linux.
For each pending ai-generated image, call OpenRouter API:
curl -s -o /tmp/blog-image-response.json -w "%{http_code}" \
https://openrouter.ai/api/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-d '{
"model": "google/gemini-3-pro-image-preview",
"messages": [
{
"role": "user",
"content": "<PROMPT_FROM_PLAN>"
}
]
}'
Important: Always use -o /tmp/blog-image-response.json to save the response to a file. The response contains base64-encoded image data that would be truncated in stdout.
Check the HTTP status code:
Validate image exists (see references/image-data-safety.md for response structure):
jq -r '.choices[0].message.images | length' /tmp/blog-image-response.json
0, null, or fails → safety-filtered. Show warning, skip this image, continue with next.1 or more → proceed to extraction.FILENAME="$ARTICLE_DIR/blog-image-<ID>.png"
jq -r '.choices[0].message.images[0].image_url.url' /tmp/blog-image-response.json \
| sed 's|^data:image/[^;]*;base64,||' \
| base64 -D > "$FILENAME" # Use -d on Linux
Verify the file was created and has reasonable size (should be >100KB for a real image):
wc -c < "$FILENAME"
If the file is 0 bytes or very small, extraction failed — skip this image and continue.
After each successful generation, update the image entry in .claude/blog-image-plans/{slug}.json:
status to "completed""file_path" to the saved image pathupdated_at timestamprm -f /tmp/blog-image-response.json
Read the article file. Process insertions from bottom to top (highest line number first) to avoid line number drift.
For each completed image, insert at the specified line using a relative path from the article's directory to the image file:

Write the updated article back to the original file.
Display a summary of all actions taken:
=== Generation Complete: my-article ===
| # | Section | Status | File |
|---|---------|--------|------|
| 1 | Intro | completed | {output_dir}/my-article/blog-image-1.png |
| 2 | Results | skipped (real) | — |
| 3 | Conclusion | completed | {output_dir}/my-article/blog-image-3.png |
Images inserted into: /path/to/my-article.md
Plan updated: .claude/blog-image-plans/my-article.json
| Situation | Action |
|---|---|
| Article file not found | Ask user for correct path |
| Plan file not found (generate phase) | Instruct user to run analyze phase first |
| Multiple plans, user didn't specify | List plans and ask user to choose |
| API key not set | Show setup instructions, stop |
| API error (401/402/429) | Show specific error, stop all remaining images |
| No image in API response | Likely safety-filtered — show warning, skip this image, continue with next. Do NOT explore alternative jq paths — the response structure is fixed |
| Empty article | Inform user, stop |