conductor-figma 0.1.0 → 0.2.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/bin/conductor.js +37 -20
- package/package.json +5 -2
- package/src/index.js +1 -0
- package/src/relay.js +174 -0
- package/src/server.js +87 -61
package/bin/conductor.js
CHANGED
|
@@ -3,21 +3,27 @@
|
|
|
3
3
|
import { startServer } from '../src/server.js';
|
|
4
4
|
import { TOOLS, CATEGORIES } from '../src/tools/registry.js';
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
var args = process.argv.slice(2);
|
|
7
7
|
|
|
8
8
|
if (args.includes('--help') || args.includes('-h')) {
|
|
9
9
|
console.log(`
|
|
10
10
|
⊞ CONDUCTOR — Design-intelligent MCP server for Figma
|
|
11
11
|
|
|
12
12
|
Usage:
|
|
13
|
-
conductor-figma
|
|
14
|
-
conductor-figma --
|
|
15
|
-
conductor-figma --
|
|
16
|
-
conductor-figma --
|
|
13
|
+
conductor-figma Start MCP server + Figma relay
|
|
14
|
+
conductor-figma --port 9800 Set WebSocket port (default: 9800)
|
|
15
|
+
conductor-figma --list List all ${TOOLS.length} tools
|
|
16
|
+
conductor-figma --categories Show tool categories
|
|
17
|
+
conductor-figma --help Show this help
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
How it works:
|
|
20
|
+
1. Cursor sends tool calls via MCP (stdio)
|
|
21
|
+
2. Design tools (color, type, spacing) resolve locally
|
|
22
|
+
3. Figma tools forward through WebSocket to the plugin
|
|
23
|
+
4. Plugin executes on canvas, results flow back
|
|
20
24
|
|
|
25
|
+
Setup:
|
|
26
|
+
~/.cursor/mcp.json:
|
|
21
27
|
{
|
|
22
28
|
"mcpServers": {
|
|
23
29
|
"conductor": {
|
|
@@ -27,32 +33,43 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
27
33
|
}
|
|
28
34
|
}
|
|
29
35
|
|
|
30
|
-
|
|
36
|
+
Then open the CONDUCTOR plugin in Figma and click Connect.
|
|
37
|
+
|
|
38
|
+
${TOOLS.length} tools · ${Object.keys(CATEGORIES).length} categories
|
|
31
39
|
Built by 0xDragoon · MIT License
|
|
32
40
|
`);
|
|
33
41
|
process.exit(0);
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
if (args.includes('--list')) {
|
|
37
|
-
for (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
for (var entries = Object.entries(CATEGORIES), i = 0; i < entries.length; i++) {
|
|
46
|
+
var catKey = entries[i][0], cat = entries[i][1];
|
|
47
|
+
var tools = TOOLS.filter(function(t) { return t.category === catKey; });
|
|
48
|
+
console.log('\n ' + cat.icon + ' ' + cat.label + ' (' + tools.length + ')');
|
|
49
|
+
for (var j = 0; j < tools.length; j++) {
|
|
50
|
+
console.log(' ' + tools[j].name.padEnd(28) + ' ' + tools[j].description.slice(0, 70));
|
|
42
51
|
}
|
|
43
52
|
}
|
|
44
|
-
console.log(
|
|
53
|
+
console.log('\n ' + TOOLS.length + ' tools total\n');
|
|
45
54
|
process.exit(0);
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
if (args.includes('--categories')) {
|
|
49
|
-
for (
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
for (var entries2 = Object.entries(CATEGORIES), k = 0; k < entries2.length; k++) {
|
|
59
|
+
var key = entries2[k][0], cat2 = entries2[k][1];
|
|
60
|
+
var count = TOOLS.filter(function(t) { return t.category === key; }).length;
|
|
61
|
+
console.log(' ' + cat2.icon + ' ' + cat2.label.padEnd(18) + ' ' + count + ' tools');
|
|
52
62
|
}
|
|
53
|
-
console.log(
|
|
63
|
+
console.log('\n ' + TOOLS.length + ' tools total');
|
|
54
64
|
process.exit(0);
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
//
|
|
58
|
-
|
|
67
|
+
// Parse port
|
|
68
|
+
var port = 9800;
|
|
69
|
+
var portIdx = args.indexOf('--port');
|
|
70
|
+
if (portIdx !== -1 && args[portIdx + 1]) {
|
|
71
|
+
port = parseInt(args[portIdx + 1]) || 9800;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Start MCP server with relay
|
|
75
|
+
startServer({ port: port });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "conductor-figma",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Design-intelligent MCP server for Figma. 61 tools across 10 categories. 8px grid, type scale ratios, auto-layout, component reuse, accessibility — real design intelligence, not shape proxying.",
|
|
5
5
|
"author": "0xDragoon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,5 +33,8 @@
|
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=18.0.0"
|
|
35
35
|
},
|
|
36
|
-
"dependencies": {}
|
|
36
|
+
"dependencies": {},
|
|
37
|
+
"optionalDependencies": {
|
|
38
|
+
"ws": "^8.0.0"
|
|
39
|
+
}
|
|
37
40
|
}
|
package/src/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
4
|
|
|
5
5
|
export { startServer } from './server.js';
|
|
6
|
+
export { Relay } from './relay.js';
|
|
6
7
|
export { TOOLS, CATEGORIES, getToolByName, getToolsByCategory, getAllToolNames } from './tools/registry.js';
|
|
7
8
|
export { handleTool } from './tools/handlers.js';
|
|
8
9
|
|
package/src/relay.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// CONDUCTOR — WebSocket Relay
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
// Bridges MCP stdio (from Cursor) to WebSocket (to Figma plugin).
|
|
5
|
+
//
|
|
6
|
+
// Flow:
|
|
7
|
+
// Cursor → MCP stdio → CONDUCTOR → design logic (local)
|
|
8
|
+
// → figma commands (WebSocket) → Plugin → Figma API
|
|
9
|
+
// ← results ← WebSocket ←
|
|
10
|
+
// ← MCP stdio ← CONDUCTOR ←
|
|
11
|
+
|
|
12
|
+
import { createServer } from 'node:http';
|
|
13
|
+
|
|
14
|
+
// Tools that need Figma (sent over WebSocket to plugin)
|
|
15
|
+
const FIGMA_COMMANDS = new Set([
|
|
16
|
+
// Create
|
|
17
|
+
'create_frame', 'create_text', 'create_rect', 'create_section', 'create_component',
|
|
18
|
+
// Layout
|
|
19
|
+
'set_auto_layout', 'set_constraints', 'apply_grid', 'align_nodes',
|
|
20
|
+
// Style
|
|
21
|
+
'set_fills', 'set_strokes', 'set_effects', 'set_corner_radius', 'set_opacity',
|
|
22
|
+
// Typography
|
|
23
|
+
'set_text_props', 'load_font',
|
|
24
|
+
// Read
|
|
25
|
+
'get_selection', 'get_page_info', 'get_styles', 'get_components',
|
|
26
|
+
'read_node', 'read_tree', 'read_spacing', 'read_colors', 'read_typography',
|
|
27
|
+
// Edit
|
|
28
|
+
'rename_node', 'move_node', 'resize_node', 'delete_node',
|
|
29
|
+
'clone_node', 'group_nodes', 'ungroup_node', 'reorder_node',
|
|
30
|
+
// Export
|
|
31
|
+
'export_png', 'export_svg',
|
|
32
|
+
// Viewport
|
|
33
|
+
'zoom_to', 'scroll_to',
|
|
34
|
+
// Meta
|
|
35
|
+
'ping',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export class Relay {
|
|
39
|
+
constructor(port) {
|
|
40
|
+
this.port = port || 9800;
|
|
41
|
+
this.pluginSocket = null;
|
|
42
|
+
this.pendingCallbacks = new Map();
|
|
43
|
+
this.cmdId = 0;
|
|
44
|
+
this.server = null;
|
|
45
|
+
this.wss = null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async start() {
|
|
49
|
+
// Dynamic import ws (may not be installed — we bundle our own minimal WS)
|
|
50
|
+
let WebSocketServer;
|
|
51
|
+
try {
|
|
52
|
+
const ws = await import('ws');
|
|
53
|
+
WebSocketServer = ws.WebSocketServer || ws.default.WebSocketServer;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
process.stderr.write('CONDUCTOR relay: "ws" package not found. Install with: npm install ws\n');
|
|
56
|
+
process.stderr.write('Falling back to MCP-only mode (no Figma bridge).\n');
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.server = createServer();
|
|
61
|
+
this.wss = new WebSocketServer({ server: this.server });
|
|
62
|
+
|
|
63
|
+
this.wss.on('connection', (socket) => {
|
|
64
|
+
this.pluginSocket = socket;
|
|
65
|
+
process.stderr.write('CONDUCTOR relay: Figma plugin connected\n');
|
|
66
|
+
|
|
67
|
+
socket.on('message', (data) => {
|
|
68
|
+
try {
|
|
69
|
+
const msg = JSON.parse(data.toString());
|
|
70
|
+
this.handlePluginMessage(msg);
|
|
71
|
+
} catch (e) {
|
|
72
|
+
// ignore
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
socket.on('close', () => {
|
|
77
|
+
this.pluginSocket = null;
|
|
78
|
+
process.stderr.write('CONDUCTOR relay: Figma plugin disconnected\n');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
socket.on('error', () => {
|
|
82
|
+
this.pluginSocket = null;
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
this.server.listen(this.port, () => {
|
|
88
|
+
process.stderr.write(`CONDUCTOR relay: WebSocket listening on ws://localhost:${this.port}\n`);
|
|
89
|
+
resolve(true);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
handlePluginMessage(msg) {
|
|
95
|
+
if (msg.type === 'plugin_ready') {
|
|
96
|
+
process.stderr.write(`CONDUCTOR relay: Plugin ready (v${msg.version || '?'})\n`);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (msg.type === 'result' && msg.id !== undefined) {
|
|
101
|
+
const callback = this.pendingCallbacks.get(msg.id);
|
|
102
|
+
if (callback) {
|
|
103
|
+
this.pendingCallbacks.delete(msg.id);
|
|
104
|
+
callback(msg.data || {});
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
isConnected() {
|
|
111
|
+
return this.pluginSocket !== null && this.pluginSocket.readyState === 1; // WebSocket.OPEN
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Send a command to the Figma plugin and wait for result.
|
|
116
|
+
* Returns a Promise that resolves with the plugin's response.
|
|
117
|
+
*/
|
|
118
|
+
sendToPlugin(commandType, commandData, timeout) {
|
|
119
|
+
timeout = timeout || 15000;
|
|
120
|
+
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
if (!this.isConnected()) {
|
|
123
|
+
resolve({ error: 'Figma plugin not connected. Open the CONDUCTOR plugin in Figma and click Connect.' });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
var id = ++this.cmdId;
|
|
128
|
+
var timer = setTimeout(function() {
|
|
129
|
+
this.pendingCallbacks.delete(id);
|
|
130
|
+
resolve({ error: 'Timeout waiting for Figma plugin response' });
|
|
131
|
+
}.bind(this), timeout);
|
|
132
|
+
|
|
133
|
+
this.pendingCallbacks.set(id, function(result) {
|
|
134
|
+
clearTimeout(timer);
|
|
135
|
+
resolve(result);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.pluginSocket.send(JSON.stringify({
|
|
139
|
+
type: 'command',
|
|
140
|
+
id: id,
|
|
141
|
+
command: { type: commandType, data: commandData || {} },
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if a tool name maps to a Figma command.
|
|
148
|
+
*/
|
|
149
|
+
isFigmaCommand(toolName) {
|
|
150
|
+
return FIGMA_COMMANDS.has(toolName);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Map a high-level tool call to one or more Figma commands.
|
|
155
|
+
* Some tools (like create_page) produce multiple Figma commands.
|
|
156
|
+
* Some tools (like color_palette) are pure design logic — no Figma needed.
|
|
157
|
+
*/
|
|
158
|
+
getFigmaCommand(toolName, toolArgs) {
|
|
159
|
+
// Direct mappings — tool name IS the Figma command
|
|
160
|
+
if (FIGMA_COMMANDS.has(toolName)) {
|
|
161
|
+
return { command: toolName, data: toolArgs };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Tools that generate Figma commands from design logic output
|
|
165
|
+
// The handler produces a JSON response with an "action" field
|
|
166
|
+
// that maps to a Figma command
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
stop() {
|
|
171
|
+
if (this.wss) this.wss.close();
|
|
172
|
+
if (this.server) this.server.close();
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/server.js
CHANGED
|
@@ -1,115 +1,141 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════
|
|
2
|
-
// CONDUCTOR — MCP Server
|
|
2
|
+
// CONDUCTOR — MCP Server + Relay Bridge
|
|
3
3
|
// ═══════════════════════════════════════════
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// MCP protocol over stdio (for Cursor/Claude Code).
|
|
5
|
+
// WebSocket relay to Figma plugin (for canvas operations).
|
|
6
|
+
//
|
|
7
|
+
// Pure design tools (color_palette, type_scale, etc.) resolve locally.
|
|
8
|
+
// Figma tools (create_frame, read_node, etc.) forward through WebSocket.
|
|
6
9
|
|
|
7
10
|
import { TOOLS } from './tools/registry.js';
|
|
8
11
|
import { handleTool } from './tools/handlers.js';
|
|
12
|
+
import { Relay } from './relay.js';
|
|
9
13
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
14
|
+
var SERVER_INFO = { name: 'conductor-figma', version: '0.2.0' };
|
|
15
|
+
var CAPABILITIES = { tools: {} };
|
|
16
|
+
var relay = null;
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
export async function startServer(options) {
|
|
19
|
+
options = options || {};
|
|
20
|
+
var port = options.port || 9800;
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
relay = new Relay(port);
|
|
23
|
+
var relayStarted = await relay.start();
|
|
24
|
+
|
|
25
|
+
if (relayStarted) {
|
|
26
|
+
process.stderr.write('CONDUCTOR: MCP + relay started (' + TOOLS.length + ' tools, ws://localhost:' + port + ')\n');
|
|
27
|
+
} else {
|
|
28
|
+
process.stderr.write('CONDUCTOR: MCP started (' + TOOLS.length + ' tools, no relay — install ws for Figma bridge)\n');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var buffer = '';
|
|
25
32
|
|
|
26
33
|
process.stdin.setEncoding('utf-8');
|
|
27
|
-
process.stdin.on('data', (chunk)
|
|
34
|
+
process.stdin.on('data', function(chunk) {
|
|
28
35
|
buffer += chunk;
|
|
29
|
-
|
|
30
|
-
// Process complete lines
|
|
31
|
-
const lines = buffer.split('\n');
|
|
36
|
+
var lines = buffer.split('\n');
|
|
32
37
|
buffer = lines.pop() || '';
|
|
33
38
|
|
|
34
|
-
for (
|
|
35
|
-
|
|
39
|
+
for (var i = 0; i < lines.length; i++) {
|
|
40
|
+
var trimmed = lines[i].trim();
|
|
36
41
|
if (!trimmed) continue;
|
|
37
|
-
|
|
38
42
|
try {
|
|
39
|
-
|
|
40
|
-
handleMessage(message);
|
|
43
|
+
handleMessage(JSON.parse(trimmed));
|
|
41
44
|
} catch (err) {
|
|
42
45
|
sendError(null, -32700, 'Parse error');
|
|
43
46
|
}
|
|
44
47
|
}
|
|
45
48
|
});
|
|
46
49
|
|
|
47
|
-
process.stdin.on('end', ()
|
|
50
|
+
process.stdin.on('end', function() {
|
|
51
|
+
if (relay) relay.stop();
|
|
48
52
|
process.exit(0);
|
|
49
53
|
});
|
|
50
|
-
|
|
51
|
-
// Log startup to stderr (not stdout — that's for MCP protocol)
|
|
52
|
-
process.stderr.write(`CONDUCTOR MCP server started (${TOOLS.length} tools)\n`);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
function handleMessage(msg) {
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
async function handleMessage(msg) {
|
|
57
|
+
var id = msg.id;
|
|
58
|
+
var method = msg.method;
|
|
59
|
+
var params = msg.params || {};
|
|
58
60
|
|
|
59
61
|
switch (method) {
|
|
60
62
|
case 'initialize':
|
|
61
|
-
sendResult(id, {
|
|
62
|
-
protocolVersion: '2024-11-05',
|
|
63
|
-
serverInfo: SERVER_INFO,
|
|
64
|
-
capabilities: CAPABILITIES,
|
|
65
|
-
});
|
|
63
|
+
sendResult(id, { protocolVersion: '2024-11-05', serverInfo: SERVER_INFO, capabilities: CAPABILITIES });
|
|
66
64
|
break;
|
|
67
65
|
|
|
68
66
|
case 'initialized':
|
|
69
|
-
// Notification, no response needed
|
|
70
67
|
break;
|
|
71
68
|
|
|
72
69
|
case 'tools/list':
|
|
73
70
|
sendResult(id, {
|
|
74
|
-
tools: TOOLS.map(t
|
|
75
|
-
name: t.name,
|
|
76
|
-
description: t.description,
|
|
77
|
-
inputSchema: t.inputSchema,
|
|
78
|
-
})),
|
|
71
|
+
tools: TOOLS.map(function(t) { return { name: t.name, description: t.description, inputSchema: t.inputSchema }; }),
|
|
79
72
|
});
|
|
80
73
|
break;
|
|
81
74
|
|
|
82
|
-
case 'tools/call':
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
sendError(id, -32602, 'Missing tool name');
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const result = handleTool(toolName, toolArgs, null);
|
|
92
|
-
sendResult(id, result);
|
|
75
|
+
case 'tools/call':
|
|
76
|
+
var toolName = params.name;
|
|
77
|
+
var toolArgs = params.arguments || {};
|
|
78
|
+
if (!toolName) { sendError(id, -32602, 'Missing tool name'); return; }
|
|
79
|
+
await handleToolCall(id, toolName, toolArgs);
|
|
93
80
|
break;
|
|
94
|
-
}
|
|
95
81
|
|
|
96
82
|
case 'ping':
|
|
97
83
|
sendResult(id, {});
|
|
98
84
|
break;
|
|
99
85
|
|
|
100
86
|
default:
|
|
101
|
-
if (id !== undefined)
|
|
102
|
-
|
|
87
|
+
if (id !== undefined) sendError(id, -32601, 'Method not found: ' + method);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function handleToolCall(id, toolName, toolArgs) {
|
|
92
|
+
// If tool is a direct Figma command and plugin is connected — forward it
|
|
93
|
+
if (relay && relay.isFigmaCommand(toolName) && relay.isConnected()) {
|
|
94
|
+
process.stderr.write('CONDUCTOR: -> Figma: ' + toolName + '\n');
|
|
95
|
+
try {
|
|
96
|
+
var figmaResult = await relay.sendToPlugin(toolName, toolArgs);
|
|
97
|
+
process.stderr.write('CONDUCTOR: <- Figma: ' + (figmaResult.name || figmaResult.id || 'ok') + '\n');
|
|
98
|
+
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(figmaResult, null, 2) }] });
|
|
99
|
+
} catch (err) {
|
|
100
|
+
sendResult(id, { content: [{ type: 'text', text: JSON.stringify({ error: String(err) }) }] });
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Run through design intelligence handler
|
|
106
|
+
var result = handleTool(toolName, toolArgs, null);
|
|
107
|
+
|
|
108
|
+
// Check if handler produced a Figma action we should forward
|
|
109
|
+
if (relay && relay.isConnected()) {
|
|
110
|
+
try {
|
|
111
|
+
var data = JSON.parse(result.content[0].text);
|
|
112
|
+
if (data.action && relay.isFigmaCommand(data.action)) {
|
|
113
|
+
process.stderr.write('CONDUCTOR: -> Figma (via ' + toolName + '): ' + data.action + '\n');
|
|
114
|
+
var fResult = await relay.sendToPlugin(data.action, data);
|
|
115
|
+
process.stderr.write('CONDUCTOR: <- Figma: ' + (fResult.name || fResult.id || 'ok') + '\n');
|
|
116
|
+
data._figmaResult = fResult;
|
|
117
|
+
sendResult(id, { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
118
|
+
return;
|
|
103
119
|
}
|
|
120
|
+
} catch (e) { /* not JSON or no action */ }
|
|
104
121
|
}
|
|
122
|
+
|
|
123
|
+
// If Figma command but plugin not connected — add note
|
|
124
|
+
if (relay && relay.isFigmaCommand(toolName) && !relay.isConnected()) {
|
|
125
|
+
try {
|
|
126
|
+
var parsed = JSON.parse(result.content[0].text);
|
|
127
|
+
parsed._note = 'Figma plugin not connected. Connect the CONDUCTOR plugin in Figma to execute on canvas.';
|
|
128
|
+
result = { content: [{ type: 'text', text: JSON.stringify(parsed, null, 2) }] };
|
|
129
|
+
} catch (e) { /* ignore */ }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
sendResult(id, result);
|
|
105
133
|
}
|
|
106
134
|
|
|
107
135
|
function sendResult(id, result) {
|
|
108
|
-
|
|
109
|
-
process.stdout.write(JSON.stringify(response) + '\n');
|
|
136
|
+
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: id, result: result }) + '\n');
|
|
110
137
|
}
|
|
111
138
|
|
|
112
139
|
function sendError(id, code, message) {
|
|
113
|
-
|
|
114
|
-
process.stdout.write(JSON.stringify(response) + '\n');
|
|
140
|
+
process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id: id, error: { code: code, message: message } }) + '\n');
|
|
115
141
|
}
|