Files
wehub-resource-sync bb5c75ce05
Component Security Validation / Security Audit (push) Waiting to run
Deploy to Cloudflare Pages / deploy (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 12:38:58 +08:00

316 lines
12 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Generate blog cover images using Google AI (Imagen API).
Reads blog articles from ../docs/blog/blog-articles.json and generates
cover images for articles that don't have images in ../docs/blog/assets/
"""
import os
import sys
import json
import base64
import requests
from pathlib import Path
def check_google_api_key():
"""Check for Google API key in environment."""
api_key = os.getenv('GOOGLE_API_KEY')
if not api_key:
# Try loading from .env file
env_file = Path('.env')
if env_file.exists():
with open(env_file, 'r') as f:
for line in f:
if line.startswith('GOOGLE_API_KEY='):
api_key = line.split('=', 1)[1].strip().strip('"').strip("'")
break
if not api_key:
print("❌ Error: GOOGLE_API_KEY not found!")
print("\nPlease set the environment variable:")
print("export GOOGLE_API_KEY=your-api-key-here")
print("\nOr create a .env file with:")
print("GOOGLE_API_KEY=your-api-key-here")
sys.exit(1)
return api_key
def generate_blog_image(title, description, component_type, component_name, install_command, output_path, api_key):
"""
Generate a blog cover image using Google's Imagen API via AI Studio.
Args:
title: Article title
description: Article description
component_type: Type of component (Agent, MCP, Skill, etc.)
component_name: Name of the component
install_command: Installation command
output_path: Path to save the generated image
api_key: Google API key
"""
# Split install command into two lines for better readability
if "--agent" in install_command:
cmd_parts = install_command.split("--agent ")
cmd_line1 = cmd_parts[0].strip()
cmd_line2 = "--agent " + cmd_parts[1]
elif "--mcp" in install_command:
cmd_parts = install_command.split("--mcp ")
cmd_line1 = cmd_parts[0].strip()
cmd_line2 = "--mcp " + cmd_parts[1]
elif "--skill" in install_command:
cmd_parts = install_command.split("--skill ")
cmd_line1 = cmd_parts[0].strip()
cmd_line2 = "--skill " + cmd_parts[1]
else:
cmd_line1 = install_command
cmd_line2 = ""
# Simplify the command display to avoid text rendering issues
# Show just the essential part
if cmd_line2:
simple_cmd = cmd_line2.strip() # Just show "--agent folder/name" etc
else:
simple_cmd = cmd_line1
# Create detailed prompt similar to supabase-claude-code-templates-cover.png
prompt = f"""Create a professional blog cover image with this EXACT layout and text:
LEFT SIDE (40% width):
- Black background (#000000)
- Text exactly as "CLAUDE CODE TEMPLATES" in pixelated/retro orange font (#F97316)
- Font style: Bold, blocky, retro gaming aesthetic similar to arcade game fonts
- Stacked vertically with equal spacing between words
- Centered vertically on left side
CENTER:
- Vertical line divider in dark gray (#333333) 2px width
- Full height of image from top to bottom
RIGHT SIDE (60% width):
- Black background (#000000)
- At top: Small badge with text "{component_type}" in solid orange rectangle (#F97316) with rounded corners, black text inside
- Below badge: Large bold white text saying "{component_name}"
- At bottom: Small gray monospace text showing: "{simple_cmd}"
CRITICAL TEXT RENDERING REQUIREMENTS:
- All text must be perfectly readable and not corrupted
- Use clear, legible fonts
- Ensure proper spacing so text doesn't overlap or get cut off
- The installation command at bottom should be clearly visible
Overall specifications:
- Exact dimensions: 1200x675 pixels (16:9 aspect ratio)
- Background: Pure black (#000000)
- Primary accent color: Orange (#F97316)
- Text colors: White (#FFFFFF) for component name, Gray (#999999) for command
- Minimalist, professional design
- No decorative elements, gradients, or patterns - just text and divider
- Clean, modern tech aesthetic similar to terminal/CLI interfaces
The layout divides the image into two sections with a vertical line: left side shows "CLAUDE CODE TEMPLATES" in retro orange font, right side shows component type, name, and install command."""
print(f"🎨 Generating image for: {title}")
print(f"📝 Prompt length: {len(prompt)} chars")
# Google AI Nano Banana (gemini-2.5-flash-image) endpoint
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-image:generateContent"
payload = {
"contents": [
{
"parts": [
{
"text": prompt
}
]
}
]
}
headers = {
"x-goog-api-key": api_key,
"Content-Type": "application/json"
}
try:
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 200:
print(f"❌ API Error ({response.status_code}): {response.text}")
return False
result = response.json()
# Extract image from Nano Banana response
if "candidates" in result and len(result["candidates"]) > 0:
candidate = result["candidates"][0]
if "content" in candidate and "parts" in candidate["content"]:
parts = candidate["content"]["parts"]
# Find the inline data part with the image
for part in parts:
if "inlineData" in part:
inline_data = part["inlineData"]
# Extract base64 data
if "data" in inline_data:
image_data = base64.b64decode(inline_data["data"])
# Save image
with open(output_path, 'wb') as f:
f.write(image_data)
print(f"✅ Image saved to: {output_path}")
return True
print(f"⚠️ No inline data found in response parts")
return False
else:
print(f"⚠️ Unexpected response structure: {result}")
return False
else:
print(f"❌ No candidates in response: {result}")
return False
except Exception as e:
print(f"❌ Error generating image: {e}")
return False
def main():
"""Main function to generate blog images."""
# Get Google API key
api_key = check_google_api_key()
# Load blog articles
blog_json_path = Path(__file__).parent.parent / "docs" / "blog" / "blog-articles.json"
if not blog_json_path.exists():
print(f"❌ Error: Blog articles file not found: {blog_json_path}")
sys.exit(1)
with open(blog_json_path, 'r') as f:
data = json.load(f)
articles = data.get("articles", [])
assets_dir = blog_json_path.parent / "assets"
# Create assets directory if it doesn't exist
assets_dir.mkdir(exist_ok=True)
print(f"\n📚 Found {len(articles)} articles")
print(f"📁 Assets directory: {assets_dir}\n")
# Filter articles that need images generated (hosted on aitmpl.com/blog/assets/)
articles_needing_images = []
for article in articles:
image_url = article.get("image", "")
if "aitmpl.com/blog/assets/" in image_url and "-cover.png" in image_url:
# Extract filename from URL
filename = image_url.split("/")[-1]
output_path = assets_dir / filename
if not output_path.exists():
articles_needing_images.append({
"article": article,
"filename": filename,
"output_path": output_path
})
else:
print(f"⏭️ Skipping {filename} (already exists)")
if not articles_needing_images:
print("\n✅ All blog images already exist!")
return
print(f"\n🎨 Generating {len(articles_needing_images)} images...\n")
# Generate images
success_count = 0
for item in articles_needing_images:
article = item["article"]
filename = item["filename"]
output_path = item["output_path"]
# Extract component information from article ID
article_id = article.get("id", "")
# Determine component type and name based on article category
category = article.get("category", "")
if "agent" in article_id.lower() or category == "Agents":
component_type = "AGENT"
# Extract agent name from ID (e.g., frontend-developer-agent -> frontend-developer)
agent_name = article_id.replace("-agent", "")
component_name = agent_name.replace("-", " ").title()
# Find actual agent path in components/agents/
import subprocess
try:
result = subprocess.run(
["find", "cli-tool/components/agents", "-name", f"{agent_name}.md", "-type", "f"],
capture_output=True,
text=True
)
agent_path = result.stdout.strip()
if agent_path:
# Extract folder/name from path (e.g., development-team/frontend-developer)
parts = agent_path.split("/agents/")[1].replace(".md", "")
install_command = f"npx claude-code-templates@latest --agent {parts}"
else:
install_command = f"npx claude-code-templates@latest --agent {agent_name}"
except:
install_command = f"npx claude-code-templates@latest --agent {agent_name}"
elif "mcp" in article_id.lower() or category == "MCP":
component_type = "MCP"
component_name = article_id.replace("-mcp", "").replace("-", " ").title()
install_command = f"npx claude-code-templates@latest --mcp {article_id.replace('-mcp', '')}"
elif "skill" in article_id.lower() or category == "Skills":
component_type = "SKILL"
component_name = article_id.replace("-skill", "").replace("-", " ").title()
install_command = f"npx claude-code-templates@latest --skill {article_id}"
elif "sandbox" in article_id.lower() or "e2b" in article_id.lower():
component_type = "SANDBOX"
component_name = article_id.replace("-", " ").title()
install_command = f"npx claude-code-templates@latest --sandbox e2b"
else:
component_type = "COMPONENT"
component_name = article_id.replace("-", " ").title()
install_command = f"npx claude-code-templates@latest"
print(f"\n{'='*60}")
print(f"📄 Article: {article['title']}")
print(f"🏷️ Type: {component_type}")
print(f"📦 Component: {component_name}")
print(f"💻 Command: {install_command}")
print(f"💾 Output: {filename}")
print(f"{'='*60}\n")
success = generate_blog_image(
title=article["title"],
description=article["description"],
component_type=component_type,
component_name=component_name,
install_command=install_command,
output_path=str(output_path),
api_key=api_key
)
if success:
success_count += 1
print() # Extra newline for readability
print(f"\n{'='*60}")
print(f"✅ Successfully generated {success_count}/{len(articles_needing_images)} images")
print(f"{'='*60}\n")
if __name__ == "__main__":
main()