figmosha2
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 61 GitHub stars
Code Fail
- new Function() — Dynamic code execution via Function constructor in plugin/code.js
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Let Claude edit your Figma file for you — bulk-rename layers, swap variants, bind colour variables, pour in real copy. Describe the change in plain words, watch it land in the file in seconds. A small plugin keeps Figma talking to your terminal.
Figmosha 2.0
Drive Figma from your terminal / Claude Code / any HTTP client. A tiny custom plugin sits inside Figma Desktop and holds a WebSocket to a local Python server — you send Figma Plugin API code over HTTP and get the result back.
No clipboard hacks. No screenshots.
Fast enough to feel synchronous: reads ~5 ms, mutations ~30 ms, library component import ~150 ms.
Why this exists
The Figma Plugin API is the most stable and powerful interface Figma offers. Thousands of plugins depend on it. But typically it's only accessible inside Figma's UI — you click "Run plugin", code executes, results appear in a panel.
Figmosha 2.0 keeps a plugin permanently open in Figma and exposes its Plugin API through a local network socket. You write code in your editor / Claude / a script, it runs inside Figma, and the result comes back to you.
flowchart TB
subgraph client["PowerShell · curl · Claude Code"]
CLI["figmosha.py<br/>or any HTTP client"]
end
subgraph bridge["bridge.py — 127.0.0.1:8787"]
HTTP["HTTP server<br/>/exec · /status"]
WSS["WS server<br/>/plugin"]
end
subgraph figma["Figma Desktop — open file"]
PLUGIN["Figmosha Bridge<br/>(plugin)"]
API["Figma Plugin API"]
end
CLI -- "POST /exec<br/>{ code }" --> HTTP
HTTP --> WSS
WSS -- "ws://localhost" --> PLUGIN
PLUGIN --> API
API -.-> PLUGIN
PLUGIN -. "result · logs" .-> WSS
WSS -.-> HTTP
HTTP -. "{ ok, result, value,<br/>logs, elapsed_ms, hint? }" .-> CLI
Solid arrows carry the request, dotted ones the response.
Highlights
- One Python file server + one Python file CLI, ~500 lines total. No npm. No frameworks.
- Custom Figma plugin, ~250 lines (JS + HTML). Imported in dev mode — no publishing.
- 15 helpers baked into the plugin runtime as
h.*so scripts stay short and safe (h.bF,h.setText,h.withFonts,h.cloneNext,h.variantsOf, …). - 9 high-level CLI subcommands for common ops (
tree,find,text,variant,clone,rm,icomp, …). - Smart error hints in responses — when a script fails with a known-pattern error, the response includes a
hintfield telling you how to fix it. - Works while Figma is minimized. WebSocket stays alive; JavaScript keeps executing in the background.
- Auto-reconnect in the plugin UI — restart the server, plugin reconnects within 2 s.
Requirements
- Figma Desktop (Stable or Beta) — download. The browser version cannot import local development plugins.
- Python 3.10+ — for the bridge server and CLI client. Stdlib + a single dependency (
aiohttp). - OS: macOS, Windows (native or WSL2), or Linux.
Install
Hand this repo to Claude Code and let it do the setup:
https://github.com/denysosadchyi/figmosha2 — set this up for me
It clones the repo, creates the venv, installs aiohttp, starts the bridge, and tells you what to click in Figma. CLAUDE.md in the repo root is written for exactly this — Claude reads it and knows the whole workflow, including the WSL2 path juggling if that's your setup.
Two things Claude cannot do for you, because Figma exposes no API for either:
- Import the plugin — in Figma Desktop: Plugins → Development → Import plugin from manifest…, pick
plugin/manifest.jsonfrom the repo. Once, ever. - Run the plugin — Plugins → Development → Figmosha Bridge. A small green Connected bar appears; the bridge logs
[plugin] connected from 127.0.0.1. You're live.
Ask Claude for the smoke test and it will confirm the round trip works end to end.
Prefer to do it by hand?git clone https://github.com/denysosadchyi/figmosha2.git
cd figmosha2
python3 -m venv venv && ./venv/bin/pip install aiohttp # macOS / Linux / WSL
python -m venv venv && .\venv\Scripts\pip install aiohttp # Windows
bash start-bridge.sh # detached tmux session "figmosha-bridge"
./venv/bin/python bridge.py # …or just keep a terminal open
The server listens on 127.0.0.1:8787. Import and run the plugin as described above, then check it:
./venv/bin/python figmosha.py status
# → {"plugin_connected": true, "pending": 0}
./venv/bin/python figmosha.py "return figma.currentPage.name"
# → "Page 1"
WSL2: localhost ports forward to the Windows host automatically, so a bridge inside WSL is reachable from Figma on Windows. But Figma can only import a plugin from a Windows path — copy it out first:
mkdir -p /mnt/c/Users/$WIN_USER/figmosha-plugin
cp plugin/* /mnt/c/Users/$WIN_USER/figmosha-plugin/
Then import C:\Users\<your-name>\figmosha-plugin\manifest.json.
Daily use
Start a session
bash start-bridge.sh # or however you start the bridge
# In Figma: Plugins → Development → Figmosha Bridge → Run
The bridge survives SSH disconnects and terminal closes (tmux). It does not survive OS reboot or WSL shutdown — restart it after either.
Send code
# Inline JS
python figmosha.py "return figma.currentPage.children.length"
# From a file
python figmosha.py exec --file my-script.js
# From stdin
cat my-script.js | python figmosha.py exec --stdin
# Plain HTTP (no Python needed)
curl -s http://localhost:8787/exec \
-H 'Content-Type: application/json' \
-d '{"code":"return 1+1"}'
High-level CLI commands
When the operation fits one of these, use the dedicated subcommand — much less typing and less risk of escape bugs:
python figmosha.py tree 1:23 --depth 2 # dump subtree
python figmosha.py find 1:23 name=Button # find by exact name
python figmosha.py find 1:23 name~Btn # substring name match
python figmosha.py find 1:23 type=INSTANCE # filter by type
python figmosha.py find 1:23 text~hello # find TEXT containing "hello"
python figmosha.py text 1:25 "new content" # set TEXT chars (autoloads fonts)
python figmosha.py variant 1:30 "Property 1=Default"
python figmosha.py clone 1:23 --right --gap 100 # clone adjacent
python figmosha.py rm 1:99 # delete a node
python figmosha.py icomp <component-key> # import library component, place + zoom
python figmosha.py status # bridge + plugin connection state
Code conventions
The plugin wraps your code as:
new Function("figma", "print", "h", `return (async () => { <YOUR CODE> })();`)(figma, print, HELPERS)
awaitworks everywhere. Body is wrapped in an async IIFE.- Whatever you
returnbecomes the HTTP response'sresult(string) andvalue(raw JSON-serializable form). print(...)collects lines into thelogsarray — also streamed to the plugin UI for live debugging.
Helpers (available as h.* in every exec)
| Helper | Use |
|---|---|
await h.bF(node, idx, varOrId) |
Bind fill paint at idx to variable (handles frozen-array dance) |
await h.bS(node, idx, varOrId) |
Bind stroke paint to variable |
await h.bN(node, prop, varOrId) |
Bind numeric prop (radius, padding, size, itemSpacing, …) |
h.findByName(root, name) |
First descendant with exact name |
h.findAllByName(root, name) |
All descendants with exact name |
h.dumpTree(node, {maxDepth, showSize, showText}) |
Indented tree string |
await h.withFonts(root, asyncFn) |
Auto-loads every unique font in the subtree, then runs your callback |
await h.setText(node, text) |
Sets node.characters with auto font load (single-font nodes only) |
h.cloneNext(node, {direction, gap, name}) |
Clone + place adjacent (right/left/up/down) |
await h.variant(instance, props) |
Wrapper around instance.setProperties(...) |
await h.variantsOf(instance) |
{current, groups, all} of the component set |
await h.node(id) |
Shorthand for figma.getNodeByIdAsync(id) |
await h.var_(idOrKey) |
Resolve variable from instance, local id, or library key |
await h.importComp(key) |
figma.importComponentByKeyAsync(key) |
await h.importVar(key) |
figma.variables.importVariableByKeyAsync(key) |
Compared to inlined boilerplate, helpers reduce a typical script by ~60–70% and avoid common gotchas (frozen node.fills, missing loadFontAsync, deprecated sync getVariableById).
Error hints
When a script fails with a recognized pattern, the response includes a hint field. The CLI prints it for you:
$ figmosha.py "node.characters = 'x'"
figmosha: Cannot write to node with unloaded font "Inter Regular"...
hint: use h.setText(node, text) or h.withFonts(root, fn) — they autoload fonts
Currently hints cover: fills/strokes variable binding, frozen arrays, missing manifest permissions, unloaded fonts, appendChild order, invalid variant values, and a few more.
Limits / gotchas
- Plugin is bound to the currently open Figma file. Switching files closes the plugin — re-Run it in the new file.
- Only one plugin instance connects to the server at a time. Opening the plugin in a second Figma window is rejected.
- Figma sync errors ("Unable to establish connection to Figma after 10 seconds") sometimes appear when fetching nodes from non-current pages. If you need cross-page access:
await figma.loadAllPagesAsync()first. - Bridge binds to
127.0.0.1by default. For LAN access:python bridge.py --host 0.0.0.0(not recommended — anyone on your LAN can then run arbitrary code in your Figma). - Manifest changes (new permissions, etc.) require re-importing the plugin in Figma.
code.jsandui.htmlchanges are picked up on next Run.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
connection refused from CLI |
Server not running | bash start-bridge.sh (or run bridge.py in a terminal) |
plugin not connected (503) |
Plugin window closed | Plugins → Development → Figmosha Bridge → Run |
Plugin says disconnected, retrying… |
Server is down or restarting | Start it; plugin auto-reconnects within 2 s |
| 504 timeout | Code threw silently or await never resolved |
Close the plugin (X), Run again. Increase --timeout for legitimately long ops |
permission not specified in manifest |
API needs a permission not declared in manifest.json |
Add to permissions array, sync to Windows path if applicable, re-import plugin |
Cannot write to node with unloaded font |
Need to load fonts first | Use await h.setText(...) or wrap edits in h.withFonts(root, fn) |
Cannot assign to read only property |
node.fills is frozen |
Use await h.bF(node, idx, varId) or copy: JSON.parse(JSON.stringify(node.fills)) |
pip install aiohttp fails on Linux |
Python externally-managed environment (PEP 668) | Use the venv approach (always preferred) or pip install --user --break-system-packages aiohttp |
| Tmux not installed (Windows native) | start-bridge.sh won't work |
Run python bridge.py in a regular terminal instead |
Project layout
bridge.py HTTP/WS server (~200 lines)
figmosha.py CLI client (~300 lines)
start-bridge.sh tmux-based bridge management
plugin/
manifest.json Permissions + allowed origins
code.js Plugin sandbox: exec + helpers
ui.html WS client + auto-reconnect + status bar
CLAUDE.md Conventions for Claude Code sessions driving Figmosha
README.md This file
Contributing / extending
The plugin runtime is just new Function("figma", "print", "h", body). Add helpers to HELPERS in plugin/code.js, sync the file to your plugin path, and they're available in your next exec.
To add a new CLI subcommand:
- Add a
cmd_<name>(args)function infigmosha.pythat builds JS viajson.dumps-escaped templates - Add a subparser in
build_parser() - Register in the
dispatchmap
To add an error hint:
- Append a
(needle, hint)tuple toERROR_HINTSinbridge.py - Restart the bridge
License
MIT
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found