CommunityProgramación y desarrollogithub.com

Glimpse — Micro-UI Nativa

Muestra interfaces de usuario nativas desde scripts y agentes: diálogos, formularios, visualizaciones, widgets flotantes y compañeros de cursor. Compatible con macOS, Linux y Windows. Úsalo cuando necesites mostrar HTML al usuario, recopilar entrada, mostrar un gráfico, renderizar Markdown o crear cualquier interacción visual sin un navegador.

¿Qué es Glimpse — Micro-UI Nativa?

Glimpse permite a los agentes de IA y scripts mostrar interfaces de usuario nativas instantáneas mediante una ventana con webview que se abre en menos de 50 milisegundos. El agente escribe HTML y el usuario lo ve al instante, con comunicación bidireccional entre el webview y Node.js a través de `window.glimpse.send()` y `.send(js)`. Es compatible con macOS (WKWebView), Linux (WebKitGTK) y Windows (WebView2). Entre sus casos de uso destacan: formularios de entrada más allá de sí/no, visualizaciones (gráficos, Markdown, imágenes, diffs), confirmaciones de acciones destructivas, indicadores flotantes y widgets acompañantes del cursor. También detecta si el agente se ejecuta dentro de una aplicación de escritorio (Claude Desktop o Codex en ChatGPT Desktop) y recomienda usar la visualización integrada de la aplicación cuando sea posible, reservando Glimpse para escenarios que requieren persistencia fuera de la aplicación o bloqueo hasta respuesta del usuario. La configuración se realiza mediante un script que instala `glimpseui` y construye su helper nativo automáticamente. Ofrece modos `prompt` (diálogo de una sola toma) y `open` (ventana persistente con eventos), con opciones como marco transparente, modo flotante, seguimiento del cursor y más. Incluye patrones prácticos como diálogos de confirmación, formularios de texto, listas de selección, visualizadores de contenido enriquecido, salida en vivo, modo oscuro/claro adaptativo, notificaciones flotantes y compañeros de cursor.

Compatible con✓Claude Code✓Codex CLI✓Cursor
npx skills add https://github.com/danmactough/glimpse-everything/tree/HEAD/plugin/skills/glimpse

Installed? Explore more Programación y desarrollo skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace Glimpse — Micro-UI Nativa?

Glimpse abre una ventana nativa con una webview en menos de 50ms. Tú escribes HTML, el usuario lo ve al instante. Comunicación bidireccional mediante window.glimpse.send() (webview → Node) y .send(js) (Node → webview). Funciona en macOS (WKWebView), Linux (WebKitGTK) y Windows (WebView2).

Cuándo usar Glimpse:

  • Necesitas entrada del usuario más allá de sí/no (formularios, selecciones, entrada de texto)
  • Quieres mostrar algo visual (gráficos, Markdown, imágenes, diffs)
  • Quieres confirmar una acción destructiva con un diálogo adecuado
  • Quieres un indicador flotante, notificación o widget compañero
  • Necesitas que el usuario interactúe con contenido enriquecido

Aplicaciones de escritorio: Verifica si te estás ejecutando dentro de una aplicación de escritorio que pueda mostrar HTML, gráficos y formularios en línea en la conversación:

  • Aplicación de escritorio Claude: CLAUDE_CODE_ENTRYPOINT es claude-desktop.
  • Codex en la aplicación de escritorio ChatGPT: CODEX_INTERNAL_ORIGINATOR_OVERRIDE es Codex Desktop o codex_work_desktop. Su visualización en línea es la skill visualize incluida.
echo "claude=${CLAUDE_CODE_ENTRYPOINT:-} codex=${CODEX_INTERNAL_ORIGINATOR_OVERRIDE:-}"

En una aplicación de escritorio, prefiere la visualización en línea de la aplicación. Usa Glimpse allí solo cuando necesites algo que la visualización en línea no puede hacer: un script en ejecución que bloquea hasta que el usuario responda, una ventana que permanece visible fuera de la aplicación (flotante, elemento de estado, seguir cursor), o una interfaz para un script que se ejecuta sin el agente. En una terminal (cualquier otro valor, o ambas variables sin establecer), usa Glimpse como se describe a continuación.

