@openclaw/searxng-plugin 0.0.0 → 2026.6.11
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 +12 -2
- package/dist/index.js +13 -0
- package/dist/searxng-client-BFVWmp9D.js +219 -0
- package/dist/searxng-search-provider-Da4Go6Vh.js +80 -0
- package/dist/web-search-provider.js +2 -0
- package/npm-shrinkwrap.json +12 -0
- package/openclaw.plugin.json +45 -0
- package/package.json +45 -7
package/README.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
-
#
|
|
1
|
+
# SearXNG OpenClaw plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official OpenClaw plugin for SearXNG.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
openclaw plugins install @openclaw/searxng-plugin
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Docs
|
|
12
|
+
|
|
13
|
+
See `docs/tools/searxng-search.md` in the OpenClaw repository, or the published docs at `https://docs.openclaw.ai/tools/searxng-search`.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { t as createSearxngWebSearchProvider } from "./searxng-search-provider-Da4Go6Vh.js";
|
|
2
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
//#region extensions/searxng/index.ts
|
|
4
|
+
var searxng_default = definePluginEntry({
|
|
5
|
+
id: "searxng",
|
|
6
|
+
name: "SearXNG Plugin",
|
|
7
|
+
description: "Bundled SearXNG web search plugin",
|
|
8
|
+
register(api) {
|
|
9
|
+
api.registerWebSearchProvider(createSearxngWebSearchProvider());
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
//#endregion
|
|
13
|
+
export { searxng_default as default };
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { DEFAULT_CACHE_TTL_MINUTES, DEFAULT_SEARCH_COUNT, normalizeCacheKey, readCache, readResponseText, resolveCacheTtlMs, resolveSearchCount, resolveSiteName, resolveTimeoutSeconds, withSelfHostedWebSearchEndpoint, withTrustedWebSearchEndpoint, wrapWebContent, writeCache } from "openclaw/plugin-sdk/provider-web-search";
|
|
2
|
+
import { assertHttpUrlTargetsPrivateNetwork, isBlockedHostnameOrIp, isPrivateIpAddress, resolvePinnedHostnameWithPolicy } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
3
|
+
import { normalizeResolvedSecretInputString, normalizeSecretInput } from "openclaw/plugin-sdk/secret-input";
|
|
4
|
+
//#region extensions/searxng/src/config.ts
|
|
5
|
+
function normalizeConfiguredString(value, path) {
|
|
6
|
+
try {
|
|
7
|
+
return normalizeSecretInput(normalizeResolvedSecretInputString({
|
|
8
|
+
value,
|
|
9
|
+
path
|
|
10
|
+
}));
|
|
11
|
+
} catch {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function readInlineEnvSecretRefValue(value, env) {
|
|
16
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
17
|
+
const record = value;
|
|
18
|
+
if (record.source !== "env" || typeof record.id !== "string") return;
|
|
19
|
+
return normalizeSecretInput(env[record.id]);
|
|
20
|
+
}
|
|
21
|
+
function normalizeTrimmedString(value) {
|
|
22
|
+
if (typeof value !== "string") return;
|
|
23
|
+
return value.trim() || void 0;
|
|
24
|
+
}
|
|
25
|
+
function normalizeBaseUrl(value) {
|
|
26
|
+
return value?.replace(/\/+$/u, "") || void 0;
|
|
27
|
+
}
|
|
28
|
+
function resolveSearxngWebSearchConfig(config) {
|
|
29
|
+
const webSearch = (config?.plugins?.entries?.searxng?.config)?.webSearch;
|
|
30
|
+
if (webSearch && typeof webSearch === "object" && !Array.isArray(webSearch)) return webSearch;
|
|
31
|
+
}
|
|
32
|
+
function resolveSearxngBaseUrl(config, env = process.env) {
|
|
33
|
+
const webSearch = resolveSearxngWebSearchConfig(config);
|
|
34
|
+
return normalizeBaseUrl(normalizeConfiguredString(webSearch?.baseUrl, "plugins.entries.searxng.config.webSearch.baseUrl")) ?? normalizeBaseUrl(readInlineEnvSecretRefValue(webSearch?.baseUrl, env)) ?? normalizeBaseUrl(normalizeSecretInput(env.SEARXNG_BASE_URL));
|
|
35
|
+
}
|
|
36
|
+
function resolveSearxngCategories(config) {
|
|
37
|
+
return normalizeTrimmedString(resolveSearxngWebSearchConfig(config)?.categories);
|
|
38
|
+
}
|
|
39
|
+
function resolveSearxngLanguage(config) {
|
|
40
|
+
return normalizeTrimmedString(resolveSearxngWebSearchConfig(config)?.language);
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region extensions/searxng/src/searxng-client.ts
|
|
44
|
+
const DEFAULT_TIMEOUT_SECONDS = 20;
|
|
45
|
+
const MAX_RESPONSE_BYTES = 1e6;
|
|
46
|
+
const SEARXNG_SEARCH_CACHE = /* @__PURE__ */ new Map();
|
|
47
|
+
function normalizeSearxngResult(value) {
|
|
48
|
+
if (!value || typeof value !== "object") return null;
|
|
49
|
+
const candidate = value;
|
|
50
|
+
if (typeof candidate.url !== "string" || typeof candidate.title !== "string") return null;
|
|
51
|
+
return {
|
|
52
|
+
url: candidate.url,
|
|
53
|
+
title: candidate.title,
|
|
54
|
+
content: typeof candidate.content === "string" ? candidate.content : void 0,
|
|
55
|
+
img_src: typeof candidate.img_src === "string" ? candidate.img_src : void 0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function buildSearxngSearchUrl(params) {
|
|
59
|
+
const url = new URL(params.baseUrl);
|
|
60
|
+
url.pathname = url.pathname.endsWith("/") ? `${url.pathname}search` : `${url.pathname}/search`;
|
|
61
|
+
url.search = "";
|
|
62
|
+
url.searchParams.set("q", params.query);
|
|
63
|
+
url.searchParams.set("format", "json");
|
|
64
|
+
if (params.categories) url.searchParams.set("categories", params.categories);
|
|
65
|
+
if (params.language) url.searchParams.set("language", params.language);
|
|
66
|
+
return url.toString();
|
|
67
|
+
}
|
|
68
|
+
function shouldRetryEmptyCategorySearchWithGeneral(categories) {
|
|
69
|
+
if (!categories) return false;
|
|
70
|
+
const normalized = categories.split(",").map((category) => category.trim().toLowerCase()).filter((category) => category.length > 0);
|
|
71
|
+
return normalized.length > 0 && !normalized.includes("general");
|
|
72
|
+
}
|
|
73
|
+
async function searxngEndpointTargetsPrivateNetwork(url, lookupFn) {
|
|
74
|
+
if (isBlockedHostnameOrIp(url.hostname)) return true;
|
|
75
|
+
try {
|
|
76
|
+
return (await resolvePinnedHostnameWithPolicy(url.hostname, {
|
|
77
|
+
lookupFn,
|
|
78
|
+
policy: {
|
|
79
|
+
allowPrivateNetwork: true,
|
|
80
|
+
allowRfc2544BenchmarkRange: true
|
|
81
|
+
}
|
|
82
|
+
})).addresses.every((address) => isPrivateIpAddress(address));
|
|
83
|
+
} catch {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async function validateSearxngBaseUrl(baseUrl, lookupFn) {
|
|
88
|
+
let parsed;
|
|
89
|
+
try {
|
|
90
|
+
parsed = new URL(baseUrl);
|
|
91
|
+
} catch {
|
|
92
|
+
throw new Error("SearXNG base URL must be a valid http:// or https:// URL.");
|
|
93
|
+
}
|
|
94
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("SearXNG base URL must use http:// or https://.");
|
|
95
|
+
if (parsed.protocol === "http:") {
|
|
96
|
+
await assertHttpUrlTargetsPrivateNetwork(parsed.toString(), {
|
|
97
|
+
dangerouslyAllowPrivateNetwork: true,
|
|
98
|
+
lookupFn,
|
|
99
|
+
errorMessage: "SearXNG HTTP base URL must target a trusted private or loopback host. Use https:// for public hosts."
|
|
100
|
+
});
|
|
101
|
+
return "selfHosted";
|
|
102
|
+
}
|
|
103
|
+
return await searxngEndpointTargetsPrivateNetwork(parsed, lookupFn) ? "selfHosted" : "strict";
|
|
104
|
+
}
|
|
105
|
+
function parseSearxngResponseText(text, count) {
|
|
106
|
+
let parsed;
|
|
107
|
+
try {
|
|
108
|
+
parsed = JSON.parse(text);
|
|
109
|
+
} catch {
|
|
110
|
+
throw new Error("SearXNG returned invalid JSON.");
|
|
111
|
+
}
|
|
112
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
113
|
+
const response = parsed;
|
|
114
|
+
const rawResults = Array.isArray(response.results) ? response.results : [];
|
|
115
|
+
const results = [];
|
|
116
|
+
for (const rawResult of rawResults) {
|
|
117
|
+
const result = normalizeSearxngResult(rawResult);
|
|
118
|
+
if (result) results.push(result);
|
|
119
|
+
if (results.length >= count) break;
|
|
120
|
+
}
|
|
121
|
+
return results;
|
|
122
|
+
}
|
|
123
|
+
async function fetchSearxngResults(params) {
|
|
124
|
+
const url = buildSearxngSearchUrl({
|
|
125
|
+
baseUrl: params.baseUrl,
|
|
126
|
+
query: params.query,
|
|
127
|
+
categories: params.categories,
|
|
128
|
+
language: params.language
|
|
129
|
+
});
|
|
130
|
+
return await (params.endpointMode === "selfHosted" ? withSelfHostedWebSearchEndpoint : withTrustedWebSearchEndpoint)({
|
|
131
|
+
url,
|
|
132
|
+
timeoutSeconds: params.timeoutSeconds,
|
|
133
|
+
init: {
|
|
134
|
+
method: "GET",
|
|
135
|
+
headers: { Accept: "application/json" }
|
|
136
|
+
}
|
|
137
|
+
}, async (response) => {
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
const detail = (await readResponseText(response, { maxBytes: 64e3 })).text;
|
|
140
|
+
throw new Error(`SearXNG search error (${response.status}): ${detail || response.statusText}`);
|
|
141
|
+
}
|
|
142
|
+
const body = await readResponseText(response, { maxBytes: MAX_RESPONSE_BYTES });
|
|
143
|
+
if (body.truncated) throw new Error("SearXNG response too large.");
|
|
144
|
+
return parseSearxngResponseText(body.text, params.count);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
async function runSearxngSearch(params) {
|
|
148
|
+
const count = resolveSearchCount(params.count, DEFAULT_SEARCH_COUNT);
|
|
149
|
+
const categories = params.categories ?? resolveSearxngCategories(params.config);
|
|
150
|
+
const language = params.language ?? resolveSearxngLanguage(params.config);
|
|
151
|
+
const baseUrl = params.baseUrl ?? resolveSearxngBaseUrl(params.config);
|
|
152
|
+
const timeoutSeconds = resolveTimeoutSeconds(params.timeoutSeconds, DEFAULT_TIMEOUT_SECONDS);
|
|
153
|
+
const cacheTtlMs = resolveCacheTtlMs(params.cacheTtlMinutes, DEFAULT_CACHE_TTL_MINUTES);
|
|
154
|
+
if (!baseUrl) throw new Error("SearXNG base URL is not configured. Set SEARXNG_BASE_URL or configure plugins.entries.searxng.config.webSearch.baseUrl.");
|
|
155
|
+
const endpointMode = await validateSearxngBaseUrl(baseUrl);
|
|
156
|
+
const cacheKey = normalizeCacheKey(JSON.stringify({
|
|
157
|
+
provider: "searxng",
|
|
158
|
+
query: params.query,
|
|
159
|
+
count,
|
|
160
|
+
categories: categories ?? "",
|
|
161
|
+
language: language ?? "",
|
|
162
|
+
baseUrl
|
|
163
|
+
}));
|
|
164
|
+
const cached = readCache(SEARXNG_SEARCH_CACHE, cacheKey);
|
|
165
|
+
if (cached) return {
|
|
166
|
+
...cached.value,
|
|
167
|
+
cached: true
|
|
168
|
+
};
|
|
169
|
+
const startedAt = Date.now();
|
|
170
|
+
let results = await fetchSearxngResults({
|
|
171
|
+
baseUrl,
|
|
172
|
+
query: params.query,
|
|
173
|
+
categories,
|
|
174
|
+
language,
|
|
175
|
+
timeoutSeconds,
|
|
176
|
+
count,
|
|
177
|
+
endpointMode
|
|
178
|
+
});
|
|
179
|
+
if (results.length === 0 && shouldRetryEmptyCategorySearchWithGeneral(categories)) results = await fetchSearxngResults({
|
|
180
|
+
baseUrl,
|
|
181
|
+
query: params.query,
|
|
182
|
+
categories: "general",
|
|
183
|
+
language,
|
|
184
|
+
timeoutSeconds,
|
|
185
|
+
count,
|
|
186
|
+
endpointMode
|
|
187
|
+
});
|
|
188
|
+
const payload = {
|
|
189
|
+
query: params.query,
|
|
190
|
+
provider: "searxng",
|
|
191
|
+
count: results.length,
|
|
192
|
+
tookMs: Date.now() - startedAt,
|
|
193
|
+
externalContent: {
|
|
194
|
+
untrusted: true,
|
|
195
|
+
source: "web_search",
|
|
196
|
+
provider: "searxng",
|
|
197
|
+
wrapped: true
|
|
198
|
+
},
|
|
199
|
+
results: results.map((result) => ({
|
|
200
|
+
title: wrapWebContent(result.title, "web_search"),
|
|
201
|
+
url: result.url,
|
|
202
|
+
snippet: result.content ? wrapWebContent(result.content, "web_search") : "",
|
|
203
|
+
siteName: resolveSiteName(result.url) || void 0,
|
|
204
|
+
img_src: result.img_src || void 0
|
|
205
|
+
}))
|
|
206
|
+
};
|
|
207
|
+
writeCache(SEARXNG_SEARCH_CACHE, cacheKey, payload, cacheTtlMs);
|
|
208
|
+
return payload;
|
|
209
|
+
}
|
|
210
|
+
const testing = {
|
|
211
|
+
buildSearxngSearchUrl,
|
|
212
|
+
normalizeSearxngResult,
|
|
213
|
+
parseSearxngResponseText,
|
|
214
|
+
shouldRetryEmptyCategorySearchWithGeneral,
|
|
215
|
+
validateSearxngBaseUrl,
|
|
216
|
+
SEARXNG_SEARCH_CACHE
|
|
217
|
+
};
|
|
218
|
+
//#endregion
|
|
219
|
+
export { testing as __testing, testing, runSearxngSearch };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { readPositiveIntegerParam, readStringParam } from "openclaw/plugin-sdk/param-readers";
|
|
2
|
+
import { createWebSearchProviderContractFields } from "openclaw/plugin-sdk/provider-web-search-contract";
|
|
3
|
+
//#region extensions/searxng/src/searxng-search-provider.ts
|
|
4
|
+
const SEARXNG_CREDENTIAL_PATH = "plugins.entries.searxng.config.webSearch.baseUrl";
|
|
5
|
+
let searxngClientModulePromise;
|
|
6
|
+
function loadSearxngClientModule() {
|
|
7
|
+
searxngClientModulePromise ??= import("./searxng-client-BFVWmp9D.js");
|
|
8
|
+
return searxngClientModulePromise;
|
|
9
|
+
}
|
|
10
|
+
const SearxngSearchSchema = {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
query: {
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Search query string."
|
|
16
|
+
},
|
|
17
|
+
count: {
|
|
18
|
+
type: "integer",
|
|
19
|
+
description: "Number of results to return (1-10).",
|
|
20
|
+
minimum: 1,
|
|
21
|
+
maximum: 10
|
|
22
|
+
},
|
|
23
|
+
categories: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Optional comma-separated search categories such as general, news, or science."
|
|
26
|
+
},
|
|
27
|
+
language: {
|
|
28
|
+
type: "string",
|
|
29
|
+
description: "Optional language code for results such as en, de, or fr."
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
additionalProperties: false
|
|
33
|
+
};
|
|
34
|
+
function createSearxngWebSearchProvider() {
|
|
35
|
+
return {
|
|
36
|
+
id: "searxng",
|
|
37
|
+
label: "SearXNG Search",
|
|
38
|
+
hint: "Self-hosted meta-search with no API key required",
|
|
39
|
+
onboardingScopes: ["text-inference"],
|
|
40
|
+
requiresCredential: true,
|
|
41
|
+
credentialLabel: "SearXNG Base URL",
|
|
42
|
+
envVars: ["SEARXNG_BASE_URL"],
|
|
43
|
+
placeholder: "http://localhost:8080",
|
|
44
|
+
signupUrl: "https://docs.searxng.org/",
|
|
45
|
+
autoDetectOrder: 200,
|
|
46
|
+
credentialPath: SEARXNG_CREDENTIAL_PATH,
|
|
47
|
+
...createWebSearchProviderContractFields({
|
|
48
|
+
credentialPath: SEARXNG_CREDENTIAL_PATH,
|
|
49
|
+
searchCredential: {
|
|
50
|
+
type: "scoped",
|
|
51
|
+
scopeId: "searxng"
|
|
52
|
+
},
|
|
53
|
+
configuredCredential: {
|
|
54
|
+
pluginId: "searxng",
|
|
55
|
+
field: "baseUrl"
|
|
56
|
+
},
|
|
57
|
+
selectionPluginId: "searxng"
|
|
58
|
+
}),
|
|
59
|
+
credentialNote: ["For the SearXNG JSON API to work, make sure your SearXNG instance", "has the json format enabled in its settings.yml under search.formats."].join("\n"),
|
|
60
|
+
createTool: (ctx) => ({
|
|
61
|
+
description: "Search the web using a self-hosted SearXNG instance. Returns titles, URLs, and snippets.",
|
|
62
|
+
parameters: SearxngSearchSchema,
|
|
63
|
+
execute: async (args) => {
|
|
64
|
+
const { runSearxngSearch } = await loadSearxngClientModule();
|
|
65
|
+
return await runSearxngSearch({
|
|
66
|
+
config: ctx.config,
|
|
67
|
+
query: readStringParam(args, "query", { required: true }),
|
|
68
|
+
count: readPositiveIntegerParam(args, "count", {
|
|
69
|
+
max: 10,
|
|
70
|
+
message: "count must be an integer from 1 to 10."
|
|
71
|
+
}),
|
|
72
|
+
categories: readStringParam(args, "categories"),
|
|
73
|
+
language: readStringParam(args, "language")
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
80
|
+
export { createSearxngWebSearchProvider as t };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "searxng",
|
|
3
|
+
"icon": "https://cdn.simpleicons.org/searxng",
|
|
4
|
+
"activation": {
|
|
5
|
+
"onStartup": false
|
|
6
|
+
},
|
|
7
|
+
"uiHints": {
|
|
8
|
+
"webSearch.baseUrl": {
|
|
9
|
+
"label": "SearXNG Base URL",
|
|
10
|
+
"help": "Base URL of your SearXNG instance, such as http://localhost:8080 or https://search.example.com/searxng."
|
|
11
|
+
},
|
|
12
|
+
"webSearch.categories": {
|
|
13
|
+
"label": "SearXNG Categories",
|
|
14
|
+
"help": "Optional comma-separated categories such as general, news, or science."
|
|
15
|
+
},
|
|
16
|
+
"webSearch.language": {
|
|
17
|
+
"label": "SearXNG Language",
|
|
18
|
+
"help": "Optional language code for results such as en, de, or fr."
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"contracts": {
|
|
22
|
+
"webSearchProviders": ["searxng"]
|
|
23
|
+
},
|
|
24
|
+
"configSchema": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"additionalProperties": false,
|
|
27
|
+
"properties": {
|
|
28
|
+
"webSearch": {
|
|
29
|
+
"type": "object",
|
|
30
|
+
"additionalProperties": false,
|
|
31
|
+
"properties": {
|
|
32
|
+
"baseUrl": {
|
|
33
|
+
"type": ["string", "object"]
|
|
34
|
+
},
|
|
35
|
+
"categories": {
|
|
36
|
+
"type": "string"
|
|
37
|
+
},
|
|
38
|
+
"language": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/searxng-plugin",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2026.6.11",
|
|
4
|
+
"description": "OpenClaw SearXNG plugin",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"openclaw": {
|
|
7
|
+
"extensions": [
|
|
8
|
+
"./index.ts"
|
|
9
|
+
],
|
|
10
|
+
"install": {
|
|
11
|
+
"clawhubSpec": "clawhub:@openclaw/searxng-plugin",
|
|
12
|
+
"npmSpec": "@openclaw/searxng-plugin",
|
|
13
|
+
"defaultChoice": "npm",
|
|
14
|
+
"minHostVersion": ">=2026.6.9",
|
|
15
|
+
"allowInvalidConfigRecovery": true
|
|
16
|
+
},
|
|
17
|
+
"compat": {
|
|
18
|
+
"pluginApi": ">=2026.6.11"
|
|
19
|
+
},
|
|
20
|
+
"build": {
|
|
21
|
+
"openclawVersion": "2026.6.11",
|
|
22
|
+
"bundledDist": false
|
|
23
|
+
},
|
|
24
|
+
"release": {
|
|
25
|
+
"publishToClawHub": true,
|
|
26
|
+
"publishToNpm": true
|
|
27
|
+
},
|
|
28
|
+
"runtimeExtensions": [
|
|
29
|
+
"./dist/index.js"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
5
32
|
"repository": {
|
|
6
33
|
"type": "git",
|
|
7
|
-
"url": "
|
|
34
|
+
"url": "https://github.com/openclaw/openclaw"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist/**",
|
|
38
|
+
"openclaw.plugin.json",
|
|
39
|
+
"npm-shrinkwrap.json",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"openclaw": ">=2026.6.11"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"openclaw": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
8
49
|
},
|
|
9
|
-
"
|
|
10
|
-
"publishConfig": {
|
|
11
|
-
"access": "public"
|
|
12
|
-
}
|
|
50
|
+
"bundledDependencies": []
|
|
13
51
|
}
|