The canvas, for agents.
A Clearly composition is a real design canvas — frames, text, vectors, images, auto-layout, components, prototypes. Agents drive it with three tools: see it, change it, and ask what else is possible. It works headless — no browser tab, no screen, no human watching — so an agent can build a page while you sleep and you open the tab to find it done.
The three tools
One loop: perceive → act → perceive again. The third tells you everything the first two can do.
clearly_canvas_perceive— SEE the canvas — the scene as a "room": contents (every node, its box, style + flags), gaze (pan/zoom), focus (selection/tool), instruments (on-screen controls). Optionally includePixels for a rendered PNG.clearly_canvas_act— CHANGE the canvas — the single mutation path. Takes one { action, args } or a batch:[…]. Every verb goes through here.clearly_canvas_catalog— KNOW the canvas — every action, its exact args, and which ones work headless. Generated from the live dispatch map, so it is never out of date.
Same three over the CLI (beehaven), where they are canvas-perceive, canvas-act and canvas-catalog. Same server, same verbs — MCP is the tool-call surface, the CLI is the shell surface.
Build something
A frame and a headline, in one call. Batch the whole build — each CLI call is a fresh process.
From the terminal
Place every node at explicit x/y so you own the layout. For a real build, send the whole thing as ONE batch rather than a call per node.
beehaven connect home
beehaven call canvas-act '{
"compositionId": "<id>",
"batch": [
{ "action": "frame.create", "args": { "name": "Hero", "x": 0, "y": 0, "w": 1440, "h": 720, "fill": "#0B0B0F" } },
{ "action": "canvas.create-node", "args": { "type": "text", "name": "headline", "text": "Ship faster",
"x": 120, "y": 260, "size": 72, "fontWeight": 800, "fill": "#FFFFFF" } }
]
}'Then check your work
The verification half. An agent that builds without perceiving is guessing — and canvas-act's own reply tells you how much of your batch actually ran.
beehaven call canvas-perceive '{"compositionId":"<id>","format":"text"}'count in the act reply is how many ops ran, and a batch stops at the first failure — if it is lower than what you sent, fix the error and resend the rest. format:"text" is a readable summary but lists only the first 40 objects (it says so when it truncates); use the default JSON to check a bigger build node by node.
Headless vs. tab-only
Most verbs need no browser. A sizeable minority do — and the catalog is the only list that stays true.
Ask, don't guess
headlessActions is generated from the dispatch map at runtime. Any prose list — including this page — can rot; that one cannot.
beehaven call canvas-catalog '{}' | jq .headlessActions # what works with no tab
beehaven call canvas-catalog '{"query":"align"}' # find a verb + its exact argsAnything not in headlessActions needs the composition open in a tab, and calling it from a headless build fails with no-client-connected. At the time of writing that includes point-level vector editing, boolean ops, undo/redo, the clipboard, selection, and PNG export — while SVG and PDF export, and everything structural, are headless. Trust the catalog over this paragraph.
With no tab open, canvas-perceive reads the persisted scene and returns live:false — you get structure and style but not the ephemeral state (selection, viewport, chrome) that only a tab has. An agent can still drive its own camera and render includePixels through it.
The node shape
Flat. Every field sits on the node itself — there is no style object.
{ id, name*, type*, x, y, w, h } // common — name is REQUIRED on every create
text: { text*, size, fontWeight, fill, align, lineHeight, letterSpacing }
rect/frame: { fill, stroke, strokeWidth, radius }
image/video: { src*, fit }
// NOT: width/height (use w/h) · fontSize (use size) · "bold" (use fontWeight 100-900)
// NOT: a nested style:{} — a text node's body is `text`, never style.textparentId is membership, not a coordinate system: a child keeps world coordinates, so nesting a node in a frame does not re-origin it. Pass parentId on create and the relative coords you give are converted to world for you.
Depth and paint work headless too — shadow, blur, opacity, blendMode, gradient, strokeAlign, isMask — and canvas-perceive reports them back, so you can verify your own elevation without looking at a screen.
The authoritative, always-current field list is the catalog: beehaven call canvas-catalog {"query":"update-nodes"}.
Connect
MCP works everywhere. The CLI ships with the desktop app, which is macOS today.
MCP — any OS, nothing to install
OAuth browser sign-in on first connect. Scopes: rpc:read for perceive + catalog, rpc:write for act.
https://relay.clearly.sh/mcp
Point Claude Code, Cursor, or any MCP client here and it drives the canvas — no download, no daemon, no local setup. If you are not on a Mac, this is the path: the desktop app (which is what carries the CLI and the local agent rooms) is macOS-only for now, so MCP is how Windows and Linux reach the same surface. Nothing about the canvas is reduced — perceive, act and catalog are the same three verbs either way.
Full setup, scopes and agent identity: Clearly MCP.
CLI — with the desktop app
The beehaven CLI ships inside the Clearly desktop app (macOS). Sign in and it is set up for your account — no tokens to manage.
beehaven connect home
beehaven call canvas-catalog '{}'Full reference: Beehaven CLI.
Where this fits
Three ways to reach the same canvas.
The human path — paste anything onto the canvas, hit Send, and the agent builds. Same canvas, no code.
This page — the agent path: your own agent driving the canvas over MCP or the CLI.
The desktop rooms — the Conversation panel in the desktop app runs Claude, Codex or Gemini under the login you already made with that CLI (claude login and friends). They drive this same surface, and their chats stay on your machine.