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,603 @@
1
+ import ansis from 'ansis';
2
+ import inquirer from 'inquirer';
3
+ import { co as ensureI18nInitialized, cj as i18n, cz as addNumbersToChoices, cQ as validateApiKey, ct as promptBoolean } from './simple-config.mjs';
4
+ import { ClaudeCodeConfigManager } from './claude-code-config-manager.mjs';
5
+ import 'node:fs';
6
+ import 'node:process';
7
+ import 'pathe';
8
+ import 'smol-toml';
9
+ import 'dayjs';
10
+ import 'node:child_process';
11
+ import 'node:os';
12
+ import 'node:util';
13
+ import 'node:url';
14
+ import 'inquirer-toggle';
15
+ import 'ora';
16
+ import 'tinyexec';
17
+ import 'semver';
18
+ import 'node:fs/promises';
19
+ import 'fs-extra';
20
+ import 'trash';
21
+ import 'i18next';
22
+ import 'i18next-fs-backend';
23
+
24
+ function getAuthTypeLabel(authType) {
25
+ ensureI18nInitialized();
26
+ switch (authType) {
27
+ case "api_key":
28
+ return i18n.t("multi-config:authType.api_key");
29
+ case "auth_token":
30
+ return i18n.t("multi-config:authType.auth_token");
31
+ case "ccr_proxy":
32
+ return i18n.t("multi-config:authType.ccr_proxy");
33
+ default:
34
+ return authType;
35
+ }
36
+ }
37
+ async function configureIncrementalManagement() {
38
+ ensureI18nInitialized();
39
+ const config = ClaudeCodeConfigManager.readConfig();
40
+ if (!config || !config.profiles || Object.keys(config.profiles).length === 0) {
41
+ await handleAddProfile();
42
+ return;
43
+ }
44
+ const profiles = Object.values(config.profiles);
45
+ const currentProfile = config.currentProfileId ? config.profiles[config.currentProfileId] : null;
46
+ console.log(ansis.cyan(i18n.t("multi-config:incrementalManagementTitle")));
47
+ console.log(ansis.gray(i18n.t("multi-config:currentProfileCount", { count: profiles.length })));
48
+ if (currentProfile) {
49
+ console.log(ansis.gray(i18n.t("multi-config:currentDefaultProfile", { profile: currentProfile.name })));
50
+ }
51
+ const choices = [
52
+ { name: i18n.t("multi-config:addProfile"), value: "add" },
53
+ { name: i18n.t("multi-config:editProfile"), value: "edit" },
54
+ { name: i18n.t("multi-config:copyProfile"), value: "copy" },
55
+ { name: i18n.t("multi-config:deleteProfile"), value: "delete" },
56
+ { name: i18n.t("common:skip"), value: "skip" }
57
+ ];
58
+ const { action } = await inquirer.prompt([{
59
+ type: "list",
60
+ name: "action",
61
+ message: i18n.t("multi-config:selectAction"),
62
+ choices: addNumbersToChoices(choices)
63
+ }]);
64
+ if (!action || action === "skip") {
65
+ console.log(ansis.yellow(i18n.t("common:skip")));
66
+ return;
67
+ }
68
+ switch (action) {
69
+ case "add":
70
+ await handleAddProfile();
71
+ break;
72
+ case "edit":
73
+ await handleEditProfile(profiles);
74
+ break;
75
+ case "copy":
76
+ await handleCopyProfile(profiles);
77
+ break;
78
+ case "delete":
79
+ await handleDeleteProfile(profiles);
80
+ break;
81
+ }
82
+ }
83
+ async function promptContinueAdding() {
84
+ return await promptBoolean({
85
+ message: i18n.t("multi-config:addAnotherProfilePrompt"),
86
+ defaultValue: false
87
+ });
88
+ }
89
+ async function handleAddProfile() {
90
+ console.log(ansis.cyan(`
91
+ ${i18n.t("multi-config:addingNewProfile")}`));
92
+ const { getApiProviders } = await import('./api-providers.mjs');
93
+ const providers = getApiProviders("claude-code");
94
+ const providerChoices = [
95
+ { name: i18n.t("api:customProvider"), value: "custom" },
96
+ ...providers.map((p) => ({ name: p.name, value: p.id }))
97
+ ];
98
+ const { selectedProvider } = await inquirer.prompt([{
99
+ type: "list",
100
+ name: "selectedProvider",
101
+ message: i18n.t("api:selectApiProvider"),
102
+ choices: addNumbersToChoices(providerChoices)
103
+ }]);
104
+ let prefilledBaseUrl;
105
+ let prefilledAuthType;
106
+ let prefilledDefaultModels;
107
+ if (selectedProvider !== "custom") {
108
+ const provider = providers.find((p) => p.id === selectedProvider);
109
+ if (provider?.claudeCode) {
110
+ prefilledBaseUrl = provider.claudeCode.baseUrl;
111
+ prefilledAuthType = provider.claudeCode.authType;
112
+ prefilledDefaultModels = provider.claudeCode.defaultModels;
113
+ console.log(ansis.gray(i18n.t("api:providerSelected", { name: provider.name })));
114
+ }
115
+ }
116
+ const answers = await inquirer.prompt([
117
+ {
118
+ type: "input",
119
+ name: "profileName",
120
+ message: i18n.t("multi-config:profileNamePrompt"),
121
+ default: selectedProvider !== "custom" ? providers.find((p) => p.id === selectedProvider)?.name : void 0,
122
+ validate: (input) => {
123
+ const trimmed = input.trim();
124
+ if (!trimmed) {
125
+ return i18n.t("multi-config:profileNameRequired");
126
+ }
127
+ if (!/^[\w\-\s.\u4E00-\u9FA5]+$/.test(trimmed)) {
128
+ return i18n.t("multi-config:profileNameInvalid");
129
+ }
130
+ return true;
131
+ }
132
+ },
133
+ {
134
+ type: "list",
135
+ name: "authType",
136
+ message: i18n.t("multi-config:authTypePrompt"),
137
+ choices: [
138
+ { name: i18n.t("multi-config:authType.api_key"), value: "api_key" },
139
+ { name: i18n.t("multi-config:authType.auth_token"), value: "auth_token" }
140
+ ],
141
+ default: prefilledAuthType || "api_key",
142
+ when: () => selectedProvider === "custom"
143
+ // Only ask if custom
144
+ },
145
+ {
146
+ type: "input",
147
+ name: "baseUrl",
148
+ message: i18n.t("multi-config:baseUrlPrompt"),
149
+ default: prefilledBaseUrl || "https://api.anthropic.com",
150
+ when: (answers2) => selectedProvider === "custom" && answers2.authType !== "ccr_proxy",
151
+ validate: (input) => {
152
+ const trimmed = input.trim();
153
+ if (!trimmed) {
154
+ return i18n.t("multi-config:baseUrlRequired");
155
+ }
156
+ try {
157
+ new URL(trimmed);
158
+ return true;
159
+ } catch {
160
+ return i18n.t("multi-config:baseUrlInvalid");
161
+ }
162
+ }
163
+ },
164
+ {
165
+ type: "input",
166
+ name: "apiKey",
167
+ message: selectedProvider !== "custom" ? i18n.t("api:enterProviderApiKey", { provider: providers.find((p) => p.id === selectedProvider)?.name || selectedProvider }) : i18n.t("multi-config:apiKeyPrompt"),
168
+ when: (answers2) => selectedProvider === "custom" ? answers2.authType !== "ccr_proxy" : true,
169
+ validate: (input) => {
170
+ const trimmed = input.trim();
171
+ if (!trimmed) {
172
+ return i18n.t("multi-config:apiKeyRequired");
173
+ }
174
+ const validation = validateApiKey(trimmed);
175
+ if (!validation.isValid) {
176
+ return validation.error || "Invalid API key format";
177
+ }
178
+ return true;
179
+ }
180
+ }
181
+ ]);
182
+ let modelConfig = null;
183
+ if (selectedProvider === "custom") {
184
+ const { promptCustomModels } = await import('./features.mjs');
185
+ modelConfig = await promptCustomModels();
186
+ }
187
+ const setAsDefault = await promptBoolean({
188
+ message: i18n.t("multi-config:setAsDefaultPrompt"),
189
+ defaultValue: true
190
+ });
191
+ const profileName = answers.profileName.trim();
192
+ const profileId = ClaudeCodeConfigManager.generateProfileId(profileName);
193
+ const profile = {
194
+ id: profileId,
195
+ name: profileName,
196
+ authType: selectedProvider === "custom" ? answers.authType : prefilledAuthType
197
+ };
198
+ if (profile.authType !== "ccr_proxy") {
199
+ profile.apiKey = answers.apiKey.trim();
200
+ profile.baseUrl = selectedProvider === "custom" ? answers.baseUrl.trim() : prefilledBaseUrl;
201
+ }
202
+ if (modelConfig) {
203
+ if (modelConfig.primaryModel.trim()) {
204
+ profile.primaryModel = modelConfig.primaryModel.trim();
205
+ }
206
+ if (modelConfig.haikuModel.trim())
207
+ profile.defaultHaikuModel = modelConfig.haikuModel.trim();
208
+ if (modelConfig.sonnetModel.trim())
209
+ profile.defaultSonnetModel = modelConfig.sonnetModel.trim();
210
+ if (modelConfig.opusModel.trim())
211
+ profile.defaultOpusModel = modelConfig.opusModel.trim();
212
+ } else if (prefilledDefaultModels?.length) {
213
+ if (prefilledDefaultModels[0]?.trim())
214
+ profile.primaryModel = prefilledDefaultModels[0].trim();
215
+ if (prefilledDefaultModels[1]?.trim())
216
+ profile.defaultHaikuModel = prefilledDefaultModels[1].trim();
217
+ if (prefilledDefaultModels[2]?.trim())
218
+ profile.defaultSonnetModel = prefilledDefaultModels[2].trim();
219
+ if (prefilledDefaultModels[3]?.trim())
220
+ profile.defaultOpusModel = prefilledDefaultModels[3].trim();
221
+ }
222
+ const existingProfile = ClaudeCodeConfigManager.getProfileByName(profile.name);
223
+ if (existingProfile) {
224
+ const overwrite = await promptBoolean({
225
+ message: i18n.t("multi-config:profileDuplicatePrompt", {
226
+ name: profile.name,
227
+ source: i18n.t("multi-config:existingConfig")
228
+ }),
229
+ defaultValue: false
230
+ });
231
+ if (!overwrite) {
232
+ console.log(ansis.yellow(i18n.t("multi-config:profileDuplicateSkipped", { name: profile.name })));
233
+ const shouldContinue2 = await promptContinueAdding();
234
+ if (shouldContinue2) {
235
+ await handleAddProfile();
236
+ }
237
+ return;
238
+ }
239
+ const updateResult = await ClaudeCodeConfigManager.updateProfile(existingProfile.id, {
240
+ name: profile.name,
241
+ authType: profile.authType,
242
+ apiKey: profile.apiKey,
243
+ baseUrl: profile.baseUrl,
244
+ primaryModel: profile.primaryModel,
245
+ defaultHaikuModel: profile.defaultHaikuModel,
246
+ defaultSonnetModel: profile.defaultSonnetModel,
247
+ defaultOpusModel: profile.defaultOpusModel
248
+ });
249
+ if (updateResult.success) {
250
+ console.log(ansis.green(i18n.t("multi-config:profileUpdated", { name: profile.name })));
251
+ if (updateResult.backupPath) {
252
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: updateResult.backupPath })));
253
+ }
254
+ if (setAsDefault) {
255
+ const switchResult = await ClaudeCodeConfigManager.switchProfile(existingProfile.id);
256
+ if (switchResult.success) {
257
+ console.log(ansis.green(i18n.t("multi-config:profileSetAsDefault", { name: profile.name })));
258
+ await ClaudeCodeConfigManager.applyProfileSettings({ ...profile, id: existingProfile.id });
259
+ }
260
+ }
261
+ } else {
262
+ console.log(ansis.red(i18n.t("multi-config:profileUpdateFailed", { error: updateResult.error || "" })));
263
+ }
264
+ } else {
265
+ const result = await ClaudeCodeConfigManager.addProfile(profile);
266
+ if (result.success) {
267
+ const runtimeProfile = result.addedProfile || { ...profile, id: profileId };
268
+ console.log(ansis.green(i18n.t("multi-config:profileAdded", { name: runtimeProfile.name })));
269
+ if (result.backupPath) {
270
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
271
+ }
272
+ if (setAsDefault) {
273
+ const switchResult = await ClaudeCodeConfigManager.switchProfile(runtimeProfile.id);
274
+ if (switchResult.success) {
275
+ console.log(ansis.green(i18n.t("multi-config:profileSetAsDefault", { name: runtimeProfile.name })));
276
+ await ClaudeCodeConfigManager.applyProfileSettings(runtimeProfile);
277
+ }
278
+ }
279
+ } else {
280
+ console.log(ansis.red(i18n.t("multi-config:profileAddFailed", { error: result.error })));
281
+ }
282
+ }
283
+ const shouldContinue = await promptContinueAdding();
284
+ if (shouldContinue) {
285
+ await handleAddProfile();
286
+ }
287
+ }
288
+ async function handleEditProfile(profiles) {
289
+ const choices = profiles.map((profile) => ({
290
+ name: `${profile.name} (${getAuthTypeLabel(profile.authType)})`,
291
+ value: profile.id
292
+ }));
293
+ const { selectedProfileId } = await inquirer.prompt([{
294
+ type: "list",
295
+ name: "selectedProfileId",
296
+ message: i18n.t("multi-config:selectProfileToEdit"),
297
+ choices: addNumbersToChoices(choices)
298
+ }]);
299
+ if (!selectedProfileId) {
300
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
301
+ return;
302
+ }
303
+ const selectedProfile = profiles.find((p) => p.id === selectedProfileId);
304
+ if (!selectedProfile) {
305
+ console.log(ansis.red(i18n.t("multi-config:profileNotFound")));
306
+ return;
307
+ }
308
+ console.log(ansis.cyan(`
309
+ ${i18n.t("multi-config:editingProfile", { name: selectedProfile.name })}`));
310
+ const answers = await inquirer.prompt([
311
+ {
312
+ type: "input",
313
+ name: "profileName",
314
+ message: i18n.t("multi-config:profileNamePrompt"),
315
+ default: selectedProfile.name,
316
+ validate: (input) => {
317
+ const trimmed = input.trim();
318
+ if (!trimmed) {
319
+ return i18n.t("multi-config:profileNameRequired");
320
+ }
321
+ if (!/^[\w\-\s\u4E00-\u9FA5]+$/.test(trimmed)) {
322
+ return i18n.t("multi-config:profileNameInvalid");
323
+ }
324
+ return true;
325
+ }
326
+ },
327
+ {
328
+ type: "input",
329
+ name: "baseUrl",
330
+ message: i18n.t("multi-config:baseUrlPrompt"),
331
+ default: selectedProfile.baseUrl || "https://api.anthropic.com",
332
+ when: () => selectedProfile.authType !== "ccr_proxy",
333
+ validate: (input) => {
334
+ const trimmed = input.trim();
335
+ if (!trimmed) {
336
+ return i18n.t("multi-config:baseUrlRequired");
337
+ }
338
+ try {
339
+ new URL(trimmed);
340
+ return true;
341
+ } catch {
342
+ return i18n.t("multi-config:baseUrlInvalid");
343
+ }
344
+ }
345
+ },
346
+ {
347
+ type: "input",
348
+ name: "apiKey",
349
+ message: i18n.t("multi-config:apiKeyPrompt"),
350
+ default: selectedProfile.apiKey,
351
+ when: () => selectedProfile.authType !== "ccr_proxy",
352
+ validate: (input) => {
353
+ const trimmed = input.trim();
354
+ if (!trimmed) {
355
+ return i18n.t("multi-config:apiKeyRequired");
356
+ }
357
+ const validation = validateApiKey(trimmed);
358
+ if (!validation.isValid) {
359
+ return validation.error || "Invalid API key format";
360
+ }
361
+ return true;
362
+ }
363
+ }
364
+ ]);
365
+ let modelConfig = null;
366
+ if (selectedProfile.authType !== "ccr_proxy") {
367
+ const { promptCustomModels } = await import('./features.mjs');
368
+ modelConfig = await promptCustomModels(
369
+ selectedProfile.primaryModel,
370
+ selectedProfile.defaultHaikuModel,
371
+ selectedProfile.defaultSonnetModel,
372
+ selectedProfile.defaultOpusModel
373
+ );
374
+ }
375
+ const updateData = {
376
+ name: answers.profileName.trim()
377
+ };
378
+ if (selectedProfile.authType !== "ccr_proxy") {
379
+ updateData.apiKey = answers.apiKey.trim();
380
+ updateData.baseUrl = answers.baseUrl.trim();
381
+ if (modelConfig) {
382
+ if (modelConfig.primaryModel.trim()) {
383
+ updateData.primaryModel = modelConfig.primaryModel.trim();
384
+ }
385
+ if (modelConfig.haikuModel.trim())
386
+ updateData.defaultHaikuModel = modelConfig.haikuModel.trim();
387
+ if (modelConfig.sonnetModel.trim())
388
+ updateData.defaultSonnetModel = modelConfig.sonnetModel.trim();
389
+ if (modelConfig.opusModel.trim())
390
+ updateData.defaultOpusModel = modelConfig.opusModel.trim();
391
+ }
392
+ }
393
+ const result = await ClaudeCodeConfigManager.updateProfile(selectedProfile.id, updateData);
394
+ if (result.success) {
395
+ console.log(ansis.green(i18n.t("multi-config:profileUpdated", { name: updateData.name })));
396
+ if (result.backupPath) {
397
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
398
+ }
399
+ const currentConfig = ClaudeCodeConfigManager.readConfig();
400
+ if (currentConfig?.currentProfileId === selectedProfile.id) {
401
+ const updatedProfile = ClaudeCodeConfigManager.getProfileById(selectedProfile.id);
402
+ if (updatedProfile) {
403
+ await ClaudeCodeConfigManager.applyProfileSettings(updatedProfile);
404
+ console.log(ansis.green(i18n.t("multi-config:settingsApplied")));
405
+ }
406
+ }
407
+ } else {
408
+ console.log(ansis.red(i18n.t("multi-config:profileUpdateFailed", { error: result.error })));
409
+ }
410
+ }
411
+ async function handleCopyProfile(profiles) {
412
+ const choices = profiles.map((profile) => ({
413
+ name: `${profile.name} (${getAuthTypeLabel(profile.authType)})`,
414
+ value: profile.id
415
+ }));
416
+ const { selectedProfileId } = await inquirer.prompt([{
417
+ type: "list",
418
+ name: "selectedProfileId",
419
+ message: i18n.t("multi-config:selectProfileToCopy"),
420
+ choices: addNumbersToChoices(choices)
421
+ }]);
422
+ if (!selectedProfileId) {
423
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
424
+ return;
425
+ }
426
+ const selectedProfile = profiles.find((p) => p.id === selectedProfileId);
427
+ if (!selectedProfile) {
428
+ console.log(ansis.red(i18n.t("multi-config:profileNotFound")));
429
+ return;
430
+ }
431
+ console.log(ansis.cyan(`
432
+ ${i18n.t("multi-config:copyingProfile", { name: selectedProfile.name })}`));
433
+ const copiedName = `${selectedProfile.name}-copy`;
434
+ const questions = [
435
+ {
436
+ type: "input",
437
+ name: "profileName",
438
+ message: i18n.t("multi-config:profileNamePrompt"),
439
+ default: copiedName,
440
+ validate: (input) => {
441
+ const trimmed = input.trim();
442
+ if (!trimmed) {
443
+ return i18n.t("multi-config:profileNameRequired");
444
+ }
445
+ if (!/^[\w\-\s.\u4E00-\u9FA5]+$/.test(trimmed)) {
446
+ return i18n.t("multi-config:profileNameInvalid");
447
+ }
448
+ return true;
449
+ }
450
+ }
451
+ ];
452
+ if (selectedProfile.authType !== "ccr_proxy") {
453
+ questions.push(
454
+ {
455
+ type: "input",
456
+ name: "baseUrl",
457
+ message: i18n.t("multi-config:baseUrlPrompt"),
458
+ default: selectedProfile.baseUrl || "https://api.anthropic.com",
459
+ validate: (input) => {
460
+ const trimmed = input.trim();
461
+ if (!trimmed) {
462
+ return i18n.t("multi-config:baseUrlRequired");
463
+ }
464
+ try {
465
+ new URL(trimmed);
466
+ return true;
467
+ } catch {
468
+ return i18n.t("multi-config:baseUrlInvalid");
469
+ }
470
+ }
471
+ },
472
+ {
473
+ type: "input",
474
+ name: "apiKey",
475
+ message: i18n.t("multi-config:apiKeyPrompt"),
476
+ default: selectedProfile.apiKey,
477
+ validate: (input) => {
478
+ const trimmed = input.trim();
479
+ if (!trimmed) {
480
+ return i18n.t("multi-config:apiKeyRequired");
481
+ }
482
+ const validation = validateApiKey(trimmed);
483
+ if (!validation.isValid) {
484
+ return validation.error || "Invalid API key format";
485
+ }
486
+ return true;
487
+ }
488
+ }
489
+ );
490
+ }
491
+ const answers = await inquirer.prompt(questions);
492
+ let modelConfig = null;
493
+ if (selectedProfile.authType !== "ccr_proxy") {
494
+ const { promptCustomModels } = await import('./features.mjs');
495
+ modelConfig = await promptCustomModels(
496
+ selectedProfile.primaryModel,
497
+ selectedProfile.defaultHaikuModel,
498
+ selectedProfile.defaultSonnetModel,
499
+ selectedProfile.defaultOpusModel
500
+ );
501
+ }
502
+ const setAsDefault = await promptBoolean({
503
+ message: i18n.t("multi-config:setAsDefaultPrompt"),
504
+ defaultValue: false
505
+ });
506
+ const profileName = answers.profileName.trim();
507
+ const profileId = ClaudeCodeConfigManager.generateProfileId(profileName);
508
+ const copiedProfile = {
509
+ id: profileId,
510
+ name: profileName,
511
+ authType: selectedProfile.authType
512
+ };
513
+ if (selectedProfile.authType !== "ccr_proxy") {
514
+ copiedProfile.apiKey = answers.apiKey.trim();
515
+ copiedProfile.baseUrl = answers.baseUrl.trim();
516
+ if (modelConfig) {
517
+ if (modelConfig.primaryModel.trim()) {
518
+ copiedProfile.primaryModel = modelConfig.primaryModel.trim();
519
+ }
520
+ if (modelConfig.haikuModel.trim())
521
+ copiedProfile.defaultHaikuModel = modelConfig.haikuModel.trim();
522
+ if (modelConfig.sonnetModel.trim())
523
+ copiedProfile.defaultSonnetModel = modelConfig.sonnetModel.trim();
524
+ if (modelConfig.opusModel.trim())
525
+ copiedProfile.defaultOpusModel = modelConfig.opusModel.trim();
526
+ }
527
+ }
528
+ const result = await ClaudeCodeConfigManager.addProfile(copiedProfile);
529
+ if (result.success) {
530
+ const runtimeProfile = result.addedProfile || { ...copiedProfile, id: profileId };
531
+ console.log(ansis.green(i18n.t("multi-config:profileCopied", { name: runtimeProfile.name })));
532
+ if (result.backupPath) {
533
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
534
+ }
535
+ if (setAsDefault) {
536
+ const switchResult = await ClaudeCodeConfigManager.switchProfile(runtimeProfile.id);
537
+ if (switchResult.success) {
538
+ console.log(ansis.green(i18n.t("multi-config:profileSetAsDefault", { name: runtimeProfile.name })));
539
+ await ClaudeCodeConfigManager.applyProfileSettings(runtimeProfile);
540
+ }
541
+ }
542
+ } else {
543
+ console.log(ansis.red(i18n.t("multi-config:profileCopyFailed", { error: result.error })));
544
+ }
545
+ }
546
+ async function handleDeleteProfile(profiles) {
547
+ if (profiles.length <= 1) {
548
+ console.log(ansis.yellow(i18n.t("multi-config:cannotDeleteLast")));
549
+ return;
550
+ }
551
+ const choices = profiles.map((profile) => ({
552
+ name: `${profile.name} (${getAuthTypeLabel(profile.authType)})`,
553
+ value: profile.id
554
+ }));
555
+ const { selectedProfileIds } = await inquirer.prompt({
556
+ type: "checkbox",
557
+ name: "selectedProfileIds",
558
+ message: i18n.t("multi-config:selectProfilesToDelete"),
559
+ choices: addNumbersToChoices(choices),
560
+ validate: (input) => {
561
+ if (input.length === 0) {
562
+ return i18n.t("multi-config:selectAtLeastOne");
563
+ }
564
+ if (input.length >= profiles.length) {
565
+ return i18n.t("multi-config:cannotDeleteAll");
566
+ }
567
+ return true;
568
+ }
569
+ });
570
+ if (!selectedProfileIds || selectedProfileIds.length === 0) {
571
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
572
+ return;
573
+ }
574
+ const selectedNames = selectedProfileIds.map(
575
+ (id) => profiles.find((p) => p.id === id)?.name || id
576
+ );
577
+ const confirmed = await promptBoolean({
578
+ message: i18n.t("multi-config:confirmDeleteProfiles", { providers: selectedNames.join(", ") }),
579
+ defaultValue: false
580
+ });
581
+ if (!confirmed) {
582
+ console.log(ansis.yellow(i18n.t("common:cancelled")));
583
+ return;
584
+ }
585
+ const result = await ClaudeCodeConfigManager.deleteProfiles(selectedProfileIds);
586
+ if (result.success) {
587
+ console.log(ansis.green(i18n.t("multi-config:profilesDeleted", { count: selectedProfileIds.length })));
588
+ if (result.backupPath) {
589
+ console.log(ansis.gray(i18n.t("common:backupCreated", { path: result.backupPath })));
590
+ }
591
+ if (result.newCurrentProfileId) {
592
+ const newProfile = ClaudeCodeConfigManager.getProfileById(result.newCurrentProfileId);
593
+ if (newProfile) {
594
+ console.log(ansis.cyan(i18n.t("multi-config:newDefaultProfile", { profile: newProfile.name })));
595
+ await ClaudeCodeConfigManager.applyProfileSettings(newProfile);
596
+ }
597
+ }
598
+ } else {
599
+ console.log(ansis.red(i18n.t("multi-config:profilesDeleteFailed", { error: result.error })));
600
+ }
601
+ }
602
+
603
+ export { configureIncrementalManagement, getAuthTypeLabel };