openclawapi 1.0.0
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 +328 -0
- package/cli.js +662 -0
- package/lib/config-manager.js +356 -0
- package/lib/speed-test.js +88 -0
- package/lib/ui.js +38 -0
- package/package.json +38 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class ConfigManager {
|
|
5
|
+
constructor(configPaths) {
|
|
6
|
+
this.openclawConfigPath = configPaths.openclawConfig;
|
|
7
|
+
this.authProfilesPath = configPaths.authProfiles;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// 检查配置文件是否存在
|
|
11
|
+
async checkConfigExists() {
|
|
12
|
+
return {
|
|
13
|
+
openclaw: await fs.pathExists(this.openclawConfigPath),
|
|
14
|
+
auth: await fs.pathExists(this.authProfilesPath)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// 读取 openclaw.json
|
|
19
|
+
async readOpenclawConfig() {
|
|
20
|
+
try {
|
|
21
|
+
return await fs.readJson(this.openclawConfigPath);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new Error(`读取配置文件失败: ${error.message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 写入 openclaw.json
|
|
28
|
+
async writeOpenclawConfig(config) {
|
|
29
|
+
try {
|
|
30
|
+
await fs.writeJson(this.openclawConfigPath, config, { spaces: 2 });
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`写入配置文件失败: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// 读取 auth-profiles.json
|
|
37
|
+
async readAuthProfiles() {
|
|
38
|
+
try {
|
|
39
|
+
if (await fs.pathExists(this.authProfilesPath)) {
|
|
40
|
+
return await fs.readJson(this.authProfilesPath);
|
|
41
|
+
}
|
|
42
|
+
return {};
|
|
43
|
+
} catch (error) {
|
|
44
|
+
throw new Error(`读取认证文件失败: ${error.message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 写入 auth-profiles.json
|
|
49
|
+
async writeAuthProfiles(profiles) {
|
|
50
|
+
try {
|
|
51
|
+
await fs.ensureDir(path.dirname(this.authProfilesPath));
|
|
52
|
+
await fs.writeJson(this.authProfilesPath, profiles, { spaces: 2 });
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new Error(`写入认证文件失败: ${error.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 添加中转站
|
|
59
|
+
async addRelay(relayConfig) {
|
|
60
|
+
const config = await this.readOpenclawConfig();
|
|
61
|
+
|
|
62
|
+
const providerName = relayConfig.name;
|
|
63
|
+
const profileKey = `${providerName}:default`;
|
|
64
|
+
|
|
65
|
+
// 添加到 models.providers
|
|
66
|
+
if (!config.models) config.models = {};
|
|
67
|
+
if (!config.models.providers) config.models.providers = {};
|
|
68
|
+
|
|
69
|
+
config.models.providers[providerName] = {
|
|
70
|
+
baseUrl: relayConfig.baseUrl,
|
|
71
|
+
auth: 'api-key',
|
|
72
|
+
api: 'openai-completions',
|
|
73
|
+
models: [
|
|
74
|
+
{
|
|
75
|
+
id: relayConfig.model.id,
|
|
76
|
+
name: relayConfig.model.name,
|
|
77
|
+
reasoning: false,
|
|
78
|
+
input: ['text', 'image'],
|
|
79
|
+
cost: {
|
|
80
|
+
input: 0,
|
|
81
|
+
output: 0,
|
|
82
|
+
cacheRead: 0,
|
|
83
|
+
cacheWrite: 0
|
|
84
|
+
},
|
|
85
|
+
contextWindow: relayConfig.model.contextWindow,
|
|
86
|
+
maxTokens: relayConfig.model.maxTokens
|
|
87
|
+
}
|
|
88
|
+
]
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// 添加到 auth.profiles
|
|
92
|
+
if (!config.auth) config.auth = {};
|
|
93
|
+
if (!config.auth.profiles) config.auth.profiles = {};
|
|
94
|
+
|
|
95
|
+
config.auth.profiles[profileKey] = {
|
|
96
|
+
provider: providerName,
|
|
97
|
+
mode: 'api_key'
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
await this.writeOpenclawConfig(config);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 列出所有中转站
|
|
104
|
+
async listRelays() {
|
|
105
|
+
const config = await this.readOpenclawConfig();
|
|
106
|
+
const relays = [];
|
|
107
|
+
|
|
108
|
+
if (config.models && config.models.providers) {
|
|
109
|
+
for (const [name, provider] of Object.entries(config.models.providers)) {
|
|
110
|
+
if (provider.models && provider.models.length > 0) {
|
|
111
|
+
const model = provider.models[0];
|
|
112
|
+
relays.push({
|
|
113
|
+
name,
|
|
114
|
+
baseUrl: provider.baseUrl,
|
|
115
|
+
modelId: model.id,
|
|
116
|
+
modelName: model.name,
|
|
117
|
+
contextWindow: model.contextWindow,
|
|
118
|
+
maxTokens: model.maxTokens
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return relays;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 更新中转站
|
|
128
|
+
async updateRelay(relayName, updates) {
|
|
129
|
+
const config = await this.readOpenclawConfig();
|
|
130
|
+
|
|
131
|
+
if (!config.models?.providers?.[relayName]) {
|
|
132
|
+
throw new Error(`中转站 "${relayName}" 不存在`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const provider = config.models.providers[relayName];
|
|
136
|
+
|
|
137
|
+
if (updates.baseUrl) {
|
|
138
|
+
provider.baseUrl = updates.baseUrl;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (provider.models && provider.models.length > 0) {
|
|
142
|
+
if (updates.contextWindow) {
|
|
143
|
+
provider.models[0].contextWindow = updates.contextWindow;
|
|
144
|
+
}
|
|
145
|
+
if (updates.maxTokens) {
|
|
146
|
+
provider.models[0].maxTokens = updates.maxTokens;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await this.writeOpenclawConfig(config);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 删除中转站
|
|
154
|
+
async deleteRelay(relayName) {
|
|
155
|
+
const config = await this.readOpenclawConfig();
|
|
156
|
+
|
|
157
|
+
// 删除 provider
|
|
158
|
+
if (config.models?.providers?.[relayName]) {
|
|
159
|
+
delete config.models.providers[relayName];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 删除 auth profile
|
|
163
|
+
const profileKey = `${relayName}:default`;
|
|
164
|
+
if (config.auth?.profiles?.[profileKey]) {
|
|
165
|
+
delete config.auth.profiles[profileKey];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 从 agents.defaults.models 中删除
|
|
169
|
+
if (config.agents?.defaults?.models) {
|
|
170
|
+
for (const key of Object.keys(config.agents.defaults.models)) {
|
|
171
|
+
if (key.startsWith(`${relayName}/`)) {
|
|
172
|
+
delete config.agents.defaults.models[key];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 如果是主模型,需要切换到其他模型
|
|
178
|
+
if (config.agents?.defaults?.model?.primary?.startsWith(`${relayName}/`)) {
|
|
179
|
+
const remainingRelays = await this.listRelays();
|
|
180
|
+
if (remainingRelays.length > 0) {
|
|
181
|
+
const firstRelay = remainingRelays[0];
|
|
182
|
+
config.agents.defaults.model.primary = `${firstRelay.name}/${firstRelay.modelId}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// 从 fallbacks 中删除
|
|
187
|
+
if (config.agents?.defaults?.model?.fallbacks) {
|
|
188
|
+
config.agents.defaults.model.fallbacks = config.agents.defaults.model.fallbacks.filter(
|
|
189
|
+
f => !f.startsWith(`${relayName}/`)
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
await this.writeOpenclawConfig(config);
|
|
194
|
+
|
|
195
|
+
// 删除 API Key
|
|
196
|
+
const authProfiles = await this.readAuthProfiles();
|
|
197
|
+
if (authProfiles[profileKey]) {
|
|
198
|
+
delete authProfiles[profileKey];
|
|
199
|
+
await this.writeAuthProfiles(authProfiles);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 获取主模型
|
|
204
|
+
async getPrimaryModel() {
|
|
205
|
+
const config = await this.readOpenclawConfig();
|
|
206
|
+
const primary = config.agents?.defaults?.model?.primary || '';
|
|
207
|
+
const [provider, modelId] = primary.split('/');
|
|
208
|
+
return { provider, modelId, full: primary };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 设置主模型
|
|
212
|
+
async setPrimaryModel(relayName) {
|
|
213
|
+
const config = await this.readOpenclawConfig();
|
|
214
|
+
const relays = await this.listRelays();
|
|
215
|
+
const relay = relays.find(r => r.name === relayName);
|
|
216
|
+
|
|
217
|
+
if (!relay) {
|
|
218
|
+
throw new Error(`中转站 "${relayName}" 不存在`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (!config.agents) config.agents = {};
|
|
222
|
+
if (!config.agents.defaults) config.agents.defaults = {};
|
|
223
|
+
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
224
|
+
|
|
225
|
+
config.agents.defaults.model.primary = `${relayName}/${relay.modelId}`;
|
|
226
|
+
|
|
227
|
+
// 确保在 models 中注册
|
|
228
|
+
if (!config.agents.defaults.models) config.agents.defaults.models = {};
|
|
229
|
+
const modelKey = `${relayName}/${relay.modelId}`;
|
|
230
|
+
if (!config.agents.defaults.models[modelKey]) {
|
|
231
|
+
config.agents.defaults.models[modelKey] = {
|
|
232
|
+
alias: relayName
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await this.writeOpenclawConfig(config);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 获取备用模型
|
|
240
|
+
async getFallbackModels() {
|
|
241
|
+
const config = await this.readOpenclawConfig();
|
|
242
|
+
return config.agents?.defaults?.model?.fallbacks || [];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// 设置备用模型
|
|
246
|
+
async setFallbackModels(models) {
|
|
247
|
+
const config = await this.readOpenclawConfig();
|
|
248
|
+
|
|
249
|
+
if (!config.agents) config.agents = {};
|
|
250
|
+
if (!config.agents.defaults) config.agents.defaults = {};
|
|
251
|
+
if (!config.agents.defaults.model) config.agents.defaults.model = {};
|
|
252
|
+
|
|
253
|
+
config.agents.defaults.model.fallbacks = models;
|
|
254
|
+
|
|
255
|
+
// 确保所有模型都在 models 中注册
|
|
256
|
+
if (!config.agents.defaults.models) config.agents.defaults.models = {};
|
|
257
|
+
for (const modelKey of models) {
|
|
258
|
+
if (!config.agents.defaults.models[modelKey]) {
|
|
259
|
+
const [provider] = modelKey.split('/');
|
|
260
|
+
config.agents.defaults.models[modelKey] = {
|
|
261
|
+
alias: provider
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
await this.writeOpenclawConfig(config);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 设置 API Key
|
|
270
|
+
async setApiKey(relayName, apiKey) {
|
|
271
|
+
const authProfiles = await this.readAuthProfiles();
|
|
272
|
+
const profileKey = `${relayName}:default`;
|
|
273
|
+
|
|
274
|
+
authProfiles[profileKey] = {
|
|
275
|
+
apiKey: apiKey
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await this.writeAuthProfiles(authProfiles);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 列出所有 API Keys
|
|
282
|
+
async listApiKeys() {
|
|
283
|
+
const authProfiles = await this.readAuthProfiles();
|
|
284
|
+
const keys = [];
|
|
285
|
+
|
|
286
|
+
for (const [profile, data] of Object.entries(authProfiles)) {
|
|
287
|
+
keys.push({
|
|
288
|
+
provider: profile,
|
|
289
|
+
key: data.apiKey || null
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return keys;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 删除 API Key
|
|
297
|
+
async deleteApiKey(provider) {
|
|
298
|
+
const authProfiles = await this.readAuthProfiles();
|
|
299
|
+
|
|
300
|
+
if (authProfiles[provider]) {
|
|
301
|
+
delete authProfiles[provider];
|
|
302
|
+
await this.writeAuthProfiles(authProfiles);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 获取高级设置
|
|
307
|
+
async getAdvancedSettings() {
|
|
308
|
+
const config = await this.readOpenclawConfig();
|
|
309
|
+
return {
|
|
310
|
+
maxConcurrent: config.agents?.defaults?.maxConcurrent || 4,
|
|
311
|
+
subagentMaxConcurrent: config.agents?.defaults?.subagents?.maxConcurrent || 8,
|
|
312
|
+
workspace: config.agents?.defaults?.workspace || ''
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// 设置高级设置
|
|
317
|
+
async setAdvancedSettings(settings) {
|
|
318
|
+
const config = await this.readOpenclawConfig();
|
|
319
|
+
|
|
320
|
+
if (!config.agents) config.agents = {};
|
|
321
|
+
if (!config.agents.defaults) config.agents.defaults = {};
|
|
322
|
+
|
|
323
|
+
if (settings.maxConcurrent !== undefined) {
|
|
324
|
+
config.agents.defaults.maxConcurrent = settings.maxConcurrent;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (settings.subagentMaxConcurrent !== undefined) {
|
|
328
|
+
if (!config.agents.defaults.subagents) config.agents.defaults.subagents = {};
|
|
329
|
+
config.agents.defaults.subagents.maxConcurrent = settings.subagentMaxConcurrent;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (settings.workspace) {
|
|
333
|
+
config.agents.defaults.workspace = settings.workspace;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await this.writeOpenclawConfig(config);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// 获取当前完整配置
|
|
340
|
+
async getCurrentConfig() {
|
|
341
|
+
const config = await this.readOpenclawConfig();
|
|
342
|
+
const relays = await this.listRelays();
|
|
343
|
+
const primary = await this.getPrimaryModel();
|
|
344
|
+
const fallbacks = await this.getFallbackModels();
|
|
345
|
+
const advanced = await this.getAdvancedSettings();
|
|
346
|
+
|
|
347
|
+
return {
|
|
348
|
+
primary: primary.full,
|
|
349
|
+
fallbacks,
|
|
350
|
+
relays,
|
|
351
|
+
advanced
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
module.exports = { ConfigManager };
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
|
|
4
|
+
// 测试 URL 的响应时间
|
|
5
|
+
async function testSpeed(url) {
|
|
6
|
+
return new Promise((resolve) => {
|
|
7
|
+
const startTime = Date.now();
|
|
8
|
+
const urlObj = new URL(url);
|
|
9
|
+
const protocol = urlObj.protocol === 'https:' ? https : http;
|
|
10
|
+
|
|
11
|
+
const timeout = 5000; // 5秒超时
|
|
12
|
+
|
|
13
|
+
const req = protocol.get(url, { timeout }, (res) => {
|
|
14
|
+
const endTime = Date.now();
|
|
15
|
+
const latency = endTime - startTime;
|
|
16
|
+
|
|
17
|
+
// 读取响应数据(避免连接挂起)
|
|
18
|
+
res.on('data', () => {});
|
|
19
|
+
res.on('end', () => {
|
|
20
|
+
resolve({
|
|
21
|
+
success: true,
|
|
22
|
+
latency,
|
|
23
|
+
statusCode: res.statusCode
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
req.on('timeout', () => {
|
|
29
|
+
req.destroy();
|
|
30
|
+
resolve({
|
|
31
|
+
success: false,
|
|
32
|
+
latency: timeout,
|
|
33
|
+
error: '超时'
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
req.on('error', (error) => {
|
|
38
|
+
const endTime = Date.now();
|
|
39
|
+
resolve({
|
|
40
|
+
success: false,
|
|
41
|
+
latency: endTime - startTime,
|
|
42
|
+
error: error.message
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 测试多个中转站的速度
|
|
49
|
+
async function testMultipleRelays(relays) {
|
|
50
|
+
const results = [];
|
|
51
|
+
|
|
52
|
+
for (const relay of relays) {
|
|
53
|
+
// 构建测试 URL(去掉 /v1 后缀,只测试基础域名)
|
|
54
|
+
const baseUrl = relay.baseUrl.replace(/\/v1$/, '');
|
|
55
|
+
const testUrl = baseUrl;
|
|
56
|
+
|
|
57
|
+
const result = await testSpeed(testUrl);
|
|
58
|
+
results.push({
|
|
59
|
+
name: relay.name,
|
|
60
|
+
url: relay.baseUrl,
|
|
61
|
+
...result
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return results;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 根据速度排序中转站
|
|
69
|
+
function sortBySpeed(results) {
|
|
70
|
+
return results
|
|
71
|
+
.filter(r => r.success)
|
|
72
|
+
.sort((a, b) => a.latency - b.latency);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 格式化延迟显示
|
|
76
|
+
function formatLatency(latency) {
|
|
77
|
+
if (latency < 100) return 'excellent';
|
|
78
|
+
if (latency < 300) return 'good';
|
|
79
|
+
if (latency < 1000) return 'fair';
|
|
80
|
+
return 'poor';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = {
|
|
84
|
+
testSpeed,
|
|
85
|
+
testMultipleRelays,
|
|
86
|
+
sortBySpeed,
|
|
87
|
+
formatLatency
|
|
88
|
+
};
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
|
|
3
|
+
// 显示菜单
|
|
4
|
+
function displayMenu(title, options) {
|
|
5
|
+
console.log(chalk.cyan.bold(`\n${title}\n`));
|
|
6
|
+
options.forEach((opt, index) => {
|
|
7
|
+
console.log(` ${chalk.yellow(index + 1)}. ${opt}`);
|
|
8
|
+
});
|
|
9
|
+
console.log();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// 显示成功消息
|
|
13
|
+
function displaySuccess(message) {
|
|
14
|
+
console.log(chalk.green(`\n${message}\n`));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// 显示错误消息
|
|
18
|
+
function displayError(message) {
|
|
19
|
+
console.log(chalk.red(`\n❌ ${message}\n`));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 显示信息消息
|
|
23
|
+
function displayInfo(message) {
|
|
24
|
+
console.log(chalk.blue(`\nℹ️ ${message}\n`));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 显示警告消息
|
|
28
|
+
function displayWarning(message) {
|
|
29
|
+
console.log(chalk.yellow(`\n⚠️ ${message}\n`));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = {
|
|
33
|
+
displayMenu,
|
|
34
|
+
displaySuccess,
|
|
35
|
+
displayError,
|
|
36
|
+
displayInfo,
|
|
37
|
+
displayWarning
|
|
38
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "openclawapi",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
|
|
5
|
+
"main": "cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"openclawapi": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node cli.js",
|
|
11
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"openclaw",
|
|
15
|
+
"clawdbot",
|
|
16
|
+
"config",
|
|
17
|
+
"cli",
|
|
18
|
+
"api-relay",
|
|
19
|
+
"model-management",
|
|
20
|
+
"speed-test"
|
|
21
|
+
],
|
|
22
|
+
"author": "OpenClaw Community",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"files": [
|
|
25
|
+
"cli.js",
|
|
26
|
+
"lib/",
|
|
27
|
+
"README.md"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"inquirer": "^8.2.5",
|
|
31
|
+
"chalk": "^4.1.2",
|
|
32
|
+
"fs-extra": "^11.1.1"
|
|
33
|
+
},
|
|
34
|
+
"engines": {
|
|
35
|
+
"node": ">=14.0.0"
|
|
36
|
+
},
|
|
37
|
+
"preferGlobal": true
|
|
38
|
+
}
|