mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-15 20:20:09 +00:00
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:
@@ -49,7 +49,7 @@ jobs:
|
||||
body: |
|
||||
## Installation
|
||||
```bash
|
||||
npm install -g @kai/ccs
|
||||
npm install -g @kaitranntt/ccs
|
||||
```
|
||||
|
||||
## What's Changed
|
||||
|
||||
@@ -14,3 +14,4 @@
|
||||
|
||||
# Plans directory
|
||||
plans/
|
||||
*.tgz
|
||||
@@ -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
|
||||
|
||||
+4
-42
@@ -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
@@ -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
|
||||
};
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user