@webcrawl/mcp 1.4.1 → 1.4.3
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/README.md +1 -1
- package/dist/bin.js +5 -7
- package/dist/core/engine/awswaf.d.ts +59 -0
- package/dist/core/engine/awswaf.js +164 -0
- package/dist/core/engine/cache.d.ts +70 -0
- package/dist/core/engine/cache.js +297 -0
- package/dist/core/engine/chrome.d.ts +14 -0
- package/dist/core/engine/chrome.js +140 -0
- package/dist/core/engine/crawl-policy.d.ts +36 -0
- package/dist/core/engine/crawl-policy.js +134 -0
- package/dist/core/engine/crawler.d.ts +152 -0
- package/dist/core/engine/crawler.js +918 -0
- package/dist/core/engine/fetcher.d.ts +92 -0
- package/dist/core/engine/fetcher.js +494 -0
- package/dist/core/engine/index.d.ts +7 -0
- package/dist/core/engine/index.js +7 -0
- package/dist/core/engine/markdown.d.ts +33 -0
- package/dist/core/engine/markdown.js +334 -0
- package/dist/core/engine/path-scope.d.ts +21 -0
- package/dist/core/engine/path-scope.js +88 -0
- package/dist/core/engine/render.d.ts +41 -0
- package/dist/core/engine/render.js +138 -0
- package/dist/core/engine/safe-url.d.ts +29 -0
- package/dist/core/engine/safe-url.js +109 -0
- package/dist/core/engine/session.d.ts +93 -0
- package/dist/core/engine/session.js +250 -0
- package/dist/core/engine/stealth.d.ts +83 -0
- package/dist/core/engine/stealth.js +222 -0
- package/dist/core/engine.d.ts +1 -0
- package/dist/core/engine.js +1 -0
- package/dist/core/extract/business.d.ts +28 -0
- package/dist/core/extract/business.js +344 -0
- package/dist/core/extract/currency.d.ts +15 -0
- package/dist/core/extract/currency.js +259 -0
- package/dist/core/extract/cve-cache.d.ts +62 -0
- package/dist/core/extract/cve-cache.js +277 -0
- package/dist/core/extract/cve.d.ts +57 -0
- package/dist/core/extract/cve.js +397 -0
- package/dist/core/extract/dedupe-key.d.ts +12 -0
- package/dist/core/extract/dedupe-key.js +36 -0
- package/dist/core/extract/flights.d.ts +37 -0
- package/dist/core/extract/flights.js +373 -0
- package/dist/core/extract/hotels.d.ts +35 -0
- package/dist/core/extract/hotels.js +473 -0
- package/dist/core/extract/index.d.ts +9 -0
- package/dist/core/extract/index.js +9 -0
- package/dist/core/extract/real-estate.d.ts +53 -0
- package/dist/core/extract/real-estate.js +835 -0
- package/dist/core/extract/scaledown.d.ts +14 -0
- package/dist/core/extract/scaledown.js +484 -0
- package/dist/core/extract/shopping.d.ts +44 -0
- package/dist/core/extract/shopping.js +393 -0
- package/dist/core/extract/structured.d.ts +14 -0
- package/dist/core/extract/structured.js +144 -0
- package/dist/core/image-url.d.ts +5 -0
- package/dist/core/image-url.js +13 -0
- package/dist/core/interact/driver.d.ts +72 -0
- package/dist/core/interact/driver.js +384 -0
- package/dist/core/interact/remote.d.ts +41 -0
- package/dist/core/interact/remote.js +133 -0
- package/dist/core/interact/spans.d.ts +46 -0
- package/dist/core/interact/spans.js +32 -0
- package/dist/core/interact/validate.d.ts +34 -0
- package/dist/core/interact/validate.js +135 -0
- package/dist/core/interact/vm.d.ts +43 -0
- package/dist/core/interact/vm.js +338 -0
- package/dist/core/local-engine.d.ts +56 -0
- package/dist/core/local-engine.js +667 -0
- package/dist/core/parse/detect.d.ts +29 -0
- package/dist/core/parse/detect.js +152 -0
- package/dist/core/parse/fetch.d.ts +20 -0
- package/dist/core/parse/fetch.js +39 -0
- package/dist/core/parse/image.d.ts +13 -0
- package/dist/core/parse/image.js +100 -0
- package/dist/core/parse/index.d.ts +53 -0
- package/dist/core/parse/index.js +153 -0
- package/dist/core/parse/limits.d.ts +12 -0
- package/dist/core/parse/limits.js +29 -0
- package/dist/core/parse/media.d.ts +42 -0
- package/dist/core/parse/media.js +238 -0
- package/dist/core/parse/merge.d.ts +19 -0
- package/dist/core/parse/merge.js +48 -0
- package/dist/core/parse/ocr.d.ts +25 -0
- package/dist/core/parse/ocr.js +121 -0
- package/dist/core/parse/office.d.ts +28 -0
- package/dist/core/parse/office.js +83 -0
- package/dist/core/parse/pdf.d.ts +27 -0
- package/dist/core/parse/pdf.js +156 -0
- package/dist/core/parse/types.d.ts +38 -0
- package/dist/core/parse/types.js +1 -0
- package/dist/core/parse/youtube.d.ts +30 -0
- package/dist/core/parse/youtube.js +264 -0
- package/dist/core/ranker/decision.d.ts +28 -0
- package/dist/core/ranker/decision.js +151 -0
- package/dist/core/ranker/index.d.ts +5 -0
- package/dist/core/ranker/index.js +361 -0
- package/dist/core/ranker/lexical.d.ts +82 -0
- package/dist/core/ranker/lexical.js +643 -0
- package/dist/core/ranker/types.d.ts +90 -0
- package/dist/core/ranker/types.js +1 -0
- package/dist/core/ranker.d.ts +1 -0
- package/dist/core/ranker.js +1 -0
- package/dist/core/sandbox.d.ts +29 -0
- package/dist/core/sandbox.js +222 -0
- package/dist/core/search/catalog.d.ts +176 -0
- package/dist/core/search/catalog.js +488 -0
- package/dist/core/search/index.d.ts +3 -0
- package/dist/core/search/index.js +3 -0
- package/dist/core/search/pipeline.d.ts +7 -0
- package/dist/core/search/pipeline.js +719 -0
- package/dist/core/search/providers.d.ts +72 -0
- package/dist/core/search/providers.js +1270 -0
- package/dist/core/search.d.ts +1 -0
- package/dist/core/search.js +1 -0
- package/dist/core/targets/developer.d.ts +44 -0
- package/dist/core/targets/developer.js +338 -0
- package/dist/core/targets/index.d.ts +3 -0
- package/dist/core/targets/index.js +3 -0
- package/dist/core/targets/registry.d.ts +32 -0
- package/dist/core/targets/registry.js +411 -0
- package/dist/core/targets/types.d.ts +72 -0
- package/dist/core/targets/types.js +8 -0
- package/dist/core/targets.d.ts +1 -0
- package/dist/core/targets.js +1 -0
- package/dist/format.js +8 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/load-local-engine.d.ts +7 -0
- package/dist/load-local-engine.js +22 -0
- package/dist/server.js +7 -0
- package/package.json +11 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Chrome dump-dom renderer (JS pages the native fetch ladder cannot hydrate).
|
|
3
|
+
*
|
|
4
|
+
* Uses a user-installed Chrome/Chromium binary — the same one `webcrawl doctor`
|
|
5
|
+
* reports — with `--dump-dom` + virtual time so `document.write` / client-rendered
|
|
6
|
+
* content appears in the HTML. Playwright is not required; this is the zero-key
|
|
7
|
+
* local JS tier.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
11
|
+
import { tmpdir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
const DUMP_MAX_BYTES = 2 * 1024 * 1024;
|
|
14
|
+
const DEFAULT_TIMEOUT_MS = 20_000;
|
|
15
|
+
const VIRTUAL_TIME_MS = 10_000;
|
|
16
|
+
/**
|
|
17
|
+
* Path to a real installed Chrome/Chromium, or null.
|
|
18
|
+
* Priority: WEBCRAWL_CHROME_PATH, INTERACT_CHROME_PATH, CHROME_PATH, then per-OS
|
|
19
|
+
* defaults (same locations `webcrawl doctor` checks).
|
|
20
|
+
*/
|
|
21
|
+
export function installedChromePath() {
|
|
22
|
+
const fromEnv = process.env.WEBCRAWL_CHROME_PATH?.trim() ||
|
|
23
|
+
process.env.INTERACT_CHROME_PATH?.trim() ||
|
|
24
|
+
process.env.CHROME_PATH?.trim();
|
|
25
|
+
if (fromEnv)
|
|
26
|
+
return fromEnv;
|
|
27
|
+
const candidates = [];
|
|
28
|
+
if (process.platform === "darwin") {
|
|
29
|
+
candidates.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", `${process.env.HOME || ""}/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`);
|
|
30
|
+
}
|
|
31
|
+
else if (process.platform === "win32") {
|
|
32
|
+
candidates.push(`${process.env["ProgramFiles"] || "C:\\Program Files"}\\Google\\Chrome\\Application\\chrome.exe`, `${process.env["ProgramFiles(x86)"] || "C:\\Program Files (x86)"}\\Google\\Chrome\\Application\\chrome.exe`);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
candidates.push("/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium-browser", "/usr/bin/chromium", "/snap/bin/chromium");
|
|
36
|
+
}
|
|
37
|
+
for (const p of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
if (p && existsSync(p))
|
|
40
|
+
return p;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
/* not installed here */
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
export function isLocalChromeAvailable() {
|
|
49
|
+
return installedChromePath() !== null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Render `url` in local Chrome and return the hydrated HTML, or null when
|
|
53
|
+
* Chrome is missing, times out, or produces nothing.
|
|
54
|
+
*/
|
|
55
|
+
export async function renderViaLocalChrome(url, opts = {}) {
|
|
56
|
+
const chrome = installedChromePath();
|
|
57
|
+
if (!chrome)
|
|
58
|
+
return null;
|
|
59
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
60
|
+
let profile = "";
|
|
61
|
+
try {
|
|
62
|
+
profile = mkdtempSync(join(tmpdir(), "wc-chrome-"));
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const args = [
|
|
68
|
+
"--headless=new",
|
|
69
|
+
"--no-sandbox",
|
|
70
|
+
"--disable-gpu",
|
|
71
|
+
"--disable-dev-shm-usage",
|
|
72
|
+
"--no-first-run",
|
|
73
|
+
"--no-default-browser-check",
|
|
74
|
+
"--disable-crash-reporter",
|
|
75
|
+
"--disable-breakpad",
|
|
76
|
+
`--user-data-dir=${profile}`,
|
|
77
|
+
`--virtual-time-budget=${VIRTUAL_TIME_MS}`,
|
|
78
|
+
`--timeout=${timeoutMs}`,
|
|
79
|
+
"--dump-dom",
|
|
80
|
+
url,
|
|
81
|
+
];
|
|
82
|
+
try {
|
|
83
|
+
return await new Promise((resolve) => {
|
|
84
|
+
let settled = false;
|
|
85
|
+
const done = (value) => {
|
|
86
|
+
if (settled)
|
|
87
|
+
return;
|
|
88
|
+
settled = true;
|
|
89
|
+
resolve(value);
|
|
90
|
+
};
|
|
91
|
+
let child;
|
|
92
|
+
try {
|
|
93
|
+
child = spawn(chrome, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
done(null);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const chunks = [];
|
|
100
|
+
let bytes = 0;
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
try {
|
|
103
|
+
child.kill("SIGKILL");
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
/* already gone */
|
|
107
|
+
}
|
|
108
|
+
done(null);
|
|
109
|
+
}, timeoutMs + 2_000);
|
|
110
|
+
child.stdout?.on("data", (c) => {
|
|
111
|
+
if (bytes >= DUMP_MAX_BYTES)
|
|
112
|
+
return;
|
|
113
|
+
const take = c.subarray(0, DUMP_MAX_BYTES - bytes);
|
|
114
|
+
chunks.push(take);
|
|
115
|
+
bytes += take.length;
|
|
116
|
+
});
|
|
117
|
+
child.on("error", () => {
|
|
118
|
+
clearTimeout(timer);
|
|
119
|
+
done(null);
|
|
120
|
+
});
|
|
121
|
+
child.on("close", () => {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
if (bytes === 0) {
|
|
124
|
+
done(null);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
128
|
+
done(text.trim().length > 0 ? text : null);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
try {
|
|
134
|
+
rmSync(profile, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
/* leftover profile is in /tmp */
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crawl politeness helpers: robots.txt rules, adaptive concurrency (AIMD) and retries.
|
|
3
|
+
* Pure and dependency-free so they are easy to test.
|
|
4
|
+
*/
|
|
5
|
+
export interface RobotsRules {
|
|
6
|
+
allows(url: string): boolean;
|
|
7
|
+
/** Crawl-delay in ms for our agent (or `*`), 0 when unset. */
|
|
8
|
+
crawlDelayMs: number;
|
|
9
|
+
}
|
|
10
|
+
/** Parses robots.txt. Our own group wins over `*`; longest matching rule wins; Allow beats Disallow on ties. */
|
|
11
|
+
export declare function parseRobots(text: string | null | undefined): RobotsRules;
|
|
12
|
+
/** Additive-increase / multiplicative-decrease concurrency controller. */
|
|
13
|
+
export declare class AdaptiveConcurrency {
|
|
14
|
+
private readonly min;
|
|
15
|
+
private readonly max;
|
|
16
|
+
private target;
|
|
17
|
+
constructor(min?: number, max?: number, initial?: number);
|
|
18
|
+
get current(): number;
|
|
19
|
+
/** Feed the outcome of a finished wave. */
|
|
20
|
+
record(outcome: {
|
|
21
|
+
ok: number;
|
|
22
|
+
failed: number;
|
|
23
|
+
throttled: boolean;
|
|
24
|
+
}): number;
|
|
25
|
+
}
|
|
26
|
+
export declare const sleep: (ms: number) => Promise<void>;
|
|
27
|
+
export declare function backoffDelayMs(attempt: number, baseMs?: number, capMs?: number, rand?: () => number): number;
|
|
28
|
+
export declare function isRetryableStatus(status: number): boolean;
|
|
29
|
+
/** Retries transient failures (network errors, 408/425/429/5xx) with jittered exponential backoff. */
|
|
30
|
+
export declare function withRetries<T extends {
|
|
31
|
+
status: number;
|
|
32
|
+
}>(run: () => Promise<T>, opts?: {
|
|
33
|
+
retries?: number;
|
|
34
|
+
baseMs?: number;
|
|
35
|
+
sleepFn?: (ms: number) => Promise<void>;
|
|
36
|
+
}): Promise<T>;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Crawl politeness helpers: robots.txt rules, adaptive concurrency (AIMD) and retries.
|
|
3
|
+
* Pure and dependency-free so they are easy to test.
|
|
4
|
+
*/
|
|
5
|
+
const ROBOT_TOKEN = "webcrawl";
|
|
6
|
+
function patternToRegExp(path) {
|
|
7
|
+
const anchored = path.endsWith("$");
|
|
8
|
+
const body = (anchored ? path.slice(0, -1) : path)
|
|
9
|
+
.replace(/[.+?^${}()|[\]\\]/g, "\\$&")
|
|
10
|
+
.replace(/\*/g, ".*");
|
|
11
|
+
return new RegExp(`^${body}${anchored ? "$" : ""}`);
|
|
12
|
+
}
|
|
13
|
+
/** Parses robots.txt. Our own group wins over `*`; longest matching rule wins; Allow beats Disallow on ties. */
|
|
14
|
+
export function parseRobots(text) {
|
|
15
|
+
const groups = [];
|
|
16
|
+
let current = null;
|
|
17
|
+
let lastWasAgent = false;
|
|
18
|
+
for (const rawLine of (text ?? "").split(/\r?\n/)) {
|
|
19
|
+
const line = rawLine.replace(/#.*$/, "").trim();
|
|
20
|
+
if (!line)
|
|
21
|
+
continue;
|
|
22
|
+
const idx = line.indexOf(":");
|
|
23
|
+
if (idx < 0)
|
|
24
|
+
continue;
|
|
25
|
+
const key = line.slice(0, idx).trim().toLowerCase();
|
|
26
|
+
const value = line.slice(idx + 1).trim();
|
|
27
|
+
if (key === "user-agent") {
|
|
28
|
+
if (!current || !lastWasAgent) {
|
|
29
|
+
current = { agents: [], rules: [], delay: 0 };
|
|
30
|
+
groups.push(current);
|
|
31
|
+
}
|
|
32
|
+
current.agents.push(value.toLowerCase());
|
|
33
|
+
lastWasAgent = true;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
lastWasAgent = false;
|
|
37
|
+
if (!current)
|
|
38
|
+
continue;
|
|
39
|
+
if (key === "disallow" || key === "allow") {
|
|
40
|
+
if (!value && key === "disallow")
|
|
41
|
+
continue; // empty Disallow = allow all
|
|
42
|
+
current.rules.push({
|
|
43
|
+
allow: key === "allow",
|
|
44
|
+
pattern: patternToRegExp(value),
|
|
45
|
+
length: value.length,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else if (key === "crawl-delay") {
|
|
49
|
+
const n = Number(value);
|
|
50
|
+
if (Number.isFinite(n) && n > 0)
|
|
51
|
+
current.delay = Math.min(n, 10) * 1000;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const mine = groups.filter((g) => g.agents.some((a) => a !== "*" && ROBOT_TOKEN.includes(a)));
|
|
55
|
+
const chosen = mine.length ? mine : groups.filter((g) => g.agents.includes("*"));
|
|
56
|
+
const rules = chosen.flatMap((g) => g.rules);
|
|
57
|
+
const crawlDelayMs = Math.max(0, ...chosen.map((g) => g.delay));
|
|
58
|
+
return {
|
|
59
|
+
crawlDelayMs,
|
|
60
|
+
allows(url) {
|
|
61
|
+
let path;
|
|
62
|
+
try {
|
|
63
|
+
const u = new URL(url);
|
|
64
|
+
path = u.pathname + u.search;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
let best = null;
|
|
70
|
+
for (const r of rules) {
|
|
71
|
+
if (!r.pattern.test(path))
|
|
72
|
+
continue;
|
|
73
|
+
if (!best || r.length > best.length || (r.length === best.length && r.allow))
|
|
74
|
+
best = r;
|
|
75
|
+
}
|
|
76
|
+
return best ? best.allow : true;
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** Additive-increase / multiplicative-decrease concurrency controller. */
|
|
81
|
+
export class AdaptiveConcurrency {
|
|
82
|
+
min;
|
|
83
|
+
max;
|
|
84
|
+
target;
|
|
85
|
+
constructor(min = 1, max = 16, initial = 4) {
|
|
86
|
+
this.min = min;
|
|
87
|
+
this.max = max;
|
|
88
|
+
this.target = Math.min(Math.max(initial, min), max);
|
|
89
|
+
}
|
|
90
|
+
get current() {
|
|
91
|
+
return this.target;
|
|
92
|
+
}
|
|
93
|
+
/** Feed the outcome of a finished wave. */
|
|
94
|
+
record(outcome) {
|
|
95
|
+
const total = outcome.ok + outcome.failed;
|
|
96
|
+
if (outcome.throttled || (total > 0 && outcome.failed / total > 0.3)) {
|
|
97
|
+
this.target = Math.max(this.min, Math.floor(this.target / 2));
|
|
98
|
+
}
|
|
99
|
+
else if (outcome.ok > 0) {
|
|
100
|
+
this.target = Math.min(this.max, this.target + 1);
|
|
101
|
+
}
|
|
102
|
+
return this.target;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
106
|
+
export function backoffDelayMs(attempt, baseMs = 250, capMs = 4000, rand = Math.random) {
|
|
107
|
+
const exp = Math.min(capMs, baseMs * 2 ** attempt);
|
|
108
|
+
return Math.round(exp / 2 + rand() * (exp / 2));
|
|
109
|
+
}
|
|
110
|
+
export function isRetryableStatus(status) {
|
|
111
|
+
return status === 408 || status === 425 || status === 429 || status >= 500;
|
|
112
|
+
}
|
|
113
|
+
/** Retries transient failures (network errors, 408/425/429/5xx) with jittered exponential backoff. */
|
|
114
|
+
export async function withRetries(run, opts = {}) {
|
|
115
|
+
const retries = opts.retries ?? 2;
|
|
116
|
+
const wait = opts.sleepFn ?? sleep;
|
|
117
|
+
let lastErr;
|
|
118
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
119
|
+
try {
|
|
120
|
+
const res = await run();
|
|
121
|
+
if (!isRetryableStatus(res.status) || attempt === retries)
|
|
122
|
+
return res;
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
if (err?.name === "UnsafeUrlError")
|
|
126
|
+
throw err;
|
|
127
|
+
lastErr = err;
|
|
128
|
+
if (attempt === retries)
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
await wait(backoffDelayMs(attempt, opts.baseMs));
|
|
132
|
+
}
|
|
133
|
+
throw lastErr;
|
|
134
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
export interface MapWebsiteOptions {
|
|
2
|
+
/** Only crawl the exact root/target domain (default: true when includeSubdomains is false and selectedDomains is empty) */
|
|
3
|
+
onlyRootDomain?: boolean;
|
|
4
|
+
/** Also crawl subdomains of the allowed domain(s), e.g. docs.example.com (default: false) */
|
|
5
|
+
includeSubdomains?: boolean;
|
|
6
|
+
/** Explicit allowlist of domains/subdomains (supports exact hosts like "docs.example.com" or wildcards like "*.example.com") */
|
|
7
|
+
selectedDomains?: string[];
|
|
8
|
+
/** Maximum number of discovered URLs to return (default: 5000) */
|
|
9
|
+
limit?: number;
|
|
10
|
+
/** Maximum number of HTML pages to fetch during multi-hop BFS crawl when sitemap is missing (default: 250) */
|
|
11
|
+
maxCrawlPages?: number;
|
|
12
|
+
/** Alias for maxCrawlPages */
|
|
13
|
+
maxCrawl?: number;
|
|
14
|
+
/** Maximum BFS hop depth from the entry URL (default: 8) */
|
|
15
|
+
maxDepth?: number;
|
|
16
|
+
/** Only keep URLs whose path matches one of these (prefix, `*` wildcards, trailing `$`). */
|
|
17
|
+
includePaths?: string[];
|
|
18
|
+
/** Drop URLs whose path matches any of these. Wins over includePaths. */
|
|
19
|
+
excludePaths?: string[];
|
|
20
|
+
/** Stay under the start URL's path when no includePaths are given (default: true). */
|
|
21
|
+
restrictToSubpath?: boolean;
|
|
22
|
+
/** Sitemaps to read first (e.g. a site whose sitemap is not at /sitemap.xml). */
|
|
23
|
+
sitemapUrls?: string[];
|
|
24
|
+
/** Overall map timeout in milliseconds (default: 25000) */
|
|
25
|
+
timeoutMs?: number;
|
|
26
|
+
/** Bypass 24-hour map cache (default: false) */
|
|
27
|
+
bypassCache?: boolean;
|
|
28
|
+
/** When a bot check blocks plain requests, find pages through a real browser (default: true) */
|
|
29
|
+
stealth?: boolean;
|
|
30
|
+
/** Time budget for the stealth browser session (default: 75000) */
|
|
31
|
+
stealthBudgetMs?: number;
|
|
32
|
+
/** Allow the paid remote stealth provider (default true). Off for unbilled runs. */
|
|
33
|
+
remoteStealth?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare function mapWebsiteUrls(targetUrl: string, options?: MapWebsiteOptions): Promise<{
|
|
36
|
+
success: boolean;
|
|
37
|
+
links: string[];
|
|
38
|
+
latencyMs: number;
|
|
39
|
+
source: string;
|
|
40
|
+
cached?: boolean;
|
|
41
|
+
cacheLayer?: "memory" | "disk" | "postgres" | "miss";
|
|
42
|
+
pagesCrawled?: number;
|
|
43
|
+
/** The site answered plain requests with a bot check. */
|
|
44
|
+
blocked?: boolean;
|
|
45
|
+
/** Pages were found through the stealth browser. */
|
|
46
|
+
stealth?: boolean;
|
|
47
|
+
/** What a remote stealth provider charged for discovery, in USD. */
|
|
48
|
+
stealthCostUsd?: number;
|
|
49
|
+
scope?: {
|
|
50
|
+
onlyRootDomain: boolean;
|
|
51
|
+
includeSubdomains: boolean;
|
|
52
|
+
selectedDomains: string[];
|
|
53
|
+
maxCrawlPages: number;
|
|
54
|
+
maxDepth: number;
|
|
55
|
+
includePaths: string[];
|
|
56
|
+
excludePaths: string[];
|
|
57
|
+
};
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Ultra-fast HTTP status batch check for `websiteDeletedPages.ts`.
|
|
61
|
+
* Checks if URLs return 404/410 without downloading/rendering full markdown!
|
|
62
|
+
*/
|
|
63
|
+
export declare function checkUrlsStatusBatch(urls: string[], timeoutMs?: number): Promise<Array<{
|
|
64
|
+
url: string;
|
|
65
|
+
statusCode: number;
|
|
66
|
+
ok: boolean;
|
|
67
|
+
}>>;
|
|
68
|
+
/**
|
|
69
|
+
* Extracts structured JSON from a URL.
|
|
70
|
+
* Honours `prompt` / `schema` when provided; otherwise falls back to
|
|
71
|
+
* `{ app_name, product_features }` from title and headings.
|
|
72
|
+
*/
|
|
73
|
+
export declare function extractWebsiteData(url: string, options?: {
|
|
74
|
+
prompt?: string;
|
|
75
|
+
schema?: Record<string, unknown>;
|
|
76
|
+
openaiApiKey?: string;
|
|
77
|
+
bypassCache?: boolean;
|
|
78
|
+
/** Already-extracted content (e.g. a parsed PDF); skips the HTML fetch and cache. */
|
|
79
|
+
markdown?: string;
|
|
80
|
+
/** Source HTML when the caller already fetched the page (quote cards, JSON-LD). */
|
|
81
|
+
html?: string;
|
|
82
|
+
}): Promise<Record<string, unknown> & {
|
|
83
|
+
cached?: boolean;
|
|
84
|
+
cacheLayer?: "memory" | "disk" | "postgres" | "miss";
|
|
85
|
+
}>;
|
|
86
|
+
/**
|
|
87
|
+
* Full-website multi-page crawler (`POST /v1/crawl`) backed by the shared 3-tier
|
|
88
|
+
* L1 Memory -> L2 Disk -> L3 Postgres URL store (`crawled_pages`).
|
|
89
|
+
* Pages already stored in Postgres for this website are served directly without re-crawling!
|
|
90
|
+
*/
|
|
91
|
+
export declare function crawlWebsitePages(targetUrl: string, options?: {
|
|
92
|
+
maxPages?: number;
|
|
93
|
+
includeSubdomains?: boolean;
|
|
94
|
+
onlyRootDomain?: boolean;
|
|
95
|
+
bypassCache?: boolean;
|
|
96
|
+
extractStructured?: boolean;
|
|
97
|
+
timeoutMs?: number;
|
|
98
|
+
}): Promise<{
|
|
99
|
+
success: boolean;
|
|
100
|
+
rootUrl: string;
|
|
101
|
+
pagesCount: number;
|
|
102
|
+
cachedPagesCount: number;
|
|
103
|
+
postgresHits: number;
|
|
104
|
+
latencyMs: number;
|
|
105
|
+
pages: CrawledPage[];
|
|
106
|
+
}>;
|
|
107
|
+
export interface CrawledPage {
|
|
108
|
+
url: string;
|
|
109
|
+
title: string;
|
|
110
|
+
markdown: string;
|
|
111
|
+
linksCount: number;
|
|
112
|
+
cached: boolean;
|
|
113
|
+
cacheLayer: "memory" | "disk" | "postgres" | "miss";
|
|
114
|
+
structuredData?: Record<string, unknown> | null;
|
|
115
|
+
statusCode?: number;
|
|
116
|
+
error?: string;
|
|
117
|
+
/** The site served a bot check instead of this page. */
|
|
118
|
+
blocked?: boolean;
|
|
119
|
+
/** Read through the stealth browser. */
|
|
120
|
+
stealth?: boolean;
|
|
121
|
+
/** Remote provider that hosted the stealth browser (billed with a surcharge). */
|
|
122
|
+
stealthProvider?: string;
|
|
123
|
+
}
|
|
124
|
+
/** True for markdown converted from a bot-check page (old cache entries). */
|
|
125
|
+
export declare function isChallengeMarkdown(title: string, markdown: string): boolean;
|
|
126
|
+
/**
|
|
127
|
+
* Re-reads blocked pages through one stealth browser session. Pages that come
|
|
128
|
+
* back clean are cached and returned as content; pages still blocked keep
|
|
129
|
+
* `blocked: true` and an honest error.
|
|
130
|
+
*/
|
|
131
|
+
export declare function crawlPagesStealth(entryUrl: string, urls: string[], options?: {
|
|
132
|
+
extractStructured?: boolean;
|
|
133
|
+
budgetMs?: number;
|
|
134
|
+
remote?: boolean;
|
|
135
|
+
}): Promise<{
|
|
136
|
+
pages: CrawledPage[];
|
|
137
|
+
solved: boolean;
|
|
138
|
+
durationMs: number;
|
|
139
|
+
error?: string;
|
|
140
|
+
provider?: string;
|
|
141
|
+
providerCostUsd?: number;
|
|
142
|
+
}>;
|
|
143
|
+
/**
|
|
144
|
+
* Fetches one page for a crawl: cache first (unless bypassed), then a
|
|
145
|
+
* TLS-impersonated fetch. Never throws; failures come back with `error`.
|
|
146
|
+
* Used by `crawlWebsitePages` and by the durable crawl workflow (one call per page).
|
|
147
|
+
*/
|
|
148
|
+
export declare function crawlPage(pageUrl: string, options?: {
|
|
149
|
+
bypassCache?: boolean;
|
|
150
|
+
extractStructured?: boolean;
|
|
151
|
+
timeoutMs?: number;
|
|
152
|
+
}): Promise<CrawledPage>;
|