@openclaw/inworld-speech 0.0.0 → 2026.6.9-beta.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/README.md +11 -2
- package/dist/index.js +13 -0
- package/dist/speech-provider.js +166 -0
- package/dist/tts.js +111 -0
- package/npm-shrinkwrap.json +12 -0
- package/openclaw.plugin.json +48 -0
- package/package.json +44 -8
package/README.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# OpenClaw Inworld Plugin
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Official OpenClaw plugin for Inworld.
|
|
4
|
+
|
|
5
|
+
Install from OpenClaw:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
openclaw plugins install @openclaw/inworld-speech
|
|
9
|
+
openclaw gateway restart
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
See <https://docs.openclaw.ai/providers/inworld> for setup and configuration.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { buildInworldSpeechProvider } from "./speech-provider.js";
|
|
2
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
3
|
+
//#region extensions/inworld/index.ts
|
|
4
|
+
var inworld_default = definePluginEntry({
|
|
5
|
+
id: "inworld",
|
|
6
|
+
name: "Inworld Speech",
|
|
7
|
+
description: "Bundled Inworld speech provider",
|
|
8
|
+
register(api) {
|
|
9
|
+
api.registerSpeechProvider(buildInworldSpeechProvider());
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
//#endregion
|
|
13
|
+
export { inworld_default as default };
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { DEFAULT_INWORLD_MODEL_ID, INWORLD_TTS_MODELS, inworldTTS, listInworldVoices, normalizeInworldBaseUrl } from "./tts.js";
|
|
2
|
+
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
|
3
|
+
import { asObject, parseSpeechDirectiveNumberOverride, trimToUndefined } from "openclaw/plugin-sdk/speech-core";
|
|
4
|
+
import { asFiniteNumberInRange } from "openclaw/plugin-sdk/string-coerce-runtime";
|
|
5
|
+
//#region extensions/inworld/speech-provider.ts
|
|
6
|
+
function normalizeInworldTemperature(value) {
|
|
7
|
+
return asFiniteNumberInRange(value, {
|
|
8
|
+
min: 0,
|
|
9
|
+
minExclusive: true,
|
|
10
|
+
max: 2
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
function normalizeInworldProviderConfig(rawConfig) {
|
|
14
|
+
const raw = asObject(asObject(rawConfig.providers)?.inworld) ?? asObject(rawConfig.inworld);
|
|
15
|
+
return {
|
|
16
|
+
apiKey: normalizeResolvedSecretInputString({
|
|
17
|
+
value: raw?.apiKey,
|
|
18
|
+
path: "messages.tts.providers.inworld.apiKey"
|
|
19
|
+
}),
|
|
20
|
+
baseUrl: normalizeInworldBaseUrl(trimToUndefined(raw?.baseUrl)),
|
|
21
|
+
voiceId: trimToUndefined(raw?.voiceId) ?? "Sarah",
|
|
22
|
+
modelId: trimToUndefined(raw?.modelId) ?? "inworld-tts-1.5-max",
|
|
23
|
+
temperature: normalizeInworldTemperature(raw?.temperature)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function readInworldProviderConfig(config) {
|
|
27
|
+
const defaults = normalizeInworldProviderConfig({});
|
|
28
|
+
return {
|
|
29
|
+
apiKey: trimToUndefined(config.apiKey) ?? defaults.apiKey,
|
|
30
|
+
baseUrl: normalizeInworldBaseUrl(trimToUndefined(config.baseUrl) ?? defaults.baseUrl),
|
|
31
|
+
voiceId: trimToUndefined(config.voiceId) ?? defaults.voiceId,
|
|
32
|
+
modelId: trimToUndefined(config.modelId) ?? defaults.modelId,
|
|
33
|
+
temperature: normalizeInworldTemperature(config.temperature) ?? defaults.temperature
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function readInworldOverrides(overrides) {
|
|
37
|
+
if (!overrides) return {};
|
|
38
|
+
return {
|
|
39
|
+
voiceId: trimToUndefined(overrides.voiceId ?? overrides.voice),
|
|
40
|
+
modelId: trimToUndefined(overrides.modelId ?? overrides.model),
|
|
41
|
+
temperature: normalizeInworldTemperature(overrides.temperature)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function parseDirectiveToken(ctx) {
|
|
45
|
+
switch (ctx.key) {
|
|
46
|
+
case "voice":
|
|
47
|
+
case "voiceid":
|
|
48
|
+
case "voice_id":
|
|
49
|
+
case "inworld_voice":
|
|
50
|
+
case "inworldvoice":
|
|
51
|
+
if (!ctx.policy.allowVoice) return { handled: true };
|
|
52
|
+
return {
|
|
53
|
+
handled: true,
|
|
54
|
+
overrides: { voiceId: ctx.value }
|
|
55
|
+
};
|
|
56
|
+
case "model":
|
|
57
|
+
case "modelid":
|
|
58
|
+
case "model_id":
|
|
59
|
+
case "inworld_model":
|
|
60
|
+
case "inworldmodel":
|
|
61
|
+
if (!ctx.policy.allowModelId) return { handled: true };
|
|
62
|
+
return {
|
|
63
|
+
handled: true,
|
|
64
|
+
overrides: { modelId: ctx.value }
|
|
65
|
+
};
|
|
66
|
+
case "temperature": return parseSpeechDirectiveNumberOverride({
|
|
67
|
+
ctx,
|
|
68
|
+
overrideKey: "temperature",
|
|
69
|
+
range: {
|
|
70
|
+
min: 0,
|
|
71
|
+
minExclusive: true,
|
|
72
|
+
max: 2
|
|
73
|
+
},
|
|
74
|
+
warning: (value) => `invalid Inworld temperature "${value}"`
|
|
75
|
+
});
|
|
76
|
+
default: return { handled: false };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function buildInworldSpeechProvider() {
|
|
80
|
+
return {
|
|
81
|
+
id: "inworld",
|
|
82
|
+
label: "Inworld",
|
|
83
|
+
autoSelectOrder: 30,
|
|
84
|
+
defaultModel: DEFAULT_INWORLD_MODEL_ID,
|
|
85
|
+
models: INWORLD_TTS_MODELS,
|
|
86
|
+
resolveConfig: ({ rawConfig }) => normalizeInworldProviderConfig(rawConfig),
|
|
87
|
+
parseDirectiveToken,
|
|
88
|
+
resolveTalkConfig: ({ baseTtsConfig, talkProviderConfig }) => {
|
|
89
|
+
const base = normalizeInworldProviderConfig(baseTtsConfig);
|
|
90
|
+
const resolvedApiKey = talkProviderConfig.apiKey === void 0 ? void 0 : normalizeResolvedSecretInputString({
|
|
91
|
+
value: talkProviderConfig.apiKey,
|
|
92
|
+
path: "talk.providers.inworld.apiKey"
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
...base,
|
|
96
|
+
...resolvedApiKey === void 0 ? {} : { apiKey: resolvedApiKey },
|
|
97
|
+
...trimToUndefined(talkProviderConfig.baseUrl) == null ? {} : { baseUrl: normalizeInworldBaseUrl(trimToUndefined(talkProviderConfig.baseUrl)) },
|
|
98
|
+
...trimToUndefined(talkProviderConfig.voiceId) == null ? {} : { voiceId: trimToUndefined(talkProviderConfig.voiceId) },
|
|
99
|
+
...trimToUndefined(talkProviderConfig.modelId) == null ? {} : { modelId: trimToUndefined(talkProviderConfig.modelId) },
|
|
100
|
+
...normalizeInworldTemperature(talkProviderConfig.temperature) == null ? {} : { temperature: normalizeInworldTemperature(talkProviderConfig.temperature) }
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
resolveTalkOverrides: ({ params }) => ({
|
|
104
|
+
...trimToUndefined(params.voiceId) == null ? {} : { voiceId: trimToUndefined(params.voiceId) },
|
|
105
|
+
...trimToUndefined(params.modelId) == null ? {} : { modelId: trimToUndefined(params.modelId) },
|
|
106
|
+
...normalizeInworldTemperature(params.temperature) == null ? {} : { temperature: normalizeInworldTemperature(params.temperature) }
|
|
107
|
+
}),
|
|
108
|
+
listVoices: async (req) => {
|
|
109
|
+
const config = req.providerConfig ? readInworldProviderConfig(req.providerConfig) : void 0;
|
|
110
|
+
const apiKey = req.apiKey || config?.apiKey || process.env.INWORLD_API_KEY;
|
|
111
|
+
if (!apiKey) throw new Error("Inworld API key missing");
|
|
112
|
+
return listInworldVoices({
|
|
113
|
+
apiKey,
|
|
114
|
+
baseUrl: req.baseUrl ?? config?.baseUrl
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
isConfigured: ({ providerConfig }) => Boolean(readInworldProviderConfig(providerConfig).apiKey || process.env.INWORLD_API_KEY),
|
|
118
|
+
synthesize: async (req) => {
|
|
119
|
+
const config = readInworldProviderConfig(req.providerConfig);
|
|
120
|
+
const overrides = readInworldOverrides(req.providerOverrides);
|
|
121
|
+
const apiKey = config.apiKey || process.env.INWORLD_API_KEY;
|
|
122
|
+
if (!apiKey) throw new Error("Inworld API key missing");
|
|
123
|
+
const useOpus = req.target === "voice-note";
|
|
124
|
+
const audioEncoding = useOpus ? "OGG_OPUS" : "MP3";
|
|
125
|
+
return {
|
|
126
|
+
audioBuffer: await inworldTTS({
|
|
127
|
+
text: req.text,
|
|
128
|
+
apiKey,
|
|
129
|
+
baseUrl: config.baseUrl,
|
|
130
|
+
voiceId: overrides.voiceId ?? config.voiceId,
|
|
131
|
+
modelId: overrides.modelId ?? config.modelId,
|
|
132
|
+
audioEncoding,
|
|
133
|
+
temperature: overrides.temperature ?? config.temperature,
|
|
134
|
+
timeoutMs: req.timeoutMs
|
|
135
|
+
}),
|
|
136
|
+
outputFormat: audioEncoding.toLowerCase(),
|
|
137
|
+
fileExtension: useOpus ? ".ogg" : ".mp3",
|
|
138
|
+
voiceCompatible: useOpus
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
synthesizeTelephony: async (req) => {
|
|
142
|
+
const config = readInworldProviderConfig(req.providerConfig);
|
|
143
|
+
const overrides = readInworldOverrides(req.providerOverrides);
|
|
144
|
+
const apiKey = config.apiKey || process.env.INWORLD_API_KEY;
|
|
145
|
+
if (!apiKey) throw new Error("Inworld API key missing");
|
|
146
|
+
const sampleRate = 22050;
|
|
147
|
+
return {
|
|
148
|
+
audioBuffer: await inworldTTS({
|
|
149
|
+
text: req.text,
|
|
150
|
+
apiKey,
|
|
151
|
+
baseUrl: config.baseUrl,
|
|
152
|
+
voiceId: overrides.voiceId ?? config.voiceId,
|
|
153
|
+
modelId: overrides.modelId ?? config.modelId,
|
|
154
|
+
audioEncoding: "PCM",
|
|
155
|
+
sampleRateHertz: sampleRate,
|
|
156
|
+
temperature: overrides.temperature ?? config.temperature,
|
|
157
|
+
timeoutMs: req.timeoutMs
|
|
158
|
+
}),
|
|
159
|
+
outputFormat: "pcm",
|
|
160
|
+
sampleRate
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { buildInworldSpeechProvider };
|
package/dist/tts.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime";
|
|
2
|
+
//#region extensions/inworld/tts.ts
|
|
3
|
+
const DEFAULT_INWORLD_BASE_URL = "https://api.inworld.ai";
|
|
4
|
+
const DEFAULT_INWORLD_VOICE_ID = "Sarah";
|
|
5
|
+
const DEFAULT_INWORLD_MODEL_ID = "inworld-tts-1.5-max";
|
|
6
|
+
const INWORLD_TTS_MODELS = [
|
|
7
|
+
"inworld-tts-1.5-max",
|
|
8
|
+
"inworld-tts-1.5-mini",
|
|
9
|
+
"inworld-tts-1-max",
|
|
10
|
+
"inworld-tts-1"
|
|
11
|
+
];
|
|
12
|
+
function normalizeInworldBaseUrl(baseUrl) {
|
|
13
|
+
return (baseUrl?.trim())?.replace(/\/+$/, "") || DEFAULT_INWORLD_BASE_URL;
|
|
14
|
+
}
|
|
15
|
+
function ssrfPolicyFromInworldBaseUrl(baseUrl) {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = new URL(baseUrl);
|
|
18
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return;
|
|
19
|
+
return { hostnameAllowlist: [parsed.hostname] };
|
|
20
|
+
} catch {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Calls the Inworld streaming TTS endpoint and concatenates every audio chunk
|
|
26
|
+
* into a single buffer. The stream returns newline-delimited JSON, each line
|
|
27
|
+
* carrying base64 audio in `result.audioContent`.
|
|
28
|
+
*/
|
|
29
|
+
async function inworldTTS(params) {
|
|
30
|
+
const baseUrl = normalizeInworldBaseUrl(params.baseUrl);
|
|
31
|
+
const url = `${baseUrl}/tts/v1/voice:stream`;
|
|
32
|
+
const requestBody = JSON.stringify({
|
|
33
|
+
text: params.text,
|
|
34
|
+
voiceId: params.voiceId ?? "Sarah",
|
|
35
|
+
modelId: params.modelId ?? "inworld-tts-1.5-max",
|
|
36
|
+
audioConfig: {
|
|
37
|
+
audioEncoding: params.audioEncoding ?? "MP3",
|
|
38
|
+
...params.sampleRateHertz && { sampleRateHertz: params.sampleRateHertz }
|
|
39
|
+
},
|
|
40
|
+
...params.temperature != null && { temperature: params.temperature }
|
|
41
|
+
});
|
|
42
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
43
|
+
url,
|
|
44
|
+
init: {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
Authorization: `Basic ${params.apiKey}`
|
|
49
|
+
},
|
|
50
|
+
body: requestBody
|
|
51
|
+
},
|
|
52
|
+
timeoutMs: params.timeoutMs,
|
|
53
|
+
policy: ssrfPolicyFromInworldBaseUrl(baseUrl),
|
|
54
|
+
auditContext: "inworld-tts"
|
|
55
|
+
});
|
|
56
|
+
try {
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
const errorBody = await response.text().catch(() => "");
|
|
59
|
+
throw new Error(`Inworld TTS API error (${response.status}): ${errorBody}`);
|
|
60
|
+
}
|
|
61
|
+
const body = await response.text();
|
|
62
|
+
const chunks = [];
|
|
63
|
+
for (const line of body.split("\n")) {
|
|
64
|
+
const trimmed = line.trim();
|
|
65
|
+
if (!trimmed) continue;
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(trimmed);
|
|
69
|
+
} catch {
|
|
70
|
+
throw new Error(`Inworld TTS stream parse error: unexpected non-JSON line: ${trimmed.slice(0, 80)}`);
|
|
71
|
+
}
|
|
72
|
+
if (parsed.error) throw new Error(`Inworld TTS stream error (${parsed.error.code}): ${parsed.error.message}`);
|
|
73
|
+
if (parsed.result?.audioContent) chunks.push(Buffer.from(parsed.result.audioContent, "base64"));
|
|
74
|
+
}
|
|
75
|
+
if (chunks.length === 0) throw new Error("Inworld TTS returned no audio data");
|
|
76
|
+
return Buffer.concat(chunks);
|
|
77
|
+
} finally {
|
|
78
|
+
await release();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async function listInworldVoices(params) {
|
|
82
|
+
const baseUrl = normalizeInworldBaseUrl(params.baseUrl);
|
|
83
|
+
const { response, release } = await fetchWithSsrFGuard({
|
|
84
|
+
url: `${baseUrl}/voices/v1/voices${params.language ? `?languages=${encodeURIComponent(params.language)}` : ""}`,
|
|
85
|
+
init: {
|
|
86
|
+
method: "GET",
|
|
87
|
+
headers: { Authorization: `Basic ${params.apiKey}` }
|
|
88
|
+
},
|
|
89
|
+
timeoutMs: params.timeoutMs,
|
|
90
|
+
policy: ssrfPolicyFromInworldBaseUrl(baseUrl),
|
|
91
|
+
auditContext: "inworld-voices"
|
|
92
|
+
});
|
|
93
|
+
try {
|
|
94
|
+
if (!response.ok) {
|
|
95
|
+
const errorBody = await response.text().catch(() => "");
|
|
96
|
+
throw new Error(`Inworld voices API error (${response.status}): ${errorBody}`);
|
|
97
|
+
}
|
|
98
|
+
const json = await response.json();
|
|
99
|
+
return Array.isArray(json.voices) ? json.voices.map((voice) => ({
|
|
100
|
+
id: voice.voiceId?.trim() ?? "",
|
|
101
|
+
name: voice.displayName?.trim() || void 0,
|
|
102
|
+
description: voice.description?.trim() || void 0,
|
|
103
|
+
locale: voice.langCode || void 0,
|
|
104
|
+
gender: voice.tags?.find((t) => t === "male" || t === "female") || void 0
|
|
105
|
+
})).filter((voice) => voice.id.length > 0) : [];
|
|
106
|
+
} finally {
|
|
107
|
+
await release();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { DEFAULT_INWORLD_MODEL_ID, DEFAULT_INWORLD_VOICE_ID, INWORLD_TTS_MODELS, inworldTTS, listInworldVoices, normalizeInworldBaseUrl };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "inworld",
|
|
3
|
+
"activation": {
|
|
4
|
+
"onStartup": false
|
|
5
|
+
},
|
|
6
|
+
"enabledByDefault": true,
|
|
7
|
+
"name": "Inworld",
|
|
8
|
+
"description": "Inworld streaming text-to-speech (MP3, OGG_OPUS, PCM telephony).",
|
|
9
|
+
"setup": {
|
|
10
|
+
"providers": [
|
|
11
|
+
{
|
|
12
|
+
"id": "inworld",
|
|
13
|
+
"envVars": ["INWORLD_API_KEY"]
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"contracts": {
|
|
18
|
+
"speechProviders": ["inworld"]
|
|
19
|
+
},
|
|
20
|
+
"configSchema": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"properties": {
|
|
24
|
+
"apiKey": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Inworld API key. Must be the Base64 credential string from the Inworld dashboard (used as Authorization: Basic <apiKey>). Falls back to INWORLD_API_KEY env var."
|
|
27
|
+
},
|
|
28
|
+
"baseUrl": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Override Inworld API base URL (default https://api.inworld.ai)."
|
|
31
|
+
},
|
|
32
|
+
"voiceId": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "Voice identifier (default Sarah)."
|
|
35
|
+
},
|
|
36
|
+
"modelId": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "TTS model id (default inworld-tts-1.5-max)."
|
|
39
|
+
},
|
|
40
|
+
"temperature": {
|
|
41
|
+
"type": "number",
|
|
42
|
+
"minimum": 0,
|
|
43
|
+
"maximum": 2,
|
|
44
|
+
"description": "Sampling temperature 0..2."
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/inworld-speech",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"license": "MIT",
|
|
3
|
+
"version": "2026.6.9-beta.1",
|
|
4
|
+
"description": "OpenClaw Inworld speech plugin.",
|
|
6
5
|
"repository": {
|
|
7
6
|
"type": "git",
|
|
8
|
-
"url": "
|
|
7
|
+
"url": "https://github.com/openclaw/openclaw"
|
|
9
8
|
},
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
9
|
+
"type": "module",
|
|
10
|
+
"openclaw": {
|
|
11
|
+
"extensions": [
|
|
12
|
+
"./index.ts"
|
|
13
|
+
],
|
|
14
|
+
"install": {
|
|
15
|
+
"clawhubSpec": "clawhub:@openclaw/inworld-speech",
|
|
16
|
+
"npmSpec": "@openclaw/inworld-speech",
|
|
17
|
+
"defaultChoice": "npm",
|
|
18
|
+
"minHostVersion": ">=2026.6.8"
|
|
19
|
+
},
|
|
20
|
+
"compat": {
|
|
21
|
+
"pluginApi": ">=2026.6.9-beta.1"
|
|
22
|
+
},
|
|
23
|
+
"build": {
|
|
24
|
+
"openclawVersion": "2026.6.9-beta.1",
|
|
25
|
+
"bundledDist": false
|
|
26
|
+
},
|
|
27
|
+
"release": {
|
|
28
|
+
"publishToClawHub": true,
|
|
29
|
+
"publishToNpm": true
|
|
30
|
+
},
|
|
31
|
+
"runtimeExtensions": [
|
|
32
|
+
"./dist/index.js"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist/**",
|
|
37
|
+
"openclaw.plugin.json",
|
|
38
|
+
"npm-shrinkwrap.json",
|
|
39
|
+
"README.md"
|
|
40
|
+
],
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"openclaw": ">=2026.6.9-beta.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"openclaw": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"bundledDependencies": []
|
|
14
50
|
}
|