From fd018296ff851f2f91b44df8b4cdbdf871727483 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 3 Dec 2025 02:19:11 +0000 Subject: [PATCH 1/8] chore(release): 5.4.1-dev.1 [skip ci] ## [5.4.1-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1-dev.1) (2025-12-03) ### Bug Fixes * **cliproxy:** resolve windows auth browser not opening ([af4d6cf](https://github.com/kaitranntt/ccs/commit/af4d6cff89395a74e2eaf56551d3f56b95e0a6ce)), closes [#42](https://github.com/kaitranntt/ccs/issues/42) * **doctor:** resolve windows claude cli detection failure ([cfe9ba0](https://github.com/kaitranntt/ccs/commit/cfe9ba05a4351302fbb330ca00b6025cb65a8f20)), closes [#41](https://github.com/kaitranntt/ccs/issues/41) --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3a3701d..2f925bfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [5.4.1-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1-dev.1) (2025-12-03) + + +### Bug Fixes + +* **cliproxy:** resolve windows auth browser not opening ([af4d6cf](https://github.com/kaitranntt/ccs/commit/af4d6cff89395a74e2eaf56551d3f56b95e0a6ce)), closes [#42](https://github.com/kaitranntt/ccs/issues/42) +* **doctor:** resolve windows claude cli detection failure ([cfe9ba0](https://github.com/kaitranntt/ccs/commit/cfe9ba05a4351302fbb330ca00b6025cb65a8f20)), closes [#41](https://github.com/kaitranntt/ccs/issues/41) + # [5.4.0](https://github.com/kaitranntt/ccs/compare/v5.3.0...v5.4.0) (2025-12-02) diff --git a/VERSION b/VERSION index 8a30e8f9..ef39ed80 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.4.0 +5.4.1-dev.1 diff --git a/installers/install.ps1 b/installers/install.ps1 index 9db970c2..a24b7d9e 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or ( # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (irm | iex) # For git installations, VERSION file is read if available -$CcsVersion = "5.4.0" +$CcsVersion = "5.4.1-dev.1" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index 4e389816..5de1b2b8 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -84,7 +84,7 @@ fi # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (curl | bash) # For git installations, VERSION file is read if available -CCS_VERSION="5.4.0" +CCS_VERSION="5.4.1-dev.1" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/package.json b/package.json index 0eaf83cd..584e8de2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "5.4.0", + "version": "5.4.1-dev.1", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", From 09f3260c2322dc0db409ec25e57ba49a7bfd2a32 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 3 Dec 2025 02:21:44 +0000 Subject: [PATCH 2/8] chore(release): 5.4.1 [skip ci] ## [5.4.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1) (2025-12-03) ### Bug Fixes * **cliproxy:** resolve windows auth browser not opening ([af4d6cf](https://github.com/kaitranntt/ccs/commit/af4d6cff89395a74e2eaf56551d3f56b95e0a6ce)), closes [#42](https://github.com/kaitranntt/ccs/issues/42) * **doctor:** resolve windows claude cli detection failure ([cfe9ba0](https://github.com/kaitranntt/ccs/commit/cfe9ba05a4351302fbb330ca00b6025cb65a8f20)), closes [#41](https://github.com/kaitranntt/ccs/issues/41) --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f925bfb..03a2bf2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [5.4.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1) (2025-12-03) + + +### Bug Fixes + +* **cliproxy:** resolve windows auth browser not opening ([af4d6cf](https://github.com/kaitranntt/ccs/commit/af4d6cff89395a74e2eaf56551d3f56b95e0a6ce)), closes [#42](https://github.com/kaitranntt/ccs/issues/42) +* **doctor:** resolve windows claude cli detection failure ([cfe9ba0](https://github.com/kaitranntt/ccs/commit/cfe9ba05a4351302fbb330ca00b6025cb65a8f20)), closes [#41](https://github.com/kaitranntt/ccs/issues/41) + ## [5.4.1-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1-dev.1) (2025-12-03) diff --git a/VERSION b/VERSION index ef39ed80..ade65226 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.4.1-dev.1 +5.4.1 diff --git a/installers/install.ps1 b/installers/install.ps1 index a24b7d9e..9df8ae3a 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or ( # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (irm | iex) # For git installations, VERSION file is read if available -$CcsVersion = "5.4.1-dev.1" +$CcsVersion = "5.4.1" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index 5de1b2b8..0d7e874d 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -84,7 +84,7 @@ fi # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (curl | bash) # For git installations, VERSION file is read if available -CCS_VERSION="5.4.1-dev.1" +CCS_VERSION="5.4.1" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/package.json b/package.json index 584e8de2..dcca315f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "5.4.1-dev.1", + "version": "5.4.1", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", From 6b3f93a80a0232e8c964d73e51aa0afb0768b00f Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 2 Dec 2025 22:01:50 -0500 Subject: [PATCH 3/8] fix(sync): implement copy fallback for windows when symlinks unavailable When symlink creation fails on Windows (due to Developer Mode being disabled or insufficient permissions), fall back to copying files and directories instead. Changes: - Add copyFallback() method for Windows symlink failures - Add copyDirRecursive() helper for directory copying - Update checkHealth() to recognize copied files as valid on Windows - Add isCopiedItem() helper to validate copied content - Update uninstall() to handle both symlinks and copied files Fixes #45 --- src/utils/claude-symlink-manager.ts | 119 +++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/src/utils/claude-symlink-manager.ts b/src/utils/claude-symlink-manager.ts index 4011d007..29c98260 100644 --- a/src/utils/claude-symlink-manager.ts +++ b/src/utils/claude-symlink-manager.ts @@ -164,12 +164,9 @@ export class ClaudeSymlinkManager { if (!silent) console.log(`[OK] Symlinked ${item.target}`); return true; } catch (err) { - // Windows fallback: stub for now, full implementation in v4.2 + // Windows fallback: copy instead of symlink when symlinks unavailable if (process.platform === 'win32') { - if (!silent) { - console.log(`[!] Symlink failed for ${item.target} (Windows fallback deferred to v4.2)`); - console.log(`[i] Enable Developer Mode or wait for next update`); - } + return this.copyFallback(sourcePath, targetPath, item, silent); } else { const error = err as Error; if (!silent) console.log(`[!] Failed to symlink ${item.target}: ${error.message}`); @@ -178,6 +175,61 @@ export class ClaudeSymlinkManager { } } + /** + * Windows fallback: copy files/directories when symlinks unavailable + * Note: Changes won't auto-sync; user must run 'ccs sync' after updates + */ + private copyFallback( + sourcePath: string, + targetPath: string, + item: CcsItem, + silent = false + ): boolean { + try { + if (item.type === 'directory') { + // Copy directory recursively + this.copyDirRecursive(sourcePath, targetPath); + } else { + // Copy single file + fs.copyFileSync(sourcePath, targetPath); + } + if (!silent) { + console.log(`[OK] Copied ${item.target} (symlink unavailable)`); + console.log(`[i] Run 'ccs sync' after CCS updates to refresh`); + } + return true; + } catch (copyErr) { + const error = copyErr as Error; + if (!silent) { + console.log(`[!] Failed to copy ${item.target}: ${error.message}`); + console.log(`[i] Enable Developer Mode for symlinks, or check permissions`); + } + return false; + } + } + + /** + * Recursively copy directory (for Windows fallback) + */ + private copyDirRecursive(src: string, dest: string): void { + if (!fs.existsSync(dest)) { + fs.mkdirSync(dest, { recursive: true }); + } + + const entries = fs.readdirSync(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + + if (entry.isDirectory()) { + this.copyDirRecursive(srcPath, destPath); + } else { + fs.copyFileSync(srcPath, destPath); + } + } + } + /** * Check if target is already the correct symlink pointing to source */ @@ -224,8 +276,8 @@ export class ClaudeSymlinkManager { } /** - * Uninstall CCS items from ~/.claude/ (remove symlinks only) - * Safe: only removes items that are CCS symlinks + * Uninstall CCS items from ~/.claude/ (remove symlinks or copied files) + * Safe: only removes items that are CCS symlinks or valid copies */ uninstall(): void { let removed = 0; @@ -234,10 +286,20 @@ export class ClaudeSymlinkManager { const targetPath = path.join(this.userClaudeDir, item.target); const sourcePath = path.join(this.ccsClaudeDir, item.source); - // Only remove if it's our symlink - if (fs.existsSync(targetPath) && this.isOurSymlink(targetPath, sourcePath)) { + // Check if it's our symlink or a valid copy (Windows fallback) + const isSymlink = this.isOurSymlink(targetPath, sourcePath); + const isCopy = + process.platform === 'win32' && this.isCopiedItem(targetPath, sourcePath, item.type); + + if (fs.existsSync(targetPath) && (isSymlink || isCopy)) { try { - fs.unlinkSync(targetPath); + if (item.type === 'directory' && !isSymlink) { + // Remove copied directory recursively + fs.rmSync(targetPath, { recursive: true, force: true }); + } else { + // Remove symlink or file + fs.unlinkSync(targetPath); + } console.log(`[OK] Removed ${item.target}`); removed++; } catch (err) { @@ -286,14 +348,47 @@ export class ClaudeSymlinkManager { issues.push(`Not installed: ${item.target} (run 'ccs sync' to install)`); healthy = false; } else if (!this.isOurSymlink(targetPath, sourcePath)) { - issues.push(`Not a CCS symlink: ${item.target} (run 'ccs sync' to fix)`); - healthy = false; + // On Windows, copied files are valid (symlink fallback) + if (process.platform === 'win32' && this.isCopiedItem(targetPath, sourcePath, item.type)) { + // Copied file is valid on Windows, but note it's not a symlink + issues.push(`${item.target} is a copy (not symlink) - run 'ccs sync' after updates`); + // Still healthy, just a warning + } else { + issues.push(`Not a CCS symlink: ${item.target} (run 'ccs sync' to fix)`); + healthy = false; + } } } return { healthy, issues }; } + /** + * Check if target is a valid copy of source (Windows fallback check) + */ + private isCopiedItem( + targetPath: string, + sourcePath: string, + type: 'file' | 'directory' + ): boolean { + try { + const targetStats = fs.statSync(targetPath); + const sourceStats = fs.statSync(sourcePath); + + if (type === 'directory') { + // For directories, just check both exist and are directories + return targetStats.isDirectory() && sourceStats.isDirectory(); + } else { + // For files, compare size as basic validation + return ( + targetStats.isFile() && sourceStats.isFile() && targetStats.size === sourceStats.size + ); + } + } catch { + return false; + } + } + /** * Sync delegation commands and skills to ~/.claude/ (used by 'ccs sync' command) * Same as install() but with explicit sync message From c45fc3e19f1807cb266b1fa18d7ab00a07d181ce Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 3 Dec 2025 03:16:27 +0000 Subject: [PATCH 4/8] chore(release): 5.4.1-dev.2 [skip ci] ## [5.4.1-dev.2](https://github.com/kaitranntt/ccs/compare/v5.4.1-dev.1...v5.4.1-dev.2) (2025-12-03) ### Bug Fixes * **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45) --- CHANGELOG.md | 7 +++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f925bfb..e0bf05d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [5.4.1-dev.2](https://github.com/kaitranntt/ccs/compare/v5.4.1-dev.1...v5.4.1-dev.2) (2025-12-03) + + +### Bug Fixes + +* **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45) + ## [5.4.1-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1-dev.1) (2025-12-03) diff --git a/VERSION b/VERSION index ef39ed80..f0e21525 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.4.1-dev.1 +5.4.1-dev.2 diff --git a/installers/install.ps1 b/installers/install.ps1 index a24b7d9e..9cad1d77 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or ( # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (irm | iex) # For git installations, VERSION file is read if available -$CcsVersion = "5.4.1-dev.1" +$CcsVersion = "5.4.1-dev.2" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index 5de1b2b8..67144893 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -84,7 +84,7 @@ fi # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (curl | bash) # For git installations, VERSION file is read if available -CCS_VERSION="5.4.1-dev.1" +CCS_VERSION="5.4.1-dev.2" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/package.json b/package.json index 584e8de2..4be95d2f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "5.4.1-dev.1", + "version": "5.4.1-dev.2", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", From 691758ff47c129a8af021ae7a0f9501bbb52b31c Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 3 Dec 2025 04:44:35 +0000 Subject: [PATCH 5/8] chore(release): 5.4.2-dev.1 [skip ci] ## [5.4.2-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.1...v5.4.2-dev.1) (2025-12-03) ### Bug Fixes * **merge:** resolve conflicts between dev and main ([8347ea6](https://github.com/kaitranntt/ccs/commit/8347ea64c6b919a79f5ab63c398b6c36f012ca2d)) * **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45) --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c204e63..97548c74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [5.4.2-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.1...v5.4.2-dev.1) (2025-12-03) + + +### Bug Fixes + +* **merge:** resolve conflicts between dev and main ([8347ea6](https://github.com/kaitranntt/ccs/commit/8347ea64c6b919a79f5ab63c398b6c36f012ca2d)) +* **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45) + ## [5.4.1](https://github.com/kaitranntt/ccs/compare/v5.4.0...v5.4.1) (2025-12-03) diff --git a/VERSION b/VERSION index ade65226..ae273b6d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.4.1 +5.4.2-dev.1 diff --git a/installers/install.ps1 b/installers/install.ps1 index 9df8ae3a..8d09b784 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or ( # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (irm | iex) # For git installations, VERSION file is read if available -$CcsVersion = "5.4.1" +$CcsVersion = "5.4.2-dev.1" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index 0d7e874d..2e981f0a 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -84,7 +84,7 @@ fi # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (curl | bash) # For git installations, VERSION file is read if available -CCS_VERSION="5.4.1" +CCS_VERSION="5.4.2-dev.1" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/package.json b/package.json index dcca315f..0dff8516 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "5.4.1", + "version": "5.4.2-dev.1", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", From 7af0473bc899e574b8d3a9620972738114b9e670 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 3 Dec 2025 04:45:30 +0000 Subject: [PATCH 6/8] chore(release): 5.4.2 [skip ci] ## [5.4.2](https://github.com/kaitranntt/ccs/compare/v5.4.1...v5.4.2) (2025-12-03) ### Bug Fixes * **merge:** resolve conflicts between dev and main ([8347ea6](https://github.com/kaitranntt/ccs/commit/8347ea64c6b919a79f5ab63c398b6c36f012ca2d)) * **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45) --- CHANGELOG.md | 8 ++++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- 5 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97548c74..8c895616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [5.4.2](https://github.com/kaitranntt/ccs/compare/v5.4.1...v5.4.2) (2025-12-03) + + +### Bug Fixes + +* **merge:** resolve conflicts between dev and main ([8347ea6](https://github.com/kaitranntt/ccs/commit/8347ea64c6b919a79f5ab63c398b6c36f012ca2d)) +* **sync:** implement copy fallback for windows when symlinks unavailable ([6b3f93a](https://github.com/kaitranntt/ccs/commit/6b3f93a80a0232e8c964d73e51aa0afb0768b00f)), closes [#45](https://github.com/kaitranntt/ccs/issues/45) + ## [5.4.2-dev.1](https://github.com/kaitranntt/ccs/compare/v5.4.1...v5.4.2-dev.1) (2025-12-03) diff --git a/VERSION b/VERSION index ae273b6d..8ae03c11 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.4.2-dev.1 +5.4.2 diff --git a/installers/install.ps1 b/installers/install.ps1 index 8d09b784..adcec9f1 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or ( # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (irm | iex) # For git installations, VERSION file is read if available -$CcsVersion = "5.4.2-dev.1" +$CcsVersion = "5.4.2" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index 2e981f0a..be805c1b 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -84,7 +84,7 @@ fi # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (curl | bash) # For git installations, VERSION file is read if available -CCS_VERSION="5.4.2-dev.1" +CCS_VERSION="5.4.2" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/package.json b/package.json index 0dff8516..cb5b98ee 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "5.4.2-dev.1", + "version": "5.4.2", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli", From 81add5a05eeb8297ceef840071f11b6a194df707 Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Wed, 3 Dec 2025 06:06:49 -0500 Subject: [PATCH 7/8] fix(postinstall): handle broken symlinks during npm install When upgrading from older CCS versions, ~/.ccs/shared/* directories may exist as broken symlinks (dangling symlinks pointing to deleted targets). Problem: - fs.existsSync() returns false for broken symlinks - fs.mkdirSync() fails with ENOENT because path exists as broken symlink Solution: - Added removeIfBrokenSymlink() helper that detects and removes dangling symlinks using lstatSync (doesn't follow symlinks) + statSync (follows) - Applied to shared directory and all subdirectories before mkdir Fixes npm install error: ENOENT: no such file or directory, mkdir --- scripts/postinstall.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/scripts/postinstall.js b/scripts/postinstall.js index c761c920..4db3256b 100755 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -24,6 +24,35 @@ function getCcsHome() { return process.env.CCS_HOME || os.homedir(); } +/** + * Check if path is a broken symlink and remove it if so + * Fixes: ENOENT error when mkdir tries to create over a dangling symlink + * @param {string} targetPath - Path to check + * @returns {boolean} true if broken symlink was removed + */ +function removeIfBrokenSymlink(targetPath) { + try { + // lstatSync doesn't follow symlinks - it checks the link itself + const stats = fs.lstatSync(targetPath); + if (stats.isSymbolicLink()) { + // Check if symlink target exists + try { + fs.statSync(targetPath); // This follows symlinks + return false; // Symlink is valid + } catch { + // Target doesn't exist - broken symlink + fs.unlinkSync(targetPath); + console.log(`[!] Removed broken symlink: ${targetPath}`); + return true; + } + } + return false; + } catch { + // Path doesn't exist at all + return false; + } +} + /** * Validate created configuration files * @returns {object} { success: boolean, errors: string[], warnings: string[] } @@ -85,6 +114,8 @@ function createConfigFiles() { // Create ~/.ccs/shared/ directory structure (Phase 1) const sharedDir = path.join(ccsDir, 'shared'); + // Handle broken symlinks (common when upgrading from older versions) + removeIfBrokenSymlink(sharedDir); if (!fs.existsSync(sharedDir)) { fs.mkdirSync(sharedDir, { recursive: true, mode: 0o755 }); console.log('[OK] Created directory: ~/.ccs/shared/'); @@ -94,6 +125,8 @@ function createConfigFiles() { const sharedSubdirs = ['commands', 'skills', 'agents', 'plugins']; for (const subdir of sharedSubdirs) { const subdirPath = path.join(sharedDir, subdir); + // Handle broken symlinks before creating directory + removeIfBrokenSymlink(subdirPath); if (!fs.existsSync(subdirPath)) { fs.mkdirSync(subdirPath, { recursive: true, mode: 0o755 }); console.log(`[OK] Created directory: ~/.ccs/shared/${subdir}/`); From b58304aefe39f2d0d10d15c09ec18fd60dfeb918 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Wed, 3 Dec 2025 11:08:50 +0000 Subject: [PATCH 8/8] chore(release): 5.4.3 [skip ci] ## [5.4.3](https://github.com/kaitranntt/ccs/compare/v5.4.2...v5.4.3) (2025-12-03) ### Bug Fixes * **postinstall:** handle broken symlinks during npm install ([81add5a](https://github.com/kaitranntt/ccs/commit/81add5a05eeb8297ceef840071f11b6a194df707)) --- CHANGELOG.md | 7 +++++++ VERSION | 2 +- installers/install.ps1 | 2 +- installers/install.sh | 2 +- package.json | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c895616..099d37f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [5.4.3](https://github.com/kaitranntt/ccs/compare/v5.4.2...v5.4.3) (2025-12-03) + + +### Bug Fixes + +* **postinstall:** handle broken symlinks during npm install ([81add5a](https://github.com/kaitranntt/ccs/commit/81add5a05eeb8297ceef840071f11b6a194df707)) + ## [5.4.2](https://github.com/kaitranntt/ccs/compare/v5.4.1...v5.4.2) (2025-12-03) diff --git a/VERSION b/VERSION index 8ae03c11..6ffbe8ba 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.4.2 +5.4.3 diff --git a/installers/install.ps1 b/installers/install.ps1 index adcec9f1..a16c3881 100644 --- a/installers/install.ps1 +++ b/installers/install.ps1 @@ -83,7 +83,7 @@ $InstallMethod = if ($ScriptDir -and ((Test-Path "$ScriptDir\lib\ccs.ps1") -or ( # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (irm | iex) # For git installations, VERSION file is read if available -$CcsVersion = "5.4.2" +$CcsVersion = "5.4.3" # Try to read VERSION file for git installations if ($ScriptDir) { diff --git a/installers/install.sh b/installers/install.sh index be805c1b..aef2873f 100755 --- a/installers/install.sh +++ b/installers/install.sh @@ -84,7 +84,7 @@ fi # IMPORTANT: Update this version when releasing new versions! # This hardcoded version is used for standalone installations (curl | bash) # For git installations, VERSION file is read if available -CCS_VERSION="5.4.2" +CCS_VERSION="5.4.3" # Try to read VERSION file for git installations if [[ -f "$SCRIPT_DIR/VERSION" ]]; then diff --git a/package.json b/package.json index cb5b98ee..e8b014ea 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kaitranntt/ccs", - "version": "5.4.2", + "version": "5.4.3", "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6", "keywords": [ "cli",