lcagent-cli 0.1.5 → 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 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 式的核心代理循环
package/dist/bin/cli.js CHANGED
@@ -46,6 +46,32 @@ function questionAsync(rl, prompt) {
46
46
  });
47
47
  });
48
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
+ }
49
75
  async function probeUrl(url) {
50
76
  try {
51
77
  const response = await fetchWithTimeout(url, {
@@ -205,21 +231,7 @@ async function runDoctor() {
205
231
  async function printAgentRun(prompt) {
206
232
  const { engine } = await createApp(process.cwd());
207
233
  for await (const event of engine.submit(prompt)) {
208
- switch (event.type) {
209
- case 'status':
210
- console.log(`\n[status] ${event.message}`);
211
- break;
212
- case 'assistant_text':
213
- console.log(`\n${event.text}`);
214
- break;
215
- case 'tool_call':
216
- console.log(`\n[tool] ${event.toolName} ${JSON.stringify(event.input)}`);
217
- break;
218
- case 'tool_result':
219
- console.log(`\n[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
220
- console.log(event.result);
221
- break;
222
- }
234
+ printEvent(event);
223
235
  }
224
236
  }
225
237
  async function runChat() {
@@ -237,21 +249,7 @@ async function runChat() {
237
249
  break;
238
250
  }
239
251
  for await (const event of engine.submit(line)) {
240
- switch (event.type) {
241
- case 'status':
242
- console.log(`[status] ${event.message}`);
243
- break;
244
- case 'assistant_text':
245
- console.log(event.text);
246
- break;
247
- case 'tool_call':
248
- console.log(`[tool] ${event.toolName} ${JSON.stringify(event.input)}`);
249
- break;
250
- case 'tool_result':
251
- console.log(`[tool-result] ${event.toolName}${event.isError ? ' (error)' : ''}`);
252
- console.log(event.result);
253
- break;
254
- }
252
+ printEvent(event);
255
253
  }
256
254
  }
257
255
  }
@@ -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
- oldText: z.ZodString;
6
- newText: z.ZodString;
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 {};
@@ -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
- oldText: z.string(),
7
- newText: z.string(),
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 absolutePath = resolvePath(context.cwd, input.path);
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(input.oldText)) {
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(input.oldText, input.newText);
55
+ const next = current.replace(oldText, newText);
43
56
  await writeFile(absolutePath, next, 'utf8');
44
57
  return {
45
- content: `Updated ${input.path}`,
58
+ content: `Updated ${normalizedPath}`,
46
59
  };
47
60
  },
48
61
  };
@@ -13,5 +13,13 @@ export async function executeToolCall(toolUse, tools, context) {
13
13
  content: parsed.error.message,
14
14
  };
15
15
  }
16
- return tool.execute(parsed.data, context);
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
  }
@@ -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 {};
@@ -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 root = input.path
48
- ? isAbsolute(input.path)
49
- ? input.path
50
- : resolve(context.cwd, input.path)
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 matcher = input.isRegex
53
- ? new RegExp(input.pattern, 'i')
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(input.pattern.toLowerCase());
84
+ : line.toLowerCase().includes(pattern.toLowerCase());
72
85
  if (!matched) {
73
86
  continue;
74
87
  }
@@ -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 {};
@@ -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 absolutePath = resolvePath(context.cwd, input.path);
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((input.startLine ?? 1) - 1, 0);
32
- const end = Math.min(input.endLine ?? lines.length, lines.length);
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}`)
@@ -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 {};
@@ -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 { stdout, stderr } = await execAsync(input.command, {
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');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lcagent-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "A minimal coding agent CLI for terminal-based coding workflows.",
5
5
  "type": "module",
6
6
  "publishConfig": {