@hraness/wordcell 0.26.3 → 0.27.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.
Files changed (77) hide show
  1. package/README.md +36 -21
  2. package/dist/capture.js +15 -7
  3. package/dist/cli-bin.js +2 -2
  4. package/dist/cli.js +21 -19
  5. package/dist/clip/acquire.js +4 -3
  6. package/dist/clip/bundle-reader.js +3 -2
  7. package/dist/clip/cli.js +8 -7
  8. package/dist/clip/defuddle-worker.js +4 -1
  9. package/dist/clip/doctor.js +5 -4
  10. package/dist/clip/jobs.js +2 -1
  11. package/dist/clip/persist.js +2 -1
  12. package/dist/clip/refresh.js +4 -3
  13. package/dist/evaluation-builder-bin.js +2 -2
  14. package/dist/evaluation-builder.js +3 -3
  15. package/dist/evaluation-kb.js +3 -3
  16. package/dist/{index-mp5y3ty3.js → index-30v9w4cy.js} +36 -20
  17. package/dist/{index-9paxge84.js → index-3hts6yy9.js} +1 -1
  18. package/dist/{index-h9vyd03z.js → index-58azscg5.js} +7 -6
  19. package/dist/{index-9zkba8hr.js → index-7bqwzjba.js} +7 -6
  20. package/dist/{index-j4zgmzjr.js → index-8djdwjjd.js} +1 -1
  21. package/dist/{index-5h5awwh4.js → index-b40e2dkx.js} +2 -2
  22. package/dist/index-e2wstc7t.js +323 -0
  23. package/dist/{index-hacpnaew.js → index-fd8j527z.js} +1 -1
  24. package/dist/{index-mxxxytys.js → index-g63d3937.js} +4 -0
  25. package/dist/{index-k86wepd8.js → index-h2w7syy2.js} +5 -1
  26. package/dist/{index-9fywek7g.js → index-ht14xne3.js} +1 -1
  27. package/dist/{index-82r6g4py.js → index-k9yzc4vn.js} +1 -1
  28. package/dist/{index-m9p2a4mj.js → index-my50xsa3.js} +221 -36
  29. package/dist/{index-ncwzmsge.js → index-nwdzhj1h.js} +2 -2
  30. package/dist/{index-9rf81m0p.js → index-pbz3qw1s.js} +35 -20
  31. package/dist/index-pn14a2m5.js +59 -0
  32. package/dist/{index-nd8v7r0z.js → index-sp43012q.js} +1 -1
  33. package/dist/{index-8pabwzqg.js → index-t4hjm1kk.js} +2 -1
  34. package/dist/{index-npg9z1a4.js → index-xn5q4kne.js} +5 -1
  35. package/dist/{index-j2jt49gr.js → index-yatn62qq.js} +1 -1
  36. package/dist/index.js +14 -4
  37. package/dist/pdf.js +438 -4
  38. package/dist/portfolio.js +3 -3
  39. package/dist/rerank-clef.js +16 -0
  40. package/dist/rerank-typesafe.js +3 -1
  41. package/dist/sdk.js +2 -2
  42. package/dist/{support-9qdr4hb8.js → support-s059eytv.js} +20 -18
  43. package/dist/url-intelligence.js +4 -3
  44. package/dist/workflows/decision-context.js +3 -3
  45. package/dist/workflows/index.js +3 -3
  46. package/package.json +23 -9
  47. package/skills/wordcell/SKILL.md +1 -1
  48. package/skills/wordcell/references/query.md +11 -6
  49. package/src/capture.ts +5 -0
  50. package/src/cli-help.ts +2 -1
  51. package/src/cli-program.ts +23 -8
  52. package/src/clip/bundle-reader.ts +3 -0
  53. package/src/clip/capture.ts +1 -0
  54. package/src/clip/extract.ts +2 -0
  55. package/src/clip/lib.ts +3 -0
  56. package/src/clip/metadata-search-tool/Cargo.lock +6 -6
  57. package/src/clip/metadata.ts +232 -0
  58. package/src/clip/persist.ts +3 -0
  59. package/src/index.ts +2 -1
  60. package/src/pdf/bundle-reader.ts +457 -0
  61. package/src/pdf/capture.ts +6 -2
  62. package/src/pdf/cli.ts +2 -1
  63. package/src/pdf/errors.ts +11 -0
  64. package/src/pdf/extract.ts +16 -13
  65. package/src/pdf/layout.ts +136 -3
  66. package/src/pdf/markdown.ts +7 -1
  67. package/src/pdf/model.ts +9 -0
  68. package/src/pdf/persist.ts +8 -2
  69. package/src/pdf/source.ts +5 -4
  70. package/src/pdf/tools.ts +8 -6
  71. package/src/pdf.ts +10 -0
  72. package/src/rerank-clef.ts +52 -0
  73. package/src/rerank-credentials.ts +11 -2
  74. package/src/rerank-typesafe.ts +44 -20
  75. package/src/rerank.ts +1 -1
  76. package/src/sdk.ts +4 -2
  77. /package/dist/{index-cd75vky9.js → index-jbtyp6q5.js} +0 -0
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  acquireArchiveTodaySnapshot,
4
4
  discoverArchiveTodaySnapshot
