@kaitranntt/ccs 2.4.1 → 2.4.2

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.2
package/bin/ccs.js CHANGED
@@ -11,6 +11,18 @@ 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 // Required for .cmd files on Windows
23
+ };
24
+ }
25
+
14
26
  // Special command handlers
15
27
  function handleVersionCommand() {
16
28
  console.log(`CCS (Claude Code Switch) version ${CCS_VERSION}`);
@@ -28,8 +40,15 @@ function handleVersionCommand() {
28
40
  function handleHelpCommand(remainingArgs) {
29
41
  const claudeCli = detectClaudeCli();
30
42
 
43
+ // Check if claude was found
44
+ if (!claudeCli) {
45
+ showClaudeNotFoundError();
46
+ process.exit(1);
47
+ }
48
+
31
49
  // Execute claude --help
32
- const child = spawn(claudeCli, ['--help', ...remainingArgs], { stdio: 'inherit' });
50
+ const spawnOpts = getSpawnOptions(claudeCli);
51
+ const child = spawn(claudeCli, ['--help', ...remainingArgs], spawnOpts);
33
52
 
34
53
  child.on('exit', (code, signal) => {
35
54
  if (signal) {
@@ -111,8 +130,15 @@ function main() {
111
130
  if (profile === 'default') {
112
131
  const claudeCli = detectClaudeCli();
113
132
 
133
+ // Check if claude was found
134
+ if (!claudeCli) {
135
+ showClaudeNotFoundError();
136
+ process.exit(1);
137
+ }
138
+
114
139
  // Execute claude with args
115
- const child = spawn(claudeCli, remainingArgs, { stdio: 'inherit' });
140
+ const spawnOpts = getSpawnOptions(claudeCli);
141
+ const child = spawn(claudeCli, remainingArgs, spawnOpts);
116
142
 
117
143
  child.on('exit', (code, signal) => {
118
144
  if (signal) {
@@ -136,9 +162,16 @@ function main() {
136
162
  // Detect Claude CLI
137
163
  const claudeCli = detectClaudeCli();
138
164
 
165
+ // Check if claude was found
166
+ if (!claudeCli) {
167
+ showClaudeNotFoundError();
168
+ process.exit(1);
169
+ }
170
+
139
171
  // Execute claude with --settings
140
172
  const claudeArgs = ['--settings', settingsPath, ...remainingArgs];
141
- const child = spawn(claudeCli, claudeArgs, { stdio: 'inherit' });
173
+ const spawnOpts = getSpawnOptions(claudeCli);
174
+ const child = spawn(claudeCli, claudeArgs, spawnOpts);
142
175
 
143
176
  child.on('exit', (code, signal) => {
144
177
  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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",