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.
@@ -1,181 +0,0 @@
1
- // ═══════════════════════════════════════════
2
- // CONDUCTOR — Design Craft Guide
3
- // ═══════════════════════════════════════════
4
- // Professional design rules. The AI reads this before designing anything.
5
- // This is what separates amateur output from production quality.
6
-
7
- export function getDesignCraftGuide() {
8
- return `# CONDUCTOR Design Craft Guide
9
-
10
- You are a senior product designer working in Figma. Every frame, every text node, every color choice must follow these rules. No exceptions.
11
-
12
- ## Layout Architecture
13
-
14
- ### Frame Structure
15
- - ALWAYS use auto-layout. Never absolute positioning.
16
- - Direction: VERTICAL for page sections, HORIZONTAL for rows of items.
17
- - Every frame needs explicit padding and gap. Never 0 unless intentional.
18
- - Use primaryAxisSizingMode: "HUG" for content-driven frames, "FIXED" for containers with set widths.
19
- - Use counterAxisSizingMode: "FILL" for child frames that should stretch to parent width.
20
-
21
- ### Spacing System (8px grid)
22
- - Base unit: 8px. All spacing values must be multiples of 8.
23
- - Micro spacing: 4px, 8px (within components, between label and input)
24
- - Small spacing: 12px, 16px (between related elements, card padding)
25
- - Medium spacing: 24px, 32px (between component groups, section padding)
26
- - Large spacing: 48px, 64px (between page sections)
27
- - Extra large: 80px, 96px, 120px (page-level vertical rhythm)
28
- - NEVER use: 5, 7, 10, 13, 15, 17, 22, 25, 30, 35, 50, 55, 65, 70, 75, 90, 100
29
-
30
- ### Content Width
31
- - Max content width: 1200px for desktop, centered in wider frames
32
- - For a 1440px page, use 48-80px horizontal padding to create margins
33
- - Card grid max: 3-4 columns. Never 5+ columns for content cards.
34
- - Sidebar: 240-280px. Never wider than 320px.
35
-
36
- ## Typography
37
-
38
- ### Type Scale (use Inter font family)
39
- - Display: 56-72px, Bold or Extra Bold, line-height 1.1
40
- - H1: 40-48px, Bold, line-height 1.15
41
- - H2: 32-36px, Bold or Semi Bold, line-height 1.2
42
- - H3: 24-28px, Semi Bold, line-height 1.25
43
- - H4: 18-20px, Semi Bold, line-height 1.3
44
- - Body large: 18px, Regular, line-height 1.6
45
- - Body: 15-16px, Regular, line-height 1.6
46
- - Body small: 13-14px, Regular, line-height 1.5
47
- - Caption: 11-12px, Medium, line-height 1.4
48
- - Overline: 10-12px, Semi Bold or Bold, uppercase, letter-spacing 0.08em
49
-
50
- ### Typography Rules
51
- - Maximum 2 font weights per section (e.g., Bold + Regular, Semi Bold + Regular)
52
- - Body text color should NEVER be pure white (#ffffff). Use #f0f0f8 or #e8e8f0 for dark themes.
53
- - Muted text: #a0a0b8 for dark themes, #666680 for light themes.
54
- - Heading to body size ratio should be at least 1.5x.
55
- - Line length: 45-75 characters for body text. Use max-width on text containers.
56
-
57
- ## Color
58
-
59
- ### Dark Theme Palette
60
- - Background levels: #09090f → #0c0c18 → #0f0f1c → #12122a → #16163a
61
- - Each level should be visibly distinct but not jarring.
62
- - Card backgrounds should be 1-2 levels lighter than the page background.
63
- - Text hierarchy: #f0f0f8 (primary) → #a0a0b8 (secondary) → #686880 (tertiary)
64
- - Dividers: #1a1a32 or #1e1e3a (subtle, never bright)
65
- - NEVER use pure black (#000000) as a background.
66
- - NEVER use pure white (#ffffff) as text on dark backgrounds.
67
-
68
- ### Light Theme Palette
69
- - Background levels: #ffffff → #f9f9fb → #f3f3f7 → #ebebf0
70
- - Card backgrounds: #ffffff with subtle border (#e4e4ec)
71
- - Text hierarchy: #111118 (primary) → #55556a (secondary) → #88889a (tertiary)
72
- - Dividers: #e4e4ec
73
-
74
- ### Brand Color Usage
75
- - Primary brand color: buttons, links, badges, icons, active states.
76
- - NEVER use brand color for large background areas.
77
- - Brand color for text only in overlines, links, and badges.
78
- - Hover states: darken brand color by 10%. Active: darken by 15%.
79
-
80
- ## Components
81
-
82
- ### Buttons
83
- - Height: 36px (small), 40px (default), 48px (large), 56px (hero)
84
- - Horizontal padding: 16px (small), 20px (default), 28px (large), 32px (hero)
85
- - Corner radius: 8px (default), 10-12px (large/hero)
86
- - Primary: brand color fill, white text, Semi Bold
87
- - Secondary: transparent fill, border or muted text, Medium weight
88
- - Ghost: transparent, text only, Medium weight
89
- - Always center text both axes in buttons
90
-
91
- ### Cards
92
- - Padding: 24-32px (compact), 28-40px (standard)
93
- - Corner radius: 12-16px
94
- - Gap between elements inside: 12-20px
95
- - On dark themes: use a lighter background, no border
96
- - On light themes: white background + subtle border (#e4e4ec) + optional shadow
97
- - Cards in a row should all be the same height (use counterAxisAlignItems: "STRETCH" on parent)
98
-
99
- ### Navigation
100
- - Height: 64-72px for top nav
101
- - Logo left, links center or right, CTA button far right
102
- - Use a spacer frame (FILL sizing) between logo and links to push them apart
103
- - Nav links: 14px Medium, 24-32px gap between items
104
- - Active state: brand color or bolder weight
105
- - Add a 1px divider below the nav
106
-
107
- ### Metric/Stat Cards
108
- - Stack: label on top (12-13px, muted, Medium), value below (28-36px, Bold)
109
- - Optional: change indicator below value (12-13px, green for positive, red for negative)
110
- - Equal width cards in a horizontal row
111
- - 20-24px gap between metric cards
112
-
113
- ### Tables
114
- - Header row: 40-48px height, uppercase 10-11px labels, muted color, Medium weight
115
- - Data rows: 48-56px height, 14-15px Regular text
116
- - Cell padding: 16-20px horizontal
117
- - Alternating row backgrounds or horizontal dividers (not both)
118
- - Status badges: small colored pills with 4-6px padding, rounded
119
-
120
- ### Forms
121
- - Input height: 40-44px
122
- - Label: 13-14px, Medium weight, 4-8px gap below label
123
- - Input: 14-15px Regular, 12-16px horizontal padding
124
- - Corner radius: 8px
125
- - Border: 1px, muted color. Focus: brand color border
126
- - 20-24px gap between field groups
127
-
128
- ## Section Patterns
129
-
130
- ### Hero Section
131
- - Vertical padding: 80-120px
132
- - Content centered (both axes)
133
- - Overline badge → Heading (56-72px) → Subtitle (18-20px) → Button row → Social proof
134
- - Max heading width: ~600px
135
- - Subtitle max width: ~500px
136
- - 28-32px gap between heading and subtitle
137
- - 32-40px gap between subtitle and buttons
138
-
139
- ### Feature Section
140
- - Heading + subtitle centered at top
141
- - 3 cards in a row (or 2 for detailed features)
142
- - Each card: icon/emoji → title → description → optional link
143
- - 48px gap between heading group and card row
144
-
145
- ### Stats/Social Proof
146
- - Background color shift (one level different from surrounding sections)
147
- - 3-4 stats in a horizontal row, centered
148
- - Each stat centered: big number + label below
149
-
150
- ### CTA Section
151
- - Often wrapped in a card or a background shift
152
- - Centered: heading → subtitle → button row
153
- - Generous padding: 64-96px vertical
154
-
155
- ### Footer
156
- - Logo left, copyright right, spacer between
157
- - Or: multi-column with link groups
158
- - Divider line above
159
- - Muted text, smaller font sizes (13px)
160
-
161
- ## Anti-Patterns (NEVER do these)
162
- - Never use absolute positioning. Always auto-layout.
163
- - Never use font sizes that aren't in the type scale.
164
- - Never use spacing values that aren't multiples of 4 or 8.
165
- - Never put more than 3-4 cards in a row.
166
- - Never use pure black or pure white on dark themes.
167
- - Never make buttons without sufficient padding (minimum 16px horizontal).
168
- - Never stack more than 4-5 levels of nesting without good reason.
169
- - Never use inconsistent corner radii on the same page.
170
- - Never use more than 2-3 distinct background colors per page.
171
- - Never center-align body paragraphs (center headings only).
172
- - Never use text smaller than 11px.
173
- - Never create frames without naming them descriptively.
174
-
175
- ## Naming Convention
176
- - Pages: "Landing Page", "Pricing Page", "Dashboard"
177
- - Sections: "Hero Section", "Features Section", "CTA Section"
178
- - Components: "Nav CTA", "Feature Card", "Stat Card", "Price Tier"
179
- - Layout: "Card Row", "Button Row", "Nav Links"
180
- - Never use "Frame 1", "Rectangle 2", or auto-generated names.`;
181
- }
@@ -1,72 +0,0 @@
1
- // ═══════════════════════════════════════════
2
- // CONDUCTOR — Token Exporter
3
- // ═══════════════════════════════════════════
4
-
5
- export function exportCSS(tokens) {
6
- let out = '/* Generated by CONDUCTOR */\n:root {\n';
7
- if (tokens.colors) for (const [k, v] of Object.entries(tokens.colors)) out += ` --color-${k}: ${v};\n`;
8
- if (tokens.spacing) tokens.spacing.forEach((v, i) => { out += ` --space-${i + 1}: ${v}px;\n`; });
9
- if (tokens.fontSizes) {
10
- const names = ['xs','sm','base','md','lg','xl','2xl','3xl','4xl','5xl','6xl'];
11
- tokens.fontSizes.forEach((v, i) => { out += ` --text-${names[i] || i}: ${v.size || v}px;\n`; });
12
- }
13
- if (tokens.radii) tokens.radii.forEach(r => { out += ` --radius-${r.name}: ${r.value >= 9999 ? '9999px' : r.value + 'px'};\n`; });
14
- if (tokens.shadows) tokens.shadows.forEach(s => { out += ` --shadow-${s.step}: ${s.css};\n`; });
15
- out += '}\n';
16
- return out;
17
- }
18
-
19
- export function exportTailwind(tokens) {
20
- const config = { theme: { extend: {} } };
21
- if (tokens.colors) config.theme.extend.colors = { ...tokens.colors };
22
- if (tokens.spacing) {
23
- config.theme.extend.spacing = {};
24
- tokens.spacing.forEach((v, i) => { config.theme.extend.spacing[String(i + 1)] = `${v}px`; });
25
- }
26
- if (tokens.fontSizes) {
27
- config.theme.extend.fontSize = {};
28
- const names = ['xs','sm','base','md','lg','xl','2xl','3xl','4xl','5xl','6xl'];
29
- tokens.fontSizes.forEach((v, i) => { config.theme.extend.fontSize[names[i] || i] = `${v.size || v}px`; });
30
- }
31
- if (tokens.radii) {
32
- config.theme.extend.borderRadius = {};
33
- tokens.radii.forEach(r => { config.theme.extend.borderRadius[r.name] = r.value >= 9999 ? '9999px' : `${r.value}px`; });
34
- }
35
- return `// tailwind.config.js — Generated by CONDUCTOR\nmodule.exports = ${JSON.stringify(config, null, 2)}\n`;
36
- }
37
-
38
- export function exportSCSS(tokens) {
39
- let out = '// Generated by CONDUCTOR\n\n';
40
- if (tokens.colors) for (const [k, v] of Object.entries(tokens.colors)) out += `$color-${k}: ${v};\n`;
41
- if (tokens.spacing) { out += '\n'; tokens.spacing.forEach((v, i) => { out += `$space-${i + 1}: ${v}px;\n`; }); }
42
- if (tokens.fontSizes) {
43
- out += '\n';
44
- const names = ['xs','sm','base','md','lg','xl','2xl','3xl','4xl','5xl','6xl'];
45
- tokens.fontSizes.forEach((v, i) => { out += `$text-${names[i] || i}: ${v.size || v}px;\n`; });
46
- }
47
- return out;
48
- }
49
-
50
- export function exportJSON(tokens) {
51
- return JSON.stringify(tokens, null, 2);
52
- }
53
-
54
- export function exportW3CTokens(tokens) {
55
- const w3c = {};
56
- if (tokens.colors) {
57
- w3c.color = {};
58
- for (const [k, v] of Object.entries(tokens.colors)) {
59
- w3c.color[k] = { $value: v, $type: 'color' };
60
- }
61
- }
62
- if (tokens.spacing) {
63
- w3c.spacing = {};
64
- tokens.spacing.forEach((v, i) => { w3c.spacing[`space-${i + 1}`] = { $value: `${v}px`, $type: 'dimension' }; });
65
- }
66
- if (tokens.fontSizes) {
67
- w3c.fontSize = {};
68
- const names = ['xs','sm','base','md','lg','xl','2xl','3xl','4xl','5xl','6xl'];
69
- tokens.fontSizes.forEach((v, i) => { w3c.fontSize[names[i] || i] = { $value: `${v.size || v}px`, $type: 'dimension' }; });
70
- }
71
- return JSON.stringify(w3c, null, 2);
72
- }
package/src/index.js DELETED
@@ -1,33 +0,0 @@
1
- // ═══════════════════════════════════════════
2
- // CONDUCTOR — Public API
3
- // ═══════════════════════════════════════════
4
-
5
- export { startServer } from './server.js';
6
- export { Relay } from './relay.js';
7
- export { executeSequence } from './orchestrator.js';
8
- export { getBlueprint, buildLandingPage, buildPricingPage, buildDashboardPage, buildSection } from './blueprints.js';
9
- export { TOOLS, CATEGORIES, getToolByName, getToolsByCategory, getAllToolNames } from './tools/registry.js';
10
- export { handleTool } from './tools/handlers.js';
11
-
12
- export {
13
- // Grid
14
- snapToGrid, isOnGrid, generateSpacingScale, findNearestGridValue, auditSpacing,
15
- // Type
16
- TYPE_SCALES, generateTypeScale, detectTypeScale, getLineHeight, getFontWeight, checkMeasure,
17
- // Color
18
- hexToRgb, rgbToHex, hexToHsl, hslToHex, generatePalette, generateSemanticColors,
19
- generateDarkMode, relativeLuminance, contrastRatio, checkContrast,
20
- // Shadow & Radius
21
- generateElevation, generateRadiusScale,
22
- // Hierarchy
23
- assessHierarchy,
24
- // Accessibility
25
- checkTouchTarget,
26
- // Layout
27
- inferLayoutDirection, inferGap, inferPadding, BREAKPOINTS, scaleForBreakpoint,
28
- // Score
29
- computeDesignScore,
30
- } from './design/intelligence.js';
31
-
32
- export { exportCSS, exportTailwind, exportSCSS, exportJSON, exportW3CTokens } from './design/exporter.js';
33
- export { getDesignCraftGuide } from './design/craftguide.js';
@@ -1,100 +0,0 @@
1
- // ═══════════════════════════════════════════
2
- // CONDUCTOR — Orchestrator
3
- // ═══════════════════════════════════════════
4
- // Takes a blueprint (sequence of commands) and executes them
5
- // through the relay one at a time. Each command can reference
6
- // results from previous commands via $ref tokens.
7
- //
8
- // Example: create a frame, then create text inside it.
9
- // The text command references the frame's ID via $ref.
10
- //
11
- // { type: 'create_frame', name: 'Hero', ... } → returns { id: '5:2' }
12
- // { type: 'create_text', parentId: '$0.id', text: ... } → parentId resolved to '5:2'
13
-
14
- /**
15
- * Execute a sequence of Figma commands through the relay.
16
- * Supports $ref tokens: '$N.field' references result N's field.
17
- *
18
- * @param {Object} relay - The WebSocket relay instance
19
- * @param {Array} commands - Array of { type, data } command objects
20
- * @param {Function} onProgress - Optional callback(stepIndex, totalSteps, result)
21
- * @returns {Promise<{ results: Array, errors: Array, success: boolean }>}
22
- */
23
- export async function executeSequence(relay, commands, onProgress) {
24
- var results = [];
25
- var errors = [];
26
-
27
- for (var i = 0; i < commands.length; i++) {
28
- var cmd = commands[i];
29
- var resolvedData = resolveRefs(cmd.data || {}, results);
30
-
31
- try {
32
- var result = await relay.sendToPlugin(cmd.type, resolvedData, 10000);
33
-
34
- if (result && result.error) {
35
- errors.push({ step: i, command: cmd.type, error: result.error });
36
- results.push(result);
37
- // Don't stop on error — skip and continue
38
- process.stderr.write('CONDUCTOR orchestrator: step ' + i + ' (' + cmd.type + ') error: ' + result.error + '\n');
39
- } else {
40
- results.push(result || {});
41
- if (onProgress) onProgress(i, commands.length, result);
42
- process.stderr.write('CONDUCTOR orchestrator: step ' + (i + 1) + '/' + commands.length + ' ' + cmd.type + ' -> ' + (result && (result.id || result.name) || 'ok') + '\n');
43
- }
44
- } catch (err) {
45
- errors.push({ step: i, command: cmd.type, error: String(err) });
46
- results.push({ error: String(err) });
47
- process.stderr.write('CONDUCTOR orchestrator: step ' + i + ' (' + cmd.type + ') threw: ' + String(err) + '\n');
48
- }
49
-
50
- // Small delay between commands to let Figma process
51
- await sleep(50);
52
- }
53
-
54
- return {
55
- results: results,
56
- errors: errors,
57
- success: errors.length === 0,
58
- totalSteps: commands.length,
59
- completedSteps: commands.length - errors.length,
60
- };
61
- }
62
-
63
- /**
64
- * Resolve $ref tokens in command data.
65
- * '$0.id' → results[0].id
66
- * '$parent' → results[results.length-1].id (last result's id)
67
- * '$2.name' → results[2].name
68
- */
69
- function resolveRefs(data, results) {
70
- if (typeof data === 'string') {
71
- // Check for $ref pattern
72
- return data.replace(/\$(\d+)\.(\w+)/g, function(match, idx, field) {
73
- var r = results[parseInt(idx)];
74
- return (r && r[field] !== undefined) ? r[field] : match;
75
- }).replace(/\$parent/g, function() {
76
- var last = results[results.length - 1];
77
- return (last && last.id) ? last.id : '';
78
- });
79
- }
80
-
81
- if (Array.isArray(data)) {
82
- return data.map(function(item) { return resolveRefs(item, results); });
83
- }
84
-
85
- if (data && typeof data === 'object') {
86
- var resolved = {};
87
- for (var key in data) {
88
- if (data.hasOwnProperty(key)) {
89
- resolved[key] = resolveRefs(data[key], results);
90
- }
91
- }
92
- return resolved;
93
- }
94
-
95
- return data;
96
- }
97
-
98
- function sleep(ms) {
99
- return new Promise(function(resolve) { setTimeout(resolve, ms); });
100
- }
package/src/relay.js DELETED
@@ -1,176 +0,0 @@
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
- 'create_ellipse', 'create_svg_node',
19
- // Layout
20
- 'set_auto_layout', 'set_constraints', 'apply_grid', 'align_nodes',
21
- // Style
22
- 'set_fills', 'set_strokes', 'set_effects', 'set_corner_radius', 'set_opacity',
23
- // Typography
24
- 'set_text_props', 'load_font', 'style_text_range',
25
- // Read
26
- 'get_selection', 'get_page_info', 'get_styles', 'get_components',
27
- 'read_node', 'read_tree', 'read_spacing', 'read_colors', 'read_typography',
28
- 'find_nodes',
29
- // Edit
30
- 'rename_node', 'move_node', 'resize_node', 'delete_node',
31
- 'clone_node', 'group_nodes', 'ungroup_node', 'reorder_node',
32
- // Export
33
- 'export_png', 'export_svg',
34
- // Viewport
35
- 'zoom_to', 'scroll_to',
36
- // Meta
37
- 'ping',
38
- ]);
39
-
40
- export class Relay {
41
- constructor(port) {
42
- this.port = port || 9800;
43
- this.pluginSocket = null;
44
- this.pendingCallbacks = new Map();
45
- this.cmdId = 0;
46
- this.server = null;
47
- this.wss = null;
48
- }
49
-
50
- async start() {
51
- // Dynamic import ws (may not be installed — we bundle our own minimal WS)
52
- let WebSocketServer;
53
- try {
54
- const ws = await import('ws');
55
- WebSocketServer = ws.WebSocketServer || ws.default.WebSocketServer;
56
- } catch (e) {
57
- process.stderr.write('CONDUCTOR relay: "ws" package not found. Install with: npm install ws\n');
58
- process.stderr.write('Falling back to MCP-only mode (no Figma bridge).\n');
59
- return false;
60
- }
61
-
62
- this.server = createServer();
63
- this.wss = new WebSocketServer({ server: this.server });
64
-
65
- this.wss.on('connection', (socket) => {
66
- this.pluginSocket = socket;
67
- process.stderr.write('CONDUCTOR relay: Figma plugin connected\n');
68
-
69
- socket.on('message', (data) => {
70
- try {
71
- const msg = JSON.parse(data.toString());
72
- this.handlePluginMessage(msg);
73
- } catch (e) {
74
- // ignore
75
- }
76
- });
77
-
78
- socket.on('close', () => {
79
- this.pluginSocket = null;
80
- process.stderr.write('CONDUCTOR relay: Figma plugin disconnected\n');
81
- });
82
-
83
- socket.on('error', () => {
84
- this.pluginSocket = null;
85
- });
86
- });
87
-
88
- return new Promise((resolve) => {
89
- this.server.listen(this.port, () => {
90
- process.stderr.write(`CONDUCTOR relay: WebSocket listening on ws://localhost:${this.port}\n`);
91
- resolve(true);
92
- });
93
- });
94
- }
95
-
96
- handlePluginMessage(msg) {
97
- if (msg.type === 'plugin_ready') {
98
- process.stderr.write(`CONDUCTOR relay: Plugin ready (v${msg.version || '?'})\n`);
99
- return;
100
- }
101
-
102
- if (msg.type === 'result' && msg.id !== undefined) {
103
- const callback = this.pendingCallbacks.get(msg.id);
104
- if (callback) {
105
- this.pendingCallbacks.delete(msg.id);
106
- callback(msg.data || {});
107
- }
108
- return;
109
- }
110
- }
111
-
112
- isConnected() {
113
- return this.pluginSocket !== null && this.pluginSocket.readyState === 1; // WebSocket.OPEN
114
- }
115
-
116
- /**
117
- * Send a command to the Figma plugin and wait for result.
118
- * Returns a Promise that resolves with the plugin's response.
119
- */
120
- sendToPlugin(commandType, commandData, timeout) {
121
- timeout = timeout || 15000;
122
-
123
- return new Promise((resolve, reject) => {
124
- if (!this.isConnected()) {
125
- resolve({ error: 'Figma plugin not connected. Open the CONDUCTOR plugin in Figma and click Connect.' });
126
- return;
127
- }
128
-
129
- var id = ++this.cmdId;
130
- var timer = setTimeout(function() {
131
- this.pendingCallbacks.delete(id);
132
- resolve({ error: 'Timeout waiting for Figma plugin response' });
133
- }.bind(this), timeout);
134
-
135
- this.pendingCallbacks.set(id, function(result) {
136
- clearTimeout(timer);
137
- resolve(result);
138
- });
139
-
140
- this.pluginSocket.send(JSON.stringify({
141
- type: 'command',
142
- id: id,
143
- command: { type: commandType, data: commandData || {} },
144
- }));
145
- });
146
- }
147
-
148
- /**
149
- * Check if a tool name maps to a Figma command.
150
- */
151
- isFigmaCommand(toolName) {
152
- return FIGMA_COMMANDS.has(toolName);
153
- }
154
-
155
- /**
156
- * Map a high-level tool call to one or more Figma commands.
157
- * Some tools (like create_page) produce multiple Figma commands.
158
- * Some tools (like color_palette) are pure design logic — no Figma needed.
159
- */
160
- getFigmaCommand(toolName, toolArgs) {
161
- // Direct mappings — tool name IS the Figma command
162
- if (FIGMA_COMMANDS.has(toolName)) {
163
- return { command: toolName, data: toolArgs };
164
- }
165
-
166
- // Tools that generate Figma commands from design logic output
167
- // The handler produces a JSON response with an "action" field
168
- // that maps to a Figma command
169
- return null;
170
- }
171
-
172
- stop() {
173
- if (this.wss) this.wss.close();
174
- if (this.server) this.server.close();
175
- }
176
- }