@szc-ft/mcp-szcd-client 0.23.1 → 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 +94 -50
  3. package/agents/qwen-extension/agents/szcd-component-expert.md +94 -50
  4. package/agents/src/szcd-component-expert.md +94 -50
  5. package/agents/src/tools.json +1 -1
  6. package/agents/szcd-component-expert.md +94 -50
  7. package/agents/szcd-component-expert.qoder.md +95 -51
  8. package/agents/szcd-component-expert.trae.md +94 -50
  9. package/opencode-extension/agents/szcd-component-expert.md +94 -50
  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 +22 -17
  13. package/opencode-extension/skills/szcd-design-to-code/SKILL.md +141 -31
  14. package/package.json +1 -1
  15. package/qwen-extension/agents/szcd-component-expert.md +94 -50
  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 +22 -17
  20. package/qwen-extension/skills/szcd-design-to-code/SKILL.md +141 -31
  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 +22 -17
  31. package/standard-skill/szcd-design-to-code/SKILL.md +141 -31
@@ -70,54 +70,54 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
70
70
 
71
71
  ### 步骤2.5:Sketch 文件解析(条件执行)
72
72
 
73
- **当用户提供 .sketch 设计文件时**,使用 `sketch-mcp-server`(独立 MCP Server,stdio 模式)解析,直接提取结构化数据,结合组件库架构推理组件方案,无需经过视觉模型和 `map_design_data`。
73
+ **当用户提供 .sketch 设计文件时**,使用 `sketch-mcp-server`(独立 MCP Server,stdio 模式,**v1.5.0+**)解析,直接提取结构化数据,结合组件库架构推理组件方案,无需经过视觉模型和 `map_design_data`。
74
74
 
75
- **核心原则**:写代码前必须先 `getMultipleNodeInfo` 拉取所有 `text` 和 `rectangle` 节点的完整样式 Token(`style.fills[0].color.hex`、`text.color`、`style.fontSize`),禁止凭节点名猜测颜色和样式。一次批量查询(50-100 ID)比逐个 `getNodeInfo` 10 倍以上,且不会遗漏。
75
+ **核心原则**:写代码前必须先 `getMultipleNodeInfo` 拉取所有 `text`、`rectangle`、`group` 节点的完整样式 Token + 父子归属。`queryNodes.type` 接受数组(一次拿三类)、`getMultipleNodeInfo` 内部自动分片、每个节点自动返回 `parent: {id,name,type}`,禁止凭节点名猜测样式,禁止用 x 坐标判定元素归属。
76
76
 
77
- **工作流(6步深度探查,像素级还原)**:
77
+ **工作流(4 步,sketch-mcp-server v1.5.0+)**:
78
78
 
79
79
  1. **加载文件**:
80
80
  - `loadSketchByPath` 加载 .sketch 文件
81
81
  - `listPages` 列出所有页面
82
- - `listNodesByPage`(type="artboard")获取画板列表
82
+ - `queryNodes`(pageId, type="artboard")获取画板列表
83
83
  - 根据画板名称选择目标页面
84
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 个节点
85
+ 2. **获取分区结构**:
86
+ - `getPageStructure`(pageId, mode="tree", maxDepth=3)— 看顶层 group 分区(通常 2-5 个,对应 LeftTree区/Query+Table区/信息栏区/标题栏区)
87
+ - 记下每个分区根 group 的 id 和 name,后续按 `parent` 字段归属元素
88
+
89
+ 3. **批量获取目标节点 ID**:
90
+ - 推荐:`queryNodes({ pageId, type: ["text", "rectangle", "group"], limit: 500 })` — **type 数组**一次拿三类节点的 ID
91
+ - 备选:`getPageStructure(pageId, mode="ids")` 返回按类型分组的全量 ID `{ text: [...], rectangle: [...], group: [...], bitmap: [...], shapePath: [...] }`
92
+ - 顺手记下 bitmap/shapePath 节点列表,传给步骤 2.5.1
93
+
94
+ 4. **批量拉取样式 Token + 父子归属**:
95
+ - `getMultipleNodeInfo`([id1, id2, ...])— 传入全部 ID,**内部自动按 100 个一批分片**,无需手动拆分
96
+ - **同批必须包含 text + rectangle + group 三类节点 ID**(漏 group 会丢失渐变背景/边框/阴影)
100
97
  - 关键字段提取:
101
- - 背景色:`style.fills[0].color.hex`
98
+ - 背景色:`style.fills[0].color.hex`(含 group 容器的渐变)
102
99
  - 文字色:`text.color`(部分版本在 `style.fills[0].color`)
103
100
  - 字号:`style.fontSize`
104
101
  - 字重:`style.fontWeight`
105
102
  - 边框:`style.borders[0].color.hex` + `thickness`
