@kikkimo/claude-launcher 1.0.0 → 2.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/docs/README-zh.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Claude Launcher
2
2
 
3
- 一个优雅的 Claude Code 交互式启动器,具有美观的 Claude 风格界面。通过直观的命令行菜单使用各种配置启动 Claude Code。
3
+ 一个优雅的 Claude Code 交互式启动器,具有美观的 Claude 风格界面和全面的第三方 API 管理功能。通过直观的多语言命令行菜单使用各种配置启动 Claude Code。
4
4
 
5
5
  ## 📖 文档
6
6
 
@@ -9,13 +9,33 @@
9
9
 
10
10
  ## ✨ 特性
11
11
 
12
- - 🎨 **Claude 风格界面** - 采用正宗的橙色/琥珀色配色方案
13
- - ⌨️ **方向键导航** - 支持方向键导航,非 TTY 环境下支持数字选择
14
- - 🔐 **加密 API 密钥存储** - 使用 AES-256-CBC 加密
15
- - 🚀 **多种启动选项** - 包括跳过权限检查和 Kimi K2 API
16
- - 🌍 **全局安装** - 在任何地方都可以使用 `claude-launcher`
17
- - 🔧 **智能配置** - 自动查找/创建配置文件
18
- - 💻 **跨平台支持** - Windows、macOS 和 Linux
12
+ ### 🎨 **精美界面**
13
+ - Claude 风格界面,采用正宗的橙色/琥珀色配色方案
14
+ - 方向键导航,流畅的菜单切换
15
+ - API 选择和管理的交互式表格
16
+ - 多语言支持(8种语言包括中文、英文、德文、法文、日文、韩文、俄文、西班牙文)
17
+
18
+ ### 🔐 **高级安全**
19
+ - 所有敏感数据使用 AES-256-CBC 加密
20
+ - 机器特定的加密密钥增强安全性
21
+ - 密码保护的配置导入/导出
22
+ - 安全的 API 令牌存储,带掩码显示
23
+ - 强密码要求和验证
24
+
25
+ ### 🚀 **第三方 API 管理**
26
+ - 全面支持多个第三方 API 提供商
27
+ - 带验证的交互式 API 配置
28
+ - API 使用统计和跟踪
29
+ - 安全的配置备份和恢复
30
+ - 简单的 API 切换和删除
31
+
32
+ ### 🌍 **企业级功能**
33
+ - 全局安装 - 在任何地方都可以使用 `claude-launcher`
34
+ - 模块化架构,包含 28+ 个专门模块
35
+ - 全面的错误处理和恢复
36
+ - 版本更新检查,自动通知
37
+ - 跨平台支持(Windows、macOS、Linux)
38
+ - 新用户首次设置向导
19
39
 
20
40
  ## 🚀 快速开始
21
41
 
@@ -29,9 +49,12 @@
29
49
  claude-launcher
