webforai 1.5.1 → 1.6.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 (79) hide show
  1. package/dist/bin.js +258 -278
  2. package/dist/cjs/extract-hast/index.js +4 -3
  3. package/dist/cjs/extract-hast/readability.js +28 -61
  4. package/dist/cjs/extract-hast/strongly.js +2 -1
  5. package/dist/cjs/extract-hast/utils.js +0 -15
  6. package/dist/cjs/html-to-mdast.js +6 -6
  7. package/dist/cjs/link-replacer.js +10 -5
  8. package/dist/cjs/loaders/playwright.js +36 -7
  9. package/dist/cjs/{cli/constants.js → mdast-handlers/custom-br-handler.js} +9 -12
  10. package/dist/cjs/mdast-to-markdown.js +1 -1
  11. package/dist/cjs/utils/hast-utils.js +71 -0
  12. package/dist/extract-hast/index.js +4 -3
  13. package/dist/extract-hast/readability.js +29 -62
  14. package/dist/extract-hast/strongly.js +2 -1
  15. package/dist/extract-hast/utils.js +0 -14
  16. package/dist/html-to-mdast.js +6 -6
  17. package/dist/link-replacer.js +10 -5
  18. package/dist/loaders/playwright.js +36 -7
  19. package/dist/mdast-handlers/custom-br-handler.js +8 -0
  20. package/dist/mdast-to-markdown.js +1 -1
  21. package/dist/types/cli/bin.js.map +1 -1
  22. package/dist/types/cli/commands/webforai/index.d.ts +6 -0
  23. package/dist/types/cli/commands/webforai/index.js.map +1 -0
  24. package/dist/types/cli/commands/webforai/loadHtml.d.ts +3 -0
  25. package/dist/types/cli/commands/webforai/loadHtml.js.map +1 -0
  26. package/dist/types/extract-hast/index.d.ts +9 -4
  27. package/dist/types/extract-hast/index.js.map +1 -1
  28. package/dist/types/extract-hast/readability.d.ts +2 -1
  29. package/dist/types/extract-hast/readability.js.map +1 -1
  30. package/dist/types/extract-hast/strongly.d.ts +2 -1
  31. package/dist/types/extract-hast/strongly.js.map +1 -1
  32. package/dist/types/extract-hast/utils.d.ts +0 -1
  33. package/dist/types/extract-hast/utils.js.map +1 -1
  34. package/dist/types/html-to-mdast.d.ts +4 -0
  35. package/dist/types/html-to-mdast.js.map +1 -1
  36. package/dist/types/link-replacer.d.ts +1 -1
  37. package/dist/types/link-replacer.js.map +1 -1
  38. package/dist/types/loaders/playwright.d.ts +8 -2
  39. package/dist/types/loaders/playwright.js.map +1 -1
  40. package/dist/types/mdast-handlers/custom-br-handler.d.ts +2 -0
  41. package/dist/types/mdast-handlers/custom-br-handler.js.map +1 -0
  42. package/dist/types/mdast-to-markdown.js.map +1 -1
  43. package/dist/types/utils/hast-utils.d.ts +4 -0
  44. package/dist/types/utils/hast-utils.js.map +1 -0
  45. package/dist/utils/hast-utils.js +46 -0
  46. package/package.json +22 -10
  47. package/dist/cjs/cli/bin.js +0 -34
  48. package/dist/cjs/cli/commands/handlers/index.js +0 -84
  49. package/dist/cjs/cli/commands/handlers/loadHtml.js +0 -127
  50. package/dist/cjs/cli/helpers/assertContinue.js +0 -34
  51. package/dist/cjs/cli/helpers/inputOutputPath.js +0 -89
  52. package/dist/cjs/cli/helpers/inputSourcePath.js +0 -64
  53. package/dist/cjs/cli/helpers/selectExtractMode.js +0 -39
  54. package/dist/cjs/cli/helpers/selectLoader.js +0 -44
  55. package/dist/cjs/cli/utils.js +0 -122
  56. package/dist/cjs/html-to-markdown.test.js +0 -118
  57. package/dist/cjs/link-replacer.test.js +0 -47
  58. package/dist/cjs/loaders/fetch.test.js +0 -9
  59. package/dist/cjs/loaders/playwright.test.js +0 -17
  60. package/dist/cjs/loaders/puppeteer.test.js +0 -17
  61. package/dist/cli/bin.js +0 -12
  62. package/dist/cli/commands/handlers/index.js +0 -51
  63. package/dist/cli/commands/handlers/loadHtml.js +0 -94
  64. package/dist/cli/constants.js +0 -9
  65. package/dist/cli/helpers/assertContinue.js +0 -11
  66. package/dist/cli/helpers/inputOutputPath.js +0 -56
  67. package/dist/cli/helpers/inputSourcePath.js +0 -31
  68. package/dist/cli/helpers/selectExtractMode.js +0 -16
  69. package/dist/cli/helpers/selectLoader.js +0 -21
  70. package/dist/cli/utils.js +0 -85
  71. package/dist/html-to-markdown.test.js +0 -118
  72. package/dist/link-replacer.test.js +0 -47
  73. package/dist/loaders/fetch.test.js +0 -9
  74. package/dist/loaders/playwright.test.js +0 -17
  75. package/dist/loaders/puppeteer.test.js +0 -17
  76. package/dist/types/cli/commands/handlers/index.d.ts +0 -13
  77. package/dist/types/cli/commands/handlers/index.js.map +0 -1
  78. package/dist/types/cli/commands/handlers/loadHtml.d.ts +0 -2
  79. package/dist/types/cli/commands/handlers/loadHtml.js.map +0 -1
