claude-switch-profile 1.4.0 → 1.4.2

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,25 @@
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.1] - 2026-03-31
13
+
14
+ ### Changed
15
+ - `default` is now a physical profile snapshot created by `csp init` instead of a virtual pass-through.
16
+ - `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.
17
+ - 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.
18
+ - Protected and session/runtime files remain excluded from snapshot capture, export, isolated runtime sync, and restore flows.
19
+ - Launching `default` keeps its metadata mode as `legacy` instead of drifting to `account-session`.
20
+ - 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).
21
+
22
+ ---
23
+
5
24
  ## [1.3.0] - 2026-03-28
6
25
 
7
26
  ### 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,22 @@ 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.
488
550
 
489
551
  ---
490
552
 
@@ -520,9 +582,10 @@ csp uninstall --force
520
582
 
521
583
  **Behavior:**
522
584
  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`
585
+ 2. Restores the active profile by default, or the `--profile` choice, to `~/.claude`
586
+ 3. If `default` is restored, it uses the physical `default` snapshot like any other profile
587
+ 4. Removes `~/.claude-profiles/` entirely
588
+ 5. Prints reminder to run `npm uninstall -g claude-switch-profile`
526
589
 
527
590
  ---
528
591
 
@@ -561,7 +624,7 @@ Profiles are stored in `~/.claude-profiles/`:
561
624
  - `agents/` — Agent scripts and configurations
562
625
  - `skills/` — Custom Luna skills
563
626
  - `hooks/` — Pre/post action hooks
564
- - `statusline.cjs` — Custom statusline
627
+ - `statusline.cjs`, `statusline.sh`, `statusline.ps1` — Custom statusline scripts
565
628
  - `.luna.json` — Luna configuration
566
629
 
567
630
  **Copied Files**:
@@ -583,7 +646,7 @@ Profiles are stored in `~/.claude-profiles/`:
583
646
  - `history.jsonl`, `metadata.json`, `stats-cache.json`, `active-plan`
584
647
  - `backups/`, `command-archive/`, `commands-archived/`
585
648
  - `plans/`, `todos/`, `tasks/`, `teams/`, `agent-memory/`, `file-history/`, `shell-snapshots/`
586
- - All other session-specific data
649
+ - All other protected or session-specific data
587
650
 
588
651
  ### Legacy vs Isolated Launch Modes
589
652
 
@@ -664,11 +727,7 @@ Before switching, CSP validates:
664
727
  1. Target profile exists
665
728
  2. Profile structure is valid
666
729
 
667
- `--force` is currently accepted for compatibility:
668
-
669
- ```bash
670
- csp use legacy --force
671
- ```
730
+ During import, CSP also validates profile contents and rejects unsafe symlinks that point outside the imported profile tree.
672
731
 
673
732
  ## Configuration via Environment Variables
674
733
 
@@ -727,7 +786,7 @@ csp use personal # Switch to personal
727
786
 
728
787
  ```bash
729
788
  # Clone current profile
730
- csp create experimental --from current -d "Testing new Luna skills"
789
+ csp create experimental --from default -d "Testing new Luna skills"
731
790
 
732
791
  # Switch and experiment
733
792
  csp use experimental
@@ -791,15 +850,14 @@ csp list
791
850
  csp use production # if "production" exists
792
851
  ```
793
852
 
794
- ### Cannot delete active profile
853
+ ### Cannot delete default profile
795
854
 
796
- **Error:** `Cannot delete active profile "default". Switch to another profile first.`
855
+ **Error:** `Cannot delete the default profile.`
797
856
 
798
- **Solution:** Switch to a different profile before deleting.
857
+ **Solution:** Keep `default` and delete only non-default profiles.
799
858
 
800
859
  ```bash
801
- csp use production
802
- csp delete default
860
+ csp delete experimental
803
861
  ```
804
862
 
805
863
  ### Stale lock file
@@ -855,13 +913,16 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and migration guidance.
855
913
  │ │ ├── init.js
856
914
  │ │ ├── current.js
857
915
  │ │ ├── list.js
916
+ │ │ ├── status.js
858
917
  │ │ ├── create.js
859
918
  │ │ ├── save.js
