@pikecode/api-key-manager 1.1.2 → 1.2.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/src/config.js CHANGED
@@ -264,16 +264,30 @@ class ConfigManager {
264
264
  const providers = this.config.providers;
265
265
  const keys = Object.keys(providers);
266
266
 
267
+ // 创建新的 providers 对象,不直接修改原对象
268
+ const updatedProviders = {};
269
+
267
270
  if (current && providers[current]) {
268
271
  keys.forEach((key) => {
269
- providers[key].current = key === current;
272
+ updatedProviders[key] = {
273
+ ...providers[key],
274
+ current: key === current
275
+ };
276
+ });
277
+ } else {
278
+ keys.forEach((key) => {
279
+ updatedProviders[key] = {
280
+ ...providers[key],
281
+ current: false
282
+ };
270
283
  });
271
- return;
272
284
  }
273
285
 
274
- keys.forEach((key) => {
275
- providers[key].current = false;
276
- });
286
+ // 只在最外层更新 config
287
+ this.config = {
288
+ ...this.config,
289
+ providers: updatedProviders
290
+ };
277
291
  }
278
292
 
279
293
  async addProvider(name, providerConfig) {
@@ -300,8 +314,8 @@ class ConfigManager {
300
314
  ? this._normalizeOptionalString(providerConfig.alias)
301
315
  : (existing?.alias || null);
302
316
 
303
- // 基础字段
304
- this.config.providers[name] = {
317
+ // 创建新的 provider 对象
318
+ const newProvider = {
305
319
  name,
306
320
  displayName: providerConfig.displayName || existing?.displayName || name,
307
321
  alias,
@@ -326,24 +340,36 @@ class ConfigManager {
326
340
  ? providerConfig.smallFastModel
327
341
  : (existing?.models?.smallFast ?? null);
328
342
 
329
- this.config.providers[name].authMode = authMode;
330
- this.config.providers[name].models = {
343
+ newProvider.authMode = authMode;
344
+ newProvider.models = {
331
345
  primary: primaryModel,
332
346
  smallFast: smallFastModel
333
347
  };
334
348
  } else {
335
349
  // Codex 不需要这些字段,设置为 null 以保持向后兼容
336
- this.config.providers[name].authMode = null;
337
- this.config.providers[name].models = null;
350
+ newProvider.authMode = null;
351
+ newProvider.models = null;
338
352
  }
339
353
 
340
354
  // 如果是第一个供应商或设置为默认,则设为当前供应商
341
- const shouldSetCurrent = (!existing && Object.keys(this.config.providers).length === 1) || providerConfig.setAsDefault;
355
+ const shouldSetCurrent = (!existing && Object.keys(this.config.providers).length === 0) || providerConfig.setAsDefault;
356
+
357
+ let newCurrentProvider = this.config.currentProvider;
342
358
  if (shouldSetCurrent) {
343
- this.config.currentProvider = name;
344
- this.config.providers[name].lastUsed = now;
359
+ newCurrentProvider = name;
360
+ newProvider.lastUsed = now;
345
361
  }
346
362
 
363
+ // 使用不可变方式更新 config
364
+ this.config = {
365
+ ...this.config,
366
+ currentProvider: newCurrentProvider,
367
+ providers: {
368
+ ...this.config.providers,
369
+ [name]: newProvider
370
+ }
371
+ };
372
+
347
373
  // 保证 current 标记与 currentProvider 一致,避免出现多个 current 或丢失 current 的情况
348
374
  this._syncCurrentFlags();
349
375
 
@@ -357,12 +383,18 @@ class ConfigManager {
357
383
  throw new Error(`供应商 '${name}' 不存在\n使用 'akm list' 查看所有已配置的供应商`);
358
384
  }
359
385
 
360
- delete this.config.providers[name];
386
+ // 创建新的 providers 对象,排除要删除的供应商
387
+ const { [name]: removed, ...remainingProviders } = this.config.providers;
361
388
 
362
389
  // 如果删除的是当前供应商,清空当前供应商
363
- if (this.config.currentProvider === name) {
364
- this.config.currentProvider = null;
365
- }
390
+ const newCurrentProvider = this.config.currentProvider === name ? null : this.config.currentProvider;
391
+
392
+ // 使用不可变方式更新 config
393
+ this.config = {
394
+ ...this.config,
395
+ currentProvider: newCurrentProvider,
396
+ providers: remainingProviders
397
+ };
366
398
 
367
399
  return await this.save();
368
400
  }
@@ -374,15 +406,24 @@ class ConfigManager {
374
406
  throw new Error(`供应商 '${name}' 不存在\n使用 'akm list' 查看所有已配置的供应商`);
375
407
  }
376
408
 
377
- // 重置所有供应商的current状态
409
+ const now = new Date().toISOString();
410
+
411
+ // 创建新的 providers 对象,重置所有 current 状态
412
+ const updatedProviders = {};
378
413
  Object.keys(this.config.providers).forEach(key => {
379
- this.config.providers[key].current = false;
414
+ updatedProviders[key] = {
415
+ ...this.config.providers[key],
416
+ current: key === name,
417
+ lastUsed: key === name ? now : this.config.providers[key].lastUsed
418
+ };
380
419
  });
381
420
 
382
- // 设置新的当前供应商
383
- this.config.providers[name].current = true;
384
- this.config.providers[name].lastUsed = new Date().toISOString();
385
- this.config.currentProvider = name;
421
+ // 使用不可变方式更新 config
422
+ this.config = {
423
+ ...this.config,
424
+ currentProvider: name,
425
+ providers: updatedProviders
426
+ };
386
427
 
387
428
  return await this.save();
388
429
  }
@@ -400,12 +441,25 @@ class ConfigManager {
400
441
  throw new Error(`供应商 '${name}' 不存在`);
401
442
  }
402
443
 
403
- // 更新上次使用的启动参数
404
- this.config.providers[name].lastUsedArgs = args;
405
- this.config.providers[name].lastUsed = new Date().toISOString();
444
+ const now = new Date().toISOString();
445
+ const provider = this.config.providers[name];
406
446
 
407
- // 增加使用次数
408
- this.config.providers[name].usageCount = (this.config.providers[name].usageCount || 0) + 1;
447
+ // 创建更新后的 provider 对象
448
+ const updatedProvider = {
449
+ ...provider,
450
+ lastUsedArgs: args,
451
+ lastUsed: now,
452
+ usageCount: (provider.usageCount || 0) + 1
453
+ };
454
+
455
+ // 使用不可变方式更新 config
456
+ this.config = {
457
+ ...this.config,
458
+ providers: {
459
+ ...this.config.providers,
460
+ [name]: updatedProvider
461
+ }
462
+ };
409
463
 
410
464
  return await this.save();
411
465
  }
@@ -423,28 +477,43 @@ class ConfigManager {
423
477
  }
424
478
 
425
479
  const provider = this.config.providers[name];
480
+ const now = new Date().toISOString();
426
481
 
427
- // 初始化统计数据
428
- if (!provider.stats) {
429
- provider.stats = {
430
- totalSessions: 0,
431
- totalDurationMs: 0,
432
- averageDurationMs: 0,
433
- lastSessionDuration: 0,
434
- firstUsed: new Date().toISOString()
435
- };
436
- }
482
+ // 初始化或更新统计数据
483
+ const existingStats = provider.stats || {
484
+ totalSessions: 0,
485
+ totalDurationMs: 0,
486
+ averageDurationMs: 0,
487
+ lastSessionDuration: 0,
488
+ firstUsed: now
489
+ };
437
490
 
438
- // 更新统计
439
- provider.stats.totalSessions = (provider.stats.totalSessions || 0) + 1;
440
- provider.stats.totalDurationMs = (provider.stats.totalDurationMs || 0) + durationMs;
441
- provider.stats.lastSessionDuration = durationMs;
442
- provider.stats.averageDurationMs = Math.round(
443
- provider.stats.totalDurationMs / provider.stats.totalSessions
444
- );
491
+ const newTotalSessions = (existingStats.totalSessions || 0) + 1;
492
+ const newTotalDurationMs = (existingStats.totalDurationMs || 0) + durationMs;
493
+
494
+ const updatedStats = {
495
+ ...existingStats,
496
+ totalSessions: newTotalSessions,
497
+ totalDurationMs: newTotalDurationMs,
498
+ lastSessionDuration: durationMs,
499
+ averageDurationMs: Math.round(newTotalDurationMs / newTotalSessions)
500
+ };
445
501
 
446
- // 更新最后使用时间
447
- provider.lastUsed = new Date().toISOString();
502
+ // 创建更新后的 provider 对象
503
+ const updatedProvider = {
504
+ ...provider,
505
+ stats: updatedStats,
506
+ lastUsed: now
507
+ };
508
+
509
+ // 使用不可变方式更新 config
510
+ this.config = {
511
+ ...this.config,
512
+ providers: {
513
+ ...this.config.providers,
514
+ [name]: updatedProvider
515
+ }
516
+ };
448
517
 
449
518
  return await this.save();
450
519
  }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * UI 文案常量
3
+ * 集中管理所有用户界面文本,便于维护和本地化
4
+ */
5
+
6
+ const UI_MESSAGES = {
7
+ // 通用操作
8
+ CANCEL: '取消',
9
+ CONFIRM: '确认',
10
+ BACK: '返回',
11
+ SKIP: '跳过',
12
+
13
+ // ESC 键提示
14
+ ESC_CANCEL: '取消操作',
15
+ ESC_BACK_TO_MENU: '返回上级菜单',
16
+ ESC_CANCEL_ADD: '取消添加',
17
+ ESC_CANCEL_CLONE: '取消克隆',
18
+ ESC_CANCEL_EDIT: '取消编辑',
19
+ ESC_SKIP_CONFIG: '跳过配置',
20
+
21
+ // 添加供应商
22
+ ADD_PROVIDER_TITLE: '添加新供应商',
23
+ ADD_PROVIDER_TOOLTIP: '请填写供应商配置信息',
24
+ ADD_PROVIDER_STEP_1: '填写供应商信息',
25
+ ADD_PROVIDER_STEP_2_LAUNCH_ARGS: '可选: 配置启动参数',
26
+ ADD_PROVIDER_STEP_2_MODELS: '可选: 配置模型参数',
27
+ ADD_PROVIDER_SUCCESS: '供应商添加完成',
28
+ ADD_PROVIDER_CANCELLED: '取消添加供应商',
29
+
30
+ // 克隆供应商
31
+ CLONE_PROVIDER_TITLE: '克隆供应商',
32
+ CLONE_PROVIDER_SUCCESS: '供应商克隆成功!',
33
+ CLONE_PROVIDER_CANCELLED: '取消克隆供应商',
34
+
35
+ // 配置启动参数
36
+ CONFIG_LAUNCH_ARGS_TITLE: '配置启动参数',
37
+ CONFIG_LAUNCH_ARGS_TOOLTIP: '选择要使用的启动参数',
38
+ CONFIG_LAUNCH_ARGS_SKIP: '跳过启动参数配置',
39
+
40
+ // 配置 Codex 启动参数
41
+ CONFIG_CODEX_LAUNCH_ARGS_TITLE: '配置 Codex 启动参数',
42
+ CONFIG_CODEX_LAUNCH_ARGS_TOOLTIP: '选择要使用的 Codex 启动参数',
43
+ CONFIG_CODEX_LAUNCH_ARGS_SKIP: '跳过 Codex 启动参数配置',
44
+
45
+ // 配置模型参数
46
+ CONFIG_MODELS_TITLE: '配置模型参数',
47
+ CONFIG_MODELS_TOOLTIP: '配置主模型和快速模型(可选)',
48
+ CONFIG_MODELS_SKIP: '跳过模型参数配置',
49
+
50
+ // 键盘提示
51
+ HINT_ENTER: 'Enter',
52
+ HINT_ENTER_DESC: '确认输入',
53
+ HINT_TAB: 'Tab',
54
+ HINT_TAB_DESC: '切换字段',
55
+ HINT_ESC: 'ESC',
56
+ HINT_ESC_CANCEL: '取消添加',
57
+ HINT_ESC_SKIP: '跳过配置',
58
+ HINT_SPACE: '空格',
59
+ HINT_SPACE_DESC: '切换选中',
60
+ HINT_A: 'A',
61
+ HINT_A_DESC: '全选',
62
+ HINT_I: 'I',
63
+ HINT_I_DESC: '反选',
64
+ HINT_ENTER_CONFIRM: '确认选择',
65
+
66
+ // 提示信息
67
+ SELECT_IDE: '选择要管理的 IDE:',
68
+ IMPORT_FROM_CODEX: '是否从现有 Codex 配置导入?',
69
+ IMPORT_FROM_CODEX_EXISTING: '从 ~/.codex 导入现有配置',
70
+ IMPORT_FROM_CODEX_MANUAL: '手动输入配置',
71
+ INPUT_PROVIDER_NAME: '请输入供应商名称:',
72
+ SELECT_AUTH_MODE: '选择认证模式:',
73
+ INPUT_BASE_URL: '请输入 API 基础URL (ANTHROPIC_BASE_URL):',
74
+ INPUT_OPENAI_BASE_URL: '请输入 OpenAI API 基础URL (如使用官方API可留空):',
75
+ INPUT_TOKEN: '请输入 Token',
76
+ INPUT_OPENAI_API_KEY: '请输入 OpenAI API Key (OPENAI_API_KEY):',
77
+ SET_AS_DEFAULT: '是否设置为当前供应商?',
78
+ CONFIGURE_LAUNCH_ARGS: '是否配置启动参数?',
79
+ CONFIGURE_CODEX_LAUNCH_ARGS: '是否配置 Codex 启动参数?',
80
+ CONFIGURE_MODELS: '是否配置模型参数?',
81
+ SELECT_LAUNCH_ARGS: '请选择启动参数:',
82
+ INPUT_PRIMARY_MODEL: '主模型 (ANTHROPIC_MODEL):',
83
+ INPUT_SMALL_FAST_MODEL: '快速模型 (ANTHROPIC_SMALL_FAST_MODEL):',
84
+
85
+ // IDE 选项
86
+ IDE_CLAUDE_CODE: 'Claude Code (Anthropic)',
87
+ IDE_CODEX: 'Codex CLI (OpenAI)',
88
+
89
+ // 认证模式
90
+ AUTH_MODE_API_KEY: '🔑 ANTHROPIC_API_KEY - 大多数第三方代理使用',
91
+ AUTH_MODE_AUTH_TOKEN: '🔐 ANTHROPIC_AUTH_TOKEN - 部分服务商使用',
92
+
93
+ // 操作结果
94
+ OPERATION_CANCELLED: '操作已取消',
95
+ OPERATION_SUCCESS: '操作成功',
96
+
97
+ // 供应商信息显示
98
+ PROVIDER_NAME: '名称',
99
+ PROVIDER_IDE: 'IDE',
100
+ PROVIDER_BASE_URL: '基础 URL',
101
+ PROVIDER_TOKEN: 'Token',
102
+ PROVIDER_LAUNCH_ARGS: '启动参数',
103
+ PROVIDER_MODEL_CONFIG: '模型配置',
104
+ PROVIDER_PRIMARY_MODEL: '主模型',
105
+ PROVIDER_SMALL_FAST_MODEL: '快速模型'
106
+ };
107
+
108
+ module.exports = { UI_MESSAGES };
@@ -31,8 +31,8 @@ class UIHelper {
31
31
  static createStatus(status, label) {
32
32
  const statusConfig = {
33
33
  current: { icon: UIHelper.icons.current, color: UIHelper.colors.success },
34
- active: { icon: '🟢', color: UIHelper.colors.success },
35
- inactive: { icon: '', color: UIHelper.colors.muted },
34
+ active: { icon: chalk.green(''), color: UIHelper.colors.success },
35
+ inactive: { icon: chalk.gray('·'), color: UIHelper.colors.muted },
36
36
  loading: { icon: UIHelper.icons.loading, color: UIHelper.colors.warning },
37
37
  error: { icon: UIHelper.icons.error, color: UIHelper.colors.error }
38
38
  };
@@ -208,23 +208,23 @@ UIHelper.colors = {
208
208
 
209
209
  // 图标
210
210
  UIHelper.icons = {
211
- success: '',
212
- error: '',
213
- warning: '⚠️',
214
- info: 'ℹ️',
215
- loading: '',
211
+ success: '',
212
+ error: '',
213
+ warning: '!',
214
+ info: 'i',
215
+ loading: '',
216
216
  arrow: '→',
217
- back: '🔙',
218
- home: '🏠',
219
- settings: '⚙️',
220
- add: '',
221
- edit: '✏️',
222
- delete: '🗑️',
223
- launch: '🚀',
224
- list: '📋',
225
- config: '🛠️',
226
- current: '🎯',
227
- search: '🔍'
217
+ back: '',
218
+ home: '~',
219
+ settings: '*',
220
+ add: '+',
221
+ edit: '~',
222
+ delete: '-',
223
+ launch: '',
224
+ list: '',
225
+ config: '#',
226
+ current: '',
227
+ search: '?'
228
228
  };
229
229
 
230
230
  module.exports = { UIHelper };
@@ -1,3 +1,5 @@
1
+ const { getClaudeLaunchArgs } = require('./launch-args');
2
+
1
3
  const validator = {
2
4
  validateName(name) {
3
5
  if (!name || typeof name !== 'string') {
@@ -8,11 +10,6 @@ const validator = {
8
10
  return '供应商名称不能为空或只包含空格';
9
11
  }
10
12
 
11
- // 禁止文件系统特殊字符
12
- if (/[<>:"/\\|?*\x00-\x1F]/.test(name)) {
13
- return '供应商名称包含非法字符 (不能包含: < > : " / \\ | ? *)';
14
- }
15
-
16
13
  // 禁止使用保留名称 (Windows)
17
14
  const reserved = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4',
18
15
  'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2',
@@ -21,11 +18,6 @@ const validator = {
21
18
  return '供应商名称不能使用系统保留名称';
22
19
  }
23
20
 
24
- // 禁止以点或空格开头/结尾 (Windows 限制)
25
- if (/^[. ]|[. ]$/.test(name)) {
26
- return '供应商名称不能以点或空格开头/结尾';
27
- }
28
-
29
21
  if (name.length > 100) {
30
22
  return '供应商名称不能超过100个字符';
31
23
  }
@@ -77,10 +69,6 @@ const validator = {
77
69
  return 'Token不能只包含空格';
78
70
  }
79
71
 
80
- if (token.trim().length < 10) {
81
- return 'Token长度不能少于10个字符';
82
- }
83
-
84
72
  // 检测常见的占位符文本
85
73
  const placeholders = [
86
74
  'your-key-here', 'your-token', 'your_key', 'your_token',
@@ -118,7 +106,6 @@ const validator = {
118
106
  },
119
107
 
120
108
  getAvailableLaunchArgs() {
121
- const { getClaudeLaunchArgs } = require('./launch-args');
122
109
  return getClaudeLaunchArgs();
123
110
  }
124
111
  };