mcp-scraper 0.5.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.
Files changed (52) hide show
  1. package/dist/bin/api-server.cjs +675 -214
  2. package/dist/bin/api-server.cjs.map +1 -1
  3. package/dist/bin/api-server.js +2 -2
  4. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  5. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  6. package/dist/bin/mcp-scraper-cli.js +1 -1
  7. package/dist/bin/mcp-scraper-install.cjs +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  9. package/dist/bin/mcp-scraper-install.js +1 -1
  10. package/dist/bin/mcp-stdio-server.cjs +322 -11
  11. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  12. package/dist/bin/mcp-stdio-server.js +3 -3
  13. package/dist/bin/paa-harvest.cjs +166 -74
  14. package/dist/bin/paa-harvest.cjs.map +1 -1
  15. package/dist/bin/paa-harvest.js +1 -1
  16. package/dist/chunk-EGWJ74EX.js +7 -0
  17. package/dist/chunk-EGWJ74EX.js.map +1 -0
  18. package/dist/{chunk-APJO2XV5.js → chunk-HL33CGJF.js} +3 -3
  19. package/dist/{chunk-APJO2XV5.js.map → chunk-HL33CGJF.js.map} +1 -1
  20. package/dist/{chunk-AUCXKRRH.js → chunk-RMPPYKUV.js} +167 -75
  21. package/dist/chunk-RMPPYKUV.js.map +1 -0
  22. package/dist/{chunk-BL7BBSYF.js → chunk-RUGJE5EB.js} +2 -2
  23. package/dist/{chunk-J4T5OSCF.js → chunk-YGRZU7IR.js} +322 -11
  24. package/dist/chunk-YGRZU7IR.js.map +1 -0
  25. package/dist/index.cjs +166 -74
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +1 -1
  28. package/dist/{server-Z4MEISH5.js → server-JNY4XPZE.js} +138 -81
  29. package/dist/server-JNY4XPZE.js.map +1 -0
  30. package/dist/{site-extract-repository-U476J44K.js → site-extract-repository-NVSZH35Y.js} +3 -3
  31. package/dist/{worker-KJZ3ZN2N.js → worker-JQTS437L.js} +4 -4
  32. package/dist/worker-JQTS437L.js.map +1 -0
  33. package/package.json +2 -2
  34. package/dist/chunk-3RTPEQJC.js +0 -7
  35. package/dist/chunk-3RTPEQJC.js.map +0 -1
  36. package/dist/chunk-AUCXKRRH.js.map +0 -1
  37. package/dist/chunk-J4T5OSCF.js.map +0 -1
  38. package/dist/server-Z4MEISH5.js.map +0 -1
  39. package/dist/worker-KJZ3ZN2N.js.map +0 -1
  40. package/docs/spec-browser-routing.md +0 -108
  41. package/docs/spec-kernel-computer-controls.md +0 -166
  42. package/docs/spec-mcp-tools.md +0 -146
  43. package/docs/spec-server-instructions.md +0 -94
  44. package/docs/specs/agent-ready-seo-packet-spec.md +0 -237
  45. package/docs/specs/audit-visual-demo.md +0 -309
  46. package/docs/specs/cli-agent-wiring-spec.md +0 -206
  47. package/docs/specs/local-competitive-audit-spec.md +0 -311
  48. package/docs/specs/scheduled-workflows-api-spec.md +0 -304
  49. package/docs/specs/seo-cli-growth-roadmap-spec.md +0 -181
  50. package/docs/specs/seo-workflow-runner-and-reports-spec.md +0 -241
  51. /package/dist/{chunk-BL7BBSYF.js.map → chunk-RUGJE5EB.js.map} +0 -0
  52. /package/dist/{site-extract-repository-U476J44K.js.map → site-extract-repository-NVSZH35Y.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -1688,13 +1688,41 @@ var PAAExtractor = class {
1688
1688
  }
1689
1689
  return parts.join(" ").replace(/\s+/g, " ").trim();
1690
1690
  }
