@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.
Files changed (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,303 @@
1
+ import { createHash } from "node:crypto";
2
+ import {
3
+ chmodSync,
4
+ closeSync,
5
+ createReadStream,
6
+ existsSync,
7
+ lstatSync,
8
+ mkdirSync,
9
+ openSync,
10
+ readFileSync,
11
+ readSync,
12
+ realpathSync,
13
+ readdirSync,
14
+ statSync,
15
+ } from "node:fs";
16
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
17
+
18
+ import { sniffImage } from "../clip/assets.js";
19
+ import {
20
+ parsePdfInfo,
21
+ parsePopplerXml,
22
+ type ParsedPdfImage,
23
+ } from "./layout.js";
24
+ import type {
25
+ PdfCaptureDependencies,
26
+ PdfImageCandidate,
27
+ PdfInspectOptions,
28
+ PdfInspection,
29
+ PdfPageLayout,
30
+ } from "./model.js";
31
+ import { resolvePdfTools, runPdfToolCommand } from "./tools.js";
32
+
33
+ export const pdfCaptureDefaults = {
34
+ timeoutMs: 120_000,
35
+ maxPdfBytes: 512 * 1024 * 1024,
36
+ maxPages: 500,
37
+ maxImages: 1_000,
38
+ maxAssetBytes: 100 * 1024 * 1024,
39
+ maxTotalAssetBytes: 512 * 1024 * 1024,
40
+ maxLayoutBytes: 128 * 1024 * 1024,
41
+ } as const;
42
+
43
+ function positiveBound(value: number | undefined, fallback: number, maximum: number, label: string): number {
44
+ const selected = value ?? fallback;
45
+ if (!Number.isSafeInteger(selected) || selected < 1 || selected > maximum) {
46
+ throw new Error(`${label} must be an integer from 1 through ${maximum}`);
47
+ }
48
+ return selected;
49
+ }
50
+
51
+ function pathInside(root: string, target: string): boolean {
52
+ const child = relative(root, target);
53
+ return child !== ""
54
+ && !isAbsolute(child)
55
+ && child !== ".."
56
+ && !child.startsWith(`..${sep}`);
57
+ }
58
+
59
+ function prepareWorkspace(path: string): string {
60
+ const absolute = resolve(path);
61
+ if (existsSync(absolute)) {
62
+ const stats = lstatSync(absolute);
63
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
64
+ throw new Error("PDF inspection workspace must be a real directory");
65
+ }
66
+ if (readdirSync(absolute).length !== 0) {
67
+ throw new Error("PDF inspection workspace must be empty");
68
+ }
69
+ } else {
70
+ mkdirSync(absolute, { recursive: false, mode: 0o700 });
71
+ }
72
+ chmodSync(absolute, 0o700);
73
+ return realpathSync(absolute);
74
+ }
75
+
76
+ function sourceIdentity(path: string, maxPdfBytes: number): {
77
+ readonly inputPath: string;
78
+ readonly originalFilename: string;
79
+ readonly bytes: number;
80
+ } {
81
+ const originalFilename = basename(path);
82
+ const canonical = realpathSync(resolve(path));
83
+ const stats = statSync(canonical);
84
+ if (!stats.isFile()) throw new Error("PDF input must be a regular file");
85
+ if (stats.size < 5) throw new Error("PDF input is too small to contain a PDF header");
86
+ if (stats.size > maxPdfBytes) throw new Error(`PDF input exceeds the ${maxPdfBytes}-byte limit`);
87
+ const descriptor = openSync(canonical, "r");
88
+ try {
89
+ const signature = Buffer.alloc(5);
90
+ const count = readSync(descriptor, signature, 0, signature.length, 0);
91
+ if (count !== signature.length || signature.toString("ascii") !== "%PDF-") {
92
+ throw new Error("PDF input does not have a valid PDF signature");
93
+ }
94
+ } finally {
95
+ closeSync(descriptor);
96
+ }
97
+ return { inputPath: canonical, originalFilename, bytes: stats.size };
98
+ }
99
+
100
+ async function sha256File(path: string): Promise<string> {
101
+ const digest = createHash("sha256");
102
+ for await (const chunk of createReadStream(path)) digest.update(chunk as Buffer);
103
+ return digest.digest("hex");
104
+ }
105
+
106
+ function safeGeneratedImage(
107
+ image: ParsedPdfImage,
108
+ workspace: string,
109
+ index: number,
110
+ limits: {
111
+ readonly maxAssetBytes: number;
112
+ readonly remainingBytes: number;
113
+ },
114
+ ): PdfImageCandidate | { readonly warning: string } {
115
+ const candidate = resolve(workspace, image.sourcePath);
116
+ if (!pathInside(workspace, candidate)) {
117
+ return { warning: `Skipped an image on page ${image.page} whose generated path escaped the workspace.` };
118
+ }
119
+ let canonical: string;
120
+ try {
121
+ canonical = realpathSync(candidate);
122
+ } catch {
123
+ return { warning: `Skipped a missing generated image on page ${image.page}.` };
124
+ }
125
+ if (!pathInside(workspace, canonical)) {
126
+ return { warning: `Skipped an image on page ${image.page} whose canonical path escaped the workspace.` };
127
+ }
128
+ const stats = lstatSync(canonical);
129
+ if (!stats.isFile() || stats.isSymbolicLink()) {
130
+ return { warning: `Skipped a non-file generated image on page ${image.page}.` };
131
+ }
132
+ if (stats.size > limits.maxAssetBytes || stats.size > limits.remainingBytes) {
133
+ return { warning: `Skipped an image on page ${image.page} because the configured asset byte limit was reached.` };
134
+ }
135
+ const bytes = readFileSync(canonical);
136
+ const sniffed = sniffImage(bytes);
137
+ if (sniffed === null) {
138
+ return { warning: `Skipped an unsupported generated image on page ${image.page}.` };
139
+ }
140
+ const sha256 = createHash("sha256").update(bytes).digest("hex");
141
+ return {
142
+ id: `page-${image.page}-image-${index + 1}-${sha256.slice(0, 12)}`,
143
+ page: image.page,
144
+ sourcePath: canonical,
145
+ top: image.top,
146
+ left: image.left,
147
+ width: image.width,
148
+ height: image.height,
149
+ bytes: stats.size,
150
+ sha256,
151
+ mimeType: sniffed.mimeType,
152
+ };
153
+ }
154
+
155
+ function generatedTreeSize(workspace: string, maximumFiles: number, maximumBytes: number): void {
156
+ const entries = readdirSync(workspace, { withFileTypes: true });
157
+ if (entries.length > maximumFiles) {
158
+ throw new Error(`PDF extraction created more than ${maximumFiles} files`);
159
+ }
160
+ let totalBytes = 0;
161
+ for (const entry of entries) {
162
+ const path = join(workspace, entry.name);
163
+ const stats = lstatSync(path);
164
+ if (!entry.isFile() || stats.isSymbolicLink()) {
165
+ throw new Error("PDF extraction created an unexpected non-file output");
166
+ }
167
+ totalBytes += stats.size;
168
+ if (totalBytes > maximumBytes) {
169
+ throw new Error(`PDF extraction exceeded the ${maximumBytes}-byte workspace limit`);
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Inspect a PDF into a caller-owned workspace. The returned image paths remain
176
+ * valid until that workspace is removed.
177
+ */
178
+ export async function inspectPdf(
179
+ options: PdfInspectOptions,
180
+ dependencies: PdfCaptureDependencies = {},
181
+ ): Promise<PdfInspection> {
182
+ const timeoutMs = positiveBound(options.timeoutMs, pdfCaptureDefaults.timeoutMs, 10 * 60_000, "timeoutMs");
183
+ const maxPdfBytes = positiveBound(options.maxPdfBytes, pdfCaptureDefaults.maxPdfBytes, 8 * 1024 ** 3, "maxPdfBytes");
184
+ const maxPages = positiveBound(options.maxPages, pdfCaptureDefaults.maxPages, 10_000, "maxPages");
185
+ const maxImages = positiveBound(options.maxImages, pdfCaptureDefaults.maxImages, 10_000, "maxImages");
186
+ const maxAssetBytes = positiveBound(
187
+ options.maxAssetBytes,
188
+ pdfCaptureDefaults.maxAssetBytes,
189
+ 2 * 1024 ** 3,
190
+ "maxAssetBytes",
191
+ );
192
+ const maxTotalAssetBytes = positiveBound(
193
+ options.maxTotalAssetBytes,
194
+ pdfCaptureDefaults.maxTotalAssetBytes,
195
+ 8 * 1024 ** 3,
196
+ "maxTotalAssetBytes",
197
+ );
198
+ const source = sourceIdentity(options.inputPath, maxPdfBytes);
199
+ const sourceSha256 = await sha256File(source.inputPath);
200
+ const workspaceDirectory = prepareWorkspace(options.workspaceDirectory);
201
+ const tools = resolvePdfTools(dependencies);
202
+ const runTool = dependencies.runTool ?? runPdfToolCommand;
203
+
204
+ const info = await runTool({
205
+ command: [tools.pdfinfo, source.inputPath],
206
+ timeoutMs,
207
+ maxOutputBytes: 2 * 1024 * 1024,
208
+ cwd: workspaceDirectory,
209
+ });
210
+ if (info.exitCode !== 0) {
211
+ throw new Error("pdfinfo could not inspect the input PDF");
212
+ }
213
+ const metadata = parsePdfInfo(info.stdout);
214
+ const processedPageLimit = Math.min(metadata.pageCount, maxPages);
215
+ const layoutPath = join(workspaceDirectory, "layout.xml");
216
+ const extracted = await runTool({
217
+ command: [
218
+ tools.pdftohtml,
219
+ "-q",
220
+ "-f",
221
+ "1",
222
+ "-l",
223
+ String(processedPageLimit),
224
+ "-xml",
225
+ "-hidden",
226
+ "-fmt",
227
+ "png",
228
+ source.inputPath,
229
+ layoutPath,
230
+ ],
231
+ timeoutMs,
232
+ maxOutputBytes: 2 * 1024 * 1024,
233
+ cwd: workspaceDirectory,
234
+ });
235
+ if (extracted.exitCode !== 0) {
236
+ throw new Error("pdftohtml could not extract the input PDF");
237
+ }
238
+ generatedTreeSize(
239
+ workspaceDirectory,
240
+ 10_002,
241
+ Math.min(
242
+ Number.MAX_SAFE_INTEGER,
243
+ 8 * 1024 ** 3 + pdfCaptureDefaults.maxLayoutBytes,
244
+ ),
245
+ );
246
+ const layoutStats = statSync(layoutPath);
247
+ if (!layoutStats.isFile() || layoutStats.size > pdfCaptureDefaults.maxLayoutBytes) {
248
+ throw new Error(`Poppler layout XML exceeds the ${pdfCaptureDefaults.maxLayoutBytes}-byte limit`);
249
+ }
250
+ const parsed = parsePopplerXml(readFileSync(layoutPath, "utf8"), {
251
+ maxPages: processedPageLimit,
252
+ maxImages,
253
+ });
254
+ const warnings: string[] = [];
255
+ if (metadata.pageCount > processedPageLimit) {
256
+ warnings.push(`PDF extraction stopped at ${processedPageLimit} of ${metadata.pageCount} pages.`);
257
+ }
258
+ if (parsed.truncated) {
259
+ warnings.push("PDF layout extraction reached a configured page, image, or text-fragment limit.");
260
+ }
261
+ if (parsed.pages.length < processedPageLimit) {
262
+ warnings.push(`Poppler returned ${parsed.pages.length} of ${processedPageLimit} requested pages.`);
263
+ }
264
+
265
+ let remainingBytes = maxTotalAssetBytes;
266
+ const pages: PdfPageLayout[] = parsed.pages.map((page) => {
267
+ const images: PdfImageCandidate[] = [];
268
+ const sorted = [...page.images].sort((left, right) =>
269
+ left.top - right.top || left.left - right.left || left.sourcePath.localeCompare(right.sourcePath));
270
+ for (const [index, rawImage] of sorted.entries()) {
271
+ const image = safeGeneratedImage(rawImage, workspaceDirectory, index, {
272
+ maxAssetBytes,
273
+ remainingBytes,
274
+ });
275
+ if ("warning" in image) {
276
+ warnings.push(image.warning);
277
+ continue;
278
+ }
279
+ images.push(image);
280
+ remainingBytes -= image.bytes;
281
+ }
282
+ return {
283
+ page: page.page,
284
+ width: page.width,
285
+ height: page.height,
286
+ text: page.text,
287
+ images,
288
+ };
289
+ });
290
+
291
+ return {
292
+ inputPath: source.inputPath,
293
+ originalFilename: source.originalFilename,
294
+ sourceBytes: source.bytes,
295
+ sourceSha256,
296
+ metadata,
297
+ processedPages: pages.length,
298
+ pages,
299
+ popplerVersion: parsed.popplerVersion,
300
+ warnings: [...new Set(warnings)],
301
+ workspaceDirectory,
302
+ };
303
+ }