danmactough/glimpse

Exibe interfaces nativas a partir de scripts e agentes — diálogos, formulários, visualizações, widgets flutuantes e companheiros de cursor. Suporta macOS, Linux e Windows. Use quando precisar mostrar HTML ao usuário, coletar entrada, exibir um gráfico, renderizar markdown ou criar qualquer interação visual sem um navegador.

O que é glimpse?

Glimpse é uma habilidade (skill) para agentes de IA como Claude Code, Codex e Cursor que permite criar interfaces de usuário nativas instantâneas sem a necessidade de um navegador completo. Ele abre uma janela nativa com um webview em menos de 50ms, suportando comunicação bidirecional entre o webview e o processo Node.js. A habilidade é útil para situações que exigem interações mais ricas do que um simples 'sim/não', como formulários, seleções, gráficos, visualizações de markdown, notificações flutuantes e indicadores de progresso. Funciona em macOS (WKWebView), Linux (WebKitGTK) e Windows (WebView2) e oferece um conjunto de APIs (prompt e open) para criar desde diálogos de confirmação até widgets flutuantes que seguem o cursor do mouse.

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

Installed? Explore more Programação e Desenvolvimento skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Glimpse — Micro-UI Nativa

O Glimpse abre uma janela nativa com um webview em menos de 50ms. Você escreve HTML, o usuário vê instantaneamente. Comunicação bidirecional via window.glimpse.send() (webview → Node) e .send(js) (Node → webview). Funciona em macOS (WKWebView), Linux (WebKitGTK) e Windows (WebView2).

Quando usar o Glimpse:

  • Você precisa de entrada do usuário além de sim/não (formulários, seleções, entrada de texto)
  • Você quer mostrar algo visual (gráficos, markdown, imagens, diffs)
  • Você quer confirmar uma ação destrutiva com um diálogo adequado
  • Você quer um indicador flutuante, notificação ou widget companheiro
  • Você precisa que o usuário interaja com conteúdo rico

Aplicativos de desktop: Verifique se você está executando dentro de um aplicativo de desktop que pode mostrar HTML, gráficos e formulários em linha na conversa:

  • Aplicativo Claude desktop: CLAUDE_CODE_ENTRYPOINT é claude-desktop.
  • Codex no aplicativo ChatGPT desktop: CODEX_INTERNAL_ORIGINATOR_OVERRIDE é Codex Desktop ou codex_work_desktop. Sua exibição em linha é a skill visualize integrada.
echo "claude=${CLAUDE_CODE_ENTRYPOINT:-} codex=${CODEX_INTERNAL_ORIGINATOR_OVERRIDE:-}"

Em um aplicativo de desktop, prefira a exibição em linha do aplicativo. Use o Glimpse lá apenas quando precisar de algo que a exibição em linha não pode fazer: um script em execução que bloqueia até o usuário responder, uma janela que permanece visível fora do aplicativo (flutuante, item de status, seguir cursor) ou uma interface para um script executado sem o agente. Em um terminal (qualquer outro valor, ou ambas as variáveis não definidas), use o Glimpse conforme descrito abaixo.

Configuração (plugin glimpse-everything): glimpseui é instalado neste plugin no primeiro uso. Antes de escrever qualquer script do Glimpse, execute isso uma vez por sessão. Ele instala glimpseui se necessário, compila seu helper nativo e imprime o caminho absoluto do pacote instalado no stdout. Esse caminho impresso é <RESOLVED_PATH> em todo lugar abaixo.

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

Se nem CLAUDE_PLUGIN_ROOT nem CODEX_PLUGIN_ROOT estiverem definidos, execute ../../bin/glimpse-path.sh resolvido em relação ao diretório deste arquivo de skill. A primeira execução pode demorar um pouco (npm install mais uma compilação nativa); o progresso vai para o stderr. Se sair com código não zero, mostre ao usuário seu stderr, que nomeia a ferramenta de compilação ausente.

