From 7e1b9cb671c537c8c57290c04ecadce238f31daa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=AAs=20Moital?= Date: Mon, 2 Nov 2026 18:29:29 +0000 Subject: [PATCH 1/6] feat(render): optional slots for text only some hosts add {{>? slot}} renders the host's slot file when it has one; otherwise its line (and one blank line after it) disappears. Validate fails if no host fills an optional slot, to catch a misspelt name. {{> slot}} stays required. Skill references are now checked per host against the shared names and that host's own names, so OpenClaw's skill "zenrows" doesn't turn every `zenrows` server mention on other hosts into a reference. --- CONTRIBUTING.md | 2 +- scripts/lib.mjs | 21 +++++++++++++++------ scripts/validate.mjs | 37 ++++++++++++++++++++----------------- 3 files changed, 36 insertions(+), 24 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ef5d3b9..0315287 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -32,7 +32,7 @@ Requires Node 22. Run `npm ci` once. ## Text that differs per host - **A word or phrase:** use a variable, for example `{{host}}`, and set it in `platforms//vars.yaml`. -- **A paragraph or a few lines:** use a slot, for example `{{> connect}}`, and write the host's text in `platforms//skills//connect.md`. Everything outside the slot stays shared, so later fixes still reach the host. +- **A paragraph or a few lines:** use a slot, for example `{{> connect}}`, and write the host's text in `platforms//skills//connect.md`. Everything outside the slot stays shared, so later fixes still reach the host. Every host needs the file for a `{{> slot}}`. Write `{{>? slot}}` for text only some hosts add, for example OpenClaw's frontmatter `metadata`: on a host without the file, the line disappears (and validate fails if no host has it, to catch a typo). - **The whole file, as a last resort:** `npm run override -- ` copies the shared skill into `platforms//` and records which version it was copied from in `overrides.lock`. When the shared skill changes later, `npm run validate` fails until someone updates the copy, or confirms the change doesn't apply, and runs `npm run override:ack -- `. - **A skill only one host has:** put it in `platforms//skills//SKILL.md`, with no shared skill of that name. It ships only on that host and needs no `overrides.lock` entry. - **A shared skill a host shouldn't ship:** list it under `skip_skills:` in that host's `vars.yaml` (Cursor uses a rules file instead of `using-zenrows`). A `{{skill:…}}` reference to a skipped skill fails the render. diff --git a/scripts/lib.mjs b/scripts/lib.mjs index 9b96f07..cb283e5 100644 --- a/scripts/lib.mjs +++ b/scripts/lib.mjs @@ -120,12 +120,21 @@ export function renderHost(host) { const own = join(platform, "skills", name, "SKILL.md"); const source = existsSync(own) ? own : join(skillsDir, name, "SKILL.md"); const where = relative(root, source); - let text = readFileSync(source, "utf8").replace(/\{\{>\s*([a-z0-9_-]+)\s*\}\}/g, (_, slot) => { - const file = join(platform, "skills", name, `${slot}.md`); - if (!existsSync(file)) throw new Error(`${where}: slot {{> ${slot}}} has no ${relative(root, file)}`); - usedSlots.add(relative(root, file)); - return readFileSync(file, "utf8").replace(/\n$/, ""); - }); + // {{> slot}} needs platforms//skills//.md. {{>? slot}} is + // optional: without the file, its line (and one blank line after it) goes. + let text = readFileSync(source, "utf8") + .replace(/^[ \t]*\{\{>\?\s*([a-z0-9_-]+)\s*\}\}[ \t]*\n(\n)?/gm, (line, slot) => + existsSync(join(platform, "skills", name, `${slot}.md`)) ? line : "", + ) + .replace(/\{\{>(\?)?\s*([a-z0-9_-]+)\s*\}\}/g, (_, optional, slot) => { + const file = join(platform, "skills", name, `${slot}.md`); + if (!existsSync(file)) { + if (optional) return ""; + throw new Error(`${where}: slot {{> ${slot}}} has no ${relative(root, file)}`); + } + usedSlots.add(relative(root, file)); + return readFileSync(file, "utf8").replace(/\n$/, ""); + }); text = fill(text, values, where); out.set(`skills/${hostSkillName(values, name)}/SKILL.md`, Buffer.from(text)); } diff --git a/scripts/validate.mjs b/scripts/validate.mjs index 66252bf..14f40c7 100644 --- a/scripts/validate.mjs +++ b/scripts/validate.mjs @@ -10,24 +10,16 @@ import { catalogs, hostSkillName, hostSkills, hosts, listFiles, platformsDir, re const errors = []; const report = []; -// Every name a skill has anywhere: shared names, each host's own skills, and -// each host's name for them. A reference is only checked if it's one of these. -const knownSkills = new Set(skillNames()); -for (const host of hosts()) { - const values = vars(host); - for (const { name } of hostSkills(host, values)) { - knownSkills.add(name); - knownSkills.add(hostSkillName(values, name)); - } -} - // Skill names a text refers to: "use map", every name inside "(use …)", and -// backticked names. One-word names count, so a plain "use map" is caught on a -// host that ships map as zenrows-map. -function skillReferences(text) { +// backticked names. Only words in `names` count: the shared names, plus a +// host's own names when checking that host. One-word names count, so a plain +// "use map" is caught on a host that ships map as zenrows-map, while OpenClaw's +// skill "zenrows" doesn't turn every `zenrows` server mention on other hosts +// into a reference. +function skillReferences(text, names) { const refs = new Set(); const add = (s) => { - for (const [word] of s.matchAll(/(?? name}}) renders as nothing on a host without its file, +// so a misspelt name would never show up. At least one host must fill it. +for (const skill of skillNames()) { + const text = readFileSync(join(root, "skills", skill, "SKILL.md"), "utf8"); + for (const [, slot] of text.matchAll(/\{\{>\?\s*([a-z0-9_-]+)\s*\}\}/g)) { + if (!hosts().some((host) => existsSync(join(platformsDir, host, "skills", skill, `${slot}.md`)))) { + errors.push(`skills/${skill}/SKILL.md: optional slot {{>? ${slot}}} has no platforms//skills/${skill}/${slot}.md on any host`); + } + } +} + console.log(report.join("\n")); if (errors.length) { console.error(`\n${errors.map((e) => `❌ ${e}`).join("\n")}`); From 8448b4d39fff1d8aee7c6ed8197fc2ef5eb8d9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?In=C3=AAs=20Moital?= Date: Mon, 2 Nov 2026 18:29:29 +0000 Subject: [PATCH 2/6] feat(openclaw): plugin for OpenClaw in the shared skills layout Moves openclaw-zenrows (v0.1.4) into platforms/openclaw, with its zenrows- skill names through skill_names, its ClawHub frontmatter and Setup section as optional slots in using-zenrows, and the openclaw mcp sign-in steps in the getting-started slot. OpenClaw now gets the shared skills, which drops the old scrape(mode='auto') guidance. Contact email is integrations@zenrows.com. --- .release-please-manifest.json | 3 +- README.md | 1 + dist/openclaw/.claude-plugin/plugin.json | 13 ++ dist/openclaw/.clawhubignore | 7 ++ dist/openclaw/.mcp.json | 10 ++ dist/openclaw/LICENSE | 21 ++++ dist/openclaw/README.md | 85 ++++++++++++++ dist/openclaw/RELEASING.md | 72 ++++++++++++ dist/openclaw/assets/icon.png | Bin 0 -> 4959 bytes dist/openclaw/assets/logo.png | Bin 0 -> 4959 bytes dist/openclaw/openclaw.plugin.json | 20 ++++ dist/openclaw/package.json | 18 +++ dist/openclaw/skills/zenrows-batch/SKILL.md | 45 +++++++ dist/openclaw/skills/zenrows-browser/SKILL.md | 60 ++++++++++ .../skills/zenrows-capture-api-calls/SKILL.md | 35 ++++++ dist/openclaw/skills/zenrows-crawl/SKILL.md | 30 +++++ dist/openclaw/skills/zenrows-extract/SKILL.md | 44 +++++++ .../skills/zenrows-getting-started/SKILL.md | 40 +++++++ dist/openclaw/skills/zenrows-map/SKILL.md | 22 ++++ dist/openclaw/skills/zenrows-scrape/SKILL.md | 17 +++ dist/openclaw/skills/zenrows/SKILL.md | 111 ++++++++++++++++++ platforms/openclaw/.claude-plugin/plugin.json | 13 ++ platforms/openclaw/.clawhubignore | 7 ++ platforms/openclaw/.mcp.json | 10 ++ platforms/openclaw/README.md | 85 ++++++++++++++ platforms/openclaw/RELEASING.md | 72 ++++++++++++ platforms/openclaw/assets/icon.png | Bin 0 -> 4959 bytes platforms/openclaw/openclaw.plugin.json | 20 ++++ platforms/openclaw/package.json | 18 +++ .../skills/getting-started/connect.md | 3 + .../skills/using-zenrows/frontmatter.md | 9 ++ .../openclaw/skills/using-zenrows/setup.md | 6 + platforms/openclaw/vars.yaml | 19 +++ release-please-config.json | 21 ++++ skills/using-zenrows/SKILL.md | 3 + 35 files changed, 939 insertions(+), 1 deletion(-) create mode 100644 dist/openclaw/.claude-plugin/plugin.json create mode 100644 dist/openclaw/.clawhubignore create mode 100644 dist/openclaw/.mcp.json create mode 100644 dist/openclaw/LICENSE create mode 100644 dist/openclaw/README.md create mode 100644 dist/openclaw/RELEASING.md create mode 100644 dist/openclaw/assets/icon.png create mode 100644 dist/openclaw/assets/logo.png create mode 100644 dist/openclaw/openclaw.plugin.json create mode 100644 dist/openclaw/package.json create mode 100644 dist/openclaw/skills/zenrows-batch/SKILL.md create mode 100644 dist/openclaw/skills/zenrows-browser/SKILL.md create mode 100644 dist/openclaw/skills/zenrows-capture-api-calls/SKILL.md create mode 100644 dist/openclaw/skills/zenrows-crawl/SKILL.md create mode 100644 dist/openclaw/skills/zenrows-extract/SKILL.md create mode 100644 dist/openclaw/skills/zenrows-getting-started/SKILL.md create mode 100644 dist/openclaw/skills/zenrows-map/SKILL.md create mode 100644 dist/openclaw/skills/zenrows-scrape/SKILL.md create mode 100644 dist/openclaw/skills/zenrows/SKILL.md create mode 100644 platforms/openclaw/.claude-plugin/plugin.json create mode 100644 platforms/openclaw/.clawhubignore create mode 100644 platforms/openclaw/.mcp.json create mode 100644 platforms/openclaw/README.md create mode 100644 platforms/openclaw/RELEASING.md create mode 100644 platforms/openclaw/assets/icon.png create mode 100644 platforms/openclaw/openclaw.plugin.json create mode 100644 platforms/openclaw/package.json create mode 100644 platforms/openclaw/skills/getting-started/connect.md create mode 100644 platforms/openclaw/skills/using-zenrows/frontmatter.md create mode 100644 platforms/openclaw/skills/using-zenrows/setup.md create mode 100644 platforms/openclaw/vars.yaml diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 9a9ceb4..1c6c6d8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,4 @@ { - "dist/copilot": "0.1.1" + "dist/copilot": "0.1.1", + "dist/openclaw": "0.1.4" } diff --git a/README.md b/README.md index 93c61ef..6a48c8a 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Each host's guide covers installing the plugin and signing in to Zenrows. You si | Host | Setup guide | |---|---| | GitHub Copilot | [dist/copilot/README.md](dist/copilot/README.md) | +| OpenClaw | [dist/openclaw/README.md](dist/openclaw/README.md) | ## Skills diff --git a/dist/openclaw/.claude-plugin/plugin.json b/dist/openclaw/.claude-plugin/plugin.json new file mode 100644 index 0000000..07f53b8 --- /dev/null +++ b/dist/openclaw/.claude-plugin/plugin.json @@ -0,0 +1,13 @@ +{ + "name": "zenrows", + "version": "0.1.4", + "description": "Web data for OpenClaw: scrape, extract and browser automation through the Zenrows MCP server.", + "author": { + "name": "Zenrows", + "email": "integrations@zenrows.com" + }, + "homepage": "https://docs.zenrows.com/integrations/openclaw", + "repository": "https://github.com/ZenRows/zenrows-integrations", + "license": "MIT", + "mcpServers": "./.mcp.json" +} diff --git a/dist/openclaw/.clawhubignore b/dist/openclaw/.clawhubignore new file mode 100644 index 0000000..a1b82bc --- /dev/null +++ b/dist/openclaw/.clawhubignore @@ -0,0 +1,7 @@ +.git/ +.github/ +.claude/ +reports/ +node_modules/ +*.tgz +RELEASING.md diff --git a/dist/openclaw/.mcp.json b/dist/openclaw/.mcp.json new file mode 100644 index 0000000..43dd8e2 --- /dev/null +++ b/dist/openclaw/.mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "zenrows": { + "url": "https://mcp.zenrows.com/mcp", + "description": "Enable this plugin and connect your Zenrows account in OpenClaw. A Zenrows account is required; usage is billed by Zenrows.", + "auth": "oauth", + "type": "http" + } + } +} diff --git a/dist/openclaw/LICENSE b/dist/openclaw/LICENSE new file mode 100644 index 0000000..e7dd33a --- /dev/null +++ b/dist/openclaw/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Zenrows + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dist/openclaw/README.md b/dist/openclaw/README.md new file mode 100644 index 0000000..c0cdb30 --- /dev/null +++ b/dist/openclaw/README.md @@ -0,0 +1,85 @@ +# Zenrows for OpenClaw + +Web data for [OpenClaw](https://openclaw.ai): fetch pages, extract structured data, run batch jobs and drive hosted browser sessions from your agent, through the official Zenrows MCP server. + +Published on ClawHub as [`@zenrows`](https://clawhub.ai/zenrows). + +## What you get + +- The Zenrows MCP server (`https://mcp.zenrows.com/mcp`), connected with OAuth. No API key in your config. +- Nine skills that tell the agent when and how to use each tool: + +| Skill | Use it for | +|---|---| +| `zenrows` | Overview: tools, defaults, errors and limits | +| `zenrows-scrape` | Reading one page | +| `zenrows-extract` | Named fields or JSON from one page | +| `zenrows-batch` | A list of five or more URLs as one job | +| `zenrows-crawl` | Many pages under one site section | +| `zenrows-map` | Listing a site's URLs without fetching them | +| `zenrows-browser` | Clicks, forms, pagination and multi-step flows | +| `zenrows-capture-api-calls` | Finding the API calls a page makes | +| `zenrows-getting-started` | First-run checks and troubleshooting | + +## Requirements + +- OpenClaw on Node.js 24.16 or newer. +- A Zenrows account. Requests use credits on your Zenrows plan. + +## Install + +```bash +openclaw plugins install clawhub:@zenrows/openclaw-zenrows +``` + +Then connect your Zenrows account: + +```bash +openclaw mcp set zenrows '{"url":"https://mcp.zenrows.com/mcp","transport":"streamable-http","auth":"oauth"}' +openclaw mcp login zenrows +``` + +Open the link that `openclaw mcp login` prints, sign in to Zenrows and approve access. OpenClaw only offers the sign-in for a server that also has an `mcp.servers` entry, which is why the first command is needed. + +Start a new session and ask: "Use Zenrows to fetch https://example.com and tell me the page title." + +## Manual setup + +To use the MCP server without the plugin, add it to `mcp.servers` in `~/.openclaw/openclaw.json`. + +With OAuth: + +```json +"zenrows": { + "url": "https://mcp.zenrows.com/mcp", + "transport": "streamable-http", + "auth": "oauth" +} +``` + +Then run `openclaw mcp login zenrows`. + +With an API key, read from the `ZENROWS_API_KEY` environment variable: + +```json +"zenrows": { + "url": "https://mcp.zenrows.com/mcp", + "transport": "streamable-http", + "headers": { "Authorization": "Bearer ${ZENROWS_API_KEY}" }, + "connectionTimeoutMs": 10000, + "requestTimeoutMs": 120000 +} +``` + +Edit the file directly. `openclaw mcp set` writes the resolved key into the file instead of the `${ZENROWS_API_KEY}` placeholder. + +Run `openclaw config validate` after editing. + +## Security + +- The agent never handles your Zenrows credentials. OAuth tokens are stored by OpenClaw. +- The skills tell the agent to treat fetched pages as untrusted content and to confirm scope before multi-page jobs. + +## License + +MIT for this repository. Skills published on ClawHub are distributed under ClawHub's MIT-0 terms. diff --git a/dist/openclaw/RELEASING.md b/dist/openclaw/RELEASING.md new file mode 100644 index 0000000..cbc1007 --- /dev/null +++ b/dist/openclaw/RELEASING.md @@ -0,0 +1,72 @@ +# Releasing + +The plugin is published to ClawHub as [`@zenrows/openclaw-zenrows`](https://clawhub.ai/zenrows/plugins/openclaw-zenrows), under the `zenrows` publisher. Publishing is manual: this repo has no release workflow yet. + +> **Source of truth:** the plugin is built in [zenrows-integrations](https://github.com/ZenRows/zenrows-integrations) from `platforms/openclaw/` into `dist/openclaw/`. ClawHub publishes from a GitHub repo at a commit, so until `openclaw-zenrows` is kept in sync from `dist/openclaw/` as a mirror, keep publishing from `openclaw-zenrows` as described below. + +## Before you start + +- You need publisher access to `zenrows` on ClawHub. Ask in #proj-integrations if `clawhub package publish ... --owner zenrows` refuses you. +- Install the `clawhub` CLI (these steps were last run with v0.23.3) and log in with `clawhub login`. + +## 1. Bump the version in the release PR + +ClawHub takes the version from `package.json`, not from `CHANGELOG.md`. The PR that ships the change bumps all three to the same version: + +- `package.json` +- `openclaw.plugin.json` +- `.claude-plugin/plugin.json` + +and adds one line under a `## X.Y.Z` heading in `CHANGELOG.md` saying what changed for the user. Never leave it under `## Unreleased`. + +## 2. Dry run against the merge commit + +Publish the merge commit, not `main`, so what you check is what ships. + +```bash +clawhub whoami +SHA=$(gh pr view -R ZenRows/openclaw-zenrows --json mergeCommit -q .mergeCommit.oid) + +clawhub package publish "ZenRows/openclaw-zenrows@$SHA" \ + --family bundle-plugin \ + --owner zenrows \ + --dry-run +``` + +Check that `Version` is the new one, `Name` is `@zenrows/openclaw-zenrows` and `Commit` is `$SHA`. + +## 3. Publish + +```bash +clawhub package publish "ZenRows/openclaw-zenrows@$SHA" \ + --family bundle-plugin \ + --owner zenrows \ + --changelog '' \ + --wait +``` + +- Put the changelog in single quotes. In double quotes the shell runs anything between backticks, and the text loses those words. +- `--wait` returns only after ClawHub's security scan. Without it, a release the scan holds back looks published but isn't installable. +- If the CLI asks for `--manual-override-reason`, a trusted publisher is configured for this package. Stop and ask in #proj-integrations instead of overriding it. + +## 4. Prove it is live and is what was merged + +```bash +curl -s https://clawhub.ai/api/v1/packages/@zenrows/openclaw-zenrows \ + | jq '.package | {latestVersion, scanStatus, sourceCommit: .verification.sourceCommit}' +``` + +`latestVersion` must be the new version, `scanStatus` `clean` and `sourceCommit` `$SHA`. + +Then compare the published files with the merged files. This needs no local clone: + +```bash +VERSION= +cd "$(mktemp -d)" +curl -sSL -o pkg.zip "https://clawhub.ai/api/v1/packages/@zenrows/openclaw-zenrows/download?version=$VERSION" +mkdir pub repo && unzip -q pkg.zip -d pub +gh api "repos/ZenRows/openclaw-zenrows/tarball/$SHA" | tar -xz -C repo --strip-components=1 +diff -r $(sed -e 's:/$::' -e '/^$/d' -e 's/^/-x /' repo/.clawhubignore) repo pub/package && echo identical +``` + +The `-x` flags skip what `.clawhubignore` keeps out of the package (this file among them), so only shipped files are compared. Anything other than `identical` means the release is not what was merged. Then post the version in #proj-integrations so the Integrations brain picks it up. diff --git a/dist/openclaw/assets/icon.png b/dist/openclaw/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..a32dc121e6d08ae56c36b82382e4671ae5cc7233 GIT binary patch literal 4959 zcmcInhgVbCw?2OigYOo(t8U< z5Qvlzr756NBnb&60wRzQLPC1E_g$Q~*82;7S;@L*uXE1c-`?N1&pnB^wKm@-zgJ#H zMrNDE#S8W_GMk~{W|^&9phr)I<|oMLvb}u8G~8yXyN=UVye^f3LOu}Xyc}V-1cD#{ zfGFFg2>T_e1Qd&am}|7;J9OT!0UOi%ng#g-NStI~fmn4yn%TJ;2m1VU3 zQl|gP+=S>`FQ3bj3b+7!Rk|=GdUkc0HZP99L7N^GMmjCO4rZj?Uy1jmMY}97QpMN; zwtxrBd=us&SLey1bl;WoSSIn4fI2Q*XG-}T;7bRu0JS>Q#*1~Mu~sCd&zOm~>8us$ z8cl*pS&MdE&I)9#uS%bL(l{*X8eNiblm3GuO1e$2OJl`er*YP#NnZ44F3W6&l*^W; z+@rsIxWZV3meJ-ZqKP3v?;9?k3oOlw-@az2+@;64)B2jYOW(zBUa|_&tIs`XADg+U z-t?Rh#t5F@lFMe&BvlD(Jq_HiINs<7e)@xzPc7V8qA1RdR-eIQE=w|eSMtMGvjZ9Q z1xZdQV`@a0AGSJ(<;{!9ZtJ?TaWu8^Jkgz>^>WvrWDow!yU$t-%h z+Skba(#flcV}5Su)g-UI3|MK(X7|){8namS>8#0NVa0Q1<#Xm}H@_*1RfJw0?&3FO zvc`J&?~2w-qL>3%UgIm)$a{WnD5JlLTbsJpRsp`8+^xWFIjKNWB0%1;;T9B zrR!xe%#O14_ZUu1(%LYdFXRCqYB}UD0zOB|TL=2yaBvmtAL=;Rf^|R&zVM-g0JtzM z{!S3Rui;1}0Iqy}eO2;vMzk;`UYrr9?e*LS30m&qMVH4iGKSF`zfFYNhCmq^C3A}l z=dOfO7}JYrN5`H0tYpoz{QsEz6)B^UDN}5UOe;~++Pq!qoR+py``;?RtyKM05_Qvb z&u@Qjw|yV$)<~bACwU%BDbEWs=5C0cPT{qd<&Llq=E>{+k+H?jP^#@4U1rj(gl7U)teKK~JEpmjK$^3@r8I;W#-@`rNxZ+Vp1Ij_h; z$B)j0*l~*Ue)NOT^zN2(#;pAOr+r?delgI zL|C9(oYO_TR##F*bY)0!RK)#Y_rdgF`KW4Ho3&J3*FS~wf~XzHy2Ywb>A@oB24!G9 zSiu1d8W5_C-s&?Eqt~L;#l3N9*Pm;PFdmglBE^3!Mo9V#RBUJFKO&m%4LT`Zl7aH^ z8iaLKyeOdP_M#8lbTLlg(Sz!ng_&cQ=(!h2csn+am)Cad2Z>b%}xJlBayM- zR-waX#rz#op8s`+kHf)d4|CeItrbFHXAidnRz#9;UJJ2Vt+J?KKzwzAk>Bq`O<1G% zAhl^(PO8zW?%sX>%=R2OtX1vYj@au4c5`^4u^s0a)zvnSI!sCEi!R9F2{jStN&+n{ z3K-?$L+x@Dz4syIE;g6e&g|CHZ3pxW^LV%V*CVuVb?5Re$N94 z&PPh=Xe;{#W0$&yrzChY3l7+5tHSAzCCO1o-;@?yg8PBT(o)!1P)wbW*Y@p<2bn|z z+hC5gL};YVDEqN)A^}V67;ts6XU>^i`;In1p_J~yNw8amV}D0F_`nvVI_23 z$y_4DW>PH5!=eSCW$Cn?^_rAfF-0#)JVjnznb9aNof2%b^bL;(he*2_DUU`ELW>p3 z1mJ#*c|GG+NU;iS^r8lUC)ZyC9~YPAok+6RUuLmJ`&{+9X=%f!8@p`fxn9O?4~5`a zr_}IsOYl@L*{8YvdHXI&)Z1)*_Lc{ybDDV3(_A+_c{(*{^S^dG!3r2?>lTIFF?ya` z@MkPSr+E@!WxfIUj`ltg|CN3@Qfu-FW4|5l#J$v8nf-E= zCv4;qF+X(2!??u(-5(A50mQtO1V0K-Q6`3#8((*wE}p>@?tcJQ(G9f5-%e#kyGw&h z4Xn>x9Jlp>C3l@$A~2oQVN=nJLO<6v&%j0}8wbebcv~{c(ww*AkR1ZhSYh&seU;u9 zR|TACJB=@=4C?#5zf$asP7y_RYxIKsxdKppn!=Z>MA*o4=Y2E)^(#KjSoG~jUXfnQAphEl55G_ELJ2n_;O~LZW23oyLH?9ETaZ%;(JXp*`e)@<}4NMjOIj}$_ zB8s8@%A8xaC1%fXhDm1`F6Rql4jk={>`q#=&~P7fw;C!%jzyZxq0KGAP8|v3+Lw49 zw~-G>=lQtuXyq3^+$m7Gc@p_jWM`;lcki-5ZB(>(vCs$3UtiyYH;5R^F0dzNvrp1S|MEG zTU`4J`6$EW=!9{{7>Wy9;4Q3!0M?*}zn6ta%=kfK$(jRm0QGAEUqGZR8&@wtwKt_;?vgQCe`KESf;y_Y2#0KN|HsxfP!~#|*?G9hS@)pj zL0PSqjEV=gkhl1;2XkmWi!d(H%HAeZUbk}GI-N&z;nDaCvX(<6NYRYkIw*x5<-}cf zKCo>=8xZ2tHzIj?UV^|iOG{6%@VR9;WH1h?Ww#&DQ|kkh$Cb-Hcd_uSLjaaeSi~Q# z1#^2r>-0mXAkXC&v_kV|6|D#N)z4stah6xXx{bMrt62|DQsQ?&yI??}v};rixbdsd z5m&GAXy!8qJME{=S10SS@NNwNi`9L&Z{tK6Q=}7uaiUENv<8*um$+^;9MW50{`DBt zYz6ZKh>|m`ZU(e2jx&$;&J#>bL*uMn8-xeUVJe}F>KLU^dj0YLMq&+W)c#6 z5Ka&)7VR~a%f_Wfl4k;{Re4Ai6jIz9530Tl%OAloizA9A*e^E+?3s4QIqRyz{j4Sa zl6bJPS{>dR4xKV)?i<4SLuGNwq&i=dLDjnLwcHQQKDS$%=EGZZ-NN_0vs`{{eSc}@XR4~Np*D0tDD#VvC4*z!9 zupbN~DEE0@y9O0(ALKXLP90#WvG5sX7KT>DeT)mEyqBAg4}@7R5fHx3%BIli(9-Sz zLEZ*l>@`tX*FGJm3m7cKtg{98z1kde5{#jwfcKG#{kx13b5baR^X zhH$`nYy8cx;g$@|wnbhb~8r3t9}#`Ne(n@bHPx3)rRYTe2LTij~Zo|q~8+FPgu zQvFC6bXl9|@NaYbQE!hzvTE4Xihe-bz<_$&b&G_d>Szus!yrm=7&S0)5}~tEtnzLV z`u2DndJ3vFc&%)pNdT$R4@=J2^8-ZcC>~6yM=f;$>Cepc?e2)k--;K4YpW{fB{!j>AS2_UHVV1pYa8J*2MYgnhBdW;g!zCDKyVO(~p z#Z$P@J1@Rz0pc<+h-SqI)(cWs+Tk?>$wYghlX$y>3U0}*VZtxe%6IRvy_qdqOAR_u z)zowJ_XeWvhDO%Ts*t^AmBUbGN}!&OZx8bU)x|)&?ebL+_8go(jR*(veO^`rG2J3X z7;wtSY7^NjFb=JATk5t;ZOaSc&Z05HUeKMk*R@g0EI?pB7cofh>>Qyo7G4|OX?`ev zwvb{np18gEREKs?rH+yx`puNxbJ20DQU%RzAe?+L0Cnt8v(MWkqCq5?>2OV#v-QX% z_3tOWGB*m0XBRS|J8N-J*v!&r<-Y)a|I>%muT`IwuUbLxzqI0)VFUNB!$-K`G+MI6 zR^7A3E*m!TT0n(47WeJTnu)vr9JF%sl~d$Za>=UQBIbU8f9XsQ(^ChF?zE6FUx)Qz zLx?Ezhir=*Qv40UGyg-;&Tm585Lx`jH^8-cP^xUy??ULmDUfC1Q`NTW&Q%79Z29If z0=!-y$IfL?btb5-=L9wvYN+!^q?zdp%+LDEFnJMl**$n=%zlCrD#b@+5$}9O$YBwIIu!|OL&+;k*+Fv=UkR$R=0x<0U&2z$ zP)Yn(IbPf%%2nlLo5m@i&`eJHO7FPn5~|_k=#$T*(^M-`%seP}WhBHf##goURf@IH zIgu&RkPZoL7y{6qwumn`xutiH^;7oaa6&5uFs?%Y5mIpd0(6PP>AaZQ`6>3gcxGz0 z2ODJFCh%f6Cdzay*mu^Bpl-f+g)WHTpr2W4xxK2|T{z=xOn7ksbTN?>8aicG^8b9; z=O6X57bM?ybwqk>S_jU3X}T0d3MAssviyky3|l@|R}&cM$Nz__T{ADDzF%%wX|D3m zkC0Ha&8wTZaql1B%@35`tuL}BOAf6qw;|dk46$9Q4r!s~E6i0|vS3(cbN-mL!=Y3b zbX~1^ecpR-Qo@NWw~$W#iAOyJx{Jz^lLkWEU_xRd>2k@A|JFZn$nS`fbe9=&H)TyP R^YTU*3sdV0^(LNC{|k;`(4+tW literal 0 HcmV?d00001 diff --git a/dist/openclaw/assets/logo.png b/dist/openclaw/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..a32dc121e6d08ae56c36b82382e4671ae5cc7233 GIT binary patch literal 4959 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 R^YTU*3sdV0^(LNC{|k;`(4+tW literal 0 HcmV?d00001 diff --git a/dist/openclaw/openclaw.plugin.json b/dist/openclaw/openclaw.plugin.json new file mode 100644 index 0000000..cf89abe --- /dev/null +++ b/dist/openclaw/openclaw.plugin.json @@ -0,0 +1,20 @@ +{ + "id": "zenrows", + "name": "Zenrows", + "version": "0.1.4", + "description": "Web data for OpenClaw: scrape, extract and browser automation through the Zenrows MCP server.", + "categories": ["web"], + "configSchema": { + "type": "object", + "additionalProperties": false, + "properties": {} + }, + "mcpServers": { + "zenrows": { + "url": "https://mcp.zenrows.com/mcp", + "description": "Enable this plugin and connect your Zenrows account in OpenClaw. A Zenrows account is required; usage is billed by Zenrows.", + "auth": "oauth", + "transport": "streamable-http" + } + } +} diff --git a/dist/openclaw/package.json b/dist/openclaw/package.json new file mode 100644 index 0000000..58dfe90 --- /dev/null +++ b/dist/openclaw/package.json @@ -0,0 +1,18 @@ +{ + "name": "@zenrows/openclaw-zenrows", + "version": "0.1.4", + "description": "Web data for OpenClaw: scrape, extract and browser automation through the Zenrows MCP server.", + "license": "MIT", + "author": "Zenrows ", + "homepage": "https://docs.zenrows.com/integrations/openclaw", + "repository": { + "type": "git", + "url": "https://github.com/ZenRows/zenrows-integrations.git" + }, + "keywords": ["openclaw", "mcp", "web-scraping", "data-extraction", "browser-automation", "zenrows"], + "openclaw": { + "build": { + "openclawVersion": "2026.9.8" + } + } +} diff --git a/dist/openclaw/skills/zenrows-batch/SKILL.md b/dist/openclaw/skills/zenrows-batch/SKILL.md new file mode 100644 index 0000000..9f248c7 --- /dev/null +++ b/dist/openclaw/skills/zenrows-batch/SKILL.md @@ -0,0 +1,45 @@ +--- +name: zenrows-batch +description: Fetch a list of URLs the user already has as one managed Zenrows job, with retries and per-task results. Use when the user supplies or points at roughly five or more URLs (a file, a pasted list, the output of zenrows-map) and wants their content or extracted fields. Not for discovering URLs by following links (use zenrows-crawl), not for listing a site's URLs (use zenrows-map), and not for a single page (use zenrows-scrape). +--- + +# Batch + +The user's instructions take precedence over this skill. If they conflict, follow the user. + +Zenrows Batch queues many URLs as one server-side job, retries transient failures, and stores the results. + +## When to use this instead of `scrape` + +Use batch at roughly five URLs or more. Below that, parallel `scrape` calls are faster and simpler, since a job costs a create, a wait and a results fetch. Above it, batch wins: one job instead of N round trips, server-side retries, and no plan-concurrency 429s (the Developer plan allows 5 concurrent requests). + +## Instructions + +1. Confirm the URL list and its size with the user before submitting. A batch bills per URL, so an unintended 5,000-URL job is expensive. +2. Submit the job: + - Same options for every URL: `batch_create(urls=[...], response_type='markdown')`. + - Different options per URL, or you need ids echoed back: `batch_create(tasks=[{url, external_id, metadata, zenrows_params}, ...])`. + - `urls` is ignored when `tasks` is provided. Do not pass both. +3. Wait for completion: + - Small jobs: pass `wait=true` to `batch_create` and get a terminal state in one call. + - Larger jobs: let `batch_create` return, then call `batch_wait(job_id)`. Default timeout is 600000 ms; raise `timeout_ms` (max 3600000) for big jobs. + - Report progress from `batch_status(job_id)` if the user asks mid-job. +4. Collect results with `batch_results(job_id)`. Use `status='successful'` or `status='failed'` to split them when reporting. +5. If you can write files (for example in OpenClaw), save results to `./.zenrows/batch-.jsonl`, one JSON object per line, and summarize instead of reading the whole file back. Otherwise, summarize the results directly. +6. If the user stops the task, call `batch_cancel(job_id)`. The job keeps consuming credits until cancelled. + +## Options + +| Need | Where | Note | +|------|-------|------| +| Same settings for all URLs | job level: `js_render`, `premium_proxy`, `proxy_country`, `response_type` | Simplest form | +| Different settings per URL | `tasks[].zenrows_params` | Accepts `js_render`, `premium_proxy`, `extract`, `autoparse`, and other scrape params | +| Structured fields, not page content | `tasks[].zenrows_params` with `extract` or `autoparse` | Batch and Extract compose | +| Track which result is which | `tasks[].external_id` and `tasks[].metadata` | Echoed back on results | +| Geo-restricted pages | `proxy_country` | Works with the default settings | + +## Notes + +- `batch_create` has no required field, but a job with neither `tasks` nor `urls` does nothing. Always supply one. +- Always report the failed count alongside the successful one. Batch retries transient failures itself, so a URL in the failed set has genuinely not worked. +- `browser_batch` is unrelated: it batches actions inside one browser session. It is not this. diff --git a/dist/openclaw/skills/zenrows-browser/SKILL.md b/dist/openclaw/skills/zenrows-browser/SKILL.md new file mode 100644 index 0000000..4676f7e --- /dev/null +++ b/dist/openclaw/skills/zenrows-browser/SKILL.md @@ -0,0 +1,60 @@ +--- +name: zenrows-browser +description: 'Drive a page through steps before its content is available: clicking, filling forms, logging in, paginating, or multi-step navigation that a single fetch cannot do. Use when interaction is required to reach the content. Not for static page reads (use zenrows-scrape), and not for passively listing the network or API calls a page makes (use zenrows-capture-api-calls).' +--- + +# Browser automation + +The user's instructions take precedence over this skill. If they conflict, follow the user. + +## Instructions + +1. Open a session with `browser_navigate(url)`. Save the returned `session_id` and pass it to every later call. +2. Find the selectors you need with `browser_get_accessibility_tree` or `browser_query_selector_all`. Both are cheaper and more precise than a screenshot. Take `browser_screenshot` only when the page state is unclear or the user asked to see it. +3. Run the interaction as one `browser_batch` call whenever the steps are known in advance. Every round trip to the hosted browser costs seconds; a login sent as six separate calls is six times slower than it needs to be. +4. Fall back to individual `browser_*` calls only when a later step depends on reading an earlier result. +5. Always call `browser_close(session_id)` when done, to free a concurrency slot. + +## browser_batch + +`browser_batch(session_id, actions, stop_on_error)` runs 1 to 50 ordered actions in one call, reads included. A login and its result: + +```json +{ + "session_id": "", + "actions": [ + { "type": "wait_for_selector", "selector": "#email" }, + { "type": "fill", "selector": "#email", "value": "user@example.com" }, + { "type": "fill", "selector": "#password", "value": "secret" }, + { "type": "click", "selector": "button[type=submit]" }, + { "type": "wait_for_navigation" }, + { "type": "get_text" } + ] +} +``` + +Action names inside `actions` drop the `browser_` prefix: `navigate`, `go_back`, `go_forward`, `reload`, `click`, `hover`, `type`, `fill`, `select`, `check`, `uncheck`, `focus`, `press_key`, `scroll`, `drag`, `wait`, `wait_for_selector`, `wait_for_navigation`, `evaluate`, `screenshot`, `get_text`, `get_html`, `get_url`, `get_title`, `get_attribute`, `get_accessibility_tree`, `query_selector_all`. + +Three traps: + +- The action is `select`, not `select_option`. +- `fill` takes `value`. `type` takes `text`. +- `stop_on_error` defaults to `true`, so one bad selector halts the rest of the list. Set it to `false` only when the later actions do not depend on the earlier ones. + +`browser_batch` batches actions inside one browser session. It is unrelated to the Batch product and its `batch_*` tools. + +## Session management + +- Pass `session_id` to every subsequent `browser_*` call. +- Do not open multiple sessions unless the task requires it. +- Browser sessions hold concurrency slots on the user's plan. Close them promptly. +- Geo-targeting is set when the session is created. Pass `proxy_country` (ISO 3166-1 alpha-2, for example `de`) or `proxy_region` (`eu`, `na`, `ap`, `sa`, `af`, `me`) to `browser_navigate`. Later `browser_*` calls do not accept either, so set it on the first call or start a new session. + +## When to prefer browser over scrape + +| Scenario | Tool | +|----------|------| +| Read a static docs page | `scrape` | +| Log in, then navigate | `browser_*` | +| Click "Load more" pagination | `browser_*` | +| Fill a search form and read results | `browser_*` | diff --git a/dist/openclaw/skills/zenrows-capture-api-calls/SKILL.md b/dist/openclaw/skills/zenrows-capture-api-calls/SKILL.md new file mode 100644 index 0000000..e79db8e --- /dev/null +++ b/dist/openclaw/skills/zenrows-capture-api-calls/SKILL.md @@ -0,0 +1,35 @@ +--- +name: zenrows-capture-api-calls +description: Discover the background API or XHR endpoints a page calls, or reverse-engineer how it loads its data. Use when the user wants the underlying request a site makes, not the rendered page. Not for interacting with the page UI (use zenrows-browser), and not for reading rendered content (use zenrows-scrape). +--- + +# Capture API calls + +The user's instructions take precedence over this skill. If they conflict, follow the user. + +## Instructions + +1. Open a session with `browser_navigate(url)` and save the `session_id`. +2. Let the page finish loading: `browser_wait(session_id, ms=3000)`. Do not use `browser_wait_for_navigation` here; that must run before a navigation-triggering action, not after `browser_navigate`. +3. Collect network resource URLs with `browser_evaluate`: + + ```javascript + JSON.stringify( + performance.getEntriesByType('resource') + .filter(r => r.initiatorType === 'fetch' || r.initiatorType === 'xmlhttprequest' || r.name.includes('/api/')) + .map(r => ({ url: r.name, type: r.initiatorType })) + ) + ``` + +4. Identify the endpoint(s) most likely returning the target data (JSON paths, `/api/` URLs, GraphQL endpoints). +5. Present the endpoint URL, the method if inferable, and a sample response shape. +6. Optionally call the endpoint directly with `scrape(url=, response_type='html')` to get the raw response body. +7. Always call `browser_close(session_id)` when done. + +## Fallback + +If `performance.getEntriesByType` returns nothing (a single-page app with deferred loads), interact with the page (scroll, click load triggers) and re-run the evaluate script, or inspect inline `