@szc-ft/mcp-szcd-client 0.11.0
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/agents/szcd-component-expert.md +147 -0
- package/agents/szcd-component-expert.trae.md +145 -0
- package/commands/szcd-mcp-url.md +25 -0
- package/mcp-proxy.js +543 -0
- package/package.json +56 -0
- package/scripts/lib/claude-code.js +342 -0
- package/scripts/lib/common.js +161 -0
- package/scripts/lib/opencode.js +37 -0
- package/scripts/lib/qoder.js +426 -0
- package/scripts/lib/qwen-code.js +408 -0
- package/scripts/lib/trae-cli.js +337 -0
- package/scripts/lib/trae-ide.js +198 -0
- package/scripts/lib/trae.js +65 -0
- package/scripts/postinstall.js +203 -0
- package/scripts/update-mcp-url.js +146 -0
- package/skill/SKILL.md +897 -0
- package/standard-skill/SKILL.md +1509 -0
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trae-cli.js — Trae CLI 兼容逻辑
|
|
3
|
+
*
|
|
4
|
+
* 负责:
|
|
5
|
+
* - ~/.trae/trae_cli.yaml 的读写同步
|
|
6
|
+
* - trae-cli mcp add-json / remove 命令调用
|
|
7
|
+
* - .traecli/agents/ 目录的 agent 复制
|
|
8
|
+
*
|
|
9
|
+
* 导出 setupTraeCli(deps) 供 trae.js 入口调用。
|
|
10
|
+
* deps 需包含: { getMcpServerUrl, getMcpServerName, safeExecSync, isCommandAvailable, ensureDirectory, copyFile, PROJECT_ROOT, PACKAGE_ROOT }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
import { execSync } from "node:child_process";
|
|
17
|
+
|
|
18
|
+
// ==================== 路径工具 ====================
|
|
19
|
+
|
|
20
|
+
function getHomeDir() {
|
|
21
|
+
if (os.platform() === "win32") {
|
|
22
|
+
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
23
|
+
}
|
|
24
|
+
return process.env.HOME || os.homedir();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getTraeCliYamlPath() {
|
|
28
|
+
return path.join(getHomeDir(), ".traecli", "trae_cli.yaml");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ==================== YAML 解析 ====================
|
|
32
|
+
|
|
33
|
+
function parseYamlMcpServers(content) {
|
|
34
|
+
const servers = [];
|
|
35
|
+
const lines = content.split("\n");
|
|
36
|
+
let inMcpServers = false;
|
|
37
|
+
let currentServer = null;
|
|
38
|
+
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (/^mcp_servers\s*:/.test(line)) {
|
|
41
|
+
inMcpServers = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (inMcpServers && /^\s*$/.test(line)) {
|
|
45
|
+
inMcpServers = false;
|
|
46
|
+
if (currentServer) servers.push(currentServer);
|
|
47
|
+
currentServer = null;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if (inMcpServers && /^[a-zA-Z]/.test(line)) {
|
|
51
|
+
inMcpServers = false;
|
|
52
|
+
if (currentServer) servers.push(currentServer);
|
|
53
|
+
currentServer = null;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (inMcpServers) {
|
|
57
|
+
const nameMatch = line.match(/^\s+-\s+name\s*:\s*(.+)/);
|
|
58
|
+
if (nameMatch) {
|
|
59
|
+
if (currentServer) servers.push(currentServer);
|
|
60
|
+
currentServer = { name: nameMatch[1].trim(), raw: [line] };
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (currentServer) {
|
|
64
|
+
currentServer.raw.push(line);
|
|
65
|
+
const urlMatch = line.match(/^\s+url\s*:\s*(.+)/);
|
|
66
|
+
if (urlMatch) currentServer.url = urlMatch[1].trim();
|
|
67
|
+
const typeMatch = line.match(/^\s+type\s*:\s*(.+)/);
|
|
68
|
+
if (typeMatch) currentServer.type = typeMatch[1].trim();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (currentServer) servers.push(currentServer);
|
|
73
|
+
return { servers, lines };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ==================== YAML 文件同步 ====================
|
|
77
|
+
|
|
78
|
+
export function syncTraeCliYaml(deps) {
|
|
79
|
+
const yamlPath = getTraeCliYamlPath();
|
|
80
|
+
const sseUrl = `${deps.getMcpServerUrl()}/sse`;
|
|
81
|
+
const serverName = deps.getMcpServerName();
|
|
82
|
+
|
|
83
|
+
if (!fs.existsSync(yamlPath)) {
|
|
84
|
+
const dir = path.dirname(yamlPath);
|
|
85
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
86
|
+
const content = `mcp_servers:\n - name: ${serverName}\n url: ${sseUrl}\n type: sse\n`;
|
|
87
|
+
fs.writeFileSync(yamlPath, content);
|
|
88
|
+
console.log(`✓ Created Trae CLI YAML: ${yamlPath}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let content = fs.readFileSync(yamlPath, "utf8");
|
|
93
|
+
const { servers } = parseYamlMcpServers(content);
|
|
94
|
+
|
|
95
|
+
const existing = servers.find(s => s.name === serverName);
|
|
96
|
+
if (existing && existing.url === sseUrl) {
|
|
97
|
+
console.log(`✓ Trae CLI YAML already up-to-date: ${sseUrl}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (existing) {
|
|
102
|
+
const newRaw = existing.raw.map(l =>
|
|
103
|
+
l.replace(/^(\s+url\s*:\s*).*/, `$1${sseUrl}`)
|
|
104
|
+
);
|
|
105
|
+
const oldBlock = existing.raw.join("\n");
|
|
106
|
+
const newBlock = newRaw.join("\n");
|
|
107
|
+
content = content.replace(oldBlock, newBlock);
|
|
108
|
+
console.log(`✓ Updated Trae CLI YAML: ${existing.url} → ${sseUrl}`);
|
|
109
|
+
} else {
|
|
110
|
+
const entry = ` - name: ${serverName}\n url: ${sseUrl}\n type: sse\n`;
|
|
111
|
+
if (/^mcp_servers\s*:\s*\[\]\s*$/m.test(content)) {
|
|
112
|
+
content = content.replace(/^mcp_servers\s*:\s*\[\]\s*$/m, `mcp_servers:\n${entry}`);
|
|
113
|
+
} else if (/^mcp_servers\s*:\s*$/m.test(content)) {
|
|
114
|
+
content = content.replace(/^mcp_servers\s*:\s*$/m, `mcp_servers:\n${entry}`);
|
|
115
|
+
} else if (/^mcp_servers\s*:/m.test(content)) {
|
|
116
|
+
content = content.replace(/^(mcp_servers\s*:)/m, `$1\n${entry}`);
|
|
117
|
+
} else {
|
|
118
|
+
content = content.trimEnd() + `\n\nmcp_servers:\n${entry}`;
|
|
119
|
+
}
|
|
120
|
+
console.log(`✓ Added to Trae CLI YAML: ${sseUrl}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fs.writeFileSync(yamlPath, content);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ==================== CLI 命令同步 ====================
|
|
127
|
+
|
|
128
|
+
export function createTraeCliConfig(deps) {
|
|
129
|
+
if (deps.isCommandAvailable("trae-cli")) {
|
|
130
|
+
const sseUrl = `${deps.getMcpServerUrl()}/sse`;
|
|
131
|
+
const serverName = deps.getMcpServerName();
|
|
132
|
+
const jsonConfig = `{"type":"sse","url":"${sseUrl}"}`;
|
|
133
|
+
deps.safeExecSync(`trae-cli mcp remove ${serverName} 2>/dev/null`);
|
|
134
|
+
const result = deps.safeExecSync(`trae-cli mcp add-json ${serverName} '${jsonConfig}'`);
|
|
135
|
+
if (result === true || result === "already_exists") {
|
|
136
|
+
console.log(`✓ Trae CLI command synced: ${sseUrl}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
syncTraeCliYaml(deps);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ==================== Skill 复制(Trae CLI) ====================
|
|
143
|
+
|
|
144
|
+
export function getTraeCliSkillsDirectory() {
|
|
145
|
+
return path.join(getHomeDir(), ".traecli", "skills");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function copySkillToTraeCli(deps, isProjectLevel = false) {
|
|
149
|
+
const skillName = deps.getMcpServerName();
|
|
150
|
+
let traeCliSkillsDir;
|
|
151
|
+
|
|
152
|
+
if (isProjectLevel) {
|
|
153
|
+
traeCliSkillsDir = path.join(deps.PROJECT_ROOT, ".traecli", "skills");
|
|
154
|
+
} else {
|
|
155
|
+
traeCliSkillsDir = getTraeCliSkillsDirectory();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const skillDir = path.join(traeCliSkillsDir, skillName);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
deps.ensureDirectory(skillDir);
|
|
162
|
+
const skillDest = path.join(skillDir, "SKILL.md");
|
|
163
|
+
deps.copyFile(deps.SKILL_SOURCE, skillDest);
|
|
164
|
+
console.log(`✓ Copied skill to Trae CLI ${isProjectLevel ? 'project' : 'user'} directory: ${skillDir}`);
|
|
165
|
+
return true;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.log(`⚠️ Failed to copy skill to Trae CLI ${isProjectLevel ? 'project' : 'user'} directory: ${error.message}`);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ==================== Slash Command 安装(Trae CLI) ====================
|
|
173
|
+
|
|
174
|
+
export function getTraeCliCommandsDirectory() {
|
|
175
|
+
return path.join(getHomeDir(), ".traecli", "commands");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function installTraeCliSlashCommand(deps) {
|
|
179
|
+
const commandSource = path.join(deps.PACKAGE_ROOT, "commands", "szcd-mcp-url.md");
|
|
180
|
+
if (!fs.existsSync(commandSource)) {
|
|
181
|
+
console.log("⚠️ Slash command source not found, skipping");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const traeCliCommandsDir = getTraeCliCommandsDirectory();
|
|
186
|
+
try {
|
|
187
|
+
deps.ensureDirectory(traeCliCommandsDir);
|
|
188
|
+
deps.copyFile(commandSource, path.join(traeCliCommandsDir, "szcd-mcp-url.md"));
|
|
189
|
+
console.log(`✓ Installed Trae CLI slash command: /szcd-mcp-url`);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.log(`⚠️ Failed to install Trae CLI slash command: ${error.message}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ==================== MCP URL 同步(供 update-mcp-url.js 调用) ====================
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* 同步 Trae CLI YAML 和 CLI 命令中的 MCP 服务器 URL
|
|
199
|
+
* @param {string} targetUrl - 新的 MCP 服务器基础 URL(如 http://localhost:3456)
|
|
200
|
+
* @param {string} serverName - MCP 服务器名称
|
|
201
|
+
*/
|
|
202
|
+
export function syncMcpUrl(targetUrl, serverName) {
|
|
203
|
+
const sseUrl = `${targetUrl}/sse`;
|
|
204
|
+
|
|
205
|
+
// 1. CLI 命令同步
|
|
206
|
+
if (isCommandAvailable("trae-cli")) {
|
|
207
|
+
const jsonConfig = `{"type":"sse","url":"${sseUrl}"}`;
|
|
208
|
+
safeExecSync(`trae-cli mcp remove ${serverName} 2>/dev/null`);
|
|
209
|
+
const result = safeExecSync(`trae-cli mcp add-json ${serverName} '${jsonConfig}'`);
|
|
210
|
+
if (result === true || result === "already_exists") {
|
|
211
|
+
console.log(`✓ Trae CLI command synced: ${sseUrl}`);
|
|
212
|
+
} else {
|
|
213
|
+
console.warn(`⚠️ trae-cli command sync failed (YAML file will be updated directly)`);
|
|
214
|
+
}
|
|
215
|
+
} else {
|
|
216
|
+
console.log("⏭️ Skipping trae-cli command sync: trae-cli not found");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// 2. 直接文件操作同步 YAML(确保最终状态正确)
|
|
220
|
+
syncTraeCliYamlDirect(targetUrl, serverName);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* 不依赖 deps 的 CLI 可用性检测
|
|
225
|
+
*/
|
|
226
|
+
function isCommandAvailable(cmd) {
|
|
227
|
+
try {
|
|
228
|
+
execSync(`which ${cmd} 2>/dev/null`, { stdio: "pipe", timeout: 3000 });
|
|
229
|
+
return true;
|
|
230
|
+
} catch {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 不依赖 deps 的安全命令执行
|
|
237
|
+
*/
|
|
238
|
+
function safeExecSync(command) {
|
|
239
|
+
try {
|
|
240
|
+
execSync(command, { stdio: "pipe", timeout: 10000 });
|
|
241
|
+
return true;
|
|
242
|
+
} catch (e) {
|
|
243
|
+
const stderr = e.stderr?.toString() || "";
|
|
244
|
+
if (stderr.includes("already exists")) return "already_exists";
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 直接文件操作同步 YAML(独立于 deps,供 syncMcpUrl 使用)
|
|
251
|
+
*/
|
|
252
|
+
function syncTraeCliYamlDirect(targetUrl, serverName) {
|
|
253
|
+
const yamlPath = getTraeCliYamlPath();
|
|
254
|
+
const sseUrl = `${targetUrl}/sse`;
|
|
255
|
+
|
|
256
|
+
if (!fs.existsSync(yamlPath)) {
|
|
257
|
+
const dir = path.dirname(yamlPath);
|
|
258
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
259
|
+
const content = `mcp_servers:\n - name: ${serverName}\n url: ${sseUrl}\n type: sse\n`;
|
|
260
|
+
fs.writeFileSync(yamlPath, content);
|
|
261
|
+
console.log(`✓ Created Trae CLI YAML: ${yamlPath}`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
let content = fs.readFileSync(yamlPath, "utf8");
|
|
266
|
+
const { servers } = parseYamlMcpServers(content);
|
|
267
|
+
|
|
268
|
+
const existing = servers.find(s => s.name === serverName);
|
|
269
|
+
if (existing && existing.url === sseUrl) {
|
|
270
|
+
console.log(`⏭️ Trae CLI YAML already up-to-date: ${sseUrl}`);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (existing) {
|
|
275
|
+
const newRaw = existing.raw.map(l =>
|
|
276
|
+
l.replace(/^(\s+url\s*:\s*).*/, `$1${sseUrl}`)
|
|
277
|
+
);
|
|
278
|
+
const oldBlock = existing.raw.join("\n");
|
|
279
|
+
const newBlock = newRaw.join("\n");
|
|
280
|
+
content = content.replace(oldBlock, newBlock);
|
|
281
|
+
console.log(`✓ Updated Trae CLI YAML: ${existing.url} → ${sseUrl}`);
|
|
282
|
+
} else {
|
|
283
|
+
const entry = ` - name: ${serverName}\n url: ${sseUrl}\n type: sse\n`;
|
|
284
|
+
if (/^mcp_servers\s*:\s*\[\]\s*$/m.test(content)) {
|
|
285
|
+
content = content.replace(/^mcp_servers\s*:\s*\[\]\s*$/m, `mcp_servers:\n${entry}`);
|
|
286
|
+
} else if (/^mcp_servers\s*:\s*$/m.test(content)) {
|
|
287
|
+
content = content.replace(/^mcp_servers\s*:\s*$/m, `mcp_servers:\n${entry}`);
|
|
288
|
+
} else if (/^mcp_servers\s*:/m.test(content)) {
|
|
289
|
+
content = content.replace(/^(mcp_servers\s*:)/m, `$1\n${entry}`);
|
|
290
|
+
} else {
|
|
291
|
+
content = content.trimEnd() + `\n\nmcp_servers:\n${entry}`;
|
|
292
|
+
}
|
|
293
|
+
console.log(`✓ Added to Trae CLI YAML: ${sseUrl}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fs.writeFileSync(yamlPath, content);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// ==================== Agent 复制(Trae CLI) ====================
|
|
300
|
+
|
|
301
|
+
export function copyTraeCliAgent(deps, isProjectLevel = false) {
|
|
302
|
+
const agentsSourceDir = path.join(deps.PACKAGE_ROOT, "agents");
|
|
303
|
+
if (!fs.existsSync(agentsSourceDir)) {
|
|
304
|
+
console.log("⏭️ Skipping agent install for Trae CLI: agents source directory not found");
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
let traeCliAgentsDir;
|
|
309
|
+
if (isProjectLevel) {
|
|
310
|
+
traeCliAgentsDir = path.join(deps.PROJECT_ROOT, ".traecli", "agents");
|
|
311
|
+
} else {
|
|
312
|
+
traeCliAgentsDir = path.join(getHomeDir(), ".traecli", "agents");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Trae CLI 只复制 .trae.md 文件,并重命名为 .md(适配 YAML frontmatter 格式)
|
|
316
|
+
const agentFiles = fs.readdirSync(agentsSourceDir).filter(f => f.endsWith(".trae.md"));
|
|
317
|
+
let copied = 0;
|
|
318
|
+
|
|
319
|
+
for (const agentFile of agentFiles) {
|
|
320
|
+
const sourcePath = path.join(agentsSourceDir, agentFile);
|
|
321
|
+
const destName = agentFile.replace(".trae.md", ".md");
|
|
322
|
+
|
|
323
|
+
try {
|
|
324
|
+
deps.ensureDirectory(traeCliAgentsDir);
|
|
325
|
+
deps.copyFile(sourcePath, path.join(traeCliAgentsDir, destName));
|
|
326
|
+
copied++;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.log(`⚠️ Failed to copy agent ${agentFile} to Trae CLI: ${error.message}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (copied > 0) {
|
|
333
|
+
console.log(`✓ Copied ${copied} agent(s) to Trae CLI ${isProjectLevel ? 'project' : 'user'} directory: ${traeCliAgentsDir}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return copied > 0;
|
|
337
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trae-ide.js — Trae IDE (Trae CN) 兼容逻辑
|
|
3
|
+
*
|
|
4
|
+
* 负责:
|
|
5
|
+
* - mcp.json 的读写同步(支持 Windows %APPDATA%\Trae CN\User\mcp.json)
|
|
6
|
+
* - skills 复制到 ~/.trae-cn/skills/
|
|
7
|
+
* - agents 复制到 ~/.trae-cn/agents/ 与项目级 .trae/agents/
|
|
8
|
+
*
|
|
9
|
+
* 导出 setupTraeIde(deps) 供 trae.js 入口调用。
|
|
10
|
+
* deps 需包含: { getMcpServerUrl, getMcpServerName, ensureDirectory, copyFile, writeFile, PROJECT_ROOT, PACKAGE_ROOT }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import os from "node:os";
|
|
16
|
+
|
|
17
|
+
// ==================== 路径工具 ====================
|
|
18
|
+
|
|
19
|
+
function getHomeDir() {
|
|
20
|
+
if (os.platform() === "win32") {
|
|
21
|
+
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
22
|
+
}
|
|
23
|
+
return process.env.HOME || os.homedir();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getTraeUserDirectory() {
|
|
27
|
+
const homeDir = getHomeDir();
|
|
28
|
+
if (!homeDir) throw new Error("Home directory not found");
|
|
29
|
+
return path.join(homeDir, ".trae-cn");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Trae IDE 的 mcp.json 路径。
|
|
34
|
+
* Windows: %APPDATA%\Trae CN\User\mcp.json
|
|
35
|
+
* macOS/Linux: ~/.trae-cn/mcp.json
|
|
36
|
+
*/
|
|
37
|
+
export function getTraeIdeMcpJsonPath() {
|
|
38
|
+
if (os.platform() === "win32") {
|
|
39
|
+
const appData = process.env.APPDATA;
|
|
40
|
+
if (appData) {
|
|
41
|
+
return path.join(appData, "Trae CN", "User", "mcp.json");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return path.join(getTraeUserDirectory(), "mcp.json");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function getTraeSkillsDirectory() {
|
|
48
|
+
return path.join(getTraeUserDirectory(), "skills");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getTraeAgentsDirectory() {
|
|
52
|
+
return path.join(getTraeUserDirectory(), "agents");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTraeProjectDirectory(deps) {
|
|
56
|
+
return path.join(deps.PROJECT_ROOT, ".trae");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ==================== mcp.json 文件同步 ====================
|
|
60
|
+
|
|
61
|
+
export function syncTraeCnMcpJson(deps) {
|
|
62
|
+
const mcpJsonPath = getTraeIdeMcpJsonPath();
|
|
63
|
+
const sseUrl = `${deps.getMcpServerUrl()}/sse`;
|
|
64
|
+
const serverName = deps.getMcpServerName();
|
|
65
|
+
|
|
66
|
+
let config = {};
|
|
67
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
68
|
+
try {
|
|
69
|
+
config = JSON.parse(fs.readFileSync(mcpJsonPath, "utf8"));
|
|
70
|
+
} catch { /* 忽略 */ }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
74
|
+
|
|
75
|
+
const current = config.mcpServers[serverName];
|
|
76
|
+
if (current && current.url === sseUrl && current.type === "sse") {
|
|
77
|
+
console.log(`✓ Trae CN mcp.json already up-to-date: ${sseUrl}`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
config.mcpServers[serverName] = {
|
|
82
|
+
type: "sse",
|
|
83
|
+
url: sseUrl,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const dir = path.dirname(mcpJsonPath);
|
|
87
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
88
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2));
|
|
89
|
+
console.log(`✓ Updated Trae CN mcp.json: ${sseUrl}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function createTraeIdeConfig(deps) {
|
|
93
|
+
syncTraeCnMcpJson(deps);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ==================== MCP URL 同步(供 update-mcp-url.js 调用) ====================
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 同步 Trae IDE (Trae CN) mcp.json 中的 MCP 服务器 URL
|
|
100
|
+
* @param {string} targetUrl - 新的 MCP 服务器基础 URL(如 http://localhost:3456)
|
|
101
|
+
* @param {string} serverName - MCP 服务器名称
|
|
102
|
+
*/
|
|
103
|
+
export function syncMcpUrl(targetUrl, serverName) {
|
|
104
|
+
const mcpJsonPath = getTraeIdeMcpJsonPath();
|
|
105
|
+
const sseUrl = `${targetUrl}/sse`;
|
|
106
|
+
|
|
107
|
+
let config = {};
|
|
108
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
109
|
+
try {
|
|
110
|
+
config = JSON.parse(fs.readFileSync(mcpJsonPath, "utf8"));
|
|
111
|
+
} catch { /* 忽略 */ }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
115
|
+
|
|
116
|
+
const current = config.mcpServers[serverName];
|
|
117
|
+
if (current && current.url === sseUrl && current.type === "sse") {
|
|
118
|
+
console.log(`⏭️ Trae CN mcp.json already up-to-date: ${sseUrl}`);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
config.mcpServers[serverName] = {
|
|
123
|
+
type: "sse",
|
|
124
|
+
url: sseUrl,
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const dir = path.dirname(mcpJsonPath);
|
|
128
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
129
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(config, null, 2));
|
|
130
|
+
console.log(`✓ Updated Trae CN mcp.json: ${sseUrl}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ==================== Skill 复制 ====================
|
|
134
|
+
|
|
135
|
+
export function copySkillToTrae(deps, isProjectLevel = false) {
|
|
136
|
+
const skillName = deps.getMcpServerName();
|
|
137
|
+
let traeSkillsDir;
|
|
138
|
+
|
|
139
|
+
if (isProjectLevel) {
|
|
140
|
+
traeSkillsDir = path.join(deps.PROJECT_ROOT, ".trae", "skills");
|
|
141
|
+
} else {
|
|
142
|
+
traeSkillsDir = getTraeSkillsDirectory();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const skillDir = path.join(traeSkillsDir, skillName);
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
deps.ensureDirectory(skillDir);
|
|
149
|
+
const skillDest = path.join(skillDir, "SKILL.md");
|
|
150
|
+
deps.copyFile(deps.SKILL_SOURCE, skillDest);
|
|
151
|
+
console.log(`✓ Copied standard skill to Trae ${isProjectLevel ? 'project' : 'user'} directory: ${skillDir}`);
|
|
152
|
+
return true;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.log(`⚠️ Failed to copy skill to Trae ${isProjectLevel ? 'project' : 'user'} directory: ${error.message}`);
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ==================== Agent 复制(Trae IDE) ====================
|
|
160
|
+
|
|
161
|
+
export function copyTraeIdeAgent(deps, isProjectLevel = false) {
|
|
162
|
+
const agentsSourceDir = path.join(deps.PACKAGE_ROOT, "agents");
|
|
163
|
+
if (!fs.existsSync(agentsSourceDir)) {
|
|
164
|
+
console.log("⏭️ Skipping agent install for Trae IDE: agents source directory not found");
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let traeIdeAgentsDir;
|
|
169
|
+
if (isProjectLevel) {
|
|
170
|
+
traeIdeAgentsDir = path.join(deps.PROJECT_ROOT, ".trae", "agents");
|
|
171
|
+
} else {
|
|
172
|
+
traeIdeAgentsDir = getTraeAgentsDirectory();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Trae IDE 只复制 .trae.md 文件,并重命名为 .md(适配 YAML frontmatter 格式)
|
|
176
|
+
const agentFiles = fs.readdirSync(agentsSourceDir).filter(f => f.endsWith(".trae.md"));
|
|
177
|
+
let copied = 0;
|
|
178
|
+
|
|
179
|
+
for (const agentFile of agentFiles) {
|
|
180
|
+
const sourcePath = path.join(agentsSourceDir, agentFile);
|
|
181
|
+
const destName = agentFile.replace(".trae.md", ".md");
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
deps.ensureDirectory(traeIdeAgentsDir);
|
|
185
|
+
deps.copyFile(sourcePath, path.join(traeIdeAgentsDir, destName));
|
|
186
|
+
copied++;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.log(`⚠️ Failed to copy agent ${agentFile} to Trae IDE: ${error.message}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (copied > 0) {
|
|
193
|
+
console.log(`✓ Copied ${copied} agent(s) to Trae IDE ${isProjectLevel ? 'project' : 'user'} directory: ${traeIdeAgentsDir}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return copied > 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trae.js — Trae CLI / Trae IDE 兼容逻辑入口
|
|
3
|
+
*
|
|
4
|
+
* 负责编排 Trae CLI 与 Trae IDE 的初始化流程。
|
|
5
|
+
* 具体逻辑已按方向拆离到:
|
|
6
|
+
* - trae-cli.js Trae CLI 专属逻辑(YAML、trae-cli 命令、.traecli/skills/agents/commands)
|
|
7
|
+
* - trae-ide.js Trae IDE 专属逻辑(mcp.json、.trae-cn/skills、.trae-cn/agents)
|
|
8
|
+
*
|
|
9
|
+
* 导出 setupTrae(deps) 供 postinstall.js 调用。
|
|
10
|
+
* deps 需包含: { getMcpServerUrl, getMcpServerName, safeExecSync, isCommandAvailable, ensureDirectory, copyFile, writeFile, SKILL_SOURCE, PROJECT_ROOT, PACKAGE_ROOT }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
createTraeCliConfig,
|
|
15
|
+
copyTraeCliAgent,
|
|
16
|
+
copySkillToTraeCli,
|
|
17
|
+
installTraeCliSlashCommand,
|
|
18
|
+
getTraeCliSkillsDirectory,
|
|
19
|
+
} from "./trae-cli.js";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
createTraeIdeConfig,
|
|
23
|
+
copySkillToTrae,
|
|
24
|
+
copyTraeIdeAgent,
|
|
25
|
+
getTraeAgentsDirectory,
|
|
26
|
+
} from "./trae-ide.js";
|
|
27
|
+
|
|
28
|
+
// ==================== 导出 ====================
|
|
29
|
+
|
|
30
|
+
export function setupTrae(deps) {
|
|
31
|
+
// ---- Trae IDE ----
|
|
32
|
+
createTraeIdeConfig(deps);
|
|
33
|
+
copySkillToTrae(deps, false);
|
|
34
|
+
// IDE 暂不支持 agent,跳过
|
|
35
|
+
// copyTraeIdeAgent(deps, false);
|
|
36
|
+
|
|
37
|
+
// ---- Trae CLI ----
|
|
38
|
+
createTraeCliConfig(deps);
|
|
39
|
+
copySkillToTraeCli(deps, false);
|
|
40
|
+
copyTraeCliAgent(deps, false);
|
|
41
|
+
installTraeCliSlashCommand(deps);
|
|
42
|
+
|
|
43
|
+
// 项目级 Trae 配置暂不启用,用户级已足够
|
|
44
|
+
// let traeProjectInstalled = false;
|
|
45
|
+
// if (deps.PROJECT_ROOT && fs.existsSync(deps.PROJECT_ROOT)) {
|
|
46
|
+
// try {
|
|
47
|
+
// traeProjectInstalled = createTraeProjectConfig(deps);
|
|
48
|
+
// if (traeProjectInstalled) {
|
|
49
|
+
// copySkillToTrae(deps, true);
|
|
50
|
+
// // IDE 暂不支持 agent,跳过
|
|
51
|
+
// // copyTraeIdeAgent(deps, true);
|
|
52
|
+
// copySkillToTraeCli(deps, true);
|
|
53
|
+
// copyTraeCliAgent(deps, true);
|
|
54
|
+
// }
|
|
55
|
+
// } catch (e) {
|
|
56
|
+
// console.log(`⚠️ Failed to install Trae project config: ${e.message}`);
|
|
57
|
+
// }
|
|
58
|
+
// }
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
traeProjectInstalled: false,
|
|
62
|
+
skillsDirectory: getTraeCliSkillsDirectory(),
|
|
63
|
+
agentsDirectory: getTraeAgentsDirectory(),
|
|
64
|
+
};
|
|
65
|
+
}
|