claude-switch-profile 1.1.0 → 1.4.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/CHANGELOG.md +24 -0
- package/README.md +145 -60
- package/bin/csp.js +44 -3
- package/package.json +1 -1
- package/src/commands/create.js +50 -35
- package/src/commands/current.js +8 -3
- package/src/commands/deactivate.js +22 -0
- package/src/commands/delete.js +19 -7
- package/src/commands/diff.js +19 -9
- package/src/commands/export.js +30 -5
- package/src/commands/import.js +11 -2
- package/src/commands/init.js +10 -31
- package/src/commands/launch.js +216 -0
- package/src/commands/list.js +2 -1
- package/src/commands/save.js +11 -4
- package/src/commands/select.js +126 -0
- package/src/commands/status.js +48 -0
- package/src/commands/toggle.js +25 -0
- package/src/commands/uninstall.js +19 -9
- package/src/commands/use.js +67 -58
- package/src/constants.js +43 -16
- package/src/file-operations.js +80 -1
- package/src/item-manager.js +155 -0
- package/src/launch-effective-env-resolver.js +192 -0
- package/src/platform.js +24 -0
- package/src/profile-store.js +139 -7
- package/src/profile-validator.js +7 -9
- package/src/runtime-instance-manager.js +190 -0
- package/src/safety.js +72 -35
- package/src/symlink-manager.js +0 -102
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `claude-switch-profile` are documented here.
|
|
4
4
|
|
|
5
|
+
## [1.3.0] - 2026-03-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- ✨ **Interactive profile selector** — Run `csp` with no arguments to get an arrow-key profile picker that launches isolated sessions. Supports ↑/↓/j/k navigation, Enter to select, Esc to cancel.
|
|
9
|
+
- ✨ **`csp status`** — Dashboard showing active profile, profile count, last launch info, and Claude running status at a glance.
|
|
10
|
+
- ✨ **`csp toggle`** — Quick-switch to the previous profile (like `cd -`). Uses isolated launch, never touches ~/.claude.
|
|
11
|
+
- ✨ **`csp select`** — Explicit alias for the interactive selector.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- 🔒 **`~/.claude` is never modified** — All profile operations now use isolated runtime (`CLAUDE_CONFIG_DIR`). The `default` profile is a virtual alias for `~/.claude` as-is.
|
|
15
|
+
- 🔒 **`default` profile is virtual** — `csp init` no longer creates a physical directory for the default profile. Default means "use ~/.claude directly".
|
|
16
|
+
- 🔒 **`csp launch default`** — Launches Claude with native `~/.claude` (no runtime dir, no `CLAUDE_CONFIG_DIR` override).
|
|
17
|
+
- 🔒 **`csp deactivate`** — Now simply resets active marker to `default`. No longer deletes files from `~/.claude`.
|
|
18
|
+
- ⚠️ **`csp use` deprecated** — Shows warning that it modifies `~/.claude` (legacy mode). Users should prefer `csp launch`.
|
|
19
|
+
- 🔄 **`csp toggle`** — Now uses `launch` (isolated) instead of `use` (legacy).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- 🐛 Removed dead `--force` option from `csp use`.
|
|
23
|
+
- 🐛 Fixed double `getActive()` call in `init` command.
|
|
24
|
+
- 🐛 Fixed stale "symlink targets" comment in safety.js.
|
|
25
|
+
- 🐛 Removed unused parameter in diff.js.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
5
29
|
## [1.1.0] - 2026-03-13
|
|
6
30
|
|
|
7
31
|
### Added
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](https://nodejs.org)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
|
|
7
|
-
A CLI tool for managing multiple Claude Code configurations and profiles.
|
|
7
|
+
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
8
|
|
|
9
9
|
## Overview
|
|
10
10
|
|
|
@@ -46,7 +46,8 @@ csp --help
|
|
|
46
46
|
### Requirements
|
|
47
47
|
|
|
48
48
|
- Node.js >= 18.0.0
|
|
49
|
-
- Unix/Linux/macOS
|
|
49
|
+
- Unix/Linux/macOS
|
|
50
|
+
- Windows 10+
|
|
50
51
|
|
|
51
52
|
## Quick Start
|
|
52
53
|
|
|
@@ -146,7 +147,7 @@ csp init
|
|
|
146
147
|
|
|
147
148
|
### current
|
|
148
149
|
|
|
149
|
-
Display the currently active profile.
|
|
150
|
+
Display the currently active **legacy** profile and isolated launch metadata (if present).
|
|
150
151
|
|
|
151
152
|
```bash
|
|
152
153
|
csp current
|
|
@@ -154,8 +155,10 @@ csp current
|
|
|
154
155
|
|
|
155
156
|
**Output:**
|
|
156
157
|
```
|
|
157
|
-
✓ Active profile: default
|
|
158
|
+
✓ Active legacy profile: default
|
|
158
159
|
ℹ Location: /home/user/.claude-profiles/default
|
|
160
|
+
ℹ Last isolated launch: 2026-03-26T14:45:00.000Z
|
|
161
|
+
ℹ Isolated runtime: /home/user/.claude-profiles/.runtime/default
|
|
159
162
|
```
|
|
160
163
|
|
|
161
164
|
---
|
|
@@ -171,8 +174,8 @@ csp ls
|
|
|
171
174
|
|
|
172
175
|
**Output format:**
|
|
173
176
|
```
|
|
174
|
-
* profile-name — optional description (YYYY-MM-DD)
|
|
175
|
-
other-profile (YYYY-MM-DD)
|
|
177
|
+
* profile-name — optional description (YYYY-MM-DD) [legacy|account-session]
|
|
178
|
+
other-profile (YYYY-MM-DD) [account-session]
|
|
176
179
|
```
|
|
177
180
|
|
|
178
181
|
The `*` marks the currently active profile.
|
|
@@ -226,7 +229,7 @@ csp save
|
|
|
226
229
|
```
|
|
227
230
|
|
|
228
231
|
**Behavior:**
|
|
229
|
-
- Captures all managed
|
|
232
|
+
- Captures all managed items and files from `~/.claude`
|
|
230
233
|
- Overwrites profile's `source.json` and file copies
|
|
231
234
|
- Useful after modifying rules, settings, or other configuration
|
|
232
235
|
- Requires active profile (run `csp create` if none exists)
|
|
@@ -244,7 +247,7 @@ csp use <name> [options]
|
|
|
244
247
|
**Options:**
|
|
245
248
|
- `--dry-run` — Show what would change without executing
|
|
246
249
|
- `--no-save` — Skip saving current profile before switching
|
|
247
|
-
- `--force` —
|
|
250
|
+
- `--force` — Accepted for compatibility (current switch path does not block on target-link validation)
|
|
248
251
|
|
|
249
252
|
**Examples:**
|
|
250
253
|
|
|
@@ -269,14 +272,13 @@ csp use legacy --force
|
|
|
269
272
|
```
|
|
270
273
|
|
|
271
274
|
**Behavior:**
|
|
272
|
-
1. Validates target profile exists and is valid
|
|
273
|
-
2. (
|
|
275
|
+
1. Validates target profile exists and profile structure is valid
|
|
276
|
+
2. Refuses to switch while Claude Code is running (legacy/global switching mutates `~/.claude` directly)
|
|
274
277
|
3. If active profile exists and `--no-save` is not set: saves current state
|
|
275
|
-
4.
|
|
276
|
-
5.
|
|
277
|
-
6.
|
|
278
|
-
7.
|
|
279
|
-
8. **Important:** Claude Code session must be restarted for changes to apply
|
|
278
|
+
4. Removes managed items/files from `~/.claude` as needed
|
|
279
|
+
5. Restores target profile configuration into `~/.claude`
|
|
280
|
+
6. Updates active marker
|
|
281
|
+
7. **Important:** Claude Code session must be restarted for changes to apply
|
|
280
282
|
|
|
281
283
|
---
|
|
282
284
|
|
|
@@ -339,7 +341,7 @@ csp export staging -o ~/backups/claude-staging.tar.gz
|
|
|
339
341
|
|
|
340
342
|
**Behavior:**
|
|
341
343
|
- Creates tar.gz archive of entire profile directory
|
|
342
|
-
- Includes source.json (
|
|
344
|
+
- Includes `source.json` (managed item map) and copied profile files/directories
|
|
343
345
|
- Useful for backup, sharing, or version control
|
|
344
346
|
|
|
345
347
|
---
|
|
@@ -403,7 +405,7 @@ csp diff current backup
|
|
|
403
405
|
```
|
|
404
406
|
Comparing: staging ↔ production
|
|
405
407
|
|
|
406
|
-
|
|
408
|
+
Managed item map (source.json): identical
|
|
407
409
|
|
|
408
410
|
File differences:
|
|
409
411
|
settings.json — different
|
|
@@ -412,10 +414,80 @@ File differences:
|
|
|
412
414
|
```
|
|
413
415
|
|
|
414
416
|
**Behavior:**
|
|
415
|
-
- Compares `source.json` (
|
|
417
|
+
- Compares `source.json` (managed item map)
|
|
416
418
|
- Lists file presence and content differences
|
|
417
419
|
- Shows which files differ and in which profile they exist
|
|
418
420
|
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
### deactivate
|
|
424
|
+
|
|
425
|
+
Deactivate the currently active non-default profile and clear `.active` without switching to another profile.
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
csp deactivate
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
**Options:**
|
|
432
|
+
- `--no-save` — Skip saving current profile state before deactivation
|
|
433
|
+
|
|
434
|
+
**Behavior:**
|
|
435
|
+
1. Exits early if no active profile or active profile is `default`
|
|
436
|
+
2. Optionally saves active profile state
|
|
437
|
+
3. Removes managed items/files from `~/.claude`
|
|
438
|
+
4. Clears active marker
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
### launch (la)
|
|
443
|
+
|
|
444
|
+
Launch an isolated Claude session for a profile. This does **not** change global active profile. All extra arguments are forwarded to `claude`.
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
csp launch <name> [claude-args...]
|
|
448
|
+
csp la <name> [claude-args...]
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
**Options:**
|
|
452
|
+
- `--legacy-global` — Use old behavior (`csp use <name>` then launch)
|
|
453
|
+
|
|
454
|
+
**Examples:**
|
|
455
|
+
|
|
456
|
+
Launch isolated session:
|
|
457
|
+
```bash
|
|
458
|
+
csp launch work
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Launch isolated with Claude flags:
|
|
462
|
+
```bash
|
|
463
|
+
csp launch work --dangerously-skip-permissions
|
|
464
|
+
csp la dev --model opus
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Launch legacy/global mode:
|
|
468
|
+
```bash
|
|
469
|
+
csp launch work --legacy-global
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**Behavior (default isolated mode):**
|
|
473
|
+
1. Validates target profile exists
|
|
474
|
+
2. Prepares per-profile runtime under `~/.claude-profiles/.runtime/<name>`
|
|
475
|
+
3. 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
|
|
476
|
+
4. Strips inherited `CLAUDECODE`, inherited `CLAUDE_CONFIG_DIR`, and inherited `ANTHROPIC_*` before applying resolved allowlisted values
|
|
477
|
+
5. Spawns `claude` with `CLAUDE_CONFIG_DIR=<runtimeDir>`
|
|
478
|
+
6. Inherits stdin/stdout/stderr for interactive use
|
|
479
|
+
7. Forwards Claude's exit code
|
|
480
|
+
8. Keeps `.active` unchanged and never mutates global `~/.claude`
|
|
481
|
+
|
|
482
|
+
`ANTHROPIC_*` keys currently in isolated launch scope:
|
|
483
|
+
- `ANTHROPIC_AUTH_TOKEN`
|
|
484
|
+
- `ANTHROPIC_BASE_URL`
|
|
485
|
+
- `ANTHROPIC_MODEL`
|
|
486
|
+
|
|
487
|
+
Set `CSP_DEBUG_LAUNCH_ENV=1` to print key-level launch diagnostics (sources only, values are never printed).
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
419
491
|
### uninstall
|
|
420
492
|
|
|
421
493
|
Remove all profiles and restore Claude Code to its pre-CSP state.
|
|
@@ -462,10 +534,13 @@ Profiles are stored in `~/.claude-profiles/`:
|
|
|
462
534
|
|
|
463
535
|
```
|
|
464
536
|
~/.claude-profiles/
|
|
465
|
-
├── .active # Current active profile name
|
|
466
|
-
├── profiles.json # Metadata for all profiles
|
|
537
|
+
├── .active # Current active profile name (legacy use mode)
|
|
538
|
+
├── profiles.json # Metadata for all profiles (v2 schema)
|
|
539
|
+
├── .runtime/ # Isolated launch roots
|
|
540
|
+
│ ├── work/
|
|
541
|
+
│ └── personal/
|
|
467
542
|
├── default/
|
|
468
|
-
│ ├── source.json #
|
|
543
|
+
│ ├── source.json # Managed item map
|
|
469
544
|
│ ├── settings.json # Copied from ~/.claude
|
|
470
545
|
│ ├── .env # Copied from ~/.claude
|
|
471
546
|
│ ├── .ck.json # Copied from ~/.claude
|
|
@@ -480,7 +555,7 @@ Profiles are stored in `~/.claude-profiles/`:
|
|
|
480
555
|
|
|
481
556
|
### What Gets Managed
|
|
482
557
|
|
|
483
|
-
**
|
|
558
|
+
**Managed Static Items** (via `source.json`):
|
|
484
559
|
- `CLAUDE.md` — Project-specific Claude configuration
|
|
485
560
|
- `rules/` — Development rules and guidelines
|
|
486
561
|
- `agents/` — Agent scripts and configurations
|
|
@@ -491,45 +566,49 @@ Profiles are stored in `~/.claude-profiles/`:
|
|
|
491
566
|
|
|
492
567
|
**Copied Files**:
|
|
493
568
|
- `settings.json` — Editor settings
|
|
494
|
-
- `.env` — Environment variables
|
|
495
|
-
- `.ck.json` — Custom settings
|
|
496
|
-
- `.
|
|
569
|
+
- `.env`, `.env.example` — Environment variables
|
|
570
|
+
- `.ck.json`, `.ckignore` — Custom settings and ignore patterns
|
|
571
|
+
- `.mcp.json`, `.mcp.json.example` — MCP configuration
|
|
572
|
+
- `.gitignore` — Local ignore settings
|
|
497
573
|
|
|
498
574
|
**Copied Directories**:
|
|
499
|
-
- `commands/` — Custom commands
|
|
500
|
-
- `
|
|
575
|
+
- `commands/`, `plugins/` — Custom commands/plugins
|
|
576
|
+
- `workflows/`, `scripts/` — Automation and workflow assets
|
|
577
|
+
- `output-styles/`, `schemas/` — Output and schema assets
|
|
501
578
|
|
|
502
579
|
**Never Touched** (runtime/session data):
|
|
503
580
|
- `.credentials.json`
|
|
504
|
-
- `projects/`
|
|
505
|
-
- `
|
|
506
|
-
- `
|
|
507
|
-
- `
|
|
508
|
-
- `plans/`, `todos/`, `tasks/`
|
|
509
|
-
- `agent-memory/`, `session-env/`
|
|
581
|
+
- `projects/`, `sessions/`, `session-env/`, `ide/`
|
|
582
|
+
- `cache/`, `paste-cache/`, `downloads/`, `telemetry/`, `debug/`, `statsig/`
|
|
583
|
+
- `history.jsonl`, `metadata.json`, `stats-cache.json`, `active-plan`
|
|
584
|
+
- `backups/`, `command-archive/`, `commands-archived/`
|
|
585
|
+
- `plans/`, `todos/`, `tasks/`, `teams/`, `agent-memory/`, `file-history/`, `shell-snapshots/`
|
|
510
586
|
- All other session-specific data
|
|
511
587
|
|
|
512
|
-
###
|
|
588
|
+
### Legacy vs Isolated Launch Modes
|
|
513
589
|
|
|
514
|
-
|
|
515
|
-
- Rules, agents, skills often live in external git repos
|
|
516
|
-
- Multiple profiles may share the same rules/skills
|
|
517
|
-
- Symlinks avoid duplication and keep everything in sync
|
|
590
|
+
CSP now supports two paths:
|
|
518
591
|
|
|
519
|
-
**
|
|
520
|
-
-
|
|
521
|
-
-
|
|
522
|
-
-
|
|
592
|
+
- **Legacy global mode (`csp use`)**
|
|
593
|
+
- Mutates `~/.claude`
|
|
594
|
+
- Updates `.active`
|
|
595
|
+
- Preserves old behavior for existing scripts
|
|
523
596
|
|
|
524
|
-
|
|
597
|
+
- **Isolated launch mode (`csp launch`)**
|
|
598
|
+
- Does not mutate `~/.claude`
|
|
599
|
+
- Does not change `.active`
|
|
600
|
+
- Creates/updates runtime root per profile at `~/.claude-profiles/.runtime/<name>`
|
|
601
|
+
- Launches Claude with `CLAUDE_CONFIG_DIR` pointing to that runtime root
|
|
525
602
|
|
|
526
|
-
|
|
603
|
+
### Runtime Sync Policy (isolated launch)
|
|
527
604
|
|
|
528
|
-
|
|
529
|
-
2. **Replaces** it with a symlink at the original location
|
|
530
|
-
3. **Records** the new location in `source.json`
|
|
605
|
+
For each launch, CSP syncs static profile config into runtime root:
|
|
531
606
|
|
|
532
|
-
|
|
607
|
+
- Managed items (`CLAUDE.md`, `rules`, `agents`, `skills`, `hooks`, statusline files, `.luna.json`)
|
|
608
|
+
- Copied files (`settings.json`, `.env`, `.ck.json`, `.ckignore`, etc.)
|
|
609
|
+
- Copied directories (`commands`, `plugins`, `workflows`, `scripts`, ...)
|
|
610
|
+
|
|
611
|
+
Runtime/account continuity stays isolated per runtime root and is not globally swapped.
|
|
533
612
|
|
|
534
613
|
## Safety Features
|
|
535
614
|
|
|
@@ -561,7 +640,7 @@ Every profile switch creates a timestamped backup:
|
|
|
561
640
|
└── ...
|
|
562
641
|
```
|
|
563
642
|
|
|
564
|
-
Backups are
|
|
643
|
+
Backups are pruned automatically; CSP keeps only the 2 most recent backups. You can manually restore by copying from backup directory.
|
|
565
644
|
|
|
566
645
|
### Claude Process Detection
|
|
567
646
|
|
|
@@ -573,14 +652,19 @@ When switching profiles, CSP detects if Claude Code is running:
|
|
|
573
652
|
|
|
574
653
|
**Important:** Changes only take effect after restarting Claude Code.
|
|
575
654
|
|
|
655
|
+
### Windows Support
|
|
656
|
+
|
|
657
|
+
Process detection uses `tasklist` instead of `pgrep` on Windows.
|
|
658
|
+
|
|
659
|
+
Export/import commands use the built-in `tar.exe` available on Windows 10+.
|
|
660
|
+
|
|
576
661
|
### Validation
|
|
577
662
|
|
|
578
663
|
Before switching, CSP validates:
|
|
579
664
|
1. Target profile exists
|
|
580
665
|
2. Profile structure is valid
|
|
581
|
-
3. (Optional with `--force`) Symlink targets are accessible
|
|
582
666
|
|
|
583
|
-
|
|
667
|
+
`--force` is currently accepted for compatibility:
|
|
584
668
|
|
|
585
669
|
```bash
|
|
586
670
|
csp use legacy --force
|
|
@@ -728,15 +812,11 @@ csp delete default
|
|
|
728
812
|
rm ~/.claude-profiles/.lock
|
|
729
813
|
```
|
|
730
814
|
|
|
731
|
-
###
|
|
732
|
-
|
|
733
|
-
**Warning:** `Some symlink targets are missing: rules/development-rules.md — missing target`
|
|
815
|
+
### Invalid profile structure
|
|
734
816
|
|
|
735
|
-
**
|
|
817
|
+
**Error:** `Profile "name" is invalid: Missing source.json — no managed items defined`
|
|
736
818
|
|
|
737
|
-
|
|
738
|
-
csp use production --force
|
|
739
|
-
```
|
|
819
|
+
**Solution:** Recreate the profile or import a valid profile archive.
|
|
740
820
|
|
|
741
821
|
### Changes not applying
|
|
742
822
|
|
|
@@ -781,10 +861,15 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and migration guidance.
|
|
|
781
861
|
│ │ ├── delete.js
|
|
782
862
|
│ │ ├── export.js
|
|
783
863
|
│ │ ├── import.js
|
|
784
|
-
│ │
|
|
864
|
+
│ │ ├── diff.js
|
|
865
|
+
│ │ ├── launch.js
|
|
866
|
+
│ │ ├── deactivate.js
|
|
867
|
+
│ │ └── uninstall.js
|
|
785
868
|
│ ├── constants.js # Configuration constants
|
|
869
|
+
│ ├── platform.js # Cross-platform compatibility
|
|
786
870
|
│ ├── profile-store.js # Profile metadata management
|
|
787
|
-
│ ├──
|
|
871
|
+
│ ├── runtime-instance-manager.js # Isolated runtime sync
|
|
872
|
+
│ ├── item-manager.js # Managed item copy/move operations
|
|
788
873
|
│ ├── file-operations.js # File copy/restore operations
|
|
789
874
|
│ ├── safety.js # Locking, backups, validation
|
|
790
875
|
│ ├── profile-validator.js # Profile validation
|
package/bin/csp.js
CHANGED
|
@@ -16,6 +16,11 @@ 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';
|
|
20
|
+
import { deactivateCommand } from '../src/commands/deactivate.js';
|
|
21
|
+
import { toggleCommand } from '../src/commands/toggle.js';
|
|
22
|
+
import { statusCommand } from '../src/commands/status.js';
|
|
23
|
+
import { selectCommand } from '../src/commands/select.js';
|
|
19
24
|
|
|
20
25
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
26
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
|
|
@@ -25,7 +30,13 @@ const program = new Command();
|
|
|
25
30
|
program
|
|
26
31
|
.name('csp')
|
|
27
32
|
.description('Claude Switch Profile — manage multiple Claude Code configurations')
|
|
28
|
-
.version(pkg.version)
|
|
33
|
+
.version(pkg.version)
|
|
34
|
+
.enablePositionalOptions();
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.command('select', { isDefault: true })
|
|
38
|
+
.description('Interactive profile selector (default when no command given)')
|
|
39
|
+
.action(selectCommand);
|
|
29
40
|
|
|
30
41
|
program
|
|
31
42
|
.command('init')
|
|
@@ -34,7 +45,7 @@ program
|
|
|
34
45
|
|
|
35
46
|
program
|
|
36
47
|
.command('current')
|
|
37
|
-
.description('Show the active profile')
|
|
48
|
+
.description('Show the active legacy profile and isolated launch metadata')
|
|
38
49
|
.action(currentCommand);
|
|
39
50
|
|
|
40
51
|
program
|
|
@@ -43,6 +54,11 @@ program
|
|
|
43
54
|
.description('List all profiles')
|
|
44
55
|
.action(listCommand);
|
|
45
56
|
|
|
57
|
+
program
|
|
58
|
+
.command('status')
|
|
59
|
+
.description('Show CSP status dashboard')
|
|
60
|
+
.action(statusCommand);
|
|
61
|
+
|
|
46
62
|
program
|
|
47
63
|
.command('create <name>')
|
|
48
64
|
.description('Create a new profile from current Claude Code state')
|
|
@@ -61,9 +77,13 @@ program
|
|
|
61
77
|
.description('Switch to a different profile')
|
|
62
78
|
.option('--dry-run', 'Show what would change without executing')
|
|
63
79
|
.option('--no-save', 'Skip saving current profile before switching')
|
|
64
|
-
.option('--force', 'Switch even if symlink targets are missing')
|
|
65
80
|
.action(useCommand);
|
|
66
81
|
|
|
82
|
+
program
|
|
83
|
+
.command('toggle')
|
|
84
|
+
.description('Switch to the previous profile')
|
|
85
|
+
.action(toggleCommand);
|
|
86
|
+
|
|
67
87
|
program
|
|
68
88
|
.command('delete <name>')
|
|
69
89
|
.alias('rm')
|
|
@@ -89,6 +109,27 @@ program
|
|
|
89
109
|
.description('Compare two profiles (use "current" for active profile)')
|
|
90
110
|
.action(diffCommand);
|
|
91
111
|
|
|
112
|
+
program
|
|
113
|
+
.command('deactivate')
|
|
114
|
+
.description('Deactivate the current profile (remove managed items from ~/.claude)')
|
|
115
|
+
.option('--no-save', 'Skip saving current profile before deactivating')
|
|
116
|
+
.action(deactivateCommand);
|
|
117
|
+
|
|
118
|
+
program
|
|
119
|
+
.command('launch <name> [args...]')
|
|
120
|
+
.alias('la')
|
|
121
|
+
.description('Launch isolated Claude session for a profile (use --legacy-global for old switch+launch behavior)')
|
|
122
|
+
.option('--legacy-global', 'Use legacy global switch path before launching Claude')
|
|
123
|
+
.allowUnknownOption(true)
|
|
124
|
+
.enablePositionalOptions(true)
|
|
125
|
+
.passThroughOptions(true)
|
|
126
|
+
.action((name, args, options, cmd) => {
|
|
127
|
+
// Merge explicit positional args with unknown options (e.g. --dangerously-skip-permissions)
|
|
128
|
+
const unknownOpts = cmd.args.filter((a) => a !== name && !args.includes(a));
|
|
129
|
+
const claudeArgs = [...args, ...unknownOpts];
|
|
130
|
+
launchCommand(name, claudeArgs, options);
|
|
131
|
+
});
|
|
132
|
+
|
|
92
133
|
program
|
|
93
134
|
.command('uninstall')
|
|
94
135
|
.description('Remove all profiles and restore Claude Code to pre-CSP state')
|
package/package.json
CHANGED
package/src/commands/create.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { mkdirSync, cpSync, existsSync, statSync, writeFileSync,
|
|
1
|
+
import { mkdirSync, cpSync, existsSync, statSync, writeFileSync, copyFileSync, readdirSync, lstatSync } from 'node:fs';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
3
|
import { addProfile, getActive, setActive, profileExists, getProfileDir } from '../profile-store.js';
|
|
4
|
-
import { saveFiles } from '../file-operations.js';
|
|
5
|
-
import { CLAUDE_DIR,
|
|
4
|
+
import { saveFiles, updateSettingsPaths } from '../file-operations.js';
|
|
5
|
+
import { CLAUDE_DIR, MANAGED_ITEMS, MANAGED_DIRS, SOURCE_FILE, NEVER_CLONE } from '../constants.js';
|
|
6
6
|
import { success, error, info, warn } from '../output-helpers.js';
|
|
7
7
|
|
|
8
8
|
export const createCommand = (name, options) => {
|
|
@@ -32,28 +32,36 @@ export const createCommand = (name, options) => {
|
|
|
32
32
|
|
|
33
33
|
mkdirSync(profileDir, { recursive: true });
|
|
34
34
|
|
|
35
|
-
//
|
|
35
|
+
// Copy items from source kit into profile dir
|
|
36
36
|
const sourceMap = {};
|
|
37
|
-
for (const item of
|
|
37
|
+
for (const item of MANAGED_ITEMS) {
|
|
38
38
|
const target = join(sourcePath, item);
|
|
39
39
|
if (existsSync(target)) {
|
|
40
|
-
|
|
40
|
+
const dest = join(profileDir, item);
|
|
41
|
+
try {
|
|
42
|
+
if (statSync(target).isDirectory()) {
|
|
43
|
+
cpSync(target, dest, { recursive: true });
|
|
44
|
+
} else {
|
|
45
|
+
copyFileSync(target, dest);
|
|
46
|
+
}
|
|
47
|
+
sourceMap[item] = dest;
|
|
48
|
+
} catch { /* skip */ }
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
if (Object.keys(sourceMap).length === 0) {
|
|
45
|
-
warn(`No recognized items found in "${sourcePath}". Expected: ${
|
|
53
|
+
warn(`No recognized items found in "${sourcePath}". Expected: ${MANAGED_ITEMS.join(', ')}`);
|
|
46
54
|
}
|
|
47
55
|
|
|
48
|
-
// Inherit missing items from current state
|
|
49
|
-
for (const item of
|
|
56
|
+
// Inherit missing items from current ~/.claude state
|
|
57
|
+
for (const item of MANAGED_ITEMS) {
|
|
50
58
|
if (sourceMap[item]) continue;
|
|
51
59
|
const src = join(CLAUDE_DIR, item);
|
|
52
60
|
if (!existsSync(src)) continue;
|
|
53
61
|
try {
|
|
54
62
|
const dest = join(profileDir, item);
|
|
55
63
|
if (statSync(src).isDirectory()) {
|
|
56
|
-
cpSync(src, dest, { recursive: true
|
|
64
|
+
cpSync(src, dest, { recursive: true });
|
|
57
65
|
} else {
|
|
58
66
|
copyFileSync(src, dest);
|
|
59
67
|
}
|
|
@@ -62,33 +70,48 @@ export const createCommand = (name, options) => {
|
|
|
62
70
|
}
|
|
63
71
|
|
|
64
72
|
writeFileSync(join(profileDir, SOURCE_FILE), JSON.stringify(sourceMap, null, 2) + '\n');
|
|
65
|
-
info(`
|
|
73
|
+
info(`Copied from kit at ${sourcePath}`);
|
|
66
74
|
info(`Items found: ${Object.keys(sourceMap).join(', ') || 'none'}`);
|
|
67
75
|
|
|
68
76
|
// Also copy current mutable files
|
|
69
77
|
saveFiles(profileDir);
|
|
70
78
|
} else {
|
|
71
|
-
// Create new
|
|
79
|
+
// Create new profile — full clone of ~/.claude/ (blacklist approach)
|
|
72
80
|
mkdirSync(profileDir, { recursive: true });
|
|
73
81
|
|
|
74
|
-
// Copy all current symlink items (dirs + files) into profile — dereference symlinks
|
|
75
82
|
const sourceMap = {};
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
let entries;
|
|
84
|
+
try {
|
|
85
|
+
entries = readdirSync(CLAUDE_DIR);
|
|
86
|
+
} catch {
|
|
87
|
+
entries = [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
if (NEVER_CLONE.includes(entry)) continue;
|
|
92
|
+
|
|
93
|
+
const src = join(CLAUDE_DIR, entry);
|
|
79
94
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
95
|
+
const stat = lstatSync(src);
|
|
96
|
+
|
|
97
|
+
if (stat.isDirectory()) {
|
|
98
|
+
const dest = join(profileDir, entry);
|
|
99
|
+
cpSync(src, dest, { recursive: true });
|
|
100
|
+
if (MANAGED_ITEMS.includes(entry)) {
|
|
101
|
+
sourceMap[entry] = dest;
|
|
102
|
+
}
|
|
103
|
+
} else if (stat.isFile()) {
|
|
104
|
+
const dest = join(profileDir, entry);
|
|
84
105
|
copyFileSync(src, dest);
|
|
106
|
+
if (MANAGED_ITEMS.includes(entry)) {
|
|
107
|
+
sourceMap[entry] = dest;
|
|
108
|
+
}
|
|
85
109
|
}
|
|
86
|
-
sourceMap[item] = dest;
|
|
87
110
|
} catch { /* skip unreadable items */ }
|
|
88
111
|
}
|
|
89
112
|
|
|
90
|
-
//
|
|
91
|
-
for (const item of
|
|
113
|
+
// Ensure empty dirs for any missing MANAGED_DIRS
|
|
114
|
+
for (const item of MANAGED_DIRS) {
|
|
92
115
|
if (!sourceMap[item]) {
|
|
93
116
|
const itemDir = join(profileDir, item);
|
|
94
117
|
mkdirSync(itemDir, { recursive: true });
|
|
@@ -98,21 +121,13 @@ export const createCommand = (name, options) => {
|
|
|
98
121
|
|
|
99
122
|
writeFileSync(join(profileDir, SOURCE_FILE), JSON.stringify(sourceMap, null, 2) + '\n');
|
|
100
123
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
if (existsSync(settingsPath)) {
|
|
104
|
-
try {
|
|
105
|
-
const settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
106
|
-
if (settings.hooks) {
|
|
107
|
-
writeFileSync(join(profileDir, 'settings.json'), JSON.stringify({ hooks: settings.hooks }, null, 2) + '\n');
|
|
108
|
-
}
|
|
109
|
-
} catch { /* parse error — skip */ }
|
|
110
|
-
}
|
|
124
|
+
// Update absolute paths in settings.json
|
|
125
|
+
updateSettingsPaths(profileDir, 'save');
|
|
111
126
|
|
|
112
|
-
info('Created new
|
|
127
|
+
info('Created new profile (full clone of current state)');
|
|
113
128
|
}
|
|
114
129
|
|
|
115
|
-
addProfile(name, { description: options.description || '' });
|
|
130
|
+
addProfile(name, { description: options.description || '', mode: 'account-session' });
|
|
116
131
|
|
|
117
132
|
// Set as active if first profile
|
|
118
133
|
if (!getActive()) {
|
package/src/commands/current.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { getActive } from '../profile-store.js';
|
|
2
|
-
import { getProfileDir } from '../profile-store.js';
|
|
1
|
+
import { getActive, getProfileDir, getProfileMeta } from '../profile-store.js';
|
|
3
2
|
import { success, info, warn } from '../output-helpers.js';
|
|
4
3
|
|
|
5
4
|
export const currentCommand = () => {
|
|
@@ -8,6 +7,12 @@ export const currentCommand = () => {
|
|
|
8
7
|
warn('No active profile. Run "csp create <name>" to create one.');
|
|
9
8
|
return;
|
|
10
9
|
}
|
|
11
|
-
success(`Active profile: ${active}`);
|
|
10
|
+
success(`Active legacy profile: ${active}`);
|
|
12
11
|
info(`Location: ${getProfileDir(active)}`);
|
|
12
|
+
|
|
13
|
+
const meta = getProfileMeta(active);
|
|
14
|
+
if (meta?.lastLaunchAt) {
|
|
15
|
+
info(`Last isolated launch: ${meta.lastLaunchAt}`);
|
|
16
|
+
if (meta.runtimeDir) info(`Isolated runtime: ${meta.runtimeDir}`);
|
|
17
|
+
}
|
|
13
18
|
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getActive, setActive } from '../profile-store.js';
|
|
2
|
+
import { success, info } from '../output-helpers.js';
|
|
3
|
+
import { DEFAULT_PROFILE } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
export const deactivateCommand = async () => {
|
|
6
|
+
const active = getActive();
|
|
7
|
+
if (!active) {
|
|
8
|
+
info('No active profile to deactivate.');
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (active === DEFAULT_PROFILE) {
|
|
13
|
+
info('Default profile uses ~/.claude directly. Nothing to deactivate.');
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Just reset to default — never touch ~/.claude
|
|
18
|
+
setActive(DEFAULT_PROFILE);
|
|
19
|
+
|
|
20
|
+
success(`Profile "${active}" deactivated. Reset to default.`);
|
|
21
|
+
info('~/.claude was not modified.');
|
|
22
|
+
};
|