30
50
  ```
31
51
 
32
- 3. **Kimi API 用户:** 首次使用时,输入以 `sk-` 开头的 Kimi API 密钥
52
+ 3. **首次设置:** 启动器将引导您完成:
53
+ - 语言选择(提供8种语言)
54
+ - 安全设置(配置导入/导出的密码)
55
+ - 第三方 API 配置(如果需要)
33
56
 
34
- 就这么简单!启动器会引导您完成设置过程。
57
+ 就这么简单!直观的界面会引导您了解所有可用选项。
35
58
 
36
59
  ## 📦 安装
37
60
 
@@ -54,19 +77,23 @@ node claude-launcher
54
77
 
55
78
  ## 🎮 使用方法
56
79
 
57
- ### 可用的启动选项
80
+ ### 可用的选项
58
81
 
59
82
  1. **启动 Claude Code** - 标准 Claude Code 启动
60
83
  2. **启动 Claude Code(跳过权限)** - 使用 `--dangerously-skip-permissions` 启动
61
- 3. **使用 Kimi K2 API 启动 Claude Code** - 使用加密存储的 Kimi API
62
- 4. **使用 Kimi K2 API 启动 Claude Code(跳过权限)** - 结合 Kimi API 和跳过权限
63
- 5. **退出** - 关闭启动器
84
+ 3. **使用第三方 API 启动 Claude Code** - 使用配置的第三方 API
85
+ 4. **使用第三方 API 启动 Claude Code(跳过权限)** - 结合第三方 API 和跳过权限
86
+ 5. **第三方 API 管理** - 配置、切换、删除 API,查看统计信息
87
+ 6. **语言设置** - 在8种支持的语言之间切换
88
+ 7. **版本更新检查** - 检查启动器更新
89
+ 8. **退出** - 关闭启动器
64
90
 
65
91
  ### 交互式导航
66
92
 
67
- - **方向键**:使用 ↑↓ 导航,Enter 选择(在 TTY 环境中)
68
- - **数字选择**:输入 1-5 并按 Enter(在非 TTY 环境中)
69
- - **快速退出**:随时按 Esc 或 Q 退出
93
+ - **方向键**:使用 ↑↓ 导航,Enter 选择
94
+ - **Escape 键**:按 Esc 返回或退出
95
+ - **多语言**:所有界面文本适应您选择的语言
96
+ - **智能表格**:API 管理的交互式表格,具有清晰的视觉反馈
70
97
 
71
98
  ### 示例会话
72
99
 
@@ -77,48 +104,80 @@ $ claude-launcher
77
104
  │ Claude Code Launcher │
78
105
  └────────────────────────────────────────┘
79
106
 
80
- Use ↑↓ arrow keys to navigate, Enter to select
107
+ 使用 ↑↓ 方向键导航,Enter 选择
81
108
 
82
- Launch Claude Code
83
- Launch Claude Code (Skip Permissions)
84
- Launch Claude Code with Kimi K2 API
85
- Launch Claude Code with Kimi K2 API (Skip Permissions)
86
- Exit
109
+ 启动 Claude Code
110
+ 启动 Claude Code(跳过权限)
111
+ 使用第三方 API 启动 Claude Code
112
+ 使用第三方 API 启动 Claude Code(跳过权限)
113
+ 第三方 API 管理
114
+ 语言设置
115
+ 版本更新检查
116
+ 退出
117
+ ```
118
+
119
+ ### 第三方 API 管理
120
+
121
+ 通过专门的菜单访问全面的 API 管理:
122
+
123
+ ```bash
124
+ 📋 第三方 API 管理
125
+
126
+ → 添加新 API
127
+ 删除 API
128
+ 切换活动 API
129
+ 查看统计信息
130
+ 导出配置
131
+ 导入配置
132
+ 更改密码
133
+ 返回主菜单
87
134
  ```
88
135
 
89
136
  ## ⚙️ 配置
90
137
 
91
- ### 自动配置
138
+ ### 现代配置系统
92
139
 
93
- 首次运行时,如果您选择 Kimi API 选项且没有现有配置,启动器将:
140
+ Claude Launcher 2.0 使用先进的配置系统:
94
141
 
95
- 1. 自动在 `~/.claude-launcher.env` 创建配置文件
96
- 2. 引导您输入 Kimi API 密钥
97
- 3. 使用机器特定的加密安全存储您的 API 密钥
142
+ 1. **加密 JSON 存储**:配置存储在 `~/.claude-launcher-apis.json`
143
+ 2. **交互式设置**:首次设置向导引导您完成所有选项
144
+ 3. **多语言支持**:界面适应您的首选语言
145
+ 4. **安全第一**:所有敏感数据使用 AES-256-CBC 加密
98
146
 
99
- ### 手动配置
147
+ ### 首次设置流程
100
148
 
101
- 如果您更喜欢手动设置,配置文件搜索顺序如下:
149
+ 1. **语言选择**:从8种支持的语言中选择
150
+ 2. **安全设置**:
151
+ - 设置导入/导出的密码保护(推荐)
152
+ - 或跳过基本使用(功能有限)
153
+ 3. **API 配置**:根据需要添加第三方 API
102
154
 
103
- 1. 当前目录中的 `.claude-launcher.env`
104
- 2. 用户主目录中的 `.claude-launcher.env`
105
- 3. 安装目录中的 `.claude-launcher.env`
155
+ ### 第三方 API 配置
106
156
 
107
- ### 配置文件格式
157
+ 通过交互界面配置任何第三方 API 提供商:
108
158
 
109
- ```env
110
- KIMI_API_KEY=your_encrypted_api_key_here
111
- KIMI_BASE_URL=https://api.moonshot.cn/anthropic/
112
- ```
159
+ - **支持的提供商**:OpenAI、Anthropic、自定义 API 等
160
+ - **安全存储**:所有 API 令牌在存储前加密
161
+ - **验证**:URL、令牌和模型的实时验证
162
+ - **使用跟踪**:监控 API 使用统计
163
+
164
+ ### 配置导入/导出
165
+
166
+ 启用密码保护后:
113
167
 
