@remeic/ccm 0.2.2 → 0.3.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 +2 -0
- package/dist/index.js +114 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -109,6 +109,7 @@ $ ccm use work
|
|
|
109
109
|
| `ccm use <name> [-- args]` | Launch Claude Code. Args after `--` are passed to Claude |
|
|
110
110
|
| `ccm login <name> [--console] [-b browser] [--url-only]` | Authenticate a profile |
|
|
111
111
|
| `ccm status [name]` | Show auth status and storage state for one or all profiles |
|
|
112
|
+
| `ccm rename <old-name> <new-name>` | Rename a profile (config, directory, and browser wrapper) |
|
|
112
113
|
| `ccm remove <name> [-f]` | Remove a profile. `-f` skips confirmation |
|
|
113
114
|
| `ccm run <name> -p <prompt>` | Run a prompt non-interactively |
|
|
114
115
|
|
|
@@ -205,6 +206,7 @@ src/
|
|
|
205
206
|
├── login.ts # Authenticate via Claude TUI or --console
|
|
206
207
|
├── use.ts # Launch Claude with profile config dir
|
|
207
208
|
├── status.ts # Show auth status and drift state
|
|
209
|
+
├── rename.ts # Rename profile with atomic rollback
|
|
208
210
|
├── remove.ts # Remove profile with staged rollback
|
|
209
211
|
└── run.ts # Run prompt with specific profile
|
|
210
212
|
```
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,14 @@ function getStagedPath(path) {
|
|
|
48
48
|
function getBrowserWrapperPath(profileName, browsersDir = BROWSERS_DIR) {
|
|
49
49
|
return join(browsersDir, `${profileName}.sh`);
|
|
50
50
|
}
|
|
51
|
+
function renameBrowserWrapper(oldName, newName, browsersDir = BROWSERS_DIR) {
|
|
52
|
+
const oldPath = getBrowserWrapperPath(oldName, browsersDir);
|
|
53
|
+
if (!existsSync(oldPath)) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const newPath = getBrowserWrapperPath(newName, browsersDir);
|
|
57
|
+
renameSync(oldPath, newPath);
|
|
58
|
+
}
|
|
51
59
|
function removeBrowserWrapper(profileName, browsersDir = BROWSERS_DIR) {
|
|
52
60
|
const wrapperPath = getBrowserWrapperPath(profileName, browsersDir);
|
|
53
61
|
if (!existsSync(wrapperPath)) {
|
|
@@ -131,6 +139,18 @@ function removeProfile(name, configFile = CONFIG_FILE) {
|
|
|
131
139
|
delete config.profiles[name];
|
|
132
140
|
saveConfig(config, configFile);
|
|
133
141
|
}
|
|
142
|
+
function renameProfile(oldName, newName, configFile = CONFIG_FILE) {
|
|
143
|
+
const config = loadConfig(configFile);
|
|
144
|
+
if (!config.profiles[oldName]) {
|
|
145
|
+
throw new Error(`Profile "${oldName}" not found`);
|
|
146
|
+
}
|
|
147
|
+
if (config.profiles[newName]) {
|
|
148
|
+
throw new Error(`Profile "${newName}" already exists`);
|
|
149
|
+
}
|
|
150
|
+
config.profiles[newName] = { ...config.profiles[oldName], name: newName };
|
|
151
|
+
delete config.profiles[oldName];
|
|
152
|
+
saveConfig(config, configFile);
|
|
153
|
+
}
|
|
134
154
|
function getProfile(name, configFile = CONFIG_FILE) {
|
|
135
155
|
return loadConfig(configFile).profiles[name];
|
|
136
156
|
}
|
|
@@ -155,6 +175,17 @@ function createProfileDir(name, profilesDir = PROFILES_DIR) {
|
|
|
155
175
|
mkdirSync(dir, { recursive: true });
|
|
156
176
|
return dir;
|
|
157
177
|
}
|
|
178
|
+
function renameProfileDir(oldName, newName, profilesDir = PROFILES_DIR) {
|
|
179
|
+
const oldDir = join(profilesDir, oldName);
|
|
180
|
+
const newDir = join(profilesDir, newName);
|
|
181
|
+
if (!existsSync(oldDir)) {
|
|
182
|
+
throw new Error(`Profile directory "${oldName}" does not exist`);
|
|
183
|
+
}
|
|
184
|
+
if (existsSync(newDir)) {
|
|
185
|
+
throw new Error(`Profile directory "${newName}" already exists`);
|
|
186
|
+
}
|
|
187
|
+
renameSync(oldDir, newDir);
|
|
188
|
+
}
|
|
158
189
|
function removeProfileDir(name, profilesDir = PROFILES_DIR) {
|
|
159
190
|
const dir = join(profilesDir, name);
|
|
160
191
|
if (!existsSync(dir)) {
|
|
@@ -288,8 +319,6 @@ function getAuthStatus(profileDir) {
|
|
|
288
319
|
});
|
|
289
320
|
});
|
|
290
321
|
}
|
|
291
|
-
|
|
292
|
-
// src/lib/profile-store.ts
|
|
293
322
|
function getProfileState(hasConfig, hasDirectory) {
|
|
294
323
|
if (hasConfig && hasDirectory) return "ready";
|
|
295
324
|
if (hasDirectory) return "orphaned";
|
|
@@ -383,6 +412,74 @@ function removeStoredProfile(name, configFile = CONFIG_FILE, profilesDir = PROFI
|
|
|
383
412
|
throw error;
|
|
384
413
|
}
|
|
385
414
|
}
|
|
415
|
+
function renameStoredProfile(oldName, newName, configFile = CONFIG_FILE, profilesDir = PROFILES_DIR, browsersDir = BROWSERS_DIR) {
|
|
416
|
+
validateProfileName(newName);
|
|
417
|
+
if (oldName === newName) {
|
|
418
|
+
throw new Error("Old and new profile names are the same");
|
|
419
|
+
}
|
|
420
|
+
const profile = getStoredProfile(oldName, configFile, profilesDir);
|
|
421
|
+
if (!profile) {
|
|
422
|
+
throw new Error(`Profile "${oldName}" does not exist`);
|
|
423
|
+
}
|
|
424
|
+
if (getProfile(newName, configFile)) {
|
|
425
|
+
throw new Error(`Profile "${newName}" already exists`);
|
|
426
|
+
}
|
|
427
|
+
if (profileExists(newName, profilesDir)) {
|
|
428
|
+
throw new Error(`Profile directory "${newName}" already exists`);
|
|
429
|
+
}
|
|
430
|
+
if (profile.hasDirectory) {
|
|
431
|
+
renameProfileDir(oldName, newName, profilesDir);
|
|
432
|
+
}
|
|
433
|
+
const hadWrapper = existsSync(getBrowserWrapperPath(oldName, browsersDir));
|
|
434
|
+
if (hadWrapper) {
|
|
435
|
+
try {
|
|
436
|
+
renameBrowserWrapper(oldName, newName, browsersDir);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
try {
|
|
439
|
+
rollbackRename(oldName, newName, profile.hasDirectory, false, profilesDir, browsersDir);
|
|
440
|
+
} catch (rollbackError) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`Failed to rename profile "${oldName}" to "${newName}": ${formatError(error)}. Rollback failed: ${formatError(rollbackError)}`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
throw error;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
try {
|
|
449
|
+
renameProfile(oldName, newName, configFile);
|
|
450
|
+
} catch (error) {
|
|
451
|
+
try {
|
|
452
|
+
rollbackRename(oldName, newName, profile.hasDirectory, hadWrapper, profilesDir, browsersDir);
|
|
453
|
+
} catch (rollbackError) {
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Failed to rename profile "${oldName}" to "${newName}": ${formatError(error)}. Rollback failed: ${formatError(rollbackError)}`
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
throw error;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function rollbackRename(oldName, newName, hadDirectory, hadWrapper, profilesDir, browsersDir) {
|
|
462
|
+
const rollbackErrors = [];
|
|
463
|
+
if (hadWrapper) {
|
|
464
|
+
try {
|
|
465
|
+
renameBrowserWrapper(newName, oldName, browsersDir);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
rollbackErrors.push(`browser wrapper: ${formatError(error)}`);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
if (hadDirectory) {
|
|
471
|
+
try {
|
|
472
|
+
renameProfileDir(newName, oldName, profilesDir);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
rollbackErrors.push(`profile dir: ${formatError(error)}`);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (rollbackErrors.length > 0) {
|
|
478
|
+
throw new Error(
|
|
479
|
+
`Rollback failed for rename "${oldName}" to "${newName}": ${rollbackErrors.join("; ")}`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
386
483
|
function rollbackStorage(name, stagedDir, stagedWrapper, profilesDir, browsersDir) {
|
|
387
484
|
const rollbackErrors = [];
|
|
388
485
|
if (stagedDir) {
|
|
@@ -507,6 +604,19 @@ function registerRemove(program2) {
|
|
|
507
604
|
});
|
|
508
605
|
}
|
|
509
606
|
|
|
607
|
+
// src/commands/rename.ts
|
|
608
|
+
function registerRename(program2) {
|
|
609
|
+
program2.command("rename <old-name> <new-name>").description("Rename a profile").action((oldName, newName) => {
|
|
610
|
+
try {
|
|
611
|
+
renameStoredProfile(oldName, newName);
|
|
612
|
+
console.log(`\x1B[32m\u2713\x1B[0m Profile "${oldName}" renamed to "${newName}"`);
|
|
613
|
+
} catch (e) {
|
|
614
|
+
console.error(`\x1B[31m\u2717\x1B[0m ${e instanceof Error ? e.message : String(e)}`);
|
|
615
|
+
process.exit(1);
|
|
616
|
+
}
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
|
|
510
620
|
// src/commands/run.ts
|
|
511
621
|
function registerRun(program2) {
|
|
512
622
|
program2.command("run <name>").description("Run Claude Code with a prompt using a profile").requiredOption("-p, --prompt <prompt>", "Prompt to send").action((name, opts) => {
|
|
@@ -591,10 +701,11 @@ function registerUse(program2) {
|
|
|
591
701
|
}
|
|
592
702
|
|
|
593
703
|
// src/index.ts
|
|
594
|
-
var program = new Command().name("ccm").version("0.
|
|
704
|
+
var program = new Command().name("ccm").version("0.3.0").description("Manage multiple Claude Code profiles");
|
|
595
705
|
registerCreate(program);
|
|
596
706
|
registerList(program);
|
|
597
707
|
registerRemove(program);
|
|
708
|
+
registerRename(program);
|
|
598
709
|
registerLogin(program);
|
|
599
710
|
registerStatus(program);
|
|
600
711
|
registerUse(program);
|