@kaitranntt/ccs 2.4.1 → 2.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 2.4.1
1
+ 2.4.3
package/bin/ccs.js CHANGED
@@ -11,6 +11,30 @@ const { getSettingsPath } = require('./config-manager');
11
11
  // Version (sync with package.json)
12
12
  const CCS_VERSION = require('../package.json').version;
13
13
 
14
+ // Helper: Get spawn options for claude execution
15
+ // On Windows, .cmd/.bat/.ps1 files need shell: true
16
+ function getSpawnOptions(claudePath) {
17
+ const isWindows = process.platform === 'win32';
18
+ const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(claudePath);
19
+
20
+ return {
21
+ stdio: 'inherit',
22
+ shell: needsShell,
23
+ windowsHide: true // Hide the console window on Windows
24
+ };
25
+ }
26
+
27
+ // Helper: Escape arguments for shell execution to prevent security vulnerabilities
28
+ function escapeShellArg(arg) {
29
+ if (process.platform !== 'win32') {
30
+ // Unix-like systems: escape single quotes and wrap in single quotes
31
+ return "'" + arg.replace(/'/g, "'\"'\"'") + "'";
32
+ } else {
33
+ // Windows: escape double quotes and wrap in double quotes
34
+ return '"' + arg.replace(/"/g, '""') + '"';
35
+ }
36
+ }
37
+
14
38
  // Special command handlers
15
39
  function handleVersionCommand() {
16
40
  console.log(`CCS (Claude Code Switch) version ${CCS_VERSION}`);
@@ -28,8 +52,25 @@ function handleVersionCommand() {
28
52
  function handleHelpCommand(remainingArgs) {
29
53
  const claudeCli = detectClaudeCli();
30
54
 
55
+ // Check if claude was found
56
+ if (!claudeCli) {
57
+ showClaudeNotFoundError();
58
+ process.exit(1);
59
+ }
60
+
31
61
  // Execute claude --help
32
- const child = spawn(claudeCli, ['--help', ...remainingArgs], { stdio: 'inherit' });
62
+ const spawnOpts = getSpawnOptions(claudeCli);
63
+ let claudeArgs, child;
64
+
65
+ if (spawnOpts.shell) {
66
+ // When shell is required, escape arguments properly
67
+ claudeArgs = [claudeCli, '--help', ...remainingArgs].map(escapeShellArg).join(' ');
68
+ child = spawn(claudeArgs, spawnOpts);
69
+ } else {
70
+ // When no shell needed, use arguments array directly
71
+ claudeArgs = ['--help', ...remainingArgs];
72
+ child = spawn(claudeCli, claudeArgs, spawnOpts);
73
+ }
33
74
 
34
75
  child.on('exit', (code, signal) => {
35
76
  if (signal) {
@@ -111,8 +152,25 @@ function main() {
111
152
  if (profile === 'default') {
112
153
  const claudeCli = detectClaudeCli();
113
154
 
155
+ // Check if claude was found
156
+ if (!claudeCli) {
157
+ showClaudeNotFoundError();
158
+ process.exit(1);
159
+ }
160
+
114
161
  // Execute claude with args
115
- const child = spawn(claudeCli, remainingArgs, { stdio: 'inherit' });
162
+ const spawnOpts = getSpawnOptions(claudeCli);
163
+ let claudeArgs, child;
164
+
165
+ if (spawnOpts.shell) {
166
+ // When shell is required, escape arguments properly
167
+ claudeArgs = [claudeCli, ...remainingArgs].map(escapeShellArg).join(' ');
168
+ child = spawn(claudeArgs, spawnOpts);
169
+ } else {
170
+ // When no shell needed, use arguments array directly
171
+ claudeArgs = remainingArgs;
172
+ child = spawn(claudeCli, claudeArgs, spawnOpts);
173
+ }
116
174
 
117
175
  child.on('exit', (code, signal) => {
118
176
  if (signal) {
@@ -136,9 +194,26 @@ function main() {
136
194
  // Detect Claude CLI
137
195
  const claudeCli = detectClaudeCli();
138
196
 
197
+ // Check if claude was found
198
+ if (!claudeCli) {
199
+ showClaudeNotFoundError();
200
+ process.exit(1);
201
+ }
202
+
139
203
  // Execute claude with --settings
140
- const claudeArgs = ['--settings', settingsPath, ...remainingArgs];
141
- const child = spawn(claudeCli, claudeArgs, { stdio: 'inherit' });
204
+ const claudeArgsList = ['--settings', settingsPath, ...remainingArgs];
205
+ const spawnOpts = getSpawnOptions(claudeCli);
206
+ let claudeArgs, child;
207
+
208
+ if (spawnOpts.shell) {
209
+ // When shell is required, escape arguments properly
210
+ claudeArgs = [claudeCli, ...claudeArgsList].map(escapeShellArg).join(' ');
211
+ child = spawn(claudeArgs, spawnOpts);
212
+ } else {
213
+ // When no shell needed, use arguments array directly
214
+ claudeArgs = claudeArgsList;
215
+ child = spawn(claudeCli, claudeArgs, spawnOpts);
216
+ }
142
217
 
143
218
  child.on('exit', (code, signal) => {
144
219
  if (signal) {
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const fs = require('fs');
4
+ const { execSync } = require('child_process');
4
5
  const { showError, expandPath } = require('./helpers');
5
6
 
6
7
  // Detect Claude CLI executable
@@ -17,19 +18,62 @@ function detectClaudeCli() {
17
18
  console.warn(' Falling back to system PATH lookup...');
18
19
  }
19
20
 
20
- // Priority 2: Use 'claude' from PATH (trust the system)
21
- // This is the standard case - if user installed Claude CLI, it's in their PATH
22
- return 'claude';
21
+ // Priority 2: Resolve 'claude' from PATH using which/where.exe
22
+ // This fixes Windows npm installation where spawn() can't resolve bare command names
23
+ // SECURITY: Commands are hardcoded literals with no user input - safe from injection
24
+ const isWindows = process.platform === 'win32';
25
+
26
+ try {
27
+ const cmd = isWindows ? 'where.exe claude' : 'which claude';
28
+ const result = execSync(cmd, {
29
+ encoding: 'utf8',
30
+ stdio: ['ignore', 'pipe', 'ignore'],
31
+ timeout: 5000 // 5 second timeout to prevent hangs
32
+ }).trim();
33
+
34
+ // where.exe may return multiple lines (all matches in PATH order)
35
+ const matches = result.split('\n').map(p => p.trim()).filter(p => p);
36
+
37
+ if (isWindows) {
38
+ // On Windows, prefer executables with extensions (.exe, .cmd, .bat)
39
+ // where.exe often returns file without extension first, then the actual .cmd wrapper
40
+ const withExtension = matches.find(p => /\.(exe|cmd|bat|ps1)$/i.test(p));
41
+ const claudePath = withExtension || matches[0];
42
+
43
+ if (claudePath && fs.existsSync(claudePath)) {
44
+ return claudePath;
45
+ }
46
+ } else {
47
+ // On Unix, first match is fine
48
+ const claudePath = matches[0];
49
+
50
+ if (claudePath && fs.existsSync(claudePath)) {
51
+ return claudePath;
52
+ }
53
+ }
54
+ } catch (err) {
55
+ // Command failed - claude not in PATH
56
+ // Fall through to return null
57
+ }
58
+
59
+ // Priority 3: Claude not found
60
+ return null;
23
61
  }
24
62
 
25
- // Show Claude not found error
63
+ // Show Claude not found error with diagnostics
26
64
  function showClaudeNotFoundError() {
27
65
  const isWindows = process.platform === 'win32';
66
+ const pathDirs = (process.env.PATH || '').split(isWindows ? ';' : ':');
28
67
 
29
68
  const errorMsg = `Claude CLI not found in PATH
30
69
 
31
70
  CCS requires Claude CLI to be installed and available in your PATH.
32
71
 
72
+ [i] Diagnostic Info:
73
+ Platform: ${process.platform}
74
+ PATH directories: ${pathDirs.length}
75
+ Looking for: claude${isWindows ? '.exe' : ''}
76
+
33
77
  Solutions:
34
78
  1. Install Claude CLI:
35
79
  https://docs.claude.com/en/docs/claude-code/installation
package/lib/ccs CHANGED
@@ -2,7 +2,7 @@
2
2
  set -euo pipefail
3
3
 
4
4
  # Version (updated by scripts/bump-version.sh)
5
- CCS_VERSION="2.4.1"
5
+ CCS_VERSION="2.4.3"
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
7
 
8
8
  # --- Color/Format Functions ---
package/lib/ccs.ps1 CHANGED
@@ -72,7 +72,7 @@ Restart your terminal after installation.
72
72
  }
73
73
 
74
74
  # Version (updated by scripts/bump-version.sh)
75
- $CcsVersion = "2.4.1"
75
+ $CcsVersion = "2.4.3"
76
76
  $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
77
77
 
78
78
  # Installation function for commands and skills
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "2.4.1",
3
+ "version": "2.4.3",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",