@kaitranntt/ccs 3.1.0 → 3.1.1

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/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.0
1
+ 3.1.1
package/bin/ccs.js CHANGED
@@ -262,11 +262,6 @@ async function main() {
262
262
  recovery.showRecoveryHints();
263
263
  }
264
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
-
270
265
  // Detect profile
271
266
  const { profile, remainingArgs } = detectProfile(args);
272
267
 
@@ -68,52 +68,100 @@ class SharedManager {
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ * Check if migration is needed
73
+ * @returns {boolean}
74
+ * @private
75
+ */
76
+ _needsMigration() {
77
+ // If shared dir doesn't exist, migration needed
78
+ if (!fs.existsSync(this.sharedDir)) {
79
+ return true;
80
+ }
81
+
82
+ // Check if ALL shared directories are empty
83
+ const allEmpty = this.sharedDirs.every(dir => {
84
+ const dirPath = path.join(this.sharedDir, dir);
85
+ if (!fs.existsSync(dirPath)) return true;
86
+ try {
87
+ const files = fs.readdirSync(dirPath);
88
+ return files.length === 0;
89
+ } catch (err) {
90
+ return true; // If can't read, assume empty
91
+ }
92
+ });
93
+
94
+ return allEmpty;
95
+ }
96
+
97
+ /**
98
+ * Perform migration from ~/.claude/ to ~/.ccs/shared/
99
+ * @returns {object} { commands: N, skills: N, agents: N }
100
+ * @private
101
+ */
102
+ _performMigration() {
103
+ const stats = { commands: 0, skills: 0, agents: 0 };
104
+ const claudeDir = path.join(this.homeDir, '.claude');
105
+
106
+ if (!fs.existsSync(claudeDir)) {
107
+ return stats; // No content to migrate
108
+ }
109
+
110
+ // Migrate commands
111
+ const commandsPath = path.join(claudeDir, 'commands');
112
+ if (fs.existsSync(commandsPath)) {
113
+ const result = this._copyDirectory(commandsPath, path.join(this.sharedDir, 'commands'));
114
+ stats.commands = result.copied;
115
+ }
116
+
117
+ // Migrate skills
118
+ const skillsPath = path.join(claudeDir, 'skills');
119
+ if (fs.existsSync(skillsPath)) {
120
+ const result = this._copyDirectory(skillsPath, path.join(this.sharedDir, 'skills'));
121
+ stats.skills = result.copied;
122
+ }
123
+
124
+ // Migrate agents
125
+ const agentsPath = path.join(claudeDir, 'agents');
126
+ if (fs.existsSync(agentsPath)) {
127
+ const result = this._copyDirectory(agentsPath, path.join(this.sharedDir, 'agents'));
128
+ stats.agents = result.copied;
129
+ }
130
+
131
+ return stats;
132
+ }
133
+
71
134
  /**
72
135
  * Migrate existing instances to shared structure
73
136
  * Idempotent: Safe to run multiple times
74
137
  */
75
138
  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
- });
139
+ console.log('[i] Checking for content migration...');
88
140
 
89
- if (!needsMigration) {
90
- return; // Already migrated with content
141
+ // Check if migration is needed
142
+ if (!this._needsMigration()) {
143
+ console.log('[OK] Migration not needed (shared dirs have content)');
144
+ return;
91
145
  }
92
146
 
147
+ console.log('[i] Migrating ~/.claude/ content to ~/.ccs/shared/...');
148
+
93
149
  // Create shared directories
94
150
  this.ensureSharedDirectories();
95
151
 
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
- }
152
+ // Perform migration
153
+ const stats = this._performMigration();
154
+
155
+ // Show results
156
+ const total = stats.commands + stats.skills + stats.agents;
157
+ if (total === 0) {
158
+ console.log('[OK] No content to migrate (empty ~/.claude/)');
159
+ } else {
160
+ const parts = [];
161
+ if (stats.commands > 0) parts.push(`${stats.commands} commands`);
162
+ if (stats.skills > 0) parts.push(`${stats.skills} skills`);
163
+ if (stats.agents > 0) parts.push(`${stats.agents} agents`);
164
+ console.log(`[OK] Migrated ${parts.join(', ')}`);
117
165
  }
