sloth-d2c-mcp 1.0.4-beta96 → 1.0.4-beta98

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 CHANGED
@@ -198,6 +198,133 @@ if (args[0] === 'config') {
198
198
  console.log(`[sloth] 共停止 ${killedCount} 个进程`);
199
199
  }
200
200
  process.exit(0);
201
+ } else if (args[0] === 'update') {
202
+ // update 命令:先停止进程,然后更新 sloth
203
+ const { execSync, spawnSync } = await import('node:child_process');
204
+ const os = await import('node:os');
205
+ const { fileURLToPath } = await import('node:url');
206
+ const path = await import('node:path');
207
+
208
+ console.log('[sloth] 正在停止运行中的进程...');
209
+
210
+ // 复用 stop 命令的逻辑
211
+ const ports = [3100, 3101];
212
+ const pidsToKill = new Set();
213
+
214
+ for (const port of ports) {
215
+ try {
216
+ let output = '';
217
+ if (os.platform() === 'win32') {
218
+ output = execSync(`netstat -ano | findstr :${port}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
219
+ const lines = output.trim().split('\n');
220
+ for (const line of lines) {
221
+ const match = line.match(/LISTENING\s+(\d+)/);
222
+ if (match) {
223
+ pidsToKill.add(match[1]);
224
+ }
225
+ }
226
+ } else {
227
+ output = execSync(`lsof -i :${port} -t`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
228
+ const pids = output.trim().split('\n').filter(Boolean);
229
+ pids.forEach(pid => pidsToKill.add(pid.trim()));
230
+ }
231
+ } catch {
232
+ // 端口未被占用,忽略错误
233
+ }
234
+ }
235
+
236
+ let killedCount = 0;
237
+ for (const pid of pidsToKill) {
238
+ try {
239
+ let cmdline = '';
240
+ if (os.platform() === 'win32') {
241
+ cmdline = execSync(`wmic process where processid=${pid} get commandline`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
242
+ } else {
243
+ cmdline = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
244
+ }
245
+
246
+ const isSlothProcess = cmdline.includes('sloth') || cmdline.includes('d2c-mcp');
247
+
248
+ if (isSlothProcess) {
249
+ process.kill(Number(pid), 'SIGTERM');
250
+ console.log(`[sloth] 已停止进程 PID: ${pid}`);
251
+ killedCount++;
252
+ }
253
+ } catch (e) {
254
+ // 进程可能已经退出
255
+ }
256
+ }
257
+
258
+ if (killedCount > 0) {
259
+ console.log(`[sloth] 共停止 ${killedCount} 个进程`);
260
+ }
261
+
262
+ // 检测包管理器:通过检查 sloth 命令的安装路径来判断
263
+ let packageManager = 'npm'; // 默认使用 npm
264
+
265
+ try {
266
+ // 获取当前脚本的实际路径
267
+ const scriptPath = fileURLToPath(import.meta.url);
268
+ const scriptDir = path.dirname(scriptPath);
269
+
270
+ // 检查路径中是否包含 pnpm 相关目录
271
+ if (scriptDir.includes('pnpm') || scriptDir.includes('.pnpm')) {
272
+ packageManager = 'pnpm';
273
+ } else {
274
+ // 尝试通过 which/where 命令检查 sloth 的安装位置
275
+ try {
276
+ const whichCmd = os.platform() === 'win32' ? 'where sloth' : 'which sloth';
277
+ const slothPath = execSync(whichCmd, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
278
+
279
+ if (slothPath.includes('pnpm') || slothPath.includes('.pnpm')) {
280
+ packageManager = 'pnpm';
281
+ }
282
+ } catch {
283
+ // 忽略错误,使用默认的 npm
284
+ }
285
+
286
+ // 额外检查:尝试检测全局 pnpm 目录
287
+ if (packageManager === 'npm') {
288
+ try {
289
+ const pnpmRoot = execSync('pnpm root -g', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
290
+ if (scriptDir.startsWith(pnpmRoot) || scriptDir.includes(path.dirname(pnpmRoot))) {
291
+ packageManager = 'pnpm';
292
+ }
293
+ } catch {
294
+ // pnpm 未安装或命令失败,使用 npm
295
+ }
296
+ }
297
+ }
298
+ } catch {
299
+ // 检测失败,使用默认的 npm
300
+ }
301
+
302
+ console.log(`[sloth] 检测到包管理器: ${packageManager}`);
303
+ console.log('[sloth] 正在更新 sloth-d2c-mcp...');
304
+
305
+ // 执行全局安装更新
306
+ const installCmd = packageManager === 'pnpm'
307
+ ? 'pnpm install -g sloth-d2c-mcp'
308
+ : 'npm install -g sloth-d2c-mcp';
309
+
310
+ try {
311
+ const result = spawnSync(installCmd, {
312
+ shell: true,
313
+ stdio: 'inherit',
314
+ env: process.env
315
+ });
316
+
317
+ if (result.status === 0) {
318
+ console.log('[sloth] 更新完成!');
319
+ } else {
320
+ console.error('[sloth] 更新失败,退出码:', result.status);
321
+ process.exit(result.status || 1);
322
+ }
323
+ } catch (e) {
324
+ console.error('[sloth] 更新失败:', e.message);
325
+ process.exit(1);
326
+ }
327
+ process.exit(0);
201
328
  } else if (args[0] === '--server') {
202
329
  process.env.SLOTH_COMMAND = 'server';
203
330
  } else {
@@ -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 { PLUGIN_HOOKS } from './types.js';
6
7
  import { createRequire } from 'module';
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = path.dirname(__filename);
@@ -235,7 +236,12 @@ export async function loadWorkspacePlugins(workspaceRoot) {
235
236
  const pluginModule = await import(localPluginPath);
236
237
  const plugin = {
237
238
  name: pluginName,
238
- ...(pluginModule.default || pluginModule),
239
+ // 只取出PluginHooks
240
+ ...PLUGIN_HOOKS.reduce((acc, hookName) => {
241
+ acc[hookName] = (pluginModule.default || pluginModule)[hookName];
242
+ return acc;
243
+ }, {}),
244
+ // ...(pluginModule.default || pluginModule),
239
245
  _config: pluginConfig,
240
246
  };
241
247
  plugins.push(plugin);
@@ -265,7 +271,10 @@ export async function loadWorkspacePlugins(workspaceRoot) {
265
271
  console.log('pluginModule', pluginModule);
266
272
  const plugin = {
267
273
  name: pluginName,
268
- ...(pluginModule.default || pluginModule),
274
+ ...PLUGIN_HOOKS.reduce((acc, hookName) => {
275
+ acc[hookName] = (pluginModule.default || pluginModule)[hookName];
276
+ return acc;
277
+ }, {}),
269
278
  _config: pluginConfig,
270
279
  };
271
280
  plugins.push(plugin);
@@ -301,7 +310,10 @@ export async function loadAllPlugins() {
301
310
  console.log('pluginConfig', pluginConfig);
302
311
  const plugin = {
303
312
  name: packageName,
304
- ...(pluginModule.default || pluginModule),
313
+ ...PLUGIN_HOOKS.reduce((acc, hookName) => {
314
+ acc[hookName] = (pluginModule.default || pluginModule)[hookName];
315
+ return acc;
316
+ }, {}),
305
317
  _config: pluginConfig,
306
318
  };
307
319
  plugins.push(plugin);
@@ -322,7 +334,10 @@ export async function loadPlugin(packageName) {
322
334
  const pluginModule = await import(packageName);
323
335
  const plugin = {
324
336
  name: packageName,
325
- ...(pluginModule.default || pluginModule),
337
+ ...PLUGIN_HOOKS.reduce((acc, hookName) => {
338
+ acc[hookName] = (pluginModule.default || pluginModule)[hookName];
339
+ return acc;
340
+ }, {}),
326
341
  };
327
342
  console.log(`[sloth] 插件 ${packageName} 加载成功`);
328
343
  return plugin;
@@ -1 +1,6 @@
1
- export {};
1
+ export const PLUGIN_HOOKS = [
2
+ 'afterSubmitConfigData',
3
+ 'onFetchImage',
4
+ 'onSamplingComplete',
5
+ 'beforeFinalPrompt',
6
+ ];
@@ -18,7 +18,7 @@ import multer from 'multer';
18
18
  import { ImageMatcher } from './utils/image-matcher.js';
19
19
  import { initOpenCV } from './utils/opencv-loader.js';
20
20
  import { extractJson } from './utils/extract.js';
21
- import { componentAnalysisPrompt, componentAnalysisPromptVue, chunkOptimizeCodePromptKuikly, aggregationOptimizeCodePromptKuikly, finalOptimizeCodePromptKuikly, chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, frameworkPromptKuikly, noSamplingAggregationPrompt, noSamplingAggregationPromptVue, noSamplingAggregationPromptKuikly, chunkOptimizeCodePromptIosOc, aggregationOptimizeCodePromptIosOc, finalOptimizeCodePromptIosOc, noSamplingAggregationPromptIosOc, chunkOptimizeCodePromptIosSwift, aggregationOptimizeCodePromptIosSwift, finalOptimizeCodePromptIosSwift, noSamplingAggregationPromptIosSwift, } from 'sloth-d2c-node/convert';
21
+ import { componentAnalysisPrompt, componentAnalysisPromptVue, chunkOptimizeCodePromptKuikly, aggregationOptimizeCodePromptKuikly, finalOptimizeCodePromptKuikly, chunkOptimizeCodePrompt, aggregationOptimizeCodePrompt, finalOptimizeCodePrompt, chunkOptimizeCodePromptVue, aggregationOptimizeCodePromptVue, finalOptimizeCodePromptVue, frameworkPromptKuikly, noSamplingAggregationPrompt, noSamplingAggregationPromptVue, noSamplingAggregationPromptKuikly, chunkOptimizeCodePromptIosOc, aggregationOptimizeCodePromptIosOc, finalOptimizeCodePromptIosOc, noSamplingAggregationPromptIosOc, chunkOptimizeCodePromptIosSwift, aggregationOptimizeCodePromptIosSwift, finalOptimizeCodePromptIosSwift, noSamplingAggregationPromptIosSwift, chunkOptimizeCodePromptTaro, aggregationOptimizeCodePromptTaro, finalOptimizeCodePromptTaro, noSamplingAggregationPromptTaro, chunkOptimizeCodePromptUniapp, aggregationOptimizeCodePromptUniapp, finalOptimizeCodePromptUniapp, noSamplingAggregationPromptUniapp, } from 'sloth-d2c-node/convert';
22
22
  import { SocketServer } from './socket-server.js';
23
23
  import { getParam } from './utils/utils.js';
24
24
  import { loadPluginFromGlobalDirAsync } from './plugin/loader.js';
@@ -83,6 +83,8 @@ export async function loadConfig(mcpServer, configManagerInstance) {
83
83
  { value: 'ios-oc', label: 'iOS OC' },
84
84
  { value: 'ios-swift', label: 'iOS Swift' },
85
85
  { value: 'kuikly', label: 'Kuikly' },
86
+ { value: 'taro', label: 'Taro' },
87
+ { value: 'uniapp', label: 'Uniapp' },
86
88
  ];
87
89
  // 初始化默认框架配置
88
90
  if (!config.frameworks || config.frameworks.length === 0) {
@@ -212,6 +214,54 @@ export async function loadConfig(mcpServer, configManagerInstance) {
212
214
  ...(kuiklyConfig || {}),
213
215
  });
214
216
  }
217
+ // 检查 taro.json 是否存在,如果不存在则创建
218
+ const taroConfigExists = await configManager.frameworkConfigExists('taro');
219
+ if (!taroConfigExists) {
220
+ await configManager.saveFrameworkConfig('taro', {
221
+ chunkOptimizePrompt: chunkOptimizeCodePromptTaro,
222
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptTaro,
223
+ finalOptimizePrompt: finalOptimizeCodePromptTaro,
224
+ componentAnalysisPrompt: '',
225
+ noSamplingAggregationPrompt: noSamplingAggregationPromptTaro,
226
+ });
227
+ const taroConfigPath = configManager.getFrameworkConfigPath('taro');
228
+ Logger.log(`已创建默认 taro 配置文件: ${taroConfigPath}`);
229
+ }
230
+ else {
231
+ const taroConfig = await configManager.loadFrameworkConfig('taro');
232
+ await configManager.saveFrameworkConfig('taro', {
233
+ chunkOptimizePrompt: chunkOptimizeCodePromptTaro,
234
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptTaro,
235
+ finalOptimizePrompt: finalOptimizeCodePromptTaro,
236
+ componentAnalysisPrompt: '',
237
+ noSamplingAggregationPrompt: noSamplingAggregationPromptTaro,
238
+ ...(taroConfig || {}),
239
+ });
240
+ }
241
+ // 检查 uniapp.json 是否存在,如果不存在则创建
242
+ const uniappConfigExists = await configManager.frameworkConfigExists('uniapp');
243
+ if (!uniappConfigExists) {
244
+ await configManager.saveFrameworkConfig('uniapp', {
245
+ chunkOptimizePrompt: chunkOptimizeCodePromptUniapp,
246
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptUniapp,
247
+ finalOptimizePrompt: finalOptimizeCodePromptUniapp,
248
+ componentAnalysisPrompt: '',
249
+ noSamplingAggregationPrompt: noSamplingAggregationPromptUniapp,
250
+ });
251
+ const uniappConfigPath = configManager.getFrameworkConfigPath('uniapp');
252
+ Logger.log(`已创建默认 uniapp 配置文件: ${uniappConfigPath}`);
253
+ }
254
+ else {
255
+ const uniappConfig = await configManager.loadFrameworkConfig('uniapp');
256
+ await configManager.saveFrameworkConfig('uniapp', {
257
+ chunkOptimizePrompt: chunkOptimizeCodePromptUniapp,
258
+ aggregationOptimizePrompt: aggregationOptimizeCodePromptUniapp,
259
+ finalOptimizePrompt: finalOptimizeCodePromptUniapp,
260
+ componentAnalysisPrompt: '',
261
+ noSamplingAggregationPrompt: noSamplingAggregationPromptUniapp,
262
+ ...(uniappConfig || {}),
263
+ });
264
+ }
215
265
  }
216
266
  catch (error) {
217
267
  Logger.error(`获取配置信息时出错: ${error}`);
@@ -657,7 +707,9 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
657
707
  const isIosOc = framework.toLowerCase() === 'ios-oc';
658
708
  const isIosSwift = framework.toLowerCase() === 'ios-swift';
659
709
  const isKuikly = framework.toLowerCase() === 'kuikly';
660
- const promptSetting = {
710
+ const isTaro = framework.toLowerCase() === 'taro';
711
+ const isUniapp = framework.toLowerCase() === 'uniapp';
712
+ let promptSetting = {
661
713
  enableFrameworkGuide: isKuikly ? true : false,
662
714
  frameworkGuidePrompt: isKuikly ? frameworkPromptKuikly : '',
663
715
  chunkOptimizePrompt: isKuikly
@@ -698,6 +750,24 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
698
750
  ? noSamplingAggregationPromptVue
699
751
  : noSamplingAggregationPrompt,
700
752
  };
753
+ if (isTaro) {
754
+ promptSetting.enableFrameworkGuide = false;
755
+ promptSetting.frameworkGuidePrompt = '';
756
+ promptSetting.chunkOptimizePrompt = chunkOptimizeCodePromptTaro;
757
+ promptSetting.aggregationOptimizePrompt = aggregationOptimizeCodePromptTaro;
758
+ promptSetting.finalOptimizePrompt = finalOptimizeCodePromptTaro;
759
+ promptSetting.noSamplingAggregationPrompt = noSamplingAggregationPromptTaro;
760
+ promptSetting.componentAnalysisPrompt = componentAnalysisPrompt;
761
+ }
762
+ if (isUniapp) {
763
+ promptSetting.enableFrameworkGuide = false;
764
+ promptSetting.frameworkGuidePrompt = '';
765
+ promptSetting.chunkOptimizePrompt = chunkOptimizeCodePromptUniapp;
766
+ promptSetting.aggregationOptimizePrompt = aggregationOptimizeCodePromptUniapp;
767
+ promptSetting.finalOptimizePrompt = finalOptimizeCodePromptUniapp;
768
+ promptSetting.noSamplingAggregationPrompt = noSamplingAggregationPromptUniapp;
769
+ promptSetting.componentAnalysisPrompt = componentAnalysisPromptVue;
770
+ }
701
771
  res.json({
702
772
  success: true,
703
773
  data: promptSetting,
@@ -948,8 +1018,6 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
948
1018
  return;
949
1019
  }
950
1020
  const renderer = pluginRenderer.default.renderConfigForm;
951
- console.log('pluginRenderer.default', pluginRenderer.default);
952
- console.log('renderer', renderer);
953
1021
  res.type('application/javascript; charset=utf-8').send(renderer);
954
1022
  });
955
1023
  // ==================== 组件映射相关接口 ====================
@@ -1871,6 +1939,30 @@ ${truncatedDiff}
1871
1939
  });
1872
1940
  }
1873
1941
  });
1942
+ app.get('/getVersion', async (req, res) => {
1943
+ try {
1944
+ // 计算 package.json 的正确路径
1945
+ // 从 dist/build/server.js 到根目录的 package.json
1946
+ const packagePath = path.join(__dirname, '../../package.json');
1947
+ const packageContent = await fs.promises.readFile(packagePath, 'utf-8');
1948
+ const packageData = JSON.parse(packageContent);
1949
+ res.json({
1950
+ success: true,
1951
+ data: {
1952
+ version: packageData.version,
1953
+ name: packageData.name,
1954
+ },
1955
+ });
1956
+ }
1957
+ catch (error) {
1958
+ Logger.error('获取版本信息失败:', error);
1959
+ res.status(500).json({
1960
+ success: false,
1961
+ message: '获取版本信息失败',
1962
+ error: error instanceof Error ? error.message : String(error),
1963
+ });
1964
+ }
1965
+ });
1874
1966
  // 启动 HTTP 服务器,监听端口
1875
1967
  httpServer = app.listen(port, async (err) => {
1876
1968
  if (err) {
@@ -1962,7 +2054,9 @@ export async function getUserInput(payload) {
1962
2054
  Logger.log('Socket 客户端已连接');
1963
2055
  const workspaceRoot = fileManager.getWorkspaceRoot();
1964
2056
  // 注册 token 并等待响应,同时传递 extra 数据给主进程
1965
- const responsePromise = socketClient.registerToken(token, { workspaceRoot, plugins: pluginManager.getPlugins() });
2057
+ const socketParams = { workspaceRoot, plugins: pluginManager.getPlugins() };
2058
+ Logger.log('socketParams', socketParams);
2059
+ const responsePromise = socketClient.registerToken(token, socketParams);
1966
2060
  // 打开浏览器
1967
2061
  await open(authUrl);
1968
2062
  // 等待认证响应
@@ -1,5 +1,5 @@
1
1
  {
2
- "buildTime": "2026-01-25T18:16:09.150Z",
2
+ "buildTime": "2026-01-26T14:18:48.853Z",
3
3
  "mode": "build",
4
4
  "pages": {
5
5
  "main": {