cardbuddy 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (313) hide show
  1. package/README.md +46 -5
  2. package/dist/cli.js +51 -1
  3. package/dist/config.js +51 -16
  4. package/dist/core.js +2 -2
  5. package/dist/init.js +425 -0
  6. package/dist/preview-editor/assets/index-CHuGGIR3.js +276 -0
  7. package/dist/preview-editor/assets/{index-B4VZgNSK.css → index-CQScMhyg.css} +1 -1
  8. package/dist/preview-editor/index.html +2 -2
  9. package/dist/standalone.js +8 -0
  10. package/dist/vendored/mcp/client.mjs +537 -0
  11. package/dist/vendored/mcp/tools.mjs +268 -0
  12. package/dist/vendored/preview/assets/default-image.png +0 -0
  13. package/dist/{server-core → vendored/server/core}/ai-chat.js +82 -38
  14. package/dist/{server-core → vendored/server/core}/browser.js +1 -1
  15. package/dist/{server-core → vendored/server/core}/cardSave.js +8 -3
  16. package/dist/{server-core → vendored/server/core}/cardSync.js +6 -14
  17. package/dist/{server-core → vendored/server/core}/cardValidate.js +11 -8
  18. package/dist/{server-core → vendored/server/core}/index.js +5 -3
  19. package/dist/vendored/server/core/skillPaths.js +124 -0
  20. package/dist/{server-core → vendored/server/core}/sliceUpload.js +5 -8
  21. package/dist/vendored/skills/README.md +93 -0
  22. package/dist/vendored/skills/card-doctor/SKILL.md +52 -0
  23. package/dist/vendored/skills/card-skill-trainer/SKILL.md +312 -0
  24. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r038n.json +43 -0
  25. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r039n.json +47 -0
  26. package/dist/vendored/skills/card-skill-trainer/baselines/v3-r050n.json +48 -0
  27. package/dist/vendored/skills/card-skill-trainer/scripts/build_dataset.py +472 -0
  28. package/dist/vendored/skills/card-skill-trainer/scripts/build_sample.mjs +162 -0
  29. package/dist/vendored/skills/card-skill-trainer/scripts/diagnose_case.mjs +241 -0
  30. package/dist/vendored/skills/card-skill-trainer/scripts/lib/scoring.mjs +525 -0
  31. package/dist/vendored/skills/card-skill-trainer/scripts/round_diff.mjs +133 -0
  32. package/dist/vendored/skills/card-skill-trainer/scripts/run_round.mjs +1043 -0
  33. package/dist/vendored/skills/card-skill-trainer/scripts/score_round.mjs +195 -0
  34. package/dist/vendored/skills/card-skill-trainer/scripts/test_missing_slice.py +122 -0
  35. package/dist/vendored/skills/dsl-card-ai/SKILL.md +191 -0
  36. package/dist/vendored/skills/dsl-card-generator/SKILL.md +480 -0
  37. package/dist/vendored/skills/dsl-card-generator/config/render-contract.json +11 -0
  38. package/dist/vendored/skills/dsl-card-generator/references/amount_formatting.md +138 -0
  39. package/dist/vendored/skills/dsl-card-generator/references/card_index.json +1597 -0
  40. package/dist/vendored/skills/dsl-card-generator/references/card_templates.json +384 -0
  41. package/dist/vendored/skills/dsl-card-generator/references/card_to_summary.md +56 -0
  42. package/dist/vendored/skills/dsl-card-generator/references/component_rules.md +76 -0
  43. package/dist/vendored/skills/dsl-card-generator/references/component_specs.md +393 -0
  44. package/dist/vendored/skills/dsl-card-generator/references/dsl_structure.md +208 -0
  45. package/dist/vendored/skills/dsl-card-generator/references/event_patterns.md +287 -0
  46. package/dist/vendored/skills/dsl-card-generator/references/expression_engine.md +121 -0
  47. package/dist/vendored/skills/dsl-card-generator/references/field_name_dict.json +34 -0
  48. package/dist/vendored/skills/dsl-card-generator/references/field_naming.md +101 -0
  49. package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +928 -0
  50. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/README.md +69 -0
  51. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/examples_index.json +2967 -0
  52. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/patterns.json +2835 -0
  53. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2.json +648 -0
  54. package/dist/vendored/skills/dsl-card-generator/references/handcrafted/recipes_v2_schema.md +295 -0
  55. package/dist/vendored/skills/dsl-card-generator/references/image_diff.md +48 -0
  56. package/dist/vendored/skills/dsl-card-generator/references/image_to_card.md +129 -0
  57. package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +212 -0
  58. package/dist/vendored/skills/dsl-card-generator/references/message-center-pattern.md +149 -0
  59. package/dist/vendored/skills/dsl-card-generator/references/platform_jsapi.md +196 -0
  60. package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +54 -0
  61. package/dist/vendored/skills/dsl-card-generator/references/quickref.md +197 -0
  62. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/absolute-badge.json +84 -0
  63. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/accordion-group-toggle.json +18 -0
  64. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertCard-pair.json +12 -0
  65. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/alertDialog-call.json +12 -0
  66. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-countdown.json +15 -0
  67. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-donut.json +15 -0
  68. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-gauge-score.json +11 -0
  69. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-radar.json +10 -0
  70. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/canvas-title-head.json +13 -0
  71. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/globalSet.json +12 -0
  72. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/label-col-modes.json +21 -0
  73. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/list-expand-collapse.json +17 -0
  74. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/money-format.json +11 -0
  75. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/opacity-switch.json +130 -0
  76. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/progress-bar.json +25 -0
  77. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/tab-billMode.json +15 -0
  78. package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +21 -0
  79. package/dist/vendored/skills/dsl-card-generator/references/recipes.md +797 -0
  80. package/dist/vendored/skills/dsl-card-generator/references/rules.md +235 -0
  81. package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +177 -0
  82. package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +609 -0
  83. package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +131 -0
  84. package/dist/vendored/skills/dsl-card-generator/scripts/add_component.mjs +118 -0
  85. package/dist/vendored/skills/dsl-card-generator/scripts/analyze_component_usage.mjs +153 -0
  86. package/dist/vendored/skills/dsl-card-generator/scripts/apply_opacity_pattern.mjs +343 -0
  87. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_index.mjs +108 -0
  88. package/dist/vendored/skills/dsl-card-generator/scripts/build_card_templates.mjs +190 -0
  89. package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +257 -0
  90. package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +79 -0
  91. package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +98 -0
  92. package/dist/vendored/skills/dsl-card-generator/scripts/describe_card.mjs +265 -0
  93. package/dist/vendored/skills/dsl-card-generator/scripts/diff_bands.mjs +257 -0
  94. package/dist/vendored/skills/dsl-card-generator/scripts/diff_card.mjs +316 -0
  95. package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +873 -0
  96. package/dist/vendored/skills/dsl-card-generator/scripts/extract_handcrafted.py +1199 -0
  97. package/dist/vendored/skills/dsl-card-generator/scripts/extract_subtree.mjs +191 -0
  98. package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +389 -0
  99. package/dist/vendored/skills/dsl-card-generator/scripts/inventory_schemas.py +160 -0
  100. package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +700 -0
  101. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_image.mjs +56 -0
  102. package/dist/vendored/skills/dsl-card-generator/scripts/lib/design_sample.mjs +328 -0
  103. package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout-tokens.mjs +79 -0
  104. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.mjs +103 -0
  105. package/dist/vendored/skills/dsl-card-generator/scripts/lib/scaffold.mjs +575 -0
  106. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +81 -0
  107. package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +147 -0
  108. package/dist/vendored/skills/dsl-card-generator/scripts/lint_autofix.mjs +363 -0
  109. package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +1399 -0
  110. package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +249 -0
  111. package/dist/vendored/skills/dsl-card-generator/scripts/register_card_template.mjs +208 -0
  112. package/dist/vendored/skills/dsl-card-generator/scripts/render_baseline.mjs +231 -0
  113. package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +801 -0
  114. package/dist/vendored/skills/dsl-card-generator/scripts/render_diff.mjs +239 -0
  115. package/dist/vendored/skills/dsl-card-generator/scripts/resolve_card_template.mjs +73 -0
  116. package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +447 -0
  117. package/dist/vendored/skills/dsl-card-generator/scripts/scaffold_card.mjs +81 -0
  118. package/dist/vendored/skills/dsl-card-generator/scripts/slices_normalize.mjs +316 -0
  119. package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +462 -0
  120. package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +7735 -0
  121. package/dist/vendored/skills/dsl-card-generator/scripts/spec_tree.mjs +355 -0
  122. package/dist/vendored/skills/dsl-card-generator/scripts/validate_all_schemas.py +71 -0
  123. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/default-image-wzZYQ86K.png +0 -0
  124. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-C9k-07y7.css +1 -0
  125. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/standalone-oFRM4f4V.js +55 -0
  126. package/dist/vendored/skills/dsl-card-generator/vendor/preview/assets/svg-symbols-B2exBrAr.svg +118 -0
  127. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/component-meta.json +2714 -0
  128. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/directive-meta.json +40 -0
  129. package/dist/vendored/skills/dsl-card-generator/vendor/preview/meta/style-meta.json +1086 -0
  130. package/dist/vendored/{standalone.html → skills/dsl-card-generator/vendor/preview/standalone.html} +2 -1
  131. package/dist/vendored/skills/finmall-card-sync/SKILL.md +262 -0
  132. package/dist/vendored/skills/finmall-session-navigator/SKILL.md +169 -0
  133. package/dist/vendored/skills/finmall-session-navigator/scripts/finmall_login.mjs +882 -0
  134. package/dist/vendored/skills/finmall-session-navigator/scripts/package-lock.json +59 -0
  135. package/dist/vendored/skills/finmall-session-navigator/scripts/package.json +10 -0
  136. package/dist/vendored/skills/finmall-session-navigator/scripts/session-manager.mjs +96 -0
  137. package/dist/vendored/skills/shared/README.md +55 -0
  138. package/dist/vendored/skills/shared/auth-model.md +79 -0
  139. package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +329 -0
  140. package/dist/vendored/skills/sketch-spec-to-card/references/boundary.json +14 -0
  141. package/dist/vendored/skills/sketch-spec-to-card/references/card_refs.json +13 -0
  142. package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +103 -0
  143. package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +797 -0
  144. package/dist/vendored/skills/sketch-spec-to-card/scripts/card_refs.py +94 -0
  145. package/dist/vendored/skills/sketch-spec-to-card/scripts/cluster_states.py +706 -0
  146. package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +1781 -0
  147. package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +318 -0
  148. package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +1118 -0
  149. package/dist/vendored/templates/helloCard/data.json +10 -0
  150. package/dist/vendored/templates/helloCard/schema.json +238 -0
  151. package/dist/vendored/tools/link-skills.mjs +485 -0
  152. package/package.json +2 -1
  153. package/dist/preview-editor/assets/index-jl4YJzKv.js +0 -180
  154. package/dist/vendored/App.vue +0 -2858
  155. package/dist/vendored/ansi.js +0 -185
  156. package/dist/vendored/assets/apple-touch-icon.png +0 -0
  157. package/dist/vendored/assets/default-image.png +0 -0
  158. package/dist/vendored/assets/favicon-16.png +0 -0
  159. package/dist/vendored/assets/favicon-32.png +0 -0
  160. package/dist/vendored/assets/site-icon.png +0 -0
  161. package/dist/vendored/assets/svgIcons/icons.svg +0 -87
  162. package/dist/vendored/assets/svgIcons/index.js +0 -39
  163. package/dist/vendored/components/AiSettings.vue +0 -392
  164. package/dist/vendored/components/AlertCardInstance.vue +0 -58
  165. package/dist/vendored/components/AlertCardLayer.vue +0 -59
  166. package/dist/vendored/components/ApiCallPanel.vue +0 -127
  167. package/dist/vendored/components/ApiPayloadTree.vue +0 -201
  168. package/dist/vendored/components/AppToast.vue +0 -37
  169. package/dist/vendored/components/CardGallery.vue +0 -291
  170. package/dist/vendored/components/CardPreview.vue +0 -72
  171. package/dist/vendored/components/CardSelect.vue +0 -206
  172. package/dist/vendored/components/ChatComposer.vue +0 -413
  173. package/dist/vendored/components/ChatMessage.vue +0 -383
  174. package/dist/vendored/components/ChatPanel.vue +0 -937
  175. package/dist/vendored/components/ComponentPalette.vue +0 -301
  176. package/dist/vendored/components/ConfirmDialog.vue +0 -54
  177. package/dist/vendored/components/DatePickerOverlay.vue +0 -345
  178. package/dist/vendored/components/DebugPanel.vue +0 -1734
  179. package/dist/vendored/components/DeviceOverlay.vue +0 -70
  180. package/dist/vendored/components/Dropdown.vue +0 -193
  181. package/dist/vendored/components/EventEditor.vue +0 -273
  182. package/dist/vendored/components/HoverGuides.vue +0 -122
  183. package/dist/vendored/components/JsonTree.vue +0 -547
  184. package/dist/vendored/components/LogValue.vue +0 -52
  185. package/dist/vendored/components/MarkdownBody.vue +0 -64
  186. package/dist/vendored/components/NewCardDialog.vue +0 -132
  187. package/dist/vendored/components/NodeInspector.vue +0 -295
  188. package/dist/vendored/components/NumberStepper.vue +0 -125
  189. package/dist/vendored/components/PickerOverlay.vue +0 -131
  190. package/dist/vendored/components/PropControl.vue +0 -245
  191. package/dist/vendored/components/PropTree.vue +0 -128
  192. package/dist/vendored/components/RemotePreview.vue +0 -173
  193. package/dist/vendored/components/SelectionOverlay.vue +0 -105
  194. package/dist/vendored/components/StructureTree.vue +0 -1002
  195. package/dist/vendored/components/Switch.vue +0 -38
  196. package/dist/vendored/components/setters/SetBgPosition.vue +0 -64
  197. package/dist/vendored/components/setters/SetBgSize.vue +0 -90
  198. package/dist/vendored/components/setters/SetCode.vue +0 -198
  199. package/dist/vendored/components/setters/SetColor.vue +0 -244
  200. package/dist/vendored/components/setters/SetCustomSelect.vue +0 -58
  201. package/dist/vendored/components/setters/SetExpr.vue +0 -107
  202. package/dist/vendored/components/setters/SetGradient.vue +0 -125
  203. package/dist/vendored/components/setters/SetGrid9.vue +0 -68
  204. package/dist/vendored/components/setters/SetIconSelect.vue +0 -67
  205. package/dist/vendored/components/setters/SetIfForm.vue +0 -61
  206. package/dist/vendored/components/setters/SetImage.vue +0 -43
  207. package/dist/vendored/components/setters/SetList.vue +0 -139
  208. package/dist/vendored/components/setters/SetNumber.vue +0 -118
  209. package/dist/vendored/components/setters/SetNumberStyle.vue +0 -113
  210. package/dist/vendored/components/setters/SetPadding.vue +0 -77
  211. package/dist/vendored/components/setters/SetSegment.vue +0 -160
  212. package/dist/vendored/components/setters/SetSelect.vue +0 -51
  213. package/dist/vendored/components/setters/SetSwitch.vue +0 -49
  214. package/dist/vendored/components/setters/SetText.vue +0 -52
  215. package/dist/vendored/components/setters/props.js +0 -23
  216. package/dist/vendored/fidelity/baseline-messageOverview.json +0 -87
  217. package/dist/vendored/fidelity/calibration.json +0 -29
  218. package/dist/vendored/fidelity/fidelity-messageOverview-after.html +0 -114
  219. package/dist/vendored/fidelity/fidelity-messageOverview-after.json +0 -272
  220. package/dist/vendored/fidelity/fidelity-messageOverview.html +0 -185
  221. package/dist/vendored/fidelity/fidelity-messageOverview.json +0 -336
  222. package/dist/vendored/fidelity/fidelity-messageOverview_v2.html +0 -262
  223. package/dist/vendored/fidelity/fidelity-messageOverview_v2.json +0 -502
  224. package/dist/vendored/fidelity/fidelity-messageOverview_v3.html +0 -201
  225. package/dist/vendored/fidelity/fidelity-messageOverview_v3.json +0 -417
  226. package/dist/vendored/fidelity/fidelity-messageOverview_visual.html +0 -125
  227. package/dist/vendored/fidelity/fidelity-messageOverview_visual.json +0 -338
  228. package/dist/vendored/index.html +0 -16
  229. package/dist/vendored/main.js +0 -37
  230. package/dist/vendored/runtime/ai-api.js +0 -165
  231. package/dist/vendored/runtime/alert-card.js +0 -125
  232. package/dist/vendored/runtime/app-toast.js +0 -33
  233. package/dist/vendored/runtime/clipboard-node.js +0 -78
  234. package/dist/vendored/runtime/clipboard.js +0 -29
  235. package/dist/vendored/runtime/event-model.js +0 -300
  236. package/dist/vendored/runtime/hoverGuide.js +0 -31
  237. package/dist/vendored/runtime/jsapi-mock.js +0 -256
  238. package/dist/vendored/runtime/markdown.js +0 -140
  239. package/dist/vendored/runtime/node-model.js +0 -217
  240. package/dist/vendored/skills/.manifest.json +0 -1101
  241. package/dist/vendored/standalone-main.js +0 -36
  242. package/dist/vendored/styles/ai-chat.css +0 -1850
  243. package/dist/vendored/styles/app.css +0 -3648
  244. package/dist/vendored/styles/card-gallery.css +0 -285
  245. package/dist/vendored/styles/card-select.css +0 -288
  246. package/dist/vendored/styles/components.css +0 -383
  247. package/dist/vendored/styles/dropdown.css +0 -324
  248. package/dist/vendored/styles/json-tree.css +0 -308
  249. package/dist/vendored/styles/node-inspector.css +0 -800
  250. package/dist/vendored/styles/setters.css +0 -1050
  251. package/dist/vendored/styles/standalone.css +0 -27
  252. package/dist/vendored/styles/structure-tree.css +0 -460
  253. /package/dist/vendored/{api.js → preview/api.js} +0 -0
  254. /package/dist/vendored/{assets → preview/assets}/svg-symbols.svg +0 -0
  255. /package/dist/vendored/{components → preview/components}/AbsoluteLayout.vue +0 -0
  256. /package/dist/vendored/{components → preview/components}/Button.vue +0 -0
  257. /package/dist/vendored/{components → preview/components}/Canvas.vue +0 -0
  258. /package/dist/vendored/{components → preview/components}/CheckGroup.vue +0 -0
  259. /package/dist/vendored/{components → preview/components}/Column.vue +0 -0
  260. /package/dist/vendored/{components → preview/components}/CustomPlaceholder.vue +0 -0
  261. /package/dist/vendored/{components → preview/components}/Divider.vue +0 -0
  262. /package/dist/vendored/{components → preview/components}/GridLayout.vue +0 -0
  263. /package/dist/vendored/{components → preview/components}/Icon.vue +0 -0
  264. /package/dist/vendored/{components → preview/components}/Image.vue +0 -0
  265. /package/dist/vendored/{components → preview/components}/Input.vue +0 -0
  266. /package/dist/vendored/{components → preview/components}/ListLayout.vue +0 -0
  267. /package/dist/vendored/{components → preview/components}/Map.vue +0 -0
  268. /package/dist/vendored/{components → preview/components}/MultiColumn.vue +0 -0
  269. /package/dist/vendored/{components → preview/components}/MultiText.vue +0 -0
  270. /package/dist/vendored/{components → preview/components}/ProgressSlider.vue +0 -0
  271. /package/dist/vendored/{components → preview/components}/RadioButton.vue +0 -0
  272. /package/dist/vendored/{components → preview/components}/RenderTree.vue +0 -0
  273. /package/dist/vendored/{components → preview/components}/Root.vue +0 -0
  274. /package/dist/vendored/{components → preview/components}/Row.vue +0 -0
  275. /package/dist/vendored/{components → preview/components}/SvgCode.vue +0 -0
  276. /package/dist/vendored/{components → preview/components}/SvgSymbols.vue +0 -0
  277. /package/dist/vendored/{components → preview/components}/Text.vue +0 -0
  278. /package/dist/vendored/{components → preview/components}/Video.vue +0 -0
  279. /package/dist/vendored/{components → preview/components}/ViewContainer.vue +0 -0
  280. /package/dist/vendored/{components → preview/components}/base.js +0 -0
  281. /package/dist/vendored/{components → preview/components}/icons.js +0 -0
  282. /package/dist/vendored/{components → preview/components}/index.js +0 -0
  283. /package/dist/vendored/{meta → preview/meta}/component-meta.json +0 -0
  284. /package/dist/vendored/{meta → preview/meta}/directive-meta.json +0 -0
  285. /package/dist/vendored/{meta → preview/meta}/style-meta.json +0 -0
  286. /package/dist/vendored/{runtime → preview/runtime}/StyleParser.js +0 -0
  287. /package/dist/vendored/{runtime → preview/runtime}/adapter.js +0 -0
  288. /package/dist/vendored/{runtime → preview/runtime}/assets.js +0 -0
  289. /package/dist/vendored/{runtime → preview/runtime}/canvas-context.js +0 -0
  290. /package/dist/vendored/{runtime → preview/runtime}/card-runtime.js +0 -0
  291. /package/dist/vendored/{runtime → preview/runtime}/evaluate.js +0 -0
  292. /package/dist/vendored/{runtime → preview/runtime}/property.js +0 -0
  293. /package/dist/vendored/{runtime → preview/runtime}/selectionOverlay.js +0 -0
  294. /package/dist/vendored/{runtime → preview/runtime}/toPreview.js +0 -0
  295. /package/dist/{server-core → vendored/server/core}/assetProxy.js +0 -0
  296. /package/dist/{server-core → vendored/server/core}/bbox.js +0 -0
  297. /package/dist/{server-core → vendored/server/core}/buildDataFromSchema.js +0 -0
  298. /package/dist/{server-core → vendored/server/core}/cardFiles.js +0 -0
  299. /package/dist/{server-core → vendored/server/core}/cardHistory.js +0 -0
  300. /package/dist/{server-core → vendored/server/core}/cardImage.js +0 -0
  301. /package/dist/{server-core → vendored/server/core}/cardImportMeta.js +0 -0
  302. /package/dist/{server-core → vendored/server/core}/cardListCache.js +0 -0
  303. /package/dist/{server-core → vendored/server/core}/cardOpenFolder.js +0 -0
  304. /package/dist/{server-core → vendored/server/core}/cardSidecar.js +0 -0
  305. /package/dist/{server-core → vendored/server/core}/codeBridge.js +0 -0
  306. /package/dist/{server-core → vendored/server/core}/logStream.js +0 -0
  307. /package/dist/{server-core → vendored/server/core}/logger.js +0 -0
  308. /package/dist/{server-core → vendored/server/core}/nodeModels.js +0 -0
  309. /package/dist/{server-core → vendored/server/core}/renderStatus.js +0 -0
  310. /package/dist/{server-core → vendored/server/core}/screenshot.js +0 -0
  311. /package/dist/{server-core → vendored/server/core}/sliceStatic.js +0 -0
  312. /package/dist/vendored/{meta → skills/dsl-card-generator/vendor/preview/meta}/action-meta.json +0 -0
  313. /package/dist/{server-core/_vendor → vendored/tools}/finmall-env.mjs +0 -0