package/dist/bin.js CHANGED
@@ -1,64 +1,12 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropNames = Object.getOwnPropertyNames;
3
- var __esm = (fn, res) => function __init() {
4
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
- };
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
-
11
- // src/loaders/fetch.ts
12
- var fetch_exports = {};
13
- __export(fetch_exports, {
14
- USER_AGENT: () => USER_AGENT,
15
- loadHtml: () => loadHtml
16
- });
17
- var USER_AGENT, loadHtml;
18
- var init_fetch = __esm({
19
- "src/loaders/fetch.ts"() {
20
- "use strict";
21
- USER_AGENT = "mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/125.0.0.0 safari/537.36";
22
- loadHtml = async (url, userAgent = USER_AGENT) => {
23
- const response = await fetch(url, { headers: { "User-Agent": userAgent } });
24
- return response.text();
25
- };
26
- }
27
- });
28
-
29
- // src/loaders/playwright.ts
30
- var playwright_exports = {};
31
- __export(playwright_exports, {
32
- loadHtml: () => loadHtml2
33
- });
34
- import { chromium } from "playwright";
35
- var loadHtml2;
36
- var init_playwright = __esm({
37
- "src/loaders/playwright.ts"() {
38
- "use strict";
39
- loadHtml2 = async (url, context) => {
40
- const _context = context ?? await chromium.launch({ headless: true });
41
- const page = await _context.newPage();
42
- await page.goto(url, { waitUntil: "load" });
43
- const html = await page.content();
44
- await page.close();
45
- if (!context) {
46
- await _context.close();
47
- }
48
- return html;
49
- };
50
- }
51
- });
1
+ #!/usr/bin/env node
52
2
 
53
3
  // src/cli/bin.ts
54
- import { intro } from "@clack/prompts";
55
4
  import { program } from "commander";
56
- import pc2 from "picocolors";
57
5
 
58
6
  // package.json
59
7
  var package_default = {
60
8
  name: "webforai",
61
- version: "1.5.1",
9
+ version: "1.6.1",
62
10
  description: "A library that provides a web interface for AI",
63
11
  author: "inaridiy",
64
12
  license: "MIT",
@@ -146,20 +94,31 @@ var package_default = {
146
94
  },
147
95
  peerDependencies: {
148
96
  "@cloudflare/puppeteer": ">=0.0.6",
149
- playwright: ">=1.4",
97
+ "playwright-core": ">=1.4",
150
98
  puppeteer: ">=22"
151
99
  },
100
+ peerDependenciesMeta: {
101
+ "@cloudflare/puppeteer": {
102
+ optional: true
103
+ },
104
+ "playwright-core": {
105
+ optional: false
106
+ },
107
+ puppeteer: {
108
+ optional: true
109
+ }
110
+ },
152
111
  dependencies: {
153
112
  "@clack/prompts": "^0.7.0",
113
+ boxen: "^8.0.1",
154
114
  commander: "^12.1.0",
155
- "fastest-levenshtein": "^1.0.16",
156
- "hast-util-from-html": "^2.0.1",
157
- "hast-util-select": "^6.0.1",
158
- "hast-util-to-html": "^9.0.0",
115
+ "hast-util-from-html": "^2.0.3",
116
+ "hast-util-select": "^6.0.2",
117
+ "hast-util-to-html": "^9.0.3",
159
118
  "hast-util-to-mdast": "^10.1.0",
160
119
  "hast-util-to-string": "^3.0.0",
161
120
  "hast-util-to-text": "^4.0.0",
162
- "mathml-to-latex": "^1.3.0",
121
+ "mathml-to-latex": "^1.4.1",
163
122
  "mdast-util-gfm": "^3.0.0",
164
123
  "mdast-util-math": "^3.0.0",
165
124
  "mdast-util-to-markdown": "^2.1.0",
@@ -173,24 +132,26 @@ var package_default = {
173
132
  "@tsconfig/recommended": "^1.0.3",
174
133
  "@types/hast": "^3.0.2",
175
134
  "@types/mdast": "^4.0.2",
176
- "@types/node": "^20.11.0",
135
+ "@types/node": "^20.14.10",
177
136
  arg: "^5.0.2",
178
137
  esbuild: "^0.19.11",
138
+ "fastest-levenshtein": "^1.0.16",
179
139
  glob: "^10.3.10",
180
140
  ncp: "^2.0.0",
181
141
  np: "^9.2.0",
182
- playwright: "^1.40.1",
142
+ "playwright-core": "^1.40.1",
183
143
  puppeteer: "^23.2.2",
184
144
  rimraf: "^5.0.5",
185
- tsx: "^4.7.0",
145
+ tsx: "^4.19.1",
186
146
  typescript: "^5.4.5"
187
147
  }
188
148
  };
189
149
 
190
- // src/cli/commands/handlers/index.ts
191
- import fs5 from "node:fs";
150
+ // src/cli/commands/webforai/index.ts
151
+ import fs5 from "node:fs/promises";
192
152
  import path2 from "node:path";
193
- import { outro } from "@clack/prompts";
153
+ import { intro, log as log2, outro, spinner } from "@clack/prompts";
154
+ import pc2 from "picocolors";
194
155
 
195
156
  // src/html-to-mdast.ts
196
157
  import { fromHtml } from "hast-util-from-html";
@@ -215,19 +176,6 @@ var isStrInclude = (value, match) => {
215
176
  }
216
177
  return false;
217
178
  };
