Affinity Control
Affinity (/Applications/Affinity.app, the unified Canva-era app) has no AppleScript
dictionary, CLI, or URL scheme, and its file formats are proprietary. Its only automation
surface is the AI connector: when enabled, the app serves its JavaScript scripting SDK
on http://localhost:6767/sse. The endpoint speaks the MCP protocol, but this skill drives
it directly over HTTP/SSE with a zero-dependency Node CLI — no MCP client configuration,
no tool schemas loaded into context. Verified end-to-end against Affinity 3.2.3.
- scripts/affinity-cli.mjs — the CLI (Node ≥ 18, no npm install needed).
- references/api-reference.md — verified endpoint tool contracts + SDK preamble digest. Read it before writing scripts.
- references/sdk-map.md — curated SDK digest: module map, node model, creation recipes (text, image, adjustments), gotchas. Read it when writing anything beyond the basic drawing recipe.
- references/sdk-docs/ — the complete vendored SDK documentation (109 topics, ~1.3 MB). Grep it for exact signatures only after sdk-map.md; never read the big files whole (
nodes.js is 180 KB).
The control loop
Preflight — the endpoint cannot be enabled remotely:
node scripts/affinity-cli.mjs ping # → "OK http://localhost:6767 — Affinity 1.0.0"
If it fails, ask the user to launch Affinity and enable the AI connector
(Affinity Settings → AI connector; see the setup guide
— the Claude-side connector steps there are NOT needed, only the in-app toggle).
Write the script — JavaScript against the Affinity SDK. Start from
references/api-reference.md, then
references/sdk-map.md for the right module and recipe;
grep references/sdk-docs/ for exact signatures. Scripts are top-level executable code; output ONLY via console.log()
(return values are dropped). Entry point:
const { app } = require('/application');
const doc = app.documents.current;
When an API resists, search the crowd-sourced hints pool:
node scripts/affinity-cli.mjs search "set blend mode".
Execute:
node scripts/affinity-cli.mjs run myscript.js
Runs via the endpoint's execute_script and prints the script's console output.
Errors come back as text — iterate on the file and re-run.
Verify visually — never assume a script worked:
node scripts/affinity-cli.mjs render --out /tmp/check.jpg # current spread
node scripts/affinity-cli.mjs render --selection --out /tmp/sel.jpg # selection only
Then Read the JPEG (max 1024 px). The document session uuid is fetched automatically.
Ship — when the user is happy with a script, install it for reuse:
node scripts/affinity-cli.mjs add --title "My Script" --description "What it does" --file myscript.js
Library scripts appear in Affinity under Window → General → Scripts.
CLI cheatsheet
node scripts/affinity-cli.mjs ping # endpoint alive?
node scripts/affinity-cli.mjs run file.js # execute script, print console output
node scripts/affinity-cli.mjs render [--selection] [--spread N] --out f.jpg
node scripts/affinity-cli.mjs tools [--json] # list endpoint tools (verified list in api-reference)
node scripts/affinity-cli.mjs call <tool> '{"arg":"val"}' # generic tool call
node scripts/affinity-cli.mjs add --title "T" --description "D" --file f.js
node scripts/affinity-cli.mjs list # library scripts
node scripts/affinity-cli.mjs save --title "T" --out f.js # export library script to disk
node scripts/affinity-cli.mjs search "query" # SDK hints search
node scripts/affinity-cli.mjs docs [<topic>] # list / print SDK doc topics
node scripts/affinity-cli.mjs docs-dump <dir> # (re-)vendor SDK docs; resumable
Env overrides: AFFINITY_MCP_URL (default http://localhost:6767), AFFINITY_TIMEOUT_MS
(default 120000 — raise for long batch scripts).
Known limitations
NOT_ALLOWED from any script command = the user disabled AI / filesystem / networking
for scripting in Affinity settings — ask them to enable what's needed.
- Script filesystem access is Desktop-only (
app.userDesktopPath); no network from
script code. Prefer render for verification over script-side exports.
- Library scripts cannot be deleted through the endpoint — only from the Scripts panel.
- Setting the current spread clears the selection; set it only when actually switching.
- Endpoint sessions can die during long batches (POST → HTTP 404) — just re-run; each CLI
invocation is a fresh session (the preamble gate is handled automatically).
- If the SDK docs seem stale after an Affinity update, re-run
docs-dump references/sdk-docs and refresh the api-reference digest.
- Fallback if the endpoint route is ever unavailable: macOS GUI automation
(System Events keystrokes/menus) works but is brittle and unverifiable — use it only for
trivial one-shot actions, never for document editing.
Security
Unlike the other app-automation skills, this one opens no port of its own — the
listener is Affinity's. But turning the AI connector on is still a decision worth naming.
- Enabling the connector makes Affinity serve its full scripting SDK on
http://localhost:6767/sse, an endpoint that executes JavaScript against the user's open
documents. affinity-cli.mjs is only a client of it.
- The endpoint carries no authentication: while it is enabled, every local process, and
every other user on a shared machine, can drive Affinity through it. The CLI refuses an
SSE endpoint that redirects off
AFFINITY_MCP_URL's origin, so a hijacked session can't
silently repoint it — but that protects this client, not the endpoint.
- What scripts may touch is gated by Affinity's own AI / filesystem / networking switches
(a
NOT_ALLOWED reply means one is off). Turning one on widens what any client can do,
not just this skill — ask rather than assume.
- To close it, turn the AI connector off in Affinity's settings. The endpoint goes with
it; nothing this skill installed keeps listening.
1---2name: affinity3description: Remote-control Affinity (the unified Affinity by Canva app: vector, pixel, and layout studios) with JavaScript through its local automation endpoint — no MCP client configuration needed. Automate document edits, batch operations, text/layer manipulation, and reusable library scripts, with rendered-JPEG visual verification. Use whenever the user wants to script or automate Affinity, programmatically edit .af / .afdesign / .afphoto / .afpub documents, batch-process designs, or says "Affinity", "Affinity script", "automate Affinity", "batch edit in Affinity", "control Affinity" — even if they don't mention scripting.4---56# Affinity Control78Affinity (`/Applications/Affinity.app`, the unified Canva-era app) has **no** AppleScript9dictionary, CLI, or URL scheme, and its file formats are proprietary. Its only automation10surface is the **AI connector**: when enabled, the app serves its JavaScript scripting SDK11on `http://localhost:6767/sse`. The endpoint speaks the MCP protocol, but this skill drives12it directly over HTTP/SSE with a zero-dependency Node CLI — no MCP client configuration,13no tool schemas loaded into context. Verified end-to-end against Affinity 3.2.3.1415- [scripts/affinity-cli.mjs](scripts/affinity-cli.mjs) — the CLI (Node ≥ 18, no npm install needed).16- [references/api-reference.md](references/api-reference.md) — verified endpoint tool contracts + SDK preamble digest. **Read it before writing scripts.**17- [references/sdk-map.md](references/sdk-map.md) — curated SDK digest: module map, node model, creation recipes (text, image, adjustments), gotchas. Read it when writing anything beyond the basic drawing recipe.18- [references/sdk-docs/](references/sdk-docs/) — the complete vendored SDK documentation (109 topics, ~1.3 MB). Grep it for exact signatures only after sdk-map.md; never read the big files whole (`nodes.js` is 180 KB).1920## The control loop21221. **Preflight** — the endpoint cannot be enabled remotely:2324 ```bash25 node scripts/affinity-cli.mjs ping # → "OK http://localhost:6767 — Affinity 1.0.0"26 ```2728 If it fails, ask the user to launch Affinity and enable the AI connector29 (Affinity Settings → AI connector; see the [setup guide](https://www.affinity.studio/help/ai-connector-setup/)30 — the Claude-side connector steps there are NOT needed, only the in-app toggle).31322. **Write the script** — JavaScript against the Affinity SDK. Start from33 [references/api-reference.md](references/api-reference.md), then34 [references/sdk-map.md](references/sdk-map.md) for the right module and recipe;35 grep `references/sdk-docs/` for exact signatures. Scripts are top-level executable code; output ONLY via `console.log()`36 (return values are dropped). Entry point:3738 ```js39 const { app } = require('/application');40 const doc = app.documents.current;41 ```4243 When an API resists, search the crowd-sourced hints pool:44 `node scripts/affinity-cli.mjs search "set blend mode"`.45463. **Execute**:4748 ```bash49 node scripts/affinity-cli.mjs run myscript.js50 ```5152 Runs via the endpoint's `execute_script` and prints the script's console output.53 Errors come back as text — iterate on the file and re-run.54554. **Verify visually** — never assume a script worked:5657 ```bash58 node scripts/affinity-cli.mjs render --out /tmp/check.jpg # current spread59 node scripts/affinity-cli.mjs render --selection --out /tmp/sel.jpg # selection only60 ```6162 Then `Read` the JPEG (max 1024 px). The document session uuid is fetched automatically.63645. **Ship** — when the user is happy with a script, install it for reuse:6566 ```bash67 node scripts/affinity-cli.mjs add --title "My Script" --description "What it does" --file myscript.js68 ```6970 Library scripts appear in Affinity under **Window → General → Scripts**.7172## CLI cheatsheet7374```bash75node scripts/affinity-cli.mjs ping # endpoint alive?76node scripts/affinity-cli.mjs run file.js # execute script, print console output77node scripts/affinity-cli.mjs render [--selection] [--spread N] --out f.jpg78node scripts/affinity-cli.mjs tools [--json] # list endpoint tools (verified list in api-reference)79node scripts/affinity-cli.mjs call <tool> '{"arg":"val"}' # generic tool call80node scripts/affinity-cli.mjs add --title "T" --description "D" --file f.js81node scripts/affinity-cli.mjs list # library scripts82node scripts/affinity-cli.mjs save --title "T" --out f.js # export library script to disk83node scripts/affinity-cli.mjs search "query" # SDK hints search84node scripts/affinity-cli.mjs docs [<topic>] # list / print SDK doc topics85node scripts/affinity-cli.mjs docs-dump <dir> # (re-)vendor SDK docs; resumable86```8788Env overrides: `AFFINITY_MCP_URL` (default `http://localhost:6767`), `AFFINITY_TIMEOUT_MS`89(default 120000 — raise for long batch scripts).9091## Known limitations9293- `NOT_ALLOWED` from any script command = the user disabled AI / filesystem / networking94 for scripting in Affinity settings — ask them to enable what's needed.95- Script filesystem access is **Desktop-only** (`app.userDesktopPath`); no network from96 script code. Prefer `render` for verification over script-side exports.97- Library scripts **cannot be deleted** through the endpoint — only from the Scripts panel.98- Setting the current spread clears the selection; set it only when actually switching.99- Endpoint sessions can die during long batches (POST → HTTP 404) — just re-run; each CLI100 invocation is a fresh session (the preamble gate is handled automatically).101- If the SDK docs seem stale after an Affinity update, re-run102 `docs-dump references/sdk-docs` and refresh the api-reference digest.103- Fallback if the endpoint route is ever unavailable: macOS GUI automation104 (System Events keystrokes/menus) works but is brittle and unverifiable — use it only for105 trivial one-shot actions, never for document editing.106107## Security108109Unlike the other app-automation skills, this one **opens no port of its own** — the110listener is Affinity's. But turning the AI connector on is still a decision worth naming.111112- Enabling the connector makes Affinity serve its full scripting SDK on113 `http://localhost:6767/sse`, an endpoint that executes JavaScript against the user's open114 documents. `affinity-cli.mjs` is only a client of it.115- The endpoint carries **no authentication**: while it is enabled, every local process, and116 every other user on a shared machine, can drive Affinity through it. The CLI refuses an117 SSE endpoint that redirects off `AFFINITY_MCP_URL`'s origin, so a hijacked session can't118 silently repoint it — but that protects this client, not the endpoint.119- What scripts may touch is gated by Affinity's own AI / filesystem / networking switches120 (a `NOT_ALLOWED` reply means one is off). Turning one on widens what *any* client can do,121 not just this skill — ask rather than assume.122- **To close it, turn the AI connector off in Affinity's settings.** The endpoint goes with123 it; nothing this skill installed keeps listening.