openclawapi 1.0.0 → 1.1.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.
Files changed (4) hide show
  1. package/README.md +120 -26
  2. package/cli.js +1241 -104
  3. package/lib/config-manager.js +405 -54
  4. package/package.json +3 -2
@@ -1,10 +1,78 @@
1
1
  const fs = require('fs-extra');
2
+ const JSON5 = require('json5');
2
3
  const path = require('path');
4
+ const os = require('os');
5
+
6
+ // 默认配置模板
7
+ const DEFAULT_CONFIG = {
8
+ models: {
9
+ providers: {}
10
+ },
11
+ auth: {
12
+ profiles: {}
13
+ },
14
+ agents: {
15
+ defaults: {
16
+ model: {
17
+ primary: '',
18
+ fallbacks: []
19
+ },
20
+ models: {},
21
+ maxConcurrent: 4,
22
+ subagents: {
23
+ maxConcurrent: 8
24
+ },
25
+ workspace: ''
26
+ }
27
+ }
28
+ };
29
+
30
+ // 获取跨平台配置目录
31
+ function getConfigDir() {
32
+ const homeDir = os.homedir();
33
+ // Windows: %USERPROFILE%\.clawdbot
34
+ // macOS/Linux: ~/.clawdbot
35
+ return path.join(homeDir, '.clawdbot');
36
+ }
37
+
38
+ // 获取跨平台默认工作区路径
39
+ function getDefaultWorkspace() {
40
+ const homeDir = os.homedir();
41
+ const openclawStateDir = process.env.OPENCLAW_STATE_DIR || path.join(homeDir, '.openclaw');
42
+ const clawdbotStateDir = process.env.CLAWDBOT_STATE_DIR || path.join(homeDir, '.clawdbot');
43
+ const profile = process.env.OPENCLAW_PROFILE;
44
+ const workspaceSuffix = profile && profile !== 'default' ? `-${profile}` : '';
45
+
46
+ if (process.env.OPENCLAW_STATE_DIR || process.env.OPENCLAW_CONFIG_PATH) {
47
+ return path.join(openclawStateDir, `workspace${workspaceSuffix}`);
48
+ }
49
+ if (process.env.CLAWDBOT_STATE_DIR || process.env.CLAWDBOT_CONFIG_PATH) {
50
+ return path.join(clawdbotStateDir, 'workspace');
51
+ }
52
+ return path.join(openclawStateDir, `workspace${workspaceSuffix}`);
53
+ }
54
+
55
+ // 验证 URL 格式
56
+ function isValidUrl(urlString) {
57
+ try {
58
+ const url = new URL(urlString);
59
+ return url.protocol === 'http:' || url.protocol === 'https:';
60
+ } catch {
61
+ return false;
62
+ }
63
+ }
64
+
65
+ // 验证数值范围
66
+ function isValidNumber(value, min, max) {
67
+ const num = Number(value);
68
+ return !isNaN(num) && num >= min && num <= max;
69
+ }
3
70
 
4
71
  class ConfigManager {
5
72
  constructor(configPaths) {
6
73
  this.openclawConfigPath = configPaths.openclawConfig;
7
74
  this.authProfilesPath = configPaths.authProfiles;
75
+ this.configDir = path.dirname(configPaths.openclawConfig);
8
76
  }
9
77
 
10
78
  // 检查配置文件是否存在
@@ -15,11 +83,64 @@ class ConfigManager {
15
83
  };
16
84
  }
17
85
 
86
+ // 初始化配置文件(如果不存在则创建)
87
+ async initializeConfig() {
88
+ try {
89
+ // 确保配置目录存在
90
+ await fs.ensureDir(this.configDir);
91
+
92
+ // 检查并创建 openclaw.json
93
+ if (!await fs.pathExists(this.openclawConfigPath)) {
94
+ const defaultConfig = JSON.parse(JSON.stringify(DEFAULT_CONFIG));
95
+ defaultConfig.agents.defaults.workspace = getDefaultWorkspace();
96
+ await fs.writeJson(this.openclawConfigPath, defaultConfig, { spaces: 2 });
97
+ }
98
+
99
+ // 确保 auth-profiles.json 目录存在
100
+ const authDir = path.dirname(this.authProfilesPath);
101
+ await fs.ensureDir(authDir);
102
+
103
+ if (!await fs.pathExists(this.authProfilesPath)) {
104
+ await fs.writeJson(this.authProfilesPath, {}, { spaces: 2 });
105
+ }
106
+
107
+ return true;
108
+ } catch (error) {
109
+ throw new Error(`初始化配置失败: ${error.message}`);
110
+ }
111
+ }
112
+
113
+ // 检查文件写入权限
114
+ async checkWritePermission(filePath) {
115
+ try {
116
+ const dir = path.dirname(filePath);
117
+ await fs.ensureDir(dir);
118
+ // 尝试写入测试文件
119
+ const testFile = path.join(dir, '.write-test-' + Date.now());
120
+ await fs.writeFile(testFile, '');
121
+ await fs.remove(testFile);
122
+ return true;
123
+ } catch (error) {
124
+ return false;
125
+ }
126
+ }
127
+
18
128
  // 读取 openclaw.json
