storysync-push

storysync-push is a command for Claude Code from brendanciccone/storysync. It costs 12 tokens per session (5,900 once invoked), scanned A, original, MIT.

A command that sends Storybook components and design tokens from a code project into Figma, a collaborative interface-design tool. It reads component styles and maps variants so the Figma file can reflect the coded components.

In plain words
What is it for?
Use it to sync design-token collections, map Storybook variants, and create or update Figma representations of coded components.
Why use it?
It reduces the manual work of copying colors, spacing, typography, and component variations from code into a design file. It also provides checks for missing tokens or an unavailable Storybook server.

Command for Claude Code

Written for Claude Code: argument-hint in frontmatter. Also seen: reads .claude/ paths.

Good fit Use it to sync design-token collections, map Storybook variants, and create or update Figma representations of coded components.

Compare 6 commands from other repositories ↓
Install with agentmods
npx agentmods add commands/brendanciccone/storysync/storysync-push
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Clone the repo
git clone --depth 1 https://github.com/brendanciccone/storysync

Made for: Claude Code.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for storysync-push

README.md
[![agentmods](https://agentmods.dev/badge/commands/brendanciccone/storysync/storysync-push/github.svg)](https://agentmods.dev/commands/brendanciccone/storysync/storysync-push)
Your own site
<a href="https://agentmods.dev/commands/brendanciccone/storysync/storysync-push"><img src="https://agentmods.dev/badge/commands/brendanciccone/storysync/storysync-push/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for storysync-push

Your own site · 80×15
<a href="https://agentmods.dev/commands/brendanciccone/storysync/storysync-push"><img src="https://agentmods.dev/badge/commands/brendanciccone/storysync/storysync-push.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 12 Only the description is in the session, so the agent can decide to use it. The body loads when it is invoked.
When invoked 5,900 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 0 findings. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00012 $0.05900
Opus 5.5 $0.00005 $0.02360
Sonnet 5.5 $0.00002 $0.01180
Haiku 4.5 $0.00001 $0.00590

Measured 23d ago against content hash 38d72d464a0a, method: parsed. Prices are Anthropic first-party input rates as of 2026-10-07, from the pricing page.

Security

Grade A, and why

storysync-push scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 23d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Nothing flagged

None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.

commands/storysync-push.md · 28 lines

How it starts

The opening of the file, as written. The whole thing — 28 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Push design tokens and Storybook components from this codebase into Figma using the storysync skill at .claude/skills/storysync/SKILL.md.

Figma file key: $ARGUMENTS

If the user did not provide a file key (or $ARGUMENTS is empty), ask for it. The file key is the part of a Figma URL between /design/ and the next /.

Workflow. The step numbers here are this command's own. The skill numbers its Components steps one lower from measuring on, so this command's steps 4 to 8 are the skill's Components steps 3 to 7: verify runs in step 7 here and in the skill's Components step 6. A reference to one of the skill's steps names its section, such as the skill's Components step 5.

  1. Run mkdir -p .storysync && npx storysync tokens --json --project . > .storysync/tokens.json to extract design tokens from the project, and read them there. If a warning says storysync can't read or find part of the Tailwind config, or doesn't follow it, such as a preset from a package, a config built by a function, or a file it names, or that it read no theme from the config, tell the user what it names, since those tokens won't reach Figma. If one says it read another source rather than a tailwind.config.mts or .cts, tell the user, who can pass --source tailwind to read the config. If no tokens are found, skip token sync and continue with components.
  2. Run npx storysync figma tokens, which writes a use_figma call per token collection (Colors, Spacing, Radius, Typography) under .storysync/figma/tokens/calls/, listed in .storysync/figma/tokens/plan.json, and send them in seq order as step 6 sends a plan's: code the file unchanged, description its description, the file key and skillNames: "figma-use", saving each response unchanged to its result. Write no variable code yourself. Each call creates or updates its collection and each variable by name, adds a Dark mode where the tokens have a dark theme, renames a lone mode Light or Default, and sets each variable's scopes, writing only what differs, so a push sent again changes nothing. Note for the summary each token it prints as one it has to leave out, any token a tokens warning starting Unresolved: names among them, and the tokens a warning says were not read from @theme, which are out of the output already, as are those a warning starting Skipped dynamic value: names: name them too. Note each response's created, updated, unchanged, modes and darkError as well. Never remove a variable on your own: each collection's first response lists in stale the variables no token has, as Collection/name (an earlier push's Radius/DEFAULT, say), and in conflicts those of a token's name that can't be its variable, all left as they are. Ask the user once which to remove, saying a layer bound to one keeps its value, detached; for those confirmed, run npx storysync figma tokens --remove <Collection/name>, a --remove for each, and send the new calls from the first. If a call says two collections have its name, ask the user which to keep and to delete the other in Figma, then send it again; for Script exceeded time limit, run figma tokens again with --max-variables 20 and send the new calls from the first; for This call differs from the file storysync figma tokens wrote, send the file again exactly as it is. It keeps the alpha variables an earlier push made up to date with their tokens; the plan step 5's figma code writes makes its own in its first call.
  3. Run npx storysync list --storybook http://localhost:6006 --json to count the components, with each one's id, title and category. If Storybook isn't running on port 6006, ask the user for the correct URL; if Storybook has no MCP endpoint, ask them to run npx storysync init, then restart Storybook, and if its install fails, pass on the command it prints after Run manually: rather than retrying. Step 4 takes several seconds a component, more on a busy Storybook (134 components took about 17 minutes), so with more than about 20 components, stop and ask the user what to push before measuring anything: all of them, some categories (name each with how many components it has), or some components, passed to snap as --components by ID. When you ask, say that a push isn't lightweight: plan on roughly 100k agent tokens a component, plus about 100k a push for setup and verification, so about 1.1M for 10 components and 3.1M for 30. Don't run map as well: snap's output carries the same variant properties, and map would read every component's docs twice. If a command fails because Storybook didn't answer in time, run it again with the flag its error names raised: --request-timeout 900000 for a request, which has five minutes by default, or --connect-timeout 180000 for connecting.
  4. REQUIRED: Measure the styling, don't infer it. Run mkdir -p .storysync && STORYSYNC_PROGRESS=1 npx storysync snap --storybook http://localhost:6006 --variants all --json > .storysync/snap-out.json 2> .storysync/snap-progress.log; echo "exit $?" >> .storysync/snap-progress.log now, in this session, adding --components with the IDs step 3 agreed on unless the user chose every component (it replaces those in .storysync/snaps/styles.json and keeps every other component an earlier push measured; where that snap was measured with other flags or another Storysync it refuses and says how, so run it with the same flags, or ask the user whether to drop the others' record with --no-merge), and --stories <ID>=<story ID> for a component the user wants measured from another of its stories (its storyIds in step 3's list). Run it in the background (run_in_background), since your shell stops a foreground command after 2 to 10 minutes and a snap stopped part-way writes nothing. You're told when it ends; its progress shows with tail -n 1 .storysync/snap-progress.log (lines like snap 12/134 UI Base/Command, variant 3/32), and its last line is its exit code. exit 0 means it finished; exit 1 that it failed, with the reason as {"error": ...} in .storysync/snap-out.json (or, for an option it can't take, in the progress log); exit 130, 143 or 129 that it was stopped. After any exit but 0, .storysync/snaps/styles.json and meta.json are an earlier run's: never build from them, and run it again. After exit 0, check too that meta.json is this run's: its measuredAt is after you started it, or, after a --merge or a --components run that merged, which keep measuredAt at the oldest component's time on purpose, its componentsMeasuredAt time for each component you named is. It renders every combination in a real browser and records each variant's outer element and every element inside it that draws, with its styles, text, icons and pictures (thumbnails under .storysync/snaps/assets/). You don't translate any of it into Figma: step 5's figma code does. Tell the user what snap's messages say, its i, ! and ✗ lines, which --json puts first in snap-out.json (jq -r '.messages[] | "\(.level) \(.component // ""): \(.text)"' .storysync/snap-out.json), this run's alone, since a run that merged says nothing of the components it kept (keptComponents): among them, a warning that it measured a component in its dark color scheme (scheme: "dark"), where Storybook would render only dark, and one that a story fails with Storybook's own error, with the story's fix. Read what it recorded from .storysync/snaps/styles.json on disk, a component at a time, not from snap-out.json, the same JSON on one line that tools cut short: each component's warnings, cap and argsNotInDocs and excludedProps, and each variant's status, appliedVia and argsChecked. Do not reuse an earlier styles.json: it may describe code that has since changed. Every snap variant becomes a Figma variant, whatever its status: a component set needs every combination. If a component has a cap field (more combinations than the limit, 256 by default), stop and ask the user before building it: build the measured subset (its uncovered list names any values it leaves out), measure it again with --components <its ID> --merge --max-combinations <total>, or narrow which props are variants: re-run snap with --exclude-props naming the props that don't change how it looks, such as modal or loop, which leaves them out of every component. Do not offer to infer the rest from source: they can be measured. For a timeout, which snap already tried twice if the story was loading, the second time with three times as long (or as long, once such a try in the component timed out too), first measure those components again, in the background, adding --components <their IDs> --merge and a longer --timeout, such as 30000, to the same command: --merge measures only those and merges them into styles.json, keeping the rest, and records each component's time in meta.json's componentsMeasuredAt. --components alone merges the same way where there is a snap to add to; never pass --no-merge to measure some again, which drops every other component's record. A variant whose status is still not "ok" is built as a placeholder labeled Not measured: <status>, which verify lists and never scores: never style one from source, and name each in the summary with the story change it needs. An arg_rejected variant is a value the story's argTypes don't allow that snap couldn't set through Storybook's preview either: name it in the summary with the story change snap's warning gives. Values snap did set through the preview (appliedVia: "preview") were measured, unless the variant's status says it failed after that: build them as measured, and name that story change in the summary too. A variant marked argsChecked: false was measured, but snap couldn't check that the story was given its values, so it may be the default under its name: measure those components again with --components <their IDs> --merge, and if they stay marked, build what was measured and name them in the summary. A component's argsNotInDocs lists choices its story's argTypes declare that Storybook's docs don't, such as a Sheet's side or a Button's disabled: snap makes them variant properties, their default the story's own arg. A prop neither the docs nor the argTypes list that the stories' own args set, as coder's Button's cva variant and size, becomes one too, of the values those stories give, its default the component's story's value; where no story names the component's default, default stands for the prop left unset (marked unset), measured with no value given. Its excludedProps lists the props snap left out, each with its reason (no-op: every value measured the same, inside the outer element too, even where a story's own args set one; no-control, hidden or event: marked in the argTypes; opens: its value opens what a closed trigger hides, or renders the component where the defaults rendered nothing, as a ConfirmDialog story's open: true, so every variant is measured with it), which it says once per component: name them in the summary, and if the user wants one as a variant property anyway, re-run snap with --keep-props <name>. Where snap warns that a prop's values all measured the same, build what was measured rather than styling it from source. Where it warns that every variant measured identically, it says whether their markup changed too: if they rendered the same markup, the story may not pass its args through; if their markup changed with the args, the story passes them, and the props it names change nothing snap measures, likely only behavior (input-otp's textAlign), so never blame the story: ask the user, with any cap or trigger question, whether to leave them out with the --exclude-props the warning gives, added to any this push passes, or to build the identical variants as measured. Where it warns that it measured a closed trigger (a dialog, popover, sheet, menu or tooltip that renders its content only while open) or a layout wrapper rather than the component, stop before building any of them and ask the user once, listing each such component with the element measured, in the same message as any cap question: build what was measured, and say so in the summary and in Figma, on a line of each such set's description that starts storysync:, which the plan's label call writes in step 6 on every push, keeping every other line, a designer's own included, and removing the line an earlier push left once a set is no longer measured that way; skip them, naming them in the summary, and pass their IDs to step 5's figma code and step 7's verify as --skip, which leaves them out of the push and out of the score, since they stay in styles.json; or stop, so they can give each a story that renders it open or on its own, then measure those again with --components <their IDs> --merge. Don't choose for them, and never style such a component from source.
  5. Save the tokens, now that their variables exist, with npx storysync tokens --json --project . > .storysync/tokens.json, then write the push's calls with npx storysync figma code --components <the agreed IDs> (and --skip for any the user chose not to push). It writes .storysync/figma/plan.json and every use_figma call under .storysync/figma/calls/: the builds, an images call per page that shows a picture, the label calls and the reads; the first build and the first read carry their template and store it in the Figma file for the calls after them, so send them first. It makes a page per category, the whole of it (Components for a component without one), found ignoring case, spaces, /, - and _, and puts each set on its category's page, named by the last segment of its title (Button for ui/base/Button), or by its name when it has no title, so ui/base/Button and ui/radix/Button stay two sets; an existing set is found by name, or by the Storybook id an earlier push stored on it, and restyled in place. Note each warning it prints for the summary: variants built as a Not measured placeholder, values left unbound because several variables share them and no class names one, or a radius left unbound because its frame is drawn at an inner radius no token holds, things not built that Figma can't draw, or built as something it draws another way, frames built without auto layout, a variant sent in parts or one with a layer too big for any call, and no tokens at …, which means save the tokens and run it again. Where it warns that an earlier push built a component as one plain component and it now has variant properties, ask the user once, before sending anything, whether to make that component the first variant of its set in place, which keeps its instances; on yes, run figma code again with --convert <it>. Write no styling code yourself: every call is generated from the snap. Where it prints Translucent paints bind to N alpha variables, the plan's first call, a variables call, makes them: each a token's color at an alpha in Light and Dark, such as destructive 10%, or at each theme's own where a dark: class paints it at another, destructive 10% dark 20%, which keeps a translucent paint's alpha when a designer switches a frame's mode: Figma then gives each bound paint its variable's color and alpha in that mode, so a token bound at an opacity turns opaque. It finds and leaves unchanged those step 2 made, and lists nothing stale. It ends with an Estimate: line: the calls, the characters you'll send and read back, and the agent tokens to plan on for this push. Before sending the first call, show the user that line and ask whether to go ahead, and send nothing until they say yes; if they'd rather spend less, run figma code again with fewer --components and push the rest in another session.
  6. Send the calls in plan.json in seq order: for each, call use_figma with code the contents of its file (under .storysync/figma/), unchanged, description its description, the file key and skillNames: "figma-use", and save the response unchanged to its result path before the next call. Never edit a call or a saved response. After a build call whose response lists uploads, call upload_assets with the file key, count the number of uploads, nodeIds their node ids in order and scaleMode: "FILL", save its response unchanged with .upload.json for the result's .json, and run npx storysync figma upload --plan .storysync/figma/plan.json --seq <the call's seq>, which posts the thumbnails. After each read call, sent for the first time or again, run npx storysync figma next --plan .storysync/figma/plan.json --seq <its seq>, and while it prints a file, send that file the same way, save its response where it says, over any there, and run it again. If a call throws, deal with what the error says, as the skill's Components step 5 lists, and send the same call again, since every call can be sent again; for Script exceeded time limit, run figma code again with --max-nodes 200 and send the new plan from its first call; for This call differs from the file storysync figma code wrote, or figma next wrote, a character changed on the way and nothing was changed, so send the file again exactly as it is. A set a build refuses is left as it was, the rest of its call built, and its sets entry is refused with the question to ask. Never act on your own on the refusals and errors that say to ask the user: two sets of a name, another component's id, a set with auto layout, a set where a plain component goes or the reverse, a variant twice. For This call runs the … template that call NN stores in the Figma file, send call NN again, then this one; for Variant … is sent in N calls, send the calls it names first. Keep the variables call's alpha, created, updated and unchanged, each build response's fonts, unbound, fallbacks (no alpha variable … being a build sent before the variables call: send that call, then the build call again), and per set migrated, renamed, converted, removed, notRemoved, designerLayers, extra, overlaps, moved and absoluteContainers, and the last label response's pageOne, for the summary; where overlaps isn't 0, ask the user whether to move the set below the rest of its page. Where extra counts variants an earlier push built that no property names now, as after a property was added or dropped, ask the user whether to remove them; for those they say yes to, run figma code again with --remove-extra <names>, the components named comma-separated, never one they said to keep, and send the new plan; where a set's response has notRemoved, the plan went to another file than the one that counted its extras, which it names, and nothing was removed: tell the user.
  7. Run npx storysync verify --plan .storysync/figma/plan.json --strict-age, adding --skip with the IDs of any component not pushed, which leaves them out of the score and names them under it, where otherwise each of their variants is missing from Figma. It assembles .storysync/figma-readback.json from the saved responses and scores Figma element by element: the headline is element fidelity, with the share of properties beside it, then each difference by component, variant, key and Figma node id. Don't fix drift by hand, and don't send a build again for drift it made itself, which a resend makes again: a font stand-in and the widths and positions it moves, a text a pixel or so wider, an icon's shapes, a truncation or a text the build's fallbacks name, a border style Figma lacks. Report those as a font stand-in or a limit of the mapping. Only where something besides the build made Figma differ, a build response that failed or came back cut short, a layer edited in Figma since, or a build sent from an earlier plan, send that component's build calls once more, then its read calls, and run verify again, at most twice. A planned read call not read whole: send it, and what figma next asks for after it, as its lines say. Translucent paints Bound without their alpha variable, which the score can't show: send the plan's first call, then those build calls and their read calls again. An entry whose checksum doesn't match, or that is incomplete or stale: send its read call again and save the response unchanged, never editing a result. A layer a designer added inside a variant fails --strict: ask the user whether it belongs there, and if it does, run verify again with --allow-designer-layers. A recorded snap failure that names a component's error means measure that component again (step 4, with --merge), and one that says components share a key, or that the snap recorded no components, means do what snap warned and run the whole snap again.
  8. Summarize what was synced: token collections (count per category), with their modes and what step 2's calls created, updated and left unchanged, the alpha variables the plan's variables call made or found (alpha), and, where a collection holds the light theme alone though its tokens have dark values, why, the tokens left out, the stale and conflicting variables with what the user chose for each, components grouped by page, whether the last label call removed Figma's default page Page 1 or why it kept it (its pageOne), variant counts, the components built as one plain component for having no variant properties, the fidelity score, worded as verify words it: element fidelity, with the share of properties beside it, and where its line says entries were unverified, not scored, how many entries it left unverified, which the score doesn't cover, the coverage it gives and what it couldn't score, the variants built as a placeholder with the story change each needs, any story that didn't pass its args through, any props snap found reach a component but change nothing it measures, and what the user chose for them, any variant snap marked argsChecked: false, each component's argsNotInDocs and excludedProps, each component measured from the story --stories named, with the story, for an audit to measure from it too, each component snap measured as a closed trigger or a layout wrapper (with the fix the warning gives) and what the user chose for it, what verify printed under the score about what snap measured, the frames built without auto layout, the layers of an earlier root-only push the build replaced (migrated), the designer layers and what the user chose for them, the values left unbound because several variables share them, every font stand-in the build responses returned in fonts (a CSS keyword such as system-ui set in Inter, a platform face such as Menlo in Roboto Mono), any component where fontAvailable was false (the measurement describes a substituted typeface, and Figma will need that font installed, which storysync cannot do), any build fallbacks, which variant Figma will treat as a set's default where that is not the component's default, any set skipped for its auto layout, any component skipped for a set or plain component of its name an earlier push left, any set or component a build returned overlaps for, and whether the user had it moved, any picture that couldn't be uploaded, that verify can't yet see variable bindings in an element-tree readback, and any failures or caps.

Read the full file on GitHub · 28 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 23d ago Changed · +4 lines 38d72d464a0a
  2. 1mo ago Changed c0d78f2d4864
  3. 2mo ago First seen · 24 lines · 12 tokens per session scan A d8baab36f83c

Subscribe to this mod's changes

storysync-push is a command published in the GitHub repository brendanciccone/storysync (75 stars, last pushed 8d ago), licensed MIT. It adds 12 tokens to every session and 5,900 once invoked, about $0.0000 per session on Opus 5.5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.

Related

storysync-push compared with commands from other repositories

storysync-push next to 6 commands from other repositories
ModTypeStarsLast pushLicenceScanTokensWorks with
storysync-pushbrendanciccone/storysyncCommand758d agoMITA12Claude Code
profilecathrynlavery/diagram-designCommand65k3d agoMITA16Claude Code
erne-animateJubaKitiashvili/everything-react-native-expoCommand5029d agoMITA21Claude Code
ui-flow-reviewMRCalderon3D/everything-game-dev-codeCommand10428d agoMITA15Claude Code
responsive-design-specialistimsaif/design-with-claudeCommand11yesterdayMITA36Claude Code
design-auditw2ur/claude-code-setupCommand23d agoMITA30Claude Code
ppt-image2-editable-rebuildwwe-dog/ppt-image2-editable-rebuildCommand2045mo agoUnlicenseA15Claude Code