@szc-ft/mcp-szcd-client 0.21.0 → 0.23.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.
Files changed (36) hide show
  1. package/agents/build.js +152 -130
  2. package/agents/opencode-extension/agents/szcd-component-expert.md +138 -12
  3. package/agents/platforms.json +17 -7
  4. package/agents/qwen-extension/agents/szcd-component-expert.md +137 -12
  5. package/agents/src/szcd-component-expert.md +211 -6
  6. package/agents/src/tools.json +10 -5
  7. package/agents/szcd-component-expert.md +139 -14
  8. package/agents/szcd-component-expert.qoder.md +227 -15
  9. package/agents/szcd-component-expert.trae.md +217 -13
  10. package/opencode-extension/agents/szcd-component-expert.md +138 -12
  11. package/opencode-extension/commands/szcd-mcp-api-config.md +48 -0
  12. package/opencode-extension/commands/szcd-mcp-auth.md +39 -0
  13. package/opencode-extension/commands/szcd-mcp-browser-test.md +57 -0
  14. package/opencode-extension/commands/szcd-mcp-coding-config.md +40 -0
  15. package/opencode-extension/commands/szcd-mcp-feedback.md +42 -0
  16. package/opencode-extension/commands/szcd-mcp-url.md +37 -0
  17. package/opencode-extension/opencode.json +15 -0
  18. package/opencode-extension/skills/local-api-tool/SKILL.md +246 -0
  19. package/opencode-extension/skills/local-browser-test/SKILL.md +249 -0
  20. package/opencode-extension/skills/szcd-component-helper/SKILL.md +523 -0
  21. package/opencode-extension/skills/szcd-design-to-code/SKILL.md +177 -0
  22. package/package.json +3 -2
  23. package/qwen-extension/QWEN.md +29 -8
  24. package/qwen-extension/agents/szcd-component-expert.md +137 -12
  25. package/qwen-extension/qwen-extension.json +6 -1
  26. package/qwen-extension/skills/szcd-component-helper/SKILL.md +81 -1
  27. package/qwen-extension/skills/szcd-design-to-code/SKILL.md +177 -0
  28. package/scripts/lib/claude-code.js +45 -1
  29. package/scripts/lib/common.js +17 -0
  30. package/scripts/lib/opencode.js +108 -12
  31. package/scripts/lib/qoder.js +42 -1
  32. package/scripts/lib/trae-cli.js +39 -1
  33. package/scripts/lib/trae-ide.js +40 -1
  34. package/scripts/postinstall.js +28 -0
  35. package/standard-skill/szcd-component-helper/SKILL.md +81 -1
  36. package/standard-skill/szcd-design-to-code/SKILL.md +177 -0
package/agents/build.js CHANGED
@@ -17,6 +17,7 @@
17
17
  import fs from "node:fs";
18
18
  import path from "node:path";
19
19
  import { fileURLToPath } from "node:url";
20
+ import YAML from "yaml";
20
21
 
21
22
  const __filename = fileURLToPath(import.meta.url);
22
23
  const __dirname = path.dirname(__filename);
@@ -162,120 +163,62 @@ function generateMcpToolTable(tools, toolPrefix) {
162
163
  }
163
164
 
164
165
  /**
165
- * frontmatter 中移除指定字段
166
+ * 自动发现项目中的 skills(从 standard-skill 目录扫描子目录名)
166
167
  */
