@aravhawk/cc-switch 1.1.1 → 2.0.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 +31 -12
- package/dist/index.js +165 -102
- 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
|
|
42
|
+
cc-switch <profile-name>
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
Or explicit:
|
|
44
46
|
```bash
|
|
45
|
-
cc-switch switch
|
|
47
|
+
cc-switch --switch <profile-name>
|
|
46
48
|
```
|
|
47
49
|
|
|
48
|
-
|
|
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
|
-
|
|
65
|
+
const trimmedName = name.trim();
|
|
66
|
+
if (!trimmedName) {
|
|
64
67
|
return { valid: false, error: "Profile name cannot be empty" };
|
|
65
68
|
}
|
|
66
|
-
if (name
|
|
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(
|
|
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 };
|
|
@@ -199,89 +208,53 @@ async function renameProfile(oldName, newName) {
|
|
|
199
208
|
}
|
|
200
209
|
|
|
201
210
|
// src/index.ts
|
|
211
|
+
var require2 = createRequire(import.meta.url);
|
|
212
|
+
function getCliVersion() {
|
|
213
|
+
try {
|
|
214
|
+
const pkg = require2("../package.json");
|
|
215
|
+
if (pkg && typeof pkg.version === "string") {
|
|
216
|
+
return pkg.version;
|
|
217
|
+
}
|
|
218
|
+
} catch {
|
|
219
|
+
return "unknown";
|
|
220
|
+
}
|
|
221
|
+
return "unknown";
|
|
222
|
+
}
|
|
223
|
+
var cliVersion = getCliVersion();
|
|
202
224
|
var program = new Command();
|
|
203
225
|
function formatActiveProfileLine(status) {
|
|
204
226
|
const missingSuffix = status.exists ? "" : " (missing)";
|
|
205
227
|
return `Current profile: "${status.name}"${missingSuffix}`;
|
|
206
228
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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);
|
|
229
|
+
function getProfileValidationMessage(value) {
|
|
230
|
+
const validation = validateProfileName(value ?? "");
|
|
231
|
+
if (!validation.valid) {
|
|
232
|
+
return validation.error ?? "Profile name is invalid";
|
|
246
233
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
console.
|
|
255
|
-
|
|
234
|
+
return void 0;
|
|
235
|
+
}
|
|
236
|
+
async function showProfileList(profiles) {
|
|
237
|
+
const activeStatus = await getActiveProfileStatus();
|
|
238
|
+
const resolvedProfiles = profiles ?? await listProfiles();
|
|
239
|
+
console.log(formatActiveProfileLine(activeStatus));
|
|
240
|
+
if (resolvedProfiles.length === 0) {
|
|
241
|
+
console.log("No profiles found");
|
|
242
|
+
return;
|
|
256
243
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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);
|
|
244
|
+
console.log("\nProfiles:");
|
|
245
|
+
for (const profile of resolvedProfiles) {
|
|
246
|
+
const marker = profile.isActive ? " (active)" : "";
|
|
247
|
+
console.log(` ${profile.name}${marker}`);
|
|
277
248
|
}
|
|
278
|
-
|
|
249
|
+
console.log();
|
|
250
|
+
}
|
|
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");
|
|
279
252
|
async function interactiveMode() {
|
|
280
253
|
clack.intro("cc-switch - Claude Code Profile Manager");
|
|
281
254
|
try {
|
|
282
255
|
const profiles = await listProfiles();
|
|
283
256
|
if (profiles.length === 0) {
|
|
284
|
-
clack.outro("No profiles found. Create one with: cc-switch create <name>");
|
|
257
|
+
clack.outro("No profiles found. Create one with: cc-switch --create <name>");
|
|
285
258
|
process.exit(0);
|
|
286
259
|
}
|
|
287
260
|
const action = await clack.select({
|
|
@@ -326,10 +299,7 @@ async function interactiveMode() {
|
|
|
326
299
|
const profileName = await clack.text({
|
|
327
300
|
message: "Enter new profile name:",
|
|
328
301
|
validate: (value) => {
|
|
329
|
-
|
|
330
|
-
if (!/^[A-Za-z0-9-_]+$/.test(value)) {
|
|
331
|
-
return "Profile name can only contain letters, numbers, hyphens, and underscores";
|
|
332
|
-
}
|
|
302
|
+
return getProfileValidationMessage(value);
|
|
333
303
|
}
|
|
334
304
|
});
|
|
335
305
|
if (clack.isCancel(profileName)) {
|
|
@@ -389,10 +359,7 @@ async function interactiveMode() {
|
|
|
389
359
|
const newName = await clack.text({
|
|
390
360
|
message: "Enter new profile name:",
|
|
391
361
|
validate: (value) => {
|
|
392
|
-
|
|
393
|
-
if (!/^[A-Za-z0-9-_]+$/.test(value)) {
|
|
394
|
-
return "Profile name can only contain letters, numbers, hyphens, and underscores";
|
|
395
|
-
}
|
|
362
|
+
return getProfileValidationMessage(value);
|
|
396
363
|
}
|
|
397
364
|
});
|
|
398
365
|
if (clack.isCancel(newName)) {
|
|
@@ -404,14 +371,7 @@ async function interactiveMode() {
|
|
|
404
371
|
break;
|
|
405
372
|
}
|
|
406
373
|
case "list": {
|
|
407
|
-
|
|
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();
|
|
374
|
+
await showProfileList(profiles);
|
|
415
375
|
clack.outro("Profile list complete");
|
|
416
376
|
break;
|
|
417
377
|
}
|
|
@@ -422,24 +382,127 @@ async function interactiveMode() {
|
|
|
422
382
|
process.exit(1);
|
|
423
383
|
}
|
|
424
384
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
385
|
+
async function runCli() {
|
|
386
|
+
const rawArgs = process.argv.slice(2);
|
|
387
|
+
if (!rawArgs.length) {
|
|
388
|
+
await interactiveMode();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (rawArgs.length === 1) {
|
|
392
|
+
const singleArg = rawArgs[0];
|
|
393
|
+
if (singleArg === "help") {
|
|
394
|
+
program.outputHelp();
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (singleArg === "version") {
|
|
398
|
+
console.log(cliVersion);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
program.parse();
|
|
403
|
+
const opts = program.opts();
|
|
404
|
+
const profileArg = program.args[0];
|
|
405
|
+
const actionFlags = [];
|
|
406
|
+
if (opts.switch) actionFlags.push("switch");
|
|
407
|
+
if (opts.create) actionFlags.push("create");
|
|
408
|
+
if (opts.delete) actionFlags.push("delete");
|
|
409
|
+
if (opts.rename || opts.to) actionFlags.push("rename");
|
|
410
|
+
if (opts.current) actionFlags.push("current");
|
|
411
|
+
if (opts.list) actionFlags.push("list");
|
|
412
|
+
if (actionFlags.length > 1) {
|
|
413
|
+
console.error("Error: Please provide only one action flag at a time.");
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
if (profileArg && actionFlags.length > 0) {
|
|
417
|
+
console.error("Error: Do not combine a profile name with action flags.");
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
if (actionFlags.length === 0) {
|
|
421
|
+
if (!profileArg) {
|
|
422
|
+
program.outputHelp();
|
|
423
|
+
process.exit(0);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
431
426
|
try {
|
|
432
|
-
await switchProfile(
|
|
433
|
-
console.log(`Switched to profile "${
|
|
427
|
+
await switchProfile(profileArg);
|
|
428
|
+
console.log(`Switched to profile "${profileArg}"`);
|
|
434
429
|
process.exit(0);
|
|
435
430
|
} catch (error) {
|
|
436
431
|
console.error(`Error: ${error.message}`);
|
|
437
432
|
process.exit(1);
|
|
438
433
|
}
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
const action = actionFlags[0];
|
|
437
|
+
try {
|
|
438
|
+
switch (action) {
|
|
439
|
+
case "switch": {
|
|
440
|
+
if (!opts.switch) {
|
|
441
|
+
throw new Error("Missing profile name for --switch");
|
|
442
|
+
}
|
|
443
|
+
await switchProfile(opts.switch);
|
|
444
|
+
console.log(`Switched to profile "${opts.switch}"`);
|
|
445
|
+
process.exit(0);
|
|
446
|
+
break;
|
|
447
|
+
}
|
|
448
|
+
case "create": {
|
|
449
|
+
if (!opts.create) {
|
|
450
|
+
throw new Error("Missing profile name for --create");
|
|
451
|
+
}
|
|
452
|
+
await createProfile(opts.create);
|
|
453
|
+
console.log(`Created profile "${opts.create}"`);
|
|
454
|
+
process.exit(0);
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case "delete": {
|
|
458
|
+
if (!opts.delete) {
|
|
459
|
+
throw new Error("Missing profile name for --delete");
|
|
460
|
+
}
|
|
461
|
+
await deleteProfile(opts.delete);
|
|
462
|
+
console.log(`Deleted profile "${opts.delete}"`);
|
|
463
|
+
process.exit(0);
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
case "rename": {
|
|
467
|
+
const renameArgs = opts.rename ?? [];
|
|
468
|
+
const renameTarget = opts.to;
|
|
469
|
+
if (!renameArgs.length && renameTarget) {
|
|
470
|
+
throw new Error("Missing old profile name for --rename");
|
|
471
|
+
}
|
|
472
|
+
if (!renameArgs.length) {
|
|
473
|
+
throw new Error("Missing profile name for --rename");
|
|
474
|
+
}
|
|
475
|
+
if (renameArgs.length > 2) {
|
|
476
|
+
throw new Error("Provide only two names for --rename");
|
|
477
|
+
}
|
|
478
|
+
if (renameArgs.length === 2 && renameTarget) {
|
|
479
|
+
throw new Error('Use either "--rename <old> <new>" or "--rename <old> --to <new>"');
|
|
480
|
+
}
|
|
481
|
+
const oldName = renameArgs[0];
|
|
482
|
+
const newName = renameArgs.length === 2 ? renameArgs[1] : renameTarget;
|
|
483
|
+
if (!newName) {
|
|
484
|
+
throw new Error("Missing new profile name for --rename");
|
|
485
|
+
}
|
|
486
|
+
await renameProfile(oldName, newName);
|
|
487
|
+
console.log(`Renamed profile "${oldName}" to "${newName}"`);
|
|
488
|
+
process.exit(0);
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case "current": {
|
|
492
|
+
const activeStatus = await getActiveProfileStatus();
|
|
493
|
+
console.log(formatActiveProfileLine(activeStatus));
|
|
494
|
+
process.exit(0);
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
case "list": {
|
|
498
|
+
await showProfileList();
|
|
499
|
+
process.exit(0);
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error(`Error: ${error.message}`);
|
|
505
|
+
process.exit(1);
|
|
444
506
|
}
|
|
445
507
|
}
|
|
508
|
+
void runCli();
|