cardbuddy 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +21 -6
  2. package/dist/cli.js +17 -6
  3. package/dist/config.js +20 -2
  4. package/dist/portGuard.js +293 -0
  5. package/dist/preview-editor/assets/index-B4VZgNSK.css +1 -0
  6. package/dist/preview-editor/assets/index-jl4YJzKv.js +180 -0
  7. package/dist/preview-editor/index.html +2 -2
  8. package/dist/server-core/_vendor/finmall-env.mjs +4 -4
  9. package/dist/server-core/ai-chat.js +3710 -0
  10. package/dist/server-core/assetProxy.js +3 -22
  11. package/dist/server-core/buildDataFromSchema.js +36 -4
  12. package/dist/server-core/cardFiles.js +95 -2
  13. package/dist/server-core/cardImage.js +14 -0
  14. package/dist/server-core/cardListCache.js +0 -17
  15. package/dist/server-core/cardSave.js +2 -0
  16. package/dist/server-core/cardValidate.js +3 -0
  17. package/dist/server-core/index.js +6 -5
  18. package/dist/server-core/logStream.js +1 -6
  19. package/dist/server-core/sliceUpload.js +8 -1
  20. package/dist/standalone.js +24 -8
  21. package/dist/vendored/App.vue +405 -125
  22. package/dist/vendored/api.js +4 -4
  23. package/dist/vendored/assets/svgIcons/icons.svg +28 -0
  24. package/dist/vendored/components/AiSettings.vue +392 -0
  25. package/dist/vendored/components/CardGallery.vue +51 -5
  26. package/dist/vendored/components/ChatComposer.vue +413 -0
  27. package/dist/vendored/components/ChatMessage.vue +383 -0
  28. package/dist/vendored/components/ChatPanel.vue +937 -0
  29. package/dist/vendored/components/ComponentPalette.vue +1 -1
  30. package/dist/vendored/components/DebugPanel.vue +732 -90
  31. package/dist/vendored/components/Dropdown.vue +4 -2
  32. package/dist/vendored/components/MarkdownBody.vue +64 -0
  33. package/dist/vendored/components/NewCardDialog.vue +132 -0
  34. package/dist/vendored/components/NumberStepper.vue +125 -0
  35. package/dist/vendored/components/ProgressSlider.vue +9 -1
  36. package/dist/vendored/components/RemotePreview.vue +1 -1
  37. package/dist/vendored/components/StructureTree.vue +2 -2
  38. package/dist/vendored/main.js +1 -0
  39. package/dist/vendored/runtime/adapter.js +1 -1
  40. package/dist/vendored/runtime/ai-api.js +165 -0
  41. package/dist/vendored/runtime/clipboard-node.js +0 -5
  42. package/dist/vendored/runtime/markdown.js +140 -0
  43. package/dist/vendored/skills/.manifest.json +1101 -0
  44. package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +249 -0
  45. package/dist/vendored/skills/dsl-card-generator/scripts/check_topology.py +678 -0
  46. package/dist/vendored/skills/dsl-card-generator/scripts/derive_data_json.py +81 -0
  47. package/dist/vendored/skills/dsl-card-generator/scripts/lib/paths.py +105 -0
  48. package/dist/vendored/skills/dsl-card-generator/scripts/validate_schema.py +624 -0
  49. package/dist/vendored/skills/finmall-card-sync/scripts/api_batch_sync.mjs +900 -0
  50. package/dist/vendored/skills/finmall-card-sync/scripts/slice_upload.mjs +699 -0
  51. package/dist/vendored/skills/shared/finmall-config.mjs +124 -0
  52. package/dist/vendored/styles/ai-chat.css +1850 -0
  53. package/dist/vendored/styles/app.css +725 -213
  54. package/dist/vendored/styles/card-gallery.css +48 -10
  55. package/dist/vendored/styles/card-select.css +13 -6
  56. package/dist/vendored/styles/dropdown.css +1 -2
  57. package/dist/vendored/styles/json-tree.css +3 -3
  58. package/dist/vendored/styles/node-inspector.css +13 -289
  59. package/dist/vendored/styles/setters.css +48 -49
  60. package/dist/vendored/styles/structure-tree.css +6 -11
  61. package/package.json +1 -1
  62. package/dist/preview-editor/assets/index-B9MOriNv.js +0 -80
  63. package/dist/preview-editor/assets/index-CwD14gm3.css +0 -1
  64. package/dist/vendored/assets/logo.png +0 -0
