claude-switch-profile 1.4.8 → 1.4.12

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/CHANGELOG.md CHANGED
@@ -9,6 +9,76 @@ All notable changes to `claude-switch-profile` are documented here.
9
9
 
10
10
  ---
11
11
 
12
+ ## [1.4.11] - 2026-04-01
13
+
14
+ ### Changed
15
+ - Version bump only — no functional code changes from `1.4.10`.
16
+
17
+ ---
18
+
19
+ ## [1.4.10] - 2026-04-01
20
+
21
+ ### Fixed
22
+ - **Settings Path Mutation Bug**: Fixed a severe issue where hook paths in `settings.json` could exponentially multiply (like `.runtime/default-profiles/.runtime/...`) when saving a profile post-launch. Path mapping logic was strengthened to use exact-boundary Regular Expressions, preventing `.claude` patterns from incorrectly matching adjacent nested paths like `.claude-profiles`.
23
+
24
+ ---
25
+
26
+ ## [1.4.9] - 2026-04-01
27
+
28
+ ### Fixed
29
+ - **Hooks execution in isolated mode**: Fixed an issue where Claude Code hook scripts failed to execute in isolated profiles. The runtime instance manager now correctly resolves shell variables (`$HOME`, `${HOME}`, `~`) within `settings.json` hook commands to accurately point to the isolated runtime directory.
30
+
31
+ ---
32
+
33
+ ## [1.4.8] - 2026-04-01
34
+
35
+ ### Changed
36
+ - Re-ran release flow to publish the latest patch after `1.4.7`.
37
+ - No functional code changes compared with `1.4.7`; this release aligns npm package/version metadata.
38
+
39
+ ---
40
+
41
+ ## [1.4.7] - 2026-04-01
42
+
43
+ ### Fixed
44
+ - Release pipeline now restores annotated git tag publishing.
45
+ - Local agent artifacts are ignored by git to keep release working tree clean.
46
+
47
+ ---
48
+
49
+ ## [1.4.6] - 2026-03-31
50
+
51
+ ### Fixed
52
+ - Excluded cocoindex cache from npm package to reduce published size.
53
+
54
+ ---
55
+
56
+ ## [1.4.5] - 2026-03-31
57
+
58
+ ### Fixed
59
+ - Release pipeline: stopped creating duplicate git tags during release.
60
+
61
+ ---
62
+
63
+ ## [1.4.4] - 2026-03-31
64
+
65
+ ### Fixed
66
+ - Release pipeline: handled noisy test output that could interrupt release flow.
67
+
68
+ ---
69
+
70
+ ## [1.4.3] - 2026-03-31
71
+
72
+ ### Added
73
+ - ✨ **`csp exec` command** — Run an arbitrary command inside an isolated profile runtime environment. Similar to `csp launch` but runs any command (not just Claude) under profile-scoped env. Supports `--` separator for command arguments.
74
+ - ✨ **Dynamic shell support for `csp exec`** — On Unix-like systems, commands run through the user's interactive shell so shell aliases and functions resolve naturally while keeping runtime isolation intact.
75
+
76
+ ### Changed
77
+ - Refactored `launch.js` to share context setup with `exec` via `isolated-launch-context.js`.
78
+ - On Windows, `exec` falls back to direct spawn with `.cmd` / `.bat` wrapper detection.
79
+
80
+ ---
81
+
12
82
  ## [1.4.2] - 2026-03-31
13
83
 
14
84
  ### Changed
package/README.md CHANGED
@@ -20,618 +20,87 @@ Profiles are stored in `~/.claude-profiles/` and are never managed by Claude Cod
20
20
 
21
21
  ## Installation
22
22
 
23
- ### Global Installation
24
-
25
23
  ```bash
26
24
  npm install -g claude-switch-profile
27
25
  ```
28
26
 
29
- Then use the `csp` command globally:
30
-
31
- ```bash
32
- csp list
33
- csp current
34
- csp create my-profile
35
- ```
36
-
37
- ### Local Development
38
-
39
- ```bash
40
- # In the project root
41
- npm install
42
- npm link
43
-
44
- # Now available as `csp` command
45
- csp --help
46
- ```
47
-
48
- ### Requirements
49
-
50
- - Node.js >= 18.0.0
51
- - Unix/Linux/macOS
52
- - Windows 10+
27
+ **Requirements:** Node.js >= 18.0.0 | macOS/Linux/Windows 10+
53
28
 
54
29
  ## Quick Start
55
30
 
56
- ### 1. Initialize
57
-
58
- Capture your current Claude Code setup as the physical `default` profile snapshot:
59
-
60
31
  ```bash
32
+ # 1. Initialize — capture current setup as "default" profile
61
33
  csp init
62
- ```
63
-
64
- This creates `~/.claude-profiles/default/` from the current managed contents of `~/.claude`. Protected and session/runtime files remain excluded.
65
-
66
- ### 2. Create Additional Profiles
67
-
68
- Create a new profile from your current state:
69
-
70
- ```bash
71
- csp create work --description "Work setup with company rules"
72
- ```
73
-
74
- Or clone from an existing profile:
75
-
76
- ```bash
77
- csp create experimental --from default --description "Testing new tools"
78
- ```
79
-
80
- ### 3. Switch Between Profiles
81
-
82
- Switch to a profile (automatically saves current state):
83
-
84
- ```bash
85
- csp use work
86
- # Restart your Claude Code session for changes to take effect
87
- ```
88
-
89
- Use `--dry-run` to preview changes:
90
-
91
- ```bash
92
- csp use work --dry-run
93
- ```
94
-
95
- ### 4. Save Current State
96
-
97
- Save the active profile with current configuration:
98
-
99
- ```bash
100
- csp save
101
- ```
102
-
103
- ### 5. List All Profiles
104
-
105
- View all available profiles with their status:
106
-
107
- ```bash
108
- csp list
109
- ```
110
-
111
- Output example:
112
34
 
