@todoforai/figma-api 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -7,7 +7,8 @@ Read this before using `figma-api`. It tells you exactly what to set up.
7
7
  - Need to **read** designs, or **write** comments / dev-resources / webhooks?
8
8
  → REST is enough. Just set a token (step 1). Works headless / in CI.
9
9
  - Need to **create canvas nodes** (frames, text, shapes)? → REST can't. Use the
10
- **plugin bridge** (step 2). Requires the Figma desktop app open.
10
+ **plugin bridge** (step 2). Requires a plugin connected in the Figma **desktop**
11
+ app (browser can't reach the localhost relay — see gotchas).
11
12
 
12
13
  ## 1. Auth (always required)
13
14
 
@@ -45,11 +46,15 @@ Setup (do all three, in order):
45
46
  ```bash
46
47
  figma-api bridge # WebSocket relay on ws://localhost:3055
47
48
  ```
48
- 2. **Load the plugin** — this is a human step, you cannot automate it:
49
- In the **Figma desktop app** (browser can't import dev plugins):
50
- Plugins Development → Import plugin from manifest → pick this package's
51
- `plugin/manifest.json`. Run it, set URL `ws://localhost:3055` + channel
52
- `figma-api`, click **Connect**.
49
+ 2. **Connect a plugin** — a human step, you cannot automate it. Pick ONE:
50
+ - **Easy (recommended):** install **"Cursor Talk To Figma MCP"** from the
51
+ Figma Community (figma.com/community/plugin/1485687494525374295), run it in
52
+ the **desktop** app, set port `3055`, click **Connect**. It auto-picks a
53
+ random channel — that's fine: the relay bridges channels, so you do **not**
54
+ copy it or pass `--channel`.
55
+ - **Advanced:** in the Figma **desktop** app, Plugins → Development → Import
56
+ plugin from manifest → this package's `plugin/manifest.json`. Run it, set URL
57
+ `ws://localhost:3055`, channel `figma-api`, click **Connect**.
53
58
  3. **Drive it** with named commands:
54
59
  ```bash
55
60
  figma-api ping # round-trip check; prints page + selection
@@ -82,9 +87,16 @@ the plugin instead of localhost.
82
87
  to `figma-api run` with `figma.variables.*` (off-Enterprise, via the bridge).
83
88
  - **"create a frame/text/shape via REST"** is impossible — there is no endpoint.
84
89
  Use the bridge (`create-frame`/`create-text`/…).
85
- - **Browser-only Figma can't load the dev plugin.** Needs the desktop app, or a
86
- published plugin. If the user is browser-only and refuses desktop, canvas
87
- writes are not available say so plainly.
90
+ - **Use the DESKTOP app for canvas writes.** Both plugins can be *opened* in
91
+ browser Figma, but a browser tab can't connect to `ws://localhost:3055`
92
+ (mixed-content: an https page blocks insecure `ws`), so it never reaches
93
+ "Connected" — this is the usual "plugin won't connect on Linux/browser" cause.
94
+ Desktop has no such block. Dev-manifest import is desktop-only regardless. If
95
+ desktop is truly impossible, expose the relay over TLS (`cloudflared tunnel
96
+ --url http://localhost:3055`) and paste the `wss://` URL into the plugin.
97
+ - **Channel names don't need to match.** The relay bridges across channels, so the
98
+ community plugin's random channel and the CLI's `figma-api` default interoperate
99
+ with no `--channel` flag.
88
100
  - **`figma-api run` / the relay have no auth** and execute arbitrary code in the
89
101
  user's document. Only run on a trusted machine; don't expose the tunnel URL
90
102
  publicly; don't run untrusted code.
package/README.md CHANGED
@@ -67,9 +67,14 @@ figma-api create-frame … ──ws──▶ relay (figma-api bridge) ◀─
67
67
  Setup:
68
68
 
69
69
  1. `figma-api bridge` — start the WebSocket relay (keep it running).
70
- 2. Figma **desktop** Plugins Development **Import plugin from manifest**
71
- `plugin/manifest.json`. Run it, set URL `ws://localhost:3055` + channel
72
- `figma-api`, click **Connect**.
70
+ 2. Connect a plugin in the Figma **desktop** app pick one:
71
+ - **Easy (recommended):** install [**Cursor Talk To Figma MCP**](https://www.figma.com/community/plugin/1485687494525374295)
72
+ from the Figma Community, run it, set port `3055`, click **Connect**. It picks
73
+ a random channel — that's fine, the relay bridges channels so you don't copy
74
+ it or pass `--channel`.
75
+ - **Advanced:** Plugins → Development → **Import plugin from manifest** →
76
+ `plugin/manifest.json`. Run it, set URL `ws://localhost:3055`, channel
77
+ `figma-api`, click **Connect**.
73
78
  3. Drive it from the CLI with named commands:
74
79
 
75
80
  ```bash
@@ -98,8 +103,11 @@ Cross-machine: `cloudflared tunnel --url http://localhost:3055` and paste the
98
103
  > Figma document. Only use it on a trusted machine/network; don't expose the tunnel
99
104
  > URL publicly or run code you don't trust.
100
105
 
101
- > Note: development (unpublished) plugins can only be imported in the **Figma
102
- > desktop app**, not the browser. Once published, the browser works too.
106
+ > Note: use the **Figma desktop app**. A browser tab can't reach `ws://localhost:3055`
107
+ > (an https page blocks insecure `ws` as mixed content), so the plugin never reaches
108
+ > "Connected" — the usual "won't connect in the browser" cause. Dev-manifest import
109
+ > is desktop-only regardless. If desktop is impossible, expose the relay over TLS
110
+ > (`cloudflared tunnel --url http://localhost:3055`) and paste the `wss://` URL in.
103
111
 
104
112
  ## Why both REST and a plugin?
105
113
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@todoforai/figma-api",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Figma CLI — full REST API wrapper (files, comments, variables, webhooks…) plus a plugin bridge for canvas writes.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/bridge.ts CHANGED
@@ -8,7 +8,8 @@
8
8
  //
9
9
  // figma-api create-frame … ──ws──▶ relay (channel broadcast) ◀──ws── plugin
10
10
  //
11
- // Protocol (channel-scoped, broadcast to the *other* peer, never echoed):
11
+ // Protocol (join/ack are channel-scoped; forwarding is cross-channel see
12
+ // startBridge — so a plugin on its own random channel still reaches the CLI):
12
13
  // join: { type:"join", channel } → { type:"system", message:{ id, result } }
13
14
  // command: { type:"message", channel, message:{ id, command, params } }
14
15
  // result: { type:"message", channel, message:{ id, result } | { id, error } }
@@ -33,6 +34,11 @@ function manifestPath(): string {
33
34
 
34
35
  export function startBridge(port: number): void {
35
36
  const channels = new Map<string, Set<ServerWebSocket<WsData>>>();
37
+ // Every connected peer, regardless of channel. The relay is single-user and
38
+ // local (one plugin + one CLI at a time), so we bridge across channels: the
39
+ // CLI joins "figma-api" while the community TTF plugin picks its own random
40
+ // channel — forwarding to all *other* peers spares the user copying it.
41
+ const all = new Set<ServerWebSocket<WsData>>();
36
42
 
37
43
  Bun.serve<WsData, undefined>({
38
44
  port,
@@ -45,6 +51,7 @@ export function startBridge(port: number): void {
45
51
  // drop-in: welcome on open, two-part join ack, peer notices, broadcasts
46
52
  // wrapped as {type:"broadcast", sender:"peer", message:<inner>}.
47
53
  open(ws) {
54
+ all.add(ws);
48
55
  ws.send(JSON.stringify({ type: "system", message: "Please join a channel to start chatting" }));
49
56
  },
50
57
  message(ws, raw) {
@@ -68,18 +75,21 @@ export function startBridge(port: number): void {
68
75
  const ch = ws.data.channel;
69
76
  if (!ch || !channels.get(ch)?.has(ws)) { ws.send(JSON.stringify({ type: "error", message: "You must join the channel first" })); return; }
70
77
 
78
+ // Forward across ALL peers (not just the sender's channel) so the CLI and
79
+ // the community plugin bridge even when their channel names differ.
71
80
  // Forward progress updates verbatim to the other peers (upstream parity).
72
81
  if (msg.type === "progress_update") {
73
- for (const peer of channels.get(ch) ?? []) if (peer !== ws) peer.send(JSON.stringify(msg));
82
+ for (const peer of all) if (peer !== ws) peer.send(JSON.stringify(msg));
74
83
  return;
75
84
  }
76
85
  if (msg.type !== "message") return;
77
86
 
78
87
  // Broadcast the inner message to the OTHER peers (sender never gets its own).
79
88
  const out = JSON.stringify({ type: "broadcast", sender: "peer", channel: ch, message: msg.message });
80
- for (const peer of channels.get(ch) ?? []) if (peer !== ws) peer.send(out);
89
+ for (const peer of all) if (peer !== ws) peer.send(out);
81
90
  },
82
91
  close(ws) {
92
+ all.delete(ws);
83
93
  const ch = ws.data.channel;
84
94
  if (!ch) return;
85
95
  const peers = channels.get(ch);
@@ -92,7 +102,7 @@ export function startBridge(port: number): void {
92
102
  });
93
103
 
94
104
  console.log(`figma-api bridge (WebSocket) listening on ws://localhost:${port}`);
95
- console.log(`Default channel: "${DEFAULT_CHANNEL}". Open the plugin, click Connect, then run figma-api commands.`);
105
+ console.log(`Open a plugin, click Connect (any channel — the relay bridges them), then run figma-api commands.`);
96
106
  }
97
107
 
98
108
  /** Setup instructions shown only when a canvas command can't reach the plugin. */
@@ -102,14 +112,25 @@ the plugin bridge, which the user must install.
102
112
 
103
113
  AGENT: be explicit with the user about this. The bridge plugin grants write access
104
114
  to their Figma documents — confirm they want it, then walk them through (the plugin
105
- runs in THEIR Figma, you can't do step 2 for them):
115
+ runs in THEIR Figma, you can't do the plugin step for them):
106
116
  1. Start the relay (keep running): figma-api bridge
107
- 2. In the Figma DESKTOP app: Plugins → Development → Import plugin from manifest
108
- ${manifestPath()}
109
- Run it, set the URL to ws://localhost:${port} and channel "${DEFAULT_CHANNEL}", click Connect.
117
+
118
+ 2. Install & connect a plugin — pick ONE:
119
+ EASY (recommended): install "Cursor Talk To Figma MCP" from the Figma
120
+ Community (figma.com/community/plugin/1485687494525374295), run it, set
121
+ port ${port}, click Connect. It picks its own random channel — that's fine,
122
+ the relay bridges channels so you do NOT need to copy it or pass --channel.
123
+ ADVANCED: in the Figma DESKTOP app, Plugins → Development → Import plugin
124
+ from manifest → ${manifestPath()}
125
+ run it, set URL ws://localhost:${port}, channel "${DEFAULT_CHANNEL}", Connect.
126
+
110
127
  3. Retry. Verify the round-trip with: figma-api ping
111
- Cross-machine: expose the relay via 'cloudflared tunnel --url http://localhost:${port}'
112
- and paste the wss:// URL into the plugin. Browser-only Figma can't load dev plugins.`;
128
+
129
+ USE THE DESKTOP APP. Both plugins can be *opened* in browser Figma, but a browser
130
+ tab can't reach ws://localhost:${port} (mixed-content: an https page blocks insecure
131
+ ws), so it never reaches "Connected". Desktop has no such block. If desktop is truly
132
+ impossible, expose the relay over TLS — 'cloudflared tunnel --url http://localhost:${port}'
133
+ — and paste that wss:// URL into the plugin. (Dev-manifest import is desktop-only.)`;
113
134
 
114
135
  /** CLI peer: connect to the relay, send one command, return the plugin's result. */
115
136
  export function sendCommand(
package/src/index.ts CHANGED
@@ -640,13 +640,19 @@ ecosystem's MCP server / plugin.
640
640
 
641
641
  Flow:
642
642
  1. figma-api bridge # start this relay (keep running)
643
- 2. In Figma desktop: Plugins Development Import plugin from manifest →
644
- plugin/manifest.json, run it, set URL ws://localhost:${DEFAULT_PORT} + channel
645
- "${DEFAULT_CHANNEL}", click Connect.
643
+ 2. Connect a plugin in the Figma DESKTOP app (pick one):
644
+ EASY: install "Cursor Talk To Figma MCP" from the Figma Community, run it,
645
+ set port ${DEFAULT_PORT}, Connect. Its random channel is fine — the relay
646
+ bridges channels, so no --channel needed.
647
+ ADVANCED: Plugins → Development → Import plugin from manifest →
648
+ plugin/manifest.json, set URL ws://localhost:${DEFAULT_PORT}, channel
649
+ "${DEFAULT_CHANNEL}", Connect.
646
650
  3. figma-api ping # verify; then create-frame / create-text / …
647
651
 
648
- Cross-machine: expose the relay with 'cloudflared tunnel --url http://localhost:${DEFAULT_PORT}'
649
- and paste the wss:// URL into the plugin.
652
+ Use the DESKTOP app: a browser tab can't reach ws://localhost:${DEFAULT_PORT} (an https
653
+ page blocks insecure ws), so it never connects. If desktop is impossible, expose the
654
+ relay over TLS with 'cloudflared tunnel --url http://localhost:${DEFAULT_PORT}' and paste the
655
+ wss:// URL into the plugin.
650
656
 
651
657
  ⚠️ Security: the relay has no auth and 'run' executes arbitrary code in your
652
658
  Figma document. Only run it on a trusted machine/network.`)
@@ -654,8 +660,10 @@ Figma document. Only run it on a trusted machine/network.`)
654
660
 
655
661
  canvas("ping")
656
662
  .description("Ping the connected plugin to verify the bridge round-trip")
657
- .addHelpText("after", `\nReturns the plugin's current page name and selection.\nExample:\n figma-api ping`)
658
- .action((o: any) => drive(o, "ping"));
663
+ .addHelpText("after", `\nReturns the current document info. Works with both the bundled plugin and the\ncommunity Cursor-Talk-To-Figma plugin.\nExample:\n figma-api ping`)
664
+ // get_document_info exists in both the bundled and the community plugin (unlike
665
+ // "ping", which is bundled-only), so verification works whichever is connected.
666
+ .action((o: any) => drive(o, "get_document_info"));
659
667
 
660
668
  // ── canvas reads ──────────────────────────────────────────────────────────────
661
669
  canvas("get-document-info")