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.
@@ -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
  [![Node.js >= 18](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen)](https://nodejs.org)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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/` containing your current configuration.
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. Uninstall CSP
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
- - Otherwise, captures current `~/.claude` state
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's `source.json` and file copies
234
- - Useful after modifying rules, settings, or other configuration
235
- - Requires active profile (run `csp create` if none exists)
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 state
278
- 4. Removes managed items/files from `~/.claude` as needed
279
- 5. Restores target profile configuration into `~/.claude`
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. **Important:** Claude Code session must be restarted for changes to apply
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 the active profile (must switch first)
365
+ - Cannot delete `default`
312
366
  - Prompts for confirmation unless `--force` is used
313
367
  - Permanently removes profile directory and metadata
314
- - Cannot be undone (but auto-backups from `use` are preserved)
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 entire profile directory
344
- - Includes `source.json` (managed item map) and copied profile files/directories
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 and clear `.active` without switching to another 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. Optionally saves active profile state
437
- 3. Removes managed items/files from `~/.claude`
438
- 4. Clears active marker
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. 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`
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 key-level launch diagnostics (sources only, values are never printed).
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 (or `--profile` choice) to `~/.claude`
524
- 3. Removes `~/.claude-profiles/` entirely
525
- 4. Prints reminder to run `npm uninstall -g claude-switch-profile`
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
- `--force` is currently accepted for compatibility:
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 current -d "Testing new Luna skills"
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 active profile
870
+ ### Cannot delete default profile
795
871
 
796
- **Error:** `Cannot delete active profile "default". Switch to another profile first.`
872
+ **Error:** `Cannot delete the default profile.`
797
873
 
798
- **Solution:** Switch to a different profile before deleting.
874
+ **Solution:** Keep `default` and delete only non-default profiles.
799
875
 
800
876
  ```bash
801
- csp use production
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-switch-profile",
3
- "version": "1.4.0",
3
+ "version": "1.4.3",
4
4
  "description": "CLI tool for managing multiple Claude Code profiles",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- import { mkdirSync, cpSync, existsSync, statSync, writeFileSync, copyFileSync, readdirSync, lstatSync } from 'node:fs';
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
- if (statSync(target).isDirectory()) {
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
- if (statSync(src).isDirectory()) {
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 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);
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
  }
@@ -1,5 +1,6 @@
1
- import { getActive, getProfileDir, getProfileMeta } from '../profile-store.js';
2
- import { success, info, warn } from '../output-helpers.js';
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, setActive } from '../profile-store.js';
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 uses ~/.claude directly. Nothing to deactivate.');
14
+ info('Default profile is already active.');
14
15
  return;
15
16
  }
16
17
 
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.');
18
+ await useCommand(DEFAULT_PROFILE, options);
19
+ success(`Profile "${active}" deactivated.`);
22
20
  };