1691
- return Array.from(document.querySelectorAll(selectors.item)).map((pair) => ({
1692
- question: pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "",
1693
- answer: cleanText(pair.querySelector(selectors.answerContainer)) || void 0,
1694
- sourceTitle: pair.querySelector(selectors.sourceTitle)?.innerText?.trim() || void 0,
1695
- sourceSite: pair.querySelector(selectors.sourceSite)?.innerText?.trim() || void 0,
1696
- sourceCite: pair.querySelector(selectors.sourceCite)?.innerText?.trim() || void 0
1697
- }));
1691
+ return Array.from(document.querySelectorAll(selectors.item)).map((pair) => {
1692
+ let sourceTitle = pair.querySelector(selectors.sourceTitle)?.innerText?.trim() || void 0;
1693
+ let sourceSite = pair.querySelector(selectors.sourceSite)?.innerText?.trim() || void 0;
1694
+ let sourceCite = pair.querySelector(selectors.sourceCite)?.innerText?.trim() || void 0;
1695
+ if (!sourceCite) {
1696
+ const seenHrefs = /* @__PURE__ */ new Set();
1697
+ const anchors = [];
1698
+ for (const a of Array.from(pair.querySelectorAll("a[href]"))) {
1699
+ const href = a.href;
1700
+ if (!/^https?:\/\//.test(href) || seenHrefs.has(href)) continue;
1701
+ let host = "";
1702
+ try {
1703
+ host = new URL(href).hostname;
1704
+ } catch {
1705
+ continue;
1706
+ }
1707
+ if (/(^|\.)google(usercontent)?\.[a-z.]+$/.test(host)) continue;
1708
+ seenHrefs.add(href);
1709
+ anchors.push({ text: (a.textContent || "").trim(), href, host });
1710
+ }
1711
+ const primary = anchors.find((x) => x.text.length > 2) ?? anchors[0];
1712
+ if (primary) {
1713
+ sourceTitle = sourceTitle || primary.text || void 0;
1714
+ sourceSite = sourceSite || primary.host.replace(/^www\./, "");
1715
+ sourceCite = primary.href;
1716
+ }
1717
+ }
1718
+ return {
1719
+ question: pair.getAttribute(selectors.itemDataQ) || pair.getAttribute(selectors.itemDataInitQ) || pair.querySelector(selectors.itemQuestionEl)?.innerText?.trim() || "",
1720
+ answer: cleanText(pair.querySelector(selectors.answerContainer)) || void 0,
1721
+ sourceTitle,
1722
+ sourceSite,
1723
+ sourceCite
1724
+ };
1725
+ });
1698
1726
  }, sels);
