mcp-scraper 0.3.24 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/api-server.cjs +8140 -5941
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +3 -3
- package/dist/bin/mcp-scraper-cli.cjs +320 -58
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +4 -3
- package/dist/bin/mcp-scraper-cli.js.map +1 -1
- package/dist/bin/mcp-scraper-install.cjs +3 -3
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +121 -24
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +5 -5
- package/dist/bin/paa-harvest.cjs +284 -10
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +5 -3
- package/dist/bin/paa-harvest.js.map +1 -1
- package/dist/{chunk-ZAP7MYVO.js → chunk-3PRO376E.js} +2 -3
- package/dist/chunk-3PRO376E.js.map +1 -0
- package/dist/{chunk-LFATOGDF.js → chunk-BP27CZ5Q.js} +4 -3
- package/dist/chunk-BP27CZ5Q.js.map +1 -0
- package/dist/{chunk-DE7WP4UU.js → chunk-DOBQN3EY.js} +3 -3
- package/dist/chunk-DOBQN3EY.js.map +1 -0
- package/dist/{chunk-GZ4W72WF.js → chunk-FE2WC4JR.js} +119 -23
- package/dist/chunk-FE2WC4JR.js.map +1 -0
- package/dist/{chunk-IGOEAKC7.js → chunk-G7PQ64LM.js} +274 -9
- package/dist/chunk-G7PQ64LM.js.map +1 -0
- package/dist/chunk-K443GQY5.js +24 -0
- package/dist/chunk-K443GQY5.js.map +1 -0
- package/dist/{chunk-H2R232HK.js → chunk-NNEIXK5L.js} +307 -62
- package/dist/chunk-NNEIXK5L.js.map +1 -0
- package/dist/{chunk-3KYRG7O7.js → chunk-TRNSXR46.js} +185 -2
- package/dist/chunk-TRNSXR46.js.map +1 -0
- package/dist/chunk-WPHAMNT7.js +7 -0
- package/dist/chunk-WPHAMNT7.js.map +1 -0
- package/dist/{chunk-JTWQGPKF.js → chunk-WYCER2HW.js} +33 -10
- package/dist/chunk-WYCER2HW.js.map +1 -0
- package/dist/{db-EG5ETPTY.js → db-FP2ABUU4.js} +32 -2
- package/dist/index.cjs +284 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/dist/{server-2SRARAO5.js → server-RRCKVBHG.js} +1563 -265
- package/dist/server-RRCKVBHG.js.map +1 -0
- package/dist/{worker-UM5UM6O5.js → worker-UC6D2756.js} +7 -6
- package/dist/{worker-UM5UM6O5.js.map → worker-UC6D2756.js.map} +1 -1
- package/docs/kernel-proxy-future-enhancements.md +80 -0
- package/docs/spec-browser-routing.md +108 -0
- package/docs/spec-mcp-tools.md +146 -0
- package/docs/specs/audit-visual-demo.md +309 -0
- package/package.json +5 -2
- package/dist/chunk-3KYRG7O7.js.map +0 -1
- package/dist/chunk-DE7WP4UU.js.map +0 -1
- package/dist/chunk-GZ4W72WF.js.map +0 -1
- package/dist/chunk-H2R232HK.js.map +0 -1
- package/dist/chunk-H7R4PPMG.js +0 -7
- package/dist/chunk-H7R4PPMG.js.map +0 -1
- package/dist/chunk-IGOEAKC7.js.map +0 -1
- package/dist/chunk-JTWQGPKF.js.map +0 -1
- package/dist/chunk-LFATOGDF.js.map +0 -1
- package/dist/chunk-ZAP7MYVO.js.map +0 -1
- package/dist/server-2SRARAO5.js.map +0 -1
- /package/dist/{db-EG5ETPTY.js.map → db-FP2ABUU4.js.map} +0 -0
|
@@ -5,18 +5,18 @@ import {
|
|
|
5
5
|
exportFanout,
|
|
6
6
|
registerPaaExtractorMcpTools,
|
|
7
7
|
sanitizeOutboundDiagnostics
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-FE2WC4JR.js";
|
|
9
9
|
import {
|
|
10
10
|
renderInstallTerminal
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-DOBQN3EY.js";
|
|
12
12
|
import {
|
|
13
13
|
PACKAGE_VERSION
|
|
14
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-WPHAMNT7.js";
|
|
15
15
|
import {
|
|
16
16
|
browserServiceProfileName,
|
|
17
17
|
browserServiceProfileSaveChanges
|
|
18
|
-
} from "../chunk-
|
|
19
|
-
import "../chunk-
|
|
18
|
+
} from "../chunk-3PRO376E.js";
|
|
19
|
+
import "../chunk-BP27CZ5Q.js";
|
|
20
20
|
|
|
21
21
|
// bin/mcp-stdio-server.ts
|
|
22
22
|
import { readFileSync } from "fs";
|
package/dist/bin/paa-harvest.cjs
CHANGED
|
@@ -32,13 +32,12 @@ function browserServiceApiKey() {
|
|
|
32
32
|
return value || void 0;
|
|
33
33
|
}
|
|
34
34
|
function browserServiceProxyId() {
|
|
35
|
-
|
|
36
|
-
return value || void 0;
|
|
35
|
+
return void 0;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
// src/schemas.ts
|
|
40
39
|
var import_zod = require("zod");
|
|
41
|
-
var DEFAULT_PROXY_MODE = "
|
|
40
|
+
var DEFAULT_PROXY_MODE = "none";
|
|
42
41
|
var DEFAULT_MAPS_PROXY_MODE = "location";
|
|
43
42
|
var HarvestOptionsSchema = import_zod.z.object({
|
|
44
43
|
query: import_zod.z.string().min(1),
|
|
@@ -60,7 +59,8 @@ var HarvestOptionsSchema = import_zod.z.object({
|
|
|
60
59
|
outputDir: import_zod.z.string().default("./paa-output"),
|
|
61
60
|
format: import_zod.z.enum(["json", "csv", "both"]).default("both"),
|
|
62
61
|
serpOnly: import_zod.z.boolean().default(false),
|
|
63
|
-
pages: import_zod.z.number().int().min(1).max(2).default(1)
|
|
62
|
+
pages: import_zod.z.number().int().min(1).max(2).default(1),
|
|
63
|
+
softDeadlineMs: import_zod.z.number().optional()
|
|
64
64
|
});
|
|
65
65
|
var MapsPlaceOptionsSchema = import_zod.z.object({
|
|
66
66
|
businessName: import_zod.z.string().min(1),
|
|
@@ -257,6 +257,192 @@ var LocationMismatchError = class extends Error {
|
|
|
257
257
|
}
|
|
258
258
|
};
|
|
259
259
|
|
|
260
|
+
// src/api/cost-telemetry.ts
|
|
261
|
+
var import_node_crypto2 = require("crypto");
|
|
262
|
+
|
|
263
|
+
// src/api/db.ts
|
|
264
|
+
var import_http = require("@libsql/client/http");
|
|
265
|
+
var import_node_crypto = require("crypto");
|
|
266
|
+
var import_zod2 = require("zod");
|
|
267
|
+
var DB_URL = process.env.TURSO_DATABASE_URL ?? "file:./paa-api.db";
|
|
268
|
+
var DB_TOKEN = process.env.TURSO_AUTH_TOKEN;
|
|
269
|
+
var _db = null;
|
|
270
|
+
function getDb() {
|
|
271
|
+
if (!_db) _db = (0, import_http.createClient)({ url: DB_URL, authToken: DB_TOKEN });
|
|
272
|
+
return _db;
|
|
273
|
+
}
|
|
274
|
+
var SiteAuditJobRowSchema = import_zod2.z.object({
|
|
275
|
+
id: import_zod2.z.string(),
|
|
276
|
+
user_id: import_zod2.z.number(),
|
|
277
|
+
status: import_zod2.z.string().default("pending"),
|
|
278
|
+
client_domain: import_zod2.z.string(),
|
|
279
|
+
session_path: import_zod2.z.string(),
|
|
280
|
+
request: import_zod2.z.string(),
|
|
281
|
+
result: import_zod2.z.string().nullable(),
|
|
282
|
+
error: import_zod2.z.string().nullable(),
|
|
283
|
+
created_at: import_zod2.z.string(),
|
|
284
|
+
updated_at: import_zod2.z.string()
|
|
285
|
+
});
|
|
286
|
+
var SiteAuditPhaseLogRowSchema = import_zod2.z.object({
|
|
287
|
+
id: import_zod2.z.number(),
|
|
288
|
+
job_id: import_zod2.z.string(),
|
|
289
|
+
phase: import_zod2.z.string(),
|
|
290
|
+
completed_at: import_zod2.z.string(),
|
|
291
|
+
output_summary: import_zod2.z.string()
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// src/api/cost-context.ts
|
|
295
|
+
var import_node_async_hooks = require("async_hooks");
|
|
296
|
+
var storage = new import_node_async_hooks.AsyncLocalStorage();
|
|
297
|
+
function runWithCostContext(ctx, fn) {
|
|
298
|
+
return storage.run(ctx, fn);
|
|
299
|
+
}
|
|
300
|
+
function currentCostContext() {
|
|
301
|
+
return storage.getStore();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/api/cost-rates.ts
|
|
305
|
+
var KERNEL_HEADLESS_USD_PER_SEC = 166667e-10;
|
|
306
|
+
var KERNEL_HEADFUL_USD_PER_SEC = 1333336e-10;
|
|
307
|
+
var HEADLESS_OPS = /* @__PURE__ */ new Set(["harvest", "maps_search", "yt_channel", "fb_search", "fb_ad", "instagram"]);
|
|
308
|
+
function kernelCostUsd(ms, headful) {
|
|
309
|
+
const sec = Math.max(0, ms) / 1e3;
|
|
310
|
+
return sec * (headful ? KERNEL_HEADFUL_USD_PER_SEC : KERNEL_HEADLESS_USD_PER_SEC);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/api/cost-telemetry.ts
|
|
314
|
+
var _ready = false;
|
|
315
|
+
async function migrateCostTelemetry() {
|
|
316
|
+
if (_ready) return;
|
|
317
|
+
const db = getDb();
|
|
318
|
+
await db.execute(`
|
|
319
|
+
CREATE TABLE IF NOT EXISTS kernel_session_log (
|
|
320
|
+
id TEXT PRIMARY KEY,
|
|
321
|
+
kernel_session_id TEXT,
|
|
322
|
+
op TEXT,
|
|
323
|
+
source TEXT NOT NULL,
|
|
324
|
+
probe_run_id TEXT,
|
|
325
|
+
user_id INTEGER,
|
|
326
|
+
stealth INTEGER,
|
|
327
|
+
headless_sent INTEGER,
|
|
328
|
+
proxy_used INTEGER,
|
|
329
|
+
fallback INTEGER NOT NULL DEFAULT 0,
|
|
330
|
+
opened_at TEXT,
|
|
331
|
+
closed_at TEXT,
|
|
332
|
+
duration_ms INTEGER NOT NULL DEFAULT 0,
|
|
333
|
+
est_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
334
|
+
est_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
335
|
+
error TEXT,
|
|
336
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
337
|
+
)
|
|
338
|
+
`);
|
|
339
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_op ON kernel_session_log(op)`);
|
|
340
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_probe ON kernel_session_log(probe_run_id)`);
|
|
341
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS kernel_session_log_created ON kernel_session_log(created_at)`);
|
|
342
|
+
try {
|
|
343
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_source TEXT`);
|
|
344
|
+
} catch {
|
|
345
|
+
}
|
|
346
|
+
try {
|
|
347
|
+
await db.execute(`ALTER TABLE kernel_session_log ADD COLUMN proxy_type TEXT`);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
await db.execute(`
|
|
351
|
+
CREATE TABLE IF NOT EXISTS vendor_usage_log (
|
|
352
|
+
id TEXT PRIMARY KEY,
|
|
353
|
+
op TEXT,
|
|
354
|
+
probe_run_id TEXT,
|
|
355
|
+
user_id INTEGER,
|
|
356
|
+
vendor TEXT NOT NULL,
|
|
357
|
+
model TEXT,
|
|
358
|
+
units REAL NOT NULL DEFAULT 0,
|
|
359
|
+
unit_type TEXT,
|
|
360
|
+
est_cost_usd REAL NOT NULL DEFAULT 0,
|
|
361
|
+
error TEXT,
|
|
362
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
363
|
+
)
|
|
364
|
+
`);
|
|
365
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_op ON vendor_usage_log(op)`);
|
|
366
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_probe ON vendor_usage_log(probe_run_id)`);
|
|
367
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS vendor_usage_log_created ON vendor_usage_log(created_at)`);
|
|
368
|
+
await db.execute(`
|
|
369
|
+
CREATE TABLE IF NOT EXISTS cost_probe_runs (
|
|
370
|
+
id TEXT PRIMARY KEY,
|
|
371
|
+
tool TEXT NOT NULL,
|
|
372
|
+
target TEXT,
|
|
373
|
+
started_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
374
|
+
wall_ms INTEGER NOT NULL DEFAULT 0,
|
|
375
|
+
success INTEGER NOT NULL DEFAULT 0,
|
|
376
|
+
error TEXT,
|
|
377
|
+
http_only INTEGER NOT NULL DEFAULT 0,
|
|
378
|
+
kernel_used INTEGER NOT NULL DEFAULT 0,
|
|
379
|
+
kernel_sessions INTEGER NOT NULL DEFAULT 0,
|
|
380
|
+
kernel_seconds_total REAL NOT NULL DEFAULT 0,
|
|
381
|
+
kernel_tier_observed TEXT,
|
|
382
|
+
est_kernel_cost_usd_headless REAL NOT NULL DEFAULT 0,
|
|
383
|
+
est_kernel_cost_usd_headful REAL NOT NULL DEFAULT 0,
|
|
384
|
+
vendor_cost_usd REAL NOT NULL DEFAULT 0,
|
|
385
|
+
charged_credits REAL NOT NULL DEFAULT 0,
|
|
386
|
+
margin_usd_headless_starter REAL NOT NULL DEFAULT 0,
|
|
387
|
+
margin_usd_headful_starter REAL NOT NULL DEFAULT 0,
|
|
388
|
+
notes TEXT
|
|
389
|
+
)
|
|
390
|
+
`);
|
|
391
|
+
await db.execute(`CREATE INDEX IF NOT EXISTS cost_probe_runs_tool ON cost_probe_runs(tool)`);
|
|
392
|
+
try {
|
|
393
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN units REAL`);
|
|
394
|
+
} catch {
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN unit_type TEXT`);
|
|
398
|
+
} catch {
|
|
399
|
+
}
|
|
400
|
+
try {
|
|
401
|
+
await db.execute(`ALTER TABLE cost_probe_runs ADD COLUMN mode TEXT`);
|
|
402
|
+
} catch {
|
|
403
|
+
}
|
|
404
|
+
_ready = true;
|
|
405
|
+
}
|
|
406
|
+
async function recordKernelSession(r) {
|
|
407
|
+
try {
|
|
408
|
+
await migrateCostTelemetry();
|
|
409
|
+
const ctx = currentCostContext();
|
|
410
|
+
const durationMs = Math.max(0, r.closedAtMs - r.openedAtMs);
|
|
411
|
+
const db = getDb();
|
|
412
|
+
await db.execute({
|
|
413
|
+
sql: `INSERT INTO kernel_session_log
|
|
414
|
+
(id, kernel_session_id, op, source, probe_run_id, user_id, stealth, headless_sent, proxy_used, proxy_source, proxy_type, fallback, opened_at, closed_at, duration_ms, est_cost_usd_headless, est_cost_usd_headful, error)
|
|
415
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`,
|
|
416
|
+
args: [
|
|
417
|
+
(0, import_node_crypto2.randomUUID)(),
|
|
418
|
+
r.kernelSessionId ?? null,
|
|
419
|
+
ctx?.op ?? null,
|
|
420
|
+
r.source,
|
|
421
|
+
ctx?.probeRunId ?? null,
|
|
422
|
+
ctx?.userId ?? null,
|
|
423
|
+
boolToInt(r.stealth),
|
|
424
|
+
boolToInt(r.headlessSent),
|
|
425
|
+
boolToInt(r.proxyUsed),
|
|
426
|
+
r.proxySource ?? null,
|
|
427
|
+
r.proxyType ?? null,
|
|
428
|
+
r.fallback ? 1 : 0,
|
|
429
|
+
new Date(r.openedAtMs).toISOString(),
|
|
430
|
+
new Date(r.closedAtMs).toISOString(),
|
|
431
|
+
durationMs,
|
|
432
|
+
kernelCostUsd(durationMs, false),
|
|
433
|
+
kernelCostUsd(durationMs, true),
|
|
434
|
+
r.error ?? null
|
|
435
|
+
]
|
|
436
|
+
});
|
|
437
|
+
} catch (err) {
|
|
438
|
+
console.warn("[cost-telemetry] recordKernelSession failed:", err instanceof Error ? err.message : String(err));
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
function boolToInt(v) {
|
|
442
|
+
if (v === null || v === void 0) return null;
|
|
443
|
+
return v ? 1 : 0;
|
|
444
|
+
}
|
|
445
|
+
|
|
260
446
|
// src/driver/BrowserDriver.ts
|
|
261
447
|
import_playwright_extra.chromium.use((0, import_puppeteer_extra_plugin_stealth.default)());
|
|
262
448
|
var DESKTOP_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36";
|
|
@@ -344,6 +530,12 @@ var BrowserDriver = class {
|
|
|
344
530
|
page = null;
|
|
345
531
|
kernelClient = null;
|
|
346
532
|
kernelSessionId = null;
|
|
533
|
+
kernelOpenedAtMs = null;
|
|
534
|
+
kernelStealth = null;
|
|
535
|
+
kernelProxyUsed = null;
|
|
536
|
+
kernelProxySource = null;
|
|
537
|
+
kernelProxyType = null;
|
|
538
|
+
kernelHeadlessSent = null;
|
|
347
539
|
debugEnabled = false;
|
|
348
540
|
debugSnapshot = {
|
|
349
541
|
kernel: null,
|
|
@@ -375,9 +567,16 @@ var BrowserDriver = class {
|
|
|
375
567
|
if (config.kernelProfileName && config.kernelProfileSaveChanges === true) {
|
|
376
568
|
await ensureKernelProfile(this.kernelClient, config.kernelProfileName);
|
|
377
569
|
}
|
|
570
|
+
const costCtx = currentCostContext();
|
|
571
|
+
let useHeadless;
|
|
572
|
+
if (config.headlessMode === "headful" || costCtx?.forceHeadful === true) useHeadless = false;
|
|
573
|
+
else if (config.headlessMode === "headless") useHeadless = true;
|
|
574
|
+
else useHeadless = costCtx?.forceHeadless === true || process.env.KERNEL_HEADLESS_DISABLED !== "true" && !!costCtx?.op && HEADLESS_OPS.has(costCtx.op);
|
|
575
|
+
this.kernelHeadlessSent = useHeadless ? true : null;
|
|
378
576
|
const kernelBrowser = await this.kernelClient.browsers.create({
|
|
379
577
|
stealth: true,
|
|
380
578
|
timeout_seconds: timeoutSeconds,
|
|
579
|
+
...useHeadless ? { headless: true } : {},
|
|
381
580
|
...config.kernelProxyId ? { proxy_id: config.kernelProxyId } : {},
|
|
382
581
|
...config.kernelProfileName ? {
|
|
383
582
|
profile: {
|
|
@@ -387,9 +586,14 @@ var BrowserDriver = class {
|
|
|
387
586
|
} : {}
|
|
388
587
|
});
|
|
389
588
|
this.kernelSessionId = kernelBrowser.session_id;
|
|
589
|
+
this.kernelOpenedAtMs = Date.now();
|
|
590
|
+
this.kernelStealth = typeof kernelBrowser.stealth === "boolean" ? kernelBrowser.stealth : null;
|
|
591
|
+
this.kernelProxyUsed = typeof kernelBrowser.proxy_id === "string" ? Boolean(kernelBrowser.proxy_id) : Boolean(config.kernelProxyId);
|
|
592
|
+
this.kernelProxySource = config.kernelProxyResolution?.source ?? null;
|
|
593
|
+
this.kernelProxyType = config.kernelProxyResolution?.proxyType ?? null;
|
|
390
594
|
let defaultProxyDisabled = null;
|
|
391
595
|
let defaultProxyDisableError = null;
|
|
392
|
-
if (
|
|
596
|
+
if (!config.kernelProxyId) {
|
|
393
597
|
try {
|
|
394
598
|
await withTimeout(
|
|
395
599
|
this.kernelClient.browsers.update(this.kernelSessionId, { disable_default_proxy: true }),
|
|
@@ -740,11 +944,24 @@ var BrowserDriver = class {
|
|
|
740
944
|
const b = this.browser;
|
|
741
945
|
const sessionId = this.kernelSessionId;
|
|
742
946
|
const client = this.kernelClient;
|
|
947
|
+
const openedAtMs = this.kernelOpenedAtMs;
|
|
948
|
+
const stealth = this.kernelStealth;
|
|
949
|
+
const proxyUsed = this.kernelProxyUsed;
|
|
950
|
+
const proxySource = this.kernelProxySource;
|
|
951
|
+
const proxyType = this.kernelProxyType;
|
|
952
|
+
const headlessSent = this.kernelHeadlessSent;
|
|
743
953
|
this.browser = null;
|
|
744
954
|
this.context = null;
|
|
745
955
|
this.page = null;
|
|
746
956
|
this.kernelSessionId = null;
|
|
747
957
|
this.kernelClient = null;
|
|
958
|
+
this.kernelOpenedAtMs = null;
|
|
959
|
+
this.kernelHeadlessSent = null;
|
|
960
|
+
this.kernelProxySource = null;
|
|
961
|
+
this.kernelProxyType = null;
|
|
962
|
+
if (client && sessionId && openedAtMs != null) {
|
|
963
|
+
void recordKernelSession({ kernelSessionId: sessionId, source: "browser_driver", stealth, headlessSent, proxyUsed, proxySource, proxyType, openedAtMs, closedAtMs: Date.now() });
|
|
964
|
+
}
|
|
748
965
|
if (client && sessionId) {
|
|
749
966
|
console.info(JSON.stringify({
|
|
750
967
|
event: "kernel_browser_delete_started",
|
|
@@ -1459,6 +1676,7 @@ var PAAExtractor = class {
|
|
|
1459
1676
|
);
|
|
1460
1677
|
let round = 0;
|
|
1461
1678
|
while (seenQs.size < options.maxQuestions) {
|
|
1679
|
+
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1462
1680
|
this.throwIfAborted(signal);
|
|
1463
1681
|
await this.throwIfCaptcha(page, "Google PAA expansion");
|
|
1464
1682
|
const beforeQs = await readAllQs();
|
|
@@ -1468,6 +1686,7 @@ var PAAExtractor = class {
|
|
|
1468
1686
|
if (unexpandedCount === 0) break;
|
|
1469
1687
|
this.reporter.onDepth(++round);
|
|
1470
1688
|
for (let ci = 0; ci < unexpandedCount; ci++) {
|
|
1689
|
+
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
1471
1690
|
this.throwIfAborted(signal);
|
|
1472
1691
|
try {
|
|
1473
1692
|
const btn = page.locator(unexpandedSel).first();
|
|
@@ -2131,6 +2350,28 @@ var PAAExtractor = class {
|
|
|
2131
2350
|
var import_node_fs = require("fs");
|
|
2132
2351
|
var import_node_path = __toESM(require("path"), 1);
|
|
2133
2352
|
var import_papaparse = __toESM(require("papaparse"), 1);
|
|
2353
|
+
|
|
2354
|
+
// src/output/memory-library-sink.ts
|
|
2355
|
+
async function postToMemoryLibrary(args) {
|
|
2356
|
+
const url = process.env.MCP_MEMORY_INGEST_URL;
|
|
2357
|
+
const key = process.env.MCP_MEMORY_KEY;
|
|
2358
|
+
if (!url || !key) return;
|
|
2359
|
+
try {
|
|
2360
|
+
const res = await fetch(url, {
|
|
2361
|
+
method: "POST",
|
|
2362
|
+
headers: { "content-type": "application/json" },
|
|
2363
|
+
body: JSON.stringify({ data: { apiKey: key, title: args.title, content: args.content, source: args.source } })
|
|
2364
|
+
});
|
|
2365
|
+
const body = await res.json().catch(() => null);
|
|
2366
|
+
if (!res.ok || body?.result?.error) {
|
|
2367
|
+
console.warn("[memory-library-sink] ingest not accepted:", res.status, body?.result?.message ?? "");
|
|
2368
|
+
}
|
|
2369
|
+
} catch (err) {
|
|
2370
|
+
console.warn("[memory-library-sink] ingest failed:", err?.message);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
|
|
2374
|
+
// src/output/OutputSerializer.ts
|
|
2134
2375
|
var OutputSerializer = class {
|
|
2135
2376
|
async writeJSON(result, outputDir) {
|
|
2136
2377
|
await import_node_fs.promises.mkdir(outputDir, { recursive: true });
|
|
@@ -2138,6 +2379,7 @@ var OutputSerializer = class {
|
|
|
2138
2379
|
const filename = `${slug}-${Date.now()}.json`;
|
|
2139
2380
|
const fullPath = import_node_path.default.join(outputDir, filename);
|
|
2140
2381
|
await import_node_fs.promises.writeFile(fullPath, JSON.stringify(result, null, 2), "utf8");
|
|
2382
|
+
await postToMemoryLibrary({ title: `${result.seed} scrape`, content: JSON.stringify(result), source: `mcp-scraper:${fullPath}` });
|
|
2141
2383
|
return fullPath;
|
|
2142
2384
|
}
|
|
2143
2385
|
async writeCSV(rows, outputDir) {
|
|
@@ -2335,11 +2577,13 @@ function proxyIdSuffix2(proxyId) {
|
|
|
2335
2577
|
return proxyId ? proxyId.slice(-6) : null;
|
|
2336
2578
|
}
|
|
2337
2579
|
function resolution(source, proxyMode, proxyId, target, error, disposable = false) {
|
|
2580
|
+
const proxyType = source === "disabled" ? "none" : source === "configured_fallback" ? "configured" : source === "isp_created" ? "isp" : source === "location_created" || source === "location_reused" ? "residential" : "unknown";
|
|
2338
2581
|
return {
|
|
2339
2582
|
kernelProxyId: proxyId,
|
|
2340
2583
|
...disposable && proxyId ? { disposableProxyId: proxyId } : {},
|
|
2341
2584
|
resolution: {
|
|
2342
2585
|
source,
|
|
2586
|
+
proxyType,
|
|
2343
2587
|
proxyMode,
|
|
2344
2588
|
proxyIdPresent: Boolean(proxyId),
|
|
2345
2589
|
proxyIdSuffix: proxyIdSuffix2(proxyId),
|
|
@@ -2534,6 +2778,22 @@ async function createFreshLocationProxy(kernel, options, target) {
|
|
|
2534
2778
|
createErrors.push(`${candidate.proxyName}: ${errorText2(err)}`);
|
|
2535
2779
|
}
|
|
2536
2780
|
}
|
|
2781
|
+
if (process.env.KERNEL_PROXY_TYPE !== "residential") {
|
|
2782
|
+
const ispTarget = stateTarget(target);
|
|
2783
|
+
try {
|
|
2784
|
+
const created = await kernel.proxies.create({
|
|
2785
|
+
type: "isp",
|
|
2786
|
+
name: freshProxyName(`mcp-serp-isp-${ispTarget.country.toLowerCase()}-${(ispTarget.state ?? "").toLowerCase()}`, options.attemptIndex),
|
|
2787
|
+
config: { country: ispTarget.country, ...ispTarget.state ? { state: ispTarget.state } : {} }
|
|
2788
|
+
});
|
|
2789
|
+
if (created.id) {
|
|
2790
|
+
return resolution("isp_created", options.proxyMode, created.id, ispTarget, null, true);
|
|
2791
|
+
}
|
|
2792
|
+
createErrors.push("isp: Kernel did not return a proxy id");
|
|
2793
|
+
} catch (err) {
|
|
2794
|
+
createErrors.push(`isp: ${errorText2(err)}`);
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2537
2797
|
return resolution("configured_fallback", options.proxyMode, options.configuredKernelProxyId, target, createErrors.join(" | "));
|
|
2538
2798
|
}
|
|
2539
2799
|
async function deleteKernelProxyId(kernelApiKey, proxyId) {
|
|
@@ -2817,9 +3077,11 @@ async function harvest(rawOptions) {
|
|
|
2817
3077
|
};
|
|
2818
3078
|
const requestedDebug = typeof raw.debug === "boolean" ? raw.debug : false;
|
|
2819
3079
|
const needsLocationEvidence = proxyMode === "location" && Boolean(proxyOpts.location);
|
|
2820
|
-
const maxAttempts = maxAttemptsForProxyMode(proxyMode);
|
|
3080
|
+
const maxAttempts = maxAttemptsForProxyMode(proxyMode) + 1;
|
|
2821
3081
|
const serializer = new OutputSerializer();
|
|
2822
3082
|
let lastError = null;
|
|
3083
|
+
let effectiveProxyMode = proxyMode;
|
|
3084
|
+
let proxyFailures = 0;
|
|
2823
3085
|
for (let i = 0; i < maxAttempts; i++) {
|
|
2824
3086
|
const attemptNumber = i + 1;
|
|
2825
3087
|
const startedAtMs = Date.now();
|
|
@@ -2827,18 +3089,19 @@ async function harvest(rawOptions) {
|
|
|
2827
3089
|
if (signal?.aborted) throw abortReason(signal);
|
|
2828
3090
|
const resolution2 = await resolveKernelProxyId({
|
|
2829
3091
|
...proxyOpts,
|
|
3092
|
+
proxyMode: effectiveProxyMode,
|
|
2830
3093
|
attemptIndex: i,
|
|
2831
|
-
fresh:
|
|
3094
|
+
fresh: effectiveProxyMode === "location"
|
|
2832
3095
|
});
|
|
2833
3096
|
const mergedAttempt = {
|
|
2834
3097
|
...raw,
|
|
2835
3098
|
kernelApiKey,
|
|
2836
3099
|
kernelProxyId: resolution2.kernelProxyId,
|
|
2837
3100
|
kernelProxyResolution: resolution2.resolution,
|
|
2838
|
-
proxyMode,
|
|
3101
|
+
proxyMode: effectiveProxyMode,
|
|
2839
3102
|
debug: requestedDebug || needsLocationEvidence
|
|
2840
3103
|
};
|
|
2841
|
-
if (
|
|
3104
|
+
if (effectiveProxyMode === "none") mergedAttempt.kernelProxyId = void 0;
|
|
2842
3105
|
const attemptOptions = HarvestOptionsSchema.parse(mergedAttempt);
|
|
2843
3106
|
await emitAttemptEvent(onAttemptEvent, {
|
|
2844
3107
|
type: "started",
|
|
@@ -2857,10 +3120,17 @@ async function harvest(rawOptions) {
|
|
|
2857
3120
|
location: attemptOptions.location ?? null,
|
|
2858
3121
|
max_questions: attemptOptions.maxQuestions
|
|
2859
3122
|
}));
|
|
2860
|
-
const
|
|
3123
|
+
const isHeadfulEscalation = i === maxAttempts - 1;
|
|
3124
|
+
const needHeadful = isHeadfulEscalation || !attemptOptions.serpOnly;
|
|
3125
|
+
const baseCtx = currentCostContext();
|
|
3126
|
+
const attempt = needHeadful && baseCtx ? await runWithCostContext({ ...baseCtx, forceHeadful: true }, () => extractOnce(attemptOptions, signal)) : await extractOnce(attemptOptions, signal);
|
|
2861
3127
|
if (attempt.error) {
|
|
2862
3128
|
const err = attempt.error;
|
|
2863
3129
|
const outcome = classifyAttemptError(err);
|
|
3130
|
+
if ((outcome === "captcha" || outcome === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3131
|
+
proxyFailures++;
|
|
3132
|
+
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3133
|
+
}
|
|
2864
3134
|
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
2865
3135
|
if (outcome === "captcha") {
|
|
2866
3136
|
console.warn(JSON.stringify({
|
|
@@ -2962,6 +3232,10 @@ async function harvest(rawOptions) {
|
|
|
2962
3232
|
return finalResult;
|
|
2963
3233
|
} catch (err) {
|
|
2964
3234
|
const outcome = classifyAttemptError(err);
|
|
3235
|
+
if ((outcome === "captcha" || outcome === "proxy_tunnel_failed") && effectiveProxyMode === "location") {
|
|
3236
|
+
proxyFailures++;
|
|
3237
|
+
if (proxyFailures >= 2) effectiveProxyMode = "none";
|
|
3238
|
+
}
|
|
2965
3239
|
const willRetry = i < maxAttempts - 1 && (outcome === "captcha" || proxyMode === "location" && retryableLocationProxyError(outcome));
|
|
2966
3240
|
if (outcome === "captcha") {
|
|
2967
3241
|
console.warn(JSON.stringify({
|