@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.
- package/README.md +31 -12
- package/dist/index.js +178 -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 };
|
|
@@ -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
|
-
|
|
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);
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
console.
|
|
255
|
-
|
|
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
|
-
|
|
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);
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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(
|
|
433
|
-
console.log(`Switched to profile "${
|
|
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
|
-
}
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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();
|