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.
@@ -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, resetNodeListPosition, saveImageFile, replaceImageSrc } from './utils/utils.js';
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
- // 并发采样:针对每个分组生成 AI 相对布局树并转码
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
- // 限制并发为 2,分批执行,多个采样并发可能存在超时问题
199
- const concurrency = 1;
200
- for (let i = 0; i < groupsData.length; i += concurrency) {
201
- const batch = groupsData.slice(i, i + concurrency);
202
- const samplingPromises = batch.map(async (group) => {
203
- const groupElements = group.elements;
204
- let nodeList = d2cNodeList.filter((node) => groupElements.includes(node.id));
205
- group.nodeList = nodeList;
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 Promise.all(samplingPromises);
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
- groupsData?.forEach((group) => {
255
- group.elements.forEach((id) => {
256
- groupedElementIds.add(id);
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
- groupsData?.forEach((group, index) => {
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_' + index,
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 显示 interceptor-web 页面地址');
401
- console.log(' version 显示版本信息');
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.toString();
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.toString(),
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.toString(),
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
+ }