zcf 3.1.4 → 3.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.
@@ -0,0 +1,705 @@
1
+ import dayjs from 'dayjs';
2
+ import { join } from 'pathe';
3
+ import { j as ZCF_CONFIG_FILE, Z as ZCF_CONFIG_DIR, a6 as ensureDir, a7 as readDefaultTomlConfig, a8 as createDefaultTomlConfig, a9 as exists, aa as readJsonConfig, ab as writeTomlConfig, S as SETTINGS_FILE, ac as copyFile } from './simple-config.mjs';
4
+ import 'node:fs';
5
+ import 'node:process';
6
+ import 'ansis';
7
+ import 'inquirer';
8
+ import 'node:child_process';
9
+ import 'node:os';
10
+ import 'node:util';
11
+ import 'node:url';
12
+ import 'ora';
13
+ import 'semver';
14
+ import 'smol-toml';
15
+ import 'tinyexec';
16
+ import 'node:fs/promises';
17
+ import 'i18next';
18
+ import 'i18next-fs-backend';
19
+
20
+ class ClaudeCodeConfigManager {
21
+ static CONFIG_FILE = ZCF_CONFIG_FILE;
22
+ static LEGACY_CONFIG_FILE = join(ZCF_CONFIG_DIR, "claude-code-configs.json");
23
+ /**
24
+ * Ensure configuration directory exists
25
+ */
26
+ static ensureConfigDir() {
27
+ ensureDir(ZCF_CONFIG_DIR);
28
+ }
29
+ /**
30
+ * Read TOML configuration
31
+ */
32
+ static readTomlConfig() {
33
+ return readDefaultTomlConfig();
34
+ }
35
+ /**
36
+ * Load TOML configuration, falling back to default when missing
37
+ */
38
+ static loadTomlConfig() {
39
+ const existingConfig = this.readTomlConfig();
40
+ if (existingConfig) {
41
+ return existingConfig;
42
+ }
43
+ return createDefaultTomlConfig();
44
+ }
45
+ /**
46
+ * Migrate legacy JSON-based configuration into TOML storage
47
+ */
48
+ static migrateFromLegacyConfig() {
49
+ if (!exists(this.LEGACY_CONFIG_FILE)) {
50
+ return null;
51
+ }
52
+ try {
53
+ const legacyConfig = readJsonConfig(this.LEGACY_CONFIG_FILE);
54
+ if (!legacyConfig) {
55
+ return null;
56
+ }
57
+ const normalizedProfiles = {};
58
+ const existingKeys = /* @__PURE__ */ new Set();
59
+ let migratedCurrentKey = "";
60
+ Object.entries(legacyConfig.profiles || {}).forEach(([legacyKey, profile]) => {
61
+ const sourceProfile = profile;
62
+ const name = sourceProfile.name?.trim() || legacyKey;
63
+ const baseKey = this.generateProfileId(name);
64
+ let uniqueKey = baseKey || legacyKey;
65
+ let suffix = 2;
66
+ while (existingKeys.has(uniqueKey)) {
67
+ uniqueKey = `${baseKey || legacyKey}-${suffix++}`;
68
+ }
69
+ existingKeys.add(uniqueKey);
70
+ const sanitizedProfile = this.sanitizeProfile({
71
+ ...sourceProfile,
72
+ name
73
+ });
74
+ normalizedProfiles[uniqueKey] = {
75
+ ...sanitizedProfile,
76
+ id: uniqueKey
77
+ };
78
+ if (legacyConfig.currentProfileId === legacyKey || legacyConfig.currentProfileId === sourceProfile.id) {
79
+ migratedCurrentKey = uniqueKey;
80
+ }
81
+ });
82
+ if (!migratedCurrentKey && legacyConfig.currentProfileId) {
83
+ const fallbackKey = this.generateProfileId(legacyConfig.currentProfileId);
84
+ if (existingKeys.has(fallbackKey)) {
85
+ migratedCurrentKey = fallbackKey;
86
+ }
87
+ }
88
+ if (!migratedCurrentKey && existingKeys.size > 0) {
89
+ migratedCurrentKey = Array.from(existingKeys)[0];
90
+ }
91
+ const migratedConfig = {
92
+ currentProfileId: migratedCurrentKey,
93
+ profiles: normalizedProfiles
94
+ };
95
+ this.writeConfig(migratedConfig);
96
+ return migratedConfig;
97
+ } catch (error) {
98
+ console.error("Failed to migrate legacy Claude Code config:", error);
99
+ return null;
100
+ }
101
+ }
102
+ /**
103
+ * Read configuration
104
+ */
105
+ static readConfig() {
106
+ try {
107
+ const tomlConfig = readDefaultTomlConfig();
108
+ if (!tomlConfig || !tomlConfig.claudeCode) {
109
+ return this.migrateFromLegacyConfig();
110
+ }
111
+ const { claudeCode } = tomlConfig;
112
+ const rawProfiles = claudeCode.profiles || {};
113
+ const sanitizedProfiles = Object.fromEntries(
114
+ Object.entries(rawProfiles).map(([key, profile]) => {
115
+ const storedProfile = this.sanitizeProfile({
116
+ ...profile,
117
+ name: profile.name || key
118
+ });
119
+ return [key, { ...storedProfile, id: key }];
120
+ })
121
+ );
122
+ const configData = {
123
+ currentProfileId: claudeCode.currentProfile || "",
124
+ profiles: sanitizedProfiles
125
+ };
126
+ if (Object.keys(configData.profiles).length === 0) {
127
+ const migrated = this.migrateFromLegacyConfig();
128
+ if (migrated) {
129
+ return migrated;
130
+ }
131
+ }
132
+ return configData;
133
+ } catch (error) {
134
+ console.error("Failed to read Claude Code config:", error);
135
+ return null;
136
+ }
137
+ }
138
+ /**
139
+ * Write configuration
140
+ */
141
+ static writeConfig(config) {
142
+ try {
143
+ this.ensureConfigDir();
144
+ const keyMap = /* @__PURE__ */ new Map();
145
+ const sanitizedProfiles = Object.fromEntries(
146
+ Object.entries(config.profiles).map(([key, profile]) => {
147
+ const normalizedName = profile.name?.trim() || key;
148
+ const profileKey = this.generateProfileId(normalizedName);
149
+ keyMap.set(key, profileKey);
150
+ const sanitizedProfile = this.sanitizeProfile({
151
+ ...profile,
152
+ name: normalizedName
153
+ });
154
+ return [profileKey, sanitizedProfile];
155
+ })
156
+ );
157
+ const tomlConfig = this.loadTomlConfig();
158
+ const nextTomlConfig = {
159
+ ...tomlConfig,
160
+ claudeCode: {
161
+ ...tomlConfig.claudeCode,
162
+ currentProfile: keyMap.get(config.currentProfileId) || config.currentProfileId,
163
+ profiles: sanitizedProfiles
164
+ }
165
+ };
166
+ writeTomlConfig(this.CONFIG_FILE, nextTomlConfig);
167
+ } catch (error) {
168
+ console.error("Failed to write Claude Code config:", error);
169
+ throw new Error(`Failed to write config: ${error instanceof Error ? error.message : String(error)}`);
170
+ }
171
+ }
172
+ /**
173
+ * Create empty configuration
174
+ */
175
+ static createEmptyConfig() {
176
+ return {
177
+ currentProfileId: "",
178
+ profiles: {}
179
+ };
180
+ }
181
+ /**
182
+ * Apply profile settings to Claude Code runtime
183
+ */
184
+ static async applyProfileSettings(profile) {
185
+ const { ensureI18nInitialized, i18n } = await import('./simple-config.mjs').then(function (n) { return n.aY; });
186
+ ensureI18nInitialized();
187
+ try {
188
+ if (!profile) {
189
+ const { switchToOfficialLogin } = await import('./simple-config.mjs').then(function (n) { return n.b0; });
190
+ switchToOfficialLogin();
191
+ return;
192
+ }
193
+ const { readJsonConfig: readJsonConfig2, writeJsonConfig } = await import('./simple-config.mjs').then(function (n) { return n.a_; });
194
+ const settings = readJsonConfig2(SETTINGS_FILE) || {};
195
+ if (!settings.env)
196
+ settings.env = {};
197
+ let shouldRestartCcr = false;
198
+ if (profile.authType === "api_key") {
199
+ settings.env.ANTHROPIC_API_KEY = profile.apiKey;
200
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
201
+ } else if (profile.authType === "auth_token") {
202
+ settings.env.ANTHROPIC_AUTH_TOKEN = profile.apiKey;
203
+ delete settings.env.ANTHROPIC_API_KEY;
204
+ } else if (profile.authType === "ccr_proxy") {
205
+ const { readCcrConfig } = await import('./simple-config.mjs').then(function (n) { return n.b1; });
206
+ const ccrConfig = readCcrConfig();
207
+ if (!ccrConfig) {
208
+ throw new Error(i18n.t("ccr:ccrNotConfigured") || "CCR proxy configuration not found");
209
+ }
210
+ const host = ccrConfig.HOST || "127.0.0.1";
211
+ const port = ccrConfig.PORT || 3456;
212
+ const apiKey = ccrConfig.APIKEY || "sk-zcf-x-ccr";
213
+ settings.env.ANTHROPIC_BASE_URL = `http://${host}:${port}`;
214
+ settings.env.ANTHROPIC_API_KEY = apiKey;
215
+ delete settings.env.ANTHROPIC_AUTH_TOKEN;
216
+ shouldRestartCcr = true;
217
+ }
218
+ if (profile.authType !== "ccr_proxy") {
219
+ if (profile.baseUrl)
220
+ settings.env.ANTHROPIC_BASE_URL = profile.baseUrl;
221
+ else
222
+ delete settings.env.ANTHROPIC_BASE_URL;
223
+ }
224
+ writeJsonConfig(SETTINGS_FILE, settings);
225
+ const { setPrimaryApiKey } = await import('./simple-config.mjs').then(function (n) { return n.a$; });
226
+ setPrimaryApiKey();
227
+ if (shouldRestartCcr) {
228
+ const { runCcrRestart } = await import('./commands.mjs');
229
+ await runCcrRestart();
230
+ }
231
+ } catch (error) {
232
+ const reason = error instanceof Error ? error.message : String(error);
233
+ throw new Error(`${i18n.t("multi-config:failedToApplySettings")}: ${reason}`);
234
+ }
235
+ }
236
+ static async applyCurrentProfile() {
237
+ const currentProfile = this.getCurrentProfile();
238
+ await this.applyProfileSettings(currentProfile);
239
+ }
240
+ /**
241
+ * Remove unsupported fields from profile payload
242
+ */
243
+ static sanitizeProfile(profile) {
244
+ const sanitized = {
245
+ name: profile.name,
246
+ authType: profile.authType
247
+ };
248
+ if (profile.apiKey)
249
+ sanitized.apiKey = profile.apiKey;
250
+ if (profile.baseUrl)
251
+ sanitized.baseUrl = profile.baseUrl;
252
+ return sanitized;
253
+ }
254
+ /**
255
+ * Backup configuration
256
+ */
257
+ static backupConfig() {
258
+ try {
259
+ if (!exists(this.CONFIG_FILE)) {
260
+ return null;
261
+ }
262
+ const timestamp = dayjs().format("YYYY-MM-DD_HH-mm-ss");
263
+ const backupPath = join(ZCF_CONFIG_DIR, `config.backup.${timestamp}.toml`);
264
+ copyFile(this.CONFIG_FILE, backupPath);
265
+ return backupPath;
266
+ } catch (error) {
267
+ console.error("Failed to backup Claude Code config:", error);
268
+ return null;
269
+ }
270
+ }
271
+ /**
272
+ * Add configuration
273
+ */
274
+ static async addProfile(profile) {
275
+ try {
276
+ const validationErrors = this.validateProfile(profile);
277
+ if (validationErrors.length > 0) {
278
+ return {
279
+ success: false,
280
+ error: `Validation failed: ${validationErrors.join(", ")}`
281
+ };
282
+ }
283
+ const backupPath = this.backupConfig();
284
+ let config = this.readConfig();
285
+ if (!config) {
286
+ config = this.createEmptyConfig();
287
+ }
288
+ if (profile.id && config.profiles[profile.id]) {
289
+ return {
290
+ success: false,
291
+ error: `Profile with ID "${profile.id}" already exists`,
292
+ backupPath: backupPath || void 0
293
+ };
294
+ }
295
+ const normalizedName = profile.name.trim();
296
+ const profileKey = this.generateProfileId(normalizedName);
297
+ const existingNames = Object.values(config.profiles).map((p) => p.name || "");
298
+ if (config.profiles[profileKey] || existingNames.some((name) => name.toLowerCase() === normalizedName.toLowerCase())) {
299
+ return {
300
+ success: false,
301
+ error: `Profile with name "${profile.name}" already exists`,
302
+ backupPath: backupPath || void 0
303
+ };
304
+ }
305
+ const sanitizedProfile = this.sanitizeProfile({
306
+ ...profile,
307
+ name: normalizedName
308
+ });
309
+ const runtimeProfile = {
310
+ ...sanitizedProfile,
311
+ id: profileKey
312
+ };
313
+ config.profiles[profileKey] = runtimeProfile;
314
+ if (!config.currentProfileId) {
315
+ config.currentProfileId = profileKey;
316
+ }
317
+ this.writeConfig(config);
318
+ return {
319
+ success: true,
320
+ backupPath: backupPath || void 0,
321
+ addedProfile: runtimeProfile
322
+ };
323
+ } catch (error) {
324
+ return {
325
+ success: false,
326
+ error: error instanceof Error ? error.message : String(error)
327
+ };
328
+ }
329
+ }
330
+ /**
331
+ * Update configuration
332
+ */
333
+ static async updateProfile(id, data) {
334
+ try {
335
+ const validationErrors = this.validateProfile(data, true);
336
+ if (validationErrors.length > 0) {
337
+ return {
338
+ success: false,
339
+ error: `Validation failed: ${validationErrors.join(", ")}`
340
+ };
341
+ }
342
+ const backupPath = this.backupConfig();
343
+ const config = this.readConfig();
344
+ if (!config || !config.profiles[id]) {
345
+ return {
346
+ success: false,
347
+ error: `Profile with ID "${id}" not found`,
348
+ backupPath: backupPath || void 0
349
+ };
350
+ }
351
+ const existingProfile = config.profiles[id];
352
+ const nextName = data.name !== void 0 ? data.name.trim() : existingProfile.name;
353
+ const nextKey = this.generateProfileId(nextName);
354
+ const nameChanged = nextKey !== id;
355
+ if (nameChanged) {
356
+ const duplicateName = Object.entries(config.profiles).some(([key, profile]) => key !== id && (profile.name || "").toLowerCase() === nextName.toLowerCase());
357
+ if (duplicateName || config.profiles[nextKey]) {
358
+ return {
359
+ success: false,
360
+ error: `Profile with name "${data.name}" already exists`,
361
+ backupPath: backupPath || void 0
362
+ };
363
+ }
364
+ }
365
+ const mergedProfile = this.sanitizeProfile({
366
+ ...existingProfile,
367
+ ...data,
368
+ name: nextName
369
+ });
370
+ if (nameChanged) {
371
+ delete config.profiles[id];
372
+ config.profiles[nextKey] = {
373
+ ...mergedProfile,
374
+ id: nextKey
375
+ };
376
+ if (config.currentProfileId === id) {
377
+ config.currentProfileId = nextKey;
378
+ }
379
+ } else {
380
+ config.profiles[id] = {
381
+ ...mergedProfile,
382
+ id
383
+ };
384
+ }
385
+ this.writeConfig(config);
386
+ return {
387
+ success: true,
388
+ backupPath: backupPath || void 0,
389
+ updatedProfile: {
390
+ ...mergedProfile,
391
+ id: nameChanged ? nextKey : id
392
+ }
393
+ };
394
+ } catch (error) {
395
+ return {
396
+ success: false,
397
+ error: error instanceof Error ? error.message : String(error)
398
+ };
399
+ }
400
+ }
401
+ /**
402
+ * Delete configuration
403
+ */
404
+ static async deleteProfile(id) {
405
+ try {
406
+ const backupPath = this.backupConfig();
407
+ const config = this.readConfig();
408
+ if (!config || !config.profiles[id]) {
409
+ return {
410
+ success: false,
411
+ error: `Profile with ID "${id}" not found`,
412
+ backupPath: backupPath || void 0
413
+ };
414
+ }
415
+ const profileCount = Object.keys(config.profiles).length;
416
+ if (profileCount === 1) {
417
+ return {
418
+ success: false,
419
+ error: "Cannot delete the last profile. At least one profile must remain.",
420
+ backupPath: backupPath || void 0
421
+ };
422
+ }
423
+ delete config.profiles[id];
424
+ if (config.currentProfileId === id) {
425
+ const remainingIds = Object.keys(config.profiles);
426
+ config.currentProfileId = remainingIds[0];
427
+ }
428
+ this.writeConfig(config);
429
+ return {
430
+ success: true,
431
+ backupPath: backupPath || void 0,
432
+ remainingProfiles: Object.entries(config.profiles).map(([key, profile]) => ({
433
+ ...profile,
434
+ id: key
435
+ }))
436
+ };
437
+ } catch (error) {
438
+ return {
439
+ success: false,
440
+ error: error instanceof Error ? error.message : String(error)
441
+ };
442
+ }
443
+ }
444
+ /**
445
+ * Delete multiple configurations
446
+ */
447
+ static async deleteProfiles(ids) {
448
+ try {
449
+ const backupPath = this.backupConfig();
450
+ const config = this.readConfig();
451
+ if (!config) {
452
+ return {
453
+ success: false,
454
+ error: "No configuration found",
455
+ backupPath: backupPath || void 0
456
+ };
457
+ }
458
+ const missingIds = ids.filter((id) => !config.profiles[id]);
459
+ if (missingIds.length > 0) {
460
+ return {
461
+ success: false,
462
+ error: `Profiles not found: ${missingIds.join(", ")}`,
463
+ backupPath: backupPath || void 0
464
+ };
465
+ }
466
+ const remainingCount = Object.keys(config.profiles).length - ids.length;
467
+ if (remainingCount === 0) {
468
+ return {
469
+ success: false,
470
+ error: "Cannot delete all profiles. At least one profile must remain.",
471
+ backupPath: backupPath || void 0
472
+ };
473
+ }
474
+ let newCurrentProfileId;
475
+ ids.forEach((id) => {
476
+ delete config.profiles[id];
477
+ });
478
+ if (ids.includes(config.currentProfileId)) {
479
+ const remainingIds = Object.keys(config.profiles);
480
+ config.currentProfileId = remainingIds[0];
481
+ newCurrentProfileId = config.currentProfileId;
482
+ }
483
+ this.writeConfig(config);
484
+ return {
485
+ success: true,
486
+ backupPath: backupPath || void 0,
487
+ newCurrentProfileId,
488
+ deletedProfiles: ids,
489
+ remainingProfiles: Object.entries(config.profiles).map(([key, profile]) => ({
490
+ ...profile,
491
+ id: key
492
+ }))
493
+ };
494
+ } catch (error) {
495
+ return {
496
+ success: false,
497
+ error: error instanceof Error ? error.message : String(error)
498
+ };
499
+ }
500
+ }
501
+ /**
502
+ * Generate profile ID from name
503
+ */
504
+ static generateProfileId(name) {
505
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "profile";
506
+ }
507
+ /**
508
+ * Switch configuration
509
+ */
510
+ static async switchProfile(id) {
511
+ try {
512
+ const config = this.readConfig();
513
+ if (!config || !config.profiles[id]) {
514
+ return {
515
+ success: false,
516
+ error: "Profile not found"
517
+ };
518
+ }
519
+ if (config.currentProfileId === id) {
520
+ return { success: true };
521
+ }
522
+ config.currentProfileId = id;
523
+ this.writeConfig(config);
524
+ return { success: true };
525
+ } catch (error) {
526
+ return {
527
+ success: false,
528
+ error: error instanceof Error ? error.message : String(error)
529
+ };
530
+ }
531
+ }
532
+ /**
533
+ * List all configurations
534
+ */
535
+ static listProfiles() {
536
+ const config = this.readConfig();
537
+ if (!config) {
538
+ return [];
539
+ }
540
+ return Object.values(config.profiles);
541
+ }
542
+ /**
543
+ * Get current configuration
544
+ */
545
+ static getCurrentProfile() {
546
+ const config = this.readConfig();
547
+ if (!config || !config.currentProfileId) {
548
+ return null;
549
+ }
550
+ return config.profiles[config.currentProfileId] || null;
551
+ }
552
+ /**
553
+ * Get configuration by ID
554
+ */
555
+ static getProfileById(id) {
556
+ const config = this.readConfig();
557
+ if (!config) {
558
+ return null;
559
+ }
560
+ return config.profiles[id] || null;
561
+ }
562
+ /**
563
+ * Get configuration by name
564
+ */
565
+ static getProfileByName(name) {
566
+ const config = this.readConfig();
567
+ if (!config) {
568
+ return null;
569
+ }
570
+ return Object.values(config.profiles).find((p) => p.name === name) || null;
571
+ }
572
+ /**
573
+ * Sync CCR configuration
574
+ */
575
+ static async syncCcrProfile() {
576
+ try {
577
+ const { readCcrConfig } = await import('./simple-config.mjs').then(function (n) { return n.b1; });
578
+ const ccrConfig = readCcrConfig();
579
+ if (!ccrConfig) {
580
+ await this.ensureCcrProfileExists(ccrConfig);
581
+ return;
582
+ }
583
+ await this.ensureCcrProfileExists(ccrConfig);
584
+ } catch (error) {
585
+ console.error("Failed to sync CCR profile:", error);
586
+ }
587
+ }
588
+ /**
589
+ * 确保CCR配置文件存在
590
+ */
591
+ static async ensureCcrProfileExists(ccrConfig) {
592
+ const config = this.readConfig() || this.createEmptyConfig();
593
+ const ccrProfileId = "ccr-proxy";
594
+ const existingCcrProfile = config.profiles[ccrProfileId];
595
+ if (!ccrConfig) {
596
+ if (existingCcrProfile) {
597
+ delete config.profiles[ccrProfileId];
598
+ if (config.currentProfileId === ccrProfileId) {
599
+ const remainingIds = Object.keys(config.profiles);
600
+ config.currentProfileId = remainingIds[0] || "";
601
+ }
602
+ this.writeConfig(config);
603
+ }
604
+ return;
605
+ }
606
+ const host = ccrConfig.HOST || "127.0.0.1";
607
+ const port = ccrConfig.PORT || 3456;
608
+ const apiKey = ccrConfig.APIKEY || "sk-zcf-x-ccr";
609
+ const baseUrl = `http://${host}:${port}`;
610
+ const ccrProfile = {
611
+ name: "CCR Proxy",
612
+ authType: "ccr_proxy",
613
+ baseUrl,
614
+ apiKey
615
+ };
616
+ config.profiles[ccrProfileId] = {
617
+ ...ccrProfile,
618
+ id: ccrProfileId
619
+ };
620
+ if (!config.currentProfileId) {
621
+ config.currentProfileId = ccrProfileId;
622
+ }
623
+ this.writeConfig(config);
624
+ }
625
+ /**
626
+ * Switch to official login
627
+ */
628
+ static async switchToOfficial() {
629
+ try {
630
+ const config = this.readConfig();
631
+ if (!config) {
632
+ return { success: true };
633
+ }
634
+ config.currentProfileId = "";
635
+ this.writeConfig(config);
636
+ return { success: true };
637
+ } catch (error) {
638
+ return {
639
+ success: false,
640
+ error: error instanceof Error ? error.message : String(error)
641
+ };
642
+ }
643
+ }
644
+ /**
645
+ * Switch to CCR proxy
646
+ */
647
+ static async switchToCcr() {
648
+ try {
649
+ await this.syncCcrProfile();
650
+ const config = this.readConfig();
651
+ if (!config || !config.profiles["ccr-proxy"]) {
652
+ return {
653
+ success: false,
654
+ error: "CCR proxy configuration not found. Please configure CCR first."
655
+ };
656
+ }
657
+ return await this.switchProfile("ccr-proxy");
658
+ } catch (error) {
659
+ return {
660
+ success: false,
661
+ error: error instanceof Error ? error.message : String(error)
662
+ };
663
+ }
664
+ }
665
+ /**
666
+ * Validate configuration
667
+ */
668
+ static validateProfile(profile, isUpdate = false) {
669
+ const errors = [];
670
+ if (!isUpdate && (!profile.name || typeof profile.name !== "string" || profile.name.trim() === "")) {
671
+ errors.push("Profile name is required");
672
+ }
673
+ if (profile.name && typeof profile.name !== "string") {
674
+ errors.push("Profile name must be a string");
675
+ }
676
+ if (profile.authType && !["api_key", "auth_token", "ccr_proxy"].includes(profile.authType)) {
677
+ errors.push("Invalid auth type. Must be one of: api_key, auth_token, ccr_proxy");
678
+ }
679
+ if (profile.authType === "api_key" || profile.authType === "auth_token") {
680
+ if (!profile.apiKey || typeof profile.apiKey !== "string" || profile.apiKey.trim() === "") {
681
+ errors.push("API key is required for api_key and auth_token types");
682
+ }
683
+ }
684
+ if (profile.baseUrl) {
685
+ try {
686
+ new URL(profile.baseUrl);
687
+ } catch {
688
+ errors.push("Invalid base URL format");
689
+ }
690
+ }
691
+ return errors;
692
+ }
693
+ /**
694
+ * 检查是否为最后一个配置
695
+ */
696
+ static isLastProfile(id) {
697
+ const config = this.readConfig();
698
+ if (!config || !config.profiles[id]) {
699
+ return false;
700
+ }
701
+ return Object.keys(config.profiles).length === 1;
702
+ }
703
+ }
704
+
705
+ export { ClaudeCodeConfigManager };