canopy renders jsonl files as functional and interactive native GUI
Here is a hello world example
```example.jsonl
{"type":"createSurface","surfaceId":"main","title":"Tasks"}
{"type":"updateDataModel","surfaceId":"main","ops":[ {"op":"replace","path":"/tasks","value":[{"title":"Ship demo","done":false}]}]}
{"type":"updateComponents","surfaceId":"main","components":[ {"componentId":"root","type":"Column","children":["title"]}, {"componentId":"title","type":"Text","props":{"content":{"path":"/tasks/0/title"}}} ]}
```
jsonl to gui renderer is one part, canopy can connect with your llm provider directly and let the llm build the app thru these incremental jsonl.
canopy supports --claude or --codex at run in "reverse controlled mode", starts claude/codex with canopy as an MCP server and appropriate prompt.
the mcp also exposes a "screenshot" tool for itself so the LLM can actually "see" whats its build. and it can obviously interact with the app itself to test it.
the images in the readme are real examples.
apart from the basic native components like input field, scroll, layouts etc, canopy can do - ffi function calls - take its own screenshot - audio/video capture - api calls
for "demo" apps it works okay, i notice different kind of "behavioral issues" in the generated apps though, mostly in terms of state management for more complex apps, the issue gets multiplied. i tried a "pixel editor to ico export", the editor part worked fine (0 ergonomics for the user though), the export did not work at all. i coulnt get to a usable "sqlite browser" at this point.
but i am sure these are prompt problems/tooling that can be fixed with better abstractions (packages, reusable components)
https://github.com/artpar/canopy
go build -o canopy
tt start -p mypassword → get a URL (qrcode) → open in browser → enter password → connected.
Direct webrtc datachannel between your machine and browser.
Signaling server is a cloudflare worker, exchanges ~2KB of sdp/ice metadata then gets out of the way. Password never transmitted - argon2id derives 256-bit key locally on both ends. All terminal i/o gets nacl secretbox encryption before hitting the datachannel. double encrypted with dtls underneath but I wanted the relay to see nothing useful even during signaling.
go + pion/webrtc, about 14k loc. browser is xterm.js + webcrypto for argon2id. stun default, turn for symmetric nat.
my use case: checking on claude code runs from my phone when im not at my desk. hotel wifi, spotty mobile data. spent time on turn fallback, keepalive with reconnection, buffered writes during disconnects. pwa so it works from home screen and survives app switching. holds up ok on 3g.
Trade-offs: ice gathering takes 2-5s on connect. browser cant initiate, need cli on host. codes expire 24h.
Single binary, no deps. daemon mode for multiple sessions. tests with race detector, chaos tests for disconnects, network condition simulation. crypto and webrtc at ~72% coverage.
https://github.com/artpar/terminal-tunnel
ps: default relay runs on my cloudflare free tier so no guarantees. you can self-host the worker or run tt relay locally.