winter-super-cli 2026.6.28 → 2026.6.29

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/design.md CHANGED
@@ -1,66 +1,3 @@
1
- # Design Guidance
1
+ # Nova
2
2
 
3
- File này không phải danh bạ trang trí. Đây là checklist bắt buộc khi Winter xử lý UI, brand, landing page, dashboard, hoặc component.
4
-
5
- ## Resource Status
6
- - Source: `resources\local\awesome-design-md\README.md`
7
- - Corpus: `resources\local\awesome-design-md\design-md`
8
- - Manifest: 142 files, 1.8 MB
9
- - Status: Corpus local sẵn sàng tại `resources\local\awesome-design-md\design-md`.
10
-
11
- ## How Winter Must Use This
12
- 1. Khi task liên quan UI/design/brand, tìm brand hoặc style gần nhất trong `awesome-design-md/design-md` trước khi viết code.
13
- 2. Nếu user nêu brand cụ thể, ưu tiên đúng brand đó; nếu không có, chọn brand gần nhất theo domain và nói rõ giả định.
14
- 3. Không tự bịa palette, spacing, typography, tone khi local design guide có dữ liệu phù hợp.
15
- 4. Với app/work tool, ưu tiên layout rõ, dense, dễ scan; không dùng hero marketing nếu user đang cần tool thật.
16
- 5. Sau khi sửa UI, kiểm tra responsive và text overflow nếu có thể chạy app.
17
-
18
- ## Available Brand Samples (40+)
19
- - airbnb
20
- - airtable
21
- - apple
22
- - binance
23
- - bmw
24
- - bmw-m
25
- - bugatti
26
- - cal
27
- - claude
28
- - clay
29
- - clickhouse
30
- - cohere
31
- - coinbase
32
- - composio
33
- - cursor
34
- - elevenlabs
35
- - expo
36
- - ferrari
37
- - figma
38
- - framer
39
- - hashicorp
40
- - ibm
41
- - intercom
42
- - kraken
43
- - lamborghini
44
- - linear.app
45
- - lovable
46
- - mastercard
47
- - meta
48
- - minimax
49
- - mintlify
50
- - miro
51
- - mistral.ai
52
- - mongodb
53
- - nike
54
- - notion
55
- - nvidia
56
- - ollama
57
- - opencode.ai
58
- - pinterest
59
-
60
- ## Useful Commands
61
- - `/designs` để liệt kê hoặc tìm design systems.
62
- - `/read resources/local/awesome-design-md/README.md` để đọc overview.
63
- - `/grep <brand> resources/local/awesome-design-md/design-md` để tìm brand/style guide.
64
-
65
- ---
66
- *File này được tự động tạo bởi Winter CLI.*
3
+ Design rules.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "winter-super-cli",
3
- "version": "2026.6.28",
3
+ "version": "2026.6.29",
4
4
  "description": "❄️ AI-Powered Development CLI with Interactive REPL",
5
5
  "type": "module",
6
6
  "main": "bin/winter.js",
@@ -3,11 +3,11 @@ import { pathToFileURL } from 'url';
3
3
  import { promises as fs } from 'fs';
4
4
 
5
5
  const DEFAULT_TOOL_SETS = {
6
- general: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'WebSearch', 'Parallel', 'Agent', 'DelegateTask'],
6
+ general: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'DesignToCode', 'WebFetch', 'WebSearch', 'Parallel', 'Agent', 'DelegateTask'],
7
7
  plan: ['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Parallel'],
8
8
  review: ['Read', 'Grep', 'Glob', 'Bash', 'WebFetch'],
