En mi día a día ya uso Pencil y Claude. Pero en sus versiones “normales”:
- Pencil como app de escritorio
- Claude desde terminal
Y funcionan bien. Muy bien, de hecho.
Pero no puedo decir:
- “genera este diseño mientras voy en el tren”
- “lánzalo desde otro sistema”
- “déjamelo preparado y luego lo reviso”
Siempre es lo mismo:
abrir la app, escribir, esperar, exportar…
Todo muy manual.
“Esto debería ser una API”
Así que me propuse algo sencillo:
“Quiero generar diseños de interfaces con una API REST. Le mando texto, me devuelve un diseño editable.”
Y de paso, aprender cómo encajar herramientas nuevas como Pencil CLI y Claude en algo más real.

La idea era esta:
curl -X POST http://localhost:8000/designs/generate \
-d '{"prompt": "Landing web para un curso de formación sobre desarrollo."}'
Y recibir algo así:
{
"success": true,
"download_url": "...design.pen",
"preview_url": "...design.png"
}
Es decir:
- Un
.peneditable (formato de Pencil) - Un PNG para ver rápido el resultado
La idea: “Pencil as a Service”
El invento básicamente hace esto:
Cliente
→ POST /designs/generate
→ FastAPI
→ Pencil CLI
→ Claude genera el diseño
→ Guardo .pen + PNG
→ Devuelvo URLs
Y ya está, ¿fácil no? pues no jejeje

Stack
He usado:
- FastAPI → porque es rápido de montar y async
- Docker → porque esto sin docker no hay quien lo reproduzca
- Pencil CLI → para generar diseños desde terminal
- Claude Code → como cerebro por debajo
Y algo importante:
No hay frontend. Y no pasa nada.
De momento … que todos sabemos que sin frontend no le gusta a nadie 🫥
Cómo funciona realmente
El core está en ejecutar Pencil CLI desde Python.
Algo así:
pencil --out design.pen --prompt "landing page for a coffee shop"
Y luego, para la exportación a .png
pencil --in design.pen --export design.png --export-type png
Todo esto envuelto en un servicio que:
- gestiona timeouts
- guarda archivos
- devuelve URLs
Pero eso si, necesitamos que se use la suscripción de Claude, paso de pagar por la API de Anthropic cuando ya les suelto todos los meses mis 100 pavazos … no tengo ninero 😖
Arquitectura
Aquí ya empiezan las cosas interesantes.
Necesitaba:
- Node (por el CLI de Pencil)
- Python (por FastAPI)
Así que acabé con algo tipo:
node:20-slimcomo base- Python encima
- instalar:
@pencil.dev/cli@anthropic-ai/claude-code
Hasta aquí todo bien.
Hasta que deja de ir.
Problema #1: Claude no quiere ejecutarse como root
Error:
--dangerously-skip-permissions cannot be used with root/sudo (que dios nos pille confesados !!!)
Traducción:
“No voy a funcionar si estás usando root, campeón”
Y claro… Docker usa root por defecto.
Solución
Usar el usuario node que ya viene en la imagen.
Lección aprendida:
No luches contra la imagen base. Ya trae cosas pensadas.
Problema #2: Claude se inventaba los .pen
Este fue el mejor, porque si tiras de vibe coding a tope, a veces no te enteras de lo que ocurre debajo del capó, y Claude decidió que la mejor opción era mandar el prompt a Claude y que este usara el MCP de pencil para generarlo, pero eso no va todo lo bien que queremos.
Yo le decía:
“genera un archivo .pen”
Y Claude:
“Claro que sí”
Y me devolvía… un JSON inventado.
{
"version": "1.0.0"
}
No era un .pen válido.
Solución
Dejar de hacer inventos y usar directamente:
👉 Pencil CLI en vez de intentar orquestarlo desde Claude
Lección:
Vive coding a tope, pero pide con criterio, no presupongas que lo va a hacer como tu quieres.
Problema #3: dos CLIs con el mismo nombre (más o menos)
@open-pencil/cli→openpencil🚫@pencil.dev/cli→pencil✅
Le hablé de «pencil», y tiró por OpenPencil, en vez de Pencil.dev … de ahí también que el .pen que me generaba estuviese mal.
Solución
Comprobar siempre:
npm view paquete bin
Lección:
npm es el salvaje oeste.
Problema #4: No le llega la CLI KEY
Yo tenía esto:
PENCIL_CLI_KEY=...
Python lo leía bien.
Pero el proceso hijo (Pencil CLI) no.
Por qué
Porque Pydantic carga el .env…
pero no lo exporta a os.environ.
Solución
from dotenv import load_dotenv
load_dotenv()
Y en Docker:
env_file:
- .env
Lección:
Que Python vea una variable no significa que el sistema la vea.
Problema #5: nombres de modelos inventados
Yo usando:
CLAUDE_MODEL=sonnet
Pencil:
no sé qué es eso
Modelos reales:
claude-haiku-4-5claude-sonnet-4-6claude-opus-4-6
Lección:
los nombres “bonitos” no funcionan en producción.
Problema #6: esto tarda… bastante
Generar un diseño no es instantáneo.
Puede tardar:
5–10 minutos
Mi timeout inicial:
CLAUDE_TIMEOUT=300
Resultado: petaba.
Solución
CLAUDE_TIMEOUT=600
Lección:
la IA es rápida… hasta que deja de serlo.
Lo que tengo ahora mismo
Una API que:
- recibe texto
- genera diseños reales
- exporta previews
- está dockerizada
- funciona sin frontend
Y que puedes levantar con:
docker compose up --build
A mejorar
- Es síncrono → te quedas esperando
- No hay cola de trabajos
- No hay autenticación
- No hay UI (de momento)
Pero funciona. Y eso ya es mucho.
Ideas para evolucionarlo
convertir esto en una pieza dentro de un sistema mayor
Si le metemos un validador, es decir, un endpoint donde poder decir OK a un diseño, podríamos pasarlo a desarrollo, ya que el contenedor ya tiene una instancia de claude code disponible 🚀 🚀 🚀
… y ya puestos, teniendo un endpoint, tenemos la opción de hacer las llamadas desde un bot de Telegram (aquí el post de cómo cree uno hace tiempo)
Está chulo, ¿eh?
Si te interesa el proyecto, lo puedes ver y descargar desde el repo:
https://github.com/ablancodev/pencil-as-a-service
Y si quieres que le meta frontend o lo lleve más lejos… dímelo, que seguramente ya lo esté pensando 😄