Files
ccs/src/utils/claude-detector.ts
T
kaitranntt 77d026869e fix(tsconfig): remove duplicate compiler options
- Remove duplicate allowSyntheticDefaultImports (lines 16 & 42)
- Remove duplicate skipLibCheck (lines 18 & 46)
- Add TypeScript source files for npm package migration
- Verify build pipeline works (42/42 tests pass)
2025-11-26 19:24:40 -05:00

91 lines
2.8 KiB
TypeScript

import * as fs from 'fs';
import { execSync } from 'child_process';
import { expandPath } from './helpers';
import { ClaudeCliInfo } from '../types';
/**
* Detect Claude CLI executable
*/
export function detectClaudeCli(): string | null {
// 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);
// Basic validation: file exists
if (fs.existsSync(ccsPath)) {
return ccsPath;
}
// 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: Resolve 'claude' from PATH using which/where.exe
// This fixes Windows npm installation where spawn() can't resolve bare command names
// SECURITY: Commands are hardcoded literals with no user input - safe from injection
const isWindows = process.platform === 'win32';
try {
const cmd = isWindows ? 'where.exe claude' : 'which claude';
const result = execSync(cmd, {
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
timeout: 5000 // 5 second timeout to prevent hangs
}).trim();
// where.exe may return multiple lines (all matches in PATH order)
const matches = result.split('\n').map(p => p.trim()).filter(p => p);
if (isWindows) {
// On Windows, prefer executables with extensions (.exe, .cmd, .bat)
// where.exe often returns file without extension first, then the actual .cmd wrapper
const withExtension = matches.find(p => /\.(exe|cmd|bat|ps1)$/i.test(p));
const claudePath = withExtension || matches[0];
if (claudePath && fs.existsSync(claudePath)) {
return claudePath;
}
} else {
// On Unix, first match is fine
const claudePath = matches[0];
if (claudePath && fs.existsSync(claudePath)) {
return claudePath;
}
}
} catch (err) {
// Command failed - claude not in PATH
// Fall through to return null
}
// Priority 3: Claude not found
return null;
}
/**
* Get detailed Claude CLI information
*/
export function getClaudeCliInfo(): ClaudeCliInfo | null {
const claudePath = detectClaudeCli();
if (!claudePath) {
return null;
}
const isWindows = process.platform === 'win32';
const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(claudePath);
return {
path: claudePath,
isWindows,
needsShell
};
}
/**
* Show Claude not found error
*/
export function showClaudeNotFoundError(): never {
console.error('ERROR: Claude CLI not found in PATH');
console.error('Install from: https://docs.claude.com/en/docs/claude-code/installation');
process.exit(1);
}