@aravhawk/cc-switch 1.1.1 → 2.0.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 +31 -12
  2. package/dist/index.js +178 -102
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -34,18 +34,20 @@ This will present a menu with options to:
34
34
 
35
35
  ### Command Line Mode
36
36
 
37
+ Actions use flags. Use a positional profile name for quick switching.
38
+
37
39
  #### Switch Profile
38
40
 
39
41
  ```bash
40
- cc-switch switch <profile-name>
42
+ cc-switch <profile-name>
41
43
  ```
42
44
 
43
- Example:
45
+ Or explicit:
44
46
  ```bash
45
- cc-switch switch work
47
+ cc-switch --switch <profile-name>
46
48
  ```
47
49
 
48
- Shorthand:
50
+ Example:
49
51
  ```bash
50
52
  cc-switch work
51
53
  ```
@@ -55,12 +57,12 @@ cc-switch work
55
57
  Create a new profile from your current `~/.claude/settings.json`:
56
58
 
57
59
  ```bash
58
- cc-switch create <profile-name>
60
+ cc-switch --create <profile-name>
59
61
  ```
60
62
 
61
63
  Example:
62
64
  ```bash
63
- cc-switch create personal
65
+ cc-switch --create personal
64
66
  ```
65
67
 
66
68
  #### Delete Profile
@@ -68,12 +70,12 @@ cc-switch create personal
68
70
  Delete an existing profile (cannot delete the active profile):
69
71
 
70
72
  ```bash
71
- cc-switch delete <profile-name>
73
+ cc-switch --delete <profile-name>
72
74
  ```
73
75
 
74
76
  Example:
75
77
  ```bash
76
- cc-switch delete old-config
78
+ cc-switch --delete old-config
77
79
  ```
78
80
 
79
81
  #### Rename Profile
@@ -81,12 +83,17 @@ cc-switch delete old-config
81
83
  Rename an existing profile:
82
84
 
83
85
  ```bash
84
- cc-switch rename <old-name> <new-name>
86
+ cc-switch --rename <old-name> <new-name>
87
+ ```
88
+
89
+ Or:
90
+ ```bash
91
+ cc-switch --rename <old-name> --to <new-name>
85
92
  ```
86
93
 
87
94
  Example:
88
95
  ```bash
89
- cc-switch rename work work-2024
96
+ cc-switch --rename work work-2024
90
97
  ```
91
98
 
92
99
  #### List Profiles
@@ -94,7 +101,7 @@ cc-switch rename work work-2024
94
101
  List all available profiles:
95
102
 
96
103
  ```bash
97
- cc-switch list
104
+ cc-switch --list
98
105
  ```
99
106
 
100
107
  #### Current Profile
@@ -102,9 +109,20 @@ cc-switch list
102
109
  Show the active profile:
103
110
 
104
111
  ```bash
105
- cc-switch current
112
+ cc-switch --current
106
113
  ```
107
114
 
115
+ #### Help and Version
116
+
117
+ ```bash
118
+ cc-switch help
119
+ cc-switch --help
120
+ cc-switch version
121
+ cc-switch --version
122
+ ```
123
+
124
+ Reserved profile names: `help` and `version`.
125
+
108
126
  ## How It Works
109
127
 
110
128
  `cc-switch` manages multiple Claude Code profiles by storing copies of your `settings.json` file in separate profile directories.
@@ -166,6 +184,7 @@ Profile names must:
166
184
  - Not be empty
167
185
  - Only contain letters, numbers, hyphens, and underscores
