AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Pencil Ig Carousel

skill-stivenrosales-pencil-ig-carousel-pencil-ig-carousel · by stivenrosales

Use when creating Instagram, TikTok or LinkedIn carousel posts inside Pencil (.pen files) using MCP tools and publishing via Buffer API. Triggers: pencil carousel, ig carousel, carousel design pencil, instagram slides, crear carrusel, carrusel instagram, publicar carrusel, programar post, buffer carousel

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-stivenrosales-pencil-ig-carousel-pencil-ig-carousel

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-stivenrosales-pencil-ig-carousel-pencil-ig-carousel)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Pencil Ig Carousel? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Pencil Instagram Carousel Builder

Create polished carousels in .pen files using Pencil MCP, export them, and publish/schedule via Buffer API.

Setup (First Time Only)

This skill needs Buffer API credentials. Do NOT hardcode them in the skill.

1. Create the credentials file

Create ~/.claude/.env.skills with your Buffer API keys:

# Buffer API — Primary account (Instagram + TikTok)
export BUFFER_API_KEY_PRIMARY="your-api-key-here"
export BUFFER_CHANNEL_IG="your-instagram-channel-id"
export BUFFER_CHANNEL_TIKTOK="your-tiktok-channel-id"

# Buffer API — Secondary account (LinkedIn, optional)
export BUFFER_API_KEY_SECONDARY="your-linkedin-api-key"
export BUFFER_CHANNEL_LINKEDIN="your-linkedin-channel-id"

Set file permissions: chmod 600 ~/.claude/.env.skills

2. Load the credentials in your shell

Add this line to ~/.zshrc (or ~/.bashrc):

[ -f ~/.claude/.env.skills ] && source ~/.claude/.env.skills

Then open a new terminal, or run source ~/.zshrc.

3. Get your Buffer API keys

  1. Go to publish.buffer.com and log in
  2. Generate an API key in Settings → Developer
  3. Use the GraphQL query in "Step 1: Get organization and channels" (below) to find your channel IDs

4. Verify the setup

echo $BUFFER_API_KEY_PRIMARY

If you see your key, you're ready. If empty, check that you opened a new terminal after editing .zshrc.

When to Use

  • User asks to create a carousel/carrusel in Pencil
  • User has a .pen file open and wants social media slides
  • User wants to turn an article, announcement, or news into carousel slides
  • User provides images to include in carousel slides

Platform Specs

| Platform | Dimensions | Aspect Ratio | |----------|-----------|--------------| | Instagram (recommended) | 1080 x 1350 | 4:5 | | Instagram square | 1080 x 1080 | 1:1 | | Stories | 1080 x 1920 | 9:16 |

Default to 1080x1350 (4:5) for carousels. More screen real estate in feed.

Workflow

Phase 1: Content Planning (BEFORE building)

  1. Read the source content (article, announcement, news)
  2. Plan the carousel structure: Define how many slides, what goes on each
  3. Write all the copy first: Titles, body text, CTA. Apply copywriting rules
  4. Present the text structure to the user as a numbered list:
Slide 1 (Hook): "Tu próximo empleado no duerme."
  → Body: "Claude ahora puede usar tu computadora..."
Slide 2 (Explain): "Trabaja en tu PC mientras tú no estás."
  → Body: "Claude controla tu mouse, teclado y navegador..."
...
  1. Ask the user for images:

> "Esta es la estructura del carrusel. Antes de construirlo necesito las imágenes. > Por favor envíame: > - Las imágenes que quieras incluir (screenshots, fotos, iconos) > - Deben estar en formato PNG o JPG (no SVG) > - Deben estar guardadas en una carpeta real (Downloads, Documents, Desktop, etc.), no pegadas directamente en el chat > - Si las pegas en el chat, guardalas primero en una carpeta y dame la ruta > > Los slides donde iría una imagen son: [listar slides con imagen]"

  1. Wait for user to provide images before building

Phase 2: Build in Pencil (AFTER receiving images)

1. get_editor_state() → identify .pen file
2. get_variables() → check existing design tokens
3. find_empty_space_on_canvas() → find placement area
4. Create ALL placeholder frames first (batch_design)
5. Build content slide by slide (batch_design, max 25 ops each)
6. Insert images using relative paths to .pen file
7. Remove placeholders
8. get_screenshot() → verify each slide visually

IMPORTANT: Never start Phase 2 without the images. The carousel looks incomplete without them.