Importação: Sempre use o caminho absoluto para glimpse.mjs dentro do pacote instalado — o especificador simples 'glimpseui' falha quando os scripts são executados de /tmp ou de qualquer lugar sem node_modules.

No macOS/Linux, use um caminho direto:

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

No Windows, imports ESM requerem uma URL file:///:

import { open, prompt } from 'file:///<RESOLVED_PATH>/src/glimpse.mjs';
// ex.: import { open, prompt } from 'file:///C:/Users/me/.pi/agent/.../glimpseui/src/glimpse.mjs';

Referência Rápida

Diálogo Único (prompt)

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

const answer = await prompt(html, {
  width: 400, height: 300,    // tamanho da janela
  title: 'Meu Diálogo',       // texto da barra de título
  frameless: true,            // sem barra de título
  transparent: true,          // fundo transparente
});
// answer = dados de window.glimpse.send(), ou null se o usuário fechou a janela

Janela Persistente (open)

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

const win = open(html, options);
win.on('ready', (info) => {});       // HTML carregado — info tem screen, appearance, cursor
win.on('message', data => {});       // interação do usuário
win.on('info', info => {});          // informações frescas do sistema (após getInfo())
win.on('closed', () => {});          // janela fechada
win.send('document.title = "Oi"');   // executa JS no webview
win.setHTML('<h1>Novo conteúdo</h1>'); // substitui HTML
win.info;                            // últimas informações do sistema conhecidas
win.getInfo();                       // solicita informações atualizadas
win.close();                         // fecha a janela

Todas as Opções

{
  width, height,          // pixels (padrão: 800×600)
  title,                  // título da janela (padrão: "Glimpse")
  frameless: true,        // sem barra de título, arrastável pelo fundo
  floating: true,         // sempre no topo
  transparent: true,      // fundo da janela transparente
  clickThrough: true,     // mouse passa através da janela
  followCursor: true,     // janela segue o cursor do mouse
  followMode: 'spring',   // 'snap' (instantâneo, padrão) ou 'spring' (elástico)
  cursorAnchor: 'top-right', // ponto de ancoragem: top-left, top-right, right, bottom-right, bottom-left, left
  cursorOffset: {x, y},   // deslocamento do cursor (padrão: 20, -20)
  openLinks: true,        // abre links http/https clicados no navegador padrão
  openLinksApp: '/Applications/Google Chrome.app', // caminho opcional do app
  autoClose: true,        // fecha após a primeira mensagem
  noDock: true,           // sem ícone no dock ou entrada no alternador de apps (macOS)
  x, y,                   // posição exata na tela
  timeout,                // para prompt() apenas — ms antes de rejeitar
}

Informações do Sistema (disponíveis no ready)

win.on('ready', (info) => {
  // Tela
  info.screen.width          // 2560 (resolução total)
  info.screen.height         // 1440
  info.screen.scaleFactor    // 2 (Retina)
  info.screen.visibleWidth   // 2560 (excluindo o dock)
  info.screen.visibleHeight  // 1367 (excluindo a barra de menus)

  // Aparência
  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 os monitores
  info.screens    // [{ x, y, width, height, scaleFactor, ... }, ...]
});

// Acesse a qualquer momento após ready:
win.info.screen.width
win.info.appearance.darkMode

Ponte JavaScript na Página

window.glimpse.send(data)  // envia dados para Node (qualquer valor serializável em JSON)
window.glimpse.close()     // fecha a janela a partir do JS

Padrões

1. Diálogo de Confirmação

O caso de uso mais simples. Pergunte sim/não, obtenha a resposta, siga em frente.