103
+ - 阴影:`style.shadows`
104
+ - **父子归属**:`parent: { id, name, type }` ← v1.5.0+ 自动返回,元素归属判定唯一可信来源
106
105
  - **禁止**逐个 `getNodeInfo` 探查
107
106
 
108
107
  **步骤 2.5 产出物**(**只产出数据,不做组件映射**):
109
- - Sketch 节点结构 + 全量节点 ID 列表
110
- - text/rectangle 节点的完整样式 Token
108
+ - Sketch 顶层分区结构(步骤 2 tree)
109
+ - text/rectangle/group 节点的完整样式 Token + parent 父子关系
111
110
  - 图表区结构规律识别结果
111
+ - bitmap ID 列表 + shapePath 节点名列表(传给步骤 2.5.1)
112
112
 
113
113
  组件候选列表推理**不**在步骤 2.5 内部完成,**统一在主流程步骤 3.5 中结合步骤 1 的 `templatePatterns` 推理**。
114
114
 
115
- **Sketch → szcd 工作流(结构化直传 + 样式 Token,无需视觉模型)**:
115
+ **Sketch → szcd 工作流(v1.5.0+)**:
116
116
  ```
117
- .sketch → loadSketchByPath → listPages → listNodesByPage(type=artboard)
118
- → getPageStructure(includeDetails=true) / getDocumentStructure(includeDetails=true)
119
- getNodesSummary(groupBy="type")
120
- → getMultipleNodeInfo([所有text+rectangle节点ID]) 步骤 2.5 结束
117
+ .sketch → loadSketchByPath → listPages → queryNodes(pageId, type="artboard")
118
+ → getPageStructure(pageId, mode="tree", maxDepth=3) ← 拿分区
119
+ queryNodes(pageId, type=["text","rectangle","group"]) ← 拿全量 ID(数组类型)
120
+ → getMultipleNodeInfo([全部 ID]) 自动分片,含 parent 字段
121
121
  → [进入主流程步骤 3.5 统一推理]
122
122
  ```
123
123
 
@@ -125,16 +125,17 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
125
125
 
126
126
  | 步骤 | 第一次做错的 | 应该做的 |
127
127
  |------|------------|---------|
128
- | 结构获取 | `getDocumentStructure(includeDetails=false)` 只有名称和位置 | `includeDetails=true` `getPageStructure`,拿到初始样式 |
129
- | 样式提取 | 没做 | `getMultipleNodeInfo` 批量拉取所有 type=text 和 type=rectangle 节点 |
130
- | 组件识别 | 凭节点名猜测(如看到 "tag" 节点就写 `<Tag>`) | `getNodesSummary(groupBy="type")` 统计类型分布,识别 text+rectangle 组合 → 标签组件 |
131
- | 颜色提取 | 跳过 | 对每个 rectangle(背景)+ text(文字)取 `style.fills[0].color.hex` + `text.color` |
128
+ | 分区识别 | 直接拉所有节点详情,靠 x/y 反推分区 | `getPageStructure(mode="tree", maxDepth=3)` 先看顶层 group 分布 |
129
+ | 节点收集 | 分两次调用 queryNodes text 和 rectangle | `queryNodes(type=["text","rectangle","group"])` 一次拿三类 |
130
+ | 样式提取 | 没做 | `getMultipleNodeInfo([全部 ID])` 内部自动分片,**单次调用** |
131
+ | 元素归属 | x/y 坐标判定("x=272 列都是同区域") | `getMultipleNodeInfo` 返回的 `parent.id/name` 字段 |
132
+ | 颜色提取 | 跳过 group 只取 text/rectangle | 同批提交 group ID,从 `style.fills` 取渐变背景 |
132
133
 
133
- **实战经验沉淀(重要,4 条核心收获)**:
134
+ **实战经验沉淀(重要)**:
134
135
 
135
136
  **收获 1:首轮 `getMultipleNodeInfo` 必须全量拉取,不能挑**
136
137
  - 错误做法:先查 7 个顶层容器,再根据需要分批补漏(结果补了 7 轮 ≈ 108 个节点)
137
- - 正确做法:`getPageStructure(pageId)` 一次性拿全量 ID → `getMultipleNodeInfo(全部 text + 全部 rectangle 节点 ID)`,≤100 个/批,**2 批搞定**
138
+ - 正确做法:`queryNodes(type=["text","rectangle","group"])` 一次性拿全量 ID → `getMultipleNodeInfo([全部 ID])`,**单次调用**(内部自动分片)
138
139
 
139
140
  **收获 2:text 节点是颜色/DOM 映射的两级索引**
140
141
  每个 text 节点同时关联两种上下文,**必须同批拉取**:
@@ -142,9 +143,28 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
142
143
  - 所属 group 父级 rectangle 样式:`bg`、`border`、`radius`、`shadow`
