Visual Instagram Carousel Maker
Launch the visual carousel maker app at ~/carousel-studio/. This is a React + Hono app for creating branded Instagram carousels with AI-generated background images, multiple slide types, and PDF/PNG export.
How to Use
Step 1: Check if already running
lsof -ti:5175 | head -1
If a process is already listening on port 5175, the app is running — skip to Step 3.
Step 2: Start the dev server
cd ~/carousel-studio && bun run dev &
Wait 3 seconds for the server to boot:
sleep 3
Verify it's up:
lsof -ti:5175 | head -1
If still nothing after 5 more seconds, check for errors:
lsof -ti:3010 | head -1
(The Hono API server runs on 3010, Vite client on 5175.)
Step 3: Open in browser
open http://localhost:5175
Step 4: Confirm to the user
Tell the user:
- The carousel maker is open at
http://localhost:5175 - They can pick a framework (Educational, Hormozi, Quick Wins, Storytelling, Instagram Writer)
- Generate AI background images per slide via the Background Image panel
- Export all slides as PNG or a combined PDF
- Carousels are auto-saved and can be reloaded from the drawer
What the App Does
- Frameworks: Pre-built slide structures for different content styles
- AI Backgrounds: Kie.ai image generation per slide or for all slides at once
- Image Library: Previously generated backgrounds are saved and reusable
- Text Scale: Per-slide font size control (XS → XL + fine slider)
- Export: Download individual PNGs or a multi-page PDF
- Save/Load: Carousels saved as JSON to
~/carousel-studio/carousels/, reload from the Library drawer
Stopping the Server
If the user wants to stop the app:
pkill -f "bun run dev" 2>/dev/null; lsof -ti:5175 | xargs kill -9 2>/dev/null; lsof -ti:3010 | xargs kill -9 2>/dev/null
Notes
App lives at
~/carousel-studio/Output PNGs go to
~/carousel-studio/output/Background images saved as
bg_*.pngin that same folderRequires
KIE_API_KEYin~/.claude/.envfor background image generationRequires
ANTHROPIC_API_KEYin~/.claude/.envfor slide generation (Claude Opus 5). Falls back toOPENAI_API_KEYwith gpt-5-mini if no Anthropic key is set.Carousels save as local JSON in
~/carousel-studio/carousels/— no database involvedExport location is configurable. Carousel Studio is a standalone app and defaults to writing into its own
exports/folder. To have exports land in the content repo instead, set this in~/carousel-studio/settings.json:{ "exportDir": "~/content/platform/carousels", "handle": "@tylerreed" }The rest of this skill assumes that setting is in place.
Carousel Storage (Two Locations)
When creating carousel content, always save to both locations:
1. App Library — ~/carousel-studio/carousels/<id>.json
This is what the carousel maker app reads. JSON format with the carousel's slides, colors, and text:
{
"id": "carousel_<timestamp>",
"title": "Carousel Title",
"platform": "instagram",
"slides": [
{
"id": "slide_0_<timestamp>",
"type": "cover",
"slideNumber": 1,
"headline": "Main Headline",
"emphasisLine": "Accent line in terra cotta",
"bodyText": "Supporting text.",
"bgColor": "#F5F0EB",
"textColor": "#1B1B1B",
"accentColor": "#E07355"
}
]
}
Slide types: cover (slide 1), content (slides 2-5, include stepNumber), cta (slide 6).
2. Content folder — ~/content/platform/carousels/<slug>/
This is where the copy, captions, and exported files live:
~/content/platform/carousels/<slug>/
content.md # 6-slide copy (Cover/Pain/Solution/How/Results/CTA)
captions.md # Instagram caption (5 hashtags) + LinkedIn caption (no hashtags)
slide_1.png # Exported from app (after building)
...
slide_7.png
<slug>.pdf # Combined PDF for LinkedIn
Always create both at the same time
When generating carousel content, save the app JSON and the content folder in the same step. This keeps the Library and the content archive in sync.
Scheduling via Blotato
- Instagram: upload slides 1-6 as a carousel (skip CTA slide 7)
- LinkedIn: upload the PDF
- X/Twitter: post as a 7-post thread (see below)
- Captions at
~/content/platform/carousels/<slug>/captions.md
X/Twitter Thread Scheduling
Carousels perform well on X as threads: slide 1 + punchy caption as the main tweet, slides 2-6 as numbered replies, then a Skool link as the final reply.
Structure
- Main tweet: slide_1.png + X-native caption (no hashtags, under 230 chars, punchy)
- Replies 2-6: each slide image + text
"2/6","3/6"..."6/6" - Final reply:
"join us at skool.com/the-ai-agency — free templates and full community 👇"
Timing
- 9 PM EDT = 01:00 UTC the next calendar day
- e.g. May 6 at 9 PM EDT →
scheduledTime: "2026-05-07T01:00:00.000Z"
Upload pattern — use Python MCP client directly
Blotato presigned URL tokens get corrupted when passed through bash heredocs or JSON strings. Always upload via the Python MCP client:
def upload_slide(session_id, carousel_id, slide_num):
path = os.path.expanduser(f"~/content/platform/carousels/{carousel_id}/slide_{slide_num}.png")
r = mcp_call(session_id, "tools/call", {
"name": "blotato_create_presigned_upload_url",
"arguments": {"filename": f"carousel_{carousel_id}_slide{slide_num}.png"}
})
data = json.loads(r["result"]["content"][0]["text"])
presigned_url = data["presignedUrl"] # use for PUT upload
public_url = data["publicUrl"] # use in post mediaUrls
with open(path, "rb") as f:
img_data = f.read()
urllib.request.urlopen(
urllib.request.Request(presigned_url, data=img_data, method="PUT",
headers={"Content-Type": "image/png", "Content-Length": str(len(img_data))}),
timeout=60
)
return slide_num, public_url
Upload all 6 slides in parallel with ThreadPoolExecutor(max_workers=6) for speed.
Post creation
post_data = {
"accountId": "<your-x-account-id>", # from blotato_list_accounts
"platform": "twitter",
"text": caption, # X-native, no hashtags, under 230 chars
"mediaUrls": [slide_urls[1]],
"scheduledTime": "2026-05-07T01:00:00.000Z",
"additionalPosts": [
{"text": "2/6", "mediaUrls": [slide_urls[2]]},
{"text": "3/6", "mediaUrls": [slide_urls[3]]},
{"text": "4/6", "mediaUrls": [slide_urls[4]]},
{"text": "5/6", "mediaUrls": [slide_urls[5]]},
{"text": "6/6", "mediaUrls": [slide_urls[6]]},
{"text": "join us at skool.com/the-ai-agency — free templates and full community 👇", "mediaUrls": []}
]
}
Reference script: /tmp/schedule_x_carousels.py