惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
罗磊的独立博客
宝玉的分享
宝玉的分享
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
V
V2EX
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tailwind CSS Blog
博客园_首页
量子位
月光博客
月光博客
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 司徒正美
人人都是产品经理
人人都是产品经理
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
爱范儿
爱范儿
S
SegmentFault 最新的问题
雷峰网
雷峰网
小众软件
小众软件
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
WordPress大学
WordPress大学
Jina AI
Jina AI
Hugging Face - Blog
Hugging Face - Blog

Butler's Log

Local Model Performance on an M5 Max GitButler 0.22 - "Catch 22" Agentic Version Control Benchmarks Grit: rewriting Git in Rust with agents Git Merge 2026 Agent-safe Git with GitButler We’ve raised $17M to build what comes after Git Announcing the GitButler CLI for Linux The Great CSS Expansion A couple of git nits Simplifying Git by Using GitButler Introducing the GitButler CLI GitButler 0.19 - "Commander Keen" But Head: Crafting a Custom Font MCP vs RAG: Two Very Different Ways to Gain Context Getting Started With GitButler Agents Using the GitButler MCP Server to Build Better AI-Driven Git Workflows Using GitButler With Multiple GitHub Accounts Advent of Code! Upcoming GitButler Events Use GitButler for your Gerrit workflow Integrating GitButler and GitHub Enterprise Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 3 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 2 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 1 Grid Happens: Because Flexbox Wasn’t Enough Using Cursor Hooks for automatic version control Deep Dive into the new Cursor Hooks A Responsive Item Counter with CSS only GitButler 0.16 - "Sweet Sixteen"
Figma agent vs. one button: shipping design tokens to npm
Pavel Laptev · 2026-09-07 · via Butler's Log

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.

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:

  1. Open my Tokens Brücke plugin in Figma
  2. Pick the export settings
  3. Download a .zip
  4. Unzip it
  5. Copy the JSON into design-core
  6. Run Terrazzo to turn JSON into CSS
  7. Commit
  8. Push
  9. Tag a version (0.x.x? 0.x.x+1? think, Pavel, think)
  10. Log in to npm and publish

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.

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.

Asking the Figma agent what it can actually reach. No plugins, but a shell.

Asking the Figma agent what it can actually reach. No plugins, but a shell.

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.

The CLI wants a Figma Personal Access Token, and variables over REST live behind the Enterprise plan.

The CLI wants a Figma Personal Access Token, and variables over REST live behind the Enterprise plan.

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

The agent's suggestion: give the CLI a way to take JSON it already has, instead of fetching it.

The agent's suggestion: give the CLI a way to take JSON it already has, instead of fetching 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:

BASH

# 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".

The Figma agent testing the rewritten CLI against the real file: snapshot in, every variable out (233 at the time), no API key.

The Figma agent testing the rewritten CLI against the real file: snapshot in, every variable out (233 at the time), no API key.

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

First attempt: "Can you also push it to GitHub?" — "I don't have git credentials."

First attempt: "Can you also push it to GitHub?" — "I don't have git credentials."

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.

The GitHub connector, one menu away from the agent that just told me it had no way to reach GitHub.

The GitHub connector, one menu away from the agent that just told me it had no way to reach GitHub.

Connector on, get_me passes. Every step still comes with a "waiting for you to approve" notice.

Connector on, get_me passes. Every step still comes with a "waiting for you to approve" notice.

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.

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.

The skill after tuning: zero thinking, no pauses, just run it.

The skill after tuning: zero thinking, no pauses, just run it.

Six minutes and forty-six seconds. The agent's own diagnosis: having to cat 77 KB of JSON (all three token files) and pass it inline is the bottleneck.

Six minutes and forty-six seconds. The agent's own diagnosis: having to cat 77 KB of JSON (all three token files) and pass it inline is the bottleneck.

To be fair to the Figma agent: none of this is what it was built for. It lives inside the file to help you design. Rename layers, audit spacing, answer questions about a component. I just expected it to be more flexible than that.

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

The confession: 54 KB is "too big to push in a single GitHub API call from here."

The confession: 54 KB is "too big to push in a single GitHub API call from here."

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.

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 I had no way to get it into the Figma file. The agent that could read couldn't push, and the agent that could push couldn't read. Or so I thought. More on that below.

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.

The button that was there the whole time: a GitHub PR target under "Push to server".

The button that was there the whole time: a GitHub PR target under "Push to server".

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:

  1. Click "Push to server" in the plugin. CI rebuilds the CSS, picks the version, comments the diff.
  2. Read the diff. Merge. CI publishes, tags, writes the changelog.

Ten steps became two clicks. And neither of them involves an LLM.

What lands after the click: a PR, a patch bump to 3.15.3, and a token-by-token diff from CI.

What lands after the click: a PR, a patch bump to 3.15.3, and a token-by-token diff from CI.

Here is the whole process in a single video:

Click, PR, diff, merge, publish: the whole pipeline in 36 seconds.

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

Figma's Dev Mode MCP vs the real token files: 126 variables returned out of the 240 in the file at the time, and the 126 came back flattened.

Figma's Dev Mode MCP vs the real token files: 126 variables returned out of the 240 in the file at the time, and the 126 came back flattened.

The same MCP server also has a use_figma tool that runs Plugin API code. I tried it while editing this post. Three calls, and every variable came back: both collections, light and dark, aliases as aliases, descriptions. Diffed against design-core, the only differences were units, 8 versus 8px, which is the plugin's job anyway. So the read half works. But the return is capped at about 20 KB per call, so the dump has to be paged. And it doesn't land on disk. It lands in the model's context, and the model writes it to a file. The clipboard didn't go away. It moved inside the agent. 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. None of this is permanent. Taking a file path instead of file contents is the kind of gap every connector closes eventually. I expect the cage to open. It just won't change the answer. Give an agent a real shell and real git, and it runs my script in thirty seconds instead of six minutes. The button still finishes first. The bottleneck is temporary. The argument isn't.

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

Pavel Laptev

Written by Pavel Laptev

Quack! A digital designer who loves open source. Flaps around making interfaces nice and helps the design and frontend pond whenever he can.

Table of Contents

  • The boring ritual
  • "Can you run my plugin?" — No.
  • The workaround: snapshot in, tokens out
  • The GitHub half
  • Six minutes
  • Why the Figma agent is slow with files
  • The twist: the old button wins
  • Could an agent have skipped the clipboard?
  • What I keep