218
- var hasAncestors = (element, tagNames, depth = 3) => {
219
- let parent = element.parent;
220
- for (let i = 0; i < depth; i++) {
221
- if (!parent || parent.type !== "element") {
222
- return false;
223
- }
224
- if (tagNames.includes(parent.tagName)) {
225
- return true;
226
- }
227
- parent = parent.parent;
228
- }
229
- return false;
230
- };
231
179
 
232
180
  // src/extract-hast/readability.ts
233
181
  var UNLIKELY_ROLES = ["menu", "menubar", "complementary", "navigation", "alert", "alertdialog", "dialog"];
@@ -235,35 +183,24 @@ var REGEXPS = {
235
183
  hidden: /hidden|invisible|fallback-image/i,
236
184
  byline: /byline|author|dateline|writtenby|p-author/i,
237
185
  specialUnlikelyCandidates: /frb-|uls-menu|language-link/i,
238
- unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore/i,
186
+ unlikelyCandidates: /-ad-|ai2html|banner|breadcrumbs|combx|comment|community|cover-wrap|tooltip|disqus|extra|footer|gdpr|header|legends|menu|related|remark|replies|rss|shoutbox|sidebar|skyscraper|social|sponsor|supplemental|ad-break|agegate|pagination|pager|popup|yom-remote|speechify-ignore|avatar/i,
239
187
  okMaybeItsaCandidate: /and|article|body|column|content|main|shadow|code/i
240
188
  };
241
- var BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry", "main"];
242
- var PARAGRAPH_TAGS = ["p", "div", "section", "article", "main", "ul", "ol", "li"];
189
+ var BODY_SELECTORS = ["article", "#article", ".article_body", ".article-body", "#content", ".entry"];
190
+ var PARAGRAPH_TAGS = ["a", "p", "div", "section", "article", "main", "ul", "ol", "li"];
243
191
  var BASE_MINIMAL_LENGTH = {
244
192
  ja: 200,
245
193
  en: 500
246
194
  };
