🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

小说工坊 ShanJun-pro-max/dsh-novel-workbench

DeepSeek Harnessv3.4.2
ShanJun-pro-max10 天前有提交已提供中文文档扫描:含敏感能力

DeepSeek Harness 的一页式小说创作工作台:开新章给方向、候选稿抽卡、剧情总结带索引、别称识别、情节体检、偏好档案按类型跨书继承

36.2/100
综合分
4
GitHub 星标
800
npm 周下载
—/5
用户评分

数据截至 2026/10/25 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-novel-workbench
npm 源 · dsh-novel-workbench 的发布者账号已校验与本仓库一致

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · other
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 10 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✓npm 包dsh-novel-workbench @ 3.4.2
✓Node 引擎要求 >=20 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/3 01:03:32

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 224 处,展示前 79 条)
  • 发起网络请求
    lib/client.js:193
    r = await fetch(API, {
  • 写入剪贴板
    lib/client.js:227
    await nav.clipboard.writeText(s)
  • 写入剪贴板
    lib/client.js:2531
    void navigator.clipboard.writeText(text).then(() => {
  • 发起网络请求
    lib/client.js:6662
    React.createElement('button', { onClick: () => leaveFlow.request(() => setOpen(false)), style: { ...btn('ghost'), fontSize: 11.5, textAlign: 'left' }
  • 发起网络请求
    lib/client.js:6945
    if (e.target === e.currentTarget) leaveFlow.request(() => setOpen(false))
  • 读取本地文件
    lib/server/context.js:39
    if (existsSync(next)) return next
  • 读取本地文件
    lib/server/context.js:40
    if (existsSync(legacy)) return legacy
  • 读取本地文件
    lib/server/context.js:87
    if (existsSync(next)) return next
  • 读取本地文件
    lib/server/context.js:88
    if (existsSync(legacy)) return legacy
  • 读取本地文件
    lib/server/context.js:100
    if (configured !== '' && existsSync(configured)) {
  • 写入本地文件
    lib/server/context.js:145
    await mkdir(dirname(p), { recursive: true })
  • 写入本地文件
    lib/server/context.js:146
    await writeFile(p, JSON.stringify(next, null, 2) + '\n', 'utf8')
  • 写入本地文件
    lib/server/context.js:173
    const ensureDir = (dir) => mkdir(dir, { recursive: true })
  • 读取本地文件
    lib/server/context.js:179
    entries = await readdir(dir, { withFileTypes: true })
  • 写入本地文件
    lib/server/api.js:86
    await mkdir(dirname(marksPathOf(dir)), { recursive: true })
  • 写入本地文件
    lib/server/api.js:87
    await writeFile(marksPathOf(dir), JSON.stringify(marks, null, 2) + '\n', 'utf8')
  • 写入本地文件
    lib/server/api.js:107
    await mkdir(dirname(picksPathOf(dir)), { recursive: true })
  • 写入本地文件
    lib/server/api.js:108
    await writeFile(picksPathOf(dir), JSON.stringify(picks, null, 2) + '\n', 'utf8')
  • 读取本地文件
    lib/server/api.js:140
    entries = await readdir(dir, { withFileTypes: true })
  • 写入本地文件
    lib/server/api.js:305
    await mkdir(bakDir, { recursive: true })
  • 写入本地文件
    lib/server/api.js:307
    await writeFile(bak, detail.text, 'utf8')
  • 读取本地文件
    lib/server/api.js:326
    if (existsSync(sum)) {
  • 读取本地文件
    lib/server/api.js:699
    while (existsSync(target)) {
  • 删除本地文件
    lib/server/api.js:731
    await rm(full, { force: true })
  • 删除本地文件
    lib/server/api.js:762
    await rm(full, { force: true })
  • 删除本地文件
    lib/server/api.js:778
    await rm(f.path, { force: true })
  • 删除本地文件
    lib/server/api.js:793
    await rm(f.path, { force: true })
  • 删除本地文件
    lib/server/api.js:799
    await rm(trashFilesIndexOf(project.projectDir), { force: true })
  • 读取本地文件
    lib/server/api.js:930
    if (existsSync(path)) throw new Error('已经有这个类型了:' + name)
  • 读取本地文件
    lib/server/api.js:954
    if (!existsSync(cx.typeProfilePath(name))) throw new Error('没有这个类型:' + name)
  • 读取本地文件
    lib/server/api.js:976
    if (!existsSync(dir)) throw new Error('没有这个类型:' + name)
  • 删除本地文件递归
    lib/server/api.js:1025
    await rm(full, { recursive: true, force: true })
  • 执行外部命令来自配置/变量
    lib/server/api.js:4916
    child = spawn(cmd, args, { windowsHide: false })
  • 执行外部命令来自配置/变量
    lib/server/api.js:5025
    const cp = spawn(opener[0], opener[1], { detached: true, stdio: 'ignore' })
  • 读取本地文件
    lib/core/workspace.js:48
    if (existsSync(next)) return next
  • 读取本地文件
    lib/core/workspace.js:49
    if (existsSync(legacy)) return legacy
  • 读取本地文件
    lib/core/workspace.js:80
    return await readdir(dir, withFileTypes === undefined ? undefined : { withFileTypes: true })
  • 写入本地文件
    lib/core/workspace.js:351
    await mkdir(stateDirOf(projectDir), { recursive: true })
  • 读取本地文件
    lib/core/text.js:46
    return await readFile(path, 'utf8')
  • 读取本地文件
    lib/core/text.js:55
    const parsed = JSON.parse(await readFile(path, 'utf8'))
  • 写入本地文件
    lib/core/text.js:64
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    lib/core/text.js:65
    await writeFile(path, JSON.stringify(value, null, 2), 'utf8')
  • 读取本地文件
    lib/core/text.js:72
    const info = await stat(path)
  • 读取本地文件
    lib/core/text.js:82
    return path !== '' && existsSync(path)
  • 写入本地文件
    lib/core/text.js:185
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    lib/core/text.js:187
    await writeFile(temp, text, 'utf8')
  • 写入本地文件
    lib/core/text.js:188
    await rename(temp, path)
  • 读取本地文件
    lib/core/text.js:205
    const parsed = JSON.parse(await readFile(path, 'utf8'))
  • 写入本地文件
    lib/core/pack.js:499
    await mkdir(dirname(result.path), { recursive: true })
  • 写入本地文件
    lib/core/pack.js:500
    await writeFile(result.path, result.text, 'utf8')
  • 写入本地文件
    lib/core/pack.js:515
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    lib/core/pack.js:516
    await writeFile(path, String(text), 'utf8')
  • 读取本地文件
    lib/core/pipeline.js:285
    return await readFile(path, 'utf8')
  • 读取本地文件
    lib/core/pipeline.js:294
    const info = await stat(path)
  • 读取本地文件
    lib/core/pipeline.js:305
    return await readdir(dir)
  • 读取本地文件
    lib/core/plot.js:212
    return await readFile(path, 'utf8')
  • 读取本地文件
    lib/core/rules.js:88
    raw = await readFile(pathOf(projectDir), 'utf8')
  • 写入本地文件
    lib/core/rules.js:128
    await writeFile(pathOf(projectDir), JSON.stringify({ items, trash }, null, 2) + '\n', 'utf8')
  • 读取本地文件
    lib/core/ledger.js:518
    text = await readFile(path, 'utf8')
  • 读取本地文件
    lib/core/ledger.js:544
    names = await readdir(dir)
  • 读取本地文件
    lib/core/ledger.js:556
    const info = await stat(path)
  • 读取本地文件
    lib/core/detail.js:16
    const read = (path) => readFile(path, 'utf8').catch(() => '')
  • 读取本地文件
    lib/core/detail.js:27
    listed = await readdir(join(projectDir, '细纲'), { withFileTypes: true })
  • 读取本地文件
    lib/core/detail.js:38
    if (existsSync(p)) files.push({ name: rel, path: p })
  • 读取本地文件
    lib/server/work-create.js:62
    if (!existsSync(p)) {
  • 写入本地文件
    lib/server/work-create.js:63
    await mkdir(p, { recursive: true })
  • 读取本地文件
    lib/server/work-create.js:68
    if (!existsSync(outline)) {
  • 写入本地文件
    lib/server/work-create.js:69
    await writeFile(outline, outlineOf(name), 'utf8')
  • 读取本地文件
    lib/server/work-create.js:73
    if (!existsSync(readme)) {
  • 写入本地文件
    lib/server/work-create.js:74
    await writeFile(readme, readmeOf(name), 'utf8')
  • 读取本地文件
    lib/server/work-create.js:100
    if (existsSync(target)) throw new Error('这个名字已经存在:' + target)
  • 写入本地文件
    lib/server/work-create.js:102
    await mkdir(target, { recursive: true })
  • 读取本地文件
    lib/server/work-create.js:115
    if (!existsSync(target)) throw new Error('这个目录不存在:' + target)
  • 读取本地文件
    install.mjs:28
    if (!existsSync(file)) {
  • 读取本地文件
    install.mjs:32
    const raw = await readFile(file, 'utf8')
  • 写入本地文件
    install.mjs:60
    await writeFile(file, JSON.stringify(pkg, null, 2) + '\n', 'utf8')
  • 写入本地文件
    lib/core/annotate.js:69
    await mkdir(dirname(path), { recursive: true })
  • 写入本地文件
    lib/core/annotate.js:71
    await writeFile(path, JSON.stringify(payload, null, 2), 'utf8')
  • 删除本地文件
    lib/core/annotate.js:160
    if (list.length === 0) await unlink(annotationPathOf(projectDir, chapter)).catch(() => {})
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/dsh-llm
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
小说工坊 · dsh-novel-workbench 给 DeepSeek Harness 用的一页式小说创作工作台。 写长篇最难受的不是写不出来,是写着写着人物崩了、伏笔丢了、前文忘了。这个插件不替你写,它做两件事: - 把这一章需要知道的一切整理好端到你面前(写作包); - 读几本你认可的成品书,把它们的写法揉成一套标准,写稿时照着来(成品书库)。 它解决什么 旧做法:每次写新章,翻七八个文件——大纲、人物卡、前几章正文、伏笔清单——翻完上下文也乱了。 这个插件:点一下「开新章」,自动挑出这一章用得上的材料,拼成一份写作包: 近 10 章剧情总结 全文,一条不删 10 章之前 每章一句话例外:本章要出场的配角在前面出现过 → 把那几章升级成全文本章大纲 前后各 1 章给全字段更远的大纲 只在提到相关人物时,带「目标 + 埋」上一章正文 只取结尾 1400 字(接话用)人物卡 名字(含别称)出现才读 每次都会告诉你读了什么、省了多少,不是黑盒。 安装 两步都要做:先装包,再把它挂进 profile 的组合层。只做第一步,包装好了但界面里什么都没有。
展开完整文档(README 剩余 1 节)
小说工坊 · dsh-novel-workbench

给 DeepSeek Harness 用的一页式小说创作工作台。

写长篇最难受的不是写不出来,是写着写着人物崩了、伏笔丢了、前文忘了。这个插件不替你写,它做两件事:

- 把这一章需要知道的一切整理好端到你面前(写作包);
- 读几本你认可的成品书,把它们的写法揉成一套标准,写稿时照着来(成品书库)。

它解决什么

旧做法:每次写新章,翻七八个文件——大纲、人物卡、前几章正文、伏笔清单——翻完上下文也乱了。

这个插件:点一下「开新章」,自动挑出这一章用得上的材料,拼成一份写作包:

近 10 章剧情总结 全文,一条不删 10 章之前 每章一句话例外:本章要出场的配角在前面出现过 → 把那几章升级成全文本章大纲 前后各 1 章给全字段更远的大纲 只在提到相关人物时,带「目标 + 埋」上一章正文 只取结尾 1400 字(接话用)人物卡 名字(含别称)出现才读

每次都会告诉你读了什么、省了多少,不是黑盒。

安装

两步都要做:先装包,再把它挂进 profile 的组合层。只做第一步,包装好了但界面里什么都没有。

1. 装包

推荐走 npm(不碰 GitHub,国内配个镜像就能装):

dsh plugin --profile web add dsh-novel-workbench

连不上 npm、或者想跟最新提交走,就改用 GitHub 源(装出来是同一个包):

dsh plugin --profile web add github:ShanJun-pro-max/dsh-novel-workbench

--profile web 必须写在 add 前面。 新版 dsh 把 --profile 定为必填项,而且它属于 plugin 这一层——写成 dsh plugin add --profile web … 会报:

error: required option '--profile <name>' not specified

因为 add 之后的内容会原样转给 pnpm,dsh 自己没读到 --profile。

profile 名怎么选:用 dsh web 就是 web;用桌面版客户端就是 desktop。不确定就看 ~/.dsh/profiles/ 下有哪些目录。

2. 挂进组合

node ./node_modules/dsh-novel-workbench/install.mjs web

脚本只做一件事:把 dsh-novel-workbench 写进 ~/.dsh/profiles/web/package.json 的 dsh.profile.bundles。手动做也行——在那个文件里找到:

"dsh": { "profile": { "bundles": [ "...", "这里加一行 dsh-novel-workbench" ] } }

(这一步别省。dsh plugin add 只把包装进 node_modules,组合层是独立的另一份配置,插件自带的 cordis.patch.yml 靠它才会被应用。)

3. 重启

重启 dsh web,侧栏底部出现「📚 小说工坊」,点开看左侧导航最底部的版本号(最新是 v3.0.5)。

本地开发:

改完代码,一条命令走完:体检 → 发 npm → 让 pnpm 拉最新 → 体检安装副本 → 接口冒烟 .\sync.ps1 -Publish

只拉最新、不发版 .\sync.ps1

⚠️ 别手动把仓库文件抄进 ~/.dsh/profiles/web/node_modules/dsh-novel-workbench。
那个目录归 pnpm 管,账记在 node_modules/.modules.yaml 里(含版本号)。手抄进去的文件一旦版本号变了,安装目录和 pnpm 的账本就对不上,宿主启动时会报 1 entry did not activate → import failed,整个 web 起不来——手抄省两分钟,要用一次「删插件 + 重装」来还。

更新

1. 让它重新解析到最新版(npm 装的就拉最新版本,github 装的就拉最新提交)
dsh plugin --profile web update dsh-novel-workbench

2. 重启 dsh web

开工作台看左下角版本号有没有变。(dsh.profile.bundles 里的那一行只需加一次,更新时不用再动。)

⚠️ 重复执行 add 是没用的。 对 github: 来源,pnpm 会把解析到的提交写进 pnpm-lock.yaml;第二次 add 看到 spec 没变,会说 Lockfile is up to date, resolution step is skipped / Already up to date,然后原样跳过——包还是旧的。必须用 update 才会重新解析。

装了插件市场的话

市场会自己判断有没有新版本:它读你仓库 package.json 的 version,和本地装的比。远端版本号更大 → 已安装列表里出现「更新」按钮,点一下就装。

所以发版时务必改版本号(本项目用 version.ps1);只推提交不改版本号的话,市场只会标一句「有新提交(未发版)」而不给更新按钮 —— 这是市场的规则,不是插件的问题。

还是旧版本? 先把依赖清掉再装:

Windows
rmdir /s /q "%USERPROFILE%\.dsh\profiles\web\node_modules\dsh-novel-workbench"
dsh plugin --profile web update dsh-novel-workbench

从源码装的(file: 方式)更新更简单:

cd /path/to/dsh-novel-workbench
git pull

然后重启 dsh web。

快速开始

1. 准备作品目录

插件按约定找文件。最小结构:

你的小说/ ├─ 大纲.md ← 全书章节骨架 ├─ 正文/ │ ├─ 第001章-标题.txt │ └─ 第002章-标题.txt └─ 剧情/ ├─ 第1章 剧情总结.md └─ 第2章 剧情总结.md

全部可选,缺什么插件会标红提醒你补。推荐再放:

├─ 设定/ │ ├─ 立项.md │ ├─ 世界观.md │ ├─ 人物设定.md │ ├─ _口径.md ← 名字表、时间线、写作禁忌 │ └─ 道具与增益台账.md └─ 人物/ ├─ 陆铮.json └─ 萧烬.json

2. 配置

侧栏 → 小说工坊 → ⚙️ 设置:

| 字段 | 说明 |
| 候选目录 | 抽卡候选稿落点,如 …/作品/.dsh-novel-studio/候选稿/第12章 |
| 作品根 | 留空则从候选目录向上自动探测 |
| 模型 | 留空用 dsh 当前默认模型 |

3. 开始用

- 章节 — 看全书进度,点标题编辑,点「📝 总结」一键生成剧情总结
- 候选稿 — 「✍️ 开新章」让模型给写作方向 → 写多篇候选 → 逐段 👍/👎 → 合稿定稿
- 情节 — 张力曲线、伏笔欠账、一致性检查、别称识别
- 偏好档案 — 标过的段落压成可复用的写作规律,按「类型」跨书继承
- 作品分析 — 偏好档案页的这个按钮:灌几本成品书,一键分析后把它们的写法融成一套标准,写进偏好档案供生成时照着写
- 回收站 — 删掉的章节 / 候选稿 / 类型都能找回
- 设定 — 浏览、编辑、新建、归档所有设定文件

核心功能

开新章

① 这一章写什么 → 编辑本章设定(目标 / 禁止发生 / 出场人物 / 要求) ② 方向 → 模型给 3–16 个写法方向,勾选要写的 ③ 写候选 → 一次一篇,看得见进度,可随时停 ④ 完成 → 去候选稿逐段标 👍/👎

写稿时不会跳页,状态不会丢。

弹窗右侧是「📊 开章体检」(默认展开,可收起):

| 块 | 看什么 | 花钱吗 |
| 📈 张力曲线 | 第 1 章到上一章的张力和一句该做的动作 | 不花(本地算) |
| 🔍 结构问题 | 只挑和这一章有关的:刚塌陷、全书偏平、上一章字数失衡/大纲对不上 | 不花 |
| 🧭 下一章往哪写 | 写作包里已给模型的节奏简报;「🤖 让模型给 3 条走向」可一键填进额外要求 | 点按钮才花 |
| 📌 这一章要注意 | 本章设定有没有写目标、有没有写禁止发生、相关欠账、目标字数 | 不花 |
| 🧵 还没收的欠账 | 未兑现的埋点,和本章人物有关的排前面 | 不花 |

剧情总结带索引头 markdown

chapter: 11
characters: [陆铮, 萧烬, 闫振军, 柯朗]
location: [城东老院子]
planted: [萧烬体质收不住, 闫振军单独留话]
paid: []

第11章 剧情总结

核心剧情点

- ...(正文照旧,怎么写都行)

头上 6 行给机器查,下面照旧给你写。 有了它才能查「李满在第几章退场」。

characters 用规则提取(扫正文里出现的已知角色名),不调模型——比模型准,而且免费。

别称识别

小说里没人一直叫全名。插件会扫出「李哥」「萧先生」这类称呼,并且:

- 共现分析:抓每次出现的前后 50 字,看这段窗口里还出现了谁

「萧总」出现 3 次第4章 "小程。""萧总。""我给他那张卡" 附近:萧烬第4章 "稍等。""萧总,没有记录。" 附近:萧烬 → 推测:萧烬

- 同姓不排除:「萧先生」可能是萧烬也可能是萧宗平,两个都列出来
- 你拍板:机器只给依据,标 ✓ 建议选它 / ⚠ 说不准是谁

采纳后写进角色卡 "别称": ["李哥"],索引、一致性检查、写作包选人三处都认。

情节体检

| 检查 | 说明 |
| 张力曲线 | 按正文冲突信号估分,标出「中段塌陷」「全程平缓」 |
| 伏笔欠账 | 大纲里「埋 / 收」配对,找出该收没收的 |
| 一致性 | 总结漏记人、有卡没出场、总结缺索引头(纯规则,不花钱) |

偏好档案按类型继承

写完一本兽人小说,建个类型「兽人校园」。下本同类型的书绑同一个类型,自动继承所有偏好规律。

类型库在 ~/.dsh/novel-studio-types/,跨书共用。删错能恢复。

从成品书库学写法

「照着自己读过的好书写」这件事,靠感觉很难量化。这功能把它做成流水线:把几本成品书灌进去,让模型提炼出能照着执行的写法。

偏好档案页有一个 📊 作品分析 按钮,点开左右分区:左边书库,右边模型给出的写法。

加书:.txt / .md / .docx / .epub 都行,几 MB 到 40MB 自动分片上传,不会撑爆请求。加进来的书默认勾上,不想要自己取消。

点一次「▶ 一键分析」,勾上的书按三步走完,最后自动合并成一套写法:

① 粗扫 每 40 块一批,模型判断哪些块值得细看(只有这步便宜) ② 精读 只读被挑中的块,逐块提炼风格 / 手法 / 可否借鉴 ③ 汇总 每本合成一份「这本书的写法」 ④ 融合 几本的结论揉成一套「照着写就能像它们」的标准 ← 最终给你的是这个

新书只扫它没扫过的部分,已扫过的书跳过;中途可以停,进度落盘,再点接着做。

融合出来的标准不是一份看完就完的报告,点 「+ 写进偏好档案」 会写进当前类型的偏好档案,而偏好档案正是写作包第 ② 节——之后每次写候选稿,模型都带着这套写法。书越多,规律越硬。

写稿时模型能读到什么:写作包新增第 ⑬ 节「对标写法」,带最近一次融合报告 + 各书报告的「风格 / 水准」两段。只喂结论,不喂正文原文——既不让上下文爆掉,也避免把别人的原句搬进你的稿子。

两个长列表(扫描判定、粗扫结果)都默认收起,只留一行汇总,点开才看逐块理由。

数据存在哪

| 内容 | 位置 |
| 章节正文 | <作品>/正文/ |
| 剧情总结 | <作品>/剧情/ |
| 人物卡 | <作品>/人物/*.json |
| 候选稿 | <作品>/.dsh-novel-studio/候选稿/第N章/ |
| 标注 | <作品>/.dsh-novel-studio/候选稿/第N章/.dsh-novel-studio/marks.json |
| 偏好档案 | <作品>/.dsh-novel-studio/作者偏好档案.md |
| 备份 | <作品>/.dsh-novel-studio/备份/ |
| 类型库 | ~/.dsh/novel-studio-types/ |
| 成品书库 | ~/.dsh/novel-learn/<任务ID>/(全书、切好的块、粗扫判定、精读、报告) |
| 跨书融合 | ~/.dsh/novel-learn/跨书分析/ |

全部是普通文件,随时能用编辑器打开、能用 Git 管理、能手动改。

安全设计

| 机制 | 说明 |
| 删除 | 一律先进回收站,可恢复;回收站里再「彻底删」才真删 |
| 覆盖 | 改正文 / 总结 / 设定文件前自动备份到 .dsh-novel-studio/备份/ |
| 未保存 | 改正文没保存就切页面 / 关窗口,会弹确认 |
| 路径 | 只能读写作品目录内的 .md / .txt / .json,越界拒绝 |
| 接口 | 只接受本机回环请求 |

常见问题

入口没出现?
重启 dsh web。还不行就 Ctrl + F5 硬刷新(浏览器会缓存旧 bundle)。

装的时候报 error: required option '--profile <name>' not specified?
把 --profile web 挪到 add 前面:dsh plugin --profile web add github:…。
--profile 属于 plugin 这一层,写在 add 之后会被当成要转给 pnpm 的参数,dsh 自己读不到。

装/更新时报 ERR_PNPM_GIT_RESOLVE_FAILED、Connection was reset?
这一步是 pnpm 在跑 git ls-remote 解析仓库,和插件本身无关——机器连不上 github.com。三选一: bash

1) 先开代理(最常见)

2) 只给 git 配代理,端口换成你自己的

git config --global http.https://github.com.proxy http://127.0.0.1:7890

3) 有 SSH key 的话走 SSH 传输(pnpm 输出里推荐的那条)

git config --global url."git@github.com:".insteadOf "https://github.com/"

同一屏里那句 git-hosted plugins build on install via their prepare script 是 dsh 的通用提示,这个包没有 prepare 脚本,忽略即可。

「这个 dsh 没挂 LLM 服务」?
开新章、生成总结、识别别称这些要调模型。在设置里指定模型,或确认 dsh 已有默认模型。

文件明明在,清单却标红?
点那个 ✗,右边有三个选项:手动新建 / 让模型去文件夹里找 / 知道了。
找到后归属关系会记在 .dsh-novel-studio/文件归属.json,以后就认了。

总结里的 HTML 注释是什么?
那对,是提炼规律时标记自动生成区块用的。别删。

开发 bash

语法检查

npm run check

装到本地 profile

dsh plugin --profile web add file:$(pwd)

结构:

lib/
├─ index.js 插件入口(注册唯一 RPC 端点 /novel-studio/api/) ├─ client.js 前端(宿主只加载这一个文件,不能拆) ├─ server/ │ ├─ api.js 74 个业务接口 │ ├─ context.js 路径约定、项目识别、配置读写 │ ├─ summary.js 总结提示词、头部解析、输出清洗 │ ├─ distill.js 提炼规律 │ └─ pack-assemble.js 写作包取材策略 └─ core/ 纯逻辑(读文件、合稿、分章、情节分析)

后端只有一个 HTTP 端点,前端用 { a: '动作名' } 分发。加功能只加一个方法。

改完代码必须重启 dsh web 才生效。

功能总览

完整的功能说明、设计思路、数据存放位置、已知限制,见 功能总览.md。

更新日志

每个版本改了什么,见 CHANGELOG.md。

License

MIT
预览分享卡片长什么样 ↓ShanJun-pro-max/dsh-novel-workbench
分享 · DeepSeek Harness Hub
ShanJun-pro-max/dsh-novel-workbench

DeepSeek Harness 的一页式小说创作工作台:开新章给方向、候选稿抽卡、剧情总结带索引、别称识别、情节体检、偏好档案按类型跨书继承

回到顶部