mcp-scraper 0.6.0 → 0.7.0
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 +296 -198
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +1 -1
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +1 -1
- package/dist/bin/mcp-stdio-server.cjs +3 -3
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +3 -3
- package/dist/bin/paa-harvest.cjs +166 -74
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/chunk-EGWJ74EX.js +7 -0
- package/dist/chunk-EGWJ74EX.js.map +1 -0
- package/dist/{chunk-APJO2XV5.js → chunk-HL33CGJF.js} +3 -3
- package/dist/{chunk-APJO2XV5.js.map → chunk-HL33CGJF.js.map} +1 -1
- package/dist/{chunk-AUCXKRRH.js → chunk-RMPPYKUV.js} +167 -75
- package/dist/chunk-RMPPYKUV.js.map +1 -0
- package/dist/{chunk-BL7BBSYF.js → chunk-RUGJE5EB.js} +2 -2
- package/dist/{chunk-3K5GT6VG.js → chunk-YGRZU7IR.js} +3 -3
- package/dist/index.cjs +166 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-WHM446IZ.js → server-JNY4XPZE.js} +81 -76
- package/dist/server-JNY4XPZE.js.map +1 -0
- package/dist/{site-extract-repository-U476J44K.js → site-extract-repository-NVSZH35Y.js} +3 -3
- package/dist/{worker-KJZ3ZN2N.js → worker-JQTS437L.js} +4 -4
- package/dist/worker-JQTS437L.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-AUCXKRRH.js.map +0 -1
- package/dist/chunk-F2BXETDX.js +0 -7
- package/dist/chunk-F2BXETDX.js.map +0 -1
- package/dist/server-WHM446IZ.js.map +0 -1
- package/dist/worker-KJZ3ZN2N.js.map +0 -1
- package/docs/spec-browser-routing.md +0 -108
- package/docs/spec-kernel-computer-controls.md +0 -166
- package/docs/spec-mcp-tools.md +0 -146
- package/docs/spec-server-instructions.md +0 -94
- package/docs/specs/agent-ready-seo-packet-spec.md +0 -237
- package/docs/specs/audit-visual-demo.md +0 -309
- package/docs/specs/cli-agent-wiring-spec.md +0 -206
- package/docs/specs/local-competitive-audit-spec.md +0 -311
- package/docs/specs/scheduled-workflows-api-spec.md +0 -304
- package/docs/specs/seo-cli-growth-roadmap-spec.md +0 -181
- package/docs/specs/seo-workflow-runner-and-reports-spec.md +0 -241
- /package/dist/{chunk-BL7BBSYF.js.map → chunk-RUGJE5EB.js.map} +0 -0
- /package/dist/{chunk-3K5GT6VG.js.map → chunk-YGRZU7IR.js.map} +0 -0
- /package/dist/{site-extract-repository-U476J44K.js.map → site-extract-repository-NVSZH35Y.js.map} +0 -0
package/dist/bin/api-server.cjs
CHANGED
|
@@ -6372,8 +6372,8 @@ async function downloadAsset(url, destDir, filename) {
|
|
|
6372
6372
|
}
|
|
6373
6373
|
const writer = (0, import_node_fs.createWriteStream)(dest);
|
|
6374
6374
|
await (0, import_promises2.pipeline)(import_node_stream.Readable.fromWeb(res.body), writer);
|
|
6375
|
-
const { statSync:
|
|
6376
|
-
const sizeBytes =
|
|
6375
|
+
const { statSync: statSync2 } = await import("fs");
|
|
6376
|
+
const sizeBytes = statSync2(dest).size;
|
|
6377
6377
|
return { savedPath: dest, sizeBytes, mimeType };
|
|
6378
6378
|
}
|
|
6379
6379
|
async function harvestPageMedia(html, pageUrl, options = {}) {
|
|
@@ -7992,8 +7992,8 @@ var init_rates = __esm({
|
|
|
7992
7992
|
"use strict";
|
|
7993
7993
|
MC_COSTS = {
|
|
7994
7994
|
serp: 100,
|
|
7995
|
-
paa:
|
|
7996
|
-
paa_base:
|
|
7995
|
+
paa: 300,
|
|
7996
|
+
paa_base: 5500,
|
|
7997
7997
|
page_scrape: 100,
|
|
7998
7998
|
url_map: 500,
|
|
7999
7999
|
yt_channel: 200,
|
|
@@ -10648,8 +10648,8 @@ var init_blob_store = __esm({
|
|
|
10648
10648
|
async get(key) {
|
|
10649
10649
|
const path6 = (0, import_node_path2.join)(this.baseDir, "blobs", key);
|
|
10650
10650
|
try {
|
|
10651
|
-
const { readFileSync:
|
|
10652
|
-
return
|
|
10651
|
+
const { readFileSync: readFileSync6 } = await import("fs");
|
|
10652
|
+
return readFileSync6(path6);
|
|
10653
10653
|
} catch {
|
|
10654
10654
|
return null;
|
|
10655
10655
|
}
|
|
@@ -15788,6 +15788,12 @@ var init_facebook_ad_routes = __esm({
|
|
|
15788
15788
|
}
|
|
15789
15789
|
}
|
|
15790
15790
|
const results = [...advertiserMap.values()].sort((a, b) => b.adCount - a.adCount).slice(0, maxResults).map((a) => ({ name: a.pageName, pageName: a.pageName, libraryId: a.sampleLibraryId, sampleLibraryId: a.sampleLibraryId, adCount: a.adCount }));
|
|
15791
|
+
if (results.length === 0) {
|
|
15792
|
+
await creditMc(fbUser.id, MC_COSTS.fb_search, LedgerOperation.FB_SEARCH_REFUND, "empty result");
|
|
15793
|
+
searchRefunded = true;
|
|
15794
|
+
await logRequestEvent({ userId: fbUser.id, source: "facebook_search", status: "failed", query: body.query.trim(), error: "empty result refunded" });
|
|
15795
|
+
return c.json({ error: "no advertisers found (refunded)" }, 503);
|
|
15796
|
+
}
|
|
15791
15797
|
const searchResult = { query: body.query.trim(), searchUrl, results };
|
|
15792
15798
|
await logRequestEvent({ userId: fbUser.id, source: "facebook_search", status: "done", query: body.query.trim(), resultCount: results.length, result: searchResult });
|
|
15793
15799
|
return c.json(searchResult);
|
|
@@ -16600,20 +16606,15 @@ async function resolveInstagramLaunch(body) {
|
|
|
16600
16606
|
}
|
|
16601
16607
|
};
|
|
16602
16608
|
}
|
|
16603
|
-
function outputBaseDir() {
|
|
16604
|
-
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path7.join)((0, import_node_os5.homedir)(), "Downloads", "mcp-scraper");
|
|
16605
|
-
}
|
|
16606
16609
|
function safeFilePart(input) {
|
|
16607
16610
|
return input.replace(/^https?:\/\//, "").replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "instagram";
|
|
16608
16611
|
}
|
|
16609
|
-
function
|
|
16612
|
+
function blobKeyPrefix(shortcode, sourceUrl) {
|
|
16610
16613
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
16611
16614
|
const slug = shortcode ? `instagram-${shortcode}` : safeFilePart(sourceUrl);
|
|
16612
|
-
|
|
16613
|
-
(0, import_node_fs5.mkdirSync)(dir, { recursive: true });
|
|
16614
|
-
return dir;
|
|
16615
|
+
return `instagram/${stamp}-${slug}`;
|
|
16615
16616
|
}
|
|
16616
|
-
async function
|
|
16617
|
+
async function fetchMediaBytes(url, referer) {
|
|
16617
16618
|
const res = await fetch(url, {
|
|
16618
16619
|
headers: {
|
|
16619
16620
|
"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",
|
|
@@ -16623,12 +16624,8 @@ async function downloadToFile(url, destPath, referer) {
|
|
|
16623
16624
|
});
|
|
16624
16625
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
16625
16626
|
if (!res.body) throw new Error("Empty response body");
|
|
16626
|
-
|
|
16627
|
-
return {
|
|
16628
|
-
savedPath: destPath,
|
|
16629
|
-
sizeBytes: (0, import_node_fs5.statSync)(destPath).size,
|
|
16630
|
-
mimeType: res.headers.get("content-type")?.split(";")[0]?.trim() ?? null
|
|
16631
|
-
};
|
|
16627
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
16628
|
+
return { bytes, mimeType: res.headers.get("content-type")?.split(";")[0]?.trim() ?? null };
|
|
16632
16629
|
}
|
|
16633
16630
|
function extFromMime(mimeType, fallback) {
|
|
16634
16631
|
const map = {
|
|
@@ -16661,7 +16658,7 @@ function trackFilename(track, index, shortcode) {
|
|
|
16661
16658
|
const bitrate = track.bitrate ? `-${track.bitrate}` : "";
|
|
16662
16659
|
return `${label}-${type}-${index}${bitrate}.mp4`;
|
|
16663
16660
|
}
|
|
16664
|
-
var import_hono7, import_zod18, import_node_fs5, import_node_os5, import_node_path7,
|
|
16661
|
+
var import_hono7, import_zod18, import_node_fs5, import_node_os5, import_node_path7, import_node_child_process2, InstagramProfileContentBodySchema, InstagramMediaTypeSchema, InstagramMediaDownloadBodySchema, instagramApp;
|
|
16665
16662
|
var init_instagram_routes = __esm({
|
|
16666
16663
|
"src/api/instagram-routes.ts"() {
|
|
16667
16664
|
"use strict";
|
|
@@ -16670,8 +16667,6 @@ var init_instagram_routes = __esm({
|
|
|
16670
16667
|
import_node_fs5 = require("fs");
|
|
16671
16668
|
import_node_os5 = require("os");
|
|
16672
16669
|
import_node_path7 = require("path");
|
|
16673
|
-
import_node_stream2 = require("stream");
|
|
16674
|
-
import_promises4 = require("stream/promises");
|
|
16675
16670
|
import_node_child_process2 = require("child_process");
|
|
16676
16671
|
init_browser_service_env();
|
|
16677
16672
|
init_BrowserDriver();
|
|
@@ -16680,6 +16675,7 @@ var init_instagram_routes = __esm({
|
|
|
16680
16675
|
init_db();
|
|
16681
16676
|
init_rates();
|
|
16682
16677
|
init_concurrency_gates();
|
|
16678
|
+
init_blob_store();
|
|
16683
16679
|
init_InstagramContentExtractor();
|
|
16684
16680
|
init_media_transcription();
|
|
16685
16681
|
InstagramProfileContentBodySchema = import_zod18.z.object({
|
|
@@ -16794,55 +16790,65 @@ var init_instagram_routes = __esm({
|
|
|
16794
16790
|
const mediaTypes = new Set(body.mediaTypes);
|
|
16795
16791
|
const downloads = [];
|
|
16796
16792
|
const warnings = [];
|
|
16797
|
-
let outputDir = null;
|
|
16798
16793
|
if (body.downloadMedia) {
|
|
16799
|
-
|
|
16800
|
-
|
|
16801
|
-
|
|
16802
|
-
|
|
16803
|
-
|
|
16804
|
-
|
|
16805
|
-
|
|
16806
|
-
|
|
16807
|
-
|
|
16808
|
-
|
|
16809
|
-
const
|
|
16810
|
-
|
|
16811
|
-
|
|
16812
|
-
|
|
16813
|
-
|
|
16814
|
-
|
|
16815
|
-
const
|
|
16816
|
-
|
|
16794
|
+
let muxWorkDir = null;
|
|
16795
|
+
try {
|
|
16796
|
+
const store = getBlobStore();
|
|
16797
|
+
const prefix = blobKeyPrefix(extraction.shortcode, sourceUrl.href);
|
|
16798
|
+
const textContent = [
|
|
16799
|
+
`URL: ${extraction.pageUrl}`,
|
|
16800
|
+
extraction.caption ? `Caption: ${extraction.caption}` : "",
|
|
16801
|
+
"",
|
|
16802
|
+
extraction.bodyText
|
|
16803
|
+
].filter(Boolean).join("\n");
|
|
16804
|
+
const storedText = await store.put(`${prefix}/text.txt`, textContent, "text/plain");
|
|
16805
|
+
downloads.push({ kind: "text", url: null, savedPath: storedText.url, sizeBytes: storedText.bytes, mimeType: "text/plain", error: null });
|
|
16806
|
+
if (mediaTypes.has("image") && extraction.imageUrl) {
|
|
16807
|
+
try {
|
|
16808
|
+
const { bytes, mimeType } = await fetchMediaBytes(extraction.imageUrl, extraction.pageUrl);
|
|
16809
|
+
const ext = extFromMime(mimeType, "jpg");
|
|
16810
|
+
const stored = await store.put(`${prefix}/image.${ext}`, bytes, mimeType ?? "application/octet-stream");
|
|
16811
|
+
downloads.push({ kind: "image", url: extraction.imageUrl, savedPath: stored.url, sizeBytes: stored.bytes, mimeType, error: null });
|
|
16812
|
+
} catch (err) {
|
|
16813
|
+
downloads.push({ kind: "image", url: extraction.imageUrl, savedPath: null, sizeBytes: null, mimeType: null, error: err instanceof Error ? err.message : String(err) });
|
|
16817
16814
|
}
|
|
16818
|
-
downloads.push({ kind: "image", url: extraction.imageUrl, savedPath: finalPath, sizeBytes: (0, import_node_fs5.statSync)(finalPath).size, mimeType: downloaded.mimeType, error: null });
|
|
16819
|
-
} catch (err) {
|
|
16820
|
-
downloads.push({ kind: "image", url: extraction.imageUrl, savedPath: null, sizeBytes: null, mimeType: null, error: err instanceof Error ? err.message : String(err) });
|
|
16821
16815
|
}
|
|
16822
|
-
|
|
16823
|
-
|
|
16824
|
-
|
|
16825
|
-
|
|
16826
|
-
|
|
16827
|
-
|
|
16828
|
-
|
|
16829
|
-
|
|
16830
|
-
|
|
16831
|
-
|
|
16832
|
-
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16816
|
+
const tracksToDownload = body.downloadAllTracks ? extraction.tracks : [extraction.selectedVideoTrack, extraction.selectedAudioTrack].filter((track) => Boolean(track));
|
|
16817
|
+
const muxEligible = Boolean(body.mux && mediaTypes.has("video") && mediaTypes.has("audio") && extraction.selectedVideoTrack && extraction.selectedAudioTrack);
|
|
16818
|
+
const muxInputs = /* @__PURE__ */ new Map();
|
|
16819
|
+
for (const [index, track] of tracksToDownload.entries()) {
|
|
16820
|
+
if (track.streamType === "video" && !mediaTypes.has("video")) continue;
|
|
16821
|
+
if (track.streamType === "audio" && !mediaTypes.has("audio")) continue;
|
|
16822
|
+
const kind = track.streamType === "audio" ? "audio" : "video";
|
|
16823
|
+
try {
|
|
16824
|
+
const { bytes, mimeType } = await fetchMediaBytes(track.url, extraction.pageUrl);
|
|
16825
|
+
const stored = await store.put(`${prefix}/${trackFilename(track, index, extraction.shortcode)}`, bytes, mimeType ?? "video/mp4");
|
|
16826
|
+
downloads.push({ kind, url: track.url, savedPath: stored.url, sizeBytes: stored.bytes, mimeType, error: null });
|
|
16827
|
+
const isMuxInput = muxEligible && (track.url === extraction.selectedVideoTrack.url || track.url === extraction.selectedAudioTrack.url);
|
|
16828
|
+
if (isMuxInput) {
|
|
16829
|
+
if (!muxWorkDir) muxWorkDir = (0, import_node_fs5.mkdtempSync)((0, import_node_path7.join)((0, import_node_os5.tmpdir)(), "ig-mux-"));
|
|
16830
|
+
const localPath = (0, import_node_path7.join)(muxWorkDir, trackFilename(track, index, extraction.shortcode));
|
|
16831
|
+
(0, import_node_fs5.writeFileSync)(localPath, bytes);
|
|
16832
|
+
muxInputs.set(track.url, localPath);
|
|
16833
|
+
}
|
|
16834
|
+
} catch (err) {
|
|
16835
|
+
downloads.push({ kind, url: track.url, savedPath: null, sizeBytes: null, mimeType: null, error: err instanceof Error ? err.message : String(err) });
|
|
16836
|
+
}
|
|
16836
16837
|
}
|
|
16837
|
-
|
|
16838
|
-
|
|
16839
|
-
|
|
16840
|
-
|
|
16841
|
-
|
|
16842
|
-
|
|
16843
|
-
|
|
16844
|
-
|
|
16838
|
+
if (muxEligible && muxWorkDir && muxInputs.has(extraction.selectedVideoTrack.url) && muxInputs.has(extraction.selectedAudioTrack.url)) {
|
|
16839
|
+
const outPath = (0, import_node_path7.join)(muxWorkDir, "muxed.mp4");
|
|
16840
|
+
const muxed = await runFfmpegMux(muxInputs.get(extraction.selectedVideoTrack.url), muxInputs.get(extraction.selectedAudioTrack.url), outPath);
|
|
16841
|
+
if (muxed.ok) {
|
|
16842
|
+
const stored = await store.put(`${prefix}/muxed.mp4`, (0, import_node_fs5.readFileSync)(outPath), "video/mp4");
|
|
16843
|
+
downloads.push({ kind: "muxed_video", url: null, savedPath: stored.url, sizeBytes: stored.bytes, mimeType: "video/mp4", error: null });
|
|
16844
|
+
} else {
|
|
16845
|
+
warnings.push(`Mux skipped/failed: ${muxed.error}`);
|
|
16846
|
+
}
|
|
16845
16847
|
}
|
|
16848
|
+
} catch (err) {
|
|
16849
|
+
warnings.push(`Media download unavailable: ${err instanceof Error ? err.message : String(err)}`);
|
|
16850
|
+
} finally {
|
|
16851
|
+
if (muxWorkDir) (0, import_node_fs5.rmSync)(muxWorkDir, { recursive: true, force: true });
|
|
16846
16852
|
}
|
|
16847
16853
|
}
|
|
16848
16854
|
let transcript = null;
|
|
@@ -16865,7 +16871,7 @@ var init_instagram_routes = __esm({
|
|
|
16865
16871
|
const result = {
|
|
16866
16872
|
...extraction,
|
|
16867
16873
|
browser: launch.browser,
|
|
16868
|
-
outputDir,
|
|
16874
|
+
outputDir: null,
|
|
16869
16875
|
downloads,
|
|
16870
16876
|
warnings,
|
|
16871
16877
|
transcript
|
|
@@ -19064,12 +19070,12 @@ function reportTitle(full) {
|
|
|
19064
19070
|
const title = full.split("\n").find((line) => line.startsWith("# "));
|
|
19065
19071
|
return title?.replace(/^#\s+/, "").trim() || "MCP Scraper Report";
|
|
19066
19072
|
}
|
|
19067
|
-
function
|
|
19073
|
+
function outputBaseDir() {
|
|
19068
19074
|
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path8.join)((0, import_node_os6.homedir)(), "Downloads", "mcp-scraper");
|
|
19069
19075
|
}
|
|
19070
19076
|
function saveFullReport(full) {
|
|
19071
19077
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
19072
|
-
const outDir =
|
|
19078
|
+
const outDir = outputBaseDir();
|
|
19073
19079
|
try {
|
|
19074
19080
|
(0, import_node_fs6.mkdirSync)(outDir, { recursive: true });
|
|
19075
19081
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -19087,7 +19093,7 @@ function saveBulkSite(siteUrl, pages, seo, imageAudit) {
|
|
|
19087
19093
|
if (!reportSavingActive()) return null;
|
|
19088
19094
|
try {
|
|
19089
19095
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19090
|
-
const dir = (0, import_node_path8.join)(
|
|
19096
|
+
const dir = (0, import_node_path8.join)(outputBaseDir(), `extract-${slugifyReportName(siteUrl)}-${stamp}`);
|
|
19091
19097
|
const pagesDir = (0, import_node_path8.join)(dir, "pages");
|
|
19092
19098
|
(0, import_node_fs6.mkdirSync)(pagesDir, { recursive: true });
|
|
19093
19099
|
const indexRows = pages.map((p, i) => {
|
|
@@ -19180,7 +19186,7 @@ ${renderImageSection(imageAudit)}` : ""), "utf8");
|
|
|
19180
19186
|
function saveUrlInventory(siteUrl, urls) {
|
|
19181
19187
|
if (!reportSavingActive()) return null;
|
|
19182
19188
|
try {
|
|
19183
|
-
const outDir =
|
|
19189
|
+
const outDir = outputBaseDir();
|
|
19184
19190
|
(0, import_node_fs6.mkdirSync)(outDir, { recursive: true });
|
|
19185
19191
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19186
19192
|
const file = (0, import_node_path8.join)(outDir, `${stamp}-urlmap-${slugifyReportName(siteUrl.replace(/^https?:\/\//, ""))}.csv`);
|
|
@@ -19195,7 +19201,7 @@ function saveUrlInventory(siteUrl, urls) {
|
|
|
19195
19201
|
function persistScreenshotLocally(base64, url) {
|
|
19196
19202
|
if (!reportSavingEnabled || process.env.MCP_SCRAPER_SAVE_REPORTS === "false") return null;
|
|
19197
19203
|
try {
|
|
19198
|
-
const dir = (0, import_node_path8.join)(
|
|
19204
|
+
const dir = (0, import_node_path8.join)(outputBaseDir(), "screenshots");
|
|
19199
19205
|
(0, import_node_fs6.mkdirSync)(dir, { recursive: true });
|
|
19200
19206
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
19201
19207
|
const slug = url.replace(/^https?:\/\//, "").replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 60);
|
|
@@ -21475,7 +21481,7 @@ function localLocationFileAllowed() {
|
|
|
21475
21481
|
async function existingPath(value) {
|
|
21476
21482
|
const trimmed = value?.trim();
|
|
21477
21483
|
if (!trimmed) return null;
|
|
21478
|
-
await (0,
|
|
21484
|
+
await (0, import_promises4.access)(trimmed);
|
|
21479
21485
|
return trimmed;
|
|
21480
21486
|
}
|
|
21481
21487
|
async function resolveUsZipsPath(requestedPath) {
|
|
@@ -21493,7 +21499,7 @@ async function loadZipGroups(stateAbbr, requestedPath, warnings) {
|
|
|
21493
21499
|
}
|
|
21494
21500
|
const path6 = await resolveUsZipsPath(requestedPath);
|
|
21495
21501
|
if (!path6) return { path: null, groups: /* @__PURE__ */ new Map() };
|
|
21496
|
-
const records = csvRecords(await (0,
|
|
21502
|
+
const records = csvRecords(await (0, import_promises4.readFile)(path6, "utf8"));
|
|
21497
21503
|
const groups = /* @__PURE__ */ new Map();
|
|
21498
21504
|
for (const record of records) {
|
|
21499
21505
|
const state = (record.state_abbr ?? record.state ?? "").trim().toUpperCase();
|
|
@@ -21544,11 +21550,11 @@ async function resolveDirectoryMarkets(options) {
|
|
|
21544
21550
|
}
|
|
21545
21551
|
return { markets, censusSourceUrl: sourceUrl, usZipsSourcePath: zipData.path, warnings };
|
|
21546
21552
|
}
|
|
21547
|
-
var
|
|
21553
|
+
var import_promises4, POPULATION_YEARS, STATE_META, STATE_BY_NAME;
|
|
21548
21554
|
var init_location_db = __esm({
|
|
21549
21555
|
"src/directory/location-db.ts"() {
|
|
21550
21556
|
"use strict";
|
|
21551
|
-
|
|
21557
|
+
import_promises4 = require("fs/promises");
|
|
21552
21558
|
init_csv();
|
|
21553
21559
|
POPULATION_YEARS = [2020, 2021, 2022, 2023, 2024, 2025];
|
|
21554
21560
|
STATE_META = {
|
|
@@ -21764,8 +21770,8 @@ function csvRowsFor(result) {
|
|
|
21764
21770
|
return rows;
|
|
21765
21771
|
}
|
|
21766
21772
|
async function saveDirectoryCsv(result) {
|
|
21767
|
-
const outDir = (0, import_node_path9.join)(
|
|
21768
|
-
await (0,
|
|
21773
|
+
const outDir = (0, import_node_path9.join)(outputBaseDir(), "directory-workflows");
|
|
21774
|
+
await (0, import_promises5.mkdir)(outDir, { recursive: true });
|
|
21769
21775
|
const stamp = result.extractedAt.replace(/[:.]/g, "-");
|
|
21770
21776
|
const slug = `${result.state}-${result.query}`.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80);
|
|
21771
21777
|
const path6 = (0, import_node_path9.join)(outDir, `${stamp}-${slug}-directory-workflow.csv`);
|
|
@@ -21800,7 +21806,7 @@ async function saveDirectoryCsv(result) {
|
|
|
21800
21806
|
"extracted_at",
|
|
21801
21807
|
"duration_ms"
|
|
21802
21808
|
];
|
|
21803
|
-
await (0,
|
|
21809
|
+
await (0, import_promises5.writeFile)(path6, rowsToCsv(headers, csvRowsFor(result)), "utf8");
|
|
21804
21810
|
return path6;
|
|
21805
21811
|
}
|
|
21806
21812
|
async function runDirectoryWorkflowFromPlan(options, plan) {
|
|
@@ -21826,11 +21832,11 @@ async function runDirectoryWorkflowFromPlan(options, plan) {
|
|
|
21826
21832
|
const csvPath = options.saveCsv ? await saveDirectoryCsv(base) : null;
|
|
21827
21833
|
return { ...base, csvPath };
|
|
21828
21834
|
}
|
|
21829
|
-
var
|
|
21835
|
+
var import_promises5, import_node_path9, import_zod24, DirectoryWorkflowOptionsSchema;
|
|
21830
21836
|
var init_directory_workflow = __esm({
|
|
21831
21837
|
"src/directory/directory-workflow.ts"() {
|
|
21832
21838
|
"use strict";
|
|
21833
|
-
|
|
21839
|
+
import_promises5 = require("fs/promises");
|
|
21834
21840
|
import_node_path9 = require("path");
|
|
21835
21841
|
import_zod24 = require("zod");
|
|
21836
21842
|
init_mcp_response_formatter();
|
|
@@ -21998,7 +22004,7 @@ async function readIndex(baseDir) {
|
|
|
21998
22004
|
const path6 = indexPath(baseDir);
|
|
21999
22005
|
if (!(0, import_node_fs7.existsSync)(path6)) return { runs: [] };
|
|
22000
22006
|
try {
|
|
22001
|
-
return JSON.parse(await (0,
|
|
22007
|
+
return JSON.parse(await (0, import_promises6.readFile)(path6, "utf8"));
|
|
22002
22008
|
} catch {
|
|
22003
22009
|
return { runs: [] };
|
|
22004
22010
|
}
|
|
@@ -22018,14 +22024,14 @@ async function updateWorkflowIndex(manifest, manifestPath, baseDir) {
|
|
|
22018
22024
|
summary: `${manifest.title} (${manifest.status})`
|
|
22019
22025
|
};
|
|
22020
22026
|
index.runs = [entry, ...index.runs.filter((r) => r.runId !== manifest.runId)].slice(0, 200);
|
|
22021
|
-
await (0,
|
|
22022
|
-
await (0,
|
|
22027
|
+
await (0, import_promises6.mkdir)((0, import_node_path10.dirname)(path6), { recursive: true });
|
|
22028
|
+
await (0, import_promises6.writeFile)(path6, JSON.stringify(index, null, 2), "utf8");
|
|
22023
22029
|
}
|
|
22024
|
-
var
|
|
22030
|
+
var import_promises6, import_node_fs7, import_node_os7, import_node_path10, import_node_child_process3, ArtifactWriter;
|
|
22025
22031
|
var init_artifact_writer = __esm({
|
|
22026
22032
|
"src/workflows/artifact-writer.ts"() {
|
|
22027
22033
|
"use strict";
|
|
22028
|
-
|
|
22034
|
+
import_promises6 = require("fs/promises");
|
|
22029
22035
|
import_node_fs7 = require("fs");
|
|
22030
22036
|
import_node_os7 = require("os");
|
|
22031
22037
|
import_node_path10 = require("path");
|
|
@@ -22057,31 +22063,31 @@ var init_artifact_writer = __esm({
|
|
|
22057
22063
|
const nameSource = String(input.keyword ?? input.query ?? input.domain ?? input.state ?? workflowId);
|
|
22058
22064
|
const baseDir = workflowOutputBaseDir(outputDir);
|
|
22059
22065
|
const runDir = (0, import_node_path10.join)(baseDir, "workflows", workflowId, `${timestamp()}-${safeSlug(nameSource)}-${safeSlug(runId).slice(0, 20)}`);
|
|
22060
|
-
await (0,
|
|
22066
|
+
await (0, import_promises6.mkdir)(runDir, { recursive: true });
|
|
22061
22067
|
return new _ArtifactWriter(workflowId, title, runId, baseDir, runDir, startedAt, input);
|
|
22062
22068
|
}
|
|
22063
22069
|
async remember(kind, label, path6, rows) {
|
|
22064
|
-
const size = await (0,
|
|
22070
|
+
const size = await (0, import_promises6.stat)(path6).then((s) => s.size).catch(() => void 0);
|
|
22065
22071
|
this.artifacts.push({ kind, label, path: path6, bytes: size, rows });
|
|
22066
22072
|
await postToMemoryLibrary({ title: `${this.title} ${label}`, content: `artifact: ${path6}`, source: `mcp-scraper-workflow:${this.workflowId}:${this.runId}` });
|
|
22067
22073
|
return path6;
|
|
22068
22074
|
}
|
|
22069
22075
|
async writeJson(label, relativePath, data) {
|
|
22070
22076
|
const path6 = (0, import_node_path10.join)(this.runDir, relativePath);
|
|
22071
|
-
await (0,
|
|
22072
|
-
await (0,
|
|
22077
|
+
await (0, import_promises6.mkdir)((0, import_node_path10.dirname)(path6), { recursive: true });
|
|
22078
|
+
await (0, import_promises6.writeFile)(path6, JSON.stringify(data, null, 2), "utf8");
|
|
22073
22079
|
return this.remember("json", label, path6);
|
|
22074
22080
|
}
|
|
22075
22081
|
async writeText(label, relativePath, text, kind = "markdown") {
|
|
22076
22082
|
const path6 = (0, import_node_path10.join)(this.runDir, relativePath);
|
|
22077
|
-
await (0,
|
|
22078
|
-
await (0,
|
|
22083
|
+
await (0, import_promises6.mkdir)((0, import_node_path10.dirname)(path6), { recursive: true });
|
|
22084
|
+
await (0, import_promises6.writeFile)(path6, text, "utf8");
|
|
22079
22085
|
return this.remember(kind, label, path6);
|
|
22080
22086
|
}
|
|
22081
22087
|
async writeCsv(label, relativePath, headers, rows) {
|
|
22082
22088
|
const path6 = (0, import_node_path10.join)(this.runDir, relativePath);
|
|
22083
|
-
await (0,
|
|
22084
|
-
await (0,
|
|
22089
|
+
await (0, import_promises6.mkdir)((0, import_node_path10.dirname)(path6), { recursive: true });
|
|
22090
|
+
await (0, import_promises6.writeFile)(path6, rowsToCsv(headers, rows), "utf8");
|
|
22085
22091
|
return this.remember("csv", label, path6, rows.length);
|
|
22086
22092
|
}
|
|
22087
22093
|
async writeHtml(label, relativePath, html) {
|
|
@@ -22102,7 +22108,7 @@ var init_artifact_writer = __esm({
|
|
|
22102
22108
|
counts
|
|
22103
22109
|
};
|
|
22104
22110
|
const path6 = (0, import_node_path10.join)(this.runDir, "manifest.json");
|
|
22105
|
-
await (0,
|
|
22111
|
+
await (0, import_promises6.writeFile)(path6, JSON.stringify(manifest, null, 2), "utf8");
|
|
22106
22112
|
await updateWorkflowIndex(manifest, path6, this.baseDir);
|
|
22107
22113
|
return path6;
|
|
22108
22114
|
}
|
|
@@ -23828,7 +23834,7 @@ async function runWorkflowStep(id, rawInput, opts) {
|
|
|
23828
23834
|
const writtenArtifacts = await Promise.all(
|
|
23829
23835
|
artifacts.artifacts.map(async (artifact) => ({
|
|
23830
23836
|
...artifact,
|
|
23831
|
-
content: await (0,
|
|
23837
|
+
content: await (0, import_promises7.readFile)(artifact.path, "utf8").catch(() => "")
|
|
23832
23838
|
}))
|
|
23833
23839
|
);
|
|
23834
23840
|
return {
|
|
@@ -23847,11 +23853,11 @@ async function runWorkflowStep(id, rawInput, opts) {
|
|
|
23847
23853
|
artifacts: writtenArtifacts
|
|
23848
23854
|
};
|
|
23849
23855
|
}
|
|
23850
|
-
var
|
|
23856
|
+
var import_promises7, import_zod30, DEFINITIONS;
|
|
23851
23857
|
var init_registry2 = __esm({
|
|
23852
23858
|
"src/workflows/registry.ts"() {
|
|
23853
23859
|
"use strict";
|
|
23854
|
-
|
|
23860
|
+
import_promises7 = require("fs/promises");
|
|
23855
23861
|
import_zod30 = require("zod");
|
|
23856
23862
|
init_artifact_writer();
|
|
23857
23863
|
init_http_client2();
|
|
@@ -23922,7 +23928,7 @@ async function readManifestFromSummary(summary) {
|
|
|
23922
23928
|
if (!summary.reportPath) return null;
|
|
23923
23929
|
const manifestPath = summary.reportPath.replace(/report\.html$/, "manifest.json");
|
|
23924
23930
|
try {
|
|
23925
|
-
return JSON.parse(await (0,
|
|
23931
|
+
return JSON.parse(await (0, import_promises8.readFile)(manifestPath, "utf8"));
|
|
23926
23932
|
} catch {
|
|
23927
23933
|
return null;
|
|
23928
23934
|
}
|
|
@@ -24137,12 +24143,12 @@ async function dispatchDueWorkflowSchedules(apiUrl, limit = 3) {
|
|
|
24137
24143
|
}
|
|
24138
24144
|
return { dispatched: results.length, results };
|
|
24139
24145
|
}
|
|
24140
|
-
var import_node_crypto8,
|
|
24146
|
+
var import_node_crypto8, import_promises8, import_hono14, import_zod31, workflowApp, WorkflowInputSchema, WorkflowIdSchema, CadenceSchema, ScheduleStatusSchema, RunBodySchema, ScheduleCreateSchema, SchedulePatchSchema, TERMINAL_RUN_STATUSES;
|
|
24141
24147
|
var init_workflow_routes = __esm({
|
|
24142
24148
|
"src/api/workflow-routes.ts"() {
|
|
24143
24149
|
"use strict";
|
|
24144
24150
|
import_node_crypto8 = require("crypto");
|
|
24145
|
-
|
|
24151
|
+
import_promises8 = require("fs/promises");
|
|
24146
24152
|
import_hono14 = require("hono");
|
|
24147
24153
|
import_zod31 = require("zod");
|
|
24148
24154
|
init_artifact_writer();
|
|
@@ -24312,7 +24318,7 @@ var init_workflow_routes = __esm({
|
|
|
24312
24318
|
return new Response(artifact.content, { headers });
|
|
24313
24319
|
}
|
|
24314
24320
|
try {
|
|
24315
|
-
const content = await (0,
|
|
24321
|
+
const content = await (0, import_promises8.readFile)(artifact.path);
|
|
24316
24322
|
return new Response(content, { headers });
|
|
24317
24323
|
} catch {
|
|
24318
24324
|
return c.json({ error: "Artifact file is no longer available" }, 410);
|
|
@@ -25251,13 +25257,41 @@ var init_PAAExtractor = __esm({
|
|
|
25251
25257
|
}
|
|
25252
25258
|
return parts.join(" ").replace(/\s+/g, " ").trim();
|
|
25253
25259
|
}
|
|
25254
|
-
return Array.from(document.querySelectorAll(selectors.item)).map((pair) =>
|
|
25255
|
-
|
|
25256
|
-
|
|
25257
|
-
|
|
25258
|
-
|
|
25259
|
-
|
|
25260
|
-
|
|
25260
|
+
return Array.from(document.querySelectorAll(selectors.item)).map((pair) => {
|
|
25261
|
+
let sourceTitle = pair.querySelector(selectors.sourceTitle)?.innerText?.trim() || void 0;
|
|
25262
|
+
let sourceSite = pair.querySelector(selectors.sourceSite)?.innerText?.trim() || void 0;
|
|
25263
|
+
let sourceCite = pair.querySelector(selectors.sourceCite)?.innerText?.trim() || void 0;
|
|
25264
|
+
if (!sourceCite) {
|
|
25265
|
+
const seenHrefs = /* @__PURE__ */ new Set();
|
|
25266
|
+
const anchors = [];
|
|
25267
|
+
for (const a of Array.from(pair.querySelectorAll("a[href]"))) {
|
|
25268
|
+
const href = a.href;
|
|
25269
|
+
if (!/^https?:\/\//.test(href) || seenHrefs.has(href)) continue;
|
|
25270
|
+
let host = "";
|
|
25271
|
+
try {
|
|
25272
|
+
host = new URL(href).hostname;
|
|
25273
|
+
} catch {
|
|
25274
|
+
continue;
|
|
25275
|
+
}
|
|
25276
|
+
if (/(^|\.)google(usercontent)?\.[a-z.]+$/.test(host)) continue;
|
|
25277
|
+
seenHrefs.add(href);
|
|
25278
|
+
anchors.push({ text: (a.textContent || "").trim(), href, host });
|
|
25279
|
+
}
|
|
25280
|
+
const primary = anchors.find((x) => x.text.length > 2) ?? anchors[0];
|
|
25281
|
+
if (primary) {
|
|
25282
|
+
sourceTitle = sourceTitle || primary.text || void 0;
|
|
25283
|
+
sourceSite = sourceSite || primary.host.replace(/^www\./, "");
|
|
25284
|
+
sourceCite = primary.href;
|
|
25285
|
+
}
|
|
25286
|
+
}
|
|
25287
|
+
return {
|
|
25288
|
+
question: pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "",
|
|
25289
|
+
answer: cleanText(pair.querySelector(selectors.answerContainer)) || void 0,
|
|
25290
|
+
sourceTitle,
|
|
25291
|
+
sourceSite,
|
|
25292
|
+
sourceCite
|
|
25293
|
+
};
|
|
25294
|
+
});
|
|
25261
25295
|
}, sels);
|
|
25262
25296
|
return raw.flatMap((item) => {
|
|
25263
25297
|
const cleaned = {
|
|
@@ -25299,54 +25333,115 @@ var init_PAAExtractor = __esm({
|
|
|
25299
25333
|
const seenQs = /* @__PURE__ */ new Set();
|
|
25300
25334
|
const orderedQs = [];
|
|
25301
25335
|
const results = [];
|
|
25302
|
-
const
|
|
25303
|
-
|
|
25304
|
-
|
|
25305
|
-
|
|
25306
|
-
|
|
25336
|
+
const abandonedNeverReclick = /* @__PURE__ */ new Set();
|
|
25337
|
+
const clickedOnceEver = /* @__PURE__ */ new Set();
|
|
25338
|
+
const itemArgs = {
|
|
25339
|
+
sel: PAASelectors.item,
|
|
25340
|
+
dataQ: PAASelectors.itemDataQ,
|
|
25341
|
+
dataInitQ: PAASelectors.itemDataInitQ,
|
|
25342
|
+
questionEl: PAASelectors.itemQuestionEl,
|
|
25343
|
+
answerSel: PAASelectors.answerContainer,
|
|
25344
|
+
expandedClass: PAASelectors.expandedClass,
|
|
25345
|
+
clickTarget: PAASelectors.clickTarget
|
|
25346
|
+
};
|
|
25347
|
+
const readItemStates = () => page.evaluate(
|
|
25348
|
+
({ sel, dataQ, dataInitQ, questionEl, expandedClass, answerSel }) => {
|
|
25349
|
+
const answerText = (root) => {
|
|
25350
|
+
if (!root) return "";
|
|
25351
|
+
let out = "";
|
|
25352
|
+
const walk = (n) => {
|
|
25353
|
+
for (const c of Array.from(n.childNodes)) {
|
|
25354
|
+
if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
|
|
25355
|
+
else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
|
|
25356
|
+
}
|
|
25357
|
+
};
|
|
25358
|
+
walk(root);
|
|
25359
|
+
return out.replace(/\s+/g, " ").trim();
|
|
25360
|
+
};
|
|
25361
|
+
return Array.from(document.querySelectorAll(sel)).map((el2) => ({
|
|
25362
|
+
q: el2.getAttribute(dataQ) || el2.getAttribute(dataInitQ) || el2.querySelector(questionEl)?.innerText?.trim() || "",
|
|
25363
|
+
done: el2.classList.contains(expandedClass) || answerText(el2.querySelector(answerSel)).length > 30
|
|
25364
|
+
})).filter((s) => s.q);
|
|
25365
|
+
},
|
|
25366
|
+
itemArgs
|
|
25307
25367
|
);
|
|
25368
|
+
const expandOneItemSerially = async (q) => {
|
|
25369
|
+
const clicked = await page.evaluate(
|
|
25370
|
+
({ sel, dataQ, dataInitQ, clickTarget, q: target }) => {
|
|
25371
|
+
const item = Array.from(document.querySelectorAll(sel)).find((el2) => (el2.getAttribute(dataQ) || el2.getAttribute(dataInitQ)) === target);
|
|
25372
|
+
const btn = item?.querySelector(clickTarget);
|
|
25373
|
+
if (!btn) return false;
|
|
25374
|
+
btn.scrollIntoView({ block: "center" });
|
|
25375
|
+
btn.click();
|
|
25376
|
+
return true;
|
|
25377
|
+
},
|
|
25378
|
+
{ ...itemArgs, q }
|
|
25379
|
+
).catch(() => false);
|
|
25380
|
+
if (!clicked) return "failed";
|
|
25381
|
+
try {
|
|
25382
|
+
const outcome = await page.waitForFunction(
|
|
25383
|
+
({ sel, dataQ, dataInitQ, answerSel, q: target }) => {
|
|
25384
|
+
const answerText = (root) => {
|
|
25385
|
+
if (!root) return "";
|
|
25386
|
+
let out = "";
|
|
25387
|
+
const walk = (n) => {
|
|
25388
|
+
for (const c of Array.from(n.childNodes)) {
|
|
25389
|
+
if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
|
|
25390
|
+
else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
|
|
25391
|
+
}
|
|
25392
|
+
};
|
|
25393
|
+
walk(root);
|
|
25394
|
+
return out.replace(/\s+/g, " ").trim();
|
|
25395
|
+
};
|
|
25396
|
+
const item = Array.from(document.querySelectorAll(sel)).find((el2) => (el2.getAttribute(dataQ) || el2.getAttribute(dataInitQ)) === target);
|
|
25397
|
+
if (!item) return "gone";
|
|
25398
|
+
const text = answerText(item.querySelector(answerSel));
|
|
25399
|
+
if (/an error has occurred/i.test(text.slice(0, 80))) return "error";
|
|
25400
|
+
if (text.length > 30) return "ok";
|
|
25401
|
+
return false;
|
|
25402
|
+
},
|
|
25403
|
+
{ ...itemArgs, q },
|
|
25404
|
+
{ timeout: 6500, polling: 200 }
|
|
25405
|
+
);
|
|
25406
|
+
return await outcome.jsonValue() === "ok" ? "ok" : "failed";
|
|
25407
|
+
} catch {
|
|
25408
|
+
return "failed";
|
|
25409
|
+
}
|
|
25410
|
+
};
|
|
25308
25411
|
let round = 0;
|
|
25309
|
-
|
|
25412
|
+
let growthWaits = 0;
|
|
25413
|
+
while (true) {
|
|
25310
25414
|
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
25311
25415
|
this.throwIfAborted(signal);
|
|
25312
|
-
|
|
25313
|
-
const
|
|
25314
|
-
|
|
25315
|
-
|
|
25316
|
-
|
|
25317
|
-
if (unexpandedCount === 0) break;
|
|
25318
|
-
this.reporter.onDepth(++round);
|
|
25319
|
-
for (let ci = 0; ci < unexpandedCount; ci++) {
|
|
25320
|
-
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
25321
|
-
this.throwIfAborted(signal);
|
|
25322
|
-
try {
|
|
25323
|
-
const btn = page.locator(unexpandedSel).first();
|
|
25324
|
-
await btn.scrollIntoViewIfNeeded();
|
|
25325
|
-
await btn.hover({ force: true });
|
|
25326
|
-
await page.waitForTimeout(100);
|
|
25327
|
-
await btn.click({ force: true });
|
|
25328
|
-
await page.waitForTimeout(500);
|
|
25329
|
-
} catch {
|
|
25416
|
+
const states = await readItemStates();
|
|
25417
|
+
for (const s of states) {
|
|
25418
|
+
if (!seenQs.has(s.q)) {
|
|
25419
|
+
seenQs.add(s.q);
|
|
25420
|
+
orderedQs.push(s.q);
|
|
25330
25421
|
}
|
|
25331
25422
|
}
|
|
25332
|
-
|
|
25333
|
-
|
|
25334
|
-
|
|
25335
|
-
|
|
25336
|
-
|
|
25337
|
-
|
|
25338
|
-
|
|
25339
|
-
|
|
25340
|
-
|
|
25341
|
-
|
|
25342
|
-
|
|
25343
|
-
|
|
25344
|
-
|
|
25345
|
-
}
|
|
25423
|
+
const kept = new Set(orderedQs.slice(0, options.maxQuestions));
|
|
25424
|
+
const clickable = states.filter((s) => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q));
|
|
25425
|
+
const target = clickable.find((s) => !s.done) ?? (seenQs.size < options.maxQuestions ? clickable[0] : void 0);
|
|
25426
|
+
if (!target) {
|
|
25427
|
+
if (seenQs.size >= options.maxQuestions) break;
|
|
25428
|
+
if (++growthWaits > 1) break;
|
|
25429
|
+
const grew = await page.waitForFunction(
|
|
25430
|
+
({ sel, min }) => document.querySelectorAll(sel).length > min,
|
|
25431
|
+
{ sel: PAASelectors.item, min: states.length },
|
|
25432
|
+
{ timeout: 3e3 }
|
|
25433
|
+
).catch(() => null);
|
|
25434
|
+
if (!grew) break;
|
|
25435
|
+
continue;
|
|
25346
25436
|
}
|
|
25437
|
+
growthWaits = 0;
|
|
25438
|
+
this.reporter.onDepth(++round);
|
|
25439
|
+
await this.throwIfCaptcha(page, "Google PAA expansion");
|
|
25440
|
+
clickedOnceEver.add(target.q);
|
|
25441
|
+
if (await expandOneItemSerially(target.q) === "failed") abandonedNeverReclick.add(target.q);
|
|
25347
25442
|
}
|
|
25348
25443
|
const itemMap = new Map((await this.extractVisibleItems(page)).map((i) => [i.question, i]));
|
|
25349
|
-
await this.fillIncompleteItems(page, orderedQs, itemMap, options);
|
|
25444
|
+
await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick);
|
|
25350
25445
|
for (const q of orderedQs) {
|
|
25351
25446
|
if (results.length >= options.maxQuestions) break;
|
|
25352
25447
|
const key = this.normalizeQuestion(q);
|
|
@@ -25364,9 +25459,11 @@ var init_PAAExtractor = __esm({
|
|
|
25364
25459
|
this.completeness.paaWithoutSource = results.filter((r) => !r.source_title && !r.source_site && !r.source_cite).length;
|
|
25365
25460
|
return results;
|
|
25366
25461
|
}
|
|
25367
|
-
async fillIncompleteItems(page, orderedQs, itemMap, options) {
|
|
25462
|
+
async fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick) {
|
|
25368
25463
|
const kept = new Set(orderedQs.slice(0, options.maxQuestions));
|
|
25369
|
-
const missingBefore = new Set(
|
|
25464
|
+
const missingBefore = new Set(
|
|
25465
|
+
[...kept].filter((q) => !itemMap.get(q)?.answer && !abandonedNeverReclick?.has(q))
|
|
25466
|
+
);
|
|
25370
25467
|
if (missingBefore.size === 0) return;
|
|
25371
25468
|
for (let round = 0; round < 2; round++) {
|
|
25372
25469
|
if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
|
|
@@ -25758,40 +25855,41 @@ var init_PAAExtractor = __esm({
|
|
|
25758
25855
|
};
|
|
25759
25856
|
}
|
|
25760
25857
|
async captureAIOverviewShareUrl(page, options) {
|
|
25761
|
-
|
|
25762
|
-
|
|
25763
|
-
const
|
|
25764
|
-
|
|
25765
|
-
|
|
25766
|
-
|
|
25767
|
-
|
|
25768
|
-
|
|
25769
|
-
|
|
25770
|
-
|
|
25771
|
-
|
|
25772
|
-
return true;
|
|
25773
|
-
}
|
|
25774
|
-
sec = sec.parentElement;
|
|
25858
|
+
if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null;
|
|
25859
|
+
const clicked = await page.evaluate(() => {
|
|
25860
|
+
const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"],div')).filter((h) => (h.textContent || "").trim() === "AI Overview");
|
|
25861
|
+
for (const head of heads) {
|
|
25862
|
+
let sec = head;
|
|
25863
|
+
for (let up = 0; up < 8 && sec; up++) {
|
|
25864
|
+
const btn = sec.querySelector('[aria-label="Share"]');
|
|
25865
|
+
if (btn) {
|
|
25866
|
+
btn.scrollIntoView({ block: "center" });
|
|
25867
|
+
btn.click();
|
|
25868
|
+
return true;
|
|
25775
25869
|
}
|
|
25870
|
+
sec = sec.parentElement;
|
|
25776
25871
|
}
|
|
25777
|
-
|
|
25778
|
-
|
|
25779
|
-
|
|
25780
|
-
|
|
25781
|
-
|
|
25872
|
+
}
|
|
25873
|
+
return false;
|
|
25874
|
+
}).catch(() => false);
|
|
25875
|
+
if (!clicked) return null;
|
|
25876
|
+
let url = null;
|
|
25877
|
+
try {
|
|
25878
|
+
const found = await page.waitForFunction(() => {
|
|
25782
25879
|
const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
25783
25880
|
while (w.nextNode()) {
|
|
25784
25881
|
const m = (w.currentNode.textContent || "").match(/https:\/\/share\.google\/aimode\/\S+/);
|
|
25785
25882
|
if (m) return m[0];
|
|
25786
25883
|
}
|
|
25787
|
-
return
|
|
25788
|
-
}
|
|
25789
|
-
await
|
|
25790
|
-
|
|
25791
|
-
|
|
25792
|
-
await page.waitForTimeout(600);
|
|
25884
|
+
return false;
|
|
25885
|
+
}, void 0, { timeout: 4500, polling: 250 });
|
|
25886
|
+
url = await found.jsonValue();
|
|
25887
|
+
} catch {
|
|
25888
|
+
url = null;
|
|
25793
25889
|
}
|
|
25794
|
-
|
|
25890
|
+
await page.keyboard.press("Escape").catch(() => {
|
|
25891
|
+
});
|
|
25892
|
+
return url;
|
|
25795
25893
|
}
|
|
25796
25894
|
buildTree(flat, _seed) {
|
|
25797
25895
|
const roots = [];
|
|
@@ -27302,7 +27400,7 @@ var PACKAGE_VERSION;
|
|
|
27302
27400
|
var init_version = __esm({
|
|
27303
27401
|
"src/version.ts"() {
|
|
27304
27402
|
"use strict";
|
|
27305
|
-
PACKAGE_VERSION = "0.
|
|
27403
|
+
PACKAGE_VERSION = "0.7.0";
|
|
27306
27404
|
}
|
|
27307
27405
|
});
|
|
27308
27406
|
|
|
@@ -28872,7 +28970,7 @@ function localPlanningToolAnnotations(title) {
|
|
|
28872
28970
|
}
|
|
28873
28971
|
function listSavedReports() {
|
|
28874
28972
|
try {
|
|
28875
|
-
const dir =
|
|
28973
|
+
const dir = outputBaseDir();
|
|
28876
28974
|
return (0, import_node_fs9.readdirSync)(dir).filter((f) => f.endsWith(".md")).map((f) => ({ filename: f, mtimeMs: (0, import_node_fs9.statSync)((0, import_node_path12.join)(dir, f)).mtimeMs })).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, 100);
|
|
28877
28975
|
} catch {
|
|
28878
28976
|
return [];
|
|
@@ -28899,7 +28997,7 @@ function registerSavedReportResources(server) {
|
|
|
28899
28997
|
const requested = Array.isArray(variables.filename) ? variables.filename[0] : variables.filename;
|
|
28900
28998
|
const filename = (0, import_node_path12.basename)(decodeURIComponent(String(requested ?? "")));
|
|
28901
28999
|
if (!filename.endsWith(".md")) throw new Error("Only saved .md reports can be read");
|
|
28902
|
-
const text = (0, import_node_fs9.readFileSync)((0, import_node_path12.join)(
|
|
29000
|
+
const text = (0, import_node_fs9.readFileSync)((0, import_node_path12.join)(outputBaseDir(), filename), "utf8");
|
|
28903
29001
|
return { contents: [{ uri: uri.href, mimeType: "text/markdown", text }] };
|
|
28904
29002
|
}
|
|
28905
29003
|
);
|
|
@@ -29498,7 +29596,7 @@ var init_http_mcp_tool_executor = __esm({
|
|
|
29498
29596
|
});
|
|
29499
29597
|
|
|
29500
29598
|
// src/services/fanout/export.ts
|
|
29501
|
-
function
|
|
29599
|
+
function outputBaseDir2() {
|
|
29502
29600
|
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path13.join)((0, import_node_os8.homedir)(), "Downloads", "mcp-scraper");
|
|
29503
29601
|
}
|
|
29504
29602
|
function safe(value) {
|
|
@@ -29509,7 +29607,7 @@ function writeTable(path6, rows, delimiter) {
|
|
|
29509
29607
|
}
|
|
29510
29608
|
function exportFanout(enriched) {
|
|
29511
29609
|
const stamp = safe(enriched.capturedAt.replace(/[:.]/g, "-"));
|
|
29512
|
-
const outputDir =
|
|
29610
|
+
const outputDir = outputBaseDir2();
|
|
29513
29611
|
const relativeDir = (0, import_node_path13.join)("fanout", `${stamp}-${safe(enriched.platform)}`);
|
|
29514
29612
|
const dir = (0, import_node_path13.join)(outputDir, relativeDir);
|
|
29515
29613
|
(0, import_node_fs10.mkdirSync)(dir, { recursive: true });
|
|
@@ -30243,10 +30341,10 @@ function ffmpegFilterPath(path6) {
|
|
|
30243
30341
|
async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
|
|
30244
30342
|
if (!options.annotations.length) throw new Error("annotations must include at least one item");
|
|
30245
30343
|
const size = await videoSize(inputFilePath);
|
|
30246
|
-
const tmp = await (0,
|
|
30344
|
+
const tmp = await (0, import_promises9.mkdtemp)((0, import_node_path14.join)((0, import_node_os9.tmpdir)(), "mcp-scraper-ass-"));
|
|
30247
30345
|
const assPath = (0, import_node_path14.join)(tmp, "annotations.ass");
|
|
30248
30346
|
try {
|
|
30249
|
-
await (0,
|
|
30347
|
+
await (0, import_promises9.writeFile)(assPath, buildAssSubtitle(options, size), "utf8");
|
|
30250
30348
|
await execFileAsync2("ffmpeg", [
|
|
30251
30349
|
"-y",
|
|
30252
30350
|
"-i",
|
|
@@ -30263,7 +30361,7 @@ async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
|
|
|
30263
30361
|
"copy",
|
|
30264
30362
|
outputFilePath
|
|
30265
30363
|
], { maxBuffer: 1024 * 1024 * 20 });
|
|
30266
|
-
const out = await (0,
|
|
30364
|
+
const out = await (0, import_promises9.stat)(outputFilePath);
|
|
30267
30365
|
return {
|
|
30268
30366
|
filePath: outputFilePath,
|
|
30269
30367
|
bytes: out.size,
|
|
@@ -30272,15 +30370,15 @@ async function annotateReplayVideo(inputFilePath, outputFilePath, options) {
|
|
|
30272
30370
|
annotationCount: options.annotations.length
|
|
30273
30371
|
};
|
|
30274
30372
|
} finally {
|
|
30275
|
-
await (0,
|
|
30373
|
+
await (0, import_promises9.rm)(tmp, { recursive: true, force: true });
|
|
30276
30374
|
}
|
|
30277
30375
|
}
|
|
30278
|
-
var import_node_child_process4,
|
|
30376
|
+
var import_node_child_process4, import_promises9, import_node_os9, import_node_path14, import_node_util2, execFileAsync2;
|
|
30279
30377
|
var init_replay_annotator = __esm({
|
|
30280
30378
|
"src/mcp/replay-annotator.ts"() {
|
|
30281
30379
|
"use strict";
|
|
30282
30380
|
import_node_child_process4 = require("child_process");
|
|
30283
|
-
|
|
30381
|
+
import_promises9 = require("fs/promises");
|
|
30284
30382
|
import_node_os9 = require("os");
|
|
30285
30383
|
import_node_path14 = require("path");
|
|
30286
30384
|
import_node_util2 = require("util");
|
|
@@ -30325,7 +30423,7 @@ function actionResult(tool, sessionId, ok, data, nextRecommendedTool = "browser_
|
|
|
30325
30423
|
nextRecommendedTool
|
|
30326
30424
|
});
|
|
30327
30425
|
}
|
|
30328
|
-
function
|
|
30426
|
+
function outputBaseDir3() {
|
|
30329
30427
|
return process.env.MCP_SCRAPER_OUTPUT_DIR?.trim() || (0, import_node_path15.join)((0, import_node_os10.homedir)(), "Downloads", "mcp-scraper");
|
|
30330
30428
|
}
|
|
30331
30429
|
function safeFilePart2(value) {
|
|
@@ -30335,7 +30433,7 @@ function replayFilePath(sessionId, replayId, filename) {
|
|
|
30335
30433
|
const requested = filename?.trim();
|
|
30336
30434
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
30337
30435
|
const name = requested ? safeFilePart2(requested).replace(/\.mp4$/i, "") : `${stamp}-${safeFilePart2(sessionId)}-${safeFilePart2(replayId)}`;
|
|
30338
|
-
return (0, import_node_path15.join)(
|
|
30436
|
+
return (0, import_node_path15.join)(outputBaseDir3(), "browser-replays", `${name}.mp4`);
|
|
30339
30437
|
}
|
|
30340
30438
|
function slugPart(value) {
|
|
30341
30439
|
const trimmed = value?.trim().toLowerCase();
|
|
@@ -30414,7 +30512,7 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
30414
30512
|
}
|
|
30415
30513
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
30416
30514
|
const filePath = replayFilePath(sessionId, replayId, filename);
|
|
30417
|
-
(0, import_node_fs11.mkdirSync)((0, import_node_path15.join)(
|
|
30515
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path15.join)(outputBaseDir3(), "browser-replays"), { recursive: true });
|
|
30418
30516
|
(0, import_node_fs11.writeFileSync)(filePath, bytes);
|
|
30419
30517
|
return {
|
|
30420
30518
|
ok: true,
|
|
@@ -30964,7 +31062,7 @@ function registerBrowserAgentMcpTools(server, opts) {
|
|
|
30964
31062
|
try {
|
|
30965
31063
|
const sourcePath = String(downloaded.data.file_path);
|
|
30966
31064
|
const outputPath = annotatedReplayFilePath(input.session_id, input.replay_id, input.filename);
|
|
30967
|
-
(0, import_node_fs11.mkdirSync)((0, import_node_path15.join)(
|
|
31065
|
+
(0, import_node_fs11.mkdirSync)((0, import_node_path15.join)(outputBaseDir3(), "browser-replays"), { recursive: true });
|
|
30968
31066
|
const result = await annotateReplayVideo(sourcePath, outputPath, {
|
|
30969
31067
|
annotations: input.annotations,
|
|
30970
31068
|
sourceWidth: input.source_width,
|
|
@@ -38245,16 +38343,16 @@ async function cleanupLocal(cutoff) {
|
|
|
38245
38343
|
let deleted = 0;
|
|
38246
38344
|
let entries;
|
|
38247
38345
|
try {
|
|
38248
|
-
entries = await (0,
|
|
38346
|
+
entries = await (0, import_promises10.readdir)(dir);
|
|
38249
38347
|
} catch {
|
|
38250
38348
|
return { deleted: 0, store: "local" };
|
|
38251
38349
|
}
|
|
38252
38350
|
for (const name of entries) {
|
|
38253
38351
|
const path6 = (0, import_node_path16.join)(dir, name);
|
|
38254
38352
|
try {
|
|
38255
|
-
const s = await (0,
|
|
38353
|
+
const s = await (0, import_promises10.stat)(path6);
|
|
38256
38354
|
if (s.isFile() && s.mtimeMs < cutoff) {
|
|
38257
|
-
await (0,
|
|
38355
|
+
await (0, import_promises10.unlink)(path6);
|
|
38258
38356
|
deleted++;
|
|
38259
38357
|
}
|
|
38260
38358
|
} catch {
|
|
@@ -38271,11 +38369,11 @@ async function cleanupExpiredScrapeBlobs(maxAgeMs = SCRAPE_BLOB_TTL_MS) {
|
|
|
38271
38369
|
return { deleted: 0, store: "none" };
|
|
38272
38370
|
}
|
|
38273
38371
|
}
|
|
38274
|
-
var
|
|
38372
|
+
var import_promises10, import_node_os11, import_node_path16;
|
|
38275
38373
|
var init_scrape_blob_cleanup = __esm({
|
|
38276
38374
|
"src/api/scrape-blob-cleanup.ts"() {
|
|
38277
38375
|
"use strict";
|
|
38278
|
-
|
|
38376
|
+
import_promises10 = require("fs/promises");
|
|
38279
38377
|
import_node_os11 = require("os");
|
|
38280
38378
|
import_node_path16 = require("path");
|
|
38281
38379
|
init_scrape_vault_sink();
|
|
@@ -38594,7 +38692,7 @@ function countPaaQuestions(result) {
|
|
|
38594
38692
|
return Array.isArray(value.flat) ? value.flat.length : 0;
|
|
38595
38693
|
}
|
|
38596
38694
|
function paaCostForQuestionCount(questionCount) {
|
|
38597
|
-
return Math.max(1, questionCount) * MC_COSTS.paa;
|
|
38695
|
+
return MC_COSTS.paa_base + Math.max(1, questionCount) * MC_COSTS.paa;
|
|
38598
38696
|
}
|
|
38599
38697
|
async function processJob(job) {
|
|
38600
38698
|
running++;
|