Configuración (plugin glimpse-everything): glimpseui se instala en este plugin en el primer uso. Antes de escribir cualquier script de Glimpse, ejecuta esto una vez por sesión. Instala glimpseui si es necesario, construye su helper nativo e imprime la ruta absoluta del paquete instalado en stdout. Esa ruta impresa es <RUTA_RESUELTA> en todas partes a continuación.

bash "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-}}/bin/glimpse-path.sh"

Si ni CLAUDE_PLUGIN_ROOT ni CODEX_PLUGIN_ROOT están establecidos, ejecuta ../../bin/glimpse-path.sh resuelto en relación al directorio de este archivo de skill. La primera ejecución puede llevar un tiempo (npm install más una compilación nativa); el progreso va a stderr. Si sale con código distinto de cero, muestra al usuario su stderr, que nombra la herramienta de compilación faltante.

Importación: Siempre usa la ruta absoluta a glimpse.mjs dentro del paquete instalado — el especificador simple 'glimpseui' falla cuando los scripts se ejecutan desde /tmp o cualquier lugar sin node_modules.

En macOS/Linux, usa una ruta directa:

import { open, prompt } from '<RUTA_RESUELTA>/src/glimpse.mjs';

En Windows, las importaciones ESM requieren una URL file:///:

import { open, prompt } from 'file:///<RUTA_RESUELTA>/src/glimpse.mjs';
// ej. import { open, prompt } from 'file:///C:/Users/yo/.pi/agent/.../glimpseui/src/glimpse.mjs';

Referencia Rápida

Diálogo de Una Sola Toma (prompt)

import { prompt } from '<RUTA_RESUELTA>/src/glimpse.mjs';

const respuesta = await prompt(html, {
  width: 400, height: 300,    // tamaño de ventana
  title: 'Mi Diálogo',         // texto de la barra de título
  frameless: true,             // sin barra de título
  transparent: true,           // fondo transparente
});
// respuesta = datos de window.glimpse.send(), o null si el usuario cerró la ventana

Ventana Persistente (open)

import { open } from '<RUTA_RESUELTA>/src/glimpse.mjs';

const win = open(html, opciones);
win.on('ready', (info) => {});       // HTML cargado — info tiene pantalla, apariencia, cursor
win.on('message', datos => {});       // interacción del usuario
win.on('info', info => {});          // información fresca del sistema (después de getInfo())
win.on('closed', () => {});          // ventana cerrada
win.send('document.title = "Hola"');   // evaluar JS en webview
win.setHTML('<h1>Nuevo contenido</h1>'); // reemplazar HTML
win.info;                            // última información conocida del sistema
win.getInfo();                       // solicitar información fresca
win.close();                         // cerrar ventana

Todas las Opciones

{
  width, height,          // píxeles (predeterminado: 800×600)
  title,                  // título de ventana (predeterminado: "Glimpse")
  frameless: true,        // sin barra de título, arrastrable por fondo
  floating: true,         // siempre al frente
  transparent: true,      // fondo de ventana transparente
  clickThrough: true,     // el ratón atraviesa la ventana
  followCursor: true,     // la ventana sigue el cursor del ratón
  followMode: 'spring',  // 'snap' (instantáneo, predeterminado) o 'spring' (elástico)
  cursorAnchor: 'top-right', // punto de anclaje: top-left, top-right, right, bottom-right, bottom-left, left
  cursorOffset: {x, y},   // desplazamiento desde el cursor (predeterminado: 20, -20)
  openLinks: true,        // abrir enlaces http/https cliqueados en el navegador predeterminado
  openLinksApp: '/Applications/Google Chrome.app', // ruta opcional del paquete de aplicación
  autoClose: true,        // cerrar después del primer mensaje
  noDock: true,           // sin icono de dock ni entrada en el selector de aplicaciones (macOS)
  x, y,                   // posición exacta en pantalla
  timeout,                // para prompt() solamente — ms antes de rechazar
}

Información del Sistema (disponible en ready)

win.on('ready', (info) => {
  // Pantalla
  info.screen.width          // 2560 (resolución completa)
  info.screen.height         // 1440
  info.screen.scaleFactor    // 2 (Retina)
  info.screen.visibleWidth   // 2560 (excluyendo dock)
  info.screen.visibleHeight  // 1367 (excluyendo barra de menú)

  // Apariencia
  info.appearance.darkMode         // true
  info.appearance.accentColor      // "#007AFF"
  info.appearance.reduceMotion     // false
  info.appearance.increaseContrast // false

  // Cursor
  info.cursor.x   // 500
  info.cursor.y   // 800

  // Todos los monitores
  info.screens    // [{ x, y, width, height, scaleFactor, ... }, ...]
});

