sloth-d2c-mcp 1.0.4-beta92 → 1.0.4-beta94
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 +3 -1
- package/dist/build/core/prompt-builder.js +50 -1
- package/dist/build/core/sampling.js +13 -8
- package/dist/build/index.js +54 -14
- package/dist/build/plugin/loader.js +25 -11
- package/dist/build/plugin/manager.js +2 -0
- package/dist/build/server.js +260 -83
- package/dist/build/socket-client.js +1 -1
- package/dist/build/utils/file-manager.js +62 -11
- package/dist/build/utils/prompt-parser.js +46 -0
- package/dist/interceptor-web/dist/build-report.json +5 -5
- package/dist/interceptor-web/dist/detail.html +1 -1
- package/dist/interceptor-web/dist/index.html +1 -1
- package/package.json +5 -4
package/cli/run.js
CHANGED
|
@@ -102,7 +102,7 @@ if (args[0] === 'config') {
|
|
|
102
102
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
103
103
|
|
|
104
104
|
// 后台启动子进程
|
|
105
|
-
const child = spawn(process.execPath, [scriptPath, '--
|
|
105
|
+
const child = spawn(process.execPath, [scriptPath, '--server'], {
|
|
106
106
|
detached: true,
|
|
107
107
|
stdio: 'ignore',
|
|
108
108
|
env: { ...process.env, NODE_ENV: 'cli' }
|
|
@@ -198,6 +198,8 @@ if (args[0] === 'config') {
|
|
|
198
198
|
console.log(`[sloth] 共停止 ${killedCount} 个进程`);
|
|
199
199
|
}
|
|
200
200
|
process.exit(0);
|
|
201
|
+
} else if (args[0] === '--server') {
|
|
202
|
+
process.env.SLOTH_COMMAND = 'server';
|
|
201
203
|
} else {
|
|
202
204
|
// 设置环境变量为CLI模式
|
|
203
205
|
process.env.NODE_ENV = "cli";
|
|
@@ -14,8 +14,12 @@ export function buildComponentContext(group) {
|
|
|
14
14
|
group.componentContext.forEach((comp) => {
|
|
15
15
|
componentMap.set(comp.name, comp);
|
|
16
16
|
});
|
|
17
|
-
// 替换 @组件名
|
|
17
|
+
// 替换 @组件名 为组件详细信息(只替换组件,不替换 Markdown 文件)
|
|
18
18
|
processedUserPrompt = processedUserPrompt.replace(mentionRegex, (match, display, id) => {
|
|
19
|
+
// 如果 id 包含路径分隔符,说明是文件,保持原样
|
|
20
|
+
if (id.includes('/') || id.includes('\\')) {
|
|
21
|
+
return match;
|
|
22
|
+
}
|
|
19
23
|
const component = componentMap.get(id || display);
|
|
20
24
|
if (component) {
|
|
21
25
|
const libInfo = component.lib ? ` (${component.lib})` : '';
|
|
@@ -37,6 +41,51 @@ export function buildComponentContext(group) {
|
|
|
37
41
|
}
|
|
38
42
|
return { processedUserPrompt, componentContextPrompt };
|
|
39
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* 处理 Markdown 文件上下文:解析 @文件名 并构建文档上下文提示词
|
|
46
|
+
* @param group 分组数据
|
|
47
|
+
* @returns 处理后的用户提示词和 Markdown 上下文提示词
|
|
48
|
+
*/
|
|
49
|
+
export function buildMarkdownContext(group) {
|
|
50
|
+
let processedUserPrompt = group.userPrompt || '';
|
|
51
|
+
let markdownContextPrompt = '';
|
|
52
|
+
if (group.markdownContext && Array.isArray(group.markdownContext) && group.markdownContext.length > 0) {
|
|
53
|
+
// 解析 userPrompt 中的 @文件名(react-mentions 格式:@[文件名](文件路径))
|
|
54
|
+
const mentionRegex = /@\[([^\]]+)\]\(([^)]+)\)/g;
|
|
55
|
+
const markdownMap = new Map();
|
|
56
|
+
// 构建 Markdown 文件映射表
|
|
57
|
+
group.markdownContext.forEach((file) => {
|
|
58
|
+
markdownMap.set(file.path, file);
|
|
59
|
+
});
|
|
60
|
+
// 替换 @文件名 为文件详细信息(只替换文件,不替换组件)
|
|
61
|
+
processedUserPrompt = processedUserPrompt.replace(mentionRegex, (match, _display, id) => {
|
|
62
|
+
// 如果 id 不包含路径分隔符,说明是组件名,保持原样
|
|
63
|
+
if (!id.includes('/') && !id.includes('\\')) {
|
|
64
|
+
return match;
|
|
65
|
+
}
|
|
66
|
+
const markdownFile = markdownMap.get(id);
|
|
67
|
+
if (markdownFile) {
|
|
68
|
+
return `[文档: ${markdownFile.name}]`;
|
|
69
|
+
}
|
|
70
|
+
return match; // 如果找不到文件,保持原样
|
|
71
|
+
});
|
|
72
|
+
// 构建 Markdown 上下文说明
|
|
73
|
+
markdownContextPrompt = '\n\n## 参考文档\n\n以下是用户提供的参考文档,请在代码生成时参考这些文档中的规范、示例和最佳实践:\n\n';
|
|
74
|
+
group.markdownContext.forEach((file) => {
|
|
75
|
+
markdownContextPrompt += `### 📄 ${file.name}\n\n`;
|
|
76
|
+
if (file.content) {
|
|
77
|
+
markdownContextPrompt += '```markdown\n';
|
|
78
|
+
markdownContextPrompt += file.content;
|
|
79
|
+
markdownContextPrompt += '\n```\n\n';
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
markdownContextPrompt += `**文件路径**: ${file.path}\n\n`;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
markdownContextPrompt += '**重要**:请仔细阅读上述参考文档,遵循文档中的编码规范、API 使用方式和最佳实践,确保生成的代码符合项目要求。\n';
|
|
86
|
+
}
|
|
87
|
+
return { processedUserPrompt, markdownContextPrompt };
|
|
88
|
+
}
|
|
40
89
|
/**
|
|
41
90
|
* 构建用户提示词文本
|
|
42
91
|
* @param userPrompt 用户提示词
|
|
@@ -2,7 +2,7 @@ import { getBoundingBox, getCode } from 'sloth-d2c-node/convert';
|
|
|
2
2
|
import { Logger } from '../utils/logger.js';
|
|
3
3
|
import { resetNodeListPosition, replaceImageSrc } from '../utils/utils.js';
|
|
4
4
|
import { extractCodeAndComponents } from '../utils/extract.js';
|
|
5
|
-
import { buildComponentContext, buildUserPromptText, buildPlaceholderPrompt } from './prompt-builder.js';
|
|
5
|
+
import { buildComponentContext, buildMarkdownContext, buildUserPromptText, buildPlaceholderPrompt } from './prompt-builder.js';
|
|
6
6
|
/**
|
|
7
7
|
* 构建嵌套结构映射
|
|
8
8
|
* @param groupsData 分组数据数组
|
|
@@ -46,9 +46,10 @@ export function buildNestingStructure(groupsData) {
|
|
|
46
46
|
* @param componentMappings 组件映射数组
|
|
47
47
|
* @param imageMap 图片映射
|
|
48
48
|
* @param convertConfig 转换配置
|
|
49
|
+
* @param isLocalMode 是否为 local 模式
|
|
49
50
|
* @returns 采样结果或 null
|
|
50
51
|
*/
|
|
51
|
-
export async function handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig) {
|
|
52
|
+
export async function handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig, isLocalMode) {
|
|
52
53
|
if (group.componentMapping) {
|
|
53
54
|
const componentName = group.componentMapping.name || `Group${group.groupIndex + 1}`;
|
|
54
55
|
group.name = componentName;
|
|
@@ -57,6 +58,7 @@ export async function handleComponentMapping(group, nodeList, componentMappings,
|
|
|
57
58
|
const code = await getCode({
|
|
58
59
|
d2cNodeList: resetNodeList,
|
|
59
60
|
config: convertConfig,
|
|
61
|
+
options: { isLocalMode },
|
|
60
62
|
});
|
|
61
63
|
const replacedCode = replaceImageSrc(code, imageMap);
|
|
62
64
|
componentMappings.push({
|
|
@@ -147,10 +149,10 @@ export function injectChildPlaceholders(nodeList, childResults, nestingInfo, par
|
|
|
147
149
|
* @returns 采样结果
|
|
148
150
|
*/
|
|
149
151
|
export async function sampleSingleGroup(group, nodeList, config) {
|
|
150
|
-
const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts, isSupportSampling } = config;
|
|
152
|
+
const { d2cNodeList, imageMap, convertConfig, frameworkPrompt, chunkPrompt, mcpServer, componentMappings, codeSnippets, componentContexts, isSupportSampling, isLocalMode } = config;
|
|
151
153
|
Logger.log(`开始采样组 ${group.groupIndex}, 元素数量: ${group.elements.length}, nodeList 长度: ${nodeList.length}`);
|
|
152
154
|
// 检查组件映射
|
|
153
|
-
const mappingResult = await handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig);
|
|
155
|
+
const mappingResult = await handleComponentMapping(group, nodeList, componentMappings, imageMap, convertConfig, isLocalMode);
|
|
154
156
|
if (mappingResult) {
|
|
155
157
|
Logger.log(`组 ${group.groupIndex} 已映射组件,跳过采样: ${mappingResult.componentName}`);
|
|
156
158
|
return mappingResult;
|
|
@@ -162,14 +164,16 @@ export async function sampleSingleGroup(group, nodeList, config) {
|
|
|
162
164
|
const code = await getCode({
|
|
163
165
|
d2cNodeList: resetNodeList,
|
|
164
166
|
config: convertConfig,
|
|
167
|
+
options: { isLocalMode },
|
|
165
168
|
});
|
|
166
169
|
Logger.log(`组 ${group.groupIndex} 代码生成完成,代码长度: ${code.length}`);
|
|
167
170
|
// 使用 imageMap 的 path 替换 code 中对应的 src
|
|
168
171
|
const replacedCode = replaceImageSrc(code, imageMap);
|
|
169
172
|
const componentName = `Group${group.groupIndex + 1}`;
|
|
170
173
|
const codeWithCustomName = replacedCode.replace(/\bApp\b/g, componentName);
|
|
171
|
-
//
|
|
172
|
-
const { processedUserPrompt, componentContextPrompt } = buildComponentContext(group);
|
|
174
|
+
// 处理组件上下文和 Markdown 上下文
|
|
175
|
+
const { processedUserPrompt: promptAfterComponent, componentContextPrompt } = buildComponentContext(group);
|
|
176
|
+
const { processedUserPrompt, markdownContextPrompt } = buildMarkdownContext({ ...group, userPrompt: promptAfterComponent });
|
|
173
177
|
const userPromptText = buildUserPromptText(processedUserPrompt);
|
|
174
178
|
// 收集组件上下文信息
|
|
175
179
|
if (group.componentContext && Array.isArray(group.componentContext) && group.componentContext.length > 0) {
|
|
@@ -186,8 +190,8 @@ export async function sampleSingleGroup(group, nodeList, config) {
|
|
|
186
190
|
.map((node) => ({
|
|
187
191
|
name: node.name || 'Unknown',
|
|
188
192
|
}));
|
|
189
|
-
const placeholderPrompt = buildPlaceholderPrompt(placeholders);
|
|
190
|
-
Logger.log(`组 ${group.groupIndex} 开始调用 AI 采样,代码长度: ${codeWithCustomName.length}, 是否有用户提示: ${!!group.userPrompt}, 是否有组件上下文: ${!!(group.componentContext && group.componentContext.length > 0)}, 占位符数量: ${placeholders.length}`);
|
|
193
|
+
const placeholderPrompt = buildPlaceholderPrompt(placeholders, isSupportSampling);
|
|
194
|
+
Logger.log(`组 ${group.groupIndex} 开始调用 AI 采样,代码长度: ${codeWithCustomName.length}, 是否有用户提示: ${!!group.userPrompt}, 是否有组件上下文: ${!!(group.componentContext && group.componentContext.length > 0)}, 是否有 Markdown 上下文: ${!!(group.markdownContext && group.markdownContext.length > 0)}, 占位符数量: ${placeholders.length}`);
|
|
191
195
|
// 执行采样
|
|
192
196
|
try {
|
|
193
197
|
if (!isSupportSampling)
|
|
@@ -202,6 +206,7 @@ export async function sampleSingleGroup(group, nodeList, config) {
|
|
|
202
206
|
chunkPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') +
|
|
203
207
|
userPromptText +
|
|
204
208
|
componentContextPrompt +
|
|
209
|
+
markdownContextPrompt +
|
|
205
210
|
placeholderPrompt,
|
|
206
211
|
},
|
|
207
212
|
},
|
package/dist/build/index.js
CHANGED
|
@@ -7,7 +7,7 @@ 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';
|
|
10
|
-
import { loadConfig, startHttpServer, stopHttpServer, generateComponentId
|
|
10
|
+
import { loadConfig, startHttpServer, stopHttpServer, generateComponentId } 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';
|
|
@@ -24,6 +24,22 @@ import * as path from 'path';
|
|
|
24
24
|
import { fileURLToPath } from 'url';
|
|
25
25
|
import { extractCodeAndComponents } from './utils/extract.js';
|
|
26
26
|
import { trackToolCall } from './utils/tj.js';
|
|
27
|
+
const spawnServerProcess = async () => {
|
|
28
|
+
// run 命令:后台运行 sloth --stdio
|
|
29
|
+
const { spawn } = await import('node:child_process');
|
|
30
|
+
const { fileURLToPath } = await import('node:url');
|
|
31
|
+
// 获取当前脚本路径(兼容 Node.js 18)
|
|
32
|
+
const scriptPath = fileURLToPath(import.meta.url);
|
|
33
|
+
// 后台启动子进程
|
|
34
|
+
const child = spawn(process.execPath, [scriptPath, '--server'], {
|
|
35
|
+
detached: true,
|
|
36
|
+
stdio: 'ignore',
|
|
37
|
+
env: { ...process.env, NODE_ENV: 'cli' }
|
|
38
|
+
});
|
|
39
|
+
// 解除父进程对子进程的引用,允许父进程退出
|
|
40
|
+
child.unref();
|
|
41
|
+
console.log(`[sloth] 已在后台启动,PID: ${child.pid}`);
|
|
42
|
+
};
|
|
27
43
|
/**
|
|
28
44
|
* 启动http & socket监听
|
|
29
45
|
* @param init 是否是初始化,初始化时需要连接stdio transport
|
|
@@ -38,9 +54,13 @@ export async function startListening(init = false) {
|
|
|
38
54
|
if (init) {
|
|
39
55
|
const transport = new StdioServerTransport();
|
|
40
56
|
await mcpServer.connect(transport);
|
|
57
|
+
// 单独启动httpServer进程,用于网页访问
|
|
58
|
+
await spawnServerProcess();
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// 启动 HTTP 服务器 - 仅启用web服务
|
|
62
|
+
await startHttpServer(port, mcpServer, configManager, false);
|
|
41
63
|
}
|
|
42
|
-
// 启动 HTTP 服务器 - 仅启用web服务
|
|
43
|
-
await startHttpServer(port, mcpServer, configManager, false);
|
|
44
64
|
}
|
|
45
65
|
else {
|
|
46
66
|
// 启动 HTTP 服务器 - 这是核心功能,不依赖 VSCode 日志
|
|
@@ -112,7 +132,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
112
132
|
catch (error) {
|
|
113
133
|
Logger.log('获取根目录时出错:', error);
|
|
114
134
|
}
|
|
115
|
-
await startListening()
|
|
135
|
+
// await startListening()
|
|
116
136
|
let userConfirmedChanges;
|
|
117
137
|
let userHtmlDiff; // 存储 HTML diff 文本
|
|
118
138
|
// 没有配置figmaApiKey,非数据推送模式,无法预览,直接唤起配置页面
|
|
@@ -189,11 +209,18 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
189
209
|
await fileManager.saveFile(fileKey, nodeId, 'imageMap.json', flatted.stringify(imageMap));
|
|
190
210
|
// 保存figma绝对定位代码
|
|
191
211
|
await fileManager.saveAbsoluteHtml(fileKey, nodeId, absoluteHtml);
|
|
212
|
+
await fileManager.saveAbsoluteHtml(fileKey, nodeId, absoluteHtml, true);
|
|
192
213
|
}
|
|
193
214
|
else {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
try {
|
|
216
|
+
d2cNodeList = flatted.parse(await fileManager.loadFile(fileKey, nodeId, 'nodeList.json'));
|
|
217
|
+
imageMap = flatted.parse(await fileManager.loadFile(fileKey, nodeId, 'imageMap.json'));
|
|
218
|
+
absoluteHtml = await fileManager.loadAbsoluteHtml(fileKey, nodeId);
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
throw new Error('加载本地数据失败, 请检查文件路径:' + fileManager.getFilePathPublic(fileKey, nodeId, 'nodeList.json'));
|
|
222
|
+
}
|
|
223
|
+
await fileManager.saveAbsoluteHtml(fileKey, nodeId, absoluteHtml, true);
|
|
197
224
|
console.log('cache load data');
|
|
198
225
|
}
|
|
199
226
|
console.log('d2cNodeList', d2cNodeList);
|
|
@@ -229,6 +256,9 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
229
256
|
else {
|
|
230
257
|
const configData = JSON.parse(configDataString);
|
|
231
258
|
const { mcp, groupsData, confirmedChanges, htmlDiff, ...rest } = configData;
|
|
259
|
+
pluginManager.executeHook('afterSubmitConfigData', {
|
|
260
|
+
configData,
|
|
261
|
+
});
|
|
232
262
|
userConfirmedChanges = confirmedChanges;
|
|
233
263
|
userHtmlDiff = htmlDiff; // 保存 HTML diff
|
|
234
264
|
// 保存旧配置用于比较
|
|
@@ -290,7 +320,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
290
320
|
const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
|
|
291
321
|
const convertConfig = config.fileConfigs?.[fileKey] || defaultConfigData;
|
|
292
322
|
// 获取提示词,优先使用用户保存的提示词,否则使用默认提示词
|
|
293
|
-
const frameworkPrompt = savedPromptSetting?.
|
|
323
|
+
const frameworkPrompt = savedPromptSetting?.frameworkGuidePrompt || '';
|
|
294
324
|
const chunkPrompt = savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt;
|
|
295
325
|
const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
|
|
296
326
|
const finalPrompt = savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt;
|
|
@@ -314,6 +344,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
314
344
|
codeSnippets,
|
|
315
345
|
componentContexts,
|
|
316
346
|
isSupportSampling,
|
|
347
|
+
isLocalMode: local,
|
|
317
348
|
};
|
|
318
349
|
// 深度遍历采样:针对每个分组生成 AI 相对布局树并转码
|
|
319
350
|
if (groupsData && groupsData?.length > 0) {
|
|
@@ -400,6 +431,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
400
431
|
const code = await getCode({
|
|
401
432
|
d2cNodeList: ungroupedNodeList,
|
|
402
433
|
config: convertConfig,
|
|
434
|
+
options: { isLocalMode: local },
|
|
403
435
|
});
|
|
404
436
|
// 使用 imageMap 的 path 替换 code 中对应的 src
|
|
405
437
|
const replacedCode = replaceImageSrc(code, imageMap);
|
|
@@ -429,18 +461,18 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
429
461
|
}, {
|
|
430
462
|
timeout: 5 * 60 * 1000,
|
|
431
463
|
});
|
|
432
|
-
Logger.log('采样成功 (分组外元素)', text);
|
|
464
|
+
Logger.log('采样成功 (分组外元素)', text, '采样请求代码', replacedCode, '采样提示词', frameworkPrompt + aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + placeholderPrompt);
|
|
433
465
|
const { code: pageCode } = extractCodeAndComponents(text)[0];
|
|
434
466
|
codeSnippets.unshift(pageCode);
|
|
435
467
|
}
|
|
436
468
|
catch (e) {
|
|
437
469
|
Logger.log('调用分组外元素采样出错,降级到传统模式', e);
|
|
438
|
-
codeSnippets.unshift(
|
|
470
|
+
codeSnippets.unshift(replacedCode);
|
|
439
471
|
}
|
|
440
472
|
}
|
|
441
473
|
const onSamplingComplete = await pluginManager.executeHook('onSamplingComplete', {
|
|
442
474
|
codeSnippets,
|
|
443
|
-
imageMap
|
|
475
|
+
imageMap,
|
|
444
476
|
});
|
|
445
477
|
codeSnippets = onSamplingComplete?.codeSnippets || codeSnippets;
|
|
446
478
|
imageMap = onSamplingComplete?.imageMap || imageMap;
|
|
@@ -469,17 +501,19 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
469
501
|
Logger.log(`加载组件数据库,共 ${componentsDatabase.length} 个组件`);
|
|
470
502
|
// 构建组件映射提示词(包含组件映射、组件上下文、标记组件和完整组件数据库)
|
|
471
503
|
const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts, markedComponents, componentsDatabase);
|
|
504
|
+
const { prompt: pluginFinalPrompt = '' } = await pluginManager.executeHook('beforeFinalPrompt', { configData: config.fileConfigs?.[fileKey] || '' });
|
|
505
|
+
console.log('pluginFinalPrompt', pluginFinalPrompt);
|
|
472
506
|
// 使用提示词(已包含默认值)
|
|
473
507
|
return {
|
|
474
508
|
content: [
|
|
475
509
|
isSupportSampling
|
|
476
510
|
? {
|
|
477
511
|
type: 'text',
|
|
478
|
-
text: finalPrompt + componentMappingPrompt,
|
|
512
|
+
text: finalPrompt + componentMappingPrompt + pluginFinalPrompt,
|
|
479
513
|
}
|
|
480
514
|
: {
|
|
481
515
|
type: 'text',
|
|
482
|
-
text: noSamplingPrompt + componentMappingPrompt,
|
|
516
|
+
text: noSamplingPrompt + componentMappingPrompt + pluginFinalPrompt,
|
|
483
517
|
},
|
|
484
518
|
...codeSnippets.map((code) => {
|
|
485
519
|
return {
|
|
@@ -1124,6 +1158,9 @@ async function main() {
|
|
|
1124
1158
|
}
|
|
1125
1159
|
process.exit(0);
|
|
1126
1160
|
}
|
|
1161
|
+
if (process.env.SLOTH_COMMAND === 'server') {
|
|
1162
|
+
await startListening();
|
|
1163
|
+
}
|
|
1127
1164
|
try {
|
|
1128
1165
|
// 先输出基本启动信息到控制台,确保即使 VSCode 日志失败也能看到启动过程
|
|
1129
1166
|
console.log(`[${new Date().toISOString()}] Starting Figma transcoding interceptor MCP server...`);
|
|
@@ -1175,7 +1212,6 @@ process.on('SIGTERM', async () => {
|
|
|
1175
1212
|
catch (error) {
|
|
1176
1213
|
// 忽略日志错误,确保清理过程继续
|
|
1177
1214
|
}
|
|
1178
|
-
await stopSocketServer();
|
|
1179
1215
|
cleanupTauri();
|
|
1180
1216
|
cleanupWeb();
|
|
1181
1217
|
cleanupVSCode();
|
|
@@ -1286,6 +1322,10 @@ else if (args[0] === 'clear') {
|
|
|
1286
1322
|
// 设置环境变量标识这是 clear 命令
|
|
1287
1323
|
process.env.SLOTH_COMMAND = 'clear';
|
|
1288
1324
|
}
|
|
1325
|
+
else if (args[0] === '--server') {
|
|
1326
|
+
// 设置环境变量标识这是 clear 命令
|
|
1327
|
+
process.env.SLOTH_COMMAND = 'server';
|
|
1328
|
+
}
|
|
1289
1329
|
else {
|
|
1290
1330
|
// 设置环境变量为CLI模式
|
|
1291
1331
|
process.env.NODE_ENV = 'cli';
|
|
@@ -3,6 +3,7 @@ import { promises as fs } from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import envPaths from 'env-paths';
|
|
6
|
+
import { createRequire } from 'module';
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = path.dirname(__filename);
|
|
8
9
|
// 检测是否存在 pnpm
|
|
@@ -17,7 +18,7 @@ function hasPnpm() {
|
|
|
17
18
|
}
|
|
18
19
|
// 获取包管理器命令
|
|
19
20
|
function getPackageManager() {
|
|
20
|
-
return
|
|
21
|
+
return 'npm';
|
|
21
22
|
}
|
|
22
23
|
// 使用 env-paths 获取配置目录(与 config-manager 保持一致)
|
|
23
24
|
const paths = envPaths('d2c-mcp');
|
|
@@ -25,7 +26,14 @@ const paths = envPaths('d2c-mcp');
|
|
|
25
26
|
function getGlobalPackageDir() {
|
|
26
27
|
// 在构建后的目录中: dist/build/plugin/loader.js
|
|
27
28
|
// 需要向上三级才能找到包根目录
|
|
28
|
-
return path.resolve(__dirname, '..', '..', '..')
|
|
29
|
+
// return path.resolve(__dirname, '..', '..', '..')
|
|
30
|
+
try {
|
|
31
|
+
const pm = getPackageManager();
|
|
32
|
+
return execSync(`${pm} root -g`, { encoding: 'utf-8' }).trim();
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return '';
|
|
36
|
+
}
|
|
29
37
|
}
|
|
30
38
|
// 获取全局插件配置文件路径(与 config.json 在同一目录)
|
|
31
39
|
function getGlobalPluginsConfigPath() {
|
|
@@ -68,6 +76,12 @@ async function loadWorkspacePluginsConfig(workspaceRoot) {
|
|
|
68
76
|
return { plugins: [] };
|
|
69
77
|
}
|
|
70
78
|
}
|
|
79
|
+
export async function loadPluginFromGlobalDirAsync(packageName) {
|
|
80
|
+
const globalDir = getGlobalPackageDir();
|
|
81
|
+
const require = createRequire(path.join(globalDir));
|
|
82
|
+
const resolvedPath = require.resolve(packageName);
|
|
83
|
+
return await import(resolvedPath);
|
|
84
|
+
}
|
|
71
85
|
/**
|
|
72
86
|
* 解析插件配置项,统一返回 { name, config } 格式
|
|
73
87
|
*/
|
|
@@ -92,7 +106,7 @@ async function checkLocalPlugin(workspaceRoot, pluginName) {
|
|
|
92
106
|
// 检查 npm 包是否已安装
|
|
93
107
|
async function isNpmPackageInstalled(packageName) {
|
|
94
108
|
try {
|
|
95
|
-
await
|
|
109
|
+
await loadPluginFromGlobalDirAsync(packageName);
|
|
96
110
|
return true;
|
|
97
111
|
}
|
|
98
112
|
catch {
|
|
@@ -125,11 +139,11 @@ async function tryInstallNpmPackage(packageName) {
|
|
|
125
139
|
export async function installPlugin(packageName) {
|
|
126
140
|
const globalDir = getGlobalPackageDir();
|
|
127
141
|
console.log(`[sloth] 正在安装插件: ${packageName}`);
|
|
128
|
-
console.log(`[sloth] 安装目录: ${globalDir}`)
|
|
142
|
+
// console.log(`[sloth] 安装目录: ${globalDir}`)
|
|
129
143
|
try {
|
|
130
144
|
const pm = getPackageManager();
|
|
131
145
|
// 在全局包目录下执行安装
|
|
132
|
-
execSync(`${pm} install ${packageName}`, {
|
|
146
|
+
execSync(`${pm} install -g ${packageName}`, {
|
|
133
147
|
cwd: globalDir,
|
|
134
148
|
stdio: 'inherit',
|
|
135
149
|
});
|
|
@@ -239,15 +253,15 @@ export async function loadWorkspacePlugins(workspaceRoot) {
|
|
|
239
253
|
if (!isInstalled) {
|
|
240
254
|
// 尝试安装
|
|
241
255
|
console.log(`[sloth] 插件 ${pluginName} 未安装,尝试安装...`);
|
|
242
|
-
const installSuccess = await tryInstallNpmPackage(pluginName)
|
|
243
|
-
if (!installSuccess) {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
}
|
|
256
|
+
// const installSuccess = await tryInstallNpmPackage(pluginName)
|
|
257
|
+
// if (!installSuccess) {
|
|
258
|
+
// failedPlugins.push(pluginName)
|
|
259
|
+
// continue
|
|
260
|
+
// }
|
|
247
261
|
}
|
|
248
262
|
// 加载 npm 包
|
|
249
263
|
try {
|
|
250
|
-
const pluginModule = await
|
|
264
|
+
const pluginModule = await loadPluginFromGlobalDirAsync(pluginName);
|
|
251
265
|
console.log('pluginModule', pluginModule);
|
|
252
266
|
const plugin = {
|
|
253
267
|
name: pluginName,
|
|
@@ -100,6 +100,7 @@ class PluginManager {
|
|
|
100
100
|
...params,
|
|
101
101
|
...(lastReturnVal || {}),
|
|
102
102
|
_config: context.config,
|
|
103
|
+
workspaceRoot: this.workspaceRoot,
|
|
103
104
|
}, context);
|
|
104
105
|
// 更新上一个插件信息
|
|
105
106
|
lastPlugin = plugin.name;
|
|
@@ -107,6 +108,7 @@ class PluginManager {
|
|
|
107
108
|
...params,
|
|
108
109
|
...(lastReturnVal || {}),
|
|
109
110
|
...(result || {}),
|
|
111
|
+
workspaceRoot: this.workspaceRoot,
|
|
110
112
|
};
|
|
111
113
|
}
|
|
112
114
|
catch (error) {
|