dsh-reading

uitested: passnpmGPL-3.0-or-later

Reads EPUB, MOBI, AZW3, TXT and PDF books in a side panel with pagination, table-of-contents jumping, highlights, notes, bookmarks, full-text search, anchored AI questions, per-chapter distillation, Legado-compatible book-source search that adds online books chapter by chapter, and a bundled reading

下载量 / Downloads
1,650
依赖体积 / node_modules
1.93 MB
测试时间 / Tested
2026-11-06
DSH 版本 / Version
0.2.2-rc.1

Install

dsh plugin --profile web add dsh-reading

Sandbox test results

StageResult
1 install✓
2 list✓
3 mount✓
4 boot—

Four stages run in an isolated sandbox: install (network on) → list → mount → boot (last three with no network).

README

dsh-Reading

在 DeepSeek Harness(下称 DSH)里读书的插件。

导入 EPUB / MOBI / AZW3 / TXT / PDF,分页读,做目录跳转,记住读到哪,划线、写笔记、加书签。

没有账号,没有服务器,没有遥测:书和笔记都在你自己的磁盘上。

状态:0.9.6,M1「能读书」+ M2「能划线」+ 搜索/标注交互 + 手机与多端 + AI 状态诊断与读书蒸馏 + 书源搜书,
支持五种输入格式(EPUB / MOBI / AZW3 / TXT / PDF 文字版)。
这一版加上:Legado 书源的导入与搜书(用自己的源,抓目录、按章取正文)、
阅读设置按「文字 / 纸张 / 翻页」分组。插件不内置任何书源,也不带默认站点。
跨端同步(WebDAV)还没做,见 [路线图](#路线图)。

它解决什么问题

DSH 里能读文档,但读书是另一件事:一本 30 万字的小说需要分页、需要目录、

需要「上次读到哪」、需要把喜欢的句子划下来日后翻回去。

dsh-Reading 就是补这一块。它只有一条设计原则,写在所有实现细节之前:

锚点是 EPUB CFI,不是百分比,不是「第几个字符」。

百分比会漂:改字号、换设备、换窗口宽度,百分比指的位置就变了。

CFI 是 EPUB 规范里定义的结构化位置(epubcfi(/6/4[ch01]!/4/2/2/1:12)),

它指「第 4 个 spine 项、body 下第 2 个 div 的第 2 个 p 的第 1 个文本块的第 12 个字符」。

改字号不影响它,换设备也不影响它 —— 前提是实现严格按规范(下面「它凭什么可信」讲这件事)。


安装

前置

项 · 要求

DSH · >= 0.2.0-rc.1 < 0.3.0(CLI dsh --version 自查)

Node.js · >= 22.19.0

平台 · 桌面版 DSH(Web UI;面板挂在左侧栏图标条)

从本地目录装

桌面版(Electron,profile 名通常是 desktop):profile 由应用独占管理,CLI 的

dsh plugin 会拒绝操作它(profile "desktop" is managed exclusively by the Electron application)。

请在应用里的插件管理界面安装这个目录。

命令行版 / 自建 profile:

dsh plugin --profile web add /path/to/dsh-Reading

(--profile 放在 add 前面;写成 dsh plugin add <dir> --profile web 会被当成 pnpm 参数、报缺 --profile。)

装完重启 DSH:插件清单在启动时确定,运行期改文件不会热载。

重启后左侧栏底部图标条里会出现一本打开的小册子,点它进书架。

卸载

dsh plugin --profile web remove dsh-reading

桌面版同样在插件管理界面里卸载。

卸载不会删你的书和笔记。它们在自己的目录里,见下节。


数据在哪

默认 ~/.dsh-reading/(可用配置项 libraryDir 改):

<libraryDir>/
├── settings.json                       # 阅读偏好:字号、行高、主题、栏宽
└── books/
    └── <bookHash>/                     # sha256(epub 字节) 的前 16 位
        ├── book.epub                   # 原始文件
        ├── meta.json                   # 书名、作者、spine 表
        ├── progress.json               # 读到哪(CFI + 百分比)
        ├── annotations.jsonl           # 划线 / 笔记 / 书签,一行一条
        ├── qa.jsonl                    # 给将来的 AI 问答留的位置(当前只读写结构)
        ├── quarantine.jsonl            # 解不开的锚点,只写不读
        └── history/                    # 被覆盖前的旧文件,保留最近 20 份

三条约定:

1. 备份就是复制这个目录。 没有数据库,没有二进制格式,全部是 JSON / JSONL。

2. quarantine.jsonl 只增不减。 解不开的锚点不进渲染,但记录永远留着 ——

那是你的数据,坏了也不该被悄悄扔掉。

3. 删除是墓碑,不是删行。 annotations.jsonl 里被删的记录变成 "deleted": true,

文件里那一行还在。这是将来做多设备合并的前提:没有墓碑,另一台设备会把它「复活」。


配置

装完就能用。要改的话,在 profile 的 cordis.patch.yml 里按行 id 覆盖:

- id: reading
  name: 'dsh-reading'
  config:
    libraryDir: /Volumes/books/dsh-reading   # 默认 '' = ~/.dsh-reading
    maxImportBytes: 209715200                # 默认 200 MB

改完同样要重启 DSH。


它能做什么

书架 — 列表带封面、进度圈、最近阅读。点封面进阅读视图。

「导入」选本地文件(也可以直接拖进窗口)。

对着卡片右键(或点右上角 ✎)是编辑入口:改书名、作者、简介,填分类、系列、

打标签、标成「我的珍藏」,换封面图,或者把封面恢复成默认的纸封面。

封面一律按 2:3 摆,同一行不会因为谁名字长就把旁边那张挤歪。

左上角两级下拉可以筛书架:全部 / 我的珍藏 / 按系列 / 按作者 / 按分类 / 按标签。

筛选是纯前端的,不动书的数据。细节见 [docs/13](docs/13-书籍信息与筛选.md)。

「导入」面板分三段:从电脑选文件、粘贴一条下载直链、或者用你自己填的书源搜关键词。

插件不内置任何站点,也不带默认书源——你填什么它抓什么,见 [docs/12](docs/12-找书与网络导入.md)。

格式 — 能读五种:

格式 · 呈现 · 说明

EPUB · 原样渲染 · 插图、版式都保留

MOBI / AZW3 · 重排为纯文字 · 没有插图与原版式,书架上标「重排」

TXT · 重排为纯文字 · 自动认 UTF-8 / UTF-16 / GB18030,按「第 X 章」切章

PDF(文字版) · 重排为纯文字 · 扫描件与加密 PDF 读不了;表格、分栏会退化成流水文字

导入按钮写「导入图书」,因为这五种格式都能导——不写「导入 EPUB」,免得让人以为只认 EPUB。

重排书的划线、笔记、进度和 EPUB 完全一样——锚点仍然是 CFI。

细节与取舍见 [docs/08](docs/08-格式支持-TXT与MOBI.md) 与 [docs/11](docs/11-格式支持-PDF.md)。

阅读 — 分栏分页;翻页可点正文两侧的窄边(悬停浮出 ‹ ›)、按键盘左右、滚轮或触控板两指横滑(一次手势只翻一页)。

字号、行高、栏宽、明暗都在阅读视图的设置里调,改动即时生效、立刻落盘。

目录 — 章节树,当前章节高亮,点一下跳过去。目录分三种来源,面板上会标明是哪一种:

  • 真目录(EPUB 自带的 nav / NCX):带层级;
  • 自动识别:TXT / MOBI 这类没有真目录的书,按正文里的「第 X 章」标题认出来,标「自动识别」;
  • 没有目录:正文里连章节标题都没有的书,面板会直接说「这本书没有目录」,不编造一个假的章节列表。

书里的目录页也能点 — 正文里那页手打的「目录」,只要章节名本身是链接(带下划线那种),

直接点那行字就跳到对应章节,跨文件的锚点也认;外链会问要不要用浏览器打开,

文内脚注(#fn-1)留在本章处理。

进度 — 关书时自动存 CFI;重开精确回到原处(不是「大概第 60%」)。

划线 / 笔记 / 书签 — 选中正文出菜单,五色高亮;一条高亮可以再挂笔记;

书签不依赖选中。侧栏列出本书全部标注,按章节、按颜色筛,点一条跳回去。

划过的线是能点的 — 点一下已有高亮,它进入选中态并浮出一条操作条:

你想做的事 · 怎么做

改颜色 · 点操作条上的五个色块,立刻生效、立刻存盘

写笔记 · 点「笔记」写到原来的锚点上,锚点不会丢

拿这段去问 AI · 点「问 AI」,这段原文自动带进提问面板

复制这段 · 双击高亮,整条文字变成系统选区,直接 Ctrl/⌘+C

删掉 · 点「删除」;5 秒内还能点提示条上的「撤销」找回

键盘党 · 高亮可 Tab 聚焦:Enter 选中整条、Delete 删除、Esc 收起

不会越叠越深 — 同一段文字不允许划第二次:再圈一段和已有高亮重叠的文字时不会新建,

而是把已有那条亮出来告诉你「这段已经划过线了」。老数据如果已经叠了,渲染时也只画一层,

同一块地方永远只有一个颜色。


圈住一段话,问问 AI

读到一段没读懂的话,用鼠标圈住它,浮出来的小菜单里点 问 AI,在底部输入框里写下你的问题(Enter 发送)。回答会直接显示在下面,同时带着这段原文的位置存进这本书的 qa.jsonl —— 下次翻回来还能找到它问过什么。

  • 回答只用这段原文和通识,不确定就说不确定,不编;
  • 小说里会刻意不剧透后面的情节;
  • 这个功能走宿主自己的子代理机制,所以你得先在 DSH 里选一个模型。没选模型时它会告诉你「还没选模型」,而不是默默失败。

问过的话找得回来 — 阅读视图工具栏有个「问答」入口,打开就是这本书的全部问答记录:

按时间倒序列出你问过什么、它答了什么、问的是哪一段原文、在第几章。顶部有搜索框,

点任意一条就跳回那段原文所在的位置,接着问也行(同一个锚点上续问,答案不会串)。

记录存在这本书自己的 qa.jsonl 里,不上传。

它凭什么可信

读完上一节你会问:凭什么说「重开回到原处」是精确的?这里把三件容易被做错的事列出来,

因为它们决定了你的笔记三年后还在不在原位。

一、高亮不碰正文

划线最自然的做法是往正文里插 <span class="hl">。dsh-Reading 不这么做,

因为插一个元素就会改变 CFI 的编号基准:

<p id="p">abcdef</p>          选 1~5  →  epubcfi(/4/2[p],/1:1,/1:5)
<p id="p">abc<span></span>def</p>   →  同一个选区变成 /1:1,/1:2

文本一个字没变,锚点全错。所以高亮画在覆盖层上:用 getClientRects() 拿到每段文字的矩形,

在父窗口的绝对定位层里画色块。正文 DOM 一个节点都不动 —— 这条有测试守着

(test/dom_immutability_test.mjs:加 50 条标注前后正文 DOM 结构指纹必须相同)。

二、CFI 计算强制带 filter

即使不碰正文,本项目自己也会往文档里注入东西(分页用的 <style>)。

所以所有 CFI 调用都强制带一个 filter:命中 data-dsh-reading-injected 的节点返回

FILTER_REJECT,整棵子树不参与索引。

(设计稿原本写的是 FILTER_SKIP。实测 vendor 对 SKIP 的处理前后不一致 ——

索引展开时把子节点并进父列表、生成路径时又把该节点那一段丢掉 —— 会算出一份

解回错位置的 CFI;改用 REJECT 后两边自洽。细节见 docs/07-审计修复记录.md 的 F7。)

这条不是可选项。client/src/cfi.js 的导出函数签名里没有 filter 参数 ——

想漏传也传不了。

三、校验分三层

foliate-js 的 parse() 很宽容:8 种明显畸形的 CFI 里有 5 种能「解析成功」,

其中两种最危险 —— 起止倒置(/1:8,/1:3)会静默折叠成零宽高亮,

跨文档(/6/4!/4/2)会落到 #document 上返回空文本。两者都不报错。

只靠 try/catch 挡不住。所以校验分三层:

层 · 检查 · 失败码

L1 语法 · parse() 不抛 + 恰好一个 ! · cfi_parse_failed

L2 语义 · 能解析成 Range + 起止不倒置 + 不跨文档 + highlight 不零宽 · `cfi_resolve

…(truncated)

Rendered from the README published on npm. Formatted only, not edited.

Versions

Published on npm::6,最后更新 2026-11-03

Links

GitHub · Homepage · npm

Same category