113
- ```
114
- * default Vanilla Claude defaults (2026-03-11)
115
- work Work setup (2026-03-11)
116
- experimental (2026-03-11)
117
- ```
118
-
119
- The `*` marks the active profile.
120
-
121
- ### 6. Launch via Interactive Selector (Default Command)
122
-
123
- Run `csp` without a subcommand to open the interactive selector and launch a profile immediately:
124
-
125
- ```bash
126
- csp
127
- ```
35
+ # 2. Create additional profiles
36
+ csp create work -d "Work setup with company rules"
37
+ csp create experimental --from default -d "Testing new tools"
128
38
 
129
- ### 7. Uninstall CSP
130
-
131
- Remove CSP and restore your Claude Code to its original state:
132
-
133
- ```bash
134
- csp uninstall
135
- # Uninstall CSP and remove all profiles? This cannot be undone. (y/N)
136
- ```
137
-
138
- ## Commands Reference
139
-
140
- ### select (default)
141
-
142
- Open interactive profile selector (default behavior when you run `csp` without subcommand).
39
+ # 3. Launch isolated sessions (recommended — does NOT touch ~/.claude)
40
+ csp launch work
41
+ csp launch experimental
143
42
 
144
- ```bash
43
+ # 4. Or use the interactive selector
145
44
  csp
146
- csp select
147
- ```
148
-
149
- **Behavior:**
150
- - If TTY and multiple profiles exist: shows arrow-key menu and launches selected profile via `csp launch <name>`
151
- - If only one profile exists: shows informational message and exits
152
- - If non-interactive terminal: asks to use `csp launch <name>` or `csp use <name>` directly
153
-
154
- ---
155
-
156
- ### init
157
-
158
- Initialize the profile system and capture current state as "default" profile.
159
-
160
- ```bash
161
- csp init
162
- ```
163
-
164
- **Options:** None
165
-
166
- **Behavior:**
167
- - Creates `~/.claude-profiles/` directory
168
- - Captures current `~/.claude` configuration
169
- - Creates `default` profile and marks it active
170
- - If already initialized, displays current active profile
171
-
172
- ---
173
-
174
- ### current
175
-
176
- Display the currently active **legacy** profile and isolated launch metadata (if present).
177
-
178
- ```bash
179
- csp current
180
- ```
181
-
182
- **Output:**
183
- ```
184
- ✓ Active legacy profile: default
185
- ℹ Location: /home/user/.claude-profiles/default
186
- ℹ Last isolated launch: 2026-03-26T14:45:00.000Z
187
- ℹ Isolated runtime: /home/user/.claude-profiles/.runtime/default
188
- ```
189
-
190
- ---
191
-
192
- ### list (ls)
193
-
194
- List all profiles with descriptions and creation dates.
195
-
196
- ```bash
197
- csp list
198
- csp ls
199
- ```
200
-
201
- **Output format:**
202
- ```
203
- * profile-name — optional description (YYYY-MM-DD) [legacy|account-session]
204
- other-profile (YYYY-MM-DD) [account-session]
205
- ```
206
-
207
- The `*` marks the currently active profile.
208
-
209
- ---
210
-
211
- ### status
212
-
213
- Show CSP status dashboard (active profile, profile count, last launch, Claude process state).
214
-
215
- ```bash
216
- csp status
217
- ```
218
-
219
- ---
220
-
221
- ### create
222
-
223
- Create a new profile from current state or clone existing profile.
224
-
225
- ```bash
226
- csp create <name> [options]
227
- ```
228
-
229
- **Options:**
230
- - `--from <profile>` — Clone from existing profile instead of current state
231
- - `-s, --source <path>` — Create from a specific kit directory, then inherit missing managed items from current `~/.claude`
232
- - `-d, --description <text>` — Add description to profile
233
-
234
- **Examples:**
235
-
236
- Create from current state:
237
- ```bash
238
- csp create production
239
- ```
240
-
241
- Create with description:
242
- ```bash
243
- csp create staging -d "Staging environment with logging enabled"
244
- ```
245
-
246
- Clone from existing:
247
- ```bash
248
- csp create backup --from production
249
- ```
250
-
251
- Create from kit directory:
252
- ```bash
253
- csp create team-kit --source ~/my-kit/.agents -d "Team baseline"
254
- ```
255
-
256
- **Behavior:**
257
- - Creates profile directory: `~/.claude-profiles/<name>/`
258
- - If `--from` is specified, clones all content from source profile
259
- - If `--source` is specified, copies managed items from that directory, then fills missing managed items from current `~/.claude`
260
- - Otherwise, performs full clone of current `~/.claude` excluding protected/session items
261
- - If this is the first profile, automatically sets it as active
262
- - Saves metadata (created timestamp, description)
263
-
264
- ---
265
-
266
- ### save
267
-
268
- Save the current `~/.claude` state to the active profile.
269
-
270
- ```bash
271
- csp save
272
- ```
273
-
274
- **Behavior:**
275
- - Captures all managed items and copied files/directories from `~/.claude`
276
- - Overwrites the active profile snapshot (`source.json` plus copied content)
277
- - When `default` is active, updates the physical `default` snapshot like any other profile
278
- - Protected and session/runtime files remain excluded
279
- - Requires an active profile
280
-
281
- ---
282
-
283
- ### use
284
-
285
- Switch to a different profile.
286
-
287
- ```bash
288
- csp use <name> [options]
289
- ```
290
-
291
- **Options:**
292
- - `--dry-run` — Show what would change without executing
293
- - `--no-save` — Skip saving current profile before switching
294
-
295
- **Examples:**
296
-
297
- Simple switch:
298
- ```bash
299
- csp use production
300
- ```
301
-
302
- Preview changes first:
303
- ```bash
304
- csp use staging --dry-run
305
- ```
306
-
307
- Switch without saving current state:
308
- ```bash
309
- csp use backup --no-save
310
- ```
311
-
312
- **Behavior:**
313
- 1. Validates target profile exists and profile structure is valid
314
- 2. Refuses to switch while Claude Code is running (legacy/global switching mutates `~/.claude` directly)
315
- 3. If the active profile exists and `--no-save` is not set: saves its current snapshot first
316
- 4. Removes managed items/files from `~/.claude`
317
- 5. Restores the target profile snapshot into `~/.claude` — including `default`
318
- 6. Updates active marker
319
- 7. On older installs missing `profiles/default`, CSP only backfills that snapshot when the active profile is `default` or no active profile is set; otherwise it fails closed with repair guidance
320
- 8. **Important:** Claude Code session must be restarted for changes to apply
321
-
322
- ---
323
-
324
- ### toggle
325
-
326
- Launch the previous profile (does not mutate global active profile in isolated mode).
327
-
328
- ```bash
329
- csp toggle
330
- ```
331
-
332
- **Behavior:**
333
- - Reads previous profile marker from `~/.claude-profiles/.previous`
334
- - Validates previous profile still exists
335
- - Delegates to `csp launch <previous>`
336
-
337
- ---
338
-
339
- ### delete (rm)
340
-
341
- Delete a profile.
342
-
343
- ```bash
344
- csp delete <name> [options]
345
- csp rm <name> [options]
346
- ```
347
-
348
- **Options:**
349
- - `-f, --force` — Skip confirmation prompt
350
-
351
- **Examples:**
352
-
353
- Delete with confirmation:
354
- ```bash
355
- csp delete experimental
356
- # Delete profile "experimental"? This cannot be undone. (y/N)
357
- ```
358
-
359
- Force delete without prompt:
360
- ```bash
361
- csp delete old-setup --force
362
- ```
363
-
364
- **Behavior:**
365
- - Cannot delete `default`
366
- - Prompts for confirmation unless `--force` is used
367
- - Permanently removes profile directory and metadata
368
- - If deleting the currently active non-default profile: clears active marker only (does not mutate `~/.claude`)
369
- - Cannot be undone
370
-
371
- ---
372
-
373
- ### export
374
-
375
- Export a profile as a compressed tar.gz archive.
376
-
377
- ```bash
378
- csp export <name> [options]
379
- ```
380
-
381
- **Options:**
382
- - `-o, --output <path>` — Output file path (defaults to `./{name}.csp.tar.gz`)
383
-
384
- **Examples:**
385
-
386
- Export with default filename:
387
- ```bash
388
- csp export production
389
- # Exports to ./production.csp.tar.gz
390
- ```
391
-
392
- Export to custom location:
393
- ```bash
394
- csp export staging -o ~/backups/claude-staging.tar.gz
395
- ```
396
-
397
- **Behavior:**
398
- - Creates tar.gz archive of the profile snapshot directory
399
- - Includes `source.json` plus copied profile files/directories
400
- - Exporting `default` works like any other profile snapshot
401
- - Protected and session/runtime files remain excluded from the archive
402
- - Useful for backup, sharing, or version control
403
-
404
- ---
405
-
406
- ### import
407
-
408
- Import a profile from tar.gz archive.
409
-
410
- ```bash
411
- csp import <file> [options]
412
- ```
413
-
414
- **Options:**
415
- - `-n, --name <name>` — Profile name (defaults to archive filename without extension)
416
- - `-d, --description <text>` — Profile description
417
-
418
- **Examples:**
419
-
420
- Import with default name:
421
- ```bash
422
- csp import production.csp.tar.gz
423
- # Creates profile named "production"
424
- ```
425
-
426
- Import with custom name and description:
427
- ```bash
428
- csp import backup.tar.gz -n restored -d "Restored from backup"
429
- ```
430
-
431
- **Behavior:**
432
- - Extracts archive to `~/.claude-profiles/<name>/`
433
- - Uses filename as profile name if `--name` not specified
434
- - Validates imported profile structure
435
- - Rejects import if managed/copied items contain symlink targets outside profile directory (safety check)
436
- - Creates metadata entry for profile
437
- - Profile is ready to use immediately
438
-
439
- ---
440
-
441
- ### diff
442
-
443
- Compare two profiles to identify differences.
444
-
445
- ```bash
446
- csp diff <profileA> <profileB>
447
45
  ```