@@ -0,0 +1,268 @@
1
+ import * as client from './client.mjs'
2
+
3
+ /**
4
+ * MCP 工具清单(16 个)+ 分发。
5
+ * 工具描述里写死了两处语义边界(与 renderStatus/screenshot 端点、方案文档三处一致):
6
+ * - card_render_status 只保证「结构合法 + 绑定可解析 + 渲染不抛错」,**不保证画得对**;
7
+ * - card_screenshot 才代表「浏览器里实际画出来长什么样」。
8
+ * Agent 误把 render_status.ok 当成「画对了」是整个闭环的头号坑,故反复强调。
9
+ * card_scaffold / card_add_component 为离线脚手架工具(P5),直接复用 scaffold lib,
10
+ * 不依赖 dev server,把「生成骨架 / 插入组件」从手写样板降级为一次工具调用。
11
+ * card_route_input 为离线建卡输入路由(Workflow 0),直接复用 skill 的 route_input.mjs 判定器:
12
+ * 识别到 Sketch MeaXure 标注导出时强制先转 sketch-spec-to-card 前处理,防止直接读整图建卡。
13
+ */
14
+
15
+ export const TOOLS = [
16
+ {
17
+ name: 'card_list',
18
+ description:
19
+ '列出工作区所有卡片(id / 名称 / pageId)。优先走 dev server 的 GET /__cards;dev server 未启动时自动降级为直接扫描 src/cards/。',
20
+ inputSchema: { type: 'object', properties: {}, required: [] },
21
+ },
22
+ {
23
+ name: 'card_read',
24
+ description:
25
+ '读取单张卡片的 schema.json(组件树/属性/样式/事件)与 data.json(输入参数默认值)。优先走 GET /__card/:id;未启动 dev server 时降级为直接读盘。',
26
+ inputSchema: {
27
+ type: 'object',
28
+ properties: { id: { type: 'string', description: '卡片 id,对应 src/cards/<id>' } },
29
+ required: ['id'],
30
+ },
31
+ },
32
+ {
33
+ name: 'card_write',
34
+ description:
35
+ '把编辑后的 schema / data 写回 src/cards/<id>/(仅允许写 schema.json / data.json,白名单由服务端校验)。【闭环收口】写前自动把当前版本存为历史快照(可被 card_rollback 撤销;snapshot:false 可关闭),写完默认自动跑 validate + render_status(dev server 在线时)把校验结果一起返回;设 verify:false 可关闭。dev server 未启动时降级为直接写盘 + 本地 Python 硬校验(render_status 需 dev server,会标注 skipped)。',
36
+ inputSchema: {
37
+ type: 'object',
38
+ properties: {
39
+ id: { type: 'string', description: '卡片 id' },
40
+ schema: { type: 'object', description: '新 schema.json 内容(可只传要改的字段,整体替换)' },
41
+ data: { type: 'object', description: '新 data.json 内容' },
42
+ verify: { type: 'boolean', description: '写后是否自动校验,默认 true' },
43
+ },
44
+ required: ['id'],
45
+ },
46
+ },
47
+ {
48
+ name: 'card_validate',
49
+ description:
50
+ '对单卡跑契约校验(Python 校验器):组件/属性/样式/表达式/data.json 一致性。返回 {ok, errors, warnings, durationMs}。优先走 GET /__validate/:id;未启动 dev server 时降级为直接子进程跑 Python。这是「硬」契约(CI 真卡口)。',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ id: { type: 'string' },
55
+ noData: { type: 'boolean', description: 'true=跳过 data.json 一致性检查' },
56
+ },
57
+ required: ['id'],
58
+ },
59
+ },
60
+ {
61
+ name: 'card_render_status',
62
+ description:
63
+ 'SSR 渲染自检:返回结构统计 + 绑定/运行时/渲染错误 + JSAPI 清单。⚠️ 语义边界:只保证「结构合法 + 绑定可解析 + 渲染不抛错」,**不保证画得对**——视觉正确性以 card_screenshot 为准。必须 dev server 已启动(npm run dev),不支持离线降级。',
64
+ inputSchema: {
65
+ type: 'object',
66
+ properties: { id: { type: 'string' } },
67
+ required: ['id'],
68
+ },
69
+ },
70
+ {
71
+ name: 'card_node_models',
72
+ description:
73
+ '读取单卡的「可编辑字段结构树」(Task #5 暴露的 Property-tree 模型,对应 dev server 的 GET /__node-models/:id)。返回每个节点的 {componentId, className, properties, style, directives, eventactions, components},其中每个字段含 itemKey/itemName/itemType/itemValue/itemValueFrom/itemTypeRange/options(iParamList|iTypeList)/children——AI 无需解析 raw DSL + component-meta 即可知道「某节点有哪些可改字段、各自类型与枚举」。⚠️ 该模型是只读视图,改卡仍走 card_write(整体替换);必须 dev server 已启动(npm run dev),不支持离线降级。',
74
+ inputSchema: {
75
+ type: 'object',
76
+ properties: { id: { type: 'string', description: '卡片 id,对应 src/cards/<id>' } },
77
+ required: ['id'],
78
+ },
79
+ },
80
+ {
81
+ name: 'card_screenshot',
82
+ description:
83
+ '无头浏览器截图(走系统 Chrome)。format=json 返回 {mime,width,height,base64};format=png 直接返回一张图片供多模态阅读(白底纯净卡片,等价于 ?chrome=off)。⚠️ 这是「画面对不对」的唯一真相来源。优先走 dev server;未启动时自动降级到独立渲染器(临时 Vite + 临时 Chrome,不占 5180/8787)。',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: {
87
+ id: { type: 'string' },
88
+ format: { type: 'string', enum: ['json', 'png'], default: 'json' },
89
+ width: { type: 'number', default: 390 },
90
+ },
91
+ required: ['id'],
92
+ },
93
+ },
94
+ {
95
+ name: 'card_sync',
96
+ description:
97
+ '经已登录的 FinMall 浏览器会话从远端拉取最新卡片写回 src/cards(复用 finmall-card-sync)。必须 dev server 已启动且已登录 FinMall,不支持离线降级。',
98
+ inputSchema: {
99
+ type: 'object',
100
+ properties: { cardCode: { type: 'string', description: '指定卡片 code;留空=同步全部' } },
101
+ required: [],
102
+ },
103
+ },
104
+ {
105
+ name: 'card_save',
106
+ description:
107
+ '把当前卡片 schema 经已登录浏览器写回 FinMall(IDC10010.do)。必须 dev server 已启动且已登录 FinMall,不支持离线降级。',
108
+ inputSchema: {
109
+ type: 'object',
110
+ properties: {
111
+ schema: { type: 'object', description: '当前卡片 schema(必须含 pageId)' },
112
+ code: { type: 'string' },
113
+ skeletonScreen: { type: 'string' },
114
+ },
115
+ required: ['schema'],
116
+ },
117
+ },
118
+ {
119
+ name: 'card_slice_upload',
120
+ description:
121
+ '把指定卡片引用的本地切图(src/slices)经已登录浏览器上传到 FinMall 图片库(CC0000.do),写「本地文件 ↔ 线上地址」映射 src/slices/manifest.json,并把该卡 schema.json / data.json 里的 "/slices/<name>" 引用改写为返回的 @filestore/… 线上地址。按 sha256 判重:内容未变的切图直接复用已有地址不重复上传;本地切图真源保留不动。**仅按需调用,不存在自动上传**——推送版本 / 出真机预览前,若卡片仍引用 /slices/ 就先跑本工具,否则线上卡片取不到图。必须 dev server 已启动且已登录 FinMall,不支持离线降级。',
122
+ inputSchema: {
123
+ type: 'object',
124
+ properties: {
125
+ cardId: { type: 'string', description: '卡片 id,对应 src/cards/<id>(上传与改写都只作用于这张卡)' },
126
+ force: { type: 'boolean', description: 'true=忽略 sha256 判重,强制重新上传(会得到新的线上地址)' },
127
+ },
128
+ required: ['cardId'],
129
+ },
130
+ },
131
+ {
132
+ name: 'card_import_meta',
133
+ description:
134
+ '读取单卡的「导入来源元数据」(Phase E 可追溯性侧车,对应 dev server 的 GET /__import-meta/:id)。返回 {source, interface, baseUrl, appId, workspaceId, cardId, cardCode, cardName, syncedAt, tool, dirty, lastExport}——AI/skill 可据此判断「这张卡是不是从远端同步来的、何时导入、上一次导出读回校验是否通过」。无侧车(本地新建、未同步)时返回 {error:"import-meta not found"}。只读,不污染 DSL。必须 dev server 已启动,不支持离线降级。',
135
+ inputSchema: {
136
+ type: 'object',
137
+ properties: { id: { type: 'string', description: '卡片 id,对应 src/cards/<id>' } },
138
+ required: ['id'],
139
+ },
140
+ },
141
+ {
142
+ name: 'card_route_input',
143
+ description:
144
+ '建卡输入路由(dsl-card-generator SKILL.md §8 Workflow 0 的机械判定器,离线):判定用户给的输入形态,返回 kind + 判定证据 + 下一步可直接执行的命令。kind=meaxure-export(Sketch MeaXure 标注导出目录:index.html 内嵌 artboards + preview/ 画板整图 + assets/ 切图)时**必须先经 sketch-spec-to-card 前处理**——next 直接给出 parse_meaxure.py(--src/--out/--draft/--build)与 cluster_states.py(--out UI/<稿名>)两条命令,禁止直接读 preview/ 整图建卡;若 UI/*/manifest.json 的 export 字段已匹配该导出,则 preprocess.needed=false 并给出存量产物路径,不重复跑管线。其余 kind:ui-bundle(要素包 → Workflow A 逐卡,overlay_dialog 不产卡)/ image(单张设计稿 → Workflow A 纯视觉推断)/ card-dir(已存在 schema.json → Workflow B 改卡)/ unknown(判不出,需向用户确认,ok=false)。无需 dev server。',
145
+ inputSchema: {
146
+ type: 'object',
147
+ properties: {
148
+ path: {
149
+ type: 'string',
150
+ description:
151
+ '输入路径:MeaXure 标注导出目录,或 UI 要素包目录(也可传其中的 manifest.json / spec.json / prompt.md / card-N.png),或卡片目录 src/cards/<id>,或设计稿图片。相对路径按工程根解析,支持 ~ 展开。',
152
+ },
153
+ },
154
+ required: ['path'],
155
+ },
156
+ },
157
+ {
158
+ name: 'card_scaffold',
159
+ description:
160
+ '新建卡片双模:① intent 模式——秒出「合法最小」骨架(blank|list|detail|form|dialog|tab|canvas-head),data.json 由 inputParams 反推自洽;② from 模式——整卡模板克隆(references/card_templates.json 提供簇→代表卡路由,如 dialog→creditCardBill、list→revenueAnalysis、tab→DepositHolding、amount→myLoan),只重写 pageId/name,版式纪律与绑定范式原样继承,封面不复制(定稿后重新渲染)。从零建卡用 intent,有近似存量卡用 from。无需 dev server。默认写到 src/cards;传 outBase 可改输出根;force=true 覆盖已存在目录。',
161
+ inputSchema: {
162
+ type: 'object',
163
+ properties: {
164
+ intent: { type: 'string', enum: ['blank', 'list', 'detail', 'form', 'dialog', 'tab', 'canvas-head'], description: '卡片意图(决定骨架结构);from 存在时忽略' },
165
+ from: { type: 'string', description: '整卡模板克隆:源模板卡 id(references/card_templates.json 的 template.sourceCard)' },
166
+ id: { type: 'string', description: '新卡片 id,对应 src/cards/<id>' },
167
+ name: { type: 'string', description: '卡片名称(写入 schema.name),可不传' },
168
+ outBase: { type: 'string', description: '输出根目录,默认 src/cards' },
169
+ platform: { type: 'string', description: '平台码,默认 002' },
170
+ force: { type: 'boolean', description: 'true=覆盖已存在的目录' },
171
+ },
172
+ required: ['id'],
173
+ },
174
+ },
175
+ {
176
+ name: 'card_add_component',
177
+ description:
178
+ '向已有卡片的某个「容器节点」插入一个「合法最小组件」(P5 脚手架)。定位 parent(必须是容器,有 slots 或 ListLayout/GridLayout)→ 在默认插槽追加一个由 scaffold lib 生成的合法节点 → 写回 → 离线跑 Python 硬校验并把结果带回。纪律:只增不改、不重排 componentId、不碰孤儿字段。text 以 $ 开头视为变量绑定(itemValueFrom=1),否则常量;click=true 给节点挂一个 customAction 点击动作骨架;setProps 可单点覆盖属性(等价 CLI 的 --set-prop key=value)。无需 dev server。',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: {
182
+ id: { type: 'string', description: '卡片 id,对应 src/cards/<id>' },
183
+ parent: { type: 'string', description: '目标容器节点的 componentId' },
184
+ className: { type: 'string', description: '要插入的组件类型,如 Text/Row/Button/Image/Column' },
185
+ text: { type: 'string', description: '可选文案/绑定表达式;$ 开头视为变量绑定' },
186
+ click: { type: 'boolean', description: 'true=附带 customAction 点击动作骨架' },
187
+ setProps: {
188
+ type: 'array',
189
+ description:
190
+ '可选属性覆盖,等价 CLI 的 --set-prop key=value。数组元素可为 "key=value" 字符串或 {key,value} 对象;value 以 $ 开头视为变量绑定(itemValueFrom=1)。存在时优先于 text(text 仅在未覆盖 value 时兜底)。',
191
+ items: {},
192
+ },
193
+ },
194
+ required: ['id', 'parent', 'className'],
195
+ },
196
+ },
197
+ {
198
+ name: 'card_history',
199
+ description:
200
+ '列出单卡的编辑历史快照元数据(ts / 时间 / 字节数 / 节点数),快照由编辑器编辑或 card_write 写卡时自动累积(.history/,上限 30)。只返回元数据不返回整份 schema,用于选择回滚目标。优先走 dev server GET /__history/:id;未启动时降级为直接读盘。',
201
+ inputSchema: {
202
+ type: 'object',
203
+ properties: { id: { type: 'string', description: '卡片 id,对应 src/cards/<id>' } },
204
+ required: ['id'],
205
+ },
206
+ },
207
+ {
208
+ name: 'card_rollback',
209
+ description:
210
+ '把卡片回滚到指定历史快照的 schema(默认最新快照 ts,即「撤销上一次写卡」)。安全语义:回滚前会自动把当前版本另存为新快照,回滚本身可再次回滚撤销;写回走 card_write 同一收口(默认自动 validate + render_status 校验并返回结果)。离线可降级(直接写盘 + Python 硬校验)。',
211
+ inputSchema: {
212
+ type: 'object',
213
+ properties: {
214
+ id: { type: 'string', description: '卡片 id,对应 src/cards/<id>' },
215
+ ts: { type: 'number', description: '目标快照 ts(来自 card_history);缺省 = 最新快照' },
216
+ verify: { type: 'boolean', description: '回滚写回后是否自动校验,默认 true' },
217
+ },
218
+ required: ['id'],
219
+ },
220
+ },
221
+ ]
222
+
223
+ /** 把工具名 + 参数分发到 client 函数;未知工具返回标准错误对象 */
224
+ export async function dispatch(name, args = {}) {
225
+ switch (name) {
226
+ case 'card_list':
227
+ return client.cardList()
228
+ case 'card_read':
229
+ return client.cardRead(args.id)
230
+ case 'card_write':
231
+ return client.cardWrite(args.id, { schema: args.schema, data: args.data }, { verify: args.verify !== false })
232
+ case 'card_validate':
233
+ return client.cardValidate(args.id, { noData: !!args.noData })
234
+ case 'card_render_status':
235
+ return client.cardRenderStatus(args.id)
236
+ case 'card_node_models':
237
+ return client.cardNodeModels(args.id)
238
+ case 'card_screenshot':
239
+ return client.cardScreenshot(args.id, { format: args.format || 'json', width: args.width || 390 })
240
+ case 'card_sync':
241
+ return client.cardSync({ cardCode: args.cardCode })
242
+ case 'card_save':
243
+ return client.cardSave({ schema: args.schema, code: args.code, skeletonScreen: args.skeletonScreen })
244
+ case 'card_slice_upload':
245
+ return client.sliceUpload({ cardId: args.cardId, force: args.force })
246
+ case 'card_import_meta':
247
+ return client.cardImportMeta(args.id)
248
+ case 'card_route_input':
249
+ return client.cardRouteInput({ path: args.path })
250
+ case 'card_scaffold':
251
+ return client.cardScaffold({ intent: args.intent, id: args.id, name: args.name, outBase: args.outBase, platform: args.platform, force: args.force, from: args.from })
252
+ case 'card_add_component':
253
+ return client.cardAddComponent({
254
+ id: args.id,
255
+ parent: args.parent,
256
+ className: args.className,
257
+ text: args.text,
258
+ click: args.click,
259
+ setProps: args.setProps,
260
+ })
261
+ case 'card_history':
262
+ return client.cardHistory(args.id)
263
+ case 'card_rollback':
264
+ return client.cardRollback(args.id, { ts: args.ts, verify: args.verify !== false })
265
+ default:
266
+ return { error: 'unknown-tool', message: `未知道具: ${name}` }
267
+ }
268
+ }
@@ -50,11 +50,14 @@ import path from 'node:path'
50
50
  import fs from 'node:fs'