168
186
  - Not contain path separators (`/`, `\`, `..`)
187
+ - Not be reserved words (`help`, `version`)
169
188
 
170
189
  ## Error Handling
171
190
 
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
5
  import * as clack from "@clack/prompts";
6
+ import { createRequire } from "module";
6
7
 
7
8
  // src/profiles.ts
8
9
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, rm, readdir, rename as fsRename, access } from "fs/promises";
@@ -59,14 +60,22 @@ async function updateState(updates) {
59
60
  }
60
61
 
61
62
  // src/validation.ts
63
+ var RESERVED_PROFILE_NAMES = /* @__PURE__ */ new Set(["help", "version"]);
62
64
  function validateProfileName(name) {
63
- if (!name || name.trim().length === 0) {
65
+ const trimmedName = name.trim();
66
+ if (!trimmedName) {
64
67
  return { valid: false, error: "Profile name cannot be empty" };
65
68
  }
66
- if (name.includes("..") || name.includes("/") || name.includes("\\")) {
69
+ if (name !== trimmedName) {
70
+ return { valid: false, error: "Profile name cannot contain leading or trailing spaces" };
71
+ }
72
+ if (RESERVED_PROFILE_NAMES.has(trimmedName.toLowerCase())) {
73
+ return { valid: false, error: `Profile name "${trimmedName}" is reserved. Choose a different name.` };
74
+ }
75
+ if (trimmedName.includes("..") || trimmedName.includes("/") || trimmedName.includes("\\")) {
67
76
  return { valid: false, error: 'Profile name cannot contain "..", "/", or "\\"' };
68
77
  }
69
- if (!/^[A-Za-z0-9-_]+$/.test(name)) {
78
+ if (!/^[A-Za-z0-9-_]+$/.test(trimmedName)) {
70
79
  return { valid: false, error: "Profile name can only contain letters, numbers, hyphens, and underscores" };
71
80
  }
72
81
  return { valid: true };
@@ -188,6 +197,9 @@ async function renameProfile(oldName, newName) {
188
197
  if (!await profileExists(oldName)) {
189
198
  throw new Error(`Profile "${oldName}" does not exist`);
190
199
  }
200
+ if (oldName === newName) {
201
+ return;
202
+ }
191
203
  if (await profileExists(newName)) {
192
204
  throw new Error(`Profile "${newName}" already exists`);
193
205
  }
@@ -199,89 +211,53 @@ async function renameProfile(oldName, newName) {
199
211
  }
200
212
 
201
213
  // src/index.ts
214
+ var require2 = createRequire(import.meta.url);
215
+ function getCliVersion() {
216
+ try {
217
+ const pkg = require2("../package.json");
218
+ if (pkg && typeof pkg.version === "string") {
219
+ return pkg.version;
220
+ }
221
+ } catch {
222
+ return "unknown";
223
+ }
224
+ return "unknown";
225
+ }
226
+ var cliVersion = getCliVersion();
202
227
  var program = new Command();
203
228
  function formatActiveProfileLine(status) {
204
229
  const missingSuffix = status.exists ? "" : " (missing)";
205
230
  return `Current profile: "${status.name}"${missingSuffix}`;
206
231
  }
207
- program.name("cc-switch").description("Profile manager for Claude Code settings").version("1.0.0");
208
- program.command("switch <name>").description("Switch to a different profile").action(async (name) => {
209
- try {
210
- await switchProfile(name);
211
- console.log(`Switched to profile "${name}"`);
212
- process.exit(0);
213
- } catch (error) {
214
- console.error(`Error: ${error.message}`);
215
- process.exit(1);
216
- }
217
- });
218
- program.command("create <name>").description("Create a new profile from current settings").action(async (name) => {
219
- try {
220
- await createProfile(name);
221
- console.log(`Created profile "${name}"`);
222
- process.exit(0);
223
- } catch (error) {
224
- console.error(`Error: ${error.message}`);
225
- process.exit(1);
226
- }
227
- });
228
- program.command("delete <name>").description("Delete a profile").action(async (name) => {
229
- try {
230
- await deleteProfile(name);
231
- console.log(`Deleted profile "${name}"`);
232
- process.exit(0);
233
- } catch (error) {
234
- console.error(`Error: ${error.message}`);
235
- process.exit(1);
236
- }
237
- });
238
- program.command("rename <oldName> <newName>").description("Rename a profile").action(async (oldName, newName) => {
239
- try {
240
- await renameProfile(oldName, newName);
241
- console.log(`Renamed profile "${oldName}" to "${newName}"`);
242
- process.exit(0);
243
- } catch (error) {
244
- console.error(`Error: ${error.message}`);
245
- process.exit(1);
232
+ function getProfileValidationMessage(value) {
233
+ const validation = validateProfileName(value ?? "");
234
+ if (!validation.valid) {
235
+ return validation.error ?? "Profile name is invalid";
246
236
  }
247
- });
248
- program.command("current").description("Show the current active profile").action(async () => {
249
- try {
250
- const activeStatus = await getActiveProfileStatus();
251
- console.log(formatActiveProfileLine(activeStatus));
252
- process.exit(0);
253
- } catch (error) {
254
- console.error(`Error: ${error.message}`);
255
- process.exit(1);
237
+ return void 0;
238
+ }
239
+ async function showProfileList(profiles) {
240
+ const activeStatus = await getActiveProfileStatus();
241
+ const resolvedProfiles = profiles ?? await listProfiles();
242
+ console.log(formatActiveProfileLine(activeStatus));
243
+ if (resolvedProfiles.length === 0) {
244
+ console.log("No profiles found");
245
+ return;
256
246
  }
257
- });
258
- program.command("list").description("List all profiles").action(async () => {
259
- try {
260
- const profiles = await listProfiles();
261
- const activeStatus = await getActiveProfileStatus();
262
- console.log(formatActiveProfileLine(activeStatus));
263
- if (profiles.length === 0) {
264
- console.log("No profiles found");
265
- process.exit(0);
266
- }
267
- console.log("\nProfiles:");
268
- for (const profile of profiles) {
269
- const marker = profile.isActive ? " (active)" : "";
270
- console.log(` ${profile.name}${marker}`);
271
- }
272
- console.log();
273
- process.exit(0);
274
- } catch (error) {
275
- console.error(`Error: ${error.message}`);
276
- process.exit(1);
247
+ console.log("\nProfiles:");
248
+ for (const profile of resolvedProfiles) {
249
+ const marker = profile.isActive ? " (active)" : "";
250
+ console.log(` ${profile.name}${marker}`);
277
251
  }
278
- });
252
+ console.log();
253
+ }
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");
279
255
  async function interactiveMode() {
280
256
  clack.intro("cc-switch - Claude Code Profile Manager");
281
257
  try {
282
258
  const profiles = await listProfiles();
283
259
  if (profiles.length === 0) {
284
- clack.outro("No profiles found. Create one with: cc-switch create <name>");
260
+ clack.outro("No profiles found. Create one with: cc-switch --create <name>");
285
261
  process.exit(0);
286
262
  }
287
263
  const action = await clack.select({
@@ -326,10 +302,7 @@ async function interactiveMode() {
326
302
  const profileName = await clack.text({
327
303
  message: "Enter new profile name:",
328
304
  validate: (value) => {
329
- if (!value) return "Profile name is required";
330
- if (!/^[A-Za-z0-9-_]+$/.test(value)) {
331
- return "Profile name can only contain letters, numbers, hyphens, and underscores";
332
- }
305
+ return getProfileValidationMessage(value);
333
306
  }
334
307
  });
335
308
  if (clack.isCancel(profileName)) {
@@ -389,29 +362,23 @@ async function interactiveMode() {
389
362
  const newName = await clack.text({
390
363
  message: "Enter new profile name:",
391
364
  validate: (value) => {
392
- if (!value) return "Profile name is required";
393
- if (!/^[A-Za-z0-9-_]+$/.test(value)) {
394
- return "Profile name can only contain letters, numbers, hyphens, and underscores";
395
- }
365
+ return getProfileValidationMessage(value);
396
366
  }
397
367
  });
398
368
  if (clack.isCancel(newName)) {
399
369
  clack.cancel("Operation cancelled");
400
370
  process.exit(0);
401
371
  }
372
+ if (oldName === newName) {
373
+ clack.outro("Profile name unchanged");
374
+ process.exit(0);
375
+ }
402
376
  await renameProfile(oldName, newName);
403
377
  clack.outro(`Renamed profile "${oldName}" to "${newName}"`);
404
378
  break;
405
379
  }
406
380
  case "list": {
407
- const activeStatus = await getActiveProfileStatus();
408
- console.log(formatActiveProfileLine(activeStatus));
409
- console.log("\nProfiles:");
410
- for (const profile of profiles) {
411
- const marker = profile.isActive ? " (active)" : "";
412
- console.log(` ${profile.name}${marker}`);
413
- }
414
- console.log();
381
+ await showProfileList(profiles);
415
382
  clack.outro("Profile list complete");
416
383
  break;
417
384
  }
@@ -422,24 +389,133 @@ async function interactiveMode() {
422
389
  process.exit(1);
423
390
  }
424
391
  }
425
- var rawArgs = process.argv.slice(2);
426
- var knownCommands = /* @__PURE__ */ new Set(["switch", "create", "delete", "rename", "list", "current"]);
427
- var firstArg = rawArgs[0];
428
- var isFlag = firstArg?.startsWith("-");
429
- if (rawArgs.length === 1 && firstArg && !knownCommands.has(firstArg) && !isFlag) {
430
- (async () => {
392
+ async function runCli() {
393
+ const rawArgs = process.argv.slice(2);
394
+ if (!rawArgs.length) {
395
+ await interactiveMode();
396
+ return;
397
+ }
398
+ if (rawArgs.length === 1) {
399
+ const singleArg = rawArgs[0];
400
+ if (singleArg === "help") {
401
+ program.outputHelp();
402
+ return;
403
+ }
404
+ if (singleArg === "version") {
405
+ console.log(cliVersion);
406
+ return;
407
+ }
408
+ }
409
+ program.parse();
410
+ const opts = program.opts();
411
+ const profileArg = program.args[0];
412
+ const actionFlags = [];
413
+ if (opts.switch) actionFlags.push("switch");
414
+ if (opts.create) actionFlags.push("create");
415
+ if (opts.delete) actionFlags.push("delete");
416
+ if (opts.rename || opts.to) actionFlags.push("rename");
417
+ if (opts.current) actionFlags.push("current");
418
+ if (opts.list) actionFlags.push("list");
419
+ if (actionFlags.length > 1) {
420
+ console.error("Error: Please provide only one action flag at a time.");
421
+ process.exit(1);
422
+ }
423
+ if (profileArg && actionFlags.length > 0) {
424
+ console.error("Error: Do not combine a profile name with action flags.");
425
+ process.exit(1);
426
+ }
427
+ if (actionFlags.length === 0) {
428
+ if (!profileArg) {
429
+ program.outputHelp();
430
+ process.exit(0);
431
+ return;
432
+ }
431
433
  try {
432
- await switchProfile(firstArg);
433
- console.log(`Switched to profile "${firstArg}"`);
434
+ await switchProfile(profileArg);
435
+ console.log(`Switched to profile "${profileArg}"`);
434
436
  process.exit(0);
435
437
  } catch (error) {
436
438
  console.error(`Error: ${error.message}`);
437
439
  process.exit(1);
438
440
  }
439
- })();
440
- } else {
441
- program.parse();
442
- if (!rawArgs.length) {
443
- interactiveMode();
441
+ return;
442
+ }
443
+ const action = actionFlags[0];
444
+ try {
445
+ switch (action) {
446
+ case "switch": {
447
+ if (!opts.switch) {
448
+ throw new Error("Missing profile name for --switch");
449
+ }
450
+ await switchProfile(opts.switch);
451
+ console.log(`Switched to profile "${opts.switch}"`);
452
+ process.exit(0);
453
+ break;
454
+ }
455
+ case "create": {
456
+ if (!opts.create) {
457
+ throw new Error("Missing profile name for --create");
458
+ }
459
+ await createProfile(opts.create);
460
+ console.log(`Created profile "${opts.create}"`);
461
+ process.exit(0);
462
+ break;
463
+ }
464
+ case "delete": {
465
+ if (!opts.delete) {
466
+ throw new Error("Missing profile name for --delete");
467
+ }
468
+ await deleteProfile(opts.delete);
469
+ console.log(`Deleted profile "${opts.delete}"`);
470
+ process.exit(0);
471
+ break;
472
+ }
473
+ case "rename": {
474
+ const renameArgs = opts.rename ?? [];
475
+ const renameTarget = opts.to;
476
+ if (!renameArgs.length && renameTarget) {
477
+ throw new Error("Missing old profile name for --rename");
478
+ }
479
+ if (!renameArgs.length) {
480
+ throw new Error("Missing profile name for --rename");
481
+ }
482
+ if (renameArgs.length > 2) {
483
+ throw new Error("Provide only two names for --rename");
484
+ }
485
+ if (renameArgs.length === 2 && renameTarget) {
486
+ throw new Error('Use either "--rename <old> <new>" or "--rename <old> --to <new>"');
487
+ }
488
+ const oldName = renameArgs[0];
489
+ const newName = renameArgs.length === 2 ? renameArgs[1] : renameTarget;
490
+ if (!newName) {
491
+ throw new Error("Missing new profile name for --rename");
492
+ }
493
+ if (oldName === newName) {
494
+ await renameProfile(oldName, newName);
495
+ console.log("Profile name unchanged");
496
+ process.exit(0);
497
+ break;
498
+ }
499
+ await renameProfile(oldName, newName);
500
+ console.log(`Renamed profile "${oldName}" to "${newName}"`);
501
+ process.exit(0);
502
+ break;
503
+ }
504
+ case "current": {
505
+ const activeStatus = await getActiveProfileStatus();
506
+ console.log(formatActiveProfileLine(activeStatus));
507
+ process.exit(0);
508
+ break;
509
+ }
510
+ case "list": {
511
+ await showProfileList();
512
+ process.exit(0);
513
+ break;
514
+ }
515
+ }
516
+ } catch (error) {
517
+ console.error(`Error: ${error.message}`);
518
+ process.exit(1);
444
519
  }
445
520
  }
521
+ void runCli();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aravhawk/cc-switch",
3
- "version": "1.1.1",
3
+ "version": "2.0.1",
4
4
  "description": "Profile manager for Claude Code settings",
5
5
  "type": "module",
6
6
  "bin": {