conductor-figma 1.0.2 → 3.0.0
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/LICENSE +20 -0
- package/README.md +59 -153
- package/bin/conductor.js +1 -75
- package/figma-plugin/code.js +755 -0
- package/figma-plugin/manifest.json +14 -0
- package/figma-plugin/ui.html +77 -0
- package/package.json +25 -16
- package/src/bridge.js +60 -0
- package/src/design/intelligence.js +273 -294
- package/src/server.js +82 -196
- package/src/tools/handlers.js +145 -463
- package/src/tools/registry.js +1144 -336
- package/src/blueprints.js +0 -775
- package/src/design/craftguide.js +0 -181
- package/src/design/exporter.js +0 -72
- package/src/index.js +0 -33
- package/src/orchestrator.js +0 -100
- package/src/relay.js +0 -176
package/src/server.js
CHANGED
|
@@ -1,217 +1,103 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════
|
|
2
|
-
// CONDUCTOR — MCP Server
|
|
2
|
+
// CONDUCTOR v3 — MCP Server (stdio)
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
|
-
// When create_page or create_section is called:
|
|
5
|
-
// 1. Generates a blueprint (30-50 sequential commands)
|
|
6
|
-
// 2. Executes each one through the relay to the Figma plugin
|
|
7
|
-
// 3. Each command references results from previous commands ($ref)
|
|
8
|
-
// 4. Returns a summary of everything created
|
|
9
|
-
|
|
10
|
-
import { TOOLS } from './tools/registry.js';
|
|
11
|
-
import { handleTool } from './tools/handlers.js';
|
|
12
|
-
import { Relay } from './relay.js';
|
|
13
|
-
import { getBlueprint } from './blueprints.js';
|
|
14
|
-
import { executeSequence } from './orchestrator.js';
|
|
15
|
-
|
|
16
|
-
var SERVER_INFO = { name: 'conductor-figma', version: '0.3.0' };
|
|
17
|
-
var CAPABILITIES = { tools: {} };
|
|
18
|
-
var relay = null;
|
|
19
|
-
|
|
20
|
-
// Tools that trigger blueprint orchestration (multi-command sequences)
|
|
21
|
-
var BLUEPRINT_TOOLS = new Set(['create_page', 'create_section']);
|
|
22
|
-
|
|
23
|
-
export async function startServer(options) {
|
|
24
|
-
options = options || {};
|
|
25
|
-
var port = options.port || 9800;
|
|
26
|
-
|
|
27
|
-
relay = new Relay(port);
|
|
28
|
-
var relayStarted = await relay.start();
|
|
29
|
-
|
|
30
|
-
if (relayStarted) {
|
|
31
|
-
process.stderr.write('CONDUCTOR v0.3.0: MCP + orchestrator ready (' + TOOLS.length + ' tools, ws://localhost:' + port + ')\n');
|
|
32
|
-
} else {
|
|
33
|
-
process.stderr.write('CONDUCTOR v0.3.0: MCP ready (' + TOOLS.length + ' tools, no relay — install ws)\n');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
var buffer = '';
|
|
37
|
-
|
|
38
|
-
process.stdin.setEncoding('utf-8');
|
|
39
|
-
process.stdin.on('data', function(chunk) {
|
|
40
|
-
buffer += chunk;
|
|
41
|
-
var lines = buffer.split('\n');
|
|
42
|
-
buffer = lines.pop() || '';
|
|
43
4
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
5
|
+
import { TOOL_LIST, TOOL_COUNT, getTool, CATEGORIES } from './tools/registry.js'
|
|
6
|
+
import { handleTool } from './tools/handlers.js'
|
|
7
|
+
import { createBridge } from './bridge.js'
|
|
8
|
+
|
|
9
|
+
const VERSION = '3.0.0'
|
|
10
|
+
let bridge = null
|
|
11
|
+
|
|
12
|
+
function log(...args) { process.stderr.write('[conductor] ' + args.join(' ') + '\n') }
|
|
13
|
+
|
|
14
|
+
// ─── JSON-RPC over stdio ───
|
|
15
|
+
let buffer = ''
|
|
16
|
+
|
|
17
|
+
process.stdin.setEncoding('utf8')
|
|
18
|
+
process.stdin.on('data', chunk => {
|
|
19
|
+
buffer += chunk
|
|
20
|
+
while (true) {
|
|
21
|
+
const headerEnd = buffer.indexOf('\r\n\r\n')
|
|
22
|
+
if (headerEnd === -1) break
|
|
23
|
+
const header = buffer.slice(0, headerEnd)
|
|
24
|
+
const match = header.match(/Content-Length:\s*(\d+)/i)
|
|
25
|
+
if (!match) { buffer = buffer.slice(headerEnd + 4); continue }
|
|
26
|
+
const len = parseInt(match[1])
|
|
27
|
+
const bodyStart = headerEnd + 4
|
|
28
|
+
if (buffer.length < bodyStart + len) break
|
|
29
|
+
const body = buffer.slice(bodyStart, bodyStart + len)
|
|
30
|
+
buffer = buffer.slice(bodyStart + len)
|
|
31
|
+
try {
|
|
32
|
+
const msg = JSON.parse(body)
|
|
33
|
+
handleMessage(msg)
|
|
34
|
+
} catch (e) { log('Parse error:', e.message) }
|
|
35
|
+
}
|
|
36
|
+
})
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
38
|
+
function send(msg) {
|
|
39
|
+
const json = JSON.stringify(msg)
|
|
40
|
+
const out = `Content-Length: ${Buffer.byteLength(json)}\r\n\r\n${json}`
|
|
41
|
+
process.stdout.write(out)
|
|
59
42
|
}
|
|
60
43
|
|
|
44
|
+
function respond(id, result) { send({ jsonrpc: '2.0', id, result }) }
|
|
45
|
+
function respondError(id, code, message) { send({ jsonrpc: '2.0', id, error: { code, message } }) }
|
|
46
|
+
|
|
61
47
|
async function handleMessage(msg) {
|
|
62
|
-
|
|
63
|
-
var method = msg.method;
|
|
64
|
-
var params = msg.params || {};
|
|
48
|
+
const { id, method, params } = msg
|
|
65
49
|
|
|
66
50
|
switch (method) {
|
|
67
51
|
case 'initialize':
|
|
68
|
-
|
|
69
|
-
|
|
52
|
+
if (!bridge) {
|
|
53
|
+
bridge = createBridge()
|
|
54
|
+
await bridge.start()
|
|
55
|
+
}
|
|
56
|
+
return respond(id, {
|
|
57
|
+
protocolVersion: '2024-11-05',
|
|
58
|
+
capabilities: { tools: { listChanged: false } },
|
|
59
|
+
serverInfo: { name: 'conductor-figma', version: VERSION },
|
|
60
|
+
})
|
|
70
61
|
|
|
71
|
-
case 'initialized':
|
|
72
|
-
|
|
62
|
+
case 'notifications/initialized':
|
|
63
|
+
log(`Conductor v${VERSION} ready — ${TOOL_COUNT} tools across ${Object.keys(CATEGORIES).length} categories`)
|
|
64
|
+
return
|
|
73
65
|
|
|
74
66
|
case 'tools/list':
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
var visibleTools = TOOLS.filter(function(t) { return !HIDDEN_FROM_AI.has(t.name); });
|
|
88
|
-
sendResult(id, {
|
|
89
|
-
tools: visibleTools.map(function(t) { return { name: t.name, description: t.description, inputSchema: t.inputSchema }; }),
|
|
90
|
-
});
|
|
91
|
-
break;
|
|
92
|
-
|
|
93
|
-
case 'tools/call':
|
|
94
|
-
var toolName = params.name;
|
|
95
|
-
var toolArgs = params.arguments || {};
|
|
96
|
-
if (!toolName) { sendError(id, -32602, 'Missing tool name'); return; }
|
|
97
|
-
await handleToolCall(id, toolName, toolArgs);
|
|
98
|
-
break;
|
|
67
|
+
return respond(id, {
|
|
68
|
+
tools: TOOL_LIST.map(t => ({
|
|
69
|
+
name: t.name,
|
|
70
|
+
description: t.description,
|
|
71
|
+
inputSchema: t.inputSchema,
|
|
72
|
+
}))
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
case 'tools/call': {
|
|
76
|
+
const { name, arguments: args } = params
|
|
77
|
+
const tool = getTool(name)
|
|
78
|
+
if (!tool) return respondError(id, -32601, `Unknown tool: ${name}`)
|
|
99
79
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// ═══ ORCHESTRATED BLUEPRINTS ═══
|
|
112
|
-
// create_page and create_section generate 20-50 commands and execute them all
|
|
113
|
-
if (BLUEPRINT_TOOLS.has(toolName) && relay && relay.isConnected()) {
|
|
114
|
-
var blueprint = getBlueprint(toolName, toolArgs);
|
|
115
|
-
|
|
116
|
-
if (blueprint && blueprint.commands && blueprint.commands.length > 0) {
|
|
117
|
-
process.stderr.write('CONDUCTOR orchestrator: ' + toolName + ' -> ' + blueprint.commands.length + ' commands\n');
|
|
118
|
-
process.stderr.write('CONDUCTOR orchestrator: ' + blueprint.description + '\n');
|
|
119
|
-
|
|
120
|
-
var outcome = await executeSequence(relay, blueprint.commands);
|
|
121
|
-
|
|
122
|
-
var summary = {
|
|
123
|
-
tool: toolName,
|
|
124
|
-
description: blueprint.description,
|
|
125
|
-
totalCommands: outcome.totalSteps,
|
|
126
|
-
completed: outcome.completedSteps,
|
|
127
|
-
errors: outcome.errors.length,
|
|
128
|
-
success: outcome.success,
|
|
129
|
-
createdNodes: [],
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
// Collect all created node IDs and names
|
|
133
|
-
for (var r = 0; r < outcome.results.length; r++) {
|
|
134
|
-
var res = outcome.results[r];
|
|
135
|
-
if (res && res.id) {
|
|
136
|
-
summary.createdNodes.push({ id: res.id, name: res.name || '', type: res.type || '' });
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (outcome.errors.length > 0) {
|
|
141
|
-
summary.errorDetails = outcome.errors;
|
|
80
|
+
try {
|
|
81
|
+
const result = await handleTool(name, args || {}, bridge)
|
|
82
|
+
return respond(id, {
|
|
83
|
+
content: [{ type: 'text', text: typeof result === 'string' ? result : JSON.stringify(result, null, 2) }]
|
|
84
|
+
})
|
|
85
|
+
} catch (e) {
|
|
86
|
+
log(`Tool error [${name}]:`, e.message)
|
|
87
|
+
return respond(id, {
|
|
88
|
+
content: [{ type: 'text', text: `Error: ${e.message}` }],
|
|
89
|
+
isError: true,
|
|
90
|
+
})
|
|
142
91
|
}
|
|
143
|
-
|
|
144
|
-
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }] });
|
|
145
|
-
return;
|
|
146
92
|
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// ═══ DIRECT FIGMA COMMANDS ═══
|
|
150
|
-
// Single commands forwarded directly to plugin
|
|
151
|
-
if (relay && relay.isFigmaCommand(toolName) && relay.isConnected()) {
|
|
152
|
-
process.stderr.write('CONDUCTOR: -> Figma: ' + toolName + '\n');
|
|
153
|
-
try {
|
|
154
|
-
var figmaResult = await relay.sendToPlugin(toolName, toolArgs);
|
|
155
|
-
process.stderr.write('CONDUCTOR: <- Figma: ' + (figmaResult.name || figmaResult.id || 'ok') + '\n');
|
|
156
|
-
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(figmaResult, null, 2) }] });
|
|
157
|
-
} catch (err) {
|
|
158
|
-
sendResult(id, { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }] });
|
|
159
|
-
}
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ═══ DESIGN INTELLIGENCE (local) ═══
|
|
164
|
-
var result = handleTool(toolName, toolArgs, null);
|
|
165
|
-
|
|
166
|
-
// Check if handler produced a Figma action to forward
|
|
167
|
-
if (relay && relay.isConnected()) {
|
|
168
|
-
try {
|
|
169
|
-
var data = JSON.parse(result.content[0].text);
|
|
170
|
-
if (data.action && relay.isFigmaCommand(data.action)) {
|
|
171
|
-
process.stderr.write('CONDUCTOR: -> Figma (via ' + toolName + '): ' + data.action + '\n');
|
|
172
|
-
var fResult = await relay.sendToPlugin(data.action, data);
|
|
173
|
-
process.stderr.write('CONDUCTOR: <- Figma: ' + (fResult.name || fResult.id || 'ok') + '\n');
|
|
174
|
-
data._figmaResult = fResult;
|
|
175
|
-
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
} catch (e) { /* not JSON or no action */ }
|
|
179
|
-
}
|
|
180
93
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
var bp = getBlueprint(toolName, toolArgs);
|
|
184
|
-
if (bp) {
|
|
185
|
-
var spec = {
|
|
186
|
-
tool: toolName,
|
|
187
|
-
description: bp.description,
|
|
188
|
-
commandCount: bp.commands.length,
|
|
189
|
-
_note: relay && !relay.isConnected()
|
|
190
|
-
? 'Figma plugin not connected. Connect the CONDUCTOR plugin to execute these ' + bp.commands.length + ' commands on canvas.'
|
|
191
|
-
: 'WebSocket relay not available. Install ws package for Figma bridge.',
|
|
192
|
-
commands: bp.commands.map(function(c, i) { return { step: i, type: c.type, name: c.data.name || c.data.text || '' }; }),
|
|
193
|
-
};
|
|
194
|
-
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(spec, null, 2) }] });
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
94
|
+
case 'ping':
|
|
95
|
+
return respond(id, {})
|
|
198
96
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
try {
|
|
202
|
-
var parsed = JSON.parse(result.content[0].text);
|
|
203
|
-
parsed._note = 'Figma plugin not connected. Connect the CONDUCTOR plugin in Figma to execute on canvas.';
|
|
204
|
-
result = { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
205
|
-
} catch (e) { /* ignore */ }
|
|
97
|
+
default:
|
|
98
|
+
if (id) respondError(id, -32601, `Unknown method: ${method}`)
|
|
206
99
|
}
|
|
207
|
-
|
|
208
|
-
sendResult(id, result);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function sendResult(id, result) {
|
|
212
|
-
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: id, result: result }) + '\n');
|
|
213
100
|
}
|
|
214
101
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
102
|
+
process.on('SIGINT', () => { if (bridge) bridge.stop(); process.exit(0) })
|
|
103
|
+
process.on('SIGTERM', () => { if (bridge) bridge.stop(); process.exit(0) })
|