19
129
  async readOpenclawConfig() {
20
130
  try {
21
- return await fs.readJson(this.openclawConfigPath);
131
+ if (!await fs.pathExists(this.openclawConfigPath)) {
132
+ // 自动初始化配置
133
+ await this.initializeConfig();
134
+ }
135
+ const raw = await fs.readFile(this.openclawConfigPath, 'utf8');
136
+ return JSON5.parse(raw);
22
137
  } catch (error) {
138
+ if (error.code === 'EACCES') {
139
+ throw new Error(`没有权限读取配置文件: ${this.openclawConfigPath}`);
140
+ }
141
+ if (error.code === 'ENOENT') {
142
+ throw new Error(`配置文件不存在: ${this.openclawConfigPath}`);
143
+ }
23
144
  throw new Error(`读取配置文件失败: ${error.message}`);
24
145
  }
25
146
  }
@@ -27,8 +148,16 @@ class ConfigManager {
27
148
  // 写入 openclaw.json
28
149
  async writeOpenclawConfig(config) {
29
150
  try {
151
+ // 检查写入权限
152
+ if (!await this.checkWritePermission(this.openclawConfigPath)) {
153
+ throw new Error(`没有权限写入配置文件: ${this.openclawConfigPath}`);
154
+ }
155
+ await fs.ensureDir(path.dirname(this.openclawConfigPath));
30
156
  await fs.writeJson(this.openclawConfigPath, config, { spaces: 2 });
31
157
  } catch (error) {
158
+ if (error.code === 'EACCES') {
159
+ throw new Error(`没有权限写入配置文件: ${this.openclawConfigPath}`);
160
+ }
32
161
  throw new Error(`写入配置文件失败: ${error.message}`);
33
162
  }
34
163
  }
@@ -41,6 +170,9 @@ class ConfigManager {
41
170
  }
42
171
  return {};
43
172
  } catch (error) {
173
+ if (error.code === 'EACCES') {
174
+ throw new Error(`没有权限读取认证文件: ${this.authProfilesPath}`);
175
+ }
44
176
  throw new Error(`读取认证文件失败: ${error.message}`);
45
177
  }
46
178
  }
