@szc-ft/mcp-szcd-client 0.34.2 → 0.38.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 (117) hide show
  1. package/agents/build.js +59 -1
  2. package/agents/platforms.json +7 -0
  3. package/agents/szcd-component-expert.qoder.md +2 -0
  4. package/opencode-extension/opencode.json +9 -0
  5. package/opencode-extension/skills/local-browser-test/SKILL.md +91 -0
  6. package/opencode-extension/skills/local-browser-test/config.example.yaml +51 -0
  7. package/opencode-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
  8. package/opencode-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
  9. package/opencode-extension/skills/local-browser-test/lib/config-loader.js +104 -0
  10. package/opencode-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
  11. package/opencode-extension/skills/local-browser-test/lib/global-setup.js +72 -0
  12. package/opencode-extension/skills/local-browser-test/lib/har-collector.js +124 -0
  13. package/opencode-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
  14. package/opencode-extension/skills/local-browser-test/lib/projects.js +46 -0
  15. package/opencode-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
  16. package/opencode-extension/skills/local-browser-test/lib/redact.js +94 -0
  17. package/opencode-extension/skills/local-browser-test/lib/report-retain.js +84 -0
  18. package/opencode-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
  19. package/opencode-extension/skills/local-browser-test/lib/storage-state.js +111 -0
  20. package/opencode-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
  21. package/opencode-extension/skills/local-browser-test/lib/test-plan.js +2 -0
  22. package/opencode-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
  23. package/opencode-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
  24. package/opencode-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
  25. package/opencode-extension/skills/local-browser-test/local-browser-executor.js +226 -2
  26. package/opencode-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
  27. package/opencode-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
  28. package/opencode-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
  29. package/opencode-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
  30. package/opencode-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
  31. package/opencode-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
  32. package/opencode-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
  33. package/opencode-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
  34. package/opencode-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
  35. package/opencode-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
  36. package/opencode-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
  37. package/opencode-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
  38. package/opencode-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
  39. package/opencode-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
  40. package/opencode-extension/skills/szcd-upload-zip/SKILL.md +45 -0
  41. package/package.json +70 -70
  42. package/qwen-extension/qwen-extension.json +10 -1
  43. package/qwen-extension/skills/local-browser-test/SKILL.md +91 -0
  44. package/qwen-extension/skills/local-browser-test/config.example.yaml +51 -0
  45. package/qwen-extension/skills/local-browser-test/lib/api-call-executor.js +251 -0
  46. package/qwen-extension/skills/local-browser-test/lib/browser-engine.js +95 -21
  47. package/qwen-extension/skills/local-browser-test/lib/config-loader.js +104 -0
  48. package/qwen-extension/skills/local-browser-test/lib/console-archiver.js +100 -0
  49. package/qwen-extension/skills/local-browser-test/lib/global-setup.js +72 -0
  50. package/qwen-extension/skills/local-browser-test/lib/har-collector.js +124 -0
  51. package/qwen-extension/skills/local-browser-test/lib/html-reporter.js +248 -0
  52. package/qwen-extension/skills/local-browser-test/lib/projects.js +46 -0
  53. package/qwen-extension/skills/local-browser-test/lib/raw-script-helpers.js +93 -0
  54. package/qwen-extension/skills/local-browser-test/lib/redact.js +94 -0
  55. package/qwen-extension/skills/local-browser-test/lib/report-retain.js +84 -0
  56. package/qwen-extension/skills/local-browser-test/lib/retry-policy.js +160 -0
  57. package/qwen-extension/skills/local-browser-test/lib/storage-state.js +111 -0
  58. package/qwen-extension/skills/local-browser-test/lib/tag-runner.js +103 -0
  59. package/qwen-extension/skills/local-browser-test/lib/test-plan.js +2 -0
  60. package/qwen-extension/skills/local-browser-test/lib/trace-collector.js +94 -0
  61. package/qwen-extension/skills/local-browser-test/lib/trend-aggregator.js +97 -0
  62. package/qwen-extension/skills/local-browser-test/lib/workspace-init.js +178 -0
  63. package/qwen-extension/skills/local-browser-test/local-browser-executor.js +226 -2
  64. package/qwen-extension/skills/local-browser-test/tests/integration/pr-01.test.js +254 -0
  65. package/qwen-extension/skills/local-browser-test/tests/integration/pr-02.test.js +263 -0
  66. package/qwen-extension/skills/local-browser-test/tests/integration/pr-03.test.js +308 -0
  67. package/qwen-extension/skills/local-browser-test/tests/integration/pr-04.test.js +168 -0
  68. package/qwen-extension/skills/local-browser-test/tests/integration/pr-05.test.js +183 -0
  69. package/qwen-extension/skills/local-browser-test/tests/integration/pr-06.test.js +252 -0
  70. package/qwen-extension/skills/local-browser-test/tests/integration/pr-07.test.js +168 -0
  71. package/qwen-extension/skills/local-browser-test/tests/integration/pr-08.test.js +293 -0
  72. package/qwen-extension/skills/shadcn-design-to-code/SKILL.md +352 -0
  73. package/qwen-extension/skills/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
  74. package/qwen-extension/skills/shadcn-design-to-code/references/install-pitfalls.md +526 -0
  75. package/qwen-extension/skills/shadcn-design-to-code/references/layouts.md +551 -0
  76. package/qwen-extension/skills/shadcn-design-to-code/references/sketch-mapping.md +247 -0
  77. package/qwen-extension/skills/shadcn-design-to-code/references/tokens.md +534 -0
  78. package/qwen-extension/skills/szcd-upload-zip/SKILL.md +45 -0
  79. package/scripts/lib/common.js +8 -1
  80. package/standard-skill/local-browser-test/SKILL.md +91 -0
  81. package/standard-skill/local-browser-test/config.example.yaml +51 -0
  82. package/standard-skill/local-browser-test/lib/api-call-executor.js +251 -0
  83. package/standard-skill/local-browser-test/lib/browser-engine.js +95 -21
  84. package/standard-skill/local-browser-test/lib/config-loader.js +104 -0
  85. package/standard-skill/local-browser-test/lib/console-archiver.js +100 -0
  86. package/standard-skill/local-browser-test/lib/global-setup.js +72 -0
  87. package/standard-skill/local-browser-test/lib/har-collector.js +124 -0
  88. package/standard-skill/local-browser-test/lib/html-reporter.js +248 -0
  89. package/standard-skill/local-browser-test/lib/projects.js +46 -0
  90. package/standard-skill/local-browser-test/lib/raw-script-helpers.js +93 -0
  91. package/standard-skill/local-browser-test/lib/redact.js +94 -0
  92. package/standard-skill/local-browser-test/lib/report-retain.js +84 -0
  93. package/standard-skill/local-browser-test/lib/retry-policy.js +160 -0
  94. package/standard-skill/local-browser-test/lib/storage-state.js +111 -0
  95. package/standard-skill/local-browser-test/lib/tag-runner.js +103 -0
  96. package/standard-skill/local-browser-test/lib/test-plan.js +2 -0
  97. package/standard-skill/local-browser-test/lib/trace-collector.js +94 -0
  98. package/standard-skill/local-browser-test/lib/trend-aggregator.js +97 -0
  99. package/standard-skill/local-browser-test/lib/workspace-init.js +178 -0
  100. package/standard-skill/local-browser-test/local-browser-executor.js +226 -2
  101. package/standard-skill/local-browser-test/tests/integration/pr-01.test.js +254 -0
  102. package/standard-skill/local-browser-test/tests/integration/pr-02.test.js +263 -0
  103. package/standard-skill/local-browser-test/tests/integration/pr-03.test.js +308 -0
  104. package/standard-skill/local-browser-test/tests/integration/pr-04.test.js +168 -0
  105. package/standard-skill/local-browser-test/tests/integration/pr-05.test.js +183 -0
  106. package/standard-skill/local-browser-test/tests/integration/pr-06.test.js +252 -0
  107. package/standard-skill/local-browser-test/tests/integration/pr-07.test.js +168 -0
  108. package/standard-skill/local-browser-test/tests/integration/pr-08.test.js +293 -0
  109. package/standard-skill/shadcn-design-to-code/SKILL.md +352 -0
  110. package/standard-skill/shadcn-design-to-code/examples/1.1-overview-page.tsx +382 -0
  111. package/standard-skill/shadcn-design-to-code/references/install-pitfalls.md +526 -0
  112. package/standard-skill/shadcn-design-to-code/references/layouts.md +551 -0
  113. package/standard-skill/shadcn-design-to-code/references/sketch-mapping.md +247 -0
  114. package/standard-skill/shadcn-design-to-code/references/tokens.md +534 -0
  115. package/standard-skill/szcd-upload-zip/SKILL.md +45 -0
  116. package/opencode-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
  117. package/qwen-extension/skills/local-browser-test/local-browser-executor.cjs +0 -742
