Que fait Glimpse — Micro-UI natif ?
Glimpse ouvre une fenêtre native avec une webview en moins de 50 ms. Vous écrivez du HTML, l'utilisateur le voit instantanément. Communication bidirectionnelle via window.glimpse.send() (webview → Node) et .send(js) (Node → webview). Fonctionne sur macOS (WKWebView), Linux (WebKitGTK) et Windows (WebView2).
Quand utiliser Glimpse :
- Vous avez besoin d'une saisie utilisateur plus complexe que oui/non (formulaires, sélections, texte)
- Vous voulez afficher quelque chose de visuel (graphiques, Markdown, images, diffs)
- Vous voulez confirmer une action destructrice avec une boîte de dialogue appropriée
- Vous voulez un indicateur flottant, une notification ou un widget compagnon
- Vous avez besoin que l'utilisateur interagisse avec du contenu riche
Applications de bureau : Vérifiez si vous êtes dans une application de bureau qui peut afficher du HTML, des graphiques et des formulaires en ligne dans la conversation :
- Application de bureau Claude :
CLAUDE_CODE_ENTRYPOINTestclaude-desktop. - Codex dans l'application de bureau ChatGPT :
CODEX_INTERNAL_ORIGINATOR_OVERRIDEestCodex Desktopoucodex_work_desktop. Son affichage en ligne est la compétencevisualizeintégrée.
echo "claude=${CLAUDE_CODE_ENTRYPOINT:-} codex=${CODEX_INTERNAL_ORIGINATOR_OVERRIDE:-}"
Dans une application de bureau, préférez l'affichage en ligne de l'application. Utilisez Glimpse uniquement lorsque l'affichage en ligne ne peut pas faire ce dont vous avez besoin : un script en cours d'exécution qui bloque jusqu'à ce que l'utilisateur réponde, une fenêtre qui reste visible en dehors de l'application (flottante, élément de la barre d'état, suivi du curseur) ou une interface utilisateur pour un script qui s'exécute sans l'agent. Dans un terminal (toute autre valeur, ou les deux variables non définies), utilisez Glimpse comme décrit ci-dessous.
Configuration (plugin glimpse-everything) : glimpseui est installé dans ce plugin lors de la première utilisation. Avant d'écrire un script Glimpse, exécutez ceci une fois par session. Il installe glimpseui si nécessaire, compile son helper natif et affiche le chemin absolu du paquet installé sur stdout. Ce chemin imprimé est <RESOLVED_PATH> partout ci-dessous.
bash "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-}}/bin/glimpse-path.sh"
Si ni CLAUDE_PLUGIN_ROOT ni CODEX_PLUGIN_ROOT n'est défini, exécutez ../../bin/glimpse-path.sh résolu par rapport au répertoire de ce fichier de compétence à la place. La première exécution peut prendre un certain temps (npm install plus une compilation native) ; la progression va sur stderr. En cas de sortie non nulle, affichez son stderr à l'utilisateur, qui nomme l'outil de compilation manquant.
Importation : Utilisez toujours le chemin absolu vers glimpse.mjs dans le paquet installé — le spécificateur nu 'glimpseui' échoue lorsque les scripts sont exécutés depuis /tmp ou n'importe où sans node_modules.
Sur macOS/Linux, utilisez un chemin direct :
import { open, prompt } from '<RESOLVED_PATH>/src/glimpse.mjs';
Sur Windows, les importations ESM nécessitent une URL file:/// :
import { open, prompt } from 'file:///<RESOLVED_PATH>/src/glimpse.mjs';
// ex. import { open, prompt } from 'file:///C:/Users/moi/.pi/agent/.../glimpseui/src/glimpse.mjs';
Référence rapide
Boîte de dialogue unique (prompt)
import { prompt } from '<RESOLVED_PATH>/src/glimpse.mjs';
const answer = await prompt(html, {
width: 400, height: 300, // taille de la fenêtre
title: 'Ma boîte de dialogue', // texte de la barre de titre
frameless: true, // pas de barre de titre
transparent: true, // arrière-plan transparent
});
// answer = données de window.glimpse.send(), ou null si l'utilisateur a fermé la fenêtre
Fenêtre persistante (open)
import { open } from '<RESOLVED_PATH>/src/glimpse.mjs';
const win = open(html, options);
win.on('ready', (info) => {}); // HTML chargé — info contient écran, apparence, curseur
win.on('message', data => {}); // interaction utilisateur
win.on('info', info => {}); // informations système fraîches (après getInfo())
win.on('closed', () => {}); // fenêtre disparue
win.send('document.title = "Salut"'); // évaluer du JS dans la webview
win.setHTML('<h1>Nouveau contenu</h1>'); // remplacer le HTML
win.info; // dernières informations système connues
win.getInfo(); // demander des informations fraîches
win.close(); // fermer la fenêtre
Toutes les options
{
width, height, // pixels (défaut : 800×600)
title, // titre de la fenêtre (défaut : "Glimpse")
frameless: true, // pas de barre de titre, déplaçable par l'arrière-plan
floating: true, // toujours au premier plan
transparent: true, // arrière-plan de fenêtre transparent
clickThrough: true, // la souris traverse la fenêtre
followCursor: true, // la fenêtre suit le curseur de la souris
followMode: 'spring', // 'snap' (instantané, défaut) ou 'spring' (élastique)
cursorAnchor: 'top-right', // point d'accroche : top-left, top-right, right, bottom-right, bottom-left, left
cursorOffset: {x, y}, // décalage par rapport au curseur (défaut : 20, -20)
openLinks: true, // ouvrir les liens http/https cliqués dans le navigateur par défaut
openLinksApp: '/Applications/Google Chrome.app', // chemin optionnel du bundle d'application
autoClose: true, // fermer après le premier message
noDock: true, // pas d'icône dans le Dock ni d'entrée dans le sélecteur d'applications (macOS)
x, y, // position exacte à l'écran
timeout, // pour prompt() seulement — ms avant le rejet
}
Informations système (disponibles au ready)
win.on('ready', (info) => {
// Écran
info.screen.width // 2560 (résolution complète)
info.screen.height // 1440
info.screen.scaleFactor // 2 (Retina)
info.screen.visibleWidth // 2560 (hors dock)
info.screen.visibleHeight // 1367 (hors barre de menus)
// Apparence
info.appearance.darkMode // true
info.appearance.accentColor // "#007AFF"
info.appearance.reduceMotion // false
info.appearance.increaseContrast // false
// Curseur
info.cursor.x // 500
info.cursor.y // 800
// Tous les moniteurs
info.screens // [{ x, y, width, height, scaleFactor, ... }, ...]
});
// Accès à tout moment après ready :
win.info.screen.width
win.info.appearance.darkMode
Pont JavaScript dans la page
window.glimpse.send(data) // envoyer des données à Node (toute valeur sérialisable en JSON)
window.glimpse.close() // fermer la fenêtre depuis JS
Modèles
1. Boîte de dialogue de confirmation
Le cas d'utilisation le plus simple. Demander oui/non, obtenir la réponse, passer à la suite.
const answer = await prompt(`
<body style="font-family: system-ui; padding: 24px; background: white;">
<h2 style="margin-top: 0;">Supprimer 47 fichiers ?</h2>
<p style="color: #666;">Cette action est irréversible.</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;">
Annuler
</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;">
Supprimer
</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: 'Confirmation' });
if (answer?.ok) { /* procéder à la suppression */ }
2. Formulaire de saisie de texte
Collecte une entrée structurée. Entrée valide, Échap annule.
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;">Nouveau composant</h3>
<input id="name" placeholder="Nom du composant" autofocus />
<select id="type">
<option value="page">Page</option>
<option value="component">Composant</option>
<option value="layout">Disposition</option>
</select>
<div style="display: flex; gap: 8px; justify-content: flex-end;">
<button onclick="window.glimpse.send(null)" style="background: #eee;">Annuler</button>
<button onclick="submit()" style="background: #4299e1; color: white;">Créer</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: 'Créer' });
if (result) console.log(`Création ${result.type} : ${result.name}`);
3. Liste de sélection
Choisissez parmi une liste d'options. Cliquez ou utilisez les flèches + Entrée.
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('Choisissez un framework', ['React', 'Vue', 'Svelte', 'Solid', 'Angular']);
4. Visualiseur de contenu Markdown / Riche
Affichez du contenu formaté. Utile pour prévisualiser de la documentation générée, des README ou des diffs.
// Rendre du Markdown avec une bibliothèque CDN
const markdown = `# Hello World\n\nCeci est **gras** et ceci est \`code\`.`;
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: 'Aperçu' });
5. Progression en direct / Sortie en streaming
Poussez des mises à jour dans la fenêtre au fur et à mesure de l'avancement du travail.
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: 'Progression de la construction' });
win.on('ready', () => {
win.send(`appendLog('Démarrage de la construction...')`);
// Au fur et à mesure que votre processus s'exécute, poussez les mises à jour :
win.send(`appendLog('✓ 42 fichiers compilés')`);
win.send(`appendLog('✓ Tests réussis')`);
win.send(`appendLog('✓ Terminé !')`);
});
6. Mode sombre/clair adaptatif
Utilisez les informations système pour styliser l'interface utilisateur afin qu'elle corresponde à l'apparence de l'utilisateur.
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 adaptative</h2>
<p>Mode sombre : ${appearance.darkMode ? 'activé' : 'désactivé'}</p>
<p>Écran : ${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;">
Fermer
</button>
</body>
`);
});
7. Notification flottante
Un toast bref qui se ferme automatiquement. Aucune interaction nécessaire.
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('✅ Déployé en production');
8. Compagnon de curseur
Un élément visuel qui suit le curseur. Idéal pour les indicateurs d'état d'agent.
// Anneau rotatif qui suit le curseur
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 });