@szc-ft/mcp-szcd-client 0.34.0 → 0.34.1
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 +13 -1
- package/opencode-extension/skills/local-browser-test/lib/browser-engine.js +19 -4
- package/package.json +1 -1
- package/qwen-extension/qwen-extension.json +1 -1
- package/qwen-extension/skills/local-browser-test/lib/browser-engine.js +19 -4
- package/scripts/lib/codex.js +105 -1
- package/standard-skill/local-browser-test/lib/browser-engine.js +19 -4
package/README.md
CHANGED
|
@@ -4,8 +4,20 @@ szcd 组件库 MCP 客户端 — 自动配置 AI 编码工具的 MCP 服务器
|
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
7
|
+
### Windows 用户(推荐)
|
|
8
|
+
|
|
9
|
+
不需要手动安装 Node.js / npm,下载并双击安装器:
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
https://mcp.szcd-mcp.top/download?path=/scity/zengzhijie/mcp/downloads/SZCD-MCP-Setup.exe&key=3f4ec52f5137dbf2b458d87a28a3948ea88149b1d506e106
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
安装器会自动检查/安装 Node.js LTS,然后执行客户端安装与 IDE 配置。安装完成后请重启已打开的 IDE。
|
|
16
|
+
|
|
17
|
+
### 开发者 / 已安装 Node.js 的用户
|
|
18
|
+
|
|
7
19
|
```bash
|
|
8
|
-
npm install @szc-ft/mcp-szcd-client
|
|
20
|
+
npm install -g @szc-ft/mcp-szcd-client
|
|
9
21
|
```
|
|
10
22
|
|
|
11
23
|
安装后自动执行配置,支持以下 AI 编码工具:
|
|
@@ -2497,14 +2497,22 @@ export class BrowserEngine {
|
|
|
2497
2497
|
for (var k = 0; k < page.frames().length; k++) {
|
|
2498
2498
|
try {
|
|
2499
2499
|
var fu = page.frames()[k].url();
|
|
2500
|
-
var am = fu.match(/appId=(
|
|
2501
|
-
var vm = fu.match(/viewId=(
|
|
2500
|
+
var am = fu.match(/appId=(\d+)/);
|
|
2501
|
+
var vm = fu.match(/viewId=(\d+)/);
|
|
2502
2502
|
if (am) { appId = am[1]; if (vm) viewId = vm[1]; break; }
|
|
2503
2503
|
} catch (e) { /* skip */ }
|
|
2504
2504
|
}
|
|
2505
2505
|
} catch (e) { /* ignore */ }
|
|
2506
2506
|
}
|
|
2507
|
-
|
|
2507
|
+
// 默认登录地址从当前页面 origin 推导,避免硬编码到其它环境
|
|
2508
|
+
var loginUrl = params.loginUrl;
|
|
2509
|
+
if (!loginUrl) {
|
|
2510
|
+
try {
|
|
2511
|
+
var curOrigin = new URL(page.url()).origin;
|
|
2512
|
+
if (curOrigin && curOrigin.indexOf('http') === 0) loginUrl = curOrigin + '/ucenter/login/';
|
|
2513
|
+
} catch (e) { /* ignore */ }
|
|
2514
|
+
if (!loginUrl) loginUrl = 'http://platform.aicityos.com:32222/ucenter/login/';
|
|
2515
|
+
}
|
|
2508
2516
|
if (appId && viewId) loginUrl += '#/user/login?appId=' + appId + '&viewId=' + viewId;
|
|
2509
2517
|
|
|
2510
2518
|
// 尝试登录,验证码过期自动重试
|
|
@@ -2615,7 +2623,14 @@ export class BrowserEngine {
|
|
|
2615
2623
|
});
|
|
2616
2624
|
|
|
2617
2625
|
// 4. 构建带参数的登录 URL(保留 appId/viewId)
|
|
2618
|
-
var loginUrl =
|
|
2626
|
+
var loginUrl = params.loginUrl;
|
|
2627
|
+
if (!loginUrl) {
|
|
2628
|
+
try {
|
|
2629
|
+
var curOrigin = new URL(page.url()).origin;
|
|
2630
|
+
if (curOrigin && curOrigin.indexOf('http') === 0) loginUrl = curOrigin + '/ucenter/login/';
|
|
2631
|
+
} catch (e) { /* ignore */ }
|
|
2632
|
+
if (!loginUrl) loginUrl = 'http://platform.aicityos.com:32222/ucenter/login/';
|
|
2633
|
+
}
|
|
2619
2634
|
if (appId && viewId) {
|
|
2620
2635
|
loginUrl += '#/user/login?appId=' + appId + '&viewId=' + viewId;
|
|
2621
2636
|
}
|
package/package.json
CHANGED
|
@@ -2497,14 +2497,22 @@ export class BrowserEngine {
|
|
|
2497
2497
|
for (var k = 0; k < page.frames().length; k++) {
|
|
2498
2498
|
try {
|
|
2499
2499
|
var fu = page.frames()[k].url();
|
|
2500
|
-
var am = fu.match(/appId=(
|
|
2501
|
-
var vm = fu.match(/viewId=(
|
|
2500
|
+
var am = fu.match(/appId=(\d+)/);
|
|
2501
|
+
var vm = fu.match(/viewId=(\d+)/);
|
|
2502
2502
|
if (am) { appId = am[1]; if (vm) viewId = vm[1]; break; }
|
|
2503
2503
|
} catch (e) { /* skip */ }
|
|
2504
2504
|
}
|
|
2505
2505
|
} catch (e) { /* ignore */ }
|
|
2506
2506
|
}
|
|
2507
|
-
|
|
2507
|
+
// 默认登录地址从当前页面 origin 推导,避免硬编码到其它环境
|
|
2508
|
+
var loginUrl = params.loginUrl;
|
|
2509
|
+
if (!loginUrl) {
|
|
2510
|
+
try {
|
|
2511
|
+
var curOrigin = new URL(page.url()).origin;
|
|
2512
|
+
if (curOrigin && curOrigin.indexOf('http') === 0) loginUrl = curOrigin + '/ucenter/login/';
|
|
2513
|
+
} catch (e) { /* ignore */ }
|
|
2514
|
+
if (!loginUrl) loginUrl = 'http://platform.aicityos.com:32222/ucenter/login/';
|
|
2515
|
+
}
|
|
2508
2516
|
if (appId && viewId) loginUrl += '#/user/login?appId=' + appId + '&viewId=' + viewId;
|
|
2509
2517
|
|
|
2510
2518
|
// 尝试登录,验证码过期自动重试
|
|
@@ -2615,7 +2623,14 @@ export class BrowserEngine {
|
|
|
2615
2623
|
});
|
|
2616
2624
|
|
|
2617
2625
|
// 4. 构建带参数的登录 URL(保留 appId/viewId)
|
|
2618
|
-
var loginUrl =
|
|
2626
|
+
var loginUrl = params.loginUrl;
|
|
2627
|
+
if (!loginUrl) {
|
|
2628
|
+
try {
|
|
2629
|
+
var curOrigin = new URL(page.url()).origin;
|
|
2630
|
+
if (curOrigin && curOrigin.indexOf('http') === 0) loginUrl = curOrigin + '/ucenter/login/';
|
|
2631
|
+
} catch (e) { /* ignore */ }
|
|
2632
|
+
if (!loginUrl) loginUrl = 'http://platform.aicityos.com:32222/ucenter/login/';
|
|
2633
|
+
}
|
|
2619
2634
|
if (appId && viewId) {
|
|
2620
2635
|
loginUrl += '#/user/login?appId=' + appId + '&viewId=' + viewId;
|
|
2621
2636
|
}
|
package/scripts/lib/codex.js
CHANGED
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
* 1. MCP:优先 spawn `codex mcp remove` 再 `codex mcp add`(CLI 自己做 TOML 合并 + 幂等)
|
|
24
24
|
* 2. MCP fallback:找不到 `codex` 时直接写 config.toml(扁平 TOML,与 CLI 序列化一致)
|
|
25
25
|
* 3. Skill:调用 deps.copySkill(...) 把 standard-skill/* 整目录拷到 ~/.agents/skills/
|
|
26
|
+
* 4. Command:Codex 无自定义命令目录,把 commands/*.md 转换为 Open Agent Skills:
|
|
27
|
+
* ~/.agents/skills/<command-name>/SKILL.md
|
|
26
28
|
*/
|
|
27
29
|
|
|
28
30
|
import fs from "node:fs";
|
|
@@ -201,6 +203,105 @@ function copySkillsToCodex(deps) {
|
|
|
201
203
|
return { ok: results.every(r => r.ok), results, skillsRoot };
|
|
202
204
|
}
|
|
203
205
|
|
|
206
|
+
// ==================== Command → Skill 转换 ====================
|
|
207
|
+
|
|
208
|
+
function getCommandsSourceDirectory(deps) {
|
|
209
|
+
return path.join(deps.PACKAGE_ROOT, "commands");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function copyCommandsAsSkillsToCodex(deps) {
|
|
213
|
+
const commandsSourceDir = getCommandsSourceDirectory(deps);
|
|
214
|
+
if (!fs.existsSync(commandsSourceDir)) {
|
|
215
|
+
console.log("⏭️ Skipping Codex command-to-skill install: commands source directory not found");
|
|
216
|
+
return { ok: true, skipped: true, results: [] };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const skillsRoot = getCodexSkillsDirectory();
|
|
220
|
+
const commandFiles = fs.readdirSync(commandsSourceDir).filter((f) => f.endsWith(".md"));
|
|
221
|
+
const results = [];
|
|
222
|
+
|
|
223
|
+
for (const commandFile of commandFiles) {
|
|
224
|
+
const sourcePath = path.join(commandsSourceDir, commandFile);
|
|
225
|
+
const commandName = path.basename(commandFile, ".md");
|
|
226
|
+
const skillDir = path.join(skillsRoot, commandName);
|
|
227
|
+
const destPath = path.join(skillDir, "SKILL.md");
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const sourceContent = fs.readFileSync(sourcePath, "utf8");
|
|
231
|
+
const skillContent = renderCommandAsSkill(commandName, sourceContent);
|
|
232
|
+
deps.ensureDirectory?.(skillDir) ?? fs.mkdirSync(skillDir, { recursive: true });
|
|
233
|
+
|
|
234
|
+
if (fs.existsSync(destPath)) {
|
|
235
|
+
const existingContent = fs.readFileSync(destPath, "utf8").trim();
|
|
236
|
+
if (existingContent !== skillContent.trim()) {
|
|
237
|
+
console.log(`⏭️ Skipping Codex command skill "${commandName}": already exists with custom content`);
|
|
238
|
+
results.push({ name: commandName, ok: false, skipped: true, path: skillDir });
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
fs.writeFileSync(destPath, skillContent);
|
|
244
|
+
console.log(`✓ Converted command "${commandName}" to Codex skill (${skillDir})`);
|
|
245
|
+
results.push({ name: commandName, ok: true, path: skillDir });
|
|
246
|
+
} catch (e) {
|
|
247
|
+
console.warn(`⚠️ Failed to convert command "${commandName}" to Codex skill: ${e.message}`);
|
|
248
|
+
results.push({ name: commandName, ok: false, error: e.message });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { ok: results.every(r => r.ok || r.skipped), results, skillsRoot };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function renderCommandAsSkill(commandName, sourceContent) {
|
|
256
|
+
const { metadata, body } = splitFrontmatter(sourceContent);
|
|
257
|
+
const description = metadata.description || `Codex skill converted from /${commandName}`;
|
|
258
|
+
const argumentHint = metadata["argument-hint"];
|
|
259
|
+
const lines = [
|
|
260
|
+
"---",
|
|
261
|
+
`name: ${yamlScalar(commandName)}`,
|
|
262
|
+
`description: ${yamlScalar(description)}`,
|
|
263
|
+
"---",
|
|
264
|
+
"",
|
|
265
|
+
`# /${commandName}`,
|
|
266
|
+
"",
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
if (argumentHint) {
|
|
270
|
+
lines.push(`参数提示:\`${argumentHint}\``, "");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
lines.push(body.trimStart());
|
|
274
|
+
return lines.join("\n").replace(/\n*$/, "\n");
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function splitFrontmatter(content) {
|
|
278
|
+
const match = String(content).match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
279
|
+
if (!match) return { metadata: {}, body: String(content) };
|
|
280
|
+
return { metadata: parseSimpleYaml(match[1]), body: match[2] };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function parseSimpleYaml(yaml) {
|
|
284
|
+
const metadata = {};
|
|
285
|
+
for (const line of yaml.split(/\r?\n/)) {
|
|
286
|
+
const match = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
287
|
+
if (!match) continue;
|
|
288
|
+
metadata[match[1]] = stripYamlScalar(match[2]);
|
|
289
|
+
}
|
|
290
|
+
return metadata;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function stripYamlScalar(value) {
|
|
294
|
+
const trimmed = String(value).trim();
|
|
295
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
296
|
+
return trimmed.slice(1, -1);
|
|
297
|
+
}
|
|
298
|
+
return trimmed;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function yamlScalar(value) {
|
|
302
|
+
return JSON.stringify(String(value));
|
|
303
|
+
}
|
|
304
|
+
|
|
204
305
|
// ==================== 主入口 ====================
|
|
205
306
|
|
|
206
307
|
export function setupCodex(deps) {
|
|
@@ -223,7 +324,10 @@ export function setupCodex(deps) {
|
|
|
223
324
|
// 3) Skill 复制(独立于 MCP 注册成败,文件操作本身基本不会失败)
|
|
224
325
|
const skillResult = copySkillsToCodex(deps);
|
|
225
326
|
|
|
226
|
-
|
|
327
|
+
// 4) Command 转 Skill(Codex 无自定义命令目录,使用 Open Agent Skills 承载入口)
|
|
328
|
+
const commandSkillResult = copyCommandsAsSkillsToCodex(deps);
|
|
329
|
+
|
|
330
|
+
return { mcp: mcpResult, skill: skillResult, commandSkill: commandSkillResult };
|
|
227
331
|
}
|
|
228
332
|
|
|
229
333
|
function registerMcpServers(ourServers) {
|
|
@@ -2497,14 +2497,22 @@ export class BrowserEngine {
|
|
|
2497
2497
|
for (var k = 0; k < page.frames().length; k++) {
|
|
2498
2498
|
try {
|
|
2499
2499
|
var fu = page.frames()[k].url();
|
|
2500
|
-
var am = fu.match(/appId=(
|
|
2501
|
-
var vm = fu.match(/viewId=(
|
|
2500
|
+
var am = fu.match(/appId=(\d+)/);
|
|
2501
|
+
var vm = fu.match(/viewId=(\d+)/);
|
|
2502
2502
|
if (am) { appId = am[1]; if (vm) viewId = vm[1]; break; }
|
|
2503
2503
|
} catch (e) { /* skip */ }
|
|
2504
2504
|
}
|
|
2505
2505
|
} catch (e) { /* ignore */ }
|
|
2506
2506
|
}
|
|
2507
|
-
|
|
2507
|
+
// 默认登录地址从当前页面 origin 推导,避免硬编码到其它环境
|
|
2508
|
+
var loginUrl = params.loginUrl;
|
|
2509
|
+
if (!loginUrl) {
|
|
2510
|
+
try {
|
|
2511
|
+
var curOrigin = new URL(page.url()).origin;
|
|
2512
|
+
if (curOrigin && curOrigin.indexOf('http') === 0) loginUrl = curOrigin + '/ucenter/login/';
|
|
2513
|
+
} catch (e) { /* ignore */ }
|
|
2514
|
+
if (!loginUrl) loginUrl = 'http://platform.aicityos.com:32222/ucenter/login/';
|
|
2515
|
+
}
|
|
2508
2516
|
if (appId && viewId) loginUrl += '#/user/login?appId=' + appId + '&viewId=' + viewId;
|
|
2509
2517
|
|
|
2510
2518
|
// 尝试登录,验证码过期自动重试
|
|
@@ -2615,7 +2623,14 @@ export class BrowserEngine {
|
|
|
2615
2623
|
});
|
|
2616
2624
|
|
|
2617
2625
|
// 4. 构建带参数的登录 URL(保留 appId/viewId)
|
|
2618
|
-
var loginUrl =
|
|
2626
|
+
var loginUrl = params.loginUrl;
|
|
2627
|
+
if (!loginUrl) {
|
|
2628
|
+
try {
|
|
2629
|
+
var curOrigin = new URL(page.url()).origin;
|
|
2630
|
+
if (curOrigin && curOrigin.indexOf('http') === 0) loginUrl = curOrigin + '/ucenter/login/';
|
|
2631
|
+
} catch (e) { /* ignore */ }
|
|
2632
|
+
if (!loginUrl) loginUrl = 'http://platform.aicityos.com:32222/ucenter/login/';
|
|
2633
|
+
}
|
|
2619
2634
|
if (appId && viewId) {
|
|
2620
2635
|
loginUrl += '#/user/login?appId=' + appId + '&viewId=' + viewId;
|
|
2621
2636
|
}
|