// Acceder en cualquier momento después de ready:
win.info.screen.width
win.info.appearance.darkMode

Puente JavaScript en la Página

window.glimpse.send(datos)  // enviar datos a Node (cualquier valor serializable en JSON)
window.glimpse.close()     // cerrar la ventana desde JS

Patrones

1. Diálogo de Confirmación

El caso de uso más simple. Pregunta sí/no, obtén la respuesta, continúa.

const respuesta = await prompt(`
<body style="font-family: system-ui; padding: 24px; background: white;">
  <h2 style="margin-top: 0;">¿Eliminar 47 archivos?</h2>
  <p style="color: #666;">Esto no se puede deshacer.</p>
  <div style="display: flex; gap: 8px; justify-content: flex-end;">
    <button onclick="window.glimpse.send({ok: false})"
      style="padding: 10px 20px; font-size: 14px; border: 1px solid #ddd; border-radius: 8px; background: white; cursor: pointer;">
      Cancelar
    </button>
    <button onclick="window.glimpse.send({ok: true})" autofocus
      style="padding: 10px 20px; font-size: 14px; border: none; border-radius: 8px; background: #e53e3e; color: white; cursor: pointer;">
      Eliminar
    </button>
  </div>
  <script>
    document.addEventListener('keydown', e => {
      if (e.key === 'Escape') window.glimpse.send({ok: false});
      if (e.key === 'Enter') window.glimpse.send({ok: true});
    });
  </script>
</body>
`, { width: 340, height: 180, title: 'Confirmar' });

if (respuesta?.ok) { /* proceder con la eliminación */ }

2. Formulario de Entrada de Texto

Recopila entrada estructurada. Enter envía, Escape cancela.

const resultado = await prompt(`
<body style="font-family: system-ui; padding: 24px; background: white;">
  <style>
    input, select { padding: 8px 12px; font-size: 14px; border: 1px solid #ddd; border-radius: 6px; width: 100%; margin-bottom: 12px; }
    input:focus, select:focus { outline: none; border-color: #4299e1; box-shadow: 0 0 0 3px rgba(66,153,225,0.15); }
    button { padding: 10px 20px; border: none; border-radius: 8px; cursor: pointer; font-size: 14px; }
  </style>
  <h3 style="margin-top: 0;">Nuevo Componente</h3>
  <input id="name" placeholder="Nombre del componente" autofocus />
  <select id="type">
    <option value="page">Página</option>
    <option value="component">Componente</option>
    <option value="layout">Diseño</option>
  </select>
  <div style="display: flex; gap: 8px; justify-content: flex-end;">
    <button onclick="window.glimpse.send(null)" style="background: #eee;">Cancelar</button>
    <button onclick="submit()" style="background: #4299e1; color: white;">Crear</button>
  </div>
  <script>
    function submit() {
      window.glimpse.send({
        name: document.getElementById('name').value,
        type: document.getElementById('type').value,
      });
    }
    document.getElementById('name').addEventListener('keydown', e => {
      if (e.key === 'Enter') submit();
      if (e.key === 'Escape') window.glimpse.send(null);
    });
  </script>
</body>
`, { width: 380, height: 260, title: 'Crear' });

if (resultado) console.log(`Creando ${resultado.type}: ${resultado.name}`);

3. Lista de Selección

Elige de una lista de opciones. Haz clic o usa las teclas de flecha + Enter.

function seleccionarDeLista(titulo, elementos) {
  const elementosHTML = elementos.map((elem, i) =>
    `<div class="item${i === 0 ? ' selected' : ''}" data-index="${i}" onclick="pick(${i})">${elem}</div>`
  ).join('');

  return prompt(`
  <body style="font-family: system-ui; margin: 0; background: white;">
    <style>
      .header { padding: 12px 16px; font-size: 13px; color: #888; border-bottom: 1px solid #eee; }
      .item { padding: 10px 16px; cursor: pointer; font-size: 14px; }
      .item:hover, .item.selected { background: #4299e1; color: white; }
    </style>
    <div class="header">${titulo}</div>
    <div id="list">${elementosHTML}</div>
    <script>
      const items = document.querySelectorAll('.item');
      let sel = 0;
      function pick(i) { window.glimpse.send({index: i, value: items[i].textContent}); }
      document.addEventListener('keydown', e => {
        if (e.key === 'ArrowDown') { items[sel].classList.remove('selected'); sel = (sel+1) % items.length; items[sel].classList.add('selected'); }
        if (e.key === 'ArrowUp') { items[sel].classList.remove('selected'); sel = (sel-1+items.length) % items.length; items[sel].classList.add('selected'); }
        if (e.key === 'Enter') pick(sel);
        if (e.key === 'Escape') window.glimpse.send(null);
        e.preventDefault();
      });
    </script>
  </body>
  `, { width: 300, height: 40 + elementos.length * 38, frameless: true });
}

