conductor-figma 3.3.1 → 3.3.3

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.
@@ -8,7 +8,7 @@
8
8
  "enableProposedApi": false,
9
9
  "editorType": ["figma"],
10
10
  "networkAccess": {
11
- "allowedDomains": ["none"],
12
- "reasoning": "Conductor connects to a local WebSocket server only"
11
+ "allowedDomains": ["*"],
12
+ "reasoning": "Connects to local MCP WebSocket server on 127.0.0.1:3055"
13
13
  }
14
14
  }
@@ -39,7 +39,7 @@ function setStatus(connected) {
39
39
 
40
40
  function connect() {
41
41
  try {
42
- ws = new WebSocket(`ws://localhost:${port}`);
42
+ ws = new WebSocket(`ws://127.0.0.1:${port}`);
43
43
  ws.onopen = () => {
44
44
  setStatus(true);
45
45
  log('Connected to MCP server');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "conductor-figma",
3
- "version": "3.3.1",
3
+ "version": "3.3.3",
4
4
  "description": "Design-intelligent MCP server for Figma. 201 design-intelligent tools for Figma. Every tool knows typography, spacing, color, accessibility. Not a shape proxy — a design engine.",
5
5
  "author": "0xDragoon",
6
6
  "license": "MIT",
package/src/bridge.js CHANGED
@@ -1,47 +1,100 @@
1
1
  // ═══════════════════════════════════════════
2
- // CONDUCTOR v3 — WebSocket Bridge
2
+ // CONDUCTOR v3.3 — WebSocket Bridge
3
3
  // ═══════════════════════════════════════════
4
4
 
5
- import { WebSocketServer } from 'ws'
5
+ import { WebSocketServer, WebSocket } from 'ws'
6
6
 
7
7
  function log() { process.stderr.write('[bridge] ' + Array.prototype.join.call(arguments, ' ') + '\n') }
8
8
 
9
9
  export function createBridge(port) {
10
10
  port = port || parseInt(process.env.CONDUCTOR_PORT) || 3055
11
11
  var wss = null
12
- var client = null
12
+ var figmaClient = null
13
+ var proxyConn = null
13
14
  var pending = new Map()
14
- var msgId = 0
15
+ var msgId = 1000
15
16
  var isOwner = false
17
+ var isProxy = false
16
18
 
17
19
  function start() {
18
20
  return new Promise(function(resolve) {
19
21
  try {
20
- wss = new WebSocketServer({ port: port })
21
- wss.on('listening', function() { isOwner = true; log('WebSocket server on port ' + port); resolve() })
22
+ wss = new WebSocketServer({ port: port, host: '0.0.0.0' })
23
+ wss.on('listening', function() {
24
+ isOwner = true
25
+ log('WebSocket server on port ' + port)
26
+ resolve()
27
+ })
22
28
  wss.on('connection', function(ws) {
23
- client = ws
24
- log('Figma plugin connected')
25
- ws.on('message', function(data) {
29
+ var role = null // 'figma' or 'proxy'
30
+
31
+ ws.on('message', function(rawData) {
26
32
  try {
27
- var msg = JSON.parse(data.toString())
33
+ var msg = JSON.parse(rawData.toString())
34
+
35
+ // Proxy identification
36
+ if (msg._identify === 'proxy') {
37
+ role = 'proxy'
38
+ log('Proxy client identified')
39
+ return
40
+ }
41
+
42
+ // Proxy forwarding a command to Figma
43
+ if (msg._proxy && msg.command) {
44
+ if (role !== 'proxy') { role = 'proxy'; log('Proxy client identified (via command)') }
45
+ if (figmaClient && figmaClient.readyState === 1) {
46
+ pending.set(msg.id, { ws: ws, type: 'proxy' })
47
+ figmaClient.send(JSON.stringify({ id: msg.id, command: msg.command, data: msg.data }))
48
+ } else {
49
+ ws.send(JSON.stringify({ id: msg.id, error: 'Figma plugin not connected' }))
50
+ }
51
+ return
52
+ }
53
+
54
+ // Response from Figma
28
55
  if (msg.id && pending.has(msg.id)) {
29
56
  var p = pending.get(msg.id)
30
- clearTimeout(p.timer)
31
57
  pending.delete(msg.id)
32
- if (msg.error) p.reject(new Error(msg.error))
33
- else p.resolve(msg.result)
58
+ if (p.type === 'proxy') {
59
+ // Route back to proxy
60
+ if (p.ws && p.ws.readyState === 1) p.ws.send(rawData.toString())
61
+ } else if (p.type === 'local') {
62
+ clearTimeout(p.timer)
63
+ if (msg.error) p.reject(new Error(msg.error))
64
+ else p.resolve(msg.result)
65
+ }
66
+ return
67
+ }
68
+
69
+ // If we get here with a result/error, it's Figma responding
70
+ if (!role) {
71
+ role = 'figma'
72
+ figmaClient = ws
73
+ log('Figma plugin connected')
34
74
  }
35
75
  } catch (e) { log('Parse error:', e.message) }
36
76
  })
37
- ws.on('close', function() { client = null; log('Figma plugin disconnected') })
77
+
78
+ ws.on('close', function() {
79
+ if (role === 'figma') { figmaClient = null; log('Figma plugin disconnected') }
80
+ if (role === 'proxy') { log('Proxy client disconnected') }
81
+ })
38
82
  ws.on('error', function(e) { log('WS error:', e.message) })
83
+
84
+ // Auto-identify as Figma if no message in 3s (Figma sends nothing on connect)
85
+ setTimeout(function() {
86
+ if (!role) {
87
+ role = 'figma'
88
+ figmaClient = ws
89
+ log('Figma plugin connected (auto)')
90
+ }
91
+ }, 3000)
39
92
  })
40
93
  wss.on('error', function(e) {
41
94
  if (e.code === 'EADDRINUSE') {
42
- log('Port ' + port + ' in use — another Conductor instance is running. This instance will handle MCP only.')
95
+ log('Port ' + port + ' in use — connecting as proxy')
43
96
  wss = null
44
- resolve()
97
+ connectAsProxy(resolve)
45
98
  } else {
46
99
  log('Server error:', e.message)
47
100
  resolve()
@@ -54,21 +107,61 @@ export function createBridge(port) {
54
107
  })
55
108
  }
56
109
 
57
- function stop() { if (wss && isOwner) { wss.close(); wss = null } }
110
+ function connectAsProxy(resolve) {
111
+ try {
112
+ proxyConn = new WebSocket('ws://127.0.0.1:' + port)
113
+ proxyConn.on('open', function() {
114
+ isProxy = true
115
+ // Identify ourselves immediately so server doesn't think we're Figma
116
+ proxyConn.send(JSON.stringify({ _identify: 'proxy' }))
117
+ log('Proxy connected to server on port ' + port)
118
+ resolve()
119
+ })
120
+ proxyConn.on('message', function(data) {
121
+ try {
122
+ var msg = JSON.parse(data.toString())
123
+ if (msg.id && pending.has(msg.id)) {
124
+ var p = pending.get(msg.id)
125
+ clearTimeout(p.timer)
126
+ pending.delete(msg.id)
127
+ if (msg.error) p.reject(new Error(msg.error))
128
+ else p.resolve(msg.result)
129
+ }
130
+ } catch (e) { log('Proxy parse error:', e.message) }
131
+ })
132
+ proxyConn.on('close', function() { isProxy = false; proxyConn = null; log('Proxy disconnected') })
133
+ proxyConn.on('error', function(e) { log('Proxy error:', e.message); isProxy = false; proxyConn = null; resolve() })
134
+ } catch (e) { log('Proxy connect error:', e.message); resolve() }
135
+ }
136
+
137
+ function stop() {
138
+ if (wss && isOwner) { wss.close(); wss = null }
139
+ if (proxyConn) { proxyConn.close(); proxyConn = null }
140
+ }
58
141
 
59
- function isConnected() { return client !== null && client.readyState === 1 }
142
+ function isConnected() {
143
+ if (isOwner) return figmaClient !== null && figmaClient.readyState === 1
144
+ if (isProxy) return proxyConn !== null && proxyConn.readyState === 1
145
+ return false
146
+ }
60
147
 
61
148
  function send(command, data, timeout) {
62
- timeout = timeout || 15000
149
+ timeout = timeout || 60000
63
150
  return new Promise(function(resolve, reject) {
64
- if (!isConnected()) return reject(new Error('Figma plugin not connected. Run the Conductor plugin in Figma, and make sure the WebSocket server is running on port ' + port))
151
+ if (!isConnected()) return reject(new Error('Figma plugin not connected'))
65
152
  var id = ++msgId
66
153
  var timer = setTimeout(function() {
67
154
  pending.delete(id)
68
- reject(new Error('Timeout waiting for Figma response (' + timeout + 'ms)'))
155
+ reject(new Error('Timeout (' + timeout + 'ms)'))
69
156
  }, timeout)
70
- pending.set(id, { resolve: resolve, reject: reject, timer: timer })
71
- client.send(JSON.stringify({ id: id, command: command, data: data }))
157
+
158
+ if (isOwner && figmaClient) {
159
+ pending.set(id, { resolve: resolve, reject: reject, timer: timer, type: 'local' })
160
+ figmaClient.send(JSON.stringify({ id: id, command: command, data: data }))
161
+ } else if (isProxy && proxyConn) {
162
+ pending.set(id, { resolve: resolve, reject: reject, timer: timer, type: 'local' })
163
+ proxyConn.send(JSON.stringify({ id: id, command: command, data: data, _proxy: true }))
164
+ }
72
165
  })
73
166
  }
74
167
 
package/src/server.js CHANGED
@@ -2,18 +2,14 @@ import { TOOL_LIST, TOOL_COUNT, getTool, CATEGORIES } from './tools/registry.js'
2
2
  import { handleTool } from './tools/handlers.js'
3
3
  import { createBridge } from './bridge.js'
4
4
 
5
- var VERSION = '3.0.4'
6
- var bridge = null
7
- var bridgeStarted = false
5
+ var VERSION = '3.3.1'
6
+ var bridge = createBridge()
7
+ bridge.start()
8
+ var bridgeStarted = true
8
9
 
9
10
  function log() { process.stderr.write('[conductor] ' + Array.prototype.join.call(arguments, ' ') + '\n') }
10
11
 
11
12
  function ensureBridge() {
12
- if (!bridgeStarted) {
13
- bridgeStarted = true
14
- bridge = createBridge()
15
- bridge.start()
16
- }
17
13
  return bridge
18
14
  }
19
15
 
@@ -10,6 +10,8 @@ import {
10
10
  import { composeSmartComponent, composeSection, composePage, runSequence } from '../design/composer.js'
11
11
  import { interpretDesign, applyDesignParams, getInterpretationSummary } from '../design/interpreter.js'
12
12
 
13
+ function log() { process.stderr.write('[conductor] ' + Array.prototype.join.call(arguments, ' ') + '\n') }
14
+
13
15
  // ─── Icon SVG Library ───
14
16
  const ICONS = {
15
17
  'arrow-right': '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M5 12h14M12 5l7 7-7 7"/></svg>',
@@ -78,27 +80,45 @@ export async function handleTool(name, args, bridge) {
78
80
 
79
81
  // Create page root
80
82
  var pageColors = semanticColors(brandColor, mode)
81
- var rootResult = await bridge.send('create_frame', {
82
- name: interpretation.industry + ' — ' + interpretation.mood,
83
- direction: 'VERTICAL', width: W, gap: 0,
84
- fill: pageColors.bg
85
- })
83
+ log('Creating root frame...')
84
+ var rootResult
85
+ try {
86
+ rootResult = await bridge.send('create_frame', {
87
+ name: interpretation.industry + ' — ' + interpretation.mood,
88
+ direction: 'VERTICAL', width: W, gap: 0,
89
+ fill: pageColors.bg
90
+ })
91
+ } catch (e) {
92
+ log('Root frame error:', e.message)
93
+ throw new Error('Failed to create root frame: ' + e.message)
94
+ }
86
95
 
96
+ if (!rootResult || !rootResult.id) {
97
+ log('Root result invalid:', JSON.stringify(rootResult))
98
+ throw new Error('Root frame created but no ID returned. Response: ' + JSON.stringify(rootResult))
99
+ }
100
+
101
+ log('Root frame created:', rootResult.id)
87
102
  var totalElements = 1
88
103
  var sectionResults = []
89
104
 
90
105
  // Build each detected section
91
106
  for (var di = 0; di < interpretation.sections.length; di++) {
92
107
  var secType = interpretation.sections[di]
108
+ log('Building section:', secType)
93
109
  var secCmds = composeSection(secType, interpretation.content, brandColor, mode, W)
94
110
  if (secCmds && secCmds.length > 0) {
95
- // Apply mood-based design params
96
111
  secCmds = applyDesignParams(secCmds, params)
97
- // Parent first element to page root
98
112
  secCmds[0].data.parentId = rootResult.id
99
- var secRes = await runSequence(bridge, secCmds)
100
- totalElements += secRes.length
101
- sectionResults.push({ section: secType, elements: secRes.length })
113
+ try {
114
+ var secRes = await runSequence(bridge, secCmds)
115
+ totalElements += secRes.length
116
+ sectionResults.push({ section: secType, elements: secRes.length })
117
+ log('Section ' + secType + ': ' + secRes.length + ' elements')
118
+ } catch (e) {
119
+ log('Section ' + secType + ' error:', e.message)
120
+ sectionResults.push({ section: secType, error: e.message })
121
+ }
102
122
  }
103
123
  }
104
124