Creo mi propio “Lovable” con Pencil CLI y Claude Code

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 .pen editable (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-slim como 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/cliopenpencil 🚫
  • @pencil.dev/clipencil

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-5
  • claude-sonnet-4-6
  • claude-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 😄