118
166
 
119
167
  // Update all instances to use symlinks
@@ -127,19 +175,18 @@ class SharedManager {
127
175
  }
128
176
  }
129
177
  }
130
-
131
- console.log('[OK] Migrated to shared structure');
132
178
  }
133
179
 
134
180
  /**
135
- * Copy directory recursively (fallback for Windows)
181
+ * Copy directory recursively (SAFE: preserves existing files)
136
182
  * @param {string} src - Source directory
137
183
  * @param {string} dest - Destination directory
184
+ * @returns {object} { copied: N, skipped: N }
138
185
  * @private
139
186
  */
140
187
  _copyDirectory(src, dest) {
141
188
  if (!fs.existsSync(src)) {
142
- return;
189
+ return { copied: 0, skipped: 0 };
143
190
  }
144
191
 
145
192
  if (!fs.existsSync(dest)) {
@@ -147,17 +194,30 @@ class SharedManager {
147
194
  }
148
195
 
149
196
  const entries = fs.readdirSync(src, { withFileTypes: true });
197
+ let copied = 0;
198
+ let skipped = 0;
150
199
 
151
200
  for (const entry of entries) {
152
201
  const srcPath = path.join(src, entry.name);
153
202
  const destPath = path.join(dest, entry.name);
154
203
 
204
+ // SAFETY: Skip if destination exists (preserve user modifications)
205
+ if (fs.existsSync(destPath)) {
206
+ skipped++;
207
+ continue;
208
+ }
209
+
155
210
  if (entry.isDirectory()) {
156
- this._copyDirectory(srcPath, destPath);
211
+ const stats = this._copyDirectory(srcPath, destPath);
212
+ copied += stats.copied;
213
+ skipped += stats.skipped;
157
214
  } else {
158
215
  fs.copyFileSync(srcPath, destPath);
216
+ copied++;
159
217
  }
160
218
  }
219
+
220
+ return { copied, skipped };
161
221
  }
162
222
  }
163
223
 
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.1.0"
5
+ CCS_VERSION="3.1.1"
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"
@@ -1010,9 +1010,6 @@ auto_recover || {
1010
1010
  exit 1
1011
1011
  }
1012
1012
 
1013
- # Run migration to shared structure (Phase 1: idempotent)
1014
- migrate_to_shared_structure
1015
-
1016
1013
  # Smart profile detection: if first arg starts with '-', it's a flag not a profile
1017
1014
  if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
1018
1015
  # 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.1.0"
15
+ $CcsVersion = "3.1.1"
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"
@@ -1000,9 +1000,6 @@ if (-not (Invoke-AutoRecovery)) {
1000
1000
  exit 1
1001
1001
  }
1002
1002
 
1003
- # Run migration to shared structure (Phase 1: idempotent)
1004
- Migrate-SharedStructure
1005
-
1006
1003
  # Smart profile detection: if first arg starts with '-', it's a flag not a profile
1007
1004
  if ($RemainingArgs.Count -eq 0 -or $RemainingArgs[0] -match '^-') {
1008
1005
  # 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.1.0",
3
+ "version": "3.1.1",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",
@@ -89,6 +89,18 @@ function createConfigFiles() {
89
89
  }
90
90
  }
91
91
 
92
+ // Migrate from ~/.claude/ to ~/.ccs/shared/ (v3.1.1)
93
+ console.log('');
94
+ try {
95
+ const SharedManager = require('../bin/shared-manager');
96
+ const sharedManager = new SharedManager();
97
+ sharedManager.migrateToSharedStructure();
98
+ } catch (err) {
99
+ console.warn('[!] Migration warning:', err.message);
100
+ console.warn(' You can manually copy files from ~/.claude/ to ~/.ccs/shared/');
101
+ }
102
+ console.log('');
103
+
92
104
  // Create config.json if missing
93
105
  const configPath = path.join(ccsDir, 'config.json');
94
106
  if (!fs.existsSync(configPath)) {