860
919
  │ │ ├── use.js
920
+ │ │ ├── toggle.js
861
921
  │ │ ├── delete.js
862
922
  │ │ ├── export.js
863
923
  │ │ ├── import.js
864
924
  │ │ ├── diff.js
925
+ │ │ ├── select.js
865
926
  │ │ ├── launch.js
866
927
  │ │ ├── deactivate.js
867
928
  │ │ └── uninstall.js
@@ -871,6 +932,7 @@ See [CHANGELOG.md](CHANGELOG.md) for version history and migration guidance.
871
932
  │ ├── runtime-instance-manager.js # Isolated runtime sync
872
933
  │ ├── item-manager.js # Managed item copy/move operations
873
934
  │ ├── file-operations.js # File copy/restore operations
935
+ │ ├── launch-effective-env-resolver.js # ANTHROPIC_* launch env resolution
874
936
  │ ├── safety.js # Locking, backups, validation
875
937
  │ ├── profile-validator.js # Profile validation
876
938
  │ └── output-helpers.js # Console output formatting
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.2",
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
  };
@@ -1,7 +1,7 @@
1
1
  import { readFileSync, existsSync, readdirSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import chalk from 'chalk';
4
- import { profileExists, getActive, getEffectiveDir } from '../profile-store.js';
4
+ import { profileExists, getActive, getEffectiveDir, getProfileDir } from '../profile-store.js';
5
5
  import { SOURCE_FILE, ALL_MANAGED } from '../constants.js';
6
6
  import { error, info } from '../output-helpers.js';
7
7
 
@@ -33,12 +33,14 @@ export const diffCommand = (profileA, profileB) => {
33
33
 
34
34
  const dirA = getEffectiveDir(nameA);
35
35
  const dirB = getEffectiveDir(nameB);
36
+ const sourceDirA = join(getProfileDir(nameA), SOURCE_FILE);
37
+ const sourceDirB = join(getProfileDir(nameB), SOURCE_FILE);
36
38
 
37
39
  console.log(`\n${chalk.bold('Comparing:')} ${chalk.cyan(nameA)} ↔ ${chalk.cyan(nameB)}\n`);
38
40
 
39
- // Compare source.json (managed item sources)
40
- const sourceA = readJsonSafe(join(dirA, SOURCE_FILE));
41
- const sourceB = readJsonSafe(join(dirB, SOURCE_FILE));
41
+ // Compare source.json from stored profile dirs, not live ~/.claude state
42
+ const sourceA = readJsonSafe(sourceDirA);
43
+ const sourceB = readJsonSafe(sourceDirB);
42
44
  diffObject('Managed item sources (source.json)', sourceA, sourceB, nameA, nameB);
43
45
 
44
46
  // Compare files that exist in either profile
@@ -1,6 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { resolve } from 'node:path';
3
- import { profileExists, getProfileDir, getActive } from '../profile-store.js';
3
+ import { profileExists, getProfileDir, getActive, ensureDefaultProfileSnapshot } from '../profile-store.js';
4
4
  import { isWindows } from '../platform.js';
5
5
  import { saveItems } from '../item-manager.js';
6
6
  import { saveFiles, updateSettingsPaths } from '../file-operations.js';
@@ -9,8 +9,12 @@ import { DEFAULT_PROFILE } from '../constants.js';
9
9
 
10
10
  export const exportCommand = (name, options) => {
11
11
  if (name === DEFAULT_PROFILE) {
12
- error('Cannot export the default profile (it uses ~/.claude directly).');
13
- process.exit(1);
12
+ try {
13
+ ensureDefaultProfileSnapshot();
14
+ } catch (err) {
15
+ error(err.message);
16
+ process.exit(1);
17
+ }
14
18
  }
15
19
 
16
20
  if (!profileExists(name)) {
@@ -22,7 +26,6 @@ export const exportCommand = (name, options) => {
22
26
  const output = options.output || `./${name}.csp.tar.gz`;
23
27
  const outputPath = resolve(output);
24
28
 
25
- // If exporting active profile, save a copy first (non-destructive)
26
29
  const active = getActive();
27
30
  if (active === name) {
28
31
  saveItems(profileDir);