dsh-cardforge
Verifieddsh-cardforge · v0.31.0 · MIT · Web UI
ElecKoi 制卡工具(forge):教 AI 做卡的命令行工具,外加一个让 ElecKoi 里的 AI 直接跑它的宿主插件。
Install
dsh plugin add dsh-cardforge Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
制卡工具(forge)
给 ElecKoi(PC + Android)做的一套制卡工具: 从零开一张卡、改现有卡、校验、挂模块、换皮肤、组装卡内界面、打包、和酒馆(SillyTavern)来回搬。
三条约定:
spec.yaml是唯一真相,card.json是派生物。只手改 spec。- 内容住
source/,界面住hud/,改哪层动哪层。 - 颜色住
hud/主题.css,版式住hud/hud.css,换皮肤碰不到机制,改机制不会把配色弄丢。
依赖只有一条:PyYAML,其余全是标准库(Python ≥ 3.9)。
装法
这是个文件夹工具:forge.py、forge/、assets/、samples/、向导/ 要待在一起。
python forge.py <命令> # ① 直接跑(最省事)
python -m forge <命令> # ② 当模块跑(等同 ①,给不想记文件名的人)
pip install -e . # ③ 装成 `forge` 命令(推荐)
forge <命令>
为什么必须
-e:仓库里 CLI 的实体是根目录的forge.py,而forge/是它的内核包 —— 两个同名。普通pip install .只装走包、装不走forge.py,forge命令就找不到它;-e把整个文件夹留在原地,命令和资源(assets/、samples/、向导/)都还在。 也正因为同名,import forge拿到的是内核包 ——forge/cli.py只做一件事:找到仓库根,把forge.py当脚本跑起来。
给别人怎么用(四种交法,别搞混)
预设和角色卡只认本机文件(安装来源只有「内建」和「用户导入」两种,没有网络装载通道)。
插件不一样 —— 包名、Git 地址、压缩包、本地绝对路径四种写法都吃;这个仓库本身就是一只能贴链接安装的插件(根目录的 package.json + cordis.patch.yml)。
| 对方想要什么 | 给他什么 | 他怎么用 |
|---|---|---|
| 做卡时被 AI 陪着 | 向导/制卡助手/成品/制卡助手.preset.png |
ElecKoi →「预设管理器」→「导入预设」→ 分类必须选「创作预设」(选错会提示「这不是创作预设。」) |
| 在对话里问「怎么建卡」 | 向导/制卡助手/成品/制卡助手.card.png |
当普通角色卡导入(手机端「导入角色」也认这张) |
| 自己要做卡 | 仓库链接或出厂 zip | 解压后 python forge.py plan 开工;也可以 pip install -e . |
| 想常驻 ElecKoi 界面 | 插件名 dsh-cardforge |
侧栏「插件」→「添加插件」→ 贴包名 dsh-cardforge → 安装 → 重启 ElecKoi(刚发版那几分钟镜像源可能还没抓到,等一会儿或把安装源切成官方源) |
四条容易踩的:
- 装完必须重启 ElecKoi(插件只在宿主启动时载入,不重启不生效)。装插件顺带把预设也装好了:
插件看有没有「制卡助手」这个创作预设,没有就自动导入,有就一个字不写;只有当前创作预设
还是官方默认那个时才切过去 —— 你自己挑过的一律不动。关掉:
installPreset/activatePreset。 预设存在 ElecKoi 自己的数据里,卸载插件不会带走它(不自动删是因为你可能改过它)。 - 「找不到 Python」不用配。 配置里
python留空=自动找:先看FORGE_PYTHON,再挨个试常见位置 与PATH,真跑一次import yaml,谁能跑通用谁 —— Windows 上「装了 Python」不等于它在PATH上。 都找不到会告诉你它找过哪些地方;机器上有好几个 Python 挑错了,才需要写死完整路径。 - 卡做在哪个目录也不用每次说。
cwd留空=<你的用户目录>\Documents\ElecKoi卡库(没有就自动建); 调用时给了cwd就听参数的(参数 > 配置 > 默认)。 - 点安装报「连接 GitHub 超时」=这台机器到 github.com 不通。 预检只有 5 秒(
git ls-remote), 直连实测 21 秒都连不上。① 给 git 配代理:git config --global http.https://github.com/.proxy http://127.0.0.1:你的代理端口; ② 或点「试试其他方式」,改成贴本机插件目录的绝对路径(出厂包里插件\制卡工坊那个目录),完全不碰网络。
插件细说看三份文档:文档/插件-DSH.md(它是什么形式的插件 · 怎么装 · 五个配置键 · 怎么用 · 在纯 DSH 里怎么活)·
文档/插件-ElecKoi.md(装进电子爱多出来的那件事:自动装创作预设 · 验收三条 · 手动兜底两条路)·
文档/装完怎么验.md(装完照着点、一条条打勾的验收清单 + 一份「症状 → 多半是因为 → 怎么办」对照表)。
那两个 PNG 是可直接分发的单文件,对方不需要装 Python、不需要克隆仓库。要改内容再重新导出:
python forge.py coach # 重新生成规程 + 向导卡源
python forge.py preset 向导/制卡助手/source/制卡助手.spec.yaml --png
python forge.py png 向导/制卡助手/source/制卡助手.spec.yaml
三分钟上手
python forge.py plan # ① 开工先落一份规划书(九节空模板 —— 填满再动手)
python forge.py new 我的世界 # 开一张新卡(内容层 + 界面层一起生成)
python forge.py check 我的世界 # 体检(含界面规矩、皮肤可读性)
python forge.py hud 我的世界 --写回 # 改过 hud/ 之后重新组装界面
python forge.py build 我的世界/source/我的世界.spec.yaml 我的世界/成品/我的世界.card.json
python forge.py new 我的世界 --模块 骰子,备份 # 顺手接两块现成机制(条目+界面一起接)
python forge.py drop 备份 我的世界/source/我的世界.spec.yaml # 后悔了就撤,接之前的样子还在账本里
python forge.py theme 墨夜 我的世界 # 换皮肤(换完记得再 hud 一次)
python forge.py theme --巡览 我的世界 # 五套皮肤摆一起对比
python forge.py bundle 我的世界 # 打成一个工程包(分发 / 备份)
python forge.py import 我的世界.工程包.zip --到 同事的卡 # 收包:落到本地,成一张能接着改的工程
python forge.py new 深夜便利店 --从 同事的卡.card.json # 接手现成的卡:界面层照抄骨架,内容层用它的
python forge.py theme --导出 我的世界 # 把这张卡调好的配色导成一份皮肤文件(可发人)
python forge.py png 我的世界/成品/我的世界.card.json # 出卡图(.card.png,ElecKoi 手机端「导入角色」认的就是它)
python forge.py preset 我的世界/成品/我的世界.card.json --png # 出「创作预设」(.preset.json / .preset.png)
python forge.py lint 我的世界/成品/我的世界.card.json # 正文体检(防八股):只查句式骨架,不查词
python forge.py lint - # 刚写完一轮?直接从标准输入丢进去查
往卡里加东西(以前只能改已有的,现在能从无到有):
python forge.py entry 我的世界 --新 "夜里的酒馆" --模板 关键词 --关键词 酒馆,打听
python forge.py entry 我的世界 --列 # 看看条目长什么样
python forge.py entry 我的世界 --看 "核心 · 世界总纲" # 读一条的全貌:正文全文 / 关键词 / 提示 / 触发…
python forge.py entry 我的世界 --改 "核心 · 世界总纲" --正文@世界总纲.txt # 长正文写成文件再 @ 引
python forge.py group 我的世界 --新 支线 # 分个组
python forge.py var 我的世界 --新 玩家.声望 --类型 number --初值 0 # 变量三处一次写齐
python forge.py regex 我的世界 --新 "清理变量" --模式 '…' --替换 '…' # 加正则(Git Bash 里先 export MSYS_NO_PATHCONV=1)
python forge.py opener 我的世界 --正文@开场白.txt # 换开场白(末尾那三个零宽标记工具负责)
python forge.py opener 别人给的卡 --正文@开场白.txt # 只有内容层的工程也认
python forge.py trash 我的世界 # 删错了?都在这儿,--还原 1 放回最近一条
看全部命令:python forge.py
在「不许删、不许改名」的环境里跑(比如 ElecKoi 里的 AI 直接跑命令):任何命令后面加 --沙箱,
或先 export FORGE_SANDBOX=1。forge 会切成「只创建、只覆盖」—— 落盘直接覆盖目标、回收站改用墓碑、
撤皮肤写空壳。代价是丢掉原子落盘,能不用就不用(自检第 42 项盯着这条)。
建议用一个装好 PyYAML 的独立 Python 环境(
venv/conda都行),别拿系统 Python 直接跑。
制卡向导(coach)—— 教 AI 怎么做卡
上面那些命令是给你用的。想让 AI 替你开新卡,缺的不是命令,是一份 AI 读得懂的制卡规程:
python forge.py coach # 默认出到 ./向导/(--到 <目录> 换地方)
一份内容,两个出口:
| 产出 | 给谁 | 怎么用 |
|---|---|---|
向导/AI制卡规程.md |
电脑上的 AI(Claude Code / Codex / Cursor 这类) | 丢进对话,说「照这个给我做一张卡」 |
向导/SKILL.md |
同上,带 frontmatter | 拷进技能目录当技能用(如 ~/.claude/skills/eleckoi-card-maker/);规程更新后不用重装 |
向导/制卡助手/成品/制卡助手.card.json |
ElecKoi 里的 AI(陪你聊天那个) | 导入挂上:它变成制卡师,在对话里一层层访谈你 |
两份出口共用同一套方法论九篇(机制 / 条目触发 / 预算 / 世界 / 故事 / 角色 / 写法 / 变量 / 界面)+ 一章红线(会被 ElecKoi 静默吃掉的写法)。
不想一次读完整份规程(一万七千字,塞进上下文既贵又冲淡重点):guide 先出目录,再按需取一段 ——
python forge.py guide # 目录:有哪些节、哪些篇、哪几篇必读、有哪几段「对话」
python forge.py guide --节 三 # 一节(一 开工流程 / 六 验收标准 / 九 红线 …)
python forge.py guide --篇 预算 # 一篇方法论 · `--对话 访谈` 一段在对话里做卡 · `--全文` 整份
认不出的名字当场报错并列出全部候选,撞名的报「不止一个」让你写全,绝不静默挑一个。 装在 ElecKoi 里的插件就是靠这条自己把知识取到手,不必等人把规程贴进对话。
向导卡自己就是范例:由 coach 生成,所以它自己也体检 0 错、常驻字数守在预算内。
一次落成(make)—— 想清楚了就别来回跑
new → group → entry × N → var → regex → opener → build → check 是七八次进程启动。
人用不累,AI 在对话里用就贵:每次调用都是一整轮。所以想清楚之后,一次落成:
python forge.py make --示例 # 先打出一张真能开的完整卡稿(长夜便利店)—— 照着改
python forge.py make --模板 # 或者只要骨架:有哪些键、怎么缩进
python forge.py make 我的卡.yaml # 一趟:建工程+写条目+加变量正则+换开场白+出成品+体检
--模板 只回答「有哪些键」,--示例 连「话该写多细、哪句放常驻、哪句放关键词」一起示范 ——
AI 最容易失手的不是键名,是「写多细算够」,一份好样板比十条规则管用。
稿子写的是这张卡该长什么样,不是「先做这步再做那步」:
- 条目按标题认人 —— 卡里已有同名的就改它,没有才新建。想清掉骨架自带、稿里没提到的条目,
写一行
清空条目: true;想删掉某一条,写删: true。 - 给已有条目写
模板:/分组:也真生效(换档与挪组都会落下去);分组 / 变量对象缺了自动补。 - 全程在临时目录里做,全绿了才整棵拷到目标位置 —— 稿子有毛病时目标位置一个字都不会被写, 静态能查的(模板名 / 类型 / 值域会不会被静默降级)一次报全。
--试只验证不出;--覆盖才允许把已有同名工程照稿重落。加--沙箱照样一趟落成。
规程是「走过一遍」的,不是「想出来」的。 它被拿去做过五轮盲跑:不看内部代码的 AI 只拿规程
照章施工,卡在哪就记下来。第一轮「从零建卡」修回 15 处,第二轮「接手别人给的卡」挖出 10 处,
第三轮补上了缺的搬运命令(new <卡名> --从 <卡>),第五轮端到端做出 11 条目 / 14 变量 / 6 对象、check 0 错。
图形工作台(已迁出)
原来那个「开在浏览器里给人点的界面」连代码一起搬到了
弃用/工作台/(2026-10-10,v0.27.0)。 主线只有两条:AI 照着规程敲命令,和给 ElecKoi 里的 AI 一个工具。 自检里原本盯着它的第 21 / 39 / 40 项一并移除,编号保留空位、不重排。
路线图
| 是什么 | 状态 | |
|---|---|---|
| A · 离线工程链(就是本工具) | forge.py 命令 + 规程,AI 照着跑,最后交出卡 |
已可用 |
| B · 卡内 HUD | 卡自带的页面内界面(面板 / 悬浮球 / 建卡器),靠一条正则送进宿主页 | 骨架与模块齐了,具体卡自己长 |
| C · ElecKoi 原生插件 | 在 ElecKoi 里给 AI 一个 cardforge 工具,直接跑 forge.py |
已发版:插件/制卡工坊/,装法见上表第 4 行 |
为什么插件比「让 AI 用自带命令行工具」强:ElecKoi 自己的命令行工具跑在受限令牌沙箱里
(读得到、写得进,但不许删、不许改名,WinError 5),而 forge 每次落盘都靠改名(os.replace /
os.rename / 回收站是「移动」)。宿主插件起的子进程不受那层沙箱管 —— 原子落盘、改名、回收站全都回来了。
一句话分工:能用插件就用插件(全功能);只能用命令行工具就加 --沙箱(丢掉原子落盘,但能干活)。
三十三个命令
| 分类 | 命令 |
|---|---|
| 开卡 | plan 规划书(不填参数=九节空模板 · plan <卡> 从现成卡反推现状 · plan --查 规划书.md 体检:哪节还空着、谁没写弱点,当场说)· make <卡稿.yaml> 照一份卡稿一次落成整张卡(--示例 出一张真能开的完整卡稿 / --模板 出骨架 / --试 只验证不出 / --覆盖 重落)· new 开新卡(可 --模块 骰子,备份)· new <卡名> --从 <卡.json / 包.zip / spec.yaml / 工程目录> 接手现成的卡(界面层照抄骨架、内容层用它、名字归新卡)· name 改卡名(spec 与界面配置两处一起改)· mods [--工程 X] 看有哪些模块 / 这张卡接没接 · add 接模块 · drop 撤模块 |
| 内容 | entry 条目(列 / 看 / 新 / 改 / 复制 / 删 / 挪组 —— 一串名字就是批量的:复制·删·挪·启停,--改 一串=批量改同一组字段)· group 分组(列 / 新 / 改名 / 删)· var 变量(列 / 新 / 删(一串=批量删,三处一起撤) / 新对象 / 改名(三处一起挪) / 换对象(挂靠+初值+Zod 一起搬) / 对齐 Zod)· regex 正则(列 / 新 / 删 / 启停)· opener 开场白(看 / 正文 / 补齐 / 写回) |
| 撤回 | trash 回收站(列 / --还原 / --清空)—— 上面删掉的东西都在这儿 |
| 皮肤 | theme 列主题 · theme <名> <工程> 换皮肤 · theme --巡览 对照图 · theme --撤 回核心默认 · theme --导出/--导入/--装 皮肤出入库 |
| 界面 | hud 组装卡内 HUD(出 预览.html,--写回 把正则写进 spec) |
| 打包 | unpack 卡→spec · build spec→卡 · bundle 整个工程打成 zip(附 sha256 清单)· import 收包:工程包/单卡 → 本地工程 · diff 语义比对 |
| 出成品 | png 卡→.card.png(ElecKoi 原生导入认的图片卡;内嵌 eleckoi-card 块,可选 --图 封面.png)· preset 卡→.preset.json / --png 出 .preset.png(ElecKoi「创作预设」:把同一批条目当一条常驻规程带过去;--说明 换说明文字、--工具组 换能力组、--留引擎条目 保留扮演机件) |
| 体检 | check 校验(值域+一致性+护栏+界面守则+工程自洽,可传目录批量)· lint 正文体检(防八股):只查句式骨架不查词 —— 提示冒号+空行接列表 / 对立句式 ≤1 次 / 破折号 0 处三条硬判据(lint - 从标准输入读一轮正文 · lint --标准 打判据表)· domains 打印真值域表 · selftest 自检四十二项 |
| 互通 | analyze 拆解任意卡 · to-eleckoi 酒馆→ElecKoi · to-tavern ElecKoi→酒馆 |
| 文档 | manual 出人读手册(HTML)· index 出给 AI 的导航索引 · coach 出「怎么做一张卡」:给 AI 的规程 + 给 ElecKoi 的向导卡 · guide 按需取规程(不给参数=目录,--节 / --篇 / --对话 / --全文) |
一张卡工程长什么样
我的世界/
source/我的世界.spec.yaml 内容层:世界观、条目、变量、正则 ← 改内容只改这里
模块账本.json 接过哪些机制、并进来了什么 ← 撤模块靠它,别手改
.forge/回收站/ 删掉的条目 / 变量 / 正则存这儿 ← 可逐项还原
hud/ 界面层:建卡器 / 面板 / 悬浮球 ← 改外观只改这里
hud.conf.json 卡名、id 前缀、HUD 全局名、要接几条正则、现在用哪套皮肤
hud.core.js 通用核心(把变量树画成面板 + 建卡器 + 悬浮球)
hud.css 版式(一个颜色都不写死,全从 --hud-* 槽取)
主题.css 皮肤(只有颜色槽覆盖;由 forge theme 写) ← 可没有
view_create.html 建卡器那一块的骨架
view_card.html 面板那一块的骨架
模块/<名>/<名>.js|.css 按需挂上的模块(悬停的机制,撤掉就删目录)
测试/test_core.js HUD 纯函数单测(node 跑,新卡自带)
成品/我的世界.card.json 交付物(导入 ElecKoi 的就是它)
文档/ 手册与索引(bundle 时生成)
怎么开工.md 这个工程怎么用
预览.html 界面在浏览器里的样子(forge hud 产出)
皮肤巡览.html 五套皮肤的对照图(forge theme --巡览 产出)
hud.core.js 不含任何世界观内容:你往 variables 里加一栏,面板上就多一行,不用改代码。
换卡只动 hud.conf.json。
目录
| 路径 | 是什么 |
|---|---|
forge.py |
命令行入口 |
forge/ |
内核:model domain pack validate zod content bridge export manual mods theme scaffold hud bundle dist sandbox coach draft guard |
forge/domain.py |
真值域表:拦下会被 ElecKoi 静默降级的字段(市面工具都没有这一段) |
forge/content.py |
内容编辑内核:往卡里加条目 / 变量 / 正则,删了进回收站;命令行内核(guide / make 的地基也是它) |
forge/zod.py |
Zod 校验代码(schema_code)的保形拆解与拼回:每个叶子表达式原样保留,不加不减 |
forge/hud.py |
界面组装器:拼整页 HTML → 转正则替换串 → 写回 spec → 出预览页 |
forge/export.py |
反向导出:ElecKoi → 酒馆,并把原卡存档塞进 extensions 以便无损回迁 |
forge/mods.py |
模块引擎 + 账本:条目/正则进 spec,脚本/样式进 hud/模块/;接过什么记一笔,所以撤得回来 |
forge/theme.py |
皮肤:把颜色槽表编成一份 CSS;外带可读性体检(对比度) |
forge/guard.py |
只读禁区名单:三处任选 —— 环境变量 FORGE_READONLY / 工程根 只读名单.txt(一行一路径)/ 目录里放个 .只读 空文件;任何写操作落盘前先问它 |
forge/bundle.py |
打包分发:重建 → 手册 → 索引 → zip(含 包信息.json) |
forge/dist.py |
收发两端:清单(每个文件的 sha256)与收包(逐条查路径,恶意包整包拒收) |
forge/sandbox.py |
沙箱友好模式:只创建、只覆盖 —— 不许删 / 不许改名时也能干活 |
forge/coach.py |
制卡向导:一份内容两个出口(给 AI 的规程 + 给 ElecKoi 的向导卡) |
forge/draft.py |
卡稿:照着「这张卡该长什么样」一次落成(全程在临时目录做,全绿了才整棵拷过去 —— 坏稿一个字都不落) |
插件/制卡工坊/ |
C 轨本体:ElecKoi 宿主插件,给会话里的 AI 一个 cardforge 工具(宿主执行,不受命令行沙箱限制) |
assets/骨架/ |
一张最小可跑卡的源,连界面层与 JS 单测一起 |
assets/模块/ |
五个可挂模块:骰子 · 尺度 · 备份 · 点数池 · 代笔(各带版本号) |
assets/主题/ |
五套皮肤:羊皮卷 · 墨夜 · 霜白 · 朱砂 · 苔原 |
samples/ |
演示工程(五个模块全挂)+ 一张最小酒馆 V2 卡 |
文档/使用说明.md |
主文档:命令、结构、值域表、校验项、路线图 |
制卡工具·方案.md |
立项方案:为什么这么做、三条路线怎么选 |
护城河是什么
九条,每条都对应一类「填了不报错、但事实上没生效」的坑(细目与例证见 文档/使用说明.md §五):
| 一句话 | |
|---|---|
| 一 | 真值域表 —— ElecKoi 对不认识的字段值静默降级,本工具把反编译取证出来的真值域表变成了会拦截的代码(forge.py domains) |
| 二 | 变量三处同步 —— objects / initial_state / schema_code 少任何一处都不报错,写入会被静默丢掉;check 会对一遍,var --对齐 能补回来 |
| 二·补 | 形状不猜 —— initial_state 里是几层对象是卡自己写的:对得上就写,对不上当场说是哪一格挡着,绝不猜着塞 |
| 三 | 界面层边界守则 —— HUD 整段注进宿主页面,check 逐文件扫三反引号 / <!-- / </script / position:fixed,且 CSS 每个选择器必须带 .hud 前缀 |
| 四 | 模块可撤 —— 模块是覆盖进 spec 的,所以每接一个都把「原来长什么样」存进账本;forge drop 才能精确撤回。你手写进 spec 的东西,工具一个字都不动 |
| 五 | 换皮肤不碰机制 —— 颜色全在 --hud-* 槽里,主题只写槽;外带对比度体检(按宿主纯白 / 近黑两路各算一遍取更差) |
| 六 | 双向无损 —— 转酒馆时把变量、分组、未映射字段、条目 id 与顺序全存进 extensions,回来时走回迁路径取回;艾瑟兰 2.5MB 实卡往返语义完全一致 |
| 七 | 能力缺口的诚实交代 —— 酒馆没有「由 AI 自己选读」这一档,转过去的条目会变成「永不自动触发」,工具点数并说清根因,不假装成功 |
| 八 | 值域在写入那一刻就拦下 —— 会静默降级的字段当场拒收,并写明「会被降级成什么」,让人没机会「填了不生效」 |
| 九 | 工程层也要对得上 —— 模块目录没登记 / 账本没记 / 改了 spec 没重出成品 / hud/ 没写回 spec,这四种错一个都不报错;check 照着 PROJ-* 逐条查,组装时也会主动说一句 |
自检
python forge.py selftest
四十二项,含九项负向用例(故意写错,断言必须被拦下)与三次往返比对:值域拦截、常驻位置、
空关键词、模块 CSS 漏前缀、变量三处同步、主题写错槽、浅灰字压浅底、恶意包逃逸 …
逐项在查什么,见 文档/使用说明.md §七。
已验:艾瑟兰 2.5MB 实卡(174 条目 / 6 分组 / 49 变量 / 15 正则,全程只读)解包再打包、转酒馆再回来, 语义均完全一致;新卡开出来即 0 错、界面守则 0 错、挂五个模块后 JS 单测 187 项全过。