webcrawl 1.3.6 → 1.3.8
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/cli.d.ts +10 -1
- package/dist/cli.js +184 -8
- package/dist/format.d.ts +1 -0
- package/dist/format.js +63 -0
- package/dist/setup.d.ts +1 -1
- package/dist/setup.js +2 -0
- package/package.json +3 -3
package/dist/cli.d.ts
CHANGED
|
@@ -17,5 +17,14 @@ export interface Deps {
|
|
|
17
17
|
/** Working directory override for `webcrawl setup` tests. */
|
|
18
18
|
cwd?: string;
|
|
19
19
|
}
|
|
20
|
-
export declare const HELP = "Webcrawl Anything: search, scrape, parse and crawl any target on the internet\n\nUsage: webcrawl <command> [options]\n\nCommands\n search <query> Search the web or verticals. -c category, --sub, -n limit, --site <domain>\n scrape <url> Inspect a URL (website, SPA, PDF, Office, YouTube, GitHub, ArXiv, llms.txt)\n map <url> List a site's URLs. --filter <text>, -n limit\n crawl <url> Read several pages. -n max pages (default 5, max 25)\n extract <url> Structured JSON. --prompt <text> or --schema <file.json>\n rerank <query> Rank documents. --doc <text> (repeatable), --file <path>, or lines on stdin. -n top\n targets List all supported crawl & search targets and their requirements\n categories List search categories and vertical filters\n setup Register Webcrawl MCP server and Agent Skill (Claude, Cursor, Codex, Gemini, Windsurf)\n doctor Check local crawl engine, parsers, stealth browser, and cloud configuration\n login / signup Create an account or sign in in your browser (default), or --email <addr> [--code <6-digit>], or --key <key>\n whoami Show execution mode (local vs cloud), account email, key source, and credit balance\n\nOptions\n --mode <m> Execution mode: auto (default: local without key, cloud with key), local, or cloud\n --email <addr> Email address for CLI account creation / OTP sign-in (assigns 1,000 free credits)\n --code <6-digit> 6-digit email verification code from `webcrawl login --email <addr>`\n --name <name> Optional display name when creating an account with `--email`\n --web Sign in via browser OAuth (default for login; no need to pass it)\n --json Print the raw JSON response\n --api-key <key> Override WEBCRAWL_API_KEY\n --url <base> Override WEBCRAWL_URL (default https://webcrawl.app)\n --country, --city Where to search from (default: this machine's timezone)\n --no-cache Skip cached pages (scrape)\n --agent <name> Target agent for setup: claude, cursor, codex, gemini, windsurf, or all (repeatable)\n --schema-profile MCP schema profile for setup: compact (default) or full\n -h, --help\n\nConfig order: flags, then WEBCRAWL_API_KEY / WEBCRAWL_URL / WEBCRAWL_MODE, then ~/.config/webcrawl/config.json";
|
|
20
|
+
export declare const HELP = "Webcrawl Anything: search, scrape, parse and crawl any target on the internet\n\nUsage: webcrawl <command> [options]\n\nCommands\n search <query> Search the web or verticals. -c category, --sub, -n limit, --site <domain>\n scrape <url> Inspect a URL (website, SPA, PDF, Office, YouTube, GitHub, ArXiv, llms.txt)\n map <url> List a site's URLs. --filter <text>, -n limit\n crawl <url> Read several pages. -n max pages (default 5, max 25)\n extract <url> Structured JSON. --prompt <text> or --schema <file.json>\n rerank <query> Rank documents. --doc <text> (repeatable), --file <path>, or lines on stdin. -n top\n monitor [subcmd] Watch pages/sites: create <url...>, list, get <id>, check <id>, pause <id>, resume <id>, delete <id>\n targets List all supported crawl & search targets and their requirements\n categories List search categories and vertical filters\n setup Register Webcrawl MCP server and Agent Skill (Claude, Cursor, Codex, Gemini, Windsurf)\n doctor Check local crawl engine, parsers, stealth browser, and cloud configuration\n login / signup Create an account or sign in in your browser (default), or --email <addr> [--code <6-digit>], or --key <key>\n whoami Show execution mode (local vs cloud), account email, key source, and credit balance\n\nOptions\n --mode <m> Execution mode: auto (default: local without key, cloud with key), local, or cloud\n --email <addr> Email address for CLI account creation / OTP sign-in (assigns 1,000 free credits)\n --code <6-digit> 6-digit email verification code from `webcrawl login --email <addr>`\n --name <name> Optional display name when creating an account with `--email`\n --web Sign in via browser OAuth (default for login; no need to pass it)\n --json Print the raw JSON response\n --api-key <key> Override WEBCRAWL_API_KEY\n --url <base> Override WEBCRAWL_URL (default https://webcrawl.app)\n --country, --city Where to search from (default: this machine's timezone)\n --no-cache Skip cached pages (scrape)\n --scope <s|u|w> Monitor scope: page (default), urls, or site\n --interval <mins> Monitor check interval in minutes (default 60)\n --watch-for <text> What to watch for (e.g. \"Pricing tiers and discounts\")\n --condition <expr> Trigger rule expression (e.g. 'price < 50', 'content contains \"In Stock\"')\n --notify-email <e> Alert email address when triggered (or 'true' for account email)\n --webhook <url> Alert webhook URL when triggered\n --agent <name> Target agent for setup: claude, cursor, codex, gemini, windsurf, or all (repeatable)\n --schema-profile MCP schema profile for setup: compact (default) or full\n -h, --help\n\nConfig order: flags, then WEBCRAWL_API_KEY / WEBCRAWL_URL / WEBCRAWL_MODE, then ~/.config/webcrawl/config.json";
|
|
21
|
+
/** Self-contained (no network assets) page served on the loopback callback. */
|
|
22
|
+
export declare function callbackPage(opts: {
|
|
23
|
+
kind: "success" | "error" | "info";
|
|
24
|
+
title: string;
|
|
25
|
+
body: string;
|
|
26
|
+
email?: string;
|
|
27
|
+
credits?: string;
|
|
28
|
+
tryIt?: string;
|
|
29
|
+
}): string;
|
|
21
30
|
export declare function main(argv: string[], io: Io, deps?: Deps): Promise<number>;
|
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { createServer } from "node:http";
|
|
|
5
5
|
import { createInterface } from "node:readline";
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
import { WebcrawlClient, WebcrawlError, configPath, describeError, readConfigFile, resolveConfig, writeConfigFile, } from "@webcrawl/client";
|
|
8
|
-
import { formatCrawl, formatCredits, formatMap, formatRerank, formatScrape, formatSearch, formatTargets, } from "./format.js";
|
|
8
|
+
import { formatCrawl, formatCredits, formatMap, formatMonitor, formatRerank, formatScrape, formatSearch, formatTargets, } from "./format.js";
|
|
9
9
|
import { SUPPORTED_AGENTS, runDoctor, runSetup, } from "./setup.js";
|
|
10
10
|
export const HELP = `Webcrawl Anything: search, scrape, parse and crawl any target on the internet
|
|
11
11
|
|
|
@@ -18,6 +18,7 @@ Commands
|
|
|
18
18
|
crawl <url> Read several pages. -n max pages (default 5, max 25)
|
|
19
19
|
extract <url> Structured JSON. --prompt <text> or --schema <file.json>
|
|
20
20
|
rerank <query> Rank documents. --doc <text> (repeatable), --file <path>, or lines on stdin. -n top
|
|
21
|
+
monitor [subcmd] Watch pages/sites: create <url...>, list, get <id>, check <id>, pause <id>, resume <id>, delete <id>
|
|
21
22
|
targets List all supported crawl & search targets and their requirements
|
|
22
23
|
categories List search categories and vertical filters
|
|
23
24
|
setup Register Webcrawl MCP server and Agent Skill (Claude, Cursor, Codex, Gemini, Windsurf)
|
|
@@ -36,6 +37,12 @@ Options
|
|
|
36
37
|
--url <base> Override WEBCRAWL_URL (default https://webcrawl.app)
|
|
37
38
|
--country, --city Where to search from (default: this machine's timezone)
|
|
38
39
|
--no-cache Skip cached pages (scrape)
|
|
40
|
+
--scope <s|u|w> Monitor scope: page (default), urls, or site
|
|
41
|
+
--interval <mins> Monitor check interval in minutes (default 60)
|
|
42
|
+
--watch-for <text> What to watch for (e.g. "Pricing tiers and discounts")
|
|
43
|
+
--condition <expr> Trigger rule expression (e.g. 'price < 50', 'content contains "In Stock"')
|
|
44
|
+
--notify-email <e> Alert email address when triggered (or 'true' for account email)
|
|
45
|
+
--webhook <url> Alert webhook URL when triggered
|
|
39
46
|
--agent <name> Target agent for setup: claude, cursor, codex, gemini, windsurf, or all (repeatable)
|
|
40
47
|
--schema-profile MCP schema profile for setup: compact (default) or full
|
|
41
48
|
-h, --help
|
|
@@ -66,6 +73,12 @@ const OPTIONS = {
|
|
|
66
73
|
"no-wait": { type: "boolean" },
|
|
67
74
|
"no-cache": { type: "boolean" },
|
|
68
75
|
content: { type: "boolean" },
|
|
76
|
+
scope: { type: "string" },
|
|
77
|
+
interval: { type: "string" },
|
|
78
|
+
"watch-for": { type: "string" },
|
|
79
|
+
condition: { type: "string" },
|
|
80
|
+
"notify-email": { type: "string" },
|
|
81
|
+
webhook: { type: "string" },
|
|
69
82
|
agent: { type: "string", multiple: true },
|
|
70
83
|
"schema-profile": { type: "string" },
|
|
71
84
|
};
|
|
@@ -112,6 +125,52 @@ function openBrowserUrl(url) {
|
|
|
112
125
|
// ignore
|
|
113
126
|
}
|
|
114
127
|
}
|
|
128
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
129
|
+
/** Self-contained (no network assets) page served on the loopback callback. */
|
|
130
|
+
export function callbackPage(opts) {
|
|
131
|
+
const tone = opts.kind === "success" ? "#157f3b" : opts.kind === "error" ? "#b42318" : "#6236e8";
|
|
132
|
+
const icon = opts.kind === "success"
|
|
133
|
+
? `<path class="mk" d="M20 33l8 8 16-17"/>`
|
|
134
|
+
: opts.kind === "error"
|
|
135
|
+
? `<path class="mk" d="M23 23l18 18M41 23L23 41"/>`
|
|
136
|
+
: `<path class="mk" d="M32 22v14M32 42v.5"/>`;
|
|
137
|
+
const chips = [opts.email ? `<span class="chip">${esc(opts.email)}</span>` : "", opts.credits ? `<span class="chip hot">${esc(opts.credits)}</span>` : ""].join("");
|
|
138
|
+
const dots = Array.from({ length: 14 }, (_, i) => `<i style="--a:${i * 25.7}deg;--d:${70 + (i % 3) * 18}px;--t:${(i % 5) * 0.06}s"></i>`).join("");
|
|
139
|
+
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${esc(opts.title)}</title>
|
|
140
|
+
<style>
|
|
141
|
+
:root{--tone:${tone}}*{box-sizing:border-box}
|
|
142
|
+
body{margin:0;min-height:100vh;display:grid;place-items:center;padding:24px;color:#1b1631;font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Inter,sans-serif;background:#fbfaf8}
|
|
143
|
+
main{width:100%;max-width:440px;text-align:center;background:#fff;border:1px solid #e2ddd5;border-radius:16px;padding:36px 32px 28px;box-shadow:0 1px 2px #1b16310d,0 12px 32px #1b16310f;animation:rise .5s cubic-bezier(.2,.8,.2,1) both}
|
|
144
|
+
.brand{display:flex;align-items:center;justify-content:center;gap:8px;font-weight:600;letter-spacing:-.035em;color:#1b1631;margin-bottom:26px}.brand svg{width:26px;height:26px}
|
|
145
|
+
.badge{position:relative;width:96px;height:96px;margin:0 auto 18px}
|
|
146
|
+
.badge svg.ring{width:96px;height:96px;overflow:visible}
|
|
147
|
+
.ring circle{fill:color-mix(in srgb,var(--tone) 12%,transparent);stroke:var(--tone);stroke-width:2.5;stroke-dasharray:190;stroke-dashoffset:190;animation:draw .7s .1s ease-out forwards}
|
|
148
|
+
.mk{fill:none;stroke:var(--tone);stroke-width:4.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:60;stroke-dashoffset:60;animation:draw .45s .6s ease-out forwards}
|
|
149
|
+
.burst{position:absolute;inset:0;pointer-events:none}.burst i{position:absolute;left:50%;top:50%;width:6px;height:6px;margin:-3px;border-radius:50%;background:var(--tone);opacity:0;transform:rotate(var(--a)) translateY(0);animation:pop .9s calc(.7s + var(--t)) ease-out forwards}
|
|
150
|
+
h1{font-size:22px;margin:0 0 6px;letter-spacing:-.02em}p{margin:0 0 18px;color:#4a4460}
|
|
151
|
+
.chips{display:flex;gap:8px;justify-content:center;flex-wrap:wrap;margin-bottom:20px}
|
|
152
|
+
.chip{font-size:12.5px;padding:4px 10px;border-radius:999px;background:#f6f4f0;border:1px solid #e2ddd5;color:#4a4460}.chip.hot{background:#f1ecff;border-color:#d9ccff;color:#4f24cf}
|
|
153
|
+
.try{text-align:left;background:#f6f4f0;border:1px solid #efebe5;border-radius:10px;padding:12px 14px;margin-bottom:18px}
|
|
154
|
+
.trow{display:flex;align-items:center;justify-content:space-between;margin-bottom:4px}.trow small{margin:0!important}
|
|
155
|
+
.copy{font:600 12px/1 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;padding:6px 10px;border-radius:7px;border:1px solid #e2ddd5;background:#fff;color:#4a4460;cursor:pointer}.copy:hover{border-color:#6236e8;color:#4f24cf}
|
|
156
|
+
.try code{user-select:all;-webkit-user-select:all;cursor:text}
|
|
157
|
+
.try small{display:block;color:#5e5873;margin-bottom:4px;font-size:12px;text-transform:uppercase;letter-spacing:.08em}
|
|
158
|
+
.try code{font:13px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;color:#4f24cf;word-break:break-all}.try code:before{content:"$ ";color:#7d7690}
|
|
159
|
+
.foot{font-size:13px;color:#5e5873;margin:0}
|
|
160
|
+
@keyframes rise{from{opacity:0;transform:translateY(14px) scale(.98)}}
|
|
161
|
+
@keyframes draw{to{stroke-dashoffset:0}}
|
|
162
|
+
@keyframes pop{0%{opacity:1}100%{opacity:0;transform:rotate(var(--a)) translateY(calc(var(--d) * -1)) scale(.3)}}
|
|
163
|
+
@media (prefers-reduced-motion:reduce){*{animation-duration:.01s!important;animation-delay:0s!important}}
|
|
164
|
+
</style></head><body><main>
|
|
165
|
+
<div class="brand"><svg viewBox="0 0 32 32" aria-hidden="true"><rect width="32" height="32" rx="7" fill="#1b1631"/><g stroke-linejoin="round" stroke-linecap="round" fill="none" stroke-width="3"><path d="M5 21.5L16 27.5L27 21.5" stroke="#8a80b8"/><path d="M5 16L16 22L27 16" stroke="#c9b8ff"/></g><path d="M16 4.5L27 10.5L16 16.5L5 10.5Z" fill="#8b6cff" stroke="#8b6cff" stroke-width="2" stroke-linejoin="round"/></svg>Webcrawl</div>
|
|
166
|
+
<div class="badge"><svg class="ring" viewBox="0 0 64 64" aria-hidden="true"><circle cx="32" cy="32" r="30"/>${icon}</svg>${opts.kind === "success" ? `<div class="burst">${dots}</div>` : ""}</div>
|
|
167
|
+
<h1>${esc(opts.title)}</h1><p>${opts.body}</p>
|
|
168
|
+
${chips ? `<div class="chips">${chips}</div>` : ""}
|
|
169
|
+
${opts.tryIt ? `<div class="try"><div class="trow"><small>Try it now</small><button type="button" class="copy" id="cp">Copy</button></div><code id="cmd">${esc(opts.tryIt)}</code></div>
|
|
170
|
+
<script>(function(){var b=document.getElementById("cp"),c=document.getElementById("cmd");if(!b||!c)return;var t=c.textContent;function done(){b.textContent="Copied";setTimeout(function(){b.textContent="Copy"},1500)}function fb(){var r=document.createRange();r.selectNodeContents(c);var s=getSelection();s.removeAllRanges();s.addRange(r);try{document.execCommand("copy");done()}catch(e){}}b.onclick=function(){if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(t).then(done,fb)}else{fb()}}})()</script>` : ""}
|
|
171
|
+
<p class="foot">You can close this tab and return to your terminal.</p>
|
|
172
|
+
</main></body></html>`;
|
|
173
|
+
}
|
|
115
174
|
async function runWebOAuthLogin(baseUrl, fetchImpl, io, openUrl = openBrowserUrl) {
|
|
116
175
|
const verifier = crypto.randomBytes(32).toString("base64url");
|
|
117
176
|
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");
|
|
@@ -167,14 +226,14 @@ async function runWebOAuthLogin(baseUrl, fetchImpl, io, openUrl = openBrowserUrl
|
|
|
167
226
|
if (handled) {
|
|
168
227
|
// Browser retry / prefetch of the same callback: the code is single-use, so never exchange it twice.
|
|
169
228
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
170
|
-
res.end("
|
|
229
|
+
res.end(callbackPage({ kind: "info", title: "Already received", body: "This sign-in link was already used. Your terminal has everything it needs." }));
|
|
171
230
|
return;
|
|
172
231
|
}
|
|
173
232
|
const oauthError = u.searchParams.get("error");
|
|
174
233
|
if (oauthError && returnedState === state) {
|
|
175
234
|
handled = true;
|
|
176
235
|
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
177
|
-
res.end("
|
|
236
|
+
res.end(callbackPage({ kind: "error", title: "Sign-in cancelled", body: "You chose not to connect the CLI. Run <b>webcrawl login</b> whenever you want to try again." }));
|
|
178
237
|
clearTimeout(timeout);
|
|
179
238
|
server.close();
|
|
180
239
|
reject(new Error(`OAuth authorization failed: ${oauthError}.`));
|
|
@@ -183,7 +242,7 @@ async function runWebOAuthLogin(baseUrl, fetchImpl, io, openUrl = openBrowserUrl
|
|
|
183
242
|
if (!code || returnedState !== state) {
|
|
184
243
|
// Stray or forged request: reject it but keep waiting for the real callback.
|
|
185
244
|
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
186
|
-
res.end("<
|
|
245
|
+
res.end(callbackPage({ kind: "error", title: "Link not recognised", body: "This sign-in link does not match the one your terminal is waiting for. Run <b>webcrawl login</b> again for a fresh link." }));
|
|
187
246
|
return;
|
|
188
247
|
}
|
|
189
248
|
handled = true;
|
|
@@ -202,7 +261,7 @@ async function runWebOAuthLogin(baseUrl, fetchImpl, io, openUrl = openBrowserUrl
|
|
|
202
261
|
const detail = (await tokenRes.json().catch(() => ({})));
|
|
203
262
|
const reason = detail.error_description || detail.error;
|
|
204
263
|
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
205
|
-
res.end("
|
|
264
|
+
res.end(callbackPage({ kind: "error", title: "Sign-in failed", body: "Webcrawl could not finish signing you in. Your terminal has the details. Run <b>webcrawl login</b> to try again." }));
|
|
206
265
|
throw new Error(`OAuth token exchange failed (HTTP ${tokenRes.status}${reason ? `: ${reason}` : ""}). Run \`webcrawl login\` again, or use \`webcrawl login --email <you@domain.com>\`.`);
|
|
207
266
|
}
|
|
208
267
|
const tokenData = (await tokenRes.json());
|
|
@@ -224,7 +283,17 @@ async function runWebOAuthLogin(baseUrl, fetchImpl, io, openUrl = openBrowserUrl
|
|
|
224
283
|
}
|
|
225
284
|
const meRes = await authedClient.me().catch(() => undefined);
|
|
226
285
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
227
|
-
|
|
286
|
+
const u2 = meRes?.user;
|
|
287
|
+
res.end(callbackPage({
|
|
288
|
+
kind: "success",
|
|
289
|
+
title: "You're connected",
|
|
290
|
+
body: "Webcrawl CLI is signed in. Search, read and crawl the web from your terminal or your agent.",
|
|
291
|
+
email: u2?.email,
|
|
292
|
+
credits: u2?.creditsRemaining !== undefined && u2?.creditsLimit !== undefined
|
|
293
|
+
? `${u2.creditsRemaining.toLocaleString("en-US")} / ${u2.creditsLimit.toLocaleString("en-US")} credits`
|
|
294
|
+
: undefined,
|
|
295
|
+
tryIt: 'webcrawl search "best ramen in shibuya"',
|
|
296
|
+
}));
|
|
228
297
|
clearTimeout(timeout);
|
|
229
298
|
server.close();
|
|
230
299
|
resolve({
|
|
@@ -312,8 +381,11 @@ export async function main(argv, io, deps = {}) {
|
|
|
312
381
|
io.err(`[${credits}]\n`);
|
|
313
382
|
}
|
|
314
383
|
};
|
|
315
|
-
const METERED_COMMANDS = new Set(["search", "scrape", "map", "crawl", "extract", "rerank"]);
|
|
316
|
-
if (METERED_COMMANDS.has(cmd) &&
|
|
384
|
+
const METERED_COMMANDS = new Set(["search", "scrape", "map", "crawl", "extract", "rerank", "monitor"]);
|
|
385
|
+
if (METERED_COMMANDS.has(cmd) &&
|
|
386
|
+
(cfg.effectiveMode === "cloud" || cmd === "monitor") &&
|
|
387
|
+
!cfg.apiKey &&
|
|
388
|
+
!deps.fetch) {
|
|
317
389
|
const authHelp = [
|
|
318
390
|
"webcrawl: Cloud mode requires a logged-in webcrawl.app account so credits can be attributed to your user (1,000 free monthly credits on signup).",
|
|
319
391
|
"",
|
|
@@ -405,6 +477,110 @@ export async function main(argv, io, deps = {}) {
|
|
|
405
477
|
emit(res, formatRerank(res));
|
|
406
478
|
return 0;
|
|
407
479
|
}
|
|
480
|
+
case "monitor": {
|
|
481
|
+
const cloudClient = new WebcrawlClient({
|
|
482
|
+
apiKey: cfg.apiKey,
|
|
483
|
+
baseUrl: cfg.baseUrl,
|
|
484
|
+
fetch: deps.fetch,
|
|
485
|
+
userAgent: "webcrawl-cli/1.3",
|
|
486
|
+
});
|
|
487
|
+
const first = (rest[0] ?? "").trim();
|
|
488
|
+
const subcommands = new Set([
|
|
489
|
+
"list",
|
|
490
|
+
"ls",
|
|
491
|
+
"create",
|
|
492
|
+
"add",
|
|
493
|
+
"watch",
|
|
494
|
+
"get",
|
|
495
|
+
"inspect",
|
|
496
|
+
"check",
|
|
497
|
+
"pause",
|
|
498
|
+
"resume",
|
|
499
|
+
"delete",
|
|
500
|
+
"rm",
|
|
501
|
+
]);
|
|
502
|
+
const sub = !first
|
|
503
|
+
? "list"
|
|
504
|
+
: subcommands.has(first.toLowerCase())
|
|
505
|
+
? first.toLowerCase()
|
|
506
|
+
: "create";
|
|
507
|
+
const subArgs = subcommands.has(first.toLowerCase()) ? rest.slice(1) : rest;
|
|
508
|
+
const requireMonitorId = (actionName) => {
|
|
509
|
+
const id = (subArgs[0] ?? "").trim();
|
|
510
|
+
if (!id) {
|
|
511
|
+
throw new UsageError(`Missing monitor id. Usage: webcrawl monitor ${actionName} <id>`);
|
|
512
|
+
}
|
|
513
|
+
return id;
|
|
514
|
+
};
|
|
515
|
+
if (sub === "list" || sub === "ls") {
|
|
516
|
+
const res = await cloudClient.monitors();
|
|
517
|
+
emit(res, formatMonitor(res));
|
|
518
|
+
return 0;
|
|
519
|
+
}
|
|
520
|
+
if (sub === "get" || sub === "inspect") {
|
|
521
|
+
const res = await cloudClient.getMonitor(requireMonitorId(sub));
|
|
522
|
+
emit(res, formatMonitor(res));
|
|
523
|
+
return 0;
|
|
524
|
+
}
|
|
525
|
+
if (sub === "check") {
|
|
526
|
+
const res = await cloudClient.checkMonitor(requireMonitorId("check"));
|
|
527
|
+
emit(res, formatMonitor(res));
|
|
528
|
+
return 0;
|
|
529
|
+
}
|
|
530
|
+
if (sub === "pause" || sub === "resume") {
|
|
531
|
+
const id = requireMonitorId(sub);
|
|
532
|
+
const notifyEmailRaw = v["notify-email"];
|
|
533
|
+
const res = await cloudClient.updateMonitor({
|
|
534
|
+
id,
|
|
535
|
+
status: sub === "pause" ? "paused" : "active",
|
|
536
|
+
name: v.name,
|
|
537
|
+
intervalMinutes: int(v.interval, "--interval"),
|
|
538
|
+
watchFor: v["watch-for"],
|
|
539
|
+
condition: v.condition,
|
|
540
|
+
notifyEmail: notifyEmailRaw === undefined
|
|
541
|
+
? undefined
|
|
542
|
+
: notifyEmailRaw === "true"
|
|
543
|
+
? true
|
|
544
|
+
: notifyEmailRaw,
|
|
545
|
+
webhookUrl: v.webhook,
|
|
546
|
+
});
|
|
547
|
+
emit(res, formatMonitor(res));
|
|
548
|
+
return 0;
|
|
549
|
+
}
|
|
550
|
+
if (sub === "delete" || sub === "rm") {
|
|
551
|
+
const res = await cloudClient.deleteMonitor(requireMonitorId(sub));
|
|
552
|
+
emit(res, formatMonitor(res));
|
|
553
|
+
return 0;
|
|
554
|
+
}
|
|
555
|
+
// create / add / watch / `webcrawl monitor <url...>`
|
|
556
|
+
const urls = subArgs.map((s) => s.trim()).filter(Boolean);
|
|
557
|
+
if (!urls.length) {
|
|
558
|
+
throw new UsageError("Missing URL to watch. Usage: webcrawl monitor <url> [--scope page|urls|site] [--watch-for <text>] [--condition <expr>]");
|
|
559
|
+
}
|
|
560
|
+
const rawScope = (v.scope ?? (urls.length > 1 ? "urls" : "page")).toLowerCase();
|
|
561
|
+
if (rawScope !== "page" && rawScope !== "urls" && rawScope !== "site") {
|
|
562
|
+
throw new UsageError(`Invalid --scope "${v.scope}". Expected page, urls, or site.`);
|
|
563
|
+
}
|
|
564
|
+
const notifyEmailRaw = v["notify-email"];
|
|
565
|
+
const res = await cloudClient.createMonitor({
|
|
566
|
+
url: urls[0],
|
|
567
|
+
urls: rawScope === "urls" || urls.length > 1 ? urls : undefined,
|
|
568
|
+
mode: rawScope,
|
|
569
|
+
maxPages: int(v.limit, "--limit"),
|
|
570
|
+
name: v.name,
|
|
571
|
+
intervalMinutes: int(v.interval, "--interval"),
|
|
572
|
+
watchFor: v["watch-for"],
|
|
573
|
+
condition: v.condition,
|
|
574
|
+
notifyEmail: notifyEmailRaw === undefined
|
|
575
|
+
? undefined
|
|
576
|
+
: notifyEmailRaw === "true"
|
|
577
|
+
? true
|
|
578
|
+
: notifyEmailRaw,
|
|
579
|
+
webhookUrl: v.webhook,
|
|
580
|
+
});
|
|
581
|
+
emit(res, formatMonitor(res));
|
|
582
|
+
return 0;
|
|
583
|
+
}
|
|
408
584
|
case "categories": {
|
|
409
585
|
const res = await client.categories();
|
|
410
586
|
emit(res, JSON.stringify(res, null, 2));
|
package/dist/format.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export declare function formatCrawl(res: Rec): string;
|
|
|
6
6
|
export declare function formatRerank(res: Rec): string;
|
|
7
7
|
export declare function formatCredits(res: Rec): string;
|
|
8
8
|
export declare function formatTargets(res: Rec): string;
|
|
9
|
+
export declare function formatMonitor(res: Rec): string;
|
|
9
10
|
export {};
|
package/dist/format.js
CHANGED
|
@@ -111,3 +111,66 @@ export function formatTargets(res) {
|
|
|
111
111
|
})
|
|
112
112
|
.join("\n\n");
|
|
113
113
|
}
|
|
114
|
+
function formatMonitorItem(m, index) {
|
|
115
|
+
const num = index !== undefined ? `${String(index + 1).padStart(2)} ` : "";
|
|
116
|
+
const id = str(m.id);
|
|
117
|
+
const name = str(m.name) || str(m.url) || "(unnamed watch)";
|
|
118
|
+
const status = str(m.status) || "active";
|
|
119
|
+
const mode = str(m.mode) || "page";
|
|
120
|
+
const urls = Array.isArray(m.urls) ? m.urls.map(str).filter(Boolean) : [];
|
|
121
|
+
const scopeLabel = mode === "site"
|
|
122
|
+
? `site (max ${str(m.maxPages) || "5"} pages)`
|
|
123
|
+
: mode === "urls"
|
|
124
|
+
? `${urls.length || 1} URLs`
|
|
125
|
+
: "1 page";
|
|
126
|
+
const interval = typeof m.intervalMinutes === "number" ? `every ${m.intervalMinutes}m` : "";
|
|
127
|
+
const condExpr = str(m.conditionJev) || (typeof m.condition === "string" ? m.condition : "");
|
|
128
|
+
const goal = condExpr ? `rule: ${condExpr}` : str(m.watchFor) ? `goal: ${str(m.watchFor)}` : "any change";
|
|
129
|
+
const condMet = typeof m.lastConditionMet === "boolean"
|
|
130
|
+
? m.lastConditionMet
|
|
131
|
+
? "triggered"
|
|
132
|
+
: "waiting"
|
|
133
|
+
: "";
|
|
134
|
+
const meta = [status, scopeLabel, interval, condMet].filter(Boolean).join(" · ");
|
|
135
|
+
const pad = index !== undefined ? " " : "";
|
|
136
|
+
const diff = str(m.lastDiffSummary);
|
|
137
|
+
return [
|
|
138
|
+
`${num}${name} [${id}] (${meta})`,
|
|
139
|
+
`${pad}${str(m.url)}${urls.length > 1 ? ` (+${urls.length - 1} more)` : ""} · ${goal}`,
|
|
140
|
+
diff ? `${pad}Latest: ${diff}` : "",
|
|
141
|
+
]
|
|
142
|
+
.filter(Boolean)
|
|
143
|
+
.join("\n");
|
|
144
|
+
}
|
|
145
|
+
export function formatMonitor(res) {
|
|
146
|
+
if (res.deleted === true) {
|
|
147
|
+
return "Monitor deleted.";
|
|
148
|
+
}
|
|
149
|
+
if (Array.isArray(res.monitors)) {
|
|
150
|
+
const list = res.monitors.filter(isRec);
|
|
151
|
+
if (!list.length) {
|
|
152
|
+
return "No active watches.";
|
|
153
|
+
}
|
|
154
|
+
return list.map((m, i) => formatMonitorItem(m, i)).join("\n\n");
|
|
155
|
+
}
|
|
156
|
+
const m = isRec(res.monitor) ? res.monitor : isRec(res.data) ? res.data : null;
|
|
157
|
+
if (!m) {
|
|
158
|
+
return JSON.stringify(res, null, 2);
|
|
159
|
+
}
|
|
160
|
+
const check = isRec(res.check) ? res.check : isRec(res.initialCheck) ? res.initialCheck : null;
|
|
161
|
+
const lines = [formatMonitorItem(m)];
|
|
162
|
+
if (check) {
|
|
163
|
+
const condRes = isRec(check.conditionResult) ? check.conditionResult : null;
|
|
164
|
+
if (condRes && str(condRes.summary)) {
|
|
165
|
+
lines.push(`Condition: ${str(condRes.summary)} (met: ${Boolean(condRes.met)})`);
|
|
166
|
+
}
|
|
167
|
+
if (str(check.diffSummary)) {
|
|
168
|
+
lines.push(`Summary: ${str(check.diffSummary)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const snap = (check && str(check.snapshot)) || str(m.lastSnapshot);
|
|
172
|
+
if (snap) {
|
|
173
|
+
lines.push(`\n${snap}`);
|
|
174
|
+
}
|
|
175
|
+
return lines.join("\n");
|
|
176
|
+
}
|
package/dist/setup.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ export interface AgentTarget {
|
|
|
6
6
|
skillPath: (home: string, cwd: string) => string;
|
|
7
7
|
}
|
|
8
8
|
export declare const SUPPORTED_AGENTS: AgentTarget[];
|
|
9
|
-
export declare const WEBCRAWL_SKILL_MD = "---\nname: webcrawl-anything\ndescription: Crawl, search, and extract structured evidence from any website, SPA, PDF, Office file, YouTube video, GitHub repo, ArXiv paper, shopping offer, travel fare, or CVE advisory.\n---\n\n# Webcrawl Anything \u2014 Agent Investigation Workflow\n\nUse **Webcrawl Anything** (`webcrawl` CLI or `webcrawl` MCP tools) whenever you need live web search, anti-bot page scraping, document/video parsing, or structured vertical extraction with provenance.\n\n## 1. Choose the Right Tool or Target\n\n1. **Structured Search (`web_search` / `webcrawl search`)**:\n - Pass `category` when the query belongs to a vertical:\n - `shopping`: Multi-retailer product prices, stock availability, ratings, shipping.\n - `flights`: IATA routes, fares, airlines, stops, cabin class.\n - `hotels`: Nightly rates, star ratings, guest reviews, amenities.\n - `real-estate`: Property listings (`subcategory: \"buy\" | \"rent\" | \"sold\"`).\n - `business`: Local businesses, clinics, phone numbers, hours, ratings.\n - `security`: CVE / GHSA records with CVSS scores, CWEs, affected/fixed versions, and CISA KEV status.\n - `academic`: ArXiv and research papers.\n - `tech` / `news` / `general`: General and developer search.\n2. **Page & Developer Target Inspection (`web_scrape` / `webcrawl scrape <url>`)**:\n - Works on any website, client-rendered SPA, GitHub repository/issue/release, ArXiv abstract page, or `llms.txt` manifest.\n3. **Binary Documents & Media (`web_parse` / `webcrawl scrape <pdf-or-youtube-url>`)**:\n - Reads PDFs (native text + automatic OCR on scanned pages), Word (`.docx`), Excel (`.xlsx`), PowerPoint (`.pptx`), and YouTube URLs (timestamped transcripts).\n4. **Site Discovery & Multi-Page Crawl (`web_map` & `web_crawl`)**:\n - Always run `web_map` first on unfamiliar domains to inspect available URLs before running `web_crawl` (capped at 25 pages).\n\n## 2. Verify Evidence & Provenance\n\n- Check the returned `evidence` block (`targetId`, `tier`, `contentHash`, `matchedSignals`, `limitations`) before stating claims as fact.\n- If `limitations` notes that a page was a client-rendered shell or blocked by a challenge in static mode, escalate to stealth browser or `webcrawl.app` cloud (`webcrawl login`).\n";
|
|
9
|
+
export declare const WEBCRAWL_SKILL_MD = "---\nname: webcrawl-anything\ndescription: Crawl, search, and extract structured evidence from any website, SPA, PDF, Office file, YouTube video, GitHub repo, ArXiv paper, shopping offer, travel fare, or CVE advisory.\n---\n\n# Webcrawl Anything \u2014 Agent Investigation Workflow\n\nUse **Webcrawl Anything** (`webcrawl` CLI or `webcrawl` MCP tools) whenever you need live web search, anti-bot page scraping, document/video parsing, or structured vertical extraction with provenance.\n\n## 1. Choose the Right Tool or Target\n\n1. **Structured Search (`web_search` / `webcrawl search`)**:\n - Pass `category` when the query belongs to a vertical:\n - `shopping`: Multi-retailer product prices, stock availability, ratings, shipping.\n - `flights`: IATA routes, fares, airlines, stops, cabin class.\n - `hotels`: Nightly rates, star ratings, guest reviews, amenities.\n - `real-estate`: Property listings (`subcategory: \"buy\" | \"rent\" | \"sold\"`).\n - `business`: Local businesses, clinics, phone numbers, hours, ratings.\n - `security`: CVE / GHSA records with CVSS scores, CWEs, affected/fixed versions, and CISA KEV status.\n - `academic`: ArXiv and research papers.\n - `tech` / `news` / `general`: General and developer search.\n2. **Page & Developer Target Inspection (`web_scrape` / `webcrawl scrape <url>`)**:\n - Works on any website, client-rendered SPA, GitHub repository/issue/release, ArXiv abstract page, or `llms.txt` manifest.\n3. **Binary Documents & Media (`web_parse` / `webcrawl scrape <pdf-or-youtube-url>`)**:\n - Reads PDFs (native text + automatic OCR on scanned pages), Word (`.docx`), Excel (`.xlsx`), PowerPoint (`.pptx`), and YouTube URLs (timestamped transcripts).\n4. **Site Discovery & Multi-Page Crawl (`web_map` & `web_crawl`)**:\n - Always run `web_map` first on unfamiliar domains to inspect available URLs before running `web_crawl` (capped at 25 pages).\n5. **Scheduled Web Monitors (`web_monitor` / `webcrawl monitor`)**:\n - Watch a single page (`page`), a list of URLs (`urls`), or a whole website section (`site`) on a schedule with natural-language `watch_for` goals, trigger `condition` rules (e.g. `price < 50`, `content contains \"In Stock\"`, `added_urls_count > 0`), and email or webhook alerts.\n\n## 2. Verify Evidence & Provenance\n\n- Check the returned `evidence` block (`targetId`, `tier`, `contentHash`, `matchedSignals`, `limitations`) before stating claims as fact.\n- If `limitations` notes that a page was a client-rendered shell or blocked by a challenge in static mode, escalate to stealth browser or `webcrawl.app` cloud (`webcrawl login`).\n";
|
|
10
10
|
export interface SetupOptions {
|
|
11
11
|
agents?: SupportedAgentId[];
|
|
12
12
|
schemaProfile?: "compact" | "full";
|
package/dist/setup.js
CHANGED
|
@@ -67,6 +67,8 @@ Use **Webcrawl Anything** (\`webcrawl\` CLI or \`webcrawl\` MCP tools) whenever
|
|
|
67
67
|
- Reads PDFs (native text + automatic OCR on scanned pages), Word (\`.docx\`), Excel (\`.xlsx\`), PowerPoint (\`.pptx\`), and YouTube URLs (timestamped transcripts).
|
|
68
68
|
4. **Site Discovery & Multi-Page Crawl (\`web_map\` & \`web_crawl\`)**:
|
|
69
69
|
- Always run \`web_map\` first on unfamiliar domains to inspect available URLs before running \`web_crawl\` (capped at 25 pages).
|
|
70
|
+
5. **Scheduled Web Monitors (\`web_monitor\` / \`webcrawl monitor\`)**:
|
|
71
|
+
- Watch a single page (\`page\`), a list of URLs (\`urls\`), or a whole website section (\`site\`) on a schedule with natural-language \`watch_for\` goals, trigger \`condition\` rules (e.g. \`price < 50\`, \`content contains "In Stock"\`, \`added_urls_count > 0\`), and email or webhook alerts.
|
|
70
72
|
|
|
71
73
|
## 2. Verify Evidence & Provenance
|
|
72
74
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcrawl",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"description": "Webcrawl Anything CLI & Core Engine — crawl, search, and extract structured evidence from any website, SPA, PDF, Office file, YouTube video, GitHub repo, ArXiv paper, or CVE",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -127,8 +127,8 @@
|
|
|
127
127
|
"@ai-sdk/gateway": "^4.0.123",
|
|
128
128
|
"@firecrawl/pdf-inspector": "^1.25.2",
|
|
129
129
|
"@vercel/sandbox": "^1.0.0",
|
|
130
|
-
"@webcrawl/client": "^1.3.
|
|
131
|
-
"@webcrawl/curl-impersonate": "^1.3.
|
|
130
|
+
"@webcrawl/client": "^1.3.8",
|
|
131
|
+
"@webcrawl/curl-impersonate": "^1.3.8",
|
|
132
132
|
"ai": "^7.0.150",
|
|
133
133
|
"impit": "^0.14.7",
|
|
134
134
|
"mammoth": "^1.13.0",
|