@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.
Files changed (3) hide show
  1. package/README.md +18 -0
  2. package/dist/index.js +184 -7
  3. 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, currentSettings, "utf-8");
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 createProfile(profileName);
313
- clack.outro(`Created profile "${profileName}"`);
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
- console.log(`Created profile "${opts.create}"`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aravhawk/cc-switch",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Profile manager for Claude Code settings",
5
5
  "type": "module",
6
6
  "bin": {