Carousel Structure (5-7 slides)

| Slide | Purpose | Background | |-------|---------|------------| | 1 | Hook — bold claim + hero image/icon | Light with accent gradient | | 2-3 | Explain — what/how with images | Alternate light/dark | | 4 | Features/Details — bullet list with icons | Light | | 5 | CTA — share, save, visit link | Dark |

Anatomy of a Slide

Every slide follows this vertical stack (layout: vertical, alignItems: center):

[Badge]           — cornerRadius:100, fill:accent, padding:[10,20]
[Title]           — fontSize:56-74, fontWeight:800, textAlign:center
[Card]            — cornerRadius:28, fill:#FFFFFF or #FFFFFF0A, padding:[36,40]
  [Body text]     — fontSize:26-30, inside the card
[Image frame]     — cornerRadius:20, clip:true, fill:{type:"image",...}
[Footer]          — your brand URL or handle
[Pagination]      — row of ellipses, active dot larger + green
[Brand line]      — "Contenido por [Brand]"

Design System

Colors

| Element | Light Slide | Dark Slide | |---------|------------|------------| | Background | #FAF9F6 + radial gradient #00A86B18 | #171717 | | Accent | #00A86B | #00A86B | | Title | #171717 | #FFFFFF | | Body text | #555555 | #FFFFFFA0 | | Card fill | #FFFFFF | #FFFFFF0A + stroke #FFFFFF12 | | Feature text | #333333 | #FFFFFFDD | | Muted text | #999999 / #BBBBBB | #FFFFFF50 / #FFFFFF30 |

Light Background with Gradient

[
  {"type":"gradient","gradientType":"radial","center":{"y":0.15},
   "size":{"width":1.2,"height":0.6},"rotation":0,
   "colors":[{"color":"#00A86B18","position":0},{"color":"#FAF9F600","position":1}]},
  "#FAF9F6"
]

Typography

| Element | Size | Weight | |---------|------|--------| | Badge text | 20-24px | 700 | | Slide title | 56-74px | 800 | | Body text | 26-30px | 400-500 | | Feature items | 28px | 500 | | Footer | 18-20px | 500 | | CTA button | 28px | 700 |

All text uses fontFamily: "Inter". Always set fill on text nodes.

Components

Badge:

badge=I(parent,{type:"frame",cornerRadius:100,fill:"#00A86B",padding:[10,20],alignItems:"center"})
I(badge,{type:"text",content:"BADGE TEXT",fontFamily:"Inter",fontSize:20,fontWeight:"700",fill:"#FFFFFF",letterSpacing:2})

White Card (light slides):

card=I(parent,{type:"frame",cornerRadius:28,fill:"#FFFFFF",padding:[36,40],gap:24,layout:"vertical",alignItems:"center",width:920})
I(card,{type:"text",content:"Body text here",fontFamily:"Inter",fontSize:28,fontWeight:"normal",fill:"#555555",lineHeight:1.4,textAlign:"center",textGrowth:"fixed-width",width:"fill_container"})

Dark Card (dark slides):

card=I(parent,{type:"frame",cornerRadius:28,fill:"#FFFFFF0A",padding:[36,40],gap:24,layout:"vertical",alignItems:"center",width:920,stroke:{fill:"#FFFFFF12",thickness:1}})

Feature Row with Icon:

row=I(parent,{type:"frame",gap:14,width:"fill_container"})
I(row,{type:"icon_font",iconFontFamily:"lucide",iconFontName:"globe",width:32,height:32,fill:"#00A86B"})
I(row,{type:"text",content:"Feature description here",fontFamily:"Inter",fontSize:28,fontWeight:"500",fill:"#333333",lineHeight:1.3,textGrowth:"fixed-width",width:"fill_container"})

Pagination Dots (per slide):

pag=I(parent,{type:"frame",gap:14,alignItems:"center"})
// Active dot = 14x14, fill:#00A86B. Inactive = 12x12, fill:#CCCCCC (light) or #FFFFFF40 (dark)
I(pag,{type:"ellipse",width:14,height:14,fill:"#00A86B"})  // active
I(pag,{type:"ellipse",width:12,height:12,fill:"#CCCCCC"})  // inactive

CTA Button:

cta=I(parent,{type:"frame",cornerRadius:100,fill:"#00A86B",padding:[20,60],alignItems:"center",justifyContent:"center"})
I(cta,{type:"text",content:"Button Text",fontFamily:"Inter",fontSize:28,fontWeight:"700",fill:"#FFFFFF"})

