About affinity
affinity is a skill for controlling Affinity's JavaScript SDK through a Node CLI. It is for users scripting document operations, and includes rendering checks and script-library management via Affinity's AI connector endpoint.
Key features
- 01Run JavaScript scripts through the CLI
- 02Check connectivity and list endpoint tools
- 03Render the current spread or selection
- 04Add scripts to the Affinity library
Use cases
- 01Run document-editing scripts in batches
- 02Render a spread or selection to inspect results
- 03Search SDK hints while refining scripts
- 04Save reusable scripts in the library
MCP support
Affinity's AI connector exposes an MCP-protocol endpoint. The CLI calls it directly over HTTP/SSE without configuring an MCP client.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ jablonkai/ skills --skill affinityAfter installing, type/affinityin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
references/api-reference.mdreferences/sdk-docs/application.jsartboardinterface.jsartboardproperties.jsbaseboxinterface.jsblendmodeinterface.jsbrushfillinterface.jsbuffer.jscollection.jscolours.jscommands.jscompoundoperationinterface.jsconfiguration.jscurvesinterface.jsdescriptioninterface.jsdialog.jsdocument.jsdocumentproperties.jsdrawingscale.jseditabilityinterface.jsenvironment.jsreferences/sdk-docs/examples/addGuides.jsaddPoints.jsadjustPageItems.jsalignToPage.jsarrowheads.jsartboardGrid.jsbitmapWriter.jsboldItalics.jsbreakFrame.jsDetails
From the project READMEAffinity 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.3.0 (build 4850).
- 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 (142 topics, ~1.8 MB). Grep it for exact signatures only after sdk-map.md; never read the big files whole (
nodes.jsis ~300 KB). - Online signature reference for the native
*Apilayer the vendored JS wraps: https://sdk.affinity.studio/latest/js/ (apis/<ApiName>/<method>.html,enums/<Enum>.html).
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 viaconsole.log()(return values are dropped). Entry point:const { app } = require('/application'); // '/application.js' works too const doc = app.documents.current; // null when no document is openThree things trip up almost every first script, so get them right up front:
- New documents default to millimetres.
NewDocumentOptionssetters are write-only; seto.units = UnitType.Pixel(from/units.js) beforeo.width/height/dpi. - Text size is in document pixels, not points:
px = pt * doc.dpi / 72. app.documents.allis a plain Array, node.childrena lazyCollection— see sdk-map.
Unsure what the user's settings allow? Log
require('/environment').Environment.permissions(→{fileSystem, network, genAI}) and.fileSystemRootsbefore writing code that needs them.When an API resists, search the crowd-sourced hints pool:
node scripts/affinity-cli.mjs search "set blend mode". - New documents default to millimetres.
-
Execute:
node scripts/affinity-cli.mjs run myscript.jsRuns via the endpoint's
execute_scriptand 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 onlyThen
Readthe 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.jsLibrary 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 run - # same, script read from stdin (heredoc)
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> [--force] # (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
- A
PERMISSION_DENIEDerror (the SDK preamble calls itNOT_ALLOWED) = the user disabled AI / filesystem / networking for scripting in Affinity settings — ask them to enable what's needed; don't probe around it. - Script filesystem access is Desktop-only (
app.userDesktopPath); no network from script code. Preferrenderfor 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.
doc.close()closes without prompting and discards unsaved changes — only close documents the script created itself, orsave()first.- 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).
- After an Affinity update, check
app.versionagainst the version noted above. If it moved, dump into an empty directory (docs-dump /tmp/sdk-new, then replacereferences/sdk-docs/) — dumping over the old tree keeps stale files and skips changed ones — and refresh the api-reference and sdk-map digests. - 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.mjsis 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
PERMISSION_DENIEDerror 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.