Native macOS micro-UI for scripts and agents. Opens a WKWebView window in under 50ms with bidirectional JSON Lines communication over stdin/stdout.
Two source files, zero dependencies:
src/glimpse.swift — Native binary (Swift/Cocoa/WebKit, ~420 lines)
src/glimpse.mjs — Node.js ESM wrapper (EventEmitter API, ~175 lines)
The Swift binary is a standalone CLI that speaks JSON Lines. The Node wrapper is a convenience layer — any language that can spawn a process and pipe JSON can use Glimpse.
Node → Swift (stdin): {"type":"html","html":"<base64>"}
{"type":"eval","js":"..."}
{"type":"file","path":"/tmp/page.html"}
{"type":"get-info"}
{"type":"follow-cursor","enabled":true,"mode":"spring"}
{"type":"close"}
Swift → Node (stdout): {"type":"ready","screen":{...},"appearance":{...},"cursor":{...},"screens":[...]}
{"type":"info","screen":{...},"appearance":{...},"cursor":{...},"screens":[...]}
{"type":"message","data":{...}}
{"type":"closed"}
HTML payloads are base64-encoded. stderr is reserved for debug logging ([glimpse] ...).
Every page gets window.glimpse injected at document start:
window.glimpse.send(data)— sends JSON to Node (main frame only)window.glimpse.close()— closes window
swiftc -O src/glimpse.swift -o src/glimpse # or: npm run buildEvery Mac has swiftc — no Xcode project, no Package.swift, no SPM. Single-file compilation.
npm test # or: node test/test.mjsEnd-to-end integration test: open → ready → eval (click button) → message → close → closed. Requires a window server (can't run headless).
- Single-file Swift. Everything is in
glimpse.swift. No splitting into multiple files — the simplicity is the feature. - No external dependencies. Node wrapper uses only
node:built-ins. Swift uses only system frameworks (Cocoa, WebKit). - ESM only. The Node wrapper is a pure ES module. No CJS, no bundler.
- Protocol-first. New features should be expressible as JSON Lines commands/events. The Node wrapper is just sugar.
- Compile on install.
npm run build/postinstallcompiles the binary. Users can fork and modify the Swift source.
@MainActoron AppDelegate — all UI work is main-thread boundnonisolatedon delegate callbacks +MainActor.assumeIsolated {}to re-enter isolation- Stdin reading on
DispatchQueue.global(qos: .userInitiated), dispatching results back to main withDispatchQueue.main.async GlimpsePanelsubclass ofNSWindowoverridescanBecomeKey/canBecomeMainfor keyboard support in frameless modewriteToStdout()helper: JSON serialize + newline + fflush
GlimpseWindowextendsEventEmitterwith private class fields (#proc,#closed,#pendingHTML,#info)- Two-phase ready handshake: first
ready= blank page (triggers HTML send), secondready= user content loaded (emitted to caller with system info) #write()guards on#closedto prevent EPIPE crashesprompt()uses aresolvedflag to prevent double-settlement across message/closed/error/timeout paths#infocaches the last system info fromready/infoevents, accessible viawin.infogetter
- Add field to
Configstruct - Add case to
parseArgs() - Use the value in
setupWindow(),setupWebView(), orapplicationDidFinishLaunching() - Add mapping in
open()inglimpse.mjs - Document in README.md and SKILL.md
- Add case to
handleCommand()in Swift - Add method on
GlimpseWindowclass in Node wrapper - Document in README.md Protocol section
- Call
writeToStdout()in Swift at the right moment - Add case to the
switch (msg.type)inGlimpseWindowconstructor - Emit via
this.emit('eventname', ...) - Document in README.md
- Bump
versioninpackage.json - Add entry to
CHANGELOG.md(focus on what matters, not commit noise) - Commit, tag
vX.Y.Z, push with tags
src/glimpse.swift — The native binary (THE core)
src/glimpse.mjs — Node.js ESM wrapper
bin/glimpse.mjs — CLI entry point (npx glimpseui)
test/test.mjs — Integration test
scripts/publish.sh — npm publish with preflight checks
package.json — NPM config, build/postinstall scripts, pi package manifest
README.md — User-facing docs (API, protocol, CLI)
skills/glimpse/SKILL.md — Agent skill (patterns, examples, creative ideas)
CHANGELOG.md — Release notes
AGENTS.md — This file (project conventions for agents)
.gitignore — Excludes compiled binary, node_modules
examples/companion/ — Example: Pi companion extension (cursor-following agent status)
examples/companion/index.ts — Extension entry point (/companion command, event tracking)
examples/companion/companion.mjs — Standalone companion process (Glimpse window, state polling)
examples/companion/socket-path.mjs — Cross-platform socket path helper