fix: parse Windows .cmd wrapper to extract real CLI script path

v0.10.7 skipped .cmd wrappers entirely, but the SDK's internal
fallback can't find the CLI either. Now we read the .cmd file,
extract the actual .js path (e.g. node_modules/@anthropic-ai/
claude-code/cli.js), resolve %~dp0 to the wrapper's directory,
and pass the resolved script path to the SDK.

Regex patterns are scoped to paths containing "claude" to avoid
matching unrelated .js files in the wrapper.

Bump version to 0.10.8.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
7418andClaude Opus 4.6 committed 2026-02-13 17:07:21 +08:00
1 parent c0e96bd1aa
commit 01e7e0c1e3
3 files changed
+46 -6

No files matched your search

+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "codepilot",
"version": "0.10.7",
"version": "0.10.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "codepilot",
"version": "0.10.7",
"version": "0.10.8",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.33",
"@hugeicons/core-free-icons": "^3.1.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "codepilot",
"version": "0.10.7",
"version": "0.10.8",
"private": true,
"author": {
"name": "op7418",
+43 -3
View File
@@ -49,6 +49,41 @@ function sanitizeEnv(env: Record<string, string>): Record<string, string> {
return clean;
}
/**
* On Windows, npm installs CLI tools as .cmd wrappers that can't be
* spawned without shell:true. Parse the wrapper to extract the real
* .js script path so we can pass it to the SDK directly.
*/
function resolveScriptFromCmd(cmdPath: string): string | undefined {
try {
const content = fs.readFileSync(cmdPath, 'utf-8');
const cmdDir = path.dirname(cmdPath);
// npm .cmd wrappers typically contain a line like:
// "%~dp0\node_modules\@anthropic-ai\claude-code\cli.js" %*
// Match paths containing claude-code or claude-agent and ending in .js
const patterns = [
// Quoted: "%~dp0\...\cli.js"
/"%~dp0\\([^"]*claude[^"]*\.js)"/i,
// Unquoted: %~dp0\...\cli.js
/%~dp0\\(\S*claude\S*\.js)/i,
// Quoted with %dp0%: "%dp0%\...\cli.js"
/"%dp0%\\([^"]*claude[^"]*\.js)"/i,
];
for (const re of patterns) {
const m = content.match(re);
if (m) {
const resolved = path.normalize(path.join(cmdDir, m[1]));
if (fs.existsSync(resolved)) return resolved;
}
}
} catch {
// ignore read errors
}
return undefined;
}
let cachedClaudePath: string | null | undefined;
function findClaudePath(): string | undefined {
@@ -296,13 +331,18 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
// Find claude binary for packaged app where PATH is limited.
// On Windows, npm installs Claude CLI as a .cmd wrapper which cannot
// be spawned directly without shell:true — skip it and let the SDK
// resolve the executable internally.
// be spawned directly without shell:true. Parse the wrapper to
// extract the real .js script path and pass that to the SDK instead.
const claudePath = findClaudePath();
if (claudePath) {
const ext = path.extname(claudePath).toLowerCase();
if (ext === '.cmd' || ext === '.bat') {
console.warn('[claude-client] Ignoring .cmd/.bat wrapper, falling back to SDK resolution:', claudePath);
const scriptPath = resolveScriptFromCmd(claudePath);
if (scriptPath) {
queryOptions.pathToClaudeCodeExecutable = scriptPath;
} else {
console.warn('[claude-client] Could not resolve .js path from .cmd wrapper, falling back to SDK resolution:', claudePath);
}
} else {
queryOptions.pathToClaudeCodeExecutable = claudePath;
}