Image Frame:

I(parent,{type:"frame",cornerRadius:20,width:880,height:500,clip:true,
  fill:{type:"image",url:"./image-name.png",mode:"fill"},
  stroke:{fill:"#E0E0E0",thickness:1}})

Image url must be relative to the .pen file. Use ./filename.png for files in the same directory. SVGs don't work as image fills; use PNG/JPG only.

Image checklist before building:

  • Images must be PNG or JPG (never SVG)
  • Images must be in a carpeta real del sistema (Downloads, Documents, Desktop, etc.)
  • Las imágenes de cache temporal (.claude/image-cache/) NO funcionan
  • Si el usuario pega imágenes en el chat, pedirle que las guarde en una carpeta real primero
  • La ruta en el fill debe ser relativa al archivo .pen: ../../ruta/a/imagen.png

Hero Image with Background Color:

I(parent,{type:"frame",width:300,height:300,cornerRadius:36,clip:true,
  fill:[{type:"color",color:"#E8825A"},{type:"image",url:"./icon.png",mode:"fill"}]})

Copywriting Rules

  • Hook slide: Bold, curiosity-driven. "Tu proximo empleado no duerme." not "Nuevo feature de Claude"
  • No guiones/dashes in any text
  • One idea per slide
  • Max 30-40 words per content slide
  • Titles: short, punchy, benefit-driven
  • Body: conversational, direct, no jargon
  • CTA: focused on sharing/saving, not just "follow"
  • Always end with brand attribution
  • Nunca llamar "carrusel" al post en el copy. Solo referirse como "post"
  • Nunca usar CTAs autorreferenciales como "este carrusel te va a interesar", "guarda este post", "si te gustó comparte". Dejar que el contenido hable por sí solo

Copy por plataforma

Instagram: Casual, con hashtags, emojis opcionales. Caption largo OK.

TikTok: Max 150 caracteres incluyendo hashtags. Directo y corto.

LinkedIn: Tono profesional orientado a valor de negocio:

  • Abrir con un problema real del negocio
  • Usar primera persona ("Nosotros lo resolvimos")
  • Listar resultados concretos numerados
  • Sin hashtags (o máximo 3 relevantes)
  • Sin CTAs tipo "guarda esto" o "comparte este post"
  • Etiquetar empresas mencionadas con annotations
  • Cerrar con el resultado, no con una invitación

Slide Spacing

| Property | Value | |----------|-------| | Frame padding | [60,80] | | Gap between elements | 28-40 | | Card internal padding | [28-36, 36-40] | | Card internal gap | 20-24 | | Feature list gap | 24 | | Card width | 920 (of 1080) | | Image width | 880 |

Frame Placement

Carousels go below existing content. Use find_empty_space_on_canvas(direction:"bottom"). Space slides horizontally with 100px gaps:

Slide 1: x=startX, y=startY
Slide 2: x=startX+1180, y=startY
Slide 3: x=startX+2360, y=startY
...

Phase 3: Export

Use export_nodes from Pencil MCP to export slides:

Export x1 (TikTok):  scale: 1  → 1080x1350px
Export x3 (Instagram): scale: 3  → 3240x4050px
Export PDF: format: "pdf" → all slides in one document

Create a folder structure:

mkdir -p "Downloads/Carrusel-[nombre]/x1" "Downloads/Carrusel-[nombre]/x3"

Export all slide nodeIds to each folder + a PDF to the root.

Phase 4: Publish via Buffer API

Authentication

Endpoint: https://api.buffer.com (GraphQL)
Header: Authorization: Bearer {API_KEY}
Header: Content-Type: application/json

User-Agent must be set or requests get 403.

Buffer API Keys (environment variables):

Credentials live in ~/.claude/.env.skills (auto-loaded from ~/.zshrc). Read values with echo $VARIABLE via the Bash tool. NEVER hardcode them in the skill.

See the Setup section at the top of this file for how to configure these variables.

| Account | Env var API Key | Env var Channel | |---------|-----------------|-----------------| | Primary (Instagram) | $BUFFER_API_KEY_PRIMARY | $BUFFER_CHANNEL_IG | | Primary (TikTok) | $BUFFER_API_KEY_PRIMARY | $BUFFER_CHANNEL_TIKTOK | | Secondary (LinkedIn) | $BUFFER_API_KEY_SECONDARY | $BUFFER_CHANNEL_LINKEDIN |

