vexp-cli 2.0.3 → 2.0.5
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/dist/agent-config.js +46 -23
- package/dist/binary.js +116 -1
- package/dist/cli.js +254 -56
- package/mcp/mcp-server.cjs +10 -7
- package/package.json +6 -6
package/dist/agent-config.js
CHANGED
|
@@ -186,13 +186,13 @@ export function configureAgents(workspaceRoot, binaryPath, version, agentFilter,
|
|
|
186
186
|
if (detector.mcpConfigFile) {
|
|
187
187
|
const mcpConfigPath = path.join(workspaceRoot, detector.mcpConfigFile);
|
|
188
188
|
const alwaysAllow = detector.agent === "Windsurf" ? VEXP_TOOLS : undefined;
|
|
189
|
-
if (writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath)) {
|
|
189
|
+
if (writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath, workspaceRoot)) {
|
|
190
190
|
mcpConfigs.push(detector.mcpConfigFile);
|
|
191
191
|
}
|
|
192
192
|
}
|
|
193
193
|
// Claude Code: configure MCP in ~/.claude.json (user-scope, stdio)
|
|
194
194
|
if (detector.agent === "Claude Code") {
|
|
195
|
-
const wrote = configureClaudeCodeGlobal(binaryPath, mcpServerPath);
|
|
195
|
+
const wrote = configureClaudeCodeGlobal(binaryPath, mcpServerPath, workspaceRoot);
|
|
196
196
|
if (wrote)
|
|
197
197
|
mcpConfigs.push("~/.claude.json");
|
|
198
198
|
// Install PreToolUse hook: blocks Grep/Glob when vexp daemon is available
|
|
@@ -216,14 +216,14 @@ export function configureAgents(workspaceRoot, binaryPath, version, agentFilter,
|
|
|
216
216
|
// GitHub Copilot: VS Code format (.vscode/mcp.json with "servers" key)
|
|
217
217
|
if (detector.agent === "GitHub Copilot") {
|
|
218
218
|
const mcpPath = path.join(workspaceRoot, ".vscode/mcp.json");
|
|
219
|
-
if (writeVsCodeMcpConfig(mcpPath, binaryPath, mcpServerPath)) {
|
|
219
|
+
if (writeVsCodeMcpConfig(mcpPath, binaryPath, mcpServerPath, workspaceRoot)) {
|
|
220
220
|
mcpConfigs.push(".vscode/mcp.json");
|
|
221
221
|
}
|
|
222
222
|
}
|
|
223
223
|
// Zed: context_servers in .zed/settings.json
|
|
224
224
|
if (detector.agent === "Zed") {
|
|
225
225
|
const zedPath = path.join(workspaceRoot, ".zed/settings.json");
|
|
226
|
-
if (writeZedMcpConfig(zedPath, binaryPath, mcpServerPath)) {
|
|
226
|
+
if (writeZedMcpConfig(zedPath, binaryPath, mcpServerPath, workspaceRoot)) {
|
|
227
227
|
mcpConfigs.push(".zed/settings.json");
|
|
228
228
|
}
|
|
229
229
|
}
|
|
@@ -306,11 +306,11 @@ export function configureSelectedAgents(workspaceRoot, binaryPath, version, sele
|
|
|
306
306
|
if (detector.mcpConfigFile) {
|
|
307
307
|
const mcpConfigPath = path.join(workspaceRoot, detector.mcpConfigFile);
|
|
308
308
|
const alwaysAllow = detector.agent === "Windsurf" ? VEXP_TOOLS : undefined;
|
|
309
|
-
if (writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath))
|
|
309
|
+
if (writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath, workspaceRoot))
|
|
310
310
|
mcpConfigs.push(detector.mcpConfigFile);
|
|
311
311
|
}
|
|
312
312
|
if (detector.agent === "Claude Code") {
|
|
313
|
-
const wrote = configureClaudeCodeGlobal(binaryPath, mcpServerPath);
|
|
313
|
+
const wrote = configureClaudeCodeGlobal(binaryPath, mcpServerPath, workspaceRoot);
|
|
314
314
|
if (wrote)
|
|
315
315
|
mcpConfigs.push("~/.claude.json");
|
|
316
316
|
installClaudeCodeHook(workspaceRoot);
|
|
@@ -322,12 +322,12 @@ export function configureSelectedAgents(workspaceRoot, binaryPath, version, sele
|
|
|
322
322
|
}
|
|
323
323
|
if (detector.agent === "GitHub Copilot") {
|
|
324
324
|
const mcpPath = path.join(workspaceRoot, ".vscode/mcp.json");
|
|
325
|
-
if (writeVsCodeMcpConfig(mcpPath, binaryPath, mcpServerPath))
|
|
325
|
+
if (writeVsCodeMcpConfig(mcpPath, binaryPath, mcpServerPath, workspaceRoot))
|
|
326
326
|
mcpConfigs.push(".vscode/mcp.json");
|
|
327
327
|
}
|
|
328
328
|
if (detector.agent === "Zed") {
|
|
329
329
|
const zedPath = path.join(workspaceRoot, ".zed/settings.json");
|
|
330
|
-
if (writeZedMcpConfig(zedPath, binaryPath, mcpServerPath))
|
|
330
|
+
if (writeZedMcpConfig(zedPath, binaryPath, mcpServerPath, workspaceRoot))
|
|
331
331
|
mcpConfigs.push(".zed/settings.json");
|
|
332
332
|
}
|
|
333
333
|
results.push({ agent: detector.agent, configFile: detector.configFile, content, alreadyExists, action });
|
|
@@ -416,7 +416,7 @@ function mergeJsonConfig(filePath, mergePayload, version) {
|
|
|
416
416
|
* Write MCP config JSON for agents that use mcpServers format (Cursor, Windsurf).
|
|
417
417
|
* Returns true if a new entry was written.
|
|
418
418
|
*/
|
|
419
|
-
function writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath) {
|
|
419
|
+
function writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath, workspaceRoot) {
|
|
420
420
|
let existing = {};
|
|
421
421
|
if (fs.existsSync(mcpConfigPath)) {
|
|
422
422
|
try {
|
|
@@ -430,11 +430,13 @@ function writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath) {
|
|
|
430
430
|
const useNode = mcpServerPath && fs.existsSync(mcpServerPath);
|
|
431
431
|
const targetCmd = useNode ? "node" : binaryPath;
|
|
432
432
|
const targetArgs = useNode ? [mcpServerPath] : ["mcp"];
|
|
433
|
+
const targetEnv = workspaceRoot ? { VEXP_WORKSPACE: workspaceRoot } : undefined;
|
|
433
434
|
if (servers && "vexp" in servers) {
|
|
434
435
|
const cur = servers["vexp"];
|
|
435
436
|
if (cur?.["command"] === targetCmd &&
|
|
436
437
|
Array.isArray(cur?.["args"]) &&
|
|
437
|
-
JSON.stringify(cur["args"]) === JSON.stringify(targetArgs)
|
|
438
|
+
JSON.stringify(cur["args"]) === JSON.stringify(targetArgs) &&
|
|
439
|
+
JSON.stringify(cur["env"]) === JSON.stringify(targetEnv)) {
|
|
438
440
|
return false; // Already up to date
|
|
439
441
|
}
|
|
440
442
|
}
|
|
@@ -443,6 +445,7 @@ function writeMcpConfig(mcpConfigPath, binaryPath, alwaysAllow, mcpServerPath) {
|
|
|
443
445
|
vexp: {
|
|
444
446
|
command: targetCmd,
|
|
445
447
|
args: targetArgs,
|
|
448
|
+
...(targetEnv ? { env: targetEnv } : {}),
|
|
446
449
|
...(alwaysAllow && alwaysAllow.length > 0 ? { alwaysAllow } : {}),
|
|
447
450
|
},
|
|
448
451
|
};
|
|
@@ -527,7 +530,7 @@ function configureCodexGlobal(binaryPath, mcpServerPath, workspaceRoot) {
|
|
|
527
530
|
* Write .vscode/mcp.json for GitHub Copilot.
|
|
528
531
|
* VS Code uses "servers" (not "mcpServers") + "type": "stdio".
|
|
529
532
|
*/
|
|
530
|
-
function writeVsCodeMcpConfig(p, binaryPath, mcpServerPath) {
|
|
533
|
+
function writeVsCodeMcpConfig(p, binaryPath, mcpServerPath, workspaceRoot) {
|
|
531
534
|
let existing = {};
|
|
532
535
|
if (fs.existsSync(p)) {
|
|
533
536
|
try {
|
|
@@ -541,17 +544,24 @@ function writeVsCodeMcpConfig(p, binaryPath, mcpServerPath) {
|
|
|
541
544
|
const useNode = mcpServerPath && fs.existsSync(mcpServerPath);
|
|
542
545
|
const targetCmd = useNode ? "node" : binaryPath;
|
|
543
546
|
const targetArgs = useNode ? [mcpServerPath] : ["mcp"];
|
|
547
|
+
const targetEnv = workspaceRoot ? { VEXP_WORKSPACE: workspaceRoot } : undefined;
|
|
544
548
|
if (servers && "vexp" in servers) {
|
|
545
549
|
const cur = servers["vexp"];
|
|
546
550
|
if (cur?.["command"] === targetCmd &&
|
|
547
551
|
Array.isArray(cur?.["args"]) &&
|
|
548
|
-
JSON.stringify(cur["args"]) === JSON.stringify(targetArgs)
|
|
552
|
+
JSON.stringify(cur["args"]) === JSON.stringify(targetArgs) &&
|
|
553
|
+
JSON.stringify(cur["env"]) === JSON.stringify(targetEnv)) {
|
|
549
554
|
return false; // Already up to date
|
|
550
555
|
}
|
|
551
556
|
}
|
|
552
557
|
existing.servers = {
|
|
553
558
|
...(servers ?? {}),
|
|
554
|
-
vexp: {
|
|
559
|
+
vexp: {
|
|
560
|
+
type: "stdio",
|
|
561
|
+
command: targetCmd,
|
|
562
|
+
args: targetArgs,
|
|
563
|
+
...(targetEnv ? { env: targetEnv } : {}),
|
|
564
|
+
},
|
|
555
565
|
};
|
|
556
566
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
557
567
|
fs.writeFileSync(p, JSON.stringify(existing, null, 2), "utf-8");
|
|
@@ -560,7 +570,7 @@ function writeVsCodeMcpConfig(p, binaryPath, mcpServerPath) {
|
|
|
560
570
|
/**
|
|
561
571
|
* Write context_servers in .zed/settings.json for Zed.
|
|
562
572
|
*/
|
|
563
|
-
function writeZedMcpConfig(p, binaryPath, mcpServerPath) {
|
|
573
|
+
function writeZedMcpConfig(p, binaryPath, mcpServerPath, workspaceRoot) {
|
|
564
574
|
let settings = {};
|
|
565
575
|
if (fs.existsSync(p)) {
|
|
566
576
|
try {
|
|
@@ -574,18 +584,26 @@ function writeZedMcpConfig(p, binaryPath, mcpServerPath) {
|
|
|
574
584
|
const useNode = mcpServerPath && fs.existsSync(mcpServerPath);
|
|
575
585
|
const targetCmd = useNode ? "node" : binaryPath;
|
|
576
586
|
const targetArgs = useNode ? [mcpServerPath] : ["mcp"];
|
|
587
|
+
const targetEnv = workspaceRoot ? { VEXP_WORKSPACE: workspaceRoot } : undefined;
|
|
577
588
|
if (cs && "vexp" in cs) {
|
|
578
589
|
const cur = cs["vexp"];
|
|
579
590
|
const curCmd = cur?.["command"];
|
|
580
591
|
if (curCmd?.["path"] === targetCmd &&
|
|
581
592
|
Array.isArray(curCmd?.["args"]) &&
|
|
582
|
-
JSON.stringify(curCmd["args"]) === JSON.stringify(targetArgs)
|
|
593
|
+
JSON.stringify(curCmd["args"]) === JSON.stringify(targetArgs) &&
|
|
594
|
+
JSON.stringify(curCmd["env"]) === JSON.stringify(targetEnv)) {
|
|
583
595
|
return false; // Already up to date
|
|
584
596
|
}
|
|
585
597
|
}
|
|
586
598
|
settings.context_servers = {
|
|
587
599
|
...(cs ?? {}),
|
|
588
|
-
vexp: {
|
|
600
|
+
vexp: {
|
|
601
|
+
command: {
|
|
602
|
+
path: targetCmd,
|
|
603
|
+
args: targetArgs,
|
|
604
|
+
...(targetEnv ? { env: targetEnv } : {}),
|
|
605
|
+
},
|
|
606
|
+
},
|
|
589
607
|
};
|
|
590
608
|
// Pre-approve all vexp tools
|
|
591
609
|
const agent = (settings.agent ?? {});
|
|
@@ -603,7 +621,7 @@ function writeZedMcpConfig(p, binaryPath, mcpServerPath) {
|
|
|
603
621
|
* Configure vexp MCP in ~/.claude.json (user-scope) using the Rust binary.
|
|
604
622
|
* Returns true if config was written/updated.
|
|
605
623
|
*/
|
|
606
|
-
function configureClaudeCodeGlobal(binaryPath, mcpServerPath) {
|
|
624
|
+
function configureClaudeCodeGlobal(binaryPath, mcpServerPath, workspaceRoot) {
|
|
607
625
|
const home = os.homedir();
|
|
608
626
|
if (!home)
|
|
609
627
|
return false;
|
|
@@ -619,18 +637,23 @@ function configureClaudeCodeGlobal(binaryPath, mcpServerPath) {
|
|
|
619
637
|
}
|
|
620
638
|
const servers = config.mcpServers;
|
|
621
639
|
const existing = servers?.["vexp"];
|
|
622
|
-
|
|
623
|
-
|
|
640
|
+
const useNode = mcpServerPath && fs.existsSync(mcpServerPath);
|
|
641
|
+
const desiredCommand = useNode ? "node" : binaryPath;
|
|
642
|
+
const desiredArgs = useNode ? [mcpServerPath] : ["mcp"];
|
|
643
|
+
const desiredEnv = workspaceRoot ? { VEXP_WORKSPACE: workspaceRoot } : undefined;
|
|
644
|
+
// Skip if already configured with identical command+args+env
|
|
645
|
+
if (existing?.["command"] === desiredCommand &&
|
|
624
646
|
Array.isArray(existing?.["args"]) &&
|
|
625
|
-
existing["args"].
|
|
647
|
+
JSON.stringify(existing["args"]) === JSON.stringify(desiredArgs) &&
|
|
648
|
+
JSON.stringify(existing["env"]) === JSON.stringify(desiredEnv)) {
|
|
626
649
|
return false;
|
|
627
650
|
}
|
|
628
|
-
const useNode = mcpServerPath && fs.existsSync(mcpServerPath);
|
|
629
651
|
config.mcpServers = {
|
|
630
652
|
...(servers ?? {}),
|
|
631
653
|
vexp: {
|
|
632
|
-
command:
|
|
633
|
-
args:
|
|
654
|
+
command: desiredCommand,
|
|
655
|
+
args: desiredArgs,
|
|
656
|
+
...(desiredEnv ? { env: desiredEnv } : {}),
|
|
634
657
|
},
|
|
635
658
|
};
|
|
636
659
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
package/dist/binary.js
CHANGED
|
@@ -28,6 +28,120 @@ function gpuRequested() {
|
|
|
28
28
|
export function preferGpuEnv() {
|
|
29
29
|
return gpuRequested() ? { VEXP_PREFER_GPU: "1" } : {};
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Build an env object that prepends the binary's directory to LD_LIBRARY_PATH
|
|
33
|
+
* (DYLD_* on macOS). Defensive fallback for binaries whose RPATH=$ORIGIN was
|
|
34
|
+
* not embedded at build time (older VSIX / npm packages).
|
|
35
|
+
*/
|
|
36
|
+
export function binaryEnv(binaryPath, extra = {}) {
|
|
37
|
+
const binaryDir = path.dirname(binaryPath);
|
|
38
|
+
const env = { ...process.env, ...extra };
|
|
39
|
+
if (process.platform === "linux") {
|
|
40
|
+
env.LD_LIBRARY_PATH = env.LD_LIBRARY_PATH ? `${binaryDir}:${env.LD_LIBRARY_PATH}` : binaryDir;
|
|
41
|
+
}
|
|
42
|
+
else if (process.platform === "darwin") {
|
|
43
|
+
env.DYLD_LIBRARY_PATH = env.DYLD_LIBRARY_PATH ? `${binaryDir}:${env.DYLD_LIBRARY_PATH}` : binaryDir;
|
|
44
|
+
env.DYLD_FALLBACK_LIBRARY_PATH = env.DYLD_FALLBACK_LIBRARY_PATH ? `${binaryDir}:${env.DYLD_FALLBACK_LIBRARY_PATH}` : binaryDir;
|
|
45
|
+
}
|
|
46
|
+
return env;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Local dev builds: `cargo build --release` drops unversioned `libggml.so`
|
|
50
|
+
* next to vexp-core, but the binary's DT_NEEDED references the soname
|
|
51
|
+
* `libggml.so.0` (+ `libllama.so.0`). Production packaging (CI) uses
|
|
52
|
+
* `cp -P` to copy the full symlink chain; dev builds don't. With RPATH=$ORIGIN,
|
|
53
|
+
* the loader needs the sonames next to the binary. Self-heal by creating
|
|
54
|
+
* the symlink chain on first CLI invocation.
|
|
55
|
+
*/
|
|
56
|
+
function ensureDevSonames(releaseDir) {
|
|
57
|
+
if (process.platform !== "linux" && process.platform !== "darwin")
|
|
58
|
+
return;
|
|
59
|
+
const ext = process.platform === "darwin" ? ".dylib" : ".so";
|
|
60
|
+
const names = ["libggml", "libggml-base", "libllama"];
|
|
61
|
+
for (const name of names) {
|
|
62
|
+
const soname = process.platform === "darwin"
|
|
63
|
+
? `${name}.0${ext}` // libggml.0.dylib
|
|
64
|
+
: `${name}${ext}.0`; // libggml.so.0
|
|
65
|
+
const sonamePath = path.join(releaseDir, soname);
|
|
66
|
+
if (fs.existsSync(sonamePath) || isSymlink(sonamePath))
|
|
67
|
+
continue;
|
|
68
|
+
// Prefer the deepest-versioned file from the llama-cpp-sys build output.
|
|
69
|
+
const versioned = findVersionedLib(releaseDir, name, ext);
|
|
70
|
+
const unversioned = path.join(releaseDir, `${name}${ext}`);
|
|
71
|
+
try {
|
|
72
|
+
if (versioned) {
|
|
73
|
+
// Copy the versioned file (e.g. libggml.so.0.9.11) into releaseDir
|
|
74
|
+
// if not already there, then symlink soname → versioned.
|
|
75
|
+
const versionedName = path.basename(versioned);
|
|
76
|
+
const versionedDest = path.join(releaseDir, versionedName);
|
|
77
|
+
if (!fs.existsSync(versionedDest)) {
|
|
78
|
+
fs.copyFileSync(versioned, versionedDest);
|
|
79
|
+
}
|
|
80
|
+
fs.symlinkSync(versionedName, sonamePath);
|
|
81
|
+
}
|
|
82
|
+
else if (fs.existsSync(unversioned)) {
|
|
83
|
+
// Fallback: symlink soname → unversioned .so
|
|
84
|
+
fs.symlinkSync(path.basename(unversioned), sonamePath);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// Non-fatal: user will get a clearer error from the binary itself.
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function isSymlink(p) {
|
|
93
|
+
try {
|
|
94
|
+
return fs.lstatSync(p).isSymbolicLink();
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/** Find the deepest-versioned matching lib in target/release/build/.../out/lib/. */
|
|
101
|
+
function findVersionedLib(releaseDir, name, ext) {
|
|
102
|
+
const buildDir = path.join(releaseDir, "build");
|
|
103
|
+
if (!fs.existsSync(buildDir))
|
|
104
|
+
return null;
|
|
105
|
+
const pattern = process.platform === "darwin"
|
|
106
|
+
? new RegExp(`^${escapeRe(name)}\\.(\\d+(?:\\.\\d+)*)${escapeRe(ext)}$`)
|
|
107
|
+
: new RegExp(`^${escapeRe(name)}${escapeRe(ext)}\\.(\\d+(?:\\.\\d+)*)$`);
|
|
108
|
+
const candidates = [];
|
|
109
|
+
try {
|
|
110
|
+
for (const entry of fs.readdirSync(buildDir)) {
|
|
111
|
+
if (!entry.startsWith("llama-cpp-sys-"))
|
|
112
|
+
continue;
|
|
113
|
+
const libDir = path.join(buildDir, entry, "out", "lib");
|
|
114
|
+
if (!fs.existsSync(libDir))
|
|
115
|
+
continue;
|
|
116
|
+
for (const f of fs.readdirSync(libDir)) {
|
|
117
|
+
const m = f.match(pattern);
|
|
118
|
+
if (m) {
|
|
119
|
+
candidates.push({
|
|
120
|
+
file: path.join(libDir, f),
|
|
121
|
+
version: m[1].split(".").map((n) => parseInt(n, 10)),
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
if (candidates.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
candidates.sort((a, b) => {
|
|
133
|
+
for (let i = 0; i < Math.max(a.version.length, b.version.length); i++) {
|
|
134
|
+
const d = (b.version[i] ?? 0) - (a.version[i] ?? 0);
|
|
135
|
+
if (d !== 0)
|
|
136
|
+
return d;
|
|
137
|
+
}
|
|
138
|
+
return 0;
|
|
139
|
+
});
|
|
140
|
+
return candidates[0].file;
|
|
141
|
+
}
|
|
142
|
+
function escapeRe(s) {
|
|
143
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
144
|
+
}
|
|
31
145
|
export function getBinaryPath() {
|
|
32
146
|
// Dev override: use VEXP_CORE_PATH env var to point to a local binary
|
|
33
147
|
const envPath = process.env.VEXP_CORE_PATH;
|
|
@@ -39,6 +153,7 @@ export function getBinaryPath() {
|
|
|
39
153
|
const scriptDir = path.dirname(new URL(import.meta.url).pathname);
|
|
40
154
|
const monorepoRelease = path.resolve(scriptDir, "..", "..", "..", "target", "release", `vexp-core${ext}`);
|
|
41
155
|
if (fs.existsSync(monorepoRelease)) {
|
|
156
|
+
ensureDevSonames(path.dirname(monorepoRelease));
|
|
42
157
|
return monorepoRelease;
|
|
43
158
|
}
|
|
44
159
|
const key = `${process.platform}-${process.arch}`;
|
|
@@ -97,7 +212,7 @@ export async function getInstalledVersion() {
|
|
|
97
212
|
try {
|
|
98
213
|
const { execFile } = await import("child_process");
|
|
99
214
|
return new Promise((resolve) => {
|
|
100
|
-
execFile(binaryPath, ["--version"], (err, stdout) => {
|
|
215
|
+
execFile(binaryPath, ["--version"], { env: binaryEnv(binaryPath) }, (err, stdout) => {
|
|
101
216
|
if (err) {
|
|
102
217
|
resolve(null);
|
|
103
218
|
return;
|