5
- } from "./index-hacpnaew.js";
5
+ } from "./index-fd8j527z.js";
6
6
  import {
7
7
  canonicalizeUrl,
8
8
  chooseBestExtraction,
@@ -12,13 +12,13 @@ import {
12
12
  localizeAssets,
13
13
  scoreExtraction,
14
14
  sniffImage
15
- } from "./index-k86wepd8.js";
15
+ } from "./index-h2w7syy2.js";
16
16
  import {
17
17
  adapterCapabilities,
18
18
  inspectClipEnvironment,
19
19
  renderAdapterCapabilities,
20
20
  renderDoctorReport
21
- } from "./index-nd8v7r0z.js";
21
+ } from "./index-sp43012q.js";
22
22
  import {
23
23
  cloneBrowserProfile
24
24
  } from "./index-hnvx7m1j.js";
@@ -34,7 +34,7 @@ import {
34
34
  renderCookiePermissionNotice,
35
35
  renderCookiePermissionRecovery,
36
36
  setCookiePermissionReporter
37
- } from "./index-ncwzmsge.js";
37
+ } from "./index-nwdzhj1h.js";
38
38
  import {
39
39
  CONTENT_REWRITE_TRUNCATION_WARNING,
40
40
  buildClipMarkdown,
@@ -51,7 +51,7 @@ import {
51
51
  stderrStyle,
52
52
  terminalOutput,
53
53
  writesToTerminal
54
- } from "./index-cd75vky9.js";
54
+ } from "./index-jbtyp6q5.js";
55
55
  import {
56
56
  MAX_COOKIE_BYTES,
57
57
  filterCookieProviderResult,
@@ -68,7 +68,7 @@ import {
68
68
  redactSensitiveTextWithCount,
69
69
  sanitizeArtifactUrl,
70
70
  writeCaptureBundle
71
- } from "./index-mxxxytys.js";
71
+ } from "./index-g63d3937.js";
72
72
  import {
73
73
  startNetworkProxy
74
74
  } from "./index-w2zc0vwa.js";
