crawlio-browser 1.5.2 → 1.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp-server/{chunk-BKBNDFXW.js → chunk-MRVXVQXV.js} +1 -1
- package/dist/mcp-server/index.js +34 -6
- package/dist/mcp-server/{init-5MP7GP7G.js → init-NSSER4A2.js} +2 -2
- package/package.json +1 -1
- package/skills/browser-automation/SKILL.md +77 -6
- package/skills/browser-automation/reference.md +3 -3
package/dist/mcp-server/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
WS_PORT_MAX,
|
|
10
10
|
WS_RECONNECT_GRACE,
|
|
11
11
|
WS_STALE_THRESHOLD
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-MRVXVQXV.js";
|
|
13
13
|
|
|
14
14
|
// src/mcp-server/index.ts
|
|
15
15
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
@@ -3709,17 +3709,37 @@ function createTools(bridge2, crawlio2) {
|
|
|
3709
3709
|
}
|
|
3710
3710
|
];
|
|
3711
3711
|
}
|
|
3712
|
+
function parseSnapshotRef(selector) {
|
|
3713
|
+
const m = /^\[ref=([a-zA-Z0-9]+)\]$/.exec(selector.trim());
|
|
3714
|
+
return m ? m[1] : null;
|
|
3715
|
+
}
|
|
3712
3716
|
async function buildSmartObject(bridge2) {
|
|
3713
|
-
const evaluate = (expression) =>
|
|
3717
|
+
const evaluate = (expression) => {
|
|
3718
|
+
const hasReturn = /(?:^|[;\n{])\s*return\s/m.test(expression);
|
|
3719
|
+
const expr = hasReturn ? `(async () => { ${expression} })()` : expression;
|
|
3720
|
+
return bridge2.send({ type: "browser_evaluate", expression: expr }, 5e3);
|
|
3721
|
+
};
|
|
3714
3722
|
const smart = {
|
|
3715
3723
|
evaluate,
|
|
3716
3724
|
click: async (selector, opts) => {
|
|
3725
|
+
const ref = parseSnapshotRef(selector);
|
|
3726
|
+
if (ref) {
|
|
3727
|
+
const result2 = await bridge2.send({ type: "browser_click", ref, button: "left", modifiers: {} }, 1e4);
|
|
3728
|
+
await new Promise((r) => setTimeout(r, opts?.settle ?? 500));
|
|
3729
|
+
return result2;
|
|
3730
|
+
}
|
|
3717
3731
|
await pollActionability(bridge2, selector);
|
|
3718
3732
|
const result = await bridge2.send({ type: "browser_click", selector, button: "left", modifiers: {} }, 1e4);
|
|
3719
3733
|
await new Promise((r) => setTimeout(r, opts?.settle ?? 500));
|
|
3720
3734
|
return result;
|
|
3721
3735
|
},
|
|
3722
3736
|
type: async (selector, text, opts) => {
|
|
3737
|
+
const ref = parseSnapshotRef(selector);
|
|
3738
|
+
if (ref) {
|
|
3739
|
+
const result2 = await bridge2.send({ type: "browser_type", ref, text, clearFirst: opts?.clearFirst ?? false, modifiers: {} }, 1e4);
|
|
3740
|
+
await new Promise((r) => setTimeout(r, opts?.settle ?? 300));
|
|
3741
|
+
return result2;
|
|
3742
|
+
}
|
|
3723
3743
|
await pollActionability(bridge2, selector);
|
|
3724
3744
|
const result = await bridge2.send({ type: "browser_type", selector, text, clearFirst: opts?.clearFirst ?? false, modifiers: {} }, 1e4);
|
|
3725
3745
|
await new Promise((r) => setTimeout(r, opts?.settle ?? 300));
|
|
@@ -3732,6 +3752,11 @@ async function buildSmartObject(bridge2) {
|
|
|
3732
3752
|
return result;
|
|
3733
3753
|
},
|
|
3734
3754
|
waitFor: async (selector, timeout) => {
|
|
3755
|
+
const ref = parseSnapshotRef(selector);
|
|
3756
|
+
if (ref) {
|
|
3757
|
+
await bridge2.send({ type: "browser_hover", ref }, timeout ?? 5e3);
|
|
3758
|
+
return { found: true, selector };
|
|
3759
|
+
}
|
|
3735
3760
|
await pollActionability(bridge2, selector, timeout ?? 5e3);
|
|
3736
3761
|
return { found: true, selector };
|
|
3737
3762
|
},
|
|
@@ -4002,8 +4027,8 @@ function createCodeModeTools(bridge2, crawlio2) {
|
|
|
4002
4027
|
"- compileRecording(session, { name, description? }) \u2014 compile RecordingSession to SKILL.md",
|
|
4003
4028
|
" Returns { skillMarkdown, name, pageCount, interactionCount }",
|
|
4004
4029
|
"- smart \u2014 auto-waiting wrappers and framework-specific data accessors:",
|
|
4005
|
-
" smart.evaluate(expr) \u2014
|
|
4006
|
-
" smart.click(selector, opts?) \u2014 poll + click + 500ms settle",
|
|
4030
|
+
" smart.evaluate(expr) \u2192 {result, type} \u2014 access .result for value. Never JSON.parse() the return directly.",
|
|
4031
|
+
" smart.click(selector, opts?) \u2014 poll + click + 500ms settle (accepts CSS or snapshot [ref=X])",
|
|
4007
4032
|
" smart.type(selector, text, opts?) \u2014 poll + type + 300ms settle",
|
|
4008
4033
|
" smart.navigate(url, opts?) \u2014 navigate + 1000ms settle",
|
|
4009
4034
|
" smart.waitFor(selector, timeout?) \u2014 poll until actionable",
|
|
@@ -4045,7 +4070,10 @@ function createCodeModeTools(bridge2, crawlio2) {
|
|
|
4045
4070
|
" // ... interact with page ...",
|
|
4046
4071
|
" const session = await bridge.send({ type: 'stop_recording' });",
|
|
4047
4072
|
" const skill = compileRecording(session, { name: 'my-flow' });",
|
|
4048
|
-
" return skill;"
|
|
4073
|
+
" return skill;",
|
|
4074
|
+
"",
|
|
4075
|
+
"IMPORTANT: Keep scripts fast (<15s). Each smart.click costs ~1-2s. Never loop 5+ clicks \u2014 use smart.evaluate to read DOM data in bulk instead.",
|
|
4076
|
+
"IMPORTANT: smart.evaluate returns {result, type}. Access .result for the value. Never JSON.stringify inside evaluate then JSON.parse outside \u2014 just return objects directly."
|
|
4049
4077
|
].join("\n"),
|
|
4050
4078
|
inputSchema: {
|
|
4051
4079
|
type: "object",
|
|
@@ -4112,7 +4140,7 @@ function createCodeModeTools(bridge2, crawlio2) {
|
|
|
4112
4140
|
process.title = "Crawlio Agent";
|
|
4113
4141
|
var initMode = process.argv.includes("init") || process.argv.includes("--setup") || process.argv.includes("setup");
|
|
4114
4142
|
if (initMode) {
|
|
4115
|
-
const { runInit } = await import("./init-
|
|
4143
|
+
const { runInit } = await import("./init-NSSER4A2.js");
|
|
4116
4144
|
await runInit(process.argv.slice(2));
|
|
4117
4145
|
process.exit(0);
|
|
4118
4146
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PKG_VERSION
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-MRVXVQXV.js";
|
|
4
4
|
|
|
5
5
|
// src/mcp-server/init.ts
|
|
6
6
|
import { execFileSync, spawn } from "child_process";
|
|
@@ -311,7 +311,7 @@ function isAlreadyConfigured(config) {
|
|
|
311
311
|
function buildCloudflareEntry(accountId, apiToken) {
|
|
312
312
|
return {
|
|
313
313
|
command: "npx",
|
|
314
|
-
args: ["-y", "@cloudflare/mcp-server-cloudflare
|
|
314
|
+
args: ["-y", "@cloudflare/mcp-server-cloudflare", "run", accountId],
|
|
315
315
|
env: { CLOUDFLARE_API_TOKEN: apiToken }
|
|
316
316
|
};
|
|
317
317
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crawlio-browser",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "MCP server with 96 CDP-backed tools for browser automation — screenshots, DOM, network capture, framework detection, cookies, storage, session recording, performance metrics via Chrome",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/mcp-server/index.js",
|
|
@@ -42,6 +42,8 @@ return await bridge.send({ type: "get_connection_status" })
|
|
|
42
42
|
2. **Return values are objects**, not primitives. Always destructure or access properties (see Return Value Shapes below).
|
|
43
43
|
3. **`close_tab` requires `tabId`** — it will error without one. Get tabId from `list_tabs` or `connect_tab`.
|
|
44
44
|
4. **`connect_tab` before any interaction** — most commands require an active tab connection.
|
|
45
|
+
5. **`smart.evaluate` returns `{ result, type }`** — NOT the raw value. Access `.result` to get the value. Never `JSON.parse()` the return directly.
|
|
46
|
+
6. **Keep scripts fast** — each `execute` call should complete in <15s. Split loops over many elements into separate `execute` calls. Never loop 5+ `smart.click` calls in one script.
|
|
45
47
|
|
|
46
48
|
## Core Patterns via `execute`
|
|
47
49
|
|
|
@@ -112,12 +114,47 @@ return await smart.snapshot()
|
|
|
112
114
|
return await smart.snapshot()
|
|
113
115
|
```
|
|
114
116
|
|
|
115
|
-
Returns `{ snapshot: string }` — the a11y tree text. Use this to discover available elements when selectors fail.
|
|
117
|
+
Returns `{ snapshot: string }` — the a11y tree text with `[ref=X]` labels on interactive elements. Use this to discover available elements when selectors fail.
|
|
118
|
+
|
|
119
|
+
Snapshot refs like `[ref=e3]` work directly with `smart.click`, `smart.type`, and `smart.waitFor`:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
const snap = await smart.snapshot()
|
|
123
|
+
// snap.snapshot contains: [ref=e3] button "Platform" ...
|
|
124
|
+
await smart.click('[ref=e3]') // clicks via the snapshot ref system (not CSS)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
These refs are resolved from the cached snapshot — they are NOT CSS selectors. They bypass `document.querySelector` and use CDP node resolution instead.
|
|
116
128
|
|
|
117
129
|
### Evaluate JavaScript
|
|
118
130
|
|
|
119
131
|
```js
|
|
120
|
-
|
|
132
|
+
const res = await smart.evaluate("document.title")
|
|
133
|
+
return res.result // "My Page Title" — always access .result!
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**`smart.evaluate` returns `{ result: <value>, type: <string> }`, NOT the raw value.** You must access `.result` to get the actual value.
|
|
137
|
+
|
|
138
|
+
For multi-statement evaluation, just use `return` — it auto-wraps in an IIFE:
|
|
139
|
+
|
|
140
|
+
```js
|
|
141
|
+
const res = await smart.evaluate(`
|
|
142
|
+
const links = Array.from(document.querySelectorAll('a[href]'));
|
|
143
|
+
return links.map(a => ({ text: a.textContent.trim(), href: a.href }));
|
|
144
|
+
`)
|
|
145
|
+
return res.result // [{text: "Home", href: "..."}, ...]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**WRONG — do NOT `JSON.stringify` inside evaluate then `JSON.parse` outside:**
|
|
149
|
+
|
|
150
|
+
```js
|
|
151
|
+
// BAD — will cause "[object Object]" is not valid JSON
|
|
152
|
+
const res = await smart.evaluate(`return JSON.stringify(data)`)
|
|
153
|
+
return JSON.parse(res) // WRONG: res is {result: "...", type: "string"}, not a string
|
|
154
|
+
|
|
155
|
+
// GOOD — return objects directly, CDP serializes them for you
|
|
156
|
+
const res = await smart.evaluate(`return data`)
|
|
157
|
+
return res.result // the actual JS object/array
|
|
121
158
|
```
|
|
122
159
|
|
|
123
160
|
### Framework Detection
|
|
@@ -305,9 +342,11 @@ const tree = snap.snapshot; // the a11y tree text
|
|
|
305
342
|
|
|
306
343
|
// smart.evaluate(expr) → { result: any, type: string }
|
|
307
344
|
const res = await smart.evaluate("document.title");
|
|
308
|
-
const title = res.result; // the actual value
|
|
345
|
+
const title = res.result; // the actual value — "My Page"
|
|
309
346
|
const type = res.type; // "string", "number", "object", etc.
|
|
310
|
-
// WRONG: res.substring()
|
|
347
|
+
// WRONG: res.substring() — res is {result, type}, not the raw value
|
|
348
|
+
// WRONG: JSON.parse(res) — will get "[object Object]" is not valid JSON
|
|
349
|
+
// WRONG: JSON.stringify inside + JSON.parse outside — just return objects directly
|
|
311
350
|
```
|
|
312
351
|
|
|
313
352
|
### bridge.send commands
|
|
@@ -357,9 +396,41 @@ const entries = result.entries; // array of network entries
|
|
|
357
396
|
| "No tab connected" | Call `connect_tab` first |
|
|
358
397
|
| "Element not found" | Use `smart.snapshot()` to see available elements, then adjust selector |
|
|
359
398
|
| "Extension disconnected" | Check that the Chrome extension is installed and the popup shows "Connected" |
|
|
360
|
-
|
|
|
399
|
+
| "timed out after 30000ms" | Script too slow — reduce interactions per call, use evaluate instead of click loops |
|
|
400
|
+
| "[object Object]" not valid JSON | You called `JSON.parse` on a `{result, type}` object — use `.result` first |
|
|
361
401
|
| "Permission required" | Click the Crawlio extension icon and grant permissions |
|
|
362
402
|
|
|
403
|
+
## Script Performance Rules
|
|
404
|
+
|
|
405
|
+
Each `execute` call has a 120s internal timeout, but **MCP clients may impose shorter timeouts** (30s is common). Keep scripts fast:
|
|
406
|
+
|
|
407
|
+
- **Max 3-4 interactions per script** — each `smart.click`/`smart.type` costs ~1-2s (actionability polling + settle time)
|
|
408
|
+
- **Never loop 5+ clicks** — split into multiple `execute` calls instead
|
|
409
|
+
- **Minimize `sleep()` calls** — use `smart.waitFor(selector)` instead of `sleep(2000)`
|
|
410
|
+
- **Avoid `JSON.stringify` in evaluate** — return objects directly, CDP serializes automatically
|
|
411
|
+
|
|
412
|
+
### BAD — will timeout (8 clicks × ~1.5s + sleeps = ~16s minimum)
|
|
413
|
+
|
|
414
|
+
```js
|
|
415
|
+
for (let i = 18; i <= 25; i++) {
|
|
416
|
+
await smart.click(`[ref=e${i}]`)
|
|
417
|
+
await sleep(300)
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### GOOD — one evaluate call reads all the data at once
|
|
422
|
+
|
|
423
|
+
```js
|
|
424
|
+
const res = await smart.evaluate(`
|
|
425
|
+
const items = document.querySelectorAll('.faq-item');
|
|
426
|
+
return Array.from(items).map(el => ({
|
|
427
|
+
question: el.querySelector('h3')?.textContent?.trim(),
|
|
428
|
+
answer: el.querySelector('.answer')?.textContent?.trim()
|
|
429
|
+
}));
|
|
430
|
+
`)
|
|
431
|
+
return res.result
|
|
432
|
+
```
|
|
433
|
+
|
|
363
434
|
## Multi-Step Workflow Example
|
|
364
435
|
|
|
365
436
|
```js
|
|
@@ -372,7 +443,7 @@ await smart.type("#password", "secret123")
|
|
|
372
443
|
|
|
373
444
|
// 3. Click login
|
|
374
445
|
await smart.click("button[type='submit']")
|
|
375
|
-
await
|
|
446
|
+
await smart.waitFor(".dashboard") // prefer waitFor over sleep
|
|
376
447
|
|
|
377
448
|
// 4. Verify navigation
|
|
378
449
|
const title = (await smart.evaluate("document.title")).result
|
|
@@ -232,9 +232,9 @@ The `smart` object provides auto-waiting wrappers and framework-specific data:
|
|
|
232
232
|
|
|
233
233
|
| Method | Description |
|
|
234
234
|
|--------|-------------|
|
|
235
|
-
| `smart.evaluate(expr)` |
|
|
236
|
-
| `smart.click(selector, opts?)` | Poll + click + 500ms settle |
|
|
237
|
-
| `smart.type(selector, text, opts?)` | Poll + type + 300ms settle |
|
|
235
|
+
| `smart.evaluate(expr)` | JS evaluation via CDP. Returns `{ result, type }` — access `.result` for the value. Auto-wraps in IIFE if `return` is used. Return objects directly, never `JSON.stringify` inside. |
|
|
236
|
+
| `smart.click(selector, opts?)` | Poll + click + 500ms settle. Accepts CSS selectors or snapshot refs (`[ref=e3]`). |
|
|
237
|
+
| `smart.type(selector, text, opts?)` | Poll + type + 300ms settle. Accepts CSS selectors or snapshot refs. |
|
|
238
238
|
| `smart.navigate(url, opts?)` | Navigate + 1000ms settle |
|
|
239
239
|
| `smart.waitFor(selector, timeout?)` | Poll until element is actionable |
|
|
240
240
|
| `smart.snapshot()` | Capture accessibility snapshot |
|