167
- function stripFrontmatterFields(content, fields) {
168
- const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
169
- if (!fmMatch) return content;
170
-
171
- const prefix = fmMatch[1];
172
- let fmBody = fmMatch[2];
173
- const suffix = fmMatch[3];
174
-
175
- for (const field of fields) {
176
- // 匹配字段行(支持简单值、多行值、YAML列表等)
177
- // 简单行:tools: ["*"]
178
- // YAML列表:tools:\n - "*"
179
- const simpleLineRegex = new RegExp(`^${field}:.*$\\n?`, "m");
180
- fmBody = fmBody.replace(simpleLineRegex, "");
181
-
182
- const yamlListRegex = new RegExp(`^${field}:\\s*\\n(?:^\\s+.*$\\n?)*`, "m");
183
- fmBody = fmBody.replace(yamlListRegex, "");
184
- }
185
-
186
- // 清理空行
187
- fmBody = fmBody.replace(/\n{2,}/g, "\n");
188
-
189
- return prefix + fmBody + suffix + content.slice(fmMatch[0].length);
168
+ function discoverSkills() {
169
+ const skillDir = path.join(AGENTS_DIR, "..", "standard-skill");
170
+ if (!fs.existsSync(skillDir)) return [];
171
+ return fs.readdirSync(skillDir).filter((name) =>
172
+ fs.statSync(path.join(skillDir, name)).isDirectory()
173
+ );
190
174
  }
191
175
 
192
176
  /**
193
- * 替换 frontmatter 中的字段为平台特定值
194
- * 支持简单值和通过模板字符串注入多行 YAML
177
+ * 处理 frontmatter:YAML 解析 → 结构化增删 → YAML 序列化
178
+ *
179
+ * 替代原 stripFrontmatterFields / replaceFrontmatterFields / simpleYamlStringify 三套正则方案。
180
+ * 流程:
181
+ * 1. 解析 frontmatter 为 JS 对象
182
+ * 2. 按 stripFrontmatterFields 删除字段
183
+ * 3. 按 frontmatter 覆盖非字符串占位符的具体值
184
+ * 4. 按 autoInject 自动注入 skills / mcpServers(含 additionalMcpServers)
185
+ * 5. 序列化回 YAML 并拼回 content
195
186
  */
196
- function replaceFrontmatterFields(content, fields) {
187
+ function processFrontmatter(content, platformConfig, discoveredSkills, mcpServerNames) {
197
188
  const fmMatch = content.match(/^(---\n)([\s\S]*?)(\n---)/);
198
189
  if (!fmMatch) return content;
199
190
 
200
- const prefix = fmMatch[1];
201
- let fmBody = fmMatch[2];
202
- const suffix = fmMatch[3];
203
-
204
- for (const [field, value] of Object.entries(fields)) {
205
- // 先移除旧字段(支持简单值和多行 YAML 块)
206
- const simpleLineRegex = new RegExp(`^${field}:.*$\\n?`, "gm");
207
- fmBody = fmBody.replace(simpleLineRegex, "");
208
- const yamlBlockRegex = new RegExp(`^${field}:\\s*\\n(?:^\\s+[\\s\\S]*?)*\\n?`, "gm");
209
- fmBody = fmBody.replace(yamlBlockRegex, "");
210
-
211
- // 插入新值
212
- let formattedValue;
213
- if (typeof value === "object") {
214
- // 对于对象类型,使用 JSON 转 YAML 的简单实现
215
- formattedValue = "\n" + simpleYamlStringify(value, 2, 2);
216
- } else if (typeof value === "string" && value.includes("{{")) {
217
- // 模板字符串,保留原样(由后续处理替换)
218
- formattedValue = ` ${value}`;
219
- } else {
220
- formattedValue = ` ${value}`;
221
- }
222
- fmBody += `${field}:${formattedValue}\n`;
223
- }
191
+ // 1. 解析为结构化数据
192
+ let data = YAML.parse(fmMatch[2]) || {};
224
193
 
225
- // 清理多余空行
226
- fmBody = fmBody.replace(/\n{3,}/g, "\n\n");
194
+ // 2. 删除不需要的字段(如 Qwen 不需要 tools/model/type)
195
+ for (const field of platformConfig.stripFrontmatterFields || []) {
196
+ delete data[field];
197
+ }
227
198
 
228
- return prefix + fmBody + suffix + content.slice(fmMatch[0].length);
229
- }
199
+ // 3. 应用平台覆盖(只跳过 {{placeholder}} 占位符,非占位符的字符串值正常应用)
200
+ for (const [key, value] of Object.entries(platformConfig.frontmatter || {})) {
201
+ if (typeof value === "string" && /^\{\{.*\}\}$/.test(value)) continue;
202
+ data[key] = value;
203
+ }
230
204
 
231
- /**
232
- * 简易 YAML 序列化(仅支持嵌套对象和布尔/数字/字符串值)
233
- */
234
- function simpleYamlStringify(obj, indent = 2, currentIndent = 0) {
235
- if (obj === null || obj === undefined) return "";
236
- if (typeof obj !== "object") return String(obj);
237
-
238
- const lines = [];
239
- const pad = " ".repeat(currentIndent);
240
-
241
- for (const [key, value] of Object.entries(obj)) {
242
- if (value === null || value === undefined) continue;
243
-
244
- if (typeof value === "object" && !Array.isArray(value)) {
245
- lines.push(`${pad}${key}:`);
246
- const childLines = simpleYamlStringify(value, indent, currentIndent + indent);
247
- if (childLines) lines.push(childLines);
248
- } else if (Array.isArray(value)) {
249
- lines.push(`${pad}${key}:`);
250
- for (const item of value) {
251
- if (typeof item === "object") {
252
- const entries = Object.entries(item);
253
- lines.push(`${pad}- ${entries[0][0]}: ${formatYamlValue(entries[0][1])}`);
254
- for (const [k, v] of entries.slice(1)) {
255
- lines.push(`${pad} ${k}: ${formatYamlValue(v)}`);
256
- }
257
- } else {
258
- lines.push(`${pad}- ${formatYamlValue(item)}`);
259
- }
260
- }
261
- } else {
262
- lines.push(`${pad}${key}: ${formatYamlValue(value)}`);
263
- }
205
+ // 4. 自动注入(基于 autoInject 能力声明)
206
+ const autoInject = platformConfig.autoInject || [];
207
+ if (autoInject.includes("skills") && discoveredSkills.length > 0) {
208
+ data.skills = discoveredSkills;
209
+ }
210
+ if (autoInject.includes("mcpServers") && mcpServerNames.length > 0) {
211
+ data.mcpServers = mcpServerNames;
264
212
  }
265
213
 
266
- return lines.join("\n");
267
- }
214
+ // 5. 序列化回 YAML
215
+ const yamlStr = YAML.stringify(data, {
216
+ lineWidth: 0,
217
+ defaultKeyType: "PLAIN",
218
+ defaultStringType: "PLAIN",
219
+ });
268
220
 
269
- function formatYamlValue(value) {
270
- if (typeof value === "boolean") return value ? "true" : "false";
271
- if (typeof value === "number") return String(value);
272
- if (typeof value === "string") {
273
- if (value.includes("\n") || value.includes(":") || value.includes("#") || value.includes("'") || value.includes('"') || value.includes("[")) {
274
- return `"${value.replace(/"/g, '\\"')}"`;
275
- }
276
- return value;
277
- }
278
- return String(value);
221
+ return fmMatch[1] + yamlStr.trimEnd() + fmMatch[3] + content.slice(fmMatch[0].length);
279
222
  }
280
223
 
281
224
  /**
@@ -283,7 +226,7 @@ function formatYamlValue(value) {
283
226
  */
284
227
  function cleanupContent(content) {
285
228
  // 移除连续3个以上空行 → 2个空行
286
- let result = content.replace(/\n{4,}/g, "\n\n\n");
229
+ let result = content.replace(new RegExp("\\n{4,}", "g"), "\n\n\n");
287
230
  // 移除 frontmatter 后多余空行(--- 后最多1个空行)
288
231
  result = result.replace(/(---\n)\n{2,}/g, "$1\n");
289
232
  return result;
@@ -310,27 +253,12 @@ function build() {
310
253
  // 对整个模板内容(含 frontmatter)进行条件过滤
311
254
  let filtered = filterByContentLevel(templateContent, platformConfig.contentLevel);
312
255
 
313
- // 剥离 frontmatter 中指定字段(如 Qwen 不需要 tools/model/type
314
- if (platformConfig.stripFrontmatterFields && platformConfig.stripFrontmatterFields.length > 0) {
315
- filtered = stripFrontmatterFields(filtered, platformConfig.stripFrontmatterFields);
316
- }
317
-
318
- // 替换 frontmatter 字段为平台特定值(仅当存在非字符串类型的具体值时)
319
- // 原有的 frontmatter 配置(如 {{name}}, {{description}})是死配置,从未被使用
320
- // 只有 OpenCode 等新平台需要替换 mode/tools 等对象类型字段
321
- if (platformConfig.frontmatter) {
322
- const concreteFields = {};
323
- for (const [key, value] of Object.entries(platformConfig.frontmatter)) {
324
- // 只处理对象类型(如 tools: { write: true })和布尔/数字类型
325
- // 跳过所有字符串类型(包括 {{name}} 等占位符和 "- \"*\"" 等 YAML 字符串)
326
- if (typeof value === "object" && value !== null) {
327
- concreteFields[key] = value;
328
- }
329
- }
330
- if (Object.keys(concreteFields).length > 0) {
331
- filtered = replaceFrontmatterFields(filtered, concreteFields);
332
- }
333
- }
256
+ // YAML 结构化处理 frontmatter(strip + override + autoInject
257
+ const skills = discoverSkills();
258
+ const mcpServerNames = [platformsConfig.mcpServerName]
259
+ .concat((platformsConfig.additionalMcpServers || []).map((s) => s.name))
260
+ .filter(Boolean);
261
+ filtered = processFrontmatter(filtered, platformConfig, skills, mcpServerNames);
334
262
 
335
263
  // 替换 {{TOOL:}} 占位符
336
264
  filtered = replaceToolPlaceholders(filtered, platformConfig.toolPrefix);
@@ -384,15 +312,21 @@ function build() {
384
312
  console.log(` ✓ Synced to opencode-extension/agents/`);
385
313
  }
386
314
 
387
- // 5. 同步 SKILL.md 和 commands 到 qwen-extension/
315
+ // 5. 共享资源准备(skills、commands
388
316
  const packageRoot = path.join(AGENTS_DIR, "..");
389
-
390
- // 同步所有 skills(从 standard-skill 目录读取)
391
317
  const standardSkillDir = path.join(packageRoot, "standard-skill");
318
+ const skillDirs = fs.existsSync(standardSkillDir)
319
+ ? fs.readdirSync(standardSkillDir).filter((name) =>
320
+ fs.statSync(path.join(standardSkillDir, name)).isDirectory()
321
+ )
322
+ : [];
323
+ const commandsSourceDir = path.join(packageRoot, "commands");
324
+ const cmdFiles = fs.existsSync(commandsSourceDir)
325
+ ? fs.readdirSync(commandsSourceDir).filter((f) => f.endsWith(".md"))
326
+ : [];
327
+
328
+ // 5a. 同步 skills 和 commands 到 qwen-extension/
392
329
  if (fs.existsSync(standardSkillDir)) {
393
- const skillDirs = fs.readdirSync(standardSkillDir).filter((name) => {
394
- return fs.statSync(path.join(standardSkillDir, name)).isDirectory();
395
- });
396
330
  for (const skillDir of skillDirs) {
397
331
  const srcSkillPath = path.join(standardSkillDir, skillDir, "SKILL.md");
398
332
  const destSkillPath = path.join(qwenExtDir, "skills", skillDir, "SKILL.md");
@@ -407,13 +341,11 @@ function build() {
407
341
  }
408
342
 
409
343
  // 同步 commands
410
- const commandsSourceDir = path.join(packageRoot, "commands");
411
344
  const commandsDestDir = path.join(qwenExtDir, "commands");
412
345
  if (fs.existsSync(commandsSourceDir)) {
413
346
  if (!fs.existsSync(commandsDestDir)) {
414
347
  fs.mkdirSync(commandsDestDir, { recursive: true });
415
348
  }
416
- const cmdFiles = fs.readdirSync(commandsSourceDir).filter((f) => f.endsWith(".md"));
417
349
  for (const cmdFile of cmdFiles) {
418
350
  fs.copyFileSync(
419
351
  path.join(commandsSourceDir, cmdFile),
@@ -425,6 +357,39 @@ function build() {
425
357
  }
426
358
  }
427
359
 
360
+ // 5b. 同步 skills 和 commands 到 opencode-extension/
361
+ if (fs.existsSync(standardSkillDir)) {
362
+ for (const skillDir of skillDirs) {
363
+ const srcSkillPath = path.join(standardSkillDir, skillDir, "SKILL.md");
364
+ const destSkillPath = path.join(opencodeExtDir, "skills", skillDir, "SKILL.md");
365
+ if (fs.existsSync(srcSkillPath)) {
366
+ if (!fs.existsSync(path.dirname(destSkillPath))) {
367
+ fs.mkdirSync(path.dirname(destSkillPath), { recursive: true });
368
+ }
369
+ fs.copyFileSync(srcSkillPath, destSkillPath);
370
+ }
371
+ }
372
+ if (skillDirs.length > 0) {
373
+ console.log(` ✓ Synced ${skillDirs.length} skill(s) to opencode-extension/skills/`);
374
+ }
375
+ }
376
+
377
+ const opencodeCommandsDestDir = path.join(opencodeExtDir, "commands");
378
+ if (fs.existsSync(commandsSourceDir)) {
379
+ if (!fs.existsSync(opencodeCommandsDestDir)) {
380
+ fs.mkdirSync(opencodeCommandsDestDir, { recursive: true });
381
+ }
382
+ for (const cmdFile of cmdFiles) {
383
+ fs.copyFileSync(
384
+ path.join(commandsSourceDir, cmdFile),
385
+ path.join(opencodeCommandsDestDir, cmdFile),
386
+ );
387
+ }
388
+ if (cmdFiles.length > 0) {
389
+ console.log(` ✓ Synced ${cmdFiles.length} command(s) to opencode-extension/commands/`);
390
+ }
391
+ }
392
+
428
393
  // 6. 生成 qwen-extension.json(MCP 配置从 platforms.json 读取)
429
394
  const qwenConfig = platformsConfig.platforms.qwen;
430
395
  if (qwenConfig && qwenConfig.mcp) {
@@ -457,10 +422,67 @@ function build() {
457
422
  excludeTools: [],
458
423
  };
459
424
 
425
+ // 追加额外的 MCP Server(HTTP 或 stdio 模式)
426
+ if (platformsConfig.additionalMcpServers) {
427
+ for (const server of platformsConfig.additionalMcpServers) {
428
+ if (server.type === "http") {
429
+ manifest.mcpServers[server.name] = {
430
+ type: "http",
431
+ httpUrl: server.httpUrl,
432
+ };
433
+ } else {
434
+ manifest.mcpServers[server.name] = {
435
+ type: server.type,
436
+ command: server.command,
437
+ args: server.args,
438
+ };
439
+ }
440
+ }
441
+ }
442
+
460
443
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 4) + "\n");
461
444
  console.log(` ✓ Generated qwen-extension.json (type: ${qwenConfig.mcp.type}, httpUrl: ${qwenConfig.mcp.httpUrl})`);
462
445
  }
463
446
 
447
+ // 7. 生成 opencode-extension/opencode.json(OpenCode 项目级 MCP 配置)
448
+ const opencodeConfig = platformsConfig.platforms.opencode;
449
+ if (opencodeConfig && opencodeConfig.mcp) {
450
+ const opencodeManifestPath = path.join(opencodeExtDir, "opencode.json");
451
+ const opencodeManifest = {
452
+ $schema: "https://opencode.ai/config.json",
453
+ mcp: {},
454
+ };
455
+
456
+ // 主 MCP Server
457
+ opencodeManifest.mcp[platformsConfig.mcpServerName] = {
458
+ type: opencodeConfig.mcp.type,
459
+ url: opencodeConfig.mcp.url,
460
+ };
461
+
462
+ // 额外的 MCP Server
463
+ if (platformsConfig.additionalMcpServers) {
464
+ for (const server of platformsConfig.additionalMcpServers) {
465
+ if (server.type === "http") {
466
+ opencodeManifest.mcp[server.name] = {
467
+ type: "remote",
468
+ url: server.httpUrl,
469
+ };
470
+ } else {
471
+ opencodeManifest.mcp[server.name] = {
472
+ type: "local",
473
+ command: [server.command, ...server.args],
474
+ };
475
+ }
476
+ }
477
+ }
478
+
479
+ if (!fs.existsSync(opencodeExtDir)) {
480
+ fs.mkdirSync(opencodeExtDir, { recursive: true });
481
+ }
482
+ fs.writeFileSync(opencodeManifestPath, JSON.stringify(opencodeManifest, null, 4) + "\n");
483
+ console.log(` ✓ Generated opencode-extension/opencode.json (type: ${opencodeConfig.mcp.type}, url: ${opencodeConfig.mcp.url})`);
484
+ }
485
+
464
486
  console.log("\n✅ Agent build complete!");
465
487
  }
466
488
 
@@ -1,18 +1,19 @@
1
1
  ---
2
2
  description: |
3
- szcd 组件库专家,用于查询组件信息、匹配需求到组件、生成基于 szcd 组件库的 React 代码。
4
- 当用户需要使用 @szc-ft/szcd 组件库开发页面、选择组件、查询组件 API、根据设计稿生成代码时,应使用此 agent。
5
- 典型触发场景:
6
- - 用户想查询 szcd 组件库中有哪些组件
7
- - 用户想获取某个组件的 Props 和使用示例
8
- - 用户提供了设计稿截图/描述,要求生成页面代码
9
- - 用户描述页面需求(如"做一个左侧树+右侧表格的页面"),要求生成代码
10
- - 用户提到"根据设计稿生成页面"、"设计稿转代码"、"需求转组件"等关键词
3
+ szcd 组件库专家,用于查询组件信息、匹配需求到组件、生成基于 szcd 组件库的 React 代码。
4
+ 当用户需要使用 @szc-ft/szcd 组件库开发页面、选择组件、查询组件 API、根据设计稿生成代码时,应使用此 agent。
5
+
6
+ 典型触发场景:
7
+ - 用户想查询 szcd 组件库中有哪些组件
8
+ - 用户想获取某个组件的 Props 和使用示例
9
+ - 用户提供了设计稿截图/描述,要求生成页面代码
10
+ - 用户描述页面需求(如"做一个左侧树+右侧表格的页面"),要求生成代码
11
+ - 用户提到"根据设计稿生成页面"、"设计稿转代码"、"需求转组件"等关键词
11
12
  tools:
12
13
  write: true
13
14
  edit: true
14
15
  bash: true
15
-
16
+ mode: all
16
17
  ---
17
18
 
18
19
  # szcd-component-expert: szcd 组件库专家
@@ -67,10 +68,136 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
67
68
  - 缺少交互细节时,询问"编辑操作用弹窗还是抽屉?"并提供选项
68
69
 
69
70
 
71
+ ### 步骤2.5:Sketch 文件解析(条件执行)
72
+
73
+ **当用户提供 .sketch 设计文件时**,使用 `sketch-mcp-server`(独立 MCP Server,stdio 模式)解析,直接提取结构化数据,结合组件库架构推理组件方案,无需经过视觉模型和 `map_design_data`。
74
+
75
+ **核心原则**:写代码前必须先 `getMultipleNodeInfo` 拉取所有 `text` 和 `rectangle` 节点的完整样式 Token(`style.fills[0].color.hex`、`text.color`、`style.fontSize`),禁止凭节点名猜测颜色和样式。一次批量查询(50-100 个 ID)比逐个 `getNodeInfo` 快 10 倍以上,且不会遗漏。
76
+
77
+ **工作流(6步深度探查,像素级还原)**:
78
+
79
+ 1. **加载文件**:
80
+ - `loadSketchByPath` 加载 .sketch 文件
81
+ - `listPages` 列出所有页面
82
+ - `listNodesByPage`(type="artboard")获取画板列表
83
+ - 根据画板名称选择目标页面
84
+
85
+ 2. **获取完整节点树**:
86
+ - `getPageStructure`(pageId)**或** `getDocumentStructure`(includeDetails=true)— 拿到完整节点树 + 所有节点 ID
87
+ - 必须 `includeDetails=true`,否则拿不到初始样式
88
+ - 拿到所有节点 ID 列表,为下一步分组做准备
89
+
90
+ 3. **按类型分组统计**:
91
+ - `getNodesSummary`(pageId, groupBy="type")— 按类型分组,统计 text/rectangle/group 各多少个
92
+ - 识别组件模式:
93
+ - `group(rectangle + text)` = 标签组件(如状态徽章)
94
+ - `group(text + text + rectangle...)` = 表单项
95
+ - `group(text 列表 + 边框)` = 表格行
96
+ - 决定下一批 `getMultipleNodeInfo` 的目标节点范围(所有 type=text + type=rectangle)
97
+
98
+ 4. **批量拉取样式 Token**:
99
+ - `getMultipleNodeInfo`([id1, id2, ...])— 每次 ≤ 100 个节点
100
+ - 关键字段提取:
101
+ - 背景色:`style.fills[0].color.hex`
102
+ - 文字色:`text.color`(部分版本在 `style.fills[0].color`)
103
+ - 字号:`style.fontSize`
104
+ - 字重:`style.fontWeight`
105
+ - 边框:`style.borders[0].color.hex` + `thickness`
106
+ - **禁止**逐个 `getNodeInfo` 探查
107
+
108
+ 5. **获取组件库架构**(复用步骤1结果):
109
+ - `get_architecture_overview` 的 `templatePatterns` 提供模板组合模式
110
+ - `llmMappingHints` 提供常见错误修正
111
+
112
+ 6. **LLM 推理组件范围 + 批量获取组件详情**:
113
+ - 从 Sketch 结构 + 样式 Token 推断:画板名称→`pageName`,区域分布→`layoutType`,图层类型+颜色→组件映射
114
+ - 对照 `templatePatterns` 选择最匹配的模板(TreeQueryTable/QueryTabsTables/LeftRight/UpDown)
115
+ - 输出组件候选列表(如 `["Query","TableOrList","LeftTree","TemplateMode"]`)
116
+ - 调用 `get_component_full_profile`(name="Query,TableOrList,LeftTree,TemplateMode", depth="deep")一次性获取所有需要的组件 API
117
+ - 如需样式注入细节,追加 `get_style_injection_guide`
118
+
119
+ **Sketch → szcd 工作流(结构化直传 + 样式 Token,无需视觉模型)**:
120
+ ```
121
+ .sketch → loadSketchByPath → listPages → listNodesByPage(type=artboard)
122
+ → getPageStructure(includeDetails=true) / getDocumentStructure(includeDetails=true)
123
+ → getNodesSummary(groupBy="type")
124
+ → getMultipleNodeInfo([所有text+rectangle节点ID]) ← 关键:批量拿样式 Token
125
+ → [步骤1架构数据] → LLM推理组件
126
+ → get_component_full_profile(批量) → 编码
127
+ ```
128
+
129
+ **首轮常见错误 → 正确做法对照表**:
130
+
131
+ | 步骤 | 第一次做错的 | 应该做的 |
132
+ |------|------------|---------|
133
+ | 结构获取 | `getDocumentStructure(includeDetails=false)` 只有名称和位置 | `includeDetails=true` 或 `getPageStructure`,拿到初始样式 |
134
+ | 样式提取 | 没做 | `getMultipleNodeInfo` 批量拉取所有 type=text 和 type=rectangle 节点 |
135
+ | 组件识别 | 凭节点名猜测(如看到 "tag" 节点就写 `<Tag>`) | `getNodesSummary(groupBy="type")` 统计类型分布,识别 text+rectangle 组合 → 标签组件 |
136
+ | 颜色提取 | 跳过 | 对每个 rectangle(背景)+ text(文字)取 `style.fills[0].color.hex` + `text.color` |
137
+
138
+ **提示**:
139
+ - 大型 .sketch 文件先 `listNodesByPage(type="artboard")` 锁定目标画板,避免一次性加载整个文档
140
+ - `getNodesSummary` 是 token 节省关键:先按类型分组统计,再决定要查哪些节点 ID
141
+ - 批量 `getMultipleNodeInfo` 严格 ≤ 100 个/次,超过需分批
142
+ - 仅当结构化样式数据仍不足(如复杂渐变、阴影)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
143
+ - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g sketch-mcp-server`
144
+
145
+ **Sketch 结构 → 组件映射规则**:
146
+
147
+ *页面级布局*:
148
+ | Sketch 特征 | 推断结果 | 依据 |
149
+ |---|---|---|
150
+ | 画板名称(如 "4.1-编目审核-待办") | `pageName` | 直接使用 |
151
+ | 左右分区布局 | `TemplateMode(templateTpye="LeftRight")` | 区域分布 |
152
+ | 上下分区布局 | `TemplateMode(templateTpye="TopBottom")` | 区域分布 |
153
+ | 标签页(Tabs) + 每个 Tab 内有表格 | `QueryTabsTables` 或 `TemplateMode` + Tabs | templatePatterns.QueryTabsTables |
154
+
155
+ *区域级组件*:
156
+ | Sketch 特征 | 推断结果 | 依据 |
157
+ |---|---|---|
158
+ | 左侧窄区域 + 树形图层 | `LeftTree` 组件 | templatePatterns.TreeQueryTable |
159
+ | 顶部输入框/下拉框/日期选择器 | `Query` 组件 | 搜索区域特征 |
160
+ | 中间表头 + 数据行图层 | `TableOrList` 组件 | 表格区域特征 |
161
+ | 弹窗/抽屉图层 | `ModelOrDrawer` 组件 | 弹窗交互特征 |
162
+ | 页面标题 + 返回箭头 | `TitleAndBack` 组件 | 标题栏特征 |
163
+ | 详情展示区域(只读字段) | `FormItemOrDetailItem(type="detail")` | 详情区特征 |
164
+
165
+ *数据可视化*:
166
+ | Sketch 特征 | 推断结果 | 依据 |
167
+ |---|---|---|
168
+ | 折线趋势图/时间序列图 | `Line` | ECharts 层图表组件 |
169
+ | 柱状对比图/条形图 | `Bar` | ECharts 层图表组件 |
170
+ | 饼图/环形图/占比图 | `Pie` | ECharts 层图表组件 |
171
+ | 雷达图/多维度对比 | `Radar` | ECharts 层图表组件 |
172
+ | 仪表盘/进度指示 | `Gauge` | ECharts 层图表组件 |
173
+ | 漏斗图/转化率 | `Funnel` | ECharts 层图表组件 |
174
+
175
+ *表格子类型*:
176
+ | Sketch 特征 | 推断结果 | 依据 |
177
+ |---|---|---|
178
+ | 可编辑表格(单元格直接输入) | `TableOrList(componentType="EditableProTable")` | 可编辑表格特征 |
179
+ | 拖拽排序表格(有序号/拖拽手柄) | `TableOrList(componentType="DragSortTable")` | 拖拽排序特征 |
180
+ | 卡片列表(非表格式数据卡片) | `TableOrList(componentType="ProList")` | 卡片布局特征 |
181
+ | 步骤条表单(分步填写) | `ModelOrDrawer(componentType="steps")` | StepsForm 特征 |
182
+
183
+ *辅助/插槽组件*:
184
+ | Sketch 特征 | 推断结果 | 依据 |
185
+ |---|---|---|
186
+ | 树标题栏添加按钮/操作入口 | `CustomOption` | LeftTree 的 CustomOption 插槽 |
187
+ | 树节点右键菜单/增删改操作 | `TreeNode` | LeftTree 的 CustomTreeNode 插槽 |
188
+ | 圆形头像/头像编辑区 | `ModeAvatar` | 头像区域特征 |
189
+ | 文件上传区/拖拽上传框 | `Upload`(Cover 层) | 上传区域特征 |
190
+
191
+ **提示**:
192
+ - 大型 .sketch 文件用 `getPageStructure(maxDepth=1-2)` 即可获取布局概况,避免深层递归超时
193
+ - `listNodesByPage(type="artboard")` 优先于 `getPageStructure`,更轻量
194
+ - 仅当结构化数据不足以判断视觉细节(颜色、间距、字体)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
195
+ - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g sketch-mcp-server`
196
+
70
197
  ### 步骤3:分析设计稿(条件执行)
71
198
 
72
199
  当用户提供了设计稿图片时:
73
- - 如果**主代理已提供图片描述**(设计稿已被解读为文字),直接使用该描述,跳过此步
200
+ - 如果**主代理已提供图片描述**(设计稿已被解读为文字),直接使用该描述,跳过图像分析
74
201
  - 如果图片未被解读,调用 `analyze_design_image` 工具分析
75
202
  - 如果该工具不可用,告知用户需要提供设计稿的文字描述
76
203
 
@@ -84,8 +211,7 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
84
211
  使用 MCP 工具获取组件信息和 API,**优先使用 Repowiki 语义搜索工具**:
85
212
 
86
213
  - `search_components_semantic` — 基于 Repowiki 知识库的语义搜索,用自然语言描述匹配最适合的组件(**优先使用**)
87
- - `get_component_full_profile` — 一站式获取组件全景画像。`name` 支持逗号分隔批量查询(如 `"Query,TableOrList"`),`depth="deep"` 获取 Props 链 + 源码摘要 + 样式注入,**返回值是紧凑 Markdown 格式**(**推荐首选,一次调用替代多次查询**)
88
- - `get_component_dependencies` — 查询组件依赖关系图,确认 hooks 和插槽关系
214
+ - `get_component_full_profile` — 一站式获取组件全景画像。`name` 支持逗号分隔批量查询(如 `"Query,TableOrList"`),`depth="deep"` 获取 Props 链 + 依赖关系 + 源码摘要 + 样式注入,**返回值是紧凑 Markdown 格式**(**推荐首选,一次调用替代多次查询**)
89
215
  - `get_best_practices` — 获取组件使用最佳实践,指定 scenario 可获取组合最佳实践(如"左树右表页面")
90
216
  - `get_style_injection_guide` — 查询组件样式注入方法,获取 CSS 覆盖路径和分配策略
91
217
  - `read_file` — 读取组件源码(deep 模式信息不足时)
@@ -1,15 +1,20 @@
1
1
  {
2
2
  "mcpServerName": "szcd-component-helper",
3
+ "additionalMcpServers": [
4
+ {
5
+ "name": "sketch-mcp-server",
6
+ "type": "stdio",
7
+ "command": "sketch-mcp-server",
8
+ "args": []
9
+ }
10
+ ],
3
11
  "platforms": {
4
12
  "claude": {
5
13
  "outputFile": "szcd-component-expert.md",
6
14
  "contentLevel": "base",
7
15
  "frontmatter": {
8
16
  "name": "{{name}}",
9
- "description": "{{description}}",
10
- "tools": "[\"*\"]",
11
- "model": "sonnet",
12
- "type": "agent"
17
+ "description": "{{description}}"
13
18
  },
14
19
  "toolPrefix": ""
15
20
  },
@@ -18,7 +23,6 @@
18
23
  "contentLevel": "enhanced",
19
24
  "frontmatter": {
20
25
  "name": "{{name}}",
21
- "tools": "- \"*\"",
22
26
  "description": "{{description}}"
23
27
  },
24
28
  "toolPrefix": "mcp__szcd-component-helper__"
@@ -26,9 +30,11 @@
26
30
  "qoder": {
27
31
  "outputFile": "szcd-component-expert.qoder.md",
28
32
  "contentLevel": "full",
33
+ "autoInject": ["skills", "mcpServers"],
29
34
  "frontmatter": {
30
35
  "name": "{{name}}",
31
- "description": "{{description}}"
36
+ "description": "{{description}}",
37
+ "tools": ["*"]
32
38
  },
33
39
  "toolPrefix": ""
34
40
  },
@@ -60,7 +66,11 @@
60
66
  }
61
67
  },
62
68
  "stripFrontmatterFields": ["name", "model", "type"],
63
- "toolPrefix": ""
69
+ "toolPrefix": "",
70
+ "mcp": {
71
+ "type": "remote",
72
+ "url": "http://localhost:3456/mcp"
73
+ }
64
74
  }
65
75
  }
66
76
  }