web-search-plus-plugin 1.2.3 → 1.3.1

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/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
1
2
  import { Type } from "@sinclair/typebox";
2
3
  import { spawnSync } from "child_process";
3
4
  import fs from "fs";
@@ -34,185 +35,191 @@ function loadEnvFile(envPath: string): Record<string, string> {
34
35
  const PLUGIN_DIR = getPluginDir();
35
36
  const scriptPath = path.join(PLUGIN_DIR, "scripts", "search.py");
36
37
 
37
- export default function (api: any) {
38
- // Bridge OpenClaw config fields to env vars expected by search.py
39
- const configEnv: Record<string, string> = {};
40
- const pluginConfig: Record<string, string> = (api as any)?.config ?? {};
41
- const configKeyMap: Record<string, string> = {
42
- serperApiKey: "SERPER_API_KEY",
43
- tavilyApiKey: "TAVILY_API_KEY",
44
- queritApiKey: "QUERIT_API_KEY",
45
- exaApiKey: "EXA_API_KEY",
46
- perplexityApiKey: "PERPLEXITY_API_KEY",
47
- kilocodeApiKey: "KILOCODE_API_KEY",
48
- youApiKey: "YOU_API_KEY",
49
- searxngInstanceUrl: "SEARXNG_INSTANCE_URL",
50
- };
51
- for (const [cfgKey, envKey] of Object.entries(configKeyMap)) {
52
- const val = pluginConfig[cfgKey];
53
- if (val && typeof val === "string") configEnv[envKey] = val;
54
- }
55
-
56
- api.registerTool(
57
- {
58
- name: "web_search_plus",
59
- description:
60
- "Search the web using multi-provider intelligent routing (Serper/Google, Tavily/Research, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG). Automatically selects the best provider based on query intent. Use for ALL web searches. Set depth='deep' for multi-source synthesis, 'deep-reasoning' for complex cross-document analysis.",
61
- parameters: Type.Object({
62
- query: Type.String({ description: "Search query" }),
63
- provider: Type.Optional(
64
- Type.Union(
65
- [
66
- Type.Literal("serper"),
67
- Type.Literal("tavily"),
68
- Type.Literal("querit"),
69
- Type.Literal("exa"),
70
- Type.Literal("perplexity"),
71
- Type.Literal("you"),
72
- Type.Literal("searxng"),
73
- Type.Literal("auto"),
74
- ],
75
- {
76
- description:
77
- "Force a specific provider, or 'auto' for smart routing (default: auto)",
78
- },
38
+ export default definePluginEntry({
39
+ id: "web-search-plus-plugin",
40
+ name: "Web Search Plus",
41
+ description:
42
+ "Multi-provider web search (Serper/Google, Tavily, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG) with intelligent auto-routing",
43
+ register(api) {
44
+ // Bridge OpenClaw config fields to env vars expected by search.py
45
+ const configEnv: Record<string, string> = {};
46
+ const pluginConfig: Record<string, string> = (api.pluginConfig ?? {}) as Record<string, string>;
47
+ const configKeyMap: Record<string, string> = {
48
+ serperApiKey: "SERPER_API_KEY",
49
+ tavilyApiKey: "TAVILY_API_KEY",
50
+ queritApiKey: "QUERIT_API_KEY",
51
+ exaApiKey: "EXA_API_KEY",
52
+ perplexityApiKey: "PERPLEXITY_API_KEY",
53
+ kilocodeApiKey: "KILOCODE_API_KEY",
54
+ youApiKey: "YOU_API_KEY",
55
+ searxngInstanceUrl: "SEARXNG_INSTANCE_URL",
56
+ };
57
+ for (const [cfgKey, envKey] of Object.entries(configKeyMap)) {
58
+ const val = pluginConfig[cfgKey];
59
+ if (val && typeof val === "string") configEnv[envKey] = val;
60
+ }
61
+
62
+ api.registerTool(
63
+ {
64
+ name: "web_search_plus",
65
+ description:
66
+ "Search the web using multi-provider intelligent routing (Serper/Google, Tavily/Research, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG). Automatically selects the best provider based on query intent. Use for ALL web searches. Set depth='deep' for multi-source synthesis, 'deep-reasoning' for complex cross-document analysis.",
67
+ parameters: Type.Object({
68
+ query: Type.String({ description: "Search query" }),
69
+ provider: Type.Optional(
70
+ Type.Union(
71
+ [
72
+ Type.Literal("serper"),
73
+ Type.Literal("tavily"),
74
+ Type.Literal("querit"),
75
+ Type.Literal("exa"),
76
+ Type.Literal("perplexity"),
77
+ Type.Literal("you"),
78
+ Type.Literal("searxng"),
79
+ Type.Literal("auto"),
80
+ ],
81
+ {
82
+ description:
83
+ "Force a specific provider, or 'auto' for smart routing (default: auto)",
84
+ },
85
+ ),
86
+ ),
87
+ count: Type.Optional(
88
+ Type.Number({ description: "Number of results (default: 5)" }),
89
+ ),
90
+ depth: Type.Optional(
91
+ Type.Union(
92
+ [
93
+ Type.Literal("normal"),
94
+ Type.Literal("deep"),
95
+ Type.Literal("deep-reasoning"),
96
+ ],
97
+ {
98
+ description:
99
+ "Exa search depth: 'deep' synthesizes across sources (4-12s), 'deep-reasoning' for complex cross-reference analysis (12-50s). When provider is auto, depth may be auto-selected based on query complexity.",
100
+ },
101
+ ),
102
+ ),
103
+ time_range: Type.Optional(
104
+ Type.Union(
105
+ [
106
+ Type.Literal("day"),
107
+ Type.Literal("week"),
108
+ Type.Literal("month"),
109
+ Type.Literal("year"),
110
+ ],
111
+ {
112
+ description:
113
+ "Filter results by recency. Applies to Serper (as tbs), Perplexity (as search_recency_filter), Tavily/You.com (as freshness). Useful for news and current events.",
114
+ },
115
+ ),
79
116
  ),
80
- ),
81
- count: Type.Optional(
82
- Type.Number({ description: "Number of results (default: 5)" }),
83
- ),
84
- depth: Type.Optional(
85
- Type.Union(
86
- [
87
- Type.Literal("normal"),
88
- Type.Literal("deep"),
89
- Type.Literal("deep-reasoning"),
90
- ],
91
- {
117
+ include_domains: Type.Optional(
118
+ Type.Array(Type.String(), {
92
119
  description:
93
- "Exa search depth: 'deep' synthesizes across sources (4-12s), 'deep-reasoning' for complex cross-reference analysis (12-50s). When provider is auto, depth may be auto-selected based on query complexity.",
94
- },
120
+ "Only include results from these domains (e.g. ['arxiv.org', 'github.com']). Supported by Tavily and Exa.",
121
+ }),
95
122
  ),
96
- ),
97
- time_range: Type.Optional(
98
- Type.Union(
99
- [
100
- Type.Literal("day"),
101
- Type.Literal("week"),
102
- Type.Literal("month"),
103
- Type.Literal("year"),
104
- ],
105
- {
123
+ exclude_domains: Type.Optional(
124
+ Type.Array(Type.String(), {
106
125
  description:
107
- "Filter results by recency. Applies to Serper (as tbs), Perplexity (as search_recency_filter), Tavily/You.com (as freshness). Useful for news and current events.",
108
- },
126
+ "Exclude results from these domains (e.g. ['reddit.com', 'pinterest.com']). Supported by Tavily and Exa.",
127
+ }),
109
128
  ),
110
- ),
111
- include_domains: Type.Optional(
112
- Type.Array(Type.String(), {
113
- description:
114
- "Only include results from these domains (e.g. ['arxiv.org', 'github.com']). Supported by Tavily and Exa.",
115
- }),
116
- ),
117
- exclude_domains: Type.Optional(
118
- Type.Array(Type.String(), {
119
- description:
120
- "Exclude results from these domains (e.g. ['reddit.com', 'pinterest.com']). Supported by Tavily and Exa.",
121
- }),
122
- ),
123
- }),
124
- async execute(
125
- _id: string,
126
- params: {
127
- query: string;
128
- provider?: string;
129
- count?: number;
130
- depth?: string;
131
- time_range?: string;
132
- include_domains?: string[];
133
- exclude_domains?: string[];
134
- },
135
- ) {
136
- const args = [scriptPath, "--query", params.query, "--compact"];
137
-
138
- if (params.provider && params.provider !== "auto") {
139
- args.push("--provider", params.provider);
140
- }
141
-
142
- if (typeof params.count === "number" && Number.isFinite(params.count)) {
143
- args.push(
144
- "--max-results",
145
- String(Math.max(1, Math.floor(params.count))),
146
- );
147
- }
148
-
149
- if (params.depth && params.depth !== "normal") {
150
- args.push("--exa-depth", params.depth);
151
- }
152
-
153
- if (params.time_range) {
154
- args.push("--time-range", params.time_range);
155
- args.push("--freshness", params.time_range);
156
- }
157
-
158
- if (params.include_domains?.length) {
159
- args.push("--include-domains", ...params.include_domains);
160
- }
161
-
162
- if (params.exclude_domains?.length) {
163
- args.push("--exclude-domains", ...params.exclude_domains);
164
- }
165
-
166
- const envPaths = [
167
- path.join(PLUGIN_DIR, ".env"),
168
- path.join(PLUGIN_DIR, "..", "web-search-plus", ".env"),
169
- ];
170
- const fileEnv: Record<string, string> = {};
171
- for (const envPath of envPaths) {
172
- Object.assign(fileEnv, loadEnvFile(envPath));
173
- }
174
- const childEnv = { ...process.env, ...configEnv, ...fileEnv };
175
-
176
- try {
177
- const child = spawnSync("python3", args, {
178
- timeout: 75000,
179
- env: childEnv,
180
- shell: false,
181
- encoding: "utf8",
182
- });
183
-
184
- if (child.error) {
185
- return {
186
- content: [
187
- { type: "text", text: `Search failed: ${child.error.message}` },
188
- ],
189
- };
129
+ }),
130
+ async execute(
131
+ _id: string,
132
+ params: {
133
+ query: string;
134
+ provider?: string;
135
+ count?: number;
136
+ depth?: string;
137
+ time_range?: string;
138
+ include_domains?: string[];
139
+ exclude_domains?: string[];
140
+ },
141
+ ) {
142
+ const args = [scriptPath, "--query", params.query, "--compact"];
143
+
144
+ if (params.provider && params.provider !== "auto") {
145
+ args.push("--provider", params.provider);
146
+ }
147
+
148
+ if (typeof params.count === "number" && Number.isFinite(params.count)) {
149
+ args.push(
150
+ "--max-results",
151
+ String(Math.max(1, Math.floor(params.count))),
152
+ );
190
153
  }
191
154
 
192
- if (child.status !== 0) {
193
- const stderr = child.stderr?.trim() || "Unknown error";
155
+ if (params.depth && params.depth !== "normal") {
156
+ args.push("--exa-depth", params.depth);
157
+ }
158
+
159
+ if (params.time_range) {
160
+ args.push("--time-range", params.time_range);
161
+ args.push("--freshness", params.time_range);
162
+ }
163
+
164
+ if (params.include_domains?.length) {
165
+ args.push("--include-domains", ...params.include_domains);
166
+ }
167
+
168
+ if (params.exclude_domains?.length) {
169
+ args.push("--exclude-domains", ...params.exclude_domains);
170
+ }
171
+
172
+ const envPaths = [
173
+ path.join(PLUGIN_DIR, ".env"),
174
+ path.join(PLUGIN_DIR, "..", "web-search-plus", ".env"),
175
+ ];
176
+ const fileEnv: Record<string, string> = {};
177
+ for (const envPath of envPaths) {
178
+ Object.assign(fileEnv, loadEnvFile(envPath));
179
+ }
180
+ const childEnv = { ...process.env, ...configEnv, ...fileEnv };
181
+
182
+ try {
183
+ const child = spawnSync("python3", args, {
184
+ timeout: 75000,
185
+ env: childEnv,
186
+ shell: false,
187
+ encoding: "utf8",
188
+ });
189
+
190
+ if (child.error) {
191
+ return {
192
+ content: [
193
+ { type: "text", text: `Search failed: ${child.error.message}` },
194
+ ],
195
+ };
196
+ }
197
+
198
+ if (child.status !== 0) {
199
+ const stderr = child.stderr?.trim() || "Unknown error";
200
+ return {
201
+ content: [
202
+ {
203
+ type: "text",
204
+ text: `Search failed (exit ${child.status}): ${stderr}`,
205
+ },
206
+ ],
207
+ };
208
+ }
209
+
210
+ return {
211
+ content: [{ type: "text", text: child.stdout?.trim() || "{}" }],
212
+ };
213
+ } catch (err: any) {
194
214
  return {
195
215
  content: [
196
- {
197
- type: "text",
198
- text: `Search failed (exit ${child.status}): ${stderr}`,
199
- },
216
+ { type: "text", text: `Search failed: ${err?.message ?? err}` },
200
217
  ],
201
218
  };
202
219
  }
203
-
204
- return {
205
- content: [{ type: "text", text: child.stdout?.trim() || "{}" }],
206
- };
207
- } catch (err: any) {
208
- return {
209
- content: [
210
- { type: "text", text: `Search failed: ${err?.message ?? err}` },
211
- ],
212
- };
213
- }
220
+ },
214
221
  },
215
- },
216
- { optional: true },
217
- );
218
- }
222
+ { optional: true },
223
+ );
224
+ },
225
+ });
@@ -1,8 +1,7 @@
1
1
  {
2
2
  "id": "web-search-plus-plugin",
3
- "kind": "skill",
4
3
  "name": "Web Search Plus",
5
- "version": "1.2.3",
4
+ "version": "1.3.1",
6
5
  "description": "Multi-provider web search (Serper/Google, Tavily, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG) with intelligent auto-routing",
7
6
  "configSchema": {
8
7
  "type": "object",
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "web-search-plus-plugin",
3
- "version": "1.2.3",
3
+ "version": "1.3.1",
4
4
  "description": "OpenClaw plugin: multi-provider web search (Serper/Google, Tavily, Querit/Multilingual AI Search, Exa/Neural+Deep, Perplexity, You.com, SearXNG) with intelligent auto-routing",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
+ "openclaw": {
8
+ "extensions": ["./index.ts"]
9
+ },
7
10
  "files": [
8
11
  "index.ts",
9
12
  "openclaw.plugin.json",
@@ -13,6 +16,9 @@
13
16
  "LICENSE"
14
17
  ],
15
18
  "keywords": ["openclaw", "plugin", "search", "serper", "tavily", "querit", "exa", "exa-deep", "perplexity", "you", "searxng", "web-search", "auto-routing"],
19
+ "peerDependencies": {
20
+ "openclaw": ">=2026.3.22"
21
+ },
16
22
  "repository": {
17
23
  "type": "git",
18
24
  "url": "https://github.com/robbyczgw-cla/web-search-plus-plugin"