把 Zotero 和 DeepSeek Harness 接起来,双向。
数据截至 2026/10/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
dsh plugin --profile web add toustifer/zotero-dsh状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
安装兼容性检查需源码安装仓库 package.json 标记 private,未发布到 npm,需从源码安装;安装清单取自 packages/dsh-plugin/package.json
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/11 22:19:42
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
查看扫描证据(共 75 处,展示前 56 条)
- 读取本地文件install/heal-sessions.cjs:75
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return out } - 写入本地文件install/heal-sessions.cjs:94
fs.mkdirSync(QUARANTINE, { recursive: true }) - 读取本地文件install/heal-sessions.cjs:95
if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true }) - 写入本地文件install/heal-sessions.cjs:96
fs.renameSync(dir, dest) - 写入本地文件packages/dsh-plugin/src/fulltranslate.ts:93
try { mkdirSync(dir, { recursive: true }) } catch { } - 读取本地文件packages/dsh-plugin/src/fulltranslate.ts:100
if (!existsSync(p)) return null - 读取本地文件packages/dsh-plugin/src/fulltranslate.ts:101
const j = JSON.parse(readFileSync(p, 'utf8')) as FullTranslateJob - 写入本地文件packages/dsh-plugin/src/fulltranslate.ts:113
writeFileSync(tmp, JSON.stringify(job), 'utf8') - 写入本地文件packages/dsh-plugin/src/fulltranslate.ts:114
renameSync(tmp, p) - 读取本地文件packages/dsh-plugin/src/fulltranslate.ts:129
if (!existsSync(dir)) return [] - 读取本地文件packages/dsh-plugin/src/fulltranslate.ts:144
return readdirSync(dir) - 读取本地文件packages/dsh-plugin/src/mineru-manager.ts:54
try { dirs = readdirSync(cacheRoot) } catch { dirs = [] } - 读取本地文件packages/dsh-plugin/src/mineru-manager.ts:58
const sizeKb = existsSync(mdPath) ? Math.round(statSync(mdPath).size / 1024) : undefined - 读取本地文件packages/dsh-plugin/src/mineru-manager.ts:58
const sizeKb = existsSync(mdPath) ? Math.round(statSync(mdPath).size / 1024) : undefined - 读取本地文件packages/dsh-plugin/src/mineru-manager.ts:151
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true }) - 删除本地文件递归packages/dsh-plugin/src/mineru-manager.ts:151
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true }) - 请求外部主机mineru.netpackages/dsh-plugin/src/mineru-manager.ts:164
const res = await fetch('https://mineru.net/api/v4/file-urls/batch', { - 发起网络请求packages/dsh-plugin/src/mineru-manager.ts:164
const res = await fetch('https://mineru.net/api/v4/file-urls/batch', { - 发起网络请求packages/dsh-plugin/src/mineru-manager.ts:174
const res = await fetch(`${base.replace(/ +$/, '')}/health`, { method: 'GET' }) - 读取本地文件packages/dsh-plugin/src/panel-api.ts:193
if (!existsSync(p)) return { conversations: [] } - 读取本地文件packages/dsh-plugin/src/panel-api.ts:194
const raw = JSON.parse(readFileSync(p, 'utf8')) as any - 写入本地文件packages/dsh-plugin/src/panel-api.ts:239
mkdirSync(dirname(chatStorePath()), { recursive: true }) - 写入本地文件packages/dsh-plugin/src/panel-api.ts:240
writeFileSync(chatStorePath(), JSON.stringify(store, null, 2), 'utf8') - 读取本地文件packages/dsh-plugin/src/panel-api.ts:1029
if (!p || !existsSync(p)) return send(res, 404, { error: '产物不存在(任务可能未完成)' }) - 读取本地文件packages/dsh-plugin/src/panel-api.ts:1359
if (!existsSync(file)) return [] - 读取本地文件packages/dsh-plugin/src/panel-api.ts:1360
const doc = JSON.parse(readFileSync(file, 'utf8')) as { - 写入本地文件packages/dsh-plugin/src/panel-api.ts:1485
try { mkdirSync(path, { recursive: true }) } catch (err: unknown) { - 写入本地文件packages/dsh-plugin/src/panel-api.ts:1509
mkdirSync(targetDir, { recursive: true }) - 读取本地文件packages/dsh-plugin/src/panel-api.ts:1516
if (!existsSync(canvasFile)) { - 写入本地文件packages/dsh-plugin/src/panel-api.ts:1517
writeFileSync(canvasFile, JSON.stringify({ - 删除本地文件packages/dsh-plugin/src/panel-api.ts:1663
try { if (existsSync(file)) rmSync(file, { force: true }) } catch { } - 写入本地文件packages/dsh-plugin/src/panel-api.ts:1666
mkdirSync(workspacePath, { recursive: true }) - 写入本地文件packages/dsh-plugin/src/pdf2zh.ts:38
try { mkdirSync(dir, { recursive: true }) } catch { } - 读取本地文件packages/dsh-plugin/src/pdf2zh.ts:45
const files = readdirSync(dir) - 读取本地文件packages/dsh-plugin/src/pdf2zh.ts:64
try { if (existsSync(c)) return c } catch { } - 执行外部命令来自配置/变量packages/dsh-plugin/src/pdf2zh.ts:155
const child = spawn(bin, args, { env, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'] }) - 读取本地文件packages/dsh-plugin/src/project-map.ts:240
if (!existsSync(p)) return { ...EMPTY } - 读取本地文件packages/dsh-plugin/src/project-map.ts:241
const raw = JSON.parse(readFileSync(p, 'utf8')) as Record<string, unknown> - 写入本地文件packages/dsh-plugin/src/project-map.ts:277
mkdirSync(dirname(p), { recursive: true }) - 写入本地文件packages/dsh-plugin/src/project-map.ts:278
writeFileSync(p, JSON.stringify(m, null, 2), 'utf8') - 读取本地文件packages/dsh-plugin/src/runtime.ts:88
if (!existsSync(p)) return {} - 读取本地文件packages/dsh-plugin/src/runtime.ts:89
return sanitizeOverlay(JSON.parse(readFileSync(p, 'utf8'))) - 写入本地文件packages/dsh-plugin/src/runtime.ts:98
mkdirSync(join(settingsPath(), '..'), { recursive: true }) - 写入本地文件packages/dsh-plugin/src/runtime.ts:99
writeFileSync(settingsPath(), JSON.stringify(merged, null, 2), 'utf8') - 写入本地文件packages/dsh-plugin/src/tools-m2.ts:207
writeFileSync(pdfPath, bytes) - 执行外部命令固定命令packages/dsh-plugin/src/tools-m2.ts:208
const child = spawn('pdftotext', ['-layout', pdfPath, '-'], { - 删除本地文件递归packages/dsh-plugin/src/tools-m2.ts:217
rmSync(tmp, { recursive: true, force: true }) - 删除本地文件递归packages/dsh-plugin/src/tools-m2.ts:221
rmSync(tmp, { recursive: true, force: true }) - 读取本地文件packages/dsh-plugin/src/tools-m2.ts:1064
if (!existsSync(imgDir)) return 0 - 读取本地文件packages/dsh-plugin/src/tools-m2.ts:1065
return readdirSync(imgDir).length - 读取本地文件packages/dsh-plugin/src/tools.ts:180
if (!existsSync(wsPath)) { - 写入本地文件packages/dsh-plugin/src/translate.ts:54
try { mkdirSync(dir, { recursive: true }) } catch { } - 读取本地文件packages/dsh-plugin/src/translate.ts:63
if (existsSync(f)) return JSON.parse(readFileSync(f, 'utf8')) as TranslationCache - 读取本地文件packages/dsh-plugin/src/translate.ts:63
if (existsSync(f)) return JSON.parse(readFileSync(f, 'utf8')) as TranslationCache - 写入本地文件packages/dsh-plugin/src/translate.ts:79
writeFileSync(tmp, JSON.stringify(cache), 'utf8') - 写入本地文件packages/dsh-plugin/src/translate.ts:80
renameSync(tmp, f)
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
功能亮点
- 内嵌DSH对话面板
- 检索与精读论文
- 翻译标注整理
展开完整介绍(131 字)↓
Zotero 与 DeepSeek Harness 的双向桥:Zotero 右侧栏内嵌真正的 DSH 对话面板,DSH 里检索、精读、翻译、标注、整理论文。A two-way bridge between Zotero and DeepSeek Harness.
README
由 DeepSeek 最新模型翻译生成展开完整文档(README 剩余 1 节)
zotero-dsh
把 Zotero 和 DeepSeek Harness 接起来,双向。
dsh-zotero 是「DSH 里的 Zotero」,zotero-dsh 是「Zotero 里的 DSH」。
这个仓库装的是后者,并且把两个方向放在一起。
姊妹项目:Fisfzy/dsh-zotero(DSH 侧,独立仓库)。
目录
| 包 | 方向 | 做什么 |
| packages/zotero-addon | Zotero → DSH | 在 Zotero 右侧栏里嵌入真正的 DSH 界面 |
| packages/dsh-plugin | DSH → Zotero | 在 DSH 里检索、精读、标注、整理论文 |
不想编译? 两步装完:[下载 xpi 装进 Zotero] + [跑一次安装脚本],都从 最新 Release 取预构建产物。
详见 install/README.md。
Zotero 里的 DSH — packages/zotero-addon
Zotero 7 插件。在条目详情面板的右侧栏注册一个 section,里面用 XUL 内嵌 DSH 的 Web 界面 —— 不是截图,不是消息转发,是在 Zotero 里跑一个真的 DSH 会话。
几个必须踩对的点(源码里都有注释):
- 必须 createXULElement("browser"),配 remote + maychangeremoteness + disableglobalhistory,否则静默失败或者直接把 Zotero 搞崩
- 导航只能走 browsingContext 的 PageData actor 的 sendQuery("loadURI"),直接调 browser.loadURI() 会 NS_ERROR_FAILURE
- DSH 每次启动都换 token,且不持久化。所以 base 来自偏好项,token 一律现取 ——从实例日志里读最后一条 dsh web: 行的 token。旧实现信任偏好项里的 token,于是 DSH 一重启,面板就停在 401 页
在 PDF 里选中文字送进 DSH
在 Zotero 自己的阅读器里选中一段话,气泡里会多出两个按钮:
- 送入 DSH —— 只把选段推进该论文的会话上下文,你接着自己写问题(对应 VSCode 聊天里 "Add to Chat" 的语义)
- 问 DSH —— 顺带追一轮,让它直接解读这段
会话按 itemKey 懒开:没打开过面板也能送,第一段送进来时会自动带上论文元数据。
实现走 renderTextSelectionPopup 事件 + POST /quote,不劫持 Zotero 的任何原生行为。
选段会出现在 DSH 输入框正上方。 DSH 侧注册在 conversation.input.dock 槽的引用卡显示刚送进来的那段话,点「附到问题」经 inputActions.setDraft 追加进草稿 —— DSH 唯一一个"把外部文本写进输入框"的公开接口。只加引用,不替你发送,和 VSCode 聊天里的 "Add to Chat" 是同一件事。送入的同一段话同时也注入了该论文会话的上下文,所以模型两处都看得到。
安装
从 最新 Release 下载 zotero-dsh.xpi, Zotero 里走 工具 → 插件 → 齿轮 → Install Plugin From File…,重启即可 —— 不需要先跑 "Load Plugin From Manifest" 那套开发流程。
自己打包
pwsh -File packages/zotero-addon/build.ps1
产出 packages/zotero-addon/zotero-dsh.xpi。
DSH 里的 Zotero — packages/dsh-plugin
DSH 插件(@dsh-external/dsh-zotero)。注册 12 个 agent 工具和一个右侧栏面板:
- 检索与阅读:Local API 搜索、条目详情、集合树、PDF/全文读取
- 全文解析:MinerU 通道(公式、表格保留),带缓存
- 精读与翻译:整篇总结、定向提问、逐段翻译、批量处理
- 标注与笔记:读 PDF 高亮与批注,回写 Markdown 笔记
- 论文 ↔ 工作区映射:区/组两级;组可以映射到真实目录,于是同一篇论文从不同 idea 打开会落到不同的工作目录
- 证据检索:索引 + 查询规划 + 重排,回答带出处
安装
从 最新 Release 下载 dsh-zotero-0.1.1.tgz 加上对应平台的脚本,放同一个目录:
pwsh -File install-dsh.ps1 # Windows
bash install-dsh.sh # macOS / Linux
脚本只做四件事:解包到 ~/.dsh/plugins/dsh-zotero、装唯一的运行依赖 pdfjs-dist、在 profile 的 node_modules 下建链接、往 cordis.patch.yml 补一次加载锚点。
不编译。 细节见 install/README.md。
宿主代码跨平台:唯一碰系统的位置是"打开文件或链接",已经按 process.platform 分了 open / xdg-open / cmd start 三条路。
自己构建
cd packages/dsh-plugin
pnpm install # 需要 DSH 环境提供的 @deepseek-ai/* peer 依赖
node scripts/build.mjs
关系图
Zotero ──[zotero-addon: 右侧栏嵌入]──► DSH ▲ │
└──────[dsh-plugin: 工具 + 面板]───────┘
两侧各走各的路,互不依赖:Zotero 侧只认一个 URL,DSH 侧只认 Zotero 的 Local API。
开发
两个包互相独立,没有共享构建。
Zotero 侧:改 addon/ 后重新打包,Zotero 里热重载或重启 pwsh -File packages/zotero-addon/build.ps1
DSH 侧:改 src/ 后重建,重启 DSH 实例
cd packages/dsh-plugin && node scripts/build.mjs
已知边界
- Zotero 侧的 token 解析按优先级扫候选日志,换实例只需改端口,不用改 token
- DSH 侧的面板依赖 DSH 的 sidebar.workspaces 相关槽位,DSH 大版本升级可能需要跟进
- 论文 ↔ 工作区映射存在本地 JSON,不进 Zotero 数据库
许可
AGPL-3.0-only。见 LICENSE。
DSH 侧源自 llm-for-zotero 的重构,后者以 AGPL-3.0 发布,因此本仓库整体采用同一许可。预览分享卡片长什么样 ↓
把 Zotero 和 DeepSeek Harness 接起来,双向。