Mac-only A2UI v0.8 appliance — a canvas agents own, a response surface for human-AI collaboration. Hardforked from HazAT/glimpse.
a2glimpse keeps Glimpse's native WKWebView shell and JSON Lines stdio framing, but removes the public HTML/eval interface. It accepts A2UI v0.8 JSONL, renders one trusted Lit-rendered surface, and emits user actions back on stdout.
This is built for me. Read HUMANS.txt. Agents read LLMS.txt.
The seven canonical patterns, painted by the canvas itself onto the canvas itself. All screenshots below are real surfaces driven through the MCP bridge during the screenshot session — no mockups.
| confirm | choice |
|---|---|
| multi-choice | free-text |
|---|---|
| status | diff-review |
|---|---|
| command-approval | |
|---|---|
git clone https://github.com/bdmorin/a2glimpse
cd a2glimpse
npm install # postinstall compiles src/a2glimpse via swiftc
npm link # puts `a2glimpse` and `a2glimpse-mcp` on PATHThe native binary is built to:
src/a2glimpseLocal-clone is the only supported install path. Not on npm; not planning to be.
npx a2glimpse --demoOr pipe A2UI v0.8 JSON Lines:
cat <<'JSONL' | npx a2glimpse --width 420 --height 260
{"surfaceUpdate":{"surfaceId":"demo","components":[{"id":"root","component":{"Column":{"children":{"explicitList":["title","button"]}}}},{"id":"title","component":{"Text":{"usageHint":"h3","text":{"literalString":"a2glimpse"}}}},{"id":"button_text","component":{"Text":{"text":{"literalString":"Confirm"}}}},{"id":"button","component":{"Button":{"child":"button_text","primary":true,"action":{"name":"confirm.clicked","context":[{"key":"answer","value":{"literalString":"yes"}}]}}}}]}}
{"dataModelUpdate":{"surfaceId":"demo","contents":[]}}
{"beginRendering":{"surfaceId":"demo","root":"root"}}
JSONLClicking the button emits:
{"userAction":{"name":"confirm.clicked","surfaceId":"demo","sourceComponentId":"button","timestamp":"...","context":{"answer":"yes"}}}import { open } from 'a2glimpse';
const win = open({ width: 420, height: 260 });
win.on('ready', () => {
win.dispatch({ surfaceUpdate: { surfaceId: 'demo', components: [] } });
});
win.on('userAction', action => console.log(action));
win.on('clientError', error => console.error(error));Stdin accepts:
- A2UI v0.8 messages:
surfaceUpdate,dataModelUpdate,beginRendering,deleteSurface - lifecycle/control commands:
{ "type": "get-info" },{ "type": "close" } - retained window commands for the spike:
show,title,resize,follow-cursor
Stdout emits:
{ "type": "ready", ...systemInfo }{ "type": "info", ...systemInfo }{ "userAction": { ... } }{ "error": { "message": "..." } }{ "type": "closed" }
- macOS only.
- A2UI v0.8 only.
- No public HTML, file, or eval command.
- The Lit renderer host is vendored as
src/a2glimpse-host.htmlfor the POC.
Multiple surfaces stack vertically in one window; the window auto-grows
to fit content. Per-surface action queues let an agent block on one
surface while another keeps emitting state. The reserved
__a2glimpse_debug surfaceId renders with distinct chrome for in-window
introspection.
a2glimpse is a stdin-driven appliance, not a long-lived MCP server. The bridge a2glimpse-mcp (in bin/) wraps the binary so any MCP-aware agent can drive it via tool calls. Lifetime is delegated to mcporter's daemon (lifecycle: "keep-alive") — the bridge stays warm across calls; the binary is held as the bridge's child process.
| Tool | Purpose |
|---|---|
surface_update |
Forward an A2UI v0.8 surfaceUpdate |
data_model_update |
Forward a dataModelUpdate |
begin_rendering |
Forward a beginRendering (adds the surface to the visible stack) |
delete_surface |
Forward a deleteSurface (removes from stack; window auto-shrinks) |
await_action |
Block until next userAction. Optional surfaceId filters per-surface queues |
resize |
Optional manual resize override (window auto-grows to content by default) |
get_info |
Return child geometry / system info |
self_check |
Bridge introspection — child state, queue depths, last actions, last rejections |
close |
Tear down the child window |
"a2glimpse": {
"command": "a2glimpse-mcp",
"lifecycle": "keep-alive"
}That's the entire registration story. Any agent that can shell out to mcporter call a2glimpse.<tool> can drive this.
For coding agents (Claude Code, Codex, anything SKILL.md-aware): a ready-to-load skill at skills/a2glimpse/SKILL.md teaches when to reach for a2glimpse and how to compose the seven canonical A2UI v0.8 patterns (confirm, choice, multi-choice, free-text, status, diff-review, command-approval).
The bridge IS the trust boundary for MCP-driven flows. Every A2UI message is validated before forwarding to the child's stdin: top-level key must be one of {surfaceUpdate, dataModelUpdate, beginRendering, deleteSurface}, and any html / file / eval key at any depth is rejected loudly. The renderer owns the rest of v0.8 schema semantics.
src/mcp/a2glimpse-mcp.ts— single-file TypeScript, ~330 lines. Node 22.18+ strips types natively at runtime.bin/a2glimpse-mcp.mjs— entry shim.test/mcp.mjs— integration test against a fake-a2glimpse stdin/stdout child. Run withnpm run test:mcp.
a2glimpse can be packaged as a Mac .app bundle for distribution and future
code-signing. The bundle is not a clickable launcher — it's an MCP appliance
spawned over stdin/stdout. Double-clicking the .app shows a friendly alert
and exits.
npm run build:app
# → dist/a2glimpse.appLayout:
a2glimpse.app/
Contents/
Info.plist (com.bdmorin.a2glimpse, LSUIElement)
MacOS/a2glimpse (compiled Swift binary)
Resources/
a2glimpse-host.html
MaterialSymbolsOutlined.woff2
AppIcon.icns (placeholder)
The bundle is unsigned. To sign and notarize for distribution see
knowledge/20260509-172625.apple-developer-onboarding.knowledge.md
— a from-zero walkthrough of the Apple Developer enrollment, certificate
hierarchy, codesign invocation, and notarytool flow.
The bridge spawns Contents/MacOS/a2glimpse directly; the wrapper around
it is purely for distribution identity.
This hardfork starts from upstream Glimpse v0.8.0. The fork point is tagged:
glimpse-upstream-v0.8.0-forkpointGlimpse's native-shell idea is doing the heavy lifting here; a2glimpse is an experiment in swapping the trust boundary from arbitrary HTML/JS to declarative A2UI.