@aravhawk/cc-switch 2.0.1 → 2.1.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.
- package/README.md +18 -0
- package/dist/index.js +184 -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.5",
|
|
118
|
+
sonnetModel: "MiniMax-M2.5",
|
|
119
|
+
opusModel: "MiniMax-M2.5"
|
|
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);
|
|
@@ -236,6 +347,48 @@ function getProfileValidationMessage(value) {
|
|
|
236
347
|
}
|
|
237
348
|
return void 0;
|
|
238
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
|
+
}
|
|
239
392
|
async function showProfileList(profiles) {
|
|
240
393
|
const activeStatus = await getActiveProfileStatus();
|
|
241
394
|
const resolvedProfiles = profiles ?? await listProfiles();
|
|
@@ -251,7 +404,7 @@ async function showProfileList(profiles) {
|
|
|
251
404
|
}
|
|
252
405
|
console.log();
|
|
253
406
|
}
|
|
254
|
-
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");
|
|
255
408
|
async function interactiveMode() {
|
|
256
409
|
clack.intro("cc-switch - Claude Code Profile Manager");
|
|
257
410
|
try {
|
|
@@ -309,8 +462,10 @@ async function interactiveMode() {
|
|
|
309
462
|
clack.cancel("Operation cancelled");
|
|
310
463
|
process.exit(0);
|
|
311
464
|
}
|
|
312
|
-
await
|
|
313
|
-
|
|
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}`);
|
|
314
469
|
break;
|
|
315
470
|
}
|
|
316
471
|
case "delete": {
|
|
@@ -408,6 +563,8 @@ async function runCli() {
|
|
|
408
563
|
}
|
|
409
564
|
program.parse();
|
|
410
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;
|
|
411
568
|
const profileArg = program.args[0];
|
|
412
569
|
const actionFlags = [];
|
|
413
570
|
if (opts.switch) actionFlags.push("switch");
|
|
@@ -416,6 +573,22 @@ async function runCli() {
|
|
|
416
573
|
if (opts.rename || opts.to) actionFlags.push("rename");
|
|
417
574
|
if (opts.current) actionFlags.push("current");
|
|
418
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
|
+
}
|
|
419
592
|
if (actionFlags.length > 1) {
|
|
420
593
|
console.error("Error: Please provide only one action flag at a time.");
|
|
421
594
|
process.exit(1);
|
|
@@ -456,8 +629,12 @@ async function runCli() {
|
|
|
456
629
|
if (!opts.create) {
|
|
457
630
|
throw new Error("Missing profile name for --create");
|
|
458
631
|
}
|
|
459
|
-
await createProfile(opts.create
|
|
460
|
-
|
|
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}`);
|
|
461
638
|
process.exit(0);
|
|
462
639
|
break;
|
|
463
640
|
}
|