lcagent-cli 0.1.4 → 0.1.6
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/README.md +9 -1
- package/dist/bin/cli.js +83 -30
- package/dist/tools/editFile.d.ts +7 -3
- package/dist/tools/editFile.js +20 -7
- package/dist/tools/execute.js +9 -1
- package/dist/tools/grep.d.ts +5 -1
- package/dist/tools/grep.js +22 -9
- package/dist/tools/readFile.d.ts +5 -1
- package/dist/tools/readFile.js +17 -4
- package/dist/tools/runShell.d.ts +2 -1
- package/dist/tools/runShell.js +11 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,6 +139,14 @@ npm run start -- model
|
|
|
139
139
|
npm run start -- doctor
|
|
140
140
|
```
|
|
141
141
|
|
|
142
|
+
当模型触发工具时,CLI 会打印:
|
|
143
|
+
|
|
144
|
+
- `[tool-call] 工具名`:后面跟模型传入的原始 JSON 参数
|
|
145
|
+
- `[tool-result] 工具名`:显示工具执行结果
|
|
146
|
+
- `[tool-result] 工具名 (error)`:显示工具失败原因
|
|
147
|
+
|
|
148
|
+
这样可以直接看出是模型参数字段名不对、路径不对,还是工具执行本身报错。
|
|
149
|
+
|
|
142
150
|
## 设计目标
|
|
143
151
|
|
|
144
152
|
- 保留 Claude Code 式的核心代理循环
|
|
@@ -165,4 +173,4 @@ npm run start -- doctor
|
|
|
165
173
|
npm run start -- doctor
|
|
166
174
|
```
|
|
167
175
|
|
|
168
|
-
`doctor` 会额外探测多个端点(如 `/v1`、`/v1/models`、`/v1/chat/completions
|
|
176
|
+
`doctor` 会额外探测多个端点(如 `/v1`、`/v1/models`、`/v1/chat/completions`),并尽量打印底层网络错误;对于 OpenAI-compatible 服务,还会额外检查 tool calling 是否真的返回 `tool_calls`。
|
package/dist/bin/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { fetch } from '../core/http.js';
|
|
|
7
7
|
import { agentConfigSchema } from '../config/schema.js';
|
|
8
8
|
import { getConfigPath, loadConfig, updateConfig } from '../config/store.js';
|
|
9
9
|
import { getDefaultTools } from '../tools/registry.js';
|
|
10
|
+
import { toOpenAICompatibleTool } from '../tools/types.js';
|
|
10
11
|
function trimTrailingSlash(value) {
|
|
11
12
|
return value.replace(/\/+$/, '');
|
|
12
13
|
}
|
|
@@ -45,6 +46,32 @@ function questionAsync(rl, prompt) {
|
|
|
45
46
|
});
|
|
46
47
|
});
|
|
47
48
|
}
|
|
49
|
+
function formatToolInput(input) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.stringify(input, null, 2);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return String(input);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function printEvent(event) {
|
|
58
|
+
switch (event.type) {
|
|
59
|
+
case 'status':
|
|
60
|
+
console.log(`\n[status] ${event.message}`);
|
|
61
|
+
break;
|
|
62
|
+
case 'assistant_text':
|
|
63
|
+
console.log(`\n${event.text}`);
|
|
64
|
+
break;
|
|
65
|
+
case 'tool_call':
|
|
66
|
+
console.log(`\n[tool-call] ${event.toolName}`);
|
|
67
|
+
console.log(formatToolInput(event.input));
|
|
68
|
+
break;
|
|
69
|
+
case 'tool_result':
|
|
70
|
+
console.log(`\n[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
|
|
71
|
+
console.log(event.result);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
48
75
|
async function probeUrl(url) {
|
|
49
76
|
try {
|
|
50
77
|
const response = await fetchWithTimeout(url, {
|
|
@@ -80,6 +107,55 @@ async function runJsonProbe(params) {
|
|
|
80
107
|
console.log(` failed: ${formatError(error)}`);
|
|
81
108
|
}
|
|
82
109
|
}
|
|
110
|
+
async function runOpenAIToolCallingProbe(params) {
|
|
111
|
+
const tool = toOpenAICompatibleTool(getDefaultTools()[0]);
|
|
112
|
+
console.log(`- POST /v1/chat/completions (tool calling): ${params.url}`);
|
|
113
|
+
try {
|
|
114
|
+
const response = await fetchWithTimeout(params.url, {
|
|
115
|
+
method: 'POST',
|
|
116
|
+
headers: {
|
|
117
|
+
'content-type': 'application/json',
|
|
118
|
+
...(params.apiKey ? { Authorization: `Bearer ${params.apiKey}` } : {}),
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
model: params.model,
|
|
122
|
+
messages: [
|
|
123
|
+
{
|
|
124
|
+
role: 'user',
|
|
125
|
+
content: 'Call the read_file tool with path set to README.md. Do not answer with plain text only.',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
tools: [tool],
|
|
129
|
+
tool_choice: 'auto',
|
|
130
|
+
max_tokens: 128,
|
|
131
|
+
}),
|
|
132
|
+
}, 8000);
|
|
133
|
+
const text = await response.text();
|
|
134
|
+
console.log(` HTTP ${response.status}`);
|
|
135
|
+
console.log(` preview: ${text.slice(0, 300) || '(empty body)'}`);
|
|
136
|
+
try {
|
|
137
|
+
const parsed = JSON.parse(text);
|
|
138
|
+
const toolCalls = parsed.choices?.[0]?.message?.tool_calls;
|
|
139
|
+
if (Array.isArray(toolCalls) && toolCalls.length > 0) {
|
|
140
|
+
console.log(` tool calling: supported (${toolCalls.length} tool call returned)`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const finishReason = parsed.choices?.[0]?.finish_reason ?? 'unknown';
|
|
144
|
+
const content = parsed.choices?.[0]?.message?.content ?? '';
|
|
145
|
+
console.log(` tool calling: no tool_calls returned (finish_reason=${finishReason})`);
|
|
146
|
+
if (content) {
|
|
147
|
+
console.log(` assistant content: ${String(content).slice(0, 160)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
console.log(' tool calling: unable to parse JSON response body');
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
console.log(` failed: ${formatError(error)}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
83
159
|
async function runDoctor() {
|
|
84
160
|
const config = await loadConfig();
|
|
85
161
|
const apiKey = config.apiKey ??
|
|
@@ -124,6 +200,11 @@ async function runDoctor() {
|
|
|
124
200
|
max_tokens: 8,
|
|
125
201
|
}),
|
|
126
202
|
});
|
|
203
|
+
await runOpenAIToolCallingProbe({
|
|
204
|
+
url: `${trimmedBaseUrl}/chat/completions`,
|
|
205
|
+
apiKey,
|
|
206
|
+
model: config.model,
|
|
207
|
+
});
|
|
127
208
|
return;
|
|
128
209
|
}
|
|
129
210
|
await runJsonProbe({
|
|
@@ -150,21 +231,7 @@ async function runDoctor() {
|
|
|
150
231
|
async function printAgentRun(prompt) {
|
|
151
232
|
const { engine } = await createApp(process.cwd());
|
|
152
233
|
for await (const event of engine.submit(prompt)) {
|
|
153
|
-
|
|
154
|
-
case 'status':
|
|
155
|
-
console.log(`\n[status] ${event.message}`);
|
|
156
|
-
break;
|
|
157
|
-
case 'assistant_text':
|
|
158
|
-
console.log(`\n${event.text}`);
|
|
159
|
-
break;
|
|
160
|
-
case 'tool_call':
|
|
161
|
-
console.log(`\n[tool] ${event.toolName} ${JSON.stringify(event.input)}`);
|
|
162
|
-
break;
|
|
163
|
-
case 'tool_result':
|
|
164
|
-
console.log(`\n[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
|
|
165
|
-
console.log(event.result);
|
|
166
|
-
break;
|
|
167
|
-
}
|
|
234
|
+
printEvent(event);
|
|
168
235
|
}
|
|
169
236
|
}
|
|
170
237
|
async function runChat() {
|
|
@@ -182,21 +249,7 @@ async function runChat() {
|
|
|
182
249
|
break;
|
|
183
250
|
}
|
|
184
251
|
for await (const event of engine.submit(line)) {
|
|
185
|
-
|
|
186
|
-
case 'status':
|
|
187
|
-
console.log(`[status] ${event.message}`);
|
|
188
|
-
break;
|
|
189
|
-
case 'assistant_text':
|
|
190
|
-
console.log(event.text);
|
|
191
|
-
break;
|
|
192
|
-
case 'tool_call':
|
|
193
|
-
console.log(`[tool] ${event.toolName} ${JSON.stringify(event.input)}`);
|
|
194
|
-
break;
|
|
195
|
-
case 'tool_result':
|
|
196
|
-
console.log(`[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
|
|
197
|
-
console.log(event.result);
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
252
|
+
printEvent(event);
|
|
200
253
|
}
|
|
201
254
|
}
|
|
202
255
|
}
|
package/dist/tools/editFile.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
path: z.ZodString
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
path: z.ZodOptional<z.ZodString>;
|
|
5
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
6
|
+
file_path: z.ZodOptional<z.ZodString>;
|
|
7
|
+
oldText: z.ZodOptional<z.ZodString>;
|
|
8
|
+
old_text: z.ZodOptional<z.ZodString>;
|
|
9
|
+
newText: z.ZodOptional<z.ZodString>;
|
|
10
|
+
new_text: z.ZodOptional<z.ZodString>;
|
|
7
11
|
}, z.core.$strip>;
|
|
8
12
|
export declare const editFileTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
9
13
|
export {};
|
package/dist/tools/editFile.js
CHANGED
|
@@ -2,9 +2,13 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const inputSchema = z.object({
|
|
5
|
-
path: z.string().min(1),
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
path: z.string().min(1).optional(),
|
|
6
|
+
filePath: z.string().min(1).optional(),
|
|
7
|
+
file_path: z.string().min(1).optional(),
|
|
8
|
+
oldText: z.string().optional(),
|
|
9
|
+
old_text: z.string().optional(),
|
|
10
|
+
newText: z.string().optional(),
|
|
11
|
+
new_text: z.string().optional(),
|
|
8
12
|
});
|
|
9
13
|
function resolvePath(cwd, filePath) {
|
|
10
14
|
return isAbsolute(filePath) ? filePath : resolve(cwd, filePath);
|
|
@@ -31,18 +35,27 @@ export const editFileTool = {
|
|
|
31
35
|
content: 'edit_file is blocked in manual approval mode. Switch config approvalMode to auto to enable file edits.',
|
|
32
36
|
};
|
|
33
37
|
}
|
|
34
|
-
const
|
|
38
|
+
const normalizedPath = input.path ?? input.filePath ?? input.file_path;
|
|
39
|
+
const oldText = input.oldText ?? input.old_text;
|
|
40
|
+
const newText = input.newText ?? input.new_text;
|
|
41
|
+
if (!normalizedPath || oldText === undefined || newText === undefined) {
|
|
42
|
+
return {
|
|
43
|
+
isError: true,
|
|
44
|
+
content: 'Missing required fields: path, oldText, or newText',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const absolutePath = resolvePath(context.cwd, normalizedPath);
|
|
35
48
|
const current = await readFile(absolutePath, 'utf8');
|
|
36
|
-
if (!current.includes(
|
|
49
|
+
if (!current.includes(oldText)) {
|
|
37
50
|
return {
|
|
38
51
|
isError: true,
|
|
39
52
|
content: 'oldText was not found in the target file.',
|
|
40
53
|
};
|
|
41
54
|
}
|
|
42
|
-
const next = current.replace(
|
|
55
|
+
const next = current.replace(oldText, newText);
|
|
43
56
|
await writeFile(absolutePath, next, 'utf8');
|
|
44
57
|
return {
|
|
45
|
-
content: `Updated ${
|
|
58
|
+
content: `Updated ${normalizedPath}`,
|
|
46
59
|
};
|
|
47
60
|
},
|
|
48
61
|
};
|
package/dist/tools/execute.js
CHANGED
|
@@ -13,5 +13,13 @@ export async function executeToolCall(toolUse, tools, context) {
|
|
|
13
13
|
content: parsed.error.message,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
try {
|
|
17
|
+
return await tool.execute(parsed.data, context);
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
return {
|
|
21
|
+
isError: true,
|
|
22
|
+
content: error instanceof Error ? error.message : String(error),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
17
25
|
}
|
package/dist/tools/grep.d.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
pattern: z.ZodString
|
|
4
|
+
pattern: z.ZodOptional<z.ZodString>;
|
|
5
|
+
query: z.ZodOptional<z.ZodString>;
|
|
5
6
|
path: z.ZodOptional<z.ZodString>;
|
|
7
|
+
directory: z.ZodOptional<z.ZodString>;
|
|
6
8
|
maxResults: z.ZodOptional<z.ZodNumber>;
|
|
9
|
+
max_results: z.ZodOptional<z.ZodNumber>;
|
|
7
10
|
isRegex: z.ZodOptional<z.ZodBoolean>;
|
|
11
|
+
is_regex: z.ZodOptional<z.ZodBoolean>;
|
|
8
12
|
}, z.core.$strip>;
|
|
9
13
|
export declare const grepTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
10
14
|
export {};
|
package/dist/tools/grep.js
CHANGED
|
@@ -2,10 +2,14 @@ import { readdir, readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { extname, isAbsolute, join, resolve } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const inputSchema = z.object({
|
|
5
|
-
pattern: z.string().min(1),
|
|
5
|
+
pattern: z.string().min(1).optional(),
|
|
6
|
+
query: z.string().min(1).optional(),
|
|
6
7
|
path: z.string().optional(),
|
|
8
|
+
directory: z.string().optional(),
|
|
7
9
|
maxResults: z.number().int().positive().max(200).optional(),
|
|
10
|
+
max_results: z.number().int().positive().max(200).optional(),
|
|
8
11
|
isRegex: z.boolean().optional(),
|
|
12
|
+
is_regex: z.boolean().optional(),
|
|
9
13
|
});
|
|
10
14
|
const textExtensions = new Set([
|
|
11
15
|
'.ts', '.tsx', '.js', '.jsx', '.json', '.md', '.txt', '.yml', '.yaml', '.css', '.html', '.mjs', '.cjs', '.sh', '.py', '.java', '.go', '.rs'
|
|
@@ -44,17 +48,26 @@ export const grepTool = {
|
|
|
44
48
|
},
|
|
45
49
|
isReadOnly: true,
|
|
46
50
|
async execute(input, context) {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
:
|
|
51
|
+
const pattern = input.pattern ?? input.query;
|
|
52
|
+
if (!pattern) {
|
|
53
|
+
return {
|
|
54
|
+
isError: true,
|
|
55
|
+
content: 'Missing required field: pattern',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const searchPath = input.path ?? input.directory;
|
|
59
|
+
const root = searchPath
|
|
60
|
+
? isAbsolute(searchPath)
|
|
61
|
+
? searchPath
|
|
62
|
+
: resolve(context.cwd, searchPath)
|
|
51
63
|
: context.cwd;
|
|
52
|
-
const
|
|
53
|
-
|
|
64
|
+
const isRegex = input.isRegex ?? input.is_regex;
|
|
65
|
+
const matcher = isRegex
|
|
66
|
+
? new RegExp(pattern, 'i')
|
|
54
67
|
: null;
|
|
55
68
|
const files = await walk(root);
|
|
56
69
|
const results = [];
|
|
57
|
-
const maxResults = input.maxResults ?? 50;
|
|
70
|
+
const maxResults = input.maxResults ?? input.max_results ?? 50;
|
|
58
71
|
for (const filePath of files) {
|
|
59
72
|
if (!textExtensions.has(extname(filePath))) {
|
|
60
73
|
continue;
|
|
@@ -68,7 +81,7 @@ export const grepTool = {
|
|
|
68
81
|
const line = lines[index] ?? '';
|
|
69
82
|
const matched = matcher
|
|
70
83
|
? matcher.test(line)
|
|
71
|
-
: line.toLowerCase().includes(
|
|
84
|
+
: line.toLowerCase().includes(pattern.toLowerCase());
|
|
72
85
|
if (!matched) {
|
|
73
86
|
continue;
|
|
74
87
|
}
|
package/dist/tools/readFile.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
path: z.ZodString
|
|
4
|
+
path: z.ZodOptional<z.ZodString>;
|
|
5
|
+
filePath: z.ZodOptional<z.ZodString>;
|
|
6
|
+
file_path: z.ZodOptional<z.ZodString>;
|
|
5
7
|
startLine: z.ZodOptional<z.ZodNumber>;
|
|
8
|
+
start_line: z.ZodOptional<z.ZodNumber>;
|
|
6
9
|
endLine: z.ZodOptional<z.ZodNumber>;
|
|
10
|
+
end_line: z.ZodOptional<z.ZodNumber>;
|
|
7
11
|
}, z.core.$strip>;
|
|
8
12
|
export declare const readFileTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
9
13
|
export {};
|
package/dist/tools/readFile.js
CHANGED
|
@@ -2,9 +2,13 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const inputSchema = z.object({
|
|
5
|
-
path: z.string().min(1),
|
|
5
|
+
path: z.string().min(1).optional(),
|
|
6
|
+
filePath: z.string().min(1).optional(),
|
|
7
|
+
file_path: z.string().min(1).optional(),
|
|
6
8
|
startLine: z.number().int().positive().optional(),
|
|
9
|
+
start_line: z.number().int().positive().optional(),
|
|
7
10
|
endLine: z.number().int().positive().optional(),
|
|
11
|
+
end_line: z.number().int().positive().optional(),
|
|
8
12
|
});
|
|
9
13
|
function resolvePath(cwd, filePath) {
|
|
10
14
|
return isAbsolute(filePath) ? filePath : resolve(cwd, filePath);
|
|
@@ -25,11 +29,20 @@ export const readFileTool = {
|
|
|
25
29
|
},
|
|
26
30
|
isReadOnly: true,
|
|
27
31
|
async execute(input, context) {
|
|
28
|
-
const
|
|
32
|
+
const normalizedPath = input.path ?? input.filePath ?? input.file_path;
|
|
33
|
+
if (!normalizedPath) {
|
|
34
|
+
return {
|
|
35
|
+
isError: true,
|
|
36
|
+
content: 'Missing required field: path',
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const startLine = input.startLine ?? input.start_line;
|
|
40
|
+
const endLine = input.endLine ?? input.end_line;
|
|
41
|
+
const absolutePath = resolvePath(context.cwd, normalizedPath);
|
|
29
42
|
const contents = await readFile(absolutePath, 'utf8');
|
|
30
43
|
const lines = contents.split(/\r?\n/);
|
|
31
|
-
const start = Math.max((
|
|
32
|
-
const end = Math.min(
|
|
44
|
+
const start = Math.max((startLine ?? 1) - 1, 0);
|
|
45
|
+
const end = Math.min(endLine ?? lines.length, lines.length);
|
|
33
46
|
const slice = lines.slice(start, end);
|
|
34
47
|
const numbered = slice
|
|
35
48
|
.map((line, index) => `${start + index + 1}: ${line}`)
|
package/dist/tools/runShell.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ToolDefinition } from './types.js';
|
|
3
3
|
declare const inputSchema: z.ZodObject<{
|
|
4
|
-
command: z.ZodString
|
|
4
|
+
command: z.ZodOptional<z.ZodString>;
|
|
5
|
+
cmd: z.ZodOptional<z.ZodString>;
|
|
5
6
|
}, z.core.$strip>;
|
|
6
7
|
export declare const runShellTool: ToolDefinition<z.infer<typeof inputSchema>>;
|
|
7
8
|
export {};
|
package/dist/tools/runShell.js
CHANGED
|
@@ -3,7 +3,8 @@ import { promisify } from 'node:util';
|
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
const execAsync = promisify(exec);
|
|
5
5
|
const inputSchema = z.object({
|
|
6
|
-
command: z.string().min(1),
|
|
6
|
+
command: z.string().min(1).optional(),
|
|
7
|
+
cmd: z.string().min(1).optional(),
|
|
7
8
|
});
|
|
8
9
|
export const runShellTool = {
|
|
9
10
|
name: 'run_shell',
|
|
@@ -29,9 +30,16 @@ export const runShellTool = {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
try {
|
|
32
|
-
const
|
|
33
|
+
const command = input.command ?? input.cmd;
|
|
34
|
+
if (!command) {
|
|
35
|
+
return {
|
|
36
|
+
isError: true,
|
|
37
|
+
content: 'Missing required field: command',
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
33
41
|
cwd: context.cwd,
|
|
34
|
-
shell: '/bin/sh',
|
|
42
|
+
shell: process.platform === 'win32' ? process.env.ComSpec || 'cmd.exe' : '/bin/sh',
|
|
35
43
|
maxBuffer: 1024 * 1024,
|
|
36
44
|
});
|
|
37
45
|
const output = [stdout.trim(), stderr.trim()].filter(Boolean).join('\n');
|