Figma agent vs. one button: shipping design tokens to npm
A month ago I decided our design tokens pipeline should run itself. I got Claude Code to rewrite my token CLI so an agent could drive it, wired up the Figma agent, and it all worked. And now I don't use any of it. I click a button instead.
The boring ritual
At GitButler our design tokens don't live in the app repo. They live in a separate package, @gitbutler/design-core, published to npm. Apps consume tokens like any other dependency, by version. Which means every token change, even one color, has to become a real npm release.
Why work like this instead of just keeping the tokens in the monorepo? Because the tokens feed more than one codebase: the desktop app, the website, whatever we build next. A version number is the only thing that keeps them all in sync. It also makes every change a deliberate, reviewable release: there's a diff, a semver bump, and consumers upgrade when they choose. A renamed variable becomes a major version with a warning label, not a surprise in tomorrow's build. The price of all that safety is exactly the problem of this article: even one color has to travel the full release pipeline.
For a long time a release looked like this:
Ten steps. All manual.
I wrote this plugin years ago, before anyone talked about agents, and the ritual never changed. It's not hard, just boring. And boring things that never change are exactly what you're supposed to automate.
"Can you run my plugin?" — No.
So I opened the Figma agent and asked: can you run my plugin and export the tokens? I knew it probably couldn't, but I wanted to see what it would say.
No. The Figma agent can't touch plugins. No clicking buttons, no driving UI on your behalf. The agent only gets APIs. But the agent can run CLI tools.
Okay, fine. Variables through the REST API then? Also no, unless you're on the Enterprise plan. The official way to read variables remotely sits behind the paywall Figma has.
To be clear, Tokens Brücke also ships as a CLI, and both the plugin and the CLI can talk to the REST API. That path has always been there for people who have the plan. So the paywall wasn't news. It was the interesting part of the puzzle: find a workaround and make it free for everyone.
The workaround: snapshot in, tokens out
The agent can read the file from the inside, through the Plugin API. It just can't run my plugin's transform logic on top of it.
And remember the CLI? Its only door into Figma was that paid REST API. So I opened the plugin repo in VS Code and asked Claude: can it also accept a raw snapshot of variables instead of calling the API?
Claude rewrote it. Now you can do this:
# agent dumps variables via Plugin API → snapshot.json
npx tokens-bruecke --input snapshot.json --output tokens.json
No Enterprise plan. No access token. Same DTCG JSON (the Design Tokens Community Group format) as the plugin. Claude also added an init onboarding (four questions, saved config, never asks again) and a skill, the agent world's name for a saved instruction set. You describe the procedure once (dump a snapshot, run the CLI, open a PR) and from then on the agent replays it whenever I say "export tokens".
Fun detail: this work needed two agents, Claude rewriting the CLI and the Figma agent testing against the real file. I didn't know how to connect them. So I connected them myself. Copy Claude's answer, paste into the Figma agent. Copy the reply, paste back.
Two agents. One human clipboard. My most senior role to date.
The GitHub half
The other half of the pipeline went to GitHub Actions. I described the rules, Claude wrote the YAML. Writing Actions by hand is mostly looking up syntax you will forget again next month, and that is exactly the part worth handing off.
The agent wasn't aware of its own connectors. While it was telling me it "doesn't have git credentials," a GitHub connector was sitting in the + menu of the very same chat window.
That covered the pushing. But a PR landing in the repo is only half the job. Someone still has to rebuild the CSS, pick a version, and publish. That someone became two GitHub Actions workflows living in design-core:
tokens-pr.yml — when a PR touches the token JSON: rebuild the CSS with Terrazzo, diff the custom properties, pick the version bump (patch or minor automatically; a major is never inferred, it needs an explicit label because removing a CSS variable breaks every consumer), bump it, and comment the full token-by-token diff on the PR.
release.yml — on merge: publish to npm, tag, create the release, write the changelog.
And here's my favorite TIL of the whole project: npm trusted publishing. I thought I'd be rotating npm access tokens every few months forever. Turns out you connect the GitHub repo to the npm package once, and Actions publish via OIDC. No tokens.
Six minutes
So: full automation achieved. I say "export tokens", and a few moments later there's a PR on GitHub with a version bump and a diff.
A few moments. Specifically: six minutes. Every single run.
I thought my skill was making the agent overthink, so I tuned it. "Don't think. Just run the script." Still six minutes. I made it more aggressive: zero questions, zero pauses, extract → transform → push → PR → done. Still six minutes.
Why the Figma agent is slow with files
An agent has no hands. It lives entirely in text: it sends tool calls as text and gets results back as text. That's true of every agent. What turns it into a cage is the tools the platform hands it. The Figma agent's GitHub connector takes file contents as an argument, and its sandbox has no git and no direct network. So to push semantic.tokens.json, the biggest of the three files at 54 KB, the agent has to cat the file into its own context and then re-type every byte into the API call. And its output stream is capped at a few tens of KB, so the file doesn't even fit: "The semantic file is too large for stdout."
It's like photocopying a book by reading it aloud over the phone to someone who writes it down. It works for a paragraph. It fails for a book.
To be fair: this is not "AI agents can't handle files." Claude Code sitting in a repo just runs git push, and the bytes go from disk to GitHub without ever entering the chat. The Figma agent isn't dumber; it's caged.
So why not run the whole pipeline from Claude Code, then? Because the cage works both ways. Claude Code can push files all day, but it isn't sitting inside the Figma file. Of the two agents I had in front of me, the one that could read couldn't push, and the one that could push couldn't read.
The twist: the old button wins
Then I remembered something embarrassing.
My plugin has had a "Push to server" button with a GitHub PR target for years. I built it before all this. And a plugin is exactly the actor the two agents couldn't be: it lives inside the file, so it can read every variable, and it has its own network access, so it can push without squeezing bytes through anyone's chat. One click: the plugin reads the tokens, pushes the JSON, the PR appears, Actions do everything else.
Total time: seconds.
So why did I never use my own button? Because on its own it only did half the job. It pushed the JSON and opened the PR, and then rebuilding the CSS, picking a version, publishing, and tagging were still on me. The missing half was GitHub Actions, and I never knew how to write Actions well enough to trust them with a release. That's the part Claude gave me. Once the workflows existed, a button that just pushes JSON was suddenly the whole job.
So after adding a skill, an onboarding, and a snapshot mode to the CLI, and after a stint as a professional human clipboard, the winning setup is:
Ten steps became two clicks. And neither of them involves an LLM.
Here is the whole process in a single video:
Could an agent have skipped the clipboard?
Claude Code with Figma's Dev Mode MCP does see something. I had it read the variables from our file and diff them against design-core: about half came back, only the ones the selected node happened to use, with no aliases, no modes, and no descriptions. Resolved colors, not tokens. Selecting the whole Tokens page instead gives the same picture: --bg-2 comes back as #f2f2f2, not as an alias to gray-90, and the dark mode value is nowhere. That's what its getvariabledefs tool is built to do (it resolves whatever the selection uses), so it's the wrong tool for a full export, not a broken one.
The same MCP server also has a usefigma tool that runs Plugin API code, which in principle could dump every collection with modes and aliases straight into a shell that has git. I haven't tried that path yet. There is also figma-console-mcp: a local MCP server plus a dev plugin you import into Figma. The plugin reads variables through the Plugin API and hands them over a WebSocket to a Node process on your machine, where Claude Code has a real disk and a real git push. Same trick as my snapshot mode, minus the human clipboard.
Note who is missing from both of those: the Figma agent. It can't attach an MCP server, so every bridge only works for an agent living outside Figma. The cage is a platform choice, not a law of nature. But none of it changes the math. A bridge still puts an LLM in the middle of a job that a button finishes in seconds.
What I keep
An LLM is a machine that pretends to be a human, and it has a human's profile. It's great at the human parts: reading a messy codebase, spotting the seam, figuring out what to change. It's bad at the machine parts: doing the same thing a hundred times, fast, without thinking about it. So use it the way you'd use a smart colleague, not the way you'd use a CPU. Claude Code rewrote my CLI and wrote my GitHub Actions. But the pipeline itself is just scripts, and scripts don't need to think. CI bumps, publishes, logs. The only human steps left are the ones that actually need a human: reading the diff and deciding to ship.
And if a tool already exists (a CLI, a button, a workflow), hand the AI the tool instead of making it redo the work by hand. An agent is only as fast as its worst tool. Until platforms let tools take a file path instead of file contents, let a script move the bytes. The agent should only say "push".
The one piece of the detour I'd keep regardless is the snapshot mode. Reading tokens through Plugin API snapshots instead of the Enterprise REST API means the same tokens for everyone, no paywall.
One last funny thing: after I shipped the AI-flavored release, plugin downloads jumped from 400 to 1,300 a week. I'd love to tell you it's because of all this brilliant agent tooling.
P.S. I pasted this article into the Figma agent and asked what I got wrong. It found nothing. I wouldn't read too much into that, but it did sum itself up better than I managed to in 2,000 words:
"I'm great at writing scripts and CI configs; I'm a terrible runtime for deterministic pipelines."