const answer = await prompt(`
<body style="font-family: system-ui; padding: 24px; background: white;">
  <h2 style="margin-top: 0;">Excluir 47 arquivos?</h2>
  <p style="color: #666;">Esta ação não pode ser desfeita.</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;">
      Excluir
    </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 (answer?.ok) { /* prosseguir com a exclusão */ }

2. Formulário de Entrada de Texto

Colete entrada estruturada. Enter envia, Escape cancela.

const result = 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;">Novo Componente</h3>
  <input id="name" placeholder="Nome do componente" autofocus />
  <select id="type">
    <option value="page">Página</option>
    <option value="component">Componente</option>
    <option value="layout">Layout</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;">Criar</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: 'Criar' });

if (result) console.log(`Criando ${result.type}: ${result.name}`);

3. Lista de Seleção

Escolha entre uma lista de opções. Clique ou use teclas de seta + Enter.

function pickFromList(title, items) {
  const itemsHTML = items.map((item, i) =>
    `<div class="item${i === 0 ? ' selected' : ''}" data-index="${i}" onclick="pick(${i})">${item}</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">${title}</div>
    <div id="list">${itemsHTML}</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 + items.length * 38, frameless: true });
}

const choice = await pickFromList('Escolha um framework', ['React', 'Vue', 'Svelte', 'Solid', 'Angular']);

4. Visualizador de Markdown / Conteúdo Rico

Mostre conteúdo formatado. Útil para pré-visualizar documentação gerada, READMEs ou diffs.

// Renderize markdown com uma biblioteca CDN
const markdown = `# Olá Mundo\n\nIsso é **negrito** e isso é \`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: 'Pré-visualização' });

5. Progresso ao Vivo / Saída em Stream

Envie atualizações para a janela conforme o trabalho progride.

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 = (text) => {
      document.getElementById('log').textContent += text + '\\n';
      window.scrollTo(0, document.body.scrollHeight);
    };
  </script>
</body>
`, { width: 500, height: 300, title: 'Progresso da Compilação' });

win.on('ready', () => {
  win.send(`appendLog('Iniciando compilação...')`);
  // Conforme seu processo executa, envie atualizações:
  win.send(`appendLog('✓ Compilados 42 arquivos')`);
  win.send(`appendLog('✓ Testes passaram')`);
  win.send(`appendLog('✓ Concluído!')`);
});

6. Modo Escuro/Claro Adaptativo

Use informações do sistema para estilizar a interface de acordo com a aparência do usuário.

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};">Interface Adaptativa</h2>
      <p>Modo escuro: ${appearance.darkMode ? 'ligado' : 'desligado'}</p>
      <p>Tela: ${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;">
        Fechar
      </button>
    </body>
  `);
});

7. Notificação Flutuante

Um toast breve que se auto-dispensa. Nenhuma interação necessária.

function notify(message, durationMs = 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);
    ">${message}</div>
  </body>
  `, { width: 300, height: 60, frameless: true, transparent: true, floating: true, clickThrough: true });

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

notify('✅ Implantado em produção');

8. Companheiro de Cursor

Um elemento visual que segue o cursor. Ótimo para indicadores de status do agente.

// Anel giratório que segue o 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, clickThrough: true, followCursor: true });

Dicas e Resolução de Problemas

Evite carregar jQuery, Bootstrap e bibliotecas pesadas se você realmente só precisa de alguns estilos e eventos. JavaScript puro e CSS inline são mais rápidos e reduzem a latência.

Use o padrão de formulário com teclas Enter/Escape. Isso torna a interação rápida e consistente com diálogos nativos.

Sempre trate de null e answer. Se o usuário fechar a janela sem interagir, prompt retorna null (e não lança um erro). Verifique antes de usar:

const result = await prompt(...);
if (result) { /* usar resultado */ } else { /* cancelado */ }

Mantenha o HTML auto-contido. Não assuma o acesso à rede ou a arquivos locais, exceto por bibliotecas CDN explícitas.

Considere o tamanho da janela. Janelas mais largas em diversos monitores podem parecer diferentes. Teste em 300-400px de largura para diálogos, 500-600px para visualizadores.

Monitore atualizações: O Glimpse é de código aberto em github.com/nicepkg/glimpse. Para atualizar, execute o script de configuração novamente: bash .../glimpse-path.sh.

No Windows, use URLs file:/// para imports ESM. O carregador de módulos do Node requer isso. Sempre use o caminho absoluto <RESOLVED_PATH>/src/glimpse.mjs.

Habilidades Relacionadas