1699
1727
  return raw.flatMap((item) => {
1700
1728
  const cleaned = {
@@ -1736,54 +1764,115 @@ var PAAExtractor = class {
1736
1764
  const seenQs = /* @__PURE__ */ new Set();
1737
1765
  const orderedQs = [];
1738
1766
  const results = [];
1739
- const readAllQs = () => page.evaluate(
1740
- ({ sel, dataQ, dataInitQ, questionEl }) => Array.from(document.querySelectorAll(sel)).map(
1741
- (el) => el.getAttribute(dataQ) || el.getAttribute(dataInitQ) || el.querySelector(questionEl)?.innerText?.trim() || ""
1742
- ).filter(Boolean),
1743
- { sel: PAASelectors.item, dataQ: PAASelectors.itemDataQ, dataInitQ: PAASelectors.itemDataInitQ, questionEl: PAASelectors.itemQuestionEl }
1767
+ const abandonedNeverReclick = /* @__PURE__ */ new Set();
1768
+ const clickedOnceEver = /* @__PURE__ */ new Set();
1769
+ const itemArgs = {
1770
+ sel: PAASelectors.item,
1771
+ dataQ: PAASelectors.itemDataQ,
1772
+ dataInitQ: PAASelectors.itemDataInitQ,
1773
+ questionEl: PAASelectors.itemQuestionEl,
1774
+ answerSel: PAASelectors.answerContainer,
1775
+ expandedClass: PAASelectors.expandedClass,
1776
+ clickTarget: PAASelectors.clickTarget
1777
+ };
1778
+ const readItemStates = () => page.evaluate(
1779
+ ({ sel, dataQ, dataInitQ, questionEl, expandedClass, answerSel }) => {
1780
+ const answerText = (root) => {
1781
+ if (!root) return "";
1782
+ let out = "";
1783
+ const walk = (n) => {
1784
+ for (const c of Array.from(n.childNodes)) {
1785
+ if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
1786
+ else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
1787
+ }
1788
+ };
1789
+ walk(root);
1790
+ return out.replace(/\s+/g, " ").trim();
1791
+ };
1792
+ return Array.from(document.querySelectorAll(sel)).map((el) => ({
1793
+ q: el.getAttribute(dataQ) || el.getAttribute(dataInitQ) || el.querySelector(questionEl)?.innerText?.trim() || "",
1794
+ done: el.classList.contains(expandedClass) || answerText(el.querySelector(answerSel)).length > 30
1795
+ })).filter((s) => s.q);
1796
+ },
1797
+ itemArgs
1744
1798
  );
1799
+ const expandOneItemSerially = async (q) => {
1800
+ const clicked = await page.evaluate(
1801
+ ({ sel, dataQ, dataInitQ, clickTarget, q: target }) => {
1802
+ const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
1803
+ const btn = item?.querySelector(clickTarget);
1804
+ if (!btn) return false;
1805
+ btn.scrollIntoView({ block: "center" });
1806
+ btn.click();
1807
+ return true;
1808
+ },
1809
+ { ...itemArgs, q }
1810
+ ).catch(() => false);
1811
+ if (!clicked) return "failed";
1812
+ try {
1813
+ const outcome = await page.waitForFunction(
1814
+ ({ sel, dataQ, dataInitQ, answerSel, q: target }) => {
1815
+ const answerText = (root) => {
1816
+ if (!root) return "";
1817
+ let out = "";
1818
+ const walk = (n) => {
1819
+ for (const c of Array.from(n.childNodes)) {
1820
+ if (c.nodeType === Node.TEXT_NODE) out += c.textContent || "";
1821
+ else if (c.nodeType === Node.ELEMENT_NODE && c.tagName !== "STYLE" && c.tagName !== "SCRIPT") walk(c);
1822
+ }
1823
+ };
1824
+ walk(root);
1825
+ return out.replace(/\s+/g, " ").trim();
1826
+ };
1827
+ const item = Array.from(document.querySelectorAll(sel)).find((el) => (el.getAttribute(dataQ) || el.getAttribute(dataInitQ)) === target);
1828
+ if (!item) return "gone";
1829
+ const text = answerText(item.querySelector(answerSel));
1830
+ if (/an error has occurred/i.test(text.slice(0, 80))) return "error";
1831
+ if (text.length > 30) return "ok";
1832
+ return false;
1833
+ },
1834
+ { ...itemArgs, q },
1835
+ { timeout: 6500, polling: 200 }
1836
+ );
1837
+ return await outcome.jsonValue() === "ok" ? "ok" : "failed";
1838
+ } catch {
1839
+ return "failed";
1840
+ }
1841
+ };
1745
1842
  let round = 0;
1746
- while (seenQs.size < options.maxQuestions) {
1843
+ let growthWaits = 0;
1844
+ while (true) {
1747
1845
  if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
1748
1846
  this.throwIfAborted(signal);
1749
- await this.throwIfCaptcha(page, "Google PAA expansion");
1750
- const beforeQs = await readAllQs();
1751
- if (beforeQs.length >= options.maxQuestions) break;
1752
- const unexpandedSel = `${PAASelectors.item}:not(.${PAASelectors.expandedClass}) ${PAASelectors.clickTarget}`;
1753
- const unexpandedCount = await page.locator(unexpandedSel).count();
1754
- if (unexpandedCount === 0) break;
1755
- this.reporter.onDepth(++round);
1756
- for (let ci = 0; ci < unexpandedCount; ci++) {
1757
- if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
1758
- this.throwIfAborted(signal);
1759
- try {
1760
- const btn = page.locator(unexpandedSel).first();
1761
- await btn.scrollIntoViewIfNeeded();
1762
- await btn.hover({ force: true });
1763
- await page.waitForTimeout(100);
1764
- await btn.click({ force: true });
1765
- await page.waitForTimeout(500);
1766
- } catch {
1847
+ const states = await readItemStates();
1848
+ for (const s of states) {
1849
+ if (!seenQs.has(s.q)) {
1850
+ seenQs.add(s.q);
1851
+ orderedQs.push(s.q);
1767
1852
  }
1768
1853
  }
1769
- await page.waitForFunction(
1770
- ({ sel, min }) => document.querySelectorAll(sel).length > min,
1771
- { sel: PAASelectors.item, min: beforeQs.length },
1772
- { timeout: 5e3 }
1773
- ).catch(() => {
1774
- });
1775
- await this.throwIfCaptcha(page, "Google PAA expansion");
1776
- const afterQs = await readAllQs();
1777
- if (afterQs.length === beforeQs.length) break;
1778
- for (const q of afterQs) {
1779
- if (!seenQs.has(q)) {
1780
- seenQs.add(q);
1781
- orderedQs.push(q);
1782
- }
1854
+ const kept = new Set(orderedQs.slice(0, options.maxQuestions));
1855
+ const clickable = states.filter((s) => kept.has(s.q) && !clickedOnceEver.has(s.q) && !abandonedNeverReclick.has(s.q));
1856
+ const target = clickable.find((s) => !s.done) ?? (seenQs.size < options.maxQuestions ? clickable[0] : void 0);
1857
+ if (!target) {
1858
+ if (seenQs.size >= options.maxQuestions) break;
1859
+ if (++growthWaits > 1) break;
1860
+ const grew = await page.waitForFunction(
1861
+ ({ sel, min }) => document.querySelectorAll(sel).length > min,
1862
+ { sel: PAASelectors.item, min: states.length },
1863
+ { timeout: 3e3 }
1864
+ ).catch(() => null);
1865
+ if (!grew) break;
1866
+ continue;
1783
1867
  }
1868
+ growthWaits = 0;
1869
+ this.reporter.onDepth(++round);
1870
+ await this.throwIfCaptcha(page, "Google PAA expansion");
1871
+ clickedOnceEver.add(target.q);
1872
+ if (await expandOneItemSerially(target.q) === "failed") abandonedNeverReclick.add(target.q);
1784
1873
  }
1785
1874
  const itemMap = new Map((await this.extractVisibleItems(page)).map((i) => [i.question, i]));
1786
- await this.fillIncompleteItems(page, orderedQs, itemMap, options);
1875
+ await this.fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick);
1787
1876
  for (const q of orderedQs) {
1788
1877
  if (results.length >= options.maxQuestions) break;
1789
1878
  const key = this.normalizeQuestion(q);
@@ -1801,9 +1890,11 @@ var PAAExtractor = class {
1801
1890
  this.completeness.paaWithoutSource = results.filter((r) => !r.source_title && !r.source_site && !r.source_cite).length;
1802
1891
  return results;
1803
1892
  }
1804
- async fillIncompleteItems(page, orderedQs, itemMap, options) {
1893
+ async fillIncompleteItems(page, orderedQs, itemMap, options, abandonedNeverReclick) {
1805
1894
  const kept = new Set(orderedQs.slice(0, options.maxQuestions));
1806
- const missingBefore = new Set([...kept].filter((q) => !itemMap.get(q)?.answer));
1895
+ const missingBefore = new Set(
1896
+ [...kept].filter((q) => !itemMap.get(q)?.answer && !abandonedNeverReclick?.has(q))
1897
+ );
1807
1898
  if (missingBefore.size === 0) return;
1808
1899
  for (let round = 0; round < 2; round++) {
1809
1900
  if (options.softDeadlineMs && Date.now() >= options.softDeadlineMs) break;
@@ -2195,40 +2286,41 @@ var PAAExtractor = class {
2195
2286
  };
2196
2287
  }
2197
2288
  async captureAIOverviewShareUrl(page, options) {
2198
- for (let attempt = 0; attempt < 3; attempt++) {
2199
- if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null;
2200
- const clicked = await page.evaluate(() => {
2201
- const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"],div')).filter((h) => (h.textContent || "").trim() === "AI Overview");
2202
- for (const head of heads) {
2203
- let sec = head;
2204
- for (let up = 0; up < 8 && sec; up++) {
2205
- const btn = sec.querySelector('[aria-label="Share"]');
2206
- if (btn) {
2207
- btn.scrollIntoView({ block: "center" });
2208
- btn.click();
2209
- return true;
2210
- }
2211
- sec = sec.parentElement;
2289
+ if (options?.softDeadlineMs && Date.now() >= options.softDeadlineMs) return null;
2290
+ const clicked = await page.evaluate(() => {
2291
+ const heads = Array.from(document.querySelectorAll('h1,h2,h3,[role="heading"],div')).filter((h) => (h.textContent || "").trim() === "AI Overview");
2292
+ for (const head of heads) {
2293
+ let sec = head;
2294
+ for (let up = 0; up < 8 && sec; up++) {
2295
+ const btn = sec.querySelector('[aria-label="Share"]');
2296
+ if (btn) {
2297
+ btn.scrollIntoView({ block: "center" });
2298
+ btn.click();
2299
+ return true;
2212
2300
  }
2301
+ sec = sec.parentElement;
2213
2302
  }
2214
- return false;
2215
- }).catch(() => false);
2216
- if (!clicked) return null;
2217
- await page.waitForTimeout(4500);
2218
- const url = await page.evaluate(() => {
2303
+ }
2304
+ return false;
2305
+ }).catch(() => false);
2306
+ if (!clicked) return null;
2307
+ let url = null;
2308
+ try {
2309
+ const found = await page.waitForFunction(() => {
2219
2310
  const w = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
2220
2311
  while (w.nextNode()) {
2221
2312
  const m = (w.currentNode.textContent || "").match(/https:\/\/share\.google\/aimode\/\S+/);
2222
2313
  if (m) return m[0];
2223
2314
  }
2224
- return null;
2225
- }).catch(() => null);
2226
- await page.keyboard.press("Escape").catch(() => {
2227
- });
2228
- if (url) return url;
2229
- await page.waitForTimeout(600);
2315
+ return false;
2316
+ }, void 0, { timeout: 4500, polling: 250 });
2317
+ url = await found.jsonValue();
2318
+ } catch {
2319
+ url = null;
2230
2320
  }
2231
- return null;
2321
+ await page.keyboard.press("Escape").catch(() => {
2322
+ });
2323
+ return url;
2232
2324
  }
2233
2325
  buildTree(flat, _seed) {
2234
2326
  const roots = [];