9
9
  debug: ['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'Parallel'],
10
- design: ['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch'],
10
+ design: ['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'DesignToCode', 'WebFetch'],
11
11
  research: ['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Parallel'],
12
12
  swe: ['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'Parallel'],
13
13
  };
@@ -64,6 +64,7 @@ export class AgentRuntime {
64
64
  model: executionProfile.model,
65
65
  enableTools: true,
66
66
  toolPromptOnly: forceTextToolFallback,
67
+ toolChoiceRequired: requireToolEvidence && !usedTools && !forceTextToolFallback,
67
68
  requireToolEvidence: requireToolEvidence && !usedTools,
68
69
  usedMutatingTools: usedMutatingTools,
69
70
  deferFinalContent: this.shouldVerifyBeforeFinal(messages, usedMutatingTools, autoVerificationPassed),
@@ -124,6 +124,7 @@ function buildOpenAIRequest(provider, messages, options) {
124
124
  if (options.reasoning?.reasoning_effort) body.reasoning_effort = options.reasoning.reasoning_effort;
125
125
  if (options.reasoning?.thinking) body.thinking = options.reasoning.thinking;
126
126
  if (options.tools?.length) body.tools = options.tools;
127
+ if (options.toolChoiceRequired && options.tools?.length) body.tool_choice = 'required';
127
128
 
128
129
  return {
129
130
  url: `${trimTrailingSlash(provider.baseURL)}/chat/completions`,
@@ -147,6 +148,7 @@ function buildAnthropicRequest(provider, messages, options) {
147
148
  if (options.stream) body.stream = true;
148
149
  if (options.reasoning?.thinking) body.thinking = options.reasoning.thinking;
149
150
  if (options.tools?.length) body.tools = options.tools.map(toAnthropicTool).filter(Boolean);
151
+ if (options.toolChoiceRequired && body.tools?.length) body.tool_choice = { type: 'any' };
150
152
 
151
153
  const headers = {
152
154
  'Content-Type': 'application/json',
package/src/cli/config.js CHANGED
@@ -7,7 +7,7 @@ import { promises as fs } from 'fs';
7
7
  import path from 'path';
8
8
  import { homedir } from 'os';
9
9
  import { loadEnvFile, stripInlineSecrets } from './secret-env.js';
10
- import { CHROME_DEVTOOLS_MCP_NAME, createChromeDevtoolsMcpServer, ensureMcpConfigShape } from '../mcp/presets.js';
10
+ import { CHROME_DEVTOOLS_MCP_NAME, FIGMA_MCP_NAME, createChromeDevtoolsMcpServer, createFigmaMcpServer, ensureMcpConfigShape } from '../mcp/presets.js';
11
11
 
12
12
  export class ConfigLoader {
13
13
  constructor() {
@@ -36,11 +36,14 @@ export class ConfigLoader {
36
36
  if (!next.mcp.servers.some(server => server?.name === CHROME_DEVTOOLS_MCP_NAME)) {
37
37
  next.mcp.servers.push(createChromeDevtoolsMcpServer(['--isolated']));
38
38
  }
39
+ if (!next.mcp.servers.some(server => server?.name === FIGMA_MCP_NAME)) {
40
+ next.mcp.servers.push(createFigmaMcpServer());
41
+ }
39
42
  next.permissions.allowlist.tools = [
40
43
  ...new Set([...(next.permissions.allowlist.tools || []), 'MCP']),
41
44
  ];
42
45
  next.permissions.allowlist.mcpServers = [
43
- ...new Set([...(next.permissions.allowlist.mcpServers || []), CHROME_DEVTOOLS_MCP_NAME]),
46
+ ...new Set([...(next.permissions.allowlist.mcpServers || []), CHROME_DEVTOOLS_MCP_NAME, FIGMA_MCP_NAME]),
44
47
  ];
45
48
  return next;
46
49
  }
@@ -79,11 +82,11 @@ export class ConfigLoader {
79
82
  allowlist: {
80
83
  tools: ['Read', 'Glob', 'Grep', 'LSP', 'TaskCreate', 'TaskUpdate', 'TaskList', 'WebFetch', 'WebSearch', 'Parallel', 'MCP'],
81
84
  commands: [],
82
- mcpServers: [CHROME_DEVTOOLS_MCP_NAME],
85
+ mcpServers: [CHROME_DEVTOOLS_MCP_NAME, FIGMA_MCP_NAME],
83
86
  },
84
87
  },
85
88
  mcp: {
86
- servers: [createChromeDevtoolsMcpServer(['--isolated'])],
89
+ servers: [createChromeDevtoolsMcpServer(['--isolated']), createFigmaMcpServer()],
87
90
  },
88
91
  routing: {
89
92
  strategy: 'heuristic',
@@ -82,58 +82,69 @@ export class WinterInputController {
82
82
  readline.emitKeypressEvents(process.stdin, repl.rl);
83
83
 
84
84
  process.stdin.on('keypress', (str, key = {}) => {
85
- if (key.ctrl && key.name === 'v') {
86
- void this.handleDirectClipboardPaste();
87
- return;
88
- }
85
+ this.handleGlobalKeypress(str, key);
86
+ });
87
+ }
89
88
 
90
- if (key.name === 'return' && (key.shift || key.meta)) {
91
- repl.rl.write('\\\n');
92
- return;
93
- }
89
+ handleGlobalKeypress(str, key = {}) {
90
+ const repl = this.repl;
94
91
 
95
- if (key.ctrl || key.meta) return;
92
+ if (repl.interactiveChecklistOpen) {
93
+ return true;
94
+ }
96
95
 
97
- if (typeof str === 'string' && str.length > 1) {
98
- return;
99
- }
96
+ if (key.ctrl && key.name === 'v') {
97
+ void this.handleDirectClipboardPaste();
98
+ return true;
99
+ }
100
100
 
101
- if (repl.slashMenu.open && this.handleSlashMenuKey(key)) {
102
- return;
103
- }
101
+ if (key.name === 'return' && (key.shift || key.meta)) {
102
+ repl.rl.write('\\\n');
103
+ return true;
104
+ }
104
105
 
105
- if (key.name === 'escape') {
106
- if (repl.isProcessing) {
107
- // Cancel current AI turn
108
- repl.isCancelled = true;
109
- if (repl.spinner) repl.spinner.stop();
110
- console.log(`\n${colors.red}[ Đã nhận lệnh HỦY... AI sẽ kết thúc ở thao tác tiếp theo ]${colors.reset}`);
111
- } else {
112
- // Double-ESC to end session
113
- const now = Date.now();
114
- if (this._lastEscTime && (now - this._lastEscTime) < 500) {
115
- console.log(`\n\n${colors.cyan}Cảm ơn đã sử dụng Winter!${colors.reset}`);
116
- console.log(`${colors.yellow}Tiếp tục phiên làm việc:${colors.reset}`);
117
- console.log(`${colors.bright}${colors.green}winter --session ${repl.session?.getSessionId?.() || ''}${colors.reset}\n`);
118
- process.exit(0);
119
- }
120
- this._lastEscTime = now;
121
- console.log(`${colors.dim}Press ESC again to end session${colors.reset}`);
106
+ if (key.ctrl || key.meta) return false;
107
+
108
+ if (typeof str === 'string' && str.length > 1) {
109
+ return false;
110
+ }
111
+
112
+ if (repl.slashMenu.open && this.handleSlashMenuKey(key)) {
113
+ return true;
114
+ }
115
+
116
+ if (key.name === 'escape') {
117
+ if (repl.isProcessing) {
118
+ // Cancel current AI turn
119
+ repl.isCancelled = true;
120
+ if (repl.spinner) repl.spinner.stop();
121
+ console.log(`\n${colors.red}[ Đã nhận lệnh HỦY... AI sẽ kết thúc ở thao tác tiếp theo ]${colors.reset}`);
122
+ } else {
123
+ // Double-ESC to end session
124
+ const now = Date.now();
125
+ if (this._lastEscTime && (now - this._lastEscTime) < 500) {
126
+ console.log(`\n\n${colors.cyan}Cảm ơn đã sử dụng Winter!${colors.reset}`);
127
+ console.log(`${colors.yellow}Tiếp tục phiên làm việc:${colors.reset}`);
128
+ console.log(`${colors.bright}${colors.green}winter --session ${repl.session?.getSessionId?.() || ''}${colors.reset}\n`);
129
+ process.exit(0);
122
130
  }
123
- return;
131
+ this._lastEscTime = now;
132
+ console.log(`${colors.dim}Press ESC again to end session${colors.reset}`);
124
133
  }
134
+ return true;
135
+ }
125
136
 
126
- queueMicrotask(() => {
127
- const line = repl.rl?.line || '';
128
- if (!line.startsWith('/')) {
129
- this.closeSlashMenu();
130
- repl.rl?.prompt?.(true);
131
- return;
132
- }
137
+ queueMicrotask(() => {
138
+ const line = repl.rl?.line || '';
139
+ if (!line.startsWith('/')) {
140
+ this.closeSlashMenu();
141
+ repl.rl?.prompt?.(true);
142
+ return;
143
+ }
133
144
 
134
- this.openSlashMenu(line);
135
- });
145
+ this.openSlashMenu(line);
136
146
  });
147
+ return true;
137
148
  }
138
149
 
139
150
  async handleDirectClipboardPaste() {
@@ -138,6 +138,7 @@ export class PromptBuilder {
138
138
  `Open browser requests: if the user asks to "mở chrome", "open Chrome", or open a URL in a visible browser, use OpenBrowser. Do NOT use Bash, Get-Command, Start-Process, cmd start, or shell app launch commands for this.`,
139
139
  `Browser capability: You CAN browse URLs! Use WebFetch only for static page text extraction. For live Chrome debugging and visible browser control, prefer MCP server "chrome-devtools" when configured: use MCP tool "new_page" or "navigate_page", then "take_snapshot", "click", "fill"/"fill_form", "take_screenshot", "evaluate_script", "list_console_messages", "list_network_requests", or performance trace tools. If MCP is unavailable, use VisibleBrowser for real visible Puppeteer control. BrowserDebug is headless fallback only when visible control is unnecessary.`,
140
140
  `Browser interaction rule: if the user asks to click, press, fill, select, submit, open a web app path, or inspect page-by-page data, WebFetch is not enough and BrowserDebug is not user-visible. Use chrome-devtools MCP or VisibleBrowser so the user can watch a normal browser. Never claim "đã bấm/đã điền/đã mở/đã kiểm tra" from prose alone.`,
141
+ `Figma/design-to-code: if the user gives a Figma URL or asks to implement a Figma frame, call DesignToCode with the Figma frame URL and an output_path. Do not only describe the design. If the user asks to modify the Figma canvas itself, use MCP server "figma" when configured and say clearly when it is not connected.`,
141
142
  `When a task touches coding, agents, UI, brand, or design, inspect the relevant required local resource in depth before deciding.`,
142
143
  `If the user asks you to modify, run, inspect, check, publish, commit, or otherwise act on the project, you MUST use tools. Do not claim completion without a tool result from this turn.`,
143
144
  ``,
@@ -248,7 +249,7 @@ export class PromptBuilder {
248
249
  rolePrompt,
249
250
  '',
250
251
  '## Tool Rules',
251
- '- Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, OpenBrowser, VisibleBrowser, BrowserDebug, WebFetch, WebSearch, MCP, Agent, DelegateTask, ParallelAgent.',
252
+ '- Canonical tools: Read, Write, Edit, Bash, Glob, Grep, TaskCreate, TaskUpdate, TaskList, OpenBrowser, VisibleBrowser, BrowserDebug, DesignToCode, WebFetch, WebSearch, MCP, Agent, DelegateTask, ParallelAgent.',
252
253
  '- For multi-agent work, use DelegateTask for one isolated subagent or ParallelAgent for independent concurrent subagents; do not pretend delegation happened without tool evidence.',
253
254
  '- If native tool calls are unavailable, output exactly one fallback tool call and no prose: <invoke name="Read"><parameter name="path">README.md</parameter></invoke> OR {"tool":"Read","arguments":{"path":"README.md"}} OR CALL_TOOL Read {"path":"README.md"}.',
254
255
  '- Treat skills, memories, bundled resources, local project rules, and the tool list as operational context. Use them proactively when relevant.',
@@ -219,6 +219,38 @@ async function handleBrowserCommand(repl, args = []) {
219
219
  if (result.recovery) console.log(`${colors.dim}${result.recovery}${colors.reset}`);
220
220
  }
221
221
 
222
+ async function handleFigmaCommand(repl, args = []) {
223
+ const parsed = parseFlagArgs(args);
224
+ const [urlArg, outputArg] = parsed.positional;
225
+ const url = parsed.url || parsed.figmaUrl || urlArg;
226
+ if (!url || url === 'help' || url === '--help') {
227
+ console.log(`${colors.cyan}/figma usage:${colors.reset}`);
228
+ console.log(' /figma <figma-frame-url> [output.tsx] --force');
229
+ console.log(' /design-to-code <figma-frame-url> --output src/components/FigmaDesign.tsx');
230
+ console.log(`${colors.dim}Requires FIGMA_TOKEN in project .env, environment, or --token.${colors.reset}`);
231
+ return;
232
+ }
233
+
234
+ const payload = {
235
+ url: String(url).replace(/^['"]|['"]$/g, ''),
236
+ output_path: parsed.output || parsed.out || parsed.component || outputArg,
237
+ assets_dir: parsed.assets || parsed.assetsDir,
238
+ force: parsed.force === true,
239
+ no_tailwind: parsed.noTailwind === true || parsed.noTailwind === 'true',
240
+ token: parsed.token,
241
+ };
242
+ console.log(`${colors.cyan}DesignToCode:${colors.reset} ${payload.url}`);
243
+ const result = await repl.tools.execute('DesignToCode', payload, { cwd: repl.projectPath });
244
+ if (result.success) {
245
+ console.log(`${colors.green}OK DesignToCode${result.outputPath ? ` -> ${result.outputPath}` : ''}${colors.reset}`);
246
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
247
+ if (output) console.log(output.length > 4000 ? `${output.slice(0, 4000)}...` : output);
248
+ return;
249
+ }
250
+ console.log(`${colors.red}FAIL DesignToCode: ${result.error || 'unknown error'}${colors.reset}`);
251
+ if (result.recovery) console.log(`${colors.dim}${result.recovery}${colors.reset}`);
252
+ }
253
+
222
254
  /**
223
255
  * Handle slash commands in the Winter REPL.
224
256
  * Delegated from WinterREPL.handleSlashCommand to reduce file size.
@@ -467,6 +499,12 @@ export async function handleSlashCommand(repl, input) {
467
499
  await handleBrowserCommand(repl, args);
468
500
  return;
469
501
 
502
+ case '/figma':
503
+ case '/design-to-code':
504
+ case '/vibefigma':
505
+ await handleFigmaCommand(repl, args);
506
+ return;
507
+
470
508
  // Inline Completion
471
509
  case '/complete':
472
510
  if (args.length === 0) {
package/src/cli/repl.js CHANGED
@@ -93,6 +93,7 @@ export class WinterREPL {
93
93
  this.history = [];
94
94
  this.maxHistory = 500;
95
95
  this.slashMenu = { open: false, line: '', items: [], selected: 0 };
96
+ this.interactiveChecklistOpen = false;
96
97
  this.inputQueue = Promise.resolve();
97
98
  this.readlineClosed = false;
98
99
  this.taskQueue = [];
@@ -1443,11 +1444,22 @@ export class WinterREPL {
1443
1444
  async showInteractiveChecklist(title, items) {
1444
1445
  if (!items || items.length === 0) return [];
1445
1446
 
1447
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
1448
+ console.log(`\n\x1b[36m${title}\x1b[0m`);
1449
+ console.log(`\x1b[2mNon-interactive terminal detected; selecting all steps.\x1b[0m`);
1450
+ items.forEach(item => console.log(` \x1b[32m[x]\x1b[0m ${item}`));
1451
+ this.interactiveChecklistOpen = false;
1452
+ return [...items];
1453
+ }
1454
+
1446
1455
  return new Promise((resolve) => {
1447
1456
  let cursor = 0;
1448
1457
  const selected = new Set(items.map((_, i) => i)); // default select all
1458
+ let ignoreInitialReturn = true;
1449
1459
 
1450
1460
  let printedLines = 0;
1461
+ const wasRaw = Boolean(process.stdin.isRaw);
1462
+ const wasPaused = process.stdin.isPaused?.() ?? false;
1451
1463
  const render = () => {
1452
1464
  // Xóa những dòng đã in trước đó.
1453
1465
  if (printedLines > 0) {
@@ -1475,6 +1487,11 @@ export class WinterREPL {
1475
1487
  if (selected.has(cursor)) selected.delete(cursor);
1476
1488
  else selected.add(cursor);
1477
1489
  } else if (key.name === 'return') {
1490
+ if (ignoreInitialReturn) {
1491
+ ignoreInitialReturn = false;
1492
+ render();
1493
+ return;
1494
+ }
1478
1495
  cleanup();
1479
1496
  const result = items.filter((_, i) => selected.has(i));
1480
1497
  resolve(result);
@@ -1489,18 +1506,37 @@ export class WinterREPL {
1489
1506
 
1490
1507
  const cleanup = () => {
1491
1508
  process.stdin.removeListener('keypress', onKeyPress);
1492
- if (process.stdin.isTTY) process.stdin.setRawMode(false);
1493
- this.rl.resume();
1509
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === 'function') {
1510
+ process.stdin.setRawMode(wasRaw);
1511
+ }
1512
+ if (wasPaused && typeof process.stdin.pause === 'function') {
1513
+ process.stdin.pause();
1514
+ }
1515
+ this.interactiveChecklistOpen = false;
1516
+ this.rl?.resume?.();
1494
1517
  process.stdout.write('\n');
1495
1518
  };
1496
1519
 
1497
- this.rl.pause();
1498
- if (process.stdin.isTTY) process.stdin.setRawMode(true);
1520
+ try {
1521
+ this.interactiveChecklistOpen = true;
1522
+ this.rl?.pause?.();
1523
+ if (process.stdin.isTTY && typeof process.stdin.setRawMode === 'function') {
1524
+ process.stdin.setRawMode(true);
1525
+ }
1526
+ process.stdin.resume();
1527
+
1528
+ readline.emitKeypressEvents(process.stdin);
1529
+ process.stdin.on('keypress', onKeyPress);
1499
1530
 
1500
- readline.emitKeypressEvents(process.stdin);
1501
- process.stdin.on('keypress', onKeyPress);
1531
+ setImmediate(() => {
1532
+ ignoreInitialReturn = false;
1533
+ });
1502
1534
 
1503
- render();
1535
+ render();
1536
+ } catch (error) {
1537
+ cleanup();
1538
+ throw error;
1539
+ }
1504
1540
  });
1505
1541
  }
1506
1542
 
@@ -1832,9 +1868,9 @@ ${colors.reset}
1832
1868
  return byName(['Read', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Parallel']);
1833
1869
  case 'design':
1834
1870
  case 'ui':
1835
- return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'MCP']);
1871
+ return byName(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'DesignToCode', 'WebFetch', 'MCP']);
1836
1872
  default:
1837
- return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'WebSearch', 'MCP', 'Parallel', 'Agent', 'DelegateTask', 'ParallelAgent']);
1873
+ return byName(['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'DesignToCode', 'WebFetch', 'WebSearch', 'MCP', 'Parallel', 'Agent', 'DelegateTask', 'ParallelAgent']);
1838
1874
  }
1839
1875
  }
1840
1876
 
@@ -1963,10 +1999,10 @@ ${colors.reset}
1963
1999
  if (this.isBrowserInteractionRequest(rawText)) return true;
1964
2000
 
1965
2001
  // Even without explicit target, some verbs are strong enough on their own
1966
- const continuationAction = /\b(continue|resume|start|begin|tiep|tiếp|bat dau|bắt đầu)\b/i;
2002
+ const continuationAction = /\b(continue|resume|start|begin|do it|go ahead|tiep|tiếp|bat dau|bắt đầu|lam di|làm đi)\b/i;
1967
2003
  if (continuationAction.test(text)) return true;
1968
2004
 
1969
- const strongActionAlone = /\b(fix|debug|deploy|build|test|commit|install|run|refactor|start|begin|sửa|chạy|cài|triển khai|xây dựng|bắt đầu|bat dau)\b/i;
2005
+ const strongActionAlone = /\b(fix|debug|deploy|build|test|commit|install|run|refactor|start|begin|do|sửa|chạy|cài|triển khai|xây dựng|bắt đầu|bat dau|làm|lam)\b/i;
1970
2006
  if (strongActionAlone.test(text)) return true;
1971
2007
 
1972
2008
  return actionPattern.test(text) && targetPattern.test(text);
@@ -2039,7 +2075,7 @@ ${colors.reset}
2039
2075
  if (!text.trim()) return false;
2040
2076
 
2041
2077
  // Detect fake completion claims - model says it did something without using tools
2042
- const fakeCompletionClaims = /(?:đã (?:sửa|tạo|viết|xóa|cập nhật|thêm|chỉnh|xong|hoàn thành|fix|update|edit|write|create|delete|remove|modify|change|apply|deploy|push)|i(?:'ve| have) (?:fixed|created|written|updated|added|modified|changed|edited|applied|deployed|deleted|removed|patched|implemented|refactored)|done!|xong rồi|hoàn thành|đã hoàn tất|hoàn tất|the (?:fix|change|update|edit|modification) (?:has been|is) (?:applied|done|completed|made)|here(?:'s| is) the (?:fix|update|change|solution|implementation|code)|file (?:has been|was) (?:updated|created|modified|written|changed)|changes? (?:have been|has been|were) (?:made|applied|saved)|successfully (?:updated|created|modified|fixed|applied|changed|written))/i;
2078
+ const fakeCompletionClaims = /(?:đã (?:làm|sửa|tạo|viết|xóa|cập nhật|thêm|chỉnh|xong|hoàn thành|fix|update|edit|write|create|delete|remove|modify|change|apply|deploy|push)|i(?:'ve| have) (?:done|fixed|created|written|updated|added|modified|changed|edited|applied|deployed|deleted|removed|patched|implemented|refactored)|done!|xong rồi|hoàn thành|đã hoàn tất|hoàn tất|phase \d+ done|build check:\s*pass|files? (?:tạo|sửa|created|modified|changed)|the (?:fix|change|update|edit|modification) (?:has been|is) (?:applied|done|completed|made)|here(?:'s| is) the (?:fix|update|change|solution|implementation|code)|file (?:has been|was) (?:updated|created|modified|written|changed)|changes? (?:have been|has been|were) (?:made|applied|saved)|successfully (?:updated|created|modified|fixed|applied|changed|written))/i;
2043
2079
  if (fakeCompletionClaims.test(text)) return true;
2044
2080
 
2045
2081
  const fakeBrowserClaims = /(?:đã|da|i(?:'ve| have))\s+(?:bấm|bam|click(?:ed)?|mở|mo|open(?:ed)?|điền|dien|fill(?:ed)?|chọn|chon|select(?:ed)?|submit(?:ted)?|vào|vao|navigate(?:d)?)/i;
@@ -2151,6 +2187,10 @@ ${colors.reset}
2151
2187
  hints.push('TOOL HINT: For "open Chrome" / "mở chrome", call OpenBrowser {"browser":"chrome","url":"about:blank"}. Do NOT call Bash/Get-Command/Start-Process. If configured, use MCP {"server":"chrome-devtools","tool":"list"} for page state, console, network, screenshots, and performance.');
2152
2188
  }
2153
2189
 
2190
+ if (/\b(figma|vibefigma|design[- ]?to[- ]?code|design to code|frame|node-id)\b/i.test(text)) {
2191
+ hints.push('TOOL HINT: For Figma design-to-code, call DesignToCode with {"url":"<Figma frame URL>","output_path":"src/components/Name.tsx"}. Do NOT only describe the design. If the user asks direct Figma canvas manipulation, use MCP server "figma" when configured; otherwise explain that Winter needs the Figma MCP server connected.');
2192
+ }
2193
+
2154
2194
  if (hints.length === 0) return null;
2155
2195
 
2156
2196
  return `[Tool Router] ${hints.join(' | ')}`;
@@ -2404,7 +2444,7 @@ ${colors.reset}
2404
2444
  '/provider', '/model', '/models', '/providers',
2405
2445
  '/theme:toggle', '/tui',
2406
2446
  '/auto', '/debug', '/doctor', '/context', '/scorecard', '/swe',
2407
- '/read', '/write', '/glob', '/grep', '/bash', '/browser', '/paste',
2447
+ '/read', '/write', '/glob', '/grep', '/bash', '/browser', '/paste', '/figma', '/design-to-code',
2408
2448
  '/codex', '/claude', '/karpathy', '/agents',
2409
2449
  '/resources', '/designs', '/skills',
2410
2450
  '/ecc',
@@ -37,6 +37,9 @@ export const SLASH_COMMANDS = [
37
37
  { cmd: '/bash', desc: 'Run command', usage: '/bash <command>' },
38
38
  { cmd: '/image', desc: 'Analyze image/screenshot or clipboard image', usage: '/image [file] [question]' },
39
39
  { cmd: '/paste', desc: 'Read clipboard text or image', usage: '/paste [prompt]' },
40
+ { cmd: '/figma', desc: 'Convert selected Figma frame to code', usage: '/figma <figma-url> [output.tsx] [--force]' },
41
+ { cmd: '/design-to-code', desc: 'Alias for Figma design-to-code', usage: '/design-to-code <figma-url> --output src/components/FigmaDesign.tsx' },
42
+ { cmd: '/vibefigma', desc: 'Run bundled vibefigma design-to-code workflow', usage: '/vibefigma <figma-url> [output.tsx]' },
40
43
  { cmd: '/design', desc: 'Design commands', sub: ['search', 'add', 'apply', 'list', 'preview'] },
41
44
  { cmd: '/designs', desc: 'List/search awesome-design-md systems', usage: '/designs [query]' },
42
45
  { cmd: '/skill', desc: 'Skills management', sub: ['list', 'enable', 'create'] },
@@ -15,35 +15,75 @@ export class DesignCommands {
15
15
  this.repl = repl;
16
16
  this.session = repl.session;
17
17
  this.config = repl.config;
18
+ this.projectPath = repl.projectPath || null;
18
19
  this.brandsDir = path.join(packageRoot, 'resources', 'local', 'awesome-design-md', 'design-md');
19
20
  }
20
21
 
21
22
  async execute(action, args) {
22
- switch (action) {
23
+ const requestedAction = String(action || '').trim();
24
+ if (!requestedAction) {
25
+ await this.printHelp();
26
+ await this.listBrands();
27
+ return;
28
+ }
29
+
30
+ switch (requestedAction.toLowerCase()) {
23
31
  case 'search':
24
32
  case 'find':
33
+ case 'tim':
25
34
  await this.search(args[0] || '');
26
35
  break;
27
36
  case 'add':
37
+ case 'install':
28
38
  await this.addBrand(args[0]);
29
39
  break;
30
40
  case 'apply':
41
+ case 'use':
42
+ case 'set':
31
43
  await this.applyBrand(args[0]);
32
44
  break;
33
45
  case 'list':
46
+ case 'ls':
34
47
  await this.listBrands();
35
48
  break;
36
49
  case 'preview':
50
+ case 'show':
51
+ case 'view':
37
52
  await this.previewBrand(args[0]);
38
53
  break;
39
54
  case 'init':
40
55
  await this.initBrandsDir();
41
56
  break;
57
+ case 'help':
58
+ case '--help':
59
+ case '-h':
60
+ await this.printHelp();
61
+ break;
42
62
  default:
43
- await this.listBrands();
63
+ if (await this.brandExists(requestedAction)) {
64
+ await this.applyBrand(requestedAction);
65
+ } else {
66
+ await this.search(requestedAction);
67
+ }
44
68
  }
45
69
  }
46
70
 
71
+ async printHelp() {
72
+ console.log(`${colors.cyan}/design usage:${colors.reset}`);
73
+ console.log(' /design <brand> Apply a design system to the current project');
74
+ console.log(' /design search <query> Search bundled design systems');
75
+ console.log(' /design preview <brand> Preview DESIGN.md');
76
+ console.log(' /design add <brand> Copy DESIGN.md into the current project');
77
+ console.log(' /design list List all bundled brands');
78
+ console.log('');
79
+ }
80
+
81
+ async brandExists(brand) {
82
+ if (!brand) return false;
83
+ const brandDir = path.join(this.brandsDir, brand);
84
+ return await fs.access(brandDir).then(() => true).catch(() => false);
85
+ }
86
+
47
87
  async search(query) {
48
88
  if (!query) {
49
89
  console.log(`${colors.yellow}Usage: winter design search <query>${colors.reset}`);
@@ -67,7 +107,7 @@ export class DesignCommands {
67
107
  });
68
108
  console.log('');
69
109
 
70
- await this.session.addToMemory(`Searched design brands for: ${query}`, 'search');
110
+ await this.session?.addToMemory?.(`Searched design brands for: ${query}`, 'search');
71
111
  } catch (error) {
72
112
  console.log(`${colors.red}${statusIcons.error} Error: ${error.message}${colors.reset}`);
73
113
  console.log(`${colors.dim}Make sure you have awesome-design-md installed${colors.reset}`);
@@ -103,11 +143,11 @@ export class DesignCommands {
103
143
  }
104
144
 
105
145
  // Write to current project
106
- const targetPath = path.join(process.cwd(), fileName);
146
+ const targetPath = path.join(this.repl?.projectPath || this.projectPath || process.cwd(), fileName);
107
147
  await fs.writeFile(targetPath, fileContent);
108
148
 
109
149
  console.log(`${statusIcons.success} Added ${fileName} for ${brand}`);
110
- await this.session.addToMemory(`Added design file: ${brand}`, 'design');
150
+ await this.session?.addToMemory?.(`Added design file: ${brand}`, 'design');
111
151
  } catch (error) {
112
152
  console.log(`${colors.red}${statusIcons.error} Error: ${error.message}${colors.reset}`);
113
153
  }
@@ -142,7 +182,7 @@ export class DesignCommands {
142
182
 
143
183
  console.log(`${colors.cyan}${statusIcons.info} Analyzing and applying ${brand} design system...${colors.reset}`);
144
184
 
145
- const prompt = `Please act as a Senior UI/UX Engineer. Analyze the following design system (${brand}) and completely refactor the UI and styles in this project to match its specifications. Focus on colors, typography, border radiuses, interactive states, and overall visual aesthetics as defined in the document.
185
+ const prompt = `Please act as a Senior UI/UX Engineer. Analyze the following design system (${brand}) and apply it to this project with real tool calls. Inspect the existing UI files/styles first, then make focused code changes and verify with the closest build/typecheck/smoke command. Do not only describe the design. Focus on colors, typography, border radiuses, interactive states, and overall visual aesthetics as defined in the document.
146
186
 
147
187
  <design_system>
148
188
  ${fileContent}
@@ -229,7 +269,7 @@ Start by reviewing the codebase, especially tailwind configs or global css, then
229
269
  }
230
270
 
231
271
  async initBrandsDir() {
232
- const localDir = path.join(process.cwd(), '.design-systems');
272
+ const localDir = path.join(this.repl?.projectPath || this.projectPath || process.cwd(), '.design-systems');
233
273
  await fs.mkdir(localDir, { recursive: true });
234
274
  console.log(`${statusIcons.success} Created ${localDir}/`);
235
275
  }
@@ -1,7 +1,11 @@
1
1
  export const CHROME_DEVTOOLS_MCP_NAME = 'chrome-devtools';
2
+ export const FIGMA_MCP_NAME = 'figma';
2
3
 
3
4
  const CHROME_DEVTOOLS_PACKAGE = 'chrome-devtools-mcp@latest';
4
5
  const CHROME_DEVTOOLS_SOURCE = 'https://github.com/ChromeDevTools/chrome-devtools-mcp';
6
+ const MCP_REMOTE_PACKAGE = 'mcp-remote@latest';
7
+ const FIGMA_DEV_MODE_MCP_URL = 'http://127.0.0.1:3845/mcp';
8
+ const FIGMA_DEV_MODE_MCP_SOURCE = 'https://developers.figma.com/docs/figma-mcp-server/local-server-installation/';
5
9
 
6
10
  const CHROME_DEVTOOLS_FLAGS_WITH_VALUES = new Set([
7
11
  '--browser-url',
@@ -27,6 +31,10 @@ export function isChromeDevtoolsPreset(name = '') {
27
31
  return ['chrome-devtools', 'chromedevtools', 'chrome', 'devtools', 'cdp'].includes(normalizeMcpPresetName(name));
28
32
  }
29
33
 
34
+ export function isFigmaPreset(name = '') {
35
+ return ['figma', 'figma-dev-mode', 'figmadevmode', 'figma-desktop', 'figmadesktop'].includes(normalizeMcpPresetName(name));
36
+ }
37
+
30
38
  export function buildChromeDevtoolsArgs(options = []) {
31
39
  const input = Array.isArray(options) ? [...options] : [];
32
40
  const args = ['-y', CHROME_DEVTOOLS_PACKAGE];
@@ -85,10 +93,51 @@ export function createChromeDevtoolsMcpServer(options = [], platform = process.p
85
93
  };
86
94
  }
87
95
 
96
+ export function createFigmaMcpServer(options = [], platform = process.platform, env = process.env) {
97
+ const input = Array.isArray(options) ? [...options] : [];
98
+ const customUrlIndex = input.findIndex(value => value === '--url' || value === '--endpoint');
99
+ const endpoint = customUrlIndex >= 0 && input[customUrlIndex + 1]
100
+ ? String(input[customUrlIndex + 1])
101
+ : FIGMA_DEV_MODE_MCP_URL;
102
+ const npxArgs = ['-y', MCP_REMOTE_PACKAGE, endpoint];
103
+ const common = {
104
+ name: FIGMA_MCP_NAME,
105
+ enabled: true,
106
+ requestTimeoutMs: 60000,
107
+ metadata: {
108
+ preset: FIGMA_MCP_NAME,
109
+ source: FIGMA_DEV_MODE_MCP_SOURCE,
110
+ endpoint,
111
+ purpose: 'Figma Dev Mode MCP for direct design context, selected frame inspection, assets, variables, and design-to-code workflows.',
112
+ },
113
+ };
114
+
115
+ if (platform === 'win32') {
116
+ return {
117
+ ...common,
118
+ command: 'cmd',
119
+ args: ['/c', 'npx', ...npxArgs],
120
+ env: {
121
+ SystemRoot: env.SystemRoot || 'C:\\Windows',
122
+ PROGRAMFILES: env.PROGRAMFILES || 'C:\\Program Files',
123
+ },
124
+ };
125
+ }
126
+
127
+ return {
128
+ ...common,
129
+ command: 'npx',
130
+ args: npxArgs,
131
+ };
132
+ }
133
+
88
134
  export function getMcpPreset(name, options = []) {
89
135
  if (isChromeDevtoolsPreset(name)) {
90
136
  return createChromeDevtoolsMcpServer(options);
91
137
  }
138
+ if (isFigmaPreset(name)) {
139
+ return createFigmaMcpServer(options);
140
+ }
92
141
  throw new Error(`Unknown MCP preset: ${name}`);
93
142
  }
94
143
 
@@ -477,6 +477,23 @@ export class ToolExecutor {
477
477
  }
478
478
  }
479
479
  },
480
+ {
481
+ type: 'function',
482
+ name: 'DesignToCode',
483
+ description: 'Convert a selected Figma design/frame URL into project code using the bundled vibefigma workflow. Use when the user gives a Figma URL, asks for Figma design-to-code, or asks to implement a selected Figma frame. Requires FIGMA_TOKEN in env/.env or a token argument.',
484
+ parameters: {
485
+ type: 'object',
486
+ properties: {
487
+ url: { type: 'string', description: 'Figma design/file URL. Best results require a selected node-id in the URL.' },
488
+ output_path: { type: 'string', description: 'Optional React component output path, e.g. src/components/FigmaDesign.tsx' },
489
+ assets_dir: { type: 'string', description: 'Optional asset output directory, default handled by vibefigma.' },
490
+ force: { type: 'boolean', description: 'Overwrite existing generated files without prompting.' },
491
+ no_tailwind: { type: 'boolean', description: 'Generate regular CSS instead of Tailwind.' },
492
+ token: { type: 'string', description: 'Optional Figma access token. Prefer FIGMA_TOKEN env/.env instead.' },
493
+ },
494
+ required: ['url']
495
+ }
496
+ },
480
497
  {
481
498
  type: 'function',
482
499
  name: 'WebFetch',
@@ -625,6 +642,8 @@ export class ToolExecutor {
625
642
  return await this.visibleBrowser(input, cwd);
626
643
  case 'OpenBrowser':
627
644
  return await this.openBrowser(input.url ?? input.uri ?? input.href, input.browser);
645
+ case 'DesignToCode':
646
+ return await this.designToCode(input, cwd);
628
647
  case 'WebFetch':
629
648
  return await this.webFetch(input.url ?? input.uri ?? input.href, input.prompt ?? input.query ?? input.extract);
630
649
  case 'WebSearch':
@@ -693,7 +712,7 @@ export class ToolExecutor {
693
712
  return {
694
713
  success: false,
695
714
  error: `Unknown tool: ${toolName}`,
696
- availableTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList', 'MCP', 'Parallel', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'WebFetch', 'WebSearch', 'WebArchive', 'HtmlEffectiveness', 'NotebookRead', 'NotebookEdit', 'TodoWrite', 'TodoList', 'ScheduleWakeup', 'AskUserQuestion', 'Agent', 'DelegateTask', 'ParallelAgent', 'InsertText', 'StrReplaceAll'],
715
+ availableTools: ['Read', 'Write', 'Edit', 'Bash', 'Glob', 'Grep', 'TaskCreate', 'TaskUpdate', 'TaskList', 'MCP', 'Parallel', 'OpenBrowser', 'VisibleBrowser', 'BrowserDebug', 'DesignToCode', 'WebFetch', 'WebSearch', 'WebArchive', 'HtmlEffectiveness', 'NotebookRead', 'NotebookEdit', 'TodoWrite', 'TodoList', 'ScheduleWakeup', 'AskUserQuestion', 'Agent', 'DelegateTask', 'ParallelAgent', 'InsertText', 'StrReplaceAll'],
697
716
  recovery: 'Call one of the available tools. For file writes use Write with { "file_path": "...", "content": "..." }. For shell commands use Bash with { "command": "..." }.',
698
717
  };
699
718
  }
@@ -852,6 +871,44 @@ export class ToolExecutor {
852
871
  return { success: true, coerced: true, args: { ...args, url, browser } };
853
872
  }
854
873
 
874
+ if (toolName === 'DesignToCode') {
875
+ const url = pick('url', 'figmaUrl', 'figma_url', 'href', 'uri', 'input');
876
+ if (!url) {
877
+ return {
878
+ success: false,
879
+ error: 'Figma url is required',
880
+ recovery: 'Example: DesignToCode {"url":"https://www.figma.com/design/FILE/Name?node-id=1-2","output_path":"src/components/FigmaDesign.tsx"}',
881
+ };
882
+ }
883
+ if (!/^https:\/\/(?:www\.)?figma\.com\/(?:design|file)\//i.test(url)) {
884
+ return {
885
+ success: false,
886
+ error: 'DesignToCode requires a Figma design/file URL',
887
+ recovery: 'Copy a Figma frame/component link such as https://www.figma.com/design/FILE/Name?node-id=1-2.',
888
+ };
889
+ }
890
+ const outputPath = pick('output_path', 'outputPath', 'component', 'path', 'file');
891
+ const assetsDir = pick('assets_dir', 'assetsDir', 'assets');
892
+ if (outputPath) {
893
+ const outputSafety = await this.validateWorkspacePath(this.resolveInputPath(outputPath, cwd), 'DesignToCode output path');
894
+ if (outputSafety?.success === false) return outputSafety;
895
+ }
896
+ if (assetsDir) {
897
+ const assetsSafety = await this.validateWorkspacePath(this.resolveInputPath(assetsDir, cwd), 'DesignToCode assets directory');
898
+ if (assetsSafety?.success === false) return assetsSafety;
899
+ }
900
+ return {
901
+ success: true,
902
+ coerced: true,
903
+ args: {
904
+ ...args,
905
+ url,
906
+ output_path: outputPath,
907
+ assets_dir: assetsDir,
908
+ },
909
+ };
910
+ }
911
+
855
912
  if (toolName === 'Agent' || toolName === 'DelegateTask') {
856
913
  const task = pick('task', 'goal', 'prompt', 'description');
857
914
  if (!task) {
@@ -1171,6 +1228,13 @@ export class ToolExecutor {
1171
1228
  browserdebug: 'BrowserDebug',
1172
1229
  browser: 'BrowserDebug',
1173
1230
  browserinspect: 'BrowserDebug',
1231
+ designtocode: 'DesignToCode',
1232
+ designcode: 'DesignToCode',
1233
+ figma: 'DesignToCode',
1234
+ figmatocode: 'DesignToCode',
1235
+ figmacode: 'DesignToCode',
1236
+ vibefigma: 'DesignToCode',
1237
+ figmadesign: 'DesignToCode',
1174
1238
  parallel: 'Parallel',
1175
1239
  parallelexecute: 'Parallel',
1176
1240
  paralleltools: 'Parallel',
@@ -1247,6 +1311,7 @@ export class ToolExecutor {
1247
1311
  case 'WebArchive':
1248
1312
  case 'BrowserDebug':
1249
1313
  case 'VisibleBrowser':
1314
+ case 'DesignToCode':
1250
1315
  return { url: value };
1251
1316
  case 'TaskCreate':
1252
1317
  case 'TodoWrite':
@@ -2151,6 +2216,84 @@ export class ToolExecutor {
2151
2216
  };
2152
2217
  }
2153
2218
 
2219
+ async readProjectEnvValue(name, cwd = this.projectPath) {
2220
+ if (process.env[name]) return process.env[name];
2221
+ try {
2222
+ const envPath = path.join(cwd || this.projectPath, '.env');
2223
+ const raw = await fs.readFile(envPath, 'utf8');
2224
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
2225
+ const pattern = new RegExp(`^\\s*${escaped}\\s*=\\s*(.*)\\s*$`, 'm');
2226
+ const match = raw.match(pattern);
2227
+ if (!match) return '';
2228
+ return String(match[1] || '').trim().replace(/^['"]|['"]$/g, '');
2229
+ } catch {
2230
+ return '';
2231
+ }
2232
+ }
2233
+
2234
+ async designToCode(input = {}, cwd = this.projectPath) {
2235
+ const url = String(input.url || input.figmaUrl || input.figma_url || input.href || input.uri || '').trim();
2236
+ if (!url) {
2237
+ return {
2238
+ success: false,
2239
+ error: 'Figma url is required',
2240
+ recovery: 'Use DesignToCode {"url":"https://www.figma.com/design/FILE/Name?node-id=1-2","output_path":"src/components/FigmaDesign.tsx"}.',
2241
+ };
2242
+ }
2243
+
2244
+ const token = String(input.token || input.access_token || input.accessToken || await this.readProjectEnvValue('FIGMA_TOKEN', cwd) || await this.readProjectEnvValue('FIGMA_ACCESS_TOKEN', cwd) || '').trim();
2245
+ if (!token) {
2246
+ return {
2247
+ success: false,
2248
+ error: 'FIGMA_TOKEN is required for Figma design-to-code',
2249
+ url,
2250
+ recovery: 'Add FIGMA_TOKEN to your project .env or pass token to DesignToCode. Then retry with a selected frame URL that includes node-id.',
2251
+ };
2252
+ }
2253
+
2254
+ const args = ['-y', 'vibefigma', url];
2255
+ const outputPath = input.output_path || input.outputPath || input.component || input.path || input.file;
2256
+ const assetsDir = input.assets_dir || input.assetsDir || input.assets;
2257
+ if (outputPath) args.push('-c', String(outputPath));
2258
+ if (assetsDir) args.push('-a', String(assetsDir));
2259
+ if (input.force) args.push('--force');
2260
+ if (input.no_tailwind || input.noTailwind) args.push('--no-tailwind');
2261
+
2262
+ const command = process.platform === 'win32' ? 'npx.cmd' : 'npx';
2263
+ try {
2264
+ const { stdout, stderr } = await execFileAsync(command, args, {
2265
+ cwd,
2266
+ env: { ...process.env, FIGMA_TOKEN: token },
2267
+ timeout: Number(input.timeout || input.timeout_ms || input.timeoutMs || 180000),
2268
+ maxBuffer: 20 * 1024 * 1024,
2269
+ windowsHide: true,
2270
+ });
2271
+ return {
2272
+ success: true,
2273
+ tool: 'DesignToCode',
2274
+ url,
2275
+ outputPath: outputPath || null,
2276
+ assetsDir: assetsDir || null,
2277
+ command: `npx -y vibefigma "${url}"${outputPath ? ` -c ${outputPath}` : ''}${assetsDir ? ` -a ${assetsDir}` : ''}${input.force ? ' --force' : ''}${input.no_tailwind || input.noTailwind ? ' --no-tailwind' : ''}`,
2278
+ stdout,
2279
+ stderr,
2280
+ recovery: 'Review the generated component, then run the project typecheck/build and clean up responsiveness if needed.',
2281
+ };
2282
+ } catch (error) {
2283
+ return {
2284
+ success: false,
2285
+ tool: 'DesignToCode',
2286
+ url,
2287
+ outputPath: outputPath || null,
2288
+ error: error.message,
2289
+ exitCode: error.code,
2290
+ stdout: error.stdout || '',
2291
+ stderr: error.stderr || '',
2292
+ recovery: 'Check that the Figma URL is a selected frame/component, FIGMA_TOKEN is valid, and network access can reach Figma. Then retry DesignToCode.',
2293
+ };
2294
+ }
2295
+ }
2296
+
2154
2297
  async webFetch(url, prompt) {
2155
2298
  if (typeof url !== 'string' || url.trim() === '') {
2156
2299
  return { success: false, error: 'url is required', recovery: 'Example: WebFetch {"url":"https://example.com"}. Include the https:// scheme.' };