mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-19 12:16:10 +00:00
fix(migration): run migration during install, not on first execution (v3.1.1)
Fixed:
- Migration now runs during installation across all methods (npm, bash, PowerShell)
- Guarantees ~/.ccs/shared/ populated immediately with ~/.claude/ content
- Users no longer need to run ccs command to trigger migration
Changed:
- Refactored SharedManager with _needsMigration() and _performMigration() methods
- _copyDirectory() returns {copied, skipped} stats and preserves existing files
- Shows detailed migration output: '[OK] Migrated 5 commands, 19 skills'
- Removed lazy migration from bin/ccs.js, lib/ccs, lib/ccs.ps1
Implementation:
- npm: Migration in scripts/postinstall.js
- bash: Migration in installers/install.sh (migrate_shared_data function)
- PowerShell: Migration in installers/install.ps1 (Invoke-SharedDataMigration)
- Fixed arithmetic expansion with set -e (changed ((var++)) to var=$((var + 1)))
Cross-platform parity maintained across all installation methods.
This commit is contained in:
1 parent
d925dfc169
commit
70471af1f8
10 files changed
+310
-56
No files matched your search
@@ -4,6 +4,37 @@ All notable changes to CCS will be documented here.
|
|||||||
|
|
||||||
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
Format based on [Keep a Changelog](https://keepachangelog.com/).
|
||||||
|
|
||||||
|
## [3.1.1] - 2025-11-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Migration Timing**: Migration now runs during installation, not on first `ccs` execution
|
||||||
|
- npm: Migration runs in `scripts/postinstall.js` during `npm install`
|
||||||
|
- bash: Migration runs in `installers/install.sh` during installation
|
||||||
|
- PowerShell: Migration runs in `installers/install.ps1` during installation
|
||||||
|
- Guarantees `~/.ccs/shared/` populated with `~/.claude/` content immediately
|
||||||
|
- Users no longer need to run `ccs` command to trigger migration
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **SharedManager Refactoring**: Improved migration logic and file preservation
|
||||||
|
- Extracted `_needsMigration()` method for clearer logic
|
||||||
|
- Extracted `_performMigration()` method with file counting stats
|
||||||
|
- `_copyDirectory()` now returns `{copied, skipped}` stats
|
||||||
|
- Preserves existing files in `~/.ccs/shared/` (never overwrites user modifications)
|
||||||
|
- Shows detailed migration output: `[OK] Migrated 5 commands, 19 skills`
|
||||||
|
- **Removed Lazy Migration**: No longer runs migration on first `ccs` execution
|
||||||
|
- Removed from `bin/ccs.js` (Node.js wrapper)
|
||||||
|
- Removed from `lib/ccs` (bash executable)
|
||||||
|
- Removed from `lib/ccs.ps1` (PowerShell executable)
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
- **Modified Files**: All implementations updated for consistency
|
||||||
|
- `bin/shared-manager.js`: Refactored with `_needsMigration()`, `_performMigration()`, improved `_copyDirectory()`
|
||||||
|
- `scripts/postinstall.js`: Calls migration after creating shared directories
|
||||||
|
- `installers/install.sh`: Added `migrate_shared_data()` function
|
||||||
|
- `installers/install.ps1`: Added `Invoke-SharedDataMigration` function
|
||||||
|
- `bin/ccs.js`, `lib/ccs`, `lib/ccs.ps1`: Removed lazy migration calls
|
||||||
|
- **Cross-Platform Parity**: All installation methods (npm, bash, PowerShell) behave identically
|
||||||
|
|
||||||
## [3.1.0] - 2025-11-10
|
## [3.1.0] - 2025-11-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -262,11 +262,6 @@ async function main() {
|
|||||||
recovery.showRecoveryHints();
|
recovery.showRecoveryHints();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run migration to shared structure (Phase 1: idempotent)
|
|
||||||
const SharedManager = require('./shared-manager');
|
|
||||||
const sharedManager = new SharedManager();
|
|
||||||
sharedManager.migrateToSharedStructure();
|
|
||||||
|
|
||||||
// Detect profile
|
// Detect profile
|
||||||
const { profile, remainingArgs } = detectProfile(args);
|
const { profile, remainingArgs } = detectProfile(args);
|
||||||
|
|
||||||
|
|||||||
+99
-39
@@ -68,52 +68,100 @@ class SharedManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if migration is needed
|
||||||
|
* @returns {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_needsMigration() {
|
||||||
|
// If shared dir doesn't exist, migration needed
|
||||||
|
if (!fs.existsSync(this.sharedDir)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if ALL shared directories are empty
|
||||||
|
const allEmpty = this.sharedDirs.every(dir => {
|
||||||
|
const dirPath = path.join(this.sharedDir, dir);
|
||||||
|
if (!fs.existsSync(dirPath)) return true;
|
||||||
|
try {
|
||||||
|
const files = fs.readdirSync(dirPath);
|
||||||
|
return files.length === 0;
|
||||||
|
} catch (err) {
|
||||||
|
return true; // If can't read, assume empty
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return allEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform migration from ~/.claude/ to ~/.ccs/shared/
|
||||||
|
* @returns {object} { commands: N, skills: N, agents: N }
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_performMigration() {
|
||||||
|
const stats = { commands: 0, skills: 0, agents: 0 };
|
||||||
|
const claudeDir = path.join(this.homeDir, '.claude');
|
||||||
|
|
||||||
|
if (!fs.existsSync(claudeDir)) {
|
||||||
|
return stats; // No content to migrate
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate commands
|
||||||
|
const commandsPath = path.join(claudeDir, 'commands');
|
||||||
|
if (fs.existsSync(commandsPath)) {
|
||||||
|
const result = this._copyDirectory(commandsPath, path.join(this.sharedDir, 'commands'));
|
||||||
|
stats.commands = result.copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate skills
|
||||||
|
const skillsPath = path.join(claudeDir, 'skills');
|
||||||
|
if (fs.existsSync(skillsPath)) {
|
||||||
|
const result = this._copyDirectory(skillsPath, path.join(this.sharedDir, 'skills'));
|
||||||
|
stats.skills = result.copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate agents
|
||||||
|
const agentsPath = path.join(claudeDir, 'agents');
|
||||||
|
if (fs.existsSync(agentsPath)) {
|
||||||
|
const result = this._copyDirectory(agentsPath, path.join(this.sharedDir, 'agents'));
|
||||||
|
stats.agents = result.copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrate existing instances to shared structure
|
* Migrate existing instances to shared structure
|
||||||
* Idempotent: Safe to run multiple times
|
* Idempotent: Safe to run multiple times
|
||||||
*/
|
*/
|
||||||
migrateToSharedStructure() {
|
migrateToSharedStructure() {
|
||||||
// Check if migration is needed (shared dirs exist but are empty)
|
console.log('[i] Checking for content migration...');
|
||||||
const needsMigration = !fs.existsSync(this.sharedDir) ||
|
|
||||||
this.sharedDirs.every(dir => {
|
|
||||||
const dirPath = path.join(this.sharedDir, dir);
|
|
||||||
if (!fs.existsSync(dirPath)) return true;
|
|
||||||
try {
|
|
||||||
const files = fs.readdirSync(dirPath);
|
|
||||||
return files.length === 0; // Empty directory needs migration
|
|
||||||
} catch (err) {
|
|
||||||
return true; // If we can't read it, assume it needs migration
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!needsMigration) {
|
// Check if migration is needed
|
||||||
return; // Already migrated with content
|
if (!this._needsMigration()) {
|
||||||
|
console.log('[OK] Migration not needed (shared dirs have content)');
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[i] Migrating ~/.claude/ content to ~/.ccs/shared/...');
|
||||||
|
|
||||||
// Create shared directories
|
// Create shared directories
|
||||||
this.ensureSharedDirectories();
|
this.ensureSharedDirectories();
|
||||||
|
|
||||||
// Copy from ~/.claude/ (actual Claude CLI directory)
|
// Perform migration
|
||||||
const claudeDir = path.join(this.homeDir, '.claude');
|
const stats = this._performMigration();
|
||||||
|
|
||||||
if (fs.existsSync(claudeDir)) {
|
// Show results
|
||||||
// Copy commands to shared (if exists)
|
const total = stats.commands + stats.skills + stats.agents;
|
||||||
const commandsPath = path.join(claudeDir, 'commands');
|
if (total === 0) {
|
||||||
if (fs.existsSync(commandsPath)) {
|
console.log('[OK] No content to migrate (empty ~/.claude/)');
|
||||||
this._copyDirectory(commandsPath, path.join(this.sharedDir, 'commands'));
|
} else {
|
||||||
}
|
const parts = [];
|
||||||
|
if (stats.commands > 0) parts.push(`${stats.commands} commands`);
|
||||||
// Copy skills to shared (if exists)
|
if (stats.skills > 0) parts.push(`${stats.skills} skills`);
|
||||||
const skillsPath = path.join(claudeDir, 'skills');
|
if (stats.agents > 0) parts.push(`${stats.agents} agents`);
|
||||||
if (fs.existsSync(skillsPath)) {
|
console.log(`[OK] Migrated ${parts.join(', ')}`);
|
||||||
this._copyDirectory(skillsPath, path.join(this.sharedDir, 'skills'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy agents to shared (if exists)
|
|
||||||
const agentsPath = path.join(claudeDir, 'agents');
|
|
||||||
if (fs.existsSync(agentsPath)) {
|
|
||||||
this._copyDirectory(agentsPath, path.join(this.sharedDir, 'agents'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update all instances to use symlinks
|
// Update all instances to use symlinks
|
||||||
@@ -127,19 +175,18 @@ class SharedManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[OK] Migrated to shared structure');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copy directory recursively (fallback for Windows)
|
* Copy directory recursively (SAFE: preserves existing files)
|
||||||
* @param {string} src - Source directory
|
* @param {string} src - Source directory
|
||||||
* @param {string} dest - Destination directory
|
* @param {string} dest - Destination directory
|
||||||
|
* @returns {object} { copied: N, skipped: N }
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_copyDirectory(src, dest) {
|
_copyDirectory(src, dest) {
|
||||||
if (!fs.existsSync(src)) {
|
if (!fs.existsSync(src)) {
|
||||||
return;
|
return { copied: 0, skipped: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!fs.existsSync(dest)) {
|
if (!fs.existsSync(dest)) {
|
||||||
@@ -147,17 +194,30 @@ class SharedManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const entries = fs.readdirSync(src, { withFileTypes: true });
|
const entries = fs.readdirSync(src, { withFileTypes: true });
|
||||||
|
let copied = 0;
|
||||||
|
let skipped = 0;
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const srcPath = path.join(src, entry.name);
|
const srcPath = path.join(src, entry.name);
|
||||||
const destPath = path.join(dest, entry.name);
|
const destPath = path.join(dest, entry.name);
|
||||||
|
|
||||||
|
// SAFETY: Skip if destination exists (preserve user modifications)
|
||||||
|
if (fs.existsSync(destPath)) {
|
||||||
|
skipped++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (entry.isDirectory()) {
|
if (entry.isDirectory()) {
|
||||||
this._copyDirectory(srcPath, destPath);
|
const stats = this._copyDirectory(srcPath, destPath);
|
||||||
|
copied += stats.copied;
|
||||||
|
skipped += stats.skipped;
|
||||||
} else {
|
} else {
|
||||||
fs.copyFileSync(srcPath, destPath);
|
fs.copyFileSync(srcPath, destPath);
|
||||||
|
copied++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { copied, skipped };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+88
-1
@@ -31,7 +31,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or (
|
|||||||
# IMPORTANT: Update this version when releasing new versions!
|
# IMPORTANT: Update this version when releasing new versions!
|
||||||
# This hardcoded version is used for standalone installations (irm | iex)
|
# This hardcoded version is used for standalone installations (irm | iex)
|
||||||
# For git installations, VERSION file is read if available
|
# For git installations, VERSION file is read if available
|
||||||
$CcsVersion = "3.1.0"
|
$CcsVersion = "3.1.1"
|
||||||
|
|
||||||
# Try to read VERSION file for git installations
|
# Try to read VERSION file for git installations
|
||||||
if ($ScriptDir) {
|
if ($ScriptDir) {
|
||||||
@@ -417,6 +417,93 @@ if (Test-Path $GlmSettings) {
|
|||||||
Write-Host "========================================="
|
Write-Host "========================================="
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
|
# Migrate from ~/.claude/ to ~/.ccs/shared/ (v3.1.1)
|
||||||
|
function Invoke-SharedDataMigration {
|
||||||
|
$SharedDir = "$CcsDir\shared"
|
||||||
|
$ClaudeDir = "$env:USERPROFILE\.claude"
|
||||||
|
|
||||||
|
# Create shared directories
|
||||||
|
@('commands', 'skills', 'agents') | ForEach-Object {
|
||||||
|
$Dir = Join-Path $SharedDir $_
|
||||||
|
if (-not (Test-Path $Dir)) {
|
||||||
|
New-Item -ItemType Directory -Path $Dir -Force | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if migration is needed (shared dirs are empty)
|
||||||
|
$NeedsMigration = $false
|
||||||
|
foreach ($Dir in @('commands', 'skills', 'agents')) {
|
||||||
|
$DirPath = Join-Path $SharedDir $Dir
|
||||||
|
if (-not (Test-Path $DirPath) -or (Get-ChildItem $DirPath -ErrorAction SilentlyContinue).Count -eq 0) {
|
||||||
|
$NeedsMigration = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $NeedsMigration) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy from ~/.claude/ if exists
|
||||||
|
if (-not (Test-Path $ClaudeDir)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$MigratedCommands = 0
|
||||||
|
$MigratedSkills = 0
|
||||||
|
$MigratedAgents = 0
|
||||||
|
|
||||||
|
# Copy commands
|
||||||
|
$CommandsPath = Join-Path $ClaudeDir "commands"
|
||||||
|
if (Test-Path $CommandsPath) {
|
||||||
|
Get-ChildItem $CommandsPath -File -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
$DestPath = Join-Path "$SharedDir\commands" $_.Name
|
||||||
|
if (-not (Test-Path $DestPath)) {
|
||||||
|
Copy-Item $_.FullName $DestPath -ErrorAction SilentlyContinue
|
||||||
|
$MigratedCommands++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy skills (directories)
|
||||||
|
$SkillsPath = Join-Path $ClaudeDir "skills"
|
||||||
|
if (Test-Path $SkillsPath) {
|
||||||
|
Get-ChildItem $SkillsPath -Directory -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
$DestPath = Join-Path "$SharedDir\skills" $_.Name
|
||||||
|
if (-not (Test-Path $DestPath)) {
|
||||||
|
Copy-Item $_.FullName $DestPath -Recurse -ErrorAction SilentlyContinue
|
||||||
|
$MigratedSkills++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Copy agents
|
||||||
|
$AgentsPath = Join-Path $ClaudeDir "agents"
|
||||||
|
if (Test-Path $AgentsPath) {
|
||||||
|
Get-ChildItem $AgentsPath -File -ErrorAction SilentlyContinue | ForEach-Object {
|
||||||
|
$DestPath = Join-Path "$SharedDir\agents" $_.Name
|
||||||
|
if (-not (Test-Path $DestPath)) {
|
||||||
|
Copy-Item $_.FullName $DestPath -ErrorAction SilentlyContinue
|
||||||
|
$MigratedAgents++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Show results
|
||||||
|
$Total = $MigratedCommands + $MigratedSkills + $MigratedAgents
|
||||||
|
if ($Total -gt 0) {
|
||||||
|
$Parts = @()
|
||||||
|
if ($MigratedCommands -gt 0) { $Parts += "$MigratedCommands commands" }
|
||||||
|
if ($MigratedSkills -gt 0) { $Parts += "$MigratedSkills skills" }
|
||||||
|
if ($MigratedAgents -gt 0) { $Parts += "$MigratedAgents agents" }
|
||||||
|
Write-Host "[i] Migrated $($Parts -join ', ')"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "[i] Checking for content migration..."
|
||||||
|
Invoke-SharedDataMigration
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
# Check and update PATH
|
# Check and update PATH
|
||||||
$UserPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User)
|
$UserPath = [Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User)
|
||||||
if ($UserPath -notlike "*$CcsDir*") {
|
if ($UserPath -notlike "*$CcsDir*") {
|
||||||
|
|||||||
+76
-1
@@ -32,7 +32,7 @@ fi
|
|||||||
# IMPORTANT: Update this version when releasing new versions!
|
# IMPORTANT: Update this version when releasing new versions!
|
||||||
# This hardcoded version is used for standalone installations (curl | bash)
|
# This hardcoded version is used for standalone installations (curl | bash)
|
||||||
# For git installations, VERSION file is read if available
|
# For git installations, VERSION file is read if available
|
||||||
CCS_VERSION="3.1.0"
|
CCS_VERSION="3.1.1"
|
||||||
|
|
||||||
# Try to read VERSION file for git installations
|
# Try to read VERSION file for git installations
|
||||||
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
|
||||||
@@ -611,6 +611,81 @@ fi
|
|||||||
echo "└─"
|
echo "└─"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Migrate from ~/.claude/ to ~/.ccs/shared/ (v3.1.1)
|
||||||
|
migrate_shared_data() {
|
||||||
|
local shared_dir="$CCS_DIR/shared"
|
||||||
|
local claude_dir="$HOME/.claude"
|
||||||
|
|
||||||
|
# Create shared directories
|
||||||
|
mkdir -p "$shared_dir"/{commands,skills,agents}
|
||||||
|
|
||||||
|
# Check if migration is needed (shared dirs are empty)
|
||||||
|
local needs_migration=false
|
||||||
|
for dir in commands skills agents; do
|
||||||
|
if [[ -z "$(ls -A "$shared_dir/$dir" 2>/dev/null)" ]]; then
|
||||||
|
needs_migration=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$needs_migration" == "false" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy from ~/.claude/ if exists
|
||||||
|
if [[ ! -d "$claude_dir" ]]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local migrated_commands=0
|
||||||
|
local migrated_skills=0
|
||||||
|
local migrated_agents=0
|
||||||
|
|
||||||
|
# Copy commands
|
||||||
|
if [[ -d "$claude_dir/commands" ]]; then
|
||||||
|
for file in "$claude_dir/commands"/*; do
|
||||||
|
[[ -f "$file" ]] || continue
|
||||||
|
local basename=$(basename "$file")
|
||||||
|
[[ -f "$shared_dir/commands/$basename" ]] && continue # Skip if exists
|
||||||
|
cp "$file" "$shared_dir/commands/" 2>/dev/null && migrated_commands=$((migrated_commands + 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy skills (directories)
|
||||||
|
if [[ -d "$claude_dir/skills" ]]; then
|
||||||
|
for dir in "$claude_dir/skills"/*; do
|
||||||
|
[[ -d "$dir" ]] || continue
|
||||||
|
local basename=$(basename "$dir")
|
||||||
|
[[ -d "$shared_dir/skills/$basename" ]] && continue # Skip if exists
|
||||||
|
cp -r "$dir" "$shared_dir/skills/" 2>/dev/null && migrated_skills=$((migrated_skills + 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Copy agents
|
||||||
|
if [[ -d "$claude_dir/agents" ]]; then
|
||||||
|
for file in "$claude_dir/agents"/*; do
|
||||||
|
[[ -f "$file" ]] || continue
|
||||||
|
local basename=$(basename "$file")
|
||||||
|
[[ -f "$shared_dir/agents/$basename" ]] && continue # Skip if exists
|
||||||
|
cp "$file" "$shared_dir/agents/" 2>/dev/null && migrated_agents=$((migrated_agents + 1))
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Show results
|
||||||
|
local total=$((migrated_commands + migrated_skills + migrated_agents))
|
||||||
|
if [[ $total -gt 0 ]]; then
|
||||||
|
local parts=()
|
||||||
|
[[ $migrated_commands -gt 0 ]] && parts+=("$migrated_commands commands")
|
||||||
|
[[ $migrated_skills -gt 0 ]] && parts+=("$migrated_skills skills")
|
||||||
|
[[ $migrated_agents -gt 0 ]] && parts+=("$migrated_agents agents")
|
||||||
|
echo "[i] Migrated $(IFS=", "; echo "${parts[*]}")"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "[i] Checking for content migration..."
|
||||||
|
migrate_shared_data
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Auto-configure PATH if needed (all Unix platforms)
|
# Auto-configure PATH if needed (all Unix platforms)
|
||||||
configure_shell_path
|
configure_shell_path
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Version (updated by scripts/bump-version.sh)
|
# Version (updated by scripts/bump-version.sh)
|
||||||
CCS_VERSION="3.1.0"
|
CCS_VERSION="3.1.1"
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
|
readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
|
||||||
readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
|
readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
|
||||||
@@ -1010,9 +1010,6 @@ auto_recover || {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run migration to shared structure (Phase 1: idempotent)
|
|
||||||
migrate_to_shared_structure
|
|
||||||
|
|
||||||
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
||||||
if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
|
if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
|
||||||
# No args or first arg is a flag → use default profile
|
# No args or first arg is a flag → use default profile
|
||||||
|
|||||||
+1
-4
@@ -12,7 +12,7 @@ param(
|
|||||||
$ErrorActionPreference = "Stop"
|
$ErrorActionPreference = "Stop"
|
||||||
|
|
||||||
# Version (updated by scripts/bump-version.sh)
|
# Version (updated by scripts/bump-version.sh)
|
||||||
$CcsVersion = "3.1.0"
|
$CcsVersion = "3.1.1"
|
||||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
|
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
|
||||||
$ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
|
$ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
|
||||||
@@ -1000,9 +1000,6 @@ if (-not (Invoke-AutoRecovery)) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run migration to shared structure (Phase 1: idempotent)
|
|
||||||
Migrate-SharedStructure
|
|
||||||
|
|
||||||
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
||||||
if ($RemainingArgs.Count -eq 0 -or $RemainingArgs[0] -match '^-') {
|
if ($RemainingArgs.Count -eq 0 -or $RemainingArgs[0] -match '^-') {
|
||||||
# No args or first arg is a flag → use default profile
|
# No args or first arg is a flag → use default profile
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kaitranntt/ccs",
|
"name": "@kaitranntt/ccs",
|
||||||
"version": "3.1.0",
|
"version": "3.1.1",
|
||||||
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
|
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"cli",
|
"cli",
|
||||||
|
|||||||
@@ -89,6 +89,18 @@ function createConfigFiles() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate from ~/.claude/ to ~/.ccs/shared/ (v3.1.1)
|
||||||
|
console.log('');
|
||||||
|
try {
|
||||||
|
const SharedManager = require('../bin/shared-manager');
|
||||||
|
const sharedManager = new SharedManager();
|
||||||
|
sharedManager.migrateToSharedStructure();
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[!] Migration warning:', err.message);
|
||||||
|
console.warn(' You can manually copy files from ~/.claude/ to ~/.ccs/shared/');
|
||||||
|
}
|
||||||
|
console.log('');
|
||||||
|
|
||||||
// Create config.json if missing
|
// Create config.json if missing
|
||||||
const configPath = path.join(ccsDir, 'config.json');
|
const configPath = path.join(ccsDir, 'config.json');
|
||||||
if (!fs.existsSync(configPath)) {
|
if (!fs.existsSync(configPath)) {
|
||||||
|
|||||||
Reference in new issue
Block a user