143
144
 
144
145
  错误做法:先查 text 拿自身样式 → 再根据 parent ID 单独查 rectangle(多走一步)
145
- 正确做法:构造查询列表时,**把同一 group 内的 text + rectangle 节点 ID 合并提交**
146
+ 正确做法:用 `queryNodes(type=["text","rectangle","group"])` 一次拿三类 ID,`getMultipleNodeInfo` 返回的 `parent` 字段直接告诉你归属关系
147
+
148
+ **收获 3:元素归属必须走 group 父子关系,禁止 x 坐标平面判定**
149
+
150
+ 实战教训:`'主题'text(x=272,y=149)` 被错误识别为内容区块标题,实际是左侧树标题(与树节点同 x=272 列)。同列坐标不代表同区域。
151
+
152
+ 正确做法:
153
+ - 步骤 2 拿到完整节点树后,先识别顶层 group 分区(通常 2-5 个,对应 LeftTree区/Query+Table区/信息栏区/标题栏区)
154
+ - `getMultipleNodeInfo` v1.5.0+ **自动返回** `parent: { id, name, type }` 字段,按 parent 归属到具体分区
155
+ - "主题"这类标题文本,先看 `parent.name` 是否带 "LeftTree" / "树" / "PageContent" 等容器关键字,不要看 x 坐标
156
+ - 坐标仅作为同 parent 内子元素排序兜底使用
157
+
158
+ **收获 4:信息栏/卡片容器必须先取 group 再取子元素**
159
+
160
+ 实战教训:仅提取 text 节点导致信息栏容器样式遗漏(渐变背景 #f3faff→#f8fdff、边框 #e9f5ff、图标编组)。
161
+
162
+ 正确做法:
163
+ - 看到画板里有 `编组N`/`infoCard`/`statBox` 等 group 节点,必须连 group 自身和内部 rectangle 一起拉 `getMultipleNodeInfo`
164
+ - group 节点的 `style.fills` 才是渐变背景、`style.borders` 才是边框
165
+ - 子图标若为 group(rectangle + shapePath),rectangle 是圆形/方形背景,shapePath 是矢量图标
146
166
 
147
- **收获 3:图表区样式提取有规律可循**
167
+ **收获 5:图表区样式提取有规律可循**
148
168
 
149
169
  | 图表子元素 | 对应 Sketch 节点类型 | 关键样式字段 |
150
170
  |---|---|---|
@@ -159,23 +179,34 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
159
179
 
160
180
  识别出图表后,**图例色块**应归入 ECharts `legend` 配置(不要用 div 硬编码),**柱体色**映射到 `series.itemStyle.color`。
161
181
 
162
- **收获 4:矢量图形是工具链盲区(兜底方案)**
163
- - `shapePath` / `shapeGroup` 在 sketch 节点中占比可能高达 46.8%(实战中 434/926),但 `renderNodeAsBase64` 不支持渲染
164
- - 解压 `.sketch`(zip 格式)只能提取 `bitmap` 中内嵌的 PNG/PDF(实战中可能仅 2 张,常是侧边栏 Logo)
165
- - **兜底方案**:① 用解压提取的 PNG/PDF 资源;② 无资源时用 antd `@ant-design/icons` 同语义图标替代;③ 在代码注释中标注"⚠️ 矢量图标用 antd 图标替代,原始 Sketch 为 shapePath"
182
+ **收获 6:矢量图形 + 图标语义优先级(v1.5.0 已内置 compoundIcons)**
183
+ - `shapePath` / `shapeGroup` 在 sketch 节点中占比可能高达 46.8%,`renderNodeAsBase64` 不支持渲染
184
+ - 解压 `.sketch` 只能提取 `bitmap` 中内嵌的 PNG/PDF(常仅几张,通常是 Logo)
185
+ - **`matchIconFromName` v1.5.0+ 评分制**:每个 nodeName 返回 Top-3 候选,complex 词组合(如"文件夹+新增")自动命中 `FolderAddOutlined`(compound score 100+),不会被"新增"单独 +20 分压制
186
+ - 优先看 `bestMatch.source === "compound"`:表示已强映射,直接采用 `antdIcon`
187
+ - `unmatched`(无关键词信号,如"编组11备份")必须看节点 `parent.name` 和兄弟节点上下文兜底
166
188
 
167
- **提示**:
168
- - 大型 .sketch 文件先 `listNodesByPage(type="artboard")` 锁定目标画板,避免一次性加载整个文档
169
- - `getNodesSummary` 是 token 节省关键:先按类型分组统计,再决定要查哪些节点 ID
170
- - 批量 `getMultipleNodeInfo` 严格 ≤ 100 个/次,超过需分批
171
- - 仅当结构化样式数据仍不足(如复杂渐变、阴影)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
172
- - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g sketch-mcp-server`
189
+ **参考表(内置 compoundIcons 已覆盖;列出供 unmatched 兜底参考)**:
173
190
 
174
- **提示**:
175
- - 大型 .sketch 文件用 `getPageStructure(maxDepth=1-2)` 即可获取布局概况,避免深层递归超时
176
- - `listNodesByPage(type="artboard")` 优先于 `getPageStructure`,更轻量
177
- - 仅当结构化数据不足以判断视觉细节(颜色、间距、字体)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
178
- - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g sketch-mcp-server`
191
+ | Sketch 节点上下文 | 期望 antd 图标 | 反例(不要选) |
192
+ |---|---|---|
193
+ | 文件夹+新增/添加 | `FolderAddOutlined` | PlusOutlined |
194
+ | 文件+新增 | `FileAddOutlined` | PlusOutlined |
195
+ | 用户+新增 | `UserAddOutlined` | PlusOutlined |
196
+ | 文件夹+打开 | `FolderOpenOutlined` | FolderOutlined |
197
+ | 编辑+笔 | `EditOutlined` | FormOutlined |
198
+ | 设置+齿轮 | `SettingOutlined` | ToolOutlined |
199
+
200
+ - **兜底方案**:① 用 `extractBitmaps` 提取的 PNG/PDF 资源;② `matchIconFromName` 的 Top-1(若 source=compound);③ unmatched 时按 parent 上下文 + 上表选 antd `@ant-design/icons` 复合图标;④ 在代码注释中标注"⚠️ 矢量图标用 antd 图标替代,原始 Sketch 为 shapePath"
201
+
202
+ **提示(sketch-mcp-server v1.5.0+)**:
203
+ - 大型 .sketch 文件先 `queryNodes(pageId, type="artboard")` 锁定目标画板,避免一次性加载整个文档
204
+ - `queryNodes(pageId, type=["text","rectangle","group"])` 一次拿三类节点 ID(type 字段支持数组),跳过 `getPageStructure(mode="ids")` 单独调用
205
+ - `getPageStructure(pageId, mode="tree", maxDepth=2-3)` 用于看分区层级;`mode="summary"` 用于按类型统计概况
206
+ - 批量 `getMultipleNodeInfo` 支持任意数量 ID(内部按 100 一批自动分片),无需手动拆分;每个节点自带 `parent: {id,name,type}` 字段,元素归属直接走 parent,禁止用 x/y 坐标
207
+ - `matchIconFromName` 评分制返回 Top-3 + `bestMatch.source === "compound"` 强映射,shapePath 节点优先走它
208
+ - 仅当结构化样式数据仍不足(如复杂渐变、阴影)时,才回退到 `renderNodeAsBase64` + `analyze_design_image`
209
+ - 如果 sketch-mcp-server 工具不可用,提示用户安装:`npm install -g @szc-ft/sketch-mcp-server`
179
210
 
180
211
  ### 步骤3:分析设计稿(条件执行)
181
212
 
@@ -244,6 +275,19 @@ szcd 是基于 Ant Design 5.27 封装的企业级 React 组件库,采用分层
244
275
  | 可编辑表格(单元格直接输入) | `TableOrList(componentType="EditableProTable")` | 可编辑表格特征 |
245
276
  | 卡片列表(非表格式数据卡片) | `TableOrList(componentType="ProList")` | 卡片布局特征 |
246
277
 
278
+ #### ⚠️ TemplateMode 槽位传参铁律(高频踩坑)
279
+
280
+ **LeftContent / Query / PageContent 三个槽位都接收单个 ReactNode,禁止传数组**:
281
+
282
+ | ❌ 错误 | ✅ 正确 | 后果 |
283
+ |---|---|---|
284
+ | `LeftContent={[<LeftTree/>]}` | `LeftContent={<LeftTree/>}` | 数组会触发多余 tab 渲染 |
285
+ | `LeftContent={[<LeftTree/>, <Filter/>]}` | `LeftContent={<><LeftTree/><Filter/></>}` 或包一层 div | 同上 |
286
+ | `Query={[<Query/>]}` | `Query={<Query/>}` | 同上 |
287
+ | `PageContent={[<TableOrList/>]}` | `PageContent={<TableOrList/>}` | 同上 |
288
+
289
+ **判定方法**:Sketch 中只要某个分区是"单棵树/单个查询面板/单个表格",对应槽位就传单个组件实例。需要多个并列时用 Fragment `<>...</>` 或容器 div 包裹,**不要用数组**。
290
+
247
291
  **长尾场景走语义搜索**(不进文档,避免膨胀):
248
292
 
249
293
  *数据可视化长尾*:`Radar`(雷达图)、`Gauge`(仪表盘)、`Funnel`(漏斗图)
@@ -1,11 +1,14 @@
1
1
  ---
2
2
  name: local-api-tool
3
- description: 本地网络 API 工具,专门处理 10.x.x.x 网段的 Swagger/YApi 文档拉取和联调测试。当服务端 api_tool LOCAL_NETWORK_UNREACHABLE 无法访问时,此技能在用户本地环境执行 curl,再将结果交给服务端解析。适用于内网开发环境、VPN 场景。**仅限 10.x.x.x 网段**,172.16-31.x.x 和 192.168.x.x 服务端可直连,不要使用本技能。
3
+ description: 本地网络 API 工具,专门处理 10.x.x.x 网段的 Swagger/YApi 文档拉取和联调测试。内置 scripts/extract_swagger.py 一步完成"下载→解析→按 tag 过滤→关联 DTO definitions→输出",与服务端 parseApiDocs 一比一对齐(递归深度 5、两层 definitions、传递闭包裁剪),避免 curl 管道截断和手写花括号平衡脚本的弯路。仅当脚本不可用时回退到服务端 parse_swagger_json 兜底。**仅限 10.x.x.x 网段**,172.16-31.x.x 和 192.168.x.x 服务端可直连,不要使用本技能。
4
4
  compatibility:
5
5
  tools:
6
6
  - run_command
7
7
  - web_search
8
8
  - web_fetch
9
+ runtime:
10
+ - python3 >= 3.7
11
+ - curl
9
12
  ---
10
13
 
11
14
  # 本地网络 API 工具(local-api-tool)
@@ -37,25 +40,50 @@ compatibility:
37
40
 
38
41
  ### 流程 A:拉取 Swagger API 文档(fetch)
39
42
 
43
+ > **优先使用内置脚本 `scripts/extract_swagger.py`**,一步完成"下载 → 解析 → 过滤 → 关联 DTO → 输出",避免 curl 管道截断、临时脚本花括号平衡等弯路。脚本与服务端 `parseApiDocs` 一比一对齐(递归深度 5、两层 definitions、传递闭包裁剪)。
44
+ > 仅当脚本不可用(无 python3 / 文件权限缺失)时才回退到「手工 curl + 服务端 parse_swagger_json」兜底流程。
45
+
46
+ #### A1. 内置脚本路径(首选)
47
+
40
48
  ```
41
49
  用户请求 fetch Swagger URL (10.x.x.x)
42
50
 
43
51
 
44
- 调用 api_tool(action="fetch", url=...)
52
+ AI 识别 10.x.x.x 网段(或服务端返回 LOCAL_NETWORK_UNREACHABLE)
53
+
54
+
55
+ 通过 run_command 在本地执行:
56
+ bash $SKILL_DIR/scripts/extract_swagger.py \
57
+ --url <apiDocsUrl> [--cookie '...'] [--filter <关键词>] [--out /tmp/parsed.json]
45
58
 
46
59
 
47
- 服务端返回 { reason: "LOCAL_NETWORK_UNREACHABLE", action: "请使用 local-api-tool ...", swaggerEndpoints: {...} }
60
+ 脚本内部:curl 下载 解析 过滤 paths 自动展开被引用 DTO → 输出 JSON
61
+
62
+
63
+ AI 读取脚本输出(或 --out 指定的文件),结构与 api_tool(parse_swagger_json) 完全一致:
64
+ { info, basePath, tags, apis, definitions, definitionsDetail, _filter? }
65
+ ```
66
+
67
+ **$SKILL_DIR 是本 skill 的绝对目录**。在 qwen/qoder 中 skill 加载时通常通过环境变量或上下文提供,若不确定可用:
68
+ - qwen-extension: `~/.qwen/extensions/szcd-component-helper/skills/local-api-tool/scripts/extract_swagger.py`
69
+ - opencode-extension: `~/.config/opencode/extensions/szcd-component-helper/skills/local-api-tool/scripts/extract_swagger.py`
70
+
71
+ #### A2. 服务端兜底(脚本不可用时)
72
+
73
+ ```
74
+ 调用 api_tool(action="fetch", url=...)
48
75
 
49
76
 
50
- AI 识别 LOCAL_NETWORK_UNREACHABLE,读取 swaggerEndpoints authInfo
77
+ 服务端返回 { reason: "LOCAL_NETWORK_UNREACHABLE", swaggerEndpoints, authInfo }
51
78
 
52
79
 
53
- AI 通过 run_command 在本地执行 curl
80
+ AI 通过 run_command 在本地 curl 写到临时文件(禁止管道截断!):
54
81
  步骤1: 鉴权获取 Cookie(如 authInfo.hasPassword 为 true)
55
- 步骤2: 获取完整 API 文档 JSON
82
+ 步骤2: curl -o /tmp/swagger.json <apiDocsUrl> ← 必须写文件
56
83
 
57
84
 
58
- AI 调用 api_tool(action="parse_swagger_json", swaggerJson=..., swaggerSourceUrl=...)
85
+ AI 读取整个文件传给服务端:
86
+ api_tool(action="parse_swagger_json", swaggerJson=<完整 JSON 字符串>, apiFilter=<可选>)
59
87
 
60
88
 
61
89
  返回解析后的 API 文档给用户
@@ -84,7 +112,97 @@ AI 通过 run_command 在本地执行对应 curl 命令
84
112
 
85
113
  ## 详细执行步骤
86
114
 
87
- ### 一、拉取 Swagger 文档(action=fetch
115
+ ### 一、拉取 Swagger 文档(推荐路径:extract_swagger.py
116
+
117
+ **前提**:本地有 python3(≥ 3.7)和 curl。脚本只依赖标准库,无第三方依赖。
118
+
119
+ #### 1.1 直接通过 URL 下载并解析(推荐)
120
+
121
+ ```bash
122
+ # 无鉴权
123
+ python3 ~/.qwen/extensions/szcd-component-helper/skills/local-api-tool/scripts/extract_swagger.py \
124
+ --url 'http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs' \
125
+ --out /tmp/parsed.json
126
+
127
+ # 有鉴权(先单独 curl 拿 Cookie,参考下方 1.3)
128
+ python3 .../extract_swagger.py \
129
+ --url 'http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs' \
130
+ --cookie 'JSESSIONID=xxx' \
131
+ --out /tmp/parsed.json
132
+
133
+ # 按关键词过滤 + 自动裁剪 definitionsDetail(仅保留被引用的 VO 及传递闭包)
134
+ python3 .../extract_swagger.py \
135
+ --url '<apiDocsUrl>' \
136
+ --filter tag --filter user \
137
+ --out /tmp/parsed.json
138
+ ```
139
+
140
+ #### 1.2 读取已下载的本地 JSON 文件
141
+
142
+ ```bash
143
+ # 第一步:curl 写文件(务必用 -o 写文件,不要用管道,避免大 JSON 被截断)
144
+ curl -sS --connect-timeout 10 --max-time 30 \
145
+ -H 'Accept: application/json' \
146
+ -H 'Cookie: <Cookie>' \
147
+ -o /tmp/swagger-raw.json \
148
+ 'http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs'
149
+
150
+ # 第二步:交给脚本解析
151
+ python3 .../extract_swagger.py --file /tmp/swagger-raw.json --filter tag --out /tmp/parsed.json
152
+ ```
153
+
154
+ #### 1.3 鉴权(如需 Cookie)
155
+
156
+ ```bash
157
+ # 取 Cookie(注意 -D - 把响应头打到 stdout)
158
+ curl -sS -D - -X GET 'http://10.2.7.60:30194/ikmp-knowledge/swagger-resources/configuration/ui' \
159
+ -H 'Authorization: Basic <base64(username:password)>' \
160
+ -H 'Accept: application/json' \
161
+ -o /dev/null \
162
+ | grep -i '^set-cookie:' | head -1 | sed 's/.*: //; s/;.*//'
163
+ ```
164
+
165
+ 将得到的 `JSESSIONID=xxx` 传给 `--cookie` 参数。
166
+
167
+ #### 1.4 单独查询某个 VO/DTO 完整定义(兜底)
168
+
169
+ 如果脚本已经解析过、但 AI 后续发现某个 `definitionsDetail` 之外的 VO 也需要展开,用:
170
+
171
+ ```bash
172
+ python3 .../extract_swagger.py --file /tmp/swagger-raw.json --definition KnowledgeOperLogVO
173
+ ```
174
+
175
+ 找不到时会自动给出模糊匹配建议(最多 10 个)。
176
+
177
+ #### 1.5 输出结构(与服务端 `parse_swagger_json` 一致)
178
+
179
+ ```json
180
+ {
181
+ "info": { "title": "...", "version": "...", "description": "..." },
182
+ "basePath": "/svc",
183
+ "tags": [{ "name": "tag", "description": "..." }],
184
+ "apis": [
185
+ {
186
+ "url": "/tag/pageList",
187
+ "method": "POST",
188
+ "tag": "tag",
189
+ "summary": "标签分页查询",
190
+ "operationId": "tagPageList",
191
+ "parameters": [
192
+ { "name": "body", "in": "body", "required": true,
193
+ "dtoRef": "TagQueryDTO",
194
+ "dtoProperties": [ /* 递归展开,required 字段已标注 */ ] }
195
+ ],
196
+ "response": { "voName": "ResponseTagPage", "properties": [ /* 递归展开 */ ] }
197
+ }
198
+ ],
199
+ "definitions": [ /* 摘要:所有 VO/DTO 的 name + type + description */ ],
200
+ "definitionsDetail": [ /* 详情:被 apis 引用的 VO/DTO 及传递闭包,深度5 */ ],
201
+ "_filter": { "keywords": ["tag"], "total": 50, "matched": 12 } // 仅 --filter 生效时出现
202
+ }
203
+ ```
204
+
205
+ ### 二、服务端兜底路径(脚本不可用时)
88
206
 
89
207
  **步骤 1:调用服务端 api_tool**
90
208
 
@@ -111,41 +229,38 @@ api_tool(action="fetch", url="http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/i
111
229
  }
112
230
  ```
113
231
 
114
- **步骤 3:AI 通过 run_command 在本地执行 curl**
115
-
116
- 根据返回数据中的 `authInfo` 和 `swaggerEndpoints`,AI 自行构造 curl 命令并通过 `run_command` 在本地执行:
117
-
118
- 3a. **鉴权(如 authInfo.hasPassword 为 true)**:
232
+ **步骤 3:本地 curl 写入临时文件(禁止管道截断!)**
119
233
 
120
234
  ```bash
121
- curl -s -D - -X GET 'http://10.2.7.60:30194/ikmp-knowledge/swagger-resources/configuration/ui' \
122
- -H 'Authorization: Basic <base64(username:password)>' \
123
- -H 'Accept: application/json'
124
- ```
235
+ # 鉴权(如需要)
236
+ curl -sS -D - -X GET '<authUrl>' \
237
+ -H 'Authorization: Basic <base64(user:pass)>' \
238
+ -o /dev/null | grep -i '^set-cookie:' | head -1 | sed 's/.*: //; s/;.*//'
125
239
 
126
- 从输出的 `Set-Cookie:` 行提取 Cookie 值(分号前的部分)。
127
-
128
- 3b. **获取 API 文档 JSON**:直接使用 `swaggerEndpoints.apiDocsUrl`:
129
-
130
- ```bash
131
- curl -s -X GET 'http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs' \
240
+ # 下载 API 文档(务必 -o 写文件,不要 | head / | python)
241
+ curl -sS --connect-timeout 10 --max-time 30 \
132
242
  -H 'Accept: application/json' \
133
- -H 'Cookie: <步骤3a获取的Cookie>'
243
+ -H 'Cookie: <步骤上一步的 Cookie>' \
244
+ -o /tmp/swagger-raw.json \
245
+ '<apiDocsUrl>'
134
246
  ```
135
247
 
136
- 如果 authInfo.hasPassword 为 false,省略 Cookie 头。
137
-
138
248
  **步骤 4:调用服务端解析**
139
249
 
140
250
  ```
141
- api_tool(action="parse_swagger_json", swaggerJson="<步骤3b获取的JSON字符串>", swaggerSourceUrl="http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/index.html")
251
+ api_tool(
252
+ action="parse_swagger_json",
253
+ swaggerJson=<读取 /tmp/swagger-raw.json 的完整内容>,
254
+ swaggerSourceUrl="http://10.2.7.60:30194/ikmp-knowledge/swagger-ui/index.html",
255
+ apiFilter=["tag"] // 可选,强烈推荐:按关键词过滤 + 自动裁剪 definitionsDetail
256
+ )
142
257
  ```
143
258
 
144
259
  **步骤 5:返回解析结果**
145
260
 
146
- 将 `parse_swagger_json` 返回的解析结果直接返回给用户,格式与正常 fetch 完全一致。
261
+ 将返回结果直接呈现给用户。
147
262
 
148
- ### 二、联调测试(action=test)
263
+ ### 三、联调测试(action=test)
149
264
 
150
265
  **步骤 1:调用服务端 api_tool**
151
266
 
@@ -196,13 +311,24 @@ curl -s -X POST 'http://10.2.7.60:30194/ikmp-knowledge/api/xxx' \
196
311
 
197
312
  将 curl 的响应内容返回给用户。如果是 JSON 格式,尽量格式化输出。
198
313
 
314
+ ## ⚠️ 避坑指南(来自历史反馈)
315
+
316
+ 1. **禁止用 `curl | head/tail/jq/python` 对完整 Swagger JSON 做管道切片** —— curl 管道经常因下游 SIGPIPE 截断,导致 JSON 不闭合 / Python 解析失败 / 临时写花括号平衡脚本,是历史踩坑率最高的反模式。**必须用 `-o /tmp/xxx.json` 写文件**,再用脚本或 `parse_swagger_json` 处理。
317
+ 2. **不要在本地手写 Python 切 Swagger** —— `extract_swagger.py` 已经做了和服务端 `parseApiDocs` 完全对齐的递归展开、传递闭包、required 标注、过滤裁剪。本地手写一个 mini 版只会丢字段或踩 `$ref`/`originalRef` 双字段陷阱。
318
+ 3. **过滤优先用 `--filter`(脚本)或 `apiFilter`(服务端)** —— 不要先拿全量再让 AI 自己挑接口看,过滤后 `definitionsDetail` 会自动按传递闭包裁剪,输出量减少 70%+。
319
+ 4. **单 VO 查询用 `--definition`(脚本)或 `get_definition`(服务端)** —— 不要回头再拉一次全量。
320
+
199
321
  ## 快捷方式:AI 直接识别 10 段地址
200
322
 
201
- 如果 AI 能识别出用户请求的 URL 中包含 `10.x.x.x` 主机地址,可以**直接跳过首次 api_tool 调用**,在本地执行 curl,再调用 `parse_swagger_json`,减少一次无用的服务端请求:
323
+ 如果 AI 能识别出用户请求的 URL 中包含 `10.x.x.x` 主机地址,可以**直接跳过首次 api_tool 调用**,在本地执行脚本,减少一次无用的服务端请求:
202
324
 
203
- 1. 从 Swagger URL 推导鉴权地址、文档地址
204
- 2. 通过 `run_command` 在本地依次 curl
205
- 3. 调用 `api_tool(action="parse_swagger_json")` 解析
325
+ ```bash
326
+ python3 ~/.qwen/extensions/szcd-component-helper/skills/local-api-tool/scripts/extract_swagger.py \
327
+ --url 'http://10.x.x.x/<service>/v2/api-docs' \
328
+ [--cookie '<JSESSIONID=...>'] \
329
+ [--filter '<关键词>'] \
330
+ --out /tmp/parsed.json
331
+ ```
206
332
 
207
333
  ### URL 推导规则
208
334
 
@@ -214,33 +340,11 @@ curl -s -X POST 'http://10.2.7.60:30194/ikmp-knowledge/api/xxx' \
214
340
  | authUrl | `basePath + /swagger-resources/configuration/ui` | `http://10.2.7.60:30194/ikmp-knowledge/swagger-resources/configuration/ui` |
215
341
  | apiDocsUrl | `basePath + /v2/api-docs` | `http://10.2.7.60:30194/ikmp-knowledge/v2/api-docs` |
216
342
 
217
- ### curl 执行模板
218
-
219
- **鉴权(带密码时):**
220
- ```bash
221
- curl -s -D - -X GET '<authUrl>' \
222
- -H 'Authorization: Basic <base64(username:password)>' \
223
- -H 'Accept: application/json'
224
- ```
225
-
226
- **获取文档(带 Cookie 时):**
227
- ```bash
228
- curl -s -X GET '<apiDocsUrl>' \
229
- -H 'Accept: application/json' \
230
- -H 'Cookie: <鉴权获取的Cookie>'
231
- ```
232
-
233
- **获取文档(无密码时):**
234
- ```bash
235
- curl -s -X GET '<apiDocsUrl>' \
236
- -H 'Accept: application/json'
237
- ```
238
-
239
343
  ## 注意事项
240
344
 
241
- 1. **Cookie 传递**:鉴权步骤获取的 Cookie 需要在后续步骤中通过 `-H 'Cookie: xxx'` 传递
345
+ 1. **Cookie 传递**:鉴权步骤获取的 Cookie 通过 `--cookie` 传给脚本,或通过 `-H 'Cookie: xxx'` 传给 curl
242
346
  2. **无密码场景**:如果用户未提供密码,跳过鉴权步骤,直接获取文档
243
- 3. **JSON 格式**:curl 获取的 API 文档必须是有效的 Swagger JSON,否则 `parse_swagger_json` 会报错
244
- 4. **超时处理**:本地 curl 可能因网络原因超时,建议添加 `--connect-timeout 10 --max-time 30` 参数
245
- 5. **错误处理**:如果某步骤 curl 失败,将错误信息返回给用户,不要继续后续步骤
246
- 6. **自动执行**:AI 识别 `reason: "LOCAL_NETWORK_UNREACHABLE"` 后应**自动**通过 `run_command` 执行 curl,无需让用户手动复制粘贴命令
347
+ 3. **JSON 格式**:脚本/curl 获取的 API 文档必须是有效的 Swagger JSON,否则 `extract_swagger.py` / `parse_swagger_json` 会报错(脚本会打印预览前 200 字符方便排查)
348
+ 4. **超时处理**:脚本和 curl 都已内置 `--connect-timeout 10 --max-time 30`,可通过 `--timeout` 调整
349
+ 5. **错误处理**:脚本退出码 1=参数错误 / 2=网络IO / 3=解析;如果某步骤失败,将错误信息返回给用户,不要继续后续步骤
350
+ 6. **自动执行**:AI 识别 10.x.x.x 或 `reason: "LOCAL_NETWORK_UNREACHABLE"` 后应**自动**通过 `run_command` 执行脚本,无需让用户手动复制粘贴命令