cardbuddy 0.14.2 → 0.15.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.
- package/dist/cli.js +0 -0
- package/dist/preview-editor/assets/{index-BN79pTkQ.js → index-CIIfMlwI.js} +43 -40
- package/dist/preview-editor/assets/index-DQn0zQBP.css +1 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/standalone.js +3 -0
- package/dist/vendored/mcp/client.mjs +8 -1
- package/dist/vendored/server/core/ai-chat.js +1521 -124
- package/dist/vendored/server/core/browser.js +12 -3
- package/dist/vendored/server/core/cardFiles.js +3 -0
- package/dist/vendored/server/core/cardHistory.js +1 -0
- package/dist/vendored/server/core/cardImportMeta.js +7 -6
- package/dist/vendored/server/core/cardOpenFolder.js +1 -0
- package/dist/vendored/server/core/cardSave.js +1 -0
- package/dist/vendored/server/core/cardSync.js +3 -0
- package/dist/vendored/server/core/codeBridge.js +5 -1
- package/dist/vendored/server/core/logStream.js +1 -0
- package/dist/vendored/server/core/sliceStatic.js +24 -4
- package/dist/vendored/server/core/sliceUpload.js +1 -0
- package/dist/vendored/server/core/turnJournal.js +6 -0
- package/dist/vendored/skills/card-skill-trainer/SKILL.md +196 -195
- package/dist/vendored/skills/dsl-card-ai/SKILL.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/SKILL.md +142 -380
- package/dist/vendored/skills/dsl-card-generator/references/generation_pitfalls.md +65 -18
- package/dist/vendored/skills/dsl-card-generator/references/ir-format.md +116 -2
- package/dist/vendored/skills/dsl-card-generator/references/iteration_protocol.md +58 -0
- package/dist/vendored/skills/dsl-card-generator/references/previewer_contract.md +1 -1
- package/dist/vendored/skills/dsl-card-generator/references/quickref.md +15 -7
- package/dist/vendored/skills/dsl-card-generator/references/recipe_snippets/title-underline-overlay.json +59 -11
- package/dist/vendored/skills/dsl-card-generator/references/recipes.md +14 -0
- package/dist/vendored/skills/dsl-card-generator/references/rules.md +23 -4
- package/dist/vendored/skills/dsl-card-generator/references/scripts_reference.md +51 -0
- package/dist/vendored/skills/dsl-card-generator/references/semantic_adjudication.md +23 -0
- package/dist/vendored/skills/dsl-card-generator/references/slices_convention.md +4 -3
- package/dist/vendored/skills/dsl-card-generator/references/spec_to_schema_patterns.md +32 -0
- package/dist/vendored/skills/dsl-card-generator/references/ui_fidelity_rules.md +13 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/autofix_schema.py +45 -2
- package/dist/vendored/skills/dsl-card-generator/scripts/check_fidelity.mjs +116 -10
- package/dist/vendored/skills/dsl-card-generator/scripts/check_magic_values.mjs +6 -1
- package/dist/vendored/skills/dsl-card-generator/scripts/crop_slices.mjs +92 -6
- package/dist/vendored/skills/dsl-card-generator/scripts/expand_ir.mjs +227 -23
- package/dist/vendored/skills/dsl-card-generator/scripts/gate.mjs +144 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/generate_cards.mjs +12 -8
- package/dist/vendored/skills/dsl-card-generator/scripts/ir_merge.mjs +288 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/ir_restore.mjs +175 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/iterate.mjs +644 -42
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/geometry_audit.mjs +131 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/layout_rules.mjs +160 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slicePath.mjs +17 -5
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/slice_dedupe.mjs +102 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_assemble.mjs +5443 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_extract.mjs +4108 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lib/v3_shared.mjs +1116 -0
- package/dist/vendored/skills/dsl-card-generator/scripts/lint_card.mjs +214 -4
- package/dist/vendored/skills/dsl-card-generator/scripts/preflight.mjs +48 -2
- package/dist/vendored/skills/dsl-card-generator/scripts/render_card.mjs +91 -43
- package/dist/vendored/skills/dsl-card-generator/scripts/route_input.mjs +4 -3
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_audit.mjs +150 -38
- package/dist/vendored/skills/dsl-card-generator/scripts/spec_to_schema_v3.mjs +43 -10499
- package/dist/vendored/skills/dsl-card-generator/scripts/v3_regression.mjs +186 -0
- package/dist/vendored/skills/sketch-spec-to-card/SKILL.md +18 -12
- package/dist/vendored/skills/sketch-spec-to-card/references/spec-schema.md +1 -1
- package/dist/vendored/skills/sketch-spec-to-card/scripts/build_prompt.py +47 -1
- package/dist/vendored/skills/sketch-spec-to-card/scripts/parse_meaxure.py +460 -49
- package/dist/vendored/skills/sketch-spec-to-card/scripts/png_utils.py +51 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/test_text_only_artboard.py +157 -0
- package/dist/vendored/skills/sketch-spec-to-card/scripts/vision.py +24 -0
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-cbTCHp8J.css +0 -1
|
@@ -6,17 +6,68 @@
|
|
|
6
6
|
(preview/*.png,8-bit RGB、无交织)裁切聚焦卡片区域。
|
|
7
7
|
"""
|
|
8
8
|
import math
|
|
9
|
+
import os
|
|
9
10
|
import struct
|
|
10
11
|
import zlib
|
|
11
12
|
|
|
12
13
|
_SIG = b"\x89PNG\r\n\x1a\n"
|
|
13
14
|
|
|
15
|
+
# 解码结果缓存:同一张图在一次运行里会被反复读(取色 / 掩码 / 逐切图裁切都各读一遍全图),
|
|
16
|
+
# 实测单画板构建 52 次 read_png 里 40 次是同一张 preview,占掉 31s / 35s。键带上
|
|
17
|
+
# mtime 与 size,文件被改写即自动失效;行是 bytes、调用方只读,故可直接共享同一份 rows。
|
|
18
|
+
# 上限按「张」计:一张 750×2204 RGB 解出来约 5MB,8 张封顶约 40MB。
|
|
19
|
+
_READ_CACHE = {}
|
|
20
|
+
_READ_CACHE_MAX = 8
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_png_size(path):
|
|
24
|
+
"""只解析 IHDR 拿 (width, height, channels),不解 IDAT。
|
|
25
|
+
|
|
26
|
+
给「只要尺寸」的调用方用(例如按设计宽度推缩放比例):33 字节 vs 整图重建,
|
|
27
|
+
大图上差三个数量级。
|
|
28
|
+
"""
|
|
29
|
+
with open(path, "rb") as fh:
|
|
30
|
+
head = fh.read(33)
|
|
31
|
+
if not head.startswith(_SIG):
|
|
32
|
+
raise ValueError("不是 PNG 文件: " + str(path))
|
|
33
|
+
(width, height, bit_depth, color_type,
|
|
34
|
+
_comp, _filt, interlace) = struct.unpack(">IIBBBBB", head[16:29])
|
|
35
|
+
if bit_depth != 8:
|
|
36
|
+
raise ValueError(f"仅支持 8-bit PNG(实际 {bit_depth})")
|
|
37
|
+
if interlace != 0:
|
|
38
|
+
raise ValueError("不支持交织 PNG")
|
|
39
|
+
if color_type == 0:
|
|
40
|
+
channels = 1
|
|
41
|
+
elif color_type == 2:
|
|
42
|
+
channels = 3
|
|
43
|
+
elif color_type == 6:
|
|
44
|
+
channels = 4
|
|
45
|
+
else:
|
|
46
|
+
raise ValueError(f"不支持的 colorType={color_type}")
|
|
47
|
+
return width, height, channels
|
|
48
|
+
|
|
14
49
|
|
|
15
50
|
def read_png(path):
|
|
16
51
|
"""解码 PNG -> (width, height, channels, rows)。
|
|
17
52
|
rows: list[bytes],每行 width*channels 字节(已去 filter)。
|
|
18
53
|
channels: 1=灰度 3=RGB 4=RGBA
|
|
54
|
+
|
|
55
|
+
带进程内缓存:键是 (路径, mtime_ns, 大小),同一次运行里重复读同一张图只解码一次。
|
|
19
56
|
"""
|
|
57
|
+
st = os.stat(path)
|
|
58
|
+
key = (str(path), st.st_mtime_ns, st.st_size)
|
|
59
|
+
hit = _READ_CACHE.get(key)
|
|
60
|
+
if hit is not None:
|
|
61
|
+
return hit
|
|
62
|
+
result = _read_png_uncached(path)
|
|
63
|
+
if len(_READ_CACHE) >= _READ_CACHE_MAX:
|
|
64
|
+
_READ_CACHE.clear()
|
|
65
|
+
_READ_CACHE[key] = result
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _read_png_uncached(path):
|
|
70
|
+
"""真正的解码实现;外部一律走 read_png(带缓存)。"""
|
|
20
71
|
data = open(path, "rb").read()
|
|
21
72
|
if not data.startswith(_SIG):
|
|
22
73
|
raise ValueError("不是 PNG 文件: " + str(path))
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""纯文本画板过滤的回归自检(直跑:python skills/sketch-spec-to-card/scripts/test_text_only_artboard.py)
|
|
3
|
+
|
|
4
|
+
覆盖用户 2026-09-20 规则:整屏纯文本的对话 / 问答 / 提示画板不产卡,且不能误伤
|
|
5
|
+
「对话里嵌着一张真卡」的状态。用例按真实稿(喜豆AI银行 24 稿)的特征构造:
|
|
6
|
+
|
|
7
|
+
1. 纯文本对话页(信用卡返现 01/03 型):气泡里只有文字 → text_only,被过滤;
|
|
8
|
+
2. 对话 + 内嵌矢量卡(理财 06 型):卡壳里是 3 宫格选项,检测器会读成「控件条」→ 保留;
|
|
9
|
+
3. 对话 + 卡片切图(房贷 32 型):卡片被整体导成一张切图 → 保留;
|
|
10
|
+
4. 底部抽屉(个人购汇 08 型):alertCard 子卡形态 → 保留(subcard);
|
|
11
|
+
5. 全屏遮罩弹框 → 保留(overlay_dialog);
|
|
12
|
+
6. 0 候选的普通文字页:保守保留,交给下游裁决(维持既有行为)。
|
|
13
|
+
"""
|
|
14
|
+
import sys
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
18
|
+
import parse_meaxure as pm # noqa: E402
|
|
19
|
+
import vision # noqa: E402
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _fill(hexv):
|
|
23
|
+
return [{"fillType": "Color", "color": {"color-hex": hexv}}]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def L(name, type_, x, y, w, h, radius=None, hexv=None, layers=None, opacity=None):
|
|
27
|
+
d = {"name": name, "type": type_, "rect": {"x": x, "y": y, "width": w, "height": h}}
|
|
28
|
+
if hexv:
|
|
29
|
+
d["fills"] = _fill(hexv)
|
|
30
|
+
if radius:
|
|
31
|
+
d["radius"] = radius
|
|
32
|
+
if layers:
|
|
33
|
+
d["layers"] = layers
|
|
34
|
+
if opacity is not None:
|
|
35
|
+
d["opacity"] = opacity
|
|
36
|
+
return d
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def ab(name, layers, w=375, h=812):
|
|
40
|
+
return {"name": name, "width": w, "height": h, "layers": layers}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def chrome():
|
|
44
|
+
"""状态栏 / 标题栏 / 弥散装饰 / 输入坞 / 底部导航——四类画板共用的界面壳。"""
|
|
45
|
+
return [
|
|
46
|
+
L("98.全局/系统控件/状态栏", "symbol", 0, 0, 375, 44),
|
|
47
|
+
L("5.导航/标题栏/黑-标题+三icon", "symbol", 0, 0, 375, 88),
|
|
48
|
+
L("椭圆形", "shape", 0, 0, 229, 260, hexv="#FFF3E0FF"),
|
|
49
|
+
L("全局_底部导航", "symbol", 0, 733, 375, 91),
|
|
50
|
+
L("输入框", "shape", 15, 733, 290, 44, radius=[22], hexv="#FFFFFFFF"),
|
|
51
|
+
L("发送", "slice", 315, 741, 30, 30),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def bubble(x, y, w, h, text_layer, radius=None):
|
|
56
|
+
return [L("用户问-单行备份", "group", x, y, w, h, layers=[
|
|
57
|
+
L("矩形", "shape", x, y, w, h, radius=radius or [21], hexv="#FFD24AFF"),
|
|
58
|
+
text_layer,
|
|
59
|
+
])]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def text(name, x, y, w, h):
|
|
63
|
+
return L(name, "text", x, y, w, h)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# ── 1. 纯文本对话页:只有气泡(用户问 + AI 长文应答),板上没有任何卡片结构 ──
|
|
67
|
+
CHAT_TEXT_ONLY = ab("01信用卡返现_登录用户角色类型为D", chrome() + bubble(
|
|
68
|
+
196, 100, 163, 42, text("查询信用卡返现", 208, 110, 140, 22)) + [
|
|
69
|
+
L("应答气泡", "shape", 16, 156, 343, 108, radius=[0, 12, 12, 12], hexv="#FFFFFFFF"),
|
|
70
|
+
text("当前登录账户无权限操作此交易,建议您使用信用卡或证件号码登录。", 30, 166, 315, 88),
|
|
71
|
+
])
|
|
72
|
+
|
|
73
|
+
# ── 2. 对话 + 内嵌矢量卡:卡壳里 3 个选项块(检测器读成「控件条」,但它就是卡)──
|
|
74
|
+
CARD_TILES = [
|
|
75
|
+
L("卡壳", "shape", 16, 238, 343, 184, radius=[12], hexv="#FFFFFFFF"),
|
|
76
|
+
text("您对这笔钱的规划是:", 28, 252, 240, 24),
|
|
77
|
+
L("选项1", "shape", 28, 288, 99, 118, radius=[8], hexv="#FFF7E6FF"),
|
|
78
|
+
L("选项2", "shape", 138, 288, 99, 118, radius=[8], hexv="#FFF7E6FF"),
|
|
79
|
+
L("选项3", "shape", 248, 288, 99, 118, radius=[8], hexv="#FFF7E6FF"),
|
|
80
|
+
text("随时要用", 36, 300, 80, 20), text("2.23%", 36, 330, 80, 30),
|
|
81
|
+
text("放1-6个月", 146, 300, 80, 20), text("3.21%", 146, 330, 80, 30),
|
|
82
|
+
text("放6个月以上", 256, 300, 80, 20), text("3.65%", 256, 330, 80, 30),
|
|
83
|
+
]
|
|
84
|
+
CHAT_WITH_VECTOR_CARD = ab("06理财_理财推荐", chrome() + bubble(
|
|
85
|
+
196, 100, 163, 42, text("理财产品推荐", 208, 110, 140, 22)) + [
|
|
86
|
+
L("应答气泡", "shape", 16, 156, 343, 68, radius=[0, 12, 12, 12], hexv="#FFFFFFFF"),
|
|
87
|
+
text("理财产品这么多,怎么找到适合自己的呢?", 30, 166, 315, 48),
|
|
88
|
+
] + CARD_TILES)
|
|
89
|
+
|
|
90
|
+
# ── 3. 对话 + 卡片切图:卡片被设计稿整体导成一张 slice(检测器看不到结构)──
|
|
91
|
+
CHAT_WITH_SLICED_CARD = ab("32房贷利率调整_有房贷_可以调整", chrome() + bubble(
|
|
92
|
+
196, 100, 163, 42, text("调整下我的房贷利率", 208, 110, 140, 22)) + [
|
|
93
|
+
L("应答气泡", "shape", 16, 156, 343, 108, radius=[0, 12, 12, 12], hexv="#FFFFFFFF"),
|
|
94
|
+
text("您的贷款为深圳地区二套住房贷款……", 30, 166, 315, 88),
|
|
95
|
+
L("编组 4", "slice", 16, 276, 343, 280),
|
|
96
|
+
])
|
|
97
|
+
|
|
98
|
+
# ── 4. 底部抽屉(alertCard 子卡)──
|
|
99
|
+
BOTTOM_SHEET = ab("08个人购汇_有人民币子账户_个人购汇申请书", chrome() + [
|
|
100
|
+
L("底部面板", "shape", 0, 600, 375, 212, radius=[16, 16, 0, 0], hexv="#FFFFFFFF"),
|
|
101
|
+
text("个人购汇申请书", 20, 620, 200, 24),
|
|
102
|
+
text("本人已知晓……", 20, 660, 320, 60),
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
# ── 5. 全屏遮罩弹框(alertDialog)──
|
|
106
|
+
OVERLAY_DIALOG = ab("05综合推荐_总资产提示", chrome() + [
|
|
107
|
+
L("遮罩", "shape", 0, 0, 375, 812, hexv="#00000066", opacity=0.4),
|
|
108
|
+
L("对话框", "shape", 48, 320, 279, 172, radius=[12], hexv="#FFFFFFFF"),
|
|
109
|
+
text("总资产提示", 72, 344, 200, 24),
|
|
110
|
+
text("您的总资产已更新", 72, 380, 220, 20),
|
|
111
|
+
])
|
|
112
|
+
|
|
113
|
+
# ── 6. 0 候选的普通文字页(无气泡、无卡壳):保守保留 ──
|
|
114
|
+
PLAIN_NO_CANDIDATE = ab("03信用卡返现_有信用卡_无返现记录", chrome() + [
|
|
115
|
+
text("当前没有可展示的内容", 30, 166, 315, 40),
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
CASES = [
|
|
119
|
+
(CHAT_TEXT_ONLY, "text_only", True, "纯文本对话页(只有问答气泡)"),
|
|
120
|
+
(CHAT_WITH_VECTOR_CARD, "card", False, "对话 + 内嵌矢量卡(3 宫格选项)"),
|
|
121
|
+
(CHAT_WITH_SLICED_CARD, "card", False, "对话 + 卡片切图"),
|
|
122
|
+
(BOTTOM_SHEET, "subcard", False, "底部抽屉(alertCard 子卡)"),
|
|
123
|
+
(OVERLAY_DIALOG, "overlay_dialog", False, "全屏遮罩弹框(alertDialog)"),
|
|
124
|
+
(PLAIN_NO_CANDIDATE, "card", False, "0 候选的普通文字页(保守保留)"),
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def main():
|
|
129
|
+
failed = 0
|
|
130
|
+
for board, kind, skip, why in CASES:
|
|
131
|
+
found = pm.detect_artboard_cards(board)
|
|
132
|
+
verdict = pm.board_verdict(board["name"], board)
|
|
133
|
+
ok = verdict["kind"] == kind and verdict["skip"] == skip
|
|
134
|
+
reasons = sorted({r["reason"] for r in found["rejected"]})
|
|
135
|
+
mark = "PASS" if ok else "FAIL"
|
|
136
|
+
if not ok:
|
|
137
|
+
failed += 1
|
|
138
|
+
print(f"[{mark}] {why}: kind={verdict['kind']}(期望 {kind}) "
|
|
139
|
+
f"skip={verdict['skip']}(期望 {skip}) cards={len(found['cards'])} "
|
|
140
|
+
f"conv={bool(found['conversationFlow'])} rejected={reasons}")
|
|
141
|
+
|
|
142
|
+
# 卡片级切图判定(房贷 32 型)单测:中段大盘切图算「板上有卡」,图标切图不算
|
|
143
|
+
flat = vision._flatten_with_tree(CHAT_WITH_SLICED_CARD["layers"])
|
|
144
|
+
assert vision.has_card_like_visual(flat, 375, 812) is True, "343x280 的中段切图应判为卡片级"
|
|
145
|
+
flat_text_only = vision._flatten_with_tree(CHAT_TEXT_ONLY["layers"])
|
|
146
|
+
assert vision.has_card_like_visual(flat_text_only, 375, 812) is False, "30x30 图标切图不该判为卡片"
|
|
147
|
+
print("[PASS] has_card_like_visual:中段大盘切图算卡,小图标不算")
|
|
148
|
+
|
|
149
|
+
if failed:
|
|
150
|
+
print(f"\n{failed} 个用例失败")
|
|
151
|
+
return 1
|
|
152
|
+
print(f"\n全部 {len(CASES)} 个用例通过")
|
|
153
|
+
return 0
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
if __name__ == "__main__":
|
|
157
|
+
sys.exit(main())
|
|
@@ -665,6 +665,30 @@ def detect_conversation_flow(elements, width, height):
|
|
|
665
665
|
}
|
|
666
666
|
|
|
667
667
|
|
|
668
|
+
def has_card_like_visual(elements, width, height):
|
|
669
|
+
"""画板里有没有「卡片级整块视觉」——设计稿把卡片整体导出成一张切图时(如「编组 4」),
|
|
670
|
+
图层里就只剩这张大图:检测器看不到它的结构(0 卡),但它显然不是纯文本。
|
|
671
|
+
|
|
672
|
+
只认贴在中段的大切图,避免把界面 chrome 当卡:
|
|
673
|
+
· 宽度 ≥ 55% 画板宽、高度 ≥ 12% 画板高;
|
|
674
|
+
· 纵向落在 10%..95% 之间(标题栏之下、输入坞 / Home Indicator 之上)。
|
|
675
|
+
20-40px 的图标切图、顶部导航栏、底部输入坞都命中不了。
|
|
676
|
+
"""
|
|
677
|
+
W, H = float(width), float(height)
|
|
678
|
+
if W <= 0 or H <= 0:
|
|
679
|
+
return False
|
|
680
|
+
for ve in elements:
|
|
681
|
+
if not ve.is_slice:
|
|
682
|
+
continue
|
|
683
|
+
x, y, w, h = ve.rect
|
|
684
|
+
if w < 0.55 * W or h < 0.12 * H:
|
|
685
|
+
continue
|
|
686
|
+
if y < 0.10 * H or (y + h) > 0.95 * H:
|
|
687
|
+
continue
|
|
688
|
+
return True
|
|
689
|
+
return False
|
|
690
|
+
|
|
691
|
+
|
|
668
692
|
def _is_repeated_control_bar(elements, rect):
|
|
669
693
|
"""识别横向重复图标/选项控件条,例如评分、开关组或快捷操作条。"""
|
|
670
694
|
x, y, w, h = rect
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cardbuddy",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Local dev service for the CardBuddy card editor — decouples card CRUD (sync/save/validate/render-status/screenshot) from the static public editor, so the editor can be deployed to any static host while each developer runs a local service against their own card source.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|