Attach protocol
Clients that draw terminals, like the app, connect to GET /v1/attach with a
WebSocket, one per client. The protocol keeps a client's copy of each
terminal identical to the server's.
Mirroring a terminal
- Send
{"type":"hello","name":"…","kind":"app","platform":"darwin","version":"…"}. The server answerswelcomewith your client ID.client.listshows the name and platform, so others know who holds a pane's size. - Send
{"type":"subscribe","block_id":"…"}. The server answerssubscribedwith aseq, then a snapshot frame. - Restore a libghostty terminal from the snapshot. Apply every frame that follows, in order.
seq counts the bytes the program has printed. Each frame says where it
starts, so a client can check that nothing is missing. A resize arrives as a
frame too, at the exact point in the output where it happened, because text
reflows differently depending on where the resize lands.
If a client falls behind, the server sends resync for that block instead of
dropping bytes. Subscribe again for a fresh snapshot.
To show a whole session, start the event stream first (events), then read
session.view, then subscribe to the panes on screen. Events that arrive in
between are then not missed. On events_resync, read the view again.
The same path works on a tailnet listener: a client on another machine
attaches to wss://box.example.ts.net/v1/attach.
Binary frames
byte 0 kind: 1 output, 2 snapshot, 3 resize
bytes 1-2 length of the block ID
… block ID
8 bytes seq
… data
A resize carries four 16-bit numbers: columns, rows, and the cell's width and height in pixels.
Messages
| Client sends | Does |
|---|---|
subscribe, unsubscribe |
Start or stop mirroring a block |
input |
Type data, or data_base64 |
key |
Press a named key |
resize |
Ask for a size. take: true claims it from another client; release: true sets it and gives the claim up, so the client in use takes the pane up again. Without take, only the holder may release, or anyone while nobody holds it. |
call |
Call any API method, with an id |
events |
Stream events on this connection, with since |
| Server sends | Means |
|---|---|
welcome |
Hello accepted: your client_id and the server's version |
subscribed |
Mirroring a block, from seq; a snapshot frame follows |
resync |
You fell behind on a block: subscribe again |
exited |
The block's program exited; no more output |
result |
The answer to a call, with its id: result, or error |
events_subscribed |
The event stream started, at seq |
event |
One event |
events_resync |
Events were lost: read the view again |
error |
A message couldn't be done, and why |
Your copy of the terminal must not answer the program. The server's terminal already did.