Delivery Factory · Part 3 · Figma Extract
The Figma plugin, from design link to files
The design stage needs the values, the images and a screenshot of one Figma node. The plugin that provides them is a separate pack with one operation. This page follows it from the link to the files: which tools it calls, which scripts decide, and when it passes, asks a human or stops. Repo: agentic-figma
Checked in Code
Everything on this page was read in the plugin's skill, scripts, manifest and README, not only in the README. Paths are relative to plugins/figma/ unless said otherwise.
The parts
A plugin of its own
The Figma pack plays one role in the pipeline: design. It has its own manifest, skill, scripts and tests, and it runs no server.
pack.yamldeclares the rolekind: provider,role: design, one operation:fetch_reference → fetch-reference. It also lists the four tools only the session can provide, each with a remedy for when one is missing, so the run can check them before it does any work..claude-plugin/plugin.jsonnames itName
figma, version0.1.0, one dependency:agentic-core. The project picks it as its design source withpacks.design: figmain.ai/project-config.yaml.skills/fetch-reference/SKILL.mdis the flowA decision graph from the link to a verdict, with one section per node. Its allowed tools are exactly the four Figma tools plus
ToolSearchandSkill.Five scripts decide
classify-node.pysorts the node.viewports.pyplans the targets.fetch-assets.pydownloads images and icons.sniff-asset.pynames a file's format.screenshot-size.pysizes the screenshot request and records what came back.Five offline test suites
One per script, 221 assertions in total, no network. They cover the scripts. The MCP calls themselves are not exercised by them.
Each with one job
get_metadata: confirms the node exists; its name, size and children.get_design_context: reference code whose classes carry layout, colour and type, and the download links for images and icons.get_variable_defs: a map of token name to value. An empty map is a normal result.get_screenshot: a short-lived image link with rendered and original size.
Load, then use
- The skill loads the four tools through
ToolSearch, then loadsfigma:figma-design-to-code, whichget_design_contextasks for. - That skill is loaded for its prerequisite only. The operation writes no application code.
- It never calls
authenticate. An unattended run has nobody to open the sign-in link.
The classifier
Four classes, decided by a script
classify-node.py reads the metadata file from disk. The model reads its first line and never re-decides the class from names, sizes or the ticket.
single: one targetAnything that is not one of the three below. The node itself is fetched.
variants: one target per versionEvery frame-like child belongs to one group of two or more viewport versions of the same design. Each is fetched, widest first.
page: a questionThe node is a page. Every frame-like child is a candidate.
multi-frame: a questionA section, or children that mix variants with other frames, or hold several variant groups. Every frame-like child is a candidate.
A child is a viewport version when its own name matches a rule, checked in this order (pattern, keyword, width), or when size alone separates same-named frames:
Pattern: Hero / Desktop, Hero - Mobile, Desktop View, @mobile.
Keyword: a name token is desktop, mobile, tablet, responsive, phone or web.
Width: a name token is 1440, 1280, 1024, 768, 375, 390 or 414, with or without px.
Size: frames with the same base name where one is wider than 900 and one is narrower than 500.
# classification.tsv, one line per frame-like child (example names)
class variants
node frame 1:100 Logo Row
child 1:118 1200 variant name:pattern=desktop Logo Row / Desktop
child 1:131 720 variant name:pattern=tablet Logo Row / Tablet
child 1:144 375 variant name:pattern=mobile Logo Row / Mobile
A page or several frames: the question lists every candidate, none dropped, reordered or marked as preferred. Not even one whose name matches the work item.
The fetch
What it writes, and what it keeps out of files
Design values and images land in .ai/figma/ in a fixed shape. Download links are short-lived and never written down.
- Check the link: a
figma.com/design/URL with anode-id. For a branch URL the branch key is used as the file key. - Load the four tools.
get_metadata, then write its response whole to<file>-<node>.metadata.xml. classify-node.pywrites.classification.tsv. Forvariants,viewports.py planprints one target per version, widest first, and only those are fetched.- Per target:
get_design_context, with the screenshot left out of it. Keep the code block and the styles line verbatim. - The code block goes to
fetch-assets.pyon stdin. It downloads every image and icon, names each format from its bytes, and rewrites the code to point at local files. get_variable_defsonce, for the referenced node.screenshot-size.py maxsizes the request to the node's longer edge.get_screenshotper target, downloaded before the link expires.- Write
<file>-<node>.jsonand report pass with the list of files written.
Formats from bytes. sniff-asset.py recognises png, jpg, gif, webp and svg from the first bytes of the file. The name or the URL is never trusted. Bytes of no known format stop the operation.
Files named by content. Each asset is written as the first 16 hex digits of its SHA-256 plus the extension. Identical bytes are one file. A name already holding different bytes is refused, so no earlier file is replaced.
No URL on disk. The asset links and the screenshot link exist only in the tool response and in the command that uses them. The script refuses a result that still contains one.
## Result verdict: pass summary: <node name> from <Figma file> artifacts: - .ai/figma/<file>-<node>.json - .ai/figma/<file>-<node>.png - .ai/figma/<file>-<node>.metadata.xml - .ai/figma/<file>-<node>.classification.tsv - .ai/figma/<file>-<node>.context.txt next_action: none metrics: variables=<n> design_context=code node_class=single downscaled=0 assets=<n>
A design context returned as structure only, or cut short by the tool's size cap, is recorded as structure_only. It is a pass, not an error: metadata, tokens and the screenshot still cover the node.
Pass, ask, stop
Three verdicts, and the class behind a fail
The operation ends in exactly one of three verdicts. A fail always carries an error class, read off what the tool actually returned.
Pass
The link is one frame, or the viewport versions of one design. Every target is fetched, the files are written, the result lists them.
Question
The link has no
node-id, or the node is a page or holds several frames. The question comes verbatim fromclassify-node.py --question, with one option per candidate from--options. No error class: it is a clarification the ticket's author can answer.Fail · VALIDATION
The reference itself is wrong: not a
figma.com/design/URL (/file/,/board/,/slides/and/make/are not supported), or the file or node is not found, or the node id is malformed. Re-running it unchanged cannot succeed.Fail · PERMANENT
The plugin is not installed, or installed but not authenticated, or the sign-in was rejected (401), or this session has no access (403). Also any error nothing else matches, because an unrecognised error is not evidence that retrying is safe. A script exit of 2 is here too: unreadable metadata, bytes of no known image format, an asset link that would be written.
Fail · TRANSIENT
A rate limit, a quota or a plan's call limit, a timeout, or an image download that failed. The only class that is retried: twice, after 2 seconds and then 4. If the third attempt fails, the operation reports a fail. It does not ask a question of its own, because only the stage that called it knows whether another source exists.
## Result verdict: fail summary: Figma is not authenticated: run /plugin, select figma, choose Authenticate, finish sign-in in the browser, then re-run. artifacts: [] next_action: none error_class: PERMANENT
The shape of a fail, with the exact summary the plugin uses when authentication is what is missing. The text tells the person what to do, not only that it failed.
The authentication remedy is also declared in pack.yaml for each of the four session tools, so the run can name it before any work starts.
What it doesn't cover
It reads the design as drawn
The plugin gets the values and the picture. It does not judge them.
Needs
The Figma plugin installed and signed in, and the network. The five test suites cover the scripts; the tool calls and the skill's own branching are not covered by them.
Scope
/design/URLs only. One reference per call: the operation takes onereference:line and fetches that node and, when it holds viewport versions, each of them.Next
Building the prototype against these files, and checking it in a real browser.