@@ -0,0 +1,249 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ autofix_schema.py — 机械修复「契约级」高频错误,把「校验-修复」多轮压成 1 轮。
4
+
5
+ 只修可由代码确定性决策的 ERROR 类问题(不臆造语义):
6
+ 1. itemValueFrom == "1" 但缺 itemDefaultValue → 按值类型补兜底(字符串 '' / 数字 0 / 布尔 False / 数组 [] / 对象 {})。
7
+ 2. 重复 componentId → 重编号为全局连续唯一 comp_N。
8
+ 3. 缺失 data.json 或 key 与 inputParams 漂移 → 由 inputParams.defaultValue 重新派生并写回。
9
+
10
+ 不处理需要语义判断的 WARNING(未知样式子键、flex 枚举等)——那些留给人工/卡片作者。
11
+
12
+ 用法:
13
+ python3 autofix_schema.py <schema.json> # 修复并写回
14
+ python3 autofix_schema.py <schema.json> --check # 只报告,不写回
15
+ python3 autofix_schema.py <schema.json> --no-data # 跳过 data.json 派生
16
+ """
17
+ import json
18
+ import os
19
+ import re
20
+ import sys
21
+ import tempfile
22
+ import time
23
+ from pathlib import Path
24
+
25
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
26
+ import validate_schema as V # 复用硬契约校验
27
+
28
+
29
+ def _fallback_for(value):
30
+ if isinstance(value, bool):
31
+ return False
32
+ if isinstance(value, (int, float)):
33
+ return 0
34
+ if isinstance(value, str):
35
+ return ''
36
+ if isinstance(value, list):
37
+ return []
38
+ if isinstance(value, dict):
39
+ return {}
40
+ return ''
41
+
42
+
43
+ def _children(node):
44
+ kids = []
45
+ if isinstance(node.get('components'), list):
46
+ kids.extend(node['components'])
47
+ for s in node.get('slots', []) or []:
48
+ if isinstance(s, dict):
49
+ kids.extend(s.get('components', []) or [])
50
+ return kids
51
+
52
+
53
+ def walk_fix_value_from(node, fixed):
54
+ """机械补 itemValueFrom:缺失/空串 → '0'(常量,最安全默认)。
55
+
56
+ 运行时 property.js onPreview 只在 itemValueFrom 为 '0'/'1'/'2' 时才把属性写进预览
57
+ 产物;缺失则整条属性被静默丢弃(背景/内边距/gap/对齐失效)。手写样式极易漏写,
58
+ 故在此统一兜底为 '0'(按 itemValue 原值当常量用)。变量绑定('1')本就必带,不会走到这里。
59
+ """
60
+ changed = False
61
+ for grp in node.get('style', {}).get('properties', []) or []:
62
+ # 顶层样式分组对象本身也必须带 itemValueFrom(如 font/background 分组),
63
+ # 否则 validate 仍报 ERROR(之前 autofix 只补子项、漏补分组本体,font 分组
64
+ # 缺失时卡片字体/背景整组被运行时静默丢弃)。
65
+ if not grp.get('itemValueFrom'):
66
+ grp['itemValueFrom'] = '0'
67
+ fixed.append(f"补 itemValueFrom='0': style.{grp.get('itemKey')}(分组)")
68
+ changed = True
69
+ for ch in grp.get('itemChildren', {}).get('properties', []) or []:
70
+ if not ch.get('itemValueFrom'):
71
+ ch['itemValueFrom'] = '0'
72
+ fixed.append(f"补 itemValueFrom='0': style.{grp.get('itemKey')}.{ch.get('itemKey')}")
73
+ changed = True
74
+ for pr in node.get('properties', []) or []:
75
+ if not pr.get('itemValueFrom'):
76
+ pr['itemValueFrom'] = '0'
77
+ fixed.append(f"补 itemValueFrom='0': {node.get('className')}/{pr.get('itemKey')}")
78
+ changed = True
79
+ for d in node.get('directives', []) or []:
80
+ if not d.get('itemValueFrom'):
81
+ d['itemValueFrom'] = '0'
82
+ fixed.append(f"补 itemValueFrom='0': directive.{d.get('itemKey')}")
83
+ changed = True
84
+ for child in _children(node):
85
+ if walk_fix_value_from(child, fixed):
86
+ changed = True
87
+ return changed
88
+
89
+
90
+ def walk_fix_defaults(node, fixed):
91
+ changed = False
92
+ for prop in node.get('properties', []) or []:
93
+ if prop.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in prop:
94
+ prop['itemDefaultValue'] = _fallback_for(prop.get('itemValue'))
95
+ fixed.append(f"补 itemDefaultValue: {node.get('className')}/{prop.get('itemKey')}")
96
+ changed = True
97
+ for grp in node.get('style', {}).get('properties', []) or []:
98
+ if grp.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in grp:
99
+ grp['itemDefaultValue'] = _fallback_for(grp.get('itemValue'))
100
+ fixed.append(f"补 style itemDefaultValue: {grp.get('itemKey')}(分组)")
101
+ changed = True
102
+ for child in grp.get('itemChildren', {}).get('properties', []) or []:
103
+ if child.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in child:
104
+ child['itemDefaultValue'] = _fallback_for(child.get('itemValue'))
105
+ fixed.append(f"补 style itemDefaultValue: {node.get('className')}/{grp.get('itemKey')}/{child.get('itemKey')}")
106
+ changed = True
107
+ for d in node.get('directives', []) or []:
108
+ if d.get('itemValueFrom') in ('1', 1) and 'itemDefaultValue' not in d:
109
+ d['itemDefaultValue'] = _fallback_for(d.get('itemValue'))
110
+ fixed.append(f"补 directive itemDefaultValue: {node.get('className')}/{d.get('itemKey')}")
111
+ changed = True
112
+ for child in _children(node):
113
+ if walk_fix_defaults(child, fixed):
114
+ changed = True
115
+ return changed
116
+
117
+
118
+ def fix_duplicate_ids(node, fixed):
119
+ seen = set()
120
+ duplicated = False
121
+
122
+ def count(n):
123
+ nonlocal duplicated
124
+ if not isinstance(n, dict):
125
+ return
126
+ cid = n.get('componentId')
127
+ if cid and cid in seen:
128
+ duplicated = True
129
+ seen.add(cid)
130
+ for c in _children(n):
131
+ count(c)
132
+ count(node)
133
+ if not duplicated:
134
+ return False
135
+
136
+ seq = {'n': 0}
137
+
138
+ def renumber(n):
139
+ if not isinstance(n, dict):
140
+ return
141
+ seq['n'] += 1
142
+ n['componentId'] = f"comp_{seq['n']}"
143
+ for c in _children(n):
144
+ renumber(c)
145
+ renumber(node)
146
+ fixed.append("重编号全部 componentId 为连续唯一序列(修复重复)")
147
+ return True
148
+
149
+
150
+ def fix_data_json(schema, schema_dir, fixed, skip=False):
151
+ if skip:
152
+ return False
153
+ data_path = os.path.join(schema_dir, 'data.json')
154
+ params = schema.get('dictionary', {}).get('inputParams', [])
155
+ expected = {p['key']: p.get('defaultValue') for p in params if 'key' in p}
156
+ if not os.path.isfile(data_path):
157
+ with open(data_path, 'w', encoding='utf-8') as f:
158
+ json.dump(expected, f, ensure_ascii=False, indent=2)
159
+ f.write('\n')
160
+ fixed.append(f"生成缺失的 data.json({len(expected)} 个 key)")
161
+ return True
162
+ try:
163
+ data = json.load(open(data_path, encoding='utf-8'))
164
+ except Exception:
165
+ data = {}
166
+ drift = (set(expected) - set(data)) | (set(data) - set(expected))
167
+ if drift:
168
+ merged = dict(data)
169
+ for k, v in expected.items():
170
+ if k not in merged:
171
+ merged[k] = v
172
+ with open(data_path, 'w', encoding='utf-8') as f:
173
+ json.dump(merged, f, ensure_ascii=False, indent=2)
174
+ f.write('\n')
175
+ fixed.append(f"修正 data.json key 漂移({len(drift)} 个 key)")
176
+ return True
177
+ return False
178
+
179
+
180
+ def _validate_obj(schema, check_data=True):
181
+ fd, tmp = tempfile.mkstemp(suffix='.json')
182
+ try:
183
+ with os.fdopen(fd, 'w', encoding='utf-8') as f:
184
+ json.dump(schema, f, ensure_ascii=False)
185
+ return V.validate_schema(tmp, check_data=check_data)
186
+ finally:
187
+ try:
188
+ os.remove(tmp)
189
+ except Exception:
190
+ pass
191
+
192
+
193
+ def autofix(path, check=False, no_data=False):
194
+ schema_dir = os.path.dirname(path)
195
+ with open(path, 'r', encoding='utf-8') as f:
196
+ schema = json.load(f)
197
+ fixed = []
198
+ walk_fix_value_from(schema.get('view', {}), fixed)
199
+ walk_fix_defaults(schema.get('view', {}), fixed)
200
+ fix_duplicate_ids(schema.get('view', {}), fixed)
201
+ fix_data_json(schema, schema_dir, fixed, skip=no_data)
202
+
203
+ if not check and fixed:
204
+ with open(path, 'w', encoding='utf-8') as f:
205
+ json.dump(schema, f, ensure_ascii=False, indent=2)
206
+ f.write('\n')
207
+
208
+ t0 = time.time()
209
+ ok, errors, warnings = _validate_obj(schema, check_data=not no_data)
210
+ duration = int((time.time() - t0) * 1000)
211
+ return {
212
+ 'fixed': fixed,
213
+ 'ok': ok,
214
+ 'errors': errors,
215
+ 'warnings': warnings,
216
+ 'durationMs': duration,
217
+ 'written': (not check and bool(fixed)),
218
+ }
219
+
220
+
221
+ def main():
222
+ args = sys.argv[1:]
223
+ positional = [a for a in args if not a.startswith('--')]
224
+ flags = set(a for a in args if a.startswith('--'))
225
+ if not positional:
226
+ print("Usage: python3 autofix_schema.py <schema.json> [--check] [--no-data]")
227
+ sys.exit(1)
228
+ path = positional[0]
229
+ check = '--check' in flags
230
+ no_data = '--no-data' in flags
231
+ if not os.path.isfile(path):
232
+ print(f"[error] File not found: {path}")
233
+ sys.exit(1)
234
+
235
+ res = autofix(path, check=check, no_data=no_data)
236
+ print(f"\n[autofix] {'--check 模式(未写回)' if check else '已尝试修复'}")
237
+ if res['fixed']:
238
+ for fx in res['fixed']:
239
+ print(f" [FIX] {fx}")
240
+ else:
241
+ print(" [FIX] 无机械错误可修复")
242
+ print(f"\n[result] ok={res['ok']} errors={len(res['errors'])} warnings={len(res['warnings'])} ({res['durationMs']}ms)")
243
+ for e in res['errors'][:20]:
244
+ print(f" [ERROR] {e}")
245
+ sys.exit(0 if res['ok'] else 1)
246
+
247
+
248
+ if __name__ == '__main__':
249
+ main()