448
46
 
449
- **Special:** Use `current` to compare against active profile.
450
-
451
- **Examples:**
452
-
453
- Compare two profiles:
454
- ```bash
455
- csp diff staging production
456
- ```
457
-
458
- Compare current profile with another:
459
- ```bash
460
- csp diff current backup
461
- ```
462
-
463
- **Output:**
464
- ```
465
- Comparing: staging ↔ production
466
-
467
- Managed item map (source.json): identical
468
-
469
- File differences:
470
- settings.json — different
471
- .env — only in production
472
- rules/CLAUDE.md — different
473
- ```
474
-
475
- **Behavior:**
476
- - Compares `source.json` (managed item map)
477
- - Lists file presence and content differences
478
- - Shows which files differ and in which profile they exist
479
-
480
- ---
481
-
482
- ### deactivate
483
-
484
- Deactivate the currently active non-default profile by switching back to the physical `default` snapshot.
485
-
486
- ```bash
487
- csp deactivate
488
- ```
489
-
490
- **Options:**
491
- - `--no-save` — Skip saving current profile state before deactivation
492
-
493
- **Behavior:**
494
- 1. Exits early if no active profile or active profile is `default`
495
- 2. Delegates to `csp use default`
496
- 3. Optionally saves the current non-default profile state
497
- 4. Restores the physical `default` snapshot into `~/.claude`
498
- 5. Marks `default` as the active legacy profile
499
-
500
- ---
47
+ ## Core Commands
501
48
 
