tokai-teiodh/dsh-script-cards ↗★ 0
dsh-script-cards
DSH 剧本档案面板:把项目里的卡片排成方片网格和两级分支画布,拖动、连线、染色,改动直接写回卡片文件。Card grid and branch canvas for a project's Markdown cards inside DeepSeek Harness; edits go straight back to the files. 适合剧本创作者在可视化界面中管理和修改Markdown卡片。
Install
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:tokai-teiodh/dsh-script-cardsREADME
Read the full README ↗dsh-script-cards
A DeepSeek Harness (DSH) panel for script archives. It lays a project's Markdown cards out as a card grid and a two-level branch canvas, and writes your edits back to the card files.
给 DeepSeek Harness(DSH) 用的剧本档案面板:把项目里的卡片文件排成方片网格和 两级分支画布,在浏览器里直接改,改动写回卡片文件(结构进 git,不是存在浏览器里)。
它来自一个实际的剧本写作流程:剧本跨很多次会话,人物、设定、待定问题、章节与 情节分支都需要一个能一眼看完的地方。这个面板就是那个地方。
它做什么
两个视图,两族卡片,互不串门。
| 视图 | 收哪些卡片 |
|---|---|
| 方片 | 存档族:project character setting scene beat dialogue open decision reference |
| 分支 | 分支族:chapter node condition result |
- 方片:自适应圆角方片网格、按类型分组、标签单行横滑(滚动条不悬停不出现)、 星标收藏 + 置顶、点开看正文。
- 分支(两级菜单):上级排章节、下级排该章节里的情节,配一条浏览器式导航栏 (后退 / 前进 / 回到上级 / 刷新 + 面包屑地址栏)。双击章节卡片进下级。
- 画布:
Ctrl+滚轮缩放(走 25/33/50/67/80/100/125/150/200% 这些档位)、滚轮平移; 从卡片右侧圆点拖出贝塞尔连线;底部是 iOS 后台式的堆叠条,把没上画的卡片拖进去或点一下落位。 - 四种卡片形态
- 章节卡片:
章节序号+章节名分两段显示,下面标签,最下简介;展开后是节点列表。 章节是容器,所以没有「故事内时间」这一项。 - 节点卡片:左上节点名、右上时间、下方简介;展开后简介让位,依次排
角色 / 场景 / 内容(读正文里的## 角色## 场景## 内容小节)。 - 分歧节点:在节点基础上,右侧一列选项,每个选项各带一个可拖的出口,卡片上就有
「+ 选项」;接通后标出走向(
→),展开后选项在下面单列一块,带一键增删。 - 条件 / 结果卡片:只有两行(类型 + 内容);条件卡只有右出口,结果卡只有左入口, 右键可以互切。
- 章节卡片:
- 右键菜单:复制 / 剪切 / 粘贴 / 原地复制 / 染色 / 展开 / 移出画布 / 删除,
空白处右键是新建(含常用类型与分歧节点)/ 粘贴 / 自动排列;快捷键
Ctrl+C X V D、空格、Delete、Ctrl+0/1、Esc。 - 展开动画:先把视角平移到卡片居中(整块画布一个 transform,别的卡片相对位置不变) → 放大 → 背景(含其他卡片)虚化。
安装
这个包没有被 npm 发布,直接以源码形式放进 DSH 的 profile:
# 1) 克隆进 profile 的 node_modules
git clone https://github.com/tokai-teiodh/dsh-script-cards "$env:DSH_HOME\profiles\desktop\node_modules\dsh-script-cards"
# 2) 把包名加进该 profile 的 dsh.profile.bundles(见 profiles/
/package.json)
# "dsh": { "profile": { "bundles": [ ..., "dsh-script-cards" ] } }
# 3) 重启 DSH
为什么必须重启:DSH 的浏览器客户端名单在 boot 时由 profile 的
dsh.profile.bundles 组装,Typert 清单也是启动时扫描的。改完不重启不生效。
装好之后:会话头会出现「剧本档案」按钮,右侧栏多一个可停靠的页(与对话并排), 左侧栏也有一页全屏的。
目录约定(可改)
/
剧本档案/
卡片/*.md ← 一张卡片一个文件
归档/*.md ← 会话总结
分支.json ← 结构:章节归属 / 画布坐标 / 分歧选项与连线 / 染色
内容在卡片文件里,结构在 分支.json 里,两个都落盘、都能进 git。
三个目录名不是写死的:面板右上角「设置」里可以改(按项目存在浏览器本地),
所以英文项目可以用 script-archive/cards/archive。
卡片格式
frontmatter 白名单(与 cards.py 保持一致):
id / type / title / code / chapter / mode / when / order /
summary / tags / color / created / updated / source
---
id: node-n1
type: node
title: 神社训练
when: 第4天
order: 10
summary: 一行简介(面板卡片最下面那行)
tags: 日常, 支线
---
## 角色
- 甲
## 场景
- 某地
## 内容
- 发生了什么
code:章节序号,例如G1.1(用于排序与地址栏)。chapter:节点所属章节的卡片键,形如card/。mode: branch:把这个节点画成分歧节点。color:卡片染色(面板右键改的是图谱里的值,优先于这里)。
配套工具(可选)
cards.py 是一个零依赖的 Python 小工具,负责建档案、加卡片、归档会话、刷新索引,
以及给模型生成简介留的两个子命令:
python cards.py init --root .
python cards.py add --spec card.json --root . # 新增卡片(--force 覆盖)
python cards.py archive --spec summary.json --root . # 归档一次会话
python cards.py index --root . # 重建索引
python cards.py list --root . [--type open]
python cards.py summaries --root . --missing-only --out summaries.json
python cards.py set-summary --spec summaries.json --root . # 只改 summary/updated
它不属于这个仓库的依赖,面板也不依赖它 —— 手工写卡片文件一样能用。
它是怎么把改动写回磁盘的
浏览器端本来只有一条只读文件通道(remote.workspaceFiles),改不动磁盘。
所以这个包有两半:
- 客户端半边
lib/client.js:全部界面。手写、不走打包, 用 DSH 的window.__ModuleLoader__.load({ id, factory })约定。 - 宿主半边
lib/index.js+lib/typert.js:注册一个 Cordis 服务scriptCardsFs(fsList / fsRead / fsWrite / fsMkdir / fsRemove / fsStat), 经 Typert 清单exports["./typert"]暴露给浏览器,客户端用ctx.remote.$mount(...)挂上自己的 descriptors,之后ctx.get('remote.scriptCardsFs')就是可调用的代理。
边界:每个方法第一个参数 root 就是边界,所有读写都必须落在它之内
(.. 逃逸、相对路径、磁盘根目录都会被拒)。宿主不需要知道目录叫什么名字。
桥挂不上时(网关没起来、版本不匹配……)面板会退化成只读模式, 并在顶部把原始失败原因写出来,而不是静默地把你的改动丢掉。
踩过的坑,写在
lib/typert.js顶部:网关会拒绝任何与它自己那个RemoteNamespaceService原型同名的办法(remove就在原型上),撞上就$mount抛错、整个命名空间装不上、面板静默只读。所以六个方法统一带fs前缀, 并且测试里断言它们不撞保留名。
开发
npm run build # src/*.js → lib/client.js(产物,别手改)
npm run check # 构建一致性 + 三个 node --check + 两套无头测试
客户端源码拆成 src/ 下 13 个片段,由 scripts/build.mjs 拼成一个
__ModuleLoader__.load(...) 调用(客户端半边必须是一个文件)。改代码请改 src/。
测试都是零依赖、纯 Node:
tests/host.mjs—— 宿主半边:六个文件原语在真临时目录上跑通、UTF-8 往返、root边界越界全被拒、清单形状、保留名不冲突,以及浏览器半边挂上去的 descriptors 与宿主清单逐端点对齐。tests/smoke.mjs—— 客户端半边:自带一个极小的 React 替身 + 假 DOM, 真的驱动lib/client.js,覆盖两族卡片、两级画布、四种卡片形态、右键菜单、 缩放平移、展开动画、目录名可配置、色卡可自定义,以及编辑真的写回了 卡片文件与图谱文件。这个替身会专门造两种时间差:setState不是同步渲染 (defer / flush)、以及组件卸载后 hook state 归零 —— 两类都对应真机上报过的 问题(拖动不落盘、对话框带着上一次的输入)。
tests/host.mjs的 D / E 两段会加载lib/typert.js,而它跟运行时一样从安装它的 那个 DSH profile 解析zod。所以在裸克隆里跑,这两段会打印SKIP并跳过; 想让它们真正跑起来,请在profiles/ /node_modules/dsh-script-cards里执行。
测不到的部分(真 DOM 布局、CSS、SVG 箭头、真实 Typert 网关)要靠真机重启验证。
License
MIT © tokai-teiodh