@hraness/kb 0.17.1
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/DISCLOSURE +43 -0
- package/LICENSE +21 -0
- package/README.md +545 -0
- package/dist/agent-context.js +35 -0
- package/dist/agent-guide-audit.js +18 -0
- package/dist/attachments.js +19 -0
- package/dist/authoring.js +33 -0
- package/dist/benchmark.js +19 -0
- package/dist/capture.js +49 -0
- package/dist/cli.js +3646 -0
- package/dist/clip/acquire.js +57 -0
- package/dist/clip/args.js +21 -0
- package/dist/clip/bounded-byte-buffer.js +9 -0
- package/dist/clip/browser-profiles.js +17 -0
- package/dist/clip/bundle-reader.js +11 -0
- package/dist/clip/cli.js +27 -0
- package/dist/clip/cookies.js +21 -0
- package/dist/clip/defuddle-worker.js +84 -0
- package/dist/clip/doctor.js +32 -0
- package/dist/clip/jobs.js +784 -0
- package/dist/clip/network-proxy.js +9 -0
- package/dist/clip/network.js +28 -0
- package/dist/clip/persist.js +30 -0
- package/dist/clip/refresh.js +11 -0
- package/dist/clip/terminal.js +11 -0
- package/dist/evaluation-builder.js +12238 -0
- package/dist/evaluation-kb.js +26 -0
- package/dist/evaluation.js +35 -0
- package/dist/git.js +39 -0
- package/dist/graph.js +39 -0
- package/dist/index-01jj6rbv.js +1103 -0
- package/dist/index-06c9ctr6.js +738 -0
- package/dist/index-0kavxzqj.js +2480 -0
- package/dist/index-1gwbassd.js +743 -0
- package/dist/index-1n418kb9.js +827 -0
- package/dist/index-1vrd1rmn.js +56 -0
- package/dist/index-1xxnjn0d.js +171 -0
- package/dist/index-3rm7cz6h.js +373 -0
- package/dist/index-3v2z4f0q.js +328 -0
- package/dist/index-48pz4jpc.js +392 -0
- package/dist/index-4j3tt0c3.js +234 -0
- package/dist/index-5n05se68.js +307 -0
- package/dist/index-5vwpzb5a.js +764 -0
- package/dist/index-6g2pv9d2.js +370 -0
- package/dist/index-7fzc32gf.js +712 -0
- package/dist/index-84x0vjjp.js +432 -0
- package/dist/index-adx6khj5.js +488 -0
- package/dist/index-b88v3vtm.js +805 -0
- package/dist/index-cv6fh7z5.js +462 -0
- package/dist/index-cxfrakt7.js +1067 -0
- package/dist/index-d13v9ckt.js +150 -0
- package/dist/index-dycjbhw2.js +1943 -0
- package/dist/index-dyqwejk5.js +531 -0
- package/dist/index-e5fbsywq.js +658 -0
- package/dist/index-f984hw45.js +927 -0
- package/dist/index-gh719d91.js +63 -0
- package/dist/index-hgve9rh2.js +2046 -0
- package/dist/index-hya40gb2.js +474 -0
- package/dist/index-j4zgmzjr.js +152 -0
- package/dist/index-jsmvyyvf.js +1166 -0
- package/dist/index-kh0pwvdd.js +1974 -0
- package/dist/index-mqx4nd6v.js +119 -0
- package/dist/index-mxxxytys.js +1254 -0
- package/dist/index-n5dd7r0v.js +711 -0
- package/dist/index-npg9z1a4.js +431 -0
- package/dist/index-pj501bh1.js +140 -0
- package/dist/index-qry4vhxk.js +1041 -0
- package/dist/index-s2gw5aw9.js +199 -0
- package/dist/index-vvdj6kca.js +70 -0
- package/dist/index-w2zc0vwa.js +571 -0
- package/dist/index-x3fthpsc.js +585 -0
- package/dist/index-zr53sf63.js +44 -0
- package/dist/index-zxdy5pby.js +2728 -0
- package/dist/index-zzhgcwyt.js +966 -0
- package/dist/index.js +453 -0
- package/dist/navigation.js +13 -0
- package/dist/pdf.js +85 -0
- package/dist/percolate.js +24 -0
- package/dist/portfolio.js +77 -0
- package/dist/query.js +29 -0
- package/dist/repository-memory.js +60 -0
- package/dist/sdk.js +36 -0
- package/dist/search-rules.js +34 -0
- package/dist/search.js +22 -0
- package/dist/semantic.js +39 -0
- package/dist/source-inbox.js +17 -0
- package/dist/untrusted-content.js +32 -0
- package/dist/url-intelligence.js +74 -0
- package/dist/workflow.js +23 -0
- package/dist/workflows/decision-context.js +20 -0
- package/dist/workflows/explain-change.js +8 -0
- package/dist/workflows/index.js +28 -0
- package/dist/workflows/plan-radar.js +10 -0
- package/package.json +409 -0
- package/skills/kb/AGENTS.md +19 -0
- package/skills/kb/SKILL.md +82 -0
- package/skills/kb/agents/openai.yaml +4 -0
- package/skills/kb/references/pdf-review.md +51 -0
- package/skills/kb/references/percolate.md +144 -0
- package/skills/kb/references/plan-structure.md +105 -0
- package/skills/kb/references/plan.md +115 -0
- package/skills/kb/references/query.md +231 -0
- package/skills/kb/references/refresh.md +133 -0
- package/skills/kb/references/save-pdf.md +125 -0
- package/skills/kb/references/save-url.md +178 -0
- package/skills/kb/references/url-authentication.md +75 -0
- package/skills/kb/references/url-platforms.md +25 -0
- package/src/agent-context.ts +1241 -0
- package/src/agent-guide-audit.ts +750 -0
- package/src/attachments.ts +723 -0
- package/src/authoring.ts +1725 -0
- package/src/benchmark.ts +362 -0
- package/src/capture.ts +68 -0
- package/src/cli.ts +3660 -0
- package/src/clip/acquire.ts +1273 -0
- package/src/clip/archive-today.ts +402 -0
- package/src/clip/args.ts +423 -0
- package/src/clip/assets.ts +262 -0
- package/src/clip/bounded-byte-buffer.ts +75 -0
- package/src/clip/browser-profiles.ts +372 -0
- package/src/clip/bundle-reader.ts +608 -0
- package/src/clip/capture.ts +1054 -0
- package/src/clip/cli.ts +181 -0
- package/src/clip/cookies.ts +512 -0
- package/src/clip/doctor.ts +1110 -0
- package/src/clip/extract.ts +868 -0
- package/src/clip/jobs.ts +959 -0
- package/src/clip/lib.ts +861 -0
- package/src/clip/media.ts +1474 -0
- package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
- package/src/clip/metadata-search-tool/Cargo.toml +18 -0
- package/src/clip/metadata-search-tool/runner.ts +685 -0
- package/src/clip/metadata-search-tool/src/main.rs +389 -0
- package/src/clip/metadata-search.ts +687 -0
- package/src/clip/network-proxy.ts +627 -0
- package/src/clip/network.ts +950 -0
- package/src/clip/package-root.ts +54 -0
- package/src/clip/persist.ts +1472 -0
- package/src/clip/platforms.ts +1824 -0
- package/src/clip/refresh.ts +197 -0
- package/src/clip/structured.ts +432 -0
- package/src/clip/terminal.ts +197 -0
- package/src/clip/url-intelligence.ts +581 -0
- package/src/clip/url-metadata-backfill.ts +616 -0
- package/src/clip/url-metadata-cli.ts +251 -0
- package/src/clip/url-metadata.ts +1397 -0
- package/src/evaluation-analysis-v2.ts +1440 -0
- package/src/evaluation-builder.ts +2539 -0
- package/src/evaluation-corpus-authoring.ts +2052 -0
- package/src/evaluation-evidence.ts +1147 -0
- package/src/evaluation-execution-v2.ts +426 -0
- package/src/evaluation-implementation.ts +131 -0
- package/src/evaluation-kb-closure.ts +1430 -0
- package/src/evaluation-kb-runner-v2.ts +735 -0
- package/src/evaluation-kb-v2.ts +2567 -0
- package/src/evaluation-kb.ts +1018 -0
- package/src/evaluation-measurement-v2.ts +1462 -0
- package/src/evaluation-packing-v2.ts +452 -0
- package/src/evaluation-redaction.ts +28 -0
- package/src/evaluation-v2.ts +3857 -0
- package/src/evaluation.ts +1220 -0
- package/src/git.ts +1276 -0
- package/src/graph.ts +1487 -0
- package/src/index.ts +20 -0
- package/src/init.ts +123 -0
- package/src/navigation.ts +277 -0
- package/src/note-lock.ts +529 -0
- package/src/pdf/args.ts +202 -0
- package/src/pdf/capture.ts +298 -0
- package/src/pdf/cli.ts +159 -0
- package/src/pdf/extract.ts +303 -0
- package/src/pdf/layout.ts +618 -0
- package/src/pdf/markdown.ts +154 -0
- package/src/pdf/model.ts +234 -0
- package/src/pdf/ocr.ts +190 -0
- package/src/pdf/persist.ts +314 -0
- package/src/pdf/source.ts +135 -0
- package/src/pdf/tools.ts +146 -0
- package/src/pdf.ts +92 -0
- package/src/percolate.ts +877 -0
- package/src/portfolio-audit.ts +574 -0
- package/src/portfolio-identity.ts +162 -0
- package/src/portfolio-registry.ts +473 -0
- package/src/portfolio.ts +765 -0
- package/src/query.ts +646 -0
- package/src/repository-memory.ts +1150 -0
- package/src/sdk.ts +1452 -0
- package/src/search-rules.ts +712 -0
- package/src/search.ts +710 -0
- package/src/semantic-runtime.ts +1139 -0
- package/src/semantic.ts +2480 -0
- package/src/source-inbox.ts +232 -0
- package/src/untrusted-content.ts +401 -0
- package/src/url-intelligence.ts +104 -0
- package/src/vault.ts +636 -0
- package/src/workflow.ts +653 -0
- package/src/workflows/decision-context.ts +81 -0
- package/src/workflows/explain-change.ts +73 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/plan-radar.ts +101 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
copyFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
mkdtempSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
realpathSync,
|
|
12
|
+
renameSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
statSync,
|
|
15
|
+
writeFileSync,
|
|
16
|
+
} from "node:fs";
|
|
17
|
+
import { homedir } from "node:os";
|
|
18
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
19
|
+
|
|
20
|
+
import { redactSensitiveText } from "../clip/persist.js";
|
|
21
|
+
import type {
|
|
22
|
+
PdfCaptureManifest,
|
|
23
|
+
PdfImageCandidate,
|
|
24
|
+
} from "./model.js";
|
|
25
|
+
import {
|
|
26
|
+
PDF_CAPTURE_ANNOTATIONS_FILENAME,
|
|
27
|
+
PDF_CAPTURE_MANIFEST_FILENAME,
|
|
28
|
+
PDF_CAPTURE_MANIFEST_SCHEMA_VERSION,
|
|
29
|
+
PDF_CAPTURE_SOURCE_FILENAME,
|
|
30
|
+
} from "./model.js";
|
|
31
|
+
|
|
32
|
+
function isConfinedChild(root: string, path: string): boolean {
|
|
33
|
+
const child = relative(root, path);
|
|
34
|
+
return child !== "" && !isAbsolute(child) && child !== ".." && !child.startsWith(`..${sep}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function assertConfinedChild(root: string, path: string, label: string): void {
|
|
38
|
+
if (!isConfinedChild(root, path)) throw new Error(`${label} escapes the PDF capture root`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function safeSlug(slug: string): string {
|
|
42
|
+
if (
|
|
43
|
+
slug.length === 0
|
|
44
|
+
|| slug.length > 240
|
|
45
|
+
|| [...slug].length > 80
|
|
46
|
+
|| slug !== slug.normalize("NFKC")
|
|
47
|
+
|| !/^[\p{Letter}\p{Number}](?:[\p{Letter}\p{Number}._-]*[\p{Letter}\p{Number}])?$/u.test(slug)
|
|
48
|
+
) throw new Error("unsafe PDF capture slug");
|
|
49
|
+
return slug;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function outputRoot(path: string): string {
|
|
53
|
+
const absolute = resolve(path);
|
|
54
|
+
mkdirSync(absolute, { recursive: true, mode: 0o755 });
|
|
55
|
+
const stats = lstatSync(absolute);
|
|
56
|
+
if (!stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
57
|
+
throw new Error("PDF capture output root is not a directory");
|
|
58
|
+
}
|
|
59
|
+
const canonical = realpathSync(absolute);
|
|
60
|
+
if (!lstatSync(canonical).isDirectory()) throw new Error("PDF capture output root is not a directory");
|
|
61
|
+
if (dirname(canonical) === canonical || canonical === realpathSync(homedir())) {
|
|
62
|
+
throw new Error("refusing dangerous PDF capture output root");
|
|
63
|
+
}
|
|
64
|
+
return canonical;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function extensionForMimeType(mimeType: string): string {
|
|
68
|
+
switch (mimeType) {
|
|
69
|
+
case "image/png": return "png";
|
|
70
|
+
case "image/jpeg": return "jpg";
|
|
71
|
+
case "image/gif": return "gif";
|
|
72
|
+
case "image/webp": return "webp";
|
|
73
|
+
case "image/avif": return "avif";
|
|
74
|
+
default: throw new Error(`unsupported PDF image MIME type: ${mimeType}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Derive a stable, content-addressed path for a retained PDF image. */
|
|
79
|
+
export function pdfImageAssetPath(image: Pick<PdfImageCandidate, "sha256" | "mimeType">): string {
|
|
80
|
+
if (!/^[0-9a-f]{64}$/u.test(image.sha256)) throw new Error("invalid PDF image sha256");
|
|
81
|
+
return `assets/${image.sha256}.${extensionForMimeType(image.mimeType)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function hashFile(path: string): string {
|
|
85
|
+
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function assertSourceIdentity(path: string, expectedBytes: number, expectedSha256: string, label: string): void {
|
|
89
|
+
const stats = statSync(path);
|
|
90
|
+
if (!stats.isFile() || stats.size !== expectedBytes || hashFile(path) !== expectedSha256) {
|
|
91
|
+
throw new Error(`${label} changed after PDF inspection`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ownedPdfTarget(targetDirectory: string, slug: string): { readonly device: number; readonly inode: number } {
|
|
96
|
+
const directory = lstatSync(targetDirectory);
|
|
97
|
+
if (!directory.isDirectory() || directory.isSymbolicLink()) {
|
|
98
|
+
throw new Error("PDF --force only replaces a regular PDF capture directory");
|
|
99
|
+
}
|
|
100
|
+
const manifestPath = join(targetDirectory, PDF_CAPTURE_MANIFEST_FILENAME);
|
|
101
|
+
const markdownPath = join(targetDirectory, `${safeSlug(slug)}.md`);
|
|
102
|
+
const sourcePath = join(targetDirectory, PDF_CAPTURE_SOURCE_FILENAME);
|
|
103
|
+
for (const path of [manifestPath, markdownPath, sourcePath]) {
|
|
104
|
+
const stats = lstatSync(path);
|
|
105
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
106
|
+
throw new Error("PDF --force refused a target without its owned files");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (lstatSync(manifestPath).size > 16 * 1024 * 1024) {
|
|
110
|
+
throw new Error("PDF --force refused an oversized capture manifest");
|
|
111
|
+
}
|
|
112
|
+
let parsed: unknown;
|
|
113
|
+
try {
|
|
114
|
+
parsed = JSON.parse(readFileSync(manifestPath, "utf8")) as unknown;
|
|
115
|
+
} catch {
|
|
116
|
+
throw new Error("PDF --force refused an invalid capture manifest");
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
typeof parsed !== "object"
|
|
120
|
+
|| parsed === null
|
|
121
|
+
|| Array.isArray(parsed)
|
|
122
|
+
|| !("schemaVersion" in parsed)
|
|
123
|
+
|| parsed.schemaVersion !== PDF_CAPTURE_MANIFEST_SCHEMA_VERSION
|
|
124
|
+
|| !("kind" in parsed)
|
|
125
|
+
|| parsed.kind !== "pdf"
|
|
126
|
+
) throw new Error("PDF --force refused an incompatible capture manifest");
|
|
127
|
+
return { device: directory.dev, inode: directory.ino };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function assertStagingTreeSafe(root: string, directory = root): void {
|
|
131
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
132
|
+
const path = join(directory, entry.name);
|
|
133
|
+
assertConfinedChild(root, path, "staged PDF artifact");
|
|
134
|
+
const stats = lstatSync(path);
|
|
135
|
+
if (stats.isSymbolicLink()) throw new Error("staged PDF artifacts must not be symbolic links");
|
|
136
|
+
if (stats.isDirectory()) assertStagingTreeSafe(root, path);
|
|
137
|
+
else if (!stats.isFile()) throw new Error("staged PDF artifacts must be regular files");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function unusedBackupPath(root: string): string {
|
|
142
|
+
for (;;) {
|
|
143
|
+
const candidate = join(root, `.pdf-capture-backup-${randomUUID()}`);
|
|
144
|
+
assertConfinedChild(root, candidate, "PDF capture backup");
|
|
145
|
+
if (!existsSync(candidate)) return candidate;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function errorMessage(error: unknown): string {
|
|
150
|
+
return error instanceof Error ? error.message : String(error);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type PersistPdfCaptureInput = {
|
|
154
|
+
readonly outputBase: string;
|
|
155
|
+
readonly slug: string;
|
|
156
|
+
readonly force: boolean;
|
|
157
|
+
readonly sourcePath: string;
|
|
158
|
+
readonly markdown: string;
|
|
159
|
+
readonly manifest: PdfCaptureManifest;
|
|
160
|
+
readonly images: readonly PdfImageCandidate[];
|
|
161
|
+
readonly annotationsJson?: string;
|
|
162
|
+
readonly afterBackup?: () => void;
|
|
163
|
+
readonly afterInstall?: () => void;
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
/** Stage and atomically install a complete PDF source bundle. */
|
|
167
|
+
export function persistPdfCapture(input: PersistPdfCaptureInput): string {
|
|
168
|
+
const slug = safeSlug(input.slug);
|
|
169
|
+
const root = outputRoot(input.outputBase);
|
|
170
|
+
const targetDirectory = join(root, slug);
|
|
171
|
+
assertConfinedChild(root, targetDirectory, "PDF capture target");
|
|
172
|
+
const targetExists = existsSync(targetDirectory);
|
|
173
|
+
if (targetExists && !input.force) {
|
|
174
|
+
throw new Error(`PDF capture already exists: ${targetDirectory}; pass --force to replace it`);
|
|
175
|
+
}
|
|
176
|
+
const expectedIdentity = targetExists ? ownedPdfTarget(targetDirectory, slug) : null;
|
|
177
|
+
const stagingDirectory = mkdtempSync(join(root, ".pdf-capture-staging-"));
|
|
178
|
+
chmodSync(stagingDirectory, 0o700);
|
|
179
|
+
assertConfinedChild(root, stagingDirectory, "PDF capture staging directory");
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
assertSourceIdentity(
|
|
183
|
+
input.sourcePath,
|
|
184
|
+
input.manifest.source.bytes,
|
|
185
|
+
input.manifest.source.sha256,
|
|
186
|
+
"PDF source",
|
|
187
|
+
);
|
|
188
|
+
const sourceDestination = join(stagingDirectory, PDF_CAPTURE_SOURCE_FILENAME);
|
|
189
|
+
copyFileSync(input.sourcePath, sourceDestination);
|
|
190
|
+
chmodSync(sourceDestination, 0o644);
|
|
191
|
+
const assetsDirectory = join(stagingDirectory, "assets");
|
|
192
|
+
mkdirSync(assetsDirectory, { recursive: true, mode: 0o755 });
|
|
193
|
+
|
|
194
|
+
const imagesByPath = new Map<string, PdfImageCandidate>();
|
|
195
|
+
for (const image of input.images) {
|
|
196
|
+
const assetPath = pdfImageAssetPath(image);
|
|
197
|
+
const existing = imagesByPath.get(assetPath);
|
|
198
|
+
if (existing !== undefined) {
|
|
199
|
+
if (existing.bytes !== image.bytes || existing.sha256 !== image.sha256) {
|
|
200
|
+
throw new Error("conflicting PDF image assets have the same destination");
|
|
201
|
+
}
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
imagesByPath.set(assetPath, image);
|
|
205
|
+
}
|
|
206
|
+
for (const [assetPath, image] of [...imagesByPath.entries()].sort(([left], [right]) =>
|
|
207
|
+
left.localeCompare(right))) {
|
|
208
|
+
assertSourceIdentity(image.sourcePath, image.bytes, image.sha256, "PDF image");
|
|
209
|
+
const destination = join(stagingDirectory, assetPath);
|
|
210
|
+
assertConfinedChild(stagingDirectory, destination, "PDF image destination");
|
|
211
|
+
copyFileSync(image.sourcePath, destination);
|
|
212
|
+
chmodSync(destination, 0o644);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
writeFileSync(
|
|
216
|
+
join(stagingDirectory, `${slug}.md`),
|
|
217
|
+
`${redactSensitiveText(input.markdown).trimEnd()}\n`,
|
|
218
|
+
{ encoding: "utf8", flag: "wx", mode: 0o644 },
|
|
219
|
+
);
|
|
220
|
+
if (input.manifest.annotations === null) {
|
|
221
|
+
if (input.annotationsJson !== undefined) {
|
|
222
|
+
throw new Error("PDF annotations were supplied without manifest provenance");
|
|
223
|
+
}
|
|
224
|
+
} else {
|
|
225
|
+
const annotationsJson = input.annotationsJson;
|
|
226
|
+
if (annotationsJson === undefined) {
|
|
227
|
+
throw new Error("PDF annotation provenance is missing its retained input");
|
|
228
|
+
}
|
|
229
|
+
if (
|
|
230
|
+
Buffer.byteLength(annotationsJson) !== input.manifest.annotations.bytes
|
|
231
|
+
|| createHash("sha256").update(annotationsJson).digest("hex")
|
|
232
|
+
!== input.manifest.annotations.sha256
|
|
233
|
+
) {
|
|
234
|
+
throw new Error("PDF annotation input does not match its manifest provenance");
|
|
235
|
+
}
|
|
236
|
+
let annotations: unknown;
|
|
237
|
+
try {
|
|
238
|
+
annotations = JSON.parse(annotationsJson) as unknown;
|
|
239
|
+
} catch {
|
|
240
|
+
throw new Error("PDF annotation input is not valid JSON");
|
|
241
|
+
}
|
|
242
|
+
if (
|
|
243
|
+
!Array.isArray(annotations)
|
|
244
|
+
|| annotations.length !== input.manifest.annotations.count
|
|
245
|
+
) {
|
|
246
|
+
throw new Error("PDF annotation input count does not match its manifest provenance");
|
|
247
|
+
}
|
|
248
|
+
writeFileSync(
|
|
249
|
+
join(stagingDirectory, PDF_CAPTURE_ANNOTATIONS_FILENAME),
|
|
250
|
+
annotationsJson,
|
|
251
|
+
{ encoding: "utf8", flag: "wx", mode: 0o644 },
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
writeFileSync(
|
|
255
|
+
join(stagingDirectory, PDF_CAPTURE_MANIFEST_FILENAME),
|
|
256
|
+
`${JSON.stringify(input.manifest, null, 2)}\n`,
|
|
257
|
+
{ encoding: "utf8", flag: "wx", mode: 0o644 },
|
|
258
|
+
);
|
|
259
|
+
assertStagingTreeSafe(stagingDirectory);
|
|
260
|
+
|
|
261
|
+
if (existsSync(targetDirectory) !== (expectedIdentity !== null)) {
|
|
262
|
+
throw new Error("PDF capture target changed during the transaction");
|
|
263
|
+
}
|
|
264
|
+
if (expectedIdentity !== null) {
|
|
265
|
+
const current = ownedPdfTarget(targetDirectory, slug);
|
|
266
|
+
if (current.device !== expectedIdentity.device || current.inode !== expectedIdentity.inode) {
|
|
267
|
+
throw new Error("PDF capture target changed during the transaction");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const backupDirectory = expectedIdentity === null ? null : unusedBackupPath(root);
|
|
272
|
+
let installed = false;
|
|
273
|
+
let backedUp = false;
|
|
274
|
+
try {
|
|
275
|
+
if (backupDirectory !== null) {
|
|
276
|
+
renameSync(targetDirectory, backupDirectory);
|
|
277
|
+
backedUp = true;
|
|
278
|
+
input.afterBackup?.();
|
|
279
|
+
}
|
|
280
|
+
renameSync(stagingDirectory, targetDirectory);
|
|
281
|
+
installed = true;
|
|
282
|
+
input.afterInstall?.();
|
|
283
|
+
if (backupDirectory !== null) rmSync(backupDirectory, { recursive: true, force: true });
|
|
284
|
+
return targetDirectory;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
let rollbackError: unknown;
|
|
287
|
+
try {
|
|
288
|
+
if (installed && existsSync(targetDirectory)) {
|
|
289
|
+
rmSync(targetDirectory, { recursive: true, force: true });
|
|
290
|
+
}
|
|
291
|
+
if (backedUp && backupDirectory !== null && existsSync(backupDirectory)) {
|
|
292
|
+
if (existsSync(targetDirectory)) throw new Error("target was recreated before rollback");
|
|
293
|
+
renameSync(backupDirectory, targetDirectory);
|
|
294
|
+
}
|
|
295
|
+
} catch (caught) {
|
|
296
|
+
rollbackError = caught;
|
|
297
|
+
}
|
|
298
|
+
if (rollbackError !== undefined) {
|
|
299
|
+
throw new Error(
|
|
300
|
+
`PDF capture commit failed (${errorMessage(error)}) and rollback failed (${errorMessage(rollbackError)})`,
|
|
301
|
+
{ cause: error },
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
if (existsSync(stagingDirectory)) rmSync(stagingDirectory, { recursive: true, force: true });
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export function pdfMarkdownFilename(slug: string): string {
|
|
313
|
+
return `${safeSlug(slug)}.md`;
|
|
314
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import {
|
|
2
|
+
chmodSync,
|
|
3
|
+
mkdtempSync,
|
|
4
|
+
rmSync,
|
|
5
|
+
writeFileSync,
|
|
6
|
+
} from "node:fs";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { basename, join } from "node:path";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
safeFetch,
|
|
12
|
+
type SafeFetchOptions,
|
|
13
|
+
type SafeFetchResult,
|
|
14
|
+
} from "../clip/network.js";
|
|
15
|
+
import { sanitizeArtifactUrl } from "../clip/persist.js";
|
|
16
|
+
import { pdfCaptureDefaults } from "./extract.js";
|
|
17
|
+
import type { PdfRemoteSource } from "./model.js";
|
|
18
|
+
|
|
19
|
+
export type PreparedPdfSource = {
|
|
20
|
+
readonly inputPath: string;
|
|
21
|
+
readonly remoteSource?: PdfRemoteSource;
|
|
22
|
+
readonly dispose: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type PdfSourceDependencies = {
|
|
26
|
+
readonly fetch?: (url: URL, options: SafeFetchOptions) => Promise<SafeFetchResult>;
|
|
27
|
+
readonly makeTemporaryDirectory?: () => string;
|
|
28
|
+
readonly writeFile?: typeof writeFileSync;
|
|
29
|
+
readonly removeDirectory?: (path: string) => void;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const remoteUserAgent = "hraness-kb/0.7 PDF capture";
|
|
33
|
+
|
|
34
|
+
function parseRemoteUrl(input: string): URL | null {
|
|
35
|
+
if (!/^https?:\/\//iu.test(input)) return null;
|
|
36
|
+
let url: URL;
|
|
37
|
+
try {
|
|
38
|
+
url = new URL(input);
|
|
39
|
+
} catch {
|
|
40
|
+
throw new Error("PDF URL is invalid");
|
|
41
|
+
}
|
|
42
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
43
|
+
throw new Error("PDF URL must use HTTP or HTTPS");
|
|
44
|
+
}
|
|
45
|
+
if (url.username !== "" || url.password !== "") {
|
|
46
|
+
throw new Error("PDF URL must not contain embedded credentials");
|
|
47
|
+
}
|
|
48
|
+
return url;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function remoteFilename(url: URL): string {
|
|
52
|
+
let decoded: string;
|
|
53
|
+
try {
|
|
54
|
+
decoded = decodeURIComponent(basename(url.pathname));
|
|
55
|
+
} catch {
|
|
56
|
+
decoded = "source.pdf";
|
|
57
|
+
}
|
|
58
|
+
const normalized = decoded
|
|
59
|
+
.normalize("NFKC")
|
|
60
|
+
.replace(/[^\p{Letter}\p{Number}._-]+/gu, "-")
|
|
61
|
+
.replace(/^[.-]+|[.-]+$/gu, "")
|
|
62
|
+
.slice(0, 180);
|
|
63
|
+
const stem = normalized === "" ? "source" : normalized.replace(/\.pdf$/iu, "");
|
|
64
|
+
return `${stem}.pdf`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function assertPdfSignature(bytes: Uint8Array): void {
|
|
68
|
+
if (
|
|
69
|
+
bytes.byteLength < 5
|
|
70
|
+
|| new TextDecoder().decode(bytes.subarray(0, 5)) !== "%PDF-"
|
|
71
|
+
) {
|
|
72
|
+
throw new Error("remote PDF input does not have a valid PDF signature");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Resolve a local path directly or download one bounded public PDF into a disposable private directory. */
|
|
77
|
+
export async function preparePdfSource(
|
|
78
|
+
input: string,
|
|
79
|
+
options: {
|
|
80
|
+
readonly timeoutMs?: number;
|
|
81
|
+
readonly maxPdfBytes?: number;
|
|
82
|
+
} = {},
|
|
83
|
+
dependencies: PdfSourceDependencies = {},
|
|
84
|
+
): Promise<PreparedPdfSource> {
|
|
85
|
+
const requestedUrl = parseRemoteUrl(input);
|
|
86
|
+
if (requestedUrl === null) {
|
|
87
|
+
return {
|
|
88
|
+
inputPath: input,
|
|
89
|
+
dispose: () => {},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const result = await (dependencies.fetch ?? safeFetch)(requestedUrl, {
|
|
94
|
+
timeoutMs: options.timeoutMs ?? pdfCaptureDefaults.timeoutMs,
|
|
95
|
+
maxBytes: options.maxPdfBytes ?? pdfCaptureDefaults.maxPdfBytes,
|
|
96
|
+
allowPrivateNetwork: false,
|
|
97
|
+
userAgent: remoteUserAgent,
|
|
98
|
+
accept: "application/pdf,application/octet-stream;q=0.9,*/*;q=0.1",
|
|
99
|
+
retries: 2,
|
|
100
|
+
maxRedirects: 5,
|
|
101
|
+
});
|
|
102
|
+
assertPdfSignature(result.bytes);
|
|
103
|
+
|
|
104
|
+
const makeTemporaryDirectory = dependencies.makeTemporaryDirectory
|
|
105
|
+
?? (() => mkdtempSync(join(tmpdir(), "hraness-kb-pdf-source-")));
|
|
106
|
+
const removeDirectory = dependencies.removeDirectory
|
|
107
|
+
?? ((path: string) => rmSync(path, { recursive: true, force: true }));
|
|
108
|
+
const directory = makeTemporaryDirectory();
|
|
109
|
+
let disposed = false;
|
|
110
|
+
const dispose = (): void => {
|
|
111
|
+
if (disposed) return;
|
|
112
|
+
disposed = true;
|
|
113
|
+
removeDirectory(directory);
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
chmodSync(directory, 0o700);
|
|
117
|
+
const inputPath = join(directory, remoteFilename(result.finalUrl));
|
|
118
|
+
(dependencies.writeFile ?? writeFileSync)(inputPath, result.bytes, {
|
|
119
|
+
encoding: null,
|
|
120
|
+
flag: "wx",
|
|
121
|
+
mode: 0o600,
|
|
122
|
+
});
|
|
123
|
+
return {
|
|
124
|
+
inputPath,
|
|
125
|
+
remoteSource: {
|
|
126
|
+
requestedUrl: sanitizeArtifactUrl(requestedUrl.href),
|
|
127
|
+
finalUrl: sanitizeArtifactUrl(result.finalUrl.href),
|
|
128
|
+
},
|
|
129
|
+
dispose,
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
dispose();
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/pdf/tools.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { BoundedByteBuffer } from "../clip/bounded-byte-buffer.js";
|
|
7
|
+
import type {
|
|
8
|
+
PdfCaptureDependencies,
|
|
9
|
+
PdfToolCommand,
|
|
10
|
+
PdfToolCommandResult,
|
|
11
|
+
PdfToolPaths,
|
|
12
|
+
PdfToolRunner,
|
|
13
|
+
} from "./model.js";
|
|
14
|
+
|
|
15
|
+
const commonExecutableDirectories = [
|
|
16
|
+
"/opt/homebrew/bin",
|
|
17
|
+
"/usr/local/bin",
|
|
18
|
+
"/usr/bin",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
async function readBoundedStream(stream: NodeJS.ReadableStream, maxBytes: number): Promise<string> {
|
|
22
|
+
const bytes = new BoundedByteBuffer(maxBytes);
|
|
23
|
+
const iterable: AsyncIterable<unknown> = stream;
|
|
24
|
+
for await (const value of iterable) {
|
|
25
|
+
let chunk: Uint8Array;
|
|
26
|
+
if (typeof value === "string") chunk = new TextEncoder().encode(value);
|
|
27
|
+
else if (value instanceof Uint8Array) chunk = value;
|
|
28
|
+
else throw new Error("PDF tool returned an unsupported output chunk");
|
|
29
|
+
if (!bytes.append(chunk)) throw new Error(`PDF tool output exceeded ${maxBytes} bytes`);
|
|
30
|
+
}
|
|
31
|
+
return new TextDecoder().decode(bytes.toUint8Array());
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Run a local PDF tool without a shell and with bounded output and wall time. */
|
|
35
|
+
export const runPdfToolCommand: PdfToolRunner = async (
|
|
36
|
+
specification: PdfToolCommand,
|
|
37
|
+
): Promise<PdfToolCommandResult> => {
|
|
38
|
+
const executable = specification.command[0];
|
|
39
|
+
if (executable === undefined) throw new Error("PDF tool command is empty");
|
|
40
|
+
const useProcessGroup = process.platform !== "win32";
|
|
41
|
+
const child = spawn(executable, specification.command.slice(1), {
|
|
42
|
+
cwd: specification.cwd,
|
|
43
|
+
detached: useProcessGroup,
|
|
44
|
+
env: {
|
|
45
|
+
...process.env,
|
|
46
|
+
LC_ALL: "C",
|
|
47
|
+
LANG: "C",
|
|
48
|
+
...specification.environment,
|
|
49
|
+
},
|
|
50
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
51
|
+
windowsHide: true,
|
|
52
|
+
});
|
|
53
|
+
const exited = new Promise<number>((resolveExit, rejectExit) => {
|
|
54
|
+
child.once("error", rejectExit);
|
|
55
|
+
child.once("close", (code) => resolveExit(code ?? 1));
|
|
56
|
+
});
|
|
57
|
+
const signalProcessTree = (signal: NodeJS.Signals): void => {
|
|
58
|
+
if (useProcessGroup && child.pid !== undefined) {
|
|
59
|
+
try {
|
|
60
|
+
process.kill(-child.pid, signal);
|
|
61
|
+
return;
|
|
62
|
+
} catch {
|
|
63
|
+
// The process may have exited before the group signal.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
child.kill(signal);
|
|
68
|
+
} catch {
|
|
69
|
+
// Killing an already exited direct child is harmless.
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const state: { failure: Error | null } = { failure: null };
|
|
74
|
+
let forceKillTimer: ReturnType<typeof setTimeout> | null = null;
|
|
75
|
+
const requestStop = (error: Error): void => {
|
|
76
|
+
state.failure ??= error;
|
|
77
|
+
if (forceKillTimer !== null) return;
|
|
78
|
+
signalProcessTree("SIGTERM");
|
|
79
|
+
forceKillTimer = setTimeout(() => signalProcessTree("SIGKILL"), 1_000);
|
|
80
|
+
};
|
|
81
|
+
const timer = setTimeout(() => {
|
|
82
|
+
requestStop(new Error(`PDF tool timed out after ${specification.timeoutMs}ms`));
|
|
83
|
+
}, specification.timeoutMs);
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const [stdout, stderr, exitCode] = await Promise.all([
|
|
87
|
+
readBoundedStream(child.stdout, specification.maxOutputBytes).catch((error: unknown) => {
|
|
88
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
89
|
+
requestStop(normalized);
|
|
90
|
+
throw normalized;
|
|
91
|
+
}),
|
|
92
|
+
readBoundedStream(child.stderr, specification.maxOutputBytes).catch((error: unknown) => {
|
|
93
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
94
|
+
requestStop(normalized);
|
|
95
|
+
throw normalized;
|
|
96
|
+
}),
|
|
97
|
+
exited,
|
|
98
|
+
]);
|
|
99
|
+
if (state.failure !== null) throw state.failure;
|
|
100
|
+
return { stdout, stderr, exitCode };
|
|
101
|
+
} catch (error) {
|
|
102
|
+
requestStop(error instanceof Error ? error : new Error(String(error)));
|
|
103
|
+
await exited.catch(() => 1);
|
|
104
|
+
throw error;
|
|
105
|
+
} finally {
|
|
106
|
+
clearTimeout(timer);
|
|
107
|
+
if (forceKillTimer !== null) clearTimeout(forceKillTimer);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function discoverExecutable(
|
|
112
|
+
name: string,
|
|
113
|
+
dependencies: Pick<PdfCaptureDependencies, "which" | "exists">,
|
|
114
|
+
): string | null {
|
|
115
|
+
const exists = dependencies.exists ?? existsSync;
|
|
116
|
+
const fromPath = (dependencies.which ?? ((value: string) => Bun.which(value)))(name);
|
|
117
|
+
if (fromPath !== null && exists(fromPath)) return fromPath;
|
|
118
|
+
const homeCandidates = name === "tesseract"
|
|
119
|
+
? [join(homedir(), ".local", "bin", name)]
|
|
120
|
+
: [];
|
|
121
|
+
for (const path of [
|
|
122
|
+
...homeCandidates,
|
|
123
|
+
...commonExecutableDirectories.map((directory) => join(directory, name)),
|
|
124
|
+
]) {
|
|
125
|
+
if (exists(path)) return path;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Resolve required Poppler tools and the optional local OCR executable. */
|
|
131
|
+
export function resolvePdfTools(dependencies: PdfCaptureDependencies = {}): PdfToolPaths {
|
|
132
|
+
const pdfinfo = dependencies.tools?.pdfinfo
|
|
133
|
+
?? discoverExecutable("pdfinfo", dependencies);
|
|
134
|
+
const pdftohtml = dependencies.tools?.pdftohtml
|
|
135
|
+
?? discoverExecutable("pdftohtml", dependencies);
|
|
136
|
+
const tesseract = dependencies.tools?.tesseract === undefined
|
|
137
|
+
? discoverExecutable("tesseract", dependencies)
|
|
138
|
+
: dependencies.tools.tesseract;
|
|
139
|
+
if (pdfinfo === null) {
|
|
140
|
+
throw new Error("pdfinfo is required for PDF ingestion; install the Poppler command-line tools");
|
|
141
|
+
}
|
|
142
|
+
if (pdftohtml === null) {
|
|
143
|
+
throw new Error("pdftohtml is required for PDF ingestion; install the Poppler command-line tools");
|
|
144
|
+
}
|
|
145
|
+
return { pdfinfo, pdftohtml, tesseract };
|
|
146
|
+
}
|
package/src/pdf.ts
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parsePdfArguments as parsePdfArgumentsImplementation,
|
|
3
|
+
pdfUsage as installedPdfUsage,
|
|
4
|
+
} from "./pdf/args.js";
|
|
5
|
+
import {
|
|
6
|
+
main as runPdfCommandImplementation,
|
|
7
|
+
pdfCaptureSummary as pdfCaptureSummaryImplementation,
|
|
8
|
+
} from "./pdf/cli.js";
|
|
9
|
+
import { runPdfCapture as runPdfCaptureImplementation } from "./pdf/capture.js";
|
|
10
|
+
import {
|
|
11
|
+
inspectPdf as inspectPdfImplementation,
|
|
12
|
+
pdfCaptureDefaults as installedPdfCaptureDefaults,
|
|
13
|
+
} from "./pdf/extract.js";
|
|
14
|
+
import {
|
|
15
|
+
decodePopplerText as decodePopplerTextImplementation,
|
|
16
|
+
layoutBlocks as layoutBlocksImplementation,
|
|
17
|
+
parsePdfImageInterpretations as parsePdfImageInterpretationsImplementation,
|
|
18
|
+
parsePdfInfo as parsePdfInfoImplementation,
|
|
19
|
+
parsePopplerXml as parsePopplerXmlImplementation,
|
|
20
|
+
type PdfLayoutBlock,
|
|
21
|
+
} from "./pdf/layout.js";
|
|
22
|
+
import { buildPdfMarkdown as buildPdfMarkdownImplementation } from "./pdf/markdown.js";
|
|
23
|
+
import {
|
|
24
|
+
PDF_CAPTURE_ANNOTATIONS_FILENAME as installedAnnotationsFilename,
|
|
25
|
+
PDF_CAPTURE_MANIFEST_FILENAME as installedManifestFilename,
|
|
26
|
+
PDF_CAPTURE_MANIFEST_SCHEMA_VERSION as installedManifestSchemaVersion,
|
|
27
|
+
PDF_CAPTURE_SOURCE_FILENAME as installedSourceFilename,
|
|
28
|
+
} from "./pdf/model.js";
|
|
29
|
+
import {
|
|
30
|
+
ocrPdfImage as ocrPdfImageImplementation,
|
|
31
|
+
parseTesseractTsv as parseTesseractTsvImplementation,
|
|
32
|
+
} from "./pdf/ocr.js";
|
|
33
|
+
import {
|
|
34
|
+
pdfImageAssetPath as pdfImageAssetPathImplementation,
|
|
35
|
+
pdfMarkdownFilename as pdfMarkdownFilenameImplementation,
|
|
36
|
+
persistPdfCapture as persistPdfCaptureImplementation,
|
|
37
|
+
} from "./pdf/persist.js";
|
|
38
|
+
import {
|
|
39
|
+
resolvePdfTools as resolvePdfToolsImplementation,
|
|
40
|
+
runPdfToolCommand as runPdfToolCommandImplementation,
|
|
41
|
+
} from "./pdf/tools.js";
|
|
42
|
+
|
|
43
|
+
// Explicit value assignments avoid a Bun bundler bug that can drop bindings
|
|
44
|
+
// used only by a re-exporting entrypoint.
|
|
45
|
+
export const PDF_CAPTURE_MANIFEST_FILENAME = installedManifestFilename;
|
|
46
|
+
export const PDF_CAPTURE_MANIFEST_SCHEMA_VERSION = installedManifestSchemaVersion;
|
|
47
|
+
export const PDF_CAPTURE_SOURCE_FILENAME = installedSourceFilename;
|
|
48
|
+
export const PDF_CAPTURE_ANNOTATIONS_FILENAME = installedAnnotationsFilename;
|
|
49
|
+
export const buildPdfMarkdown = buildPdfMarkdownImplementation;
|
|
50
|
+
export const decodePopplerText = decodePopplerTextImplementation;
|
|
51
|
+
export const inspectPdf = inspectPdfImplementation;
|
|
52
|
+
export const layoutBlocks = layoutBlocksImplementation;
|
|
53
|
+
export const ocrPdfImage = ocrPdfImageImplementation;
|
|
54
|
+
export const parsePdfArguments = parsePdfArgumentsImplementation;
|
|
55
|
+
export const parsePdfImageInterpretations = parsePdfImageInterpretationsImplementation;
|
|
56
|
+
export const parsePdfInfo = parsePdfInfoImplementation;
|
|
57
|
+
export const parsePopplerXml = parsePopplerXmlImplementation;
|
|
58
|
+
export const parseTesseractTsv = parseTesseractTsvImplementation;
|
|
59
|
+
export const pdfCaptureDefaults = installedPdfCaptureDefaults;
|
|
60
|
+
export const pdfCaptureSummary = pdfCaptureSummaryImplementation;
|
|
61
|
+
export const pdfImageAssetPath = pdfImageAssetPathImplementation;
|
|
62
|
+
export const pdfMarkdownFilename = pdfMarkdownFilenameImplementation;
|
|
63
|
+
export const pdfUsage = installedPdfUsage;
|
|
64
|
+
export const persistPdfCapture = persistPdfCaptureImplementation;
|
|
65
|
+
export const resolvePdfTools = resolvePdfToolsImplementation;
|
|
66
|
+
export const runPdfCapture = runPdfCaptureImplementation;
|
|
67
|
+
export const runPdfCommand = runPdfCommandImplementation;
|
|
68
|
+
export const runPdfToolCommand = runPdfToolCommandImplementation;
|
|
69
|
+
|
|
70
|
+
export type { PdfLayoutBlock };
|
|
71
|
+
export type {
|
|
72
|
+
PdfBounds,
|
|
73
|
+
PdfCaptureDependencies,
|
|
74
|
+
PdfCaptureManifest,
|
|
75
|
+
PdfCaptureOptions,
|
|
76
|
+
PdfCaptureOutcome,
|
|
77
|
+
PdfCaptureStatus,
|
|
78
|
+
PdfDocumentMetadata,
|
|
79
|
+
PdfImageCandidate,
|
|
80
|
+
PdfImageInterpretation,
|
|
81
|
+
PdfImageSemanticMetadata,
|
|
82
|
+
PdfInspection,
|
|
83
|
+
PdfManifestAsset,
|
|
84
|
+
PdfManifestImage,
|
|
85
|
+
PdfOcrResult,
|
|
86
|
+
PdfPageLayout,
|
|
87
|
+
PdfTextFragment,
|
|
88
|
+
PdfToolCommand,
|
|
89
|
+
PdfToolCommandResult,
|
|
90
|
+
PdfToolPaths,
|
|
91
|
+
PdfToolRunner,
|
|
92
|
+
} from "./pdf/model.js";
|