const eleccion = await seleccionarDeLista('Elige un framework', ['React', 'Vue', 'Svelte', 'Solid', 'Angular']);

4. Visor de Markdown / Contenido Enriquecido

Muestra contenido formateado. Útil para previsualizar documentos generados, READMEs o diffs.

// Renderizar markdown con una librería CDN
const markdown = `# Hola Mundo\n\nEsto es **negrita** y esto es \`código\`.`;

const win = open(`
<body style="font-family: system-ui; padding: 24px; background: white;">
  <div id="content"></div>
  <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
  <script>
    document.getElementById('content').innerHTML = marked.parse(${JSON.stringify(markdown)});
  </script>
</body>
`, { width: 600, height: 400, title: 'Vista Previa' });

5. Progreso en Vivo / Salida Continua

Empuja actualizaciones a la ventana a medida que avanza el trabajo.

const win = open(`
<body style="font-family: monospace; padding: 16px; background: #1a1a2e; color: #0f0; font-size: 13px;">
  <div id="log" style="white-space: pre-wrap;"></div>
  <script>
    window.appendLog = (texto) => {
      document.getElementById('log').textContent += texto + '\\n';
      window.scrollTo(0, document.body.scrollHeight);
    };
  </script>
</body>
`, { width: 500, height: 300, title: 'Progreso de Compilación' });

win.on('ready', () => {
  win.send(`appendLog('Iniciando compilación...')`);
  // A medida que tu proceso se ejecuta, empuja actualizaciones:
  win.send(`appendLog('✓ Compilados 42 archivos')`);
  win.send(`appendLog('✓ Pruebas pasadas')`);
  win.send(`appendLog('✓ Listo!')`);
});

6. Modo Oscuro/Claro Adaptativo

Usa la información del sistema para diseñar la interfaz de usuario que coincida con la apariencia del usuario.

const win = open('', { width: 400, height: 200 });

win.on('ready', ({ appearance, screen }) => {
  const bg = appearance.darkMode ? '#1a1a2e' : '#ffffff';
  const fg = appearance.darkMode ? '#ffffff' : '#333333';
  const accent = appearance.accentColor;

  win.setHTML(`
    <body style="font-family: system-ui; padding: 24px; background: ${bg}; color: ${fg};">
      <h2 style="color: ${accent};">IU Adaptativa</h2>
      <p>Modo oscuro: ${appearance.darkMode ? 'activado' : 'desactivado'}</p>
      <p>Pantalla: ${screen.width}×${screen.height} @${screen.scaleFactor}x</p>
      <button onclick="window.glimpse.close()"
        style="padding: 8px 16px; background: ${accent}; color: white; border: none; border-radius: 6px; cursor: pointer;">
        Cerrar
      </button>
    </body>
  `);
});

7. Notificación Flotante

Un mensaje breve que se cierra automáticamente. Sin interacción necesaria.

function notificar(mensaje, duracionMs = 3000) {
  const win = open(`
  <body style="margin: 0; background: transparent !important;">
    <div style="
      background: rgba(0,0,0,0.85); color: white; padding: 12px 20px;
      border-radius: 10px; font-family: system-ui; font-size: 14px;
      backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
    ">${mensaje}</div>
  </body>
  `, { width: 300, height: 60, frameless: true, transparent: true, floating: true, clickThrough: true });

  win.on('ready', () => setTimeout(() => win.close(), duracionMs));
}

notificar('✅ Desplegado en producción');

8. Compañero de Cursor

Un elemento visual que sigue al cursor. Ideal para indicadores de estado del agente.