114
- **注意**: `KIMI_API_KEY` 通过启动器输入时会自动加密。请勿手动编辑加密值。
168
+ - **导出**:所有配置的安全备份
169
+ - **导入**:在新机器上恢复配置
170
+ - **密码保护**:所有导出都使用您的密码加密
171
+ - **自动验证**:导入验证确保数据完整性
115
172
 
116
- ### 安全功能
173
+ ### 增强的安全功能
117
174
 
118
- - **AES-256-CBC 加密**: 使用行业标准加密算法加密 API 密钥
119
- - **机器特定密钥**: 加密密钥从机器特定数据派生
120
- - **仅本地存储**: 加密密钥无法在其他机器上解密
121
- - **安全输入**: API 密钥输入支持复制/粘贴和验证
175
+ - **AES-256-CBC 加密**:所有敏感数据使用行业标准算法加密
176
+ - **机器特定密钥**:从独特机器特征派生的加密密钥
177
+ - **密码保护**:配置导入/导出的可选密码层
178
+ - **安全令牌显示**:所有界面显示中的 API 令牌都经过掩码处理
179
+ - **强密码要求**:强制执行密码复杂性以确保最大安全性
180
+ - **仅本地存储**:所有数据保留在您的机器上,无法在其他地方解密
122
181
 
123
182
  ## 📋 系统要求
124
183
 
@@ -174,4 +233,4 @@ node claude-launcher
174
233
 
175
234
  ---
176
235
 
