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 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
  [![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
- A CLI tool for managing multiple Claude Code configurations and profiles. Effortlessly switch between different development setups, each with their own rules, agents, skills, and settings.
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 (uses symlinks and POSIX tools)
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 symlinks and files from `~/.claude`
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` — Switch even if some symlink targets are missing
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. (Optional) Validates symlink targets exist
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. Creates automatic backup at `~/.claude-profiles/.backup/`
276
- 5. Removes all managed items from `~/.claude`
277
- 6. Restores target profile's symlinks and files
278
- 7. Updates active marker
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 (symlink targets) and all copied files
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
- Symlink targets (source.json): identical
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` (symlink targets)
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 # Symlink targets
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
- **Symlinked Items** (via `source.json`):
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
- - `.ckignore` — Ignore patterns
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
- - `plugins/` — Custom plugins
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
- - `backups/`
506
- - `cache/`, `debug/`, `telemetry/`
507
- - `history.jsonl`
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
- ### Symlink vs. Copy Strategy
588
+ ### Legacy vs Isolated Launch Modes
513
589
 
514
- **Why symlinks for some items?**
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
- **Why copies for others?**
520
- - Settings and env vars are environment-specific
521
- - Each profile needs its own independent configuration
522
- - Prevents accidental modifications from affecting other profiles
592
+ - **Legacy global mode (`csp use`)**
593
+ - Mutates `~/.claude`
594
+ - Updates `.active`
595
+ - Preserves old behavior for existing scripts
523
596
 
524
- ### Real Directory Handling
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
- When `csp save` (or `csp use`) encounters a **real directory/file** in `~/.claude` for a managed item (instead of a symlink), it automatically:
603
+ ### Runtime Sync Policy (isolated launch)
527
604
 
528
- 1. **Moves** the real item into the profile directory (`~/.claude-profiles/<name>/<item>`)
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
- This ensures that profiles created from a fresh `~/.claude` setup (before any symlinks exist) work correctly on first use.
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 kept indefinitely. You can manually restore by copying from backup directory.
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
- Use `--force` to proceed even if validation fails:
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
- ### Symlink targets missing
732
-
733
- **Warning:** `Some symlink targets are missing: rules/development-rules.md — missing target`
815
+ ### Invalid profile structure
734
816
 
735
- **Solution:** Either restore the missing files to their original location or use `--force`:
817
+ **Error:** `Profile "name" is invalid: Missing source.json no managed items defined`
736
818
 
737
- ```bash
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
- │ │ └── diff.js
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
- │ ├── symlink-manager.js # Symlink operations
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-switch-profile",
3
- "version": "1.1.0",
3
+ "version": "1.4.0",
4
4
  "description": "CLI tool for managing multiple Claude Code profiles",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,8 +1,8 @@
1
- import { mkdirSync, cpSync, existsSync, statSync, writeFileSync, readFileSync, copyFileSync } from 'node:fs';
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, SYMLINK_ITEMS, SYMLINK_DIRS, SOURCE_FILE } from '../constants.js';
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
- // Build source.json from the kit directory
35
+ // Copy items from source kit into profile dir
36
36
  const sourceMap = {};
37
- for (const item of SYMLINK_ITEMS) {
37
+ for (const item of MANAGED_ITEMS) {
38
38
  const target = join(sourcePath, item);
39
39
  if (existsSync(target)) {
40
- sourceMap[item] = target;
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: ${SYMLINK_ITEMS.join(', ')}`);
53
+ warn(`No recognized items found in "${sourcePath}". Expected: ${MANAGED_ITEMS.join(', ')}`);
46
54
  }
47
55
 
48
- // Inherit missing items from current state (hooks, statusline, etc.)
49
- for (const item of SYMLINK_ITEMS) {
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, dereference: 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(`Linked to kit at ${sourcePath}`);
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 independent profile — inherit infrastructure from current state
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
- for (const item of SYMLINK_ITEMS) {
77
- const src = join(CLAUDE_DIR, item);
78
- const dest = join(profileDir, item);
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
- if (!existsSync(src)) continue;
81
- if (statSync(src).isDirectory()) {
82
- cpSync(src, dest, { recursive: true, dereference: true });
83
- } else {
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
- // Fallback: ensure empty dirs for any missing SYMLINK_DIRS
91
- for (const item of SYMLINK_DIRS) {
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
- // Copy hooks config from settings.json (hooks won't run without it)
102
- const settingsPath = join(CLAUDE_DIR, 'settings.json');
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 independent profile (infrastructure inherited)');
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()) {
@@ -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
+ };