502
- ### launch (la)
49
+ | Command | Description |
50
+ |---|---|
51
+ | `csp` / `csp select` | Interactive profile selector (default) |
52
+ | `csp init` | Initialize and capture current state as `default` profile |
53
+ | `csp create <name>` | Create a new profile (`--from`, `--source`, `-d` options) |
54
+ | `csp launch <name>` | Launch isolated Claude session for a profile |
55
+ | `csp exec <name> -- <cmd>` | Run any command inside isolated profile runtime |
56
+ | `csp use <name>` | Legacy global switch (mutates `~/.claude`) |
57
+ | `csp save` | Save current state to active profile |
58
+ | `csp list` / `csp ls` | List all profiles |
59
+ | `csp status` | Show CSP status dashboard |
60
+ | `csp current` | Show active legacy profile |
61
+ | `csp toggle` | Quick-switch to previous profile |
62
+ | `csp diff <a> <b>` | Compare two profiles |
63
+ | `csp export <name>` | Export profile as `.tar.gz` archive |
64
+ | `csp import <file>` | Import profile from archive |
65
+ | `csp delete <name>` | Delete a profile |
66
+ | `csp deactivate` | Switch back to `default` profile |
67
+ | `csp uninstall` | Remove CSP and restore Claude to pre-CSP state |
503
68
 
504
- Launch an isolated Claude session for a profile. This does **not** change global active profile. All extra arguments are forwarded to `claude`.
69
+ > 📖 **Full command reference with all options and detailed behavior:** [docs/commands-reference.md](docs/commands-reference.md)
505
70
 
506
- ```bash
507
- csp launch <name> [claude-args...]
508
- csp la <name> [claude-args...]
509
- ```
510
-
511
- **Options:**
512
- - `--legacy-global` — Use old behavior (`csp use <name>` then launch)
71
+ ## Two Launch Modes
513
72
 
514
- **Examples:**
73
+ ### Isolated Launch (recommended)
515
74
 
516
- Launch isolated session:
517
75
  ```bash
518
76
  csp launch work
519
- ```
520
-
521
- Launch isolated with Claude flags:
522
- ```bash
523
77
  csp launch work --dangerously-skip-permissions
524
78
  csp la dev --model opus
525
79
  ```
526
80
 
527
- Launch legacy/global mode:
528
- ```bash
529
- csp launch work --legacy-global
530
- ```
531
-
532
- **Behavior (default isolated mode):**
533
- 1. Validates target profile exists
534
- 2. Ensures the profile snapshot exists; for legacy installs missing `default/`, guarded backfill only runs when the active profile is `default` or no active profile is set
535
- 3. Prepares per-profile runtime under `~/.claude-profiles/.runtime/<name>`
536
- 4. Resolves effective allowlisted `ANTHROPIC_*` launch env (`ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_MODEL`) with precedence: `settings.json env` > profile `.env` allowlist > parent process env
537
- 5. Strips inherited `CLAUDECODE`, inherited `CLAUDE_CONFIG_DIR`, inherited `ANTHROPIC_*`, and Claude session env vars before applying resolved allowlisted values
538
- 6. Spawns `claude` with `CLAUDE_CONFIG_DIR=<runtimeDir>`
539
- 7. Inherits stdin/stdout/stderr for interactive use
540
- 8. Forwards Claude's exit code
541
- 9. Keeps `.active` unchanged and never mutates global `~/.claude`
542
- 10. Launching `default` preserves its `legacy` mode metadata even though it uses an isolated runtime snapshot
543
-
544
- `ANTHROPIC_*` keys currently in isolated launch scope:
545
- - `ANTHROPIC_AUTH_TOKEN`
546
- - `ANTHROPIC_BASE_URL`
547
- - `ANTHROPIC_MODEL`
548
-
549
- Set `CSP_DEBUG_LAUNCH_ENV=1` to print extended launch diagnostics. Do not use in shared logs because it can include resolved `ANTHROPIC_*` values.
550
-
551
- ---
81
+ - Does **not** mutate `~/.claude` or change `.active`
82
+ - Creates per-profile runtime at `~/.claude-profiles/.runtime/<name>`
83
+ - Spawns Claude with `CLAUDE_CONFIG_DIR` pointing to runtime root
84
+ - Supports `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_MODEL` env resolution
552
85
 