@@ -0,0 +1,534 @@
1
+ # shadcn 设计 Token 提取与落地
2
+
3
+ > 从 .sketch 节点提取设计 token(颜色/字号/间距/圆角/阴影),落地到 tailwind.config.ts + globals.css
4
+
5
+ ## 核心原则
6
+
7
+ **3 次出现法则**:
8
+ - 出现 ≥ 3 次的颜色/字号/间距/圆角 → 抽到 `tailwind.config.ts` 主题
9
+ - 出现 1-2 次的 → 用 Tailwind arbitrary value 写 className(`bg-[#f2f8fd]`)
10
+ - 一次性色值(如品牌 logo 特殊色)→ 内联 style 或 CSS variable
11
+
12
+ **HSL 通道优先**:
13
+ - shadcn CSS variables 用 HSL 不带单位(如 `--primary: 222.2 47.4% 11.2%`)
14
+ - 任意值用 HEX(如 `--primary: #0f172a`)也能工作,但失去 `bg-primary/50` 这种透明度调节
15
+
16
+ ---
17
+
18
+ ## 1. 颜色 token 提取
19
+
20
+ ### 1.1 从 Sketch 提取
21
+
22
+ 每个 `rectangle` / `group` / `text` 节点都有 `style.fills[i].color.hex` 字段。统计所有 hex 出现频次:
23
+
24
+ ```python
25
+ # 伪代码:从 .sketch 解析结果统计
26
+ from collections import Counter
27
+ fills = Counter()
28
+ for node in all_nodes:
29
+ for fill in node.get('style', {}).get('fills', []):
30
+ if fill.get('color', {}).get('hex'):
31
+ fills[fill['color']['hex']] += 1
32
+
33
+ # 输出 top 20
34
+ for hex, count in fills.most_common(20):
35
+ print(f" {hex}: {count}次")
36
+ ```
37
+
38
+ **示例输出**:
39
+ ```
40
+ #ffffff: 47次 ← 背景主色
41
+ #0f172a: 32次 ← 文字主色
42
+ #64748b: 28次 ← 文字次色 (text-muted-foreground)
43
+ #f1f5f9: 18次 ← 弱背景 (bg-muted)
44
+ #e2e8f0: 15次 ← 边框 (border)
45
+ #3b82f6: 12次 ← 品牌主色
46
+ #f8fafc: 9次 ← 卡片背景
47
+ ```
48
+
49
+ ### 1.2 落地策略
50
+
51
+ **shadcn 默认色板 vs Sketch 提取色**:
52
+
53
+ | shadcn 默认 | 默认 HSL | 对应 Sketch | 落地 |
54
+ |---|---|---|---|
55
+ | `--background` | 0 0% 100% | `#ffffff` | 直接用,不改 |
56
+ | `--foreground` | 222.2 47.4% 11.2% | `#0f172a` | 直接用,不改 |
57
+ | `--primary` | 222.2 47.4% 11.2% | `#3b82f6`(品牌蓝) | 改 HSL |
58
+ | `--primary-foreground` | 210 40% 98% | `#ffffff` | 直接用 |
59
+ | `--muted` | 210 40% 96.1% | `#f1f5f9` | 直接用 |
60
+ | `--muted-foreground` | 215.4 16.3% 46.9% | `#64748b` | 直接用 |
61
+ | `--border` | 214.3 31.8% 91.4% | `#e2e8f0` | 直接用 |
62
+
63
+ **如果 Sketch 颜色和 shadcn 默认色板接近**:直接用,不改(保留生态兼容性)。
64
+
65
+ **如果 Sketch 用了品牌自定义色**(如 `#3b82f6` 而不是 `#0f172a`):改 `globals.css` 的 HSL 值。
66
+
67
+ ### 1.3 落地代码
68
+
69
+ #### 1.3.1 globals.css(CSS variables)
70
+
71
+ ```css
72
+ @layer base {
73
+ :root {
74
+ --background: 0 0% 100%; /* #ffffff */
75
+ --foreground: 222.2 47.4% 11.2%; /* #0f172a */
76
+ --card: 0 0% 100%;
77
+ --card-foreground: 222.2 47.4% 11.2%;
78
+ --primary: 217 91% 60%; /* #3b82f6 → HSL 转换 */
79
+ --primary-foreground: 0 0% 100%;
80
+ --secondary: 210 40% 96.1%;
81
+ --secondary-foreground: 222.2 47.4% 11.2%;
82
+ --muted: 210 40% 96.1%; /* #f1f5f9 */
83
+ --muted-foreground: 215 16% 47%; /* #64748b */
84
+ --accent: 210 40% 96.1%;
85
+ --accent-foreground: 222.2 47.4% 11.2%;
86
+ --destructive: 0 84% 60%;
87
+ --destructive-foreground: 210 40% 98%;
88
+ --border: 214 32% 91%; /* #e2e8f0 */
89
+ --input: 214 32% 91%;
90
+ --ring: 217 91% 60%; /* 焦点环 = primary */
91
+ --radius: 0.5rem;
92
+ }
93
+
94
+ .dark {
95
+ --background: 222 47% 11%;
96
+ --foreground: 210 40% 98%;
97
+ /* ... 暗色版 */
98
+ }
99
+ }
100
+ ```
101
+
102
+ **HEX → HSL 转换工具**:
103
+ ```bash
104
+ # Node.js
105
+ node -e "const c=require('color');console.log(c('#3b82f6').hsl().string())"
106
+ # 输出: hsl(217, 91%, 60%)
107
+
108
+ # 提取 HSL 通道值(用于 CSS variable)
109
+ node -e "const c=require('color')('#3b82f6');const hsl=c.hsl().object();console.log(`${hsl.h} ${hsl.s.toFixed(1)}% ${hsl.l.toFixed(1)}%`)"
110
+ # 输出: 217 91% 60%
111
+ ```
112
+
113
+ #### 1.3.2 tailwind.config.ts(业务色扩展)
114
+
115
+ ```ts
116
+ import type { Config } from "tailwindcss"
117
+
118
+ const config: Config = {
119
+ darkMode: ["class"],
120
+ content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
121
+ theme: {
122
+ extend: {
123
+ colors: {
124
+ // shadcn HSL 通道(globals.css 改这里会同步生效)
125
+ border: "hsl(var(--border))",
126
+ input: "hsl(var(--input))",
127
+ ring: "hsl(var(--ring))",
128
+ background: "hsl(var(--background))",
129
+ foreground: "hsl(var(--foreground))",
130
+ primary: {
131
+ DEFAULT: "hsl(var(--primary))",
132
+ foreground: "hsl(var(--primary-foreground))",
133
+ },
134
+ // ... 其余 shadcn 默认色
135
+
136
+ // 业务扩展色(出现 ≥ 3 次的 token)
137
+ brand: {
138
+ 50: "#eff6ff",
139
+ 100: "#dbeafe",
140
+ 500: "#3b82f6", // ← 品牌主色
141
+ 600: "#2563eb",
142
+ 900: "#1e3a8a",
143
+ },
144
+ success: "#10b981",
145
+ warning: "#f59e0b",
146
+ danger: "#ef4444",
147
+ // Sketch 中出现 ≥ 3 次的色块
148
+ segment: {
149
+ blue: "#f2f8fd",
150
+ green: "#f5ffed",
151
+ yellow: "#fffbe6",
152
+ gray: "#f5f6f7",
153
+ },
154
+ },
155
+ borderRadius: {
156
+ lg: "var(--radius)",
157
+ md: "calc(var(--radius) - 2px)",
158
+ sm: "calc(var(--radius) - 4px)",
159
+ },
160
+ },
161
+ },
162
+ plugins: [require("tailwindcss-animate")],
163
+ }
164
+ export default config
165
+ ```
166
+
167
+ #### 1.3.3 使用示例
168
+
169
+ ```tsx
170
+ // 用 shadcn 语义色
171
+ <Button className="bg-primary text-primary-foreground">主操作</Button>
172
+ <Badge className="bg-destructive/10 text-destructive">错误</Badge>
173
+
174
+ // 用业务色
175
+ <div className="bg-brand-500 text-white">品牌主色</div>
176
+ <div className="bg-segment-blue border border-blue-200">蓝色段码块</div>
177
+ <div className="bg-segment-green border border-green-200">绿色段码块</div>
178
+
179
+ // 用 arbitrary value(一次性色值)
180
+ <div className="bg-[#abcdef]">特殊色</div>
181
+ ```
182
+
183
+ ---
184
+
185
+ ## 2. 字号/字重 token 提取
186
+
187
+ ### 2.1 从 Sketch 提取
188
+
189
+ 每个 `text` 节点都有 `text.attributes[0].attributes.{fontSize, fontFamily, color, lineHeight}`。
190
+
191
+ 字重从 `fontFamily` 末尾推断:
192
+ - `PingFangSC-Regular` → 400
193
+ - `PingFangSC-Medium` → 500
194
+ - `PingFangSC-Semibold` → 600
195
+ - `PingFangSC-Bold` → 700
196
+
197
+ ```python
198
+ # 伪代码:统计字号频次
199
+ sizes = Counter()
200
+ for node in text_nodes:
201
+ for attr in node.get('text', {}).get('attributes', []):
202
+ size = attr.get('attributes', {}).get('fontSize')
203
+ if size:
204
+ sizes[size] += 1
205
+ ```
206
+
207
+ **典型输出**:
208
+ ```
209
+ 12: 28次 → text-xs (12px)
210
+ 14: 47次 → text-sm (14px)
211
+ 16: 32次 → text-base (16px)
212
+ 18: 15次 → text-lg (18px)
213
+ 20: 9次 → text-xl (20px)
214
+ 24: 6次 → text-2xl (24px)
215
+ 30: 3次 → text-3xl (30px)
216
+ ```
217
+
218
+ ### 2.2 落地代码
219
+
220
+ shadcn 默认 Tailwind 配置已含 text-xs 到 text-9xl。如果 Sketch 用了非标准字号(13px / 15px / 17px),在 `tailwind.config.ts` 扩展:
221
+
222
+ ```ts
223
+ theme: {
224
+ extend: {
225
+ fontSize: {
226
+ // 非标字号
227
+ '2xs': ['11px', '16px'],
228
+ '13': ['13px', '20px'],
229
+ '15': ['15px', '22px'],
230
+ '17': ['17px', '26px'],
231
+ },
232
+ },
233
+ }
234
+ ```
235
+
236
+ **字重对应**:
237
+
238
+ | Sketch fontFamily | font-weight | Tailwind class |
239
+ |---|---|---|
240
+ | `-Regular` / `-Book` | 400 | `font-normal` |
241
+ | `-Medium` | 500 | `font-medium` |
242
+ | `-Semibold` / `-Demibold` | 600 | `font-semibold` |
243
+ | `-Bold` / `-Heavy` | 700 | `font-bold` |
244
+
245
+ ---
246
+
247
+ ## 3. 间距 token 提取
248
+
249
+ ### 3.1 从 Sketch 提取
250
+
251
+ ```python
252
+ # 伪代码:统计元素 x/y 差值(即间距)
253
+ spacings = Counter()
254
+ for node in all_nodes:
255
+ for sibling in siblings(node):
256
+ gap = abs(sibling.x - (node.x + node.width))
257
+ if 0 < gap < 200:
258
+ spacings[gap] += 1
259
+ ```
260
+
261
+ **典型输出**(Tailwind 标准 4px 栅格):
262
+ ```
263
+ 4: 67次 → p-1, m-1, gap-1
264
+ 8: 89次 → p-2, m-2, gap-2 (最常用)
265
+ 12: 23次 → p-3, m-3
266
+ 16: 54次 → p-4, m-4
267
+ 20: 18次 → p-5
268
+ 24: 41次 → p-6
269
+ 32: 15次 → p-8
270
+ 40: 7次 → p-10
271
+ 48: 4次 → p-12
272
+ ```
273
+
274
+ ### 3.2 落地代码
275
+
276
+ shadcn 默认 Tailwind 已含 4px 栅格的 spacing。**如果 Sketch 用了 8px 栅格外值**(如 6px / 10px / 14px),扩展:
277
+
278
+ ```ts
279
+ theme: {
280
+ extend: {
281
+ spacing: {
282
+ '1.5': '6px',
283
+ '2.5': '10px',
284
+ '3.5': '14px',
285
+ },
286
+ },
287
+ }
288
+ ```
289
+
290
+ **容器最大宽度**(常被忽略):
291
+
292
+ ```ts
293
+ theme: {
294
+ extend: {
295
+ maxWidth: {
296
+ '8xl': '88rem', // 1408px
297
+ '9xl': '96rem', // 1536px
298
+ 'screen-2xl': '1536px',
299
+ },
300
+ },
301
+ }
302
+ ```
303
+
304
+ ---
305
+
306
+ ## 4. 圆角 token 提取
307
+
308
+ ### 4.1 从 Sketch 提取
309
+
310
+ `shape.cornerRadius` 字段:
311
+
312
+ ```python
313
+ radii = Counter()
314
+ for node in rect_nodes:
315
+ r = node.get('shape', {}).get('cornerRadius')
316
+ if r:
317
+ radii[r] += 1
318
+ ```
319
+
320
+ **典型输出**:
321
+ ```
322
+ 0: 89次 → rounded-none
323
+ 4: 12次 → rounded-sm
324
+ 6: 23次 → rounded-md
325
+ 8: 31次 → rounded-lg
326
+ 12: 18次 → rounded-xl
327
+ 16: 7次 → rounded-2xl
328
+ 9999: 4次 → rounded-full
329
+ ```
330
+
331
+ ### 4.2 落地代码
332
+
333
+ shadcn 默认 CSS variable `--radius: 0.5rem`(即 8px)对应 `rounded-lg`。
334
+
335
+ **修改全局圆角**(一次性影响所有 shadcn 组件):
336
+
337
+ ```css
338
+ /* globals.css */
339
+ :root {
340
+ --radius: 0.75rem; /* 12px → rounded-lg 变 12px */
341
+ }
342
+ ```
343
+
344
+ **单独覆盖某个组件**:
345
+
346
+ ```tsx
347
+ <Button className="rounded-full">完全圆角按钮</Button>
348
+ <Card className="rounded-none">直角卡片</Card>
349
+ <Input className="rounded-md">中等圆角输入框</Input>
350
+ ```
351
+
352
+ ---
353
+
354
+ ## 5. 阴影 token 提取
355
+
356
+ ### 5.1 从 Sketch 提取
357
+
358
+ 每个节点的 `style.shadows[]` 数组:
359
+
360
+ ```python
361
+ shadows = Counter()
362
+ for node in all_nodes:
363
+ for sh in node.get('style', {}).get('shadows', []):
364
+ key = f"{sh.get('color',{}).get('hex','?')}|{sh.get('offsetX',0)},{sh.get('offsetY',0)},{sh.get('blurRadius',0)}"
365
+ shadows[key] += 1
366
+ ```
367
+
368
+ **典型输出**:
369
+ ```
370
+ #00000010|0,1,2: 18次 → shadow-sm
371
+ #00000014|0,4,6: 9次 → shadow (default)
372
+ #0000001f|0,10,15: 4次 → shadow-md
373
+ #00000026|0,20,25: 2次 → shadow-lg
374
+ ```
375
+
376
+ ### 5.2 落地代码
377
+
378
+ shadcn 默认含 `shadow-sm / shadow / shadow-md / shadow-lg / shadow-xl / shadow-2xl`。
379
+
380
+ **Sketch 中的非标阴影**(如 `0,0,20 #0079f740` 蓝色光晕):
381
+
382
+ ```ts
383
+ theme: {
384
+ extend: {
385
+ boxShadow: {
386
+ 'glow-blue': '0 0 20px rgba(0, 121, 247, 0.25)',
387
+ 'card-hover': '0 8px 30px rgba(0, 0, 0, 0.12)',
388
+ },
389
+ },
390
+ }
391
+ ```
392
+
393
+ ```tsx
394
+ <Card className="shadow-glow-blue">发光卡片</Card>
395
+ ```
396
+
397
+ ---
398
+
399
+ ## 6. 渐变 token 提取
400
+
401
+ ### 6.1 从 Sketch 提取
402
+
403
+ `style.fills[0].gradient.stops[]`:
404
+
405
+ ```python
406
+ gradients = Counter()
407
+ for node in all_nodes:
408
+ fill = node.get('style', {}).get('fills', [{}])[0]
409
+ grad = fill.get('gradient')
410
+ if grad:
411
+ key = '|'.join(f"{s['color']['hex']}@{s.get('position',0):.0%}" for s in grad.get('stops', []))
412
+ gradients[(grad.get('type'), key)] += 1
413
+ ```
414
+
415
+ **典型输出**:
416
+ ```
417
+ ('linear', #f3faff@0%|#f8fdff@100%): 5次 → 卡片背景渐变
418
+ ('linear', #0ea5e9@0%|#3b82f6@100%): 3次 → 按钮渐变
419
+ ```
420
+
421
+ ### 6.2 落地代码
422
+
423
+ ```tsx
424
+ {/* 单次使用:arbitrary value */}
425
+ <div className="bg-gradient-to-b from-[#f3faff] to-[#f8fdff]" />
426
+
427
+ {/* 多次使用:抽到 config */}
428
+ <button className="bg-gradient-primary text-white" />
429
+ ```
430
+
431
+ ```ts
432
+ // tailwind.config.ts
433
+ theme: {
434
+ extend: {
435
+ backgroundImage: {
436
+ 'gradient-primary': 'linear-gradient(135deg, #0ea5e9 0%, #3b82f6 100%)',
437
+ 'gradient-card': 'linear-gradient(180deg, #f3faff 0%, #f8fdff 100%)',
438
+ },
439
+ },
440
+ }
441
+ ```
442
+
443
+ ---
444
+
445
+ ## 7. 落地校验清单(写代码后必跑)
446
+
447
+ ```
448
+ [ ] 所有出现 ≥ 3 次的颜色都在 tailwind.config.ts colors / globals.css --* 中
449
+ [ ] 所有出现 ≥ 3 次的字号用 text-* 语义类(text-xs/sm/base/lg/xl/2xl)映射
450
+ [ ] 所有出现 ≥ 3 次的圆角用 rounded-* 语义类(rounded-sm/md/lg/xl/full)映射
451
+ [ ] 所有出现 ≥ 3 次的间距用 p-* / m-* / gap-* 映射
452
+ [ ] Sketch 特殊字号(如 13px)已在 fontSize 扩展中
453
+ [ ] Sketch 渐变已抽到 backgroundImage 或 CSS variable
454
+ [ ] Sketch 阴影已抽到 boxShadow 扩展
455
+ [ ] 暗色模式 .dark 段已写(如需要)
456
+ ```
457
+
458
+ **反例(违反 = 重做)**:
459
+ - ❌ 出现 8 次的 `#3b82f6` 还是 `bg-[#3b82f6]` 写 className(应抽到 brand.500)
460
+ - ❌ 出现 5 次的 13px 还是 `text-[13px]`(应抽到 fontSize.13)
461
+ - ❌ globals.css 的 `--primary` 还是默认蓝 `#0f172a`,但代码里到处 `bg-blue-500`
462
+
463
+ ---
464
+
465
+ ## 8. 工具函数:自动从 Sketch 提取
466
+
467
+ 把以下 Python 脚本保存到 `scripts/extract-design-tokens.py`:
468
+
469
+ ```python
470
+ #!/usr/bin/env python3
471
+ """从 sketch-mcp-server 输出的节点 JSON 提取设计 token"""
472
+ import json, sys
473
+ from collections import Counter
474
+
475
+ def extract_tokens(nodes):
476
+ colors = Counter()
477
+ sizes = Counter()
478
+ radii = Counter()
479
+ gradients = Counter()
480
+
481
+ for n in nodes:
482
+ style = n.get('style', {})
483
+ # 颜色
484
+ for f in style.get('fills', []):
485
+ hex = f.get('color', {}).get('hex')
486
+ if hex:
487
+ colors[hex] += 1
488
+ if f.get('gradient'):
489
+ stops = f['gradient'].get('stops', [])
490
+ key = '|'.join(f"{s['color']['hex']}@{s.get('position',0):.0%}" for s in stops)
491
+ gradients[key] += 1
492
+ # 字号
493
+ for attr in n.get('text', {}).get('attributes', []):
494
+ size = attr.get('attributes', {}).get('fontSize')
495
+ if size:
496
+ sizes[size] += 1
497
+ # 圆角
498
+ r = n.get('shape', {}).get('cornerRadius')
499
+ if r is not None:
500
+ radii[r] += 1
501
+
502
+ return {
503
+ 'colors': colors.most_common(20),
504
+ 'sizes': sizes.most_common(10),
505
+ 'radii': radii.most_common(10),
506
+ 'gradients': gradients.most_common(5),
507
+ }
508
+
509
+ if __name__ == '__main__':
510
+ nodes = json.load(sys.stdin)
511
+ tokens = extract_tokens(nodes)
512
+ print(json.dumps(tokens, indent=2, ensure_ascii=False))
513
+ ```
514
+
515
+ **用法**:
516
+ ```bash
517
+ # 把 getMultipleNodeInfo 的输出保存到 nodes.json
518
+ cat nodes.json | python3 extract-design-tokens.py > tokens.json
519
+ ```
520
+
521
+ 输出示例:
522
+ ```json
523
+ {
524
+ "colors": [
525
+ ["#ffffff", 47],
526
+ ["#0f172a", 32],
527
+ ["#3b82f6", 12],
528
+ ...
529
+ ],
530
+ "sizes": [["14", 47], ["16", 32], ...],
531
+ "radii": [["8", 31], ["6", 23], ...],
532
+ "gradients": []
533
+ }
534
+ ```
@@ -52,6 +52,50 @@ cat ~/.szcd-mcp-config.json
52
52
  - 若 `MCP_API_KEY` 为空字符串,则上传时不带 `X-API-Key` header
