sloth-d2c-mcp 1.0.4-beta100

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.
Files changed (34) hide show
  1. package/README.md +83 -0
  2. package/cli/run.js +328 -0
  3. package/cli/sloth-server.log +1622 -0
  4. package/dist/build/config-manager/index.js +240 -0
  5. package/dist/build/core/prompt-builder.js +366 -0
  6. package/dist/build/core/sampling.js +375 -0
  7. package/dist/build/core/types.js +1 -0
  8. package/dist/build/index.js +852 -0
  9. package/dist/build/interceptor/client.js +142 -0
  10. package/dist/build/interceptor/vscode.js +143 -0
  11. package/dist/build/interceptor/web.js +28 -0
  12. package/dist/build/plugin/index.js +4 -0
  13. package/dist/build/plugin/loader.js +349 -0
  14. package/dist/build/plugin/manager.js +129 -0
  15. package/dist/build/plugin/types.js +6 -0
  16. package/dist/build/server.js +2116 -0
  17. package/dist/build/socket-client.js +166 -0
  18. package/dist/build/socket-server.js +260 -0
  19. package/dist/build/utils/client-capabilities.js +143 -0
  20. package/dist/build/utils/extract.js +168 -0
  21. package/dist/build/utils/file-manager.js +868 -0
  22. package/dist/build/utils/image-matcher.js +154 -0
  23. package/dist/build/utils/logger.js +90 -0
  24. package/dist/build/utils/opencv-loader.js +70 -0
  25. package/dist/build/utils/prompt-parser.js +46 -0
  26. package/dist/build/utils/tj.js +139 -0
  27. package/dist/build/utils/update.js +100 -0
  28. package/dist/build/utils/utils.js +184 -0
  29. package/dist/build/utils/vscode-logger.js +133 -0
  30. package/dist/build/utils/webpack-substitutions.js +196 -0
  31. package/dist/interceptor-web/dist/build-report.json +18 -0
  32. package/dist/interceptor-web/dist/detail.html +1 -0
  33. package/dist/interceptor-web/dist/index.html +1 -0
  34. package/package.json +96 -0
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # Sloth D2C MCP
2
+
3
+ ## 简介
4
+
5
+ Sloth D2C 设计稿转代码工具,包括 Sloth D2C 设计稿转代码 Figma 插件及 Sloth D2C MCP 工具。
6
+
7
+ Sloth D2C 设计稿转代码插件,面向前端开发与设计团队的智能化工具,支持设计稿快速转前端代码,并通过协同 MCP 工具,写入生产级前端代码;同时支持文本/图片+Prompt输入AI,生成目标风格页面并转译至Figma节点。
8
+
9
+ Sloth D2C MCP 工具,获取插件推送的转码数据,通过拦截页面配置框架选择、转换倍率、颜色格式、图片存储、提示词编辑等基础设置,同时支持圈选分组、组件映射、模块标记等高级设置,以生成提示词指导 Agent 生成目标代码并写入。
10
+
11
+ ## 核心能力
12
+
13
+ <!-- ### 节点清洗 -->
14
+ <!-- 待补充 -->
15
+
16
+ ### 效果预览及源码调整
17
+
18
+ 插件支持生成代码页面预览,同时支持源码二次编辑。在插件转码后,提供代码编辑器编辑源码,实时预览效果。
19
+
20
+ ### AI 生成页面
21
+
22
+ 插件支持文本/图片+Prompt 输入 AI,生成目标风格页面代码并预览。同时支持转译至成 Figma 节点并插入 Figma 设计稿。
23
+
24
+ ### 自研 MCP 拦截器
25
+
26
+ 设计了创新的 MCP 拦截器。利用 await 挂起 MCP 的核心请求,为用户创造了一个交互窗口。通过配置拦截及数据推送突破 RESTful API 限制,最大化还原度。
27
+
28
+ ### 支持多种框架
29
+
30
+ MCP 默认支持 React、Vue 代码生成,系统内置提示词默认配置。同时支持新增自定义框架,配置自定义框架生成提示词。
31
+
32
+ ### 多种图片存储方式
33
+
34
+ 支持图片本地存储、OSS 存储及自定义图片上传方式。
35
+
36
+ ### 圈选分组与采样
37
+
38
+ 支持圈选分组与智能采样,圈选分组功能允许你在设计稿预览页中手动划分代码结构区域,每个区域分别在不同的采用请求流程进行代码生成,提高转码效率和准确性。
39
+
40
+ ### 组件映射
41
+
42
+ 从项目中选择组件文件,将项目中的组件映射对应圈选元素,生成代码时直接引用对应组件。
43
+
44
+ ### 模块标记
45
+
46
+ 对圈选后的模块进行标记,方便其他设计稿类似模块的直接复用,无需重复生成。
47
+
48
+ ## 安装 MCP
49
+
50
+ ```bash
51
+ # 使用 pnpm(推荐)
52
+ pnpm install sloth-d2c-mcp -g
53
+
54
+ # 或使用 npm
55
+ npm install sloth-d2c-mcp -g
56
+
57
+ # 验证安装
58
+ sloth
59
+ ```
60
+
61
+ ## 配置 MCP
62
+
63
+ **TME-Continue**
64
+
65
+ ```yaml
66
+ - name: sloth-d2c-mcp
67
+ command: sloth
68
+ connectionTimeout: 3000000
69
+ args:
70
+ - --stdio
71
+ ```
72
+
73
+ **Copilot**
74
+
75
+ ```json
76
+ "d2c-cli": {
77
+ "type": "stdio",
78
+ "command": "sloth",
79
+ "args": ["--stdio"]
80
+ }
81
+ ```
82
+
83
+ > **提示**:推荐使用 Claude AI 模型。若需转码长设计稿,请开启 MCP 采样功能以优化转码策略(TME-Continue 和 Copilot 均支持)。
package/cli/run.js ADDED
@@ -0,0 +1,328 @@
1
+ #!/usr/bin/env node
2
+
3
+ // 获取命令行参数
4
+ const args = process.argv.slice(2);
5
+
6
+ // 处理 --log 参数:开启文件日志(同时保留控制台输出)
7
+ const logArgIndex = args.findIndex((a) => a === '--log' || a.startsWith('--log='));
8
+ if (logArgIndex !== -1) {
9
+ let logFilePath = 'runtime.log';
10
+ const raw = args[logArgIndex];
11
+ // 移除 --log 参数,避免传递给后续程序
12
+ args.splice(logArgIndex, 1);
13
+ process.argv = [process.argv[0], process.argv[1], ...args];
14
+
15
+ try {
16
+ const { createWriteStream, mkdirSync, existsSync } = await import('node:fs');
17
+ const path = await import('node:path');
18
+ const util = await import('node:util');
19
+
20
+ if (raw.startsWith('--log=')) {
21
+ const provided = raw.slice('--log='.length).trim();
22
+ if (provided) {
23
+ logFilePath = path.isAbsolute(provided) ? provided : path.resolve(process.cwd(), provided);
24
+ }
25
+ } else {
26
+ logFilePath = path.resolve(process.cwd(), logFilePath);
27
+ }
28
+
29
+ const logDir = path.dirname(logFilePath);
30
+ if (!existsSync(logDir)) {
31
+ mkdirSync(logDir, { recursive: true });
32
+ }
33
+ const logStream = createWriteStream(logFilePath, { flags: 'a' });
34
+
35
+ const originalConsole = {
36
+ log: console.log,
37
+ info: console.info,
38
+ warn: console.warn,
39
+ error: console.error,
40
+ };
41
+
42
+ const writeLine = (...args) => {
43
+ try {
44
+ const line = util.format(...args) + '\n';
45
+ logStream.write(line);
46
+ } catch {}
47
+ };
48
+
49
+ console.log = (...a) => { originalConsole.log(...a); writeLine(...a); };
50
+ console.info = (...a) => { originalConsole.info(...a); writeLine(...a); };
51
+ console.warn = (...a) => { originalConsole.warn(...a); writeLine(...a); };
52
+ console.error = (...a) => { originalConsole.error(...a); writeLine(...a); };
53
+
54
+ process.env.SLOTH_LOG_FILE = logFilePath;
55
+ originalConsole.log(`[sloth] 日志已开启,写入: ${logFilePath}`);
56
+
57
+ const close = () => {
58
+ try { logStream.end(); } catch {}
59
+ };
60
+ process.on('exit', close);
61
+ process.on('SIGINT', close);
62
+ process.on('SIGTERM', close);
63
+ process.on('uncaughtException', close);
64
+ process.on('unhandledRejection', close);
65
+ } catch (e) {
66
+ // 记录但不中断启动
67
+ console.warn('[sloth] 启用文件日志失败:', e);
68
+ }
69
+ }
70
+
71
+ // 检查是否是 config 命令
72
+ if (args[0] === 'config') {
73
+ // 设置环境变量标识这是 config 命令,直接返回配置路径
74
+ process.env.SLOTH_COMMAND = 'config';
75
+ process.env.SLOTH_CONFIG_ARGS = JSON.stringify(['--path']);
76
+ } else if (args[0] === 'version' || args[0] === '--version' || args[0] === '-v') {
77
+ // 设置环境变量标识这是 version 命令
78
+ process.env.SLOTH_COMMAND = 'version';
79
+ } else if (args[0] === '--help' || args[0] === '-h' || args.length === 0) {
80
+ // 显示主帮助信息
81
+ process.env.SLOTH_COMMAND = 'help';
82
+ } else if (args[0] === 'server' && args[1] === 'start') {
83
+ // run 命令:后台运行 sloth --stdio
84
+ const { spawn } = await import('node:child_process');
85
+ // const { fileURLToPath } = await import('node:url');
86
+ // const path = await import('node:path');
87
+
88
+ // // 获取当前脚本路径(兼容 Node.js 18)
89
+ // const scriptPath = fileURLToPath(import.meta.url);
90
+ // // 指向scriptPath目录下的log
91
+ const logPath = path.resolve(path.dirname(scriptPath), 'sloth-server.log');
92
+
93
+ // // 后台启动子进程
94
+ // const child = spawn(process.execPath, [scriptPath, '--server', '--log='+logPath], {
95
+ // detached: true,
96
+ // stdio: 'ignore',
97
+ // env: { ...process.env, NODE_ENV: 'cli' }
98
+ // });
99
+
100
+ // // 解除父进程对子进程的引用,允许父进程退出
101
+ // child.unref();
102
+
103
+ // 直接调用 sloth CLI 命令
104
+ const child = spawn('sloth', ['--server', '--log='+logPath], {
105
+ detached: true,
106
+ stdio: 'ignore',
107
+ shell: true, // 需要 shell 来解析命令
108
+ env: { ...process.env }
109
+ });
110
+
111
+ child.unref();
112
+
113
+ console.log(`[sloth] 已在后台启动,PID: ${child.pid}`);
114
+ process.exit(0);
115
+ } else if (args[0] === 'server' && args[1] === 'stop') {
116
+ // stop 命令:停止监听 3100/3101 端口的 sloth 进程
117
+ const { execSync } = await import('node:child_process');
118
+ const os = await import('node:os');
119
+
120
+ const ports = [3100, 3101];
121
+ const pidsToKill = new Set();
122
+
123
+ for (const port of ports) {
124
+ try {
125
+ let output = '';
126
+ if (os.platform() === 'win32') {
127
+ // Windows: netstat -ano | findstr :PORT
128
+ output = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
129
+ const lines = output.trim().split('\n');
130
+ for (const line of lines) {
131
+ const match = line.match(/LISTENING\s+(\d+)/);
132
+ if (match) {
133
+ pidsToKill.add(match[1]);
134
+ }
135
+ }
136
+ } else {
137
+ // macOS/Linux: lsof -i :PORT
138
+ output = execSync(`lsof -i :${port} -t`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
139
+ const pids = output.trim().split('\n').filter(Boolean);
140
+ pids.forEach(pid => pidsToKill.add(pid.trim()));
141
+ }
142
+ } catch {
143
+ // 端口未被占用,忽略错误
144
+ }
145
+ }
146
+
147
+ if (pidsToKill.size === 0) {
148
+ console.log('[sloth] 未找到监听 3100/3101 端口的进程');
149
+ process.exit(0);
150
+ }
151
+
152
+ // 验证进程是否是 sloth 相关进程
153
+ let killedCount = 0;
154
+ for (const pid of pidsToKill) {
155
+ try {
156
+ let cmdline = '';
157
+ if (os.platform() === 'win32') {
158
+ cmdline = execSync(`wmic process where processid=${pid} get commandline`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
159
+ } else {
160
+ cmdline = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
161
+ }
162
+
163
+ // 检查是否是 sloth 进程
164
+ const isSlothProcess = cmdline.includes('sloth') || cmdline.includes('d2c-mcp');
165
+
166
+ if (isSlothProcess) {
167
+ process.kill(Number(pid), 'SIGTERM');
168
+ console.log(`[sloth] 已停止进程 PID: ${pid}`);
169
+ killedCount++;
170
+ } else {
171
+ console.log(`[sloth] 跳过非 sloth 进程 PID: ${pid} (${cmdline.trim().slice(0, 50)}...)`);
172
+ }
173
+ } catch (e) {
174
+ // 进程可能已经退出
175
+ console.warn(`[sloth] 无法停止进程 PID: ${pid}`, e.message);
176
+ }
177
+ }
178
+
179
+ if (killedCount === 0) {
180
+ console.log('[sloth] 未找到正在运行的 sloth 进程');
181
+ } else {
182
+ console.log(`[sloth] 共停止 ${killedCount} 个进程`);
183
+ }
184
+ process.exit(0);
185
+ } else if (args[0] === 'update') {
186
+ // update 命令:先停止进程,然后更新 sloth
187
+ const { execSync, spawnSync } = await import('node:child_process');
188
+ const os = await import('node:os');
189
+ const { fileURLToPath } = await import('node:url');
190
+ const path = await import('node:path');
191
+
192
+ console.log('[sloth] 正在停止运行中的进程...');
193
+
194
+ // 复用 stop 命令的逻辑
195
+ const ports = [3100, 3101];
196
+ const pidsToKill = new Set();
197
+
198
+ for (const port of ports) {
199
+ try {
200
+ let output = '';
201
+ if (os.platform() === 'win32') {
202
+ output = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
203
+ const lines = output.trim().split('\n');
204
+ for (const line of lines) {
205
+ const match = line.match(/LISTENING\s+(\d+)/);
206
+ if (match) {
207
+ pidsToKill.add(match[1]);
208
+ }
209
+ }
210
+ } else {
211
+ output = execSync(`lsof -i :${port} -t`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
212
+ const pids = output.trim().split('\n').filter(Boolean);
213
+ pids.forEach(pid => pidsToKill.add(pid.trim()));
214
+ }
215
+ } catch {
216
+ // 端口未被占用,忽略错误
217
+ }
218
+ }
219
+
220
+ let killedCount = 0;
221
+ for (const pid of pidsToKill) {
222
+ try {
223
+ let cmdline = '';
224
+ if (os.platform() === 'win32') {
225
+ cmdline = execSync(`wmic process where processid=${pid} get commandline`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
226
+ } else {
227
+ cmdline = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
228
+ }
229
+
230
+ const isSlothProcess = cmdline.includes('sloth') || cmdline.includes('d2c-mcp');
231
+
232
+ if (isSlothProcess) {
233
+ process.kill(Number(pid), 'SIGTERM');
234
+ console.log(`[sloth] 已停止进程 PID: ${pid}`);
235
+ killedCount++;
236
+ }
237
+ } catch (e) {
238
+ // 进程可能已经退出
239
+ }
240
+ }
241
+
242
+ if (killedCount > 0) {
243
+ console.log(`[sloth] 共停止 ${killedCount} 个进程`);
244
+ }
245
+
246
+ // 检测包管理器:通过检查 sloth 命令的安装路径来判断
247
+ let packageManager = 'npm'; // 默认使用 npm
248
+
249
+ try {
250
+ // 获取当前脚本的实际路径
251
+ const scriptPath = fileURLToPath(import.meta.url);
252
+ const scriptDir = path.dirname(scriptPath);
253
+
254
+ // 检查路径中是否包含 pnpm 相关目录
255
+ if (scriptDir.includes('pnpm') || scriptDir.includes('.pnpm')) {
256
+ packageManager = 'pnpm';
257
+ } else {
258
+ // 尝试通过 which/where 命令检查 sloth 的安装位置
259
+ try {
260
+ const whichCmd = os.platform() === 'win32' ? 'where sloth' : 'which sloth';
261
+ const slothPath = execSync(whichCmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
262
+
263
+ if (slothPath.includes('pnpm') || slothPath.includes('.pnpm')) {
264
+ packageManager = 'pnpm';
265
+ }
266
+ } catch {
267
+ // 忽略错误,使用默认的 npm
268
+ }
269
+
270
+ // 额外检查:尝试检测全局 pnpm 目录
271
+ if (packageManager === 'npm') {
272
+ try {
273
+ const pnpmRoot = execSync('pnpm root -g', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
274
+ if (scriptDir.startsWith(pnpmRoot) || scriptDir.includes(path.dirname(pnpmRoot))) {
275
+ packageManager = 'pnpm';
276
+ }
277
+ } catch {
278
+ // pnpm 未安装或命令失败,使用 npm
279
+ }
280
+ }
281
+ }
282
+ } catch {
283
+ // 检测失败,使用默认的 npm
284
+ }
285
+
286
+ console.log(`[sloth] 检测到包管理器: ${packageManager}`);
287
+ console.log('[sloth] 正在更新 sloth-d2c-mcp...');
288
+
289
+ // 执行全局安装更新
290
+ const installCmd = packageManager === 'pnpm'
291
+ ? 'pnpm install -g sloth-d2c-mcp'
292
+ : 'npm install -g sloth-d2c-mcp';
293
+
294
+ try {
295
+ const result = spawnSync(installCmd, {
296
+ shell: true,
297
+ stdio: 'inherit',
298
+ env: process.env
299
+ });
300
+
301
+ if (result.status === 0) {
302
+ console.log('[sloth] 更新完成!');
303
+ } else {
304
+ console.error('[sloth] 更新失败,退出码:', result.status);
305
+ process.exit(result.status || 1);
306
+ }
307
+ } catch (e) {
308
+ console.error('[sloth] 更新失败:', e.message);
309
+ process.exit(1);
310
+ }
311
+ process.exit(0);
312
+ } else if (args[0] === 'cache') {
313
+ // cache 命令:显示缓存目录路径
314
+ process.env.SLOTH_COMMAND = 'cache';
315
+ } else if (args[0] === '--server') {
316
+ process.env.SLOTH_COMMAND = 'server';
317
+ } else {
318
+ // 设置环境变量为CLI模式
319
+ process.env.NODE_ENV = "cli";
320
+
321
+ // 确保--stdio参数被传递
322
+ if (!process.argv.includes("--stdio")) {
323
+ process.argv.push("--stdio");
324
+ }
325
+ }
326
+
327
+ // 导入并启动主程序
328
+ import('../dist/build/index.js');