553
- ### exec
86
+ ### Legacy Global Switch
554
87
 
555
- Run an arbitrary command inside isolated profile runtime env. This does **not** change global active profile.
556
-
557
- ```bash
558
- csp exec <name> -- <command> [args...]
559
- ```
560
-
561
- **Examples:**
562
-
563
- Run any CLI tool with profile runtime env:
564
88
  ```bash
565
- csp exec work -- env
566
- csp exec work -- node scripts/check-env.js
567
- ```
568
-
569
- Run a shell function/alias defined by your interactive shell:
570
- ```bash
571
- csp exec hd -- claude-hd2
572
- ```
573
-
574
- **Behavior:**
575
- 1. Validates target profile exists
576
- 2. Ensures the profile snapshot exists; for legacy installs missing `default/`, guarded backfill only runs when the active profile is `default` or no active profile is set
577
- 3. Prepares per-profile runtime under `~/.claude-profiles/.runtime/<name>`
578
- 4. Resolves effective allowlisted `ANTHROPIC_*` launch env (`ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_BASE_URL`, `ANTHROPIC_MODEL`) with precedence: `settings.json env` > profile `.env` allowlist > parent process env
579
- 5. Strips inherited `CLAUDECODE`, inherited `CLAUDE_CONFIG_DIR`, inherited `ANTHROPIC_*`, and Claude session env vars before applying resolved allowlisted values
580
- 6. On Unix-like systems, runs the command through your interactive shell so shell functions/aliases can resolve like a normal terminal command; on Windows, keeps direct spawn behavior with `.cmd` / `.bat` wrapper detection
581
- 7. Reasserts `CLAUDE_CONFIG_DIR` and allowlisted `ANTHROPIC_*` after shell init so profile isolation wins over shell startup overrides
582
- 8. Inherits stdin/stdout/stderr and forwards child exit code
583
- 9. Keeps `.active` unchanged and never mutates global `~/.claude`
584
-
585
- ---
586
-
587
- ### uninstall
588
-
589
- Remove all profiles and restore Claude Code to its pre-CSP state.
590
-
591
- ```bash
592
- csp uninstall
593
- ```
594
-
595
- **Options:**
596
- - `-f, --force` — Skip confirmation prompt
597
- - `--profile <name>` — Restore a specific profile instead of the active one
598
-
599
- **Examples:**
600
-
601
- Uninstall with confirmation:
602
- ```bash
603
- csp uninstall
604
- # Uninstall CSP and remove all profiles? This cannot be undone. (y/N)
605
- ```
606
-
607
- Restore a specific profile during uninstall:
608
- ```bash
609
- csp uninstall --profile production
610
- ```
611
-
612
- Force uninstall without prompt:
613
- ```bash
614
- csp uninstall --force
89
+ csp use work
90
+ # Restart Claude Code for changes to take effect
615
91
  ```
616
92
 
617
- **Behavior:**
618
- 1. Creates a final backup at `~/.claude-profiles/.backup/`
619
- 2. Restores the active profile by default, or the `--profile` choice, to `~/.claude`
620
- 3. If `default` is restored, it uses the physical `default` snapshot like any other profile
621
- 4. Removes `~/.claude-profiles/` entirely
622
- 5. Prints reminder to run `npm uninstall -g claude-switch-profile`
623
-
624
- ---
93
+ - Mutates `~/.claude` directly
94
+ - Updates `.active` marker
95
+ - Requires Claude restart
625
96
 
626
97
  ## How Profiles Work
627
98
 
628
99
  ### Profile Storage
629
100
 
630
- Profiles are stored in `~/.claude-profiles/`:
631
-
632
101
  ```
633
102
  ~/.claude-profiles/
634
- ├── .active # Current active profile name (legacy use mode)
103
+ ├── .active # Current active profile name (legacy)
635
104
  ├── profiles.json # Metadata for all profiles (v2 schema)
636
105
  ├── .runtime/ # Isolated launch roots
637
106
  │ ├── work/
@@ -640,329 +109,65 @@ Profiles are stored in `~/.claude-profiles/`:
640
109
  │ ├── source.json # Managed item map
641
110
  │ ├── settings.json # Copied from ~/.claude
642
111
  │ ├── .env # Copied from ~/.claude
643
- ├── .ck.json # Copied from ~/.claude
644
- │ ├── .ckignore # Copied from ~/.claude
645
- │ ├── commands/ # Copied from ~/.claude
646
- │ └── plugins/ # Copied from ~/.claude
112
+ └── ...
647
113
  └── production/
648
114
  ├── source.json
649
- ├── settings.json
650
115
  └── ...
651
116
  ```
652
117
 
653
118
  ### What Gets Managed
654
119
 