@@ -48,44 +180,87 @@ class ConfigManager {
48
180
  // 写入 auth-profiles.json
49
181
  async writeAuthProfiles(profiles) {
50
182
  try {
51
- await fs.ensureDir(path.dirname(this.authProfilesPath));
183
+ const authDir = path.dirname(this.authProfilesPath);
184
+ if (!await this.checkWritePermission(this.authProfilesPath)) {
185
+ throw new Error(`没有权限写入认证文件: ${this.authProfilesPath}`);
186
+ }
187
+ await fs.ensureDir(authDir);
52
188
  await fs.writeJson(this.authProfilesPath, profiles, { spaces: 2 });
53
189
  } catch (error) {
190
+ if (error.code === 'EACCES') {
191
+ throw new Error(`没有权限写入认证文件: ${this.authProfilesPath}`);
192
+ }
54
193
  throw new Error(`写入认证文件失败: ${error.message}`);
55
194
  }
56
195
  }
57
196
 
58
197
  // 添加中转站
59
198
  async addRelay(relayConfig) {
199
+ // 验证输入
200
+ if (!relayConfig.name || relayConfig.name.trim() === '') {
201
+ throw new Error('中转站名称不能为空');
202
+ }
203
+ if (!relayConfig.baseUrl || !isValidUrl(relayConfig.baseUrl)) {
204
+ throw new Error('请输入有效的 URL (http:// 或 https://)');
205
+ }
206
+ const hasModelsArray = Array.isArray(relayConfig.models);
207
+ const primaryModelId = relayConfig.model?.id || relayConfig.models?.[0]?.id;
208
+ if (!primaryModelId) {
209
+ throw new Error('模型 ID 不能为空');
210
+ }
211
+ if (!hasModelsArray) {
212
+ if (!relayConfig.model || !relayConfig.model.id) {
213
+ throw new Error('模型配置不能为空');
214
+ }
215
+ if (!isValidNumber(relayConfig.model.contextWindow, 1000, 10000000)) {
216
+ throw new Error('上下文窗口大小必须在 1000 到 10000000 之间');
217
+ }
218
+ if (!isValidNumber(relayConfig.model.maxTokens, 100, 1000000)) {
219
+ throw new Error('最大输出 tokens 必须在 100 到 1000000 之间');
220
+ }
221
+ }
222
+
60
223
  const config = await this.readOpenclawConfig();
61
224
 
62
- const providerName = relayConfig.name;
225
+ const providerName = relayConfig.name.trim();
63
226
  const profileKey = `${providerName}:default`;
64
227
 
228
+ // 检查是否已存在同名中转站
229
+ if (config.models?.providers?.[providerName]) {
230
+ throw new Error(`中转站 "${providerName}" 已存在,请使用其他名称或编辑现有中转站`);
231
+ }
232
+
65
233
  // 添加到 models.providers
66
234
  if (!config.models) config.models = {};
67
235
  if (!config.models.providers) config.models.providers = {};
236
+ if (!config.models.mode && relayConfig.modelsMode) {
237
+ config.models.mode = relayConfig.modelsMode;
238
+ }
68
239
 
69
240
  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
- ]
241
+ baseUrl: relayConfig.baseUrl.trim(),
242
+ auth: relayConfig.auth || 'api-key',
243
+ api: relayConfig.api || 'openai-completions',
244
+ headers: relayConfig.headers || {},
245
+ authHeader: relayConfig.authHeader === true,
246
+ models: Array.isArray(relayConfig.models)
247
+ ? relayConfig.models
248
+ : [
249
+ {
250
+ id: relayConfig.model.id.trim(),
251
+ name: relayConfig.model.name || relayConfig.model.id,
252
+ reasoning: !!relayConfig.model.reasoning,
253
+ input: relayConfig.model.input || ['text'],
254
+ cost: relayConfig.model.cost || {
255
+ input: 0,
256
+ output: 0,
257
+ cacheRead: 0,
258
+ cacheWrite: 0
259
+ },
260
+ contextWindow: Number(relayConfig.model.contextWindow),
261
+ maxTokens: Number(relayConfig.model.maxTokens)
262
+ }
263
+ ]
89
264
  };
90
265
 
91
266
  // 添加到 auth.profiles
@@ -97,6 +272,17 @@ class ConfigManager {
97
272
  mode: 'api_key'
98
273
  };
99
274
 
275
+ // 注册到 agents.defaults.models,便于切换与备用
276
+ if (!config.agents) config.agents = {};
277
+ if (!config.agents.defaults) config.agents.defaults = {};
278
+ if (!config.agents.defaults.models) config.agents.defaults.models = {};
279
+ const modelKey = `${providerName}/${primaryModelId}`;
280
+ if (!config.agents.defaults.models[modelKey]) {
281
+ config.agents.defaults.models[modelKey] = {
282
+ alias: providerName
283
+ };
284
+ }
285
+
100
286
  await this.writeOpenclawConfig(config);
101
287
  }
102
288
 
@@ -104,20 +290,41 @@ class ConfigManager {
104
290
  async listRelays() {
105
291
  const config = await this.readOpenclawConfig();
106
292
  const relays = [];
293
+ const primary = config.agents?.defaults?.model?.primary || '';
294
+ const registeredModels = config.agents?.defaults?.models || {};
107
295
 
108
296
  if (config.models && config.models.providers) {
109
297
  for (const [name, provider] of Object.entries(config.models.providers)) {
298
+ let modelId = '';
299
+ let modelName = 'N/A';
300
+ let contextWindow = undefined;
301
+ let maxTokens = undefined;
302
+
110
303
  if (provider.models && provider.models.length > 0) {
111
304
  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
- });
305
+ modelId = model.id;
306
+ modelName = model.name || model.id;
307
+ contextWindow = model.contextWindow;
308
+ maxTokens = model.maxTokens;
309
+ } else if (primary.startsWith(`${name}/`)) {
310
+ modelId = primary.split('/')[1] || '';
311
+ modelName = modelId || 'N/A';
312
+ } else {
313
+ const modelKey = Object.keys(registeredModels).find(key => key.startsWith(`${name}/`));
314
+ if (modelKey) {
315
+ modelId = modelKey.split('/')[1] || '';
316
+ modelName = modelId || 'N/A';
317
+ }
120
318
  }
319
+
320
+ relays.push({
321
+ name,
322
+ baseUrl: provider.baseUrl,
323
+ modelId,
324
+ modelName,
325
+ contextWindow,
326
+ maxTokens
327
+ });
121
328
  }
122
329
  }
