@luffysolution/pi-scholar 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md ADDED
@@ -0,0 +1,145 @@
1
+ # 📚 pi-scholar
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@luffysolution/pi-scholar.svg)](https://www.npmjs.com/package/@luffysolution/pi-scholar)
4
+ [![GitHub release](https://img.shields.io/github/v/release/luffysolution-svg/pi-scholar)](https://github.com/luffysolution-svg/pi-scholar/releases)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
+ [![Node.js](https://img.shields.io/node/v/@luffysolution/pi-scholar.svg)](https://nodejs.org)
7
+
8
+ 🌐 [简体中文](./README.md) | **English**
9
+
10
+ Pi-native TypeScript research workflow combining the tested `pi-ai4scholar` extension, read-only Zotero Desktop access, and MinerU's official Precision API. It writes ordinary UTF-8 Markdown and a sibling per-document `.assets` directory — **no Obsidian, Vault, plugin, or database required**.
11
+
12
+ > 📦 Published to npm as **`@luffysolution/pi-scholar`** (scoped) — the unscoped name `pi-scholar` is already taken by an unrelated package. All in-product names — the `/pi-scholar` command, the `pi-scholar` skill, `pi_scholar_parse`, and `pi-scholar.config.json` — are unaffected by the npm scope.
13
+
14
+ ## ✨ Features
15
+
16
+ - 🔎 **Online search + local matching**: reuses `pi-ai4scholar` for online literature search/citation evidence, matched against your local Zotero library by DOI/title.
17
+ - 🗂️ **Full metadata aggregation**: `zotero_item` returns structured creators, dates, DOI/ISBN/ISSN, tags, notes, annotations, attachments, and the selected PDF in one call.
18
+ - 🧬 **Deep PDF parsing**: `pi_scholar_parse` calls the official MinerU API to recognize formulas/tables/layout and extract figures, publishing results safely.
19
+ - 📝 **Clean Markdown output**: compact YAML frontmatter plus a full-provenance `metadata.json` sidecar, with image paths that work out of the box (Obsidian-friendly, never Obsidian-dependent).
20
+ - 🔒 **Secure by default**: Zotero requests are hard-locked to loopback addresses, MinerU secrets are redacted everywhere, downloaded ZIPs are validated against path traversal/symlinks before extraction, and publishing is transactional.
21
+
22
+ ## 📦 Install
23
+
24
+ **Requirements**: Node.js `>=22.19`, Pi `>=0.84.4`, Zotero `7+` with the Local API enabled, Windows / macOS / Linux.
25
+
26
+ ```sh
27
+ pi install npm:@luffysolution/pi-scholar
28
+ pi install npm:@luffysolution/pi-scholar@0.1.2 # pinned version
29
+ pi install git:https://github.com/luffysolution-svg/pi-scholar.git#main
30
+ pi install ./path/to/pi-scholar # local persistent install
31
+ pi -e ./path/to/pi-scholar # temporary dev load
32
+ ```
33
+
34
+ Restart Pi or run `/reload`, then verify these tools/commands are available: `zotero_collections`, `zotero_search`, `zotero_item`, `pi_scholar_parse`, the imported `ai4scholar_search` / `ai4scholar_paper` / `ai4scholar_cite`, and `/skill:pi-scholar`.
35
+
36
+ Update / uninstall:
37
+
38
+ ```sh
39
+ pi update npm:@luffysolution/pi-scholar
40
+ pi remove npm:@luffysolution/pi-scholar
41
+ ```
42
+
43
+ Installing the package already supplies its pinned/tested `pi-ai4scholar` dependency — imported exactly once, with no Ai4Scholar client source copied or reimplemented.
44
+
45
+ <details>
46
+ <summary>⚠️ Already have <code>pi-ai4scholar</code> installed standalone? Read this first</summary>
47
+
48
+ Pi loads every installed extension independently. If `pi-ai4scholar` is already installed as its own top-level extension, it and the copy bundled inside `pi-scholar` will both try to register the same `ai4scholar_*` tool names, and **Pi will refuse to load `pi-scholar` at all** ("Tool conflicts" errors).
49
+
50
+ Remove the standalone install first, then install/update `pi-scholar`:
51
+
52
+ ```sh
53
+ pi remove npm:pi-ai4scholar
54
+ pi update npm:@luffysolution/pi-scholar
55
+ ```
56
+
57
+ </details>
58
+
59
+ ### 💬 `/pi-scholar` chat command
60
+
61
+ Once installed, type `/pi-scholar` inside Pi's chat.
62
+
63
+ - **With no arguments**: opens a one-line input dialog ("Pi Scholar: what do you want to research?").
64
+ - **With arguments** (e.g. `/pi-scholar find photothermal catalysis papers and match my local Zotero`): skips the dialog and runs immediately.
65
+
66
+ Either way, the command hands your request to the `pi-scholar` skill (equivalent to `/skill:pi-scholar ...`) through Pi's normal prompt-template expansion, so the model plans and calls `zotero_*` / `ai4scholar_*` / `pi_scholar_parse` under Pi's usual tool-permission flow — **the command itself never calls Zotero or MinerU directly**.
67
+
68
+ > In print/JSON/non-interactive Pi modes (`ctx.hasUI === false`) there is no dialog to fall back on; you must pass the request inline as an argument.
69
+
70
+ ### 🩺 Setup diagnostics (`npx` / `npm`, no Pi required)
71
+
72
+ A small zero-dependency CLI ships alongside the extension for verifying your machine *before* or *after* installing into Pi. It only performs a bounded, read-only reachability probe and never touches Zotero data, and it cannot install or configure the Pi extension itself — only `pi install ...` does that.
73
+
74
+ ```sh
75
+ npx @luffysolution/pi-scholar doctor # Node version, config discovery, Zotero reachability, MinerU token presence
76
+ npx @luffysolution/pi-scholar --version
77
+ npx @luffysolution/pi-scholar --help
78
+ ```
79
+
80
+ > 💡 Running `npx` from inside the `pi-scholar` project's own directory can trip up Node's local-resolution precedence; running it from any other directory works normally.
81
+
82
+ ## ⚙️ Configuration
83
+
84
+ All configuration lives in **one JSON file** covering the Zotero connection, MinerU parsing behavior, output location, asset naming, and tag formatting. Copy [`pi-scholar.config.example.json`](./pi-scholar.config.example.json) to `pi-scholar.config.json` and edit as needed.
85
+
86
+ **Discovery order**: `PI_SCHOLAR_CONFIG` env var → nearest `pi-scholar.config.json` (trusted projects only) → `~/.config/pi-scholar/config.json` → `~/.pi-scholar.json` → built-in defaults. Environment variables always override the corresponding JSON fields.
87
+
88
+ > 📖 **For the full field reference — every option's default, valid range, and env var name — see [docs/CONFIGURATION.en.md](./docs/CONFIGURATION.en.md)([中文](./docs/CONFIGURATION.md)).**
89
+
90
+ Ai4Scholar configuration remains owned by that extension, independent of this file: `/ai4scholar setup`, `AI4SCHOLAR_API_KEY`, `AI4SCHOLAR_BASE_URL`, etc.
91
+
92
+ Don't forget to enable **Allow other applications on this computer to communicate with Zotero** in Zotero's settings. Zotero requests are unauthenticated loopback GETs only, with redirects disabled. Never expose port 23119 externally.
93
+
94
+ ## 🔍 Behavior
95
+
96
+ <details>
97
+ <summary>Expand for detailed behavior notes</summary>
98
+
99
+ **`zotero_collections`** can list collections, read collection metadata, or list a collection's top-level items.
100
+
101
+ **`zotero_item`** maps raw records into one typed `Paper`: complete forward-compatible parent metadata, structured creators, original date/year, DOI/ISBN/ISSN, publication fields, tags/collections, child notes/attachments, PDF-child annotations, indexed-text availability, and the selected PDF. When a parent is clearly sparse, missing bibliographic fields are filled only if Zotero contains **exactly one** non-deleted item with the same normalized title and first author; the donor key, fields, and raw metadata are recorded for provenance. When no attachment key is supplied, PDFs are sorted by key and the first is selected and reported. Identifier matching uses normalized DOI first, then normalized title/year; **nothing is written back to Zotero**.
102
+
103
+ **`pi_scholar_parse`** validates `%PDF-`, hashes SHA-256, requests an official MinerU signed upload, uploads raw bytes with PUT, polls with bounded backoff, downloads and safely inspects the ZIP, repairs local image links, and transactionally publishes. Result names are `FirstAuthor+Year+Title.md`; missing components use `UnknownAuthor`, `UnknownYear`, or `Untitled`. Invalid cross-platform characters and the component separator `+` become spaces, reserved device names are prefixed, and UTF-8 length is bounded. Collisions use ` (2)`, ` (3)`, etc. — **never** a Zotero key. Reprocessing is recognized by the YAML `zotero://select/...` deep link (legacy `zotero_key` remains readable).
104
+
105
+ The YAML frontmatter is intentionally compact for note-property UIs: it contains only non-empty, commonly queried bibliographic fields plus Zotero/attachment identity and parse time. Null fields, raw objects, notes, annotations, attachment arrays, and verbose parser details are omitted. Complete path-safe provenance — including selected raw Zotero metadata, any exact-match enrichment donor, notes, annotations, attachments, and MinerU options — is written to the sibling `.assets/metadata.json`; indexed full text and local filesystem paths are not duplicated. Zotero and the selected PDF are exposed as clickable `zotero://select/...` and `zotero://open-pdf/...` frontmatter links. Images use explicit relative paths such as `![](<./<document>.assets/figure-01.jpg>)`, so spaces and Unicode render correctly in Obsidian when the Markdown and sibling asset directory are copied together. The directory suffix, image prefix, metadata filename, and document separator are all configurable. Frontmatter tags preserve their original form in the metadata sidecar while the Obsidian-facing values replace whitespace (default `-`) and unsupported punctuation; for example, `frustrated Lewis pairs` becomes `frustrated-Lewis-pairs` and `Ni/NiOx@C` becomes `Ni/NiOx-C`.
106
+
107
+ MinerU receives the PDF over the network; consult its privacy policy. MinerU and Ai4Scholar have quotas and may charge credits. Tokens, Authorization headers, and signed URLs are never put in output/YAML. Tool output is bounded to 50KB/2000 lines.
108
+
109
+ </details>
110
+
111
+ ## 🔒 Security notes
112
+
113
+ - Zotero access is hard-restricted in code to `http://localhost:23119/api` or `http://127.0.0.1:23119/api`, GET-only, with redirects disabled — an extension-level compromise or misconfiguration cannot be used to reach an arbitrary host or mutate your library.
114
+ - MinerU requests must be HTTPS with no embedded credentials; signed upload/download URLs, bearer tokens, and raw error bodies are redacted from every thrown error and from tool output.
115
+ - The downloaded MinerU result archive is validated before extraction (central-directory inspection, entry-count/size caps, symlink/encrypted-entry rejection, path-traversal and absolute-path rejection, duplicate-entry rejection) before any file is written to disk.
116
+ - Publishing a parsed paper is transactional: work happens in a temporary staging directory, existing files are only replaced after the new content is fully written, and a crash or cancellation restores the prior state instead of leaving a partial `.md`/`.assets` pair.
117
+ - No dependency on this project's own registry account is required at runtime: `pi-ai4scholar` is pinned to an exact version and bundled, so a compromised or yanked upstream release cannot silently change behavior after install.
118
+
119
+ ## 🛠️ Development
120
+
121
+ ```sh
122
+ npm install
123
+ npm test
124
+ npm run check
125
+ npm run pack:check
126
+ npm pack --dry-run
127
+ ```
128
+
129
+ Tests use mocks and temporary files; they do not contact Zotero or MinerU.
130
+
131
+ <details>
132
+ <summary>🚀 Release process (maintainers)</summary>
133
+
134
+ 1. Bump `version` in `package.json`, update this README's install snippets if the version is pinned anywhere, and run `npm run prepublishOnly` locally (also runs automatically before `npm publish`).
135
+ 2. Tag and push: `git tag vX.Y.Z && git push origin main --tags`, then create a GitHub Release from the tag (`gh release create vX.Y.Z --generate-notes`).
136
+ 3. Publish to npm: `npm publish` (the package is scoped and marked `"publishConfig": {"access": "public"}`, so no extra `--access` flag is required).
137
+ 4. Verify with `pi install npm:@luffysolution/pi-scholar@X.Y.Z` in a scratch Pi session before announcing.
138
+
139
+ </details>
140
+
141
+ ## 📄 License
142
+
143
+ [MIT](./LICENSE) © pi-scholar contributors
144
+
145
+ Third-party dependency notices: [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md).
package/README.md CHANGED
@@ -1,132 +1,122 @@
1
- # pi-scholar
1
+ # 📚 pi-scholar
2
2
 
3
- Pi-native TypeScript research workflow combining the tested `pi-ai4scholar` extension, read-only Zotero Desktop access, and MinerU's official Precision API. It writes ordinary UTF-8 Markdown and a sibling per-document `.assets` directory—no Obsidian, Vault, plugin, or database required.
3
+ [![npm version](https://img.shields.io/npm/v/@luffysolution/pi-scholar.svg)](https://www.npmjs.com/package/@luffysolution/pi-scholar)
4
+ [![GitHub release](https://img.shields.io/github/v/release/luffysolution-svg/pi-scholar)](https://github.com/luffysolution-svg/pi-scholar/releases)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
6
+ [![Node.js](https://img.shields.io/node/v/@luffysolution/pi-scholar.svg)](https://nodejs.org)
4
7
 
5
- Published to npm as **`@luffysolution/pi-scholar`** (the unscoped name `pi-scholar` is already taken by an unrelated package). All in-product names — the `/pi-scholar` command, the `pi-scholar` skill, `pi_scholar_parse`, and `pi-scholar.config.json` — are unaffected by the npm scope.
8
+ 🌐 **简体中文** [English](./README.en.md)
6
9
 
7
- ## Install
10
+ Pi 原生的 TypeScript 科研工作流:整合已验证的 `pi-ai4scholar` 扩展、只读的本地 Zotero 访问,以及 MinerU 官方 Precision API。产出是普通的 UTF-8 Markdown 文件 + 同级 `.assets` 资源目录——**不依赖 Obsidian、Vault、插件或任何数据库**。
8
11
 
9
- Requires Node.js **>=22.19**, Pi **>=0.84.4**, Zotero **7+** with the Local API enabled, and Windows/macOS/Linux.
12
+ > 📦 npm 包名为 **`@luffysolution/pi-scholar`**(带作用域)——不带作用域的 `pi-scholar` 已被一个无关的第三方包占用。命令 `/pi-scholar`、技能 `pi-scholar`、工具 `pi_scholar_parse`、配置文件名 `pi-scholar.config.json` 均不受此影响,只有 npm 包标识变了。
13
+
14
+ ## ✨ 功能一览
15
+
16
+ - 🔎 **在线检索 + 本地匹配**:复用 `pi-ai4scholar` 做在线文献检索/引用取证,并与本地 Zotero 条目做 DOI/标题精确匹配。
17
+ - 🗂️ **完整元数据聚合**:`zotero_item` 一次性给出结构化作者、日期、DOI/ISBN/ISSN、标签、笔记、批注、附件与已选 PDF。
18
+ - 🧬 **PDF 深度解析**:`pi_scholar_parse` 调用 MinerU 官方 API,识别公式/表格/版面,提取插图,安全落盘。
19
+ - 📝 **干净的 Markdown 输出**:精简 YAML frontmatter + 完整溯源信息旁车文件 `metadata.json`,图片路径开箱即用(对 Obsidian 友好但不依赖它)。
20
+ - 🔒 **安全默认值**:Zotero 请求锁死本地回环地址、MinerU 密钥全程脱敏、ZIP 解压前做路径穿越/符号链接校验、发布过程事务化。
21
+
22
+ ## 📦 安装
23
+
24
+ **环境要求**:Node.js `>=22.19`、Pi `>=0.84.4`、Zotero `7+`(已开启 Local API)、Windows / macOS / Linux 均可。
10
25
 
11
26
  ```sh
12
27
  pi install npm:@luffysolution/pi-scholar
13
- pi install npm:@luffysolution/pi-scholar@0.1.0 # pinned
28
+ pi install npm:@luffysolution/pi-scholar@0.1.2 # 指定版本
14
29
  pi install git:https://github.com/luffysolution-svg/pi-scholar.git#main
15
- pi install ./path/to/pi-scholar # local persistent install
16
- pi -e ./path/to/pi-scholar # temporary development load
30
+ pi install ./path/to/pi-scholar # 本地持久安装
31
+ pi -e ./path/to/pi-scholar # 临时开发加载
32
+ ```
33
+
34
+ 安装后重启 Pi 或执行 `/reload`,然后确认以下工具/命令均可用:`zotero_collections`、`zotero_search`、`zotero_item`、`pi_scholar_parse`、随包引入的 `ai4scholar_search` / `ai4scholar_paper` / `ai4scholar_cite`,以及 `/skill:pi-scholar`。
35
+
36
+ 更新 / 卸载:
37
+
38
+ ```sh
39
+ pi update npm:@luffysolution/pi-scholar
40
+ pi remove npm:@luffysolution/pi-scholar
41
+ ```
42
+
43
+ 安装本包会自动带上其锁定测试过的 `pi-ai4scholar` 依赖(仅引入一次,未复制或重写任何 Ai4Scholar 客户端源码)。
44
+
45
+ <details>
46
+ <summary>⚠️ 如果你已经单独安装过 <code>pi-ai4scholar</code>,点此展开必读</summary>
47
+
48
+ Pi 会独立加载每一个已安装的扩展。如果 `pi-ai4scholar` 已作为独立扩展安装,它和 `pi-scholar` 内置的那份会同时尝试注册相同的 `ai4scholar_*` 工具名,导致 **Pi 完全拒绝加载 `pi-scholar`**(报 "Tool conflicts" 错误)。
49
+
50
+ 请先移除独立安装的版本,再安装/更新 `pi-scholar`:
51
+
52
+ ```sh
53
+ pi remove npm:pi-ai4scholar
54
+ pi update npm:@luffysolution/pi-scholar
17
55
  ```
18
56
 
19
- Restart Pi or run `/reload`. Verify `zotero_collections`, `zotero_search`, `zotero_item`, `pi_scholar_parse`, imported `ai4scholar_search`/`ai4scholar_paper`/`ai4scholar_cite`, and `/skill:pi-scholar`. Update/uninstall with `pi update npm:@luffysolution/pi-scholar` and `pi remove npm:@luffysolution/pi-scholar`.
57
+ </details>
58
+
59
+ ### 💬 `/pi-scholar` 对话命令
60
+
61
+ 安装完成后,在 Pi 聊天框输入 `/pi-scholar` 即可调用。
20
62
 
21
- Installing the package already supplies its pinned/tested `pi-ai4scholar` dependency. The upstream extension is imported exactly once; no Ai4Scholar client source is copied or reimplemented.
63
+ - **不带参数**:弹出单行输入对话框("Pi Scholar:你想研究什么?")。
64
+ - **带参数**(如 `/pi-scholar 查找光热催化论文,并匹配本地 Zotero`):跳过对话框,直接执行。
22
65
 
23
- ### `/pi-scholar` command
66
+ 无论哪种方式,命令都会通过 Pi 的提示词模板展开机制,把请求转发给 `pi-scholar` 技能(等价于 `/skill:pi-scholar ...`),由模型在 Pi 正常的工具权限流程下规划并调用 `zotero_*` / `ai4scholar_*` / `pi_scholar_parse`——**命令本身从不直接访问 Zotero 或 MinerU**。
24
67
 
25
- Once installed, type `/pi-scholar` inside Pi's chat. With no arguments it opens a one-line input dialog ("Pi Scholar:你想研究什么?"); with arguments (e.g. `/pi-scholar 查找光热催化论文,并匹配本地 Zotero`) it skips the dialog. Either way it hands your request to the `pi-scholar` skill (`/skill:pi-scholar ...`) through Pi's normal prompt-template expansion, so the model plans and calls `zotero_*`/`ai4scholar_*`/`pi_scholar_parse` under Pi's usual tool-permission flow — the command itself never calls Zotero or MinerU directly. In print/JSON/non-interactive Pi modes (`ctx.hasUI === false`), you must pass the request inline as an argument; there is no dialog to fall back on.
68
+ > print/JSON/非交互式 Pi 模式下(`ctx.hasUI === false`)没有对话框可用,必须把请求作为参数直接传入。
26
69
 
27
- ### Setup diagnostics (`npx`/`npm`, no Pi required)
70
+ ### 🩺 环境自检(`npx` / `npm`,无需 Pi
28
71
 
29
- A small zero-dependency CLI ships alongside the extension for verifying your machine *before* or *after* installing into Pi. It never touches Zotero data beyond a bounded read-only reachability probe, and it cannot install or configure the Pi extension itself — only `pi install ...` does that.
72
+ 包内附带一个零依赖的诊断 CLI,用于在安装到 Pi **之前或之后**检查你的环境。它只做一次有边界的只读可达性探测,不会读取 Zotero 数据,也不能代替 `pi install ...` 完成扩展安装/配置。
30
73
 
31
74
  ```sh
32
- npx @luffysolution/pi-scholar doctor # Node version, config discovery, Zotero reachability, MinerU token presence
75
+ npx @luffysolution/pi-scholar doctor # 检查 Node 版本、配置发现、Zotero 可达性、MinerU 令牌是否存在
33
76
  npx @luffysolution/pi-scholar --version
34
77
  npx @luffysolution/pi-scholar --help
35
78
  ```
36
79
 
37
- ## Configuration
38
-
39
- Use one JSON file for Zotero, MinerU behavior, output location, asset naming, and tag formatting; copy `pi-scholar.config.example.json` to `pi-scholar.config.json` (or a user-level location, see below) and edit it. **Never commit `pi-scholar.config.json`** — it is already covered by `.gitignore`.
40
-
41
- ### Discovery order
42
-
43
- 1. `PI_SCHOLAR_CONFIG` environment variable — an explicit path (must exist, or Pi refuses to start the tool).
44
- 2. The nearest `pi-scholar.config.json`, searched from the current working directory upward. Only used when the project directory is trusted by Pi (`ctx.isProjectTrusted()`), so an untrusted project cannot silently redirect output or the Zotero endpoint.
45
- 3. `~/.config/pi-scholar/config.json` (all platforms, including Windows — use `%USERPROFILE%\.config\pi-scholar\config.json`).
46
- 4. `~/.pi-scholar.json` as a last resort.
47
- 5. Built-in defaults if none of the above exist.
48
-
49
- Relative `output.directory` and `zotero.dataDir` values resolve **relative to the config file's own directory**, not the current working directory. Environment variables (below) are read fresh on every tool call and always win over the JSON file, which is convenient for one-off overrides (e.g. CI, a temporary output directory) without editing the file.
50
-
51
- ### Full reference
52
-
53
- ```json
54
- {
55
- "output": {
56
- "directory": "F:/个人知识库",
57
- "filenameSeparator": "+",
58
- "assetsSuffix": ".assets",
59
- "assetFilePrefix": "figure",
60
- "metadataFileName": "metadata.json",
61
- "tagSpaceReplacement": "-"
62
- },
63
- "zotero": {
64
- "baseUrl": "http://127.0.0.1:23119/api",
65
- "timeoutMs": 15000,
66
- "maxItems": 5000
67
- },
68
- "mineru": {
69
- "tokenEnv": "MINERU_API_TOKEN",
70
- "timeoutMs": 600000,
71
- "pollInitialMs": 3000,
72
- "pollMaxMs": 15000,
73
- "maxAttempts": 120,
74
- "language": "en",
75
- "enableFormula": true,
76
- "enableTable": true,
77
- "isOcr": false,
78
- "modelVersion": "vlm"
79
- }
80
- }
81
- ```
80
+ > 💡 若在 `pi-scholar` 项目自身目录内运行 `npx`,Node 的本地解析优先级可能导致命令行为异常;在其他任意目录运行则完全正常。
81
+
82
+ ## ⚙️ 配置
83
+
84
+ 所有配置项集中在**一个 JSON 文件**里,涵盖 Zotero 连接、MinerU 解析行为、输出位置、资源命名与标签格式。复制 [`pi-scholar.config.example.json`](./pi-scholar.config.example.json) 为 `pi-scholar.config.json` 并按需修改。
82
85
 
83
- | Field | Env override | Default | Notes |
84
- |---|---|---|---|
85
- | `output.directory` | `PI_SCHOLAR_OUTPUT_DIR` | `~/pi-scholar` | Where `.md` files and `.assets` folders are published. May be an Obsidian vault folder, but Obsidian is never required. Resolved relative to the config file's directory when given as a relative path in JSON; resolved relative to `cwd` when given via the env var. |
86
- | `output.filenameSeparator` | `PI_SCHOLAR_FILENAME_SEPARATOR` | `+` | 1–3 characters from `[+._ -]`, joins `Author`, `Year`, `Title` in generated filenames. |
87
- | `output.assetsSuffix` | `PI_SCHOLAR_ASSETS_SUFFIX` | `.assets` | Sibling directory suffix, e.g. `Paper.assets/`. Must be a safe filename component (no path separators, max 64 UTF-8 bytes). |
88
- | `output.assetFilePrefix` | `PI_SCHOLAR_ASSET_FILE_PREFIX` | `figure` | Extracted image basename prefix, e.g. `figure-01.png`. |
89
- | `output.metadataFileName` | `PI_SCHOLAR_METADATA_FILE` | `metadata.json` | Sidecar filename inside the assets directory; must end in `.json`. |
90
- | `output.tagSpaceReplacement` | `PI_SCHOLAR_TAG_SPACE_REPLACEMENT` | `-` | `-` or `_`; character substituted for whitespace/unsupported punctuation in Obsidian-facing tags only (the metadata sidecar keeps the original Zotero tag text). |
91
- | `zotero.baseUrl` | `ZOTERO_BASE_URL` | `http://127.0.0.1:23119/api` | Must be exactly `http://localhost:23119/api` or `http://127.0.0.1:23119/api` — no other host, port, path, credentials, query, or fragment is accepted. This is enforced in code, not just documented. |
92
- | `zotero.dataDir` | `ZOTERO_DATA_DIR` | none | Zotero's data directory (contains `storage/`), only needed as a fallback when Zotero's own `file/view/url` endpoint cannot resolve a managed attachment's path. |
93
- | `zotero.timeoutMs` | `ZOTERO_TIMEOUT_MS` | `15000` | Per-request timeout, 1,000–120,000 ms. |
94
- | `zotero.maxItems` | `ZOTERO_MAX_ITEMS` | `5000` | Upper bound on paged results per call, 1–50,000. |
95
- | `mineru.tokenEnv` | — | `MINERU_API_TOKEN` | Names *which* environment variable holds the MinerU secret. The config file never stores the secret itself — only the variable name (must be uppercase, `[A-Z_][A-Z0-9_]*`). |
96
- | — | `MINERU_API_TOKEN` (always checked) or the variable named by `mineru.tokenEnv` | none | The actual MinerU API token. Required only for `pi_scholar_parse`; every other tool works without it. |
97
- | `mineru.timeoutMs` | `MINERU_TIMEOUT_MS` | `600000` | Overall extraction deadline, 10,000–3,600,000 ms. Individual HTTP attempts are separately capped at 60 s. |
98
- | `mineru.pollInitialMs` / `mineru.pollMaxMs` | `MINERU_POLL_INITIAL_MS` / `MINERU_POLL_MAX_MS` | `3000` / `15000` | Polling backoff bounds while MinerU extracts the PDF. |
99
- | `mineru.maxAttempts` | `MINERU_MAX_ATTEMPTS` | `120` | Max poll attempts, 1–1000. |
100
- | `mineru.language` | `MINERU_LANGUAGE` | `en` | MinerU OCR/layout language hint. |
101
- | `mineru.enableFormula` / `mineru.enableTable` | `MINERU_ENABLE_FORMULA` / `MINERU_ENABLE_TABLE` | `true` / `true` | Accepts `1/0`, `true/false`, `yes/no` (case-insensitive) via env. |
102
- | `mineru.isOcr` | `MINERU_IS_OCR` | `false` | Force OCR even for text-layer PDFs. |
103
- | `mineru.modelVersion` | `MINERU_MODEL_VERSION` | `vlm` | MinerU model version identifier. |
86
+ **查找顺序**:`PI_SCHOLAR_CONFIG` 环境变量 就近的 `pi-scholar.config.json`(仅可信项目)→ `~/.config/pi-scholar/config.json` `~/.pi-scholar.json` 内置默认值。环境变量随时可覆盖 JSON 文件中的对应字段。
104
87
 
105
- Every field is validated at load time (type, range, and — for filenames/URLs — safety checks); an invalid config file or environment variable throws a clear error instead of silently falling back. See `.env.example` for a documentation-only list of the same environment variables — **this package does not read `.env` files itself**; export the variables through your shell, OS, or Pi's own environment configuration.
88
+ > 📖 **完整字段参考、每个选项的默认值/取值范围/环境变量名,请查阅 [docs/CONFIGURATION.md](./docs/CONFIGURATION.md)([English](./docs/CONFIGURATION.en.md))。**
106
89
 
107
- Ai4Scholar configuration remains owned by the directly reused `pi-ai4scholar` extension: use `/ai4scholar setup`, `AI4SCHOLAR_API_KEY` (or legacy `AI4S_API_KEY`), `AI4SCHOLAR_BASE_URL`, `AI4SCHOLAR_TIMEOUT_MS`, and its proxy settings.
90
+ Ai4Scholar 相关配置由其自身管理,与本文件无关:`/ai4scholar setup`、`AI4SCHOLAR_API_KEY`、`AI4SCHOLAR_BASE_URL` 等。
108
91
 
109
- Enable **Allow other applications on this computer to communicate with Zotero** in Zotero settings. Zotero requests are unauthenticated loopback GETs only, with redirects disabled. Never expose port 23119 externally.
92
+ 别忘了在 Zotero 设置里开启 **允许其他应用程序与 Zotero 通信**。所有 Zotero 请求都是无认证的本地回环 GET,且禁用重定向;切勿把 23119 端口暴露到公网。
110
93
 
111
- ## Behavior
94
+ ## 🔍 工作原理
112
95
 
113
- `zotero_collections` can list collections, read collection metadata, or list a collection's top-level items. `zotero_item` maps raw records into one typed `Paper`: complete forward-compatible parent metadata, structured creators, original date/year, DOI/ISBN/ISSN, publication fields, tags/collections, child notes/attachments, PDF-child annotations, indexed-text availability, and selected PDF. When a parent is clearly sparse, missing bibliographic fields are filled only if Zotero contains exactly one non-deleted item with the same normalized title and first author; the donor key, fields, and raw metadata are recorded for provenance. When no attachment key is supplied, PDFs are sorted by key and the first is selected and reported. Identifier matching uses normalized DOI first, then normalized title/year; nothing is written back to Zotero.
96
+ <details>
97
+ <summary>点此展开详细行为说明</summary>
114
98
 
115
- `pi_scholar_parse` validates `%PDF-`, hashes SHA-256, requests an official MinerU signed upload, uploads raw bytes with PUT, polls with bounded backoff, downloads and safely inspects the ZIP, repairs local image links, and transactionally publishes. Result names are `FirstAuthor+Year+Title.md`; missing components use `UnknownAuthor`, `UnknownYear`, or `Untitled`. Invalid cross-platform characters and component `+` signs become spaces, reserved device names are prefixed, and UTF-8 length is bounded. Collisions use ` (2)`, ` (3)`, etc.—never a Zotero key. Reprocessing is recognized by the YAML `zotero://select/...` deep link (legacy `zotero_key` remains readable).
99
+ **`zotero_collections`** 可以列出收藏夹、读取收藏夹元数据,或列出收藏夹下的顶层条目。
116
100
 
117
- The YAML frontmatter is intentionally compact for note-property UIs: it contains only non-empty, commonly queried bibliographic fields plus Zotero/attachment identity and parse time. Null fields, raw objects, notes, annotations, attachment arrays, and verbose parser details are omitted. Complete path-safe provenance—including selected raw Zotero metadata, any exact-match enrichment donor, notes, annotations, attachments, and MinerU options—is written to the sibling `.assets/metadata.json`; indexed full text and local filesystem paths are not duplicated. Zotero and the selected PDF are exposed as clickable `zotero://select/...` and `zotero://open-pdf/...` frontmatter links. Images use explicit relative paths such as `![](<./<document>.assets/figure-01.jpg>)`, so spaces and Unicode render correctly in Obsidian when the Markdown and sibling asset directory are copied together. The directory suffix, image prefix, metadata filename, and document separator are configurable. Frontmatter tags preserve their original form in the metadata sidecar while the Obsidian-facing values replace whitespace (default `-`) and unsupported punctuation; for example, `frustrated Lewis pairs` becomes `frustrated-Lewis-pairs` and `Ni/NiOx@C` becomes `Ni/NiOx-C`.
101
+ **`zotero_item`** 把原始记录映射为一个类型化的 `Paper`:完整的向前兼容父级元数据、结构化作者、原始日期/年份、DOI/ISBN/ISSN、出版信息字段、标签/收藏夹、子笔记/附件、PDF 子项批注、全文索引可用性,以及已选 PDF。当父条目明显信息稀疏时,只有在 Zotero 中存在**恰好一条**标准化标题与第一作者都相同的未删除条目时,才会补全缺失的文献字段,并记录来源条目键、字段与原始元数据以供溯源。未显式指定附件键时,按键排序选取第一个 PDF 并在结果中说明。标识符匹配优先使用标准化 DOI,其次是标准化标题/年份;**不会**向 Zotero 写回任何内容。
118
102
 
119
- MinerU receives the PDF over the network; consult its privacy policy. MinerU and Ai4Scholar have quotas and may charge credits. Tokens, Authorization headers, and signed URLs are never put in output/YAML. Tool output is bounded to 50KB/2000 lines.
103
+ **`pi_scholar_parse`** 校验 `%PDF-` 文件头、计算 SHA-256、向 MinerU 请求签名上传地址、用 PUT 上传原始字节、带退避策略轮询状态、下载并安全检查返回的 ZIP、修复本地图片链接,并以事务方式发布结果。生成的文件名格式为 `FirstAuthor+Year+Title.md`;缺失的部分用 `UnknownAuthor`、`UnknownYear` `Untitled` 填充。跨平台非法字符与文件名分隔符 `+` 会被替换为空格,保留设备名会加前缀,UTF-8 长度有上限。文件名冲突用 ` (2)`、` (3)` 等后缀区分——**从不**使用 Zotero 键。是否已经解析过,通过 YAML 中的 `zotero://select/...` 深链识别(旧版 `zotero_key` 字段仍可读取)。
120
104
 
121
- ## Security notes
105
+ YAML frontmatter 刻意保持精简,只包含非空、常用于查询的文献字段,加上 Zotero/附件身份信息与解析时间;空字段、原始对象、笔记、批注、附件数组、详细解析参数均不出现在其中。完整的、路径安全的溯源信息——包括选中的原始 Zotero 元数据、任何精确匹配的补全来源、笔记、批注、附件、MinerU 参数——写入同级的 `.assets/metadata.json`;全文索引内容和本地文件系统路径不会重复存储。Zotero 条目与已选 PDF 分别以可点击的 `zotero://select/...` 和 `zotero://open-pdf/...` 链接出现在 frontmatter 中。图片使用显式相对路径,如 `![](<./<document>.assets/figure-01.jpg>)`,因此当 Markdown 文件与同级资源目录一起复制时,空格与 Unicode 文件名也能在 Obsidian 中正确渲染。资源目录后缀、图片前缀、元数据文件名与文件名分隔符均可配置。frontmatter 标签在元数据旁车文件中保留原始形式,而面向 Obsidian 的值会把空白和不受支持的标点替换掉(默认用 `-`);例如 `frustrated Lewis pairs` 会变成 `frustrated-Lewis-pairs`,`Ni/NiOx@C` 会变成 `Ni/NiOx-C`。
122
106
 
123
- - Zotero access is hard-restricted in code to `http://localhost:23119/api` or `http://127.0.0.1:23119/api`, GET-only, with redirects disabled — an extension-level compromise or misconfiguration cannot be used to reach an arbitrary host or mutate your library.
124
- - MinerU requests must be HTTPS with no embedded credentials; signed upload/download URLs, bearer tokens, and raw error bodies are redacted from every thrown error and from tool output.
125
- - The downloaded MinerU result archive is validated before extraction (central-directory inspection, entry-count/size caps, symlink/encrypted-entry rejection, path-traversal and absolute-path rejection, duplicate-entry rejection) before any file is written to disk.
126
- - Publishing a parsed paper is transactional: work happens in a temporary staging directory, existing files are only replaced after the new content is fully written, and a crash or cancellation restores the prior state instead of leaving a partial `.md`/`.assets` pair.
127
- - No dependency on this project's own registry account is required at runtime: `pi-ai4scholar` is pinned to an exact version and bundled, so a compromised or yanked upstream release cannot silently change behavior after install.
107
+ MinerU 会通过网络接收 PDF 内容,请自行了解其隐私政策;MinerU Ai4Scholar 均有用量配额,可能产生费用。令牌、Authorization 头与签名 URL 从不写入输出或 YAML。工具输出统一限制在 50KB / 2000 行以内。
128
108
 
129
- ## Development
109
+ </details>
110
+
111
+ ## 🔒 安全说明
112
+
113
+ - Zotero 访问在代码层面被硬性限制为 `http://localhost:23119/api` 或 `http://127.0.0.1:23119/api`,仅允许 GET,且禁用重定向——即便扩展本身被攻破或配置错误,也无法访问任意主机或修改你的文献库。
114
+ - MinerU 请求必须使用 HTTPS 且不含内嵌凭据;签名上传/下载 URL、Bearer 令牌与原始错误响应体,会从所有抛出的错误与工具输出中脱敏。
115
+ - 下载的 MinerU 结果压缩包在解压前会先做校验(中央目录检查、条目数/大小上限、拒绝符号链接/加密条目、拒绝路径穿越/绝对路径、拒绝重复条目),确保写盘前的安全性。
116
+ - 发布解析结果是事务化的:先在临时暂存目录完成全部工作,只有新内容完全写入后才替换已有文件;中途崩溃或取消会恢复到发布前的状态,不会留下半成品的 `.md` / `.assets`。
117
+ - 运行时不依赖本项目自身的 registry 账号:`pi-ai4scholar` 被锁定到精确版本并随包捆绑,即使上游发布被攻破或撤回,也不会静默改变已安装版本的行为。
118
+
119
+ ## 🛠️ 开发
130
120
 
131
121
  ```sh
132
122
  npm install
@@ -136,11 +126,20 @@ npm run pack:check
136
126
  npm pack --dry-run
137
127
  ```
138
128
 
139
- Tests use mocks and temporary files; they do not contact Zotero or MinerU.
129
+ 测试全部基于 mock 与临时文件,**不会**联网访问真实的 Zotero MinerU
130
+
131
+ <details>
132
+ <summary>🚀 发布流程(维护者参考)</summary>
133
+
134
+ 1. 在 `package.json` 中升级 `version`,如有需要同步更新 README 中带版本号的安装示例,本地运行 `npm run prepublishOnly`(`npm publish` 前也会自动执行)。
135
+ 2. 打标签并推送:`git tag vX.Y.Z && git push origin main --tags`,随后从该标签创建 GitHub Release:`gh release create vX.Y.Z --generate-notes`。
136
+ 3. 发布到 npm:`npm publish`(包已设置作用域并标记 `"publishConfig": {"access": "public"}`,无需额外的 `--access` 参数)。
137
+ 4. 发布公告前,先在一个临时 Pi 会话中用 `pi install npm:@luffysolution/pi-scholar@X.Y.Z` 验证安装无误。
138
+
139
+ </details>
140
+
141
+ ## 📄 许可证
140
142
 
141
- ## Release process
143
+ [MIT](./LICENSE) © pi-scholar contributors
142
144
 
143
- 1. Bump `version` in `package.json`, update this README's install snippets if the version is pinned anywhere, and run `npm run prepublishOnly` locally (also runs automatically before `npm publish`).
144
- 2. Tag and push: `git tag vX.Y.Z && git push origin main --tags`, then create a GitHub Release from the tag (`gh release create vX.Y.Z --generate-notes`).
145
- 3. Publish to npm: `npm publish` (the package is scoped and marked `"publishConfig": {"access": "public"}`, so no extra `--access` flag is required).
146
- 4. Verify with `pi install npm:@luffysolution/pi-scholar@X.Y.Z` in a scratch Pi session before announcing.
145
+ 第三方依赖与致谢见 [THIRD_PARTY_NOTICES.md](./THIRD_PARTY_NOTICES.md)
@@ -0,0 +1,150 @@
1
+ # ⚙️ Configuration Reference
2
+
3
+ [中文版](./CONFIGURATION.md)
4
+
5
+ `pi-scholar` uses **one JSON file** to configure the Zotero connection, MinerU parsing behavior, output location, asset naming, and tag formatting. Every field is validated at load time (type, range, and safety checks); an invalid config throws a clear error instead of silently falling back.
6
+
7
+ ## 📁 Where the config file lives
8
+
9
+ Copy the repo-root [`pi-scholar.config.example.json`](../pi-scholar.config.example.json) to `pi-scholar.config.json` (or one of the user-level locations below) and edit it as needed.
10
+
11
+ > ⚠️ **Never commit `pi-scholar.config.json`** — it's already covered by `.gitignore` since it typically contains your personal output path and other local details.
12
+
13
+ ### 🔍 Discovery order
14
+
15
+ Checked in order, first match wins:
16
+
17
+ 1. **`PI_SCHOLAR_CONFIG` env var** — an explicit path; it must exist, or the tool refuses to start.
18
+ 2. **The nearest `pi-scholar.config.json`**, searched from the current working directory upward. Only used when the project directory is trusted by Pi (`ctx.isProjectTrusted()`), so an untrusted project can't silently redirect output or the Zotero endpoint.
19
+ 3. **`~/.config/pi-scholar/config.json`** — all platforms, including Windows (`%USERPROFILE%\.config\pi-scholar\config.json`).
20
+ 4. **`~/.pi-scholar.json`** — last resort.
21
+ 5. Built-in defaults if none of the above exist.
22
+
23
+ ### 📌 Path resolution
24
+
25
+ - Relative `output.directory` / `zotero.dataDir` values in the **JSON file** resolve relative to the **config file's own directory**.
26
+ - The same values set via **environment variables** resolve relative to the **runtime `cwd`**.
27
+ - Environment variables are read fresh on every tool call and **always win over the JSON file** — convenient for one-off overrides (CI, a temporary output dir) without editing the file.
28
+
29
+ ## 📝 Full example
30
+
31
+ ```json
32
+ {
33
+ "output": {
34
+ "directory": "F:/my-vault",
35
+ "filenameSeparator": "+",
36
+ "assetsSuffix": ".assets",
37
+ "assetFilePrefix": "figure",
38
+ "metadataFileName": "metadata.json",
39
+ "tagSpaceReplacement": "-"
40
+ },
41
+ "zotero": {
42
+ "baseUrl": "http://127.0.0.1:23119/api",
43
+ "timeoutMs": 15000,
44
+ "maxItems": 5000
45
+ },
46
+ "mineru": {
47
+ "tokenEnv": "MINERU_API_TOKEN",
48
+ "timeoutMs": 600000,
49
+ "pollInitialMs": 3000,
50
+ "pollMaxMs": 15000,
51
+ "maxAttempts": 120,
52
+ "language": "en",
53
+ "enableFormula": true,
54
+ "enableTable": true,
55
+ "isOcr": false,
56
+ "modelVersion": "vlm"
57
+ }
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 📤 `output` — location & naming
64
+
65
+ | Field | Env override | Default |
66
+ |---|---|---|
67
+ | `directory` | `PI_SCHOLAR_OUTPUT_DIR` | `~/pi-scholar` |
68
+ | `filenameSeparator` | `PI_SCHOLAR_FILENAME_SEPARATOR` | `+` |
69
+ | `assetsSuffix` | `PI_SCHOLAR_ASSETS_SUFFIX` | `.assets` |
70
+ | `assetFilePrefix` | `PI_SCHOLAR_ASSET_FILE_PREFIX` | `figure` |
71
+ | `metadataFileName` | `PI_SCHOLAR_METADATA_FILE` | `metadata.json` |
72
+ | `tagSpaceReplacement` | `PI_SCHOLAR_TAG_SPACE_REPLACEMENT` | `-` |
73
+
74
+ <details>
75
+ <summary>Field details</summary>
76
+
77
+ - **`directory`**: Where generated `.md` files and `.assets` folders are published. May be an Obsidian vault folder, but Obsidian is never required. Relative paths in JSON resolve relative to the config file's directory; relative paths via env var resolve relative to `cwd`.
78
+ - **`filenameSeparator`**: 1–3 characters from `[+._ -]` used to join `Author`, `Year`, `Title` in generated filenames.
79
+ - **`assetsSuffix`**: Sibling directory suffix, e.g. `Paper.assets/`. Must be a safe filename component (no path separators, max 64 UTF-8 bytes).
80
+ - **`assetFilePrefix`**: Extracted image basename prefix, e.g. `figure-01.png`.
81
+ - **`metadataFileName`**: Sidecar filename inside the assets directory; must end in `.json`.
82
+ - **`tagSpaceReplacement`**: `-` or `_`; character substituted for whitespace/unsupported punctuation in Obsidian-facing tags only (the metadata sidecar keeps the original Zotero tag text).
83
+
84
+ </details>
85
+
86
+ ## 🗂️ `zotero` — local Zotero connection
87
+
88
+ | Field | Env override | Default |
89
+ |---|---|---|
90
+ | `baseUrl` | `ZOTERO_BASE_URL` | `http://127.0.0.1:23119/api` |
91
+ | `dataDir` | `ZOTERO_DATA_DIR` | none |
92
+ | `timeoutMs` | `ZOTERO_TIMEOUT_MS` | `15000` |
93
+ | `maxItems` | `ZOTERO_MAX_ITEMS` | `5000` |
94
+
95
+ <details>
96
+ <summary>Field details</summary>
97
+
98
+ - **`baseUrl`**: Must be exactly `http://localhost:23119/api` or `http://127.0.0.1:23119/api` — no other host, port, path, credentials, query, or fragment is accepted. Enforced in code, not just documented.
99
+ - **`dataDir`**: Zotero's data directory (contains `storage/`), only needed as a fallback when Zotero's own `file/view/url` endpoint cannot resolve a managed attachment's path.
100
+ - **`timeoutMs`**: Per-request timeout, 1,000–120,000 ms.
101
+ - **`maxItems`**: Upper bound on paged results per call, 1–50,000.
102
+
103
+ </details>
104
+
105
+ > Enable **Allow other applications on this computer to communicate with Zotero** in Zotero's settings. Zotero requests are unauthenticated loopback GETs only, with redirects disabled. Never expose port 23119 externally.
106
+
107
+ ## 🧬 `mineru` — PDF parsing behavior
108
+
109
+ | Field | Env override | Default |
110
+ |---|---|---|
111
+ | `tokenEnv` | — | `MINERU_API_TOKEN` |
112
+ | (actual token) | `MINERU_API_TOKEN` or the var named by `tokenEnv` | none |
113
+ | `timeoutMs` | `MINERU_TIMEOUT_MS` | `600000` |
114
+ | `pollInitialMs` / `pollMaxMs` | `MINERU_POLL_INITIAL_MS` / `MINERU_POLL_MAX_MS` | `3000` / `15000` |
115
+ | `maxAttempts` | `MINERU_MAX_ATTEMPTS` | `120` |
116
+ | `language` | `MINERU_LANGUAGE` | `en` |
117
+ | `enableFormula` / `enableTable` | `MINERU_ENABLE_FORMULA` / `MINERU_ENABLE_TABLE` | `true` / `true` |
118
+ | `isOcr` | `MINERU_IS_OCR` | `false` |
119
+ | `modelVersion` | `MINERU_MODEL_VERSION` | `vlm` |
120
+
121
+ <details>
122
+ <summary>Field details</summary>
123
+
124
+ - **`tokenEnv`**: Names *which* environment variable holds the MinerU secret. The config file never stores the secret itself — only the variable name (must be uppercase, `[A-Z_][A-Z0-9_]*`).
125
+ - **The actual MinerU API token**: Required only for `pi_scholar_parse`; every other tool works without it.
126
+ - **`timeoutMs`**: Overall extraction deadline. Individual HTTP attempts are separately capped at 60 s.
127
+ - **`pollInitialMs` / `pollMaxMs`**: Polling backoff bounds while MinerU extracts the PDF.
128
+ - **`maxAttempts`**: Max poll attempts, 1–1000.
129
+ - **`language`**: MinerU OCR/layout language hint.
130
+ - **`enableFormula` / `enableTable`**: Accepts `1/0`, `true/false`, `yes/no` (case-insensitive) via env.
131
+ - **`isOcr`**: Force OCR even for text-layer PDFs.
132
+ - **`modelVersion`**: MinerU model version identifier.
133
+
134
+ </details>
135
+
136
+ ---
137
+
138
+ ## 🌱 Environment variable quick reference
139
+
140
+ See [`.env.example`](../.env.example) at the repo root for a documentation-only list of all variables above. **This package does not read `.env` files itself** — export the variables through your shell, OS, or Pi's own environment configuration.
141
+
142
+ ## 🔗 Ai4Scholar configuration
143
+
144
+ Ai4Scholar configuration remains owned by the directly reused `pi-ai4scholar` extension, independent of this file:
145
+
146
+ - `/ai4scholar setup`
147
+ - `AI4SCHOLAR_API_KEY` (legacy `AI4S_API_KEY`)
148
+ - `AI4SCHOLAR_BASE_URL`
149
+ - `AI4SCHOLAR_TIMEOUT_MS`
150
+ - and its proxy settings
@@ -0,0 +1,150 @@
1
+ # ⚙️ 配置说明(中文)
2
+
3
+ [English version](./CONFIGURATION.en.md)
4
+
5
+ `pi-scholar` 使用**一个 JSON 文件**统一配置 Zotero 连接、MinerU 解析行为、输出位置、资源命名与标签格式。所有字段在加载时都会做类型/范围/安全校验,配置错误会直接抛出清晰的错误,而不是静默使用默认值。
6
+
7
+ ## 📁 配置文件在哪里
8
+
9
+ 复制项目根目录下的 [`pi-scholar.config.example.json`](../pi-scholar.config.example.json) 为 `pi-scholar.config.json`(或放到下面的用户级路径),然后按需修改。
10
+
11
+ > ⚠️ **不要提交 `pi-scholar.config.json` 到仓库**——它已经被 `.gitignore` 忽略,因为其中通常包含你本地的输出路径等私有信息。
12
+
13
+ ### 🔍 查找顺序
14
+
15
+ 按以下顺序依次查找,命中即停止:
16
+
17
+ 1. **`PI_SCHOLAR_CONFIG` 环境变量** —— 显式指定一个文件路径;该路径必须存在,否则工具会拒绝启动。
18
+ 2. **就近的 `pi-scholar.config.json`** —— 从当前工作目录开始向上查找。仅当项目目录被 Pi 判定为可信(`ctx.isProjectTrusted()`)时才会生效,防止一个不可信项目悄悄劫持你的输出目录或 Zotero 地址。
19
+ 3. **`~/.config/pi-scholar/config.json`** —— 所有平台通用,包括 Windows(对应 `%USERPROFILE%\.config\pi-scholar\config.json`)。
20
+ 4. **`~/.pi-scholar.json`** —— 最后的兜底位置。
21
+ 5. 以上都不存在时,使用内置默认值。
22
+
23
+ ### 📌 路径解析规则
24
+
25
+ - JSON 文件里的 `output.directory`、`zotero.dataDir` 若写成**相对路径**,会相对于**配置文件所在目录**解析(而不是当前工作目录)。
26
+ - 通过**环境变量**覆盖的路径,则相对于**运行时的 `cwd`** 解析。
27
+ - 环境变量在每次工具调用时都会重新读取,并且**始终优先于 JSON 文件**,适合做一次性覆盖(例如 CI、临时输出目录)而不用改配置文件。
28
+
29
+ ## 📝 完整示例
30
+
31
+ ```json
32
+ {
33
+ "output": {
34
+ "directory": "F:/个人知识库",
35
+ "filenameSeparator": "+",
36
+ "assetsSuffix": ".assets",
37
+ "assetFilePrefix": "figure",
38
+ "metadataFileName": "metadata.json",
39
+ "tagSpaceReplacement": "-"
40
+ },
41
+ "zotero": {
42
+ "baseUrl": "http://127.0.0.1:23119/api",
43
+ "timeoutMs": 15000,
44
+ "maxItems": 5000
45
+ },
46
+ "mineru": {
47
+ "tokenEnv": "MINERU_API_TOKEN",
48
+ "timeoutMs": 600000,
49
+ "pollInitialMs": 3000,
50
+ "pollMaxMs": 15000,
51
+ "maxAttempts": 120,
52
+ "language": "en",
53
+ "enableFormula": true,
54
+ "enableTable": true,
55
+ "isOcr": false,
56
+ "modelVersion": "vlm"
57
+ }
58
+ }
59
+ ```
60
+
61
+ ---
62
+
63
+ ## 📤 `output` — 输出位置与命名
64
+
65
+ | 字段 | 环境变量 | 默认值 |
66
+ |---|---|---|
67
+ | `directory` | `PI_SCHOLAR_OUTPUT_DIR` | `~/pi-scholar` |
68
+ | `filenameSeparator` | `PI_SCHOLAR_FILENAME_SEPARATOR` | `+` |
69
+ | `assetsSuffix` | `PI_SCHOLAR_ASSETS_SUFFIX` | `.assets` |
70
+ | `assetFilePrefix` | `PI_SCHOLAR_ASSET_FILE_PREFIX` | `figure` |
71
+ | `metadataFileName` | `PI_SCHOLAR_METADATA_FILE` | `metadata.json` |
72
+ | `tagSpaceReplacement` | `PI_SCHOLAR_TAG_SPACE_REPLACEMENT` | `-` |
73
+
74
+ <details>
75
+ <summary>字段详细说明</summary>
76
+
77
+ - **`directory`**:生成的 `.md` 文件与 `.assets` 目录的发布位置。可以指向一个 Obsidian Vault 文件夹,但从不依赖 Obsidian 本身。JSON 中的相对路径相对配置文件目录解析;环境变量中的相对路径相对 `cwd` 解析。
78
+ - **`filenameSeparator`**:拼接 `Author`、`Year`、`Title` 生成文件名时使用的分隔符,仅允许 1–3 个字符,取自 `[+._ -]`。
79
+ - **`assetsSuffix`**:与 Markdown 同级的资源目录后缀,例如 `Paper.assets/`。必须是安全的文件名片段(不含路径分隔符,UTF-8 字节数不超过 64)。
80
+ - **`assetFilePrefix`**:提取出的图片文件名前缀,例如 `figure-01.png`。
81
+ - **`metadataFileName`**:资源目录内的元数据旁车文件名,必须以 `.json` 结尾。
82
+ - **`tagSpaceReplacement`**:`-` 或 `_`,仅用于替换 Obsidian 侧标签中的空白/不受支持标点;元数据旁车文件始终保留 Zotero 原始标签文本。
83
+
84
+ </details>
85
+
86
+ ## 🗂️ `zotero` — 本地 Zotero 连接
87
+
88
+ | 字段 | 环境变量 | 默认值 |
89
+ |---|---|---|
90
+ | `baseUrl` | `ZOTERO_BASE_URL` | `http://127.0.0.1:23119/api` |
91
+ | `dataDir` | `ZOTERO_DATA_DIR` | 无 |
92
+ | `timeoutMs` | `ZOTERO_TIMEOUT_MS` | `15000` |
93
+ | `maxItems` | `ZOTERO_MAX_ITEMS` | `5000` |
94
+
95
+ <details>
96
+ <summary>字段详细说明</summary>
97
+
98
+ - **`baseUrl`**:只允许精确等于 `http://localhost:23119/api` 或 `http://127.0.0.1:23119/api`——不接受其他主机、端口、路径、凭据、查询串或片段。这是代码层面强制校验的,而不仅仅是文档约定。
99
+ - **`dataDir`**:Zotero 数据目录(内含 `storage/`),仅在 Zotero 自身的 `file/view/url` 接口无法解析某个受管附件路径时作为兜底使用。
100
+ - **`timeoutMs`**:单次请求超时时间,允许范围 1,000–120,000 毫秒。
101
+ - **`maxItems`**:单次调用分页结果数量上限,允许范围 1–50,000。
102
+
103
+ </details>
104
+
105
+ > 需要在 Zotero 设置中开启 **允许其他应用程序与 Zotero 通信**。所有 Zotero 请求都是无认证的本地回环 GET 请求,且禁用重定向;切勿将 23119 端口暴露到外网。
106
+
107
+ ## 🧬 `mineru` — PDF 解析行为
108
+
109
+ | 字段 | 环境变量 | 默认值 |
110
+ |---|---|---|
111
+ | `tokenEnv` | — | `MINERU_API_TOKEN` |
112
+ | (实际密钥) | `MINERU_API_TOKEN` 或 `tokenEnv` 指定的变量名 | 无 |
113
+ | `timeoutMs` | `MINERU_TIMEOUT_MS` | `600000` |
114
+ | `pollInitialMs` / `pollMaxMs` | `MINERU_POLL_INITIAL_MS` / `MINERU_POLL_MAX_MS` | `3000` / `15000` |
115
+ | `maxAttempts` | `MINERU_MAX_ATTEMPTS` | `120` |
116
+ | `language` | `MINERU_LANGUAGE` | `en` |
117
+ | `enableFormula` / `enableTable` | `MINERU_ENABLE_FORMULA` / `MINERU_ENABLE_TABLE` | `true` / `true` |
118
+ | `isOcr` | `MINERU_IS_OCR` | `false` |
119
+ | `modelVersion` | `MINERU_MODEL_VERSION` | `vlm` |
120
+
121
+ <details>
122
+ <summary>字段详细说明</summary>
123
+
124
+ - **`tokenEnv`**:指定*哪个*环境变量存放 MinerU 密钥。配置文件本身从不存储密钥内容,只存变量名(必须是大写、匹配 `[A-Z_][A-Z0-9_]*`)。
125
+ - **实际的 MinerU API 令牌**:只有调用 `pi_scholar_parse` 时才需要,其他工具均无需此令牌。
126
+ - **`timeoutMs`**:整体解析截止时间;单次 HTTP 请求另有独立的 60 秒上限。
127
+ - **`pollInitialMs` / `pollMaxMs`**:等待 MinerU 完成解析时的退避轮询区间。
128
+ - **`maxAttempts`**:最大轮询次数,允许范围 1–1000。
129
+ - **`language`**:MinerU OCR / 版面识别的语言提示。
130
+ - **`enableFormula` / `enableTable`**:环境变量支持 `1/0`、`true/false`、`yes/no`(大小写不敏感)。
131
+ - **`isOcr`**:即使 PDF 已有文本层,也强制走 OCR。
132
+ - **`modelVersion`**:MinerU 模型版本标识。
133
+
134
+ </details>
135
+
136
+ ---
137
+
138
+ ## 🌱 环境变量速查
139
+
140
+ 完整的环境变量清单见仓库根目录的 [`.env.example`](../.env.example)(仅作文档用途)。**本项目不会自行读取 `.env` 文件**,请通过 Shell、操作系统或 Pi 自身的环境变量机制导出这些变量。
141
+
142
+ ## 🔗 Ai4Scholar 配置
143
+
144
+ Ai4Scholar 部分的配置由直接复用的 `pi-ai4scholar` 扩展自行管理,与本文件无关:
145
+
146
+ - `/ai4scholar setup`
147
+ - `AI4SCHOLAR_API_KEY`(旧版兼容 `AI4S_API_KEY`)
148
+ - `AI4SCHOLAR_BASE_URL`
149
+ - `AI4SCHOLAR_TIMEOUT_MS`
150
+ - 及其代理相关设置
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@luffysolution/pi-scholar",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Pi-native scholarly workflow for read-only Zotero and MinerU, with Ai4Scholar reuse",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "exports": "./src/index.ts",
27
27
  "bin": {
28
- "pi-scholar": "./bin/pi-scholar.mjs"
28
+ "pi-scholar": "bin/pi-scholar.mjs"
29
29
  },
30
30
  "publishConfig": {
31
31
  "access": "public"
@@ -34,7 +34,9 @@
34
34
  "src",
35
35
  "skills",
36
36
  "bin",
37
+ "docs",
37
38
  "README.md",
39
+ "README.en.md",
38
40
  "LICENSE",
39
41
  "THIRD_PARTY_NOTICES.md",
40
42
  ".env.example",