pi-quiver 5.2.4 → 5.3.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.
@@ -0,0 +1,108 @@
1
+ import type { InputType } from "./doc-to-md-options.ts";
2
+
3
+ export type Tier = "primary" | "fallback" | "unpdf" | "excel";
4
+ export type Engine = "pymupdf4llm" | "pymupdf-text" | "unpdf" | "openpyxl" | "xlrd";
5
+ export type BackendKind = "uv" | "python" | "venv" | "none";
6
+
7
+ export interface OutlineEntry { line: number; level: number; title: string; }
8
+ export interface TocEntry { level: number; title: string; page: number; }
9
+ export interface SheetInfo { index: number; name: string; hidden: boolean; rows: number | null; cols: number | null; hiddenRows: number; hiddenCols: number; }
10
+
11
+ export interface HandleData {
12
+ savedTo: string; imagesDir: string | null; type: InputType; engine: Engine; tier: Tier;
13
+ pageCount: number | null; pages: number[] | null; imageCount: number; bytes: number; lines: number;
14
+ degraded: string | null; fallbackReason: string | null; failedPages: number[]; emptyPages: number[];
15
+ notes: string[]; outline: OutlineEntry[]; outlineTotal: number;
16
+ }
17
+
18
+ export interface InfoData {
19
+ type: InputType; backend: BackendKind; pageCount: number | null; metadata: Record<string, string>;
20
+ toc: TocEntry[]; tocTotal: number; sheets: SheetInfo[] | null; sheetsTotal: number;
21
+ }
22
+
23
+ const TITLE_MAX = 80;
24
+ const NOTE_MAX_LINES = 5;
25
+ const NOTE_MAX_CHARS = 200;
26
+ const META_MAX_CHARS = 120;
27
+
28
+ // Deliberate local copy of the host package's formatSize - this module must stay pi-free.
29
+ export function formatSize(bytes: number): string {
30
+ if (bytes < 1024) return `${bytes}B`;
31
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
32
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
33
+ }
34
+
35
+ const trunc = (s: string, max: number) => (s.length > max ? `${s.slice(0, max - 3)}...` : s);
36
+
37
+ export function compactRanges(nums: number[], maxEntries = 20): string {
38
+ const sorted = [...new Set(nums)].sort((a, b) => a - b);
39
+ const parts: string[] = [];
40
+ for (let i = 0; i < sorted.length;) {
41
+ let j = i;
42
+ while (j + 1 < sorted.length && sorted[j + 1] === sorted[j] + 1) j++;
43
+ parts.push(j > i ? `${sorted[i]}-${sorted[j]}` : `${sorted[i]}`);
44
+ i = j + 1;
45
+ }
46
+ if (parts.length <= maxEntries) return parts.join(", ");
47
+ return `${parts.slice(0, maxEntries).join(", ")} (+${parts.length - maxEntries} more)`;
48
+ }
49
+
50
+ export function scanOutline(md: string, max: number): { entries: OutlineEntry[]; total: number } {
51
+ const entries: OutlineEntry[] = [];
52
+ let total = 0, inFence = false;
53
+ md.split("\n").forEach((raw, i) => {
54
+ const line = raw.replace(/\r$/, "");
55
+ if (/^\s*(```|~~~)/.test(line)) { inFence = !inFence; return; }
56
+ if (inFence) return;
57
+ const m = line.match(/^(#{1,6}) (.*)$/);
58
+ if (!m) return;
59
+ total++;
60
+ if (entries.length < max) entries.push({ line: i + 1, level: m[1].length, title: trunc(m[2].trim(), TITLE_MAX) });
61
+ });
62
+ return { entries, total };
63
+ }
64
+
65
+ function outlineLines(entries: OutlineEntry[], total: number): string[] {
66
+ if (entries.length === 0) return [];
67
+ const width = Math.max(5, Math.max(...entries.map((e) => `L${e.line}`.length)) + 2);
68
+ const out = ["Outline:", ...entries.map((e) => ` ${`L${e.line}`.padEnd(width)}${"#".repeat(e.level)} ${trunc(e.title, TITLE_MAX)}`)];
69
+ if (total > entries.length) out.push(` (+${total - entries.length} more)`);
70
+ return out;
71
+ }
72
+
73
+ export function formatHandle(h: HandleData): string {
74
+ const lines = [`Saved-To: ${h.savedTo}`];
75
+ if (h.imagesDir && h.imageCount > 0) lines.push(`Images-Dir: ${h.imagesDir}`);
76
+ lines.push(`Type: ${h.type} Engine: ${h.engine} Tier: ${h.tier}`);
77
+ lines.push(`Page-Count: ${h.pageCount ?? "?"} Pages: ${h.pages ? compactRanges(h.pages) : "all"} Images: ${h.imageCount} Size: ${formatSize(h.bytes)} / ${h.lines} lines`);
78
+ if (h.degraded) lines.push(`Degraded: ${h.degraded}`);
79
+ if (h.fallbackReason) lines.push(`Fallback-Reason: ${h.fallbackReason}`);
80
+ const fe: string[] = [];
81
+ if (h.failedPages.length) fe.push(`Failed-Pages: ${compactRanges(h.failedPages)}`);
82
+ if (h.emptyPages.length) fe.push(`Empty-Pages: ${compactRanges(h.emptyPages)}`);
83
+ if (fe.length) lines.push(fe.join(" "));
84
+ h.notes.slice(0, NOTE_MAX_LINES).forEach((n, i) => lines.push(`${i === 0 ? "Notes: " : " "}${trunc(n, NOTE_MAX_CHARS)}`));
85
+ lines.push(...outlineLines(h.outline, h.outlineTotal));
86
+ return lines.join("\n");
87
+ }
88
+
89
+ export function formatInfoHandle(i: InfoData, max: number): string {
90
+ if (i.sheets) {
91
+ const lines = [`Type: ${i.type} Sheets: ${i.sheetsTotal}`];
92
+ for (const s of i.sheets.slice(0, max)) {
93
+ const dims = `rows=${s.rows ?? "?"} cols=${s.cols ?? "?"}`;
94
+ const hidden = s.hiddenRows || s.hiddenCols ? ` hiddenRows=${s.hiddenRows} hiddenCols=${s.hiddenCols}` : "";
95
+ lines.push(` ${trunc(s.name, TITLE_MAX)} ${s.hidden ? "hidden " : ""}${dims}${hidden}`);
96
+ }
97
+ if (i.sheetsTotal > max) lines.push(` (+${i.sheetsTotal - max} more)`);
98
+ return lines.join("\n");
99
+ }
100
+ const lines = [`Type: ${i.type} Page-Count: ${i.pageCount ?? "?"} Backend: ${i.backend}`];
101
+ const meta = Object.entries(i.metadata).filter(([, v]) => v).map(([k, v]) => `${k[0].toUpperCase()}${k.slice(1)}: ${trunc(v, META_MAX_CHARS)}`);
102
+ if (meta.length) lines.push(meta.join(" "));
103
+ if (i.toc.length) {
104
+ lines.push("TOC:", ...i.toc.slice(0, max).map((t) => ` L${t.level} ${trunc(t.title, TITLE_MAX)} (p${t.page})`));
105
+ if (i.tocTotal > max) lines.push(` (+${i.tocTotal - max} more)`);
106
+ }
107
+ return lines.join("\n");
108
+ }
@@ -0,0 +1,186 @@
1
+ /**
2
+ * doc_to_md option descriptors - the single source of truth for the pi tool schema,
3
+ * the CLI flags/--help, and the `quiver.docToMd` settings shape. pi-free.
4
+ */
5
+ import { extname } from "node:path";
6
+
7
+ export type InputType = "pdf" | "docx" | "pptx" | "xlsx" | "xls";
8
+ export type ImageFormat = "png" | "jpg";
9
+
10
+ export interface Tunables {
11
+ primaryTimeoutMs: number;
12
+ fallbackTimeoutMs: number;
13
+ sofficeTimeoutMs: number;
14
+ excelTimeoutMs: number;
15
+ warmTimeoutMs: number;
16
+ pymupdfVersion: string;
17
+ imageDpi: number;
18
+ imageFormat: ImageFormat;
19
+ maxCellsPerSheet: number;
20
+ maxOutputBytes: number;
21
+ outlineMaxEntries: number;
22
+ }
23
+
24
+ export interface DocToMdOptions extends Tunables {
25
+ path: string;
26
+ info: boolean;
27
+ pages: number[] | null;
28
+ outputDir: string | null;
29
+ overwrite: boolean;
30
+ }
31
+
32
+ /** What adapters pass in: intents as raw strings/booleans, tunables optional. */
33
+ export interface PerCallInput extends Partial<Tunables> {
34
+ path: string;
35
+ info?: boolean;
36
+ pages?: string | null;
37
+ outputDir?: string | null;
38
+ overwrite?: boolean;
39
+ }
40
+
41
+ export type DescriptorType = "string" | "int" | "bool" | "pages" | "enum" | "version";
42
+
43
+ export interface OptionDescriptor {
44
+ key: keyof DocToMdOptions;
45
+ flag: string | null;
46
+ type: DescriptorType;
47
+ default: string | number | boolean | null;
48
+ settable: boolean;
49
+ env?: string;
50
+ enumValues?: readonly string[];
51
+ help: string;
52
+ }
53
+
54
+ export class UsageError extends Error {}
55
+
56
+ export const MIN_PYMUPDF4LLM = "1.27.0";
57
+ const VERSION_RE = /^\d+(\.\d+)*$/;
58
+
59
+ export const DOC_TO_MD_OPTIONS: readonly OptionDescriptor[] = [
60
+ { key: "path", flag: null, type: "string", default: null, settable: false, help: "Local .pdf .docx .pptx .xlsx .xls file" },
61
+ { key: "info", flag: "--info", type: "bool", default: false, settable: false, help: "Inspect only (page count, metadata, TOC or sheet inventory); no bundle" },
62
+ { key: "pages", flag: "--pages", type: "pages", default: null, settable: false, help: "Inclusive 1-based pages, e.g. \"12-15\" or \"3,7,10-12\" (PDF/DOCX/PPTX only); default all" },
63
+ { key: "outputDir", flag: "--output-dir", type: "string", default: null, settable: false, help: "Bundle root for <stem>.md + images/; default a per-call temp dir" },
64
+ { key: "overwrite", flag: "--overwrite", type: "bool", default: false, settable: false, help: "Replace an existing completed <stem>.md bundle" },
65
+ { key: "primaryTimeoutMs", flag: "--primary-timeout", type: "int", default: 60000, settable: true, env: "PI_DOC_TO_MD_CONVERT_TIMEOUT_MS", help: "pymupdf4llm tier; also the unpdf tier" },
66
+ { key: "fallbackTimeoutMs", flag: "--fallback-timeout", type: "int", default: 30000, settable: true, help: "PyMuPDF get_text tier; also info on PDF" },
67
+ { key: "sofficeTimeoutMs", flag: "--soffice-timeout", type: "int", default: 120000, settable: true, env: "PI_DOC_TO_MD_SOFFICE_TIMEOUT_MS", help: "DOCX/PPTX -> PDF via LibreOffice" },
68
+ { key: "excelTimeoutMs", flag: "--excel-timeout", type: "int", default: 60000, settable: true, help: "Excel child (both openpyxl loads); also info on Excel" },
69
+ { key: "warmTimeoutMs", flag: "--warm-timeout", type: "int", default: 120000, settable: true, env: "PI_DOC_TO_MD_WARM_TIMEOUT_MS", help: "Absolute backend discovery/bootstrap deadline (first call per process)" },
70
+ { key: "pymupdfVersion", flag: "--pymupdf-version", type: "version", default: "1.27.2.3", settable: true, env: "PI_DOC_TO_MD_PYMUPDF_VERSION", help: "pymupdf4llm pin (>= 1.27.0)" },
71
+ { key: "imageDpi", flag: "--image-dpi", type: "int", default: 150, settable: true, help: "Render DPI for page images" },
72
+ { key: "imageFormat", flag: "--image-format", type: "enum", default: "png", settable: true, enumValues: ["png", "jpg"], help: "Rendered image format (embedded images keep their native extension)" },
73
+ { key: "maxCellsPerSheet", flag: "--max-cells-per-sheet", type: "int", default: 50000, settable: true, help: "rows x cols budget per worksheet" },
74
+ { key: "maxOutputBytes", flag: "--max-output-bytes", type: "int", default: 20000000, settable: true, help: "Child stdout cap in bytes" },
75
+ { key: "outlineMaxEntries", flag: "--outline-max-entries", type: "int", default: 40, settable: true, help: "Heading outline / TOC / sheet inventory cap in the handle" },
76
+ ];
77
+
78
+ export const TUNABLE_DEFAULTS: Tunables = Object.fromEntries(
79
+ DOC_TO_MD_OPTIONS.filter((d) => d.settable).map((d) => [d.key, d.default]),
80
+ ) as unknown as Tunables;
81
+
82
+ export function versionAtLeast(v: string, min: string): boolean {
83
+ const a = v.split(".").map(Number), b = min.split(".").map(Number);
84
+ for (let i = 0; i < Math.max(a.length, b.length); i++) {
85
+ const x = a[i] ?? 0, y = b[i] ?? 0;
86
+ if (x !== y) return x > y;
87
+ }
88
+ return true;
89
+ }
90
+
91
+ function coerceValue(d: OptionDescriptor, raw: unknown, fromString = false): { ok: true; value: unknown } | { ok: false; reason: string } {
92
+ switch (d.type) {
93
+ case "int": {
94
+ const n = fromString && typeof raw === "string" ? Number(raw) : raw;
95
+ return typeof n === "number" && Number.isInteger(n) && n > 0 ? { ok: true, value: n } : { ok: false, reason: "must be a positive integer" };
96
+ }
97
+ case "bool": return typeof raw === "boolean" ? { ok: true, value: raw } : { ok: false, reason: "must be true or false" };
98
+ case "enum": return typeof raw === "string" && d.enumValues!.includes(raw) ? { ok: true, value: raw } : { ok: false, reason: `must be one of ${d.enumValues!.join(", ")}` };
99
+ case "version":
100
+ if (typeof raw !== "string" || !VERSION_RE.test(raw)) return { ok: false, reason: "must be digits and dots" };
101
+ if (!versionAtLeast(raw, MIN_PYMUPDF4LLM)) return { ok: false, reason: `must be >= ${MIN_PYMUPDF4LLM}` };
102
+ return { ok: true, value: raw };
103
+ case "string": return typeof raw === "string" && raw.length > 0 ? { ok: true, value: raw } : { ok: false, reason: "must be a non-empty string" };
104
+ case "pages": return typeof raw === "string" ? { ok: true, value: parsePages(raw) } : { ok: false, reason: "must be a string" };
105
+ }
106
+ }
107
+
108
+ /** Single validation boundary for `quiver.docToMd`: unknown/ill-typed/intent keys are dropped with one warning each. */
109
+ export function coerceDocToMdSettings(raw: unknown, warn: (message: string) => void = console.warn): Partial<Tunables> | undefined {
110
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return undefined;
111
+ const out: Record<string, unknown> = {};
112
+ for (const [k, v] of Object.entries(raw as Record<string, unknown>)) {
113
+ const d = DOC_TO_MD_OPTIONS.find((o) => o.key === k);
114
+ if (!d || !d.settable) { warn(`pi-quiver: quiver.docToMd.${k} is not a tunable setting; ignored.`); continue; }
115
+ const c = coerceValue(d, v);
116
+ if (!c.ok) { warn(`pi-quiver: quiver.docToMd.${k} ${c.reason}; ignored.`); continue; }
117
+ out[k] = c.value;
118
+ }
119
+ return out as Partial<Tunables>;
120
+ }
121
+
122
+ export function parsePages(spec: string): number[] {
123
+ const out = new Set<number>();
124
+ const parts = spec.split(",").map((s) => s.trim());
125
+ if (parts.length === 0 || parts.some((p) => p === "")) throw new UsageError(`invalid --pages "${spec}": expected e.g. "12-15" or "3,7,10-12"`);
126
+ for (const p of parts) {
127
+ const m = p.match(/^(\d+)(?:-(\d+))?$/);
128
+ if (!m) throw new UsageError(`invalid --pages "${spec}": bad token "${p}"`);
129
+ const a = Number(m[1]), b = m[2] === undefined ? a : Number(m[2]);
130
+ if (a < 1 || b < a) throw new UsageError(`invalid --pages "${spec}": pages are 1-based and ranges ascend`);
131
+ for (let i = a; i <= b; i++) out.add(i);
132
+ }
133
+ return [...out].sort((x, y) => x - y);
134
+ }
135
+
136
+ export function sanitizeStem(base: string): string {
137
+ const s = base.replace(/[^A-Za-z0-9._-]+/g, "_");
138
+ return s.length ? s : "document";
139
+ }
140
+
141
+ const SUPPORTED: Record<string, InputType> = { ".pdf": "pdf", ".docx": "docx", ".pptx": "pptx", ".xlsx": "xlsx", ".xls": "xls" };
142
+
143
+ export function classifyInput(filePath: string): InputType {
144
+ const t = SUPPORTED[extname(filePath).toLowerCase()];
145
+ if (!t) throw new Error(`Unsupported file type "${extname(filePath) || "(none)"}"; supported: .pdf, .docx, .pptx, .xlsx, .xls`);
146
+ return t;
147
+ }
148
+
149
+ /** per-call > settings > deprecated env > default. Throws UsageError for bad per-call values or info + bundle option. */
150
+ export function resolveOptions(perCall: PerCallInput, settings: Partial<Tunables>, env: NodeJS.ProcessEnv): DocToMdOptions {
151
+ const out: Record<string, unknown> = { path: perCall.path };
152
+ for (const d of DOC_TO_MD_OPTIONS) {
153
+ if (d.key === "path") continue;
154
+ let value: unknown = d.default;
155
+ if (d.env && env[d.env] !== undefined) {
156
+ const c = coerceValue(d, env[d.env], true);
157
+ if (!c.ok) throw new UsageError(`${d.env} ${c.reason} (got "${env[d.env]}")`);
158
+ value = c.value;
159
+ }
160
+ if (d.settable && (settings as Record<string, unknown>)[d.key] !== undefined) value = (settings as Record<string, unknown>)[d.key];
161
+ const pc = (perCall as unknown as Record<string, unknown>)[d.key];
162
+ if (pc !== undefined && pc !== null) {
163
+ const c = coerceValue(d, pc);
164
+ if (!c.ok) throw new UsageError(`${d.flag} ${c.reason}`);
165
+ value = c.value;
166
+ }
167
+ out[d.key] = value;
168
+ }
169
+ const o = out as unknown as DocToMdOptions;
170
+ if (o.info && (o.pages !== null || o.outputDir !== null || o.overwrite)) {
171
+ throw new UsageError("--info cannot be combined with --pages, --output-dir or --overwrite");
172
+ }
173
+ return o;
174
+ }
175
+
176
+ export function renderHelp(): string {
177
+ const row = (d: OptionDescriptor) => ` ${(d.flag ?? "<path>").padEnd(26)} ${d.help}${d.default !== null && d.key !== "info" && d.key !== "overwrite" ? ` (default ${d.default})` : ""}`;
178
+ return [
179
+ "Usage: pi-quiver doc-to-md [flags] <path>",
180
+ "", "Per-call:", ...DOC_TO_MD_OPTIONS.filter((d) => !d.settable).map(row),
181
+ "", "Tunables (also settable under quiver.docToMd in settings.json; per-call > settings > default):",
182
+ ...DOC_TO_MD_OPTIONS.filter((d) => d.settable).map(row),
183
+ "", "Result: a handle (Saved-To, Images-Dir, Page-Count, Outline ...). Read the Saved-To file for the Markdown.",
184
+ "Exit codes: 0 success, 1 runtime error, 2 usage error.",
185
+ ].join("\n");
186
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * unpdf child (Node). argv[2] = mode (info | pdf-text); options JSON on stdin; JSON result on stdout.
3
+ * Exit 0 ok, 1 failure, 3 user error. Runs as its own process so a PDF.js stall is killable.
4
+ * Only selected pages are requested (getPage(n)), so an unselected toxic page cannot burn the budget.
5
+ */
6
+ import { readFileSync } from "node:fs";
7
+ import { getDocumentProxy } from "unpdf";
8
+
9
+ const SEP = (n: number) => `--- end of page.page_number=${n} ---`;
10
+
11
+ async function readStdin(): Promise<string> {
12
+ const chunks: Buffer[] = [];
13
+ for await (const c of process.stdin) chunks.push(c as Buffer);
14
+ return Buffer.concat(chunks).toString("utf8");
15
+ }
16
+
17
+ async function main(): Promise<number> {
18
+ const mode = process.argv[2];
19
+ if (mode !== "info" && mode !== "pdf-text") { process.stderr.write("usage: unpdf-worker <info|pdf-text>\n"); return 1; }
20
+ const o = JSON.parse((await readStdin()) || "{}") as { path: string; pages?: number[] | null };
21
+ try {
22
+ const pdf = await getDocumentProxy(new Uint8Array(readFileSync(o.path)));
23
+ const pageCount = pdf.numPages;
24
+ if (mode === "info") {
25
+ const meta = (await pdf.getMetadata().catch(() => null))?.info as Record<string, unknown> | undefined;
26
+ const metadata: Record<string, string> = {};
27
+ for (const k of ["Title", "Author", "Subject", "Creator", "Producer"]) if (typeof meta?.[k] === "string" && meta[k]) metadata[k.toLowerCase()] = meta[k] as string;
28
+ process.stdout.write(JSON.stringify({ pageCount, metadata, toc: [] }));
29
+ return 0;
30
+ }
31
+ const pages = o.pages ?? Array.from({ length: pageCount }, (_, i) => i + 1);
32
+ const bad = pages.filter((p) => p < 1 || p > pageCount);
33
+ if (bad.length) { process.stdout.write(JSON.stringify({ error: `pages out of range: ${bad.join(", ")} (document has ${pageCount} pages)`, pageCount })); return 3; }
34
+ const out: string[] = [], emptyPages: number[] = [], failedPages: { page: number; error: string }[] = [];
35
+ for (const n of pages) {
36
+ let text = "";
37
+ try {
38
+ const page = await pdf.getPage(n);
39
+ const content = await page.getTextContent();
40
+ text = content.items.map((it) => ("str" in it ? it.str + (it.hasEOL ? "\n" : " ") : "")).join("").trim();
41
+ } catch (e) { failedPages.push({ page: n, error: String(e).slice(0, 300) }); }
42
+ if (!text) emptyPages.push(n);
43
+ out.push(text, SEP(n));
44
+ }
45
+ if (failedPages.length === pages.length) throw new Error(`every selected page failed: ${failedPages[0].error}`);
46
+ process.stdout.write(JSON.stringify({ markdown: `${out.join("\n\n")}\n`, pages, pageCount, emptyPages, failedPages, notes: ["No images: unpdf backend"] }));
47
+ return 0;
48
+ } catch (e) {
49
+ process.stderr.write(`${e instanceof Error ? e.stack ?? e.message : String(e)}\n`);
50
+ return 1;
51
+ }
52
+ }
53
+
54
+ main().then((code) => { process.exitCode = code; });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-quiver",
3
- "version": "5.2.4",
3
+ "version": "5.3.0",
4
4
  "description": "Personal pack of Pi coding-agent extensions: context-safe fetch, doc_to_md PDF/DOCX/PPTX-to-Markdown conversion, session naming, a themed ASCII startup header, Opus 4.8 fast mode, and a provider-stall watchdog.",
5
5
  "author": "Jacek Juraszek",
6
6
  "license": "MIT",
@@ -22,6 +22,8 @@
22
22
  "pdf",
23
23
  "docx",
24
24
  "pptx",
25
+ "xlsx",
26
+ "xls",
25
27
  "cli"
26
28
  ],
27
29
  "engines": {
@@ -34,7 +36,7 @@
34
36
  "extensions",
35
37
  "lib",
36
38
  "dist",
37
- "scripts/pdf_to_md.py",
39
+ "scripts/doc_to_md.py",
38
40
  "types/**/*.d.ts",
39
41
  "README.md",
40
42
  "CHANGELOG.md"
@@ -44,7 +46,7 @@
44
46
  "test": "node --test \"test/*.test.ts\"",
45
47
  "typecheck": "npx -y tsc --noEmit",
46
48
  "test:all": "npm run check:agents-core && npm run test && npm run typecheck",
47
- "build": "esbuild bin/pi-quiver.ts --bundle --packages=external --format=esm --platform=node --outfile=dist/bin/pi-quiver.js",
49
+ "build": "esbuild bin/pi-quiver.ts lib/unpdf-worker.ts --bundle --packages=external --format=esm --platform=node --outdir=dist --outbase=.",
48
50
  "prepack": "npm run build"
49
51
  },
50
52
  "pi": {
@@ -82,7 +84,7 @@
82
84
  "jsdom": "^26.0.0",
83
85
  "turndown": "^7.2.0",
84
86
  "turndown-plugin-gfm": "^1.0.2",
85
- "unpdf": "^1.6.0"
87
+ "unpdf": "^1.8.1"
86
88
  },
87
89
  "devDependencies": {
88
90
  "@earendil-works/pi-ai": "^0.84.2",