sloth-d2c-mcp 1.0.4-beta69 → 1.0.4-beta71

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);
@@ -182,88 +206,70 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
182
206
  const chunkPrompt = savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt;
183
207
  const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
184
208
  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
209
  const codeSnippets = [];
195
210
  let isSupportSampling = true;
196
- // 并发采样:针对每个分组生成 AI 相对布局树并转码
211
+ // 收集已映射的组件信息,用于在最终写入时添加提示词
212
+ const componentMappings = [];
213
+ // 收集所有组的组件上下文信息,用于在最终提示词中显示
214
+ const componentContexts = [];
215
+ // 构建采样配置
216
+ const samplingConfig = {
217
+ d2cNodeList,
218
+ imageMap,
219
+ convertConfig,
220
+ chunkPrompt,
221
+ mcpServer,
222
+ componentMappings,
223
+ codeSnippets,
224
+ componentContexts,
225
+ };
226
+ // 深度遍历采样:针对每个分组生成 AI 相对布局树并转码
197
227
  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 : ''));
228
+ isSupportSampling = await processSampling(groupsData, samplingConfig);
229
+ // 采样完成后,更新组件名并保存到文件
230
+ try {
231
+ const groupsDataToSave = groupsData.map(group => {
232
+ // 将提取的组件名更新到 componentName(仅标记的分组)
233
+ if (group.marked && group.name) {
234
+ group.componentName = group.name;
235
+ Logger.log(`组 ${group.groupIndex} 已标记,更新组件名: ${group.componentName}`);
247
236
  }
237
+ // 去掉 nodeList 后返回
238
+ const { nodeList, ...groupWithoutNodeList } = group;
239
+ return groupWithoutNodeList;
248
240
  });
249
- await Promise.all(samplingPromises);
241
+ await fileManager.saveGroupsData(fileKey, nodeId, groupsDataToSave);
242
+ Logger.log(`已保存更新后的 groupsData,包含 ${groupsData.filter(g => g.marked).length} 个标记的组件`);
243
+ }
244
+ catch (error) {
245
+ Logger.error(`保存 groupsData 失败:`, error);
250
246
  }
251
247
  }
252
248
  // 分组外元素:注入分组占位并进行整合采样
249
+ // 构建嵌套结构,只收集根组(第一层)的元素
250
+ const nestingInfo = buildNestingStructure(groupsData || []);
253
251
  const groupedElementIds = new Set();