655
- **Managed Static Items** (via `source.json`):
656
- - `CLAUDE.md` — Project-specific Claude configuration
657
- - `rules/` Development rules and guidelines
658
- - `agents/` Agent scripts and configurations
659
- - `skills/` Custom Luna skills
660
- - `hooks/` Pre/post action hooks
661
- - `statusline.cjs`, `statusline.sh`, `statusline.ps1` — Custom statusline scripts
662
- - `.luna.json` — Luna configuration
663
-
664
- **Copied Files**:
665
- - `settings.json` — Editor settings
666
- - `.env`, `.env.example` — Environment variables
667
- - `.ck.json`, `.ckignore` — Custom settings and ignore patterns
668
- - `.mcp.json`, `.mcp.json.example` — MCP configuration
669
- - `.gitignore` — Local ignore settings
670
-
671
- **Copied Directories**:
672
- - `commands/`, `plugins/` — Custom commands/plugins
673
- - `workflows/`, `scripts/` — Automation and workflow assets
674
- - `output-styles/`, `schemas/` — Output and schema assets
675
-
676
- **Never Touched** (runtime/session data):
677
- - `.credentials.json`
678
- - `projects/`, `sessions/`, `session-env/`, `ide/`
679
- - `cache/`, `paste-cache/`, `downloads/`, `telemetry/`, `debug/`, `statsig/`
680
- - `history.jsonl`, `metadata.json`, `stats-cache.json`, `active-plan`
681
- - `backups/`, `command-archive/`, `commands-archived/`
682
- - `plans/`, `todos/`, `tasks/`, `teams/`, `agent-memory/`, `file-history/`, `shell-snapshots/`
683
- - All other protected or session-specific data
684
-
685
- ### Legacy vs Isolated Launch Modes
686
-
687
- CSP now supports two paths:
688
-
689
- - **Legacy global mode (`csp use`)**
690
- - Mutates `~/.claude`
691
- - Updates `.active`
692
- - Preserves old behavior for existing scripts
693
-
694
- - **Isolated launch mode (`csp launch`)**
695
- - Does not mutate `~/.claude`
696
- - Does not change `.active`
697
- - Creates/updates runtime root per profile at `~/.claude-profiles/.runtime/<name>`
698
- - Launches Claude with `CLAUDE_CONFIG_DIR` pointing to that runtime root
699
-
700
- ### Runtime Sync Policy (isolated launch)
701
-
702
- For each launch, CSP syncs static profile config into runtime root:
703
-
704
- - Managed items (`CLAUDE.md`, `rules`, `agents`, `skills`, `hooks`, statusline files, `.luna.json`)
705
- - Copied files (`settings.json`, `.env`, `.ck.json`, `.ckignore`, etc.)
706
- - Copied directories (`commands`, `plugins`, `workflows`, `scripts`, ...)
707
-
708
- Runtime/account continuity stays isolated per runtime root and is not globally swapped.
120
+ | Category | Items |
121
+ |---|---|
122
+ | **Managed Static** | `CLAUDE.md`, `rules/`, `agents/`, `skills/`, `hooks/`, `statusline.*`, `.luna.json` |
123
+ | **Copied Files** | `settings.json`, `.env`, `.ck.json`, `.ckignore`, `.mcp.json`, `.gitignore`, etc. |
124
+ | **Copied Dirs** | `commands/`, `plugins/`, `workflows/`, `scripts/`, `output-styles/`, `schemas/` |
125
+ | **Never Touched** | `.credentials.json`, `projects/`, `sessions/`, `cache/`, `telemetry/`, etc. |
709
126
 
710
127
  ## Safety Features
711
128
 
712
- ### Lock File
713
-
714
- Prevents concurrent profile switches:
715
- - Created at `~/.claude-profiles/.lock` during operations
716
- - Contains process ID (PID) of the running operation
717
- - Auto-detects stale locks (process no longer running)
718
- - Throws error if another switch is in progress
719
-
720
- ```
721
- Another csp operation is running (PID: 12345).
722
- Remove ~/.claude-profiles/.lock if stale.
723
- ```
724
-
725
- ### Claude Process Detection
726
-
727
- When switching profiles, CSP detects if Claude Code is running:
728
-
729
- ```
730
- ⚠ Claude Code appears to be running. Restart your Claude session after switching profiles.
731
- ```
732
-
733
- **Important:** Changes only take effect after restarting Claude Code.
734
-
735
- ### Windows Support
736
-
737
- Process detection uses `tasklist` instead of `pgrep` on Windows.
738
-
739
- Export/import commands use the built-in `tar.exe` available on Windows 10+.
740
-
741
- ### Validation
742
-
743
- Before switching, CSP validates:
744
- 1. Target profile exists
745
- 2. Profile structure is valid
746
-
747
- During import, CSP also validates profile contents and rejects unsafe symlinks that point outside the imported profile tree.
748
-
749
- ## Configuration via Environment Variables
750
-
751
- Override default behavior for testing or advanced use:
752
-
753
- ### CSP_HOME
754
-
755
- Override the home directory (default: `process.env.HOME`).
756
-
757
- ```bash
758
- CSP_HOME=/tmp/test csp list
759
- ```
760
-
761
- ### CSP_CLAUDE_DIR
762
-
763
- Override Claude config directory (default: `~/.claude`).
764
-
765
- ```bash
766
- CSP_CLAUDE_DIR=/tmp/test-claude csp init
767
- ```
768
-
769
- ### CSP_PROFILES_DIR
770
-
771
- Override profiles storage directory (default: `~/.claude-profiles`).
772
-
773
- ```bash
774
- CSP_PROFILES_DIR=/tmp/test-profiles csp list
775
- ```
776
-
777
- **Use case:** Testing in isolated environments without affecting your real configuration.
129
+ - **Lock file** — prevents concurrent profile switches (PID-based, auto-detects stale locks)
130
+ - **Claude process detection** — warns when Claude is running during switch
131
+ - **Import validation** — rejects unsafe symlinks pointing outside profile tree
132
+ - **Profile validation** checks structure before applying
133
+ - **Auto-backups** created before destructive operations
778
134
 