177
- **注意**: 此启动器设计用于 Claude Code Kimi K2 API。使用此工具前请确保已安装 Claude Code
236
+ **注意**: 此启动器设计用于 Claude Code 和各种第三方 API。使用此工具前请确保已安装 Claude Code。使用第三方 API 时,请确保您拥有首选提供商的有效 API 凭据。
@@ -0,0 +1,449 @@
1
+ /**
2
+ * API Manager Module - Manages third-party API configurations
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const { encrypt, decrypt } = require('./crypto');
9
+ const { validateBaseUrl, validateAuthToken, validateModel } = require('./validators');
10
+
11
+ class ApiManager {
12
+ constructor() {
13
+ this.configFile = path.join(os.homedir(), '.claude-launcher-apis.json');
14
+ this.config = this.loadConfig();
15
+ }
16
+
17
+ /**
18
+ * Load configuration from encrypted file
19
+ */
20
+ loadConfig() {
21
+ try {
22
+ if (fs.existsSync(this.configFile)) {
23
+ const encryptedData = fs.readFileSync(this.configFile, 'utf8');
24
+ const decrypted = decrypt(encryptedData);
25
+ if (decrypted.success) {
26
+ const config = JSON.parse(decrypted.value);
27
+ // Ensure required fields exist
28
+ if (!config.hasOwnProperty('exportPassword')) {
29
+ config.exportPassword = null;
30
+ }
31
+ if (!config.hasOwnProperty('passwordSkipped')) {
32
+ config.passwordSkipped = false;
33
+ }
34
+
35
+ return config;
36
+ }
37
+ }
38
+ } catch (error) {
39
+ console.error(`[!] Could not load API config: ${error.message}`);
40
+ }
41
+
42
+ return {
43
+ apis: [],
44
+ activeIndex: -1,
45
+ version: '2.0.0',
46
+ createdAt: new Date().toISOString(),
47
+ exportPassword: null, // Hashed export password for validation
48
+ passwordSkipped: false // Whether user permanently skipped password setup
49
+ };
50
+ }
51
+
52
+ /**
53
+ * Save configuration to encrypted file
54
+ */
55
+ saveConfig() {
56
+ try {
57
+ const configJson = JSON.stringify(this.config, null, 2);
58
+ const encrypted = encrypt(configJson);
59
+ if (encrypted.success) {
60
+ fs.writeFileSync(this.configFile, encrypted.value);
61
+ return true;
62
+ } else {
63
+ console.error(`[!] Failed to save API config: ${encrypted.error}`);
64
+ return false;
65
+ }
66
+ } catch (error) {
67
+ console.error(`[!] Error saving API config: ${error.message}`);
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Check for duplicate API configurations - URL + authToken + model must be unique
74
+ */
75
+ checkDuplicate(baseUrl, authToken, model) {
76
+ const existing = this.config.apis.find(api => {
77
+ const decryptedToken = decrypt(api.authToken);
78
+ const existingToken = decryptedToken.success ? decryptedToken.value : '';
79
+ return api.baseUrl === baseUrl &&
80
+ existingToken === authToken &&
81
+ api.model === model;
82
+ });
83
+
84
+ if (existing) {
85
+ return {
86
+ isDuplicate: true,
87
+ type: 'Complete Configuration (URL + Token + Model)',
88
+ existing
89
+ };
90
+ }
91
+
92
+ return { isDuplicate: false };
93
+ }
94
+
95
+ /**
96
+ * Add a new API configuration
97
+ */
98
+ addApi(baseUrl, authToken, model, name, provider = 'custom') {
99
+ // Validate inputs
100
+ const urlValidation = validateBaseUrl(baseUrl);
101
+ if (!urlValidation.valid) {
102
+ throw new Error(`Invalid Base URL: ${urlValidation.error}`);
103
+ }
104
+
105
+ const tokenValidation = validateAuthToken(authToken);
106
+ if (!tokenValidation.valid) {
107
+ throw new Error(`Invalid Auth Token: ${tokenValidation.error}`);
108
+ }
109
+
110
+ const modelValidation = validateModel(model);
111
+ if (!modelValidation.valid) {
112
+ throw new Error(`Invalid Model: ${modelValidation.error}`);
113
+ }
114
+
115
+ // Check for duplicates
116
+ const duplicate = this.checkDuplicate(baseUrl, authToken, model);
117
+ if (duplicate.isDuplicate) {
118
+ throw new Error(`${duplicate.type} already exists for API: ${duplicate.existing.name}`);
119
+ }
120
+
121
+ // Encrypt the auth token before storing
122
+ const encryptedToken = encrypt(tokenValidation.value);
123
+ if (!encryptedToken.success) {
124
+ throw new Error(`Failed to encrypt auth token: ${encryptedToken.error}`);
125
+ }
126
+
127
+ const newApi = {
128
+ id: Date.now().toString(),
129
+ name: name || `API-${this.config.apis.length + 1}`,
130
+ provider: provider,
131
+ baseUrl: urlValidation.value,
132
+ authToken: encryptedToken.value,
133
+ model: modelValidation.value,
134
+ smallFastModel: modelValidation.value, // Same as model as requested
135
+ createdAt: new Date().toISOString(),
136
+ lastUsed: null,
137
+ usageCount: 0
138
+ };
139
+
140
+ this.config.apis.push(newApi);
141
+
142
+ // Set as active if it's the first API
143
+ if (this.config.apis.length === 1) {
144
+ this.config.activeIndex = 0;
145
+ }
146
+
147
+ this.saveConfig();
148
+ return newApi;
149
+ }
150
+
151
+ /**
152
+ * Remove an API configuration
153
+ */
154
+ removeApi(index) {
155
+ if (index < 0 || index >= this.config.apis.length) {
156
+ throw new Error('Invalid API index');
157
+ }
158
+
159
+ const removedApi = this.config.apis[index];
160
+ this.config.apis.splice(index, 1);
161
+
162
+ // Adjust active index
163
+ if (this.config.activeIndex >= index) {
164
+ this.config.activeIndex = this.config.activeIndex > 0 ? this.config.activeIndex - 1 : -1;
165
+ }
166
+
167
+ if (this.config.apis.length === 0) {
168
+ this.config.activeIndex = -1;
169
+ }
170
+
171
+ this.saveConfig();
172
+ return removedApi;
173
+ }
174
+
175
+ /**
176
+ * Get all API configurations
177
+ */
178
+ getApis() {
179
+ return this.config.apis;
180
+ }
181
+
182
+ /**
183
+ * Set the active API
184
+ */
185
+ setActiveApi(index) {
186
+ if (index < 0 || index >= this.config.apis.length) {
187
+ throw new Error('Invalid API index');
188
+ }
189
+
190
+ this.config.activeIndex = index;
191
+ this.saveConfig();
192
+ return this.config.apis[index];
193
+ }
194
+
195
+ /**
196
+ * Get the currently active API
197
+ */
198
+ getActiveApi() {
199
+ if (this.config.activeIndex >= 0 && this.config.activeIndex < this.config.apis.length) {
200
+ return this.config.apis[this.config.activeIndex];
201
+ }
202
+ return null;
203
+ }
204
+
205
+ /**
206
+ * Increment usage count for the active API when actually used
207
+ */
208
+ incrementActiveApiUsage() {
209
+ const activeApi = this.getActiveApi();
210
+ if (activeApi) {
211
+ const index = this.config.activeIndex;
212
+ this.config.apis[index].lastUsed = new Date().toISOString();
213
+ this.config.apis[index].usageCount = (this.config.apis[index].usageCount || 0) + 1;
214
+ this.saveConfig();
215
+ return this.config.apis[index];
216
+ }
217
+ return null;
218
+ }
219
+
220
+
221
+
222
+ /**
223
+ * Get statistics about API usage
224
+ */
225
+ getStatistics() {
226
+ const totalApis = this.config.apis.length;
227
+ const activeApi = this.getActiveApi();
228
+ const mostUsed = this.config.apis.reduce((prev, current) =>
229
+ (current.usageCount > (prev?.usageCount || 0)) ? current : prev, null);
230
+
231
+ return {
232
+ totalApis,
233
+ activeApiName: activeApi?.name || 'None',
234
+ mostUsedApi: mostUsed?.name || 'None',
235
+ totalUsage: this.config.apis.reduce((sum, api) => sum + api.usageCount, 0)
236
+ };
237
+ }
238
+
239
+ /**
240
+ * Check if this is first time usage (no password set AND no APIs configured)
241
+ */
242
+ isFirstTimeUsage() {
243
+ return this.config.exportPassword === null &&
244
+ this.config.apis.length === 0 &&
245
+ !this.config.passwordSkipped;
246
+ }
247
+
248
+ /**
249
+ * Check if export password is set
250
+ */
251
+ hasExportPassword() {
252
+ return this.config.exportPassword !== null;
253
+ }
254
+
255
+ /**
256
+ * Check if password was permanently skipped
257
+ */
258
+ isPasswordSkipped() {
259
+ return this.config.passwordSkipped === true;
260
+ }
261
+
262
+ /**
263
+ * Check if import/export features should be available
264
+ */
265
+ canUseImportExport() {
266
+ return this.hasExportPassword();
267
+ }
268
+
269
+ /**
270
+ * Set export password (hashed)
271
+ */
272
+ setExportPassword(password) {
273
+ const crypto = require('crypto');
274
+ this.config.exportPassword = crypto.createHash('sha256').update(password).digest('hex');
275
+ this.saveConfig();
276
+ }
277
+
278
+ /**
279
+ * Permanently skip password setup (one-time only, can't be undone)
280
+ */
281
+ skipPasswordSetup() {
282
+ if (!this.isFirstTimeUsage()) {
283
+ throw new Error('Password setup can only be skipped during first time usage');
284
+ }
285
+ this.config.passwordSkipped = true;
286
+ this.saveConfig();
287
+ }
288
+
289
+ /**
290
+ * Verify export password
291
+ */
292
+ verifyExportPassword(password) {
293
+ if (!this.hasExportPassword()) {
294
+ return false;
295
+ }
296
+ const crypto = require('crypto');
297
+ const hashedInput = crypto.createHash('sha256').update(password).digest('hex');
298
+ return hashedInput === this.config.exportPassword;
299
+ }
300
+
301
+ /**
302
+ * Remove export password
303
+ */
304
+ removeExportPassword() {
305
+ this.config.exportPassword = null;
306
+ this.saveConfig();
307
+ }
308
+
309
+ /**
310
+ * Export configuration as plaintext JSON (password verification required)
311
+ */
312
+ exportConfig(password) {
313
+ // Verify password before export
314
+ if (!this.verifyExportPassword(password)) {
315
+ throw new Error('Invalid password for export operation');
316
+ }
317
+
318
+ return this.exportConfigAuthenticated();
319
+ }
320
+
321
+ /**
322
+ * Export configuration as plaintext JSON (already authenticated)
323
+ */
324
+ exportConfigAuthenticated() {
325
+ // Create export data with plaintext API keys
326
+ const exportData = {
327
+ version: this.config.version,
328
+ exportedAt: new Date().toISOString(),
329
+ apis: this.config.apis.map(api => {
330
+ const decrypted = decrypt(api.authToken);
331
+ return {
332
+ ...api,
333
+ authToken: decrypted.success ? decrypted.value : '***DECRYPTION_FAILED***'
334
+ };
335
+ }),
336
+ activeIndex: this.config.activeIndex
337
+ };
338
+
339
+ return JSON.stringify(exportData, null, 2);
340
+ }
341
+
342
+ /**
343
+ * Import configuration from plaintext JSON (password verification required)
344
+ */
345
+ importConfig(plaintextData, password) {
346
+ // Verify password before import
347
+ if (!this.verifyExportPassword(password)) {
348
+ throw new Error('Invalid password for import operation');
349
+ }
350
+
351
+ return this.importConfigAuthenticated(plaintextData);
352
+ }
353
+
354
+ /**
355
+ * Import configuration from plaintext JSON (already authenticated)
356
+ */
357
+ importConfigAuthenticated(plaintextData) {
358
+ const configData = JSON.parse(plaintextData);
359
+ return this.processImportData(configData);
360
+ }
361
+
362
+
363
+ /**
364
+ * Process import data (merge with existing)
365
+ */
366
+ processImportData(configData) {
367
+ let imported = 0;
368
+ let skipped = 0;
369
+
370
+ if (!configData.apis || !Array.isArray(configData.apis)) {
371
+ throw new Error('Invalid configuration format - no APIs found');
372
+ }
373
+
374
+ configData.apis.forEach(importApi => {
375
+ // Validate the API configuration before importing
376
+ try {
377
+ // Validate Base URL
378
+ const urlValidation = validateBaseUrl(importApi.baseUrl);
379
+ if (!urlValidation.valid) {
380
+ console.warn(`Skipping API "${importApi.name || 'Unknown'}" - Invalid Base URL: ${urlValidation.error}`);
381
+ skipped++;
382
+ return;
383
+ }
384
+
385
+ // Validate Auth Token (skip validation for placeholder tokens)
386
+ if (importApi.authToken !== '***REQUIRES_MANUAL_INPUT***') {
387
+ const tokenValidation = validateAuthToken(importApi.authToken);
388
+ if (!tokenValidation.valid) {
389
+ console.warn(`Skipping API "${importApi.name || 'Unknown'}" - Invalid Auth Token: ${tokenValidation.error}`);
390
+ skipped++;
391
+ return;
392
+ }
393
+ }
394
+
395
+ // Validate Model
396
+ const modelValidation = validateModel(importApi.model);
397
+ if (!modelValidation.valid) {
398
+ console.warn(`Skipping API "${importApi.name || 'Unknown'}" - Invalid Model: ${modelValidation.error}`);
399
+ skipped++;
400
+ return;
401
+ }
402
+
403
+ // Check for duplicates using the same logic as addApi
404
+ const importToken = importApi.authToken === '***REQUIRES_MANUAL_INPUT***' ? '' : importApi.authToken;
405
+ const duplicate = this.checkDuplicate(importApi.baseUrl, importToken, importApi.model);
406
+
407
+ if (duplicate.isDuplicate) {
408
+ skipped++;
409
+ } else {
410
+ // Encrypt the auth token if it's not already encrypted or masked
411
+ let encryptedToken;
412
+ if (importApi.authToken === '***REQUIRES_MANUAL_INPUT***') {
413
+ encryptedToken = encrypt('').value; // Empty encrypted token
414
+ } else {
415
+ encryptedToken = encrypt(importApi.authToken).value;
416
+ }
417
+
418
+ const newApi = {
419
+ id: Date.now() + Math.random(),
420
+ name: importApi.name || `Imported API ${this.config.apis.length + 1}`,
421
+ baseUrl: urlValidation.value,
422
+ authToken: encryptedToken,
423
+ model: modelValidation.value,
424
+ provider: importApi.provider || 'custom',
425
+ createdAt: new Date().toISOString(),
426
+ lastUsed: null,
427
+ usageCount: 0
428
+ };
429
+
430
+ this.config.apis.push(newApi);
431
+ imported++;
432
+
433
+ // Set as active if this is the first API
434
+ if (this.config.apis.length === 1) {
435
+ this.config.activeIndex = 0;
436
+ }
437
+ }
438
+ } catch (error) {
439
+ console.warn(`Skipping API "${importApi.name || 'Unknown'}" - Validation error: ${error.message}`);
440
+ skipped++;
441
+ }
442
+ });
443
+
444
+ this.saveConfig();
445
+ return { imported, skipped };
446
+ }
447
+ }
448
+
449
+ module.exports = ApiManager;