> You can rename these variables or add more accounts (e.g., $BUFFER_API_KEY_CLIENT1). > The structure is flexible — adjust to your own setup.

If a variable is empty, tell the user to verify that:

  1. The ~/.claude/.env.skills file exists
  2. ~/.zshrc contains [ -f ~/.claude/.env.skills ] && source ~/.claude/.env.skills
  3. The terminal session was restarted (or they ran source ~/.zshrc)

Step 1: Get organization and channels

query { account { organizations { id name } } }
query { channels(input: { organizationId: "ORG_ID" }) { id name displayName service timezone } }

Step 2: Get suggested posting times

query { channel(input: { id: "CHANNEL_ID" }) { postingSchedule { day times } timezone } }

Step 3: Upload images to public URL

Images must be publicly accessible URLs. Use tmpfiles.org for temporary hosting:

curl -s -F "file=@image.png" https://tmpfiles.org/api/v1/upload
# Response: {"data":{"url":"http://tmpfiles.org/12345/image.png"}}
# Convert to direct link: replace tmpfiles.org/ with tmpfiles.org/dl/

Step 4: Create scheduled posts

Key parameters:

  • schedulingType: automatic → publishes automatically (NOT notification which only sends a reminder)
  • mode: customScheduled → publish at specific time
  • dueAt → ISO 8601 datetime in UTC
  • saveToDraft: true → saves as draft instead of scheduling

Instagram carousel:

mutation CreatePost { createPost(input: {
  text: """Caption with hashtags"""
  channelId: "IG_CHANNEL_ID"
  schedulingType: automatic
  mode: customScheduled
  dueAt: "2026-03-30T03:57:00.000Z"
  metadata: { instagram: { type: carousel, shouldShareToFeed: true } }
  assets: { images: [
    {url: "https://public-url/slide1.png"}
    {url: "https://public-url/slide2.png"}
  ] }
}) { ... on PostActionSuccess { post { id text } } ... on MutationError { message } } }

TikTok carousel:

mutation CreatePost { createPost(input: {
  text: """Short caption max 150 chars"""
  channelId: "TIKTOK_CHANNEL_ID"
  schedulingType: automatic
  mode: customScheduled
  dueAt: "2026-03-29T17:00:00.000Z"
  metadata: { tiktok: { title: "Short title" } }
  assets: { images: [
    {url: "https://public-url/slide1.png"}
  ] }
}) { ... on PostActionSuccess { post { id text } } ... on MutationError { message } } }

Platform-specific rules

| Platform | Image Scale | Caption Limit | Metadata Required | |----------|------------|---------------|-------------------| | Instagram | x3 (3240x4050) | No limit | instagram: { type: carousel, shouldShareToFeed: true } | | TikTok | x1 (1080x1350) | 150 chars | tiktok: { title: "..." } |

ShareMode enum values

addToQueue | shareNow | shareNext | customScheduled | recommendedTime

SchedulingType enum values

  • automatic → Buffer publishes automatically (use this)
  • notification → Only sends a mobile reminder (avoid)

Instagram PostType enum values

post | reel | story | carousel

Buffer API gotchas

  • Use http.client in Python, NOT urllib (gets 403 from Cloudflare)
  • Always set User-Agent: Mozilla/5.0 header
  • Buffer rejects duplicate posts (same text + images). Change caption slightly if reposting
  • TikTok captions max 150 characters including hashtags
  • Convert timezone to UTC for dueAt. Lima = UTC-5
  • API cannot edit or delete posts. Remove duplicates manually from Buffer UI

Common Mistakes

| Mistake | Fix | |---------|-----| | SVG as image fill | Convert to PNG first, SVGs won't render | | Image path not relative | Use ./filename.png relative to .pen file | | Text without fill | Always set fill on text, it's invisible by default | | Overcrowded slide | Max 30-40 words, one idea per slide | | No pagination dots | Add to every slide showing position | | Missing brand attribution | Always include brand footer + "Contenido por X" | | Titles inside cards | Keep titles outside cards, only body text inside | | Generic copy | Use curiosity hooks, benefit-driven language | | schedulingType: notification | Use automatic for auto-publish | | TikTok caption too long | Max 150 chars including hashtags | | Images not public URLs | Upload to tmpfiles.org first, use /dl/ direct links | | Duplicate post error | Change caption text slightly |

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.