53
53
  - 若 `MCP_SERVER_URL` 未配置,默认使用 `http://localhost:3456`
54
54
 
55
+ ### 第 1.5 步:打包目录为 ZIP(跨平台注意事项)
56
+
57
+ > ⚠️ **关键:不要用 Windows PowerShell 的 `Compress-Archive`。**
58
+ > 它生成的 ZIP 用反斜杠 `\` 作路径分隔符,Linux 服务器 `unzip` 会拒绝解压并报错
59
+ > `appears to use backslashes as path sep`,导致子目录结构丢失。
60
+ > (服务端已增加反斜杠归一化兜底,但仍应优先用正确方式打包,避免依赖兜底。)
61
+
62
+ 按平台选择打包方式(均产出正斜杠路径的标准 ZIP):
63
+
64
+ ```bash
65
+ # macOS / Linux / Git-Bash / WSL —— 系统 zip
66
+ zip -r my-package.zip my-package/
67
+
68
+ # 任意平台(含 Windows,只要有 Node)—— 用 tar 产出 zip 不可行,改用下面的 Node 兜底
69
+ ```
70
+
71
+ **Windows 推荐**(按优先级):
72
+
73
+ 1. **Git Bash / WSL 里的 `zip`**(最简单,路径分隔符正确):
74
+ ```bash
75
+ zip -r my-package.zip my-package/
76
+ ```
77
+ 2. **若只有 PowerShell**,不要用 `Compress-Archive`;改用 Node.js 内置逻辑打包,强制正斜杠:
78
+ ```bash
79
+ node -e "
80
+ const fs=require('fs'),path=require('path'),zlib=require('zlib');
81
+ const root=process.argv[1], out=process.argv[2];
82
+ function crc32(b){let t=[];for(let n=0;n<256;n++){let c=n;for(let k=0;k<8;k++)c=c&1?0xEDB88320^(c>>>1):c>>>1;t[n]=c>>>0;}let crc=-1;for(let i=0;i<b.length;i++)crc=(crc>>>8)^t[(crc^b[i])&0xFF];return(crc^-1)>>>0;}
83
+ let locals=[],centrals=[],off=0;
84
+ (function walk(dir,pre){for(const e of fs.readdirSync(dir,{withFileTypes:true})){const rel=pre?pre+'/'+e.name:e.name;const abs=path.join(dir,e.name);
85
+ if(e.isDirectory())walk(abs,rel);else{
86
+ const data=fs.readFileSync(abs),name=Buffer.from(rel,'utf8'),comp=zlib.deflateRawSync(data),crc=crc32(data);
87
+ const lh=Buffer.alloc(30);lh.writeUInt32LE(0x04034b50,0);lh.writeUInt16LE(20,4);lh.writeUInt16LE(8,8);lh.writeUInt32LE(crc,14);lh.writeUInt32LE(comp.length,18);lh.writeUInt32LE(data.length,22);lh.writeUInt16LE(name.length,26);
88
+ locals.push(Buffer.concat([lh,name,comp]));
89
+ const ch=Buffer.alloc(46);ch.writeUInt32LE(0x02014b50,0);ch.writeUInt16LE(20,4);ch.writeUInt16LE(20,6);ch.writeUInt16LE(8,10);ch.writeUInt32LE(crc,16);ch.writeUInt32LE(comp.length,20);ch.writeUInt32LE(data.length,24);ch.writeUInt16LE(name.length,28);ch.writeUInt32LE(off,42);
90
+ centrals.push(Buffer.concat([ch,name]));off+=locals[locals.length-1].length;}}})(root,'');
91
+ const lb=Buffer.concat(locals),cb=Buffer.concat(centrals),eo=Buffer.alloc(22);
92
+ eo.writeUInt32LE(0x06054b50,0);eo.writeUInt16LE(centrals.length,8);eo.writeUInt16LE(centrals.length,10);eo.writeUInt32LE(cb.length,12);eo.writeUInt32LE(lb.length,16);
93
+ fs.writeFileSync(out,Buffer.concat([lb,cb,eo]));console.log('zip written:',out);
94
+ " my-package my-package.zip
95
+ ```
96
+
97
+ 打包后用 `unzip -l my-package.zip` 检查路径是否为正斜杠(若看到 `\` 则不可上传)。
98
+
55
99
  ### 第 2 步:上传 ZIP 文件
56
100
 
57
101
  ```bash
