@pikecode/api-key-manager 1.0.31 → 1.0.33

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.
@@ -2,7 +2,7 @@ const path = require('path');
2
2
  const inquirer = require('inquirer');
3
3
  const chalk = require('chalk');
4
4
  const Choices = require('inquirer/lib/objects/choices');
5
- const { ConfigManager } = require('../config');
5
+ const { configManager } = require('../config');
6
6
  const { executeWithEnv } = require('../utils/env-launcher');
7
7
  const { executeCodexWithEnv } = require('../utils/codex-launcher');
8
8
  const { Logger } = require('../utils/logger');
@@ -11,16 +11,18 @@ const { findSettingsConflict, backupSettingsFile, clearConflictKeys, saveSetting
11
11
  const { BaseCommand } = require('./BaseCommand');
12
12
  const { validator } = require('../utils/validator');
13
13
  const { ProviderStatusChecker } = require('../utils/provider-status-checker');
14
+ const { maskToken } = require('../utils/secrets');
14
15
 
15
16
  class EnvSwitcher extends BaseCommand {
16
17
  constructor() {
17
18
  super();
18
- this.configManager = new ConfigManager();
19
+ this.configManager = configManager;
19
20
  this.statusChecker = new ProviderStatusChecker();
20
21
  this.latestStatusMap = {};
21
22
  this.currentPromptContext = null;
22
23
  this.activeStatusRefresh = null;
23
24
  this.filter = null;
25
+ this.filteredProviders = null; // 保存过滤后的供应商列表用于状态更新
24
26
  }
25
27
 
26
28
  async validateProvider(providerName) {
@@ -38,6 +40,22 @@ class EnvSwitcher extends BaseCommand {
38
40
  const provider = await this.validateProvider(providerName);
39
41
  const isCodex = provider.ideName === 'codex';
40
42
  const availableArgs = isCodex ? this.getCodexLaunchArgs() : this.getAvailableLaunchArgs();
43
+ const defaultLaunchArgs = Array.isArray(provider.launchArgs) ? provider.launchArgs : [];
44
+ const knownArgNames = new Set(availableArgs.map(arg => arg.name));
45
+ const customLaunchArgs = defaultLaunchArgs
46
+ .filter(arg => typeof arg === 'string' && !knownArgNames.has(arg));
47
+ const mergedArgs = [
48
+ ...availableArgs.map(arg => ({
49
+ ...arg,
50
+ checked: defaultLaunchArgs.includes(arg.name) || Boolean(arg.checked)
51
+ })),
52
+ ...customLaunchArgs.map(name => ({
53
+ name,
54
+ label: name,
55
+ description: '自定义启动参数',
56
+ checked: true
57
+ }))
58
+ ];
41
59
  const ideDisplayName = isCodex ? 'Codex CLI' : 'Claude Code';
42
60
 
43
61
  console.log(UIHelper.createTitle('启动配置', UIHelper.icons.launch));
@@ -65,7 +83,7 @@ class EnvSwitcher extends BaseCommand {
65
83
  type: 'checkbox',
66
84
  name: 'selectedArgs',
67
85
  message: '选择启动参数:',
68
- choices: availableArgs.map(arg => {
86
+ choices: mergedArgs.map(arg => {
69
87
  const commandText = UIHelper.colors.muted(`(${arg.name})`);
70
88
  const descriptionText = arg.description
71
89
  ? ` ${UIHelper.colors.muted(arg.description)}`
@@ -74,7 +92,7 @@ class EnvSwitcher extends BaseCommand {
74
92
  return {
75
93
  name: `${UIHelper.colors.accent(arg.label || arg.name)} ${commandText}${descriptionText}`,
76
94
  value: arg.name,
77
- checked: arg.checked || false
95
+ checked: Boolean(arg.checked)
78
96
  };
79
97
  })
80
98
  }
@@ -265,78 +283,20 @@ class EnvSwitcher extends BaseCommand {
265
283
  }
266
284
 
267
285
  getAvailableLaunchArgs() {
268
- return [
269
- {
270
- name: '--continue',
271
- label: '继续上次对话',
272
- description: '恢复上次的对话记录',
273
- checked: false
274
- },
275
- {
276
- name: '--dangerously-skip-permissions',
277
- label: '最高权限',
278
- description: '仅在沙盒环境中使用',
279
- checked: false
280
- }
281
- ];
286
+ const { getClaudeLaunchArgs } = require('../utils/launch-args');
287
+ return getClaudeLaunchArgs();
282
288
  }
283
289
 
284
290
  checkExclusiveArgs(selectedArgs, availableArgs) {
285
- if (!selectedArgs || selectedArgs.length < 2) {
286
- return null;
287
- }
288
-
289
- for (const argDef of availableArgs) {
290
- if (!argDef.exclusive || !selectedArgs.includes(argDef.name)) {
291
- continue;
292
- }
293
-
294
- for (const exclusiveArg of argDef.exclusive) {
295
- if (selectedArgs.includes(exclusiveArg)) {
296
- const arg1 = availableArgs.find(a => a.name === argDef.name);
297
- const arg2 = availableArgs.find(a => a.name === exclusiveArg);
298
- return `"${arg1?.label || argDef.name}" 和 "${arg2?.label || exclusiveArg}" 不能同时选择`;
299
- }
300
- }
301
- }
302
-
303
- return null;
291
+ const { checkExclusiveArgs } = require('../utils/launch-args');
292
+ return checkExclusiveArgs(selectedArgs, availableArgs);
304
293
  }
305
294
 
306
295
  getCodexLaunchArgs() {
307
- return [
308
- {
309
- name: 'resume',
310
- label: '继续上次对话',
311
- description: '恢复之前的会话',
312
- checked: false,
313
- isSubcommand: true
314
- },
315
- {
316
- name: '--full-auto',
317
- label: '全自动模式',
318
- description: '自动批准 + 工作区写入沙盒',
319
- checked: false,
320
- exclusive: ['--dangerously-bypass-approvals-and-sandbox']
321
- },
322
- {
323
- name: '--dangerously-bypass-approvals-and-sandbox',
324
- label: '跳过审批和沙盒',
325
- description: '危险:跳过所有安全检查',
326
- checked: false,
327
- exclusive: ['--full-auto']
328
- },
329
- {
330
- name: '--search',
331
- label: '启用网页搜索',
332
- description: '允许模型搜索网页',
333
- checked: false
334
- }
335
- ];
296
+ const { getCodexLaunchArgs } = require('../utils/launch-args');
297
+ return getCodexLaunchArgs();
336
298
  }
337
299
 
338
- // getArgDescription 方法已被移除,直接使用 arg.description
339
-
340
300
  async showProviderSelection() {
341
301
  try {
342
302
  // 并行加载配置和准备界面
@@ -348,7 +308,10 @@ class EnvSwitcher extends BaseCommand {
348
308
  } else if (this.filter === 'claude') {
349
309
  providers = providers.filter(p => p.ideName !== 'codex');
350
310
  }
351
-
311
+
312
+ // 保存过滤后的供应商列表用于状态更新
313
+ this.filteredProviders = providers;
314
+
352
315
  const initialStatusMap = this._buildInitialStatusMap(providers);
353
316
  // 显示欢迎界面(立即渲染)
354
317
  this.showWelcomeScreen(providers, initialStatusMap, null);
@@ -394,6 +357,7 @@ class EnvSwitcher extends BaseCommand {
394
357
  const escListener = this.createESCListener(() => {
395
358
  Logger.info('退出程序');
396
359
  this.showExitScreen();
360
+ this.destroy();
397
361
  process.exit(0);
398
362
  }, '退出程序');
399
363
 
@@ -429,6 +393,7 @@ class EnvSwitcher extends BaseCommand {
429
393
  this.currentPromptContext = null;
430
394
  }
431
395
  this._cancelStatusRefresh();
396
+ this.filteredProviders = null; // 清除保存的供应商列表
432
397
  }
433
398
  }
434
399
 
@@ -474,6 +439,7 @@ class EnvSwitcher extends BaseCommand {
474
439
  return await this.showManageMenu();
475
440
  case '__EXIT__':
476
441
  this.showExitScreen();
442
+ this.destroy();
477
443
  process.exit(0);
478
444
  default:
479
445
  return await this.showLaunchArgsSelection(selection);
@@ -1141,7 +1107,8 @@ class EnvSwitcher extends BaseCommand {
1141
1107
  }
1142
1108
 
1143
1109
  const includeActions = this.currentPromptContext === 'manage';
1144
- const providers = this.configManager.listProviders();
1110
+ // 使用保存的过滤后供应商列表,而不是重新获取全部
1111
+ const providers = this.filteredProviders || this.configManager.listProviders();
1145
1112
  const statusMap = this.latestStatusMap || {};
1146
1113
  const updatedChoicesBase = this.createProviderChoices(providers, includeActions, statusMap);
1147
1114
  const updatedChoices = [...updatedChoicesBase];
@@ -1192,6 +1159,7 @@ class EnvSwitcher extends BaseCommand {
1192
1159
  return await this.showProviderSelection();
1193
1160
  case 'exit':
1194
1161
  Logger.info('👋 再见!');
1162
+ this.destroy();
1195
1163
  process.exit(0);
1196
1164
  default:
1197
1165
  // 如果选择的是供应商名称,显示该供应商的详细信息
@@ -1233,9 +1201,13 @@ class EnvSwitcher extends BaseCommand {
1233
1201
  }
1234
1202
 
1235
1203
  // 继续添加其他信息
1204
+ const baseUrlDisplay = provider.baseUrl
1205
+ || ((provider.authMode === 'oauth_token' || provider.authMode === 'auth_token')
1206
+ ? '✨ 官方默认服务器'
1207
+ : '⚠️ 未设置');
1236
1208
  details.push(
1237
- ['基础URL', provider.baseUrl || (provider.authMode === 'oauth_token' ? '✨ 官方默认服务器' : '⚠️ 未设置')],
1238
- ['认证令牌', provider.authToken || '未设置'],
1209
+ ['基础URL', baseUrlDisplay],
1210
+ ['认证令牌', provider.authToken ? maskToken(provider.authToken) : '未设置'],
1239
1211
  ['主模型', provider.models?.primary || '未设置'],
1240
1212
  ['快速模型', provider.models?.smallFast || '未设置'],
1241
1213
  ['创建时间', UIHelper.formatTime(provider.createdAt)],
@@ -1453,7 +1425,8 @@ class EnvSwitcher extends BaseCommand {
1453
1425
  await this.configManager.ensureLoaded();
1454
1426
  const providersMap = this.configManager.config.providers;
1455
1427
 
1456
- if (providersMap[newName] && providersMap[newName] !== provider) {
1428
+ // 如果新名称已存在且不是当前供应商,则报错
1429
+ if (providersMap[newName]) {
1457
1430
  Logger.error(`供应商名称 '${newName}' 已存在,请使用其他名称`);
1458
1431
  return await this.showManageMenu();
1459
1432
  }
package/src/config.js CHANGED
@@ -12,6 +12,17 @@ class ConfigManager {
12
12
  this.loadPromise = null; // 防止并发加载
13
13
  }
14
14
 
15
+ _normalizeOptionalString(value) {
16
+ if (value === null || value === undefined) {
17
+ return null;
18
+ }
19
+ if (typeof value !== 'string') {
20
+ return value;
21
+ }
22
+ const trimmed = value.trim();
23
+ return trimmed.length === 0 ? null : trimmed;
24
+ }
25
+
15
26
  getDefaultConfig() {
16
27
  return {
17
28
  version: '1.0.0',
@@ -78,6 +89,8 @@ class ConfigManager {
78
89
 
79
90
  // 迁移旧的认证模式
80
91
  this._migrateAuthModes();
92
+ // 修复 current 标记与 currentProvider 不一致的问题(兼容旧版本写入的脏数据)
93
+ this._syncCurrentFlags();
81
94
 
82
95
  const stat = await fs.stat(this.configPath);
83
96
  this.lastModified = stat.mtime;
@@ -195,30 +208,78 @@ class ConfigManager {
195
208
  }
196
209
  }
197
210
 
211
+ _syncCurrentFlags() {
212
+ if (!this.config || !this.config.providers) {
213
+ return;
214
+ }
215
+
216
+ const current = this.config.currentProvider;
217
+ const providers = this.config.providers;
218
+ const keys = Object.keys(providers);
219
+
220
+ if (current && providers[current]) {
221
+ keys.forEach((key) => {
222
+ providers[key].current = key === current;
223
+ });
224
+ return;
225
+ }
226
+
227
+ keys.forEach((key) => {
228
+ providers[key].current = false;
229
+ });
230
+ }
231
+
198
232
  async addProvider(name, providerConfig) {
199
233
  await this.ensureLoaded();
200
234
 
201
- const isCodex = providerConfig.ideName === 'codex';
235
+ const existing = this.config.providers[name];
236
+ const now = new Date().toISOString();
237
+
238
+ const ideName = providerConfig.ideName || existing?.ideName || 'claude';
239
+ const isCodex = ideName === 'codex';
240
+
241
+ const baseUrl = this._normalizeOptionalString(
242
+ providerConfig.baseUrl !== undefined ? providerConfig.baseUrl : existing?.baseUrl
243
+ );
202
244
 
245
+ const authToken = providerConfig.authToken !== undefined ? providerConfig.authToken : existing?.authToken;
246
+
247
+ const launchArgs = Array.isArray(providerConfig.launchArgs)
248
+ ? providerConfig.launchArgs
249
+ : (existing?.launchArgs || []);
250
+
251
+ // 基础字段
203
252
  this.config.providers[name] = {
204
253
  name,
205
- displayName: providerConfig.displayName || name,
206
- ideName: providerConfig.ideName || 'claude',
207
- baseUrl: providerConfig.baseUrl,
208
- authToken: providerConfig.authToken,
209
- launchArgs: providerConfig.launchArgs || [],
210
- createdAt: new Date().toISOString(),
211
- lastUsed: new Date().toISOString(),
212
- current: false
254
+ displayName: providerConfig.displayName || existing?.displayName || name,
255
+ ideName,
256
+ baseUrl,
257
+ authToken,
258
+ launchArgs,
259
+ createdAt: existing?.createdAt || now,
260
+ lastUsed: existing?.lastUsed || now,
261
+ usageCount: existing?.usageCount || 0,
262
+ current: Boolean(existing?.current || this.config.currentProvider === name)
213
263
  };
214
264
 
215
265
  // Claude Code 特定字段
216
266
  if (!isCodex) {
217
- this.config.providers[name].authMode = providerConfig.authMode || 'api_key';
218
- this.config.providers[name].tokenType = providerConfig.tokenType || 'api_key';
267
+ const authMode = providerConfig.authMode || existing?.authMode || 'api_key';
268
+ const tokenType = authMode === 'api_key'
269
+ ? (providerConfig.tokenType ?? existing?.tokenType ?? 'api_key')
270
+ : null;
271
+ const primaryModel = providerConfig.primaryModel !== undefined
272
+ ? providerConfig.primaryModel
273
+ : (existing?.models?.primary ?? null);
274
+ const smallFastModel = providerConfig.smallFastModel !== undefined
275
+ ? providerConfig.smallFastModel
276
+ : (existing?.models?.smallFast ?? null);
277
+
278
+ this.config.providers[name].authMode = authMode;
279
+ this.config.providers[name].tokenType = tokenType;
219
280
  this.config.providers[name].models = {
220
- primary: providerConfig.primaryModel || null,
221
- smallFast: providerConfig.smallFastModel || null
281
+ primary: primaryModel,
282
+ smallFast: smallFastModel
222
283
  };
223
284
  } else {
224
285
  // Codex 不需要这些字段,设置为 null 以保持向后兼容
@@ -228,18 +289,15 @@ class ConfigManager {
228
289
  }
229
290
 
230
291
  // 如果是第一个供应商或设置为默认,则设为当前供应商
231
- if (Object.keys(this.config.providers).length === 1 || providerConfig.setAsDefault) {
232
- // 重置所有供应商的current状态
233
- Object.keys(this.config.providers).forEach(key => {
234
- this.config.providers[key].current = false;
235
- });
236
-
237
- // 设置新的当前供应商
238
- this.config.providers[name].current = true;
239
- this.config.providers[name].lastUsed = new Date().toISOString();
292
+ const shouldSetCurrent = (!existing && Object.keys(this.config.providers).length === 1) || providerConfig.setAsDefault;
293
+ if (shouldSetCurrent) {
240
294
  this.config.currentProvider = name;
295
+ this.config.providers[name].lastUsed = now;
241
296
  }
242
297
 
298
+ // 保证 current 标记与 currentProvider 一致,避免出现多个 current 或丢失 current 的情况
299
+ this._syncCurrentFlags();
300
+
243
301
  return await this.save();
244
302
  }
245
303
 
@@ -317,4 +375,7 @@ class ConfigManager {
317
375
  }
318
376
  }
319
377
 
320
- module.exports = { ConfigManager };
378
+ // 单例实例
379
+ const configManager = new ConfigManager();
380
+
381
+ module.exports = { ConfigManager, configManager };
@@ -1,46 +1,5 @@
1
1
  const spawn = require('cross-spawn');
2
-
3
- /**
4
- * 清理环境变量值,移除危险字符
5
- * @param {string} value - 要清理的值
6
- * @returns {string} 清理后的值
7
- */
8
- function sanitizeEnvValue(value) {
9
- if (typeof value !== 'string') {
10
- throw new Error('环境变量值必须是字符串');
11
- }
12
-
13
- // 移除控制字符
14
- let cleaned = value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
15
-
16
- // 检测可能的 shell 命令注入
17
- if (/[;&|`$()]/.test(cleaned)) {
18
- throw new Error('环境变量值包含潜在不安全的字符');
19
- }
20
-
21
- return cleaned;
22
- }
23
-
24
- function clearTerminal() {
25
- if (!process.stdout || typeof process.stdout.write !== 'function') {
26
- return;
27
- }
28
-
29
- try {
30
- process.stdout.write('\x1bc');
31
- } catch (error) {
32
- // 某些终端可能不支持 RIS 序列,忽略即可
33
- }
34
-
35
- const sequence = process.platform === 'win32'
36
- ? '\x1b[3J\x1b[2J\x1b[0f'
37
- : '\x1b[3J\x1b[2J\x1b[H';
38
- try {
39
- process.stdout.write(sequence);
40
- } catch (error) {
41
- // 忽略清屏失败
42
- }
43
- }
2
+ const { sanitizeEnvValue, clearTerminal } = require('./env-utils');
44
3
 
45
4
  /**
46
5
  * 构建 Codex CLI 环境变量
@@ -1,46 +1,5 @@
1
1
  const spawn = require('cross-spawn');
2
-
3
- /**
4
- * 清理环境变量值,移除危险字符
5
- * @param {string} value - 要清理的值
6
- * @returns {string} 清理后的值
7
- */
8
- function sanitizeEnvValue(value) {
9
- if (typeof value !== 'string') {
10
- throw new Error('环境变量值必须是字符串');
11
- }
12
-
13
- // 移除控制字符
14
- let cleaned = value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
15
-
16
- // 检测可能的 shell 命令注入
17
- if (/[;&|`$()]/.test(cleaned)) {
18
- throw new Error('环境变量值包含潜在不安全的字符');
19
- }
20
-
21
- return cleaned;
22
- }
23
-
24
- function clearTerminal() {
25
- if (!process.stdout || typeof process.stdout.write !== 'function') {
26
- return;
27
- }
28
-
29
- try {
30
- process.stdout.write('\x1bc');
31
- } catch (error) {
32
- // 某些终端可能不支持 RIS 序列,忽略即可
33
- }
34
-
35
- const sequence = process.platform === 'win32'
36
- ? '\x1b[3J\x1b[2J\x1b[0f'
37
- : '\x1b[3J\x1b[2J\x1b[H';
38
- try {
39
- process.stdout.write(sequence);
40
- } catch (error) {
41
- // 忽略清屏失败
42
- }
43
- }
2
+ const { sanitizeEnvValue, clearTerminal } = require('./env-utils');
44
3
 
45
4
  function buildEnvVariables(config) {
46
5
  const env = { ...process.env };
@@ -50,6 +9,9 @@ function buildEnvVariables(config) {
50
9
  if (config.authMode === 'oauth_token') {
51
10
  env.CLAUDE_CODE_OAUTH_TOKEN = sanitizeEnvValue(config.authToken);
52
11
  } else if (config.authMode === 'api_key') {
12
+ if (!config.baseUrl) {
13
+ throw new Error('未配置基础地址');
14
+ }
53
15
  env.ANTHROPIC_BASE_URL = sanitizeEnvValue(config.baseUrl);
54
16
  // 根据 tokenType 选择设置哪种 token
55
17
  if (config.tokenType === 'auth_token') {
@@ -60,7 +22,9 @@ function buildEnvVariables(config) {
60
22
  }
61
23
  } else {
62
24
  // auth_token 模式
63
- env.ANTHROPIC_BASE_URL = sanitizeEnvValue(config.baseUrl);
25
+ if (config.baseUrl) {
26
+ env.ANTHROPIC_BASE_URL = sanitizeEnvValue(config.baseUrl);
27
+ }
64
28
  env.ANTHROPIC_AUTH_TOKEN = sanitizeEnvValue(config.authToken);
65
29
  }
66
30
 
@@ -0,0 +1,52 @@
1
+ /**
2
+ * 环境变量工具函数
3
+ * 提供 env-launcher.js 和 codex-launcher.js 共用的功能
4
+ */
5
+
6
+ /**
7
+ * 清理环境变量值,移除危险字符
8
+ * @param {string} value - 要清理的值
9
+ * @returns {string} 清理后的值
10
+ */
11
+ function sanitizeEnvValue(value) {
12
+ if (typeof value !== 'string') {
13
+ throw new Error('环境变量值必须是字符串');
14
+ }
15
+
16
+ // 移除控制字符
17
+ let cleaned = value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
18
+
19
+ // 检测可能的 shell 命令注入(允许 $ 因为 token 可能包含)
20
+ // 只禁止明确的命令分隔符和反引号执行
21
+ if (/[;&|`]/.test(cleaned)) {
22
+ throw new Error('环境变量值包含潜在不安全的字符');
23
+ }
24
+
25
+ return cleaned;
26
+ }
27
+
28
+ /**
29
+ * 清屏函数
30
+ */
31
+ function clearTerminal() {
32
+ if (!process.stdout || typeof process.stdout.write !== 'function') {
33
+ return;
34
+ }
35
+
36
+ try {
37
+ process.stdout.write('\x1bc');
38
+ } catch (error) {
39
+ // 某些终端可能不支持 RIS 序列,忽略即可
40
+ }
41
+
42
+ const sequence = process.platform === 'win32'
43
+ ? '\x1b[3J\x1b[2J\x1b[0f'
44
+ : '\x1b[3J\x1b[2J\x1b[H';
45
+ try {
46
+ process.stdout.write(sequence);
47
+ } catch (error) {
48
+ // 忽略清屏失败
49
+ }
50
+ }
51
+
52
+ module.exports = { sanitizeEnvValue, clearTerminal };
@@ -1,5 +1,5 @@
1
1
  const chalk = require('chalk');
2
- const { Logger } = require('./utils/logger');
2
+ const { Logger } = require('./logger');
3
3
 
4
4
  class ErrorHandler {
5
5
  static handle(error, context = '') {
@@ -0,0 +1,96 @@
1
+ /**
2
+ * 启动参数定义模块
3
+ * 统一管理 Claude Code 和 Codex CLI 的启动参数
4
+ */
5
+
6
+ const claudeLaunchArgs = [
7
+ {
8
+ name: '--continue',
9
+ label: '继续上次对话',
10
+ description: '恢复上次的对话记录',
11
+ checked: false
12
+ },
13
+ {
14
+ name: '--dangerously-skip-permissions',
15
+ label: '最高权限',
16
+ description: '仅在沙盒环境中使用',
17
+ checked: false
18
+ }
19
+ ];
20
+
21
+ const codexLaunchArgs = [
22
+ {
23
+ name: 'resume',
24
+ label: '继续上次对话',
25
+ description: '恢复之前的会话',
26
+ checked: false,
27
+ isSubcommand: true
28
+ },
29
+ {
30
+ name: '--full-auto',
31
+ label: '全自动模式',
32
+ description: '自动批准 + 工作区写入沙盒',
33
+ checked: false,
34
+ exclusive: ['--dangerously-bypass-approvals-and-sandbox']
35
+ },
36
+ {
37
+ name: '--dangerously-bypass-approvals-and-sandbox',
38
+ label: '跳过审批和沙盒',
39
+ description: '危险:跳过所有安全检查',
40
+ checked: false,
41
+ exclusive: ['--full-auto']
42
+ },
43
+ {
44
+ name: '--search',
45
+ label: '启用网页搜索',
46
+ description: '允许模型搜索网页',
47
+ checked: false
48
+ }
49
+ ];
50
+
51
+ function getClaudeLaunchArgs() {
52
+ return claudeLaunchArgs.map(arg => ({ ...arg }));
53
+ }
54
+
55
+ function getCodexLaunchArgs() {
56
+ return codexLaunchArgs.map(arg => ({ ...arg }));
57
+ }
58
+
59
+ function getLaunchArgs(ideName) {
60
+ return ideName === 'codex' ? getCodexLaunchArgs() : getClaudeLaunchArgs();
61
+ }
62
+
63
+ /**
64
+ * 检查互斥参数
65
+ * @param {string[]} selectedArgs - 选中的参数列表
66
+ * @param {Array} availableArgs - 可用参数定义
67
+ * @returns {string|null} 冲突错误信息或 null
68
+ */
69
+ function checkExclusiveArgs(selectedArgs, availableArgs) {
70
+ if (!selectedArgs || selectedArgs.length < 2) {
71
+ return null;
72
+ }
73
+
74
+ for (const argDef of availableArgs) {
75
+ if (!argDef.exclusive || !selectedArgs.includes(argDef.name)) {
76
+ continue;
77
+ }
78
+
79
+ for (const exclusiveArg of argDef.exclusive) {
80
+ if (selectedArgs.includes(exclusiveArg)) {
81
+ const arg1 = availableArgs.find(a => a.name === argDef.name);
82
+ const arg2 = availableArgs.find(a => a.name === exclusiveArg);
83
+ return `"${arg1?.label || argDef.name}" 和 "${arg2?.label || exclusiveArg}" 不能同时选择`;
84
+ }
85
+ }
86
+ }
87
+
88
+ return null;
89
+ }
90
+
91
+ module.exports = {
92
+ getClaudeLaunchArgs,
93
+ getCodexLaunchArgs,
94
+ getLaunchArgs,
95
+ checkExclusiveArgs
96
+ };