779
135
  ## Workflow Examples
780
136
 
781
- ### Scenario 1: Work vs. Personal
137
+ ### Work vs. Personal
782
138
 
783
139
  ```bash
784
- # Initial setup
785
140
  csp init
786
-
787
- # Create work profile
788
- csp create work -d "Work environment with company rules"
789
- # ... modify rules, settings, etc. ...
790
- csp save
791
-
792
- # Create personal profile
793
- csp use default
141
+ csp create work -d "Work environment"
794
142
  csp create personal -d "Personal projects"
795
- csp save
796
-
797
- # Switch between them
798
- csp use work # Switch to work
799
- csp use personal # Switch to personal
800
- ```
801
143
 
802
- ### Scenario 2: Testing New Tools
803
-
804
- ```bash
805
- # Clone current profile
806
- csp create experimental --from default -d "Testing new Luna skills"
807
-
808
- # Switch and experiment
809
- csp use experimental
810
- # ... install new skills, modify rules ...
811
- csp save
812
-
813
- # If good, merge back to default
814
- csp diff current default
815
- # ... review differences ...
816
- csp use default
817
- # ... copy changes manually or recreate ...
818
-
819
- # Clean up
820
- csp delete experimental
144
+ # Launch whichever you need
145
+ csp launch work
146
+ csp launch personal
821
147
  ```
822
148
 
823
- ### Scenario 3: Backup and Restore
149
+ ### Backup and Restore
824
150
 
825
151
  ```bash
826
- # Backup production profile
827
152
  csp export production -o ~/backups/production-2026-03.tar.gz
828
-
829
- # ... time passes ...
830
-
831
- # Restore if needed
832
153
  csp import ~/backups/production-2026-03.tar.gz -n production-restored
833
- csp use production-restored
834
154
  ```
835
155
 
836
- ### Scenario 4: Share Profile with Team
156
+ ### Share with Team
837
157
 
838
158
  ```bash
839
- # Export your setup
840
159
  csp export my-setup -o ./my-setup.csp.tar.gz
841
-
842
- # Teammate imports
160
+ # Teammate:
843
161
  csp import my-setup.csp.tar.gz -n shared-team-setup
844
- csp use shared-team-setup
845
162
  ```
846
163
 
847
- ## Troubleshooting
164
+ ## Documentation
848
165
 
849
- ### No active profile
850
-
851
- **Error:** `No active profile. Run "csp create <name>" first.`
852
-
853
- **Solution:** Initialize with `csp init` to create the default profile.
854
-
855
- ```bash
856
- csp init
857
- ```
858
-
859
- ### Profile doesn't exist
860
-
861
- **Error:** `Profile "name" does not exist. Run "csp list" to see available profiles.`
862
-
863
- **Solution:** Check available profiles and use exact name.
864
-
865
- ```bash
866
- csp list
867
- csp use production # if "production" exists
868
- ```
869
-
870
- ### Cannot delete default profile
871
-
872
- **Error:** `Cannot delete the default profile.`
873
-
874
- **Solution:** Keep `default` and delete only non-default profiles.
875
-
876
- ```bash
877
- csp delete experimental
878
- ```
879
-
880
- ### Stale lock file
881
-
882
- **Error:** `Another csp operation is running (PID: 12345). Remove ~/.claude-profiles/.lock if stale.`
883
-
884
- **Solution:** If the process is not running, manually remove the lock:
885
-
886
- ```bash
887
- rm ~/.claude-profiles/.lock
888
- ```
889
-
890
- ### Invalid profile structure
891
-
892
- **Error:** `Profile "name" is invalid: Missing source.json — no managed items defined`
893
-
894
- **Solution:** Recreate the profile or import a valid profile archive.
895
-
896
- ### Changes not applying
897
-
898
- **Issue:** Switched profiles but changes don't appear in Claude Code.
899
-
900
- **Solution:** Restart Claude Code session after switching.
901
-
902
- ```bash
903
- csp use staging
904
- # Close and restart Claude Code
905
- ```
906
-
907
- ## Development
908
-
909
- ### Run Tests
910
-
911
- ```bash
912
- npm test # All tests
913
- npm run test:core # Core library tests
914
- npm run test:cli # CLI integration tests
915
- npm run test:safety # Safety feature tests
916
- ```
917
-
918
- ### Changelog
919
-
920
- See [CHANGELOG.md](CHANGELOG.md) for version history and migration guidance.
921
-
922
- ### Project Structure
923
-
924
- ```
925
- .
926
- ├── bin/
927
- │ └── csp.js # CLI entry point
928
- ├── src/
929
- │ ├── commands/ # Command implementations
930
- │ │ ├── init.js
931
- │ │ ├── current.js
932
- │ │ ├── list.js
933
- │ │ ├── status.js
934
- │ │ ├── create.js
935
- │ │ ├── save.js
936
- │ │ ├── use.js
937
- │ │ ├── toggle.js
938
- │ │ ├── delete.js
939
- │ │ ├── export.js
940
- │ │ ├── import.js
941
- │ │ ├── diff.js
942
- │ │ ├── select.js
943
- │ │ ├── launch.js
944
- │ │ ├── deactivate.js
945
- │ │ └── uninstall.js
946
- │ ├── constants.js # Configuration constants
947
- │ ├── platform.js # Cross-platform compatibility
948
- │ ├── profile-store.js # Profile metadata management
949
- │ ├── runtime-instance-manager.js # Isolated runtime sync
950
- │ ├── item-manager.js # Managed item copy/move operations
951
- │ ├── file-operations.js # File copy/restore operations
952
- │ ├── launch-effective-env-resolver.js # ANTHROPIC_* launch env resolution
953
- │ ├── safety.js # Locking, backups, validation
954
- │ ├── profile-validator.js # Profile validation
955
- │ └── output-helpers.js # Console output formatting
956
- ├── tests/
957
- │ ├── core-library.test.js
958
- │ ├── cli-integration.test.js
959
- │ └── safety.test.js
960
- └── package.json
961
- ```
962
-
963
- ## Planned / Future Features
964
-
965
- - Automatic backups during profile switch flow (`csp use`) with backup retention/pruning.
166
+ - [Commands Reference](docs/commands-reference.md) — Full command options, behavior, and troubleshooting
167
+ - [System Architecture](docs/system-architecture.md) — Module architecture, data flows, and internals
168
+ - [Project Overview](docs/project-overview-pdr.md) Product design review and requirements
169
+ - [Code Standards](docs/code-standards.md) — Coding conventions and guidelines
170
+ - [Changelog](CHANGELOG.md) Version history and migration guidance
966
171
 
