@webcrawl/client 0.0.0-stage → 1.2.2
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 +47 -2
- package/dist/client.d.ts +80 -0
- package/dist/client.js +420 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +58 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +55 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/types.d.ts +126 -0
- package/dist/types.js +1 -0
- package/package.json +50 -4
package/README.md
CHANGED
|
@@ -1,3 +1,48 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @webcrawl/client
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Zero-dependency TypeScript & Node.js SDK for **[Webcrawl Anything](https://github.com/brainfish-ai/webcrawl-anything)** and **[webcrawl.app](https://webcrawl.app)**.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @webcrawl/client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quickstart
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { WebcrawlClient } from "@webcrawl/client";
|
|
15
|
+
|
|
16
|
+
const client = new WebcrawlClient({
|
|
17
|
+
apiKey: process.env.WEBCRAWL_API_KEY, // or run `npx webcrawl login`
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// 1. Search the web or structured verticals (shopping, flights, hotels, real-estate, security CVEs)
|
|
21
|
+
const results = await client.search({
|
|
22
|
+
query: "CVE-2024-3094 xz utils",
|
|
23
|
+
category: "security",
|
|
24
|
+
subcategory: "cve",
|
|
25
|
+
limit: 5,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// 2. Scrape any URL into clean Markdown
|
|
29
|
+
const page = await client.scrape({
|
|
30
|
+
url: "https://github.com/brainfish-ai/webcrawl-anything",
|
|
31
|
+
formats: ["markdown"],
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// 3. Parse PDFs, Office files (.docx, .xlsx, .pptx), or YouTube transcripts
|
|
35
|
+
const doc = await client.parse({
|
|
36
|
+
url: "https://arxiv.org/pdf/1706.03762.pdf",
|
|
37
|
+
ocr: "auto",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// 4. List all supported crawl & search targets
|
|
41
|
+
const targets = await client.targets();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Documentation & Source
|
|
45
|
+
|
|
46
|
+
- **GitHub Repository:** [brainfish-ai/webcrawl-anything](https://github.com/brainfish-ai/webcrawl-anything)
|
|
47
|
+
- **Cloud Platform:** [https://webcrawl.app](https://webcrawl.app)
|
|
48
|
+
- **License:** MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { ApiResponse, CrawlParams, ExtractParams, MapParams, ParseParams, ParseResponse, RerankParams, ScrapeParams, SearchParams, SearchResponse } from "./types.js";
|
|
2
|
+
export interface ClientOptions {
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
baseUrl?: string;
|
|
5
|
+
/** Per-request timeout in ms. Default 60s. */
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
fetch?: typeof fetch;
|
|
8
|
+
userAgent?: string;
|
|
9
|
+
/** IANA timezone sent as `X-User-Timezone` so results are local. Defaults to this machine's. */
|
|
10
|
+
timezone?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class WebcrawlClient {
|
|
13
|
+
readonly baseUrl: string;
|
|
14
|
+
private readonly apiKey?;
|
|
15
|
+
private readonly timeoutMs;
|
|
16
|
+
private readonly fetchImpl;
|
|
17
|
+
private readonly hasCustomFetch;
|
|
18
|
+
private readonly userAgent;
|
|
19
|
+
private readonly timezone?;
|
|
20
|
+
constructor(opts?: ClientOptions);
|
|
21
|
+
request<T = ApiResponse>(method: "GET" | "POST", path: string, body?: unknown): Promise<T>;
|
|
22
|
+
search(p: SearchParams): Promise<SearchResponse>;
|
|
23
|
+
scrape(p: ScrapeParams): Promise<ApiResponse>;
|
|
24
|
+
/** PDF, DOCX/XLSX/PPTX, image or audio/video to Markdown. Long files: pass `async: true` and poll `run()`. */
|
|
25
|
+
parse(p: ParseParams): Promise<ParseResponse>;
|
|
26
|
+
/** Status, trace and (when finished) result of an async run. */
|
|
27
|
+
run(id: string): Promise<ApiResponse>;
|
|
28
|
+
map(p: MapParams): Promise<ApiResponse>;
|
|
29
|
+
crawl(p: CrawlParams): Promise<ApiResponse>;
|
|
30
|
+
extract(p: ExtractParams): Promise<ApiResponse>;
|
|
31
|
+
rerank(p: RerankParams): Promise<ApiResponse>;
|
|
32
|
+
categories(): Promise<ApiResponse>;
|
|
33
|
+
targets(): Promise<ApiResponse>;
|
|
34
|
+
usage(): Promise<ApiResponse>;
|
|
35
|
+
/** Send a 6-digit OTP verification code to an email address to sign up or log in (provisions 1,000 free monthly credits). */
|
|
36
|
+
sendOtp(email: string): Promise<{
|
|
37
|
+
success: boolean;
|
|
38
|
+
email: string;
|
|
39
|
+
expiresInSeconds: number;
|
|
40
|
+
transport?: string;
|
|
41
|
+
devCode?: string;
|
|
42
|
+
error?: string;
|
|
43
|
+
}>;
|
|
44
|
+
/** Verify a 6-digit OTP code, creating the user account (with 1,000 monthly credits) if new, and return a `wc_live_...` API key. */
|
|
45
|
+
verifyOtp(params: {
|
|
46
|
+
email: string;
|
|
47
|
+
code: string;
|
|
48
|
+
name?: string;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
success: boolean;
|
|
51
|
+
isNewUser?: boolean;
|
|
52
|
+
apiKey?: string;
|
|
53
|
+
sessionToken?: string;
|
|
54
|
+
user?: {
|
|
55
|
+
id: string;
|
|
56
|
+
email: string;
|
|
57
|
+
name?: string | null;
|
|
58
|
+
plan: string;
|
|
59
|
+
creditsLimit: number;
|
|
60
|
+
creditsUsed: number;
|
|
61
|
+
creditsRemaining: number;
|
|
62
|
+
};
|
|
63
|
+
error?: string;
|
|
64
|
+
}>;
|
|
65
|
+
/** Fetch authenticated user profile & remaining credit balance. */
|
|
66
|
+
me(): Promise<{
|
|
67
|
+
success: boolean;
|
|
68
|
+
authenticated: boolean;
|
|
69
|
+
user?: {
|
|
70
|
+
id: string;
|
|
71
|
+
email: string;
|
|
72
|
+
name?: string | null;
|
|
73
|
+
plan: string;
|
|
74
|
+
creditsLimit: number;
|
|
75
|
+
creditsUsed: number;
|
|
76
|
+
creditsRemaining: number;
|
|
77
|
+
};
|
|
78
|
+
}>;
|
|
79
|
+
}
|
|
80
|
+
export declare function createClient(opts?: ClientOptions): WebcrawlClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import { DEFAULT_BASE_URL } from "./config.js";
|
|
2
|
+
import { WebcrawlError, kindForStatus } from "./errors.js";
|
|
3
|
+
function localTimezone() {
|
|
4
|
+
try {
|
|
5
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function bytesToBase64(bytes) {
|
|
12
|
+
if (typeof Buffer !== "undefined") {
|
|
13
|
+
return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
|
|
14
|
+
}
|
|
15
|
+
let s = "";
|
|
16
|
+
for (let i = 0; i < bytes.length; i += 0x8000) {
|
|
17
|
+
s += String.fromCharCode(...bytes.subarray(i, i + 0x8000));
|
|
18
|
+
}
|
|
19
|
+
return btoa(s);
|
|
20
|
+
}
|
|
21
|
+
const compact = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
|
|
22
|
+
export class WebcrawlClient {
|
|
23
|
+
baseUrl;
|
|
24
|
+
apiKey;
|
|
25
|
+
timeoutMs;
|
|
26
|
+
fetchImpl;
|
|
27
|
+
hasCustomFetch;
|
|
28
|
+
userAgent;
|
|
29
|
+
timezone;
|
|
30
|
+
constructor(opts = {}) {
|
|
31
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
32
|
+
this.apiKey = opts.apiKey;
|
|
33
|
+
this.timeoutMs = opts.timeoutMs ?? 60_000;
|
|
34
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
35
|
+
this.hasCustomFetch = Boolean(opts.fetch);
|
|
36
|
+
this.userAgent = opts.userAgent ?? "webcrawl-client/0.1";
|
|
37
|
+
this.timezone = opts.timezone ?? localTimezone();
|
|
38
|
+
}
|
|
39
|
+
async request(method, path, body) {
|
|
40
|
+
const headers = {
|
|
41
|
+
accept: "application/json",
|
|
42
|
+
"user-agent": this.userAgent,
|
|
43
|
+
};
|
|
44
|
+
if (this.apiKey) {
|
|
45
|
+
headers.authorization = `Bearer ${this.apiKey}`;
|
|
46
|
+
}
|
|
47
|
+
if (this.timezone) {
|
|
48
|
+
headers["x-user-timezone"] = this.timezone;
|
|
49
|
+
}
|
|
50
|
+
if (body !== undefined) {
|
|
51
|
+
headers["content-type"] = "application/json";
|
|
52
|
+
}
|
|
53
|
+
let res;
|
|
54
|
+
try {
|
|
55
|
+
res = await this.fetchImpl(`${this.baseUrl}${path}`, {
|
|
56
|
+
method,
|
|
57
|
+
headers,
|
|
58
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
59
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
const name = e instanceof Error ? e.name : "";
|
|
64
|
+
if (name === "TimeoutError" || name === "AbortError") {
|
|
65
|
+
throw new WebcrawlError("timeout", `Request to ${path} timed out after ${this.timeoutMs}ms`);
|
|
66
|
+
}
|
|
67
|
+
throw new WebcrawlError("network", e instanceof Error ? e.message : String(e));
|
|
68
|
+
}
|
|
69
|
+
const text = await res.text();
|
|
70
|
+
let json;
|
|
71
|
+
try {
|
|
72
|
+
json = text ? JSON.parse(text) : {};
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
json = { error: text.slice(0, 300) };
|
|
76
|
+
}
|
|
77
|
+
if (!res.ok) {
|
|
78
|
+
const obj = (json && typeof json === "object" ? json : {});
|
|
79
|
+
const retry = Number(res.headers.get("retry-after"));
|
|
80
|
+
throw new WebcrawlError(kindForStatus(res.status), typeof obj.error === "string" ? obj.error : `HTTP ${res.status}`, { status: res.status, retryAfter: Number.isFinite(retry) && retry > 0 ? retry : undefined, body: json });
|
|
81
|
+
}
|
|
82
|
+
return json;
|
|
83
|
+
}
|
|
84
|
+
async search(p) {
|
|
85
|
+
const { sites, scrapeContent, ...rest } = p;
|
|
86
|
+
const rawCat = (p.category || "").trim().toLowerCase();
|
|
87
|
+
const rawSub = (p.subcategory || "").trim().toLowerCase();
|
|
88
|
+
const isAcademic = rawCat === "academic" ||
|
|
89
|
+
rawCat === "paper" ||
|
|
90
|
+
rawCat === "papers" ||
|
|
91
|
+
rawCat === "research" ||
|
|
92
|
+
rawCat === "arxiv" ||
|
|
93
|
+
rawCat === "scholar" ||
|
|
94
|
+
rawCat === "scholarly" ||
|
|
95
|
+
rawSub === "arxiv" ||
|
|
96
|
+
rawSub === "papers" ||
|
|
97
|
+
rawSub === "ml" ||
|
|
98
|
+
rawSub === "pubmed" ||
|
|
99
|
+
rawSub === "math";
|
|
100
|
+
const proxyPromise = this.request("POST", "/v1/search", {
|
|
101
|
+
...compact({ ...rest, ...(isAcademic ? { category: "academic" } : {}) }),
|
|
102
|
+
...(sites?.length ? { filters: { sites } } : {}),
|
|
103
|
+
...(scrapeContent ? { scrapeOptions: { formats: ["markdown"] } } : {}),
|
|
104
|
+
});
|
|
105
|
+
if (!isAcademic || this.hasCustomFetch) {
|
|
106
|
+
return proxyPromise;
|
|
107
|
+
}
|
|
108
|
+
const limit = p.limit ?? 5;
|
|
109
|
+
if (!this.apiKey) {
|
|
110
|
+
const scholarlyItems = await fetchAcademicPapersForClient(this.fetchImpl, p.query, limit);
|
|
111
|
+
return {
|
|
112
|
+
success: true,
|
|
113
|
+
category: "academic",
|
|
114
|
+
results: scholarlyItems.slice(0, limit),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const [proxyRes, scholarlyItems] = await Promise.all([
|
|
118
|
+
proxyPromise.catch(() => ({ success: true, results: [] })),
|
|
119
|
+
fetchAcademicPapersForClient(this.fetchImpl, p.query, limit),
|
|
120
|
+
]);
|
|
121
|
+
if (!scholarlyItems.length) {
|
|
122
|
+
return proxyRes;
|
|
123
|
+
}
|
|
124
|
+
const existing = Array.isArray(proxyRes.results) ? proxyRes.results : [];
|
|
125
|
+
const merged = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
for (const item of [...scholarlyItems, ...existing]) {
|
|
128
|
+
const url = typeof item.url === "string" ? item.url.toLowerCase().replace(/v\d+$/, "") : "";
|
|
129
|
+
if (!url || seen.has(url)) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
seen.add(url);
|
|
133
|
+
merged.push(item);
|
|
134
|
+
if (merged.length >= limit) {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
...proxyRes,
|
|
140
|
+
success: true,
|
|
141
|
+
category: "academic",
|
|
142
|
+
results: merged,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
scrape(p) {
|
|
146
|
+
return this.request("POST", "/v1/scrape", compact({ url: p.url, formats: p.formats, noCache: p.noCache }));
|
|
147
|
+
}
|
|
148
|
+
/** PDF, DOCX/XLSX/PPTX, image or audio/video to Markdown. Long files: pass `async: true` and poll `run()`. */
|
|
149
|
+
parse(p) {
|
|
150
|
+
const { bytes, noCache, ...rest } = p;
|
|
151
|
+
if (!p.url && !bytes && !p.base64) {
|
|
152
|
+
throw new WebcrawlError("bad_request", "parse needs `url`, `bytes` or `base64`");
|
|
153
|
+
}
|
|
154
|
+
const base64 = bytes ? bytesToBase64(bytes) : p.base64;
|
|
155
|
+
return this.request("POST", "/v1/parse", compact({ ...rest, base64, ...(noCache ? { noCache: true } : {}) }));
|
|
156
|
+
}
|
|
157
|
+
/** Status, trace and (when finished) result of an async run. */
|
|
158
|
+
run(id) {
|
|
159
|
+
return this.request("GET", `/v1/runs/${encodeURIComponent(id)}`);
|
|
160
|
+
}
|
|
161
|
+
map(p) {
|
|
162
|
+
return this.request("POST", "/v1/map", compact({ ...p }));
|
|
163
|
+
}
|
|
164
|
+
crawl(p) {
|
|
165
|
+
return this.request("POST", "/v1/crawl", compact({ ...p, maxPages: p.maxPages === undefined ? undefined : Math.min(25, Math.max(1, p.maxPages)) }));
|
|
166
|
+
}
|
|
167
|
+
extract(p) {
|
|
168
|
+
return this.request("POST", "/v1/extract", compact({ ...p }));
|
|
169
|
+
}
|
|
170
|
+
rerank(p) {
|
|
171
|
+
return this.request("POST", "/v1/rerank", compact({ ...p }));
|
|
172
|
+
}
|
|
173
|
+
categories() {
|
|
174
|
+
return this.request("GET", "/v1/search/categories");
|
|
175
|
+
}
|
|
176
|
+
async targets() {
|
|
177
|
+
try {
|
|
178
|
+
return await this.request("GET", "/v1/targets");
|
|
179
|
+
}
|
|
180
|
+
catch (e) {
|
|
181
|
+
if (!this.hasCustomFetch && e instanceof WebcrawlError && (e.status === 404 || e.status === 405)) {
|
|
182
|
+
return { success: true, count: BUILTIN_CLIENT_TARGETS.length, targets: BUILTIN_CLIENT_TARGETS };
|
|
183
|
+
}
|
|
184
|
+
throw e;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
usage() {
|
|
188
|
+
return this.request("GET", "/v1/account/usage");
|
|
189
|
+
}
|
|
190
|
+
/** Send a 6-digit OTP verification code to an email address to sign up or log in (provisions 1,000 free monthly credits). */
|
|
191
|
+
sendOtp(email) {
|
|
192
|
+
return this.request("POST", "/v1/auth/otp/send", { email: email.trim() });
|
|
193
|
+
}
|
|
194
|
+
/** Verify a 6-digit OTP code, creating the user account (with 1,000 monthly credits) if new, and return a `wc_live_...` API key. */
|
|
195
|
+
async verifyOtp(params) {
|
|
196
|
+
const res = await this.request("POST", "/v1/auth/otp/verify", {
|
|
197
|
+
email: params.email.trim(),
|
|
198
|
+
code: params.code.trim(),
|
|
199
|
+
...(params.name ? { name: params.name.trim() } : {}),
|
|
200
|
+
});
|
|
201
|
+
if (res.success && !res.apiKey && res.sessionToken) {
|
|
202
|
+
try {
|
|
203
|
+
const sessionClient = new WebcrawlClient({
|
|
204
|
+
baseUrl: this.baseUrl,
|
|
205
|
+
apiKey: res.sessionToken,
|
|
206
|
+
fetch: this.fetchImpl,
|
|
207
|
+
userAgent: this.userAgent,
|
|
208
|
+
});
|
|
209
|
+
const keyRes = await sessionClient.request("POST", "/v1/account/keys", { name: "CLI API Key" });
|
|
210
|
+
if (keyRes.apiKey) {
|
|
211
|
+
res.apiKey = keyRes.apiKey;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
// Fallback to sessionToken if key creation fails
|
|
216
|
+
res.apiKey = res.sessionToken;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return res;
|
|
220
|
+
}
|
|
221
|
+
/** Fetch authenticated user profile & remaining credit balance. */
|
|
222
|
+
me() {
|
|
223
|
+
return this.request("GET", "/v1/auth/me");
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const decodeXmlEntities = (s) => s
|
|
227
|
+
.replace(/&/g, "&")
|
|
228
|
+
.replace(/</g, "<")
|
|
229
|
+
.replace(/>/g, ">")
|
|
230
|
+
.replace(/"/g, '"')
|
|
231
|
+
.replace(/'/g, "'");
|
|
232
|
+
async function fetchAcademicPapersForClient(fetchImpl, rawQuery, limit) {
|
|
233
|
+
const clean = rawQuery
|
|
234
|
+
.replace(/\([^)]*\)|-?site:\S+/g, " ")
|
|
235
|
+
.replace(/["'`]+/g, " ")
|
|
236
|
+
.replace(/\s+/g, " ")
|
|
237
|
+
.trim();
|
|
238
|
+
if (!clean) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
const arxivIdMatch = clean.match(/\b(\d{4}\.\d{4,5}(?:v\d+)?)\b/i);
|
|
242
|
+
const stop = new Set(["the", "and", "for", "with", "from", "paper", "papers", "arxiv", "research"]);
|
|
243
|
+
const words = clean
|
|
244
|
+
.split(/\s+/)
|
|
245
|
+
.map((w) => w.replace(/[^a-zA-Z0-9.-]/g, ""))
|
|
246
|
+
.filter((w) => w.length >= 2 && !stop.has(w.toLowerCase()))
|
|
247
|
+
.slice(0, 6);
|
|
248
|
+
const searchExpr = arxivIdMatch
|
|
249
|
+
? `id:${encodeURIComponent(arxivIdMatch[1])}`
|
|
250
|
+
: words.length <= 1
|
|
251
|
+
? `all:${encodeURIComponent(words[0] || clean)}`
|
|
252
|
+
: words.map((w) => `all:${encodeURIComponent(w)}`).join("+AND+");
|
|
253
|
+
const arxivPromise = (async () => {
|
|
254
|
+
try {
|
|
255
|
+
const url = `https://export.arxiv.org/api/query?search_query=${searchExpr}&start=0&max_results=${Math.max(limit, 6)}`;
|
|
256
|
+
const res = await fetchImpl(url, {
|
|
257
|
+
headers: { "user-agent": "webcrawl-anything/1.2 (https://webcrawl.app)" },
|
|
258
|
+
signal: AbortSignal.timeout(4000),
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok) {
|
|
261
|
+
return [];
|
|
262
|
+
}
|
|
263
|
+
const xml = await res.text();
|
|
264
|
+
const entries = xml.match(/<entry>([\s\S]*?)<\/entry>/gi) || [];
|
|
265
|
+
const out = [];
|
|
266
|
+
for (const entry of entries) {
|
|
267
|
+
if (out.length >= limit) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
const title = decodeXmlEntities((entry.match(/<title>([\s\S]*?)<\/title>/i)?.[1] || "").replace(/\s+/g, " ").trim());
|
|
271
|
+
const rawIdUrl = (entry.match(/<id>([\s\S]*?)<\/id>/i)?.[1] || "").trim();
|
|
272
|
+
const idUrl = rawIdUrl.replace(/^http:\/\/arxiv\.org\//i, "https://arxiv.org/");
|
|
273
|
+
const summary = decodeXmlEntities((entry.match(/<summary>([\s\S]*?)<\/summary>/i)?.[1] || "").replace(/\s+/g, " ").trim());
|
|
274
|
+
const published = (entry.match(/<published>([\s\S]*?)<\/published>/i)?.[1] || "").trim();
|
|
275
|
+
const authors = Array.from(entry.matchAll(/<author>[\s\S]*?<name>([\s\S]*?)<\/name>[\s\S]*?<\/author>/gi))
|
|
276
|
+
.map((m) => decodeXmlEntities(m[1].trim()))
|
|
277
|
+
.filter(Boolean)
|
|
278
|
+
.slice(0, 4);
|
|
279
|
+
const pdfUrl = (entry.match(/<link[^>]+title=["']pdf["'][^>]+href=["']([^"']+)["']/i)?.[1] ||
|
|
280
|
+
idUrl.replace("/abs/", "/pdf/")).replace(/^http:\/\//i, "https://");
|
|
281
|
+
if (title && idUrl) {
|
|
282
|
+
const metaPrefix = [
|
|
283
|
+
authors.length ? `Authors: ${authors.join(", ")}` : "",
|
|
284
|
+
pdfUrl ? `PDF: ${pdfUrl}` : "",
|
|
285
|
+
]
|
|
286
|
+
.filter(Boolean)
|
|
287
|
+
.join(" · ");
|
|
288
|
+
out.push({
|
|
289
|
+
title: `[arXiv] ${title}`,
|
|
290
|
+
url: idUrl,
|
|
291
|
+
description: (metaPrefix ? `${metaPrefix} — ${summary}` : summary).slice(0, 460),
|
|
292
|
+
publishedDate: published || undefined,
|
|
293
|
+
sources: ["arxiv"],
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return out;
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return [];
|
|
301
|
+
}
|
|
302
|
+
})();
|
|
303
|
+
const openAlexPromise = (async () => {
|
|
304
|
+
try {
|
|
305
|
+
const url = `https://api.openalex.org/works?search=${encodeURIComponent(clean)}&per-page=${Math.min(limit, 6)}`;
|
|
306
|
+
const res = await fetchImpl(url, {
|
|
307
|
+
headers: { "user-agent": "webcrawl-anything/1.2 (mailto:support@webcrawl.app)" },
|
|
308
|
+
signal: AbortSignal.timeout(3200),
|
|
309
|
+
});
|
|
310
|
+
if (!res.ok) {
|
|
311
|
+
return [];
|
|
312
|
+
}
|
|
313
|
+
const data = (await res.json());
|
|
314
|
+
const out = [];
|
|
315
|
+
for (const w of data.results || []) {
|
|
316
|
+
if (out.length >= limit) {
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
const title = (w.title || "").replace(/\s+/g, " ").trim();
|
|
320
|
+
const targetUrl = w.primary_location?.landing_page_url || w.open_access?.oa_url || w.doi || "";
|
|
321
|
+
if (!title || !targetUrl) {
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const authors = (w.authorships || [])
|
|
325
|
+
.map((a) => a.author?.display_name)
|
|
326
|
+
.filter((n) => Boolean(n))
|
|
327
|
+
.slice(0, 4)
|
|
328
|
+
.join(", ");
|
|
329
|
+
const venue = w.primary_location?.source?.display_name;
|
|
330
|
+
const pdfUrl = w.primary_location?.pdf_url || w.open_access?.oa_url;
|
|
331
|
+
const desc = [
|
|
332
|
+
authors ? `Authors: ${authors}` : "",
|
|
333
|
+
venue ? `Venue: ${venue}` : "",
|
|
334
|
+
typeof w.cited_by_count === "number" ? `Citations: ${w.cited_by_count}` : "",
|
|
335
|
+
pdfUrl ? `PDF: ${pdfUrl}` : "",
|
|
336
|
+
]
|
|
337
|
+
.filter(Boolean)
|
|
338
|
+
.join(" · ");
|
|
339
|
+
out.push({
|
|
340
|
+
title: `[Paper] ${title}`,
|
|
341
|
+
url: targetUrl,
|
|
342
|
+
description: desc || title,
|
|
343
|
+
publishedDate: w.publication_date,
|
|
344
|
+
sources: ["arxiv"],
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
return out;
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
return [];
|
|
351
|
+
}
|
|
352
|
+
})();
|
|
353
|
+
const [arxivItems, openAlexItems] = await Promise.all([arxivPromise, openAlexPromise]);
|
|
354
|
+
return [...arxivItems, ...openAlexItems];
|
|
355
|
+
}
|
|
356
|
+
const BUILTIN_CLIENT_TARGETS = [
|
|
357
|
+
{
|
|
358
|
+
id: "llms-txt",
|
|
359
|
+
name: "llms.txt & AI Documentation Indexes",
|
|
360
|
+
returns: "Structured sections, doc links, summaries, and full markdown manifest for LLM context ingestion",
|
|
361
|
+
requirements: "Built-in zero-key markdown manifest parser",
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: "github",
|
|
365
|
+
name: "GitHub Repositories, Issues & Releases",
|
|
366
|
+
returns: "Repository metadata, stars/forks, topics, language, issue/PR threads, release notes, and README markdown",
|
|
367
|
+
requirements: "Built-in zero-key HTML/JSON inspector via impit TLS fetch",
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: "arxiv",
|
|
371
|
+
name: "ArXiv & Academic Research Papers",
|
|
372
|
+
returns: "Paper title, authors, abstract, primary subject, DOI, submission date, and direct PDF link",
|
|
373
|
+
requirements: "Built-in ArXiv + OpenAlex paper search (-c academic) and abstract/PDF parser",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "youtube-media",
|
|
377
|
+
name: "YouTube Videos, Audio & Media Files",
|
|
378
|
+
returns: "Timestamped transcript Markdown, video title/channel/duration metadata, and spoken audio transcription",
|
|
379
|
+
requirements: "Built-in zero-key YouTube multi-client caption extractor",
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: "pdf-office",
|
|
383
|
+
name: "PDFs & Office Documents (DOCX, XLSX, PPTX)",
|
|
384
|
+
returns: "Page-numbered Markdown, tables, native text extraction, automatic OCR on scanned pages",
|
|
385
|
+
requirements: "Built-in (@firecrawl/pdf-inspector, pdf-parse, mammoth, officeparser, tesseract.js)",
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "cve",
|
|
389
|
+
name: "Security Advisories & CVEs (NVD / OSV / CISA KEV)",
|
|
390
|
+
returns: "Structured CVERecord with CVSS v3.1/v4.0 score, severity, CWEs, affected/fixed versions, and CISA KEV status",
|
|
391
|
+
requirements: "Built-in live NVD 2.0 + OSV.dev + CISA KEV enrichment (-c security --sub cve)",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
id: "shopping",
|
|
395
|
+
name: "Shopping & Multi-Retailer Product Offers",
|
|
396
|
+
returns: "AggregatedProduct & ProductOffer records with price, currency, stock availability, retailer, rating, and reviews",
|
|
397
|
+
requirements: "Zero-key local HTML/JSON-LD product extractor (-c shopping)",
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
id: "travel",
|
|
401
|
+
name: "Flights & Hotels",
|
|
402
|
+
returns: "Structured FlightOffer & HotelListing items with price, currency, airline/stops or star/guest rating",
|
|
403
|
+
requirements: "Zero-key local search + schema.org extractor (-c flights / -c hotels)",
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: "real-estate",
|
|
407
|
+
name: "Real Estate & Local Business Listings",
|
|
408
|
+
returns: "PropertyListing (bedrooms, bathrooms, parking, suburb) & BusinessListing (phone, address, rating, hours)",
|
|
409
|
+
requirements: "Zero-key local extractor (-c real-estate --sub buy|rent|sold / -c business)",
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: "web",
|
|
413
|
+
name: "Websites, Docs & Single-Page Apps (SPAs)",
|
|
414
|
+
returns: "Clean Markdown, title, metadata, canonical links, content hash, and evidence source tier",
|
|
415
|
+
requirements: "Zero-key local (impit TLS impersonation + local Chrome) or webcrawl.app cloud stealth browsers",
|
|
416
|
+
},
|
|
417
|
+
];
|
|
418
|
+
export function createClient(opts = {}) {
|
|
419
|
+
return new WebcrawlClient(opts);
|
|
420
|
+
}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const DEFAULT_BASE_URL = "https://webcrawl.app";
|
|
2
|
+
export type ExecutionMode = "local" | "cloud" | "auto";
|
|
3
|
+
export interface FileConfig {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
baseUrl?: string;
|
|
6
|
+
mode?: ExecutionMode;
|
|
7
|
+
}
|
|
8
|
+
export interface ResolvedConfig {
|
|
9
|
+
apiKey?: string;
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
mode: ExecutionMode;
|
|
12
|
+
effectiveMode: "local" | "cloud";
|
|
13
|
+
source: {
|
|
14
|
+
apiKey: "flag" | "env" | "file" | "none";
|
|
15
|
+
baseUrl: "flag" | "env" | "file" | "default";
|
|
16
|
+
mode: "flag" | "env" | "file" | "default";
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export declare function configPath(env?: NodeJS.ProcessEnv): string;
|
|
20
|
+
export declare function readConfigFile(path?: string): FileConfig;
|
|
21
|
+
export declare function writeConfigFile(cfg: FileConfig, path?: string): void;
|
|
22
|
+
/** Precedence: explicit flags > environment > config file > defaults. */
|
|
23
|
+
export declare function resolveConfig(flags?: {
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
baseUrl?: string;
|
|
26
|
+
mode?: string;
|
|
27
|
+
}, env?: NodeJS.ProcessEnv, file?: FileConfig): ResolvedConfig;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export const DEFAULT_BASE_URL = "https://webcrawl.app";
|
|
5
|
+
export function configPath(env = process.env) {
|
|
6
|
+
const base = env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
7
|
+
return join(base, "webcrawl", "config.json");
|
|
8
|
+
}
|
|
9
|
+
export function readConfigFile(path = configPath()) {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
+
if (parsed && typeof parsed === "object") {
|
|
13
|
+
const o = parsed;
|
|
14
|
+
return {
|
|
15
|
+
apiKey: typeof o.apiKey === "string" ? o.apiKey : undefined,
|
|
16
|
+
baseUrl: typeof o.baseUrl === "string" ? o.baseUrl : undefined,
|
|
17
|
+
mode: o.mode === "local" || o.mode === "cloud" || o.mode === "auto" ? o.mode : undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// missing or unreadable config is fine
|
|
23
|
+
}
|
|
24
|
+
return {};
|
|
25
|
+
}
|
|
26
|
+
export function writeConfigFile(cfg, path = configPath()) {
|
|
27
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
28
|
+
writeFileSync(path, `${JSON.stringify(cfg, null, 2)}\n`, { mode: 0o600 });
|
|
29
|
+
chmodSync(path, 0o600);
|
|
30
|
+
}
|
|
31
|
+
const clean = (v) => (v && v.trim() ? v.trim() : undefined);
|
|
32
|
+
/** Precedence: explicit flags > environment > config file > defaults. */
|
|
33
|
+
export function resolveConfig(flags = {}, env = process.env, file = readConfigFile(configPath(env))) {
|
|
34
|
+
const pick = (f, e, fl, none) => {
|
|
35
|
+
if (clean(f)) {
|
|
36
|
+
return [clean(f), "flag"];
|
|
37
|
+
}
|
|
38
|
+
if (clean(e)) {
|
|
39
|
+
return [clean(e), "env"];
|
|
40
|
+
}
|
|
41
|
+
if (clean(fl)) {
|
|
42
|
+
return [clean(fl), "file"];
|
|
43
|
+
}
|
|
44
|
+
return [undefined, none];
|
|
45
|
+
};
|
|
46
|
+
const [apiKey, keySrc] = pick(flags.apiKey, env.WEBCRAWL_API_KEY, file.apiKey, "none");
|
|
47
|
+
const [baseUrl, urlSrc] = pick(flags.baseUrl, env.WEBCRAWL_URL, file.baseUrl, "default");
|
|
48
|
+
const [rawMode, modeSrc] = pick(flags.mode, env.WEBCRAWL_MODE, file.mode, "default");
|
|
49
|
+
const mode = rawMode === "local" || rawMode === "cloud" || rawMode === "auto" ? rawMode : "auto";
|
|
50
|
+
const effectiveMode = mode === "local" ? "local" : mode === "cloud" ? "cloud" : apiKey ? "cloud" : "local";
|
|
51
|
+
return {
|
|
52
|
+
apiKey,
|
|
53
|
+
baseUrl: (baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, ""),
|
|
54
|
+
mode,
|
|
55
|
+
effectiveMode,
|
|
56
|
+
source: { apiKey: keySrc, baseUrl: urlSrc, mode: modeSrc },
|
|
57
|
+
};
|
|
58
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type WebcrawlErrorKind = "auth" | "credits" | "rate_limit" | "bad_request" | "not_found" | "server" | "network" | "timeout";
|
|
2
|
+
export declare class WebcrawlError extends Error {
|
|
3
|
+
readonly kind: WebcrawlErrorKind;
|
|
4
|
+
readonly status?: number;
|
|
5
|
+
/** Seconds to wait before retrying, from `Retry-After` on 429. */
|
|
6
|
+
readonly retryAfter?: number;
|
|
7
|
+
readonly body?: unknown;
|
|
8
|
+
constructor(kind: WebcrawlErrorKind, message: string, opts?: {
|
|
9
|
+
status?: number;
|
|
10
|
+
retryAfter?: number;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export declare function kindForStatus(status: number): WebcrawlErrorKind;
|
|
15
|
+
/** One actionable sentence for people (and models) reading the error. */
|
|
16
|
+
export declare function describeError(err: unknown, baseUrl?: string): string;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class WebcrawlError extends Error {
|
|
2
|
+
kind;
|
|
3
|
+
status;
|
|
4
|
+
/** Seconds to wait before retrying, from `Retry-After` on 429. */
|
|
5
|
+
retryAfter;
|
|
6
|
+
body;
|
|
7
|
+
constructor(kind, message, opts = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "WebcrawlError";
|
|
10
|
+
this.kind = kind;
|
|
11
|
+
this.status = opts.status;
|
|
12
|
+
this.retryAfter = opts.retryAfter;
|
|
13
|
+
this.body = opts.body;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function kindForStatus(status) {
|
|
17
|
+
if (status === 401 || status === 403) {
|
|
18
|
+
return "auth";
|
|
19
|
+
}
|
|
20
|
+
if (status === 402) {
|
|
21
|
+
return "credits";
|
|
22
|
+
}
|
|
23
|
+
if (status === 429) {
|
|
24
|
+
return "rate_limit";
|
|
25
|
+
}
|
|
26
|
+
if (status === 404) {
|
|
27
|
+
return "not_found";
|
|
28
|
+
}
|
|
29
|
+
if (status >= 500) {
|
|
30
|
+
return "server";
|
|
31
|
+
}
|
|
32
|
+
return "bad_request";
|
|
33
|
+
}
|
|
34
|
+
/** One actionable sentence for people (and models) reading the error. */
|
|
35
|
+
export function describeError(err, baseUrl = "https://webcrawl.app") {
|
|
36
|
+
if (!(err instanceof WebcrawlError)) {
|
|
37
|
+
return err instanceof Error ? err.message : String(err);
|
|
38
|
+
}
|
|
39
|
+
switch (err.kind) {
|
|
40
|
+
case "auth":
|
|
41
|
+
return "Your API key is missing or invalid. Set WEBCRAWL_API_KEY or run `webcrawl login`.";
|
|
42
|
+
case "credits":
|
|
43
|
+
return `You are out of credits. Add more at ${baseUrl}/account.`;
|
|
44
|
+
case "rate_limit":
|
|
45
|
+
return err.retryAfter
|
|
46
|
+
? `Rate limited. Retry in ${err.retryAfter}s.`
|
|
47
|
+
: "Rate limited. Wait a moment and retry.";
|
|
48
|
+
case "timeout":
|
|
49
|
+
return "The request timed out. Try again, or narrow the request.";
|
|
50
|
+
case "network":
|
|
51
|
+
return `Could not reach ${baseUrl}. Check your connection or WEBCRAWL_URL.`;
|
|
52
|
+
default:
|
|
53
|
+
return err.message;
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { WebcrawlClient, createClient, type ClientOptions } from "./client.js";
|
|
2
|
+
export { DEFAULT_BASE_URL, configPath, readConfigFile, resolveConfig, writeConfigFile, type ExecutionMode, type FileConfig, type ResolvedConfig, } from "./config.js";
|
|
3
|
+
export { WebcrawlError, describeError, kindForStatus, type WebcrawlErrorKind } from "./errors.js";
|
|
4
|
+
export type * from "./types.js";
|
package/dist/index.js
ADDED
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/** Fields every API response may carry. Responses are intentionally loose: the API grows faster than this client. */
|
|
2
|
+
export interface CreditInfo {
|
|
3
|
+
charged?: number;
|
|
4
|
+
used?: number;
|
|
5
|
+
remaining?: number;
|
|
6
|
+
limit?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ApiResponse {
|
|
9
|
+
success?: boolean;
|
|
10
|
+
credits?: CreditInfo;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
}
|
|
13
|
+
export interface Location {
|
|
14
|
+
country?: string;
|
|
15
|
+
region?: string;
|
|
16
|
+
city?: string;
|
|
17
|
+
timezone?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface SearchParams extends Location {
|
|
20
|
+
query: string;
|
|
21
|
+
category?: string;
|
|
22
|
+
subcategory?: string;
|
|
23
|
+
limit?: number;
|
|
24
|
+
/** Restrict to these domains. */
|
|
25
|
+
sites?: string[];
|
|
26
|
+
includeDomains?: string[];
|
|
27
|
+
excludeDomains?: string[];
|
|
28
|
+
/** Time filter, e.g. `qdr:w`. */
|
|
29
|
+
tbs?: string;
|
|
30
|
+
scrapeContent?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export interface SearchResponse extends ApiResponse {
|
|
33
|
+
results?: Array<{
|
|
34
|
+
title?: string;
|
|
35
|
+
url?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
markdown?: string;
|
|
38
|
+
}>;
|
|
39
|
+
data?: unknown;
|
|
40
|
+
offers?: unknown[];
|
|
41
|
+
products?: unknown[];
|
|
42
|
+
listings?: unknown[];
|
|
43
|
+
flights?: unknown[];
|
|
44
|
+
hotels?: unknown[];
|
|
45
|
+
businesses?: unknown[];
|
|
46
|
+
/** Structured CVE records (category "security"): id, cvssScore, severity, affected, fixed versions, kev, references. */
|
|
47
|
+
cves?: unknown[];
|
|
48
|
+
}
|
|
49
|
+
export interface ScrapeParams {
|
|
50
|
+
url: string;
|
|
51
|
+
formats?: Array<"markdown" | "links" | "html">;
|
|
52
|
+
noCache?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Parse a file (PDF, Office, image, audio/video) from a URL, raw bytes or base64. */
|
|
55
|
+
export interface ParseParams {
|
|
56
|
+
/** File URL. Provide one of `url`, `bytes` or `base64`. */
|
|
57
|
+
url?: string;
|
|
58
|
+
/** Raw file bytes (sent as base64 JSON). */
|
|
59
|
+
bytes?: Uint8Array;
|
|
60
|
+
base64?: string;
|
|
61
|
+
fileName?: string;
|
|
62
|
+
contentType?: string;
|
|
63
|
+
/** `auto` OCRs only scanned pages; `force` OCRs every page; `off` never OCRs. */
|
|
64
|
+
ocr?: "auto" | "force" | "off";
|
|
65
|
+
/** 1-indexed PDF pages to read. */
|
|
66
|
+
pages?: number[];
|
|
67
|
+
maxOcrPages?: number;
|
|
68
|
+
/** ISO-639-1 hint for transcription. */
|
|
69
|
+
language?: string;
|
|
70
|
+
/** WebVTT/SRT captions to use instead of transcribing. */
|
|
71
|
+
captions?: string;
|
|
72
|
+
describeVideo?: boolean;
|
|
73
|
+
/** Run as a durable job (returns a run id), for big PDFs and long media. */
|
|
74
|
+
async?: boolean;
|
|
75
|
+
noCache?: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface ParseMetadata {
|
|
78
|
+
kind?: string;
|
|
79
|
+
method?: "native" | "ocr" | "vision" | "transcript";
|
|
80
|
+
pages?: number;
|
|
81
|
+
pagesOcr?: number[];
|
|
82
|
+
pagesSkipped?: Array<{
|
|
83
|
+
page: number;
|
|
84
|
+
reason: string;
|
|
85
|
+
}>;
|
|
86
|
+
durationSeconds?: number;
|
|
87
|
+
warnings?: string[];
|
|
88
|
+
}
|
|
89
|
+
export interface ParseResponse extends ApiResponse {
|
|
90
|
+
title?: string;
|
|
91
|
+
markdown?: string;
|
|
92
|
+
/** Present when the parse runs as a durable job. */
|
|
93
|
+
id?: string;
|
|
94
|
+
status?: string;
|
|
95
|
+
statusUrl?: string;
|
|
96
|
+
metadata?: {
|
|
97
|
+
parse?: ParseMetadata;
|
|
98
|
+
[key: string]: unknown;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
export interface MapParams {
|
|
102
|
+
url: string;
|
|
103
|
+
limit?: number;
|
|
104
|
+
search?: string;
|
|
105
|
+
}
|
|
106
|
+
export interface CrawlParams {
|
|
107
|
+
url: string;
|
|
108
|
+
maxPages?: number;
|
|
109
|
+
extractStructured?: boolean;
|
|
110
|
+
}
|
|
111
|
+
export interface ExtractParams {
|
|
112
|
+
url: string;
|
|
113
|
+
prompt?: string;
|
|
114
|
+
schema?: Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
export type RerankDocument = string | {
|
|
117
|
+
title?: string;
|
|
118
|
+
url?: string;
|
|
119
|
+
text?: string;
|
|
120
|
+
description?: string;
|
|
121
|
+
};
|
|
122
|
+
export interface RerankParams {
|
|
123
|
+
query: string;
|
|
124
|
+
documents: RerankDocument[];
|
|
125
|
+
topK?: number;
|
|
126
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webcrawl/client",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "Zero-dependency TypeScript & Node.js SDK for Webcrawl Anything (search, scrape, parse, map, crawl, extract, rerank, targets)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Brainfish AI <support@webcrawl.app> (https://webcrawl.app)",
|
|
8
|
+
"homepage": "https://webcrawl.app",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/brainfish-ai/webcrawl-anything.git",
|
|
12
|
+
"directory": "packages/client"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/brainfish-ai/webcrawl-anything/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"webcrawl",
|
|
19
|
+
"web-crawler",
|
|
20
|
+
"web-scraper",
|
|
21
|
+
"search-api",
|
|
22
|
+
"markdown",
|
|
23
|
+
"pdf-parser",
|
|
24
|
+
"cve",
|
|
25
|
+
"mcp",
|
|
26
|
+
"ai-agents"
|
|
27
|
+
],
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md"
|
|
34
|
+
],
|
|
35
|
+
"main": "./dist/index.js",
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": {
|
|
39
|
+
"development": "./src/index.ts",
|
|
40
|
+
"types": "./dist/index.d.ts",
|
|
41
|
+
"default": "./dist/index.js"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsc -p tsconfig.build.json",
|
|
46
|
+
"prepack": "npm run build",
|
|
47
|
+
"test": "node --conditions=development --import tsx --test test/*.test.ts"
|
|
48
|
+
},
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=20"
|
|
51
|
+
}
|
|
52
|
+
}
|