247
- var tryGetLang = (node) => {
248
- if (node.type !== "element") {
249
- return;
250
- }
251
- const element = node;
252
- if (element.tagName !== "html") {
253
- return;
254
- }
255
- const langAttr = element.properties.lang || element.properties["xml:lang"];
256
- if (langAttr) {
257
- return langAttr;
258
- }
195
+ var metadataFilter = (node) => {
196
+ return !(["comment", "doctype"].includes(node.type) || node.type === "element" && ["script", "style", "link", "meta", "noscript", "svg", "title"].includes(node.tagName));
259
197
  };
260
- var metadataFilter = (node) => !["comment", "doctype"].includes(node.type);
261
198
  var universalElementFilter = (node) => {
262
199
  if (node.type !== "element") {
263
200
  return true;
264
201
  }
265
202
  const element = node;
266
- if (["script", "aside"].includes(element.tagName)) {
203
+ if (["aside", "nav"].includes(element.tagName)) {
267
204
  return false;
268
205
  }
269
206
  if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
@@ -294,10 +231,7 @@ var unlikelyElementFilter = (node) => {
294
231
  return true;
295
232
  }
296
233
  const element = node;
297
- if (["body", "article", "main", "section", "a"].includes(element.tagName)) {
298
- return true;
299
- }
300
- if (hasAncestors(element, ["table", "code"], 3)) {
234
+ if (["body", "article", "main", "section", "h1", "h2", "h3", "h4", "h5", "h6", "p"].includes(element.tagName)) {
301
235
  return true;
302
236
  }
303
237
  const match = matchString(element);
@@ -309,55 +243,36 @@ var unlikelyElementFilter = (node) => {
309
243
  }
310
244
  return true;
311
245
  };
312
- var isImageLink = (element) => {
313
- const a = select("a", element);
314
- const img = select("img", a);
315
- if (!(a && img)) {
316
- return false;
317
- }
318
- const imgFilename = img.properties.src?.toString().split("/").pop();
319
- const hrefFilename = a.properties.href?.toString().split("/").pop();
320
- return imgFilename === hrefFilename && imgFilename;
321
- };
322
- var removeEmptyFilter = (node) => {
246
+ var removeEmptyFilter = (node, _lang) => {
323
247
  if (node.type !== "element") {
324
248
  return true;
325
249
  }
326
250
  const element = node;
327
- if (!PARAGRAPH_TAGS.includes(element.tagName)) {
328
- return true;
329
- }
330
- if (isImageLink(element)) {
251
+ if (PARAGRAPH_TAGS.includes(element.tagName)) {
331
252
  return true;
332
253
  }
333
- const text3 = hastToString(element);
334
- if (text3.length < 10) {
254
+ if (element.tagName === "img" && !element.properties.src) {
335
255
  return false;
336
256
  }
337
257
  return true;
338
258
  };
339
- var readabilityExtractHast = (hast) => {
340
- const lang = String(select("html", hast)?.properties.lang || tryGetLang(hast) || "en");
259
+ var readabilityExtractHast = (params) => {
260
+ const { hast, lang = "en" } = params;
341
261
  const body = select("body", hast) ?? hast;
342
- const bodyText = hastToString(body);
343
- const baseFilterd = filter(body, (node) => {
344
- if (!metadataFilter(node)) {
345
- return false;
346
- }
347
- if (!universalElementFilter(node)) {
348
- return false;
349
- }
350
- return true;
351
- });
352
- if (!baseFilterd) {
353
- return { type: "root", children: [] };
354
- }
355
- const baseFilterdText = hastToString(baseFilterd);
356
- let [baseTree, baseText] = baseFilterdText.length > bodyText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [body, bodyText];
262
+ const metadataFilteredHast = filter(body, (node) => metadataFilter(node));
263
+ const metadataFilteredHastText = metadataFilteredHast && hastToString(metadataFilteredHast);
264
+ if (!(metadataFilteredHast && metadataFilteredHastText)) {
265
+ return body;
266
+ }
267
+ const baseFilterd = filter(metadataFilteredHast, (node) => universalElementFilter(node));
268
+ const baseFilterdText = baseFilterd ? hastToString(baseFilterd) : "";
269
+ const [baseTree, baseText] = baseFilterdText.length > metadataFilteredHastText.length / 3 || baseFilterdText.length > 5e3 ? [baseFilterd, baseFilterdText] : [metadataFilteredHast, metadataFilteredHastText];
357
270
  let minimalLength = lang in BASE_MINIMAL_LENGTH ? BASE_MINIMAL_LENGTH[lang] : 500;
358
271
  if (baseText.length < minimalLength) {
359
272
  minimalLength = Math.max(0, baseText.length - 200);
360
273
  }
274
+ let extractedTree = baseTree;
275
+ let extractedText = baseText;
361
276
  for (const selector of BODY_SELECTORS) {
362
277
  const content = { type: "root", children: selectAll(selector, baseFilterd) };
363
278
  const contentText = hastToString(content);
@@ -366,18 +281,18 @@ var readabilityExtractHast = (hast) => {
366
281
  }
367
282
  const links = selectAll("a", content);
368
283
  const linkText = links.map((link) => hastToString(link)).join("");
369
- const linkDensity = linkText.length / bodyText.length;
284
+ const linkDensity = linkText.length / contentText.length;
370
285
  if (linkDensity > 0.4) {
371
286
  continue;
372
287
  }
373
288
  if (contentText.length > minimalLength) {
374
- baseTree = content;
375
- baseText = contentText;
289
+ extractedTree = content;
290
+ extractedText = contentText;
376
291
  break;
377
292
  }
378
293
  }
379
- const finalFilteredTree = filter(baseTree, (node) => {
380
- if (!removeEmptyFilter(node)) {
294
+ const finalFilteredTree = filter(extractedTree, (node) => {
295
+ if (!removeEmptyFilter(node, lang)) {
381
296
  return false;
382
297
  }
383
298
  if (!unlikelyElementFilter(node)) {
@@ -385,7 +300,7 @@ var readabilityExtractHast = (hast) => {
385
300
  }
386
301
  return true;
387
302
  });
388
- const finalTree = hastToString(finalFilteredTree).length > baseText.length / 3 ? finalFilteredTree : baseTree;
303
+ const finalTree = hastToString(finalFilteredTree).length > extractedText.length / 3 ? finalFilteredTree : extractedTree;
389
304
  return finalTree;
390
305
  };
391
306
 
@@ -393,7 +308,8 @@ var readabilityExtractHast = (hast) => {
393
308
  import { select as select2 } from "hast-util-select";
394
309
  import { filter as filter2 } from "unist-util-filter";
395
310
  var REMOVE_REGEX = /fallback|icon|header|nav|speechify-ignore|assist|-ad-|banner|combx|comment|com-|contact|foot|footer|footnote|gdpr|masthead|media|meta|outbrain|promo|related|scroll|share|assist|shoutbox|sidebar|skyscraper|sponsor|shopping|tags|tool|widget/i;
396
- var stronglyExtractHast = (hast) => {
311
+ var stronglyExtractHast = (params) => {
312
+ const { hast } = params;
397
313
  const selectors = ["article", "#article", "#content", "main", "div.container"];
398
314
  let selectedElement = hast;
399
315
  for (const selector of selectors) {
@@ -426,17 +342,18 @@ var PRESET_EXTRACT_HAST = {
426
342
  readability: readabilityExtractHast
427
343
  };
428
344
  var DEFAULT_EXTRACT_HAST = ["readability"];
429
- var extractHast = (hast, extractors = DEFAULT_EXTRACT_HAST) => {
345
+ var extractHast = (params, extractors = DEFAULT_EXTRACT_HAST) => {
346
+ const { hast, lang } = params;
430
347
  const _extractors = Array.isArray(extractors) ? extractors : [extractors];
431
348
  const extracted = _extractors.reduce((acc, extractor) => {
432
349
  if (extractor === false) {
433
350
  return acc;
434
351
  }
435
352
  if (typeof extractor === "string" && extractor in PRESET_EXTRACT_HAST) {
436
- return PRESET_EXTRACT_HAST[extractor](acc);
353
+ return PRESET_EXTRACT_HAST[extractor]({ hast: acc, lang });
437
354
  }
438
355
  if (typeof extractor === "function") {
439
- return extractor(acc);
356
+ return extractor({ hast: acc, lang });
440
357
  }
441
358
  throw new Error(`Invalid extractor: ${extractor}`);
442
359
  }, hast) || hast;
@@ -625,10 +542,6 @@ var customTableHandler = (options) => (state, node) => {
625
542
  return defaultHandlers4.table(state, node);
626
543
  };
627
544
 
628
- // src/mdast-handlers/empty-handler.ts
629
- var emptyHandler = () => {
630
- };
631
-
632
545
  // src/mdast-handlers/math-handler.ts
633
546
  import { toHtml } from "hast-util-to-html";
634
547
  import { MathMLToLaTeX } from "mathml-to-latex";
@@ -640,11 +553,54 @@ var mathHandler = (state, node) => {
640
553
  return result;
641
554
  };
642
555
 
556
+ // src/utils/hast-utils.ts
557
+ import { select as select4, selectAll as selectAll2 } from "hast-util-select";
558
+ var getLangFromHast = (node) => {
559
+ const html = select4("html", node);
560
+ if (html && typeof html.properties.lang === "string") {
561
+ return html.properties.lang;
562
+ }
563
+ if (node.type !== "element") {
564
+ return;
565
+ }
566
+ const element = node;
567
+ if (element.tagName !== "html") {
568
+ return;
569
+ }
570
+ const langAttr = element.properties.lang || element.properties["xml:lang"];
571
+ if (langAttr) {
572
+ return langAttr;
573
+ }
574
+ return void 0;
575
+ };
576
+ var getLangFromStr = (str) => {
577
+ const match = str.match(/lang=["']([^"']+)["']/);
578
+ if (match) {
579
+ return match[1];
580
+ }
581
+ return void 0;
582
+ };
583
+ var getUrlFromHast = (node) => {
584
+ if (node.type !== "element") {
585
+ return void 0;
586
+ }
587
+ const metaTagAttributes = ["og:url", "twitter:url"];
588
+ const metaTags = selectAll2("meta", node);
589
+ for (const meta of metaTags) {
590
+ const property = meta.properties.property || meta.properties.name;
591
+ if (typeof property === "string" && metaTagAttributes.includes(property)) {
592
+ return typeof meta.properties.content === "string" ? meta.properties.content : void 0;
593
+ }
594
+ }
595
+ return void 0;
596
+ };
597
+
643
598
  // src/html-to-mdast.ts
644
599
  var htmlToMdast = (htmlOrHast, options) => {
645
- const { extractors } = options || {};
646
- const hast = typeof htmlOrHast === "string" ? fromHtml(htmlOrHast, { fragment: true }) : htmlOrHast;
647
- const extractedHast = extractHast(hast, extractors);
600
+ const { extractors, url: defaultUrl, lang: defaultLang } = options || {};
601
+ const [lang, hast] = typeof htmlOrHast === "string" ? [defaultLang || getLangFromStr(htmlOrHast), fromHtml(htmlOrHast, { fragment: true })] : [defaultLang || getLangFromHast(htmlOrHast), htmlOrHast];
602
+ const url = defaultUrl || getUrlFromHast(hast);
603
+ const extractedHast = extractHast({ hast, lang, url }, extractors);
648
604
  const mdast = toMdast(extractedHast, {
649
605
  handlers: {
650
606
  math: mathHandler,
@@ -652,8 +608,7 @@ var htmlToMdast = (htmlOrHast, options) => {
652
608
  pre: customCodeHandler,
653
609
  a: customAHandler({ asText: options?.linkAsText }),
654
610
  img: customImgHandler({ hideImage: options?.hideImage }),
655
- table: customTableHandler({ asText: options?.tableAsText }),
656
- br: emptyHandler
611
+ table: customTableHandler({ asText: options?.tableAsText })
657
612
  }
658
613
  });
659
614
  const extractedMdast = extractMdast(mdast);
@@ -666,13 +621,18 @@ import { mathToMarkdown } from "mdast-util-math";
666
621
  import { toMarkdown } from "mdast-util-to-markdown";
667
622
 
668
623
  // src/link-replacer.ts
669
- var linkReplacer = (markdown, link) => {
670
- const regex = /(!?\[.*?\]\()(.+?)(\))/g;
671
- return markdown.replace(regex, (match, pre, thisUrl, post) => {
672
- if (thisUrl.startsWith("http") || thisUrl.startsWith("#")) {
624
+ var linkReplacer = (markdown, base) => {
625
+ const regex = /(!?\[.*?\]\()([^)\s]+)(\))/g;
626
+ return markdown.replace(regex, (match, pre, url, post) => {
627
+ if (/^(https?:|#)/.test(url)) {
628
+ return match;
629
+ }
630
+ try {
631
+ const absoluteUrl = new URL(url, base).href;
632
+ return `${pre}${absoluteUrl}${post}`;
633
+ } catch {
673
634
  return match;
674
635
  }
675
- return `${pre}${new URL(thisUrl, link).href}${post}`;
676
636
  });
677
637
  };
678
638
 
@@ -691,7 +651,7 @@ var DEFAULT_MDAST_TO_MARKDOWN_OPTIONS = {
691
651
  };
692
652
  var mdastToMarkdown = (mdast, options) => {
693
653
  const { baseUrl, ...toMarkdownOptions } = { ...DEFAULT_MDAST_TO_MARKDOWN_OPTIONS, ...options };
694
- let markdown = toMarkdown(warpRoot(mdast), toMarkdownOptions);
654
+ let markdown = toMarkdown(warpRoot(mdast), toMarkdownOptions).replace(/\*\*\*\*/g, "");
695
655
  if (baseUrl) {
696
656
  markdown = linkReplacer(markdown, baseUrl);
697
657
  }
@@ -882,9 +842,9 @@ var inputSourcePath = async () => {
882
842
  };
883
843
 
884
844
  // src/cli/helpers/selectExtractMode.ts
885
- import { select as select4 } from "@clack/prompts";
845
+ import { select as select5 } from "@clack/prompts";
886
846
  var selectExtractMode = async () => {
887
- const result = await select4({
847
+ const result = await select5({
888
848
  message: "Select processing mode:",
889
849
  options: MODES.map((mode) => ({ value: mode, label: mode })),
890
850
  initialValue: MODES[0]
@@ -894,14 +854,14 @@ var selectExtractMode = async () => {
894
854
  };
895
855
 
896
856
  // src/cli/helpers/selectLoader.ts
897
- import { select as select5 } from "@clack/prompts";
857
+ import { select as select6 } from "@clack/prompts";
898
858
  var loadersHint = {
899
859
  fetch: "Fetch HTML content from the given URL",
900
860
  playwright: "Retrieve HTML content after rendering using Playwright; Playwright must be installed in advance.",
901
861
  puppeteer: "Retrieve HTML content after rendering using Puppeteer; Puppeteer must be installed in advance."
902
862
  };
903
863
  var selectLoader = async () => {
904
- const result = await select5({
864
+ const result = await select6({
905
865
  message: "Select loader:",
906
866
  initialValue: "fetch",
907
867
  options: LOADERS.map((mode) => ({ value: mode, label: mode, hint: loadersHint[mode] || "" }))
@@ -910,140 +870,160 @@ var selectLoader = async () => {
910
870
  return result;
911
871
  };
912
872
 
913
- // src/cli/commands/handlers/loadHtml.ts
873
+ // src/cli/commands/webforai/loadHtml.ts
914
874
  import fs4 from "node:fs/promises";
915
- import { cancel as cancel2, spinner as clackSpinner, confirm as confirm2, spinner } from "@clack/prompts";
875
+ import { log } from "@clack/prompts";
876
+ import boxen from "boxen";
916
877
  import pc from "picocolors";
917
- import { $ } from "zx";
918
- var loadHtml3 = async (sourcePath, loader) => {
919
- const loadSpinner = clackSpinner();
920
- try {
921
- switch (loader.type) {
922
- case "local":
923
- return await loadLocalHtml(sourcePath, loadSpinner);
924
- case "web":
925
- switch (loader.loader) {
926
- case "fetch":
927
- return await loadFetchHtml(sourcePath, loadSpinner);
928
- case "playwright":
929
- return await loadPlaywrightHtml(sourcePath, loadSpinner);
930
- default:
931
- throw new Error("Invalid loader");
932
- }
933
- default:
934
- throw new Error("Invalid loader type");
935
- }
936
- } catch (error) {
937
- loadSpinner.stop(pc.red("Content loading failed!"));
938
- console.error(error);
939
- process.exit(1);
940
- }
878
+ import { chromium as chromium2 } from "playwright-core";
879
+
880
+ // src/loaders/fetch.ts
881
+ var USER_AGENT = "mozilla/5.0 (windows nt 10.0; win64; x64) applewebkit/537.36 (khtml, like gecko) chrome/125.0.0.0 safari/537.36";
882
+ var loadHtml = async (url, userAgent = USER_AGENT) => {
883
+ const response = await fetch(url, { headers: { "User-Agent": userAgent } });
884
+ return response.text();
941
885
  };
942
- var loadLocalHtml = async (sourcePath, loadSpinner) => {
943
- loadSpinner.start("Retrieving content.");
944
- try {
945
- await fs4.access(sourcePath);
946
- } catch {
947
- throw new Error("The file does not exist");
948
- }
949
- const stats = await fs4.stat(sourcePath);
950
- if (!stats.isFile()) {
951
- throw new Error("The source must be a file");
952
- }
953
- const content = await fs4.readFile(sourcePath, "utf-8");
954
- loadSpinner.stop("Content retrieval is complete!");
955
- return content;
886
+
887
+ // src/loaders/playwright.ts
888
+ import { chromium, devices } from "playwright-core";
889
+ var SUPER_BYPASS_DEVICE = {
890
+ userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
891
+ viewport: { width: 1920, height: 1080 },
892
+ deviceScaleFactor: 1,
893
+ hasTouch: false,
894
+ isMobile: false,
895
+ javaScriptEnabled: true,
896
+ locale: "en-US",
897
+ timezoneId: "America/New_York"
956
898
  };
957
- var loadFetchHtml = async (sourcePath, loadSpinner) => {
958
- loadSpinner.start("Downloading content.");
959
- const { loadHtml: loadHtml4 } = await Promise.resolve().then(() => (init_fetch(), fetch_exports));
960
- const content = await loadHtml4(sourcePath);
961
- loadSpinner.stop("Content download is complete!");
962
- return content;
899
+ var loadHtml2 = async (url, options) => {
900
+ const { browser, waitUntil, timeout, superBypassMode } = options ?? {};
901
+ const _browser = browser ?? await chromium.launch({ headless: true });
902
+ const context = await _browser.newContext(superBypassMode ? SUPER_BYPASS_DEVICE : devices["Desktop Chrome"]);
903
+ if (superBypassMode) {
904
+ await context.addInitScript(() => {
905
+ Object.defineProperty(navigator, "webdriver", { get: () => void 0 });
906
+ Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
907
+ Object.defineProperty(navigator, "plugins", { get: () => [1, 2, 3, 4, 5] });
908
+ });
909
+ }
910
+ const page = await context.newPage();
911
+ if (superBypassMode) {
912
+ await page.route("**/*.js", (route) => {
913
+ if (route.request().url().includes("captcha-delivery")) {
914
+ return route.abort();
915
+ }
916
+ return route.continue();
917
+ });
918
+ }
919
+ await page.goto(url, { waitUntil: waitUntil ?? "load", timeout });
920
+ const html = await page.content();
921
+ await page.close();
922
+ if (browser) {
923
+ await context.close();
924
+ } else {
925
+ await _browser.close();
926
+ }
927
+ return html;
963
928
  };
964
- var loadPlaywrightHtml = async (sourcePath, loadSpinner) => {
965
- const launchSpinner = clackSpinner();
966
- launchSpinner.start("Launching playwright.");
967
- const playwright = await import("playwright");
968
- let browser;
969
- try {
970
- browser = await playwright.chromium.launch();
971
- launchSpinner.stop("Playwright launched!");
972
- } catch (error) {
973
- await handlePlaywrightLaunchError(error);
974
- browser = await playwright.chromium.launch();
975
- }
976
- loadSpinner.start("Loading content.");
977
- const { loadHtml: loadHtml4 } = await Promise.resolve().then(() => (init_playwright(), playwright_exports));
978
- const content = await loadHtml4(sourcePath, browser);
979
- loadSpinner.stop("Content download is complete!");
980
- await browser.close();
981
- return content;
929
+
930
+ // src/cli/commands/webforai/loadHtml.ts
931
+ var checkPlaywrightAvailable = async () => {
932
+ const path3 = chromium2.executablePath();
933
+ const isAvailable = await fs4.access(path3).catch(() => false);
934
+ return !!isAvailable;
982
935
  };
983
- var handlePlaywrightLaunchError = async (error) => {
984
- if (!error.message.includes("npx playwright install")) {
985
- throw error;
986
- }
987
- const isInstall = await confirm2({
988
- message: "To continue, you will need to download a browser. Do you wish to continue?"
989
- });
990
- if (!isInstall) {
991
- cancel2("End of process.");
992
- process.exit(1);
993
- }
994
- const installDepsSpinner = spinner();
995
- installDepsSpinner.start("Installing browser dependencies.");
996
- try {
997
- await $`npx playwright install`;
998
- installDepsSpinner.stop("Browser dependencies installed!");
999
- } catch (error2) {
1000
- installDepsSpinner.stop(pc.red("Browser dependencies failed to install!"));
1001
- throw error2;
936
+ var getPlaywrightVersion = async () => {
937
+ const path3 = await import.meta.resolve("playwright-core/package.json");
938
+ const pwPackageJson = await fs4.readFile(path3.replace("file://", ""), "utf-8").then((res) => JSON.parse(res.toString())).catch(() => null);
939
+ return pwPackageJson?.version;
940
+ };
941
+ var loadHtml3 = async (sourcePath, loader, options) => {
942
+ if (loader === "local") {
943
+ options.debug && log.info(`Loading HTML from local file: ${sourcePath}`);
944
+ const content = await fs4.readFile(sourcePath, "utf-8");
945
+ options.debug && log.info(`HTML loaded: ${content.slice(0, 100)}`);
946
+ return content;
947
+ }
948
+ if (loader === "fetch") {
949
+ options.debug && log.info(`Loading HTML from URL: ${sourcePath}`);
950
+ const content = await loadHtml(sourcePath);
951
+ options.debug && log.info(`HTML loaded: ${content.slice(0, 100)}`);
952
+ return content;
953
+ }
954
+ if (loader === "playwright") {
955
+ options.debug && log.info(`Loading HTML from playwright: ${sourcePath}`);
956
+ const isPlaywrightAvailable = await checkPlaywrightAvailable();
957
+ options.debug && log.info(`Playwright available: ${isPlaywrightAvailable}`);
958
+ const pwVersion = await getPlaywrightVersion();
959
+ if (!isPlaywrightAvailable) {
960
+ const message = [
961
+ pc.bold("Error: Playwright is not available"),
962
+ "",
963
+ "To use the Playwright loader, please install Playwright by running:",
964
+ "",
965
+ ` npx playwright@${pwVersion} install chromium`,
966
+ "",
967
+ "Hint 1: If you receive a warning like this:",
968
+ ` "WARNING: It looks like you are running 'npx playwright install' without first installing your project's dependencies."`,
969
+ "You can safely ignore this warning.",
970
+ "",
971
+ "Hint 2: If you encounter the following message:",
972
+ ` "Host system is missing dependencies to run browsers."`,
973
+ "You should install the necessary dependencies by executing:",
974
+ "",
975
+ ` sudo npx playwright@${pwVersion} install-deps`
976
+ ];
977
+ log.error(boxen(message.join("\n"), { padding: 1, borderStyle: "round" }));
978
+ throw new Error("Playwright is not available");
979
+ }
980
+ const content = await loadHtml2(sourcePath);
981
+ options.debug && log.info(`HTML loaded: ${content.slice(0, 100)}`);
982
+ return content;
1002
983
  }
984
+ throw new Error(`Unsupported loader: ${loader}`);
1003
985
  };
1004
986
 
1005
- // src/cli/commands/handlers/index.ts
987
+ // src/cli/commands/webforai/index.ts
1006
988
  var aiModeOptions = { linkAsText: true, tableAsText: true, hideImage: true };
1007
989
  var readabilityModeOptions = { linkAsText: false, tableAsText: false, hideImage: false };
1008
- var webforaiCommand = async (initialPath, initialOutputPath, options) => {
990
+ var webforaiCommand = async (initialPath, options) => {
991
+ intro(pc2.bold(pc2.green(`webforai CLI version ${package_default.version}`)));
1009
992
  const sourcePath = initialPath ?? await inputSourcePath();
1010
- if (options.debug) {
1011
- console.debug(`sourcePath: ${sourcePath}`);
1012
- }
1013
- const loader = isUrl(sourcePath) ? { type: "web", loader: options.loader ?? await selectLoader() } : { type: "local", loader: null };
1014
- if (options.debug) {
1015
- console.debug(`loader: ${loader}`);
1016
- }
1017
- const outputPath = initialOutputPath ?? await inputOutputPath(sourcePath);
1018
- if (options.debug) {
1019
- console.debug(`outputPath: ${outputPath}`);
1020
- }
993
+ options.debug && log2.info(`sourcePath: ${sourcePath}`);
994
+ const loader = isUrl(sourcePath) ? options.loader ?? await selectLoader() : "local";
995
+ options.debug && log2.info(`loader: ${loader}`);
996
+ const outputPath = options.output ?? await inputOutputPath(sourcePath);
997
+ options.debug && log2.info(`outputPath: ${outputPath}`);
1021
998
  const mode = options.mode ?? await selectExtractMode();
1022
- if (options.debug) {
1023
- console.debug(`mode: ${mode}`);
1024
- }
1025
- const content = await loadHtml3(sourcePath, loader);
1026
- if (options.debug) {
1027
- console.debug(`content: ${content}`);
999
+ options.debug && log2.info(`mode: ${mode}`);
1000
+ let html;
1001
+ const s = spinner();
1002
+ try {
1003
+ s.start("Loading content...");
1004
+ html = await loadHtml3(sourcePath, loader, { debug: options.debug });
1005
+ s.stop(pc2.green("Content loaded!"));
1006
+ } catch (error) {
1007
+ s.stop(pc2.red("Content loading failed!"));
1008
+ console.error(error);
1009
+ process.exit(1);
1028
1010
  }
1029
- const markdown = htmlToMarkdown(content, {
1030
- baseUrl: isUrl(sourcePath) ? sourcePath : options.baseUrl,
1011
+ options.debug && log2.info(`html: ${html}`);
1012
+ const markdown = htmlToMarkdown(html, {
1013
+ baseUrl: isUrl(sourcePath) ? sourcePath : void 0,
1031
1014
  ...mode === "ai" ? aiModeOptions : readabilityModeOptions
1032
1015
  });
1033
- if (options.debug) {
1034
- console.debug(`markdown: ${markdown}`);
1035
- }
1016
+ options.debug && log2.info(`markdown: ${markdown}`);
1036
1017
  const directory = path2.dirname(outputPath);
1037
- if (!fs5.existsSync(directory)) {
1038
- fs5.mkdirSync(directory, { recursive: true });
1018
+ const isDirectoryExists = await fs5.stat(directory).then((stat) => stat.isDirectory());
1019
+ if (!isDirectoryExists) {
1020
+ await fs5.mkdir(directory, { recursive: true });
1039
1021
  }
1040
- fs5.writeFileSync(outputPath, markdown);
1041
- outro("Markdown conversion is complete!");
1022
+ await fs5.writeFile(outputPath, markdown);
1023
+ outro(pc2.green(`${pc2.bold("Done!")} Markdown saved to ${outputPath}`));
1042
1024
  };
1043
1025
 
1044
1026
  // src/cli/bin.ts
1045
- program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(package_default.version, "-v, --version", "output the current version").argument("[source]", "URL or path to process").argument("[outputPath]", "Path to output file or directory").option("--mode <mode>", `Processing mode (${MODES.join(", ")})`).option("--loader <loader>", `Loader to use (${LOADERS.join(", ")})`).option("--baseUrl <baseUrl>", "Base URL for relative links").option("-o, --stdout", "output to stdout").option("-d, --debug", "output extra debugging information").on("option:debug", () => {
1046
- process.env.DEBUG = "true";
1047
- }).action(webforaiCommand);
1048
- intro(pc2.inverse(`webforai CLI version ${package_default.version}`));
1027
+ program.name("webforai").description("CLI tool for ultra-precise HTML to Markdown conversion").version(package_default.version, "-v, --version", "output the current version");
1028
+ program.argument("[source]", "URL or path to process").option("-o, --output <output>", "Path to output file or directory").option("-m, --mode <mode>", `Processing mode (${MODES.join(", ")})`).option("-l, --loader <loader>", `Loader to use (${LOADERS.join(", ")})`).option("-d, --debug", "output extra debugging information").action(webforaiCommand);
1049
1029
  program.parse();