967
172
  ## License
968
173
 
@@ -970,8 +175,4 @@ MIT
970
175
 
971
176
  ## Contributing
972
177
 
973
- Contributions welcome! Please ensure tests pass and follow existing code style.
974
-
975
- ```bash
976
- npm test
977
- ```
178
+ Contributions welcome! See the [docs/](docs/) for architecture and code standards.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-switch-profile",
3
- "version": "1.4.8",
3
+ "version": "1.4.12",
4
4
  "description": "CLI tool for managing multiple Claude Code profiles",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,6 +11,7 @@ import {
11
11
  readlinkSync,
12
12
  } from 'node:fs';
13
13
  import { join, dirname } from 'node:path';
14
+ import { homedir } from 'node:os';
14
15
  import { MANAGED_ITEMS, COPY_ITEMS, COPY_DIRS, CLAUDE_DIR } from './constants.js';
15
16
  import {
16
17
  getActive,
@@ -76,6 +77,8 @@ const shouldSyncDir = (src, dest) => {
76
77
  }
77
78
  };
78
79
 
80
+ const escapeRegExp = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
81
+
79
82
  const replacePathVariants = (content, fromPath, toPath) => {
80
83
  const fromEscaped = fromPath.replaceAll('\\', '\\\\');
81
84
  const toEscaped = toPath.replaceAll('\\', '\\\\');
@@ -83,10 +86,20 @@ const replacePathVariants = (content, fromPath, toPath) => {
83
86
  const toFwd = toPath.replaceAll('\\', '/');
84
87
 
85
88
  let updated = content;
86
- updated = updated.replaceAll(fromEscaped, toEscaped);
87
- updated = updated.replaceAll(fromFwd, toFwd);
89
+
90
+ // Prevent matching substrings of other paths (e.g. ~/.claude matching ~/.claude-profiles)
91
+ // by ensuring it's not immediately followed by an alphanumeric character or hyphen.
92
+ const negativeLookahead = '(?![a-zA-Z0-9\\-])';
93
+
94
+ const replaceWithRegex = (str, targetF, targetT) => {
95
+ const re = new RegExp(escapeRegExp(targetF) + negativeLookahead, 'g');
96
+ return str.replace(re, targetT);
97
+ };
98
+
99
+ updated = replaceWithRegex(updated, fromEscaped, toEscaped);
100
+ updated = replaceWithRegex(updated, fromFwd, toFwd);
88
101
  if (fromPath !== fromEscaped) {
89
- updated = updated.replaceAll(fromPath, toPath);
102
+ updated = replaceWithRegex(updated, fromPath, toPath);
90
103
  }
91
104
 
92
105
  return updated;
@@ -103,6 +116,25 @@ const rewriteSettingsForRuntime = (runtimeDir, sourceDir) => {
103
116
  updated = replacePathVariants(updated, sourceDir, runtimeDir);
104
117
  updated = replacePathVariants(updated, CLAUDE_DIR, runtimeDir);
105
118
 
119
+ // Also replace shell variable patterns commonly used in hook commands.
120
+ // Hook commands in settings.json often reference paths like:
121
+ // $HOME/.claude/hooks/... or ${HOME}/.claude/hooks/... or ~/.claude/hooks/...
122
+ // These won't match the literal path replacement above.
123
+ const home = homedir();
124
+ const claudeRelSuffix = CLAUDE_DIR.startsWith(home) ? CLAUDE_DIR.slice(home.length) : '/.claude';
125
+
126
+ const shellPatterns = [
127
+ `$HOME${claudeRelSuffix}`,
128
+ `\${HOME}${claudeRelSuffix}`,
129
+ `~${claudeRelSuffix}`,
130
+ ];
131
+
132
+ for (const pattern of shellPatterns) {
133
+ if (updated.includes(pattern)) {
134
+ updated = updated.replaceAll(pattern, runtimeDir);
135
+ }
136
+ }
137
+
106
138
  if (updated !== raw) {
107
139
  writeFileSync(settingsPath, updated);
108
140
  }