fix: terminal termination & simplified detection (v2.4.1)

Critical Fixes:
- Fixed PowerShell terminal closing when using `irm | iex` installation
  * Changed `exit 1` to `return` in install.ps1 (line 229)
  * Terminal now stays open on errors, showing full error messages
  * Affects: Windows PowerShell 5.1+, PowerShell 7+

- Fixed installation download path
  * Changed `/ccs.ps1` to `/lib/ccs.ps1` in install.ps1 (line 223)
  * Resolves standalone installation failures from GitHub

- Simplified Claude CLI detection logic across all platforms
  * Removed complex validation that failed with npm-installed Claude
  * Now trusts system PATH (standard case for users)
  * Falls back to CCS_CLAUDE_PATH for custom installations
  * Fixes: `where.exe claude` shows installed but CCS reports "not found"

Improvements:
- Simplified error messages (removed lengthy search location details)
- Reduced codebase by 332 lines (454 deleted, 122 added)
- npm package size reduced: 17.2 KB → 15.9 KB (7.6% smaller)

Cross-Platform Parity:
- bash (lib/ccs): Simplified detection, removed validate_claude_cli
- PowerShell (lib/ccs.ps1): Simplified detection, removed Test-ClaudeCli
- Node.js (bin/*.js): Simplified detection, removed validateClaudeCli
- All versions now use identical trust-the-PATH approach

Files Modified:
- installers/install.ps1: exit→return, download path fix
- lib/ccs.ps1: simplified detection (165 lines removed)
- lib/ccs: simplified detection (172 lines removed)
- bin/claude-detector.js: simplified detection (86 lines removed)
- bin/ccs.js: removed validation calls (31 lines removed)
- .github/workflows/publish-npm.yml: fixed package name
- CHANGELOG.md: comprehensive v2.4.1 release notes
- VERSION, package.json: bumped to 2.4.1

Testing: bash validated, npm syntax checked, manual Windows testing recommended
This commit is contained in:
kaitranntt
2025-11-04 21:54:05 -05:00
parent f066faca43
commit 80a5200cae
11 changed files with 126 additions and 458 deletions
+1 -1
View File
@@ -49,7 +49,7 @@ jobs:
body: |
## Installation
```bash
npm install -g @kai/ccs
npm install -g @kaitranntt/ccs
```
## What's Changed
+1
View File
@@ -14,3 +14,4 @@
# Plans directory
plans/
*.tgz
+36
View File
@@ -4,6 +4,42 @@ All notable changes to CCS will be documented here.
Format based on [Keep a Changelog](https://keepachangelog.com/).
## [2.4.1] - 2025-11-04
### Fixed
- **CRITICAL: PowerShell Terminal Termination**: Fixed PowerShell 7 terminal closing when using `irm | iex` installation
- Changed `exit 1` to `return` in install.ps1 line 229 for piped script contexts
- Terminal now stays open on installation errors, showing error messages properly
- Affects: Windows PowerShell 5.1+, PowerShell 7+, all piped installations
- **Installation Download Path**: Fixed incorrect download path in install.ps1
- Changed `/ccs.ps1` to `/lib/ccs.ps1` (line 223) to match repository structure
- Resolves standalone installation failures from GitHub
- **Claude CLI Detection**: Simplified detection logic, removed overengineered validation
- Removed complex path validation that failed with npm-installed Claude CLI (.cmd wrappers)
- Now trusts system PATH for Claude detection (standard case for users)
- Falls back to CCS_CLAUDE_PATH if set for custom installations
- Affects: Both bash (lib/ccs) and PowerShell (lib/ccs.ps1) versions
- Fixes: `where.exe claude` shows Claude exists but CCS reports "not found"
### Changed
- **Error Messages**: Simplified Claude CLI not found error message
- Removed lengthy "searched locations" output
- Focused on actionable solutions (install, verify, set custom path)
- Cleaner UX with less information overload
### Technical Details
- **Files Modified**:
- `installers/install.ps1`: Line 229 (exit → return), Line 223 (download path fix)
- `lib/ccs.ps1`: Lines 27-72 (simplified detection, removed Test-ClaudeCli function)
- `lib/ccs`: Lines 32-72 (simplified detection, removed validate_claude_cli function)
- `bin/claude-detector.js`: Lines 1-113 (simplified detection for npm package)
- `bin/ccs.js`: Removed validateClaudeCli calls, simplified error handling
- **Root Cause**: `exit` in piped PowerShell scripts terminates entire session, not just script
- **Solution**: `return` exits script scope only, preserving terminal
- **Cross-Platform Parity**: Applied same simplification to bash, PowerShell, and Node.js versions
- **npm Package**: Updated with simplified detection logic (v2.4.1)
- **Testing**: Validated bash version, npm package syntax, manual Windows testing recommended
## [2.4.0] - 2025-11-04
### ⚠️ BREAKING CHANGES
+1 -1
View File
@@ -1 +1 @@
2.4.0
2.4.1
+4 -42
View File
@@ -5,7 +5,7 @@ const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const { showError, colors } = require('./helpers');
const { detectClaudeCli, validateClaudeCli, showClaudeNotFoundError } = require('./claude-detector');
const { detectClaudeCli, showClaudeNotFoundError } = require('./claude-detector');
const { getSettingsPath } = require('./config-manager');
// Version (sync with package.json)
@@ -26,21 +26,8 @@ function handleVersionCommand() {
}
function handleHelpCommand(remainingArgs) {
// Detect and validate Claude CLI
const claudeCli = detectClaudeCli();
if (!claudeCli) {
showClaudeNotFoundError();
process.exit(1);
}
try {
validateClaudeCli(claudeCli);
} catch (e) {
showError(e.message);
process.exit(1);
}
// Execute claude --help
const child = spawn(claudeCli, ['--help', ...remainingArgs], { stdio: 'inherit' });
@@ -53,7 +40,7 @@ function handleHelpCommand(remainingArgs) {
});
child.on('error', (err) => {
console.error(`Error executing claude --help: ${err.message}`);
showClaudeNotFoundError();
process.exit(1);
});
}
@@ -124,18 +111,6 @@ function main() {
if (profile === 'default') {
const claudeCli = detectClaudeCli();
if (!claudeCli) {
showClaudeNotFoundError();
process.exit(1);
}
try {
validateClaudeCli(claudeCli);
} catch (e) {
showError(e.message);
process.exit(1);
}
// Execute claude with args
const child = spawn(claudeCli, remainingArgs, { stdio: 'inherit' });
@@ -148,7 +123,7 @@ function main() {
});
child.on('error', (err) => {
console.error(`Error executing claude: ${err.message}`);
showClaudeNotFoundError();
process.exit(1);
});
@@ -161,19 +136,6 @@ function main() {
// Detect Claude CLI
const claudeCli = detectClaudeCli();
if (!claudeCli) {
showClaudeNotFoundError();
process.exit(1);
}
// Validate Claude CLI path
try {
validateClaudeCli(claudeCli);
} catch (e) {
showError(e.message);
process.exit(1);
}
// Execute claude with --settings
const claudeArgs = ['--settings', settingsPath, ...remainingArgs];
const child = spawn(claudeCli, claudeArgs, { stdio: 'inherit' });
@@ -187,7 +149,7 @@ function main() {
});
child.on('error', (err) => {
console.error(`Error executing claude: ${err.message}`);
showClaudeNotFoundError();
process.exit(1);
});
}
+27 -125
View File
@@ -1,156 +1,58 @@
'use strict';
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { showError, expandPath, isPathSafe } = require('./helpers');
const { showError, expandPath } = require('./helpers');
// Detect Claude CLI executable
function detectClaudeCli() {
// Priority 1: CCS_CLAUDE_PATH environment variable
// Priority 1: CCS_CLAUDE_PATH environment variable (if user wants custom path)
if (process.env.CCS_CLAUDE_PATH) {
const ccsPath = expandPath(process.env.CCS_CLAUDE_PATH);
if (fs.existsSync(ccsPath) && isExecutable(ccsPath)) {
// Basic validation: file exists
if (fs.existsSync(ccsPath)) {
return ccsPath;
}
// Invalid CCS_CLAUDE_PATH - continue to fallbacks
// Invalid CCS_CLAUDE_PATH - show warning and fall back to PATH
console.warn('[!] Warning: CCS_CLAUDE_PATH is set but file not found:', ccsPath);
console.warn(' Falling back to system PATH lookup...');
}
// Priority 2: Check if claude in PATH
try {
const claudePath = execSync(
process.platform === 'win32' ? 'where claude' : 'which claude',
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
).trim().split('\n')[0];
if (claudePath && fs.existsSync(claudePath)) {
return claudePath;
}
} catch (e) {
// Not in PATH, continue to common locations
}
// Priority 3: Check common installation locations
const commonLocations = getCommonLocations();
for (const location of commonLocations) {
const expandedPath = expandPath(location);
if (fs.existsSync(expandedPath) && isExecutable(expandedPath)) {
return expandedPath;
}
}
// Not found
return null;
}
// Get platform-specific common locations
function getCommonLocations() {
const home = require('os').homedir();
if (process.platform === 'win32') {
return [
path.join(process.env.LOCALAPPDATA || '', 'Claude', 'claude.exe'),
path.join(process.env.PROGRAMFILES || '', 'Claude', 'claude.exe'),
'C:\\Program Files\\Claude\\claude.exe',
'D:\\Program Files\\Claude\\claude.exe',
path.join(home, '.local', 'bin', 'claude.exe')
];
} else if (process.platform === 'darwin') {
return [
'/usr/local/bin/claude',
path.join(home, '.local/bin/claude'),
'/opt/homebrew/bin/claude'
];
} else {
return [
'/usr/local/bin/claude',
path.join(home, '.local/bin/claude'),
'/usr/bin/claude'
];
}
}
// Check if file is executable
function isExecutable(filePath) {
try {
fs.accessSync(filePath, fs.constants.X_OK);
return true;
} catch (e) {
return false;
}
}
// Validate Claude CLI path
function validateClaudeCli(claudePath) {
// Check 1: Empty path
if (!claudePath) {
throw new Error('No path provided');
}
// Check 2: File exists
if (!fs.existsSync(claudePath)) {
throw new Error(`File not found: ${claudePath}`);
}
// Check 3: Is regular file (not directory)
const stats = fs.statSync(claudePath);
if (!stats.isFile()) {
throw new Error(`Path is a directory: ${claudePath}`);
}
// Check 4: Is executable
if (!isExecutable(claudePath)) {
throw new Error(`File is not executable: ${claudePath}\n\nTry: chmod +x ${claudePath}`);
}
// Check 5: Path safety (prevent injection)
if (!isPathSafe(claudePath)) {
throw new Error(`Path contains unsafe characters: ${claudePath}\n\nAllowed: alphanumeric, path separators, spaces, hyphens, underscores, dots`);
}
return true;
// Priority 2: Use 'claude' from PATH (trust the system)
// This is the standard case - if user installed Claude CLI, it's in their PATH
return 'claude';
}
// Show Claude not found error
function showClaudeNotFoundError() {
const envVarStatus = process.env.CCS_CLAUDE_PATH || '(not set)';
const isWindows = process.platform === 'win32';
const errorMsg = `Claude CLI not found
const errorMsg = `Claude CLI not found in PATH
Searched:
- CCS_CLAUDE_PATH: ${envVarStatus}
- System PATH: not found
- Common locations: not found
CCS requires Claude CLI to be installed and available in your PATH.
Solutions:
1. Add Claude CLI to PATH:
${isWindows
? '# Find where Claude is installed\n Get-ChildItem -Path C:\\,D:\\ -Filter claude.exe -Recurse\n\n # Add to PATH\n $env:Path += \';D:\\path\\to\\claude\\directory\'\n [Environment]::SetEnvironmentVariable(\'Path\', $env:Path, \'User\')'
: '# Find where Claude is installed\n sudo find / -name claude 2>/dev/null\n\n # Add to PATH\n export PATH="/path/to/claude/bin:$PATH"\n echo \'export PATH="/path/to/claude/bin:$PATH"\' >> ~/.bashrc\n source ~/.bashrc'
}
2. Or set custom path:
${isWindows
? '$env:CCS_CLAUDE_PATH = \'D:\\full\\path\\to\\claude.exe\'\n [Environment]::SetEnvironmentVariable(\'CCS_CLAUDE_PATH\', \'D:\\full\\path\\to\\claude.exe\', \'User\')'
: 'export CCS_CLAUDE_PATH="/full/path/to/claude"\n echo \'export CCS_CLAUDE_PATH="/full/path/to/claude"\' >> ~/.bashrc\n source ~/.bashrc'
}
3. Or install Claude CLI:
1. Install Claude CLI:
https://docs.claude.com/en/docs/claude-code/installation
Verify installation:
ccs --version`;
2. Verify installation:
${isWindows ? 'Get-Command claude' : 'command -v claude'}
3. If installed but not in PATH, add it:
# Find Claude installation
${isWindows ? 'where.exe claude' : 'which claude'}
# Or set custom path
${isWindows
? '$env:CCS_CLAUDE_PATH = \'C:\\path\\to\\claude.exe\''
: 'export CCS_CLAUDE_PATH=\'/path/to/claude\''
}
Restart your terminal after installation.`;
showError(errorMsg);
}
module.exports = {
detectClaudeCli,
validateClaudeCli,
showClaudeNotFoundError
};
+1 -1
View File
@@ -30,7 +30,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 = "2.4.0"
$CcsVersion = "2.4.1"
# Try to read VERSION file for git installations
if ($ScriptDir) {
+1 -1
View File
@@ -31,7 +31,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="2.4.0"
CCS_VERSION="2.4.1"
# Try to read VERSION file for git installations
if [[ -f "$SCRIPT_DIR/VERSION" ]]; then
+27 -145
View File
@@ -2,7 +2,7 @@
set -euo pipefail
# Version (updated by scripts/bump-version.sh)
CCS_VERSION="2.4.0"
CCS_VERSION="2.4.1"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# --- Color/Format Functions ---
@@ -32,147 +32,44 @@ setup_colors
# --- Claude CLI Detection Logic ---
detect_claude_cli() {
local claude_path=""
# Priority 1: CCS_CLAUDE_PATH environment variable
# Priority 1: CCS_CLAUDE_PATH environment variable (if user wants custom path)
if [[ -n "${CCS_CLAUDE_PATH:-}" ]]; then
if [[ -f "$CCS_CLAUDE_PATH" ]] && [[ -x "$CCS_CLAUDE_PATH" ]]; then
# Basic validation: file exists
if [[ -f "$CCS_CLAUDE_PATH" ]]; then
echo "$CCS_CLAUDE_PATH"
return 0
fi
# Invalid CCS_CLAUDE_PATH - continue to fallbacks
# Warning will be shown later in validation phase
# Invalid CCS_CLAUDE_PATH - show warning and fall back to PATH
echo "[!] Warning: CCS_CLAUDE_PATH is set but file not found: $CCS_CLAUDE_PATH" >&2
echo " Falling back to system PATH lookup..." >&2
fi
# Priority 2: Check if claude in PATH
claude_path=$(command -v claude 2>/dev/null || true)
if [[ -n "$claude_path" ]]; then
echo "$claude_path"
return 0
fi
# Priority 3: Check common installation locations
local common_locations=()
# Platform-specific common locations
if [[ "$OSTYPE" == darwin* ]]; then
# macOS
common_locations=(
"/usr/local/bin/claude"
"$HOME/.local/bin/claude"
"/opt/homebrew/bin/claude"
)
else
# Linux
common_locations=(
"/usr/local/bin/claude"
"$HOME/.local/bin/claude"
"/usr/bin/claude"
)
fi
# Check each common location
for location in "${common_locations[@]}"; do
if [[ -f "$location" ]] && [[ -x "$location" ]]; then
echo "$location"
return 0
fi
done
# Not found
echo ""
return 1
}
# Global variable for validation error message
VALIDATION_ERROR=""
validate_claude_cli() {
local path="$1"
VALIDATION_ERROR=""
# Check 1: Empty path
if [[ -z "$path" ]]; then
VALIDATION_ERROR="No path provided"
return 1
fi
# Check 2: File exists
if [[ ! -e "$path" ]]; then
VALIDATION_ERROR="File not found: $path"
return 1
fi
# Check 3: Is regular file (not directory)
if [[ -d "$path" ]]; then
VALIDATION_ERROR="Path is a directory: $path"
return 1
fi
# Check 4: Is executable
if [[ ! -x "$path" ]]; then
VALIDATION_ERROR="File is not executable: $path
Try: chmod +x $path"
return 1
fi
# Check 5: Path safety (prevent injection)
# Allow: alphanumeric, /, \, :, space, -, _, ., ~
if [[ "$path" =~ [^\;a-zA-Z0-9/\\:\ \._~-] ]]; then
VALIDATION_ERROR="Path contains unsafe characters: $path
Allowed: alphanumeric, path separators, spaces, hyphens, underscores, dots"
return 1
fi
# All checks passed
# Priority 2: Use 'claude' from PATH (trust the system)
# This is the standard case - if user installed Claude CLI, it's in their PATH
echo "claude"
return 0
}
show_claude_not_found_error() {
local env_var_status="${CCS_CLAUDE_PATH:-(not set)}"
msg_error "Claude CLI not found in PATH
msg_error "Claude CLI not found
Searched:
- CCS_CLAUDE_PATH: $env_var_status
- System PATH: not found
- Common locations: not found
CCS requires Claude CLI to be installed and available in your PATH.
Solutions:
1. Add Claude CLI to PATH:
# Find where Claude is installed
sudo find / -name claude 2>/dev/null
# Then add to PATH (replace /path/to with actual path)
export PATH=\"/path/to/claude/bin:\$PATH\"
echo 'export PATH=\"/path/to/claude/bin:\$PATH\"' >> ~/.bashrc
source ~/.bashrc
2. Or set custom path:
export CCS_CLAUDE_PATH=\"/full/path/to/claude\"
echo 'export CCS_CLAUDE_PATH=\"/full/path/to/claude\"' >> ~/.bashrc
source ~/.bashrc
Example (D drive on Windows/WSL):
export CCS_CLAUDE_PATH=\"/mnt/d/Tools/Claude/claude.exe\"
3. Or install Claude CLI:
1. Install Claude CLI:
https://docs.claude.com/en/docs/claude-code/installation
Verify installation:
ccs --version
2. Verify installation:
command -v claude
Debugging:
# Check if claude command exists
command -v claude
3. If installed but not in PATH, add it:
# Find Claude installation
which claude
# Check CCS_CLAUDE_PATH
echo \$CCS_CLAUDE_PATH"
# Or set custom path
export CCS_CLAUDE_PATH='/path/to/claude'
Restart your terminal after installation."
}
CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
@@ -387,20 +284,12 @@ fi
# Special case: help command (check BEFORE profile detection)
if [[ $# -gt 0 ]] && [[ "${1}" == "--help" || "${1}" == "-h" || "${1}" == "help" ]]; then
shift # Remove the help argument
# Detect and validate Claude CLI for help command
CLAUDE_CLI=$(detect_claude_cli)
if [[ -z "$CLAUDE_CLI" ]]; then
if ! exec "$CLAUDE_CLI" --help "$@"; then
show_claude_not_found_error
exit 1
fi
if ! validate_claude_cli "$CLAUDE_CLI"; then
msg_error "$VALIDATION_ERROR"
exit 1
fi
exec "$CLAUDE_CLI" --help "$@"
fi
# Special case: install command (check BEFORE profile detection)
@@ -517,16 +406,9 @@ fi
# Detect Claude CLI executable
CLAUDE_CLI=$(detect_claude_cli)
if [[ -z "$CLAUDE_CLI" ]]; then
# Execute Claude with the profile settings
# If claude is not found, exec will fail and show an error
if ! exec "$CLAUDE_CLI" --settings "$SETTINGS_PATH" "$@"; then
show_claude_not_found_error
exit 1
fi
# Validate detected path
if ! validate_claude_cli "$CLAUDE_CLI"; then
msg_error "$VALIDATION_ERROR"
exit 1
fi
# Execute with validated path
exec "$CLAUDE_CLI" --settings "$SETTINGS_PATH" "$@"
+25 -140
View File
@@ -30,136 +30,49 @@ function Find-ClaudeCli {
[OutputType([string])]
param()
# Priority 1: CCS_CLAUDE_PATH environment variable
# Priority 1: CCS_CLAUDE_PATH environment variable (if user wants custom path)
$CcsClaudePath = $env:CCS_CLAUDE_PATH
if ($CcsClaudePath) {
if ((Test-Path $CcsClaudePath -PathType Leaf) -and
(Get-Command $CcsClaudePath -ErrorAction SilentlyContinue)) {
# Basic validation: file exists
if (Test-Path $CcsClaudePath -PathType Leaf) {
return $CcsClaudePath
}
# Invalid CCS_CLAUDE_PATH - continue to fallbacks
# Warning will be shown later in validation phase
# Invalid CCS_CLAUDE_PATH - show warning and fall back to PATH
Write-Host "[!] Warning: CCS_CLAUDE_PATH is set but file not found: $CcsClaudePath" -ForegroundColor Yellow
Write-Host " Falling back to system PATH lookup..." -ForegroundColor Yellow
}
# Priority 2: Check if claude in PATH
$ClaudeInPath = Get-Command claude -ErrorAction SilentlyContinue
if ($ClaudeInPath) {
return $ClaudeInPath.Source
}
# Priority 3: Check common installation locations
$CommonLocations = @(
"$env:LOCALAPPDATA\Claude\claude.exe",
"$env:PROGRAMFILES\Claude\claude.exe",
"C:\Program Files\Claude\claude.exe",
"D:\Program Files\Claude\claude.exe",
"$env:USERPROFILE\.local\bin\claude.exe"
)
foreach ($Location in $CommonLocations) {
$ExpandedPath = [System.Environment]::ExpandEnvironmentVariables($Location)
if ((Test-Path $ExpandedPath -PathType Leaf) -and
(Get-Command $ExpandedPath -ErrorAction SilentlyContinue)) {
return $ExpandedPath
}
}
# Not found
return ""
}
function Test-ClaudeCli {
[OutputType([bool])]
param(
[Parameter(Mandatory=$true)]
[AllowEmptyString()]
[string]$Path
)
# Check 1: Empty path
if ([string]::IsNullOrWhiteSpace($Path)) {
throw "No path provided"
}
# Check 2: File exists
if (-not (Test-Path $Path)) {
throw "File not found: $Path"
}
# Check 3: Is regular file (not directory)
if (Test-Path $Path -PathType Container) {
throw "Path is a directory: $Path"
}
# Check 4: Is executable (Get-Command can load it)
try {
$null = Get-Command $Path -ErrorAction Stop
} catch {
throw "File is not executable: $Path`n`nCheck file permissions and file type"
}
# Check 5: Path safety (prevent injection)
# Allow: alphanumeric, \, /, :, space, -, _, ., ~
if ($Path -match '[;\|&<>``\$\*\?\[\]''\"()]') {
throw "Path contains unsafe characters: $Path`n`nAllowed: alphanumeric, path separators, spaces, hyphens, underscores, dots"
}
# All checks passed
return $true
# Priority 2: Use 'claude' from PATH (trust the system)
# This is the standard case - if user installed Claude CLI, it's in their PATH
return "claude"
}
function Show-ClaudeNotFoundError {
$EnvVarStatus = if ($env:CCS_CLAUDE_PATH) { $env:CCS_CLAUDE_PATH } else { "(not set)" }
Write-ErrorMsg @"
Claude CLI not found
Claude CLI not found in PATH
Searched:
- CCS_CLAUDE_PATH: $EnvVarStatus
- System PATH: not found
- Common locations: not found
CCS requires Claude CLI to be installed and available in your PATH.
Solutions:
1. Add Claude CLI to PATH:
# Find where Claude is installed
Get-ChildItem -Path C:\,D:\ -Filter claude.exe -Recurse -ErrorAction SilentlyContinue | Select-Object FullName
# Then add to PATH (replace with actual path)
`$env:Path += ';D:\path\to\claude\directory'
[Environment]::SetEnvironmentVariable('Path', `$env:Path, 'User')
# Restart terminal for changes to take effect
2. Or set custom path:
`$env:CCS_CLAUDE_PATH = 'D:\full\path\to\claude.exe'
[Environment]::SetEnvironmentVariable('CCS_CLAUDE_PATH', 'D:\full\path\to\claude.exe', 'User')
Example (D drive installation):
`$env:CCS_CLAUDE_PATH = 'D:\Tools\Claude\claude.exe'
[Environment]::SetEnvironmentVariable('CCS_CLAUDE_PATH', 'D:\Tools\Claude\claude.exe', 'User')
# Restart terminal for changes to take effect
3. Or install Claude CLI:
1. Install Claude CLI:
https://docs.claude.com/en/docs/claude-code/installation
Verify installation:
ccs --version
2. Verify installation:
Get-Command claude
Debugging:
# Check if claude command exists
Get-Command claude -ErrorAction SilentlyContinue
3. If installed but not in PATH, add it:
# Find Claude installation
where.exe claude
# Check CCS_CLAUDE_PATH
`$env:CCS_CLAUDE_PATH
# Or set custom path
`$env:CCS_CLAUDE_PATH = 'C:\path\to\claude.exe'
Restart your terminal after installation.
"@
}
# Version (updated by scripts/bump-version.sh)
$CcsVersion = "2.4.0"
$CcsVersion = "2.4.1"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
# Installation function for commands and skills
@@ -382,21 +295,8 @@ if ($FirstArg -eq "version" -or $FirstArg -eq "--version" -or $FirstArg -eq "-v"
# Special case: help command (check BEFORE profile detection)
if ($FirstArg -eq "--help" -or $FirstArg -eq "-h" -or $FirstArg -eq "help") {
# Detect and validate Claude CLI for help command
$ClaudeCli = Find-ClaudeCli
if ([string]::IsNullOrEmpty($ClaudeCli)) {
Show-ClaudeNotFoundError
exit 1
}
try {
$null = Test-ClaudeCli -Path $ClaudeCli
} catch {
Write-ErrorMsg $_.Exception.Message
exit 1
}
try {
if ($RemainingArgs) {
& $ClaudeCli --help @RemainingArgs
@@ -405,8 +305,7 @@ if ($FirstArg -eq "--help" -or $FirstArg -eq "-h" -or $FirstArg -eq "help") {
}
exit $LASTEXITCODE
} catch {
Write-Host "Error: Failed to execute claude --help" -ForegroundColor Red
Write-Host $_.Exception.Message
Show-ClaudeNotFoundError
exit 1
}
}
@@ -580,20 +479,7 @@ Solutions:
# Detect Claude CLI executable
$ClaudeCli = Find-ClaudeCli
if ([string]::IsNullOrEmpty($ClaudeCli)) {
Show-ClaudeNotFoundError
exit 1
}
# Validate detected path
try {
$null = Test-ClaudeCli -Path $ClaudeCli
} catch {
Write-ErrorMsg $_.Exception.Message
exit 1
}
# Execute with validated path
# Execute Claude with the profile settings
try {
if ($RemainingArgs) {
& $ClaudeCli --settings $SettingsPath @RemainingArgs
@@ -602,7 +488,6 @@ try {
}
exit $LASTEXITCODE
} catch {
Write-Host "Error: Failed to execute claude" -ForegroundColor Red
Write-Host $_.Exception.Message
Show-ClaudeNotFoundError
exit 1
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@kai/ccs",
"version": "2.4.0",
"name": "@kaitranntt/ccs",
"version": "2.4.1",
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
"keywords": [
"cli",