@szc-ft/mcp-szcd-client 0.33.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 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=(\\d+)/);
2501
- var vm = fu.match(/viewId=(\\d+)/);
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
- var loginUrl = params.loginUrl || 'http://platform.aicityos.com:32222/ucenter/login/';
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 = 'http://platform.aicityos.com:32222/ucenter/login/';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@szc-ft/mcp-szcd-client",
3
- "version": "0.33.0",
3
+ "version": "0.34.1",
4
4
  "description": "MCP client for szcd component library - auto-configures AI coding tools with MCP server, skills, agents and commands",
5
5
  "keywords": [
6
6
  "mcp",
@@ -22,6 +22,7 @@
22
22
  "bin": {
23
23
  "szcd-mcp-proxy": "mcp-proxy.js",
24
24
  "szcd-mcp-setup": "scripts/postinstall.js",
25
+ "szcd-mcp-install-shortcuts": "scripts/install-dev-shortcuts.js",
25
26
  "szcd-mcp-update-url": "scripts/update-mcp-url.js",
26
27
  "szcd-mcp-coding-config": "scripts/update-coding-config.js",
27
28
  "szcd-mcp-api-config": "scripts/update-api-config.js",
@@ -32,6 +33,7 @@
32
33
  "local-browser-executor.js",
33
34
  "lib/",
34
35
  "scripts/postinstall.js",
36
+ "scripts/install-dev-shortcuts.js",
35
37
  "scripts/update-mcp-url.js",
36
38
  "scripts/update-coding-config.js",
37
39
  "scripts/update-api-config.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "szcd-component-helper",
3
- "version": "0.33.0",
3
+ "version": "0.34.1",
4
4
  "description": "szcd 组件库 MCP 助手 — 查询组件信息、匹配需求、生成代码",
5
5
  "mcpServers": {
6
6
  "szcd-component-helper": {
@@ -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=(\\d+)/);
2501
- var vm = fu.match(/viewId=(\\d+)/);
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
- var loginUrl = params.loginUrl || 'http://platform.aicityos.com:32222/ucenter/login/';
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 = 'http://platform.aicityos.com:32222/ucenter/login/';
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
  }
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * install-dev-shortcuts.js — 手动重跑 dev-browser 快捷方式安装的 CLI
4
+ *
5
+ * 用途:postinstall 静默失败时(特别是 Windows 中文系统、AV 拦截、ExecutionPolicy 等)
6
+ * 用户跑 `npx szcd-mcp-install-shortcuts` 单独重试并看到完整错误信息。
7
+ *
8
+ * 与 postinstall 调用同一个 setupDevBrowserShortcut;本 CLI 区别在于:
9
+ * 1. 进程退出码:失败时 exit 1(postinstall 是 exit 0 不阻塞 npm install)
10
+ * 2. 输出更详细:直接传入 process 的 stdout / stderr,不被 npm install 折叠
11
+ * 3. 文末汇总:成功/失败计数 + 每条失败的完整错误,便于贴回 issue
12
+ */
13
+
14
+ import { setupDevBrowserShortcut } from "./lib/dev-browser-shortcut.js";
15
+
16
+ console.log("============================================================");
17
+ console.log(" szcd-mcp dev-browser DevSession shortcut installer");
18
+ console.log("============================================================");
19
+ console.log(`Platform: ${process.platform}`);
20
+ console.log(`Node: ${process.version}`);
21
+ console.log("");
22
+
23
+ const result = setupDevBrowserShortcut({});
24
+
25
+ console.log("");
26
+ console.log("============================================================");
27
+ console.log(" Summary");
28
+ console.log("============================================================");
29
+ if (result.skipped) {
30
+ console.log("⚠️ No Chrome/Edge detected on this machine.");
31
+ console.log(" Install Google Chrome or Microsoft Edge, then re-run this command.");
32
+ process.exit(1);
33
+ }
34
+
35
+ const okCount = result.results.filter(r => r.ok).length;
36
+ const total = result.results.length;
37
+ console.log(`Installed: ${okCount}/${total}`);
38
+ for (const r of result.results) {
39
+ if (r.ok) {
40
+ console.log(` ✓ ${r.name}: ${r.path}`);
41
+ } else {
42
+ console.log(` ✗ ${r.name}:`);
43
+ for (const line of String(r.error || "(unknown error)").split("\n")) {
44
+ console.log(` ${line}`);
45
+ }
46
+ }
47
+ }
48
+ console.log("");
49
+
50
+ process.exit(result.ok ? 0 : 1);
@@ -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
- return { mcp: mcpResult, skill: skillResult };
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) {
@@ -19,7 +19,7 @@
19
19
  import fs from "node:fs";
20
20
  import path from "node:path";
21
21
  import os from "node:os";
22
- import { execSync } from "node:child_process";
22
+ import { execSync, spawnSync } from "node:child_process";
23
23
 
24
24
  // 与 SKILL.md 第 186-191 行保持一致
25
25
  const CDP_PORT = 9222;
@@ -117,12 +117,40 @@ function installWindows(browser) {
117
117
  ].join("; ");
118
118
  // -EncodedCommand 要求脚本块本身是 UTF-16LE base64
119
119
  const encoded = Buffer.from(psScript, "utf16le").toString("base64");
120
- try {
121
- execSync(`powershell -NoProfile -ExecutionPolicy Bypass -EncodedCommand ${encoded}`, { stdio: "ignore" });
122
- return { ok: true, path: lnkPath };
123
- } catch (e) {
124
- return { ok: false, error: e.message };
120
+
121
+ // 反馈 #18(2026-06-24, accuracy=1):原先用
122
+ // execSync('powershell ... -EncodedCommand ...', { stdio: 'ignore' })
123
+ // 在中文 Windows + 全局 npm install 场景下静默失败:execSync 经 cmd.exe /d /s /c
124
+ // 调用 PowerShell,中文 Windows PowerShell 启动期向 stderr 写 CLIXML
125
+ // (progress/information record),stdio:'ignore' 导致 stderr 写入失败,
126
+ // PowerShell 以 exit code 1 终止,CreateShortcut COM 调用未执行;
127
+ // 外层 try/catch 仅 console.warn,postinstall 整体仍成功,用户无法察觉。
128
+ //
129
+ // 修复:用 spawnSync 直接调 powershell.exe,绕开 cmd.exe;stderr/stdout 用 pipe
130
+ // 而不是 ignore,捕获真实失败信息以便排查。
131
+ const result = spawnSync(
132
+ "powershell.exe",
133
+ ["-NoProfile", "-ExecutionPolicy", "Bypass", "-EncodedCommand", encoded],
134
+ { stdio: ["ignore", "pipe", "pipe"], windowsHide: true }
135
+ );
136
+ if (result.error) {
137
+ return { ok: false, error: `spawn failed: ${result.error.message}` };
125
138
  }
139
+ if (result.status !== 0) {
140
+ const stderr = (result.stderr?.toString() || "").trim();
141
+ const stdout = (result.stdout?.toString() || "").trim();
142
+ return {
143
+ ok: false,
144
+ error: `powershell exit code ${result.status}` +
145
+ (stderr ? `\n stderr: ${stderr.slice(0, 400)}` : "") +
146
+ (stdout ? `\n stdout: ${stdout.slice(0, 200)}` : ""),
147
+ };
148
+ }
149
+ // 二次校验:文件实际存在再算成功
150
+ if (!fs.existsSync(lnkPath)) {
151
+ return { ok: false, error: `powershell exit 0 but ${lnkPath} not created` };
152
+ }
153
+ return { ok: true, path: lnkPath };
126
154
  }
127
155
 
128
156
  // 字符串 → UTF-8 bytes → base64,给 PowerShell 内联反序列化用
@@ -216,14 +244,15 @@ export function setupDevBrowserShortcut(deps = {}) {
216
244
  installLinux;
217
245
  for (const b of browsers) {
218
246
  const r = install(b);
219
- results.push({ browser: b.id, ...r });
247
+ results.push({ browser: b.id, name: b.name, ...r });
220
248
  if (r.ok) {
221
249
  console.log(`✓ Created ${b.name} DevSession shortcut: ${r.path}`);
222
250
  } else {
223
- console.warn(`⚠️ Failed to create ${b.name} DevSession shortcut: ${r.error}`);
251
+ console.warn(`⚠️ Failed to create ${b.name} DevSession shortcut:\n ${r.error}`);
224
252
  }
225
253
  }
226
254
  const okCount = results.filter(r => r.ok).length;
255
+ const failCount = results.length - okCount;
227
256
  if (okCount > 0) {
228
257
  const dirHint = platform === "win32" ? "桌面 (Desktop)"
229
258
  : platform === "darwin" ? "~/Applications/ (Launchpad / Spotlight)"
@@ -231,7 +260,16 @@ export function setupDevBrowserShortcut(deps = {}) {
231
260
  console.log(`\n💡 Double-click the "DevSession" shortcut in ${dirHint} to start a CDP-enabled browser`);
232
261
  console.log(` on port ${CDP_PORT} with an isolated profile, then re-run your local-browser-test plan.`);
233
262
  }
234
- return { ok: okCount > 0, results };
263
+ if (failCount > 0) {
264
+ // 醒目告警 + 手动重跑命令(反馈 #18 要求)
265
+ console.warn("");
266
+ console.warn("⚠️ ============================================================");
267
+ console.warn(`⚠️ ${failCount}/${results.length} dev-browser shortcut(s) failed to install.`);
268
+ console.warn("⚠️ Re-run the dedicated CLI to retry with full diagnostics:");
269
+ console.warn("⚠️ npx szcd-mcp-install-shortcuts");
270
+ console.warn("⚠️ ============================================================");
271
+ }
272
+ return { ok: okCount > 0 && failCount === 0, results, platform };
235
273
  } catch (e) {
236
274
  console.warn(`⚠️ setupDevBrowserShortcut error: ${e.message}`);
237
275
  return { ok: false, results, error: e.message };
@@ -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=(\\d+)/);
2501
- var vm = fu.match(/viewId=(\\d+)/);
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
- var loginUrl = params.loginUrl || 'http://platform.aicityos.com:32222/ucenter/login/';
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 = 'http://platform.aicityos.com:32222/ucenter/login/';
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
  }