@aravhawk/cc-switch 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/index.js +197 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,6 +65,24 @@ Example:
|
|
|
65
65
|
cc-switch --create personal
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
+
New profiles are activated immediately after creation.
|
|
69
|
+
|
|
70
|
+
#### Create From Provider Template
|
|
71
|
+
|
|
72
|
+
Create and apply provider defaults in one step (prompts for API key if omitted):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
cc-switch --create <profile-name> --template moonshot --api-key <your-key>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Available templates:
|
|
79
|
+
- anthropic (claude) – no API key needed; leaves defaults in place
|
|
80
|
+
- moonshot (kimi)
|
|
81
|
+
- zai (glm)
|
|
82
|
+
- minimax
|
|
83
|
+
|
|
84
|
+
Templates set `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, and default Haiku/Sonnet/Opus models, and remove any existing `ANTHROPIC_API_KEY`.
|
|
85
|
+
|
|
68
86
|
#### Delete Profile
|
|
69
87
|
|
|
70
88
|
Delete an existing profile (cannot delete the active profile):
|
package/dist/index.js
CHANGED
|
@@ -82,6 +82,66 @@ function validateProfileName(name) {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// src/profiles.ts
|
|
85
|
+
var PROVIDER_TEMPLATES = [
|
|
86
|
+
{
|
|
87
|
+
name: "anthropic",
|
|
88
|
+
label: "Anthropic (Claude default)",
|
|
89
|
+
requiresApiKey: false,
|
|
90
|
+
aliases: ["claude"]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "moonshot",
|
|
94
|
+
label: "Moonshot (Kimi)",
|
|
95
|
+
requiresApiKey: true,
|
|
96
|
+
baseUrl: "https://api.kimi.com/coding/",
|
|
97
|
+
haikuModel: "kimi-for-coding",
|
|
98
|
+
sonnetModel: "kimi-for-coding",
|
|
99
|
+
opusModel: "kimi-for-coding",
|
|
100
|
+
aliases: ["kimi"]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "zai",
|
|
104
|
+
label: "Z.ai (GLM)",
|
|
105
|
+
requiresApiKey: true,
|
|
106
|
+
baseUrl: "https://api.z.ai/api/anthropic",
|
|
107
|
+
haikuModel: "glm-4.5-air",
|
|
108
|
+
sonnetModel: "glm-4.7",
|
|
109
|
+
opusModel: "glm-4.7",
|
|
110
|
+
aliases: ["glm"]
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "minimax",
|
|
114
|
+
label: "MiniMax",
|
|
115
|
+
requiresApiKey: true,
|
|
116
|
+
baseUrl: "https://api.minimax.io/anthropic",
|
|
117
|
+
haikuModel: "MiniMax-M2.1",
|
|
118
|
+
sonnetModel: "MiniMax-M2.1",
|
|
119
|
+
opusModel: "MiniMax-M2.1"
|
|
120
|
+
}
|
|
121
|
+
];
|
|
122
|
+
var TEMPLATE_ALIASES = /* @__PURE__ */ new Map();
|
|
123
|
+
for (const template of PROVIDER_TEMPLATES) {
|
|
124
|
+
TEMPLATE_ALIASES.set(template.name.toLowerCase(), template.name);
|
|
125
|
+
for (const alias of template.aliases ?? []) {
|
|
126
|
+
TEMPLATE_ALIASES.set(alias.toLowerCase(), template.name);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function getTemplateConfig(name) {
|
|
130
|
+
return PROVIDER_TEMPLATES.find((template) => template.name === name);
|
|
131
|
+
}
|
|
132
|
+
function getProviderTemplates() {
|
|
133
|
+
return PROVIDER_TEMPLATES.map((template) => ({
|
|
134
|
+
name: template.name,
|
|
135
|
+
label: template.label,
|
|
136
|
+
requiresApiKey: template.requiresApiKey
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
function resolveTemplateName(input) {
|
|
140
|
+
if (!input) {
|
|
141
|
+
return void 0;
|
|
142
|
+
}
|
|
143
|
+
return TEMPLATE_ALIASES.get(input.trim().toLowerCase());
|
|
144
|
+
}
|
|
85
145
|
async function checkClaudeSettings() {
|
|
86
146
|
try {
|
|
87
147
|
await access(CLAUDE_SETTINGS);
|
|
@@ -125,6 +185,30 @@ async function getActiveProfileStatus() {
|
|
|
125
185
|
const exists = await profileExists(state.activeProfile);
|
|
126
186
|
return { name: state.activeProfile, exists };
|
|
127
187
|
}
|
|
188
|
+
function applyTemplateOverrides(settingsContent, template, apiKey) {
|
|
189
|
+
const parsed = JSON.parse(settingsContent);
|
|
190
|
+
const env = { ...parsed.env ?? {} };
|
|
191
|
+
delete env.ANTHROPIC_API_KEY;
|
|
192
|
+
if (template.baseUrl) {
|
|
193
|
+
env.ANTHROPIC_BASE_URL = template.baseUrl;
|
|
194
|
+
}
|
|
195
|
+
if (template.requiresApiKey && apiKey) {
|
|
196
|
+
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
197
|
+
} else if (apiKey) {
|
|
198
|
+
env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
199
|
+
}
|
|
200
|
+
if (template.haikuModel) {
|
|
201
|
+
env.ANTHROPIC_DEFAULT_HAIKU_MODEL = template.haikuModel;
|
|
202
|
+
}
|
|
203
|
+
if (template.sonnetModel) {
|
|
204
|
+
env.ANTHROPIC_DEFAULT_SONNET_MODEL = template.sonnetModel;
|
|
205
|
+
}
|
|
206
|
+
if (template.opusModel) {
|
|
207
|
+
env.ANTHROPIC_DEFAULT_OPUS_MODEL = template.opusModel;
|
|
208
|
+
}
|
|
209
|
+
parsed.env = env;
|
|
210
|
+
return JSON.stringify(parsed, null, 2);
|
|
211
|
+
}
|
|
128
212
|
async function mirrorSettings(profileName) {
|
|
129
213
|
const profileSettings = getProfileSettings(profileName);
|
|
130
214
|
await mkdir2(dirname2(profileSettings), { recursive: true });
|
|
@@ -155,7 +239,7 @@ async function switchProfile(targetProfile) {
|
|
|
155
239
|
await fsRename(tempFile, CLAUDE_SETTINGS);
|
|
156
240
|
await updateState({ activeProfile: targetProfile });
|
|
157
241
|
}
|
|
158
|
-
async function createProfile(profileName) {
|
|
242
|
+
async function createProfile(profileName, options) {
|
|
159
243
|
const validation = validateProfileName(profileName);
|
|
160
244
|
if (!validation.valid) {
|
|
161
245
|
throw new Error(validation.error);
|
|
@@ -164,12 +248,39 @@ async function createProfile(profileName) {
|
|
|
164
248
|
if (await profileExists(profileName)) {
|
|
165
249
|
throw new Error(`Profile "${profileName}" already exists`);
|
|
166
250
|
}
|
|
251
|
+
const templateConfig = options?.template ? getTemplateConfig(options.template) : void 0;
|
|
252
|
+
if (options?.template && !templateConfig) {
|
|
253
|
+
throw new Error(`Unknown template "${options.template}"`);
|
|
254
|
+
}
|
|
255
|
+
const apiKey = options?.apiKey?.trim();
|
|
256
|
+
if (templateConfig?.requiresApiKey && !apiKey) {
|
|
257
|
+
throw new Error(`API key is required for template "${templateConfig.label}"`);
|
|
258
|
+
}
|
|
259
|
+
if (!templateConfig && options?.apiKey) {
|
|
260
|
+
throw new Error("API key can only be used with a template");
|
|
261
|
+
}
|
|
167
262
|
const profileSettings = getProfileSettings(profileName);
|
|
168
263
|
await mkdir2(dirname2(profileSettings), { recursive: true });
|
|
169
264
|
const currentSettings = await readFile2(CLAUDE_SETTINGS, "utf-8");
|
|
265
|
+
let activeProfileForTemplate;
|
|
266
|
+
if (templateConfig) {
|
|
267
|
+
const state = await readState();
|
|
268
|
+
activeProfileForTemplate = state.activeProfile;
|
|
269
|
+
}
|
|
270
|
+
let settingsToWrite = currentSettings;
|
|
271
|
+
if (templateConfig) {
|
|
272
|
+
await mirrorSettings(activeProfileForTemplate ?? "default");
|
|
273
|
+
settingsToWrite = applyTemplateOverrides(currentSettings, templateConfig, apiKey);
|
|
274
|
+
}
|
|
170
275
|
const tempFile = `${profileSettings}.tmp`;
|
|
171
|
-
await writeFile2(tempFile,
|
|
276
|
+
await writeFile2(tempFile, settingsToWrite, "utf-8");
|
|
172
277
|
await fsRename(tempFile, profileSettings);
|
|
278
|
+
if (templateConfig) {
|
|
279
|
+
const tempCurrentSettings = `${CLAUDE_SETTINGS}.tmp`;
|
|
280
|
+
await writeFile2(tempCurrentSettings, settingsToWrite, "utf-8");
|
|
281
|
+
await fsRename(tempCurrentSettings, CLAUDE_SETTINGS);
|
|
282
|
+
}
|
|
283
|
+
await updateState({ activeProfile: profileName });
|
|
173
284
|
}
|
|
174
285
|
async function deleteProfile(profileName) {
|
|
175
286
|
const validation = validateProfileName(profileName);
|
|
@@ -197,6 +308,9 @@ async function renameProfile(oldName, newName) {
|
|
|
197
308
|
if (!await profileExists(oldName)) {
|
|
198
309
|
throw new Error(`Profile "${oldName}" does not exist`);
|
|
199
310
|
}
|
|
311
|
+
if (oldName === newName) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
200
314
|
if (await profileExists(newName)) {
|
|
201
315
|
throw new Error(`Profile "${newName}" already exists`);
|
|
202
316
|
}
|
|
@@ -233,6 +347,48 @@ function getProfileValidationMessage(value) {
|
|
|
233
347
|
}
|
|
234
348
|
return void 0;
|
|
235
349
|
}
|
|
350
|
+
async function promptForTemplateSelection() {
|
|
351
|
+
const templates = getProviderTemplates();
|
|
352
|
+
const selection = await clack.select({
|
|
353
|
+
message: "Choose a provider template (optional):",
|
|
354
|
+
options: [
|
|
355
|
+
{ value: "none", label: "None (copy current settings)" },
|
|
356
|
+
...templates.map((template) => ({
|
|
357
|
+
value: template.name,
|
|
358
|
+
label: template.label
|
|
359
|
+
}))
|
|
360
|
+
]
|
|
361
|
+
});
|
|
362
|
+
if (clack.isCancel(selection)) {
|
|
363
|
+
clack.cancel("Operation cancelled");
|
|
364
|
+
process.exit(0);
|
|
365
|
+
}
|
|
366
|
+
if (selection === "none") {
|
|
367
|
+
return {};
|
|
368
|
+
}
|
|
369
|
+
const chosenTemplate = selection;
|
|
370
|
+
const meta = templates.find((template) => template.name === chosenTemplate);
|
|
371
|
+
if (!meta) {
|
|
372
|
+
throw new Error("Selected template is not available");
|
|
373
|
+
}
|
|
374
|
+
if (!meta.requiresApiKey) {
|
|
375
|
+
return { template: chosenTemplate };
|
|
376
|
+
}
|
|
377
|
+
const apiKey = await clack.password({
|
|
378
|
+
message: `Enter API key for ${meta.label}:`,
|
|
379
|
+
validate: (value) => {
|
|
380
|
+
if (!value || !String(value).trim()) {
|
|
381
|
+
return "API key cannot be empty";
|
|
382
|
+
}
|
|
383
|
+
return void 0;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
if (clack.isCancel(apiKey)) {
|
|
387
|
+
clack.cancel("Operation cancelled");
|
|
388
|
+
process.exit(0);
|
|
389
|
+
}
|
|
390
|
+
return { template: chosenTemplate, apiKey };
|
|
391
|
+
}
|
|
236
392
|
async function showProfileList(profiles) {
|
|
237
393
|
const activeStatus = await getActiveProfileStatus();
|
|
238
394
|
const resolvedProfiles = profiles ?? await listProfiles();
|
|
@@ -248,7 +404,7 @@ async function showProfileList(profiles) {
|
|
|
248
404
|
}
|
|
249
405
|
console.log();
|
|
250
406
|
}
|
|
251
|
-
program.name("cc-switch").description("Profile manager for Claude Code settings").version(cliVersion, "-V, --version", "output the version number").argument("[profile]", "Profile name to switch to").option("--switch <name>", "Switch to a different profile").option("--create <name>", "Create a new profile from current settings").option("--delete <name>", "Delete a profile").option("--rename <names...>", "Rename a profile").option("--to <name>", "New name for rename").option("--current", "Show the current active profile").option("--list", "List all profiles");
|
|
407
|
+
program.name("cc-switch").description("Profile manager for Claude Code settings").version(cliVersion, "-V, --version", "output the version number").argument("[profile]", "Profile name to switch to").option("--switch <name>", "Switch to a different profile").option("--create <name>", "Create a new profile from current settings").option("--delete <name>", "Delete a profile").option("--rename <names...>", "Rename a profile").option("--to <name>", "New name for rename").option("--current", "Show the current active profile").option("--list", "List all profiles").option("--template <provider>", "Create profile using a provider template").option("--api-key <key>", "API key for the provider template");
|
|
252
408
|
async function interactiveMode() {
|
|
253
409
|
clack.intro("cc-switch - Claude Code Profile Manager");
|
|
254
410
|
try {
|
|
@@ -306,8 +462,10 @@ async function interactiveMode() {
|
|
|
306
462
|
clack.cancel("Operation cancelled");
|
|
307
463
|
process.exit(0);
|
|
308
464
|
}
|
|
309
|
-
await
|
|
310
|
-
|
|
465
|
+
const templateSelection = await promptForTemplateSelection();
|
|
466
|
+
await createProfile(profileName, templateSelection);
|
|
467
|
+
const templateSuffix = templateSelection.template ? ` with template "${templateSelection.template}"` : "";
|
|
468
|
+
clack.outro(`Created profile "${profileName}"${templateSuffix}`);
|
|
311
469
|
break;
|
|
312
470
|
}
|
|
313
471
|
case "delete": {
|
|
@@ -366,6 +524,10 @@ async function interactiveMode() {
|
|
|
366
524
|
clack.cancel("Operation cancelled");
|
|
367
525
|
process.exit(0);
|
|
368
526
|
}
|
|
527
|
+
if (oldName === newName) {
|
|
528
|
+
clack.outro("Profile name unchanged");
|
|
529
|
+
process.exit(0);
|
|
530
|
+
}
|
|
369
531
|
await renameProfile(oldName, newName);
|
|
370
532
|
clack.outro(`Renamed profile "${oldName}" to "${newName}"`);
|
|
371
533
|
break;
|
|
@@ -401,6 +563,8 @@ async function runCli() {
|
|
|
401
563
|
}
|
|
402
564
|
program.parse();
|
|
403
565
|
const opts = program.opts();
|
|
566
|
+
const resolvedTemplate = opts.template ? resolveTemplateName(opts.template) : void 0;
|
|
567
|
+
const templateMeta = resolvedTemplate ? getProviderTemplates().find((template) => template.name === resolvedTemplate) : void 0;
|
|
404
568
|
const profileArg = program.args[0];
|
|
405
569
|
const actionFlags = [];
|
|
406
570
|
if (opts.switch) actionFlags.push("switch");
|
|
@@ -409,6 +573,22 @@ async function runCli() {
|
|
|
409
573
|
if (opts.rename || opts.to) actionFlags.push("rename");
|
|
410
574
|
if (opts.current) actionFlags.push("current");
|
|
411
575
|
if (opts.list) actionFlags.push("list");
|
|
576
|
+
if (opts.template && !opts.create) {
|
|
577
|
+
console.error("Error: --template can only be used with --create.");
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
if (opts.apiKey && !opts.template) {
|
|
581
|
+
console.error("Error: --api-key can only be used with --template.");
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
if (opts.template && !resolvedTemplate) {
|
|
585
|
+
console.error(`Error: Unknown template "${opts.template}".`);
|
|
586
|
+
process.exit(1);
|
|
587
|
+
}
|
|
588
|
+
if (templateMeta?.requiresApiKey && !opts.apiKey) {
|
|
589
|
+
console.error(`Error: API key is required for template "${templateMeta.label}".`);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
}
|
|
412
592
|
if (actionFlags.length > 1) {
|
|
413
593
|
console.error("Error: Please provide only one action flag at a time.");
|
|
414
594
|
process.exit(1);
|
|
@@ -449,8 +629,12 @@ async function runCli() {
|
|
|
449
629
|
if (!opts.create) {
|
|
450
630
|
throw new Error("Missing profile name for --create");
|
|
451
631
|
}
|
|
452
|
-
await createProfile(opts.create
|
|
453
|
-
|
|
632
|
+
await createProfile(opts.create, {
|
|
633
|
+
template: resolvedTemplate,
|
|
634
|
+
apiKey: opts.apiKey
|
|
635
|
+
});
|
|
636
|
+
const templateSuffix = resolvedTemplate ? ` with template "${resolvedTemplate}"` : "";
|
|
637
|
+
console.log(`Created profile "${opts.create}"${templateSuffix}`);
|
|
454
638
|
process.exit(0);
|
|
455
639
|
break;
|
|
456
640
|
}
|
|
@@ -483,6 +667,12 @@ async function runCli() {
|
|
|
483
667
|
if (!newName) {
|
|
484
668
|
throw new Error("Missing new profile name for --rename");
|
|
485
669
|
}
|
|
670
|
+
if (oldName === newName) {
|
|
671
|
+
await renameProfile(oldName, newName);
|
|
672
|
+
console.log("Profile name unchanged");
|
|
673
|
+
process.exit(0);
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
486
676
|
await renameProfile(oldName, newName);
|
|
487
677
|
console.log(`Renamed profile "${oldName}" to "${newName}"`);
|
|
488
678
|
process.exit(0);
|