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
@@ -8,8 +8,8 @@
8
8
  <link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16-Cg6Nrosk.png" />
9
9
  <link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32-B_1744sp.png" />
10
10
  <link rel="apple-touch-icon" sizes="180x180" href="/assets/apple-touch-icon-BQcES9QK.png" />
11
- <script type="module" crossorigin src="/assets/index-jl4YJzKv.js"></script>
12
- <link rel="stylesheet" crossorigin href="/assets/index-B4VZgNSK.css">
11
+ <script type="module" crossorigin src="/assets/index-CHuGGIR3.js"></script>
12
+ <link rel="stylesheet" crossorigin href="/assets/index-CQScMhyg.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="app"></div>
@@ -430,12 +430,20 @@ export async function startServer(userConfig = {}) {
430
430
  subscribeLog((lines) => events.notify({ lines }, 'service-log'))
431
431
 
432
432
  const vite = await createEmbeddedVite(config)
433
+ // AI 工具调度复用仓库的 mcp/tools.mjs(发布态为包内快照),其 mcp/client.mjs 在 import 时
434
+ // 读取 CARD_ROOT(卡片工程根)与 CARD_API_BASE(dev server 基址)。loadTools() 是懒加载
435
+ // (首次 AI 请求才 import),故这里在启动期注入即可;已有的显式环境变量优先,不覆盖。
436
+ if (!process.env.CARD_ROOT) process.env.CARD_ROOT = config.root
437
+ if (!process.env.CARD_API_BASE) process.env.CARD_API_BASE = apiBase
433
438
  // upstream(FINMALL_BASE_URL)透传给资源代理中间件,使其能回源 FinMall 取站内资源
434
439
  mountCoreMiddlewares(app, {
435
440
  root: config.root,
436
441
  viteServer: vite,
437
442
  upstream: config.upstream,
438
443
  notify: events.notify,
444
+ // card_patch 走 card_write 管线(历史快照 + validate/render_status):本服务的 root 就是
445
+ // 卡片工程根,且 CARD_ROOT 已指向同一处,dispatch 落点一致,故显式开启。
446
+ allowDispatch: true,
439
447
  // 代码桥单文件监听注入 chokidar 版:原子保存(改名覆盖)由 add 兜住,awaitWriteFinish 防半截读
440
448
  watchFile: (file, onChange) => {
441
449
  const watcher = chokidar.watch(file, {
@@ -0,0 +1,537 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { dirname, resolve } from 'node:path'
5
+ import { listCards, readCardFiles, writeCardFiles } from '../server/core/cardFiles.js'
6
+ import { validateCard, resolvePython, scriptPath } from '../server/core/cardValidate.js'
7
+ import { listSnapshots, saveSnapshot, HISTORY_DIR } from '../server/core/cardHistory.js'
8
+ import { devApiBase } from '../tools/finmall-env.mjs'
9
+
10
+ /**
11
+ * MCP 薄 HTTP 客户端:只封装对 dev server(端口见 .env.cb 的 FINMALL_DEV_PORT)的请求,不复制任何业务逻辑。
12
+ *
13
+ * 降级策略(与 tools/card-check.mjs 一致):
14
+ * - list / read / write / validate:dev server 未启动(fetch 抛连接错误)时,
15
+ * 降级为直接读盘 / 写盘 / 跑 Python —— 复用 server/* 导出的同一份实现,避免漂移。
16
+ * - render_status / sync / save:依赖 dev server 与 FinMall 登录态,不支持离线降级;
17
+ * screenshot 在 dev server 未启动时降级到独立离线渲染器。
18
+ */
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url))
21
+ const ROOT = process.env.CARD_ROOT ? resolve(process.env.CARD_ROOT) : resolve(__dirname, '..')
22
+ // dev server 端口由 .env.cb 的 FINMALL_DEV_PORT 控制(默认 5180);
23
+ // 仍可用 CARD_API_BASE 覆盖(如 dev server 跑在非默认端口)。
24
+ const BASE = (process.env.CARD_API_BASE || devApiBase()).replace(/\/$/, '')
25
+
26
+ /** 发起 GET,返回 { status, buf, text, json, contentType };任何网络错误向上抛,由调用方决定降级 */
27
+ async function httpGet(p) {
28
+ const res = await fetch(BASE + p, { headers: { Accept: 'application/json, image/png' } })
29
+ const buf = Buffer.from(await res.arrayBuffer())
30
+ let text = null
31
+ let json = null
32
+ try {
33
+ text = buf.toString('utf8')
34
+ json = text ? JSON.parse(text) : null
35
+ } catch {
36
+ /* 非 JSON 响应(如截图 png) */
37
+ }
38
+ return { status: res.status, buf, text, json, contentType: res.headers.get('content-type') || '' }
39
+ }
40
+
41
+ /** 发起 POST(JSON body) */
42
+ async function httpPost(p, body) {
43
+ const res = await fetch(BASE + p, {
44
+ method: 'POST',
45
+ headers: { 'Content-Type': 'application/json' },
46
+ body: JSON.stringify(body || {}),
47
+ })
48
+ const buf = Buffer.from(await res.arrayBuffer())
49
+ let text = null
50
+ let json = null
51
+ try {
52
+ text = buf.toString('utf8')
53
+ json = text ? JSON.parse(text) : null
54
+ } catch {
55
+ /* 非 JSON 响应 */
56
+ }
57
+ return { status: res.status, buf, text, json, contentType: res.headers.get('content-type') || '' }
58
+ }
59
+
60
+ // 需要 FinMall 登录态的远端工具:提示语要额外说明「且已登录 FinMall」。
61
+ const REMOTE_TOOLS = new Set(['card_sync', 'card_save', 'card_slice_upload'])
62
+
63
+ const DEV_REQUIRED = (tool) => ({
64
+ source: null,
65
+ error: 'dev-server-required',
66
+ message: `${tool} 需要 dev server 已启动(npm run dev)${REMOTE_TOOLS.has(tool) ? ' 且已登录 FinMall' : ''}。本工具不支持离线降级。`,
67
+ })
68
+
69
+ // ---- card_list ----
70
+ export async function cardList() {
71
+ try {
72
+ const { status, json } = await httpGet('/__cards')
73
+ if (status === 200 && json && Array.isArray(json.cards)) return { source: 'http', cards: json.cards }
74
+ } catch {
75
+ /* 降级 */
76
+ }
77
+ return { source: 'local', cards: listCards(ROOT) }
78
+ }
79
+
80
+ // ---- card_read ----
81
+ export async function cardRead(id) {
82
+ try {
83
+ const { status, json } = await httpGet(`/__card/${encodeURIComponent(id)}`)
84
+ if (status === 200 && json) return { source: 'http', ...json }
85
+ } catch {
86
+ /* 降级 */
87
+ }
88
+ const r = readCardFiles(ROOT, id)
89
+ if (!r) return { source: 'local', error: 'card not found', id }
90
+ return { source: 'local', ...r }
91
+ }
92
+
93
+ // ---- card_write(闭环收口:写前自动留历史快照,写完自动校验)----
94
+ export async function cardWrite(id, payload, { verify = true, snapshot = true } = {}) {
95
+ const dir = path.join(ROOT, 'src', 'cards', id)
96
+ let writeResult = null
97
+ let source = 'local'
98
+
99
+ // 安全网:写卡前把「当前版本 schema」落一张历史快照(与编辑器改卡先 POST /__history 同语义),
100
+ // 使 AI 编辑同样可被 card_rollback 撤销。card_rollback 内部写回传 snapshot:false 避免重复留快照。
101
+ let safetyTs = null
102
+ if (snapshot && payload && payload.schema) {
103
+ try {
104
+ const { status, json } = await httpGet(`/__card/${encodeURIComponent(id)}`)
105
+ if (status === 200 && json && json.schema) {
106
+ const h = await httpPost(`/__history/${encodeURIComponent(id)}`, { schema: json.schema })
107
+ if (h.status === 200 && h.json && h.json.ok) safetyTs = h.json.ts
108
+ }
109
+ } catch {
110
+ /* dev server 不在线,走本地降级 */
111
+ }
112
+ if (safetyTs == null) {
113
+ try {
114
+ const sp = path.join(dir, 'schema.json')
115
+ if (fs.existsSync(sp)) {
116
+ const prev = JSON.parse(fs.readFileSync(sp, 'utf8'))
117
+ safetyTs = saveSnapshot(dir, prev).ts
118
+ }
119
+ } catch {
120
+ /* 首次写卡(无旧 schema)或快照失败不阻断写卡 */
121
+ }
122
+ }
123
+ }
124
+
125
+ try {
126
+ const { status, json } = await httpPost(`/__card/${encodeURIComponent(id)}`, payload)
127
+ if (status === 200 && json) {
128
+ writeResult = json
129
+ source = 'http'
130
+ } else if (status === 404) {
131
+ return { source: 'http', error: 'card not found', id }
132
+ } else if (status === 400) {
133
+ return { source: 'http', error: json?.error || 'invalid request', detail: json }
134
+ }
135
+ } catch {
136
+ /* 降级 */
137
+ }
138
+
139
+ if (!writeResult) {
140
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
141
+ return { source: 'local', error: 'card not found', id }
142
+ }
143
+ try {
144
+ writeResult = { ok: true, written: writeCardFiles(dir, payload) }
145
+ try { await import('../server/core/cardImportMeta.js').then((m) => m.markDirty(dir, true)) } catch { /* 无侧车时忽略 */ }
146
+ } catch (e) {
147
+ return { source: 'local', error: 'write failed', message: String(e && e.message) }
148
+ }
149
+ }
150
+
151
+ const verifyResult = { validate: null, renderStatus: null }
152
+ if (verify) {
153
+ if (source === 'http') {
154
+ try {
155
+ const v = await httpGet(`/__validate/${encodeURIComponent(id)}`)
156
+ if (v.status === 200 && v.json) verifyResult.validate = v.json
157
+ } catch {
158
+ /* 忽略 */
159
+ }
160
+ try {
161
+ const r = await httpGet(`/__render-status/${encodeURIComponent(id)}`)
162
+ if (r.status === 200 && r.json) verifyResult.renderStatus = r.json
163
+ } catch {
164
+ /* 忽略 */
165
+ }
166
+ } else {
167
+ // 本地降级:只能跑 Python 硬校验;render_status 需 dev server(SSR),标注 skipped
168
+ const python = resolvePython()
169
+ const sp = path.join(dir, 'schema.json')
170
+ if (python && fs.existsSync(sp)) {
171
+ verifyResult.validate = await validateCard(python, scriptPath(ROOT), sp, { checkData: true })
172
+ } else if (!python) {
173
+ verifyResult.validate = { ok: false, errors: ['python-not-found'], warnings: [], toolError: 'python-not-found' }
174
+ }
175
+ verifyResult.renderStatus = { skipped: true, reason: 'dev server 未启动,无法 SSR 自检;请先 npm run dev' }
176
+ }
177
+ }
178
+
179
+ return {
180
+ source,
181
+ written: writeResult.written,
182
+ ...(safetyTs != null ? { safetySnapshotTs: safetyTs } : {}),
183
+ ...(verify ? { verify: verifyResult } : {}),
184
+ }
185
+ }
186
+
187
+ // ---- card_validate ----
188
+ export async function cardValidate(id, { noData = false } = {}) {
189
+ try {
190
+ const { status, json } = await httpGet(`/__validate/${encodeURIComponent(id)}${noData ? '?no-data' : ''}`)
191
+ if (status === 200 && json) return { source: 'http', ...json }
192
+ if (status === 404) return { source: 'http', error: 'card not found', id }
193
+ } catch {
194
+ /* 降级 */
195
+ }
196
+ const dir = path.join(ROOT, 'src', 'cards', id)
197
+ const sp = path.join(dir, 'schema.json')
198
+ if (!fs.existsSync(sp)) return { source: 'local', error: 'card not found', id }
199
+ const python = resolvePython()
200
+ if (!python) {
201
+ return {
202
+ source: 'local',
203
+ error: 'python-not-found',
204
+ message: '未找到 python3/python,请安装 Python 或设置 DSL_PYTHON 环境变量',
205
+ }
206
+ }
207
+ const r = await validateCard(python, scriptPath(ROOT), sp, { checkData: !noData })
208
+ return { source: 'local', id, ...r }
209
+ }
210
+
211
+ // ---- card_render_status(需 dev server)----
212
+ export async function cardRenderStatus(id) {
213
+ try {
214
+ const { status, json } = await httpGet(`/__render-status/${encodeURIComponent(id)}`)
215
+ if (status === 200 && json) return { source: 'http', ...json }
216
+ if (status === 404) return { source: 'http', error: 'card not found', id }
217
+ } catch {
218
+ /* 降级不可用 */
219
+ }
220
+ return DEV_REQUIRED('card_render_status')
221
+ }
222
+
223
+ // ---- card_node_models(需 dev server,Task #5 暴露的字段结构树)----
224
+ export async function cardNodeModels(id) {
225
+ try {
226
+ const { status, json } = await httpGet(`/__node-models/${encodeURIComponent(id)}`)
227
+ if (status === 200 && json) return { source: 'http', ...json }
228
+ if (status === 404) return { source: 'http', error: 'card not found', id }
229
+ } catch {
230
+ /* 降级不可用 */
231
+ }
232
+ return DEV_REQUIRED('card_node_models')
233
+ }
234
+
235
+ // ---- card_import_meta(需 dev server)----
236
+ export async function cardImportMeta(id) {
237
+ try {
238
+ const { status, json } = await httpGet(`/__import-meta/${encodeURIComponent(id)}`)
239
+ if (status === 200 && json) return { source: 'http', ...json }
240
+ if (status === 404) return { source: 'http', error: 'import-meta not found', id, hint: json?.hint }
241
+ } catch {
242
+ /* 降级不可用 */
243
+ }
244
+ return DEV_REQUIRED('card_import_meta')
245
+ }
246
+
247
+ // ---- card_route_input(离线:直接复用 skill 的输入路由判定器,无需 dev server)----
248
+ /** 展开 `~` 与工作区相对路径:MCP 调用方常给 `~/Downloads/...` 或相对工程根的路径 */
249
+ function expandInputPath(p) {
250
+ const raw = String(p == null ? '' : p).trim()
251
+ if (!raw) return raw
252
+ if (raw === '~') return process.env.HOME || raw
253
+ if (raw.startsWith('~/') || raw.startsWith('~\\')) return path.join(process.env.HOME || '', raw.slice(2))
254
+ return path.isAbsolute(raw) ? raw : resolve(ROOT, raw)
255
+ }
256
+
257
+ export async function cardRouteInput({ path: inputPath } = {}) {
258
+ if (!inputPath) {
259
+ return {
260
+ source: 'local',
261
+ error: 'missing-args',
262
+ message: 'path 必填:MeaXure 标注导出目录 / UI 要素包 / 卡片目录 / 设计稿 PNG',
263
+ }
264
+ }
265
+ const mod = await import('../skills/dsl-card-generator/scripts/route_input.mjs')
266
+ const r = mod.routeInput(expandInputPath(inputPath))
267
+ if (r.error) return { source: 'local', ...r }
268
+ const hint =
269
+ r.kind === 'meaxure-export' && r.preprocess && r.preprocess.needed
270
+ ? '先执行 next 里的两条命令跑 sketch-spec-to-card 前处理,产出 UI/<稿名>/ 要素包后再逐卡生成;禁止直接读 preview/ 整图建卡'
271
+ : r.kind === 'meaxure-export'
272
+ ? '该导出已前处理过,直接消费 next 给出的存量产物,不重复跑管线'
273
+ : r.kind === 'unknown'
274
+ ? '判不出输入形态:向用户确认,不要猜(CLI 等价退出码 3)'
275
+ : '按 next 的命令继续(Workflow A 建卡 / Workflow B 改卡)'
276
+ return { source: 'local', ok: r.kind !== 'unknown', ...r, hint }
277
+ }
278
+
279
+ // ---- card_scaffold(离线:直接调 scaffold lib,无需 dev server)----
280
+ export async function cardScaffold({ intent, id, name, outBase, platform, force, from } = {}) {
281
+ const lib = await import('../skills/dsl-card-generator/scripts/lib/scaffold.mjs')
282
+ if (!from && (!intent || !lib.SCAFFOLD_INTENTS.includes(intent))) {
283
+ return { error: 'invalid-intent', message: `intent 必须是 ${lib.SCAFFOLD_INTENTS.join('|')}`, intent }
284
+ }
285
+ if (!id) return { error: 'missing-id', message: '缺少 id' }
286
+ const base = outBase ? resolve(outBase) : path.join(ROOT, 'src', 'cards')
287
+ if (force) {
288
+ const dir = path.join(base, id)
289
+ if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true })
290
+ }
291
+ try {
292
+ if (from) {
293
+ const dir = lib.cloneCardFrom(from, id, { name, outBase: base })
294
+ return { source: 'local', ok: true, from, id, name: name || undefined, dir, note: '整卡模板克隆:pageId/name 已重写,封面未复制(定稿后重新渲染生成)' }
295
+ }
296
+ const dir = lib.writeSkeleton(id, intent, { name, platform, outBase: base })
297
+ return { source: 'local', ok: true, intent, id, name: name || '新建卡片', dir }
298
+ } catch (e) {
299
+ return { source: 'local', error: 'scaffold-failed', message: String((e && e.message) || e) }
300
+ }
301
+ }
302
+
303
+ // ---- card_add_component(离线:直接调 scaffold lib + 离线硬校验)----
304
+ /** 归一化 --set-prop 风格的属性覆盖:支持 "key=value" 字符串数组或 {key,value} 对象数组 */
305
+ function normalizeSetProps(setProps) {
306
+ const list = Array.isArray(setProps) ? setProps : setProps ? [setProps] : []
307
+ return list.map((p) => {
308
+ if (typeof p === 'string') {
309
+ const i = p.indexOf('=')
310
+ if (i < 0) throw new Error(`setProps 需 "key=value" 形式:${p}`)
311
+ const key = p.slice(0, i).trim()
312
+ const value = p.slice(i + 1)
313
+ if (!key) throw new Error(`setProps 缺 key:${p}`)
314
+ return { key, value, isVar: value.startsWith('$') }
315
+ }
316
+ const key = String((p && p.key) || '').trim()
317
+ const value = p ? String(p.value ?? '') : ''
318
+ if (!key) throw new Error('setProps 元素缺 key')
319
+ return { key, value, isVar: value.startsWith('$') }
320
+ })
321
+ }
322
+
323
+ export async function cardAddComponent({ id, parent, className, text, click, setProps } = {}) {
324
+ if (!id || !parent || !className) {
325
+ return { error: 'missing-args', message: 'id / parent / className 必填' }
326
+ }
327
+ const lib = await import('../skills/dsl-card-generator/scripts/lib/scaffold.mjs')
328
+ let r
329
+ let propOverrides = []
330
+ try {
331
+ // insertComponent 签名是 (id, opts),与 CLI add_component.mjs 保持一致
332
+ propOverrides = normalizeSetProps(setProps)
333
+ r = lib.insertComponent(id, { parentId: parent, className, text, click, propOverrides })
334
+ } catch (e) {
335
+ return {
336
+ source: 'local',
337
+ error: 'insert-failed',
338
+ message: String((e && e.message) || e),
339
+ hint: '确认 parent 是容器节点(有 slots,或 ListLayout/GridLayout);用 card_read 查 componentId',
340
+ }
341
+ }
342
+ // 插入成功后离线硬校验,把结果一并带回(python 缺失时 validate 会标注 python-not-found)
343
+ const validate = await cardValidate(id, { noData: false })
344
+ return {
345
+ source: 'local',
346
+ ok: true,
347
+ inserted: { componentId: r.id, className: r.className, parentId: r.parentId, parentClassName: r.parentClassName },
348
+ setProps: r.setProps,
349
+ dir: r.dir,
350
+ validate,
351
+ }
352
+ }
353
+
354
+ // ---- card_screenshot(优先 dev server;未启动时降级到独立渲染器)----
355
+ export async function cardScreenshot(id, { format = 'json', width = 390 } = {}) {
356
+ try {
357
+ const { status, buf, json, contentType } = await httpGet(
358
+ `/__screenshot/${encodeURIComponent(id)}?width=${width}&format=${format}`,
359
+ )
360
+ if (status === 200) {
361
+ if (format === 'json') return { source: 'http', ...(json || {}) }
362
+ return { source: 'http', mime: contentType || 'image/png', base64: buf.toString('base64') }
363
+ }
364
+ if (status === 404) return { source: 'http', error: 'card not found', id }
365
+ return { source: 'http', error: json?.error || 'screenshot-failed', message: json?.message, fix: json?.fix }
366
+ } catch {
367
+ /* dev server 不可用:走独立离线渲染器 */
368
+ }
369
+ try {
370
+ const { renderCardScreenshot } = await import('../skills/dsl-card-generator/scripts/render_card.mjs')
371
+ const r = await renderCardScreenshot({ id, width, format })
372
+ if (r.error) return { source: 'standalone', error: r.error, message: r.message }
373
+ return { source: 'standalone', ...(format === 'json' ? r : { mime: r.mime, base64: r.base64 }) }
374
+ } catch (e) {
375
+ return DEV_REQUIRED('card_screenshot')
376
+ }
377
+ }
378
+
379
+ // ---- card_sync(需 dev server + FinMall 登录态)----
380
+ export async function cardSync({ cardCode } = {}) {
381
+ try {
382
+ const { status, json } = await httpPost('/__sync', { cardCode })
383
+ if (status === 200) return { source: 'http', ...(json || {}) }
384
+ return { source: 'http', error: json?.error || 'sync-failed', message: json?.message }
385
+ } catch {
386
+ /* 降级不可用 */
387
+ }
388
+ return DEV_REQUIRED('card_sync')
389
+ }
390
+
391
+ // ---- card_save(需 dev server + FinMall 登录态)----
392
+ export async function cardSave({ schema, code, skeletonScreen } = {}) {
393
+ try {
394
+ const { status, json } = await httpPost('/__save', { schema, code, skeletonScreen })
395
+ if (status === 200) return { source: 'http', ...(json || {}) }
396
+ return { source: 'http', error: json?.error || 'save-failed', message: json?.message }
397
+ } catch {
398
+ /* 降级不可用 */
399
+ }
400
+ return DEV_REQUIRED('card_save')
401
+ }
402
+
403
+ // ---- card_slice_upload(需 dev server + FinMall 登录态)----
404
+ // 切图上传只按单卡范围执行:端点内部复用 finmall-card-sync 的 uploadSlices({ cardId }),
405
+ // 上传 src/slices 里该卡引用的图并把 schema/data 的 /slices/ 引用改写为 @filestore/ 线上地址。
406
+ export async function sliceUpload({ cardId, force } = {}) {
407
+ try {
408
+ const { status, json } = await httpPost('/__slice-upload', { cardId, force })
409
+ if (status === 200) return { source: 'http', ...(json || {}) }
410
+ return { source: 'http', error: json?.error || 'slice-upload-failed', message: json?.message }
411
+ } catch {
412
+ /* 降级不可用 */
413
+ }
414
+ return DEV_REQUIRED('card_slice_upload')
415
+ }
416
+
417
+ // ---- 历史快照:内部共用读取 ----
418
+ // 与编辑器撤销/重做同源(.history/ 由 cardWrite/编辑器写卡时自动累积)。
419
+ // card_history 只返回元数据(ts/时间/字节数/节点数),不把整份 schema 倒进上下文。
420
+
421
+ /** 递归统计节点数(view 即根节点;子节点双通道:.components 直连 + .slots[].components 插槽) */
422
+ function countNodes(node) {
423
+ if (!node || typeof node !== 'object') return 0
424
+ let n = 1
425
+ for (const c of Array.isArray(node.components) ? node.components : []) n += countNodes(c)
426
+ for (const s of Array.isArray(node.slots) ? node.slots : []) {
427
+ for (const c of Array.isArray(s && s.components) ? s.components : []) n += countNodes(c)
428
+ }
429
+ return n
430
+ }
431
+
432
+ function snapshotMeta(s) {
433
+ const schema = s.schema != null ? s.schema : s
434
+ return {
435
+ ts: s.ts,
436
+ time: new Date(s.ts).toISOString(),
437
+ bytes: Buffer.byteLength(JSON.stringify(schema), 'utf8'),
438
+ components: countNodes(schema.view),
439
+ }
440
+ }
441
+
442
+ /** 读取全部快照 [{ts, schema}](旧→新)。优先 dev server,离线降级直接读 .history/ */
443
+ async function readSnapshots(id) {
444
+ try {
445
+ const { status, json } = await httpGet(`/__history/${encodeURIComponent(id)}`)
446
+ if (status === 200 && json && Array.isArray(json.snapshots)) {
447
+ return { source: 'http', snapshots: json.snapshots.map((s) => ({ ts: s.ts, schema: s.schema })) }
448
+ }
449
+ if (status === 404) return { source: 'http', error: 'card not found', id }
450
+ } catch {
451
+ /* 降级 */
452
+ }
453
+ const dir = path.join(ROOT, 'src', 'cards', id)
454
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
455
+ return { source: 'local', error: 'card not found', id }
456
+ }
457
+ const histDir = path.join(dir, HISTORY_DIR)
458
+ const out = []
459
+ for (const s of listSnapshots(histDir)) {
460
+ try {
461
+ const data = JSON.parse(fs.readFileSync(path.join(histDir, s.file), 'utf8'))
462
+ out.push({ ts: s.ts, schema: data.schema != null ? data.schema : data })
463
+ } catch {
464
+ /* 单文件损坏跳过 */
465
+ }
466
+ }
467
+ return { source: 'local', snapshots: out }
468
+ }
469
+
470
+ // ---- card_history(列出编辑历史快照元数据)----
471
+ export async function cardHistory(id) {
472
+ const r = await readSnapshots(id)
473
+ if (r.error) return r
474
+ const snapshots = r.snapshots.map(snapshotMeta)
475
+ return {
476
+ source: r.source,
477
+ id,
478
+ count: snapshots.length,
479
+ latestTs: snapshots.length ? snapshots[snapshots.length - 1].ts : null,
480
+ snapshots,
481
+ hint: snapshots.length
482
+ ? '用 card_rollback 回滚到指定 ts(默认 latestTs);回滚前会自动把当前版本另存为新快照,回滚本身可撤销。'
483
+ : '暂无历史快照——快照在编辑器或 card_write 每次写卡时自动累积。',
484
+ }
485
+ }
486
+
487
+ // ---- card_rollback(回滚到指定快照,默认最新)----
488
+ export async function cardRollback(id, { ts, verify = true } = {}) {
489
+ const r = await readSnapshots(id)
490
+ if (r.error) return r
491
+ if (!r.snapshots.length) {
492
+ return { source: r.source, id, error: 'no-history', message: '该卡片暂无历史快照,无法回滚。' }
493
+ }
494
+ const target = ts != null ? r.snapshots.find((s) => s.ts === Number(ts)) : r.snapshots[r.snapshots.length - 1]
495
+ if (!target) {
496
+ return {
497
+ source: r.source,
498
+ id,
499
+ error: 'snapshot-not-found',
500
+ message: `快照 ts=${ts} 不存在。`,
501
+ available: r.snapshots.map((s) => s.ts),
502
+ }
503
+ }
504
+
505
+ // 安全网:先把「回滚前的当前版本」另存为新快照,保证回滚本身可撤销。
506
+ let safetyTs = null
507
+ const current = await cardRead(id)
508
+ if (current && current.schema && typeof current.schema === 'object') {
509
+ if (r.source === 'http') {
510
+ try {
511
+ const { status, json } = await httpPost(`/__history/${encodeURIComponent(id)}`, { schema: current.schema })
512
+ if (status === 200 && json && json.ok) safetyTs = json.ts
513
+ } catch {
514
+ /* 安全快照失败不阻断回滚,但会在结果中标注 */
515
+ }
516
+ } else {
517
+ try {
518
+ safetyTs = saveSnapshot(path.join(ROOT, 'src', 'cards', id), current.schema).ts
519
+ } catch {
520
+ /* 同上 */
521
+ }
522
+ }
523
+ }
524
+
525
+ const written = await cardWrite(id, { schema: target.schema }, { verify, snapshot: false })
526
+ if (written.error) return { source: written.source, id, error: written.error, message: written.message }
527
+ return {
528
+ source: written.source,
529
+ id,
530
+ rolledBackTo: target.ts,
531
+ rolledBackToTime: new Date(target.ts).toISOString(),
532
+ safetySnapshotTs: safetyTs,
533
+ ...(safetyTs == null ? { warning: '回滚前版本未能另存为安全快照,本次回滚不可通过快照撤销。' } : {}),
534
+ written: written.written,
535
+ ...(verify ? { verify: written.verify } : {}),
536
+ }
537
+ }