claude-switch-profile 1.4.0 → 1.4.3
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/.cocoindex_code/cocoindex.db/mdb/data.mdb +0 -0
- package/.cocoindex_code/cocoindex.db/mdb/lock.mdb +0 -0
- package/.cocoindex_code/settings.yml +41 -0
- package/.cocoindex_code/target_sqlite.db +0 -0
- package/CHANGELOG.md +27 -0
- package/README.md +150 -67
- package/bin/csp.js +21 -1
- package/package.json +1 -1
- package/src/commands/create.js +9 -26
- package/src/commands/current.js +13 -2
- package/src/commands/deactivate.js +6 -8
- package/src/commands/diff.js +6 -4
- package/src/commands/export.js +7 -4
- package/src/commands/import.js +47 -2
- package/src/commands/init.js +29 -9
- package/src/commands/launch.js +158 -120
- package/src/commands/save.js +8 -4
- package/src/commands/uninstall.js +19 -22
- package/src/commands/use.js +55 -50
- package/src/constants.js +1 -1
- package/src/file-operations.js +13 -11
- package/src/isolated-launch-context.js +100 -0
- package/src/item-manager.js +10 -16
- package/src/profile-store.js +37 -14
- package/src/runtime-instance-manager.js +29 -29
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
exclude_patterns:
|
|
2
|
+
- '**/.*'
|
|
3
|
+
- '**/__pycache__'
|
|
4
|
+
- '**/node_modules'
|
|
5
|
+
- '**/target'
|
|
6
|
+
- '**/build/assets'
|
|
7
|
+
- '**/dist'
|
|
8
|
+
- '**/vendor/*.*/*'
|
|
9
|
+
- '**/vendor/*'
|
|
10
|
+
- '**/.cocoindex_code'
|
|
11
|
+
include_patterns:
|
|
12
|
+
- '**/*.py'
|
|
13
|
+
- '**/*.pyi'
|
|
14
|
+
- '**/*.js'
|
|
15
|
+
- '**/*.jsx'
|
|
16
|
+
- '**/*.ts'
|
|
17
|
+
- '**/*.tsx'
|
|
18
|
+
- '**/*.mjs'
|
|
19
|
+
- '**/*.cjs'
|
|
20
|
+
- '**/*.rs'
|
|
21
|
+
- '**/*.go'
|
|
22
|
+
- '**/*.java'
|
|
23
|
+
- '**/*.c'
|
|
24
|
+
- '**/*.h'
|
|
25
|
+
- '**/*.cpp'
|
|
26
|
+
- '**/*.hpp'
|
|
27
|
+
- '**/*.cc'
|
|
28
|
+
- '**/*.cxx'
|
|
29
|
+
- '**/*.hxx'
|
|
30
|
+
- '**/*.hh'
|
|
31
|
+
- '**/*.cs'
|
|
32
|
+
- '**/*.sql'
|
|
33
|
+
- '**/*.sh'
|
|
34
|
+
- '**/*.bash'
|
|
35
|
+
- '**/*.zsh'
|
|
36
|
+
- '**/*.md'
|
|
37
|
+
- '**/*.mdx'
|
|
38
|
+
- '**/*.txt'
|
|
39
|
+
- '**/*.rst'
|
|
40
|
+
- '**/*.php'
|
|
41
|
+
- '**/*.lua'
|
|
Binary file
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `claude-switch-profile` are documented here.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
- _No changes yet._
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## [1.4.2] - 2026-03-31
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Re-released the previous patch after an interrupted publish flow (OTP step).
|
|
16
|
+
- No code changes from `1.4.1`; this version reflects successful npm publication and release metadata synchronization.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [1.4.1] - 2026-03-31
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- `default` is now a physical profile snapshot created by `csp init` instead of a virtual pass-through.
|
|
24
|
+
- `csp save`, `csp use`, `csp export`, `csp launch`, and `csp uninstall` now operate on the `default` snapshot the same way they do for other profiles.
|
|
25
|
+
- Legacy installs missing `~/.claude-profiles/default` only backfill that snapshot when the active profile is `default` or no active profile is set; if a non-default profile is active, CSP fails closed with repair guidance.
|
|
26
|
+
- Protected and session/runtime files remain excluded from snapshot capture, export, isolated runtime sync, and restore flows.
|
|
27
|
+
- Launching `default` keeps its metadata mode as `legacy` instead of drifting to `account-session`.
|
|
28
|
+
- README command reference refreshed to match current CLI behavior (`select` default command, `status`, `toggle`, `create --source`, import symlink safety validation, updated project structure, and GitHub repository link).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
5
32
|
## [1.3.0] - 2026-03-28
|
|
6
33
|
|
|
7
34
|
### Added
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
[](https://nodejs.org)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
+
**GitHub:** https://github.com/ThanhThi2895/claude-switch-profile
|
|
8
|
+
|
|
7
9
|
A CLI tool for managing multiple Claude Code configurations and profiles. Use legacy global switching (`csp use`) or concurrent isolated account sessions (`csp launch`) with per-profile runtime roots.
|
|
8
10
|
|
|
9
11
|
## Overview
|
|
@@ -53,13 +55,13 @@ csp --help
|
|
|
53
55
|
|
|
54
56
|
### 1. Initialize
|
|
55
57
|
|
|
56
|
-
Capture your current Claude Code setup as the default profile:
|
|
58
|
+
Capture your current Claude Code setup as the physical `default` profile snapshot:
|
|
57
59
|
|
|
58
60
|
```bash
|
|
59
61
|
csp init
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
This creates `~/.claude-profiles/default/`
|
|
64
|
+
This creates `~/.claude-profiles/default/` from the current managed contents of `~/.claude`. Protected and session/runtime files remain excluded.
|
|
63
65
|
|
|
64
66
|
### 2. Create Additional Profiles
|
|
65
67
|
|
|
@@ -116,7 +118,15 @@ Output example:
|
|
|
116
118
|
|
|
117
119
|
The `*` marks the active profile.
|
|
118
120
|
|
|
119
|
-
### 6.
|
|
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
|
+
```
|
|
128
|
+
|
|
129
|
+
### 7. Uninstall CSP
|
|
120
130
|
|
|
121
131
|
Remove CSP and restore your Claude Code to its original state:
|
|
122
132
|
|
|
@@ -127,6 +137,22 @@ csp uninstall
|
|
|
127
137
|
|
|
128
138
|
## Commands Reference
|
|
129
139
|
|
|
140
|
+
### select (default)
|
|
141
|
+
|
|
142
|
+
Open interactive profile selector (default behavior when you run `csp` without subcommand).
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
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
|
+
|
|
130
156
|
### init
|
|
131
157
|
|
|
132
158
|
Initialize the profile system and capture current state as "default" profile.
|
|
@@ -182,6 +208,16 @@ The `*` marks the currently active profile.
|
|
|
182
208
|
|
|
183
209
|
---
|
|
184
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
|
+
|
|
185
221
|
### create
|
|
186
222
|
|
|
187
223
|
Create a new profile from current state or clone existing profile.
|
|
@@ -192,6 +228,7 @@ csp create <name> [options]
|
|
|
192
228
|
|
|
193
229
|
**Options:**
|
|
194
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`
|
|
195
232
|
- `-d, --description <text>` — Add description to profile
|
|
196
233
|
|
|
197
234
|
**Examples:**
|
|
@@ -211,10 +248,16 @@ Clone from existing:
|
|
|
211
248
|
csp create backup --from production
|
|
212
249
|
```
|
|
213
250
|
|
|
251
|
+
Create from kit directory:
|
|
252
|
+
```bash
|
|
253
|
+
csp create team-kit --source ~/my-kit/.agents -d "Team baseline"
|
|
254
|
+
```
|
|
255
|
+
|
|
214
256
|
**Behavior:**
|
|
215
257
|
- Creates profile directory: `~/.claude-profiles/<name>/`
|
|
216
258
|
- If `--from` is specified, clones all content from source profile
|
|
217
|
-
-
|
|
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
|
|
218
261
|
- If this is the first profile, automatically sets it as active
|
|
219
262
|
- Saves metadata (created timestamp, description)
|
|
220
263
|
|
|
@@ -229,10 +272,11 @@ csp save
|
|
|
229
272
|
```
|
|
230
273
|
|
|
231
274
|
**Behavior:**
|
|
232
|
-
- Captures all managed items and files from `~/.claude`
|
|
233
|
-
- Overwrites profile
|
|
234
|
-
-
|
|
235
|
-
-
|
|
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
|
|
236
280
|
|
|
237
281
|
---
|
|
238
282
|
|
|
@@ -247,7 +291,6 @@ csp use <name> [options]
|
|
|
247
291
|
**Options:**
|
|
248
292
|
- `--dry-run` — Show what would change without executing
|
|
249
293
|
- `--no-save` — Skip saving current profile before switching
|
|
250
|
-
- `--force` — Accepted for compatibility (current switch path does not block on target-link validation)
|
|
251
294
|
|
|
252
295
|
**Examples:**
|
|
253
296
|
|
|
@@ -266,19 +309,30 @@ Switch without saving current state:
|
|
|
266
309
|
csp use backup --no-save
|
|
267
310
|
```
|
|
268
311
|
|
|
269
|
-
Force switch with missing targets:
|
|
270
|
-
```bash
|
|
271
|
-
csp use legacy --force
|
|
272
|
-
```
|
|
273
|
-
|
|
274
312
|
**Behavior:**
|
|
275
313
|
1. Validates target profile exists and profile structure is valid
|
|
276
314
|
2. Refuses to switch while Claude Code is running (legacy/global switching mutates `~/.claude` directly)
|
|
277
|
-
3. If active profile exists and `--no-save` is not set: saves current
|
|
278
|
-
4. Removes managed items/files from `~/.claude`
|
|
279
|
-
5. Restores target profile
|
|
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`
|
|
280
318
|
6. Updates active marker
|
|
281
|
-
7.
|
|
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>`
|
|
282
336
|
|
|
283
337
|
---
|
|
284
338
|
|
|
@@ -308,10 +362,11 @@ csp delete old-setup --force
|
|
|
308
362
|
```
|
|
309
363
|
|
|
310
364
|
**Behavior:**
|
|
311
|
-
- Cannot delete
|
|
365
|
+
- Cannot delete `default`
|
|
312
366
|
- Prompts for confirmation unless `--force` is used
|
|
313
367
|
- Permanently removes profile directory and metadata
|
|
314
|
-
-
|
|
368
|
+
- If deleting the currently active non-default profile: clears active marker only (does not mutate `~/.claude`)
|
|
369
|
+
- Cannot be undone
|
|
315
370
|
|
|
316
371
|
---
|
|
317
372
|
|
|
@@ -340,8 +395,10 @@ csp export staging -o ~/backups/claude-staging.tar.gz
|
|
|
340
395
|
```
|
|
341
396
|
|
|
342
397
|
**Behavior:**
|
|
343
|
-
- Creates tar.gz archive of
|
|
344
|
-
- Includes `source.json`
|
|
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
|
|
345
402
|
- Useful for backup, sharing, or version control
|
|
346
403
|
|
|
347
404
|
---
|
|
@@ -374,6 +431,8 @@ csp import backup.tar.gz -n restored -d "Restored from backup"
|
|
|
374
431
|
**Behavior:**
|
|
375
432
|
- Extracts archive to `~/.claude-profiles/<name>/`
|
|
376
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)
|
|
377
436
|
- Creates metadata entry for profile
|
|
378
437
|
- Profile is ready to use immediately
|
|
379
438
|
|
|
@@ -422,7 +481,7 @@ File differences:
|
|
|
422
481
|
|
|
423
482
|
### deactivate
|
|
424
483
|
|
|
425
|
-
Deactivate the currently active non-default profile
|
|
484
|
+
Deactivate the currently active non-default profile by switching back to the physical `default` snapshot.
|
|
426
485
|
|
|
427
486
|
```bash
|
|
428
487
|
csp deactivate
|
|
@@ -433,9 +492,10 @@ csp deactivate
|
|
|
433
492
|
|
|
434
493
|
**Behavior:**
|
|
435
494
|
1. Exits early if no active profile or active profile is `default`
|
|
436
|
-
2.
|
|
437
|
-
3.
|
|
438
|
-
4.
|
|
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
|
|
439
499
|
|
|
440
500
|
---
|
|
441
501
|
|
|
@@ -471,20 +531,56 @@ csp launch work --legacy-global
|
|
|
471
531
|
|
|
472
532
|
**Behavior (default isolated mode):**
|
|
473
533
|
1. Validates target profile exists
|
|
474
|
-
2.
|
|
475
|
-
3.
|
|
476
|
-
4.
|
|
477
|
-
5.
|
|
478
|
-
6.
|
|
479
|
-
7.
|
|
480
|
-
8.
|
|
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
|
|
481
543
|
|
|
482
544
|
`ANTHROPIC_*` keys currently in isolated launch scope:
|
|
483
545
|
- `ANTHROPIC_AUTH_TOKEN`
|
|
484
546
|
- `ANTHROPIC_BASE_URL`
|
|
485
547
|
- `ANTHROPIC_MODEL`
|
|
486
548
|
|
|
487
|
-
Set `CSP_DEBUG_LAUNCH_ENV=1` to print
|
|
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
|
+
---
|
|
552
|
+
|
|
553
|
+
### exec
|
|
554
|
+
|
|
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
|
+
```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`
|
|
488
584
|
|
|
489
585
|
---
|
|
490
586
|
|
|
@@ -520,9 +616,10 @@ csp uninstall --force
|
|
|
520
616
|
|
|
521
617
|
**Behavior:**
|
|
522
618
|
1. Creates a final backup at `~/.claude-profiles/.backup/`
|
|
523
|
-
2. Restores the active profile
|
|
524
|
-
3.
|
|
525
|
-
4.
|
|
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`
|
|
526
623
|
|
|
527
624
|
---
|
|
528
625
|
|
|
@@ -561,7 +658,7 @@ Profiles are stored in `~/.claude-profiles/`:
|
|
|
561
658
|
- `agents/` — Agent scripts and configurations
|
|
562
659
|
- `skills/` — Custom Luna skills
|
|
563
660
|
- `hooks/` — Pre/post action hooks
|
|
564
|
-
- `statusline.cjs` — Custom statusline
|
|
661
|
+
- `statusline.cjs`, `statusline.sh`, `statusline.ps1` — Custom statusline scripts
|
|
565
662
|
- `.luna.json` — Luna configuration
|
|
566
663
|
|
|
567
664
|
**Copied Files**:
|
|
@@ -583,7 +680,7 @@ Profiles are stored in `~/.claude-profiles/`:
|
|
|
583
680
|
- `history.jsonl`, `metadata.json`, `stats-cache.json`, `active-plan`
|
|
584
681
|
- `backups/`, `command-archive/`, `commands-archived/`
|
|
585
682
|
- `plans/`, `todos/`, `tasks/`, `teams/`, `agent-memory/`, `file-history/`, `shell-snapshots/`
|
|
586
|
-
- All other session-specific data
|
|
683
|
+
- All other protected or session-specific data
|
|
587
684
|
|
|
588
685
|
### Legacy vs Isolated Launch Modes
|
|
589
686
|
|
|
@@ -625,23 +722,6 @@ Another csp operation is running (PID: 12345).
|
|
|
625
722
|
Remove ~/.claude-profiles/.lock if stale.
|
|
626
723
|
```
|
|
627
724
|
|
|
628
|
-
### Automatic Backups
|
|
629
|
-
|
|
630
|
-
Every profile switch creates a timestamped backup:
|
|
631
|
-
|
|
632
|
-
```
|
|
633
|
-
~/.claude-profiles/.backup/
|
|
634
|
-
├── 2026-03-11T14-30-45-123Z/
|
|
635
|
-
│ ├── source.json
|
|
636
|
-
│ ├── settings.json
|
|
637
|
-
│ ├── .env
|
|
638
|
-
│ └── ...
|
|
639
|
-
└── 2026-03-11T15-45-22-456Z/
|
|
640
|
-
└── ...
|
|
641
|
-
```
|
|
642
|
-
|
|
643
|
-
Backups are pruned automatically; CSP keeps only the 2 most recent backups. You can manually restore by copying from backup directory.
|
|
644
|
-
|
|
645
725
|
### Claude Process Detection
|
|
646
726
|
|
|
647
727
|
When switching profiles, CSP detects if Claude Code is running:
|
|
@@ -664,11 +744,7 @@ Before switching, CSP validates:
|
|
|
664
744
|
1. Target profile exists
|
|
665
745
|
2. Profile structure is valid
|
|
666
746
|
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
```bash
|
|
670
|
-
csp use legacy --force
|
|
671
|
-
```
|
|
747
|
+
During import, CSP also validates profile contents and rejects unsafe symlinks that point outside the imported profile tree.
|
|
672
748
|
|
|
673
749
|
## Configuration via Environment Variables
|
|
674
750
|
|
|
@@ -727,7 +803,7 @@ csp use personal # Switch to personal
|
|
|
727
803
|
|
|
728
804
|
```bash
|
|
729
805
|
# Clone current profile
|
|
730
|
-
csp create experimental --from
|
|
806
|
+
csp create experimental --from default -d "Testing new Luna skills"
|
|
731
807
|
|
|
732
808
|
# Switch and experiment
|
|
733
809
|
csp use experimental
|
|
@@ -791,15 +867,14 @@ csp list
|
|
|
791
867
|
csp use production # if "production" exists
|
|
792
868
|
```
|
|
793
869
|
|
|
794
|
-
### Cannot delete
|
|
870
|
+
### Cannot delete default profile
|
|
795
871
|
|
|
796
|
-
**Error:** `Cannot delete
|
|
872
|
+
**Error:** `Cannot delete the default profile.`
|
|
797
873
|
|
|
798
|
-
**Solution:**
|
|
874
|
+
**Solution:** Keep `default` and delete only non-default profiles.
|
|
799
875
|
|
|
800
876
|
```bash
|
|
801
|
-
csp
|
|
802
|
-
csp delete default
|
|
877
|
+
csp delete experimental
|
|
803
878
|
```
|
|
804
879
|
|
|
805
880
|
### Stale lock file
|
|
@@ -855,13 +930,16 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and migration guidance.
|
|
|
855
930
|
│ │ ├── init.js
|
|
856
931
|
│ │ ├── current.js
|
|
857
932
|
│ │ ├── list.js
|
|
933
|
+
│ │ ├── status.js
|
|
858
934
|
│ │ ├── create.js
|
|
859
935
|
│ │ ├── save.js
|
|
860
936
|
│ │ ├── use.js
|
|
937
|
+
│ │ ├── toggle.js
|
|
861
938
|
│ │ ├── delete.js
|
|
862
939
|
│ │ ├── export.js
|
|
863
940
|
│ │ ├── import.js
|
|
864
941
|
│ │ ├── diff.js
|
|
942
|
+
│ │ ├── select.js
|
|
865
943
|
│ │ ├── launch.js
|
|
866
944
|
│ │ ├── deactivate.js
|
|
867
945
|
│ │ └── uninstall.js
|
|
@@ -871,6 +949,7 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and migration guidance.
|
|
|
871
949
|
│ ├── runtime-instance-manager.js # Isolated runtime sync
|
|
872
950
|
│ ├── item-manager.js # Managed item copy/move operations
|
|
873
951
|
│ ├── file-operations.js # File copy/restore operations
|
|
952
|
+
│ ├── launch-effective-env-resolver.js # ANTHROPIC_* launch env resolution
|
|
874
953
|
│ ├── safety.js # Locking, backups, validation
|
|
875
954
|
│ ├── profile-validator.js # Profile validation
|
|
876
955
|
│ └── output-helpers.js # Console output formatting
|
|
@@ -881,6 +960,10 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and migration guidance.
|
|
|
881
960
|
└── package.json
|
|
882
961
|
```
|
|
883
962
|
|
|
963
|
+
## Planned / Future Features
|
|
964
|
+
|
|
965
|
+
- Automatic backups during profile switch flow (`csp use`) with backup retention/pruning.
|
|
966
|
+
|
|
884
967
|
## License
|
|
885
968
|
|
|
886
969
|
MIT
|
package/bin/csp.js
CHANGED
|
@@ -16,7 +16,7 @@ import { importCommand } from '../src/commands/import.js';
|
|
|
16
16
|
import { diffCommand } from '../src/commands/diff.js';
|
|
17
17
|
import { initCommand } from '../src/commands/init.js';
|
|
18
18
|
import { uninstallCommand } from '../src/commands/uninstall.js';
|
|
19
|
-
import { launchCommand } from '../src/commands/launch.js';
|
|
19
|
+
import { launchCommand, execCommand } from '../src/commands/launch.js';
|
|
20
20
|
import { deactivateCommand } from '../src/commands/deactivate.js';
|
|
21
21
|
import { toggleCommand } from '../src/commands/toggle.js';
|
|
22
22
|
import { statusCommand } from '../src/commands/status.js';
|
|
@@ -130,6 +130,26 @@ program
|
|
|
130
130
|
launchCommand(name, claudeArgs, options);
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
program
|
|
134
|
+
.command('exec <name> [args...]')
|
|
135
|
+
.description('Run arbitrary command inside isolated profile runtime environment')
|
|
136
|
+
.allowUnknownOption(true)
|
|
137
|
+
.enablePositionalOptions(true)
|
|
138
|
+
.passThroughOptions(true)
|
|
139
|
+
.action((name, args, _options, cmd) => {
|
|
140
|
+
const passthrough = [...(args || [])];
|
|
141
|
+
const unknown = cmd.args.filter((a) => a !== name && !passthrough.includes(a));
|
|
142
|
+
const tokens = [...passthrough, ...unknown];
|
|
143
|
+
|
|
144
|
+
while (tokens[0] === '--') {
|
|
145
|
+
tokens.shift();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const command = tokens[0];
|
|
149
|
+
const commandArgs = tokens.slice(1);
|
|
150
|
+
execCommand(name, command, commandArgs);
|
|
151
|
+
});
|
|
152
|
+
|
|
133
153
|
program
|
|
134
154
|
.command('uninstall')
|
|
135
155
|
.description('Remove all profiles and restore Claude Code to pre-CSP state')
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdirSync, cpSync, existsSync,
|
|
1
|
+
import { mkdirSync, cpSync, existsSync, writeFileSync, readdirSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { addProfile, getActive, setActive, profileExists, getProfileDir } from '../profile-store.js';
|
|
4
4
|
import { saveFiles, updateSettingsPaths } from '../file-operations.js';
|
|
@@ -20,7 +20,7 @@ export const createCommand = (name, options) => {
|
|
|
20
20
|
process.exit(1);
|
|
21
21
|
}
|
|
22
22
|
const sourceDir = getProfileDir(options.from);
|
|
23
|
-
cpSync(sourceDir, profileDir, { recursive: true });
|
|
23
|
+
cpSync(sourceDir, profileDir, { recursive: true, verbatimSymlinks: true });
|
|
24
24
|
info(`Cloned from profile "${options.from}"`);
|
|
25
25
|
} else if (options.source) {
|
|
26
26
|
// Create from a specific .agents/ directory (or any kit directory)
|
|
@@ -39,11 +39,7 @@ export const createCommand = (name, options) => {
|
|
|
39
39
|
if (existsSync(target)) {
|
|
40
40
|
const dest = join(profileDir, item);
|
|
41
41
|
try {
|
|
42
|
-
|
|
43
|
-
cpSync(target, dest, { recursive: true });
|
|
44
|
-
} else {
|
|
45
|
-
copyFileSync(target, dest);
|
|
46
|
-
}
|
|
42
|
+
cpSync(target, dest, { recursive: true, verbatimSymlinks: true });
|
|
47
43
|
sourceMap[item] = dest;
|
|
48
44
|
} catch { /* skip */ }
|
|
49
45
|
}
|
|
@@ -60,11 +56,7 @@ export const createCommand = (name, options) => {
|
|
|
60
56
|
if (!existsSync(src)) continue;
|
|
61
57
|
try {
|
|
62
58
|
const dest = join(profileDir, item);
|
|
63
|
-
|
|
64
|
-
cpSync(src, dest, { recursive: true });
|
|
65
|
-
} else {
|
|
66
|
-
copyFileSync(src, dest);
|
|
67
|
-
}
|
|
59
|
+
cpSync(src, dest, { recursive: true, verbatimSymlinks: true });
|
|
68
60
|
sourceMap[item] = dest;
|
|
69
61
|
} catch { /* skip */ }
|
|
70
62
|
}
|
|
@@ -92,20 +84,11 @@ export const createCommand = (name, options) => {
|
|
|
92
84
|
|
|
93
85
|
const src = join(CLAUDE_DIR, entry);
|
|
94
86
|
try {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (MANAGED_ITEMS.includes(entry)) {
|
|
101
|
-
sourceMap[entry] = dest;
|
|
102
|
-
}
|
|
103
|
-
} else if (stat.isFile()) {
|
|
104
|
-
const dest = join(profileDir, entry);
|
|
105
|
-
copyFileSync(src, dest);
|
|
106
|
-
if (MANAGED_ITEMS.includes(entry)) {
|
|
107
|
-
sourceMap[entry] = dest;
|
|
108
|
-
}
|
|
87
|
+
const dest = join(profileDir, entry);
|
|
88
|
+
cpSync(src, dest, { recursive: true, verbatimSymlinks: true });
|
|
89
|
+
|
|
90
|
+
if (MANAGED_ITEMS.includes(entry)) {
|
|
91
|
+
sourceMap[entry] = dest;
|
|
109
92
|
}
|
|
110
93
|
} catch { /* skip unreadable items */ }
|
|
111
94
|
}
|
package/src/commands/current.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { getActive, getProfileDir, getProfileMeta } from '../profile-store.js';
|
|
2
|
-
import {
|
|
1
|
+
import { getActive, getProfileDir, getProfileMeta, ensureDefaultProfileSnapshot } from '../profile-store.js';
|
|
2
|
+
import { DEFAULT_PROFILE } from '../constants.js';
|
|
3
|
+
import { success, info, warn, error } from '../output-helpers.js';
|
|
3
4
|
|
|
4
5
|
export const currentCommand = () => {
|
|
5
6
|
const active = getActive();
|
|
@@ -7,6 +8,16 @@ export const currentCommand = () => {
|
|
|
7
8
|
warn('No active profile. Run "csp create <name>" to create one.');
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
11
|
+
|
|
12
|
+
if (active === DEFAULT_PROFILE) {
|
|
13
|
+
try {
|
|
14
|
+
ensureDefaultProfileSnapshot();
|
|
15
|
+
} catch (err) {
|
|
16
|
+
error(err.message);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
10
21
|
success(`Active legacy profile: ${active}`);
|
|
11
22
|
info(`Location: ${getProfileDir(active)}`);
|
|
12
23
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { getActive
|
|
1
|
+
import { getActive } from '../profile-store.js';
|
|
2
2
|
import { success, info } from '../output-helpers.js';
|
|
3
3
|
import { DEFAULT_PROFILE } from '../constants.js';
|
|
4
|
+
import { useCommand } from './use.js';
|
|
4
5
|
|
|
5
|
-
export const deactivateCommand = async () => {
|
|
6
|
+
export const deactivateCommand = async (options = {}) => {
|
|
6
7
|
const active = getActive();
|
|
7
8
|
if (!active) {
|
|
8
9
|
info('No active profile to deactivate.');
|
|
@@ -10,13 +11,10 @@ export const deactivateCommand = async () => {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
if (active === DEFAULT_PROFILE) {
|
|
13
|
-
info('Default profile
|
|
14
|
+
info('Default profile is already active.');
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
success(`Profile "${active}" deactivated. Reset to default.`);
|
|
21
|
-
info('~/.claude was not modified.');
|
|
18
|
+
await useCommand(DEFAULT_PROFILE, options);
|
|
19
|
+
success(`Profile "${active}" deactivated.`);
|
|
22
20
|
};
|