254
- groupsData?.forEach((group) => {
255
- group.elements.forEach((id) => {
256
- groupedElementIds.add(id);
257
- });
252
+ // 只收集根组的元素ID
253
+ nestingInfo.rootGroups.forEach((rootIndex) => {
254
+ const rootGroup = nestingInfo.groupMap.get(rootIndex);
255
+ if (rootGroup) {
256
+ rootGroup.elements.forEach((id) => {
257
+ groupedElementIds.add(id);
258
+ });
259
+ }
258
260
  });
261
+ Logger.log(`收集到 ${groupedElementIds.size} 个根组元素ID,根组数量: ${nestingInfo.rootGroups.length}`);
259
262
  // 分组外元素
260
263
  const ungroupedNodeList = d2cNodeList.filter((node) => !groupedElementIds.has(node.id));
261
264
  if (ungroupedNodeList.length > 0) {
262
- // 注入分组占位
263
- groupsData?.forEach((group, index) => {
265
+ // 只注入根组占位符
266
+ nestingInfo.rootGroups.forEach((rootIndex) => {
267
+ const group = nestingInfo.groupMap.get(rootIndex);
268
+ if (!group)
269
+ return;
264
270
  const { x, y, width, height, absoluteRenderX, absoluteRenderY, absoluteRenderWidth, absoluteRenderHeight } = getBoundingBox(group.nodeList);
265
271
  ungroupedNodeList.push({
266
- id: 'G_' + index,
272
+ id: 'G_' + rootIndex,
267
273
  name: group.name || `Group${group.groupIndex + 1}`,
268
274
  x,
269
275
  y,
@@ -290,12 +296,19 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
290
296
  });
291
297
  });
292
298
  console.log('ungroupedNodeList', ungroupedNodeList);
299
+ // 检测占位符元素(分组占位符)
300
+ const placeholders = ungroupedNodeList
301
+ .filter((node) => node.type === 'COMPONENT' && node.id?.startsWith('G_'))
302
+ .map((node) => ({
303
+ name: node.name || 'Unknown',
304
+ }));
305
+ const placeholderPrompt = buildPlaceholderPrompt(placeholders);
306
+ Logger.log(`整合采样检测到 ${placeholders.length} 个分组占位符`);
293
307
  // 获取代码
294
308
  const code = await getCode({
295
309
  d2cNodeList: ungroupedNodeList,
296
310
  config: convertConfig,
297
311
  });
298
- console.log('code', code);
299
312
  // 使用 imageMap 的 path 替换 code 中对应的 src
300
313
  const replacedCode = replaceImageSrc(code, imageMap);
301
314
  // 整合采样
@@ -307,7 +320,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
307
320
  role: 'user',
308
321
  content: {
309
322
  type: 'text',
310
- text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || ''),
323
+ text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + placeholderPrompt,
311
324
  },
312
325
  },
313
326
  {
@@ -329,23 +342,27 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
329
342
  catch (e) {
330
343
  isSupportSampling = false;
331
344
  Logger.log('调用分组外元素采样出错,降级到传统模式', e);
332
- codeSnippets.unshift(replacedCode);
345
+ codeSnippets.unshift(placeholderPrompt + '\n' + replacedCode);
333
346
  }
334
347
  }
335
348
  // 保存图片文件
336
349
  saveImageFile({ imageMap, root });
337
350
  Logger.log('处理完成,生成的代码片段数量:', codeSnippets.length);
351
+ Logger.log('已映射的组件数量:', componentMappings.length);
352
+ Logger.log('组件上下文数量:', componentContexts.length);
353
+ // 构建组件映射提示词(包含组件映射和组件上下文)
354
+ const componentMappingPrompt = buildComponentMappingPrompt(componentMappings, componentContexts);
338
355
  // 使用提示词(已包含默认值)
339
356
  return {
340
357
  content: [
341
358
  isSupportSampling
342
359
  ? {
343
360
  type: 'text',
344
- text: finalPrompt,
361
+ text: finalPrompt + componentMappingPrompt,
345
362
  }
346
363
  : {
347
364
  type: 'text',
348
- text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || ''),
365
+ text: aggregationPrompt.replace(/{FRAMEWORK}/g, convertConfig.convertSetting?.framework || '') + componentMappingPrompt,
349
366
  },
350
367
  ...codeSnippets.map((code) => {
351
368
  return {
@@ -396,9 +413,11 @@ async function main() {
396
413
  console.log('使用方法: sloth <命令> [选项]');
397
414
  console.log('');
398
415
  console.log('可用命令:');
399
- console.log(' config 显示配置文件信息');
400
- console.log(' page 显示 interceptor-web 页面地址');
401
- console.log(' version 显示版本信息');
416
+ console.log(' config 显示配置文件信息');
417
+ console.log(' page 显示 interceptor-web 页面地址');
418
+ console.log(' framework 添加或管理框架配置');
419
+ console.log(' clear 清理缓存文件');
420
+ console.log(' version 显示版本信息');
402
421
  console.log('');
403
422
  console.log('全局选项:');
404
423
  console.log(' --help, -h 显示帮助信息');
@@ -411,6 +430,9 @@ async function main() {
411
430
  console.log(' sloth config --path # 仅显示配置路径');
412
431
  console.log(' sloth page # 显示页面地址');
413
432
  console.log(' sloth page --list # 列出所有页面文件');
433
+ console.log(' sloth framework react # 添加 react 框架');
434
+ console.log(' sloth framework --list # 列出所有框架');
435
+ console.log(' sloth clear # 清理缓存文件');
414
436
  console.log(' sloth version # 显示版本信息');
415
437
  console.log(' sloth --version # 显示版本信息');
416
438
  process.exit(0);
@@ -454,7 +476,7 @@ async function main() {
454
476
  config = await configManager.load();
455
477
  }
456
478
  catch (err) {
457
- error = err.toString();
479
+ error = err instanceof Error ? err.message : String(err);
458
480
  }
459
481
  }
460
482
  if (isJsonOutput) {
@@ -581,7 +603,7 @@ async function main() {
581
603
  catch (error) {
582
604
  const errorInfo = {
583
605
  error: '读取目录失败',
584
- message: error.toString(),
606
+ message: error instanceof Error ? error.message : String(error),
585
607
  directory: interceptorWebPath,
586
608
  };
587
609
  if (isJsonOutput) {
@@ -640,7 +662,7 @@ async function main() {
640
662
  catch (error) {
641
663
  const errorInfo = {
642
664
  error: '获取页面信息失败',
643
- message: error.toString(),
665
+ message: error instanceof Error ? error.message : String(error),
644
666
  };
645
667
  if (isJsonOutput) {
646
668
  console.log(JSON.stringify(errorInfo, null, 2));
@@ -656,6 +678,114 @@ async function main() {
656
678
  fileManager.cleanup();
657
679
  process.exit(0);
658
680
  }
681
+ // 检查是否是 framework 命令
682
+ if (process.env.SLOTH_COMMAND === 'framework') {
683
+ const configManager = new ConfigManager('d2c-mcp');
684
+ // 获取子命令参数
685
+ const frameworkArgsStr = process.env.SLOTH_FRAMEWORK_ARGS || '[]';
686
+ const frameworkArgs = JSON.parse(frameworkArgsStr);
687
+ const isJsonOutput = frameworkArgs.includes('--json');
688
+ // 处理帮助参数
689
+ if (frameworkArgs.includes('--help') || frameworkArgs.includes('-h')) {
690
+ console.log('使用方法: sloth framework <框架名称> [选项]');
691
+ console.log('');
692
+ console.log('添加或管理框架配置');
693
+ console.log('');
694
+ console.log('选项:');
695
+ console.log(' --help, -h 显示帮助信息');
696
+ console.log(' --list 列出所有已添加的框架');
697
+ console.log(' --json 以 JSON 格式输出');
698
+ console.log('');
699
+ console.log('示例:');
700
+ console.log(' sloth framework react # 添加 react 框架');
701
+ console.log(' sloth framework vue # 添加 vue 框架');
702
+ console.log(' sloth framework --list # 列出所有框架');
703
+ process.exit(0);
704
+ }
705
+ // 处理列出所有框架
706
+ if (frameworkArgs.includes('--list')) {
707
+ try {
708
+ const frameworks = await configManager.getFrameworks();
709
+ if (isJsonOutput) {
710
+ console.log(JSON.stringify({ frameworks }, null, 2));
711
+ }
712
+ else {
713
+ if (frameworks.length === 0) {
714
+ console.log('暂无已添加的框架');
715
+ }
716
+ else {
717
+ console.log('已添加的框架:');
718
+ frameworks.forEach((framework, index) => {
719
+ console.log(` ${index + 1}. ${framework}`);
720
+ });
721
+ }
722
+ }
723
+ }
724
+ catch (error) {
725
+ if (isJsonOutput) {
726
+ console.log(JSON.stringify({ error: error.toString() }, null, 2));
727
+ }
728
+ else {
729
+ console.log(`获取框架列表失败: ${error}`);
730
+ }
731
+ process.exit(1);
732
+ }
733
+ process.exit(0);
734
+ }
735
+ // 添加框架
736
+ if (frameworkArgs.length === 0) {
737
+ console.log('错误: 请提供框架名称');
738
+ console.log('使用 "sloth framework --help" 查看帮助信息');
739
+ process.exit(1);
740
+ }
741
+ const frameworkName = frameworkArgs[0];
742
+ try {
743
+ // 添加框架到配置
744
+ await configManager.addFramework(frameworkName);
745
+ // 检查框架配置文件是否存在
746
+ const configExists = await configManager.frameworkConfigExists(frameworkName);
747
+ if (!configExists) {
748
+ // 创建默认的框架配置文件
749
+ await configManager.saveFrameworkConfig(frameworkName, {
750
+ chunkOptimizePrompt: chunkOptimizeCodePrompt,
751
+ aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
752
+ finalOptimizePrompt: finalOptimizeCodePrompt,
753
+ });
754
+ }
755
+ const frameworkPath = configManager.getFrameworkConfigPath(frameworkName);
756
+ if (isJsonOutput) {
757
+ console.log(JSON.stringify({
758
+ success: true,
759
+ framework: frameworkName,
760
+ configPath: frameworkPath,
761
+ created: !configExists
762
+ }, null, 2));
763
+ }
764
+ else {
765
+ console.log(`✓ 框架 "${frameworkName}" 添加成功`);
766
+ console.log(`配置文件路径: ${frameworkPath}`);
767
+ if (!configExists) {
768
+ console.log('已创建默认配置文件');
769
+ }
770
+ else {
771
+ console.log('使用现有配置文件');
772
+ }
773
+ }
774
+ }
775
+ catch (error) {
776
+ if (isJsonOutput) {
777
+ console.log(JSON.stringify({
778
+ success: false,
779
+ error: error.toString()
780
+ }, null, 2));
781
+ }
782
+ else {
783
+ console.log(`添加框架失败: ${error}`);
784
+ }
785
+ process.exit(1);
786
+ }
787
+ process.exit(0);
788
+ }
659
789
  try {
660
790
  // 先输出基本启动信息到控制台,确保即使 VSCode 日志失败也能看到启动过程
661
791
  console.log(`[${new Date().toISOString()}] Starting Figma transcoding interceptor MCP server...`);
@@ -728,3 +858,112 @@ process.on('SIGTERM', async () => {
728
858
  process.exit(0);
729
859
  });
730
860
  main();
861
+ // 获取命令行参数
862
+ const args = process.argv.slice(2);
863
+ // 处理 --log 参数:开启文件日志(同时保留控制台输出)
864
+ const logArgIndex = args.findIndex((a) => a === '--log' || a.startsWith('--log='));
865
+ if (logArgIndex !== -1) {
866
+ let logFilePath = 'runtime.log';
867
+ const raw = args[logArgIndex];
868
+ // 移除 --log 参数,避免传递给后续程序
869
+ args.splice(logArgIndex, 1);
870
+ process.argv = [process.argv[0], process.argv[1], ...args];
871
+ try {
872
+ const { createWriteStream, mkdirSync, existsSync } = await import('node:fs');
873
+ const path = await import('node:path');
874
+ const util = await import('node:util');
875
+ if (raw.startsWith('--log=')) {
876
+ const provided = raw.slice('--log='.length).trim();
877
+ if (provided) {
878
+ logFilePath = path.isAbsolute(provided) ? provided : path.resolve(process.cwd(), provided);
879
+ }
880
+ }
881
+ else {
882
+ logFilePath = path.resolve(process.cwd(), logFilePath);
883
+ }
884
+ const logDir = path.dirname(logFilePath);
885
+ if (!existsSync(logDir)) {
886
+ mkdirSync(logDir, { recursive: true });
887
+ }
888
+ const logStream = createWriteStream(logFilePath, { flags: 'a' });
889
+ const originalConsole = {
890
+ log: console.log,
891
+ info: console.info,
892
+ warn: console.warn,
893
+ error: console.error,
894
+ };
895
+ const writeLine = (...args) => {
896
+ try {
897
+ const line = util.format(...args) + '\n';
898
+ logStream.write(line);
899
+ }
900
+ catch { }
901
+ };
902
+ console.log = (...a) => {
903
+ originalConsole.log(...a);
904
+ writeLine(...a);
905
+ };
906
+ console.info = (...a) => {
907
+ originalConsole.info(...a);
908
+ writeLine(...a);
909
+ };
910
+ console.warn = (...a) => {
911
+ originalConsole.warn(...a);
912
+ writeLine(...a);
913
+ };
914
+ console.error = (...a) => {
915
+ originalConsole.error(...a);
916
+ writeLine(...a);
917
+ };
918
+ process.env.SLOTH_LOG_FILE = logFilePath;
919
+ originalConsole.log(`[sloth] 日志已开启,写入: ${logFilePath}`);
920
+ const close = () => {
921
+ try {
922
+ logStream.end();
923
+ }
924
+ catch { }
925
+ };
926
+ process.on('exit', close);
927
+ process.on('SIGINT', close);
928
+ process.on('SIGTERM', close);
929
+ process.on('uncaughtException', close);
930
+ process.on('unhandledRejection', close);
931
+ }
932
+ catch (e) {
933
+ // 记录但不中断启动
934
+ console.warn('[sloth] 启用文件日志失败:', e);
935
+ }
936
+ }
937
+ // 检查是否是 config 命令
938
+ if (args[0] === 'config') {
939
+ // 设置环境变量标识这是 config 命令
940
+ process.env.SLOTH_COMMAND = 'config';
941
+ // 传递子命令参数
942
+ process.env.SLOTH_CONFIG_ARGS = JSON.stringify(args.slice(1));
943
+ }
944
+ else if (args[0] === 'page') {
945
+ // 设置环境变量标识这是 page 命令
946
+ process.env.SLOTH_COMMAND = 'page';
947
+ // 传递子命令参数
948
+ process.env.SLOTH_PAGE_ARGS = JSON.stringify(args.slice(1));
949
+ }
950
+ else if (args[0] === 'version' || args[0] === '--version' || args[0] === '-v') {
951
+ // 设置环境变量标识这是 version 命令
952
+ process.env.SLOTH_COMMAND = 'version';
953
+ }
954
+ else if (args[0] === '--help' || args[0] === '-h' || args.length === 0) {
955
+ // 显示主帮助信息
956
+ process.env.SLOTH_COMMAND = 'help';
957
+ }
958
+ else if (args[0] === 'clear') {
959
+ // 设置环境变量标识这是 clear 命令
960
+ process.env.SLOTH_COMMAND = 'clear';
961
+ }
962
+ else {
963
+ // 设置环境变量为CLI模式
964
+ process.env.NODE_ENV = 'cli';
965
+ // 确保--stdio参数被传递
966
+ if (!process.argv.includes('--stdio')) {
967
+ process.argv.push('--stdio');
968
+ }
969
+ }