ccjk 1.3.1

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