@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,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* claude-code.js — Claude Code 全部兼容逻辑
|
|
3
|
+
*
|
|
4
|
+
* 导出 setupClaudeCode(deps) 供 postinstall.js 调用。
|
|
5
|
+
* deps 需包含: { getMcpServerUrl, getMcpServerName, safeExecSync, isCommandAvailable, ensureDirectory, copyFile, SKILL_SOURCE, PROJECT_ROOT }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import { execSync } from "node:child_process";
|
|
12
|
+
|
|
13
|
+
// ==================== 路径工具 ====================
|
|
14
|
+
|
|
15
|
+
function getHomeDir() {
|
|
16
|
+
if (os.platform() === "win32") {
|
|
17
|
+
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
18
|
+
}
|
|
19
|
+
return process.env.HOME || os.homedir();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getClaudeCodeUserDirectory() {
|
|
23
|
+
const homeDir = getHomeDir();
|
|
24
|
+
if (!homeDir) throw new Error("Home directory not found");
|
|
25
|
+
return path.join(homeDir, ".claude");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getClaudeCodeSkillsDirectory() {
|
|
29
|
+
return path.join(getClaudeCodeUserDirectory(), "skills");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getClaudeCodeAgentsDirectory() {
|
|
33
|
+
return path.join(getClaudeCodeUserDirectory(), "agents");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getClaudeCodeConfigPath() {
|
|
37
|
+
return path.join(getHomeDir(), ".claude.json");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ==================== 直接文件操作 ====================
|
|
41
|
+
|
|
42
|
+
function syncClaudeCodeConfig(deps) {
|
|
43
|
+
const configPath = getClaudeCodeConfigPath();
|
|
44
|
+
const sseUrl = `${deps.getMcpServerUrl()}/sse`;
|
|
45
|
+
const serverName = deps.getMcpServerName();
|
|
46
|
+
|
|
47
|
+
let config = {};
|
|
48
|
+
if (fs.existsSync(configPath)) {
|
|
49
|
+
try {
|
|
50
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
51
|
+
} catch { /* 忽略 */ }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
55
|
+
|
|
56
|
+
const current = config.mcpServers[serverName];
|
|
57
|
+
if (current && current.type === "sse" && current.url === sseUrl) {
|
|
58
|
+
console.log(`✓ Claude Code ~/.claude.json already up-to-date: ${sseUrl}`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
config.mcpServers[serverName] = {
|
|
63
|
+
type: "sse",
|
|
64
|
+
url: sseUrl,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
68
|
+
console.log(`✓ Updated Claude Code ~/.claude.json: ${sseUrl}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ==================== CLI 命令同步 ====================
|
|
72
|
+
|
|
73
|
+
function createClaudeCodeUserConfig(deps) {
|
|
74
|
+
if (deps.isCommandAvailable("claude")) {
|
|
75
|
+
const sseUrl = `${deps.getMcpServerUrl()}/sse`;
|
|
76
|
+
const serverName = deps.getMcpServerName();
|
|
77
|
+
|
|
78
|
+
const result = deps.safeExecSync(`claude mcp add --transport sse --scope user ${serverName} ${sseUrl}`);
|
|
79
|
+
if (result === true) {
|
|
80
|
+
console.log(`✓ Claude Code user MCP server added via CLI: ${serverName} (${sseUrl})`);
|
|
81
|
+
} else if (result === "already_exists") {
|
|
82
|
+
console.log(`✓ Claude Code user MCP server already configured: ${serverName} (${sseUrl})`);
|
|
83
|
+
} else {
|
|
84
|
+
if (deps.safeExecSync(`claude mcp remove --scope user ${serverName} 2>/dev/null`) &&
|
|
85
|
+
deps.safeExecSync(`claude mcp add --transport sse --scope user ${serverName} ${sseUrl}`)) {
|
|
86
|
+
console.log(`✓ Claude Code user MCP server updated via CLI: ${serverName} (${sseUrl})`);
|
|
87
|
+
} else {
|
|
88
|
+
console.warn(`⚠️ Failed to configure Claude Code MCP server via CLI (config file will be updated directly)`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
console.log("⏭️ Skipping Claude Code CLI sync: claude command not found");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
syncClaudeCodeConfig(deps);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function createClaudeCodeProjectConfig(deps) {
|
|
99
|
+
if (!deps.isCommandAvailable("claude")) {
|
|
100
|
+
console.log("⏭️ Skipping Claude Code project config: claude command not found");
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sseUrl = `${deps.getMcpServerUrl()}/sse`;
|
|
105
|
+
const serverName = deps.getMcpServerName();
|
|
106
|
+
|
|
107
|
+
const result = deps.safeExecSync(`claude mcp add --transport sse --scope project ${serverName} ${sseUrl}`);
|
|
108
|
+
if (result === true) {
|
|
109
|
+
console.log(`✓ Claude Code project MCP server added via CLI: ${serverName} (${sseUrl})`);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (result === "already_exists") {
|
|
113
|
+
console.log(`✓ Claude Code project MCP server already configured: ${serverName} (${sseUrl})`);
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (deps.safeExecSync(`claude mcp remove --scope project ${serverName} 2>/dev/null`) &&
|
|
118
|
+
deps.safeExecSync(`claude mcp add --transport sse --scope project ${serverName} ${sseUrl}`)) {
|
|
119
|
+
console.log(`✓ Claude Code project MCP server updated via CLI: ${serverName} (${sseUrl})`);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.warn(`⚠️ Failed to configure Claude Code project MCP server, please configure manually:`);
|
|
124
|
+
console.warn(` claude mcp add --transport sse --scope project ${serverName} ${sseUrl}`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ==================== Skill 复制 ====================
|
|
129
|
+
|
|
130
|
+
function copySkillToClaudeCode(deps, isProjectLevel = false) {
|
|
131
|
+
const skillName = deps.getMcpServerName();
|
|
132
|
+
let claudeSkillsDir;
|
|
133
|
+
|
|
134
|
+
if (isProjectLevel) {
|
|
135
|
+
claudeSkillsDir = path.join(deps.PROJECT_ROOT, ".claude", "skills");
|
|
136
|
+
} else {
|
|
137
|
+
claudeSkillsDir = getClaudeCodeSkillsDirectory();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const skillDir = path.join(claudeSkillsDir, skillName);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
deps.ensureDirectory(skillDir);
|
|
144
|
+
const skillDest = path.join(skillDir, "SKILL.md");
|
|
145
|
+
deps.copyFile(deps.SKILL_SOURCE, skillDest);
|
|
146
|
+
console.log(`✓ Copied standard skill to Claude Code ${isProjectLevel ? 'project' : 'user'} directory: ${skillDir}`);
|
|
147
|
+
return true;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.log(`⚠️ Failed to copy skill to Claude Code ${isProjectLevel ? 'project' : 'user'} directory: ${error.message}`);
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ==================== Agent 安装 ====================
|
|
155
|
+
|
|
156
|
+
function copyAgentToClaudeCode(deps, isProjectLevel = false) {
|
|
157
|
+
const agentsSourceDir = path.join(deps.PACKAGE_ROOT, "agents");
|
|
158
|
+
if (!fs.existsSync(agentsSourceDir)) {
|
|
159
|
+
console.log("⏭️ Skipping agent install: agents source directory not found");
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let claudeAgentsDir;
|
|
164
|
+
if (isProjectLevel) {
|
|
165
|
+
claudeAgentsDir = path.join(deps.PROJECT_ROOT, ".claude", "agents");
|
|
166
|
+
} else {
|
|
167
|
+
claudeAgentsDir = getClaudeCodeAgentsDirectory();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Claude Code 只复制不含 .trae. 的 .md 文件(原生格式)
|
|
171
|
+
const agentFiles = fs.readdirSync(agentsSourceDir).filter(f => f.endsWith(".md") && !f.includes(".trae."));
|
|
172
|
+
let copied = 0;
|
|
173
|
+
|
|
174
|
+
for (const agentFile of agentFiles) {
|
|
175
|
+
const sourcePath = path.join(agentsSourceDir, agentFile);
|
|
176
|
+
const destPath = path.join(claudeAgentsDir, agentFile);
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
deps.ensureDirectory(claudeAgentsDir);
|
|
180
|
+
deps.copyFile(sourcePath, destPath);
|
|
181
|
+
copied++;
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.log(`⚠️ Failed to copy agent ${agentFile}: ${error.message}`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (copied > 0) {
|
|
188
|
+
console.log(`✓ Copied ${copied} agent(s) to Claude Code ${isProjectLevel ? 'project' : 'user'} directory: ${claudeAgentsDir}`);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ==================== Slash Command 安装 ====================
|
|
195
|
+
|
|
196
|
+
function installClaudeCodeSlashCommand(deps) {
|
|
197
|
+
const commandSource = path.join(deps.PACKAGE_ROOT, "commands", "szcd-mcp-url.md");
|
|
198
|
+
if (!fs.existsSync(commandSource)) {
|
|
199
|
+
console.log("⚠️ Slash command source not found, skipping Claude Code");
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Claude Code:项目级 .claude/commands/
|
|
204
|
+
const claudeDir = path.join(deps.PROJECT_ROOT, ".claude");
|
|
205
|
+
const claudeCommandsDir = path.join(claudeDir, "commands");
|
|
206
|
+
const commandDest = path.join(claudeCommandsDir, "szcd-mcp-url.md");
|
|
207
|
+
|
|
208
|
+
if (fs.existsSync(claudeDir) && !fs.statSync(claudeDir).isDirectory()) {
|
|
209
|
+
console.log(`⚠️ Skipping Claude Code slash command: ${claudeDir} exists but is not a directory`);
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
deps.ensureDirectory(claudeCommandsDir);
|
|
215
|
+
|
|
216
|
+
if (fs.existsSync(commandDest)) {
|
|
217
|
+
const existingContent = fs.readFileSync(commandDest, "utf8").trim();
|
|
218
|
+
const sourceContent = fs.readFileSync(commandSource, "utf8").trim();
|
|
219
|
+
if (existingContent !== sourceContent) {
|
|
220
|
+
console.log(`⏭️ Skipping Claude Code slash command: ${commandDest} already exists (user modified)`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
deps.copyFile(commandSource, commandDest);
|
|
226
|
+
console.log(`✓ Installed Claude Code project slash command: /szcd-mcp-url`);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
console.log(`⚠️ Failed to install Claude Code slash command: ${error.message}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ==================== MCP URL 同步(供 update-mcp-url.js 调用) ====================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 不依赖 deps 的 CLI 可用性检测
|
|
236
|
+
*/
|
|
237
|
+
function isCommandAvailable(cmd) {
|
|
238
|
+
try {
|
|
239
|
+
execSync(`which ${cmd} 2>/dev/null`, { stdio: "pipe", timeout: 3000 });
|
|
240
|
+
return true;
|
|
241
|
+
} catch {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 不依赖 deps 的安全命令执行
|
|
248
|
+
*/
|
|
249
|
+
function safeExecSync(command) {
|
|
250
|
+
try {
|
|
251
|
+
execSync(command, { stdio: "pipe", timeout: 10000 });
|
|
252
|
+
return true;
|
|
253
|
+
} catch (e) {
|
|
254
|
+
const stderr = e.stderr?.toString() || "";
|
|
255
|
+
if (stderr.includes("already exists")) return "already_exists";
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* 同步 Claude Code 配置中的 MCP 服务器 URL
|
|
262
|
+
* @param {string} targetUrl - 新的 MCP 服务器基础 URL(如 http://localhost:3456)
|
|
263
|
+
* @param {string} serverName - MCP 服务器名称
|
|
264
|
+
*/
|
|
265
|
+
export function syncMcpUrl(targetUrl, serverName) {
|
|
266
|
+
const sseUrl = `${targetUrl}/sse`;
|
|
267
|
+
|
|
268
|
+
// 1. CLI 命令同步
|
|
269
|
+
if (isCommandAvailable("claude")) {
|
|
270
|
+
safeExecSync(`claude mcp remove --scope user ${serverName} 2>/dev/null`);
|
|
271
|
+
const result = safeExecSync(`claude mcp add --transport sse --scope user ${serverName} ${sseUrl}`);
|
|
272
|
+
if (result === true || result === "already_exists") {
|
|
273
|
+
console.log(`✓ Claude Code CLI synced: ${sseUrl}`);
|
|
274
|
+
} else {
|
|
275
|
+
console.warn(`⚠️ Claude Code CLI sync failed (~/.claude.json will be updated directly)`);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
console.log("⏭️ Skipping Claude Code CLI sync: claude not found");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 2. 直接文件操作同步 ~/.claude.json(确保最终状态正确)
|
|
282
|
+
syncClaudeCodeConfigDirect(targetUrl, serverName);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 直接文件操作同步 ~/.claude.json(独立于 deps)
|
|
287
|
+
*/
|
|
288
|
+
function syncClaudeCodeConfigDirect(targetUrl, serverName) {
|
|
289
|
+
const configPath = getClaudeCodeConfigPath();
|
|
290
|
+
const sseUrl = `${targetUrl}/sse`;
|
|
291
|
+
|
|
292
|
+
let config = {};
|
|
293
|
+
if (fs.existsSync(configPath)) {
|
|
294
|
+
try {
|
|
295
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
296
|
+
} catch { /* 忽略 */ }
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
300
|
+
|
|
301
|
+
const current = config.mcpServers[serverName];
|
|
302
|
+
if (current && current.type === "sse" && current.url === sseUrl) {
|
|
303
|
+
console.log(`⏭️ Claude Code ~/.claude.json already up-to-date: ${sseUrl}`);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
config.mcpServers[serverName] = {
|
|
308
|
+
type: "sse",
|
|
309
|
+
url: sseUrl,
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
313
|
+
console.log(`✓ Updated Claude Code ~/.claude.json: ${sseUrl}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// ==================== 导出 ====================
|
|
317
|
+
|
|
318
|
+
export function setupClaudeCode(deps) {
|
|
319
|
+
createClaudeCodeUserConfig(deps);
|
|
320
|
+
copySkillToClaudeCode(deps, false);
|
|
321
|
+
copyAgentToClaudeCode(deps, false);
|
|
322
|
+
installClaudeCodeSlashCommand(deps);
|
|
323
|
+
|
|
324
|
+
let claudeProjectInstalled = false;
|
|
325
|
+
if (deps.PROJECT_ROOT && fs.existsSync(deps.PROJECT_ROOT)) {
|
|
326
|
+
try {
|
|
327
|
+
claudeProjectInstalled = createClaudeCodeProjectConfig(deps);
|
|
328
|
+
if (claudeProjectInstalled) {
|
|
329
|
+
copySkillToClaudeCode(deps, true);
|
|
330
|
+
copyAgentToClaudeCode(deps, true);
|
|
331
|
+
}
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.log(`⚠️ Failed to install Claude Code project config: ${e.message}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
claudeProjectInstalled,
|
|
339
|
+
skillsDirectory: getClaudeCodeSkillsDirectory(),
|
|
340
|
+
agentsDirectory: getClaudeCodeAgentsDirectory(),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* common.js — postinstall / update-mcp-url 共用的通用基础设施
|
|
3
|
+
*
|
|
4
|
+
* 导出常量、安全保护、文件操作、配置读取等工具函数。
|
|
5
|
+
* 递归守卫在模块加载时自动执行。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import { execSync } from "node:child_process";
|
|
13
|
+
|
|
14
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
+
const __dirname = path.dirname(__filename);
|
|
16
|
+
|
|
17
|
+
export const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
18
|
+
export const SKILL_SOURCE = path.join(PACKAGE_ROOT, "standard-skill", "SKILL.md");
|
|
19
|
+
|
|
20
|
+
// 项目根目录:从 PACKAGE_ROOT 向上推导
|
|
21
|
+
export function findProjectRoot() {
|
|
22
|
+
let dir = PACKAGE_ROOT;
|
|
23
|
+
while (dir !== path.dirname(dir)) {
|
|
24
|
+
const parts = dir.split(path.sep);
|
|
25
|
+
const nodeModulesIndex = parts.lastIndexOf("node_modules");
|
|
26
|
+
if (nodeModulesIndex === -1) {
|
|
27
|
+
return dir;
|
|
28
|
+
}
|
|
29
|
+
dir = parts.slice(0, nodeModulesIndex).join(path.sep) || "/";
|
|
30
|
+
}
|
|
31
|
+
return dir;
|
|
32
|
+
}
|
|
33
|
+
export const PROJECT_ROOT = findProjectRoot();
|
|
34
|
+
|
|
35
|
+
// 默认值
|
|
36
|
+
export const DEFAULT_MCP_SERVER_URL = "http://localhost:3456";
|
|
37
|
+
export const DEFAULT_MCP_SERVER_NAME = "szcd-component-helper";
|
|
38
|
+
|
|
39
|
+
// ==================== 安全保护 ====================
|
|
40
|
+
|
|
41
|
+
export const RECURSION_GUARD_ENV = "SZCD_POSTINSTALL_RUNNING";
|
|
42
|
+
if (process.env[RECURSION_GUARD_ENV]) {
|
|
43
|
+
console.log("⏭️ Skipping postinstall: detected recursive invocation (SZCD_POSTINSTALL_RUNNING is set)");
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
process.env[RECURSION_GUARD_ENV] = "1";
|
|
47
|
+
|
|
48
|
+
const POSTINSTALL_TIMEOUT = 30000;
|
|
49
|
+
const scriptStartTime = Date.now();
|
|
50
|
+
export function checkGlobalTimeout() {
|
|
51
|
+
if (Date.now() - scriptStartTime > POSTINSTALL_TIMEOUT) {
|
|
52
|
+
console.warn("⚠️ Postinstall exceeded global timeout (30s), aborting remaining steps");
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function isCommandAvailable(cmd) {
|
|
59
|
+
try {
|
|
60
|
+
execSync(`which ${cmd} 2>/dev/null`, { stdio: "pipe", timeout: 500 });
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const CMD_TIMEOUT = 5000;
|
|
68
|
+
export function safeExecSync(command, options = {}) {
|
|
69
|
+
if (!checkGlobalTimeout()) return false;
|
|
70
|
+
try {
|
|
71
|
+
execSync(command, { stdio: "pipe", timeout: CMD_TIMEOUT, ...options });
|
|
72
|
+
return true;
|
|
73
|
+
} catch (e) {
|
|
74
|
+
const stderr = e.stderr?.toString() || e.stdout?.toString() || "";
|
|
75
|
+
if (stderr.includes("already exists")) return "already_exists";
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ==================== 通用工具函数 ====================
|
|
81
|
+
|
|
82
|
+
export function ensureDirectory(dirPath) {
|
|
83
|
+
if (!fs.existsSync(dirPath)) {
|
|
84
|
+
try {
|
|
85
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
86
|
+
console.log(`✓ Created directory: ${dirPath}`);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error(`❌ Failed to create directory ${dirPath}: ${error.message}`);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function copyFile(source, destination) {
|
|
95
|
+
try {
|
|
96
|
+
if (!fs.existsSync(source)) {
|
|
97
|
+
throw new Error(`Source file not found: ${source}`);
|
|
98
|
+
}
|
|
99
|
+
fs.copyFileSync(source, destination);
|
|
100
|
+
console.log(`✓ Copied file: ${source} → ${destination}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`❌ Failed to copy file: ${error.message}`);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function writeFile(filePath, content) {
|
|
108
|
+
try {
|
|
109
|
+
fs.writeFileSync(filePath, content);
|
|
110
|
+
console.log(`✓ Created file: ${filePath}`);
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`❌ Failed to write file ${filePath}: ${error.message}`);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ==================== 路径工具 ====================
|
|
118
|
+
|
|
119
|
+
export function getHomeDir() {
|
|
120
|
+
if (os.platform() === "win32") {
|
|
121
|
+
return process.env.USERPROFILE || process.env.HOME || os.homedir();
|
|
122
|
+
}
|
|
123
|
+
return process.env.HOME || os.homedir();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function getConfigFilePath() {
|
|
127
|
+
return path.join(getHomeDir(), ".szcd-mcp-config.json");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getProjectConfigFilePath() {
|
|
131
|
+
return path.join(PROJECT_ROOT, ".szcd-mcp-config.json");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// 优先级:项目级配置 > 用户级配置 > 默认值
|
|
135
|
+
export function loadExistingConfig() {
|
|
136
|
+
const projectConfigPath = getProjectConfigFilePath();
|
|
137
|
+
try {
|
|
138
|
+
if (fs.existsSync(projectConfigPath)) {
|
|
139
|
+
return JSON.parse(fs.readFileSync(projectConfigPath, "utf8"));
|
|
140
|
+
}
|
|
141
|
+
} catch (error) { /* 忽略 */ }
|
|
142
|
+
|
|
143
|
+
const userConfigPath = getConfigFilePath();
|
|
144
|
+
try {
|
|
145
|
+
if (fs.existsSync(userConfigPath)) {
|
|
146
|
+
return JSON.parse(fs.readFileSync(userConfigPath, "utf8"));
|
|
147
|
+
}
|
|
148
|
+
} catch (error) { /* 忽略 */ }
|
|
149
|
+
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function getMcpServerUrl() {
|
|
154
|
+
const config = loadExistingConfig();
|
|
155
|
+
return config.MCP_SERVER_URL || DEFAULT_MCP_SERVER_URL;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function getMcpServerName() {
|
|
159
|
+
const config = loadExistingConfig();
|
|
160
|
+
return config.MCP_SERVER_NAME || DEFAULT_MCP_SERVER_NAME;
|
|
161
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* opencode.js — OpenCode 兼容逻辑
|
|
3
|
+
*
|
|
4
|
+
* 导出 setupOpenCode(deps) 供 postinstall.js 调用。
|
|
5
|
+
* deps 需包含: { getMcpServerUrl, PROJECT_ROOT, fs }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
|
|
11
|
+
export function setupOpenCode(deps) {
|
|
12
|
+
const projectRoot = deps.PROJECT_ROOT;
|
|
13
|
+
const opencodeConfigPath = path.join(projectRoot, "opencode.json");
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(opencodeConfigPath)) {
|
|
16
|
+
console.log(`✓ opencode.json already exists: ${opencodeConfigPath}`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const opencodeConfig = {
|
|
21
|
+
$schema: "https://opencode.ai/config.json",
|
|
22
|
+
autoupdate: "notify",
|
|
23
|
+
instructions: ["请使用中文回复所有问题"],
|
|
24
|
+
mcp: {
|
|
25
|
+
"szcd-component-helper": {
|
|
26
|
+
type: "local",
|
|
27
|
+
command: ["npx", "szcd-mcp-proxy"],
|
|
28
|
+
environment: {
|
|
29
|
+
MCP_SERVER_URL: deps.getMcpServerUrl(),
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
deps.writeFile(opencodeConfigPath, JSON.stringify(opencodeConfig, null, 2));
|
|
36
|
+
console.log(`✓ Created opencode.json: ${opencodeConfigPath}`);
|
|
37
|
+
}
|