Skip to content

Commit 01e7e0c

Browse files
7418claude
andcommitted
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>
1 parent c0e96bd commit 01e7e0c

File tree

3 files changed

+46
-6
lines changed

3 files changed

+46
-6
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codepilot",
3-
"version": "0.10.7",
3+
"version": "0.10.8",
44
"private": true,
55
"author": {
66
"name": "op7418",

src/lib/claude-client.ts

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,41 @@ function sanitizeEnv(env: Record<string, string>): Record<string, string> {
4949
return clean;
5050
}
5151

52+
/**
53+
* On Windows, npm installs CLI tools as .cmd wrappers that can't be
54+
* spawned without shell:true. Parse the wrapper to extract the real
55+
* .js script path so we can pass it to the SDK directly.
56+
*/
57+
function resolveScriptFromCmd(cmdPath: string): string | undefined {
58+
try {
59+
const content = fs.readFileSync(cmdPath, 'utf-8');
60+
const cmdDir = path.dirname(cmdPath);
61+
62+
// npm .cmd wrappers typically contain a line like:
63+
// "%~dp0\node_modules\@anthropic-ai\claude-code\cli.js" %*
64+
// Match paths containing claude-code or claude-agent and ending in .js
65+
const patterns = [
66+
// Quoted: "%~dp0\...\cli.js"
67+
/"%~dp0\\([^"]*claude[^"]*\.js)"/i,
68+
// Unquoted: %~dp0\...\cli.js
69+
/%~dp0\\(\S*claude\S*\.js)/i,
70+
// Quoted with %dp0%: "%dp0%\...\cli.js"
71+
/"%dp0%\\([^"]*claude[^"]*\.js)"/i,
72+
];
73+
74+
for (const re of patterns) {
75+
const m = content.match(re);
76+
if (m) {
77+
const resolved = path.normalize(path.join(cmdDir, m[1]));
78+
if (fs.existsSync(resolved)) return resolved;
79+
}
80+
}
81+
} catch {
82+
// ignore read errors
83+
}
84+
return undefined;
85+
}
86+
5287
let cachedClaudePath: string | null | undefined;
5388

5489
function findClaudePath(): string | undefined {
@@ -296,13 +331,18 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream<strin
296331

297332
// Find claude binary for packaged app where PATH is limited.
298333
// On Windows, npm installs Claude CLI as a .cmd wrapper which cannot
299-
// be spawned directly without shell:true — skip it and let the SDK
300-
// resolve the executable internally.
334+
// be spawned directly without shell:true. Parse the wrapper to
335+
// extract the real .js script path and pass that to the SDK instead.
301336
const claudePath = findClaudePath();
302337
if (claudePath) {
303338
const ext = path.extname(claudePath).toLowerCase();
304339
if (ext === '.cmd' || ext === '.bat') {
305-
console.warn('[claude-client] Ignoring .cmd/.bat wrapper, falling back to SDK resolution:', claudePath);
340+
const scriptPath = resolveScriptFromCmd(claudePath);
341+
if (scriptPath) {
342+
queryOptions.pathToClaudeCodeExecutable = scriptPath;
343+
} else {
344+
console.warn('[claude-client] Could not resolve .js path from .cmd wrapper, falling back to SDK resolution:', claudePath);
345+
}
306346
} else {
307347
queryOptions.pathToClaudeCodeExecutable = claudePath;
308348
}

0 commit comments

Comments
 (0)