sloth-d2c-mcp 1.0.4-beta65
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/README.md +100 -0
- package/cli/run.js +102 -0
- package/dist/build/config-manager/index.js +160 -0
- package/dist/build/index.js +839 -0
- package/dist/build/interceptor/client.js +142 -0
- package/dist/build/interceptor/vscode.js +143 -0
- package/dist/build/interceptor/web.js +28 -0
- package/dist/build/server.js +539 -0
- package/dist/build/utils/extract.js +166 -0
- package/dist/build/utils/file-manager.js +241 -0
- package/dist/build/utils/logger.js +90 -0
- package/dist/build/utils/update.js +54 -0
- package/dist/build/utils/utils.js +165 -0
- package/dist/build/utils/vscode-logger.js +133 -0
- package/dist/build/utils/webpack-substitutions.js +196 -0
- package/dist/interceptor-web/dist/build-report.json +18 -0
- package/dist/interceptor-web/dist/detail.html +1 -0
- package/dist/interceptor-web/dist/index.html +1 -0
- package/package.json +90 -0
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
## D2C MCP Server
|
|
2
|
+
|
|
3
|
+
这是一个MCP (Model Context Protocol) 服务器,提供Figma设计转代码的功能。
|
|
4
|
+
|
|
5
|
+
## 可用工具
|
|
6
|
+
|
|
7
|
+
### 1\. d2c\_figma\_intercept
|
|
8
|
+
|
|
9
|
+
使用Tauri桌面应用获取用户输入的工具。
|
|
10
|
+
|
|
11
|
+
**特点:**
|
|
12
|
+
|
|
13
|
+
* 打开原生桌面应用
|
|
14
|
+
* 更好的用户体验
|
|
15
|
+
* 窗口始终保持在最前面
|
|
16
|
+
* 支持快捷键操作
|
|
17
|
+
|
|
18
|
+
**参数:**
|
|
19
|
+
|
|
20
|
+
* `fileKey`: Figma文件的key (必需)
|
|
21
|
+
* `nodeId`: 节点ID (可选)
|
|
22
|
+
* `depth`: 遍历深度 (可选)
|
|
23
|
+
|
|
24
|
+
### 2\. d2c\_figma\_intercept\_web
|
|
25
|
+
|
|
26
|
+
使用网页浏览器获取用户输入的工具。
|
|
27
|
+
|
|
28
|
+
**特点:**
|
|
29
|
+
|
|
30
|
+
* 在默认浏览器中打开网页
|
|
31
|
+
* 兼容性更好
|
|
32
|
+
* 无需额外的桌面应用
|
|
33
|
+
|
|
34
|
+
**参数:**
|
|
35
|
+
|
|
36
|
+
* `fileKey`: Figma文件的key (必需)
|
|
37
|
+
* `nodeId`: 节点ID (可选)
|
|
38
|
+
* `depth`: 遍历深度 (可选)
|
|
39
|
+
|
|
40
|
+
## 使用方法
|
|
41
|
+
|
|
42
|
+
启动MCP服务器:
|
|
43
|
+
|
|
44
|
+
通过MCP客户端调用工具:
|
|
45
|
+
|
|
46
|
+
或者使用网页版本:
|
|
47
|
+
|
|
48
|
+
```plaintext
|
|
49
|
+
{
|
|
50
|
+
"method": "tools/call",
|
|
51
|
+
"params": {
|
|
52
|
+
"name": "d2c_figma_intercept_web",
|
|
53
|
+
"arguments": {
|
|
54
|
+
"fileKey": "your-figma-file-key",
|
|
55
|
+
"nodeId": "optional-node-id"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 端口配置
|
|
62
|
+
|
|
63
|
+
* MCP服务器: 端口3001 (如果被占用会自动尝试下一个端口)
|
|
64
|
+
* 网页服务器: 端口3002
|
|
65
|
+
* Tauri回调服务器: 动态端口 (从3001开始)
|
|
66
|
+
|
|
67
|
+
## 开发
|
|
68
|
+
|
|
69
|
+
```plaintext
|
|
70
|
+
# 安装依赖
|
|
71
|
+
npm install
|
|
72
|
+
|
|
73
|
+
# 开发模式
|
|
74
|
+
npm run dev
|
|
75
|
+
|
|
76
|
+
# 构建
|
|
77
|
+
npm run build
|
|
78
|
+
|
|
79
|
+
# 测试
|
|
80
|
+
node test-both-tools.js
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
```plaintext
|
|
84
|
+
{
|
|
85
|
+
"method": "tools/call",
|
|
86
|
+
"params": {
|
|
87
|
+
"name": "d2c_figma_intercept",
|
|
88
|
+
"arguments": {
|
|
89
|
+
"fileKey": "your-figma-file-key",
|
|
90
|
+
"nodeId": "optional-node-id"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```plaintext
|
|
97
|
+
npm run dev
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
npx @modelcontextprotocol/_inspector_
|
package/cli/run.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// 获取命令行参数
|
|
4
|
+
const args = process.argv.slice(2);
|
|
5
|
+
|
|
6
|
+
// 处理 --log 参数:开启文件日志(同时保留控制台输出)
|
|
7
|
+
const logArgIndex = args.findIndex((a) => a === '--log' || a.startsWith('--log='));
|
|
8
|
+
if (logArgIndex !== -1) {
|
|
9
|
+
let logFilePath = 'runtime.log';
|
|
10
|
+
const raw = args[logArgIndex];
|
|
11
|
+
// 移除 --log 参数,避免传递给后续程序
|
|
12
|
+
args.splice(logArgIndex, 1);
|
|
13
|
+
process.argv = [process.argv[0], process.argv[1], ...args];
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const { createWriteStream, mkdirSync, existsSync } = await import('node:fs');
|
|
17
|
+
const path = await import('node:path');
|
|
18
|
+
const util = await import('node:util');
|
|
19
|
+
|
|
20
|
+
if (raw.startsWith('--log=')) {
|
|
21
|
+
const provided = raw.slice('--log='.length).trim();
|
|
22
|
+
if (provided) {
|
|
23
|
+
logFilePath = path.isAbsolute(provided) ? provided : path.resolve(process.cwd(), provided);
|
|
24
|
+
}
|
|
25
|
+
} else {
|
|
26
|
+
logFilePath = path.resolve(process.cwd(), logFilePath);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const logDir = path.dirname(logFilePath);
|
|
30
|
+
if (!existsSync(logDir)) {
|
|
31
|
+
mkdirSync(logDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
const logStream = createWriteStream(logFilePath, { flags: 'a' });
|
|
34
|
+
|
|
35
|
+
const originalConsole = {
|
|
36
|
+
log: console.log,
|
|
37
|
+
info: console.info,
|
|
38
|
+
warn: console.warn,
|
|
39
|
+
error: console.error,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const writeLine = (...args) => {
|
|
43
|
+
try {
|
|
44
|
+
const line = util.format(...args) + '\n';
|
|
45
|
+
logStream.write(line);
|
|
46
|
+
} catch {}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
console.log = (...a) => { originalConsole.log(...a); writeLine(...a); };
|
|
50
|
+
console.info = (...a) => { originalConsole.info(...a); writeLine(...a); };
|
|
51
|
+
console.warn = (...a) => { originalConsole.warn(...a); writeLine(...a); };
|
|
52
|
+
console.error = (...a) => { originalConsole.error(...a); writeLine(...a); };
|
|
53
|
+
|
|
54
|
+
process.env.SLOTH_LOG_FILE = logFilePath;
|
|
55
|
+
originalConsole.log(`[sloth] 日志已开启,写入: ${logFilePath}`);
|
|
56
|
+
|
|
57
|
+
const close = () => {
|
|
58
|
+
try { logStream.end(); } catch {}
|
|
59
|
+
};
|
|
60
|
+
process.on('exit', close);
|
|
61
|
+
process.on('SIGINT', close);
|
|
62
|
+
process.on('SIGTERM', close);
|
|
63
|
+
process.on('uncaughtException', close);
|
|
64
|
+
process.on('unhandledRejection', close);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
// 记录但不中断启动
|
|
67
|
+
console.warn('[sloth] 启用文件日志失败:', e);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 检查是否是 config 命令
|
|
72
|
+
if (args[0] === 'config') {
|
|
73
|
+
// 设置环境变量标识这是 config 命令
|
|
74
|
+
process.env.SLOTH_COMMAND = 'config';
|
|
75
|
+
// 传递子命令参数
|
|
76
|
+
process.env.SLOTH_CONFIG_ARGS = JSON.stringify(args.slice(1));
|
|
77
|
+
} else if (args[0] === 'page') {
|
|
78
|
+
// 设置环境变量标识这是 page 命令
|
|
79
|
+
process.env.SLOTH_COMMAND = 'page';
|
|
80
|
+
// 传递子命令参数
|
|
81
|
+
process.env.SLOTH_PAGE_ARGS = JSON.stringify(args.slice(1));
|
|
82
|
+
} else if (args[0] === 'version' || args[0] === '--version' || args[0] === '-v') {
|
|
83
|
+
// 设置环境变量标识这是 version 命令
|
|
84
|
+
process.env.SLOTH_COMMAND = 'version';
|
|
85
|
+
} else if (args[0] === '--help' || args[0] === '-h' || args.length === 0) {
|
|
86
|
+
// 显示主帮助信息
|
|
87
|
+
process.env.SLOTH_COMMAND = 'help';
|
|
88
|
+
} else if (args[0] === 'clear') {
|
|
89
|
+
// 设置环境变量标识这是 clear 命令
|
|
90
|
+
process.env.SLOTH_COMMAND = 'clear';
|
|
91
|
+
} else {
|
|
92
|
+
// 设置环境变量为CLI模式
|
|
93
|
+
process.env.NODE_ENV = "cli";
|
|
94
|
+
|
|
95
|
+
// 确保--stdio参数被传递
|
|
96
|
+
if (!process.argv.includes("--stdio")) {
|
|
97
|
+
process.argv.push("--stdio");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 导入并启动主程序
|
|
102
|
+
import('../dist/build/index.js');
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import envPaths from 'env-paths';
|
|
4
|
+
import { Logger } from '../utils/logger.js';
|
|
5
|
+
export const defaultConfigData = {
|
|
6
|
+
convertSetting: {
|
|
7
|
+
framework: 'react',
|
|
8
|
+
convertScale: 1,
|
|
9
|
+
colorValue: 'hex',
|
|
10
|
+
},
|
|
11
|
+
imageSetting: {
|
|
12
|
+
imageStorageType: 'local',
|
|
13
|
+
imageStorageScale: 1,
|
|
14
|
+
imageIconType: 'svg',
|
|
15
|
+
// 本地存储配置
|
|
16
|
+
imageStoragePath: './static',
|
|
17
|
+
imageStorageNamingRule: '[name]_[contenthash:6]',
|
|
18
|
+
// OSS 配置
|
|
19
|
+
ossRegion: '',
|
|
20
|
+
ossAccessKeyId: '',
|
|
21
|
+
ossAccessKeySecret: '',
|
|
22
|
+
ossBucket: '',
|
|
23
|
+
ossPath: '',
|
|
24
|
+
ossCdnDomain: '',
|
|
25
|
+
// 接口上传配置
|
|
26
|
+
imageStorageApiUrl: '',
|
|
27
|
+
imageStorageApiFileField: 'file',
|
|
28
|
+
imageStorageApiUrlField: 'url',
|
|
29
|
+
imageStorageApiCustomHeader: '',
|
|
30
|
+
imageStorageApiCustomBody: '',
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
// 配置管理器类
|
|
34
|
+
export class ConfigManager {
|
|
35
|
+
paths; // 应用路径配置
|
|
36
|
+
configPath; // 配置文件路径
|
|
37
|
+
constructor(appName) {
|
|
38
|
+
this.paths = envPaths(appName);
|
|
39
|
+
this.configPath = path.join(this.paths.config, 'config.json');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 从文件加载配置
|
|
43
|
+
* @returns Promise<ConfigData> - 配置对象,如果文件不存在则返回空对象
|
|
44
|
+
*/
|
|
45
|
+
async load() {
|
|
46
|
+
try {
|
|
47
|
+
const data = await fs.readFile(this.configPath, 'utf-8');
|
|
48
|
+
return JSON.parse(data);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
Logger.error(`配置文件加载出错。${err.toString()}`);
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 保存配置到文件
|
|
57
|
+
* @param data - 要保存的配置对象
|
|
58
|
+
*/
|
|
59
|
+
async save(data) {
|
|
60
|
+
try {
|
|
61
|
+
await fs.mkdir(path.dirname(this.configPath), { recursive: true }); // 确保目录存在
|
|
62
|
+
await fs.writeFile(this.configPath, JSON.stringify(data, null, 2)); // 格式化保存JSON
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
Logger.error(error);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 获取配置文件路径
|
|
70
|
+
* @returns string - 配置文件的完整路径
|
|
71
|
+
*/
|
|
72
|
+
getConfigPath() {
|
|
73
|
+
return this.configPath;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 检查配置文件是否存在
|
|
77
|
+
* @returns Promise<boolean> - 如果配置文件存在则返回true
|
|
78
|
+
*/
|
|
79
|
+
async exists() {
|
|
80
|
+
try {
|
|
81
|
+
await fs.access(this.configPath);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 删除配置文件
|
|
90
|
+
*/
|
|
91
|
+
async delete() {
|
|
92
|
+
try {
|
|
93
|
+
await fs.unlink(this.configPath);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
if (err.code !== 'ENOENT') {
|
|
97
|
+
throw err; // 如果不是文件不存在错误,则抛出异常
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 获取特定 fileKey 的配置,如果不存在则返回默认配置
|
|
103
|
+
* @param fileKey - 文件键
|
|
104
|
+
* @returns Promise<{ convertSetting: ConvertSetting, imageSetting: ImageSetting }> - 合并后的配置
|
|
105
|
+
*/
|
|
106
|
+
async getFileConfig(fileKey) {
|
|
107
|
+
const config = await this.load();
|
|
108
|
+
// 获取默认配置
|
|
109
|
+
const defaultConvertSetting = config.convertSetting || {};
|
|
110
|
+
const defaultImageSetting = config.imageSetting || {};
|
|
111
|
+
// 获取 fileKey 特定的配置
|
|
112
|
+
const fileConfig = config.fileConfigs?.[fileKey] || {};
|
|
113
|
+
// 合并配置:fileKey 配置优先于默认配置
|
|
114
|
+
const convertSetting = { ...defaultConvertSetting, ...fileConfig.convertSetting };
|
|
115
|
+
const imageSetting = { ...defaultImageSetting, ...fileConfig.imageSetting };
|
|
116
|
+
return { convertSetting, imageSetting };
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* 保存特定 fileKey 的配置
|
|
120
|
+
* @param fileKey - 文件键
|
|
121
|
+
* @param convertSetting - 转换设置(可选)
|
|
122
|
+
* @param imageSetting - 图片设置(可选)
|
|
123
|
+
*/
|
|
124
|
+
async saveFileConfig(fileKey, convertSetting, imageSetting) {
|
|
125
|
+
const config = await this.load();
|
|
126
|
+
// 初始化 fileConfigs 如果不存在
|
|
127
|
+
if (!config.fileConfigs) {
|
|
128
|
+
config.fileConfigs = {};
|
|
129
|
+
}
|
|
130
|
+
// 初始化特定 fileKey 的配置如果不存在
|
|
131
|
+
if (!config.fileConfigs[fileKey]) {
|
|
132
|
+
config.fileConfigs[fileKey] = {};
|
|
133
|
+
}
|
|
134
|
+
// 更新配置
|
|
135
|
+
if (convertSetting) {
|
|
136
|
+
config.fileConfigs[fileKey].convertSetting = convertSetting;
|
|
137
|
+
}
|
|
138
|
+
if (imageSetting) {
|
|
139
|
+
config.fileConfigs[fileKey].imageSetting = imageSetting;
|
|
140
|
+
}
|
|
141
|
+
// 保存配置
|
|
142
|
+
await this.save(config);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 更新默认配置
|
|
146
|
+
* @param convertSetting - 默认转换设置(可选)
|
|
147
|
+
* @param imageSetting - 默认图片设置(可选)
|
|
148
|
+
*/
|
|
149
|
+
async updateDefaultConfig(convertSetting, imageSetting) {
|
|
150
|
+
const config = await this.load();
|
|
151
|
+
if (convertSetting) {
|
|
152
|
+
config.convertSetting = convertSetting;
|
|
153
|
+
}
|
|
154
|
+
if (imageSetting) {
|
|
155
|
+
config.imageSetting = imageSetting;
|
|
156
|
+
}
|
|
157
|
+
await this.save(config);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export default ConfigManager;
|