@@ -2285,6 +2285,7 @@ async function runCapture(rawOptions, dependencies = {}) {
2285
2285
  const finalWarnings = finalizedWarnings(combinedWarnings, redactedMarkdown.count);
2286
2286
  const includeSource = options.evidence === "source" || options.evidence === "all";
2287
2287
  const manifestInput = {
2288
+ ...article.metadata === undefined ? {} : { metadata: article.metadata },
2288
2289
  sourceUrl,
2289
2290
  canonicalUrl: best.canonicalUrl.href,
2290
2291
  capturedAt,
@@ -1,4 +1,8 @@
1
1
  // @bun
2
+ import {
3
+ UntrustedContentBudgetError,
4
+ createUntrustedToolResult
5
+ } from "./index-4j3tt0c3.js";
2
6
  import {
3
7
  expandSearchRequest,
4
8
  parseSearchRules,
@@ -8,10 +12,6 @@ import {
8
12
  openSemanticSearchSession,
9
13
  recommendedEmbeddingModel
10
14
  } from "./index-fp732bgg.js";
11
- import {
12
- UntrustedContentBudgetError,
13
- createUntrustedToolResult
14
- } from "./index-4j3tt0c3.js";
15
15
  import {
16
16
  percolateWithGraph
17
17
  } from "./index-wqx1x32f.js";
@@ -529,8 +529,8 @@ function checkedRerankRequest(value, rerankers) {
529
529
  throw new TypeError("Search rerank options must be an options object.");
530
530
  }
531
531
  const options = value;
532
- if (options.engine !== "typesafe") {
533
- throw new Error('Search rerank engine must be "typesafe".');
532
+ if (options.engine !== "clef" && options.engine !== "typesafe") {
533
+ throw new Error('Search rerank engine must be "clef" or legacy "typesafe".');
534
534
  }
535
535
  const reranker = rerankers?.find((entry) => entry.id === options.engine);
536
536
  if (reranker === undefined) {
@@ -856,6 +856,7 @@ async function openKnowledgeBase(options, dependencies = {}) {
856
856
  const rerankMessage = applied.status === "ready" ? details.length === 0 ? undefined : details.join("; ") : applied.message ?? "Rerank engine did not return a result.";
857
857
  diagnostics.push({
858
858
  lane: "rerank",
859
+ engine: rerankRequest.reranker.id,
859
860
  ...Object.keys(resultDetails).length === 0 ? {} : { rerank: resultDetails },
860
861
  status: applied.status === "failed" ? "degraded" : applied.status,
861
862
  results: applied.status === "ready" ? applied.placements.size : 0,
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  readCaptureBundle
4
- } from "./index-npg9z1a4.js";
4
+ } from "./index-xn5q4kne.js";
5
5
  import {
6
6
  runGitCommand
7
7
  } from "./index-1gwbassd.js";
@@ -5,10 +5,10 @@ import {
5
5
  parseArchiveTodayMementoUrl,
6
6
  parseMetadataSearchResponse,
7
7
  rankMetadataSearchResults
8
- } from "./index-hacpnaew.js";
8
+ } from "./index-fd8j527z.js";
9
9
  import {
10
10
  sanitizeArtifactUrl
11
- } from "./index-mxxxytys.js";
11
+ } from "./index-g63d3937.js";
12
12
  import {
13
13
  assertSafeNetworkUrl,
14
14
  isPrivateHostname,
@@ -0,0 +1,323 @@
1
+ // @bun
2
+ // src/clip/metadata.ts
3
+ import { decodeHTML } from "entities";
4
+ var metadataLimits = { htmlBytes: 8 * 1024 * 1024, claims: 256, value: 8192, totalBytes: 256 * 1024, jsonBytes: 128 * 1024, jsonNodes: 2048, jsonDepth: 12 };
5
+ var record = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
6
+ var sources = new Set(["html-title", "html-meta", "html-link", "html-language", "json-ld", "extractor"]);
7
+ var warningKinds = new Set(["input-limit", "claim-limit", "value-limit", "json-limit", "json-invalid"]);
8
+ var credentialName = /(?:csrf|xsrf|token|nonce|password|secret|session|authorization|api[-_]?key|signature)/i;
9
+ var urlFields = new Set(["canonical", "alternate", "image", "url"]);
10
+ var dateFields = new Set(["published", "modified"]);
11
+ function publicUrl(value, base) {
12
+ try {
13
+ const url = new URL(value, base);
14
+ if (!/^https?:$/.test(url.protocol))
15
+ return null;
16
+ url.username = "";
17
+ url.password = "";
18
+ url.search = "";
19
+ url.hash = "";
20
+ return url.href;
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ function cleanText(value) {
26
+ return value.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f-\u009f]/gu, "").replace(/https?:\/\/[^\s<>"']+/giu, (url) => publicUrl(url) ?? "[invalid URL]").replace(/\s+/gu, " ").trim();
27
+ }
28
+ function dateValue(value) {
29
+ const dateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value);
30
+ const zoned = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:\d{2})$/i.test(value);
31
+ const local = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?$/.test(value);
32
+ const parsed = Date.parse(dateOnly ? value + "T00:00:00Z" : local ? value + "Z" : value);
33
+ const validDay = Number.isFinite(parsed) && new Date(Date.UTC(Number(value.slice(0, 4)), Number(value.slice(5, 7)) - 1, Number(value.slice(8, 10)))).toISOString().slice(0, 10) === value.slice(0, 10);
34
+ if (!dateOnly && !zoned && !local || !validDay)
35
+ return { value, datePrecision: "invalid", timezone: "unknown" };
36
+ if (dateOnly)
37
+ return { value, datePrecision: "date", timezone: "not-applicable" };
38
+ return { value: zoned ? new Date(parsed).toISOString() : value, datePrecision: "datetime", timezone: zoned ? "explicit" : "unknown" };
39
+ }
40
+ function rank(claim) {
41
+ if (claim.datePrecision === "invalid")
42
+ return 1000;
43
+ if (claim.source === "json-ld")
44
+ return claim.rule.includes("jsonld-type=WebPage") ? 15 : 10;
45
+ if (claim.source === "html-link")
46
+ return 5;
47
+ if (claim.locator.includes("property=article:"))
48
+ return 20;
49
+ if (claim.locator.includes("property=og:"))
50
+ return 30;
51
+ if (claim.locator.includes("name=twitter:"))
52
+ return 40;
53
+ if (claim.source === "html-meta")
54
+ return 50;
55
+ if (claim.source === "html-title" || claim.source === "html-language")
56
+ return 60;
57
+ return 90;
58
+ }
59
+ function select(claims) {
60
+ const ranks = new Map;
61
+ for (const claim of claims)
62
+ ranks.set(claim.field, Math.min(ranks.get(claim.field) ?? 1000, rank(claim)));
63
+ const chosen = new Set;
64
+ return claims.map((claim) => {
65
+ const multiple = ["authors", "tags", "alternate"].includes(claim.field);
66
+ const selected = rank(claim) < 1000 && rank(claim) === ranks.get(claim.field) && (multiple || !chosen.has(claim.field));
67
+ if (selected)
68
+ chosen.add(claim.field);
69
+ return { ...claim, selected, rule: `metadata-precedence-v1:${claim.rule}` };
70
+ });
71
+ }
72
+ function parseArticleMetadata(input, redact = cleanText) {
73
+ if (!record(input) || input.schemaVersion !== 1 || input.extractorVersion !== "wordcell-metadata-v1" || !Array.isArray(input.claims) || input.claims.length > metadataLimits.claims || !Array.isArray(input.warnings))
74
+ throw new Error("Unsupported or malformed article metadata envelope");
75
+ let bytes = 0;
76
+ const text = (value2, limit = metadataLimits.value) => {
77
+ if (typeof value2 !== "string" || value2.length > limit)
78
+ throw new Error("Invalid article metadata text");
79
+ const result = cleanText(redact(value2));
80
+ bytes += Buffer.byteLength(result);
81
+ if (bytes > metadataLimits.totalBytes)
82
+ throw new Error("Article metadata exceeds byte limit");
83
+ return result;
84
+ };
85
+ const value = (v) => Array.isArray(v) ? v.length <= 32 ? v.map((part) => text(part)) : (() => {
86
+ throw new Error("Article metadata list exceeds limit");
87
+ })() : text(v);
88
+ const claims = input.claims.map((raw) => {
89
+ if (!record(raw) || !sources.has(raw.source) || typeof raw.selected !== "boolean")
90
+ throw new Error("Invalid article metadata claim");
91
+ const field = text(raw.field, 128);
92
+ if (!/^[a-z][a-z0-9:_-]{0,127}$/.test(field) || credentialName.test(field))
93
+ throw new Error("Invalid article metadata field");
94
+ let observed = value(raw.observed), normalized = value(raw.value);
95
+ if (urlFields.has(field)) {
96
+ const urls = (input2) => {
97
+ const normalize = (part) => {
98
+ const parsed = publicUrl(part);
99
+ if (parsed === null)
100
+ throw new Error("Invalid article metadata URL");
101
+ return parsed;
102
+ };
103
+ return typeof input2 === "string" ? normalize(input2) : input2.map(normalize);
104
+ };
105
+ observed = urls(observed);
106
+ normalized = urls(normalized);
107
+ }
108
+ const temporal = raw.datePrecision === undefined ? {} : dateFields.has(field) && typeof observed === "string" ? dateValue(observed) : (() => {
109
+ throw new Error("Invalid article metadata date");
110
+ })();
111
+ if (raw.datePrecision !== undefined && (raw.datePrecision !== temporal.datePrecision || raw.timezone !== temporal.timezone))
112
+ throw new Error("Invalid article metadata date precision");
113
+ return { field, observed, value: normalized, source: raw.source, locator: text(raw.locator, 512), rule: text(raw.rule, 256), selected: raw.selected, ...temporal };
114
+ });
115
+ if (input.warnings.length > warningKinds.size || input.warnings.some((warning) => !warningKinds.has(warning)))
116
+ throw new Error("Invalid article metadata warnings");
117
+ return { schemaVersion: 1, extractorVersion: "wordcell-metadata-v1", claims, warnings: [...input.warnings] };
118
+ }
119
+ var fields = {
120
+ "og:title": "title",
121
+ "twitter:title": "title",
122
+ title: "title",
123
+ citation_title: "title",
124
+ "dc.title": "title",
125
+ description: "description",
126
+ "og:description": "description",
127
+ "twitter:description": "description",
128
+ "dc.description": "description",
129
+ author: "authors",
130
+ "article:author": "authors",
131
+ citation_author: "authors",
132
+ "dc.creator": "authors",
133
+ "article:published_time": "published",
134
+ datepublished: "published",
135
+ citation_publication_date: "published",
136
+ "dc.date": "published",
137
+ "article:modified_time": "modified",
138
+ datemodified: "modified",
139
+ "last-modified": "modified",
140
+ "og:url": "canonical",
141
+ "twitter:url": "canonical",
142
+ "og:image": "image",
143
+ "og:image:url": "image",
144
+ "twitter:image": "image",
145
+ "twitter:image:src": "image",
146
+ "og:site_name": "site",
147
+ publisher: "publisher",
148
+ "dc.publisher": "publisher",
149
+ citation_journal_title: "publisher",
150
+ "og:locale": "language",
151
+ language: "language",
152
+ "dc.language": "language",
153
+ "og:type": "type",
154
+ "twitter:card": "card",
155
+ "article:tag": "tags",
156
+ keywords: "tags",
157
+ news_keywords: "tags",
158
+ citation_keywords: "tags",
159
+ citation_doi: "doi",
160
+ "dc.identifier": "identifier",
161
+ citation_pdf_url: "alternate"
162
+ };
163
+ async function extractArticleMetadata(html, base, fallback = {}) {
164
+ const claims = [];
165
+ const warnings = new Set;
166
+ let totalBytes = 0, title = "", script = "", scriptIndex = 0, activeScript = false, jsonBytes = 0;
167
+ const add = (field, raw, source, locator, decode = false, evidenceRule) => {
168
+ if (claims.length >= metadataLimits.claims) {
169
+ warnings.add("claim-limit");
170
+ return;
171
+ }
172
+ let values = Array.isArray(raw) ? raw : [raw];
173
+ if (values.length > 32) {
174
+ warnings.add("value-limit");
175
+ values = values.slice(0, 32);
176
+ }
177
+ const strings = [];
178
+ for (const value2 of values) {
179
+ if (typeof value2 !== "string")
180
+ continue;
181
+ if (value2.length > metadataLimits.value) {
182
+ warnings.add("value-limit");
183
+ continue;
184
+ }
185
+ const decoded = decode ? decodeHTML(value2) : value2;
186
+ const cleaned = cleanText(urlFields.has(field) ? publicUrl(decoded, base) ?? "" : decoded);
187
+ if (cleaned)
188
+ strings.push(cleaned);
189
+ }
190
+ if (!strings.length)
191
+ return;
192
+ const observed = Array.isArray(raw) ? strings : strings[0];
193
+ let value = observed;
194
+ if (urlFields.has(field)) {
195
+ const urls = strings.map((part) => publicUrl(part, base)).filter((part) => part !== null);
196
+ if (!urls.length)
197
+ return;
198
+ value = Array.isArray(raw) ? urls : urls[0];
199
+ }
200
+ const temporal = dateFields.has(field) && typeof observed === "string" ? dateValue(observed) : {};
201
+ const claim = { field, observed, value, source, locator, rule: `trim-whitespace-url-query-redaction${evidenceRule === undefined ? "" : `:${evidenceRule}`}`, selected: false, ...temporal };
202
+ const bytes = Buffer.byteLength(JSON.stringify(claim));
203
+ if (totalBytes + bytes > metadataLimits.totalBytes / 2) {
204
+ warnings.add("claim-limit");
205
+ return;
206
+ }
207
+ totalBytes += bytes;
208
+ claims.push(claim);
209
+ };
210
+ const parseLd = (raw, index) => {
211
+ if (!raw.trim())
212
+ return;
213
+ if (Buffer.byteLength(raw) > metadataLimits.jsonBytes || (jsonBytes += Buffer.byteLength(raw)) > metadataLimits.jsonBytes * 4) {
214
+ warnings.add("json-limit");
215
+ return;
216
+ }
217
+ let parsed;
218
+ try {
219
+ parsed = JSON.parse(raw);
220
+ } catch {
221
+ warnings.add("json-invalid");
222
+ return;
223
+ }
224
+ let nodes = 0;
225
+ const stack = [{ value: parsed, depth: 0 }];
226
+ while (stack.length) {
227
+ const node = stack.pop();
228
+ if (++nodes > metadataLimits.jsonNodes || node.depth > metadataLimits.jsonDepth) {
229
+ warnings.add("json-limit");
230
+ return;
231
+ }
232
+ const children = Array.isArray(node.value) ? node.value : record(node.value) ? Object.values(node.value) : [];
233
+ if (children.length + nodes + stack.length > metadataLimits.jsonNodes) {
234
+ warnings.add("json-limit");
235
+ return;
236
+ }
237
+ for (const child of children)
238
+ stack.push({ value: child, depth: node.depth + 1 });
239
+ }
240
+ const items = Array.isArray(parsed) ? parsed.map((value, i) => ({ value, path: `$[${i}]` })) : [{ value: parsed, path: "$" }];
241
+ for (let cursor = 0;cursor < items.length && cursor < metadataLimits.jsonNodes; cursor++) {
242
+ const item = items[cursor];
243
+ if (!record(item.value))
244
+ continue;
245
+ const object = item.value;
246
+ if (Array.isArray(object["@graph"]))
247
+ items.push(...object["@graph"].map((value, i) => ({ value, path: `${item.path}.@graph[${i}]` })));
248
+ const types = Array.isArray(object["@type"]) ? object["@type"] : [object["@type"]];
249
+ const entityTypes = types.flatMap((type) => {
250
+ const match = typeof type === "string" ? /^(?:(?:https?:\/\/)?schema\.org\/)?(Article|NewsArticle|BlogPosting|ScholarlyArticle|TechArticle|Report|WebPage|VideoObject|AudioObject|PodcastEpisode)$/.exec(type) : null;
251
+ return match?.[1] === undefined ? [] : [match[1]];
252
+ });
253
+ const entityType = entityTypes.find((type) => type !== "WebPage") ?? entityTypes[0];
254
+ if (entityType === undefined)
255
+ continue;
256
+ const names = (v) => Array.isArray(v) ? v.slice(0, 32).map((entry) => record(entry) ? entry.name : entry) : record(v) ? v.name : v;
257
+ const props = { headline: "title", name: "title", description: "description", author: "authors", creator: "authors", datePublished: "published", dateModified: "modified", inLanguage: "language", publisher: "publisher", keywords: "tags", image: "image", thumbnailUrl: "image", url: "canonical", mainEntityOfPage: "canonical", duration: "duration", episodeNumber: "episode", identifier: "identifier" };
258
+ for (const [key, field] of Object.entries(props)) {
259
+ let value = object[key];
260
+ if (value === undefined)
261
+ continue;
262
+ if (["author", "creator", "publisher"].includes(key))
263
+ value = names(value);
264
+ if (key === "image")
265
+ value = Array.isArray(value) ? value.map((v) => record(v) ? v.url ?? v.contentUrl : v) : record(value) ? value.url ?? value.contentUrl : value;
266
+ if (key === "mainEntityOfPage" && record(value))
267
+ value = value["@id"] ?? value.url;
268
+ if (key === "episodeNumber" && typeof value === "number" && Number.isFinite(value))
269
+ value = String(value);
270
+ add(field, value, "json-ld", `script[${index}]${item.path}.${key}`, false, `jsonld-type=${entityType}`);
271
+ }
272
+ }
273
+ };
274
+ let input = html;
275
+ if (Buffer.byteLength(input) > metadataLimits.htmlBytes) {
276
+ input = Buffer.from(input).subarray(0, metadataLimits.htmlBytes).toString("utf8");
277
+ warnings.add("input-limit");
278
+ }
279
+ await new HTMLRewriter().on("html", { element(element) {
280
+ add("language", element.getAttribute("lang"), "html-language", "html@lang", true);
281
+ } }).on("title", { text(chunk) {
282
+ if (title.length <= metadataLimits.value)
283
+ title += chunk.text;
284
+ } }).on("meta", { element(element) {
285
+ const attribute = element.getAttribute("property") ? "property" : "name";
286
+ const key = decodeHTML(element.getAttribute(attribute) ?? "").toLowerCase();
287
+ if (!/^[a-z][a-z0-9:._-]{0,100}$/.test(key) || credentialName.test(key))
288
+ return;
289
+ const field = Object.hasOwn(fields, key) ? fields[key] : `meta:${key.replaceAll(".", "_")}`;
290
+ add(field, element.getAttribute("content"), "html-meta", `meta[${attribute}=${key}]@content`, true);
291
+ } }).on("link", { element(element) {
292
+ const rel = (element.getAttribute("rel") ?? "").toLowerCase().split(/\s+/);
293
+ const field = rel.includes("canonical") ? "canonical" : rel.includes("alternate") ? "alternate" : null;
294
+ if (field)
295
+ add(field, element.getAttribute("href"), "html-link", `link[rel=${field}]@href`, true);
296
+ } }).on("script", { element(element) {
297
+ activeScript = (element.getAttribute("type") ?? "").toLowerCase().trim() === "application/ld+json";
298
+ script = "";
299
+ if (activeScript) {
300
+ const index = scriptIndex++;
301
+ if (index >= 64) {
302
+ activeScript = false;
303
+ warnings.add("json-limit");
304
+ return;
305
+ }
306
+ element.onEndTag(() => {
307
+ parseLd(script, index);
308
+ activeScript = false;
309
+ });
310
+ }
311
+ }, text(chunk) {
312
+ if (activeScript && script.length <= metadataLimits.jsonBytes)
313
+ script += chunk.text;
314
+ } }).transform(new Response(input)).text();
315
+ if (activeScript)
316
+ warnings.add("json-invalid");
317
+ add("title", title, "html-title", "title", true);
318
+ for (const [key, field] of Object.entries({ title: "title", author: "authors", published: "published", description: "description" }))
319
+ add(field, fallback[key], "extractor", `defuddle.${key}`);
320
+ return parseArticleMetadata({ schemaVersion: 1, extractorVersion: "wordcell-metadata-v1", claims: select(claims), warnings: [...warnings] });
321
+ }
322
+
323
+ export { parseArticleMetadata, extractArticleMetadata };
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  sanitizeArtifactUrl
4
- } from "./index-mxxxytys.js";
4
+ } from "./index-g63d3937.js";
5
5
  import {
6
6
  assertSafeNetworkUrl,
7
7
  decodeBytes,
@@ -1,4 +1,7 @@
1
1
  // @bun
2
+ import {
3
+ parseArticleMetadata
4
+ } from "./index-e2wstc7t.js";
2
5
  import {
3
6
  sanitizeTerminalText
4
7
  } from "./index-1xxnjn0d.js";
@@ -1070,6 +1073,7 @@ function normalizeManifest(input, hasSourceHtml, document) {
1070
1073
  return {
1071
1074
  schemaVersion: CAPTURE_MANIFEST_SCHEMA_VERSION,
1072
1075
  document,
1076
+ ...input.metadata === undefined ? {} : { metadata: parseArticleMetadata(input.metadata, redactSensitiveText) },
1073
1077
  sourceUrl: sanitizeArtifactUrl(input.sourceUrl),
1074
1078
  canonicalUrl: sanitizeArtifactUrl(input.canonicalUrl),
1075
1079
  capturedAt: new Date(capturedAtTime).toISOString(),
@@ -6,7 +6,10 @@ import {
6
6
  resolveRemote,
7
7
  rewriteContentWithStatus,
8
8
  scanImageSources
9
- } from "./index-cd75vky9.js";
9
+ } from "./index-jbtyp6q5.js";
10
+ import {
11
+ parseArticleMetadata
12
+ } from "./index-e2wstc7t.js";
10
13
  import {
11
14
  FetchFailure,
12
15
  safeFetch
@@ -812,6 +815,7 @@ async function extractPage(acquisition, scope, timeoutMs = 30000) {
812
815
  article = {
813
816
  content: retainDefuddleMedia(restoredContent, response, acquisition.finalUrl),
814
817
  title: boundedMetadata(response.title, articleMetadataLimits.title) ?? boundedMetadata(acquisition.browserTitle, articleMetadataLimits.title),
818
+ ...response.metadata === undefined ? {} : { metadata: parseArticleMetadata(response.metadata) },
815
819
  author: boundedMetadata(response.author, articleMetadataLimits.author),
816
820
  published: boundedMetadata(response.published, articleMetadataLimits.published),
817
821
  description
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-h170byqw.js";
5
5
  import {
6
6
  packUntrustedSearchContext
7
- } from "./index-9zkba8hr.js";
7
+ } from "./index-7bqwzjba.js";
8
8
 
9
9
  // src/workflows/decision-context.ts
10
10
  var decisionContextWorkflow = defineWorkflow({
@@ -4,7 +4,7 @@ import {
4
4
  } from "./index-3t0v457d.js";
5
5
  import {
6
6
  parseCliStartupCommand
7
- } from "./index-8pabwzqg.js";
7
+ } from "./index-t4hjm1kk.js";
8
8
  import {
9
9
  findKbPackageRoot
10
10
  } from "./index-4knsp9qj.js";