CLI y automatización
CLI y automatización
El CLI op: diseño como código
La herramienta de línea de comandos op es lo que convierte a OpenPencil de un editor visual en una plataforma de diseño programable. Con op puedes automatizar tareas repetitivas, integrar el diseño en pipelines de CI/CD, escribir scripts que generan variantes de diseño, y mucho más.
La filosofía del CLI es simple: todo lo que puedes hacer en la interfaz gráfica, debes poder hacerlo desde la terminal. Y más: el CLI expone operaciones que no tienen una representación visual directa, como el análisis de tokens, el linting de accesibilidad y la generación en batch.
graph LR
A[Desarrollador] --> B[CLI op]
B --> C{Operación}
C --> D[Diseño interactivo<br/>op start]
C --> E[Batch design<br/>op design]
C --> F[Exportar código<br/>op export]
C --> G[Importar Figma<br/>op import:figma]
C --> H[Linting<br/>op lint]
C --> I[Tokens<br/>op tokens]
Instalación y configuración del CLI
Si instalaste OpenPencil con Homebrew o Scoop, el CLI op ya está disponible. Para instalarlo de forma standalone:
# Con npm
npm install -g @zseven-w/openpencil
# Con bun (recomendado)
bun install -g @zseven-w/openpencil
# Verificar versión
op --version
# Diagnóstico del entorno
op doctor
Configuración global
El CLI usa un archivo de configuración en ~/.config/openpencil/config.json (o %APPDATA%\openpencil\config.json en Windows):
# Configurar API key de Anthropic
op config set ai.providers.anthropic.apiKey "sk-ant-..."
# Configurar directorio de proyectos por defecto
op config set workspace "~/Diseños"
# Ver configuración actual
op config list
# Reset a valores por defecto
op config reset
Comandos fundamentales
op start — Lanzar la app de escritorio
# Abrir la app sin archivo específico
op start
# Abrir un archivo específico
op start mi-proyecto.op
# Abrir con servidor colaborativo
op start mi-proyecto.op --collab-port 8080
op new — Crear un nuevo documento
# Crear documento vacío
op new mi-proyecto.op
# Crear con template
op new dashboard.op --template dashboard-saas
# Listar templates disponibles
op templates list
op info — Información del documento
# Mostrar metadatos del documento
op info mi-proyecto.op
# Salida ejemplo:
# Document: mi-proyecto.op
# Version: 1.0
# Pages: 3 (Landing, Dashboard, Mobile)
# Nodes: 847
# Components: 23
# Variables: 45
# Last modified: 2026-04-05T10:30:00Z
Diseño batch: op design
El comando op design es la joya del CLI. Permite generar diseños a partir de descripciones en lenguaje natural, ya sea desde un archivo de texto o desde stdin.
Desde un archivo de texto
Crea un archivo landing.txt con la descripción de tu diseño:
# landing.txt
Diseña una landing page para una startup de SaaS de gestión de proyectos.
## Hero Section
- Fondo: gradiente de #1E293B a #0F172A
- Título grande: "Gestiona proyectos con IA"
- Subtítulo: descripción de 2 líneas
- Dos botones: primario "Empezar gratis" y secundario "Ver demo"
- Imagen de mockup a la derecha (placeholder 600x400)
## Features Grid
- 3 columnas, 6 features totales
- Cada feature: icono de Heroicons, título, descripción corta
- Fondo blanco, bordes suaves
## Pricing
- 3 planes: Free, Pro ($29/mes), Enterprise
- Plan Pro destacado como recomendado
- Lista de 5 features por plan
- CTA en cada plan
## Footer
- Links de navegación en 4 columnas
- Copyright y links legales
- Modo oscuro
Ejecuta:
op design @landing.txt --output landing.op
# Con especificación de modelo
op design @landing.txt \
--model claude-opus-4-5 \
--level full \
--output landing.op
# Monitorear el progreso
op design @landing.txt --output landing.op --verbose
Desde stdin (pipes)
# Diseñar desde texto en pipe
echo "Crea un formulario de login con email y contraseña" | op design - --output login.op
# Usar con heredoc
op design - --output componente.op << 'EOF'
Diseña un componente de notificación toast con:
- 4 variantes: success, error, warning, info
- Icono, título y descripción en cada variante
- Botón de cerrar
- Animación de entrada desde la derecha
EOF
# Combinar con herramientas de texto
cat diseños/*.txt | op design - --output resultado.op
Diseño incremental
Para agregar elementos a un documento existente sin reemplazarlo:
# Agregar nuevos componentes a un documento existente
op design @nuevos-componentes.txt \
--input mi-proyecto.op \
--output mi-proyecto.op \
--mode append
Modo dry-run
Antes de ejecutar un diseño batch costoso, puedes hacer un dry-run que estima el costo y tiempo:
op design @landing.txt --dry-run
# Salida:
# Estimated tokens: 45,000
# Estimated cost: $0.18 (Anthropic Opus)
# Estimated time: ~3 minutes
# Pages to create: 1
# Estimated nodes: ~200
Insertar nodos: op insert
El comando op insert permite agregar nodos individuales a un documento mediante JSON:
# Insertar un rectángulo
op insert '{"type":"RECT","x":100,"y":100,"width":200,"height":100}' \
--file mi-proyecto.op
# Insertar texto
op insert '{
"type": "TEXT",
"x": 50,
"y": 50,
"content": "Hola mundo",
"fontSize": 24,
"fontFamily": "Inter",
"fills": [{"type": "SOLID", "color": "#1E293B"}]
}' --file mi-proyecto.op
# Insertar frame con children
op insert '{
"type": "FRAME",
"x": 0, "y": 0,
"width": 1440, "height": 900,
"name": "Landing",
"autoLayout": {"direction": "vertical", "gap": 0},
"children": []
}' --file mi-proyecto.op --page "Página 1"
Insertar desde un archivo JSON
Para inserciones complejas, usa un archivo:
// nodo.json
{
"type": "COMPONENT",
"name": "Button/Primary",
"x": 100,
"y": 100,
"autoLayout": {
"direction": "horizontal",
"gap": 8,
"padding": {"top": 12, "right": 20, "bottom": 12, "left": 20}
},
"fills": [{"type": "SOLID", "color": "#2563EB"}],
"cornerRadius": 8,
"children": [
{
"type": "TEXT",
"content": "Enviar",
"fontSize": 16,
"fontFamily": "Inter",
"fontWeight": 600,
"fills": [{"type": "SOLID", "color": "#FFFFFF"}]
}
]
}
op insert --from nodo.json --file mi-proyecto.op
Exportar diseños: op export
Exportar a React + Tailwind
# Exportar la página activa
op export react --file diseño.op --out ./src/components
# Exportar una página específica
op export react --file diseño.op --page "Dashboard" --out ./src/components
# Exportar un componente específico por nombre
op export react --file diseño.op --component "Button/Primary" --out ./src/components
# Exportar con opciones adicionales
op export react \
--file diseño.op \
--out ./src \
--tailwind-config ./tailwind.config.js \
--use-shadcn \
--typescript
El resultado es un componente React con Tailwind:
// Generado por: op export react
import { cn } from '@/lib/utils'
interface ButtonPrimaryProps {
children: React.ReactNode
className?: string
disabled?: boolean
}
export function ButtonPrimary({ children, className, disabled }: ButtonPrimaryProps) {
return (
<button
className={cn(
'flex items-center gap-2 px-5 py-3 rounded-lg',
'bg-blue-600 text-white font-semibold text-base',
'hover:bg-blue-700 transition-colors',
'disabled:opacity-50 disabled:cursor-not-allowed',
className
)}
disabled={disabled}
>
{children}
</button>
)
}
Exportar a otros frameworks
# Vue 3 + Tailwind
op export vue --file diseño.op --out ./src/components
# Svelte + Tailwind
op export svelte --file diseño.op --out ./src/lib
# HTML + CSS
op export html --file diseño.op --out ./dist
op export html --file diseño.op --out ./dist --css-variables # Usa CSS custom props
# Flutter
op export flutter --file diseño.op --out ./lib/widgets
# SwiftUI
op export swiftui --file diseño.op --out ./Sources/Views
# Jetpack Compose
op export compose --file diseño.op --out ./app/src/main/java
# React Native
op export react-native --file diseño.op --out ./src/components
Exportar assets
# Exportar solo los assets (imágenes, iconos SVG)
op export assets --file diseño.op --out ./public/assets --format png
op export assets --file diseño.op --out ./public/assets --format svg
op export assets --file diseño.op --out ./public/assets --format webp --scale 2
Importar Figma: op import:figma
# Importar archivo Figma exportado localmente
op import:figma diseño-figma.fig --output mi-proyecto.op
# Importar desde la API de Figma (requiere token)
op import:figma --figma-token "figd_..." \
--file-id "XXXXXX" \
--output mi-proyecto.op
# Importar solo una página específica
op import:figma diseño-figma.fig \
--page "Mobile" \
--output mobile.op
# Importar con log detallado de conversión
op import:figma diseño-figma.fig \
--output mi-proyecto.op \
--verbose 2>&1 | tee import-log.txt
Análisis y linting: op lint
OpenPencil incluye un linter de diseño que verifica reglas de consistencia, accesibilidad y convenciones:
# Análisis completo
op lint mi-proyecto.op
# Solo accesibilidad (contraste de colores, tamaños de texto)
op lint mi-proyecto.op --rules accessibility
# Solo tokens (verifica que todos los valores estén vinculados a variables)
op lint mi-proyecto.op --rules tokens
# Solo consistencia de espaciado (múltiplos de 4 u 8)
op lint mi-proyecto.op --rules spacing
# Formato JSON para integración con CI
op lint mi-proyecto.op --format json > lint-results.json
Ejemplo de salida:
🔍 OpenPencil Lint Results: mi-proyecto.op
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ WARNINGS (3)
[spacing] Frame "Hero" > Text "Subtitle": margin-top is 13px, expected multiple of 4
[tokens] Rect "Card BG": fill color #F8F9FA not bound to a variable
[tokens] Text "Footnote": font-size 13px not in typography scale
❌ ERRORS (1)
[accessibility] Text "Button Label" on "Button/Primary": contrast ratio 2.8:1 < 4.5:1 (WCAG AA)
📊 Summary: 1 error, 3 warnings, 234 nodes checked
Gestión de tokens: op tokens
# Extraer todos los tokens del documento
op tokens extract mi-proyecto.op
# Exportar tokens como JSON (Style Dictionary)
op tokens export mi-proyecto.op --format style-dictionary --out tokens/
# Exportar tokens como CSS custom properties
op tokens export mi-proyecto.op --format css --out ./src/styles/tokens.css
# Exportar como Tailwind config
op tokens export mi-proyecto.op --format tailwind --out tailwind.tokens.js
# Sincronizar tokens desde un archivo externo hacia el documento
op tokens sync tokens/tokens.json --to mi-proyecto.op
El formato Style Dictionary es compatible con la mayoría de pipelines de tokens modernos:
// tokens/color.json (generado por op tokens export)
{
"color": {
"primary": {
"50": { "value": "#EFF6FF" },
"100": { "value": "#DBEAFE" },
"500": { "value": "#3B82F6" },
"600": { "value": "#2563EB" },
"900": { "value": "#1E3A8A" }
},
"semantic": {
"background": { "value": "{color.primary.50}" },
"text-on-primary": { "value": "#FFFFFF" }
}
}
}
Integración en CI/CD
GitHub Actions: verificar tokens en cada PR
# .github/workflows/design-check.yml
name: Design Token Check
on:
pull_request:
paths:
- 'designs/**/*.op'
jobs:
lint-design:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install OpenPencil CLI
run: npm install -g @zseven-w/openpencil
- name: Lint design files
run: |
for file in designs/*.op; do
op lint "$file" --format json > lint-$(basename $file .op).json
done
- name: Check for errors
run: |
for report in lint-*.json; do
errors=$(jq '.errors | length' "$report")
if [ "$errors" -gt "0" ]; then
echo "❌ Errors found in $report"
jq '.errors[]' "$report"
exit 1
fi
done
echo "✅ All design files pass lint"
- name: Upload lint reports
uses: actions/upload-artifact@v4
if: always()
with:
name: lint-reports
path: lint-*.json
GitHub Actions: exportar componentes automáticamente
# .github/workflows/export-components.yml
name: Export Design Components
on:
push:
branches: [main]
paths:
- 'designs/components.op'
jobs:
export:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Install OpenPencil CLI
run: npm install -g @zseven-w/openpencil
- name: Export React components
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
op export react \
--file designs/components.op \
--out src/components/generated \
--typescript
- name: Commit exported components
run: |
git config user.name "OpenPencil Bot"
git config user.email "[email protected]"
git add src/components/generated/
git diff --staged --quiet || git commit -m "chore: sync design components [skip ci]"
git push
Pipeline de validación de accesibilidad
#!/bin/bash
# scripts/check-design-a11y.sh
DESIGN_FILE="designs/mi-app.op"
THRESHOLD_ERRORS=0
echo "Verificando accesibilidad en $DESIGN_FILE..."
RESULT=$(op lint "$DESIGN_FILE" --rules accessibility --format json)
ERRORS=$(echo "$RESULT" | jq '.errors | length')
WARNINGS=$(echo "$RESULT" | jq '.warnings | length')
echo "Errores: $ERRORS"
echo "Advertencias: $WARNINGS"
if [ "$ERRORS" -gt "$THRESHOLD_ERRORS" ]; then
echo "❌ Falló la verificación de accesibilidad"
echo "$RESULT" | jq '.errors[]'
exit 1
fi
echo "✅ Verificación de accesibilidad aprobada"
Integración con pre-commit hooks
# .husky/pre-commit (si usas Husky)
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
# Verificar archivos .op modificados
CHANGED_OP_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep '\.op$')
if [ -n "$CHANGED_OP_FILES" ]; then
echo "Verificando archivos de diseño..."
for file in $CHANGED_OP_FILES; do
op lint "$file" --format json | python3 -c "
import json, sys
data = json.load(sys.stdin)
if data['errors']:
print(f'❌ {len(data[\"errors\"])} error(s) en $file')
sys.exit(1)
print(f'✅ $file OK')
"
done
fi
Casos de uso avanzados
Generador de variantes de componentes
#!/bin/bash
# scripts/generate-variants.sh
# Genera variantes de color para un componente
BASE_COMPONENT="Button/Primary"
COLORS=("#2563EB" "#7C3AED" "#DC2626" "#16A34A" "#D97706")
NAMES=("Blue" "Purple" "Red" "Green" "Amber")
for i in "${!COLORS[@]}"; do
COLOR=${COLORS[$i]}
NAME=${NAMES[$i]}
op design - --input diseño.op --output diseño.op --mode append << EOF
Duplica el componente "$BASE_COMPONENT" y créalo como "Button/$NAME".
Cambia el color de fondo a $COLOR.
Asegura que el color de texto mantenga contraste WCAG AA.
EOF
echo "✅ Generado Button/$NAME ($COLOR)"
done
Sincronización automática de design tokens con el código
#!/bin/bash
# scripts/sync-tokens.sh
# Sincroniza tokens de diseño con el proyecto de código
DESIGN_FILE="./designs/design-system.op"
TOKENS_DIR="./src/styles"
# Exportar tokens en múltiples formatos
op tokens export "$DESIGN_FILE" \
--format css \
--out "$TOKENS_DIR/tokens.css"
op tokens export "$DESIGN_FILE" \
--format json \
--out "$TOKENS_DIR/tokens.json"
op tokens export "$DESIGN_FILE" \
--format tailwind \
--out "./tailwind.tokens.js"
echo "✅ Tokens sincronizados"
echo "Archivos actualizados:"
echo " - $TOKENS_DIR/tokens.css"
echo " - $TOKENS_DIR/tokens.json"
echo " - ./tailwind.tokens.js"
Análisis de uso de tokens
# Ver qué valores no están vinculados a tokens (hardcoded)
op lint mi-proyecto.op --rules tokens --format json | \
jq '[.warnings[] | select(.rule == "tokens")] |
group_by(.value) |
map({value: .[0].value, count: length}) |
sort_by(.count) | reverse'
# Resultado: ordenado por frecuencia de uso
# [{"value": "#F8F9FA", "count": 23}, ...]
# Estos son buenos candidatos para convertir en variables
Scripting avanzado con la API JSON de .op
Como los archivos .op son JSON, puedes manipularlos con cualquier lenguaje de programación:
// scripts/rename-components.ts
import { readFileSync, writeFileSync } from 'fs'
const doc = JSON.parse(readFileSync('mi-proyecto.op', 'utf-8'))
// Encontrar todos los componentes con nombre antiguo
function renameNodes(nodes: any[], oldPrefix: string, newPrefix: string) {
for (const node of nodes) {
if (node.name?.startsWith(oldPrefix)) {
node.name = node.name.replace(oldPrefix, newPrefix)
}
if (node.children) {
renameNodes(node.children, oldPrefix, newPrefix)
}
}
}
for (const page of doc.pages) {
renameNodes(page.nodes, 'UI/', 'Components/')
}
writeFileSync('mi-proyecto.op', JSON.stringify(doc, null, 2))
console.log('Componentes renombrados')
# scripts/audit_colors.py
import json
from collections import Counter
with open('mi-proyecto.op') as f:
doc = json.load(f)
colors = []
def extract_colors(node):
for fill in node.get('fills', []):
if fill['type'] == 'SOLID':
colors.append(fill['color'])
for child in node.get('children', []):
extract_colors(child)
for page in doc['pages']:
for node in page['nodes']:
extract_colors(node)
counter = Counter(colors)
print("Top 10 colores más usados:")
for color, count in counter.most_common(10):
print(f" {color}: {count} usos")
Resumen
En este capítulo has aprendido:
- Todos los comandos fundamentales del CLI
op - Diseño en batch desde archivos de texto y pipes
- Inserción de nodos por JSON
- Exportación a múltiples frameworks y formatos
- Importación desde Figma
- Análisis y linting de diseños
- Gestión de tokens de diseño
- Integración en pipelines CI/CD con GitHub Actions
- Scripting avanzado con la API JSON de
.op
En el próximo capítulo aprenderemos a configurar el MCP Server de OpenPencil para trabajar con Claude Code directamente desde el editor.