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.
@@ -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
+ }