agentleFS
jablonkaijablonkai/skills/skills/affinity/SKILL.md

affinity

Skill2 starsChanged 44 days agoMITView on GitHub

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.

  • Installs packages

2 other projects use a version of this.

Adopted by

Most recently taken up by

The file

We split this skill into the rules it gives an agent

3 rules. About 0 are common, 1 is distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If it fails, ask the user to launch Affinity and enable the AI connector (Affinity Settings → AI connector.

    line 38
  2. Very specific:

    When an API resists, search the crowd-sourced hints pool: node scripts/affinity-cli.mjs search "set blend mode".

    line 62
  3. Very specific:

    After an Affinity update, check app.version against the version noted above.

    line 124

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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.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.js is ~300 KB).
  • Online signature reference for the native *Api layer the vendored JS wraps: https://sdk.affinity.studio/latest/js/ (apis/<ApiName>/<method>.html, enums/<Enum>.html).

The control loop

  1. 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).

  2. 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');   // '/application.js' works too
    const doc = app.documents.current;          // null when no document is open

    Three things trip up almost every first script, so get them right up front:

    • New documents default to millimetres. NewDocumentOptions setters are write-only; set o.units = UnitType.Pixel (from /units.js) before o.width/height/dpi.
    • Text size is in document pixels, not points: px = pt * doc.dpi / 72.
    • app.documents.all is a plain Array, node .children a lazy Collection — see sdk-map.

    Unsure what the user's settings allow? Log require('/environment').Environment.permissions (→ {fileSystem, network, genAI}) and .fileSystemRoots before writing code that needs them.

    When an API resists, search the crowd-sourced hints pool: node scripts/affinity-cli.mjs search "set blend mode".

  3. 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.

  4. 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.

  5. 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 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_DENIED error (the SDK preamble calls it NOT_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. 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.
  • doc.close() closes without prompting and discards unsaved changes — only close documents the script created itself, or save() 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.version against the version noted above. If it moved, dump into an empty directory (docs-dump /tmp/sdk-new, then replace references/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.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 PERMISSION_DENIED error 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.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.