--- name: decal description: Edit decal.sh image boards as an agent — open a board (a shared /r/ room, a private end-to-end-encrypted /p/ room, or a local board), read it as JSON, and do anything a human can in the editor (select, move, draw figures/text/equations, import images, remove backgrounds, cut out subjects, inpaint, upscale, group, export) through the `decal` CLI or its local HTTP API. Use when the user mentions decal, decal.sh, a /r/ or /p/ board link, or wants an image board/canvas edited, annotated or composed programmatically. --- # decal decal (https://decal.sh) is a WebGPU image-editing canvas. The `decal` CLI runs a real decal editor headlessly — the same app, models and actions a human uses — and lets you drive it. **Every action the UI has, you have**: the CLI reads the action list live from the page (the ⌘K palette, buttons and shortcuts all run the same registry), so `decal actions` is always the truth. ## Install (once) ```sh curl -fsSL https://decal.sh/install.sh | sh # CLI + Chromium + this skill decal version ``` Needs Node ≥ 22. No GPU? Fine — sessions fall back to software WebGPU. ## The loop ```sh decal open https://decal.sh/r/3-brave-otters-jump-quickly # or: decal open new | decal open (local board) decal state # the board as JSON — read this first, and after edits decal actions # what can run right now (add --all to see why others can't) decal describe insert.figure # parameters of one action (same as: decal insert.figure --help) decal insert.figure --kind rectangle --from 0,0 --to 240,160 --color '#e03131' decal screenshot view.png # look at what a human would see decal close # ALWAYS close: it saves the room. If the save fails it # says so and stays open — run it again; never --force # unless discarding the edits is fine ``` - `decal open` prints the room URL; share it with the user (humans in the room see your edits live and your cursor labelled " (agent)"; set the name with `--name`). - Several boards at once — or several agents on one machine: add `--session ` (or set `DECAL_SESSION`) on every command. Never `decal close` a session you didn't open. - `decal guide` prints this guide; `decal help` the command summary. - Output is JSON on stdout; errors go to stderr with a non-zero exit and say exactly what was wrong (`params.x: expected number, got string`, `selection.delete is unavailable: select something first`). ## Coordinates and state - Everything is in **world units** (1 unit = 1 image pixel at 100% zoom). The view doesn't matter — you never need to pan to act somewhere. - `decal state` gives `records` (topmost first, children after their parent) with `id`, `type` (image/path/text/latex/group), `title` (the layer panel's name), `bounds {x, y, width, height}` in world units, and the record's own fields; plus `selection`, `tool`, `preview`, `pendingResult`, `history`, `room` (peers + their cursors) and `viewport.worldBounds`. - Path points are omitted unless you pass `--points`. ## Doing things like a human | Goal | Actions | | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Select | `select.at --x --y` (click; alpha-aware — unfilled shapes only hit on their stroke), `select.in-rect`, `select.set --ids a,b`, `select.all`, `select.none` | | Arrange / edit selection | `selection.move --dx --dy`, `selection.delete`, `selection.duplicate`, `selection.group`, `selection.ungroup`, `selection.merge`, `selection.bring-to-front` … | | Edit one record | `records.update --id --patch '{"text":"…","color":"#4263eb","transform":{"rotation":0.3}}'` | | Create | `insert.figure` (rectangle/ellipse/line/arrow/tick/cross), `insert.text`, `insert.equation --source '\\int_0^1 x^2\\,dx'`, `insert.path --points x,y,p,…`, `insert.image --data @photo.jpg --x --y` | | Image ML | select one image, then `image.remove-background`, `image.depth-cut`, `image.cut-out-subject --points '[{"x":…,"y":…}]'`, `image.isolate-window --x --y`, `image.fix-perspective --x --y`, `image.upscale`, `image.inpaint --points x,y,p,… --radius 30`, `image.split`, `image.resize-content-aware --width --height` | | Review ML results | previews (cutout, depth of field): `cutout.adjust`, then `cutout.apply` or `cutout.cancel` (`dof.*` likewise). Baked results (upscale, inpaint): `result.keep` or `result.discard` | | Undo | `history.undo`, `history.redo` | | Export | `export.png --out board.png` / `export.svg --out board.svg` / `export.json` (`--scope selection` for just the selection) — without `--out` the content is printed (PNG as base64) | | Anything else a hand can do | `tool.set --kind brush` then `input.drag --points x,y,x,y,…`; `input.click --x --y`; `input.key --key g --meta` | Things that trip agents up: - **Creating selects the new record** (like the tools) — `select.none` before acting on something else. - ML actions need **exactly one image selected** and open a preview or a pending result; nothing else is baked until you apply/keep it. The first run of each model downloads weights (tens of MB) — it can take a while. - Text and equations are measured by the editor; change them with `records.update` (`text`, `fontSize`, `source`) and the box re-measures. - `decal state` after each meaningful step is cheap; prefer it over guessing. ## Private rooms `/p/alice/notes` is private to user alice; `/p/alice,bob/plans` to alice and bob (keybase-style: the name is the member list; content is end-to-end encrypted, the server only stores ciphertext). To open one, this machine must be a **device** of a member — accounts are a username plus the devices (browsers, agents) that hold its keys: ```sh decal login alice # prints {"pendingLink": {code, fingerprint}} and waits # → tell the user: ⌘K → "Add a device" in their decal tab, enter the code, # approve only if the fingerprint matches what you printed decal whoami # → {"username": "alice", …} once approved decal open p/alice/notes # or the full https://decal.sh/p/alice/notes URL decal room.open-private --board plans --members bob # new board for alice + bob (inside a session) ``` - Your own bot account instead: `decal signup ` (this machine becomes its first device). - An agent device can approve others (e.g. the user's new laptop): `decal device.lookup --code …`, check the fingerprint with them, then `decal device.approve --code … --fingerprint "…"`. - `decal account.devices` lists devices; `decal device.revoke --id …` cuts one off; `decal logout` forgets this machine's credentials (`~/.decal/device-.json`). - Never approve a code whose fingerprint you haven't confirmed with the person holding the new device. ## HTTP API (same actions, no CLI) `decal api` prints `{base, token}` for the running session: ```sh curl -H "Authorization: Bearer $TOKEN" $BASE/actions # list (JSON Schema params) curl -H "Authorization: Bearer $TOKEN" -X POST $BASE/actions/select.at -d '{"x":10,"y":20}' curl -H "Authorization: Bearer $TOKEN" $BASE/state curl -H "Authorization: Bearer $TOKEN" $BASE/screenshot > view.png ``` In a browser you control directly (Playwright etc.), the same API is `window.decal`: `await decal.actions()`, `await decal.run(id, params)`, `decal.state()`, `await decal.whenIdle()`.