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 Desktopoucodex_work_desktop. Sua exibição em linha é a skillvisualizeintegrada.
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.