Was macht Glimpse — Native Micro-UI?
Glimpse öffnet ein natives Fenster mit einem Webview in unter 50 ms. Du schreibst HTML, der Benutzer sieht es sofort. Bidirektionale Kommunikation über window.glimpse.send() (Webview → Node) und .send(js) (Node → Webview). Funktioniert auf macOS (WKWebView), Linux (WebKitGTK) und Windows (WebView2).
Wann sollte Glimpse verwendet werden:
- Du benötigst Benutzereingaben, die über Ja/Nein hinausgehen (Formulare, Auswahlen, Texteingabe)
- Du möchtest etwas Visuelles anzeigen (Diagramme, Markdown, Bilder, Diffs)
- Du möchtest eine destruktive Aktion mit einem ordentlichen Dialog bestätigen
- Du möchtest einen schwebenden Indikator, eine Benachrichtigung oder ein Companion-Widget
- Der Benutzer soll mit umfangreichen Inhalten interagieren
Desktop-Apps: Überprüfe, ob du in einer Desktop-App läufst, die HTML, Diagramme und Formulare inline im Gespräch anzeigen kann:
- Claude Desktop-App:
CLAUDE_CODE_ENTRYPOINTistclaude-desktop. - Codex in der ChatGPT Desktop-App:
CODEX_INTERNAL_ORIGINATOR_OVERRIDEistCodex Desktopodercodex_work_desktop. Die Inline-Anzeige ist das gebündelte Skillvisualize.
echo "claude=${CLAUDE_CODE_ENTRYPOINT:-} codex=${CODEX_INTERNAL_ORIGINATOR_OVERRIDE:-}"
In einer Desktop-App bevorzugst du die Inline-Anzeige der App. Verwende Glimpse dort nur, wenn die Inline-Anzeige etwas nicht leisten kann: ein laufendes Skript, das blockiert, bis der Benutzer antwortet; ein Fenster, das außerhalb der App sichtbar bleibt (schwebend, Status-Punkt, Cursor-folgend); oder eine Benutzeroberfläche für ein Skript, das ohne den Agenten läuft. Verwende in einem Terminal (jeder andere Wert oder beide Variablen nicht gesetzt) Glimpse wie unten beschrieben.
Einrichtung (glimpse-everything-Plugin): glimpseui wird bei der ersten Verwendung in dieses Plugin installiert. Führe dies einmal pro Sitzung aus, bevor du ein Glimpse-Skript schreibst. Es installiert glimpseui bei Bedarf, erstellt den nativen Helfer und gibt den absoluten Pfad des installierten Pakets auf stdout aus. Dieser ausgegebene Pfad ist <RESOLVED_PATH> überall unten.
bash "${CLAUDE_PLUGIN_ROOT:-${CODEX_PLUGIN_ROOT:-}}/bin/glimpse-path.sh"
Wenn weder CLAUDE_PLUGIN_ROOT noch CODEX_PLUGIN_ROOT gesetzt ist, führe stattdessen ../../bin/glimpse-path.sh relativ zum Verzeichnis dieser Skill-Datei aus. Die erste Ausführung kann eine Weile dauern (npm install plus nativer Build); der Fortschritt geht an stderr. Wenn es mit einem Fehler beendet wird, zeige dem Benutzer das stderr, in dem das fehlende Build-Tool genannt wird.
Import: Verwende immer den absoluten Pfad zu glimpse.mjs innerhalb des installierten Pakets — der reine Bezeichner 'glimpseui' schlägt fehl, wenn Skripte von /tmp oder von einem Ort ohne node_modules ausgeführt werden.
Auf macOS/Linux verwende einen direkten Pfad:
import { open, prompt } from '<RESOLVED_PATH>/src/glimpse.mjs';
Auf Windows erfordern ESM-Importe eine file:///-URL:
import { open, prompt } from 'file:///<RESOLVED_PATH>/src/glimpse.mjs';
// z.B. import { open, prompt } from 'file:///C:/Users/me/.pi/agent/.../glimpseui/src/glimpse.mjs';
Kurzreferenz
Einmaliger Dialog (prompt)
import { prompt } from '<RESOLVED_PATH>/src/glimpse.mjs';
const answer = await prompt(html, {
width: 400, height: 300, // Fenstergröße
title: 'Mein Dialog', // Text der Titelleiste
frameless: true, // keine Titelleiste
transparent: true, // durchsichtiger Hintergrund
});
// answer = Daten von window.glimpse.send() oder null, wenn der Benutzer das Fenster geschlossen hat
Beständiges Fenster (open)
import { open } from '<RESOLVED_PATH>/src/glimpse.mjs';
const win = open(html, options);
win.on('ready', (info) => {}); // HTML geladen — info enthält Bildschirm, Darstellung, Cursor
win.on('message', data => {}); // Benutzerinteraktion
win.on('info', info => {}); // aktuelle Systeminformationen (nach getInfo())
win.on('closed', () => {}); // Fenster weg
win.send('document.title = "Hi"'); // JS im Webview auswerten
win.setHTML('<h1>Neuer Inhalt</h1>'); // HTML ersetzen
win.info; // zuletzt bekannte Systeminformationen
win.getInfo(); // aktuelle Informationen anfordern
win.close(); // Fenster schließen
Alle Optionen
{
width, height, // Pixel (Standard: 800×600)
title, // Fenstertitel (Standard: "Glimpse")
frameless: true, // keine Titelleiste, verschiebbar über Hintergrund
floating: true, // immer im Vordergrund
transparent: true, // durchsichtiger Fensterhintergrund
clickThrough: true, // Maus geht durch das Fenster hindurch
followCursor: true, // Fenster folgt dem Mauszeiger
followMode: 'spring', // 'snap' (sofort, Standard) oder 'spring' (elastisch)
cursorAnchor: 'oben-rechts', // Eimrastpunkt: oben-links, oben-rechts, rechts, unten-rechts, unten-links, links
cursorOffset: {x, y}, // Versatz vom Cursor (Standard: 20, -20)
openLinks: true, // angeklickte http/https-Links im Standardbrowser öffnen
openLinksApp: '/Applications/Google Chrome.app', // optionaler App-Bundle-Pfad
autoClose: true, // nach der ersten Nachricht schließen
noDock: true, // kein Dock-Symbol oder App-Umschalter-Eintrag (macOS)
x, y, // exakte Bildschirmposition
timeout, // für prompt() — ms vor dem Ablehnen
}
Systeminformationen (verfügbar mit ready)
win.on('ready', (info) => {
// Bildschirm
info.screen.width // 2560 (volle Auflösung)
info.screen.height // 1440
info.screen.scaleFactor // 2 (Retina)
info.screen.visibleWidth // 2560 (ohne Dock)
info.screen.visibleHeight // 1367 (ohne Menüleiste)
// Darstellung
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
// Alle Monitore
info.screens // [{ x, y, width, height, scaleFactor, ... }, ...]
});
// Jederzeit nach ready zugreifbar:
win.info.screen.width
win.info.appearance.darkMode
JavaScript-Brücke in der Seite
window.glimpse.send(data) // Daten an Node senden (jeder JSON-serialisierbare Wert)
window.glimpse.close() // Fenster aus JS schließen
Muster
1. Bestätigungsdialog
Der einfachste Anwendungsfall. Frage Ja/Nein, erhalte die Antwort, mach weiter.
const answer = await prompt(`
<body style="font-family: system-ui; padding: 24px; background: white;">
<h2 style="margin-top: 0;">47 Dateien löschen?</h2>
<p style="color: #666;">Dies kann nicht rückgängig gemacht werden.</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;">
Abbrechen
</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;">
Löschen
</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: 'Bestätigen' });
if (answer?.ok) { /* mit dem Löschen fortfahren */ }
2. Texteingabe-Formular
Erfasse strukturierte Eingaben. Enter sendet ab, Escape bricht ab.
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;">Neue Komponente</h3>
<input id="name" placeholder="Komponentenname" autofocus />
<select id="type">
<option value="page">Seite</option>
<option value="component">Komponente</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;">Abbrechen</button>
<button onclick="submit()" style="background: #4299e1; color: white;">Erstellen</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: 'Erstellen' });
if (result) console.log(`Erstelle ${result.type}: ${result.name}`);
3. Auswahlliste
Wähle aus einer Liste von Optionen. Klicke oder verwende Pfeiltasten + Enter.
function pickFromList(titel, 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">${titel}</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('Wähle ein Framework', ['React', 'Vue', 'Svelte', 'Solid', 'Angular']);
4. Markdown-/Rich-Content-Viewer
Zeige formatierten Inhalt an. Nützlich für die Vorschau von generierten Dokumentationen, READMEs oder Diffs.
// Markdown mit einer CDN-Bibliothek rendern
const markdown = `# Hallo Welt\n\nDas ist **fett** und das ist \`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: 'Vorschau' });
5. Live-Fortschritt / Streaming-Ausgabe
Schiebe Aktualisierungen in das Fenster, während die Arbeit fortschreitet.
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: 'Build-Fortschritt' });
win.on('ready', () => {
win.send(`appendLog('Baue start...')`);
// Während dein Prozess läuft, Aktualisierungen senden:
win.send(`appendLog('✓ 42 Dateien kompiliert')`);
win.send(`appendLog('✓ Tests bestanden')`);
win.send(`appendLog('✓ Fertig!')`);
});
6. Adaptive Dunkel-/Hell-Modus
Verwende Systeminformationen, um die Benutzeroberfläche an die Erscheinung des Benutzers anzupassen.
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};">Adaptive Benutzeroberfläche</h2>
<p>Dunkelmodus: ${appearance.darkMode ? 'an' : 'aus'}</p>
<p>Bildschirm: ${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;">
Schließen
</button>
</body>
`);
});
7. Schwebende Benachrichtigung
Ein kurzer, automatisch verschwindender Toast. Keine Interaktion erforderlich.
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('✅ In Produktion bereitgestellt');
8. Cursor-Begleiter
Ein visuelles Element, das dem Cursor folgt. Großartig für Agent-Statusanzeigen.
// Rotierender Ring, der dem Cursor folgt
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="1.5s" repeatCount="indefinite" />
</circle>
</svg>
</body>
`, { width: 50, height: 50, frameless: true, transparent: true, floating: true, followCursor: true });
// Später, wenn die Arbeit erledigt ist:
// win.close();