pi-quiver 5.2.4 → 5.4.0

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/CHANGELOG.md CHANGED
@@ -8,6 +8,45 @@ Published to npm as `pi-quiver` (`pi install npm:pi-quiver`). Pushing a
8
8
  via OIDC trusted publishing. The release helper at
9
9
  `.agents/skills/release/scripts/release.sh` cuts the tag; CI publishes.
10
10
 
11
+ ## v5.4.0 - 2026-09-10
12
+
13
+ - Settings: one condensed `pi-quiver settings` warning (each distinct message
14
+ once per process) lists flat legacy keys in use, unknown `quiver.<block>`
15
+ names, and unknown fields inside
16
+ known blocks, with accepted names inline; unknown keys fall back to defaults.
17
+ Replaces the separate non-object-`quiver` and flat+nested-duplicate sentences;
18
+ a flat legacy key alone now warns (still honoured). Accepted names are the
19
+ exported `QUIVER_CONFIG_KEYS` registry in `lib/extension-config.ts` (#14).
20
+ - Settings warnings from `fast-mode`, `session-name`, and `slack` reach stderr
21
+ when no UI is bound (print/json modes) instead of being dropped by the no-op
22
+ headless `notify`.
23
+ - doc_to_md: the coercer no longer warns about unknown `quiver.docToMd` keys (the
24
+ settings lint does); the `pi-quiver` CLI reader warns about them itself.
25
+
26
+ ## v5.3.0 - 2026-09-09
27
+
28
+ - doc_to_md: results are now a bundle on disk (`<stem>.md` + `images/`, temp dir
29
+ or `outputDir`) plus a bounded handle (paths, page count, outline,
30
+ diagnostics) - Markdown is never returned inline; `read` the `Saved-To` file.
31
+ The former 32KB inline/spill size gate is gone (#13).
32
+ - doc_to_md: `info` mode (page count, metadata, TOC or sheet inventory),
33
+ optional 1-based `pages` selection, canonical `--- end of page.page_number=N ---`
34
+ separators on every tier, images always extracted with relative links.
35
+ - doc_to_md: three terminable child tiers - pymupdf4llm -> PyMuPDF per-page text
36
+ (degraded, marked) -> unpdf worker only when no Python backend; each spawn is
37
+ tree-killed on its own timeout; backend discovery bounded by one absolute
38
+ `warmTimeoutMs`; managed venv moves to `doc-to-md-venv-v2` with openpyxl,
39
+ xlrd and pillow pinned alongside pymupdf4llm.
40
+ - doc_to_md: direct `.xlsx`/`.xls` conversion to per-worksheet matrices
41
+ (formulas + cached values, merged/hidden/truncation disclosed, sheet images).
42
+ - doc_to_md: one option descriptor table drives the tool schema, the new
43
+ `quiver.docToMd` settings block (per-call > settings > deprecated
44
+ `PI_DOC_TO_MD_*` env > default) and the CLI (`pi-quiver doc-to-md [flags]`,
45
+ `--help`, exit codes 0/1/2). `scripts/pdf_to_md.py` replaced by
46
+ `scripts/doc_to_md.py`; `unpdf` bumped to ^1.8.1.
47
+ - Tests: synthetic PDF/DOCX/PPTX/XLSX/XLS fixtures, uv/soffice-gated integration
48
+ suite; CI installs uv and LibreOffice.
49
+
11
50
  ## v5.2.4 - 2026-09-08
12
51
 
13
52
  - doc_to_md: build the LibreOffice `-env:UserInstallation` value with
package/README.md CHANGED
@@ -16,7 +16,7 @@ But the moment an agent does that, one `fetch` or PDF read can dump hundreds of
16
16
 
17
17
  ## Why pi-quiver exists
18
18
 
19
- `fetch` and `doc_to_md` bring real web pages, GitHub issues/PRs, and local PDF/DOCX/PPTX files into context - and every result is size-gated by construction: over 32 KB or 1000 lines spills to a temp file with a preview and a grep/read hint, so a single call can never flood the window. Ingestion is what makes data-driven work possible; the gate is what keeps it safe.
19
+ `fetch` brings real web pages and GitHub issues/PRs into context and is size-gated by construction: over 32 KB or 1000 lines spills to a temp file with a preview and a grep/read hint. `doc_to_md` converts local PDF/DOCX/PPTX/XLSX/XLS files into a Markdown bundle on disk and returns only a concise handle. Ingestion is what makes data-driven work possible; bounded tool results keep it safe.
20
20
 
21
21
  `session-name`, `sword-header`, `fast-mode`, `provider-stall-watchdog`, and `slack` are opt-in ergonomics, recovery, and integration controls: session labeling, a themed startup header, Anthropic fast mode, semantic-stall recovery, and context-safe Slack search/threads/posting with repo-policy injection, `@name` mention resolution, cached emails, and per-call unfurl control.
22
22
 
@@ -29,19 +29,21 @@ Four independent extensions for the [pi coding agent](https://github.com/earendi
29
29
  - [pi-condense](https://github.com/jjuraszek/pi-condense) - context economy (prune context, keep it recoverable)
30
30
  - [pi-gauntlet](https://github.com/jjuraszek/pi-gauntlet) - process (the gated brainstorm->ship workflow)
31
31
 
32
- No code dependency between them. pi-quiver is call-level: it gates the size of what comes *in*. [pi-condense](https://github.com/jjuraszek/pi-condense) is loop-level: it prunes what's already *in context* once a tool call is done. Different problem, same discipline.
32
+ No code dependency between them. pi-quiver is call-level: `fetch` gates the size of what comes *in*, while `doc_to_md` writes a bundle and returns its handle. [pi-condense](https://github.com/jjuraszek/pi-condense) is loop-level: it prunes what's already *in context* once a tool call is done. Different problem, same discipline.
33
33
 
34
34
  ## Mental model
35
35
 
36
- Every ingestion extension here is context-safe by construction, not by convention: the size check runs on every call, there's no flag to forget. `fetch` and `doc_to_md` bring real sources in; `session-name`, `sword-header`, `fast-mode`, `provider-stall-watchdog`, and `slack` are opt-in.
36
+ Every ingestion extension here is context-safe by construction, not by convention: `fetch` size-gates every call, while `doc_to_md` always writes a bundle and returns a handle instead of inline Markdown. `fetch` and `doc_to_md` bring real sources in; `session-name`, `sword-header`, `fast-mode`, `provider-stall-watchdog`, and `slack` are opt-in.
37
37
 
38
38
  ```mermaid
39
39
  flowchart LR
40
- S[web page / PDF / doc] --> T["fetch / doc_to_md"]
40
+ S[web page] --> T[fetch]
41
41
  T --> E[extract main content]
42
42
  E --> G{"over 32KB or 1000 lines?"}
43
43
  G -->|no| I[return inline to context]
44
44
  G -->|yes| F[spill to temp file<br/>return preview + grep/read hint]
45
+ D[local PDF / Office / Excel] --> B[doc_to_md]
46
+ B --> H[Markdown bundle on disk<br/>return handle]
45
47
  ```
46
48
 
47
49
  ## Quick example
@@ -62,8 +64,8 @@ A 300 KB changelog page never touches your context window - you get a preview an
62
64
 
63
65
  | Extension | Tool | What it does |
64
66
  | --- | --- | --- |
65
- | `extensions/fetch.ts` | `fetch` | Retrieve URLs over HTTP(S). HTML -> Markdown (Readability extraction, Turndown conversion). Binary saved untouched to a temp file. GitHub issue/PR/repo/actions-run/actions-job URLs auto-route through `gh` (falls back to HTTP); failed runs/jobs include failed-step logs (best-effort, summary-only otherwise). Same size gate as `doc_to_md`. Behavior lives in `lib/fetch-core.ts`; also exposed as the `pi-quiver fetch` CLI (see [Claude Code support](#claude-code-support)). |
66
- | `extensions/doc_to_md.ts` | `doc_to_md` | Convert a local PDF/DOCX/PPTX to Markdown. High-fidelity via `pymupdf4llm`, resolved per process (`uv` -> system Python >= 3.12 with the package -> one-time managed venv in the user cache dir); degraded pure-JS fallback (`unpdf`) otherwise. DOCX/PPTX convert via LibreOffice first. Behavior lives in `lib/doc-to-md-core.ts`; also exposed as the `pi-quiver doc-to-md` CLI (see [Claude Code support](#claude-code-support)). |
67
+ | `extensions/fetch.ts` | `fetch` | Retrieve URLs over HTTP(S). HTML -> Markdown (Readability extraction, Turndown conversion). Binary saved untouched to a temp file. GitHub issue/PR/repo/actions-run/actions-job URLs auto-route through `gh` (falls back to HTTP); failed runs/jobs include failed-step logs (best-effort, summary-only otherwise). Same size gate as `fetch`. Behavior lives in `lib/fetch-core.ts`; also exposed as the `pi-quiver fetch` CLI (see [Claude Code support](#claude-code-support)). |
68
+ | `extensions/doc_to_md.ts` | `doc_to_md` | Convert a local PDF/DOCX/PPTX/XLSX/XLS to a Markdown bundle on disk (`<stem>.md` + `images/`) and return a handle (paths, page count, outline, diagnostics) - never inline Markdown. `info` mode inspects first; `pages` selects 1-based pages; every page ends with `--- end of page.page_number=N ---`. Tiers: pymupdf4llm -> PyMuPDF text (degraded) -> unpdf worker (no Python only). Excel -> per-worksheet matrices with formulas/cached values/merged/hidden. Settings under `quiver.docToMd`. |
67
69
  | `extensions/session-name.ts` | `/session-name` | Manual + opt-in automatic session naming, naming rules and deny list, long-session revisits, and Ghostty/Herdr tab rename. OFF by default. |
68
70
  | `extensions/sword-header.ts` | `/builtin-header` | Themed ASCII startup header replacing pi's default logo. OFF by default. |
69
71
  | `extensions/fast-mode.ts` | `/fast` | Inject Anthropic fast-mode (`speed: "fast"` + `anthropic-beta: fast-mode-2026-02-01`) into every Claude Opus 4.8 / Opus 5 request, any thinking level. `--fast` flag + `/fast [on\|off\|status]`. OFF by default. |
@@ -91,7 +93,7 @@ Full routing rules, size-gate mechanics, and config: [doc/fetch.md](doc/fetch.md
91
93
  ## When NOT to use
92
94
 
93
95
  - You need a general-purpose web scraper (JS-rendered pages, pagination, auth flows) - `fetch` does plain HTTP + Readability extraction, nothing more.
94
- - You need spreadsheet conversion - `doc_to_md` explicitly excludes spreadsheets (they paginate badly via PDF).
96
+ - You need `.xlsm`, sheet/range selection or chart rendering - out of scope.
95
97
  - You want automatic session naming, a custom header, fast mode, or stall recovery without opting in - all stay off until you flip the config.
96
98
  - You need *mid-stream* stall recovery in JSON, RPC, or print runs - only the pre-first-event tier arms there; mid-stream silence falls through to pi's transport timeout.
97
99
 
@@ -131,7 +133,7 @@ The npm package's bundled JS deps install automatically on `pi install`. A few *
131
133
  | Prerequisite | Needed by | If absent |
132
134
  | --- | --- | --- |
133
135
  | `gh` (GitHub CLI, installed + `gh auth login`) | `fetch` GitHub issue/PR/repo/actions-run/actions-job routing | Falls back to an HTTP fetch of the rendered page (private repos hit a login wall). |
134
- | `uv` (+ managed Python 3.14, fetched on first use) | `doc_to_md` high-fidelity PDF conversion (preferred route) | Falls back to a system Python >= 3.12 with `pymupdf4llm` (or a one-time managed venv it bootstraps); only when no capable Python exists does it degrade to `unpdf`. |
136
+ | `uv` (+ managed Python 3.14, fetched on first use) | `doc_to_md` high-fidelity PDF and Excel conversion (preferred route), with `pymupdf4llm`, `openpyxl`, `xlrd`, and `pillow` | Falls back to a system Python >= 3.12 or one-time managed venv; PDF degrades to `unpdf` only when no capable Python exists. Excel requires the Python backend (no JS fallback). |
135
137
  | LibreOffice (`soffice` on `PATH`) | `doc_to_md` DOCX/PPTX conversion | Office inputs error (no JS fallback for office->PDF); PDFs unaffected. |
136
138
 
137
139
  None is a hard install-time dependency of the package; they are tools you provide in the environment where pi runs.
@@ -171,6 +173,22 @@ legacy keys, frozen at `fastMode`, `sessionAutoName`, `swordHeader`, and
171
173
  `providerStallWatchdog` - never extended to new settings (see [Migrating from
172
174
  flat keys](#migrating-from-flat-keys)).
173
175
 
176
+ pi-quiver lints the whole `quiver` object in both files each time a setting is
177
+ resolved and emits **one** condensed warning (TUI: a `Warning:` block in the
178
+ chat; headless: stderr) listing every flat legacy key in use, every unknown
179
+ `quiver.<block>`, and every unknown field inside a known block, with the
180
+ accepted names inline. Unknown keys fall back to their defaults. Each distinct
181
+ message fires once per pi process; a clean file emits nothing. A wrong value
182
+ type (`"fastMode": "yes"`) is a separate per-key `unrecognized value` warning.
183
+ The accepted names live in `QUIVER_CONFIG_KEYS` in `lib/extension-config.ts` -
184
+ a new setting is registered there or it warns as unknown.
185
+
186
+ ```text
187
+ Warning: pi-quiver settings (/Users/x/.pi/agent/settings.json): unknown or misplaced keys - unknown ones fall back to defaults
188
+ "providerStallWatchdog" at top level - move under "quiver"
189
+ "quiver.providerStallWatchdog.timeoutMs" - unknown; accepted: enabled, firstEventMs, warningMs, recoveryMs, maxStallRetries
190
+ ```
191
+
174
192
  Worked mixed-shape example: global `settings.json` has flat
175
193
  `"fastMode": false`, project `.pi/settings.json` has
176
194
  `"quiver": { "fastMode": { "enabled": true } }`. The project layer's
@@ -262,6 +280,30 @@ Operational notes:
262
280
 
263
281
  Each setting can also be overridden per-process via `PI_QUIVER_SLACK_ENABLED`, `PI_QUIVER_SLACK_CACHE_PATH`, `PI_QUIVER_SLACK_USER_TOKEN_ENV`, `PI_QUIVER_SLACK_BOT_TOKEN_ENV`, and `PI_QUIVER_SLACK_UPLOAD_THRESHOLD_CHARS` - applied on top of the resolved `settings.json` layers, same override rung the extension's config resolver defines. Tokens themselves are resolved per call: process env first, then the repo's `.env` file (or the primary checkout's, for a worktree with none) - never a fallback across identities. Full reference incl. cache layering, the announce protocol, and the `search.messages`/`conversations.replies` throttle caveats: [doc/slack.md](doc/slack.md).
264
282
 
283
+ ### doc_to_md settings
284
+
285
+ `doc_to_md` is always registered. Configure tunables in `quiver.docToMd`; per-call > `quiver.docToMd` > `PI_DOC_TO_MD_*` env (deprecated) > default. The result is a handle; `read` the `Saved-To` file (offset/limit) for the Markdown, images live under `Images-Dir`.
286
+
287
+ | Key | Default | Meaning |
288
+ |---|---|---|
289
+ | `primaryTimeoutMs` | `60000` | pymupdf4llm tier and unpdf tier deadline. |
290
+ | `fallbackTimeoutMs` | `30000` | PyMuPDF text tier and PDF info deadline. |
291
+ | `sofficeTimeoutMs` | `120000` | DOCX/PPTX -> PDF deadline. |
292
+ | `excelTimeoutMs` | `60000` | Excel child and Excel info deadline. |
293
+ | `warmTimeoutMs` | `120000` | Absolute first-call backend discovery/bootstrap deadline. |
294
+ | `pymupdfVersion` | `1.27.2.3` | pymupdf4llm pin, minimum `1.27.0`. |
295
+ | `imageDpi` | `150` | Render DPI for page images. |
296
+ | `imageFormat` | `png` | Rendered image format: `png` or `jpg`; embedded images retain their extension. |
297
+ | `maxCellsPerSheet` | `50000` | Rows x columns budget per worksheet. |
298
+ | `maxOutputBytes` | `20000000` | Child stdout cap in bytes. |
299
+ | `outlineMaxEntries` | `40` | Heading outline, TOC, or sheet inventory entries in the handle. |
300
+
301
+ A bundle is `<outputDir>/<stem>.md` plus `<outputDir>/images/`; without `outputDir`, the tool creates a per-call temp root. A conversion owns `<stem>.md.lock` until it atomically publishes the Markdown. An existing `<stem>.md` fails the call unless `overwrite` is set; `overwrite` replaces that Markdown and the images it owns (`<stem>-p<N>-<n>.*` / `<stem>-s<idx>-<n>.*`), nothing else. Temp bundles are caller-owned - the tool never deletes a bundle it produced.
302
+
303
+ Excel needs a Python backend with openpyxl, xlrd and pillow - otherwise the call fails with `Remedy: install uv, or pip install openpyxl xlrd pillow`.
304
+
305
+ Worst-case wall time is `warmTimeoutMs (first call) + sofficeTimeoutMs (Office only) + primaryTimeoutMs + fallbackTimeoutMs + KILL_GRACE_MS x kills` (Excel: `warmTimeoutMs + excelTimeoutMs + KILL_GRACE_MS`). There is no cap on image count, image bytes or workbook memory - deliberately; the per-tier timeouts and `maxOutputBytes` are the bounds.
306
+
265
307
  ### Migrating from flat keys
266
308
 
267
309
  The flat top-level form (`"fastMode": ...` etc. directly under `settings.json`)
@@ -278,19 +320,18 @@ existing keys under `"quiver": { ... }` and delete the flat copies:
278
320
  { "quiver": { "fastMode": true } }
279
321
  ```
280
322
 
281
- Until you delete the flat copy, having both set is not an error - the
282
- duplicate resolves per the precedence above (nested wins within a layer) -
283
- but it emits a warning notification, deduped per process (each unique
284
- message fires at most once per pi process - in practice once per interactive
285
- session) until the flat entry is removed. Every new pi-quiver setting introduced after this change
286
- (for example a future `slack` key) is nested-only from day one: it has no
287
- flat form to fall back to.
323
+ A flat copy keeps resolving (nested wins within a layer, project layer wins
324
+ across layers) but every flat legacy key in use is listed in the condensed
325
+ settings warning above (`"fastMode" at top level - move under "quiver"`) until
326
+ it is moved. Every new pi-quiver setting introduced after this change (for
327
+ example `slack`) is nested-only from day one: it has no flat form to fall back
328
+ to, and a flat `slack` block is ignored without a warning.
288
329
 
289
330
  ## Claude Code support
290
331
 
291
- `fetch`'s core (`lib/fetch-core.ts`) is also published as a CLI, so Claude Code can use the same routing, size gate, and spill behavior as pi's native tool - without pi ever seeing Claude-only files.
332
+ `fetch` and `doc_to_md` cores are also published through the CLI, so Claude Code can use the same routing or bundle-and-handle behavior as pi's native tools - without pi ever seeing Claude-only files.
292
333
 
293
- **Exposed:** the `quiver` plugin, served from this repo's `.claude-plugin/marketplace.json`, with two skills: `fetch` (invoked as `quiver:fetch` / `/quiver:fetch`) and `doc-to-md` (invoked as `quiver:doc-to-md` / `/quiver:doc-to-md`). The `fetch` skill runs `npx -y pi-quiver@latest fetch <url> [flags]` via Bash - full parameter parity with the pi tool (`--method`, `--header`, `--body`, `--raw`, `--timeout-ms`), same GitHub `gh` routing (including failed-step logs on failed runs/jobs), same size gate, same binary-to-temp-file handling. See [doc/fetch.md](doc/fetch.md#claude-code-cli-pi-quiver-fetch) for exit codes and flags. The `doc-to-md` skill runs `npx -y pi-quiver@latest doc-to-md <path>` via Bash - same backend ladder, size gate, and degraded-fallback marking as the pi tool. See [doc/doc-to-md.md](doc/doc-to-md.md#cli-pi-quiver-doc-to-md) for exit codes.
334
+ **Exposed:** the `quiver` plugin, served from this repo's `.claude-plugin/marketplace.json`, with two skills: `fetch` (invoked as `quiver:fetch` / `/quiver:fetch`) and `doc-to-md` (invoked as `quiver:doc-to-md` / `/quiver:doc-to-md`). The `fetch` skill runs `npx -y pi-quiver@latest fetch <url> [flags]` via Bash - full parameter parity with the pi tool (`--method`, `--header`, `--body`, `--raw`, `--timeout-ms`), same GitHub `gh` routing (including failed-step logs on failed runs/jobs), same size gate, same binary-to-temp-file handling. See [doc/fetch.md](doc/fetch.md#claude-code-cli-pi-quiver-fetch) for exit codes and flags. The `doc-to-md` skill runs `npx -y pi-quiver@latest doc-to-md [flags] <path>` with full flag parity and the same handle output. See [doc/doc-to-md.md](doc/doc-to-md.md#cli-pi-quiver-doc-to-md) for exit codes and flags.
294
335
 
295
336
  **Not exposed:** the other pi extensions in this package (`session-name`, `sword-header`, `fast-mode`, `provider-stall-watchdog`, `slack`) - the marketplace allowlists only `./skills/fetch` and `./skills/doc-to-md`, and the npm tarball never ships `skills/` or `.claude-plugin/` (pi's own `files` allowlist excludes them, and pi's explicit `pi.extensions` manifest makes them invisible to pi's convention-directory auto-discovery either way).
296
337