@superagnt/web-scraping 2.0.2

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 ADDED
@@ -0,0 +1,73 @@
1
+ # Web data for OpenClaw — superagnt plugin
2
+
3
+ One API key to scrape any web page as clean markdown or structured JSON, search the web with full page content, and map a site's URLs. Built for agents — credit-based pricing, no separate vendor accounts. Scrape any page as clean markdown or structured JSON, search the web and get full page content in one call, and map a site's URLs. Designed for LLMs and automation.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ openclaw plugins install @superagnt/web-scraping
9
+ ```
10
+
11
+ This package is the official **OpenClaw** plugin for **Web** social and public data via [superagnt](https://superagnt.com). Pair it with the [documentation](https://superagnt.com) and your superagnt API key.
12
+
13
+ ## Live MCP mode (recommended)
14
+
15
+ Set a workspace MCP token and the plugin upgrades itself at load: it fetches
16
+ the live tool list from the scoped MCP endpoint and proxies calls through it —
17
+ the tool surface then grows with `agnt_tools_enable`, no plugin update needed.
18
+
19
+ ```bash
20
+ export SUPERAGNT_MCP_TOKEN=... # from the MCP server card in the dashboard
21
+ ```
22
+
23
+ Without the token the plugin uses its built-in toolset with your
24
+ `SUPERAGNT_API_KEY` — no behavior change from previous versions.
25
+
26
+ ## Overview
27
+
28
+ Web scraping, search, and site mapping through the superagnt unified API.
29
+
30
+ ### Highlights
31
+
32
+ - Scrape any page as clean markdown or schema-driven JSON
33
+ - Search the web and return full page content in one request
34
+ - Map a website's URLs quickly before deep scraping
35
+ - Handles JavaScript-rendered pages and anti-bot challenges
36
+ - Single API key and unified credit balance across superagnt
37
+ - Usage-based pricing that scales with each request's options
38
+
39
+ ### Example use cases
40
+
41
+ - Research and deep-research agents that read live web pages
42
+ - RAG pipelines that ingest fresh page content on demand
43
+ - Competitive and market monitoring agents
44
+ - Lead and company enrichment from public web pages
45
+ - Content extraction into structured JSON for downstream tools
46
+
47
+ ## Authentication
48
+
49
+ Use a single **superagnt API key** (Bearer token). Set `SUPERAGNT_API_KEY` in your environment, or complete browser login when the gateway prompts you. Create and manage keys in the [superagnt dashboard](https://app.superagnt.com/dashboard).
50
+
51
+ ## API coverage
52
+
53
+ | | |
54
+ | --- | --- |
55
+ | **Platform** | `web` |
56
+ | **REST base** | `https://api.superagnt.com/v1/data/web` |
57
+ | **OpenAPI operations** | 3 |
58
+ | **OpenClaw tools** | 3 |
59
+
60
+ ## Documentation
61
+
62
+ - [Documentation](https://superagnt.com) — getting started and concepts
63
+ - [API reference (Web)](https://superagnt.com/apis/social/web) — endpoints and schemas
64
+ - [Dashboard](https://app.superagnt.com/dashboard) — API keys and usage
65
+
66
+ ## Related
67
+
68
+ - **Skill (curl-oriented):** `clawhub install web-scraping` on [ClawHub](https://clawhub.ai/superagnt/skills/web-scraping)
69
+ - **All platforms plugin:** `@superagnt/openclaw-agnt-data` if you need LinkedIn, YouTube, TikTok, X, Instagram, Reddit, and Facebook in one package.
70
+
71
+ ## License
72
+
73
+ MIT
package/index.js ADDED
@@ -0,0 +1,484 @@
1
+ /**
2
+ * Web Scraping — OpenClaw plugin
3
+ * Auto-generated by @superagnt/clawhub. Do not edit by hand.
4
+ */
5
+
6
+ import { homedir as _homedir } from 'node:os'
7
+ import { join as _join } from 'node:path'
8
+ import { existsSync as _existsSync, readFileSync as _readFileSync } from 'node:fs'
9
+
10
+ const BASE_URL = 'https://api.superagnt.com/v1/data/web'
11
+
12
+
13
+ // ── Auth Module (inlined) ────────────────────────────────────────────────────
14
+
15
+ function getConfigPath() {
16
+ return _join(_homedir(), '.superagnt', 'config.json')
17
+ }
18
+
19
+ function readAgntConfig() {
20
+ const configFile = getConfigPath()
21
+ if (!_existsSync(configFile)) return null
22
+ try {
23
+ return JSON.parse(_readFileSync(configFile, 'utf-8'))
24
+ } catch {
25
+ return null
26
+ }
27
+ }
28
+
29
+ // ── End Auth Module ──────────────────────────────────────────────────────────
30
+
31
+
32
+ // ── CLI Module (inlined) ─────────────────────────────────────────────────────
33
+
34
+ function maskApiKey(key) {
35
+ if (!key || typeof key !== 'string') return '(none)'
36
+ const k = key.trim()
37
+ if (k.length <= 8) return k.slice(0, 2) + '…'
38
+ return k.slice(0, 6) + '…' + k.slice(-4)
39
+ }
40
+
41
+ function registerAgntCli(api) {
42
+ api.registerCli(
43
+ ({ program }) => {
44
+ const cmd = program
45
+ .command('superagnt')
46
+ .description('superagnt social data API (status, doctor)')
47
+
48
+ cmd
49
+ .command('status')
50
+ .description('Show whether an API key is configured (masked)')
51
+ .action(async () => {
52
+ const cfg = readAgntConfig()
53
+ const key = cfg?.apiKey
54
+
55
+ console.log('\nsuperagnt status\n')
56
+ if (!key) {
57
+ console.log(' API key: not configured')
58
+ console.log(' Workspace: —')
59
+ console.log('\n Get your API key at: https://app.superagnt.com/dashboard\n')
60
+ return
61
+ }
62
+ console.log(' API key: ' + maskApiKey(key) + ' (from ~/.superagnt/config.json)')
63
+ console.log(' Workspace: ' + (cfg?.workspaceId ?? '—'))
64
+ console.log('')
65
+ })
66
+
67
+ cmd
68
+ .command('doctor')
69
+ .description('Check connectivity and validate API key against api.superagnt.dev')
70
+ .action(async () => {
71
+ console.log('\nsuperagnt doctor\n')
72
+
73
+ try {
74
+ const publicRes = await fetch('https://api.superagnt.com/v1/platforms')
75
+ console.log(' Public API: ' + (publicRes.ok ? 'OK (' + publicRes.status + ')' : 'HTTP ' + publicRes.status))
76
+ } catch (err) {
77
+ const msg = err instanceof Error ? err.message : String(err)
78
+ console.log(' Public API: failed — ' + msg)
79
+ }
80
+
81
+ const cfg = readAgntConfig()
82
+ const key = cfg?.apiKey
83
+
84
+ if (!key) {
85
+ console.log('\n Authenticated: skipped (no API key)')
86
+ console.log(' Get your API key at: https://app.superagnt.com/dashboard\n')
87
+ return
88
+ }
89
+
90
+ try {
91
+ const res = await fetch('https://api.superagnt.com/v1/platforms', {
92
+ headers: { Authorization: 'Bearer ' + key },
93
+ })
94
+ const text = await res.text()
95
+ let summary = 'HTTP ' + res.status
96
+ if (res.ok) {
97
+ try {
98
+ const json = JSON.parse(text)
99
+ const n = Array.isArray(json?.data) ? json.data.length : (json?.data ? Object.keys(json.data).length : 'ok')
100
+ summary = 'OK — platforms payload received (' + (typeof n === 'number' ? n + ' entries' : String(n)) + ')'
101
+ } catch {
102
+ summary = 'OK (' + res.status + ')'
103
+ }
104
+ }
105
+ console.log(' Authenticated: ' + summary)
106
+ if (!res.ok) {
107
+ console.log(' If 401, get a new key at: https://app.superagnt.com/dashboard')
108
+ }
109
+ } catch (err) {
110
+ const msg = err instanceof Error ? err.message : String(err)
111
+ console.log(' Authenticated: failed — ' + msg)
112
+ }
113
+ console.log('')
114
+ })
115
+ },
116
+ { commands: ['superagnt'] },
117
+ )
118
+ }
119
+
120
+ // ── End CLI Module ───────────────────────────────────────────────────────────
121
+
122
+
123
+ // ── System prompt (inlined) ─────────────────────────────────────────────────
124
+
125
+ function getSystemPrompt(state) {
126
+ const scope = "Only the Web API (web)."
127
+ if (state.ready && state.toolCount > 0) {
128
+ return (
129
+ '<superagnt>\n' +
130
+ 'Ignore pretrained knowledge about superagnt tool names — use only tools that exist in this session.\n' +
131
+ '\n' +
132
+ '## What superagnt is\n' +
133
+ 'superagnt exposes **structured social-data HTTP APIs** behind OpenClaw tools. Scope for this plugin: **' + scope + '**.\n' +
134
+ '\n' +
135
+ '## When to use superagnt tools\n' +
136
+ '- Use superagnt when the user needs **live social/network data** (profiles, posts, companies, comments, etc.) from supported platforms.\n' +
137
+ '- Prefer **discovery** when unsure which endpoint fits: call public discovery (GET /v1/platforms and GET /v1/platforms/{slug}) or read tool descriptions; do not invent endpoints or parameters.\n' +
138
+ '\n' +
139
+ '## Platform selection (high level)\n' +
140
+ '- **linkedin** — B2B enrichment, org/person research, hiring/sales workflows\n' +
141
+ '- **youtube** / **tiktok** — video/creator/channel metadata and content signals\n' +
142
+ '- **x** / **reddit** — short-form social and community threads\n' +
143
+ '- **instagram** — visual/creator discovery where Instagram is the source of truth\n' +
144
+ '- **facebook** — pages/groups/marketplace/adjacent Facebook surface areas exposed by the API\n' +
145
+ '\n' +
146
+ '## Rules\n' +
147
+ '- Do **not** fabricate tool names — pick from the registered superagnt_* tools only.\n' +
148
+ '- If authentication fails (401), tell the user to get a new key at https://app.superagnt.com/dashboard\n' +
149
+ '</superagnt>'
150
+ )
151
+ }
152
+
153
+ if (state.ready && state.toolCount === 0) {
154
+ return (
155
+ '<superagnt>\n' +
156
+ 'The superagnt plugin loaded but registered **zero** API tools. Check your OpenClaw plugin packaging and regenerate the plugin from @superagnt/clawhub.\n' +
157
+ '</superagnt>'
158
+ )
159
+ }
160
+
161
+ if (state.error) {
162
+ return (
163
+ '<superagnt>\n' +
164
+ 'superagnt plugin diagnostic: ' + state.error + '\n' +
165
+ 'Do not pretend superagnt tools exist. Ask the user to fix configuration and restart the gateway.\n' +
166
+ '</superagnt>'
167
+ )
168
+ }
169
+
170
+ return (
171
+ '<superagnt>\n' +
172
+ 'superagnt tools are still loading. If the user asks for social data right now, ask them to wait a moment and retry.\n' +
173
+ '</superagnt>'
174
+ )
175
+ }
176
+
177
+ // ── End System prompt ────────────────────────────────────────────────────────
178
+
179
+ const MCP_URL = 'https://mcp.superagnt.com/mcp/web-scraping'
180
+
181
+ async function mcpRpc(token, method, params) {
182
+ const res = await fetch(MCP_URL, {
183
+ method: 'POST',
184
+ headers: {
185
+ Authorization: `Bearer ${token}`,
186
+ 'Content-Type': 'application/json',
187
+ Accept: 'application/json, text/event-stream',
188
+ },
189
+ body: JSON.stringify({ jsonrpc: '2.0', id: Date.now(), method, params }),
190
+ })
191
+ if (!res.ok) throw new Error(`MCP ${method} failed: HTTP ${res.status}`)
192
+ const data = await res.json()
193
+ if (data.error) throw new Error(`MCP ${method} error: ${data.error.message}`)
194
+ return data.result
195
+ }
196
+
197
+ async function registerLiveMcpTools(api, token) {
198
+ const result = await mcpRpc(token, 'tools/list', {})
199
+ const tools = Array.isArray(result?.tools) ? result.tools : []
200
+ if (tools.length === 0) throw new Error('MCP tools/list returned no tools')
201
+ for (const t of tools) {
202
+ api.registerTool({
203
+ name: t.name,
204
+ description: t.description,
205
+ inputSchema: t.inputSchema,
206
+ execute: async (input) => {
207
+ const args = parseToolInput(input)
208
+ const r = await mcpRpc(token, 'tools/call', { name: t.name, arguments: args })
209
+ if (r && r.structuredContent !== undefined) return r.structuredContent
210
+ const text = Array.isArray(r?.content) ? r.content.find((c) => c.type === 'text')?.text : undefined
211
+ if (text === undefined) return r
212
+ try { return JSON.parse(text) } catch { return text }
213
+ },
214
+ })
215
+ }
216
+ return tools.length
217
+ }
218
+
219
+ function parseToolInput(input) {
220
+ if (input == null) return {}
221
+ if (typeof input === 'object' && !Array.isArray(input)) return input
222
+ if (typeof input === 'string') {
223
+ const trimmed = input.trim()
224
+ if (trimmed.startsWith('{')) {
225
+ try { const parsed = JSON.parse(trimmed); if (typeof parsed === 'object' && parsed && !Array.isArray(parsed)) return parsed } catch {}
226
+ }
227
+ throw new Error('Invalid tool input: expected a JSON object with named parameters, got string: ' + trimmed.slice(0, 80))
228
+ }
229
+ throw new Error('Invalid tool input: expected a JSON object, got ' + typeof input)
230
+ }
231
+
232
+ function substitutePathParams(path, source) {
233
+ if (!source || typeof source !== 'object' || Array.isArray(source)) {
234
+ return { path, consumed: new Set() }
235
+ }
236
+ const consumed = new Set()
237
+ const next = path.replace(/\{([^/{}]+)\}/g, (_, name) => {
238
+ const v = source[name]
239
+ if (v === undefined || v === null || v === '') return `{${name}}`
240
+ consumed.add(name)
241
+ return encodeURIComponent(String(v))
242
+ })
243
+ return { path: next, consumed }
244
+ }
245
+
246
+ async function callSuperagnt(apiKey, method, path, query, body) {
247
+ const params = (query && typeof query === 'object' && !Array.isArray(query)) ? query : body
248
+ const { path: resolvedPath, consumed } = substitutePathParams(path, params)
249
+ const url = new URL(`${BASE_URL}${resolvedPath}`)
250
+ if (query && typeof query === 'object' && !Array.isArray(query)) {
251
+ for (const [k, v] of Object.entries(query)) {
252
+ if (consumed.has(k)) continue
253
+ if (v !== undefined && v !== '') url.searchParams.set(k, String(v))
254
+ }
255
+ }
256
+ const filteredBody = body && typeof body === 'object' && !Array.isArray(body)
257
+ ? Object.fromEntries(Object.entries(body).filter(([k]) => !consumed.has(k)))
258
+ : body
259
+ const res = await fetch(url.toString(), {
260
+ method,
261
+ headers: { Authorization: `Bearer ${apiKey}`, 'Content-Type': 'application/json' },
262
+ body: filteredBody ? JSON.stringify(filteredBody) : undefined,
263
+ })
264
+ if (res.status === 401) {
265
+ return { error: 'Invalid or expired API key. Get a new key at https://app.superagnt.com/dashboard' }
266
+ }
267
+ return res.json()
268
+ }
269
+
270
+ export default async function register(api) {
271
+ const apiKey = api.pluginConfig.apiKey || process.env.SUPERAGNT_API_KEY || readAgntConfig()?.apiKey
272
+ const mcpToken = api.pluginConfig.mcpToken || process.env.SUPERAGNT_MCP_TOKEN
273
+
274
+ function ensureApiKey() {
275
+ if (apiKey) return apiKey
276
+ throw new Error(
277
+ '[web-scraping] No API key configured. ' +
278
+ 'Get your key at https://app.superagnt.com/dashboard and either:\n' +
279
+ ' 1. Export it as SUPERAGNT_API_KEY, or\n' +
280
+ ' 2. Add apiKey to your plugin config'
281
+ )
282
+ }
283
+
284
+ registerAgntCli(api)
285
+
286
+ const promptState = { toolCount: 0, error: '', ready: false }
287
+ api.on('before_prompt_build', () => ({
288
+ prependSystemContext: getSystemPrompt(promptState),
289
+ }))
290
+
291
+ // Live MCP mode: a workspace MCP token upgrades this plugin to the server's
292
+ // own tool surface, fetched fresh at load — it grows with agnt_tools_enable
293
+ // and never goes stale. Any failure falls back to the baked toolset below.
294
+ if (mcpToken) {
295
+ try {
296
+ promptState.toolCount = await registerLiveMcpTools(api, mcpToken)
297
+ promptState.ready = true
298
+ return
299
+ } catch (err) {
300
+ promptState.error = 'Live MCP connect failed (' + (err && err.message ? err.message : err) + '); using the built-in toolset.'
301
+ }
302
+ }
303
+
304
+ api.registerTool({
305
+ name: 'superagnt_web_scrape',
306
+ description: "Scrape a web page",
307
+ inputSchema: {
308
+ "type": "object",
309
+ "properties": {
310
+ "url": {
311
+ "type": "string",
312
+ "description": "The URL to scrape (must be a publicly reachable http(s) URL)."
313
+ },
314
+ "formats": {
315
+ "type": "array",
316
+ "description": "Output formats to return. Each entry is either a string preset or an object form. String presets: 'markdown' (default), 'summary', 'html', 'rawHtml', 'links', 'images'. Object forms include { type: 'json', schema, prompt } for structured extraction, { type: 'screenshot', fullPage, quality } for an image, and { type: 'changeTracking', modes } for diffs. Defaults to ['markdown'] when omitted."
317
+ },
318
+ "onlyMainContent": {
319
+ "type": "boolean",
320
+ "description": "Return only the main content of the page, excluding navs, headers, and footers."
321
+ },
322
+ "includeTags": {
323
+ "type": "array",
324
+ "description": "Only include content within these HTML tags / CSS selectors."
325
+ },
326
+ "excludeTags": {
327
+ "type": "array",
328
+ "description": "Exclude content within these HTML tags / CSS selectors."
329
+ },
330
+ "maxAge": {
331
+ "type": "integer",
332
+ "description": "Return cached content up to this age in milliseconds (faster, cheaper). Default 172800000 (2 days)."
333
+ },
334
+ "waitFor": {
335
+ "type": "integer",
336
+ "description": "Milliseconds to wait for the page to render before scraping."
337
+ },
338
+ "mobile": {
339
+ "type": "boolean",
340
+ "description": "Emulate a mobile device."
341
+ },
342
+ "timeout": {
343
+ "type": "integer",
344
+ "description": "Request timeout in milliseconds (1000-300000). Default 60000."
345
+ },
346
+ "parsers": {
347
+ "type": "array",
348
+ "description": "PDF parsing configuration."
349
+ },
350
+ "proxy": {
351
+ "type": "string",
352
+ "description": "Proxy / anti-bot mode. 'auto' (default) escalates only on failure; 'enhanced' forces advanced anti-bot handling and costs more."
353
+ },
354
+ "location": {
355
+ "type": "object",
356
+ "description": "Geographic proxy location and language settings."
357
+ }
358
+ },
359
+ "required": [
360
+ "url"
361
+ ]
362
+ },
363
+ execute: async (input) => {
364
+ const key = await ensureApiKey()
365
+ const params = parseToolInput(input)
366
+ return callSuperagnt(key, 'POST', '/scrape', undefined, params)
367
+ },
368
+ })
369
+
370
+ api.registerTool({
371
+ name: 'superagnt_web_search',
372
+ description: "Search the web",
373
+ inputSchema: {
374
+ "type": "object",
375
+ "properties": {
376
+ "query": {
377
+ "type": "string",
378
+ "description": "The search query."
379
+ },
380
+ "limit": {
381
+ "type": "integer",
382
+ "description": "Number of results per source. Default 10."
383
+ },
384
+ "sources": {
385
+ "type": "array",
386
+ "description": "Which result sources to query. Defaults to ['web']. Each entry is a preset string or an object with a `type`."
387
+ },
388
+ "categories": {
389
+ "type": "array",
390
+ "description": "Restrict results to these categories."
391
+ },
392
+ "tbs": {
393
+ "type": "string",
394
+ "description": "Time-based search filter (e.g. 'qdr:d' for past day)."
395
+ },
396
+ "location": {
397
+ "type": "string",
398
+ "description": "Geo-target for the search (e.g. 'San Francisco, California, United States')."
399
+ },
400
+ "country": {
401
+ "type": "string",
402
+ "description": "ISO country code. Default 'US'."
403
+ },
404
+ "includeDomains": {
405
+ "type": "array",
406
+ "description": "Only return results from these hostnames (mutually exclusive with excludeDomains)."
407
+ },
408
+ "excludeDomains": {
409
+ "type": "array",
410
+ "description": "Exclude results from these hostnames (mutually exclusive with includeDomains)."
411
+ },
412
+ "timeout": {
413
+ "type": "integer",
414
+ "description": "Request timeout in milliseconds. Default 60000."
415
+ },
416
+ "scrapeOptions": {
417
+ "type": "object",
418
+ "description": "When provided, each result is also scraped and its content returned. Accepts the same content options as /scrape (e.g. `formats`, `onlyMainContent`). Increases cost because every result is fetched."
419
+ }
420
+ },
421
+ "required": [
422
+ "query"
423
+ ]
424
+ },
425
+ execute: async (input) => {
426
+ const key = await ensureApiKey()
427
+ const params = parseToolInput(input)
428
+ return callSuperagnt(key, 'POST', '/search', undefined, params)
429
+ },
430
+ })
431
+
432
+ api.registerTool({
433
+ name: 'superagnt_web_map',
434
+ description: "Map a website's URLs",
435
+ inputSchema: {
436
+ "type": "object",
437
+ "properties": {
438
+ "url": {
439
+ "type": "string",
440
+ "description": "The base URL of the site to map."
441
+ },
442
+ "search": {
443
+ "type": "string",
444
+ "description": "Order returned URLs by relevance to this query."
445
+ },
446
+ "sitemap": {
447
+ "type": "string",
448
+ "description": "How to use the site's sitemap. Default 'include'."
449
+ },
450
+ "includeSubdomains": {
451
+ "type": "boolean",
452
+ "description": "Include URLs on subdomains."
453
+ },
454
+ "ignoreQueryParameters": {
455
+ "type": "boolean",
456
+ "description": "Treat URLs that differ only by query string as the same URL."
457
+ },
458
+ "limit": {
459
+ "type": "integer",
460
+ "description": "Maximum number of URLs to return. Default 5000, max 100000."
461
+ },
462
+ "timeout": {
463
+ "type": "integer",
464
+ "description": "Request timeout in milliseconds."
465
+ },
466
+ "location": {
467
+ "type": "object",
468
+ "description": "Geographic proxy location and language settings."
469
+ }
470
+ },
471
+ "required": [
472
+ "url"
473
+ ]
474
+ },
475
+ execute: async (input) => {
476
+ const key = await ensureApiKey()
477
+ const params = parseToolInput(input)
478
+ return callSuperagnt(key, 'POST', '/map', undefined, params)
479
+ },
480
+ })
481
+
482
+ promptState.toolCount = 3
483
+ promptState.ready = true
484
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "web-scraping",
3
+ "name": "Web Scraping",
4
+ "description": "Web scraping, search, and site mapping through the superagnt unified API.",
5
+ "version": "2.0.2",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "properties": {
9
+ "apiKey": {
10
+ "type": "string",
11
+ "description": "Your superagnt API key. Get one at https://app.superagnt.com/dashboard or store in ~/.superagnt/config.json via CLI login."
12
+ }
13
+ },
14
+ "additionalProperties": false
15
+ }
16
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@superagnt/web-scraping",
3
+ "version": "2.0.2",
4
+ "type": "module",
5
+ "description": "Web scraping, search, and site mapping through the superagnt unified API.",
6
+ "main": "index.js",
7
+ "license": "MIT",
8
+ "homepage": "https://superagnt.com/r/npm-web-scraping-docs",
9
+ "keywords": [
10
+ "openclaw",
11
+ "openclaw-plugin",
12
+ "superagnt",
13
+ "mcp",
14
+ "web",
15
+ "social-data",
16
+ "api",
17
+ "ai-agent",
18
+ "web scraping api",
19
+ "web search api for ai agents",
20
+ "scrape to markdown api",
21
+ "structured web extraction api",
22
+ "site map url discovery api",
23
+ "agent-ready web data api",
24
+ "mcp web data source"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "files": [
30
+ "index.js",
31
+ "openclaw.plugin.json",
32
+ "README.md"
33
+ ],
34
+ "openclaw": {
35
+ "extensions": [
36
+ "./index.js"
37
+ ],
38
+ "compat": {
39
+ "pluginApi": ">=2026.3.24-beta.2",
40
+ "minGatewayVersion": "2026.3.24-beta.2"
41
+ },
42
+ "build": {
43
+ "openclawVersion": "2026.3.24-beta.2",
44
+ "pluginSdkVersion": "2026.3.24-beta.2"
45
+ }
46
+ }
47
+ }