sloth-d2c-mcp 1.0.4-beta70 → 1.0.4-beta72
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/cli/run.js +5 -0
- package/dist/build/component-mapping/adapter-manager.js +45 -0
- package/dist/build/component-mapping/adapters/base-adapter.js +137 -0
- package/dist/build/component-mapping/adapters/ios-adapter.js +697 -0
- package/dist/build/component-mapping/adapters/web-adapter.js +536 -0
- package/dist/build/component-mapping/index.js +32 -0
- package/dist/build/component-mapping/storage.js +142 -0
- package/dist/build/component-mapping/types.js +4 -0
- package/dist/build/config-manager/index.js +80 -0
- package/dist/build/core/prompt-builder.js +110 -0
- package/dist/build/core/sampling.js +382 -0
- package/dist/build/core/types.js +1 -0
- package/dist/build/index.js +322 -81
- package/dist/build/server.js +807 -14
- package/dist/build/utils/file-manager.js +371 -10
- package/dist/build/utils/image-matcher.js +154 -0
- package/dist/build/utils/opencv-loader.js +70 -0
- package/dist/interceptor-web/dist/build-report.json +7 -7
- package/dist/interceptor-web/dist/detail.html +1 -1
- package/dist/interceptor-web/dist/index.html +1 -1
- package/package.json +6 -3
package/dist/build/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import { convertFigmaToD2CNode, generateAbsoluteHtml, getBoundingBox, getCode, chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, } from 'sloth-d2c-node/convert';
|
|
4
4
|
import axios from 'axios';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
-
import { ConfigManager, defaultConfigData } from './config-manager/index.js';
|
|
6
|
+
import { ConfigManager, defaultConfigData, } from './config-manager/index.js';
|
|
7
7
|
import { cleanup as cleanupTauri } from './interceptor/client.js';
|
|
8
8
|
import { cleanup as cleanupVSCode, getUserInputFromVSCode, isVSCodeAvailable } from './interceptor/vscode.js';
|
|
9
9
|
import { cleanup as cleanupWeb, getUserInput } from './interceptor/web.js';
|
|
@@ -11,7 +11,9 @@ import { loadConfig, startHttpServer, stopHttpServer } from './server.js';
|
|
|
11
11
|
import { FileManager } from './utils/file-manager.js';
|
|
12
12
|
import { updateImageMapIfNeeded } from './utils/update.js';
|
|
13
13
|
import { Logger } from './utils/logger.js';
|
|
14
|
-
import { getAvailablePort,
|
|
14
|
+
import { getAvailablePort, saveImageFile, replaceImageSrc } from './utils/utils.js';
|
|
15
|
+
import { processSampling, buildNestingStructure } from './core/sampling.js';
|
|
16
|
+
import { buildComponentMappingPrompt, buildPlaceholderPrompt } from './core/prompt-builder.js';
|
|
15
17
|
// @ts-ignore
|
|
16
18
|
import * as flatted from 'flatted';
|
|
17
19
|
import { promises as fs } from 'fs';
|
|
@@ -42,7 +44,7 @@ const mcpServer = new D2CMcpServer({
|
|
|
42
44
|
},
|
|
43
45
|
});
|
|
44
46
|
const configManager = new ConfigManager('d2c-mcp');
|
|
45
|
-
const fileManager = new FileManager('d2c-mcp');
|
|
47
|
+
export const fileManager = new FileManager('d2c-mcp');
|
|
46
48
|
// 注册 Figma 转码拦截工具
|
|
47
49
|
mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
48
50
|
fileKey: z.string().describe('The key of the Figma file to fetch, often found in a provided URL like figma.com/(file|design)/<fileKey>/...'),
|
|
@@ -58,6 +60,17 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
58
60
|
// 检测 VSCode 扩展是否可用
|
|
59
61
|
const isVSCodePluginAvailable = await isVSCodeAvailable();
|
|
60
62
|
Logger.log(`VSCode 扩展检测结果: ${isVSCodePluginAvailable ? '可用' : '不可用'}`);
|
|
63
|
+
let root = './';
|
|
64
|
+
try {
|
|
65
|
+
const rootRes = await mcpServer.server.listRoots();
|
|
66
|
+
Logger.log('获取根目录:', rootRes);
|
|
67
|
+
root = rootRes.roots[0]?.uri?.slice(7) || './';
|
|
68
|
+
// 设置 fileManager 的工作目录根路径
|
|
69
|
+
fileManager.setWorkspaceRoot(root);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
Logger.log('获取根目录时出错:', error);
|
|
73
|
+
}
|
|
61
74
|
// 没有配置figmaApiKey,无法预览,直接唤起配置页面
|
|
62
75
|
if (!config.mcp?.figmaApiKey) {
|
|
63
76
|
hasLaunchWebview = true;
|
|
@@ -72,6 +85,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
72
85
|
Logger.log('VSCode 扩展不可用,降级到网页浏览器');
|
|
73
86
|
configDataString = await getUserInput({ fileKey: fileKey, nodeId: nodeId });
|
|
74
87
|
}
|
|
88
|
+
console.log('getting configDataString', configDataString);
|
|
75
89
|
if (!configDataString || configDataString.trim() === '') {
|
|
76
90
|
throw new Error('未提供有效的转码配置');
|
|
77
91
|
}
|
|
@@ -88,6 +102,11 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
88
102
|
};
|
|
89
103
|
mcpServer.setConfig(config.mcp);
|
|
90
104
|
configManager.save(config);
|
|
105
|
+
if (rest) {
|
|
106
|
+
const { promptSetting, ...restConfig } = rest;
|
|
107
|
+
await fileManager.saveConfigSetting(fileKey, nodeId, restConfig);
|
|
108
|
+
Logger.log(`已保存 configSetting 到 fileKey: ${fileKey}, nodeId: ${nodeId}`);
|
|
109
|
+
}
|
|
91
110
|
// 将 groupsData 保存到 fileManager 按 nodeId 存储
|
|
92
111
|
if (groupsData && groupsData.length > 0) {
|
|
93
112
|
await fileManager.saveGroupsData(fileKey, nodeId, groupsData);
|
|
@@ -153,6 +172,11 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
153
172
|
};
|
|
154
173
|
mcpServer.setConfig(config.mcp);
|
|
155
174
|
configManager.save(config);
|
|
175
|
+
if (rest) {
|
|
176
|
+
const { promptSetting, ...restConfig } = rest;
|
|
177
|
+
await fileManager.saveConfigSetting(fileKey, nodeId, restConfig);
|
|
178
|
+
Logger.log(`已保存 configSetting 到 fileKey: ${fileKey}, nodeId: ${nodeId}`);
|
|
179
|
+
}
|
|
156
180
|
// 将 groupsData 保存到 fileManager 按 nodeId 存储
|
|
157
181
|
if (groupsData && groupsData.length > 0) {
|
|
158
182
|
await fileManager.saveGroupsData(fileKey, nodeId, groupsData);
|
|
@@ -179,91 +203,75 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
179
203
|
const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
|
|
180
204
|
const convertConfig = config.fileConfigs?.[fileKey] || defaultConfigData;
|
|
181
205
|
// 获取提示词,优先使用用户保存的提示词,否则使用默认提示词
|
|
206
|
+
const frameworkPrompt = savedPromptSetting?.frameworkGuidePrompt;
|
|
182
207
|
const chunkPrompt = savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt;
|
|
183
208
|
const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
|
|
184
209
|
const finalPrompt = savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt;
|
|
185
|
-
let root = './';
|
|
186
|
-
try {
|
|
187
|
-
const rootRes = await mcpServer.server.listRoots();
|
|
188
|
-
Logger.log('获取根目录:', rootRes);
|
|
189
|
-
root = rootRes.roots[0]?.uri?.slice(7) || './';
|
|
190
|
-
}
|
|
191
|
-
catch (error) {
|
|
192
|
-
Logger.log('获取根目录时出错:', error);
|
|
193
|
-
}
|
|
194
210
|
const codeSnippets = [];
|
|
195
211
|
let isSupportSampling = true;
|
|
196
|
-
//
|
|
212
|
+
// 收集已映射的组件信息,用于在最终写入时添加提示词
|
|
213
|
+
const componentMappings = [];
|
|
214
|
+
// 收集所有组的组件上下文信息,用于在最终提示词中显示
|
|
215
|
+
const componentContexts = [];
|
|
216
|
+
// 构建采样配置
|
|
217
|
+
const samplingConfig = {
|
|
218
|
+
d2cNodeList,
|
|
219
|
+
imageMap,
|
|
220
|
+
convertConfig,
|
|
221
|
+
chunkPrompt,
|
|
222
|
+
frameworkPrompt,
|
|
223
|
+
mcpServer,
|
|
224
|
+
componentMappings,
|
|
225
|
+
codeSnippets,
|
|
226
|
+
componentContexts,
|
|
227
|
+
};
|
|
228
|
+
// 深度遍历采样:针对每个分组生成 AI 相对布局树并转码
|
|
197
229
|
if (groupsData && groupsData?.length > 0) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// 重置节点位置
|
|
207
|
-
nodeList = resetNodeListPosition(nodeList);
|
|
208
|
-
// 获取代码
|
|
209
|
-
const code = await getCode({
|
|
210
|
-
d2cNodeList: nodeList,
|
|
211
|
-
config: convertConfig,
|
|
212
|
-
});
|
|
213
|
-
console.log('code', code);
|
|
214
|
-
// 使用 imageMap 的 path 替换 code 中对应的 src
|
|
215
|
-
let replacedCode = replaceImageSrc(code, imageMap);
|
|
216
|
-
const componentName = `Group${group.groupIndex + 1}`;
|
|
217
|
-
const codeWithCustomName = replacedCode.replace(/\bApp\b/g, componentName);
|
|
218
|
-
try {
|
|
219
|
-
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
220
|
-
messages: [
|
|
221
|
-
{
|
|
222
|
-
role: 'user',
|
|
223
|
-
content: {
|
|
224
|
-
type: 'text',
|
|
225
|
-
text: chunkPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || ''),
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
role: 'user',
|
|
230
|
-
content: {
|
|
231
|
-
type: 'text',
|
|
232
|
-
text: codeWithCustomName + (group.userPrompt ? '\n以下是用户针对该代码的优化提出的补充说明和要求,请在优化代码时特别关注这些指导并尽可能实现:\n' + group.userPrompt : ''),
|
|
233
|
-
},
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
maxTokens: 48000,
|
|
237
|
-
}, { timeout: 5 * 60 * 1000 });
|
|
238
|
-
Logger.log('采样成功', text);
|
|
239
|
-
const { code: chunkCode, componentName } = extractCodeAndComponents(text)[0];
|
|
240
|
-
Logger.log('采样解析成功', chunkCode, componentName);
|
|
241
|
-
group.name = componentName;
|
|
242
|
-
codeSnippets.push(chunkCode);
|
|
243
|
-
}
|
|
244
|
-
catch (e) {
|
|
245
|
-
Logger.log('调用采样出错', e);
|
|
246
|
-
codeSnippets.push(codeWithCustomName + (group.userPrompt ? '\n以下是用户针对该代码的优化提出的补充说明和要求,请在优化代码时特别关注这些指导并尽可能实现:\n' + group.userPrompt : ''));
|
|
230
|
+
isSupportSampling = await processSampling(groupsData, samplingConfig);
|
|
231
|
+
// 采样完成后,更新组件名并保存到文件
|
|
232
|
+
try {
|
|
233
|
+
const groupsDataToSave = groupsData.map(group => {
|
|
234
|
+
// 将提取的组件名更新到 componentName(仅标记的分组)
|
|
235
|
+
if (group.marked && group.name) {
|
|
236
|
+
group.componentName = group.name;
|
|
237
|
+
Logger.log(`组 ${group.groupIndex} 已标记,更新组件名: ${group.componentName}`);
|
|
247
238
|
}
|
|
239
|
+
// 去掉 nodeList 后返回
|
|
240
|
+
const { nodeList, ...groupWithoutNodeList } = group;
|
|
241
|
+
return groupWithoutNodeList;
|
|
248
242
|
});
|
|
249
|
-
await
|
|
243
|
+
await fileManager.saveGroupsData(fileKey, nodeId, groupsDataToSave);
|
|
244
|
+
Logger.log(`已保存更新后的 groupsData,包含 ${groupsData.filter(g => g.marked).length} 个标记的组件`);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
Logger.error(`保存 groupsData 失败:`, error);
|
|
250
248
|
}
|
|
251
249
|
}
|
|
252
250
|
// 分组外元素:注入分组占位并进行整合采样
|
|
251
|
+
// 构建嵌套结构,只收集根组(第一层)的元素
|
|
252
|
+
const nestingInfo = buildNestingStructure(groupsData || []);
|
|
253
253
|
const groupedElementIds = new Set();
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
254
|
+
// 只收集根组的元素ID
|
|
255
|
+
nestingInfo.rootGroups.forEach((rootIndex) => {
|
|
256
|
+
const rootGroup = nestingInfo.groupMap.get(rootIndex);
|
|
257
|
+
if (rootGroup) {
|
|
258
|
+
rootGroup.elements.forEach((id) => {
|
|
259
|
+
groupedElementIds.add(id);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
258
262
|
});
|
|
263
|
+
Logger.log(`收集到 ${groupedElementIds.size} 个根组元素ID,根组数量: ${nestingInfo.rootGroups.length}`);
|
|
259
264
|
// 分组外元素
|
|
260
265
|
const ungroupedNodeList = d2cNodeList.filter((node) => !groupedElementIds.has(node.id));
|
|
261
266
|
if (ungroupedNodeList.length > 0) {
|
|
262
|
-
//
|
|
263
|
-
|
|
267
|
+
// 只注入根组占位符
|
|
268
|
+
nestingInfo.rootGroups.forEach((rootIndex) => {
|
|
269
|
+
const group = nestingInfo.groupMap.get(rootIndex);
|
|
270
|
+
if (!group)
|
|
271
|
+
return;
|
|
264
272
|
const { x, y, width, height, absoluteRenderX, absoluteRenderY, absoluteRenderWidth, absoluteRenderHeight } = getBoundingBox(group.nodeList);
|
|
265
273
|
ungroupedNodeList.push({
|
|
266
|
-
id: 'G_' +
|
|
274
|
+
id: 'G_' + rootIndex,
|
|
267
275
|
name: group.name || `Group${group.groupIndex + 1}`,
|
|
268
276
|
x,
|
|
269
277
|
y,
|
|
@@ -290,12 +298,19 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
290
298
|
});
|
|
291
299
|
});
|
|
292
300
|
console.log('ungroupedNodeList', ungroupedNodeList);
|
|
301
|
+
// 检测占位符元素(分组占位符)
|
|
302
|
+
const placeholders = ungroupedNodeList
|
|
303
|
+
.filter((node) => node.type === 'COMPONENT' && node.id?.startsWith('G_'))
|
|
304
|
+
.map((node) => ({
|
|
305
|
+
name: node.name || 'Unknown',
|
|
306
|
+
}));
|
|
307
|
+
const placeholderPrompt = buildPlaceholderPrompt(placeholders);
|
|
308
|
+
Logger.log(`整合采样检测到 ${placeholders.length} 个分组占位符`);
|
|
293
309
|
// 获取代码
|
|
294
310
|
const code = await getCode({
|
|
295
311
|
d2cNodeList: ungroupedNodeList,
|
|
296
312
|
config: convertConfig,
|
|
297
313
|
});
|
|
298
|
-
console.log('code', code);
|
|
299
314
|
// 使用 imageMap 的 path 替换 code 中对应的 src
|
|
300
315
|
const replacedCode = replaceImageSrc(code, imageMap);
|
|
301
316
|
// 整合采样
|
|
@@ -307,7 +322,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
307
322
|
role: 'user',
|
|
308
323
|
content: {
|
|
309
324
|
type: 'text',
|
|
310
|
-
text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || ''),
|
|
325
|
+
text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + placeholderPrompt,
|
|
311
326
|
},
|
|
312
327
|
},
|
|
313
328
|
{
|
|
@@ -329,23 +344,27 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
329
344
|
catch (e) {
|
|
330
345
|
isSupportSampling = false;
|
|
331
346
|
Logger.log('调用分组外元素采样出错,降级到传统模式', e);
|
|
332
|
-
codeSnippets.unshift(replacedCode);
|
|
347
|
+
codeSnippets.unshift(placeholderPrompt + '\n' + replacedCode);
|
|
333
348
|
}
|
|
334
349
|
}
|
|
335
350
|
// 保存图片文件
|
|
336
351
|
saveImageFile({ imageMap, root });
|
|
337
352
|
Logger.log('处理完成,生成的代码片段数量:', codeSnippets.length);
|
|
353
|
+
Logger.log('已映射的组件数量:', componentMappings.length);
|
|
354
|
+
Logger.log('组件上下文数量:', componentContexts.length);
|
|
355
|
+
// 构建组件映射提示词(包含组件映射和组件上下文)
|
|
356
|
+
const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts);
|
|
338
357
|
// 使用提示词(已包含默认值)
|
|
339
358
|
return {
|
|
340
359
|
content: [
|
|
341
360
|
isSupportSampling
|
|
342
361
|
? {
|
|
343
362
|
type: 'text',
|
|
344
|
-
text: finalPrompt,
|
|
363
|
+
text: finalPrompt + componentMappingPrompt,
|
|
345
364
|
}
|
|
346
365
|
: {
|
|
347
366
|
type: 'text',
|
|
348
|
-
text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || ''),
|
|
367
|
+
text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + componentMappingPrompt,
|
|
349
368
|
},
|
|
350
369
|
...codeSnippets.map((code) => {
|
|
351
370
|
return {
|
|
@@ -396,9 +415,11 @@ async function main() {
|
|
|
396
415
|
console.log('使用方法: sloth <命令> [选项]');
|
|
397
416
|
console.log('');
|
|
398
417
|
console.log('可用命令:');
|
|
399
|
-
console.log(' config
|
|
400
|
-
console.log(' page
|
|
401
|
-
console.log('
|
|
418
|
+
console.log(' config 显示配置文件信息');
|
|
419
|
+
console.log(' page 显示 interceptor-web 页面地址');
|
|
420
|
+
console.log(' framework 添加或管理框架配置');
|
|
421
|
+
console.log(' clear 清理缓存文件');
|
|
422
|
+
console.log(' version 显示版本信息');
|
|
402
423
|
console.log('');
|
|
403
424
|
console.log('全局选项:');
|
|
404
425
|
console.log(' --help, -h 显示帮助信息');
|
|
@@ -411,6 +432,9 @@ async function main() {
|
|
|
411
432
|
console.log(' sloth config --path # 仅显示配置路径');
|
|
412
433
|
console.log(' sloth page # 显示页面地址');
|
|
413
434
|
console.log(' sloth page --list # 列出所有页面文件');
|
|
435
|
+
console.log(' sloth framework react # 添加 react 框架');
|
|
436
|
+
console.log(' sloth framework --list # 列出所有框架');
|
|
437
|
+
console.log(' sloth clear # 清理缓存文件');
|
|
414
438
|
console.log(' sloth version # 显示版本信息');
|
|
415
439
|
console.log(' sloth --version # 显示版本信息');
|
|
416
440
|
process.exit(0);
|
|
@@ -454,7 +478,7 @@ async function main() {
|
|
|
454
478
|
config = await configManager.load();
|
|
455
479
|
}
|
|
456
480
|
catch (err) {
|
|
457
|
-
error = err.
|
|
481
|
+
error = err instanceof Error ? err.message : String(err);
|
|
458
482
|
}
|
|
459
483
|
}
|
|
460
484
|
if (isJsonOutput) {
|
|
@@ -581,7 +605,7 @@ async function main() {
|
|
|
581
605
|
catch (error) {
|
|
582
606
|
const errorInfo = {
|
|
583
607
|
error: '读取目录失败',
|
|
584
|
-
message: error.
|
|
608
|
+
message: error instanceof Error ? error.message : String(error),
|
|
585
609
|
directory: interceptorWebPath,
|
|
586
610
|
};
|
|
587
611
|
if (isJsonOutput) {
|
|
@@ -640,7 +664,7 @@ async function main() {
|
|
|
640
664
|
catch (error) {
|
|
641
665
|
const errorInfo = {
|
|
642
666
|
error: '获取页面信息失败',
|
|
643
|
-
message: error.
|
|
667
|
+
message: error instanceof Error ? error.message : String(error),
|
|
644
668
|
};
|
|
645
669
|
if (isJsonOutput) {
|
|
646
670
|
console.log(JSON.stringify(errorInfo, null, 2));
|
|
@@ -656,6 +680,114 @@ async function main() {
|
|
|
656
680
|
fileManager.cleanup();
|
|
657
681
|
process.exit(0);
|
|
658
682
|
}
|
|
683
|
+
// 检查是否是 framework 命令
|
|
684
|
+
if (process.env.SLOTH_COMMAND === 'framework') {
|
|
685
|
+
const configManager = new ConfigManager('d2c-mcp');
|
|
686
|
+
// 获取子命令参数
|
|
687
|
+
const frameworkArgsStr = process.env.SLOTH_FRAMEWORK_ARGS || '[]';
|
|
688
|
+
const frameworkArgs = JSON.parse(frameworkArgsStr);
|
|
689
|
+
const isJsonOutput = frameworkArgs.includes('--json');
|
|
690
|
+
// 处理帮助参数
|
|
691
|
+
if (frameworkArgs.includes('--help') || frameworkArgs.includes('-h')) {
|
|
692
|
+
console.log('使用方法: sloth framework <框架名称> [选项]');
|
|
693
|
+
console.log('');
|
|
694
|
+
console.log('添加或管理框架配置');
|
|
695
|
+
console.log('');
|
|
696
|
+
console.log('选项:');
|
|
697
|
+
console.log(' --help, -h 显示帮助信息');
|
|
698
|
+
console.log(' --list 列出所有已添加的框架');
|
|
699
|
+
console.log(' --json 以 JSON 格式输出');
|
|
700
|
+
console.log('');
|
|
701
|
+
console.log('示例:');
|
|
702
|
+
console.log(' sloth framework react # 添加 react 框架');
|
|
703
|
+
console.log(' sloth framework vue # 添加 vue 框架');
|
|
704
|
+
console.log(' sloth framework --list # 列出所有框架');
|
|
705
|
+
process.exit(0);
|
|
706
|
+
}
|
|
707
|
+
// 处理列出所有框架
|
|
708
|
+
if (frameworkArgs.includes('--list')) {
|
|
709
|
+
try {
|
|
710
|
+
const frameworks = await configManager.getFrameworks();
|
|
711
|
+
if (isJsonOutput) {
|
|
712
|
+
console.log(JSON.stringify({ frameworks }, null, 2));
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
if (frameworks.length === 0) {
|
|
716
|
+
console.log('暂无已添加的框架');
|
|
717
|
+
}
|
|
718
|
+
else {
|
|
719
|
+
console.log('已添加的框架:');
|
|
720
|
+
frameworks.forEach((framework, index) => {
|
|
721
|
+
console.log(` ${index + 1}. ${framework}`);
|
|
722
|
+
});
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
catch (error) {
|
|
727
|
+
if (isJsonOutput) {
|
|
728
|
+
console.log(JSON.stringify({ error: error.toString() }, null, 2));
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
console.log(`获取框架列表失败: ${error}`);
|
|
732
|
+
}
|
|
733
|
+
process.exit(1);
|
|
734
|
+
}
|
|
735
|
+
process.exit(0);
|
|
736
|
+
}
|
|
737
|
+
// 添加框架
|
|
738
|
+
if (frameworkArgs.length === 0) {
|
|
739
|
+
console.log('错误: 请提供框架名称');
|
|
740
|
+
console.log('使用 "sloth framework --help" 查看帮助信息');
|
|
741
|
+
process.exit(1);
|
|
742
|
+
}
|
|
743
|
+
const frameworkName = frameworkArgs[0];
|
|
744
|
+
try {
|
|
745
|
+
// 添加框架到配置
|
|
746
|
+
await configManager.addFramework(frameworkName);
|
|
747
|
+
// 检查框架配置文件是否存在
|
|
748
|
+
const configExists = await configManager.frameworkConfigExists(frameworkName);
|
|
749
|
+
if (!configExists) {
|
|
750
|
+
// 创建默认的框架配置文件
|
|
751
|
+
await configManager.saveFrameworkConfig(frameworkName, {
|
|
752
|
+
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
753
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
754
|
+
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
const frameworkPath = configManager.getFrameworkConfigPath(frameworkName);
|
|
758
|
+
if (isJsonOutput) {
|
|
759
|
+
console.log(JSON.stringify({
|
|
760
|
+
success: true,
|
|
761
|
+
framework: frameworkName,
|
|
762
|
+
configPath: frameworkPath,
|
|
763
|
+
created: !configExists
|
|
764
|
+
}, null, 2));
|
|
765
|
+
}
|
|
766
|
+
else {
|
|
767
|
+
console.log(`✓ 框架 "${frameworkName}" 添加成功`);
|
|
768
|
+
console.log(`配置文件路径: ${frameworkPath}`);
|
|
769
|
+
if (!configExists) {
|
|
770
|
+
console.log('已创建默认配置文件');
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
console.log('使用现有配置文件');
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
catch (error) {
|
|
778
|
+
if (isJsonOutput) {
|
|
779
|
+
console.log(JSON.stringify({
|
|
780
|
+
success: false,
|
|
781
|
+
error: error.toString()
|
|
782
|
+
}, null, 2));
|
|
783
|
+
}
|
|
784
|
+
else {
|
|
785
|
+
console.log(`添加框架失败: ${error}`);
|
|
786
|
+
}
|
|
787
|
+
process.exit(1);
|
|
788
|
+
}
|
|
789
|
+
process.exit(0);
|
|
790
|
+
}
|
|
659
791
|
try {
|
|
660
792
|
// 先输出基本启动信息到控制台,确保即使 VSCode 日志失败也能看到启动过程
|
|
661
793
|
console.log(`[${new Date().toISOString()}] Starting Figma transcoding interceptor MCP server...`);
|
|
@@ -728,3 +860,112 @@ process.on('SIGTERM', async () => {
|
|
|
728
860
|
process.exit(0);
|
|
729
861
|
});
|
|
730
862
|
main();
|
|
863
|
+
// 获取命令行参数
|
|
864
|
+
const args = process.argv.slice(2);
|
|
865
|
+
// 处理 --log 参数:开启文件日志(同时保留控制台输出)
|
|
866
|
+
const logArgIndex = args.findIndex((a) => a === '--log' || a.startsWith('--log='));
|
|
867
|
+
if (logArgIndex !== -1) {
|
|
868
|
+
let logFilePath = 'runtime.log';
|
|
869
|
+
const raw = args[logArgIndex];
|
|
870
|
+
// 移除 --log 参数,避免传递给后续程序
|
|
871
|
+
args.splice(logArgIndex, 1);
|
|
872
|
+
process.argv = [process.argv[0], process.argv[1], ...args];
|
|
873
|
+
try {
|
|
874
|
+
const { createWriteStream, mkdirSync, existsSync } = await import('node:fs');
|
|
875
|
+
const path = await import('node:path');
|
|
876
|
+
const util = await import('node:util');
|
|
877
|
+
if (raw.startsWith('--log=')) {
|
|
878
|
+
const provided = raw.slice('--log='.length).trim();
|
|
879
|
+
if (provided) {
|
|
880
|
+
logFilePath = path.isAbsolute(provided) ? provided : path.resolve(process.cwd(), provided);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else {
|
|
884
|
+
logFilePath = path.resolve(process.cwd(), logFilePath);
|
|
885
|
+
}
|
|
886
|
+
const logDir = path.dirname(logFilePath);
|
|
887
|
+
if (!existsSync(logDir)) {
|
|
888
|
+
mkdirSync(logDir, { recursive: true });
|
|
889
|
+
}
|
|
890
|
+
const logStream = createWriteStream(logFilePath, { flags: 'a' });
|
|
891
|
+
const originalConsole = {
|
|
892
|
+
log: console.log,
|
|
893
|
+
info: console.info,
|
|
894
|
+
warn: console.warn,
|
|
895
|
+
error: console.error,
|
|
896
|
+
};
|
|
897
|
+
const writeLine = (...args) => {
|
|
898
|
+
try {
|
|
899
|
+
const line = util.format(...args) + '\n';
|
|
900
|
+
logStream.write(line);
|
|
901
|
+
}
|
|
902
|
+
catch { }
|
|
903
|
+
};
|
|
904
|
+
console.log = (...a) => {
|
|
905
|
+
originalConsole.log(...a);
|
|
906
|
+
writeLine(...a);
|
|
907
|
+
};
|
|
908
|
+
console.info = (...a) => {
|
|
909
|
+
originalConsole.info(...a);
|
|
910
|
+
writeLine(...a);
|
|
911
|
+
};
|
|
912
|
+
console.warn = (...a) => {
|
|
913
|
+
originalConsole.warn(...a);
|
|
914
|
+
writeLine(...a);
|
|
915
|
+
};
|
|
916
|
+
console.error = (...a) => {
|
|
917
|
+
originalConsole.error(...a);
|
|
918
|
+
writeLine(...a);
|
|
919
|
+
};
|
|
920
|
+
process.env.SLOTH_LOG_FILE = logFilePath;
|
|
921
|
+
originalConsole.log(`[sloth] 日志已开启,写入: ${logFilePath}`);
|
|
922
|
+
const close = () => {
|
|
923
|
+
try {
|
|
924
|
+
logStream.end();
|
|
925
|
+
}
|
|
926
|
+
catch { }
|
|
927
|
+
};
|
|
928
|
+
process.on('exit', close);
|
|
929
|
+
process.on('SIGINT', close);
|
|
930
|
+
process.on('SIGTERM', close);
|
|
931
|
+
process.on('uncaughtException', close);
|
|
932
|
+
process.on('unhandledRejection', close);
|
|
933
|
+
}
|
|
934
|
+
catch (e) {
|
|
935
|
+
// 记录但不中断启动
|
|
936
|
+
console.warn('[sloth] 启用文件日志失败:', e);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
// 检查是否是 config 命令
|
|
940
|
+
if (args[0] === 'config') {
|
|
941
|
+
// 设置环境变量标识这是 config 命令
|
|
942
|
+
process.env.SLOTH_COMMAND = 'config';
|
|
943
|
+
// 传递子命令参数
|
|
944
|
+
process.env.SLOTH_CONFIG_ARGS = JSON.stringify(args.slice(1));
|
|
945
|
+
}
|
|
946
|
+
else if (args[0] === 'page') {
|
|
947
|
+
// 设置环境变量标识这是 page 命令
|
|
948
|
+
process.env.SLOTH_COMMAND = 'page';
|
|
949
|
+
// 传递子命令参数
|
|
950
|
+
process.env.SLOTH_PAGE_ARGS = JSON.stringify(args.slice(1));
|
|
951
|
+
}
|
|
952
|
+
else if (args[0] === 'version' || args[0] === '--version' || args[0] === '-v') {
|
|
953
|
+
// 设置环境变量标识这是 version 命令
|
|
954
|
+
process.env.SLOTH_COMMAND = 'version';
|
|
955
|
+
}
|
|
956
|
+
else if (args[0] === '--help' || args[0] === '-h' || args.length === 0) {
|
|
957
|
+
// 显示主帮助信息
|
|
958
|
+
process.env.SLOTH_COMMAND = 'help';
|
|
959
|
+
}
|
|
960
|
+
else if (args[0] === 'clear') {
|
|
961
|
+
// 设置环境变量标识这是 clear 命令
|
|
962
|
+
process.env.SLOTH_COMMAND = 'clear';
|
|
963
|
+
}
|
|
964
|
+
else {
|
|
965
|
+
// 设置环境变量为CLI模式
|
|
966
|
+
process.env.NODE_ENV = 'cli';
|
|
967
|
+
// 确保--stdio参数被传递
|
|
968
|
+
if (!process.argv.includes('--stdio')) {
|
|
969
|
+
process.argv.push('--stdio');
|
|
970
|
+
}
|
|
971
|
+
}
|