@kaitranntt/ccs 3.0.1 → 3.1.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/README.md CHANGED
@@ -92,6 +92,16 @@ $env:CCS_CLAUDE_PATH = "D:\Tools\Claude\claude.exe" # Windows
92
92
 
93
93
  **See [Troubleshooting Guide](./docs/en/troubleshooting.md#claude-cli-in-non-standard-location) for detailed setup instructions.**
94
94
 
95
+ ### Windows Symlink Support (Developer Mode)
96
+
97
+ **Windows users**: Enable Developer Mode for true symlinks (better performance, instant sync):
98
+
99
+ 1. Open **Settings** → **Privacy & Security** → **For developers**
100
+ 2. Enable **Developer Mode**
101
+ 3. Reinstall CCS: `npm install -g @kaitranntt/ccs`
102
+
103
+ **Without Developer Mode**: CCS automatically falls back to copying directories (works but no instant sync across profiles).
104
+
95
105
  ---
96
106
 
97
107
  ### Your First Switch
@@ -153,6 +163,44 @@ ccs work-2 # Switch to second company account
153
163
 
154
164
  ---
155
165
 
166
+ ## 📁 Shared Data Architecture
167
+
168
+ **v3.1 Shared Global Data**: Commands and skills are symlinked across all profiles via `~/.ccs/shared/`, eliminating duplication.
169
+
170
+ **Directory Structure**:
171
+ ```
172
+ ~/.ccs/
173
+ ├── shared/ # Shared across all profiles
174
+ │ ├── commands/ # Custom slash commands
175
+ │ └── skills/ # Claude Code skills
176
+ ├── instances/ # Profile-specific data
177
+ │ ├── work/
178
+ │ │ ├── commands@ → ~/.ccs/shared/commands/ # Symlink
179
+ │ │ ├── skills@ → ~/.ccs/shared/skills/ # Symlink
180
+ │ │ ├── settings.json # Profile-specific config
181
+ │ │ └── sessions/ # Profile-specific sessions
182
+ │ └── personal/
183
+ │ └── ...
184
+ ```
185
+
186
+ **Benefits**:
187
+ - No duplication of commands/skills across profiles
188
+ - Single source of truth for shared resources
189
+ - Automatic migration from v3.0 (runs on first use)
190
+ - Windows fallback: copies if symlinks unavailable (enable Developer Mode for true symlinks)
191
+
192
+ **What's Shared**:
193
+ - `.claude/commands/` - Custom slash commands
194
+ - `.claude/skills/` - Claude Code skills
195
+
196
+ **What's Profile-Specific**:
197
+ - `settings.json` - API keys, credentials
198
+ - `sessions/` - Conversation history
199
+ - `todolists/` - Task tracking
200
+ - `logs/` - Profile-specific logs
201
+
202
+ ---
203
+
156
204
  ## 🏗️ Architecture Overview
157
205
 
158
206
  **v3.0 Login-Per-Profile Model**: Each profile is an isolated Claude instance where users login directly. No credential copying or vault encryption.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 3.0.1
1
+ 3.1.0
@@ -39,12 +39,18 @@ class AuthCommands {
39
39
  console.log('');
40
40
  console.log(colored('Examples:', 'cyan'));
41
41
  console.log(` ${colored('ccs auth create work', 'yellow')} # Create & login to work profile`);
42
+ console.log(` ${colored('ccs auth default work', 'yellow')} # Set work as default`);
42
43
  console.log(` ${colored('ccs auth list', 'yellow')} # List all profiles`);
43
44
  console.log(` ${colored('ccs work "review code"', 'yellow')} # Use work profile`);
45
+ console.log(` ${colored('ccs "review code"', 'yellow')} # Use default profile`);
44
46
  console.log('');
45
47
  console.log(colored('Options:', 'cyan'));
46
48
  console.log(` ${colored('--force', 'yellow')} Allow overwriting existing profile`);
47
49
  console.log('');
50
+ console.log(colored('Note:', 'cyan'));
51
+ console.log(` By default, ${colored('ccs', 'yellow')} uses Claude CLI defaults from ~/.claude/`);
52
+ console.log(` Use ${colored('ccs auth default <profile>', 'yellow')} to change the default profile.`);
53
+ console.log('');
48
54
  }
49
55
 
50
56
  /**
@@ -119,7 +125,10 @@ class AuthCommands {
119
125
  console.log(` Instance: ${instancePath}`);
120
126
  console.log('');
121
127
  console.log('Usage:');
122
- console.log(` ${colored(`ccs ${profileName} "your prompt here"`, 'yellow')}`);
128
+ console.log(` ${colored(`ccs ${profileName} "your prompt here"`, 'yellow')} # Use this specific profile`);
129
+ console.log('');
130
+ console.log('To set as default (so you can use just "ccs"):');
131
+ console.log(` ${colored(`ccs auth default ${profileName}`, 'yellow')}`);
123
132
  console.log('');
124
133
  process.exit(0);
125
134
  } else {
package/bin/ccs.js CHANGED
@@ -114,14 +114,9 @@ function handleHelpCommand() {
114
114
 
115
115
  // Account Management
116
116
  console.log(colored('Account Management:', 'cyan'));
117
- console.log(` ${colored('ccs auth create <profile>', 'yellow')} Create new profile and login`);
118
- console.log(` ${colored('ccs auth list', 'yellow')} List all saved profiles`);
119
- console.log(` ${colored('ccs auth show <profile>', 'yellow')} Show profile details`);
120
- console.log(` ${colored('ccs auth remove <profile>', 'yellow')} Remove profile (requires --force)`);
121
- console.log(` ${colored('ccs auth default <profile>', 'yellow')} Set default profile`);
117
+ console.log(` ${colored('ccs auth --help', 'yellow')} Manage multiple Claude accounts`);
122
118
  console.log(` ${colored('ccs work', 'yellow')} Switch to work account`);
123
119
  console.log(` ${colored('ccs personal', 'yellow')} Switch to personal account`);
124
- console.log(` ${colored('ccs work', 'yellow')} "review code" Run command with work account`);
125
120
  console.log('');
126
121
 
127
122
  // Diagnostics
@@ -138,28 +133,18 @@ function handleHelpCommand() {
138
133
  // Configuration
139
134
  console.log(colored('Configuration:', 'cyan'));
140
135
  console.log(' Config File: ~/.ccs/config.json');
136
+ console.log(' Profiles: ~/.ccs/profiles.json');
137
+ console.log(' Instances: ~/.ccs/instances/');
141
138
  console.log(' Settings: ~/.ccs/*.settings.json');
142
139
  console.log(' Environment: CCS_CONFIG (override config path)');
143
140
  console.log('');
144
141
 
145
- // Examples
146
- console.log(colored('Examples:', 'cyan'));
147
- console.log(' # Try without installing');
148
- console.log(` ${colored('npx @kaitranntt/ccs glm', 'yellow')} "write tests"`);
149
- console.log(` ${colored('npx @kaitranntt/ccs kimi', 'yellow')} "write tests"`);
150
- console.log('');
151
- console.log(' # Use default Claude subscription');
152
- console.log(` ${colored('ccs', 'yellow')} "Review this architecture"`);
153
- console.log('');
154
- console.log(' # Switch to GLM for cost-effective tasks');
155
- console.log(` ${colored('ccs glm', 'yellow')} "Write unit tests"`);
156
- console.log('');
157
- console.log(' # Switch to Kimi for alternative option');
158
- console.log(` ${colored('ccs kimi', 'yellow')} "Write integration tests"`);
159
- console.log('');
160
- console.log(' # Use with verbose output');
161
- console.log(` ${colored('ccs glm', 'yellow')} --verbose "Debug error"`);
162
- console.log(` ${colored('ccs kimi', 'yellow')} --verbose "Review code"`);
142
+ // Shared Data
143
+ console.log(colored('Shared Data:', 'cyan'));
144
+ console.log(' Commands: ~/.ccs/shared/commands/');
145
+ console.log(' Skills: ~/.ccs/shared/skills/');
146
+ console.log(' Agents: ~/.ccs/shared/agents/');
147
+ console.log(' Note: Commands, skills, and agents are symlinked across all profiles');
163
148
  console.log('');
164
149
 
165
150
  // Uninstall
@@ -277,6 +262,11 @@ async function main() {
277
262
  recovery.showRecoveryHints();
278
263
  }
279
264
 
265
+ // Run migration to shared structure (Phase 1: idempotent)
266
+ const SharedManager = require('./shared-manager');
267
+ const sharedManager = new SharedManager();
268
+ sharedManager.migrateToSharedStructure();
269
+
280
270
  // Detect profile
281
271
  const { profile, remainingArgs } = detectProfile(args);
282
272
 
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
+ const SharedManager = require('./shared-manager');
6
7
 
7
8
  /**
8
9
  * Instance Manager (Simplified)
@@ -14,6 +15,7 @@ const os = require('os');
14
15
  class InstanceManager {
15
16
  constructor() {
16
17
  this.instancesDir = path.join(os.homedir(), '.ccs', 'instances');
18
+ this.sharedManager = new SharedManager();
17
19
  }
18
20
 
19
21
  /**
@@ -56,7 +58,7 @@ class InstanceManager {
56
58
  // Create base directory
57
59
  fs.mkdirSync(instancePath, { recursive: true, mode: 0o700 });
58
60
 
59
- // Create Claude-expected subdirectories
61
+ // Create Claude-expected subdirectories (profile-specific only)
60
62
  const subdirs = [
61
63
  'session-env',
62
64
  'todos',
@@ -64,9 +66,7 @@ class InstanceManager {
64
66
  'file-history',
65
67
  'shell-snapshots',
66
68
  'debug',
67
- '.anthropic',
68
- 'commands',
69
- 'skills'
69
+ '.anthropic'
70
70
  ];
71
71
 
72
72
  subdirs.forEach(dir => {
@@ -76,7 +76,10 @@ class InstanceManager {
76
76
  }
77
77
  });
78
78
 
79
- // Copy global configs if exist
79
+ // Symlink shared directories (Phase 1: commands, skills)
80
+ this.sharedManager.linkSharedDirectories(instancePath);
81
+
82
+ // Copy global configs if exist (settings.json only)
80
83
  this._copyGlobalConfigs(instancePath);
81
84
  } catch (error) {
82
85
  throw new Error(`Failed to initialize instance for ${profileName}: ${error.message}`);
@@ -158,26 +161,12 @@ class InstanceManager {
158
161
  _copyGlobalConfigs(instancePath) {
159
162
  const globalConfigDir = path.join(os.homedir(), '.claude');
160
163
 
161
- // Copy settings.json if exists
164
+ // Copy settings.json only (commands/skills are now symlinked to shared/)
162
165
  const globalSettings = path.join(globalConfigDir, 'settings.json');
163
166
  if (fs.existsSync(globalSettings)) {
164
167
  const instanceSettings = path.join(instancePath, 'settings.json');
165
168
  fs.copyFileSync(globalSettings, instanceSettings);
166
169
  }
167
-
168
- // Copy commands directory if exists
169
- const globalCommands = path.join(globalConfigDir, 'commands');
170
- if (fs.existsSync(globalCommands)) {
171
- const instanceCommands = path.join(instancePath, 'commands');
172
- this._copyDirectory(globalCommands, instanceCommands);
173
- }
174
-
175
- // Copy skills directory if exists
176
- const globalSkills = path.join(globalConfigDir, 'skills');
177
- if (fs.existsSync(globalSkills)) {
178
- const instanceSkills = path.join(instancePath, 'skills');
179
- this._copyDirectory(globalSkills, instanceSkills);
180
- }
181
170
  }
182
171
 
183
172
  /**
@@ -94,10 +94,9 @@ class ProfileRegistry {
94
94
  last_used: null
95
95
  };
96
96
 
97
- // Set as default if no default exists
98
- if (!data.default) {
99
- data.default = name;
100
- }
97
+ // Note: No longer auto-set as default
98
+ // Users must explicitly run: ccs auth default <profile>
99
+ // Default always stays on implicit 'default' profile (uses ~/.claude/)
101
100
 
102
101
  this._write(data);
103
102
  }
@@ -0,0 +1,164 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ /**
8
+ * SharedManager - Manages symlinked shared directories for CCS
9
+ * Phase 1: Shared Global Data via Symlinks
10
+ *
11
+ * Purpose: Eliminates duplication of commands/skills across profile instances
12
+ * by symlinking to a single ~/.ccs/shared/ directory.
13
+ */
14
+ class SharedManager {
15
+ constructor() {
16
+ this.homeDir = os.homedir();
17
+ this.sharedDir = path.join(this.homeDir, '.ccs', 'shared');
18
+ this.instancesDir = path.join(this.homeDir, '.ccs', 'instances');
19
+ this.sharedDirs = ['commands', 'skills', 'agents'];
20
+ }
21
+
22
+ /**
23
+ * Ensure shared directories exist
24
+ */
25
+ ensureSharedDirectories() {
26
+ // Create shared directory
27
+ if (!fs.existsSync(this.sharedDir)) {
28
+ fs.mkdirSync(this.sharedDir, { recursive: true, mode: 0o700 });
29
+ }
30
+
31
+ // Create shared subdirectories
32
+ for (const dir of this.sharedDirs) {
33
+ const dirPath = path.join(this.sharedDir, dir);
34
+ if (!fs.existsSync(dirPath)) {
35
+ fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Link shared directories to instance
42
+ * @param {string} instancePath - Path to instance directory
43
+ */
44
+ linkSharedDirectories(instancePath) {
45
+ this.ensureSharedDirectories();
46
+
47
+ for (const dir of this.sharedDirs) {
48
+ const linkPath = path.join(instancePath, dir);
49
+ const targetPath = path.join(this.sharedDir, dir);
50
+
51
+ // Remove existing directory/link
52
+ if (fs.existsSync(linkPath)) {
53
+ fs.rmSync(linkPath, { recursive: true, force: true });
54
+ }
55
+
56
+ // Create symlink
57
+ try {
58
+ fs.symlinkSync(targetPath, linkPath, 'dir');
59
+ } catch (err) {
60
+ // Windows fallback: copy directory if symlink fails
61
+ if (process.platform === 'win32') {
62
+ this._copyDirectory(targetPath, linkPath);
63
+ console.log(`[!] Symlink failed for ${dir}, copied instead (enable Developer Mode)`);
64
+ } else {
65
+ throw err;
66
+ }
67
+ }
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Migrate existing instances to shared structure
73
+ * Idempotent: Safe to run multiple times
74
+ */
75
+ migrateToSharedStructure() {
76
+ // Check if migration is needed (shared dirs exist but are empty)
77
+ const needsMigration = !fs.existsSync(this.sharedDir) ||
78
+ this.sharedDirs.every(dir => {
79
+ const dirPath = path.join(this.sharedDir, dir);
80
+ if (!fs.existsSync(dirPath)) return true;
81
+ try {
82
+ const files = fs.readdirSync(dirPath);
83
+ return files.length === 0; // Empty directory needs migration
84
+ } catch (err) {
85
+ return true; // If we can't read it, assume it needs migration
86
+ }
87
+ });
88
+
89
+ if (!needsMigration) {
90
+ return; // Already migrated with content
91
+ }
92
+
93
+ // Create shared directories
94
+ this.ensureSharedDirectories();
95
+
96
+ // Copy from ~/.claude/ (actual Claude CLI directory)
97
+ const claudeDir = path.join(this.homeDir, '.claude');
98
+
99
+ if (fs.existsSync(claudeDir)) {
100
+ // Copy commands to shared (if exists)
101
+ const commandsPath = path.join(claudeDir, 'commands');
102
+ if (fs.existsSync(commandsPath)) {
103
+ this._copyDirectory(commandsPath, path.join(this.sharedDir, 'commands'));
104
+ }
105
+
106
+ // Copy skills to shared (if exists)
107
+ const skillsPath = path.join(claudeDir, 'skills');
108
+ if (fs.existsSync(skillsPath)) {
109
+ this._copyDirectory(skillsPath, path.join(this.sharedDir, 'skills'));
110
+ }
111
+
112
+ // Copy agents to shared (if exists)
113
+ const agentsPath = path.join(claudeDir, 'agents');
114
+ if (fs.existsSync(agentsPath)) {
115
+ this._copyDirectory(agentsPath, path.join(this.sharedDir, 'agents'));
116
+ }
117
+ }
118
+
119
+ // Update all instances to use symlinks
120
+ if (fs.existsSync(this.instancesDir)) {
121
+ const instances = fs.readdirSync(this.instancesDir);
122
+
123
+ for (const instance of instances) {
124
+ const instancePath = path.join(this.instancesDir, instance);
125
+ if (fs.statSync(instancePath).isDirectory()) {
126
+ this.linkSharedDirectories(instancePath);
127
+ }
128
+ }
129
+ }
130
+
131
+ console.log('[OK] Migrated to shared structure');
132
+ }
133
+
134
+ /**
135
+ * Copy directory recursively (fallback for Windows)
136
+ * @param {string} src - Source directory
137
+ * @param {string} dest - Destination directory
138
+ * @private
139
+ */
140
+ _copyDirectory(src, dest) {
141
+ if (!fs.existsSync(src)) {
142
+ return;
143
+ }
144
+
145
+ if (!fs.existsSync(dest)) {
146
+ fs.mkdirSync(dest, { recursive: true });
147
+ }
148
+
149
+ const entries = fs.readdirSync(src, { withFileTypes: true });
150
+
151
+ for (const entry of entries) {
152
+ const srcPath = path.join(src, entry.name);
153
+ const destPath = path.join(dest, entry.name);
154
+
155
+ if (entry.isDirectory()) {
156
+ this._copyDirectory(srcPath, destPath);
157
+ } else {
158
+ fs.copyFileSync(srcPath, destPath);
159
+ }
160
+ }
161
+ }
162
+ }
163
+
164
+ module.exports = SharedManager;
package/lib/ccs CHANGED
@@ -2,7 +2,7 @@
2
2
  set -euo pipefail
3
3
 
4
4
  # Version (updated by scripts/bump-version.sh)
5
- CCS_VERSION="3.0.1"
5
+ CCS_VERSION="3.1.0"
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
7
  readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
8
8
  readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
@@ -55,14 +55,9 @@ show_help() {
55
55
  echo -e " ${YELLOW}ccs glm${RESET} \"debug this code\" Use GLM and run command"
56
56
  echo ""
57
57
  echo -e "${CYAN}Account Management:${RESET}"
58
- echo -e " ${YELLOW}ccs auth create <profile>${RESET} Create new account profile"
59
- echo -e " ${YELLOW}ccs auth list${RESET} List all profiles"
60
- echo -e " ${YELLOW}ccs auth show <profile>${RESET} Show profile details"
61
- echo -e " ${YELLOW}ccs auth remove <profile>${RESET} Remove profile (requires --force)"
62
- echo -e " ${YELLOW}ccs auth default <profile>${RESET} Set default profile"
58
+ echo -e " ${YELLOW}ccs auth --help${RESET} Manage multiple Claude accounts"
63
59
  echo -e " ${YELLOW}ccs work${RESET} Switch to work account"
64
60
  echo -e " ${YELLOW}ccs personal${RESET} Switch to personal account"
65
- echo -e " ${YELLOW}ccs work${RESET} \"review code\" Run command with work account"
66
61
  echo ""
67
62
  echo -e "${CYAN}Diagnostics:${RESET}"
68
63
  echo -e " ${YELLOW}ccs doctor${RESET} Run health check and diagnostics"
@@ -72,19 +67,15 @@ show_help() {
72
67
  echo -e " ${YELLOW}-v, --version${RESET} Show version and installation info"
73
68
  echo ""
74
69
  echo -e "${CYAN}Configuration:${RESET}"
75
- echo -e " Config: ~/.ccs/config.json"
76
- echo -e " Profiles: ~/.ccs/profiles.json"
77
- echo -e " Settings: ~/.ccs/*.settings.json"
70
+ echo -e " Config: ~/.ccs/config.json"
71
+ echo -e " Profiles: ~/.ccs/profiles.json"
72
+ echo -e " Instances: ~/.ccs/instances/"
73
+ echo -e " Settings: ~/.ccs/*.settings.json"
78
74
  echo ""
79
- echo -e "${CYAN}Examples:${RESET}"
80
- echo -e " # Create work account profile"
81
- echo -e " ${YELLOW}ccs auth create work${RESET}"
82
- echo ""
83
- echo -e " # Use work account"
84
- echo -e " ${YELLOW}ccs work${RESET} \"Review architecture\""
85
- echo ""
86
- echo -e " # Switch to GLM for cost-effective tasks"
87
- echo -e " ${YELLOW}ccs glm${RESET} \"Write unit tests\""
75
+ echo -e "${CYAN}Shared Data:${RESET}"
76
+ echo -e " Commands: ~/.ccs/shared/commands/"
77
+ echo -e " Skills: ~/.ccs/shared/skills/"
78
+ echo -e " Note: Commands, skills, and agents are symlinked across all profiles"
88
79
  echo ""
89
80
  echo -e "${CYAN}Documentation:${RESET}"
90
81
  echo -e " GitHub: ${CYAN}https://github.com/kaitranntt/ccs${RESET}"
@@ -428,11 +419,9 @@ register_profile() {
428
419
  "last_used": null
429
420
  }')
430
421
 
431
- # Set as default if none exists
432
- local has_default=$(echo "$data" | jq -r '.default // empty')
433
- if [[ -z "$has_default" ]]; then
434
- data=$(echo "$data" | jq --arg name "$profile_name" '.default = $name')
435
- fi
422
+ # Note: No longer auto-set as default
423
+ # Users must explicitly run: ccs auth default <profile>
424
+ # Default always stays on implicit 'default' profile (uses ~/.claude/)
436
425
 
437
426
  write_profiles_json "$data"
438
427
  }
@@ -510,6 +499,73 @@ sanitize_profile_name() {
510
499
  echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/-/g'
511
500
  }
512
501
 
502
+ # Link shared directories (Phase 1: Shared Global Data)
503
+ link_shared_directories() {
504
+ local instance_path="$1"
505
+ local shared_dir="$HOME/.ccs/shared"
506
+
507
+ # Ensure shared directories exist
508
+ mkdir -p "$shared_dir"/{commands,skills,agents}
509
+
510
+ # Create symlinks (remove existing first if present)
511
+ for dir in commands skills agents; do
512
+ local link_path="$instance_path/$dir"
513
+ local target_path="$shared_dir/$dir"
514
+
515
+ # Remove existing directory/link
516
+ [[ -e "$link_path" ]] && rm -rf "$link_path"
517
+
518
+ # Create symlink
519
+ ln -sf "$target_path" "$link_path"
520
+ done
521
+ }
522
+
523
+ # Migrate to shared structure (Phase 1: Auto-migration)
524
+ migrate_to_shared_structure() {
525
+ local shared_dir="$HOME/.ccs/shared"
526
+
527
+ # Check if migration is needed (shared dirs exist but are empty)
528
+ if [[ -d "$shared_dir" ]]; then
529
+ local needs_migration=false
530
+ for dir in commands skills agents; do
531
+ if [[ ! -d "$shared_dir/$dir" ]] || [[ -z "$(ls -A "$shared_dir/$dir" 2>/dev/null)" ]]; then
532
+ needs_migration=true
533
+ break
534
+ fi
535
+ done
536
+
537
+ [[ "$needs_migration" == "false" ]] && return 0
538
+ fi
539
+
540
+ # Create shared directory
541
+ mkdir -p "$shared_dir"/{commands,skills,agents}
542
+
543
+ # Copy from ~/.claude/ (actual Claude CLI directory)
544
+ local claude_dir="$HOME/.claude"
545
+
546
+ if [[ -d "$claude_dir" ]]; then
547
+ # Copy commands to shared (if exists)
548
+ [[ -d "$claude_dir/commands" ]] && \
549
+ cp -r "$claude_dir/commands"/* "$shared_dir/commands/" 2>/dev/null || true
550
+
551
+ # Copy skills to shared (if exists)
552
+ [[ -d "$claude_dir/skills" ]] && \
553
+ cp -r "$claude_dir/skills"/* "$shared_dir/skills/" 2>/dev/null || true
554
+
555
+ # Copy agents to shared (if exists)
556
+ [[ -d "$claude_dir/agents" ]] && \
557
+ cp -r "$claude_dir/agents"/* "$shared_dir/agents/" 2>/dev/null || true
558
+ fi
559
+
560
+ # Update all instances to use symlinks
561
+ for instance_path in "$INSTANCES_DIR"/*; do
562
+ [[ -d "$instance_path" ]] || continue
563
+ link_shared_directories "$instance_path"
564
+ done
565
+
566
+ echo "[OK] Migrated to shared structure"
567
+ }
568
+
513
569
  # Initialize new instance directory
514
570
  initialize_instance() {
515
571
  local instance_path="$1"
@@ -517,12 +573,15 @@ initialize_instance() {
517
573
  # Create base directory
518
574
  mkdir -m 0700 -p "$instance_path"
519
575
 
520
- # Create subdirectories
521
- local subdirs=(session-env todos logs file-history shell-snapshots debug .anthropic commands skills)
576
+ # Create subdirectories (profile-specific only)
577
+ local subdirs=(session-env todos logs file-history shell-snapshots debug .anthropic)
522
578
  for dir in "${subdirs[@]}"; do
523
579
  mkdir -m 0700 -p "$instance_path/$dir"
524
580
  done
525
581
 
582
+ # Symlink shared directories
583
+ link_shared_directories "$instance_path"
584
+
526
585
  # Copy global configs (optional)
527
586
  copy_global_configs "$instance_path"
528
587
  }
@@ -544,17 +603,9 @@ copy_global_configs() {
544
603
  local instance_path="$1"
545
604
  local global_claude="$HOME/.claude"
546
605
 
547
- # Copy settings.json
606
+ # Copy settings.json only (commands/skills are now symlinked to shared/)
548
607
  [[ -f "$global_claude/settings.json" ]] && \
549
608
  cp "$global_claude/settings.json" "$instance_path/settings.json" 2>/dev/null || true
550
-
551
- # Copy commands/
552
- [[ -d "$global_claude/commands" ]] && \
553
- cp -r "$global_claude/commands" "$instance_path/" 2>/dev/null || true
554
-
555
- # Copy skills/
556
- [[ -d "$global_claude/skills" ]] && \
557
- cp -r "$global_claude/skills" "$instance_path/" 2>/dev/null || true
558
609
  }
559
610
 
560
611
  # Ensure instance exists (lazy initialization)
@@ -687,9 +738,15 @@ auth_help() {
687
738
  echo -e " ${YELLOW}default <profile>${RESET} Set default profile"
688
739
  echo ""
689
740
  echo -e "${CYAN}Examples:${RESET}"
690
- echo -e " ${YELLOW}ccs auth create work${RESET}"
691
- echo -e " ${YELLOW}ccs auth list${RESET}"
692
- echo -e " ${YELLOW}ccs auth remove work --force${RESET}"
741
+ echo -e " ${YELLOW}ccs auth create work${RESET} # Create & login to work profile"
742
+ echo -e " ${YELLOW}ccs auth default work${RESET} # Set work as default"
743
+ echo -e " ${YELLOW}ccs auth list${RESET} # List all profiles"
744
+ echo -e " ${YELLOW}ccs work \"review code\"${RESET} # Use work profile"
745
+ echo -e " ${YELLOW}ccs \"review code\"${RESET} # Use default profile"
746
+ echo ""
747
+ echo -e "${CYAN}Note:${RESET}"
748
+ echo -e " By default, ${YELLOW}ccs${RESET} uses Claude CLI defaults from ~/.claude/"
749
+ echo -e " Use ${YELLOW}ccs auth default <profile>${RESET} to change the default profile."
693
750
  echo ""
694
751
  }
695
752
 
@@ -749,7 +806,10 @@ auth_create() {
749
806
  echo " Instance: $instance_path"
750
807
  echo ""
751
808
  echo "Usage:"
752
- echo " ${YELLOW}ccs $profile_name \"your prompt here\"${RESET}"
809
+ echo " ${YELLOW}ccs $profile_name \"your prompt here\"${RESET} # Use this specific profile"
810
+ echo ""
811
+ echo "To set as default (so you can use just \"ccs\"):"
812
+ echo " ${YELLOW}ccs auth default $profile_name${RESET}"
753
813
  echo ""
754
814
  }
755
815
 
@@ -950,6 +1010,9 @@ auto_recover || {
950
1010
  exit 1
951
1011
  }
952
1012
 
1013
+ # Run migration to shared structure (Phase 1: idempotent)
1014
+ migrate_to_shared_structure
1015
+
953
1016
  # Smart profile detection: if first arg starts with '-', it's a flag not a profile
954
1017
  if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
955
1018
  # No args or first arg is a flag → use default profile
package/lib/ccs.ps1 CHANGED
@@ -12,7 +12,7 @@ param(
12
12
  $ErrorActionPreference = "Stop"
13
13
 
14
14
  # Version (updated by scripts/bump-version.sh)
15
- $CcsVersion = "3.0.1"
15
+ $CcsVersion = "3.1.0"
16
16
  $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
17
17
  $ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
18
18
  $ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
@@ -111,14 +111,9 @@ function Show-Help {
111
111
  Write-ColorLine " ccs glm 'debug this code' Use GLM and run command" "Yellow"
112
112
  Write-Host ""
113
113
  Write-ColorLine "Account Management:" "Cyan"
114
- Write-ColorLine " ccs auth create <profile> Create new account profile" "Yellow"
115
- Write-ColorLine " ccs auth list List all profiles" "Yellow"
116
- Write-ColorLine " ccs auth show <profile> Show profile details" "Yellow"
117
- Write-ColorLine " ccs auth remove <profile> Remove profile (requires --force)" "Yellow"
118
- Write-ColorLine " ccs auth default <profile> Set default profile" "Yellow"
114
+ Write-ColorLine " ccs auth --help Manage multiple Claude accounts" "Yellow"
119
115
  Write-ColorLine " ccs work Switch to work account" "Yellow"
120
116
  Write-ColorLine " ccs personal Switch to personal account" "Yellow"
121
- Write-ColorLine " ccs work 'review code' Run command with work account" "Yellow"
122
117
  Write-Host ""
123
118
  Write-ColorLine "Diagnostics:" "Cyan"
124
119
  Write-ColorLine " ccs doctor Run health check and diagnostics" "Yellow"
@@ -128,19 +123,15 @@ function Show-Help {
128
123
  Write-ColorLine " -v, --version Show version and installation info" "Yellow"
129
124
  Write-Host ""
130
125
  Write-ColorLine "Configuration:" "Cyan"
131
- Write-Host " Config: ~/.ccs/config.json"
132
- Write-Host " Profiles: ~/.ccs/profiles.json"
133
- Write-Host " Settings: ~/.ccs/*.settings.json"
126
+ Write-Host " Config: ~/.ccs/config.json"
127
+ Write-Host " Profiles: ~/.ccs/profiles.json"
128
+ Write-Host " Instances: ~/.ccs/instances/"
129
+ Write-Host " Settings: ~/.ccs/*.settings.json"
134
130
  Write-Host ""
135
- Write-ColorLine "Examples:" "Cyan"
136
- Write-Host " # Create work account profile"
137
- Write-ColorLine " ccs auth create work" "Yellow"
138
- Write-Host ""
139
- Write-Host " # Use work account"
140
- Write-ColorLine " ccs work 'Review architecture'" "Yellow"
141
- Write-Host ""
142
- Write-Host " # Switch to GLM for cost-effective tasks"
143
- Write-ColorLine " ccs glm 'Write unit tests'" "Yellow"
131
+ Write-ColorLine "Shared Data:" "Cyan"
132
+ Write-Host " Commands: ~/.ccs/shared/commands/"
133
+ Write-Host " Skills: ~/.ccs/shared/skills/"
134
+ Write-Host " Note: Commands, skills, and agents are symlinked across all profiles"
144
135
  Write-Host ""
145
136
  Write-ColorLine "Documentation:" "Cyan"
146
137
  Write-Host " GitHub: https://github.com/kaitranntt/ccs"
@@ -350,10 +341,9 @@ function Register-Profile {
350
341
  last_used = $null
351
342
  })
352
343
 
353
- # Set as default if none exists
354
- if (-not $Data.default) {
355
- $Data.default = $ProfileName
356
- }
344
+ # Note: No longer auto-set as default
345
+ # Users must explicitly run: ccs auth default <profile>
346
+ # Default always stays on implicit 'default' profile (uses ~/.claude/)
357
347
 
358
348
  Write-ProfilesJson $Data
359
349
  }
@@ -438,28 +428,106 @@ function Set-InstancePermissions {
438
428
  Set-Acl -Path $Path -AclObject $Acl
439
429
  }
440
430
 
431
+ function Link-SharedDirectories {
432
+ param([string]$InstancePath)
433
+
434
+ $SharedDir = "$env:USERPROFILE\.ccs\shared"
435
+
436
+ # Ensure shared directories exist
437
+ @('commands', 'skills', 'agents') | ForEach-Object {
438
+ $Dir = Join-Path $SharedDir $_
439
+ if (-not (Test-Path $Dir)) {
440
+ New-Item -ItemType Directory -Path $Dir -Force | Out-Null
441
+ }
442
+ }
443
+
444
+ # Create symlinks (requires Windows Developer Mode or admin)
445
+ @('commands', 'skills', 'agents') | ForEach-Object {
446
+ $LinkPath = Join-Path $InstancePath $_
447
+ $TargetPath = Join-Path $SharedDir $_
448
+
449
+ # Remove existing directory/link
450
+ if (Test-Path $LinkPath) {
451
+ Remove-Item $LinkPath -Recurse -Force
452
+ }
453
+
454
+ # Try creating symlink (requires privileges)
455
+ try {
456
+ New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetPath -Force | Out-Null
457
+ } catch {
458
+ # Fallback: Copy directory instead (suboptimal but functional)
459
+ Copy-Item $TargetPath -Destination $LinkPath -Recurse -Force
460
+ Write-Host "[!] Symlink failed for $_, copied instead (enable Developer Mode)" -ForegroundColor Yellow
461
+ }
462
+ }
463
+ }
464
+
465
+ function Migrate-SharedStructure {
466
+ $SharedDir = "$env:USERPROFILE\.ccs\shared"
467
+
468
+ # Check if migration is needed (shared dirs exist but are empty)
469
+ if (Test-Path $SharedDir) {
470
+ $NeedsMigration = $false
471
+ foreach ($Dir in @('commands', 'skills', 'agents')) {
472
+ $DirPath = Join-Path $SharedDir $Dir
473
+ if (-not (Test-Path $DirPath) -or (Get-ChildItem $DirPath -ErrorAction SilentlyContinue).Count -eq 0) {
474
+ $NeedsMigration = $true
475
+ break
476
+ }
477
+ }
478
+
479
+ if (-not $NeedsMigration) { return }
480
+ }
481
+
482
+ # Create shared directory
483
+ @('commands', 'skills', 'agents') | ForEach-Object {
484
+ $Dir = Join-Path $SharedDir $_
485
+ New-Item -ItemType Directory -Path $Dir -Force | Out-Null
486
+ }
487
+
488
+ # Copy from ~/.claude/ (actual Claude CLI directory)
489
+ $ClaudeDir = "$env:USERPROFILE\.claude"
490
+
491
+ if (Test-Path $ClaudeDir) {
492
+ # Copy commands to shared (if exists)
493
+ $CommandsPath = Join-Path $ClaudeDir "commands"
494
+ if (Test-Path $CommandsPath) {
495
+ Copy-Item "$CommandsPath\*" -Destination "$SharedDir\commands\" -Recurse -ErrorAction SilentlyContinue
496
+ }
497
+
498
+ # Copy skills to shared (if exists)
499
+ $SkillsPath = Join-Path $ClaudeDir "skills"
500
+ if (Test-Path $SkillsPath) {
501
+ Copy-Item "$SkillsPath\*" -Destination "$SharedDir\skills\" -Recurse -ErrorAction SilentlyContinue
502
+ }
503
+
504
+ # Copy agents to shared (if exists)
505
+ $AgentsPath = Join-Path $ClaudeDir "agents"
506
+ if (Test-Path $AgentsPath) {
507
+ Copy-Item "$AgentsPath\*" -Destination "$SharedDir\agents\" -Recurse -ErrorAction SilentlyContinue
508
+ }
509
+ }
510
+
511
+ # Update all instances to use symlinks
512
+ if (Test-Path $InstancesDir) {
513
+ Get-ChildItem $InstancesDir -Directory | ForEach-Object {
514
+ Link-SharedDirectories $_.FullName
515
+ }
516
+ }
517
+
518
+ Write-Host "[OK] Migrated to shared structure"
519
+ }
520
+
441
521
  function Copy-GlobalConfigs {
442
522
  param([string]$InstancePath)
443
523
 
444
524
  $GlobalClaude = "$env:USERPROFILE\.claude"
445
525
 
446
- # Copy settings.json
526
+ # Copy settings.json only (commands/skills are now symlinked to shared/)
447
527
  $GlobalSettings = Join-Path $GlobalClaude "settings.json"
448
528
  if (Test-Path $GlobalSettings) {
449
529
  Copy-Item $GlobalSettings -Destination (Join-Path $InstancePath "settings.json") -ErrorAction SilentlyContinue
450
530
  }
451
-
452
- # Copy commands/
453
- $GlobalCommands = Join-Path $GlobalClaude "commands"
454
- if (Test-Path $GlobalCommands) {
455
- Copy-Item $GlobalCommands -Destination $InstancePath -Recurse -ErrorAction SilentlyContinue
456
- }
457
-
458
- # Copy skills/
459
- $GlobalSkills = Join-Path $GlobalClaude "skills"
460
- if (Test-Path $GlobalSkills) {
461
- Copy-Item $GlobalSkills -Destination $InstancePath -Recurse -ErrorAction SilentlyContinue
462
- }
463
531
  }
464
532
 
465
533
  function Initialize-Instance {
@@ -469,15 +537,18 @@ function Initialize-Instance {
469
537
  New-Item -ItemType Directory -Path $InstancePath -Force | Out-Null
470
538
  Set-InstancePermissions $InstancePath
471
539
 
472
- # Create subdirectories
540
+ # Create subdirectories (profile-specific only)
473
541
  $Subdirs = @('session-env', 'todos', 'logs', 'file-history',
474
- 'shell-snapshots', 'debug', '.anthropic', 'commands', 'skills')
542
+ 'shell-snapshots', 'debug', '.anthropic')
475
543
 
476
544
  foreach ($Dir in $Subdirs) {
477
545
  $DirPath = Join-Path $InstancePath $Dir
478
546
  New-Item -ItemType Directory -Path $DirPath -Force | Out-Null
479
547
  }
480
548
 
549
+ # Symlink shared directories
550
+ Link-SharedDirectories $InstancePath
551
+
481
552
  # Copy global configs
482
553
  Copy-GlobalConfigs $InstancePath
483
554
  }
@@ -640,9 +711,19 @@ function Show-AuthHelp {
640
711
  Write-Host " default <profile> Set default profile" -ForegroundColor Yellow
641
712
  Write-Host ""
642
713
  Write-Host "Examples:" -ForegroundColor Cyan
643
- Write-Host " ccs auth create work" -ForegroundColor Yellow
644
- Write-Host " ccs auth list" -ForegroundColor Yellow
645
- Write-Host " ccs auth remove work --force" -ForegroundColor Yellow
714
+ Write-Host " ccs auth create work # Create & login to work profile" -ForegroundColor Yellow
715
+ Write-Host " ccs auth default work # Set work as default" -ForegroundColor Yellow
716
+ Write-Host " ccs auth list # List all profiles" -ForegroundColor Yellow
717
+ Write-Host ' ccs work "review code" # Use work profile' -ForegroundColor Yellow
718
+ Write-Host ' ccs "review code" # Use default profile' -ForegroundColor Yellow
719
+ Write-Host ""
720
+ Write-Host "Note:" -ForegroundColor Cyan
721
+ Write-Host " By default, " -NoNewline
722
+ Write-Host "ccs" -ForegroundColor Yellow -NoNewline
723
+ Write-Host " uses Claude CLI defaults from ~/.claude/"
724
+ Write-Host " Use " -NoNewline
725
+ Write-Host "ccs auth default <profile>" -ForegroundColor Yellow -NoNewline
726
+ Write-Host " to change the default profile."
646
727
  Write-Host ""
647
728
  }
648
729
 
@@ -703,7 +784,10 @@ function Invoke-AuthCreate {
703
784
  Write-Host " Instance: $InstancePath"
704
785
  Write-Host ""
705
786
  Write-Host "Usage:"
706
- Write-Host " ccs $ProfileName `"your prompt here`"" -ForegroundColor Yellow
787
+ Write-Host " ccs $ProfileName `"your prompt here`" # Use this specific profile" -ForegroundColor Yellow
788
+ Write-Host ""
789
+ Write-Host 'To set as default (so you can use just "ccs"):'
790
+ Write-Host " ccs auth default $ProfileName" -ForegroundColor Yellow
707
791
  Write-Host ""
708
792
  }
709
793
 
@@ -916,6 +1000,9 @@ if (-not (Invoke-AutoRecovery)) {
916
1000
  exit 1
917
1001
  }
918
1002
 
1003
+ # Run migration to shared structure (Phase 1: idempotent)
1004
+ Migrate-SharedStructure
1005
+
919
1006
  # Smart profile detection: if first arg starts with '-', it's a flag not a profile
920
1007
  if ($RemainingArgs.Count -eq 0 -or $RemainingArgs[0] -match '^-') {
921
1008
  # No args or first arg is a flag → use default profile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "3.0.1",
3
+ "version": "3.1.0",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",
@@ -72,6 +72,23 @@ function createConfigFiles() {
72
72
  console.log('[OK] Created directory: ~/.ccs/');
73
73
  }
74
74
 
75
+ // Create ~/.ccs/shared/ directory structure (Phase 1)
76
+ const sharedDir = path.join(ccsDir, 'shared');
77
+ if (!fs.existsSync(sharedDir)) {
78
+ fs.mkdirSync(sharedDir, { recursive: true, mode: 0o755 });
79
+ console.log('[OK] Created directory: ~/.ccs/shared/');
80
+ }
81
+
82
+ // Create shared subdirectories
83
+ const sharedSubdirs = ['commands', 'skills', 'agents'];
84
+ for (const subdir of sharedSubdirs) {
85
+ const subdirPath = path.join(sharedDir, subdir);
86
+ if (!fs.existsSync(subdirPath)) {
87
+ fs.mkdirSync(subdirPath, { recursive: true, mode: 0o755 });
88
+ console.log(`[OK] Created directory: ~/.ccs/shared/${subdir}/`);
89
+ }
90
+ }
91
+
75
92
  // Create config.json if missing
76
93
  const configPath = path.join(ccsDir, 'config.json');
77
94
  if (!fs.existsSync(configPath)) {