webforai 1.5.0 → 1.5.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/dist/bin.js +1032 -216
- package/dist/cjs/cli/bin.js +34 -0
- package/dist/cjs/cli/commands/handlers/index.js +84 -0
- package/dist/cjs/cli/commands/handlers/loadHtml.js +127 -0
- package/dist/cjs/cli/constants.js +34 -0
- package/dist/cjs/cli/helpers/assertContinue.js +34 -0
- package/dist/cjs/cli/helpers/inputOutputPath.js +89 -0
- package/dist/cjs/cli/helpers/inputSourcePath.js +64 -0
- package/dist/cjs/cli/helpers/selectExtractMode.js +39 -0
- package/dist/cjs/cli/helpers/selectLoader.js +44 -0
- package/dist/cjs/{utils/bin-utils.js → cli/utils.js} +31 -33
- package/dist/cjs/html-to-markdown.test.js +6 -34
- package/dist/cli/bin.js +12 -0
- package/dist/cli/commands/handlers/index.js +51 -0
- package/dist/cli/commands/handlers/loadHtml.js +94 -0
- package/dist/cli/constants.js +9 -0
- package/dist/cli/helpers/assertContinue.js +11 -0
- package/dist/cli/helpers/inputOutputPath.js +56 -0
- package/dist/cli/helpers/inputSourcePath.js +31 -0
- package/dist/cli/helpers/selectExtractMode.js +16 -0
- package/dist/cli/helpers/selectLoader.js +21 -0
- package/dist/{utils/bin-utils.js → cli/utils.js} +28 -30
- package/dist/html-to-markdown.test.js +6 -12
- package/dist/types/cli/bin.js.map +1 -0
- package/dist/types/cli/commands/handlers/index.d.ts +13 -0
- package/dist/types/cli/commands/handlers/index.js.map +1 -0
- package/dist/types/cli/commands/handlers/loadHtml.d.ts +2 -0
- package/dist/types/cli/commands/handlers/loadHtml.js.map +1 -0
- package/dist/types/cli/constants.d.ts +5 -0
- package/dist/types/cli/constants.js.map +1 -0
- package/dist/types/cli/helpers/assertContinue.d.ts +1 -0
- package/dist/types/cli/helpers/assertContinue.js.map +1 -0
- package/dist/types/cli/helpers/inputOutputPath.d.ts +1 -0
- package/dist/types/cli/helpers/inputOutputPath.js.map +1 -0
- package/dist/types/cli/helpers/inputSourcePath.d.ts +1 -0
- package/dist/types/cli/helpers/inputSourcePath.js.map +1 -0
- package/dist/types/cli/helpers/selectExtractMode.d.ts +1 -0
- package/dist/types/cli/helpers/selectExtractMode.js.map +1 -0
- package/dist/types/cli/helpers/selectLoader.d.ts +1 -0
- package/dist/types/cli/helpers/selectLoader.js.map +1 -0
- package/dist/types/{utils/bin-utils.d.ts → cli/utils.d.ts} +3 -3
- package/dist/types/cli/utils.js.map +1 -0
- package/package.json +7 -17
- package/dist/cjs/bin.js +0 -254
- package/dist/types/bin.js.map +0 -1
- package/dist/types/utils/bin-utils.js.map +0 -1
- /package/dist/types/{bin.d.ts → cli/bin.d.ts} +0 -0
package/dist/bin.js
CHANGED
|
@@ -1,233 +1,1049 @@
|
|
|
1
|
-
|
|
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
|
+
});
|
|
52
|
+
|
|
53
|
+
// src/cli/bin.ts
|
|
54
|
+
import { intro } from "@clack/prompts";
|
|
55
|
+
import { program } from "commander";
|
|
56
|
+
import pc2 from "picocolors";
|
|
57
|
+
|
|
58
|
+
// package.json
|
|
59
|
+
var package_default = {
|
|
60
|
+
name: "webforai",
|
|
61
|
+
version: "1.5.1",
|
|
62
|
+
description: "A library that provides a web interface for AI",
|
|
63
|
+
author: "inaridiy",
|
|
64
|
+
license: "MIT",
|
|
65
|
+
keywords: [
|
|
66
|
+
"web",
|
|
67
|
+
"ai",
|
|
68
|
+
"html",
|
|
69
|
+
"html2md",
|
|
70
|
+
"markdown",
|
|
71
|
+
"mdast",
|
|
72
|
+
"hast"
|
|
73
|
+
],
|
|
74
|
+
repository: {
|
|
75
|
+
type: "git",
|
|
76
|
+
url: "https://github.com/inaridiy/webforai.git"
|
|
77
|
+
},
|
|
78
|
+
homepage: "https://github.com/inaridiy/webforai.git",
|
|
79
|
+
scripts: {
|
|
80
|
+
"copy:package.cjs.json": "pnpm ncp ./package.cjs.json ./dist/cjs/package.json && pnpm ncp ./package.cjs.json ./dist/types/package.json ",
|
|
81
|
+
clean: "rimraf dist",
|
|
82
|
+
build: "pnpm clean && tsx build.ts && pnpm copy:package.cjs.json",
|
|
83
|
+
test: 'echo "Error: no test specified"',
|
|
84
|
+
prerelease: "pnpm build",
|
|
85
|
+
release: "np"
|
|
86
|
+
},
|
|
87
|
+
files: [
|
|
88
|
+
"dist",
|
|
89
|
+
"!dist/types/**/*.js"
|
|
90
|
+
],
|
|
91
|
+
main: "dist/cjs/index.js",
|
|
92
|
+
type: "module",
|
|
93
|
+
module: "dist/index.js",
|
|
94
|
+
types: "dist/types/index.d.ts",
|
|
95
|
+
bin: "dist/bin.js",
|
|
96
|
+
exports: {
|
|
97
|
+
".": {
|
|
98
|
+
types: "./dist/types/index.d.ts",
|
|
99
|
+
import: "./dist/index.js",
|
|
100
|
+
require: "./dist/cjs/index.js"
|
|
101
|
+
},
|
|
102
|
+
"./types": {
|
|
103
|
+
types: "./dist/types/index.d.ts",
|
|
104
|
+
import: "./dist/index.js",
|
|
105
|
+
require: "./dist/cjs/index.js"
|
|
106
|
+
},
|
|
107
|
+
"./loaders/playwright": {
|
|
108
|
+
types: "./dist/types/loaders/playwright.d.ts",
|
|
109
|
+
import: "./dist/loaders/playwright.js",
|
|
110
|
+
require: "./dist/cjs/loaders/playwright.js"
|
|
111
|
+
},
|
|
112
|
+
"./loaders/fetch": {
|
|
113
|
+
types: "./dist/types/loaders/fetch.d.ts",
|
|
114
|
+
import: "./dist/loaders/fetch.js",
|
|
115
|
+
require: "./dist/cjs/loaders/fetch.js"
|
|
116
|
+
},
|
|
117
|
+
"./loaders/cf-puppeteer": {
|
|
118
|
+
types: "./dist/types/loaders/cf-puppeteer.d.ts",
|
|
119
|
+
import: "./dist/loaders/cf-puppeteer.js",
|
|
120
|
+
require: "./dist/cjs/loaders/cf-puppeteer.js"
|
|
121
|
+
},
|
|
122
|
+
"./loaders/puppeteer": {
|
|
123
|
+
types: "./dist/types/loaders/puppeteer.d.ts",
|
|
124
|
+
import: "./dist/loaders/puppeteer.js",
|
|
125
|
+
require: "./dist/cjs/loaders/puppeteer.js"
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
typesVersions: {
|
|
129
|
+
"*": {
|
|
130
|
+
types: [
|
|
131
|
+
"./dist/types/index.d.ts"
|
|
132
|
+
],
|
|
133
|
+
"loaders/playwright": [
|
|
134
|
+
"./dist/types/loaders/playwright.d.ts"
|
|
135
|
+
],
|
|
136
|
+
"loaders/cf-puppeteer": [
|
|
137
|
+
"./dist/types/loaders/cf-puppeteer.d.ts"
|
|
138
|
+
],
|
|
139
|
+
"loaders/fetch": [
|
|
140
|
+
"./dist/types/loaders/fetch.d.ts"
|
|
141
|
+
],
|
|
142
|
+
"loaders/puppeteer": [
|
|
143
|
+
"./dist/types/loaders/puppeteer.d.ts"
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
peerDependencies: {
|
|
148
|
+
"@cloudflare/puppeteer": ">=0.0.6",
|
|
149
|
+
playwright: ">=1.4",
|
|
150
|
+
puppeteer: ">=22"
|
|
151
|
+
},
|
|
152
|
+
dependencies: {
|
|
153
|
+
"@clack/prompts": "^0.7.0",
|
|
154
|
+
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",
|
|
159
|
+
"hast-util-to-mdast": "^10.1.0",
|
|
160
|
+
"hast-util-to-string": "^3.0.0",
|
|
161
|
+
"hast-util-to-text": "^4.0.0",
|
|
162
|
+
"mathml-to-latex": "^1.3.0",
|
|
163
|
+
"mdast-util-gfm": "^3.0.0",
|
|
164
|
+
"mdast-util-math": "^3.0.0",
|
|
165
|
+
"mdast-util-to-markdown": "^2.1.0",
|
|
166
|
+
picocolors: "^1.0.1",
|
|
167
|
+
"trim-trailing-lines": "^2.1.0",
|
|
168
|
+
"unist-util-filter": "^5.0.1",
|
|
169
|
+
zx: "^8.1.5"
|
|
170
|
+
},
|
|
171
|
+
devDependencies: {
|
|
172
|
+
"@cloudflare/puppeteer": "^0.0.6",
|
|
173
|
+
"@tsconfig/recommended": "^1.0.3",
|
|
174
|
+
"@types/hast": "^3.0.2",
|
|
175
|
+
"@types/mdast": "^4.0.2",
|
|
176
|
+
"@types/node": "^20.11.0",
|
|
177
|
+
arg: "^5.0.2",
|
|
178
|
+
esbuild: "^0.19.11",
|
|
179
|
+
glob: "^10.3.10",
|
|
180
|
+
ncp: "^2.0.0",
|
|
181
|
+
np: "^9.2.0",
|
|
182
|
+
playwright: "^1.40.1",
|
|
183
|
+
puppeteer: "^23.2.2",
|
|
184
|
+
rimraf: "^5.0.5",
|
|
185
|
+
tsx: "^4.7.0",
|
|
186
|
+
typescript: "^5.4.5"
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/cli/commands/handlers/index.ts
|
|
191
|
+
import fs5 from "node:fs";
|
|
192
|
+
import path2 from "node:path";
|
|
193
|
+
import { outro } from "@clack/prompts";
|
|
194
|
+
|
|
195
|
+
// src/html-to-mdast.ts
|
|
196
|
+
import { fromHtml } from "hast-util-from-html";
|
|
197
|
+
import { toMdast } from "hast-util-to-mdast";
|
|
198
|
+
|
|
199
|
+
// src/extract-hast/readability.ts
|
|
200
|
+
import { select, selectAll } from "hast-util-select";
|
|
201
|
+
import { toString as hastToString } from "hast-util-to-string";
|
|
202
|
+
import { filter } from "unist-util-filter";
|
|
203
|
+
|
|
204
|
+
// src/extract-hast/utils.ts
|
|
205
|
+
var matchString = (element) => `${element.tagName} ${element.properties.id} ${classnames(element).join(" ")}`;
|
|
206
|
+
var classnames = (element) => {
|
|
207
|
+
if (Array.isArray(element.properties.className)) {
|
|
208
|
+
return element.properties.className;
|
|
209
|
+
}
|
|
210
|
+
return [];
|
|
211
|
+
};
|
|
212
|
+
var isStrInclude = (value, match) => {
|
|
213
|
+
if (typeof value === "string") {
|
|
214
|
+
return value.includes(match);
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
};
|
|
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
|
+
|
|
232
|
+
// src/extract-hast/readability.ts
|
|
233
|
+
var UNLIKELY_ROLES = ["menu", "menubar", "complementary", "navigation", "alert", "alertdialog", "dialog"];
|
|
234
|
+
var REGEXPS = {
|
|
235
|
+
hidden: /hidden|invisible|fallback-image/i,
|
|
236
|
+
byline: /byline|author|dateline|writtenby|p-author/i,
|
|
237
|
+
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,
|
|
239
|
+
okMaybeItsaCandidate: /and|article|body|column|content|main|shadow|code/i
|
|
240
|
+
};
|
|
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"];
|
|
243
|
+
var BASE_MINIMAL_LENGTH = {
|
|
244
|
+
ja: 200,
|
|
245
|
+
en: 500
|
|
246
|
+
};
|
|
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
|
+
}
|
|
259
|
+
};
|
|
260
|
+
var metadataFilter = (node) => !["comment", "doctype"].includes(node.type);
|
|
261
|
+
var universalElementFilter = (node) => {
|
|
262
|
+
if (node.type !== "element") {
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
const element = node;
|
|
266
|
+
if (["script", "aside"].includes(element.tagName)) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (["hidden", "aria-hidden"].some((key) => element.properties[key])) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (classnames(element).some((classname) => REGEXPS.hidden.test(classname))) {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (element.tagName === "dialog") {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
if (element.properties.role === "dialog" && element.properties["aria-modal"]) {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
if (element.properties.rel === "author" && isStrInclude(element.properties.itemprop, "author")) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
if (REGEXPS.byline.test(matchString(element))) {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
if (element.properties.role && UNLIKELY_ROLES.includes(element.properties.role)) {
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
return true;
|
|
291
|
+
};
|
|
292
|
+
var unlikelyElementFilter = (node) => {
|
|
293
|
+
if (node.type !== "element") {
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
const element = node;
|
|
297
|
+
if (["body", "article", "main", "section", "a"].includes(element.tagName)) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
if (hasAncestors(element, ["table", "code"], 3)) {
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
const match = matchString(element);
|
|
304
|
+
if (REGEXPS.specialUnlikelyCandidates.test(match)) {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
if (REGEXPS.unlikelyCandidates.test(match) && !REGEXPS.okMaybeItsaCandidate.test(match)) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
return true;
|
|
311
|
+
};
|
|
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) => {
|
|
323
|
+
if (node.type !== "element") {
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
const element = node;
|
|
327
|
+
if (!PARAGRAPH_TAGS.includes(element.tagName)) {
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
if (isImageLink(element)) {
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
const text3 = hastToString(element);
|
|
334
|
+
if (text3.length < 10) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
};
|
|
339
|
+
var readabilityExtractHast = (hast) => {
|
|
340
|
+
const lang = String(select("html", hast)?.properties.lang || tryGetLang(hast) || "en");
|
|
341
|
+
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];
|
|
357
|
+
let minimalLength = lang in BASE_MINIMAL_LENGTH ? BASE_MINIMAL_LENGTH[lang] : 500;
|
|
358
|
+
if (baseText.length < minimalLength) {
|
|
359
|
+
minimalLength = Math.max(0, baseText.length - 200);
|
|
360
|
+
}
|
|
361
|
+
for (const selector of BODY_SELECTORS) {
|
|
362
|
+
const content = { type: "root", children: selectAll(selector, baseFilterd) };
|
|
363
|
+
const contentText = hastToString(content);
|
|
364
|
+
if (contentText.length < 25) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const links = selectAll("a", content);
|
|
368
|
+
const linkText = links.map((link) => hastToString(link)).join("");
|
|
369
|
+
const linkDensity = linkText.length / bodyText.length;
|
|
370
|
+
if (linkDensity > 0.4) {
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (contentText.length > minimalLength) {
|
|
374
|
+
baseTree = content;
|
|
375
|
+
baseText = contentText;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
const finalFilteredTree = filter(baseTree, (node) => {
|
|
380
|
+
if (!removeEmptyFilter(node)) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
if (!unlikelyElementFilter(node)) {
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
});
|
|
388
|
+
const finalTree = hastToString(finalFilteredTree).length > baseText.length / 3 ? finalFilteredTree : baseTree;
|
|
389
|
+
return finalTree;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/extract-hast/strongly.ts
|
|
393
|
+
import { select as select2 } from "hast-util-select";
|
|
394
|
+
import { filter as filter2 } from "unist-util-filter";
|
|
395
|
+
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) => {
|
|
397
|
+
const selectors = ["article", "#article", "#content", "main", "div.container"];
|
|
398
|
+
let selectedElement = hast;
|
|
399
|
+
for (const selector of selectors) {
|
|
400
|
+
const element = select2(selector, hast);
|
|
401
|
+
if (element) {
|
|
402
|
+
selectedElement = element;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
const removedElements = filter2(selectedElement, (node) => {
|
|
407
|
+
if (node.type === "comment") {
|
|
408
|
+
return false;
|
|
409
|
+
}
|
|
410
|
+
if (node.type !== "element") {
|
|
411
|
+
return true;
|
|
412
|
+
}
|
|
413
|
+
const element = node;
|
|
414
|
+
const elementName = `${element.tagName} ${element.properties.id} ${element.properties.className}`;
|
|
415
|
+
if (REMOVE_REGEX.test(elementName)) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
return true;
|
|
419
|
+
});
|
|
420
|
+
return removedElements;
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/extract-hast/index.ts
|
|
424
|
+
var PRESET_EXTRACT_HAST = {
|
|
425
|
+
strongly: stronglyExtractHast,
|
|
426
|
+
readability: readabilityExtractHast
|
|
427
|
+
};
|
|
428
|
+
var DEFAULT_EXTRACT_HAST = ["readability"];
|
|
429
|
+
var extractHast = (hast, extractors = DEFAULT_EXTRACT_HAST) => {
|
|
430
|
+
const _extractors = Array.isArray(extractors) ? extractors : [extractors];
|
|
431
|
+
const extracted = _extractors.reduce((acc, extractor) => {
|
|
432
|
+
if (extractor === false) {
|
|
433
|
+
return acc;
|
|
434
|
+
}
|
|
435
|
+
if (typeof extractor === "string" && extractor in PRESET_EXTRACT_HAST) {
|
|
436
|
+
return PRESET_EXTRACT_HAST[extractor](acc);
|
|
437
|
+
}
|
|
438
|
+
if (typeof extractor === "function") {
|
|
439
|
+
return extractor(acc);
|
|
440
|
+
}
|
|
441
|
+
throw new Error(`Invalid extractor: ${extractor}`);
|
|
442
|
+
}, hast) || hast;
|
|
443
|
+
return extracted;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
// src/extract-mdast.ts
|
|
447
|
+
import { filter as filter3 } from "unist-util-filter";
|
|
448
|
+
var DECLATION_TYPES = ["blockquote", "strong", "emphasis", "delete"];
|
|
449
|
+
var emptyDeclarationFilter = (node) => {
|
|
450
|
+
if (!DECLATION_TYPES.includes(node.type)) {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
if (node.children.length === 0) {
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
return true;
|
|
457
|
+
};
|
|
458
|
+
var extractMdast = (node) => {
|
|
459
|
+
const extracted = filter3(node, (node2) => {
|
|
460
|
+
if (!emptyDeclarationFilter(node2)) {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
return true;
|
|
464
|
+
});
|
|
465
|
+
return extracted;
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
// src/mdast-handlers/custom-a-handler.ts
|
|
469
|
+
import { defaultHandlers } from "hast-util-to-mdast";
|
|
470
|
+
import { toString as hastToString2 } from "hast-util-to-string";
|
|
471
|
+
var customAHandler = (options) => (state, node) => {
|
|
472
|
+
if (options?.asText) {
|
|
473
|
+
const text3 = hastToString2(node);
|
|
474
|
+
if (3 >= text3.length) {
|
|
475
|
+
return void 0;
|
|
476
|
+
}
|
|
477
|
+
const link2 = { type: "text", value: text3 };
|
|
478
|
+
state.patch(node, link2);
|
|
479
|
+
return link2;
|
|
480
|
+
}
|
|
481
|
+
const link = defaultHandlers.a(state, node);
|
|
482
|
+
if (link.children.length > 0) {
|
|
483
|
+
return link;
|
|
484
|
+
}
|
|
485
|
+
return void 0;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// src/mdast-handlers/custom-code-handler.ts
|
|
489
|
+
import { toText } from "hast-util-to-text";
|
|
490
|
+
import { trimTrailingLines } from "trim-trailing-lines";
|
|
491
|
+
|
|
492
|
+
// src/utils/detect-code-lang.ts
|
|
493
|
+
var languages = [
|
|
494
|
+
["bash", [/#!(\/usr)?\/bin\/bash/g, 500], [/\b(if|elif|then|fi|echo)\b|\$/g, 10]],
|
|
495
|
+
["html", [/<\/?[a-z-]+[^\n>]*>/g, 10], [/^\s+<!DOCTYPE\s+html/g, 500]],
|
|
496
|
+
["http", [/^(GET|HEAD|POST|PUT|DELETE|PATCH|HTTP)\b/g, 500]],
|
|
497
|
+
[
|
|
498
|
+
"ts",
|
|
499
|
+
[/(import .* from|export \*|export const|const .* = await)/g, 400],
|
|
500
|
+
[
|
|
501
|
+
/\b(console|await|async|function|undefined|export|import|this|class|for|let|const|map|join|require|implements|interface|namespace)\b/g,
|
|
502
|
+
10
|
|
503
|
+
]
|
|
504
|
+
],
|
|
505
|
+
[
|
|
506
|
+
"tsx",
|
|
507
|
+
[/(import .* from|export \*|export const)/g, 400],
|
|
508
|
+
[/\b(react|next|FC)\b/g, 200],
|
|
509
|
+
[
|
|
510
|
+
/\b(console|await|async|function|export|undefined|import|this|class|for|let|const|map|join|require|implements|interface)\b/g,
|
|
511
|
+
10
|
|
512
|
+
]
|
|
513
|
+
],
|
|
514
|
+
["py", [/\b(def|print|class|and|or|lambda)\b/g, 10]],
|
|
515
|
+
["sql", [/\b(SELECT|INSERT|FROM)\b/g, 50]],
|
|
516
|
+
["pl", [/#!(\/usr)?\/bin\/perl/g, 500], [/\b(use|print)\b|\$/g, 10]],
|
|
517
|
+
["lua", [/#!(\/usr)?\/bin\/lua/g, 500]],
|
|
518
|
+
["make", [/\b(ifneq|endif|if|elif|then|fi|echo|.PHONY|^[a-z]+ ?:$)\b|\$/gm, 10]],
|
|
519
|
+
["uri", [/https?:|mailto:|tel:|ftp:/g, 30]],
|
|
520
|
+
["css", [/^(@import|@page|@media|(\.|#)[a-z]+)/gm, 20]],
|
|
521
|
+
["diff", [/^[+><-]/gm, 10], [/^@@ ?[-+,0-9 ]+ ?@@/gm, 25]],
|
|
522
|
+
["md", [/^(>|\t\*|\t\d+.)/gm, 10], [/\[.*\](.*)/g, 10]],
|
|
523
|
+
["docker", [/^(FROM|ENTRYPOINT|RUN)/gm, 500]],
|
|
524
|
+
["xml", [/<\/?[a-z-]+[^\n>]*>/g, 10], [/^<\?xml/g, 500]],
|
|
525
|
+
["c", [/#include\b|\bprintf\s+\(/g, 100]],
|
|
526
|
+
["rs", [/^\s+(use|fn|mut|match)\b/gm, 100]],
|
|
527
|
+
["go", [/\b(func|fmt|package)\b/g, 100]],
|
|
528
|
+
["java", [/^import\s+java/gm, 500]],
|
|
529
|
+
["asm", [/^(section|global main|extern|\t(call|mov|ret))/gm, 100]],
|
|
530
|
+
["css", [/^(@import|@page|@media|(\.|#)[a-z]+)/gm, 20]],
|
|
531
|
+
["json", [/\b(true|false|null|\{})\b|\"[^"]+\":/g, 10]],
|
|
532
|
+
["yaml", [/^(\s+)?[a-z][a-z0-9]*:/gim, 10]]
|
|
533
|
+
];
|
|
534
|
+
var detectLanguage = (code) => {
|
|
535
|
+
return languages.map(
|
|
536
|
+
([lang, ...features]) => [lang, features.reduce((acc, [match, score]) => acc + [...code.matchAll(match)].length * score, 0)]
|
|
537
|
+
).filter(([_, score]) => score > 20).sort((a, b) => b[1] - a[1])[0]?.[0] || "plain";
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
// src/mdast-handlers/custom-code-handler.ts
|
|
541
|
+
var LANGUAGE_MATCH_REGEX = [/language-(\w+)/, /highlight-source-(\w+)/, /CodeBlock--language-(\w+)/];
|
|
542
|
+
var customCodeHandler = (state, node) => {
|
|
543
|
+
const classNames = node.properties?.className || [];
|
|
544
|
+
const codeValue = trimTrailingLines(toText(node)).trim();
|
|
545
|
+
const classLang = classNames.map((className) => {
|
|
546
|
+
const match = LANGUAGE_MATCH_REGEX.map((regex) => className.match(regex)).find((match2) => match2);
|
|
547
|
+
return match?.[1];
|
|
548
|
+
}).find((className) => className);
|
|
549
|
+
const lang = classLang || detectLanguage(codeValue) || null;
|
|
550
|
+
const result = { type: "code", lang, meta: null, value: codeValue };
|
|
551
|
+
state.patch(node, result);
|
|
552
|
+
return result;
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// src/mdast-handlers/custom-div-handler.ts
|
|
556
|
+
import { select as select3 } from "hast-util-select";
|
|
557
|
+
import { defaultHandlers as defaultHandlers2 } from "hast-util-to-mdast";
|
|
558
|
+
import { toString as hastToString3 } from "hast-util-to-string";
|
|
559
|
+
import { toText as toText2 } from "hast-util-to-text";
|
|
560
|
+
import { trimTrailingLines as trimTrailingLines2 } from "trim-trailing-lines";
|
|
561
|
+
var CODE_BLOCK_REGEX = /highlight-source|language-|codegroup|codeblock|code-block/i;
|
|
562
|
+
var CODE_FILENAME_SELECTORS = "[class*='fileName'],[class*='fileName'],[class*='title'],[class*='Title']";
|
|
563
|
+
var LANGUAGE_MATCH_REGEX2 = [/language-(\w+)/, /highlight-source-(\w+)/, /CodeBlock--language-(\w+)/];
|
|
564
|
+
var findRecursive = (array, condition, maxDepth = 3) => {
|
|
565
|
+
if (maxDepth <= 0) {
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
for (const value of array) {
|
|
569
|
+
const result = condition(value);
|
|
570
|
+
if (Array.isArray(result)) {
|
|
571
|
+
return findRecursive(result, condition, maxDepth - 1);
|
|
572
|
+
}
|
|
573
|
+
if (result) {
|
|
574
|
+
return value;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
};
|
|
579
|
+
var customDivHandler = (state, node) => {
|
|
580
|
+
const classNames = Array.isArray(node.properties.className) ? node.properties.className : [];
|
|
581
|
+
const codeBlock = findRecursive(node.children, (child) => {
|
|
582
|
+
if (child.type !== "element") {
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
if (child.tagName === "pre") {
|
|
586
|
+
return true;
|
|
587
|
+
}
|
|
588
|
+
return child.children.filter((child2) => child2.type === "element");
|
|
589
|
+
});
|
|
590
|
+
if (codeBlock && classNames.some((className) => CODE_BLOCK_REGEX.test(className))) {
|
|
591
|
+
const codeBlockClassNames = codeBlock.type === "element" ? codeBlock.properties.className ?? [] : [];
|
|
592
|
+
const codeValue = trimTrailingLines2(toText2(codeBlock)).trim();
|
|
593
|
+
const filenameElement = select3(CODE_FILENAME_SELECTORS, node);
|
|
594
|
+
const fileLang = filenameElement ? hastToString3(filenameElement).match(/\.(\w+)$/)?.[1] : null;
|
|
595
|
+
const classLang = [...classNames, ...codeBlockClassNames].map((className) => {
|
|
596
|
+
const match = LANGUAGE_MATCH_REGEX2.map((regex) => className.match(regex)).find((match2) => match2);
|
|
597
|
+
return match?.[1];
|
|
598
|
+
}).find((className) => className);
|
|
599
|
+
const lang = fileLang || classLang || detectLanguage(codeValue) || null;
|
|
600
|
+
const result = { type: "code", lang, meta: null, value: codeValue };
|
|
601
|
+
state.patch(node, result);
|
|
602
|
+
return result;
|
|
603
|
+
}
|
|
604
|
+
return defaultHandlers2.div(state, node);
|
|
605
|
+
};
|
|
2
606
|
|
|
3
|
-
// src/
|
|
607
|
+
// src/mdast-handlers/custom-img-handler.ts
|
|
608
|
+
import { defaultHandlers as defaultHandlers3 } from "hast-util-to-mdast";
|
|
609
|
+
var customImgHandler = (options) => (state, node) => {
|
|
610
|
+
if (options?.hideImage) {
|
|
611
|
+
return void 0;
|
|
612
|
+
}
|
|
613
|
+
return defaultHandlers3.image(state, node);
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
// src/mdast-handlers/custom-table-handler.ts
|
|
617
|
+
import { defaultHandlers as defaultHandlers4 } from "hast-util-to-mdast";
|
|
618
|
+
import { toText as toText3 } from "hast-util-to-text";
|
|
619
|
+
var customTableHandler = (options) => (state, node) => {
|
|
620
|
+
if (options?.asText) {
|
|
621
|
+
const paragraph = { type: "paragraph", children: [{ type: "text", value: toText3(node) }] };
|
|
622
|
+
state.patch(node, paragraph);
|
|
623
|
+
return paragraph;
|
|
624
|
+
}
|
|
625
|
+
return defaultHandlers4.table(state, node);
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
// src/mdast-handlers/empty-handler.ts
|
|
629
|
+
var emptyHandler = () => {
|
|
630
|
+
};
|
|
631
|
+
|
|
632
|
+
// src/mdast-handlers/math-handler.ts
|
|
633
|
+
import { toHtml } from "hast-util-to-html";
|
|
634
|
+
import { MathMLToLaTeX } from "mathml-to-latex";
|
|
635
|
+
var mathHandler = (state, node) => {
|
|
636
|
+
const mathMl = toHtml(node);
|
|
637
|
+
const latex = MathMLToLaTeX.convert(mathMl);
|
|
638
|
+
const result = { type: "inlineMath", value: latex };
|
|
639
|
+
state.patch(node, result);
|
|
640
|
+
return result;
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// src/html-to-mdast.ts
|
|
644
|
+
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);
|
|
648
|
+
const mdast = toMdast(extractedHast, {
|
|
649
|
+
handlers: {
|
|
650
|
+
math: mathHandler,
|
|
651
|
+
div: customDivHandler,
|
|
652
|
+
pre: customCodeHandler,
|
|
653
|
+
a: customAHandler({ asText: options?.linkAsText }),
|
|
654
|
+
img: customImgHandler({ hideImage: options?.hideImage }),
|
|
655
|
+
table: customTableHandler({ asText: options?.tableAsText }),
|
|
656
|
+
br: emptyHandler
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
const extractedMdast = extractMdast(mdast);
|
|
660
|
+
return extractedMdast;
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
// src/mdast-to-markdown.ts
|
|
664
|
+
import { gfmToMarkdown } from "mdast-util-gfm";
|
|
665
|
+
import { mathToMarkdown } from "mdast-util-math";
|
|
666
|
+
import { toMarkdown } from "mdast-util-to-markdown";
|
|
667
|
+
|
|
668
|
+
// 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("#")) {
|
|
673
|
+
return match;
|
|
674
|
+
}
|
|
675
|
+
return `${pre}${new URL(thisUrl, link).href}${post}`;
|
|
676
|
+
});
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// src/utils/mdast-utils.ts
|
|
680
|
+
var warpRoot = (mdast) => {
|
|
681
|
+
if (Array.isArray(mdast)) {
|
|
682
|
+
return { type: "root", children: mdast };
|
|
683
|
+
}
|
|
684
|
+
return mdast;
|
|
685
|
+
};
|
|
686
|
+
|
|
687
|
+
// src/mdast-to-markdown.ts
|
|
688
|
+
var DEFAULT_MDAST_TO_MARKDOWN_OPTIONS = {
|
|
689
|
+
extensions: [gfmToMarkdown(), mathToMarkdown()],
|
|
690
|
+
bullet: "-"
|
|
691
|
+
};
|
|
692
|
+
var mdastToMarkdown = (mdast, options) => {
|
|
693
|
+
const { baseUrl, ...toMarkdownOptions } = { ...DEFAULT_MDAST_TO_MARKDOWN_OPTIONS, ...options };
|
|
694
|
+
let markdown = toMarkdown(warpRoot(mdast), toMarkdownOptions);
|
|
695
|
+
if (baseUrl) {
|
|
696
|
+
markdown = linkReplacer(markdown, baseUrl);
|
|
697
|
+
}
|
|
698
|
+
return markdown;
|
|
699
|
+
};
|
|
700
|
+
|
|
701
|
+
// src/html-to-markdown.ts
|
|
702
|
+
var htmlToMarkdown = (htmlOrHast, options) => {
|
|
703
|
+
const { baseUrl, formatting: toMarkdownOptions, ...toMdastOptions } = options || {};
|
|
704
|
+
const mdast = htmlToMdast(htmlOrHast, toMdastOptions);
|
|
705
|
+
const markdown = mdastToMarkdown(mdast, { baseUrl, ...toMarkdownOptions });
|
|
706
|
+
return markdown;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// src/cli/helpers/inputOutputPath.ts
|
|
710
|
+
import fs2 from "node:fs";
|
|
711
|
+
import { confirm, text } from "@clack/prompts";
|
|
712
|
+
|
|
713
|
+
// src/cli/utils.ts
|
|
4
714
|
import fs from "node:fs";
|
|
5
715
|
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import { changeFileExtension, getNextAvailableFilePath, isUrl, urlToFilename } from "./utils/bin-utils.js";
|
|
14
|
-
var MODES = ["default", "ai"];
|
|
15
|
-
var LOADERS = ["fetch", "playwright", "puppeteer"];
|
|
16
|
-
var loadersHint = {
|
|
17
|
-
fetch: "Fetch HTML content from the given URL",
|
|
18
|
-
playwright: "Retrieve HTML content after rendering using Playwright; Playwright must be installed in advance.",
|
|
19
|
-
puppeteer: "Retrieve HTML content after rendering using Puppeteer; Puppeteer must be installed in advance."
|
|
716
|
+
var isUrl = (maybeUrl) => {
|
|
717
|
+
try {
|
|
718
|
+
new URL(maybeUrl);
|
|
719
|
+
return true;
|
|
720
|
+
} catch {
|
|
721
|
+
return false;
|
|
722
|
+
}
|
|
20
723
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
placeholder: "https://example.com",
|
|
46
|
-
initialValue: source,
|
|
47
|
-
validate: (value) => {
|
|
48
|
-
if (value.trim() === "") {
|
|
49
|
-
return "Source is required";
|
|
50
|
-
}
|
|
51
|
-
if (!isUrl(value)) {
|
|
52
|
-
if (!fs.existsSync(value)) {
|
|
53
|
-
return "It appears that you are specifying a local file, but the file cannot be found. hint: when specifying a url, start with http or https.";
|
|
54
|
-
}
|
|
55
|
-
if (fs.statSync(value).isDirectory()) {
|
|
56
|
-
return "You are specifying a local file, but you cannot specify a directory. hint: when specifying a url, start with http or https.";
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
if (isCancel(finalSource)) {
|
|
62
|
-
cancel("Canceled.");
|
|
63
|
-
return;
|
|
724
|
+
function changeFileExtension(filePath, newExtension) {
|
|
725
|
+
const parsedPath = filePath.split("/");
|
|
726
|
+
const fileName = parsedPath[parsedPath.length - 1];
|
|
727
|
+
const formattedNewExtension = newExtension.startsWith(".") ? newExtension : `.${newExtension}`;
|
|
728
|
+
if (fileName.startsWith(".")) {
|
|
729
|
+
const parts = fileName.split(".");
|
|
730
|
+
if (parts.length === 2) {
|
|
731
|
+
return parsedPath.slice(0, -1).concat(`${fileName}${formattedNewExtension}`).join("/");
|
|
732
|
+
}
|
|
733
|
+
parts[parts.length - 1] = newExtension.replace(/^\./, "");
|
|
734
|
+
return parsedPath.slice(0, -1).concat(parts.join(".")).join("/");
|
|
735
|
+
}
|
|
736
|
+
const lastDotIndex = fileName.lastIndexOf(".");
|
|
737
|
+
const baseName = lastDotIndex !== -1 ? fileName.slice(0, lastDotIndex) : fileName;
|
|
738
|
+
const newFileName = `${baseName}${formattedNewExtension}`;
|
|
739
|
+
parsedPath[parsedPath.length - 1] = newFileName;
|
|
740
|
+
return parsedPath.join("/");
|
|
741
|
+
}
|
|
742
|
+
function urlToFilename(url) {
|
|
743
|
+
try {
|
|
744
|
+
const urlObj = new URL(url);
|
|
745
|
+
const domainParts = urlObj.hostname.split(".").reverse().reduce((acc, part, index) => {
|
|
746
|
+
if (index === 0) {
|
|
747
|
+
return acc;
|
|
64
748
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
finalLoader = await select({
|
|
68
|
-
message: "Select loader:",
|
|
69
|
-
options: LOADERS.map((mode) => ({ value: mode, label: mode, hint: loadersHint[mode] || "" })),
|
|
70
|
-
initialValue: options.loader && LOADERS.includes(options.loader) ? options.loader : "fetch"
|
|
71
|
-
});
|
|
72
|
-
if (isCancel(finalLoader)) {
|
|
73
|
-
cancel("Canceled.");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
749
|
+
if (acc.length >= 2) {
|
|
750
|
+
return acc;
|
|
76
751
|
}
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
message: "Enter the output file path:",
|
|
80
|
-
placeholder: "output.md",
|
|
81
|
-
initialValue: outputPath ? outputPath : sourceIsUrl ? `${urlToFilename(finalSource)}.md` : changeFileExtension(finalSource, "md"),
|
|
82
|
-
validate: (value) => {
|
|
83
|
-
if (value.trim() === "") {
|
|
84
|
-
return "Output path is required";
|
|
85
|
-
}
|
|
86
|
-
if (fs.existsSync(value) && fs.statSync(value).isDirectory()) {
|
|
87
|
-
return "No directory can be specified.";
|
|
88
|
-
}
|
|
89
|
-
if (!fs.existsSync(value) && value.endsWith("/")) {
|
|
90
|
-
return "No directory can be specified.";
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
if (isCancel(finalOutputPath)) {
|
|
95
|
-
cancel("Canceled.");
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
if (fs.existsSync(finalOutputPath)) {
|
|
99
|
-
const isOutputFileOverwrite = await confirm({
|
|
100
|
-
message: "The file already exists. Overwrite?",
|
|
101
|
-
initialValue: false
|
|
102
|
-
});
|
|
103
|
-
if (isCancel(isOutputFileOverwrite)) {
|
|
104
|
-
cancel("Canceled.");
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (isOutputFileOverwrite === false) {
|
|
108
|
-
finalOutputPath = await text({
|
|
109
|
-
message: "Enter the output file path:",
|
|
110
|
-
placeholder: "output.md",
|
|
111
|
-
initialValue: getNextAvailableFilePath(finalOutputPath),
|
|
112
|
-
validate: (value) => {
|
|
113
|
-
if (value.trim() === "") {
|
|
114
|
-
return "Output path is required";
|
|
115
|
-
}
|
|
116
|
-
if (fs.existsSync(value)) {
|
|
117
|
-
return "The file already exists";
|
|
118
|
-
}
|
|
119
|
-
if (!fs.existsSync(value) && value.endsWith("/")) {
|
|
120
|
-
return "No directory can be specified.";
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
if (isCancel(finalOutputPath)) {
|
|
125
|
-
cancel("Canceled.");
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
} else {
|
|
131
|
-
finalOutputPath = "";
|
|
752
|
+
if (part === "www") {
|
|
753
|
+
return acc;
|
|
132
754
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
755
|
+
return [part, ...acc];
|
|
756
|
+
}, []);
|
|
757
|
+
const domainString = domainParts.reverse().join("-");
|
|
758
|
+
const pathParts = urlObj.pathname.split("/").filter(Boolean);
|
|
759
|
+
const relevantPathParts = pathParts.slice(-2);
|
|
760
|
+
const pathString = relevantPathParts.map((part) => decodeURIComponent(part)).join("-");
|
|
761
|
+
let filename = [domainString, pathString].filter(Boolean).join("-");
|
|
762
|
+
filename = filename.toLowerCase().replace(/[<>:"/\\|?*\x00-\x1F]/g, "").replace(/[\s.]+/g, "-").replace(/^-+|-+$/g, "");
|
|
763
|
+
return filename || "output";
|
|
764
|
+
} catch {
|
|
765
|
+
return "output";
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
var sourcePathToOutputPath = (sourcePath) => {
|
|
769
|
+
return isUrl(sourcePath) ? `${urlToFilename(sourcePath)}.md` : changeFileExtension(sourcePath, "md");
|
|
770
|
+
};
|
|
771
|
+
function getNextAvailableFilePath(filePath) {
|
|
772
|
+
const parsedPath = path.parse(filePath);
|
|
773
|
+
const directory = parsedPath.dir;
|
|
774
|
+
const fullName = parsedPath.base;
|
|
775
|
+
const [firstPart, ...restParts] = fullName.split(".");
|
|
776
|
+
const restName = restParts.length > 0 ? `.${restParts.join(".")}` : "";
|
|
777
|
+
const baseName = firstPart.replace(/_\d+$/, "");
|
|
778
|
+
let counter = 1;
|
|
779
|
+
let nextFilePath = filePath;
|
|
780
|
+
while (fs.existsSync(nextFilePath)) {
|
|
781
|
+
const match = firstPart.match(/_(\d+)$/);
|
|
782
|
+
if (match) {
|
|
783
|
+
counter = Number.parseInt(match[1], 10) + 1;
|
|
784
|
+
}
|
|
785
|
+
const newName = `${baseName}_${counter}${restName}`;
|
|
786
|
+
nextFilePath = path.join(directory, newName);
|
|
787
|
+
counter++;
|
|
788
|
+
}
|
|
789
|
+
return nextFilePath;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// src/cli/helpers/assertContinue.ts
|
|
793
|
+
import { cancel, isCancel } from "@clack/prompts";
|
|
794
|
+
function assertContinue(message, cancelMessage = "Canceled.") {
|
|
795
|
+
if (isCancel(message)) {
|
|
796
|
+
cancel(cancelMessage);
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/cli/helpers/inputOutputPath.ts
|
|
802
|
+
var inputOutputPath = async (sourcePath) => {
|
|
803
|
+
const outputPath = await text({
|
|
804
|
+
message: "Enter the output file path:",
|
|
805
|
+
placeholder: "output.md",
|
|
806
|
+
initialValue: sourcePathToOutputPath(sourcePath),
|
|
807
|
+
validate: (value) => {
|
|
808
|
+
if (value.trim() === "") {
|
|
809
|
+
return "Output path is required";
|
|
141
810
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
console.error(pc.red("An error occurred while processing the given value:"), error.message);
|
|
145
|
-
if (process.env.DEBUG) {
|
|
146
|
-
console.error(error.stack);
|
|
811
|
+
if (fs2.existsSync(value) && fs2.statSync(value).isDirectory()) {
|
|
812
|
+
return "No directory can be specified.";
|
|
147
813
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (process.env.DEBUG) {
|
|
151
|
-
console.info("Processing with the following options:");
|
|
152
|
-
console.table({ finalSource, sourceIsUrl, isOutputFile, finalOutputPath, finalMode, finalLoader });
|
|
153
|
-
}
|
|
154
|
-
let content;
|
|
155
|
-
const loadSpinner = spinner();
|
|
156
|
-
try {
|
|
157
|
-
if (sourceIsUrl) {
|
|
158
|
-
loadSpinner.start("Downloading content.");
|
|
159
|
-
if (finalLoader === "playwright") {
|
|
160
|
-
content = await playwrightLoadHtml(finalSource);
|
|
161
|
-
} else if (finalLoader === "puppeteer") {
|
|
162
|
-
content = await puppeteerLoadHtml(finalSource);
|
|
163
|
-
} else {
|
|
164
|
-
const response = await fetch(finalSource);
|
|
165
|
-
content = await response.text();
|
|
166
|
-
}
|
|
167
|
-
loadSpinner.stop("Content download is complete!");
|
|
168
|
-
} else {
|
|
169
|
-
loadSpinner.start("Retrieving content.");
|
|
170
|
-
if (!fs.existsSync(finalSource)) {
|
|
171
|
-
console.error(pc.red("The file does not exist"));
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
const stats = fs.statSync(finalSource);
|
|
175
|
-
if (!stats.isFile()) {
|
|
176
|
-
console.error(pc.red("The source must be a file"));
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
content = fs.readFileSync(finalSource, "utf-8");
|
|
180
|
-
loadSpinner.stop("Content retrieval is complete!");
|
|
814
|
+
if (!fs2.existsSync(value) && value.endsWith("/")) {
|
|
815
|
+
return "No directory can be specified.";
|
|
181
816
|
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
assertContinue(outputPath);
|
|
820
|
+
if (!fs2.existsSync(outputPath)) {
|
|
821
|
+
return outputPath;
|
|
822
|
+
}
|
|
823
|
+
const isOutputFileOverwrite = await confirm({
|
|
824
|
+
message: "The file already exists. Overwrite?",
|
|
825
|
+
initialValue: false
|
|
826
|
+
});
|
|
827
|
+
assertContinue(isOutputFileOverwrite);
|
|
828
|
+
if (isOutputFileOverwrite) {
|
|
829
|
+
return outputPath;
|
|
830
|
+
}
|
|
831
|
+
const escapedOutputPath = await text({
|
|
832
|
+
message: "Enter the output file path:",
|
|
833
|
+
placeholder: "output.md",
|
|
834
|
+
initialValue: getNextAvailableFilePath(outputPath),
|
|
835
|
+
validate: (value) => {
|
|
836
|
+
if (value.trim() === "") {
|
|
837
|
+
return "Output path is required";
|
|
188
838
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (process.env.DEBUG) {
|
|
192
|
-
console.info("Content:");
|
|
193
|
-
console.info(content);
|
|
194
|
-
}
|
|
195
|
-
const convertSpinner = spinner();
|
|
196
|
-
try {
|
|
197
|
-
convertSpinner.start("Converting content to markdown.");
|
|
198
|
-
const markdown = htmlToMarkdown(content, {
|
|
199
|
-
...sourceIsUrl || !!options.baseUrl && options.baseUrl.trim() !== "" ? { baseUrl: options.baseUrl || finalSource } : {},
|
|
200
|
-
...finalMode === "ai" ? aiModeOptions : readabilityModeOptions
|
|
201
|
-
});
|
|
202
|
-
if (process.env.DEBUG) {
|
|
203
|
-
console.info("Markdown:");
|
|
204
|
-
console.info(markdown);
|
|
839
|
+
if (fs2.existsSync(value)) {
|
|
840
|
+
return "The file already exists";
|
|
205
841
|
}
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
if (!fs.existsSync(directory)) {
|
|
209
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
210
|
-
}
|
|
211
|
-
fs.writeFileSync(finalOutputPath, markdown);
|
|
212
|
-
convertSpinner.stop("Markdown conversion is complete!");
|
|
213
|
-
outro(`Converted successfully to markdown! Output saved to: ${path.resolve(finalOutputPath)} \u{1F389}`);
|
|
214
|
-
} else {
|
|
215
|
-
convertSpinner.stop("Markdown conversion is complete!");
|
|
216
|
-
outro("Converted successfully to markdown\u{1F389}");
|
|
217
|
-
console.log(`
|
|
218
|
-
|
|
219
|
-
${markdown}`);
|
|
842
|
+
if (!fs2.existsSync(value) && value.endsWith("/")) {
|
|
843
|
+
return "No directory can be specified.";
|
|
220
844
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
845
|
+
}
|
|
846
|
+
});
|
|
847
|
+
assertContinue(escapedOutputPath);
|
|
848
|
+
return escapedOutputPath;
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// src/cli/helpers/inputSourcePath.ts
|
|
852
|
+
import fs3 from "node:fs";
|
|
853
|
+
import { text as text2 } from "@clack/prompts";
|
|
854
|
+
|
|
855
|
+
// src/cli/constants.ts
|
|
856
|
+
var DEFAULT_PATH = "https://example.com";
|
|
857
|
+
var LOADERS = ["fetch", "playwright"];
|
|
858
|
+
var MODES = ["default", "ai"];
|
|
859
|
+
|
|
860
|
+
// src/cli/helpers/inputSourcePath.ts
|
|
861
|
+
var inputSourcePath = async () => {
|
|
862
|
+
const result = await text2({
|
|
863
|
+
message: "Enter the URL or html path to be converted to markdown:",
|
|
864
|
+
placeholder: DEFAULT_PATH,
|
|
865
|
+
initialValue: "",
|
|
866
|
+
validate: (value) => {
|
|
867
|
+
if (value.trim() === "") {
|
|
868
|
+
return "Source is required";
|
|
869
|
+
}
|
|
870
|
+
if (!isUrl(value)) {
|
|
871
|
+
if (!fs3.existsSync(value)) {
|
|
872
|
+
return "It appears that you are specifying a local file, but the file cannot be found. hint: when specifying a url, start with http or https.";
|
|
873
|
+
}
|
|
874
|
+
if (fs3.statSync(value).isDirectory()) {
|
|
875
|
+
return "You are specifying a local file, but you cannot specify a directory. hint: when specifying a url, start with http or https.";
|
|
876
|
+
}
|
|
227
877
|
}
|
|
228
|
-
process.exit(1);
|
|
229
878
|
}
|
|
879
|
+
});
|
|
880
|
+
assertContinue(result);
|
|
881
|
+
return result;
|
|
882
|
+
};
|
|
883
|
+
|
|
884
|
+
// src/cli/helpers/selectExtractMode.ts
|
|
885
|
+
import { select as select4 } from "@clack/prompts";
|
|
886
|
+
var selectExtractMode = async () => {
|
|
887
|
+
const result = await select4({
|
|
888
|
+
message: "Select processing mode:",
|
|
889
|
+
options: MODES.map((mode) => ({ value: mode, label: mode })),
|
|
890
|
+
initialValue: MODES[0]
|
|
891
|
+
});
|
|
892
|
+
assertContinue(result);
|
|
893
|
+
return result;
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
// src/cli/helpers/selectLoader.ts
|
|
897
|
+
import { select as select5 } from "@clack/prompts";
|
|
898
|
+
var loadersHint = {
|
|
899
|
+
fetch: "Fetch HTML content from the given URL",
|
|
900
|
+
playwright: "Retrieve HTML content after rendering using Playwright; Playwright must be installed in advance.",
|
|
901
|
+
puppeteer: "Retrieve HTML content after rendering using Puppeteer; Puppeteer must be installed in advance."
|
|
902
|
+
};
|
|
903
|
+
var selectLoader = async () => {
|
|
904
|
+
const result = await select5({
|
|
905
|
+
message: "Select loader:",
|
|
906
|
+
initialValue: "fetch",
|
|
907
|
+
options: LOADERS.map((mode) => ({ value: mode, label: mode, hint: loadersHint[mode] || "" }))
|
|
908
|
+
});
|
|
909
|
+
assertContinue(result);
|
|
910
|
+
return result;
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
// src/cli/commands/handlers/loadHtml.ts
|
|
914
|
+
import fs4 from "node:fs/promises";
|
|
915
|
+
import { cancel as cancel2, spinner as clackSpinner, confirm as confirm2, spinner } from "@clack/prompts";
|
|
916
|
+
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
|
+
}
|
|
941
|
+
};
|
|
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;
|
|
956
|
+
};
|
|
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;
|
|
963
|
+
};
|
|
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;
|
|
982
|
+
};
|
|
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;
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
// src/cli/commands/handlers/index.ts
|
|
1006
|
+
var aiModeOptions = { linkAsText: true, tableAsText: true, hideImage: true };
|
|
1007
|
+
var readabilityModeOptions = { linkAsText: false, tableAsText: false, hideImage: false };
|
|
1008
|
+
var webforaiCommand = async (initialPath, initialOutputPath, options) => {
|
|
1009
|
+
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
|
+
}
|
|
1021
|
+
const mode = options.mode ?? await selectExtractMode();
|
|
1022
|
+
if (options.debug) {
|
|
1023
|
+
console.debug(`mode: ${mode}`);
|
|
230
1024
|
}
|
|
231
|
-
);
|
|
232
|
-
|
|
1025
|
+
const content = await loadHtml3(sourcePath, loader);
|
|
1026
|
+
if (options.debug) {
|
|
1027
|
+
console.debug(`content: ${content}`);
|
|
1028
|
+
}
|
|
1029
|
+
const markdown = htmlToMarkdown(content, {
|
|
1030
|
+
baseUrl: isUrl(sourcePath) ? sourcePath : options.baseUrl,
|
|
1031
|
+
...mode === "ai" ? aiModeOptions : readabilityModeOptions
|
|
1032
|
+
});
|
|
1033
|
+
if (options.debug) {
|
|
1034
|
+
console.debug(`markdown: ${markdown}`);
|
|
1035
|
+
}
|
|
1036
|
+
const directory = path2.dirname(outputPath);
|
|
1037
|
+
if (!fs5.existsSync(directory)) {
|
|
1038
|
+
fs5.mkdirSync(directory, { recursive: true });
|
|
1039
|
+
}
|
|
1040
|
+
fs5.writeFileSync(outputPath, markdown);
|
|
1041
|
+
outro("Markdown conversion is complete!");
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
// 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}`));
|
|
233
1049
|
program.parse();
|