mcp-scraper 0.2.5 → 0.2.7

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.
@@ -41,13 +41,13 @@ import {
41
41
  harvestTimeoutBudget,
42
42
  liveWebToolAnnotations,
43
43
  outputBaseDir
44
- } from "./chunk-ENI4DM3S.js";
44
+ } from "./chunk-7SI6XIR3.js";
45
45
  import {
46
46
  CaptchaError,
47
47
  RECAPTCHA_INSTRUCTIONS,
48
48
  sanitizeVendorName
49
49
  } from "./chunk-M2S27J6Z.js";
50
- import "./chunk-JDX57SEC.js";
50
+ import "./chunk-IZE5UW7Y.js";
51
51
  import {
52
52
  SiteAuditJobRowSchema,
53
53
  cancelJob,
@@ -11710,18 +11710,26 @@ async function recordAction(input) {
11710
11710
  async function recordReplayStart(input) {
11711
11711
  const db = getDb();
11712
11712
  await db.execute({
11713
- sql: `INSERT INTO browser_agent_replays (replay_id, session_id, view_url, label)
11714
- VALUES (?, ?, ?, ?)`,
11715
- args: [input.replayId, input.sessionId, input.viewUrl, input.label]
11713
+ sql: `INSERT INTO browser_agent_replays (replay_id, session_id, view_url, label, started_at)
11714
+ VALUES (?, ?, ?, ?, ?)`,
11715
+ args: [input.replayId, input.sessionId, input.viewUrl, input.label, (/* @__PURE__ */ new Date()).toISOString()]
11716
11716
  });
11717
11717
  }
11718
11718
  async function recordReplayStop(replayId, viewUrl) {
11719
11719
  const db = getDb();
11720
11720
  await db.execute({
11721
- sql: `UPDATE browser_agent_replays SET stopped_at = datetime('now'), view_url = COALESCE(?, view_url) WHERE replay_id = ?`,
11722
- args: [viewUrl, replayId]
11721
+ sql: `UPDATE browser_agent_replays SET stopped_at = ?, view_url = COALESCE(?, view_url) WHERE replay_id = ?`,
11722
+ args: [(/* @__PURE__ */ new Date()).toISOString(), viewUrl, replayId]
11723
11723
  });
11724
11724
  }
11725
+ async function getActiveReplayRow(sessionId) {
11726
+ const db = getDb();
11727
+ const res = await db.execute({
11728
+ sql: `SELECT * FROM browser_agent_replays WHERE session_id = ? AND stopped_at IS NULL ORDER BY started_at DESC LIMIT 1`,
11729
+ args: [sessionId]
11730
+ });
11731
+ return res.rows[0] ?? null;
11732
+ }
11725
11733
  async function listReplayRows(sessionId) {
11726
11734
  const db = getDb();
11727
11735
  const res = await db.execute({
@@ -11835,7 +11843,13 @@ async function readPage(cdpWsUrl) {
11835
11843
  role: el2.getAttribute("role") || el2.tagName.toLowerCase(),
11836
11844
  name,
11837
11845
  x: Math.round(r.left + r.width / 2),
11838
- y: Math.round(r.top + r.height / 2)
11846
+ y: Math.round(r.top + r.height / 2),
11847
+ left: Math.round(r.left),
11848
+ top: Math.round(r.top),
11849
+ width: Math.round(r.width),
11850
+ height: Math.round(r.height),
11851
+ right: Math.round(r.right),
11852
+ bottom: Math.round(r.bottom)
11839
11853
  });
11840
11854
  }
11841
11855
  const text = (document.body?.innerText || "").replace(/\n{3,}/g, "\n\n").trim().slice(0, 6e3);
@@ -11845,13 +11859,168 @@ async function readPage(cdpWsUrl) {
11845
11859
  url,
11846
11860
  title,
11847
11861
  text: data.text,
11848
- elements: data.els.map((e, i) => ({ ref: i + 1, role: e.role, name: e.name, x: e.x, y: e.y }))
11862
+ elements: data.els.map((e, i) => ({
11863
+ ref: i + 1,
11864
+ role: e.role,
11865
+ name: e.name,
11866
+ x: e.x,
11867
+ y: e.y,
11868
+ left: e.left,
11869
+ top: e.top,
11870
+ width: e.width,
11871
+ height: e.height,
11872
+ right: e.right,
11873
+ bottom: e.bottom
11874
+ }))
11849
11875
  };
11850
11876
  } finally {
11851
11877
  await browser.close().catch(() => {
11852
11878
  });
11853
11879
  }
11854
11880
  }
11881
+ async function locatePageTargets(cdpWsUrl, targets) {
11882
+ const browser = await playwrightChromium.connectOverCDP(cdpWsUrl);
11883
+ try {
11884
+ const context = browser.contexts()[0] ?? await browser.newContext();
11885
+ const page = context.pages()[0] ?? await context.newPage();
11886
+ const url = page.url();
11887
+ const title = await page.title().catch(() => "");
11888
+ await page.evaluate(() => {
11889
+ ;
11890
+ globalThis.__name = (target) => target;
11891
+ }).catch(() => {
11892
+ });
11893
+ const data = await page.evaluate((rawTargets) => {
11894
+ const normalize = (value) => value.replace(/\s+/g, " ").trim();
11895
+ const isVisibleRect = (r) => r.width >= 1 && r.height >= 1 && r.bottom >= 0 && r.right >= 0 && r.top <= window.innerHeight && r.left <= window.innerWidth;
11896
+ const roleOf = (el2) => el2.getAttribute("role") || el2.tagName.toLowerCase();
11897
+ const nameOf = (el2) => {
11898
+ const e = el2;
11899
+ return normalize(
11900
+ e.getAttribute("aria-label") || e.placeholder || e.innerText || e.getAttribute("value") || e.getAttribute("title") || ""
11901
+ ).slice(0, 160);
11902
+ };
11903
+ const textOf = (el2) => normalize(el2.innerText || el2.textContent || "").slice(0, 500);
11904
+ const unionRects = (rects) => {
11905
+ const visible = rects.filter(isVisibleRect);
11906
+ if (!visible.length) return null;
11907
+ const left = Math.min(...visible.map((r) => r.left));
11908
+ const top = Math.min(...visible.map((r) => r.top));
11909
+ const right = Math.max(...visible.map((r) => r.right));
11910
+ const bottom = Math.max(...visible.map((r) => r.bottom));
11911
+ return {
11912
+ left,
11913
+ top,
11914
+ right,
11915
+ bottom,
11916
+ width: right - left,
11917
+ height: bottom - top,
11918
+ x: left,
11919
+ y: top,
11920
+ toJSON: () => ({})
11921
+ };
11922
+ };
11923
+ const elementPayload = (el2, rect) => ({
11924
+ role: roleOf(el2),
11925
+ tag: el2.tagName.toLowerCase(),
11926
+ name: nameOf(el2),
11927
+ text: textOf(el2),
11928
+ x: Math.round(rect.left + rect.width / 2),
11929
+ y: Math.round(rect.top + rect.height / 2),
11930
+ left: Math.round(rect.left),
11931
+ top: Math.round(rect.top),
11932
+ width: Math.round(rect.width),
11933
+ height: Math.round(rect.height),
11934
+ right: Math.round(rect.right),
11935
+ bottom: Math.round(rect.bottom)
11936
+ });
11937
+ const selectorMatches = (selector) => {
11938
+ const nodes = Array.from(document.querySelectorAll(selector));
11939
+ return nodes.map((el2) => ({ el: el2, rect: el2.getBoundingClientRect() })).filter((item) => isVisibleRect(item.rect));
11940
+ };
11941
+ const textMatches = (needle, match) => {
11942
+ const wanted = normalize(needle);
11943
+ const wantedLower = wanted.toLowerCase();
11944
+ if (!wantedLower) return [];
11945
+ const matches = [];
11946
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
11947
+ let node = walker.nextNode();
11948
+ while (node) {
11949
+ const raw = node.textContent || "";
11950
+ const normalizedRaw = normalize(raw);
11951
+ const rawLower = raw.toLowerCase();
11952
+ const normalizedLower = normalizedRaw.toLowerCase();
11953
+ const ok = match === "exact" ? normalizedLower === wantedLower : normalizedLower.includes(wantedLower);
11954
+ if (ok) {
11955
+ const start = match === "exact" ? 0 : rawLower.indexOf(wantedLower);
11956
+ const parent = node.parentElement;
11957
+ if (parent && start >= 0) {
11958
+ try {
11959
+ const range = document.createRange();
11960
+ range.setStart(node, Math.min(start, raw.length));
11961
+ range.setEnd(node, Math.min(raw.length, start + needle.length));
11962
+ const rect = unionRects(Array.from(range.getClientRects()));
11963
+ range.detach();
11964
+ if (rect) matches.push({ el: parent, rect });
11965
+ } catch {
11966
+ const rect = parent.getBoundingClientRect();
11967
+ if (isVisibleRect(rect)) matches.push({ el: parent, rect });
11968
+ }
11969
+ }
11970
+ }
11971
+ node = walker.nextNode();
11972
+ }
11973
+ if (matches.length) return matches;
11974
+ const fallback = Array.from(document.querySelectorAll("body *")).map((el2) => ({ el: el2, rect: el2.getBoundingClientRect(), text: textOf(el2) })).filter((item) => isVisibleRect(item.rect)).filter((item) => {
11975
+ const value = item.text.toLowerCase();
11976
+ return match === "exact" ? value === wantedLower : value.includes(wantedLower);
11977
+ }).sort((a, b) => a.rect.width * a.rect.height - b.rect.width * b.rect.height);
11978
+ return fallback.map(({ el: el2, rect }) => ({ el: el2, rect }));
11979
+ };
11980
+ const viewport = {
11981
+ width: window.innerWidth,
11982
+ height: window.innerHeight,
11983
+ devicePixelRatio: window.devicePixelRatio || 1
11984
+ };
11985
+ const results = rawTargets.map((target) => {
11986
+ const index = Math.max(0, Math.floor(target.index ?? 0));
11987
+ const match = target.match === "exact" ? "exact" : "contains";
11988
+ try {
11989
+ const selector = target.selector?.trim();
11990
+ const text = target.text?.trim();
11991
+ const candidates = selector ? selectorMatches(selector) : text ? textMatches(text, match) : [];
11992
+ const selected = candidates[index];
11993
+ return {
11994
+ name: target.name ?? null,
11995
+ selector: selector || null,
11996
+ text: text || null,
11997
+ match,
11998
+ index,
11999
+ found: Boolean(selected),
12000
+ element: selected ? elementPayload(selected.el, selected.rect) : null,
12001
+ ...selected ? {} : { error: "target not found in current viewport" }
12002
+ };
12003
+ } catch (err) {
12004
+ return {
12005
+ name: target.name ?? null,
12006
+ selector: target.selector ?? null,
12007
+ text: target.text ?? null,
12008
+ match,
12009
+ index,
12010
+ found: false,
12011
+ element: null,
12012
+ error: err instanceof Error ? err.message : String(err)
12013
+ };
12014
+ }
12015
+ });
12016
+ return { viewport, targets: results };
12017
+ }, targets);
12018
+ return { url, title, viewport: data.viewport, targets: data.targets };
12019
+ } finally {
12020
+ await browser.close().catch(() => {
12021
+ });
12022
+ }
12023
+ }
11855
12024
  async function replayStart(runtimeSessionId) {
11856
12025
  const k = client();
11857
12026
  const res = await k.browsers.replays.start(runtimeSessionId);
@@ -11905,6 +12074,22 @@ function failure(err) {
11905
12074
  const msg = err instanceof Error ? err.message : String(err);
11906
12075
  return { error: sanitizeVendorName(msg) };
11907
12076
  }
12077
+ function parseAgentDate(value) {
12078
+ if (!value) return null;
12079
+ const parsed = Date.parse(value);
12080
+ if (Number.isFinite(parsed)) return parsed;
12081
+ const legacyParsed = Date.parse(`${value.replace(" ", "T")}Z`);
12082
+ return Number.isFinite(legacyParsed) ? legacyParsed : null;
12083
+ }
12084
+ function replayClock(row) {
12085
+ if (!row) return null;
12086
+ const startedAtMs = parseAgentDate(row.started_at);
12087
+ return {
12088
+ replay_id: row.replay_id,
12089
+ started_at: row.started_at,
12090
+ replay_elapsed_seconds: startedAtMs == null ? null : Math.max(0, (Date.now() - startedAtMs) / 1e3)
12091
+ };
12092
+ }
11908
12093
  function replayDownloadUrl(sessionId, replayId) {
11909
12094
  return `/agent/sessions/${encodeURIComponent(sessionId)}/replays/${encodeURIComponent(replayId)}/download`;
11910
12095
  }
@@ -12015,13 +12200,15 @@ function buildBrowserAgentRoutes() {
12015
12200
  }
12016
12201
  await charge(row.id, user.id, t0);
12017
12202
  await recordAction({ sessionId: row.id, type: "screenshot", params: null, ok: true });
12203
+ const activeReplay = await getActiveReplayRow(row.id);
12018
12204
  return c.json({
12019
12205
  image_base64: shot.base64,
12020
12206
  mime_type: shot.mimeType,
12021
12207
  url: page?.url ?? null,
12022
12208
  title: page?.title ?? null,
12023
12209
  elements: page?.elements ?? [],
12024
- text: page?.text ?? null
12210
+ text: page?.text ?? null,
12211
+ replay: replayClock(activeReplay)
12025
12212
  });
12026
12213
  } catch (err) {
12027
12214
  await charge(row.id, user.id, t0);
@@ -12038,7 +12225,8 @@ function buildBrowserAgentRoutes() {
12038
12225
  const page = await readPage(row.cdp_ws_url);
12039
12226
  await charge(row.id, user.id, t0);
12040
12227
  await recordAction({ sessionId: row.id, type: "read", params: null, ok: true });
12041
- return c.json(page);
12228
+ const activeReplay = await getActiveReplayRow(row.id);
12229
+ return c.json({ ...page, replay: replayClock(activeReplay) });
12042
12230
  } catch (err) {
12043
12231
  await charge(row.id, user.id, t0);
12044
12232
  return c.json(failure(err), 502);
@@ -12067,6 +12255,27 @@ function buildBrowserAgentRoutes() {
12067
12255
  return c.json(failure(err), 502);
12068
12256
  }
12069
12257
  });
12258
+ app2.post("/sessions/:id/locate", async (c) => {
12259
+ const user = c.get("user");
12260
+ const row = await loadOpenSession(c.req.param("id"), user.id);
12261
+ if (!row) return c.json({ error: "not found" }, 404);
12262
+ const body = await c.req.json().catch(() => ({}));
12263
+ const rawTargets = Array.isArray(body.targets) ? body.targets : [];
12264
+ const targets = rawTargets.filter((value) => value && typeof value === "object").slice(0, 20).map((value) => value);
12265
+ if (!targets.length) return c.json({ error: "targets is required" }, 400);
12266
+ const t0 = Date.now();
12267
+ try {
12268
+ const result = await locatePageTargets(row.cdp_ws_url, targets);
12269
+ await charge(row.id, user.id, t0);
12270
+ await recordAction({ sessionId: row.id, type: "locate", params: { targets: targets.length }, ok: true });
12271
+ const activeReplay = await getActiveReplayRow(row.id);
12272
+ return c.json({ ...result, replay: replayClock(activeReplay) });
12273
+ } catch (err) {
12274
+ await charge(row.id, user.id, t0);
12275
+ await recordAction({ sessionId: row.id, type: "locate", params: { targets: targets.length }, ok: false, error: String(err) });
12276
+ return c.json(failure(err), 502);
12277
+ }
12278
+ });
12070
12279
  app2.post("/sessions/:id/type", async (c) => {
12071
12280
  const user = c.get("user");
12072
12281
  const row = await loadOpenSession(c.req.param("id"), user.id);
@@ -13511,4 +13720,4 @@ app.get("/blog/:slug/", (c) => {
13511
13720
  export {
13512
13721
  app
13513
13722
  };
13514
- //# sourceMappingURL=server-Q3WHIS63.js.map
13723
+ //# sourceMappingURL=server-RIKRBDOI.js.map