@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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +114 -3
  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.2.2").description("Manage multiple Claude Code profiles");
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remeic/ccm",
3
- "version": "0.2.2",
3
+ "version": "0.3.0",
4
4
  "description": "nvm-like manager for Claude Code profiles",
5
5
  "license": "MIT",
6
6
  "author": "Giulio Fagioli",