@benbenwu/zcf 3.6.4

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