// Anillo giratorio que sigue al cursor
const win = open(`
<body style="background: transparent !important; margin: 0;">
  <svg width="50" height="50" style="filter: drop-shadow(0 0 6px rgba(0,255,200,0.5));">
    <circle cx="25" cy="25" r="18" fill="none" stroke="cyan" stroke-width="2" stroke-dasharray="20 60">
      <animateTransform attributeName="transform" type="rotate"
        from="0 25 25" to="360 25 25" dur="1s" repeatCount="indefinite" />
    </circle>
  </svg>
</body>
`, { width: 50, height: 50, frameless: true, transparent: true, floating: true, followCursor: true });

9. Diálogo con Entrada de Archivo

Un diálogo que acepta arrastrar y soltar archivos o selecciona archivos mediante un input nativo.

const archivos = await prompt(`
<body style="font-family: system-ui; padding: 24px; background: white;">
  <style>
    .dropzone { border: 2px dashed #4299e1; border-radius: 12px; padding: 40px 20px; text-align: center; cursor: pointer; transition: all 0.2s; }
    .dropzone.drag-over { background: #ebf8ff; border-color: #2b6cb0; }
  </style>
  <h3 style="margin-top: 0;">Seleccionar Archivos</h3>
  <div class="dropzone" id="dropzone" onclick="document.getElementById('fileInput').click()">
    <p>Arrastra archivos aquí o haz clic para seleccionar</p>
    <input type="file" id="fileInput" multiple style="display: none;" onchange="handleFiles(this.files)" />
  </div>
  <div id="fileList" style="margin-top: 12px;"></div>
  <div style="display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px;">
    <button onclick="window.glimpse.send(null)" style="background: #eee; padding: 8px 16px; border: none; border-radius: 6px;">Cancelar</button>
  </div>
  <script>
    const dropzone = document.getElementById('dropzone');
    dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.classList.add('drag-over'); });
    dropzone.addEventListener('dragleave', () => dropzone.classList.remove('drag-over'));
    dropzone.addEventListener('drop', e => {
      e.preventDefault();
      dropzone.classList.remove('drag-over');
      handleFiles(e.dataTransfer.files);
    });
    function handleFiles(archivos) {
      const nombres = Array.from(archivos).map(f => f.name);
      document.getElementById('fileList').innerHTML = nombres.map(n => '<div>📄 ' + n + '</div>').join('');
      window.glimpse.send({ archivos: nombres });
    }
  </script>
</body>
`, { width: 400, height: 300, title: 'Cargar Archivos' });

Referencia de API

FunciónDescripción
prompt(html, opciones?)Muestra HTML. Devuelve una promesa que se resuelve con los primeros datos enviados desde el webview, o null si se cerró.
open(html, opciones?)Abre una ventana persistente conectada a un EventEmitter.
win.on(evt, cb)Escucha eventos ready, message, info, closed.
win.send(js)Ejecuta JavaScript en el webview.
win.setHTML(html)Reemplaza el contenido completo del webview.
win.getInfo()Solicita información actualizada del sistema (desencadena evento info).
win.close()Cierra la ventana.
OpciónTipoPredeterminadoDescripción
widthnumero800Ancho de la ventana en píxeles
heightnumero600Alto de la ventana en píxeles
titlestring'Glimpse'Título de la ventana
framelessbooleanfalseOculta la barra de título; arrastrable por el fondo
floatingbooleanfalseSe mantiene sobre otras ventanas
transparentbooleanfalseFondo transparente; el HTML debe establecer el suyo propio
clickThroughbooleanfalseLos clics pasan a través de la ventana
followCursorbooleanfalseLa ventana sigue la posición del cursor
x, ynumero—Posición absoluta en pantalla
timeoutnumero—(solo prompt) Tiempo en ms antes de rechazar la promesa

Notas de la plataforma: Escritura de contenido HTML de marcado para una ventana sin marcos, el fondo es arrastrable normalmente. En macOS, establece -webkit-app-region: drag en los elementos del fondo. En Windows, comportamiento similar con selección predeterminada de cursor. Las ventanas transparentes requieren que el HTML de la página tenga un background explícito o use transparent !important. Los iconos de aplicación y dock se pueden ocultar con noDock: true (macOS). La compilación nativa requiere Xcode Command Line Tools en macOS, build-essential en Linux, y la cadena de herramientas de VS Build en Windows.

Skills relacionados