Repository navigation
Expand file tree
/
Copy pathClaudeExecutable.ts
More file actions
97 lines (86 loc) · 3.81 KB
/
Copy pathClaudeExecutable.ts
File metadata and controls
97 lines (86 loc) · 3.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
// @effect-diagnostics nodeBuiltinImport:off
import * as NodeFS from "node:fs";
import * as NodePath from "node:path";
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import { SpawnExecutableResolution } from "@t3tools/shared/shell";
import * as Context from "effect/Context";
import * as Effect from "effect/Effect";
/**
* Windows launcher-script extensions that Node cannot spawn without a shell
* (`spawn EINVAL` since Node 20.12) and that the Claude Agent SDK therefore
* cannot use as `pathToClaudeCodeExecutable`.
*/
const WINDOWS_SHIM_EXTENSIONS: ReadonlySet<string> = new Set([".cmd", ".bat", ".ps1"]);
/**
* Whether `filePath` names a Windows launcher script that the Claude Agent SDK
* cannot spawn directly as `pathToClaudeCodeExecutable`.
*/
export const isWindowsClaudeLauncherShimPath = (filePath: string): boolean =>
WINDOWS_SHIM_EXTENSIONS.has(NodePath.win32.extname(filePath).toLowerCase());
/**
* Entry points of the npm `@anthropic-ai/claude-code` package relative to the
* global `node_modules` directory that sits next to the npm launcher shim.
* Newer package versions ship a native `bin/claude.exe`; older versions only
* ship `cli.js`, which the SDK runs with a JavaScript runtime.
*/
const NPM_PACKAGE_ENTRY_CANDIDATES = [
["node_modules", "@anthropic-ai", "claude-code", "bin", "claude.exe"],
["node_modules", "@anthropic-ai", "claude-code", "cli.js"],
] as const;
export type ExecutableFileCheck = (filePath: string) => boolean;
function isExistingFile(filePath: string): boolean {
try {
return NodeFS.statSync(filePath).isFile();
} catch {
return false;
}
}
/** Injectable file-existence check so tests can run against a fake filesystem. */
export const ClaudeExecutableFileCheck = Context.Reference<ExecutableFileCheck>(
"server/provider/Drivers/ClaudeExecutableFileCheck",
{
defaultValue: () => isExistingFile,
},
);
/**
* Resolves the configured Claude binary path into a value the Claude Agent
* SDK can spawn directly via `pathToClaudeCodeExecutable`.
*
* The SDK spawns the given path without a shell and without Windows PATH /
* PATHEXT resolution, so a bare command name like `claude` fails with
* "native binary not found" and an npm `claude.cmd` shim fails with
* `spawn EINVAL`. CLI probes avoid this via `resolveSpawnCommand`, which can
* fall back to `shell: true`; the SDK offers no such escape hatch.
*
* On Windows this resolves the command against PATH/PATHEXT and, when the
* result is an npm launcher shim, follows it to the real package entry
* (`bin/claude.exe`, or `cli.js` for older package versions). On other
* platforms the configured value is returned unchanged.
*/
export const resolveClaudeSdkExecutablePath = Effect.fn("resolveClaudeSdkExecutablePath")(
function* (binaryPath: string, environment: NodeJS.ProcessEnv): Effect.fn.Return<string> {
const platform = yield* HostProcessPlatform;
if (platform !== "win32") {
return binaryPath;
}
const resolveExecutable = yield* SpawnExecutableResolution;
const isFile = yield* ClaudeExecutableFileCheck;
const resolved = resolveExecutable(binaryPath, platform, environment) ?? binaryPath;
const extension = NodePath.win32.extname(resolved).toLowerCase();
if (!WINDOWS_SHIM_EXTENSIONS.has(extension)) {
return resolved;
}
const shimDirectory = NodePath.win32.dirname(resolved);
for (const entrySegments of NPM_PACKAGE_ENTRY_CANDIDATES) {
const candidate = NodePath.win32.join(shimDirectory, ...entrySegments);
if (isFile(candidate)) {
return candidate;
}
}
yield* Effect.logWarning(
"Claude launcher shim resolved but no known package entry was found next to it; the Claude Agent SDK cannot spawn launcher scripts directly.",
{ binaryPath, resolvedShimPath: resolved },
);
return binaryPath;
},
);