@@ -107,6 +151,7 @@ curl -s "$MCP_URL/api/download?path=/scity/zengzhijie/mcp/uploads/my-package/dat
107
151
  - 同名 zip 包名会覆盖服务器上已有的目录
108
152
  - 解压后文件持久存储,无自动过期
109
153
  - 上传路径始终为 `/upload-zip`(不含 `/api/` 前缀)
154
+ - 服务端解压前会把条目名中的反斜杠 `\` 归一化为正斜杠 `/`(兼容 Windows `Compress-Archive` 产物),但应优先按「第 1.5 步」用正确方式打包
110
155
 
111
156
  ## 完整命令示例(一步完成)
112
157
 
@@ -15,7 +15,7 @@ const __filename = fileURLToPath(import.meta.url);
15
15
  const __dirname = path.dirname(__filename);
16
16
 
17
17
  // 默认值(需在 SKILL_SOURCE 之前定义)
18
- export const DEFAULT_MCP_SERVER_URL = "http://localhost:3456";
18
+ export const DEFAULT_MCP_SERVER_URL = "https://mcp.szcd-mcp.top"; // 本地调试: http://localhost:3456
19
19
  export const DEFAULT_MCP_SERVER_NAME = "szcd-component-helper";
20
20
  export const DEFAULT_SKETCH_MCP_SERVER_URL = "http://localhost:3457";
21
21
 
@@ -28,6 +28,13 @@ export const ADDITIONAL_MCP_SERVERS = [
28
28
  args: [],
29
29
  description: "Sketch 设计文件解析(szcd 维护版),解析 .sketch 文件提取页面结构、图层信息和导出图片。postinstall 会尝试全局安装以加速启动",
30
30
  },
31
+ {
32
+ name: "shadcn",
33
+ type: "stdio",
34
+ command: "npx",
35
+ args: ["-y", "shadcn@latest", "mcp"],
36
+ description: "shadcn/ui 官方 stdio MCP(registry 搜索/查看/add 命令)。需项目根目录有 components.json,否则调 list_items_in_registries 报 'No registries are configured'。",
37
+ },
31
38
  ];
32
39
 
33
40
  export const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");