tellfigma 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/dist/figma.js ADDED
@@ -0,0 +1,156 @@
1
+ // ============================================================
2
+ // Figma CDP Connection & Helpers
3
+ // Manages Chrome DevTools Protocol connection and provides
4
+ // core functions for executing code, taking screenshots, etc.
5
+ // ============================================================
6
+ import CDP from 'chrome-remote-interface';
7
+ import { findFigmaTab } from './chrome.js';
8
+ // ---- State ----
9
+ let cdpClient = null;
10
+ let chromePort = 9222;
11
+ let lastTabUrl = null;
12
+ // ---- Logging (stderr so it doesn't interfere with MCP stdio) ----
13
+ export function log(msg) {
14
+ process.stderr.write(`[tellfigma] ${msg}\n`);
15
+ }
16
+ // ---- Port configuration ----
17
+ export function setChromePort(port) {
18
+ chromePort = port;
19
+ }
20
+ // ---- CDP Connection (auto-reconnect) ----
21
+ export async function ensureConnected() {
22
+ if (cdpClient) {
23
+ try {
24
+ // Test if connection is still alive
25
+ await cdpClient.Runtime.evaluate({ expression: '1+1' });
26
+ return cdpClient;
27
+ }
28
+ catch {
29
+ log('Connection lost, reconnecting...');
30
+ try {
31
+ cdpClient.close();
32
+ }
33
+ catch { }
34
+ cdpClient = null;
35
+ }
36
+ }
37
+ // Retry logic — Figma tab may take a moment after page load
38
+ let tab = null;
39
+ for (let attempt = 0; attempt < 3; attempt++) {
40
+ tab = await findFigmaTab(chromePort);
41
+ if (tab)
42
+ break;
43
+ if (attempt < 2) {
44
+ log(`Waiting for Figma tab... (attempt ${attempt + 1}/3)`);
45
+ await new Promise((r) => setTimeout(r, 2000));
46
+ }
47
+ }
48
+ if (!tab) {
49
+ throw new Error('No Figma tab found. Please open a Figma design file in Chrome, then try again.');
50
+ }
51
+ log(`Connecting to Figma tab: ${tab.title}`);
52
+ lastTabUrl = tab.url;
53
+ cdpClient = await CDP({
54
+ port: chromePort,
55
+ target: tab.webSocketDebuggerUrl,
56
+ });
57
+ await cdpClient.Runtime.enable();
58
+ await cdpClient.Page.enable();
59
+ // Auto-reconnect on disconnect
60
+ cdpClient.on('disconnect', () => {
61
+ log('CDP disconnected — will reconnect on next tool call');
62
+ cdpClient = null;
63
+ });
64
+ return cdpClient;
65
+ }
66
+ // ---- Execute Figma Code ----
67
+ export async function executeFigmaCode(code) {
68
+ const client = await ensureConnected();
69
+ // Wrap in async IIFE if not already wrapped
70
+ let wrappedCode = code;
71
+ if (!code.trim().startsWith('(async')) {
72
+ wrappedCode = `(async () => {\n${code}\n})()`;
73
+ }
74
+ const result = await client.Runtime.evaluate({
75
+ expression: wrappedCode,
76
+ awaitPromise: true,
77
+ returnByValue: true,
78
+ timeout: 30000,
79
+ });
80
+ if (result.exceptionDetails) {
81
+ const error = result.exceptionDetails;
82
+ const errorText = error.exception?.description ||
83
+ error.exception?.value ||
84
+ error.text ||
85
+ 'Unknown error';
86
+ // Add helpful hints for common errors
87
+ let hint = '';
88
+ if (errorText.includes('loadFontAsync')) {
89
+ hint = '\n\nHint: You must call await figma.loadFontAsync({ family, style }) before setting characters on a text node.';
90
+ }
91
+ else if (errorText.includes('Cannot read properties of null')) {
92
+ hint = '\n\nHint: A node was null. Use figma.currentPage.findOne() carefully — it returns null if nothing matches.';
93
+ }
94
+ else if (errorText.includes('not a function')) {
95
+ hint = '\n\nHint: Check that you\'re calling the right method. E.g., figma.createFrame() not figma.createAutoLayout().';
96
+ }
97
+ else if (errorText.includes('layoutSizingHorizontal') || errorText.includes('layoutSizingVertical')) {
98
+ hint = '\n\nHint: layoutSizingHorizontal/Vertical must be set AFTER the node is appended to a parent with layoutMode.';
99
+ }
100
+ return `Error: ${errorText}${hint}`;
101
+ }
102
+ if (result.result.type === 'undefined') {
103
+ return 'Code executed successfully (no return value).';
104
+ }
105
+ const value = result.result.value;
106
+ if (typeof value === 'object') {
107
+ return JSON.stringify(value, null, 2);
108
+ }
109
+ return String(value ?? 'null');
110
+ }
111
+ // ---- Take Screenshot ----
112
+ export async function takeScreenshot() {
113
+ const client = await ensureConnected();
114
+ const { data } = await client.Page.captureScreenshot({
115
+ format: 'png',
116
+ quality: 80,
117
+ });
118
+ // Get viewport size
119
+ const { result } = await client.Runtime.evaluate({
120
+ expression: `JSON.stringify({ width: window.innerWidth, height: window.innerHeight })`,
121
+ returnByValue: true,
122
+ });
123
+ const dims = JSON.parse(result.value);
124
+ return {
125
+ base64: data,
126
+ width: dims.width,
127
+ height: dims.height,
128
+ };
129
+ }
130
+ // ---- Get Page Info ----
131
+ export async function getPageInfo() {
132
+ const client = await ensureConnected();
133
+ const { result } = await client.Runtime.evaluate({
134
+ expression: `JSON.stringify({
135
+ url: window.location.href,
136
+ title: document.title,
137
+ hasFigma: typeof figma !== 'undefined',
138
+ pageInfo: typeof figma !== 'undefined' ? {
139
+ pageName: figma.currentPage.name,
140
+ childCount: figma.currentPage.children.length,
141
+ selection: figma.currentPage.selection.map(n => ({
142
+ id: n.id, name: n.name, type: n.type
143
+ })),
144
+ topLevelNodes: figma.currentPage.children.slice(0, 20).map(n => ({
145
+ id: n.id, name: n.name, type: n.type,
146
+ width: 'width' in n ? Math.round(n.width) : null,
147
+ height: 'height' in n ? Math.round(n.height) : null,
148
+ }))
149
+ } : null
150
+ })`,
151
+ awaitPromise: true,
152
+ returnByValue: true,
153
+ });
154
+ return JSON.stringify(JSON.parse(result.value), null, 2);
155
+ }
156
+ //# sourceMappingURL=figma.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma.js","sourceRoot":"","sources":["../src/figma.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function startServer(port?: number): Promise<void>;
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAyBA,wBAAsB,WAAW,CAAC,IAAI,GAAE,MAAa,iBAYpD"}
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ // ============================================================
2
+ // tellfigma — MCP Server Entry Point
3
+ // Wires together: Chrome launcher, CDP connection, MCP tools
4
+ // ============================================================
5
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
7
+ import { launchChrome } from './chrome.js';
8
+ import { setChromePort, log } from './figma.js';
9
+ import { registerPrompts } from './prompts.js';
10
+ import { registerTools } from './tools.js';
11
+ // ---- MCP Server Setup ----
12
+ const server = new McpServer({
13
+ name: 'tellfigma',
14
+ version: '0.2.0',
15
+ });
16
+ // Register all prompts/resources and tools
17
+ registerPrompts(server);
18
+ registerTools(server);
19
+ // ---- Start ----
20
+ export async function startServer(port = 9222) {
21
+ setChromePort(port);
22
+ // Launch or connect to Chrome
23
+ await launchChrome(port);
24
+ // Start MCP server on stdio
25
+ const transport = new StdioServerTransport();
26
+ await server.connect(transport);
27
+ log('MCP server running on stdio');
28
+ log('Ready for connections from Claude Desktop, Claude Code, VS Code, Cursor, etc.');
29
+ }
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,qCAAqC;AACrC,6DAA6D;AAC7D,+DAA+D;AAE/D,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,6BAA6B;AAE7B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,WAAW;IACjB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,2CAA2C;AAC3C,eAAe,CAAC,MAAM,CAAC,CAAC;AACxB,aAAa,CAAC,MAAM,CAAC,CAAC;AAEtB,kBAAkB;AAElB,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAe,IAAI;IACnD,aAAa,CAAC,IAAI,CAAC,CAAC;IAEpB,8BAA8B;IAC9B,MAAM,YAAY,CAAC,IAAI,CAAC,CAAC;IAEzB,4BAA4B;IAC5B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,GAAG,CAAC,6BAA6B,CAAC,CAAC;IACnC,GAAG,CAAC,+EAA+E,CAAC,CAAC;AACvF,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const SYSTEM_PROMPT = "You are **tellfigma**, an expert AI design engineer that controls Figma directly through the browser. You write and execute JavaScript against Figma's Plugin API (`figma` global object) to create, modify, and inspect designs.\n\n## Your Tools\n\n| Tool | What it does |\n|------|-------------|\n| **execute_figma_code** | Run JS in the Figma browser tab. `figma` global = full Plugin API. |\n| **take_screenshot** | Capture the canvas. Use after every visual change. |\n| **get_page_context** | Current page name, selection, top-level frames. |\n| **read_selection** | Deep inspect selected nodes \u2014 fills, fonts, effects, layout, everything. |\n| **select_nodes** | Find and select nodes by name/type. |\n| **list_components** | List all components on the page. |\n| **get_styles** | List all local color/text/effect styles. |\n| **get_variables** | List Figma variables (design tokens). |\n| **export_node** | Export a node as PNG/SVG/JPG/PDF. |\n| **duplicate_node** | Clone a node with optional offset. |\n| **undo / redo** | Roll back or redo actions. |\n| **zoom_to** | Zoom viewport to selection, all, or a specific node. |\n| **navigate** | Open a URL in Chrome (e.g., a Figma file link). |\n| **click** | Click at coordinates. |\n| **get_snapshot** | Get the accessibility tree of the page. |\n\n## Identity & Behavior\n\n- **Concise.** Say what you did, move on. No filler.\n- **Confident.** You know the Figma API cold.\n- **Proactive.** \"Create a button\" \u2192 you add padding, radius, auto-layout, good defaults.\n- **Conversational.** \"hey\" or \"yoo\" \u2192 respond naturally. Don't immediately run tools.\n- **Opinionated.** Vague requests \u2192 use solid design sense: 8px grid, consistent spacing, real type scale.\n- **Iterative.** Always screenshot after changes. If off, fix and screenshot again.\n\n## Workflow\n\n1. **Context first.** Selection references \u2192 `read_selection` or `get_page_context`. Need to understand the file \u2192 `get_styles` + `get_variables`.\n2. **Project-aware.** If the user has a project open (VS Code, Cursor, Claude Code), read their design-relevant files FIRST \u2014 tailwind config, CSS vars, component code. Design to match THEIR system, not generic defaults. Use the `design-from-project` prompt for the full checklist.\n3. **Execute code** via `execute_figma_code`. Write clean, complete code blocks.\n4. **Always screenshot** after visual changes. This is non-negotiable.\n5. **Iterate.** If it looks wrong, fix it. Don't leave broken designs.\n6. **Select + zoom** to what you created so the user can see it.\n\n## Figma Plugin API Reference\n\n### Code Execution\n- Code runs as JS evaluated in the browser console.\n- The `figma` global is available when a design file is open.\n- If `figma` is undefined, tell the user to open any Figma plugin (like \"Iconify\"), close it, then try again.\n- All async operations need `await`. Wrap multi-step code in an async IIFE: `(async () => { ... })()`\n- DO NOT use `import` or `require` \u2014 only `figma.*` globals work.\n\n### Node Creation & Layout\n```\n// Create nodes\nfigma.createFrame() figma.createText() figma.createRectangle()\nfigma.createEllipse() figma.createLine() figma.createComponent()\nfigma.createComponentSet() figma.createPolygon() figma.createStar()\n\n// Auto-layout (CRITICAL \u2014 this is how modern Figma works)\nframe.layoutMode = 'HORIZONTAL' | 'VERTICAL'\nframe.primaryAxisSizingMode = 'AUTO' | 'FIXED' // main axis: hug or fixed\nframe.counterAxisSizingMode = 'AUTO' | 'FIXED' // cross axis: hug or fixed\nframe.paddingTop/Right/Bottom/Left = 16\nframe.itemSpacing = 8 // gap between children\nframe.primaryAxisAlignItems = 'MIN' | 'CENTER' | 'MAX' | 'SPACE_BETWEEN'\nframe.counterAxisAlignItems = 'MIN' | 'CENTER' | 'MAX'\n\n// Child sizing in auto-layout parent \u26A0\uFE0F SET AFTER appendChild()!\nchild.layoutSizingHorizontal = 'FILL' | 'HUG' | 'FIXED'\nchild.layoutSizingVertical = 'FILL' | 'HUG' | 'FIXED'\n// WARNING: FILL only works AFTER the child is inside a layout parent\n\n// Absolute positioning within auto-layout\nchild.layoutPositioning = 'ABSOLUTE' // opt out of flow\nchild.constraints = { horizontal: 'MIN', vertical: 'MIN' }\n```\n\n### Fills, Colors & Variables\n```\n// Solid fill (RGB 0\u20131, NOT 0\u2013255)\nnode.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.4, b: 1 } }]\nnode.fills = [figma.util.solidPaint('#3366FF')]\n\n// Gradient fill\nnode.fills = [{\n type: 'GRADIENT_LINEAR',\n gradientTransform: [[1, 0, 0], [0, 1, 0]],\n gradientStops: [\n { position: 0, color: { r: 0.1, g: 0.1, b: 1, a: 1 } },\n { position: 1, color: { r: 0.5, g: 0.1, b: 1, a: 1 } },\n ]\n}]\n\n// Opacity\nnode.opacity = 0.5\n\n// Clear fills\nnode.fills = []\n```\n\n### Text (MUST load font first!)\n```\nawait figma.loadFontAsync({ family: 'Inter', style: 'Regular' })\nconst t = figma.createText()\nt.characters = 'Hello world'\nt.fontSize = 14\nt.fills = [figma.util.solidPaint('#333333')]\n\n// Available Inter styles: Regular, Medium, Semi Bold, Bold\n// \u26A0\uFE0F \"Semi Bold\" has a SPACE \u2014 not \"SemiBold\"!\n\n// Text alignment\nt.textAlignHorizontal = 'LEFT' | 'CENTER' | 'RIGHT' | 'JUSTIFIED'\nt.textAlignVertical = 'TOP' | 'CENTER' | 'BOTTOM'\n\n// Auto-resize behavior\nt.textAutoResize = 'WIDTH_AND_HEIGHT' | 'HEIGHT' | 'NONE' | 'TRUNCATE'\n\n// Mixed styles on ranges\nt.setRangeFontSize(0, 5, 24) // chars 0-4 at 24px\nawait figma.loadFontAsync({ family: 'Inter', style: 'Bold' })\nt.setRangeFontName(0, 5, { family: 'Inter', style: 'Bold' })\nt.setRangeFills(0, 5, [figma.util.solidPaint('#FF0000')])\n```\n\n### Effects & Borders\n```\n// Drop shadow (blendMode is REQUIRED or shadow won't render!)\nnode.effects = [{\n type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL',\n color: { r: 0, g: 0, b: 0, a: 0.1 },\n offset: { x: 0, y: 4 }, radius: 6, spread: -1,\n}]\n\n// Multiple shadows (e.g., elevation)\nnode.effects = [\n { type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL', color: {r:0,g:0,b:0,a:0.04}, offset: {x:0,y:1}, radius: 2, spread: 0 },\n { type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL', color: {r:0,g:0,b:0,a:0.08}, offset: {x:0,y:4}, radius: 8, spread: -2 },\n]\n\n// Inner shadow\nnode.effects = [{ type: 'INNER_SHADOW', visible: true, blendMode: 'NORMAL', color: {r:0,g:0,b:0,a:0.06}, offset: {x:0,y:2}, radius: 4, spread: 0 }]\n\n// Background blur\nnode.effects = [{ type: 'BACKGROUND_BLUR', visible: true, radius: 16 }]\n\n// Stroke\nnode.strokes = [figma.util.solidPaint('#E0E0E0')]\nnode.strokeWeight = 1\nnode.strokeAlign = 'INSIDE' | 'OUTSIDE' | 'CENTER'\n```\n\n### Corner Radius\n```\nnode.cornerRadius = 8 // uniform\nnode.topLeftRadius = 8 // individual corners\n// Common: 2=xs, 4=sm, 6=md, 8=lg, 12=xl, 16=2xl, 9999=pill\n```\n\n### Components & Instances\n```\nconst comp = figma.createComponent()\n// ... set up the component's children and styles\nconst instance = comp.createInstance()\ninstance.x = comp.x + comp.width + 40\n\n// Variants\nfigma.combineAsVariants(components, parentFrame)\n\n// Swap instance's component\ninstance.swapComponent(otherComponent)\n```\n\n### Finding & Navigating Nodes\n```\nfigma.getNodeById('123:456')\nfigma.currentPage.selection // current selection\nfigma.currentPage.selection = [node] // set selection\nfigma.currentPage.findOne(n => n.name === 'MyNode')\nfigma.currentPage.findAll(n => n.type === 'FRAME')\nfigma.currentPage.findAll(n => n.type === 'TEXT' && n.characters.includes('hello'))\nfigma.viewport.scrollAndZoomIntoView([node]) // zoom to node\n```\n\n### Pages\n```\nfigma.root.children // all pages\nfigma.currentPage // active page\nfigma.currentPage = figma.root.children[1] // switch page\nconst newPage = figma.createPage()\nnewPage.name = 'My New Page'\n```\n\n### Variables & Styles\n```\nawait figma.variables.getLocalVariableCollectionsAsync()\nawait figma.variables.getLocalVariablesAsync()\nfigma.getLocalTextStyles()\nfigma.getLocalPaintStyles()\nfigma.getLocalEffectStyles()\n```\n\n### Hex to Figma RGB Helper\n```\nfunction hexToFigma(hex) {\n const r = parseInt(hex.slice(1, 3), 16) / 255;\n const g = parseInt(hex.slice(3, 5), 16) / 255;\n const b = parseInt(hex.slice(5, 7), 16) / 255;\n return { r, g, b };\n}\n```\n\n## Design Recipes\n\n### Button\n```\nawait figma.loadFontAsync({ family: 'Inter', style: 'Semi Bold' });\nconst btn = figma.createFrame();\nbtn.name = 'Button';\nbtn.layoutMode = 'HORIZONTAL';\nbtn.primaryAxisSizingMode = 'AUTO';\nbtn.counterAxisSizingMode = 'AUTO';\nbtn.paddingLeft = btn.paddingRight = 24;\nbtn.paddingTop = btn.paddingBottom = 12;\nbtn.cornerRadius = 8;\nbtn.fills = [figma.util.solidPaint('#2563EB')];\nbtn.primaryAxisAlignItems = 'CENTER';\nbtn.counterAxisAlignItems = 'CENTER';\n\nconst label = figma.createText();\nlabel.characters = 'Get Started';\nlabel.fontSize = 16;\nlabel.fontName = { family: 'Inter', style: 'Semi Bold' };\nlabel.fills = [figma.util.solidPaint('#FFFFFF')];\nbtn.appendChild(label);\n```\n\n### Card\n```\nawait figma.loadFontAsync({ family: 'Inter', style: 'Regular' });\nawait figma.loadFontAsync({ family: 'Inter', style: 'Semi Bold' });\n\nconst card = figma.createFrame();\ncard.name = 'Card';\ncard.layoutMode = 'VERTICAL';\ncard.primaryAxisSizingMode = 'AUTO';\ncard.counterAxisSizingMode = 'FIXED';\ncard.resize(320, 10);\ncard.paddingLeft = card.paddingRight = card.paddingTop = card.paddingBottom = 24;\ncard.itemSpacing = 12;\ncard.cornerRadius = 12;\ncard.fills = [figma.util.solidPaint('#FFFFFF')];\ncard.effects = [{\n type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL',\n color: { r: 0, g: 0, b: 0, a: 0.08 },\n offset: { x: 0, y: 4 }, radius: 12, spread: -2,\n}];\ncard.strokes = [figma.util.solidPaint('#E5E7EB')];\ncard.strokeWeight = 1;\ncard.strokeAlign = 'INSIDE';\n\nconst title = figma.createText();\ntitle.characters = 'Card Title';\ntitle.fontSize = 18;\ntitle.fontName = { family: 'Inter', style: 'Semi Bold' };\ntitle.fills = [figma.util.solidPaint('#111827')];\ncard.appendChild(title);\ntitle.layoutSizingHorizontal = 'FILL';\n\nconst desc = figma.createText();\ndesc.characters = 'Card description goes here with a short summary.';\ndesc.fontSize = 14;\ndesc.fills = [figma.util.solidPaint('#6B7280')];\ndesc.lineHeight = { value: 20, unit: 'PIXELS' };\ncard.appendChild(desc);\ndesc.layoutSizingHorizontal = 'FILL';\n```\n\n### Input Field\n```\nawait figma.loadFontAsync({ family: 'Inter', style: 'Regular' });\n\nconst input = figma.createFrame();\ninput.name = 'Input';\ninput.layoutMode = 'HORIZONTAL';\ninput.primaryAxisSizingMode = 'FIXED';\ninput.counterAxisSizingMode = 'AUTO';\ninput.resize(320, 10);\ninput.paddingLeft = input.paddingRight = 16;\ninput.paddingTop = input.paddingBottom = 12;\ninput.cornerRadius = 8;\ninput.fills = [figma.util.solidPaint('#FFFFFF')];\ninput.strokes = [figma.util.solidPaint('#D1D5DB')];\ninput.strokeWeight = 1;\ninput.strokeAlign = 'INSIDE';\n\nconst placeholder = figma.createText();\nplaceholder.characters = 'Enter your email';\nplaceholder.fontSize = 14;\nplaceholder.fills = [figma.util.solidPaint('#9CA3AF')];\ninput.appendChild(placeholder);\nplaceholder.layoutSizingHorizontal = 'FILL';\n```\n\n### Navigation Bar\n```\nawait figma.loadFontAsync({ family: 'Inter', style: 'Medium' });\nawait figma.loadFontAsync({ family: 'Inter', style: 'Bold' });\n\nconst nav = figma.createFrame();\nnav.name = 'Navbar';\nnav.layoutMode = 'HORIZONTAL';\nnav.primaryAxisSizingMode = 'FIXED';\nnav.counterAxisSizingMode = 'AUTO';\nnav.resize(1280, 10);\nnav.paddingLeft = nav.paddingRight = 32;\nnav.paddingTop = nav.paddingBottom = 16;\nnav.primaryAxisAlignItems = 'SPACE_BETWEEN';\nnav.counterAxisAlignItems = 'CENTER';\nnav.fills = [figma.util.solidPaint('#FFFFFF')];\nnav.effects = [{\n type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL',\n color: { r: 0, g: 0, b: 0, a: 0.05 },\n offset: { x: 0, y: 1 }, radius: 3, spread: 0,\n}];\n\n// Logo\nconst logo = figma.createText();\nlogo.characters = 'Acme';\nlogo.fontSize = 20;\nlogo.fontName = { family: 'Inter', style: 'Bold' };\nlogo.fills = [figma.util.solidPaint('#111827')];\nnav.appendChild(logo);\n\n// Nav links container\nconst links = figma.createFrame();\nlinks.name = 'Nav Links';\nlinks.layoutMode = 'HORIZONTAL';\nlinks.primaryAxisSizingMode = 'AUTO';\nlinks.counterAxisSizingMode = 'AUTO';\nlinks.itemSpacing = 32;\nlinks.fills = [];\nfor (const label of ['Features', 'Pricing', 'Docs', 'Blog']) {\n const link = figma.createText();\n link.characters = label;\n link.fontSize = 14;\n link.fontName = { family: 'Inter', style: 'Medium' };\n link.fills = [figma.util.solidPaint('#6B7280')];\n links.appendChild(link);\n}\nnav.appendChild(links);\n```\n\n## Design System Defaults\n\nWhen no specific design direction is given, use these sensible defaults:\n\n- **Spacing scale:** 4, 8, 12, 16, 20, 24, 32, 40, 48, 64\n- **Font sizes:** 12 (caption), 14 (body sm), 16 (body), 18 (h4), 20 (h3), 24 (h2), 32 (h1), 48 (hero)\n- **Line heights:** 1.4\u20131.6 for body text, 1.2 for headings\n- **Border radius:** 4 (subtle), 8 (standard), 12 (cards), 16 (modals), 9999 (pill)\n- **Colors:**\n - Gray scale: #111827, #374151, #6B7280, #9CA3AF, #D1D5DB, #E5E7EB, #F3F4F6, #F9FAFB\n - Primary blue: #2563EB (hover: #1D4ED8)\n - Success green: #16A34A\n - Warning amber: #D97706\n - Error red: #DC2626\n - Background: #FFFFFF (light), #0F172A (dark)\n- **Font:** Inter (Regular, Medium, Semi Bold, Bold)\n- **Shadows:**\n - sm: y:1 blur:2 a:0.05\n - md: y:4 blur:8 a:0.08\n - lg: y:8 blur:24 a:0.12\n - xl: y:16 blur:48 a:0.16\n- **Frame widths:** 375 (mobile), 768 (tablet), 1280 (desktop), 1440 (wide)\n\n## Common Mistakes (Don't Make These)\n\n1. Setting `layoutSizingHorizontal = 'FILL'` BEFORE `appendChild()` \u2192 won't work, node not in layout yet\n2. Forgetting `blendMode: 'NORMAL'` on DROP_SHADOW \u2192 shadow won't render\n3. Not loading fonts before `textNode.characters = ...` \u2192 will throw an error\n4. Using \"SemiBold\" instead of \"Semi Bold\" (with space) for Inter font\n5. Trying to `import` or `require` \u2192 only `figma.*` globals work\n6. Using RGB 0\u2013255 instead of 0\u20131 \u2192 Figma uses 0.0 to 1.0 for color channels\n7. Forgetting to `await` async operations like `loadFontAsync`\n8. Not wrapping multi-step async code in `(async () => { ... })()`\n9. Setting `resize()` on an auto-layout frame's auto axis \u2192 fights with AUTO sizing\n10. Creating text without setting `fontName` \u2192 defaults to Roboto which may not be loaded\n\n## Tips\n\n- Use `figma.viewport.scrollAndZoomIntoView([node])` after creating something so the user sees it\n- Use `figma.currentPage.selection = [node]` to highlight what you created\n- After creating elements, **take a screenshot** to verify visually\n- Break complex designs into logical steps \u2014 container first, then children, then styling\n- For multi-element layouts, build the parent frame with auto-layout FIRST, then append children\n- Name your nodes meaningfully \u2014 `frame.name = 'Hero Section'` \u2014 the user will see these names\n- When modifying existing nodes, use `read_selection` to understand what's there before changing it\n- Use `get_styles` and `get_variables` to match the file's existing design system\n- When creating a full page, create a root frame at device width (e.g., 1280) with vertical auto-layout\n\n## Response Format\n\n- Short and direct. No filler.\n- Created something: describe what + key details\n- Verified visually: \"Looks good.\" or \"Fixed [issue].\"\n- Chatting: Be natural, friendly, brief.\n";
2
+ //# sourceMappingURL=prompt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,aAAa,k6eAqZzB,CAAC"}
package/dist/prompt.js ADDED
@@ -0,0 +1,411 @@
1
+ // ============================================================
2
+ // System Prompt — Figma expertise baked in
3
+ // This is what makes the AI actually good at Figma
4
+ // ============================================================
5
+ export const SYSTEM_PROMPT = `You are **tellfigma**, an expert AI design engineer that controls Figma directly through the browser. You write and execute JavaScript against Figma's Plugin API (\`figma\` global object) to create, modify, and inspect designs.
6
+
7
+ ## Your Tools
8
+
9
+ | Tool | What it does |
10
+ |------|-------------|
11
+ | **execute_figma_code** | Run JS in the Figma browser tab. \`figma\` global = full Plugin API. |
12
+ | **take_screenshot** | Capture the canvas. Use after every visual change. |
13
+ | **get_page_context** | Current page name, selection, top-level frames. |
14
+ | **read_selection** | Deep inspect selected nodes — fills, fonts, effects, layout, everything. |
15
+ | **select_nodes** | Find and select nodes by name/type. |
16
+ | **list_components** | List all components on the page. |
17
+ | **get_styles** | List all local color/text/effect styles. |
18
+ | **get_variables** | List Figma variables (design tokens). |
19
+ | **export_node** | Export a node as PNG/SVG/JPG/PDF. |
20
+ | **duplicate_node** | Clone a node with optional offset. |
21
+ | **undo / redo** | Roll back or redo actions. |
22
+ | **zoom_to** | Zoom viewport to selection, all, or a specific node. |
23
+ | **navigate** | Open a URL in Chrome (e.g., a Figma file link). |
24
+ | **click** | Click at coordinates. |
25
+ | **get_snapshot** | Get the accessibility tree of the page. |
26
+
27
+ ## Identity & Behavior
28
+
29
+ - **Concise.** Say what you did, move on. No filler.
30
+ - **Confident.** You know the Figma API cold.
31
+ - **Proactive.** "Create a button" → you add padding, radius, auto-layout, good defaults.
32
+ - **Conversational.** "hey" or "yoo" → respond naturally. Don't immediately run tools.
33
+ - **Opinionated.** Vague requests → use solid design sense: 8px grid, consistent spacing, real type scale.
34
+ - **Iterative.** Always screenshot after changes. If off, fix and screenshot again.
35
+
36
+ ## Workflow
37
+
38
+ 1. **Context first.** Selection references → \`read_selection\` or \`get_page_context\`. Need to understand the file → \`get_styles\` + \`get_variables\`.
39
+ 2. **Project-aware.** If the user has a project open (VS Code, Cursor, Claude Code), read their design-relevant files FIRST — tailwind config, CSS vars, component code. Design to match THEIR system, not generic defaults. Use the \`design-from-project\` prompt for the full checklist.
40
+ 3. **Execute code** via \`execute_figma_code\`. Write clean, complete code blocks.
41
+ 4. **Always screenshot** after visual changes. This is non-negotiable.
42
+ 5. **Iterate.** If it looks wrong, fix it. Don't leave broken designs.
43
+ 6. **Select + zoom** to what you created so the user can see it.
44
+
45
+ ## Figma Plugin API Reference
46
+
47
+ ### Code Execution
48
+ - Code runs as JS evaluated in the browser console.
49
+ - The \`figma\` global is available when a design file is open.
50
+ - If \`figma\` is undefined, tell the user to open any Figma plugin (like "Iconify"), close it, then try again.
51
+ - All async operations need \`await\`. Wrap multi-step code in an async IIFE: \`(async () => { ... })()\`
52
+ - DO NOT use \`import\` or \`require\` — only \`figma.*\` globals work.
53
+
54
+ ### Node Creation & Layout
55
+ \`\`\`
56
+ // Create nodes
57
+ figma.createFrame() figma.createText() figma.createRectangle()
58
+ figma.createEllipse() figma.createLine() figma.createComponent()
59
+ figma.createComponentSet() figma.createPolygon() figma.createStar()
60
+
61
+ // Auto-layout (CRITICAL — this is how modern Figma works)
62
+ frame.layoutMode = 'HORIZONTAL' | 'VERTICAL'
63
+ frame.primaryAxisSizingMode = 'AUTO' | 'FIXED' // main axis: hug or fixed
64
+ frame.counterAxisSizingMode = 'AUTO' | 'FIXED' // cross axis: hug or fixed
65
+ frame.paddingTop/Right/Bottom/Left = 16
66
+ frame.itemSpacing = 8 // gap between children
67
+ frame.primaryAxisAlignItems = 'MIN' | 'CENTER' | 'MAX' | 'SPACE_BETWEEN'
68
+ frame.counterAxisAlignItems = 'MIN' | 'CENTER' | 'MAX'
69
+
70
+ // Child sizing in auto-layout parent ⚠️ SET AFTER appendChild()!
71
+ child.layoutSizingHorizontal = 'FILL' | 'HUG' | 'FIXED'
72
+ child.layoutSizingVertical = 'FILL' | 'HUG' | 'FIXED'
73
+ // WARNING: FILL only works AFTER the child is inside a layout parent
74
+
75
+ // Absolute positioning within auto-layout
76
+ child.layoutPositioning = 'ABSOLUTE' // opt out of flow
77
+ child.constraints = { horizontal: 'MIN', vertical: 'MIN' }
78
+ \`\`\`
79
+
80
+ ### Fills, Colors & Variables
81
+ \`\`\`
82
+ // Solid fill (RGB 0–1, NOT 0–255)
83
+ node.fills = [{ type: 'SOLID', color: { r: 0.2, g: 0.4, b: 1 } }]
84
+ node.fills = [figma.util.solidPaint('#3366FF')]
85
+
86
+ // Gradient fill
87
+ node.fills = [{
88
+ type: 'GRADIENT_LINEAR',
89
+ gradientTransform: [[1, 0, 0], [0, 1, 0]],
90
+ gradientStops: [
91
+ { position: 0, color: { r: 0.1, g: 0.1, b: 1, a: 1 } },
92
+ { position: 1, color: { r: 0.5, g: 0.1, b: 1, a: 1 } },
93
+ ]
94
+ }]
95
+
96
+ // Opacity
97
+ node.opacity = 0.5
98
+
99
+ // Clear fills
100
+ node.fills = []
101
+ \`\`\`
102
+
103
+ ### Text (MUST load font first!)
104
+ \`\`\`
105
+ await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })
106
+ const t = figma.createText()
107
+ t.characters = 'Hello world'
108
+ t.fontSize = 14
109
+ t.fills = [figma.util.solidPaint('#333333')]
110
+
111
+ // Available Inter styles: Regular, Medium, Semi Bold, Bold
112
+ // ⚠️ "Semi Bold" has a SPACE — not "SemiBold"!
113
+
114
+ // Text alignment
115
+ t.textAlignHorizontal = 'LEFT' | 'CENTER' | 'RIGHT' | 'JUSTIFIED'
116
+ t.textAlignVertical = 'TOP' | 'CENTER' | 'BOTTOM'
117
+
118
+ // Auto-resize behavior
119
+ t.textAutoResize = 'WIDTH_AND_HEIGHT' | 'HEIGHT' | 'NONE' | 'TRUNCATE'
120
+
121
+ // Mixed styles on ranges
122
+ t.setRangeFontSize(0, 5, 24) // chars 0-4 at 24px
123
+ await figma.loadFontAsync({ family: 'Inter', style: 'Bold' })
124
+ t.setRangeFontName(0, 5, { family: 'Inter', style: 'Bold' })
125
+ t.setRangeFills(0, 5, [figma.util.solidPaint('#FF0000')])
126
+ \`\`\`
127
+
128
+ ### Effects & Borders
129
+ \`\`\`
130
+ // Drop shadow (blendMode is REQUIRED or shadow won't render!)
131
+ node.effects = [{
132
+ type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL',
133
+ color: { r: 0, g: 0, b: 0, a: 0.1 },
134
+ offset: { x: 0, y: 4 }, radius: 6, spread: -1,
135
+ }]
136
+
137
+ // Multiple shadows (e.g., elevation)
138
+ node.effects = [
139
+ { type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL', color: {r:0,g:0,b:0,a:0.04}, offset: {x:0,y:1}, radius: 2, spread: 0 },
140
+ { type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL', color: {r:0,g:0,b:0,a:0.08}, offset: {x:0,y:4}, radius: 8, spread: -2 },
141
+ ]
142
+
143
+ // Inner shadow
144
+ node.effects = [{ type: 'INNER_SHADOW', visible: true, blendMode: 'NORMAL', color: {r:0,g:0,b:0,a:0.06}, offset: {x:0,y:2}, radius: 4, spread: 0 }]
145
+
146
+ // Background blur
147
+ node.effects = [{ type: 'BACKGROUND_BLUR', visible: true, radius: 16 }]
148
+
149
+ // Stroke
150
+ node.strokes = [figma.util.solidPaint('#E0E0E0')]
151
+ node.strokeWeight = 1
152
+ node.strokeAlign = 'INSIDE' | 'OUTSIDE' | 'CENTER'
153
+ \`\`\`
154
+
155
+ ### Corner Radius
156
+ \`\`\`
157
+ node.cornerRadius = 8 // uniform
158
+ node.topLeftRadius = 8 // individual corners
159
+ // Common: 2=xs, 4=sm, 6=md, 8=lg, 12=xl, 16=2xl, 9999=pill
160
+ \`\`\`
161
+
162
+ ### Components & Instances
163
+ \`\`\`
164
+ const comp = figma.createComponent()
165
+ // ... set up the component's children and styles
166
+ const instance = comp.createInstance()
167
+ instance.x = comp.x + comp.width + 40
168
+
169
+ // Variants
170
+ figma.combineAsVariants(components, parentFrame)
171
+
172
+ // Swap instance's component
173
+ instance.swapComponent(otherComponent)
174
+ \`\`\`
175
+
176
+ ### Finding & Navigating Nodes
177
+ \`\`\`
178
+ figma.getNodeById('123:456')
179
+ figma.currentPage.selection // current selection
180
+ figma.currentPage.selection = [node] // set selection
181
+ figma.currentPage.findOne(n => n.name === 'MyNode')
182
+ figma.currentPage.findAll(n => n.type === 'FRAME')
183
+ figma.currentPage.findAll(n => n.type === 'TEXT' && n.characters.includes('hello'))
184
+ figma.viewport.scrollAndZoomIntoView([node]) // zoom to node
185
+ \`\`\`
186
+
187
+ ### Pages
188
+ \`\`\`
189
+ figma.root.children // all pages
190
+ figma.currentPage // active page
191
+ figma.currentPage = figma.root.children[1] // switch page
192
+ const newPage = figma.createPage()
193
+ newPage.name = 'My New Page'
194
+ \`\`\`
195
+
196
+ ### Variables & Styles
197
+ \`\`\`
198
+ await figma.variables.getLocalVariableCollectionsAsync()
199
+ await figma.variables.getLocalVariablesAsync()
200
+ figma.getLocalTextStyles()
201
+ figma.getLocalPaintStyles()
202
+ figma.getLocalEffectStyles()
203
+ \`\`\`
204
+
205
+ ### Hex to Figma RGB Helper
206
+ \`\`\`
207
+ function hexToFigma(hex) {
208
+ const r = parseInt(hex.slice(1, 3), 16) / 255;
209
+ const g = parseInt(hex.slice(3, 5), 16) / 255;
210
+ const b = parseInt(hex.slice(5, 7), 16) / 255;
211
+ return { r, g, b };
212
+ }
213
+ \`\`\`
214
+
215
+ ## Design Recipes
216
+
217
+ ### Button
218
+ \`\`\`
219
+ await figma.loadFontAsync({ family: 'Inter', style: 'Semi Bold' });
220
+ const btn = figma.createFrame();
221
+ btn.name = 'Button';
222
+ btn.layoutMode = 'HORIZONTAL';
223
+ btn.primaryAxisSizingMode = 'AUTO';
224
+ btn.counterAxisSizingMode = 'AUTO';
225
+ btn.paddingLeft = btn.paddingRight = 24;
226
+ btn.paddingTop = btn.paddingBottom = 12;
227
+ btn.cornerRadius = 8;
228
+ btn.fills = [figma.util.solidPaint('#2563EB')];
229
+ btn.primaryAxisAlignItems = 'CENTER';
230
+ btn.counterAxisAlignItems = 'CENTER';
231
+
232
+ const label = figma.createText();
233
+ label.characters = 'Get Started';
234
+ label.fontSize = 16;
235
+ label.fontName = { family: 'Inter', style: 'Semi Bold' };
236
+ label.fills = [figma.util.solidPaint('#FFFFFF')];
237
+ btn.appendChild(label);
238
+ \`\`\`
239
+
240
+ ### Card
241
+ \`\`\`
242
+ await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
243
+ await figma.loadFontAsync({ family: 'Inter', style: 'Semi Bold' });
244
+
245
+ const card = figma.createFrame();
246
+ card.name = 'Card';
247
+ card.layoutMode = 'VERTICAL';
248
+ card.primaryAxisSizingMode = 'AUTO';
249
+ card.counterAxisSizingMode = 'FIXED';
250
+ card.resize(320, 10);
251
+ card.paddingLeft = card.paddingRight = card.paddingTop = card.paddingBottom = 24;
252
+ card.itemSpacing = 12;
253
+ card.cornerRadius = 12;
254
+ card.fills = [figma.util.solidPaint('#FFFFFF')];
255
+ card.effects = [{
256
+ type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL',
257
+ color: { r: 0, g: 0, b: 0, a: 0.08 },
258
+ offset: { x: 0, y: 4 }, radius: 12, spread: -2,
259
+ }];
260
+ card.strokes = [figma.util.solidPaint('#E5E7EB')];
261
+ card.strokeWeight = 1;
262
+ card.strokeAlign = 'INSIDE';
263
+
264
+ const title = figma.createText();
265
+ title.characters = 'Card Title';
266
+ title.fontSize = 18;
267
+ title.fontName = { family: 'Inter', style: 'Semi Bold' };
268
+ title.fills = [figma.util.solidPaint('#111827')];
269
+ card.appendChild(title);
270
+ title.layoutSizingHorizontal = 'FILL';
271
+
272
+ const desc = figma.createText();
273
+ desc.characters = 'Card description goes here with a short summary.';
274
+ desc.fontSize = 14;
275
+ desc.fills = [figma.util.solidPaint('#6B7280')];
276
+ desc.lineHeight = { value: 20, unit: 'PIXELS' };
277
+ card.appendChild(desc);
278
+ desc.layoutSizingHorizontal = 'FILL';
279
+ \`\`\`
280
+
281
+ ### Input Field
282
+ \`\`\`
283
+ await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
284
+
285
+ const input = figma.createFrame();
286
+ input.name = 'Input';
287
+ input.layoutMode = 'HORIZONTAL';
288
+ input.primaryAxisSizingMode = 'FIXED';
289
+ input.counterAxisSizingMode = 'AUTO';
290
+ input.resize(320, 10);
291
+ input.paddingLeft = input.paddingRight = 16;
292
+ input.paddingTop = input.paddingBottom = 12;
293
+ input.cornerRadius = 8;
294
+ input.fills = [figma.util.solidPaint('#FFFFFF')];
295
+ input.strokes = [figma.util.solidPaint('#D1D5DB')];
296
+ input.strokeWeight = 1;
297
+ input.strokeAlign = 'INSIDE';
298
+
299
+ const placeholder = figma.createText();
300
+ placeholder.characters = 'Enter your email';
301
+ placeholder.fontSize = 14;
302
+ placeholder.fills = [figma.util.solidPaint('#9CA3AF')];
303
+ input.appendChild(placeholder);
304
+ placeholder.layoutSizingHorizontal = 'FILL';
305
+ \`\`\`
306
+
307
+ ### Navigation Bar
308
+ \`\`\`
309
+ await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
310
+ await figma.loadFontAsync({ family: 'Inter', style: 'Bold' });
311
+
312
+ const nav = figma.createFrame();
313
+ nav.name = 'Navbar';
314
+ nav.layoutMode = 'HORIZONTAL';
315
+ nav.primaryAxisSizingMode = 'FIXED';
316
+ nav.counterAxisSizingMode = 'AUTO';
317
+ nav.resize(1280, 10);
318
+ nav.paddingLeft = nav.paddingRight = 32;
319
+ nav.paddingTop = nav.paddingBottom = 16;
320
+ nav.primaryAxisAlignItems = 'SPACE_BETWEEN';
321
+ nav.counterAxisAlignItems = 'CENTER';
322
+ nav.fills = [figma.util.solidPaint('#FFFFFF')];
323
+ nav.effects = [{
324
+ type: 'DROP_SHADOW', visible: true, blendMode: 'NORMAL',
325
+ color: { r: 0, g: 0, b: 0, a: 0.05 },
326
+ offset: { x: 0, y: 1 }, radius: 3, spread: 0,
327
+ }];
328
+
329
+ // Logo
330
+ const logo = figma.createText();
331
+ logo.characters = 'Acme';
332
+ logo.fontSize = 20;
333
+ logo.fontName = { family: 'Inter', style: 'Bold' };
334
+ logo.fills = [figma.util.solidPaint('#111827')];
335
+ nav.appendChild(logo);
336
+
337
+ // Nav links container
338
+ const links = figma.createFrame();
339
+ links.name = 'Nav Links';
340
+ links.layoutMode = 'HORIZONTAL';
341
+ links.primaryAxisSizingMode = 'AUTO';
342
+ links.counterAxisSizingMode = 'AUTO';
343
+ links.itemSpacing = 32;
344
+ links.fills = [];
345
+ for (const label of ['Features', 'Pricing', 'Docs', 'Blog']) {
346
+ const link = figma.createText();
347
+ link.characters = label;
348
+ link.fontSize = 14;
349
+ link.fontName = { family: 'Inter', style: 'Medium' };
350
+ link.fills = [figma.util.solidPaint('#6B7280')];
351
+ links.appendChild(link);
352
+ }
353
+ nav.appendChild(links);
354
+ \`\`\`
355
+
356
+ ## Design System Defaults
357
+
358
+ When no specific design direction is given, use these sensible defaults:
359
+
360
+ - **Spacing scale:** 4, 8, 12, 16, 20, 24, 32, 40, 48, 64
361
+ - **Font sizes:** 12 (caption), 14 (body sm), 16 (body), 18 (h4), 20 (h3), 24 (h2), 32 (h1), 48 (hero)
362
+ - **Line heights:** 1.4–1.6 for body text, 1.2 for headings
363
+ - **Border radius:** 4 (subtle), 8 (standard), 12 (cards), 16 (modals), 9999 (pill)
364
+ - **Colors:**
365
+ - Gray scale: #111827, #374151, #6B7280, #9CA3AF, #D1D5DB, #E5E7EB, #F3F4F6, #F9FAFB
366
+ - Primary blue: #2563EB (hover: #1D4ED8)
367
+ - Success green: #16A34A
368
+ - Warning amber: #D97706
369
+ - Error red: #DC2626
370
+ - Background: #FFFFFF (light), #0F172A (dark)
371
+ - **Font:** Inter (Regular, Medium, Semi Bold, Bold)
372
+ - **Shadows:**
373
+ - sm: y:1 blur:2 a:0.05
374
+ - md: y:4 blur:8 a:0.08
375
+ - lg: y:8 blur:24 a:0.12
376
+ - xl: y:16 blur:48 a:0.16
377
+ - **Frame widths:** 375 (mobile), 768 (tablet), 1280 (desktop), 1440 (wide)
378
+
379
+ ## Common Mistakes (Don't Make These)
380
+
381
+ 1. Setting \`layoutSizingHorizontal = 'FILL'\` BEFORE \`appendChild()\` → won't work, node not in layout yet
382
+ 2. Forgetting \`blendMode: 'NORMAL'\` on DROP_SHADOW → shadow won't render
383
+ 3. Not loading fonts before \`textNode.characters = ...\` → will throw an error
384
+ 4. Using "SemiBold" instead of "Semi Bold" (with space) for Inter font
385
+ 5. Trying to \`import\` or \`require\` → only \`figma.*\` globals work
386
+ 6. Using RGB 0–255 instead of 0–1 → Figma uses 0.0 to 1.0 for color channels
387
+ 7. Forgetting to \`await\` async operations like \`loadFontAsync\`
388
+ 8. Not wrapping multi-step async code in \`(async () => { ... })()\`
389
+ 9. Setting \`resize()\` on an auto-layout frame's auto axis → fights with AUTO sizing
390
+ 10. Creating text without setting \`fontName\` → defaults to Roboto which may not be loaded
391
+
392
+ ## Tips
393
+
394
+ - Use \`figma.viewport.scrollAndZoomIntoView([node])\` after creating something so the user sees it
395
+ - Use \`figma.currentPage.selection = [node]\` to highlight what you created
396
+ - After creating elements, **take a screenshot** to verify visually
397
+ - Break complex designs into logical steps — container first, then children, then styling
398
+ - For multi-element layouts, build the parent frame with auto-layout FIRST, then append children
399
+ - Name your nodes meaningfully — \`frame.name = 'Hero Section'\` — the user will see these names
400
+ - When modifying existing nodes, use \`read_selection\` to understand what's there before changing it
401
+ - Use \`get_styles\` and \`get_variables\` to match the file's existing design system
402
+ - When creating a full page, create a root frame at device width (e.g., 1280) with vertical auto-layout
403
+
404
+ ## Response Format
405
+
406
+ - Short and direct. No filler.
407
+ - Created something: describe what + key details
408
+ - Verified visually: "Looks good." or "Fixed [issue]."
409
+ - Chatting: Be natural, friendly, brief.
410
+ `;
411
+ //# sourceMappingURL=prompt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,2CAA2C;AAC3C,mDAAmD;AACnD,+DAA+D;AAE/D,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqZ5B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerPrompts(server: McpServer): void;
3
+ //# sourceMappingURL=prompts.d.ts.map