@fusengine/browser-mcp 0.1.15 → 0.1.17
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/README.md +13 -3
- package/dist/actions/navigation.d.ts +10 -2
- package/dist/actions/navigation.d.ts.map +1 -1
- package/dist/actions/navigation.js +12 -2
- package/dist/actions/navigation.js.map +1 -1
- package/dist/actions/perform.d.ts.map +1 -1
- package/dist/actions/perform.js +4 -1
- package/dist/actions/perform.js.map +1 -1
- package/dist/extraction/collect-merge.d.ts +19 -0
- package/dist/extraction/collect-merge.d.ts.map +1 -0
- package/dist/extraction/collect-merge.js +18 -0
- package/dist/extraction/collect-merge.js.map +1 -0
- package/dist/extraction/scroll-script.d.ts +11 -0
- package/dist/extraction/scroll-script.d.ts.map +1 -0
- package/dist/extraction/scroll-script.js +55 -0
- package/dist/extraction/scroll-script.js.map +1 -0
- package/dist/extraction/selector.d.ts +18 -0
- package/dist/extraction/selector.d.ts.map +1 -0
- package/dist/extraction/selector.js +53 -0
- package/dist/extraction/selector.js.map +1 -0
- package/dist/extraction/snapshot-walk.d.ts +1 -1
- package/dist/extraction/snapshot-walk.d.ts.map +1 -1
- package/dist/extraction/snapshot-walk.js +4 -1
- package/dist/extraction/snapshot-walk.js.map +1 -1
- package/dist/extraction/snapshot.d.ts +1 -1
- package/dist/extraction/snapshot.d.ts.map +1 -1
- package/dist/extraction/snapshot.js +4 -3
- package/dist/extraction/snapshot.js.map +1 -1
- package/dist/interfaces/extraction.d.ts +9 -0
- package/dist/interfaces/extraction.d.ts.map +1 -1
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +2 -0
- package/dist/server/server.js.map +1 -1
- package/dist/server/tools/act.d.ts.map +1 -1
- package/dist/server/tools/act.js +6 -2
- package/dist/server/tools/act.js.map +1 -1
- package/dist/server/tools/collect.d.ts +13 -0
- package/dist/server/tools/collect.d.ts.map +1 -0
- package/dist/server/tools/collect.js +35 -0
- package/dist/server/tools/collect.js.map +1 -0
- package/dist/server/tools/snapshot.d.ts.map +1 -1
- package/dist/server/tools/snapshot.js +13 -10
- package/dist/server/tools/snapshot.js.map +1 -1
- package/dist/state/action-memory.d.ts +22 -0
- package/dist/state/action-memory.d.ts.map +1 -0
- package/dist/state/action-memory.js +19 -0
- package/dist/state/action-memory.js.map +1 -0
- package/dist/state/scroll-collect.d.ts +27 -0
- package/dist/state/scroll-collect.d.ts.map +1 -0
- package/dist/state/scroll-collect.js +34 -0
- package/dist/state/scroll-collect.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ Supported: `FUSE_ENGINE`, `FUSE_CHANNEL`, `FUSE_CDP_ENDPOINT`, `FUSE_EXECUTABLE_
|
|
|
132
132
|
`FUSE_HEADLESS`, `FUSE_COUNTRY`, `FUSE_CURRENCY`, `FUSE_USER_DATA_DIR`,
|
|
133
133
|
`FUSE_STORAGE_STATE`, `FUSE_OUTPUT_DIR`.
|
|
134
134
|
|
|
135
|
-
### Tools (
|
|
135
|
+
### Tools (26)
|
|
136
136
|
|
|
137
137
|
| Group | Tools |
|
|
138
138
|
| --- | --- |
|
|
@@ -140,8 +140,8 @@ Supported: `FUSE_ENGINE`, `FUSE_CHANNEL`, `FUSE_CDP_ENDPOINT`, `FUSE_EXECUTABLE_
|
|
|
140
140
|
| **Fast-path** | `browser_fetch` — HTTP fetch with browser TLS/HTTP2 impersonation, **no browser launch** (~10× faster) for server-rendered HTML |
|
|
141
141
|
| **Session** | `browser_open`, `browser_connect`, `browser_status`, `browser_close` |
|
|
142
142
|
| **Navigate** | `browser_navigate`, `browser_back`, `browser_forward`, `browser_wait`, `browser_wait_for` |
|
|
143
|
-
| **Act** | `browser_click`, `browser_fill`, `browser_login`, `browser_scroll
|
|
144
|
-
| **Agentic** | `browser_snapshot` (indexed refs), `browser_act` (by ref + page diff), `browser_run` (multi-step plan) |
|
|
143
|
+
| **Act** | `browser_click`, `browser_fill`, `browser_login`, `browser_scroll` (window or a container via `selector`/`to:"end"`), `browser_press`, `browser_select` |
|
|
144
|
+
| **Agentic** | `browser_snapshot` (indexed refs), `browser_act` (by ref + page diff), `browser_run` (multi-step plan), `browser_collect` (exhaust a virtualized/infinite list) |
|
|
145
145
|
| **Extract** | `browser_extract` (text/prices/hotels/challenges), `browser_extract_schema` (typed, by CSS selectors) |
|
|
146
146
|
| **SERP** | `browser_serp_batch` — multi-query Google search in one session, per-query organic results + domain rank |
|
|
147
147
|
| **Vision** | `browser_screenshot` (page, single element by `ref`, or responsive set via `viewport`/`viewports`) |
|
|
@@ -156,6 +156,16 @@ Key agentic patterns:
|
|
|
156
156
|
checkouts are visible and actionable.
|
|
157
157
|
- **`browser_wait_for`** — wait on a condition (`text` / `selector` / `gone` / `urlContains`),
|
|
158
158
|
not a fixed delay.
|
|
159
|
+
- **`browser_snapshot` `selectors:true`** — each element also gets a **durable CSS `selector`**
|
|
160
|
+
(finder-style: prefers `data-testid`/stable id/semantic class, rejects generated hashes,
|
|
161
|
+
validated unique). Cache it to act on later visits via `browser_click target=<selector>`
|
|
162
|
+
without re-snapshotting. The act tools also **remember the winning locator strategy per
|
|
163
|
+
site** (under the output dir) and replay it first on repeat actions.
|
|
164
|
+
- **`browser_collect`** — exhaust a **virtualized / infinite-scroll** list (hotel/flight
|
|
165
|
+
results, feeds): auto-detects the scroll container, scrolls incrementally and **dedups
|
|
166
|
+
rows by stable key** until the list ends. Returns the full set of rows (text / url /
|
|
167
|
+
optional prices) — data, not refs (virtualization recycles nodes), so to act on a found
|
|
168
|
+
row use `browser_act` by text or `browser_scroll` then `browser_snapshot`.
|
|
159
169
|
- **`browser_run`** — execute an ordered plan (navigate/act/wait/extract) in one call,
|
|
160
170
|
stopping at the first failure. Guardrails apply to the whole plan.
|
|
161
171
|
|
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import type { Page } from "playwright";
|
|
6
6
|
import type { ActionResult } from "../interfaces/types.js";
|
|
7
|
-
/**
|
|
8
|
-
export
|
|
7
|
+
/** Optional scroll target: a specific container and/or jump to its end. */
|
|
8
|
+
export interface ScrollOpts {
|
|
9
|
+
selector?: string;
|
|
10
|
+
to?: "end";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Scroll by a pixel delta (window, positive = down/right). With `selector` or
|
|
14
|
+
* `to:"end"`, scroll the matching (or auto-detected) scrollable container.
|
|
15
|
+
*/
|
|
16
|
+
export declare function scroll(page: Page, deltaY: number, deltaX?: number, opts?: ScrollOpts): Promise<ActionResult>;
|
|
9
17
|
/** Press a key or shortcut (e.g. "Enter", "ArrowDown", "Control+a"). */
|
|
10
18
|
export declare function pressKey(page: Page, key: string): Promise<ActionResult>;
|
|
11
19
|
/** Select option(s) in a <select> by value, label or index. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/actions/navigation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/actions/navigation.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAW3D,2EAA2E;AAC3E,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,KAAK,CAAC;CACZ;AAED;;;GAGG;AACH,wBAAsB,MAAM,CAC1B,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,MAAM,SAAI,EACV,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,YAAY,CAAC,CAQvB;AAED,wEAAwE;AACxE,wBAAsB,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAO7E;AAED,+DAA+D;AAC/D,wBAAsB,YAAY,CAChC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,YAAY,CAAC,CAOvB;AAED,6CAA6C;AAC7C,wBAAsB,eAAe,CACnC,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,YAAY,CAAC,CAMvB"}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { SCROLL_SCRIPT } from "../extraction/scroll-script.js";
|
|
2
|
+
import { evalScriptArg } from "../lib/evaluate.js";
|
|
3
|
+
/**
|
|
4
|
+
* Scroll by a pixel delta (window, positive = down/right). With `selector` or
|
|
5
|
+
* `to:"end"`, scroll the matching (or auto-detected) scrollable container.
|
|
6
|
+
*/
|
|
7
|
+
export async function scroll(page, deltaY, deltaX = 0, opts = {}) {
|
|
8
|
+
if (opts.selector || opts.to === "end") {
|
|
9
|
+
const arg = { selector: opts.selector ?? null, to: opts.to ?? null, delta: deltaY };
|
|
10
|
+
const geo = await evalScriptArg(page, SCROLL_SCRIPT, arg);
|
|
11
|
+
return { type: "scroll", ok: geo.found, deltaY, selector: opts.selector ?? null, scrollTop: geo.scrollTop, atEnd: geo.atEnd };
|
|
12
|
+
}
|
|
3
13
|
await page.mouse.wheel(deltaX, deltaY);
|
|
4
14
|
return { type: "scroll", ok: true, deltaX, deltaY };
|
|
5
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/actions/navigation.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/actions/navigation.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perform.d.ts","sourceRoot":"","sources":["../../src/actions/perform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAO3D,oFAAoF;AACpF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,6CAA6C;AAC7C,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,WAAW,EACnB,SAAS,UAAQ,GAChB,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"perform.d.ts","sourceRoot":"","sources":["../../src/actions/perform.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAO3D,oFAAoF;AACpF,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErE,6CAA6C;AAC7C,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,WAAW,EACnB,SAAS,UAAQ,GAChB,OAAO,CAAC,YAAY,CAAC,CAgCvB;AAED,yDAAyD;AACzD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAIhF"}
|
package/dist/actions/perform.js
CHANGED
|
@@ -15,7 +15,10 @@ export async function performAction(page, action, humanMode = false) {
|
|
|
15
15
|
case "login":
|
|
16
16
|
return login(page, action, humanMode);
|
|
17
17
|
case "scroll":
|
|
18
|
-
return scroll(page, Number(action.deltaY ?? 600), Number(action.deltaX ?? 0)
|
|
18
|
+
return scroll(page, Number(action.deltaY ?? 600), Number(action.deltaX ?? 0), {
|
|
19
|
+
selector: typeof action.selector === "string" ? action.selector : undefined,
|
|
20
|
+
to: action.to === "end" ? "end" : undefined,
|
|
21
|
+
});
|
|
19
22
|
case "press":
|
|
20
23
|
return pressKey(page, String(action.key ?? ""));
|
|
21
24
|
case "select":
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"perform.js","sourceRoot":"","sources":["../../src/actions/perform.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAoB,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAK5C,6CAA6C;AAC7C,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAU,EACV,MAAmB,EACnB,SAAS,GAAG,KAAK;IAEjB,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;IACzD,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,OAAO;YACV,OAAO,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QACxD,KAAK,MAAM;YACT,OAAO,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QACnF,KAAK,OAAO;YACV,OAAO,KAAK,CAAC,IAAI,EAAE,MAAqB,EAAE,SAAS,CAAC,CAAC;QACvD,KAAK,QAAQ;YACX,OAAO,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"perform.js","sourceRoot":"","sources":["../../src/actions/perform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure merge/dedup for scroll-collected rows. Virtualized lists recycle DOM
|
|
3
|
+
* nodes, so rows are keyed by a stable identifier (data-id › href › text), not
|
|
4
|
+
* by node identity. Kept side-effect-free for unit testing.
|
|
5
|
+
* @module extraction/collect-merge
|
|
6
|
+
*/
|
|
7
|
+
import type { CollectedItem, Price } from "../interfaces/extraction.js";
|
|
8
|
+
/** A raw row harvested in the page context (before price enrichment). */
|
|
9
|
+
export interface RawRow {
|
|
10
|
+
key: string;
|
|
11
|
+
text: string;
|
|
12
|
+
url: string | null;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Merge `rows` into `seen` (keyed dedup), optionally enriching each new row
|
|
16
|
+
* with prices via `pricer`. Returns the number of rows that were new.
|
|
17
|
+
*/
|
|
18
|
+
export declare function mergeItems(seen: Map<string, CollectedItem>, rows: RawRow[], pricer: ((text: string) => Price[]) | null): number;
|
|
19
|
+
//# sourceMappingURL=collect-merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect-merge.d.ts","sourceRoot":"","sources":["../../src/extraction/collect-merge.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,6BAA6B,CAAC;AAExE,yEAAyE;AACzE,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACpB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,EAChC,IAAI,EAAE,MAAM,EAAE,EACd,MAAM,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,EAAE,CAAC,GAAG,IAAI,GACzC,MAAM,CAUR"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge `rows` into `seen` (keyed dedup), optionally enriching each new row
|
|
3
|
+
* with prices via `pricer`. Returns the number of rows that were new.
|
|
4
|
+
*/
|
|
5
|
+
export function mergeItems(seen, rows, pricer) {
|
|
6
|
+
let added = 0;
|
|
7
|
+
for (const row of rows) {
|
|
8
|
+
if (!row.key || seen.has(row.key))
|
|
9
|
+
continue;
|
|
10
|
+
const item = { key: row.key, text: row.text, url: row.url };
|
|
11
|
+
if (pricer)
|
|
12
|
+
item.prices = pricer(row.text);
|
|
13
|
+
seen.set(row.key, item);
|
|
14
|
+
added++;
|
|
15
|
+
}
|
|
16
|
+
return added;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=collect-merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect-merge.js","sourceRoot":"","sources":["../../src/extraction/collect-merge.ts"],"names":[],"mappings":"AAeA;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,IAAgC,EAChC,IAAc,EACd,MAA0C;IAE1C,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5C,MAAM,IAAI,GAAkB,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC;QAC3E,IAAI,MAAM;YAAE,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACxB,KAAK,EAAE,CAAC;IACV,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side scripts for virtualized/infinite lists: detect the scrollable
|
|
3
|
+
* container (explicit selector or auto by overflow + scrollHeight), scroll it,
|
|
4
|
+
* and harvest the currently-mounted rows. Runs in the page context.
|
|
5
|
+
* @module extraction/scroll-script
|
|
6
|
+
*/
|
|
7
|
+
/** Scroll the (auto-detected or `selector`) container; `to:"end"` jumps to bottom. */
|
|
8
|
+
export declare const SCROLL_SCRIPT = "(arg) => {\n const pick = (sel) => {\n if (sel) return document.querySelector(sel);\n let best = null, bestH = 0;\n for (const el of document.querySelectorAll('*')) {\n const oy = getComputedStyle(el).overflowY;\n if ((oy === 'auto' || oy === 'scroll' || oy === 'overlay') &&\n el.scrollHeight > el.clientHeight + 4 && el.clientHeight > innerHeight * 0.3 &&\n el.scrollHeight > bestH) { best = el; bestH = el.scrollHeight; }\n }\n return best || document.scrollingElement || document.documentElement;\n };\n const el = pick(arg.selector);\n if (!el) return { found: false };\n el.style.overflowAnchor = 'none';\n const before = el.scrollTop;\n if (arg.to === 'end') el.scrollTop = el.scrollHeight;\n else el.scrollTop += (arg.delta || Math.floor(el.clientHeight * 0.9));\n return { found: true, scrollTop: el.scrollTop, scrollHeight: el.scrollHeight,\n clientHeight: el.clientHeight, moved: el.scrollTop - before,\n atEnd: el.scrollTop + el.clientHeight >= el.scrollHeight - 4 };\n}";
|
|
9
|
+
/** Harvest visible `item` rows, then scroll the container one step. */
|
|
10
|
+
export declare const SCAN_SCRIPT = "(arg) => {\n const pick = (sel) => {\n if (sel) return document.querySelector(sel);\n let best = null, bestH = 0;\n for (const el of document.querySelectorAll('*')) {\n const oy = getComputedStyle(el).overflowY;\n if ((oy === 'auto' || oy === 'scroll' || oy === 'overlay') &&\n el.scrollHeight > el.clientHeight + 4 && el.clientHeight > innerHeight * 0.3 &&\n el.scrollHeight > bestH) { best = el; bestH = el.scrollHeight; }\n }\n return best || document.scrollingElement || document.documentElement;\n };\n const keyOf = (el) => (el.getAttribute('data-id') || el.getAttribute('data-key') ||\n (el.querySelector('a[href]') && el.querySelector('a[href]').href) ||\n (el.textContent || '').replace(/\\s+/g,' ').trim().slice(0, 120));\n const el = pick(arg.selector);\n const rows = [...document.querySelectorAll(arg.item)].slice(0, 500).map((r) => {\n const a = r.querySelector('a[href]');\n return { key: keyOf(r), text: (r.textContent || '').replace(/\\s+/g,' ').trim().slice(0, 300), url: a ? a.href : null };\n });\n let geo = { moved: 0, atEnd: true, scrollHeight: 0, clientHeight: 0, scrollTop: 0 };\n if (el) {\n el.style.overflowAnchor = 'none';\n const before = el.scrollTop;\n el.scrollTop += Math.floor(el.clientHeight * (arg.stepFraction || 0.9));\n geo = { moved: el.scrollTop - before, scrollHeight: el.scrollHeight,\n clientHeight: el.clientHeight, scrollTop: el.scrollTop,\n atEnd: el.scrollTop + el.clientHeight >= el.scrollHeight - 4 };\n }\n return { items: rows, geo };\n}";
|
|
11
|
+
//# sourceMappingURL=scroll-script.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-script.d.ts","sourceRoot":"","sources":["../../src/extraction/scroll-script.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAsBH,sFAAsF;AACtF,eAAO,MAAM,aAAa,qhCAUxB,CAAC;AAEH,uEAAuE;AACvE,eAAO,MAAM,WAAW,8iDAgBtB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser-side scripts for virtualized/infinite lists: detect the scrollable
|
|
3
|
+
* container (explicit selector or auto by overflow + scrollHeight), scroll it,
|
|
4
|
+
* and harvest the currently-mounted rows. Runs in the page context.
|
|
5
|
+
* @module extraction/scroll-script
|
|
6
|
+
*/
|
|
7
|
+
/** Container picker: explicit `sel`, else the tallest overflow:auto/scroll element. */
|
|
8
|
+
const FINDER = `
|
|
9
|
+
const pick = (sel) => {
|
|
10
|
+
if (sel) return document.querySelector(sel);
|
|
11
|
+
let best = null, bestH = 0;
|
|
12
|
+
for (const el of document.querySelectorAll('*')) {
|
|
13
|
+
const oy = getComputedStyle(el).overflowY;
|
|
14
|
+
if ((oy === 'auto' || oy === 'scroll' || oy === 'overlay') &&
|
|
15
|
+
el.scrollHeight > el.clientHeight + 4 && el.clientHeight > innerHeight * 0.3 &&
|
|
16
|
+
el.scrollHeight > bestH) { best = el; bestH = el.scrollHeight; }
|
|
17
|
+
}
|
|
18
|
+
return best || document.scrollingElement || document.documentElement;
|
|
19
|
+
};`;
|
|
20
|
+
/** Stable key for a row: data-id › data-key › first href › trimmed text. */
|
|
21
|
+
const KEYER = `
|
|
22
|
+
const keyOf = (el) => (el.getAttribute('data-id') || el.getAttribute('data-key') ||
|
|
23
|
+
(el.querySelector('a[href]') && el.querySelector('a[href]').href) ||
|
|
24
|
+
(el.textContent || '').replace(/\\s+/g,' ').trim().slice(0, 120));`;
|
|
25
|
+
/** Scroll the (auto-detected or `selector`) container; `to:"end"` jumps to bottom. */
|
|
26
|
+
export const SCROLL_SCRIPT = `(arg) => {${FINDER}
|
|
27
|
+
const el = pick(arg.selector);
|
|
28
|
+
if (!el) return { found: false };
|
|
29
|
+
el.style.overflowAnchor = 'none';
|
|
30
|
+
const before = el.scrollTop;
|
|
31
|
+
if (arg.to === 'end') el.scrollTop = el.scrollHeight;
|
|
32
|
+
else el.scrollTop += (arg.delta || Math.floor(el.clientHeight * 0.9));
|
|
33
|
+
return { found: true, scrollTop: el.scrollTop, scrollHeight: el.scrollHeight,
|
|
34
|
+
clientHeight: el.clientHeight, moved: el.scrollTop - before,
|
|
35
|
+
atEnd: el.scrollTop + el.clientHeight >= el.scrollHeight - 4 };
|
|
36
|
+
}`;
|
|
37
|
+
/** Harvest visible `item` rows, then scroll the container one step. */
|
|
38
|
+
export const SCAN_SCRIPT = `(arg) => {${FINDER}${KEYER}
|
|
39
|
+
const el = pick(arg.selector);
|
|
40
|
+
const rows = [...document.querySelectorAll(arg.item)].slice(0, 500).map((r) => {
|
|
41
|
+
const a = r.querySelector('a[href]');
|
|
42
|
+
return { key: keyOf(r), text: (r.textContent || '').replace(/\\s+/g,' ').trim().slice(0, 300), url: a ? a.href : null };
|
|
43
|
+
});
|
|
44
|
+
let geo = { moved: 0, atEnd: true, scrollHeight: 0, clientHeight: 0, scrollTop: 0 };
|
|
45
|
+
if (el) {
|
|
46
|
+
el.style.overflowAnchor = 'none';
|
|
47
|
+
const before = el.scrollTop;
|
|
48
|
+
el.scrollTop += Math.floor(el.clientHeight * (arg.stepFraction || 0.9));
|
|
49
|
+
geo = { moved: el.scrollTop - before, scrollHeight: el.scrollHeight,
|
|
50
|
+
clientHeight: el.clientHeight, scrollTop: el.scrollTop,
|
|
51
|
+
atEnd: el.scrollTop + el.clientHeight >= el.scrollHeight - 4 };
|
|
52
|
+
}
|
|
53
|
+
return { items: rows, geo };
|
|
54
|
+
}`;
|
|
55
|
+
//# sourceMappingURL=scroll-script.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-script.js","sourceRoot":"","sources":["../../src/extraction/scroll-script.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,uFAAuF;AACvF,MAAM,MAAM,GAAG;;;;;;;;;;;KAWV,CAAC;AAEN,4EAA4E;AAC5E,MAAM,KAAK,GAAG;;;uEAGyD,CAAC;AAExE,sFAAsF;AACtF,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,MAAM;;;;;;;;;;EAU9C,CAAC;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,MAAM,GAAG,KAAK;;;;;;;;;;;;;;;;EAgBpD,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robust CSS selector generation for the action cache. A `@medv/finder`-style
|
|
3
|
+
* walk builds the shortest unique path, preferring stable hooks (data-testid ›
|
|
4
|
+
* stable id › semantic class › nth-of-type) and rejecting generated tokens
|
|
5
|
+
* (hashes, css-in-js, long digit runs). The browser-side defs are embedded into
|
|
6
|
+
* the snapshot script; `isStableToken` mirrors the filter for unit testing.
|
|
7
|
+
* @module extraction/selector
|
|
8
|
+
*/
|
|
9
|
+
/** A token (id/class) is unstable if it looks machine-generated. */
|
|
10
|
+
export declare function isStableToken(value: string | null | undefined): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Browser-side definitions injected into snapshot scripts: defines `genSelector`
|
|
13
|
+
* (returns a unique selector within the element's root, or null). Relative to
|
|
14
|
+
* the element's root node, so light-DOM selectors work with `page.locator`;
|
|
15
|
+
* shadow-scoped selectors are best-effort.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SELECTOR_DEFS = "\n const UNSTABLE = /([0-9a-f]{5,}|\\d{3,}|^(?:css|sc|jss|emotion|Mui|chakra|styled))/i;\n const stableTok = (v) => !!v && v.length >= 3 && !UNSTABLE.test(v);\n const uniqSel = (root, sel) => { try { return root.querySelectorAll(sel).length === 1; } catch (e) { return false; } };\n const genSelector = (el) => {\n const root = el.getRootNode();\n const parts = [];\n let node = el;\n while (node && node.nodeType === 1 && node !== root) {\n let seg = '';\n for (const a of ['data-testid','data-test','data-cy','data-qa']) {\n const v = node.getAttribute && node.getAttribute(a);\n if (v) { seg = '[' + a + '=\"' + v + '\"]'; break; }\n }\n if (!seg && node.id && stableTok(node.id)) seg = '#' + CSS.escape(node.id);\n if (!seg) {\n const cls = (node.classList ? [...node.classList] : []).filter(stableTok).slice(0, 2);\n const tag = node.tagName.toLowerCase();\n if (cls.length) seg = tag + cls.map((c) => '.' + CSS.escape(c)).join('');\n else {\n const sib = node.parentNode ? [...node.parentNode.children].filter((c) => c.tagName === node.tagName) : [node];\n seg = tag + (sib.length > 1 ? ':nth-of-type(' + (sib.indexOf(node) + 1) + ')' : '');\n }\n }\n parts.unshift(seg);\n const sel = parts.join(' > ');\n if (uniqSel(root, sel)) return sel;\n node = node.parentElement || (node.parentNode && node.parentNode.host);\n }\n const full = parts.join(' > ');\n return uniqSel(root, full) ? full : null;\n };";
|
|
18
|
+
//# sourceMappingURL=selector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.d.ts","sourceRoot":"","sources":["../../src/extraction/selector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oEAAoE;AACpE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAGvE;AAED;;;;;GAKG;AACH,eAAO,MAAM,aAAa,qhDA+BrB,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robust CSS selector generation for the action cache. A `@medv/finder`-style
|
|
3
|
+
* walk builds the shortest unique path, preferring stable hooks (data-testid ›
|
|
4
|
+
* stable id › semantic class › nth-of-type) and rejecting generated tokens
|
|
5
|
+
* (hashes, css-in-js, long digit runs). The browser-side defs are embedded into
|
|
6
|
+
* the snapshot script; `isStableToken` mirrors the filter for unit testing.
|
|
7
|
+
* @module extraction/selector
|
|
8
|
+
*/
|
|
9
|
+
/** A token (id/class) is unstable if it looks machine-generated. */
|
|
10
|
+
export function isStableToken(value) {
|
|
11
|
+
if (!value || value.length < 3)
|
|
12
|
+
return false;
|
|
13
|
+
return !/([0-9a-f]{5,}|\d{3,}|^(?:css|sc|jss|emotion|Mui|chakra|styled))/i.test(value);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Browser-side definitions injected into snapshot scripts: defines `genSelector`
|
|
17
|
+
* (returns a unique selector within the element's root, or null). Relative to
|
|
18
|
+
* the element's root node, so light-DOM selectors work with `page.locator`;
|
|
19
|
+
* shadow-scoped selectors are best-effort.
|
|
20
|
+
*/
|
|
21
|
+
export const SELECTOR_DEFS = `
|
|
22
|
+
const UNSTABLE = /([0-9a-f]{5,}|\\d{3,}|^(?:css|sc|jss|emotion|Mui|chakra|styled))/i;
|
|
23
|
+
const stableTok = (v) => !!v && v.length >= 3 && !UNSTABLE.test(v);
|
|
24
|
+
const uniqSel = (root, sel) => { try { return root.querySelectorAll(sel).length === 1; } catch (e) { return false; } };
|
|
25
|
+
const genSelector = (el) => {
|
|
26
|
+
const root = el.getRootNode();
|
|
27
|
+
const parts = [];
|
|
28
|
+
let node = el;
|
|
29
|
+
while (node && node.nodeType === 1 && node !== root) {
|
|
30
|
+
let seg = '';
|
|
31
|
+
for (const a of ['data-testid','data-test','data-cy','data-qa']) {
|
|
32
|
+
const v = node.getAttribute && node.getAttribute(a);
|
|
33
|
+
if (v) { seg = '[' + a + '="' + v + '"]'; break; }
|
|
34
|
+
}
|
|
35
|
+
if (!seg && node.id && stableTok(node.id)) seg = '#' + CSS.escape(node.id);
|
|
36
|
+
if (!seg) {
|
|
37
|
+
const cls = (node.classList ? [...node.classList] : []).filter(stableTok).slice(0, 2);
|
|
38
|
+
const tag = node.tagName.toLowerCase();
|
|
39
|
+
if (cls.length) seg = tag + cls.map((c) => '.' + CSS.escape(c)).join('');
|
|
40
|
+
else {
|
|
41
|
+
const sib = node.parentNode ? [...node.parentNode.children].filter((c) => c.tagName === node.tagName) : [node];
|
|
42
|
+
seg = tag + (sib.length > 1 ? ':nth-of-type(' + (sib.indexOf(node) + 1) + ')' : '');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
parts.unshift(seg);
|
|
46
|
+
const sel = parts.join(' > ');
|
|
47
|
+
if (uniqSel(root, sel)) return sel;
|
|
48
|
+
node = node.parentElement || (node.parentNode && node.parentNode.host);
|
|
49
|
+
}
|
|
50
|
+
const full = parts.join(' > ');
|
|
51
|
+
return uniqSel(root, full) ? full : null;
|
|
52
|
+
};`;
|
|
53
|
+
//# sourceMappingURL=selector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector.js","sourceRoot":"","sources":["../../src/extraction/selector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oEAAoE;AACpE,MAAM,UAAU,aAAa,CAAC,KAAgC;IAC5D,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7C,OAAO,CAAC,kEAAkE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACzF,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA+BxB,CAAC"}
|
|
@@ -14,5 +14,5 @@ export declare const REF_ATTRIBUTE = "data-fuse-ref";
|
|
|
14
14
|
* the Node side rewrites `index` to a global counter and adds the frame-scoped
|
|
15
15
|
* `ref`. Capped at 200 elements per frame.
|
|
16
16
|
*/
|
|
17
|
-
export declare const SNAPSHOT_SCRIPT = "() => {\n const SEL = 'button,a,input,select,textarea,[role=button],[role=combobox],[role=checkbox],[role=radio],[role=switch],[role=tab],[role=menuitem],[role=option],[contenteditable=true]';\n const obscured = (el, r) => {\n const cx = r.x + r.width / 2, cy = r.y + r.height / 2;\n if (r.width === 0 || cx < 0 || cy < 0 || cx > innerWidth || cy > innerHeight) return false;\n const top = document.elementFromPoint(cx, cy);\n return !!top && top !== el && !el.contains(top) && !top.contains(el);\n };\n const out = [];\n const describe = (el, index) => {\n const r = el.getBoundingClientRect();\n const val = typeof el.value === 'string' ? el.value : null;\n const isCheck = el.type === 'checkbox' || el.type === 'radio';\n return {\n index,\n tag: el.tagName.toLowerCase(),\n text: (el.innerText || el.getAttribute('aria-label') || el.getAttribute('placeholder') || '').trim().slice(0, 120),\n role: el.getAttribute('role'), id: el.id || null, name: el.getAttribute('name'),\n type: el.getAttribute('type'), href: el.getAttribute('href'),\n value: val ? val.slice(0, 120) : null, placeholder: el.getAttribute('placeholder'),\n disabled: !!el.disabled || el.getAttribute('aria-disabled') === 'true',\n checked: isCheck ? !!el.checked : undefined,\n options: el.tagName === 'SELECT' ? [...el.options].slice(0, 12).map((o) => o.label || o.value) : undefined,\n ariaExpanded: el.getAttribute('aria-expanded'), ariaControls: el.getAttribute('aria-controls'),\n visible: r.width > 0 && r.height > 0, obscured: obscured(el, r),\n box: {x: Math.round(r.x), y: Math.round(r.y), width: Math.round(r.width), height: Math.round(r.height)}\n };\n };\n const visit = (root) => {\n if (out.length >= 200) return;\n for (const el of root.querySelectorAll(SEL)) {\n if (out.length >= 200) return;\n const i = out.length;\n el.setAttribute('data-fuse-ref', String(i));\n out.push(describe(el, i));\n }\n for (const host of root.querySelectorAll('*')) {\n if (host.shadowRoot) visit(host.shadowRoot);\n }\n };\n visit(document);\n return out;\n}";
|
|
17
|
+
export declare const SNAPSHOT_SCRIPT = "(arg) => {\n const SEL = 'button,a,input,select,textarea,[role=button],[role=combobox],[role=checkbox],[role=radio],[role=switch],[role=tab],[role=menuitem],[role=option],[contenteditable=true]';\n const wantSel = !!(arg && arg.selectors);\n const UNSTABLE = /([0-9a-f]{5,}|\\d{3,}|^(?:css|sc|jss|emotion|Mui|chakra|styled))/i;\n const stableTok = (v) => !!v && v.length >= 3 && !UNSTABLE.test(v);\n const uniqSel = (root, sel) => { try { return root.querySelectorAll(sel).length === 1; } catch (e) { return false; } };\n const genSelector = (el) => {\n const root = el.getRootNode();\n const parts = [];\n let node = el;\n while (node && node.nodeType === 1 && node !== root) {\n let seg = '';\n for (const a of ['data-testid','data-test','data-cy','data-qa']) {\n const v = node.getAttribute && node.getAttribute(a);\n if (v) { seg = '[' + a + '=\"' + v + '\"]'; break; }\n }\n if (!seg && node.id && stableTok(node.id)) seg = '#' + CSS.escape(node.id);\n if (!seg) {\n const cls = (node.classList ? [...node.classList] : []).filter(stableTok).slice(0, 2);\n const tag = node.tagName.toLowerCase();\n if (cls.length) seg = tag + cls.map((c) => '.' + CSS.escape(c)).join('');\n else {\n const sib = node.parentNode ? [...node.parentNode.children].filter((c) => c.tagName === node.tagName) : [node];\n seg = tag + (sib.length > 1 ? ':nth-of-type(' + (sib.indexOf(node) + 1) + ')' : '');\n }\n }\n parts.unshift(seg);\n const sel = parts.join(' > ');\n if (uniqSel(root, sel)) return sel;\n node = node.parentElement || (node.parentNode && node.parentNode.host);\n }\n const full = parts.join(' > ');\n return uniqSel(root, full) ? full : null;\n };\n const obscured = (el, r) => {\n const cx = r.x + r.width / 2, cy = r.y + r.height / 2;\n if (r.width === 0 || cx < 0 || cy < 0 || cx > innerWidth || cy > innerHeight) return false;\n const top = document.elementFromPoint(cx, cy);\n return !!top && top !== el && !el.contains(top) && !top.contains(el);\n };\n const out = [];\n const describe = (el, index) => {\n const r = el.getBoundingClientRect();\n const val = typeof el.value === 'string' ? el.value : null;\n const isCheck = el.type === 'checkbox' || el.type === 'radio';\n return {\n index,\n tag: el.tagName.toLowerCase(),\n text: (el.innerText || el.getAttribute('aria-label') || el.getAttribute('placeholder') || '').trim().slice(0, 120),\n role: el.getAttribute('role'), id: el.id || null, name: el.getAttribute('name'),\n type: el.getAttribute('type'), href: el.getAttribute('href'),\n value: val ? val.slice(0, 120) : null, placeholder: el.getAttribute('placeholder'),\n disabled: !!el.disabled || el.getAttribute('aria-disabled') === 'true',\n checked: isCheck ? !!el.checked : undefined,\n options: el.tagName === 'SELECT' ? [...el.options].slice(0, 12).map((o) => o.label || o.value) : undefined,\n ariaExpanded: el.getAttribute('aria-expanded'), ariaControls: el.getAttribute('aria-controls'),\n visible: r.width > 0 && r.height > 0, obscured: obscured(el, r),\n selector: wantSel ? genSelector(el) : undefined,\n box: {x: Math.round(r.x), y: Math.round(r.y), width: Math.round(r.width), height: Math.round(r.height)}\n };\n };\n const visit = (root) => {\n if (out.length >= 200) return;\n for (const el of root.querySelectorAll(SEL)) {\n if (out.length >= 200) return;\n const i = out.length;\n el.setAttribute('data-fuse-ref', String(i));\n out.push(describe(el, i));\n }\n for (const host of root.querySelectorAll('*')) {\n if (host.shadowRoot) visit(host.shadowRoot);\n }\n };\n visit(document);\n return out;\n}";
|
|
18
18
|
//# sourceMappingURL=snapshot-walk.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-walk.d.ts","sourceRoot":"","sources":["../../src/extraction/snapshot-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"snapshot-walk.d.ts","sourceRoot":"","sources":["../../src/extraction/snapshot-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,6EAA6E;AAC7E,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAK7C;;;;;GAKG;AACH,eAAO,MAAM,eAAe,kvHA4C1B,CAAC"}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* Closed shadow roots are inaccessible by browser SOP and stay opaque.
|
|
7
7
|
* @module extraction/snapshot-walk
|
|
8
8
|
*/
|
|
9
|
+
import { SELECTOR_DEFS } from "./selector.js";
|
|
9
10
|
/** Attribute injected on each interactive element to anchor a stable ref. */
|
|
10
11
|
export const REF_ATTRIBUTE = "data-fuse-ref";
|
|
11
12
|
const SELECTOR = "button,a,input,select,textarea,[role=button],[role=combobox],[role=checkbox],[role=radio],[role=switch],[role=tab],[role=menuitem],[role=option],[contenteditable=true]";
|
|
@@ -15,8 +16,9 @@ const SELECTOR = "button,a,input,select,textarea,[role=button],[role=combobox],[
|
|
|
15
16
|
* the Node side rewrites `index` to a global counter and adds the frame-scoped
|
|
16
17
|
* `ref`. Capped at 200 elements per frame.
|
|
17
18
|
*/
|
|
18
|
-
export const SNAPSHOT_SCRIPT = `() => {
|
|
19
|
+
export const SNAPSHOT_SCRIPT = `(arg) => {
|
|
19
20
|
const SEL = '${SELECTOR}';
|
|
21
|
+
const wantSel = !!(arg && arg.selectors);${SELECTOR_DEFS}
|
|
20
22
|
const obscured = (el, r) => {
|
|
21
23
|
const cx = r.x + r.width / 2, cy = r.y + r.height / 2;
|
|
22
24
|
if (r.width === 0 || cx < 0 || cy < 0 || cx > innerWidth || cy > innerHeight) return false;
|
|
@@ -40,6 +42,7 @@ export const SNAPSHOT_SCRIPT = `() => {
|
|
|
40
42
|
options: el.tagName === 'SELECT' ? [...el.options].slice(0, 12).map((o) => o.label || o.value) : undefined,
|
|
41
43
|
ariaExpanded: el.getAttribute('aria-expanded'), ariaControls: el.getAttribute('aria-controls'),
|
|
42
44
|
visible: r.width > 0 && r.height > 0, obscured: obscured(el, r),
|
|
45
|
+
selector: wantSel ? genSelector(el) : undefined,
|
|
43
46
|
box: {x: Math.round(r.x), y: Math.round(r.y), width: Math.round(r.width), height: Math.round(r.height)}
|
|
44
47
|
};
|
|
45
48
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot-walk.js","sourceRoot":"","sources":["../../src/extraction/snapshot-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,6EAA6E;AAC7E,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,MAAM,QAAQ,GACZ,yKAAyK,CAAC;AAE5K;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;iBACd,QAAQ
|
|
1
|
+
{"version":3,"file":"snapshot-walk.js","sourceRoot":"","sources":["../../src/extraction/snapshot-walk.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9C,6EAA6E;AAC7E,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAC;AAE7C,MAAM,QAAQ,GACZ,yKAAyK,CAAC;AAE5K;;;;;GAKG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;iBACd,QAAQ;6CACoB,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAiCjC,aAAa;;;;;;;;;EASpC,CAAC"}
|
|
@@ -16,5 +16,5 @@ export { REF_ATTRIBUTE } from "./snapshot-walk.js";
|
|
|
16
16
|
* Detached frames and frames that reject evaluation (e.g. mid-navigation) are
|
|
17
17
|
* skipped rather than aborting the whole snapshot.
|
|
18
18
|
*/
|
|
19
|
-
export declare function captureSnapshot(page: Page): Promise<InteractiveElement[]>;
|
|
19
|
+
export declare function captureSnapshot(page: Page, selectors?: boolean): Promise<InteractiveElement[]>;
|
|
20
20
|
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/extraction/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAItE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAKnD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/extraction/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAItE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAKnD;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,UAAQ,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAuBlG"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { evalScriptArg } from "../lib/evaluate.js";
|
|
2
2
|
import { SNAPSHOT_SCRIPT } from "./snapshot-walk.js";
|
|
3
3
|
export { REF_ATTRIBUTE } from "./snapshot-walk.js";
|
|
4
4
|
/** Soft cap on total elements across all frames, to bound output size. */
|
|
@@ -9,9 +9,10 @@ const MAX_ELEMENTS = 400;
|
|
|
9
9
|
* Detached frames and frames that reject evaluation (e.g. mid-navigation) are
|
|
10
10
|
* skipped rather than aborting the whole snapshot.
|
|
11
11
|
*/
|
|
12
|
-
export async function captureSnapshot(page) {
|
|
12
|
+
export async function captureSnapshot(page, selectors = false) {
|
|
13
13
|
const frames = page.frames();
|
|
14
14
|
const all = [];
|
|
15
|
+
const arg = { selectors };
|
|
15
16
|
let global = 0;
|
|
16
17
|
for (let f = 0; f < frames.length && all.length < MAX_ELEMENTS; f++) {
|
|
17
18
|
const frame = frames[f];
|
|
@@ -19,7 +20,7 @@ export async function captureSnapshot(page) {
|
|
|
19
20
|
continue;
|
|
20
21
|
let local;
|
|
21
22
|
try {
|
|
22
|
-
local = await
|
|
23
|
+
local = await evalScriptArg(frame, SNAPSHOT_SCRIPT, arg);
|
|
23
24
|
}
|
|
24
25
|
catch {
|
|
25
26
|
continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/extraction/snapshot.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/extraction/snapshot.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,YAAY,GAAG,GAAG,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAU,EAAE,SAAS,GAAG,KAAK;IACjE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAyB,EAAE,CAAC;IACrC,MAAM,GAAG,GAAG,EAAE,SAAS,EAAE,CAAC;IAC1B,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,YAAY,EAAE,CAAC,EAAE,EAAE,CAAC;QACpE,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,EAAE;YAAE,SAAS;QAC3C,IAAI,KAA2B,CAAC;QAChC,IAAI,CAAC;YACH,KAAK,GAAG,MAAM,aAAa,CAAmC,KAAK,EAAE,eAAe,EAAE,GAAG,CAAC,CAAC;QAC7F,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;YACvB,IAAI,GAAG,CAAC,MAAM,IAAI,YAAY;gBAAE,MAAM;YACtC,EAAE,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,EAAE,CAAC;YACzD,IAAI,CAAC,GAAG,CAAC;gBAAE,EAAE,CAAC,KAAK,GAAG,CAAC,CAAC;YACxB,EAAE,CAAC,KAAK,GAAG,MAAM,EAAE,CAAC;YACpB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -62,6 +62,15 @@ export interface InteractiveElement {
|
|
|
62
62
|
ariaExpanded?: string | null;
|
|
63
63
|
ariaControls?: string | null;
|
|
64
64
|
obscured?: boolean;
|
|
65
|
+
/** Robust, reusable CSS selector (only when snapshot is asked for `selectors`). */
|
|
66
|
+
selector?: string | null;
|
|
67
|
+
}
|
|
68
|
+
/** A row harvested from a (possibly virtualized) list via scroll-collect. */
|
|
69
|
+
export interface CollectedItem {
|
|
70
|
+
key: string;
|
|
71
|
+
text: string;
|
|
72
|
+
url: string | null;
|
|
73
|
+
prices?: Price[];
|
|
65
74
|
}
|
|
66
75
|
/** Visual observation (screenshot + interactive elements). */
|
|
67
76
|
export interface Visual {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../../src/interfaces/extraction.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,yCAAyC;AACzC,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,EAAE,UAAU,GAAG,IAAI,CAAC;CAC9B;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;CACd;AAED,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"extraction.d.ts","sourceRoot":"","sources":["../../src/interfaces/extraction.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,yCAAyC;AACzC,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACtD,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,SAAS,EAAE,UAAU,GAAG,IAAI,CAAC;CAC9B;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,KAAK,EAAE,OAAO,CAAC;IACf,GAAG,EAAE,OAAO,CAAC;CACd;AAED,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,8FAA8F;IAC9F,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,EAAE,EAAE,MAAM,GAAG,IAAI,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,mFAAmF;IACnF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,6EAA6E;AAC7E,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;CAClB;AAED,8DAA8D;AAC9D,MAAM,WAAW,MAAM;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,mBAAmB,CAAC,EAAE,kBAAkB,EAAE,CAAC;CAC5C;AAED,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAiBvD,+DAA+D;AAC/D,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,SAAS,CAAC;IAClB,QAAQ,EAAE,cAAc,CAAC;CAC1B;AAED,6EAA6E;AAC7E,wBAAgB,YAAY,IAAI,WAAW,CAmB1C"}
|
package/dist/server/server.js
CHANGED
|
@@ -7,6 +7,7 @@ import { VERSION } from "../lib/version.js";
|
|
|
7
7
|
import { SessionManager } from "../session/manager.js";
|
|
8
8
|
import { registerResources } from "./resources.js";
|
|
9
9
|
import { registerActTools } from "./tools/act.js";
|
|
10
|
+
import { registerCollectTool } from "./tools/collect.js";
|
|
10
11
|
import { registerConnectTool } from "./tools/connect.js";
|
|
11
12
|
import { registerExtractTool } from "./tools/extract.js";
|
|
12
13
|
import { registerExtractSchemaTool } from "./tools/extract-schema.js";
|
|
@@ -31,6 +32,7 @@ export function createServer() {
|
|
|
31
32
|
registerNavigateTool(server, sessions);
|
|
32
33
|
registerActTools(server, sessions);
|
|
33
34
|
registerSnapshotTools(server, sessions);
|
|
35
|
+
registerCollectTool(server, sessions);
|
|
34
36
|
registerWaitTool(server, sessions);
|
|
35
37
|
registerRunTool(server, sessions);
|
|
36
38
|
registerExtractTool(server, sessions);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAQnD,6EAA6E;AAC7E,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IACtC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,yBAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAQnD,6EAA6E;AAC7E,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACzE,MAAM,QAAQ,GAAG,IAAI,cAAc,EAAE,CAAC;IACtC,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3B,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC9B,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACxC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,gBAAgB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,eAAe,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAClC,mBAAmB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACtC,yBAAyB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC5C,sBAAsB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzC,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC9B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../../src/server/tools/act.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"act.d.ts","sourceRoot":"","sources":["../../../src/server/tools/act.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA4B/D,8CAA8C;AAC9C,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAuDlF"}
|
package/dist/server/tools/act.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { performAction } from "../../actions/perform.js";
|
|
3
|
+
import { runWithMemory } from "../../state/action-memory.js";
|
|
3
4
|
import { jsonResult } from "../result.js";
|
|
4
5
|
import { withSession } from "./with-session.js";
|
|
5
6
|
function actTool(server, sessions, name, description, inputSchema, build) {
|
|
6
7
|
server.registerTool(name, { title: name, description, inputSchema }, async (args) => {
|
|
7
8
|
const a = args;
|
|
8
9
|
return withSession(sessions, String(a.sessionId), async (s) => {
|
|
9
|
-
const
|
|
10
|
+
const action = build(a);
|
|
11
|
+
const result = await runWithMemory(s.config.siteMemoryDir, s.page, action, (act) => performAction(s.page, act, s.config.humanMode));
|
|
10
12
|
return jsonResult({ result, url: s.page.url() });
|
|
11
13
|
});
|
|
12
14
|
});
|
|
@@ -23,10 +25,12 @@ export function registerActTools(server, sessions) {
|
|
|
23
25
|
target: String(a.target),
|
|
24
26
|
value: String(a.value),
|
|
25
27
|
}));
|
|
26
|
-
actTool(server, sessions, "browser_scroll", "Scroll
|
|
28
|
+
actTool(server, sessions, "browser_scroll", "Scroll by a pixel delta (positive deltaY scrolls down). Pass `selector` to scroll a specific scrollable container (auto-detected if omitted with `to`), or `to:\"end\"` to jump to its bottom.", { sessionId, deltaY: z.number().optional(), deltaX: z.number().optional(), selector: z.string().optional(), to: z.enum(["end"]).optional() }, (a) => ({
|
|
27
29
|
type: "scroll",
|
|
28
30
|
deltaY: a.deltaY ?? 600,
|
|
29
31
|
deltaX: a.deltaX ?? 0,
|
|
32
|
+
selector: a.selector,
|
|
33
|
+
to: a.to,
|
|
30
34
|
}));
|
|
31
35
|
actTool(server, sessions, "browser_press", "Press a key or shortcut (Enter, ArrowDown, Control+a...).", { sessionId, key: z.string() }, (a) => ({
|
|
32
36
|
type: "press",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"act.js","sourceRoot":"","sources":["../../../src/server/tools/act.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAoB,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE3E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKhD,SAAS,OAAO,CACd,MAAiB,EACjB,QAAwB,EACxB,IAAY,EACZ,WAAmB,EACnB,WAAkB,EAClB,KAAY;IAEZ,MAAM,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClF,MAAM,CAAC,GAAG,IAA+B,CAAC;QAC1C,OAAO,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"act.js","sourceRoot":"","sources":["../../../src/server/tools/act.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `browser_collect` tool: exhaust a (virtualized/infinite) list by scrolling its
|
|
3
|
+
* container and merging the rows it mounts along the way — perception beyond the
|
|
4
|
+
* fold. Returns DATA (key/text/url/prices), not actionable refs: virtualization
|
|
5
|
+
* recycles nodes, so to act on a found row use browser_act by `target` text or
|
|
6
|
+
* browser_scroll(selector) then browser_snapshot.
|
|
7
|
+
* @module server/tools/collect
|
|
8
|
+
*/
|
|
9
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import type { SessionManager } from "../../session/manager.js";
|
|
11
|
+
/** Register `browser_collect`. */
|
|
12
|
+
export declare function registerCollectTool(server: McpServer, sessions: SessionManager): void;
|
|
13
|
+
//# sourceMappingURL=collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.d.ts","sourceRoot":"","sources":["../../../src/server/tools/collect.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAGzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAI/D,kCAAkC;AAClC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAiCrF"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { scrollCollect } from "../../state/scroll-collect.js";
|
|
3
|
+
import { jsonResult } from "../result.js";
|
|
4
|
+
import { withSession } from "./with-session.js";
|
|
5
|
+
/** Register `browser_collect`. */
|
|
6
|
+
export function registerCollectTool(server, sessions) {
|
|
7
|
+
server.registerTool("browser_collect", {
|
|
8
|
+
title: "Collect a scrolling list",
|
|
9
|
+
description: "Scroll a (virtualized/infinite) list and return the deduped union of its rows — items beyond the first screen that a single snapshot misses (hotel/flight results, feeds). `item` is the CSS selector for one row; `container` is optional (auto-detected otherwise). Returns data (key/text/url, optional prices), NOT refs — to act on a row, use browser_act by text.",
|
|
10
|
+
inputSchema: {
|
|
11
|
+
sessionId: z.string(),
|
|
12
|
+
item: z.string(),
|
|
13
|
+
container: z.string().optional(),
|
|
14
|
+
maxSteps: z.number().int().optional(),
|
|
15
|
+
extractPrices: z.boolean().optional(),
|
|
16
|
+
},
|
|
17
|
+
}, async (args) => {
|
|
18
|
+
const a = args;
|
|
19
|
+
return withSession(sessions, String(a.sessionId), async (s) => {
|
|
20
|
+
const result = await scrollCollect(s.page, {
|
|
21
|
+
item: String(a.item),
|
|
22
|
+
container: typeof a.container === "string" ? a.container : undefined,
|
|
23
|
+
maxSteps: typeof a.maxSteps === "number" ? a.maxSteps : undefined,
|
|
24
|
+
extractPrices: a.extractPrices === true,
|
|
25
|
+
});
|
|
26
|
+
return jsonResult({
|
|
27
|
+
count: result.items.length,
|
|
28
|
+
steps: result.steps,
|
|
29
|
+
reachedEnd: result.reachedEnd,
|
|
30
|
+
items: result.items,
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../../../src/server/tools/collect.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,kCAAkC;AAClC,MAAM,UAAU,mBAAmB,CAAC,MAAiB,EAAE,QAAwB;IAC7E,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;QACE,KAAK,EAAE,0BAA0B;QACjC,WAAW,EACT,0WAA0W;QAC5W,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;YACrB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAChC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;YACrC,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;SACtC;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,CAAC,GAAG,IAA+B,CAAC;QAC1C,OAAO,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE;gBACzC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;gBACpB,SAAS,EAAE,OAAO,CAAC,CAAC,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS;gBACpE,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS;gBACjE,aAAa,EAAE,CAAC,CAAC,aAAa,KAAK,IAAI;aACxC,CAAC,CAAC;YACH,OAAO,UAAU,CAAC;gBAChB,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM;gBAC1B,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,UAAU,EAAE,MAAM,CAAC,UAAU;gBAC7B,KAAK,EAAE,MAAM,CAAC,KAAK;aACpB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../src/server/tools/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../../src/server/tools/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAUzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AA2B/D,qDAAqD;AACrD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CA8CvF"}
|
|
@@ -5,11 +5,12 @@ import { smartClick } from "../../actions/smart-click.js";
|
|
|
5
5
|
import { smartFill } from "../../actions/smart-fill.js";
|
|
6
6
|
import { captureSnapshot } from "../../extraction/snapshot.js";
|
|
7
7
|
import { diffSnapshots } from "../../extraction/snapshot-diff.js";
|
|
8
|
+
import { runWithMemory } from "../../state/action-memory.js";
|
|
8
9
|
import { errorResult, jsonResult } from "../result.js";
|
|
9
10
|
import { withSession } from "./with-session.js";
|
|
10
11
|
const KIND = z.enum(["click", "fill", "select", "pick"]);
|
|
11
|
-
/** Run the chosen action (by ref or text fallback). */
|
|
12
|
-
async function runAct(page, a, human) {
|
|
12
|
+
/** Run the chosen action (by ref or text fallback), with site-memory assist. */
|
|
13
|
+
async function runAct(page, a, human, dir) {
|
|
13
14
|
const kind = a.kind;
|
|
14
15
|
const value = a.value ? String(a.value) : "";
|
|
15
16
|
const option = a.option ? String(a.option) : "";
|
|
@@ -17,22 +18,24 @@ async function runAct(page, a, human) {
|
|
|
17
18
|
return actByRef(page, a.ref, kind, value, option);
|
|
18
19
|
if (typeof a.target !== "string")
|
|
19
20
|
return null;
|
|
21
|
+
const target = a.target;
|
|
20
22
|
if (kind === "pick")
|
|
21
|
-
return pickAutocomplete(page, page.locator(
|
|
22
|
-
return kind
|
|
23
|
-
|
|
24
|
-
: smartClick(page,
|
|
23
|
+
return pickAutocomplete(page, page.locator(target).first(), value, option);
|
|
24
|
+
return runWithMemory(dir, page, { type: kind, target }, (act) => {
|
|
25
|
+
const pref = String(act.preferredStrategy ?? "");
|
|
26
|
+
return kind === "fill" ? smartFill(page, target, value, pref, human) : smartClick(page, target, pref, human);
|
|
27
|
+
});
|
|
25
28
|
}
|
|
26
29
|
/** Register `browser_snapshot` and `browser_act`. */
|
|
27
30
|
export function registerSnapshotTools(server, sessions) {
|
|
28
31
|
server.registerTool("browser_snapshot", {
|
|
29
32
|
title: "Snapshot",
|
|
30
|
-
description: "Return the indexed interactive elements of the live page, including those inside open Shadow DOM and iframes (same- and cross-origin). Use each element's `ref` (e.g. \"12\" or \"3:4\" for a sub-frame) with browser_act for deterministic targeting.",
|
|
31
|
-
inputSchema: { sessionId: z.string() },
|
|
33
|
+
description: "Return the indexed interactive elements of the live page, including those inside open Shadow DOM and iframes (same- and cross-origin). Use each element's `ref` (e.g. \"12\" or \"3:4\" for a sub-frame) with browser_act for deterministic targeting. Pass `selectors:true` to also get a durable CSS `selector` per element (cacheable to act later without re-snapshotting).",
|
|
34
|
+
inputSchema: { sessionId: z.string(), selectors: z.boolean().optional() },
|
|
32
35
|
}, async (args) => {
|
|
33
36
|
const a = args;
|
|
34
37
|
return withSession(sessions, String(a.sessionId), async (s) => {
|
|
35
|
-
const elements = await captureSnapshot(s.page);
|
|
38
|
+
const elements = await captureSnapshot(s.page, a.selectors === true);
|
|
36
39
|
return jsonResult({ url: s.page.url(), count: elements.length, elements });
|
|
37
40
|
});
|
|
38
41
|
});
|
|
@@ -52,7 +55,7 @@ export function registerSnapshotTools(server, sessions) {
|
|
|
52
55
|
return withSession(sessions, String(a.sessionId), async (s) => {
|
|
53
56
|
const before = await captureSnapshot(s.page);
|
|
54
57
|
const urlBefore = s.page.url();
|
|
55
|
-
const result = await runAct(s.page, a, s.config.humanMode);
|
|
58
|
+
const result = await runAct(s.page, a, s.config.humanMode, s.config.siteMemoryDir);
|
|
56
59
|
if (!result)
|
|
57
60
|
return errorResult("browser_act requires either `ref` or `target`");
|
|
58
61
|
const after = await captureSnapshot(s.page);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/server/tools/snapshot.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAsB,MAAM,6BAA6B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAEzD,
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../../src/server/tools/snapshot.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAsB,MAAM,6BAA6B,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AAEzD,gFAAgF;AAChF,KAAK,UAAU,MAAM,CACnB,IAAU,EACV,CAA0B,EAC1B,KAAc,EACd,GAAW;IAEX,MAAM,IAAI,GAAG,CAAC,CAAC,IAAqB,CAAC;IACrC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7C,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChD,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9G,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC9C,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;IACxB,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAChG,OAAO,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,EAAE,EAAE;QAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;QACjD,OAAO,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/G,CAAC,CAAC,CAAC;AACL,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,qBAAqB,CAAC,MAAiB,EAAE,QAAwB;IAC/E,MAAM,CAAC,YAAY,CACjB,kBAAkB,EAClB;QACE,KAAK,EAAE,UAAU;QACjB,WAAW,EACT,iXAAiX;QACnX,WAAW,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,EAAE;KAC1E,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,CAAC,GAAG,IAA+B,CAAC;QAC1C,OAAO,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC5D,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC;YACrE,OAAO,UAAU,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC7E,CAAC,CAAC,CAAC;IACL,CAAC,CACF,CAAC;IAEF,MAAM,CAAC,YAAY,CACjB,aAAa,EACb;QACE,KAAK,EAAE,gBAAgB;QACvB,WAAW,EACT,oRAAoR;QACtR,WAAW,EAAE;YACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;YACrB,IAAI,EAAE,IAAI;YACV,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;YACvD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC7B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;YAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;SAC9B;KACF,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;QACb,MAAM,CAAC,GAAG,IAA+B,CAAC;QAC1C,OAAO,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;YAC5D,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC7C,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YAC/B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;YACnF,IAAI,CAAC,MAAM;gBAAE,OAAO,WAAW,CAAC,+CAA+C,CAAC,CAAC;YACjF,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YAC5C,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,SAAS,CAAC,CAAC;YACtE,OAAO,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wire per-site memory into a single action execution: inject the remembered
|
|
3
|
+
* winning `preferredStrategy` before running, and persist the winner on success.
|
|
4
|
+
* No-op for non click/fill actions (they have no memory key). Used by the MCP
|
|
5
|
+
* act tools, which otherwise would never consult site memory.
|
|
6
|
+
* @module state/action-memory
|
|
7
|
+
*/
|
|
8
|
+
import type { Page } from "playwright";
|
|
9
|
+
import type { ActionResult } from "../interfaces/types.js";
|
|
10
|
+
/** A loose action with the fields site memory keys on. */
|
|
11
|
+
type Action = Record<string, unknown> & {
|
|
12
|
+
type: string;
|
|
13
|
+
target?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Run `exec(action)` with site-memory assist: prefill `preferredStrategy` from
|
|
17
|
+
* memory, then persist the winning strategy if the action succeeds. When `dir`
|
|
18
|
+
* is empty memory is skipped.
|
|
19
|
+
*/
|
|
20
|
+
export declare function runWithMemory(dir: string, page: Page, action: Action, exec: (a: Action) => Promise<ActionResult>): Promise<ActionResult>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=action-memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-memory.d.ts","sourceRoot":"","sources":["../../src/state/action-memory.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAG3D,0DAA0D;AAC1D,KAAK,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1E;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,GACzC,OAAO,CAAC,YAAY,CAAC,CAQvB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { loadSiteMemory, rememberActionStrategy, rememberedAction } from "./site-memory.js";
|
|
2
|
+
/**
|
|
3
|
+
* Run `exec(action)` with site-memory assist: prefill `preferredStrategy` from
|
|
4
|
+
* memory, then persist the winning strategy if the action succeeds. When `dir`
|
|
5
|
+
* is empty memory is skipped.
|
|
6
|
+
*/
|
|
7
|
+
export async function runWithMemory(dir, page, action, exec) {
|
|
8
|
+
if (!dir)
|
|
9
|
+
return exec(action);
|
|
10
|
+
const url = page.url();
|
|
11
|
+
const remembered = rememberedAction(loadSiteMemory(dir, url), action);
|
|
12
|
+
if (remembered && !action.preferredStrategy)
|
|
13
|
+
action.preferredStrategy = remembered.strategy;
|
|
14
|
+
const result = await exec(action);
|
|
15
|
+
if (result.ok)
|
|
16
|
+
rememberActionStrategy(dir, url, action, result);
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=action-memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-memory.js","sourceRoot":"","sources":["../../src/state/action-memory.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAK5F;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAW,EACX,IAAU,EACV,MAAc,EACd,IAA0C;IAE1C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,UAAU,GAAG,gBAAgB,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IACtE,IAAI,UAAU,IAAI,CAAC,MAAM,CAAC,iBAAiB;QAAE,MAAM,CAAC,iBAAiB,GAAG,UAAU,CAAC,QAAQ,CAAC;IAC5F,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;IAClC,IAAI,MAAM,CAAC,EAAE;QAAE,sBAAsB,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAChE,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scroll-and-collect loop for virtualized/infinite lists: repeatedly harvest the
|
|
3
|
+
* mounted rows, scroll the container one step, settle, and merge until the
|
|
4
|
+
* container stops growing (K stable rounds) or refuses to advance (true bottom)
|
|
5
|
+
* or a step cap is hit. Returns the deduped union of rows seen.
|
|
6
|
+
* @module state/scroll-collect
|
|
7
|
+
*/
|
|
8
|
+
import type { Page } from "playwright";
|
|
9
|
+
import type { CollectedItem } from "../interfaces/extraction.js";
|
|
10
|
+
/** Options for {@link scrollCollect}. */
|
|
11
|
+
export interface CollectOptions {
|
|
12
|
+
item: string;
|
|
13
|
+
container?: string;
|
|
14
|
+
maxSteps?: number;
|
|
15
|
+
stableRounds?: number;
|
|
16
|
+
extractPrices?: boolean;
|
|
17
|
+
settleMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/** Result of a scroll-collect run. */
|
|
20
|
+
export interface CollectResult {
|
|
21
|
+
items: CollectedItem[];
|
|
22
|
+
steps: number;
|
|
23
|
+
reachedEnd: boolean;
|
|
24
|
+
}
|
|
25
|
+
/** Exhaust a (virtualized) list by scrolling its container and merging rows. */
|
|
26
|
+
export declare function scrollCollect(page: Page, opts: CollectOptions): Promise<CollectResult>;
|
|
27
|
+
//# sourceMappingURL=scroll-collect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-collect.d.ts","sourceRoot":"","sources":["../../src/state/scroll-collect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAIvC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGjE,yCAAyC;AACzC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,sCAAsC;AACtC,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,OAAO,CAAC;CACrB;AAOD,gFAAgF;AAChF,wBAAsB,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CA2B5F"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { mergeItems } from "../extraction/collect-merge.js";
|
|
2
|
+
import { extractPrices } from "../extraction/prices.js";
|
|
3
|
+
import { SCAN_SCRIPT } from "../extraction/scroll-script.js";
|
|
4
|
+
import { evalScriptArg } from "../lib/evaluate.js";
|
|
5
|
+
/** Exhaust a (virtualized) list by scrolling its container and merging rows. */
|
|
6
|
+
export async function scrollCollect(page, opts) {
|
|
7
|
+
const maxSteps = opts.maxSteps ?? 60;
|
|
8
|
+
const need = opts.stableRounds ?? 3;
|
|
9
|
+
const arg = { item: opts.item, selector: opts.container ?? null, stepFraction: 0.9 };
|
|
10
|
+
const pricer = opts.extractPrices ? extractPrices : null;
|
|
11
|
+
const seen = new Map();
|
|
12
|
+
let stable = 0;
|
|
13
|
+
let prevH = 0;
|
|
14
|
+
let reachedEnd = false;
|
|
15
|
+
let steps = 0;
|
|
16
|
+
for (; steps < maxSteps; steps++) {
|
|
17
|
+
const out = await evalScriptArg(page, SCAN_SCRIPT, arg);
|
|
18
|
+
const added = mergeItems(seen, out.items, pricer);
|
|
19
|
+
const grew = out.geo.scrollHeight > prevH + 4;
|
|
20
|
+
prevH = out.geo.scrollHeight;
|
|
21
|
+
if (out.geo.moved < 2) {
|
|
22
|
+
reachedEnd = true;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
stable = !grew && added === 0 ? stable + 1 : 0;
|
|
26
|
+
if (stable >= need) {
|
|
27
|
+
reachedEnd = out.geo.atEnd;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
await page.waitForTimeout(opts.settleMs ?? 150);
|
|
31
|
+
}
|
|
32
|
+
return { items: [...seen.values()], steps: steps + 1, reachedEnd };
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=scroll-collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scroll-collect.js","sourceRoot":"","sources":["../../src/state/scroll-collect.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,UAAU,EAAe,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAE7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAwBnD,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAU,EAAE,IAAoB;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC;IACpC,MAAM,GAAG,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,SAAS,IAAI,IAAI,EAAE,YAAY,EAAE,GAAG,EAAE,CAAC;IACrF,MAAM,MAAM,GAAG,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC;IACzD,MAAM,IAAI,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC9C,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,UAAU,GAAG,KAAK,CAAC;IACvB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,OAAO,KAAK,GAAG,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,MAAM,aAAa,CAAsB,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;QAC7E,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,YAAY,GAAG,KAAK,GAAG,CAAC,CAAC;QAC9C,KAAK,GAAG,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;QAC7B,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC;YACtB,UAAU,GAAG,IAAI,CAAC;YAClB,MAAM;QACR,CAAC;QACD,MAAM,GAAG,CAAC,IAAI,IAAI,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,IAAI,MAAM,IAAI,IAAI,EAAE,CAAC;YACnB,UAAU,GAAG,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;YAC3B,MAAM;QACR,CAAC;QACD,MAAM,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,UAAU,EAAE,CAAC;AACrE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fusengine/browser-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.17",
|
|
4
4
|
"description": "MCP server + CLI giving AI agents a real, stealth browser (Patchright/Playwright) — per-country identity, self-healing actions, snapshots, multi-step plans, structured extraction, CDP attach.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Fusengine",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build": "tsc -p tsconfig.json",
|
|
55
55
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
56
56
|
"test": "bun test tests/unit",
|
|
57
|
-
"test:integration": "node --test --import tsx tests/integration/mcp.test.ts tests/integration/probe.test.ts tests/integration/snapshot.test.ts tests/integration/snapshot-frames.test.ts tests/integration/run.test.ts tests/integration/extract-schema.test.ts",
|
|
57
|
+
"test:integration": "node --test --import tsx tests/integration/mcp.test.ts tests/integration/probe.test.ts tests/integration/snapshot.test.ts tests/integration/snapshot-frames.test.ts tests/integration/collect.test.ts tests/integration/selectors.test.ts tests/integration/run.test.ts tests/integration/extract-schema.test.ts",
|
|
58
58
|
"browsers": "patchright install chromium",
|
|
59
59
|
"mcp": "node --import tsx src/bin/mcp.ts",
|
|
60
60
|
"cli": "node --import tsx src/bin/cli.ts"
|