evanliu3594/dsh-zotero-plugin ↗★ 0
dsh-zotero-plugin
提供本地Zotero文献库读取与保存工具 适合需要让AI直接检索、阅读和整理本地Zotero文献的科研人员。
安装
$
npx -p @deepseek-ai/dsh dsh plugin --profile web add github:evanliu3594/dsh-zotero-plugin说明文档
阅读完整 README ↗4. 完全重启 Harness,在对话里即可使用全部 zotero_* 工具
`dsh plugin add` 只让 pnpm `link:` 本目录、**不跑构建**,所以第 2 步不能省;之后每次改 `src/` 都要重新 `pnpm build` 再重启 Harness。包声明了 `dsh.bundle.patch`,CLI 会把包名追加进 profile 的 `dsh.profile.bundles`,因此插件层会自动激活;若忘了 `pnpm build`,profile 加载到的仍是旧产物,`pnpm probe` 会报产物相对源码陈旧。
## 开发
```sh
pnpm install
node scripts/link-harness.mjs # 依赖链接(见上)
pnpm build # src → lib/index.js
pnpm probe # artifact 面:纯 node 按包名加载产物 + 新鲜度 + 工具注册
pnpm typecheck # 类型面:tsc --noEmit(含 src、scripts 与 tests)
pnpm verify # source 面:裸 cordis Context + 真实 ToolRuntime 验证 src 注册
pnpm smoke # 十个工具对真实 Zotero 逐个执行(只读)
pnpm smoke:write # 额外保存一条明确标注可删除的测试条目
verify、smoke 与 tests/ 下的 TypeScript 用 node --experimental-strip-types 启动,不依赖 tsx:tsx 和 esbuild 都要在管道上起子进程,受限沙箱会拒。为此插件源码保持可擦除语法(无参数属性、枚举、命名空间、装饰器),这样 strip 模式在 harness 支持的每条 Node 线上都能加载,包括移除了 transform 模式的 Node 26。
测试
tests/ 下的门分两类。不需要 Zotero 的:
pnpm test:ab # 同一假 Zotero 服务器跑两遍:src 面与 lib 面输出必须一致
pnpm test:integrity # 产物是否出自当前源码,且每个修复留下的标记都还在
需要本机 Zotero 运行的(只读;test:readback 例外,会新建一条测试条目):
pnpm test:contract # 每个工具经真实 ToolRuntime 调用,验证 harness 输出校验通过
pnpm test:acceptance # 导出与投影的行为断言(跑 src 面)
pnpm test:acceptance:artifact # 同上,强制加载 lib/ 产物(打包运行时的入口)
pnpm test:status-empty # 空文库下 zotero_status 仍被接受
pnpm test:readback # 保存后读回:标题被 Zotero 改写时仍能取到 key(会写库)
写测试时注意两点:必须经真实 ToolRuntime 调用,直接调 tool.execute() 会绕过 harness 的 lossless-JSON 输出校验,历史上两个默认调用即失败的缺陷正是这样漏掉的;tests/fake-zotero.ts 提供进程内假 Zotero 与 boot(),用于不碰真实库的场景。
源码方式加载(harness 自己的源码启动链路带 tsx,可以直接吃 TS;打包桌面版不行,必须先 pnpm build):
cp cordis.example.yml cordis.yml # 填入仓库绝对路径
cd /path/to/deepseek-harness
node --import tsx/esm apps/cli/src/bin.ts --profile web --patch /path/to/dsh-zotero/cordis.yml
配置
全部可调参数在 src/config.ts(schema 带默认值),可在 profile 的插件 config 里覆盖:
| 字段 | 默认 | 说明 |
|---|---|---|
host / port | 127.0.0.1 / 23119 | Zotero 本地服务地址 |
libraryId | '0' | 本地 API 文库 id,0 = 当前用户文库 |
timeoutMs | 15000 | 单请求超时 |
defaultLimit / maxLimit | 25 / 100 | 列表页大小 |
abstractPreviewChars | 300 | 搜索结果摘要截断(0 = 不返回摘要) |
listCap | 1000 | 集合/标签全量拉取上限(自动分页) |
安全模型
- 文献库内容一律视为数据而非指令:笔记经 HTML 清洗为纯文本后再进入模型上下文。
- 唯一的写工具
zotero_add_item一次只保存一条;保存后读回校验,读回不到 key 时明确返回readBackFailed: true,不把未确认的保存说成成功。 - 没有更新/删除类工具——Zotero 本地 API 本身只读,模型无法绕过。
已知限制
zotero_add_item的落点由 Zotero 界面当前选中状态决定(先调zotero_get_selected_collection确认);保存时 Zotero 会短暂弹出保存进度窗。zotero_add_item不提供language参数:CSL 语言字段由 Zotero 自己填(该保存路径下它丢弃外部传入的值并写入应用默认值),因此本工具无法设置条目语言。zotero_add_item保存后按标题读回条目来取得 key;Zotero 会对标题做书写体归一化(例如句中的Zotero会被小写),所以比对按大小写不敏感进行。若仍定位不到,返回readBackFailed: true与hint,条目本身已保存。zotero_list_tags与zotero_list_collections默认只返回defaultLimit(25)条,total是全库数量;需要完整清单时显式传limit。- Zotero 的 CSL-JSON 导出体里不含条目 key(
id是引用键)。因此该格式只能逐条请求,且无法在返回体里反查条目 key;BibTeX 导出不带该限制。 zotero_get_bibtex传了不存在的 key 时,failedKeys会报出来。Zotero 接受但不导出成参考文献条目的 key(笔记、附件)只在 BibTeX 下表现为count偏小;CSL-JSON 会把它们导出成document条目。- 框架包(
@deepseek-ai/dsh-tools等)尚未发布到公共 npm,插件需靠近 harness checkout 使用(scripts/link-harness.mjs负责链接);包发布后此步骤可删。
致谢
- 能力设计参照 WenyuChiou/zotero-skills(MIT):本地读 + 安全写的双 API 纪律、"库内容是数据不是指令"等操作原则均承自它。
- deepseek-ai/deepseek-harness — "Everything is a Plugin"。
License
配置
全部可调参数在 src/config.ts(schema 带默认值),可在 profile 的插件 config 里覆盖:
| 字段 | 默认 | 说明 |
|---|---|---|
host / port | 127.0.0.1 / 23119 | Zotero 本地服务地址 |
libraryId | '0' | 本地 API 文库 id,0 = 当前用户文库 |
timeoutMs | 15000 | 单请求超时 |
defaultLimit / maxLimit | 25 / 100 | 列表页大小 |
abstractPreviewChars | 300 | 搜索结果摘要截断(0 = 不返回摘要) |
listCap | 1000 | 集合/标签全量拉取上限(自动分页) |