51
51
  import os from 'node:os'
52
52
  import { execFile, spawn, spawnSync } from 'node:child_process'
53
- import { fileURLToPath } from 'node:url'
53
+ import { SNAPSHOT_ROOT, skillsDirFor, resolveSkillPath, normalizeSkillRel } from './skillPaths.js'
54
54
 
55
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
56
- const REPO_ROOT = path.resolve(__dirname, '..', '..')
57
- const SKILLS_DIR = path.join(REPO_ROOT, 'skills')
55
+ /**
56
+ * 快照根:仓库内运行 = 真仓库根;cardbuddy 发布态 = 包内 dist/vendored(构建期内联的迷你仓库快照,
57
+ * 其下同样有 skills/ 与 mcp/,故本文件里 `../../mcp/tools.mjs` 这类相对引用两种形态都成立)。
58
+ * 它只作「随代码走的那份资源」(mcp 工具 / 技能文档)的兜底根;卡片工程根一律用中间件的 root。
59
+ */
60
+ const REPO_ROOT = SNAPSHOT_ROOT
58
61
 
59
62
  /** 单卡片最多保留的消息条数,超出从头部裁剪(避免历史无限增长撑爆上下文与磁盘) */
60
63
  const MAX_HISTORY_MESSAGES = 200
@@ -83,7 +86,15 @@ async function loadTools() {
83
86
 
84
87
  // ---- System prompt:精简协议 + 按需参考索引 ----
85
88
 
86
- const SKILL_REFS_DIR = path.join(SKILLS_DIR, 'dsl-card-generator', 'references')
89
+ /** 技能库根:项目内 skills/ 优先,缺失时回退随包发布的快照(cardbuddy 未跑 init 也能用) */
90
+ function skillsDir(root) {
91
+ return skillsDirFor(root)
92
+ }
93
+
94
+ /** dsl-card-generator 的 references 目录:系统提示词「按需参考索引」的来源 */
95
+ function skillRefsDir(root) {
96
+ return path.join(skillsDirFor(root), 'dsl-card-generator', 'references')
97
+ }
87
98
  /** 参考索引的一句话说明:够模型选对文件即可,全文不进上下文 */
88
99
  const SKILL_REF_HINTS = {
89
100
  'rules.md': '卡片生成铁律(card_write 前必读:结构 / 绑定 / 样式红线)',
@@ -121,15 +132,17 @@ let _systemPromptCache = null
121
132
  * 此前整份注入两份 SKILL.md + rules.md(约 125KB / 数万 token),上下文臃肿会显著放大
122
133
  * 推理模型的空转思考(实测单轮 reasoning 4 分钟不收敛、界面一直「思考中」)。
123
134
  */
124
- function buildSystemPrompt({ shellAllowed = false, vision = false, autoApprove = DEFAULT_AUTO_APPROVE } = {}) {
125
- const cacheKey = `${shellAllowed ? 'shell' : 'base'}${vision ? '+vision' : ''}+aa:${autoApprove}`
135
+ function buildSystemPrompt({ shellAllowed = false, vision = false, autoApprove = DEFAULT_AUTO_APPROVE, root = '' } = {}) {
136
+ const refsDir = skillRefsDir(root)
137
+ // 缓存键带上实际命中的技能库:仓库内与消费者项目(init 释放的副本)文档集可能不同
138
+ const cacheKey = `${shellAllowed ? 'shell' : 'base'}${vision ? '+vision' : ''}+aa:${autoApprove}+sk:${skillsDir(root)}`
126
139
  if (_systemPromptCache && _systemPromptCache.key === cacheKey) return _systemPromptCache.text
127
140
  // 免确认策略要如实写进协议:模型若以为「每次都要用户点确认」,会主动停下来等批准 / 把命令甩给用户
128
141
  const skillAuto = autoApproves(autoApprove, 'skill_exec')
129
142
  const shellAuto = autoApproves(autoApprove, 'shell_exec')
130
143
  const lines = []
131
144
  for (const entry of SKILL_DOC_ENTRIES) {
132
- const abs = path.join(SKILLS_DIR, entry.path)
145
+ const abs = path.join(skillsDir(root), entry.path)
133
146
  let size = 0
134
147
  try {
135
148
  size = fs.statSync(abs).size
@@ -139,10 +152,10 @@ function buildSystemPrompt({ shellAllowed = false, vision = false, autoApprove =
139
152
  lines.push(`- ${entry.path}(${Math.max(1, Math.round(size / 1024))}KB):${entry.hint}`)
140
153
  }
141
154
  try {
142
- for (const name of fs.readdirSync(SKILL_REFS_DIR)) {
155
+ for (const name of fs.readdirSync(refsDir)) {
143
156
  let st
144
157
  try {
145
- st = fs.statSync(path.join(SKILL_REFS_DIR, name))
158
+ st = fs.statSync(path.join(refsDir, name))
146
159
  } catch {
147
160
  continue
148
161
  }
@@ -151,7 +164,7 @@ function buildSystemPrompt({ shellAllowed = false, vision = false, autoApprove =
151
164
  }
152
165
  // 范式片段是子目录,不列出来模型根本不知道存在(曾经为此反推 expand_ir 源码猜字段形状)
153
166
  const snippets = fs
154
- .readdirSync(path.join(SKILL_REFS_DIR, 'recipe_snippets'))
167
+ .readdirSync(path.join(refsDir, 'recipe_snippets'))
155
168
  .filter((f) => f.endsWith('.json'))
156
169
  .sort()
157
170
  if (snippets.length) {
@@ -259,13 +272,14 @@ const CARD_PATCH_TOOL = {
259
272
  type: 'function',
260
273
  function: {
261
274
  name: 'card_patch',
262
- description: '按 JSON 点路径(数组用数字段)对卡片 schema/data 做最小修改并写回;大卡必用本工具,禁止用 card_write 整包重写。路径必须先用 card_grep 拿真实路径——schema 的节点树是 view.components[].slots[].components[](不是 view.children)。路径中段不存在会直接报错(不再静默新建分支),确要新建传 create:true。仓库根下走 card_write 管线(带历史快照与校验)。',
275
+ description: '按 JSON 点路径(数组用数字段)对卡片 schema/data 做最小修改并写回;大卡必用本工具,禁止用 card_write 整包重写。路径必须先用 card_grep 拿真实路径——schema 的节点树是 view.components[].slots[].components[](不是 view.children)。路径中段不存在会直接报错(不再静默新建分支),确要新建传 create:true。patches(改/增)与 deletes(删)至少给一个,可只删不改(清理无用样式项、多余节点)。仓库根下走 card_write 管线(带历史快照与校验)。',
263
276
  parameters: {
264
277
  type: 'object',
265
278
  properties: {
266
279
  id: { type: 'string', description: '卡片 id' },
267
280
  patches: {
268
281
  type: 'array',
282
+ description: '要写入的 {path,value} 列表;只删除时可省略(此时必须给 deletes)',
269
283
  items: {
270
284
  type: 'object',
271
285
  properties: {
@@ -275,11 +289,11 @@ const CARD_PATCH_TOOL = {
275
289
  required: ['path', 'value'],
276
290
  },
277
291
  },
278
- deletes: { type: 'array', items: { type: 'string' }, description: '要删除的 JSON 点路径' },
292
+ deletes: { type: 'array', items: { type: 'string' }, description: '要删除的 JSON 点路径;可单独使用(只删不改)' },
279
293
  target: { type: 'string', enum: ['schema', 'data'], description: '默认 schema' },
280
294
  create: { type: 'boolean', description: '路径中段不存在时是否新建分支(默认 false = 响亮报错)' },
281
295
  },
282
- required: ['id', 'patches'],
296
+ required: ['id'],
283
297
  },
284
298
  },
285
299
  }
@@ -337,16 +351,44 @@ function applyByPath(doc, p, setter, { create = false } = {}) {
337
351
  setter(cur, segs[segs.length - 1])
338
352
  }
339
353
 
340
- async function cardPatch(root, args, dispatch) {
354
+ async function cardPatch(root, args, dispatch, { allowDispatch = false } = {}) {
341
355
  const { id, patches = [], deletes = [], target = 'schema' } = args || {}
342
- if (!Array.isArray(patches) || !patches.length) throw new Error('缺少 patches')
356
+ if (!Array.isArray(patches)) throw new Error('patches 必须是数组')
357
+ if (!Array.isArray(deletes)) throw new Error('deletes 必须是数组')
358
+ // 「只删不改」是合法操作(清理无用样式项 / 多余节点);真正的错误是两个都空 —— 空补丁什么都不做,
359
+ // 却会照样走一遍 card_write 管线、平白多存一份历史快照,所以在这里响亮拒掉。
360
+ if (!patches.length && !deletes.length) throw new Error('patches 与 deletes 至少给一个(空补丁无意义)')
343
361
  const file = path.join(root, 'src', 'cards', String(id || ''), target === 'data' ? 'data.json' : 'schema.json')
344
362
  const doc = JSON.parse(fs.readFileSync(file, 'utf8'))
345
363
  const create = args.create === true
346
364
  for (const pt of patches) applyByPath(doc, pt.path, (obj, k) => (obj[k] = pt.value), { create })
347
- for (const d of deletes) applyByPath(doc, d, (obj, k) => delete obj[k], { create })
348
- // 仓库根:走 card_write 管线(历史快照 + validate/render_status);其它根(测试/自定义)直接落盘
349
- if (path.resolve(root) === REPO_ROOT && dispatch) {
365
+ // 删数组元素必须 splice:早先用 delete obj[k],在数组上只留一个 null 空洞
366
+ // (properties 变成 [null, {...}]),补丁报「成功」却把 schema 写成稀疏数组,渲染与校验随后就崩。
367
+ // 同一父数组里删多项时按原始下标从大到小删 —— splice 会让后面的索引左移,升序删会误删邻居。
368
+ const orderedDeletes = [...deletes].sort((a, b) => {
369
+ const sa = String(a).split('.')
370
+ const sb = String(b).split('.')
371
+ const pa = sa.slice(0, -1).join('.')
372
+ const pb = sb.slice(0, -1).join('.')
373
+ if (pa !== pb) return pa < pb ? -1 : 1
374
+ const na = Number(sa[sa.length - 1])
375
+ const nb = Number(sb[sb.length - 1])
376
+ return Number.isFinite(na) && Number.isFinite(nb) ? nb - na : 0
377
+ })
378
+ for (const d of orderedDeletes)
379
+ applyByPath(
380
+ doc,
381
+ d,
382
+ (obj, k) => {
383
+ if (Array.isArray(obj)) obj.splice(Number(k), 1)
384
+ else delete obj[k]
385
+ },
386
+ { create },
387
+ )
388
+ // 工程根:走 card_write 管线(历史快照 + validate/render_status);其它根(测试/自定义)直接落盘。
389
+ // 仓库内运行由「root === 仓库根」判定;cardbuddy 发布态快照根在包内、永不等于工程根,
390
+ // 故由宿主显式 allowDispatch 开启(它已把 CARD_ROOT 指到同一个工程根,dispatch 落点一致)。
391
+ if (dispatch && (allowDispatch || path.resolve(root) === REPO_ROOT)) {
350
392
  const res = await dispatch('card_write', { id, [target]: doc })
351
393
  return { ok: true, via: 'card_write', patches: patches.length, deletes: deletes.length, write: res }
352
394
  }
@@ -458,9 +500,12 @@ function resolveSkillScript(root, script) {
458
500
  if (!SKILL_SCRIPT_RE.test(rel)) {
459
501
  throw new Error(`只能执行 skills/<skill>/scripts/ 下的 .mjs / .js / .py 脚本(收到:${rel});其余命令用 shell_exec`)
460
502
  }
461
- const abs = path.resolve(root, rel)
462
- if (!abs.startsWith(path.join(root, 'skills') + path.sep)) throw new Error('非法路径:脚本必须在仓库 skills/ 目录内')
463
- if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) throw new Error(`脚本不存在:${rel}`)
503
+ // 项目内 skills/ 优先;cardbuddy 安装态尚未释放技能库时回退包内快照。
504
+ // normalizeSkillRel 拒绝绝对路径与任何 `..` 片段,resolveSkillPath 只在候选 skills 根内查找。
505
+ const clean = normalizeSkillRel(rel)
506
+ if (!clean) throw new Error('非法路径:脚本必须在仓库 skills/ 目录内')
507
+ const abs = resolveSkillPath(root, clean)
508
+ if (!abs || !fs.statSync(abs).isFile()) throw new Error(`脚本不存在:${rel}`)
464
509
  return { rel, abs, bin: path.extname(abs).toLowerCase() === '.py' ? pythonBin() : 'node' }
465
510
  }
466
511
 
@@ -1255,21 +1300,14 @@ const SKILL_DOC_EXT = new Set(['.md', '.json', '.txt', '.csv'])
1255
1300
  function skillRead(root, args) {
1256
1301
  const name = String(args?.path || '').trim().replace(/^\.?\/+/u, '')
1257
1302
  if (!name) throw new Error('缺少 path')
1258
- const refsDir = path.join(root, 'skills', 'dsl-card-generator', 'references')
1259
- const rel = name.includes('/') ? name.replace(/^skills\//, '') : path.join('dsl-card-generator', 'references', name)
1260
- const full = path.resolve(path.join(root, 'skills'), rel)
1261
- const skillsRoot = path.join(root, 'skills')
1262
- if (!full.startsWith(skillsRoot + path.sep)) throw new Error('非法路径:只能读 skills/ 目录内的文档')
1303
+ const rel = name.includes('/') ? name.replace(/^skills\//, '') : `dsl-card-generator/references/${name}`
1304
+ // 越界(绝对路径 / 任何 `..` 片段)先判非法,再谈存在性 —— 与 skill_exec 同一套闸。
1305
+ if (!normalizeSkillRel(rel)) throw new Error('非法路径:只能读 skills/ 目录内的文档')
1306
+ // 项目内 skills/ 优先,缺失时回退随包发布的技能快照(cardbuddy 安装态未跑 init 也能读文档)。
1307
+ const full = resolveSkillPath(root, rel)
1308
+ if (!full) throw new Error(`文档不存在:skills/${rel}`)
1263
1309
  if (!SKILL_DOC_EXT.has(path.extname(full).toLowerCase())) throw new Error('只能读文档(.md / .json / .txt / .csv);跑脚本用 skill_exec')
1264
- if (!fs.existsSync(full) || !fs.statSync(full).isFile()) {
1265
- // 测试根等场景下 skills/ 可能不存在:退回模块加载时的仓库 references 目录
1266
- const fallback = path.resolve(fs.existsSync(refsDir) ? refsDir : SKILL_REFS_DIR, name)
1267
- if (!name.includes('/') && fallback.startsWith(SKILL_REFS_DIR + path.sep) && fs.existsSync(fallback)) {
1268
- const text = fs.readFileSync(fallback, 'utf8')
1269
- return text.length <= SKILL_READ_MAX ? text : `${text.slice(0, SKILL_READ_MAX)}\n…(已截断,省略 ${text.length - SKILL_READ_MAX} 字符)`
1270
- }
1271
- throw new Error(`文档不存在:skills/${rel}`)
1272
- }
1310
+ if (!fs.statSync(full).isFile()) throw new Error(`文档不存在:skills/${rel}`)
1273
1311
  const text = fs.readFileSync(full, 'utf8')
1274
1312
  if (text.length <= SKILL_READ_MAX) return text
1275
1313
  return `${text.slice(0, SKILL_READ_MAX)}\n…(已截断,省略 ${text.length - SKILL_READ_MAX} 字符)`
@@ -1732,7 +1770,7 @@ function describePath(target) {
1732
1770
  * 搜索根先剪掉「被其它根包含」的子根:否则同一文件会在父 / 子根各命中一次,被误判成重名歧义而返回空。
1733
1771
  * 同一个根里出现多个同名同尺寸文件才算歧义,此时不猜。
1734
1772
  */
1735
- async function locateDropped({ name, relPath = '', size = -1 }) {
1773
+ async function locateDropped({ name, relPath = '', size = -1, root = '' }) {
1736
1774
  const fileName = String(name || '').trim()
1737
1775
  if (!fileName) return null
1738
1776
  const wantSize = Number(size) >= 0 ? Number(size) : -1
@@ -1770,6 +1808,7 @@ async function locateDropped({ name, relPath = '', size = -1 }) {
1770
1808
  pushRoot(path.join(home, 'Desktop'))
1771
1809
  pushRoot(path.join(home, 'Downloads'))
1772
1810
  pushRoot(path.join(home, 'Documents'))
1811
+ if (root) pushRoot(root)
1773
1812
  pushRoot(REPO_ROOT)
1774
1813
  pushRoot(path.dirname(REPO_ROOT))
1775
1814
  pushRoot(home)
@@ -2693,6 +2732,9 @@ function toOpenAiMessages(messages) {
2693
2732
  */
2694
2733
  export function createAiChatMiddleware(opts = {}) {
2695
2734
  const root = opts.root ? path.resolve(opts.root) : REPO_ROOT
2735
+ // card_patch 是否走 card_write 管线(历史快照 + validate/render_status):
2736
+ // 仓库内运行由「root === 仓库根」自动成立;cardbuddy 发布态快照根在包内,由宿主显式开启。
2737
+ const allowDispatch = opts.allowDispatch === true
2696
2738
  /** 对话记录落盘目录(.cache/ 已在 .gitignore):随 root 走,测试可用临时根隔离 */
2697
2739
  const chatDir = path.join(root, '.cache', 'ai-chats')
2698
2740
 
@@ -3007,6 +3049,7 @@ export function createAiChatMiddleware(opts = {}) {
3007
3049
  name: body.name,
3008
3050
  relPath: body.relPath || '',
3009
3051
  size: Number.isFinite(Number(body.size)) ? Number(body.size) : -1,
3052
+ root,
3010
3053
  })
3011
3054
  if (!found) return sendJson(req, res, 404, { error: 'not-located' })
3012
3055
  const info = describePath(found)
@@ -3093,6 +3136,7 @@ export function createAiChatMiddleware(opts = {}) {
3093
3136
  shellAllowed: Boolean(defaults.shellAllowed),
3094
3137
  vision: visionOn,
3095
3138
  autoApprove: defaults.autoApprove,
3139
+ root,
3096
3140
  })
3097
3141
  } catch (e) {
3098
3142
  return sendJson(req, res, 500, { error: 'tools-load-failed', message: String((e && e.message) || e) })
@@ -3540,7 +3584,7 @@ export function createAiChatMiddleware(opts = {}) {
3540
3584
  result = cardGrep(root, parsedArgs)
3541
3585
  break
3542
3586
  case 'card_patch':
3543
- result = await cardPatch(root, parsedArgs, dispatch)
3587
+ result = await cardPatch(root, parsedArgs, dispatch, { allowDispatch })
3544
3588
  break
3545
3589
  case 'fs_read':
3546
3590
  result = fsRead(root, parsedArgs, mountedRoots)
@@ -1,4 +1,4 @@
1
- import { devApiBase } from './_vendor/finmall-env.mjs'
1
+ import { devApiBase } from '../../tools/finmall-env.mjs'
2
2
 
3
3
  /**
4
4
  * 共享浏览器单例 + 卡片页面加载(screenshot.js / bbox.js 共用)。
@@ -4,12 +4,14 @@ import { pathToFileURL } from 'node:url'
4
4
  import { fileURLToPath } from 'node:url'
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
6
6
 
7
- const skillDir = path.resolve(process.cwd(), 'skills', 'finmall-card-sync')
8
- const skillPath = path.join(skillDir, 'scripts', 'api_batch_sync.mjs')
9
7
  import { recordExport } from './cardImportMeta.js'
10
8
  import { checkPushAllowed } from './cardListCache.js'
11
9
  import { assertRemoteSource, findCardDir } from './cardSidecar.js'
12
10
  import { log } from './logger.js'
11
+ import { resolveSkillPathOrProject } from './skillPaths.js'
12
+
13
+ /** 远端保存脚本相对 skills/ 的路径(与「同步」共用 finmall-card-sync) */
14
+ const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
13
15
 
14
16
  /**
15
17
  * 远端保存(写回 FinMall)服务端处理。
@@ -94,7 +96,10 @@ export function createSaveMiddleware({ root }) {
94
96
  // 骨架屏:线上编辑器始终传 SVG 缩略图;本地无生成器,传空串(仅影响编辑器列表缩略图)。
95
97
  const skeletonScreen = typeof payload.skeletonScreen === 'string' ? payload.skeletonScreen : ''
96
98
  try {
97
- const mod = await import(pathToFileURL(skillPath).href)
99
+ // 脚本按「项目根 skills/ → 随包技能快照」解析:cardbuddy 安装态无 skills/ 时也能保存。
100
+ const mod = await import(
101
+ pathToFileURL(resolveSkillPathOrProject(root || process.cwd(), SYNC_SCRIPT_REL)).href
102
+ )
98
103
  const { response, verified } = await mod.saveCardToRemote(schema, { skeletonScreen })
99
104
  // 导出可追溯(Phase E):把远端保存的读回校验结果写进导入元数据侧车。
100
105
  // 注意:目录按 cardCode 命名,必须按 pageId 反查到正确的 src/cards/<cardCode>/。
@@ -5,6 +5,10 @@ import { writeFile, mkdir } from 'node:fs/promises'
5
5
  import { checkPushAllowed } from './cardListCache.js'
6
6
  import { assertRemoteSource } from './cardSidecar.js'
7
7
  import { log, fmtBytes } from './logger.js'
8
+ import { resolveSkillPathOrProject } from './skillPaths.js'
9
+
10
+ /** finmall-card-sync 的批量同步脚本(同步 / 远端预览共用),相对 skills/ */
11
+ const SYNC_SCRIPT_REL = 'finmall-card-sync/scripts/api_batch_sync.mjs'
8
12
 
9
13
  /**
10
14
  * 预览「同步远程数据」按钮的服务端处理:经已认证的 FinMall 浏览器会话拉取最新卡片写回 src/cards。
@@ -34,13 +38,7 @@ export function createSyncMiddleware({ root }) {
34
38
  return res.end(JSON.stringify({ error: 'invalid json', message: String(e.message) }))
35
39
  }
36
40
  try {
37
- const skillPath = path.join(
38
- root,
39
- 'skills',
40
- 'finmall-card-sync',
41
- 'scripts',
42
- 'api_batch_sync.mjs'
43
- )
41
+ const skillPath = resolveSkillPathOrProject(root, SYNC_SCRIPT_REL)
44
42
  const { syncCards } = await import(pathToFileURL(skillPath).href)
45
43
  const summary = await syncCards({
46
44
  cardCode: payload.cardCode || undefined,
@@ -160,13 +158,7 @@ export function createPreviewMiddleware({ root }) {
160
158
  }
161
159
  }
162
160
  try {
163
- const skillPath = path.join(
164
- root,
165
- 'skills',
166
- 'finmall-card-sync',
167
- 'scripts',
168
- 'api_batch_sync.mjs'
169
- )
161
+ const skillPath = resolveSkillPathOrProject(root, SYNC_SCRIPT_REL)
170
162
  const { previewCardRemote } = await import(pathToFileURL(skillPath).href)
171
163
  const result = await previewCardRemote(payload.schema)
172
164
  log.ok('远端预览完成', {
@@ -5,6 +5,11 @@ import { execFile, execFileSync } from 'node:child_process'
5
5
  import { fileURLToPath } from 'node:url'
6
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
7
7
 
8
+ import { resolveSkillPathOrProject } from './skillPaths.js'
9
+
10
+ /** 校验脚本相对 skills/ 的固定路径(导出供 MCP 复用) */
11
+ const VALIDATE_SCRIPT_REL = 'dsl-card-generator/scripts/validate_schema.py'
12
+
8
13
  /**
9
14
  * 契约校验端点(L0 闭环之一)。
10
15
  *
@@ -18,15 +23,13 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
18
23
  * @param {{ root: string }} opts
19
24
  */
20
25
 
21
- /** 校验脚本在仓库根下的固定相对路径(导出供 MCP 复用) */
26
+ /**
27
+ * 校验脚本绝对路径(导出供 MCP 复用)。
28
+ * 优先项目根下的 skills/;缺失时回退随包发布的技能快照 —— cardbuddy 安装态即便
29
+ * 没跑过 `cardbuddy init`(项目里还没有 skills/)也能正常校验。
30
+ */
22
31
  export function scriptPath(root) {
23
- return path.join(
24
- root,
25
- 'skills',
26
- 'dsl-card-generator',
27
- 'scripts',
28
- 'validate_schema.py',
29
- )
32
+ return resolveSkillPathOrProject(root, VALIDATE_SCRIPT_REL)
30
33
  }
31
34
 
32
35
  /** 解析可用的 python 解释器;找不到返回 null(导出供 MCP 复用) */