123
330
 
@@ -132,18 +339,44 @@ class ConfigManager {
132
339
  throw new Error(`中转站 "${relayName}" 不存在`);
133
340
  }
134
341
 
342
+ // 验证输入
343
+ if (updates.baseUrl && !isValidUrl(updates.baseUrl)) {
344
+ throw new Error('请输入有效的 URL (http:// 或 https://)');
345
+ }
346
+ if (updates.contextWindow !== undefined && !isValidNumber(updates.contextWindow, 1000, 10000000)) {
347
+ throw new Error('上下文窗口大小必须在 1000 到 10000000 之间');
348
+ }
349
+ if (updates.maxTokens !== undefined && !isValidNumber(updates.maxTokens, 100, 1000000)) {
350
+ throw new Error('最大输出 tokens 必须在 100 到 1000000 之间');
351
+ }
352
+
135
353
  const provider = config.models.providers[relayName];
136
354
 
137
355
  if (updates.baseUrl) {
138
- provider.baseUrl = updates.baseUrl;
356
+ provider.baseUrl = updates.baseUrl.trim();
357
+ }
358
+ if (updates.api) {
359
+ provider.api = updates.api;
360
+ }
361
+ if (updates.auth) {
362
+ provider.auth = updates.auth;
363
+ }
364
+ if (updates.headers && typeof updates.headers === 'object') {
365
+ provider.headers = updates.headers;
366
+ }
367
+ if (updates.authHeader !== undefined) {
368
+ provider.authHeader = !!updates.authHeader;
369
+ }
370
+ if (updates.apiKey) {
371
+ provider.apiKey = updates.apiKey.trim();
139
372
  }
140
373
 
