@szc-ft/mcp-szcd-client 0.24.0 → 0.25.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 (31) hide show
  1. package/agents/build.js +22 -12
  2. package/agents/opencode-extension/agents/szcd-component-expert.md +92 -48
  3. package/agents/qwen-extension/agents/szcd-component-expert.md +92 -48
  4. package/agents/src/szcd-component-expert.md +92 -48
  5. package/agents/src/tools.json +1 -1
  6. package/agents/szcd-component-expert.md +92 -48
  7. package/agents/szcd-component-expert.qoder.md +93 -49
  8. package/agents/szcd-component-expert.trae.md +92 -48
  9. package/opencode-extension/agents/szcd-component-expert.md +92 -48
  10. package/opencode-extension/skills/local-api-tool/SKILL.md +164 -60
  11. package/opencode-extension/skills/local-api-tool/scripts/extract_swagger.py +477 -0
  12. package/opencode-extension/skills/szcd-component-helper/SKILL.md +7 -4
  13. package/opencode-extension/skills/szcd-design-to-code/SKILL.md +95 -24
  14. package/package.json +1 -1
  15. package/qwen-extension/agents/szcd-component-expert.md +92 -48
  16. package/qwen-extension/qwen-extension.json +1 -1
  17. package/qwen-extension/skills/local-api-tool/SKILL.md +164 -60
  18. package/qwen-extension/skills/local-api-tool/scripts/extract_swagger.py +477 -0
  19. package/qwen-extension/skills/szcd-component-helper/SKILL.md +7 -4
  20. package/qwen-extension/skills/szcd-design-to-code/SKILL.md +95 -24
  21. package/scripts/lib/claude-code.js +1 -1
  22. package/scripts/lib/common.js +44 -0
  23. package/scripts/lib/opencode.js +1 -1
  24. package/scripts/lib/qoder.js +1 -1
  25. package/scripts/lib/trae-cli.js +1 -1
  26. package/scripts/lib/trae-ide.js +1 -1
  27. package/scripts/postinstall.js +1 -0
  28. package/standard-skill/local-api-tool/SKILL.md +164 -60
  29. package/standard-skill/local-api-tool/scripts/extract_swagger.py +477 -0
  30. package/standard-skill/szcd-component-helper/SKILL.md +7 -4
  31. package/standard-skill/szcd-design-to-code/SKILL.md +95 -24
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ extract_swagger.py — 本地 Swagger 文档提取与解析(与服务端 parseApiDocs 一比一对齐)
4
+
5
+ 设计目标:
6
+ - 一步完成"下载 → 解析 → 按 tag/path 过滤 → 关联 DTO definitions → 输出"
7
+ - 避免 AI 在客户端用 curl|head 截断 JSON / 写临时 python 脚本踩花括号坑
8
+ - 输出结构与服务端 api_tool(action="parse_swagger_json") 完全一致,可平替
9
+
10
+ 参考实现:szcd-mcp-server/src/lib/swagger.js 的 parseApiDocs + server.js 的 filterApis
11
+ 递归深度 5,两层 definitions(summary + detail),引用树传递闭包裁剪。
12
+
13
+ 用法:
14
+ # 方式 A:脚本内部 curl 下载
15
+ extract_swagger.py --url http://10.x.x.x/svc/v2/api-docs [--cookie 'JSESSIONID=xxx']
16
+
17
+ # 方式 B:读取本地已下载的 JSON 文件
18
+ extract_swagger.py --file /tmp/swagger.json
19
+
20
+ # 过滤(对 url/summary/operationId/tag/description/dtoRef/responseVoName 模糊匹配,大小写不敏感)
21
+ extract_swagger.py --file /tmp/swagger.json --filter tag
22
+ extract_swagger.py --file /tmp/swagger.json --filter tag --filter user
23
+
24
+ # 仅查询单个 definition(兜底单 VO 查询,等价于服务端 get_definition)
25
+ extract_swagger.py --file /tmp/swagger.json --definition KnowledgeOperLogVO
26
+
27
+ # 输出到文件而非 stdout
28
+ extract_swagger.py --file /tmp/swagger.json --filter tag --out /tmp/parsed.json
29
+
30
+ 退出码:0 成功;1 参数错误;2 网络/IO 错误;3 解析错误
31
+ """
32
+
33
+ import argparse
34
+ import json
35
+ import sys
36
+ import subprocess
37
+ from pathlib import Path
38
+
39
+ MAX_DEPTH = 5
40
+ METHOD_KEYS = ["post", "get", "put", "delete", "patch"]
41
+
42
+
43
+ # ==================== 工具函数 ====================
44
+
45
+ def _ref_name(prop):
46
+ """从 property 定义中提取 $ref/originalRef 名称(不含数组 items 层)。"""
47
+ if not isinstance(prop, dict):
48
+ return None
49
+ if prop.get("originalRef"):
50
+ return prop["originalRef"]
51
+ ref = prop.get("$ref")
52
+ if ref:
53
+ return ref.rsplit("/", 1)[-1]
54
+ return None
55
+
56
+
57
+ def _ref_name_with_items(prop):
58
+ """提取 $ref 名称,包含数组 items 层(对齐 JS 端 expandProperties 第 250-251 行)。"""
59
+ if not isinstance(prop, dict):
60
+ return None
61
+ name = _ref_name(prop)
62
+ if name:
63
+ return name
64
+ items = prop.get("items")
65
+ if isinstance(items, dict):
66
+ return _ref_name(items)
67
+ return None
68
+
69
+
70
+ def _resolve_property_type(prop):
71
+ """对齐 JS 端 resolvePropertyType:数组返回 array<XXX>,否则返回 type 或 $ref 名。"""
72
+ if not isinstance(prop, dict):
73
+ return "string"
74
+ if prop.get("type") == "array":
75
+ items = prop.get("items") or {}
76
+ item_type = (
77
+ items.get("type")
78
+ or items.get("originalRef")
79
+ or (items.get("$ref", "").rsplit("/", 1)[-1] if items.get("$ref") else None)
80
+ or "object"
81
+ )
82
+ return f"array<{item_type}>"
83
+ if prop.get("type"):
84
+ return prop["type"]
85
+ if prop.get("originalRef"):
86
+ return prop["originalRef"]
87
+ if prop.get("$ref"):
88
+ return prop["$ref"].rsplit("/", 1)[-1]
89
+ return "object"
90
+
91
+
92
+ # ==================== 递归展开(对齐 JS 端 expandProperties,深度 5) ====================
93
+
94
+ def expand_properties(properties, definitions, depth=0, max_depth=MAX_DEPTH):
95
+ if not properties:
96
+ return []
97
+ result = []
98
+ for name, prop in properties.items():
99
+ if not isinstance(prop, dict):
100
+ prop = {}
101
+ entry = {
102
+ "name": name,
103
+ "type": _resolve_property_type(prop),
104
+ "description": prop.get("description", ""),
105
+ }
106
+ if "format" in prop:
107
+ entry["format"] = prop["format"]
108
+ if "example" in prop:
109
+ entry["example"] = prop["example"]
110
+ if "enum" in prop:
111
+ entry["enum"] = prop["enum"]
112
+
113
+ ref_name = _ref_name_with_items(prop)
114
+ if ref_name:
115
+ entry["refName"] = ref_name
116
+
117
+ # 数组类型:展开 items 的嵌套属性
118
+ if prop.get("type") == "array" and ref_name and depth < max_depth:
119
+ item_def = (definitions or {}).get(ref_name)
120
+ if isinstance(item_def, dict) and item_def.get("properties"):
121
+ entry["itemProperties"] = expand_properties(
122
+ item_def["properties"], definitions, depth + 1, max_depth
123
+ )
124
+
125
+ # 对象类型(非数组):展开嵌套属性
126
+ if prop.get("type") == "object" and ref_name and depth < max_depth:
127
+ nested_def = (definitions or {}).get(ref_name)
128
+ if isinstance(nested_def, dict) and nested_def.get("properties"):
129
+ entry["nestedProperties"] = expand_properties(
130
+ nested_def["properties"], definitions, depth + 1, max_depth
131
+ )
132
+
133
+ # 无 $ref 的 object 且有自身 properties(内联定义)
134
+ if (
135
+ prop.get("type") == "object"
136
+ and not ref_name
137
+ and prop.get("properties")
138
+ and depth < max_depth
139
+ ):
140
+ entry["nestedProperties"] = expand_properties(
141
+ prop["properties"], definitions, depth + 1, max_depth
142
+ )
143
+
144
+ result.append(entry)
145
+ return result
146
+
147
+
148
+ # ==================== 主解析(对齐 JS 端 parseApiDocs) ====================
149
+
150
+ def parse_api_docs(api_docs, referenced_defs=None):
151
+ paths = api_docs.get("paths") or {}
152
+ tags = api_docs.get("tags") or []
153
+ definitions = api_docs.get("definitions") or {}
154
+ info = api_docs.get("info") or {}
155
+ api_base_path = api_docs.get("basePath") or ""
156
+
157
+ parsed_tags = [
158
+ {"name": t.get("name"), "description": t.get("description", "")} for t in tags
159
+ ]
160
+
161
+ parsed_apis = []
162
+ auto_referenced_defs = set()
163
+
164
+ for url, path_value in paths.items():
165
+ if not isinstance(path_value, dict):
166
+ continue
167
+ for method in METHOD_KEYS:
168
+ api_def = path_value.get(method)
169
+ if not isinstance(api_def, dict):
170
+ continue
171
+ tag = (api_def.get("tags") or ["default"])[0]
172
+ parameters = api_def.get("parameters") or []
173
+
174
+ params = []
175
+ for p in parameters:
176
+ schema = p.get("schema") or {}
177
+ param_info = {
178
+ "name": p.get("name"),
179
+ "in": p.get("in"),
180
+ "required": p.get("required", False),
181
+ "type": p.get("type") or schema.get("type") or "string",
182
+ "description": p.get("description", ""),
183
+ }
184
+ # body 参数引用的 DTO
185
+ ref_name = schema.get("originalRef") or (
186
+ schema.get("$ref", "").rsplit("/", 1)[-1] if schema.get("$ref") else None
187
+ )
188
+ if ref_name:
189
+ param_info["dtoRef"] = ref_name
190
+ auto_referenced_defs.add(ref_name)
191
+ dto = definitions.get(ref_name)
192
+ if isinstance(dto, dict) and dto.get("properties"):
193
+ dto_props = expand_properties(dto["properties"], definitions, 0, MAX_DEPTH)
194
+ required_fields = dto.get("required") or []
195
+ for prop in dto_props:
196
+ if prop["name"] in required_fields:
197
+ prop["required"] = True
198
+ param_info["dtoProperties"] = dto_props
199
+ params.append(param_info)
200
+
201
+ # 响应(递归展开嵌套 VO)
202
+ response_info = None
203
+ responses = api_def.get("responses") or {}
204
+ r200 = responses.get("200") or {}
205
+ r_schema = r200.get("schema") if isinstance(r200, dict) else None
206
+ if isinstance(r_schema, dict):
207
+ ref_name = r_schema.get("originalRef") or (
208
+ r_schema.get("$ref", "").rsplit("/", 1)[-1] if r_schema.get("$ref") else None
209
+ )
210
+ if ref_name:
211
+ auto_referenced_defs.add(ref_name)
212
+ vo = definitions.get(ref_name)
213
+ if isinstance(vo, dict) and vo.get("properties"):
214
+ response_info = {
215
+ "voName": ref_name,
216
+ "properties": expand_properties(
217
+ vo["properties"], definitions, 0, MAX_DEPTH
218
+ ),
219
+ }
220
+ else:
221
+ response_info = {"voName": ref_name}
222
+
223
+ parsed_apis.append({
224
+ "url": url,
225
+ "method": method.upper(),
226
+ "tag": tag,
227
+ "summary": api_def.get("summary", ""),
228
+ "description": api_def.get("description", ""),
229
+ "deprecated": api_def.get("deprecated", False),
230
+ "parameters": params,
231
+ "response": response_info,
232
+ "operationId": api_def.get("operationId"),
233
+ })
234
+
235
+ # 收集被引用 definitions 的传递闭包
236
+ collected_refs = set(auto_referenced_defs)
237
+ queue = list(auto_referenced_defs)
238
+ while queue:
239
+ name = queue.pop(0)
240
+ d = definitions.get(name)
241
+ if not isinstance(d, dict) or not d.get("properties"):
242
+ continue
243
+ for prop in d["properties"].values():
244
+ ref_name = _ref_name_with_items(prop)
245
+ if ref_name and ref_name in definitions and ref_name not in collected_refs:
246
+ collected_refs.add(ref_name)
247
+ queue.append(ref_name)
248
+
249
+ # definitions 摘要层:所有 VO/DTO 的 name + type + description
250
+ definitions_summary = [
251
+ {
252
+ "name": name,
253
+ "type": d.get("type", "object") if isinstance(d, dict) else "object",
254
+ "description": (d.get("description") or d.get("title") or "") if isinstance(d, dict) else "",
255
+ }
256
+ for name, d in definitions.items()
257
+ ]
258
+
259
+ # definitionsDetail 详情层:仅展开被引用的(含传递闭包),递归深度 5
260
+ effective_refs = referenced_defs if referenced_defs is not None else collected_refs
261
+ definitions_detail = []
262
+ for name in effective_refs:
263
+ d = definitions.get(name)
264
+ if isinstance(d, dict):
265
+ definitions_detail.append({
266
+ "name": name,
267
+ "type": d.get("type", "object"),
268
+ "description": d.get("description") or d.get("title") or "",
269
+ "properties": expand_properties(
270
+ d.get("properties") or {}, definitions, 0, MAX_DEPTH
271
+ ),
272
+ })
273
+
274
+ return {
275
+ "info": {
276
+ "title": info.get("title"),
277
+ "version": info.get("version"),
278
+ "description": info.get("description"),
279
+ } if info else None,
280
+ "basePath": api_base_path,
281
+ "tags": parsed_tags,
282
+ "apis": parsed_apis,
283
+ "definitions": definitions_summary,
284
+ "definitionsDetail": definitions_detail,
285
+ }
286
+
287
+
288
+ # ==================== 过滤(对齐 JS 端 filterApis + apiFilter 裁剪 definitionsDetail) ====================
289
+
290
+ def filter_apis(apis, keywords):
291
+ """对齐 JS 端 filterApis:对 url/summary/operationId/tag/description/dtoRef/voName 模糊匹配。"""
292
+ if not keywords or not apis:
293
+ return apis
294
+ lower_keywords = [k.lower() for k in keywords]
295
+ out = []
296
+ for api in apis:
297
+ dto_refs = " ".join(p.get("dtoRef", "") for p in (api.get("parameters") or []) if p.get("dtoRef"))
298
+ response_vo = (api.get("response") or {}).get("voName", "") if api.get("response") else ""
299
+ searchable = " ".join([
300
+ api.get("url", ""),
301
+ api.get("summary", ""),
302
+ api.get("operationId") or "",
303
+ api.get("tag", ""),
304
+ api.get("description", ""),
305
+ dto_refs,
306
+ response_vo,
307
+ ]).lower()
308
+ if any(kw in searchable for kw in lower_keywords):
309
+ out.append(api)
310
+ return out
311
+
312
+
313
+ def trim_definitions_detail(result, keywords):
314
+ """apiFilter 生效时,definitionsDetail 仅保留匹配 API 引用的(含传递闭包)。
315
+ 对齐 JS 端 server.js 第 1581-1604 行的裁剪逻辑。"""
316
+ apis = result.get("apis") or []
317
+ matched_refs = set()
318
+ for api in apis:
319
+ for p in api.get("parameters") or []:
320
+ if p.get("dtoRef"):
321
+ matched_refs.add(p["dtoRef"])
322
+ if api.get("response") and api["response"].get("voName"):
323
+ matched_refs.add(api["response"]["voName"])
324
+
325
+ detail_index = {d["name"]: d for d in result.get("definitionsDetail") or []}
326
+ detail_names = set(matched_refs)
327
+ queue = list(matched_refs)
328
+ while queue:
329
+ name = queue.pop(0)
330
+ detail = detail_index.get(name)
331
+ if not detail or not detail.get("properties"):
332
+ continue
333
+ for prop in detail["properties"]:
334
+ ref = prop.get("refName")
335
+ if ref and ref not in detail_names:
336
+ detail_names.add(ref)
337
+ queue.append(ref)
338
+
339
+ result["definitionsDetail"] = [d for d in result.get("definitionsDetail") or [] if d["name"] in detail_names]
340
+ result["_filter"] = {
341
+ "keywords": keywords,
342
+ "matched": len(apis),
343
+ }
344
+ return result
345
+
346
+
347
+ # ==================== get_definition(单 VO 查询兜底) ====================
348
+
349
+ def get_definition(api_docs, definition_name):
350
+ """对齐 JS 端 get_definition action:按名查询单个 VO,找不到给模糊匹配建议。"""
351
+ definitions = api_docs.get("definitions") or {}
352
+ if definition_name in definitions:
353
+ result = parse_api_docs(api_docs, referenced_defs={definition_name})
354
+ target = next((d for d in result["definitionsDetail"] if d["name"] == definition_name), None)
355
+ return {"found": True, "definition": target}
356
+
357
+ # 模糊匹配
358
+ lower = definition_name.lower()
359
+ suggestions = [n for n in definitions.keys() if lower in n.lower()][:10]
360
+ return {
361
+ "found": False,
362
+ "definitionName": definition_name,
363
+ "suggestions": suggestions,
364
+ "message": f"未找到 definition '{definition_name}'。" + (
365
+ f"相近建议:{', '.join(suggestions)}" if suggestions else "无相近匹配。"
366
+ ),
367
+ }
368
+
369
+
370
+ # ==================== 输入:curl 下载 / 读文件 ====================
371
+
372
+ def fetch_via_curl(url, cookie=None, auth=None, timeout=30):
373
+ """脚本内部走 curl 下载 Swagger JSON。失败抛 RuntimeError。"""
374
+ cmd = ["curl", "-sS", "--connect-timeout", "10", "--max-time", str(timeout),
375
+ "-H", "Accept: application/json"]
376
+ if cookie:
377
+ cmd += ["-H", f"Cookie: {cookie}"]
378
+ if auth:
379
+ cmd += ["-H", f"Authorization: Basic {auth}"]
380
+ cmd.append(url)
381
+ try:
382
+ r = subprocess.run(cmd, capture_output=True, text=True, check=False)
383
+ except FileNotFoundError:
384
+ raise RuntimeError("找不到 curl 命令,请确认本地已安装 curl") from None
385
+ if r.returncode != 0:
386
+ raise RuntimeError(f"curl 失败 (exit {r.returncode}): {r.stderr.strip() or '无错误输出'}")
387
+ if not r.stdout.strip():
388
+ raise RuntimeError("curl 返回空响应")
389
+ return r.stdout
390
+
391
+
392
+ def load_json_text(text):
393
+ try:
394
+ return json.loads(text)
395
+ except json.JSONDecodeError as e:
396
+ # 给出第一行 200 字符 + 错误位置,方便排查
397
+ preview = text[:200].replace("\n", "\\n")
398
+ raise RuntimeError(f"JSON 解析失败: {e.msg} (line {e.lineno} col {e.colno})。响应预览: {preview}") from None
399
+
400
+
401
+ # ==================== CLI ====================
402
+
403
+ def main():
404
+ parser = argparse.ArgumentParser(
405
+ description="本地 Swagger 文档提取与解析(与服务端 parseApiDocs 一比一对齐)",
406
+ formatter_class=argparse.RawDescriptionHelpFormatter,
407
+ epilog=__doc__,
408
+ )
409
+ src = parser.add_mutually_exclusive_group(required=True)
410
+ src.add_argument("--url", help="Swagger api-docs URL(脚本内部走 curl 下载)")
411
+ src.add_argument("--file", help="本地 Swagger JSON 文件路径")
412
+
413
+ parser.add_argument("--cookie", help="--url 模式可选,鉴权后的 Cookie 值(如 'JSESSIONID=xxx')")
414
+ parser.add_argument("--auth", help="--url 模式可选,Basic 鉴权 base64(user:pass)")
415
+ parser.add_argument("--timeout", type=int, default=30, help="--url 模式 curl 超时秒数,默认 30")
416
+
417
+ parser.add_argument(
418
+ "--filter", action="append", default=[],
419
+ help="按关键词过滤 api(可多次指定,对 url/summary/operationId/tag/description/dtoRef/voName 模糊匹配)",
420
+ )
421
+ parser.add_argument(
422
+ "--definition",
423
+ help="仅查询单个 VO/DTO 的完整字段定义(等价于服务端 get_definition action)",
424
+ )
425
+ parser.add_argument("--out", help="输出到文件(默认 stdout)")
426
+ parser.add_argument(
427
+ "--indent", type=int, default=2,
428
+ help="JSON 输出缩进,默认 2;设为 0 输出紧凑 JSON",
429
+ )
430
+
431
+ args = parser.parse_args()
432
+
433
+ # 1. 读取原始 Swagger JSON
434
+ try:
435
+ if args.url:
436
+ raw_text = fetch_via_curl(args.url, cookie=args.cookie, auth=args.auth, timeout=args.timeout)
437
+ else:
438
+ raw_text = Path(args.file).read_text(encoding="utf-8")
439
+ except Exception as e:
440
+ print(f"[ERROR] 读取 Swagger 失败: {e}", file=sys.stderr)
441
+ sys.exit(2)
442
+
443
+ # 2. 解析 JSON
444
+ try:
445
+ api_docs = load_json_text(raw_text)
446
+ except Exception as e:
447
+ print(f"[ERROR] {e}", file=sys.stderr)
448
+ sys.exit(3)
449
+
450
+ # 3. 路径分支:--definition 单查 / 默认完整解析
451
+ try:
452
+ if args.definition:
453
+ result = get_definition(api_docs, args.definition)
454
+ else:
455
+ result = parse_api_docs(api_docs)
456
+ if args.filter:
457
+ total = len(result["apis"])
458
+ result["apis"] = filter_apis(result["apis"], args.filter)
459
+ result = trim_definitions_detail(result, args.filter)
460
+ result["_filter"]["total"] = total
461
+ except Exception as e:
462
+ print(f"[ERROR] 解析失败: {e}", file=sys.stderr)
463
+ sys.exit(3)
464
+
465
+ # 4. 输出
466
+ indent = args.indent if args.indent > 0 else None
467
+ out_text = json.dumps(result, ensure_ascii=False, indent=indent)
468
+ if args.out:
469
+ Path(args.out).write_text(out_text, encoding="utf-8")
470
+ print(f"[OK] 已输出到 {args.out} ({len(out_text)} bytes)", file=sys.stderr)
471
+ else:
472
+ sys.stdout.write(out_text)
473
+ sys.stdout.write("\n")
474
+
475
+
476
+ if __name__ == "__main__":
477
+ main()
@@ -211,16 +211,19 @@ sketch-mcp-server 是社区版 MCP Server(npm: `sketch-mcp-server`),通过
211
211
  ### 设计稿分析工具
212
212
 
213
213
  #### 5. analyze_design_image
214
- **功能**:分析 UI 设计稿图片,提取 Token 配置、CSS 覆盖样式、视觉细节。支持 base64/URL/文件路径,pixel_perfect 模式最高还原度。
214
+ **功能**:分析 UI 设计稿图片,提取 Token 配置、CSS 覆盖样式、视觉细节。支持 upload_id/base64/URL/文件路径,pixel_perfect 模式最高还原度。
215
215
  **参数**:
216
- - `imageSource` (string, required): 图片数据(base64/URL/文件路径)
217
- - `sourceType` (enum, optional, default: "file_path"): base64/url/file_path
216
+ - `imageSource` (string, required): 图片数据(upload_id/base64/URL/文件路径)
217
+ - `sourceType` (enum, optional, default: "upload_id"): upload_id/base64/url/file_path
218
218
  - `analysisType` (enum, optional, default: "full"): layout/components/tokens/full/pixel_perfect
219
219
  - `pageContext` (string, optional): 页面业务上下文
220
220
  - `targetLibrary` (enum, optional, default: "auto"): szcd/antd/pro-components/auto
221
221
  - `outputFormat` (enum, optional, default: "markdown"): markdown/json/structured_text/restoration_code
222
222
 
223
- **重要提示**:远程 SSE/HTTP 连接时优先使用 base64;本地 stdio 可用 file_path。
223
+ **重要提示**:
224
+ - 远程 SSE/HTTP 连接时优先使用 upload_id:先调用 `get_upload_endpoint` 获取上传地址,用 curl 上传图片获取 upload_id,再传给本工具
225
+ - **上传重试限制**:curl 上传最多重试 3 次(含首次),超过后停止重试并向用户说明失败原因,不要无限重试浪费 token
226
+ - 本地 stdio 直连可用 file_path
224
227
 
225
228
  ### 样式注入指南工具
226
229