@superagnt/exa-search 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,79 @@
1
+ # Exa integration for OpenClaw — superagnt plugin
2
+
3
+ Connect your Exa AI account to superagnt to power agents with neural web search, parsed page contents, similar-page discovery, and agentic Q&A — all through a unified API. Neural web search, parsed page contents, similar-URL discovery, and agentic Q&A — wired into your agents through a single superagnt API key.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ openclaw plugins install @superagnt/exa-search
9
+ ```
10
+
11
+ This package is the official **OpenClaw** plugin for the **Exa** integration 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
+ OpenClaw plugin for Exa — run neural web search, fetch parsed contents, find similar URLs, and get agentic answers through your superagnt API key.
29
+
30
+ ### Highlights
31
+
32
+ - Neural + keyword search — semantic web search with optional category filters and date ranges
33
+ - Parsed contents — return text, highlights, and LLM-generated summaries for any URL
34
+ - Find similar — surface semantically related pages from a seed URL
35
+ - Agentic answers — Q&A over the live web with cited sources
36
+ - Domain + published-date filters — scope results to trusted sources or recent news
37
+ - Livecrawl control — force fresh fetches or rely on Exa's cache per request
38
+ - Subpage expansion — pull nested content for deeper research workflows
39
+
40
+ ### Example use cases
41
+
42
+ - Give agents real-time web knowledge beyond their training cutoff
43
+ - Research a company, competitor, or topic with cited sources and summaries
44
+ - Build retrieval-augmented generation pipelines grounded in the live web
45
+ - Pull clean page text for summarization, extraction, or ingest into a vector store
46
+ - Monitor news and blog posts filtered to trusted domains and date ranges
47
+ - Run agentic Q&A that returns answers plus the sources Exa used
48
+
49
+ ## Prerequisites
50
+
51
+ 1. An **superagnt API key** — create and manage keys in the [superagnt dashboard](https://app.superagnt.com/dashboard).
52
+ 2. A connected **Exa** account — configure in the [integrations dashboard](https://app.superagnt.com/dashboard/connections).
53
+
54
+ ## Authentication
55
+
56
+ Set `SUPERAGNT_API_KEY` in your environment, or complete browser login when the gateway prompts you.
57
+
58
+ ## API coverage
59
+
60
+ | | |
61
+ | --- | --- |
62
+ | **Integration** | `exa` |
63
+ | **REST base** | `https://api.superagnt.com/v1/connections/exa` |
64
+ | **OpenAPI operations** | 4 |
65
+ | **OpenClaw tools** | 4 |
66
+
67
+ ## Documentation
68
+
69
+ - [Documentation](https://superagnt.com) — getting started and concepts
70
+ - [Connection guide (Exa)](https://superagnt.com/connections/exa) — setup and endpoints
71
+ - [Dashboard](https://app.superagnt.com/dashboard) — API keys and usage
72
+
73
+ ## Related
74
+
75
+ - **Skill (curl-oriented):** `clawhub install exa-search` on [ClawHub](https://clawhub.ai/superagnt/skills/exa-search)
76
+
77
+ ## License
78
+
79
+ MIT
package/index.js ADDED
@@ -0,0 +1,546 @@
1
+ /**
2
+ * Exa Integration — 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/connections/exa'
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 Exa integration (exa)."
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'
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
+ '[exa-search] 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_connection_exa_search',
306
+ description: "Search",
307
+ inputSchema: {
308
+ "type": "object",
309
+ "properties": {
310
+ "query": {
311
+ "type": "string",
312
+ "description": "The query string for the search."
313
+ },
314
+ "additionalQueries": {
315
+ "type": "array",
316
+ "description": "Additional query variations for deep-search variants. Used alongside the main query for more comprehensive results."
317
+ },
318
+ "stream": {
319
+ "type": "boolean",
320
+ "description": "If true, the response is returned as an SSE stream of OpenAI-compatible chat completion chunks."
321
+ },
322
+ "outputSchema": {
323
+ "type": "object",
324
+ "description": "JSON schema for synthesized output. When provided, the response includes an output object and output.content matches this schema."
325
+ },
326
+ "systemPrompt": {
327
+ "type": "string",
328
+ "description": "Instructions that guide synthesized output and, for deep-search variants, search planning."
329
+ },
330
+ "type": {
331
+ "type": "string",
332
+ "description": "Search strategy. `auto` intelligently combines neural and other search methods; `fast` uses streamlined models; `deep-lite` / `deep` / `deep-reasoning` are synthesized-output modes; `instant` is the lowest-latency mode."
333
+ },
334
+ "category": {
335
+ "type": "string",
336
+ "description": "A data category to focus on. `people` and `company` support a limited filter set — `startPublishedDate`, `endPublishedDate`, `startCrawlDate`, `endCrawlDate`, and `excludeDomains` are not allowed; `includeDomains` for `people` only accepts LinkedIn domains."
337
+ },
338
+ "userLocation": {
339
+ "type": "string",
340
+ "description": "Two-letter ISO country code of the user (e.g. `US`)."
341
+ },
342
+ "numResults": {
343
+ "type": "integer",
344
+ "description": "Number of results to return. Max 100 for `neural` and deep-search variants."
345
+ },
346
+ "includeDomains": {
347
+ "type": "array",
348
+ "description": "Restrict results to these domains."
349
+ },
350
+ "excludeDomains": {
351
+ "type": "array",
352
+ "description": "Exclude these domains from results."
353
+ },
354
+ "startCrawlDate": {
355
+ "type": "string",
356
+ "description": "Only include links crawled after this ISO 8601 date."
357
+ },
358
+ "endCrawlDate": {
359
+ "type": "string",
360
+ "description": "Only include links crawled before this ISO 8601 date."
361
+ },
362
+ "startPublishedDate": {
363
+ "type": "string",
364
+ "description": "Only include links with a published date after this ISO 8601 date."
365
+ },
366
+ "endPublishedDate": {
367
+ "type": "string",
368
+ "description": "Only include links with a published date before this ISO 8601 date."
369
+ },
370
+ "moderation": {
371
+ "type": "boolean",
372
+ "description": "Enable content moderation to filter unsafe content from results."
373
+ },
374
+ "contents": {
375
+ "type": "object",
376
+ "description": "Content enrichment options returned alongside search results. See /contents for the full schema."
377
+ }
378
+ },
379
+ "required": [
380
+ "query"
381
+ ]
382
+ },
383
+ execute: async (input) => {
384
+ const key = await ensureApiKey()
385
+ const params = parseToolInput(input)
386
+ return callSuperagnt(key, 'POST', '/search', undefined, params)
387
+ },
388
+ })
389
+
390
+ api.registerTool({
391
+ name: 'superagnt_connection_exa_getContents',
392
+ description: "Get contents",
393
+ inputSchema: {
394
+ "type": "object",
395
+ "properties": {
396
+ "urls": {
397
+ "type": "array",
398
+ "description": "URLs to crawl (backwards compatible with `ids`)."
399
+ },
400
+ "ids": {
401
+ "type": "array",
402
+ "description": "Document IDs obtained from previous searches."
403
+ },
404
+ "text": {
405
+ "type": "string",
406
+ "description": "Boolean or advanced text options object. Object form supports `maxCharacters`, `includeHtmlTags`, `verbosity` (`compact` | `standard` | `full`), `includeSections`, `excludeSections` (header | navigation | banner | body | sidebar | footer | metadata)."
407
+ },
408
+ "highlights": {
409
+ "type": "string",
410
+ "description": "Boolean or advanced highlights object. Object form supports `maxCharacters`, `query`."
411
+ },
412
+ "summary": {
413
+ "type": "object",
414
+ "description": "LLM-generated summary options."
415
+ },
416
+ "livecrawl": {
417
+ "type": "string",
418
+ "description": "Deprecated — use `maxAgeHours`."
419
+ },
420
+ "livecrawlTimeout": {
421
+ "type": "integer",
422
+ "description": "livecrawlTimeout"
423
+ },
424
+ "maxAgeHours": {
425
+ "type": "integer",
426
+ "description": "Max age of cached content in hours. Positive = livecrawl if older, 0 = always livecrawl, -1 = never livecrawl."
427
+ },
428
+ "subpages": {
429
+ "type": "integer",
430
+ "description": "subpages"
431
+ },
432
+ "subpageTarget": {
433
+ "type": "string",
434
+ "description": "String or array of strings."
435
+ },
436
+ "extras": {
437
+ "type": "object",
438
+ "description": "extras"
439
+ }
440
+ },
441
+ "required": [
442
+ "urls"
443
+ ]
444
+ },
445
+ execute: async (input) => {
446
+ const key = await ensureApiKey()
447
+ const params = parseToolInput(input)
448
+ return callSuperagnt(key, 'POST', '/contents', undefined, params)
449
+ },
450
+ })
451
+
452
+ api.registerTool({
453
+ name: 'superagnt_connection_exa_findSimilar',
454
+ description: "Find similar links",
455
+ inputSchema: {
456
+ "type": "object",
457
+ "properties": {
458
+ "url": {
459
+ "type": "string",
460
+ "description": "The URL to find similar links for."
461
+ },
462
+ "numResults": {
463
+ "type": "integer",
464
+ "description": "numResults"
465
+ },
466
+ "includeDomains": {
467
+ "type": "array",
468
+ "description": "includeDomains"
469
+ },
470
+ "excludeDomains": {
471
+ "type": "array",
472
+ "description": "excludeDomains"
473
+ },
474
+ "startCrawlDate": {
475
+ "type": "string",
476
+ "description": "startCrawlDate"
477
+ },
478
+ "endCrawlDate": {
479
+ "type": "string",
480
+ "description": "endCrawlDate"
481
+ },
482
+ "startPublishedDate": {
483
+ "type": "string",
484
+ "description": "startPublishedDate"
485
+ },
486
+ "endPublishedDate": {
487
+ "type": "string",
488
+ "description": "endPublishedDate"
489
+ },
490
+ "moderation": {
491
+ "type": "boolean",
492
+ "description": "moderation"
493
+ },
494
+ "contents": {
495
+ "type": "object",
496
+ "description": "Content enrichment options. Same shape as in /search and /contents."
497
+ }
498
+ },
499
+ "required": [
500
+ "url"
501
+ ]
502
+ },
503
+ execute: async (input) => {
504
+ const key = await ensureApiKey()
505
+ const params = parseToolInput(input)
506
+ return callSuperagnt(key, 'POST', '/findSimilar', undefined, params)
507
+ },
508
+ })
509
+
510
+ api.registerTool({
511
+ name: 'superagnt_connection_exa_answer',
512
+ description: "Generate an answer from search results",
513
+ inputSchema: {
514
+ "type": "object",
515
+ "properties": {
516
+ "query": {
517
+ "type": "string",
518
+ "description": "The question or query to answer."
519
+ },
520
+ "stream": {
521
+ "type": "boolean",
522
+ "description": "If true, returns an SSE stream."
523
+ },
524
+ "text": {
525
+ "type": "boolean",
526
+ "description": "If true, citations include full text content of each source."
527
+ },
528
+ "outputSchema": {
529
+ "type": "object",
530
+ "description": "JSON Schema Draft 7 for structured answers."
531
+ }
532
+ },
533
+ "required": [
534
+ "query"
535
+ ]
536
+ },
537
+ execute: async (input) => {
538
+ const key = await ensureApiKey()
539
+ const params = parseToolInput(input)
540
+ return callSuperagnt(key, 'POST', '/answer', undefined, params)
541
+ },
542
+ })
543
+
544
+ promptState.toolCount = 4
545
+ promptState.ready = true
546
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "exa-search",
3
+ "name": "Exa Integration",
4
+ "description": "OpenClaw plugin for Exa — run neural web search, fetch parsed contents, find similar URLs, and get agentic answers through your superagnt API key.",
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,50 @@
1
+ {
2
+ "name": "@superagnt/exa-search",
3
+ "version": "2.0.2",
4
+ "type": "module",
5
+ "description": "OpenClaw plugin for Exa — run neural web search, fetch parsed contents, find similar URLs, and get agentic answers through your superagnt API key.",
6
+ "main": "index.js",
7
+ "license": "MIT",
8
+ "homepage": "https://superagnt.com/r/npm-exa-search-docs",
9
+ "keywords": [
10
+ "openclaw",
11
+ "openclaw-plugin",
12
+ "superagnt",
13
+ "mcp",
14
+ "exa",
15
+ "integration",
16
+ "api",
17
+ "ai-agent",
18
+ "exa ai",
19
+ "neural search",
20
+ "web search api",
21
+ "semantic search",
22
+ "retrieval augmented generation",
23
+ "rag",
24
+ "llm search",
25
+ "agentic search",
26
+ "find similar",
27
+ "answer api"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "files": [
33
+ "index.js",
34
+ "openclaw.plugin.json",
35
+ "README.md"
36
+ ],
37
+ "openclaw": {
38
+ "extensions": [
39
+ "./index.js"
40
+ ],
41
+ "compat": {
42
+ "pluginApi": ">=2026.3.24-beta.2",
43
+ "minGatewayVersion": "2026.3.24-beta.2"
44
+ },
45
+ "build": {
46
+ "openclawVersion": "2026.3.24-beta.2",
47
+ "pluginSdkVersion": "2026.3.24-beta.2"
48
+ }
49
+ }
50
+ }