141
374
  if (provider.models && provider.models.length > 0) {
142
- if (updates.contextWindow) {
143
- provider.models[0].contextWindow = updates.contextWindow;
375
+ if (updates.contextWindow !== undefined) {
376
+ provider.models[0].contextWindow = Number(updates.contextWindow);
144
377
  }
145
- if (updates.maxTokens) {
146
- provider.models[0].maxTokens = updates.maxTokens;
378
+ if (updates.maxTokens !== undefined) {
379
+ provider.models[0].maxTokens = Number(updates.maxTokens);
147
380
  }
148
381
  }
149
382
 
@@ -209,24 +442,31 @@ class ConfigManager {
209
442
  }
210
443
 
211
444
  // 设置主模型
212
- async setPrimaryModel(relayName) {
445
+ async setPrimaryModel(relayName, modelIdOverride) {
213
446
  const config = await this.readOpenclawConfig();
214
- const relays = await this.listRelays();
215
- const relay = relays.find(r => r.name === relayName);
447
+ let modelId = modelIdOverride;
448
+ if (!modelId) {
449
+ const relays = await this.listRelays();
450
+ const relay = relays.find(r => r.name === relayName);
216
451
 
217
- if (!relay) {
218
- throw new Error(`中转站 "${relayName}" 不存在`);
452
+ if (!relay) {
453
+ throw new Error(`中转站 "${relayName}" 不存在`);
454
+ }
455
+ modelId = relay.modelId;
456
+ }
457
+ if (!modelId) {
458
+ throw new Error(`中转站 "${relayName}" 未配置模型 ID`);
219
459
  }
220
460
 
221
461
  if (!config.agents) config.agents = {};
222
462
  if (!config.agents.defaults) config.agents.defaults = {};
223
463
  if (!config.agents.defaults.model) config.agents.defaults.model = {};
224
464
 
225
- config.agents.defaults.model.primary = `${relayName}/${relay.modelId}`;
465
+ config.agents.defaults.model.primary = `${relayName}/${modelId}`;
226
466
 
227
467
  // 确保在 models 中注册
228
468
  if (!config.agents.defaults.models) config.agents.defaults.models = {};
229
- const modelKey = `${relayName}/${relay.modelId}`;
469
+ const modelKey = `${relayName}/${modelId}`;
230
470
  if (!config.agents.defaults.models[modelKey]) {
231
471
  config.agents.defaults.models[modelKey] = {
232
472
  alias: relayName
@@ -268,37 +508,116 @@ class ConfigManager {
268
508
 
269
509
  // 设置 API Key
270
510
  async setApiKey(relayName, apiKey) {
511
+ if (!apiKey || apiKey.trim() === '') {
512
+ throw new Error('API Key 不能为空');
513
+ }
514
+
515
+ const config = await this.readOpenclawConfig();
516
+ if (config.models?.providers?.[relayName]) {
517
+ config.models.providers[relayName].apiKey = apiKey.trim();
518
+ await this.writeOpenclawConfig(config);
519
+ }
520
+
271
521
  const authProfiles = await this.readAuthProfiles();
272
522
  const profileKey = `${relayName}:default`;
273
523
 
524
+ // 保留现有的 token 设置
525
+ const existing = authProfiles[profileKey] || {};
274
526
  authProfiles[profileKey] = {
275
- apiKey: apiKey
527
+ ...existing,
528
+ apiKey: apiKey.trim()
276
529
  };
277
530
 
278
531
  await this.writeAuthProfiles(authProfiles);
279
532
  }
280
533
 
281
- // 列出所有 API Keys
534
+ // 设置 Token
535
+ async setToken(relayName, token) {
536
+ if (!token || token.trim() === '') {
537
+ throw new Error('Token 不能为空');
538
+ }
539
+
540
+ const authProfiles = await this.readAuthProfiles();
541
+ const profileKey = `${relayName}:default`;
542
+
543
+ // 保留现有的 apiKey 设置
544
+ const existing = authProfiles[profileKey] || {};
545
+ authProfiles[profileKey] = {
546
+ ...existing,
547
+ token: token.trim()
548
+ };
549
+
550
+ await this.writeAuthProfiles(authProfiles);
551
+ }
552
+
553
+ // 获取 Token
554
+ async getToken(relayName) {
555
+ const authProfiles = await this.readAuthProfiles();
556
+ const profileKey = `${relayName}:default`;
557
+ return authProfiles[profileKey]?.token || null;
558
+ }
559
+
560
+ // 列出所有 API Keys 和 Tokens
282
561
  async listApiKeys() {
562
+ const config = await this.readOpenclawConfig();
283
563
  const authProfiles = await this.readAuthProfiles();
284
564
  const keys = [];
565
+ const providers = new Set();
285
566
 
286
- for (const [profile, data] of Object.entries(authProfiles)) {
567
+ if (config.models?.providers) {
568
+ Object.keys(config.models.providers).forEach(p => providers.add(p));
569
+ }
570
+ Object.keys(authProfiles).forEach(profile => {
571
+ const provider = profile.split(':')[0];
572
+ providers.add(provider);
573
+ });
574
+
575
+ for (const provider of providers) {
576
+ const profileKey = `${provider}:default`;
577
+ const providerConfig = config.models?.providers?.[provider] || {};
578
+ const profileData = authProfiles[profileKey] || {};
287
579
  keys.push({
288
- provider: profile,
289
- key: data.apiKey || null
580
+ provider,
581
+ key: providerConfig.apiKey || profileData.apiKey || null,
582
+ token: profileData.token || null
290
583
  });
291
584
  }
292
585
 
293
586
  return keys;
294
587
  }
295
588
 
589
+ // 删除 Token
590
+ async deleteToken(provider) {
591
+ const authProfiles = await this.readAuthProfiles();
592
+ const profileKey = provider.includes(':') ? provider : `${provider}:default`;
593
+
594
+ if (authProfiles[profileKey] && authProfiles[profileKey].token) {
595
+ delete authProfiles[profileKey].token;
596
+ // 如果没有其他数据,删除整个 profile
597
+ if (!authProfiles[profileKey].apiKey) {
598
+ delete authProfiles[profileKey];
599
+ }
600
+ await this.writeAuthProfiles(authProfiles);
601
+ }
602
+ }
603
+
296
604
  // 删除 API Key
297
605
  async deleteApiKey(provider) {
606
+ const config = await this.readOpenclawConfig();
607
+ if (config.models?.providers?.[provider]) {
608
+ delete config.models.providers[provider].apiKey;
609
+ await this.writeOpenclawConfig(config);
610
+ }
611
+
298
612
  const authProfiles = await this.readAuthProfiles();
613
+ const profileKey = provider.includes(':') ? provider : `${provider}:default`;
299
614
 
300
- if (authProfiles[provider]) {
301
- delete authProfiles[provider];
615
+ if (authProfiles[profileKey]) {
616
+ delete authProfiles[profileKey].apiKey;
617
+ // 如果没有其他数据,删除整个 profile
618
+ if (!authProfiles[profileKey].token) {
619
+ delete authProfiles[profileKey];
620
+ }
302
621
  await this.writeAuthProfiles(authProfiles);
303
622
  }
304
623
  }
@@ -309,7 +628,12 @@ class ConfigManager {
309
628
  return {
310
629
  maxConcurrent: config.agents?.defaults?.maxConcurrent || 4,
311
630
  subagentMaxConcurrent: config.agents?.defaults?.subagents?.maxConcurrent || 8,
312
- workspace: config.agents?.defaults?.workspace || ''
631
+ workspace: config.agents?.defaults?.workspace || getDefaultWorkspace(),
632
+ compactionMode: config.agents?.defaults?.compaction?.mode || '',
633
+ gatewayAuthMode: config.gateway?.auth?.mode || '',
634
+ gatewayToken: config.gateway?.auth?.token || '',
635
+ gatewayPort: config.gateway?.port,
636
+ gatewayBind: config.gateway?.bind
313
637
  };
314
638
  }
315
639
 
@@ -317,20 +641,41 @@ class ConfigManager {
317
641
  async setAdvancedSettings(settings) {
318
642
  const config = await this.readOpenclawConfig();
319
643
 
644
+ // 验证输入
645
+ if (settings.maxConcurrent !== undefined && !isValidNumber(settings.maxConcurrent, 1, 100)) {
646
+ throw new Error('最大并发任务数必须在 1 到 100 之间');
647
+ }
648
+ if (settings.subagentMaxConcurrent !== undefined && !isValidNumber(settings.subagentMaxConcurrent, 1, 100)) {
649
+ throw new Error('子代理最大并发数必须在 1 到 100 之间');
650
+ }
651
+
320
652
  if (!config.agents) config.agents = {};
321
653
  if (!config.agents.defaults) config.agents.defaults = {};
322
654
 
323
655
  if (settings.maxConcurrent !== undefined) {
324
- config.agents.defaults.maxConcurrent = settings.maxConcurrent;
656
+ config.agents.defaults.maxConcurrent = Number(settings.maxConcurrent);
325
657
  }
326
658
 
327
659
  if (settings.subagentMaxConcurrent !== undefined) {
328
660
  if (!config.agents.defaults.subagents) config.agents.defaults.subagents = {};
329
- config.agents.defaults.subagents.maxConcurrent = settings.subagentMaxConcurrent;
661
+ config.agents.defaults.subagents.maxConcurrent = Number(settings.subagentMaxConcurrent);
330
662
  }
331
663
 
332
664
  if (settings.workspace) {
333
- config.agents.defaults.workspace = settings.workspace;
665
+ // 规范化路径(跨平台)
666
+ config.agents.defaults.workspace = path.normalize(settings.workspace.trim());
667
+ }
668
+
669
+ if (settings.compactionMode) {
670
+ if (!config.agents.defaults.compaction) config.agents.defaults.compaction = {};
671
+ config.agents.defaults.compaction.mode = settings.compactionMode;
672
+ }
673
+
674
+ if (settings.gatewayToken) {
675
+ if (!config.gateway) config.gateway = {};
676
+ if (!config.gateway.auth) config.gateway.auth = {};
677
+ config.gateway.auth.mode = config.gateway.auth.mode || 'token';
678
+ config.gateway.auth.token = settings.gatewayToken.trim();
334
679
  }
335
680
 
336
681
  await this.writeOpenclawConfig(config);
@@ -353,4 +698,10 @@ class ConfigManager {
353
698
  }
354
699
  }
355
700
 
356
- module.exports = { ConfigManager };
701
+ module.exports = {
702
+ ConfigManager,
703
+ getConfigDir,
704
+ getDefaultWorkspace,
705
+ isValidUrl,
706
+ isValidNumber
707
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclawapi",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "cli.js",
6
6
  "bin": {
@@ -29,7 +29,8 @@
29
29
  "dependencies": {
30
30
  "inquirer": "^8.2.5",
31
31
  "chalk": "^4.1.2",
32
- "fs-extra": "^11.1.1"
32
+ "fs-extra": "^11.1.1",
33
+ "json5": "^2.2.3"
33
34
  },
34
35
  "engines": {
35
36
  "node": ">=14.0.0"