jarvis-ai-assistant 0.1.126__py3-none-any.whl → 0.1.129__py3-none-any.whl

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.

Potentially problematic release.


This version of jarvis-ai-assistant might be problematic. Click here for more details.

Files changed (42) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +108 -95
  3. jarvis/jarvis_agent/main.py +77 -0
  4. jarvis/jarvis_code_agent/builtin_input_handler.py +43 -0
  5. jarvis/jarvis_code_agent/code_agent.py +17 -81
  6. jarvis/jarvis_code_agent/file_input_handler.py +88 -0
  7. jarvis/jarvis_code_agent/patch.py +142 -114
  8. jarvis/jarvis_code_agent/shell_input_handler.py +8 -2
  9. jarvis/jarvis_codebase/main.py +240 -213
  10. jarvis/jarvis_dev/main.py +4 -3
  11. jarvis/jarvis_multi_agent/__init__.py +51 -40
  12. jarvis/jarvis_platform/base.py +6 -5
  13. jarvis/jarvis_platform_manager/main.py +1 -1
  14. jarvis/jarvis_rag/main.py +250 -186
  15. jarvis/jarvis_smart_shell/main.py +0 -1
  16. jarvis/jarvis_tools/ask_codebase.py +4 -3
  17. jarvis/jarvis_tools/chdir.py +22 -22
  18. jarvis/jarvis_tools/code_review.py +38 -33
  19. jarvis/jarvis_tools/execute_shell.py +0 -3
  20. jarvis/jarvis_tools/file_operation.py +56 -55
  21. jarvis/jarvis_tools/git_commiter.py +60 -50
  22. jarvis/jarvis_tools/read_code.py +143 -0
  23. jarvis/jarvis_tools/read_webpage.py +50 -30
  24. jarvis/jarvis_tools/registry.py +4 -21
  25. jarvis/jarvis_tools/search_web.py +61 -36
  26. jarvis/jarvis_tools/tool_generator.py +78 -36
  27. jarvis/jarvis_utils/__init__.py +17 -17
  28. jarvis/jarvis_utils/config.py +87 -51
  29. jarvis/jarvis_utils/embedding.py +49 -48
  30. jarvis/jarvis_utils/git_utils.py +34 -34
  31. jarvis/jarvis_utils/globals.py +26 -26
  32. jarvis/jarvis_utils/input.py +61 -45
  33. jarvis/jarvis_utils/methodology.py +94 -76
  34. jarvis/jarvis_utils/output.py +63 -62
  35. jarvis/jarvis_utils/utils.py +2 -2
  36. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/METADATA +1 -1
  37. jarvis_ai_assistant-0.1.129.dist-info/RECORD +78 -0
  38. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/entry_points.txt +2 -0
  39. jarvis_ai_assistant-0.1.126.dist-info/RECORD +0 -74
  40. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/LICENSE +0 -0
  41. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/WHEEL +0 -0
  42. {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/top_level.txt +0 -0
@@ -16,31 +16,31 @@ class ChdirTool:
16
16
  }
17
17
 
18
18
  def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
19
- """Execute directory change operation with comprehensive error handling.
19
+ """执行目录切换操作,并提供全面的错误处理。
20
20
 
21
- Args:
22
- args: Dictionary containing 'path' key with target directory path
21
+ 参数:
22
+ args: 包含 'path' 键的字典,目标目录路径
23
23
 
24
- Returns:
25
- Dictionary containing:
26
- - success: Boolean indicating operation status
27
- - stdout: Success message or empty string
28
- - stderr: Error message or empty string
24
+ 返回:
25
+ 字典,包含以下内容:
26
+ - success: 布尔值,表示操作状态
27
+ - stdout: 成功消息或空字符串
28
+ - stderr: 错误消息或空字符串
29
29
 
30
- Raises:
31
- Handles and returns appropriate error messages for:
32
- - Non-existent paths
33
- - Non-directory paths
34
- - Permission errors
35
- - Generic exceptions
30
+ 异常处理:
31
+ 处理并返回适当的错误消息:
32
+ - 不存在的路径
33
+ - 非目录路径
34
+ - 权限错误
35
+ - 其他通用异常
36
36
  """
37
- # Main execution block with comprehensive error handling
37
+ # 主执行块,包含全面的错误处理
38
38
  try:
39
- # Normalize and expand the input path (handles ~ and relative paths)
39
+ # 规范化并展开输入路径(处理 ~ 和相对路径)
40
40
  path = os.path.expanduser(args["path"].strip())
41
41
  path = os.path.abspath(path)
42
42
 
43
- # Validate that the target path exists
43
+ # 验证目标路径是否存在
44
44
  if not os.path.exists(path):
45
45
  return {
46
46
  "success": False,
@@ -48,7 +48,7 @@ class ChdirTool:
48
48
  "stderr": f"目录不存在: {path}"
49
49
  }
50
50
 
51
- # Ensure the path points to a directory, not a file
51
+ # 确保路径指向的是目录,而不是文件
52
52
  if not os.path.isdir(path):
53
53
  return {
54
54
  "success": False,
@@ -56,7 +56,7 @@ class ChdirTool:
56
56
  "stderr": f"路径不是目录: {path}"
57
57
  }
58
58
 
59
- # Capture current directory and attempt to change to new path
59
+ # 获取当前目录并尝试切换到新路径
60
60
  old_path = os.getcwd()
61
61
  os.chdir(path)
62
62
 
@@ -66,17 +66,17 @@ class ChdirTool:
66
66
  "stderr": ""
67
67
  }
68
68
 
69
- # Handle cases where user lacks directory access permissions
69
+ # 处理用户没有目录访问权限的情况
70
70
  except PermissionError:
71
71
  return {
72
72
  "success": False,
73
73
  "stdout": "",
74
74
  "stderr": f"无权限访问目录: {path}"
75
75
  }
76
- # Catch-all for any other unexpected errors during directory change
76
+ # 捕获在目录切换过程中可能出现的其他意外错误
77
77
  except Exception as e:
78
78
  return {
79
79
  "success": False,
80
80
  "stdout": "",
81
81
  "stderr": f"切换目录失败: {str(e)}"
82
- }
82
+ }
@@ -1,5 +1,7 @@
1
1
  from typing import Dict, Any
2
2
  import subprocess
3
+
4
+ from yaspin import yaspin
3
5
  from jarvis.jarvis_platform.registry import PlatformRegistry
4
6
  from jarvis.jarvis_tools.registry import ToolRegistry
5
7
  from jarvis.jarvis_agent import Agent
@@ -41,44 +43,47 @@ class CodeReviewTool:
41
43
  review_type = args.get("review_type", "current").strip()
42
44
 
43
45
  # Build git diff command based on review type
44
- if review_type == "commit":
45
- if "commit_sha" not in args:
46
- return {
47
- "success": False,
48
- "stdout": {},
49
- "stderr": "commit_sha is required for commit review type"
50
- }
51
- commit_sha = args["commit_sha"].strip()
52
- diff_cmd = f"git show {commit_sha} | cat -"
53
- elif review_type == "range":
54
- if "start_commit" not in args or "end_commit" not in args:
55
- return {
56
- "success": False,
57
- "stdout": {},
58
- "stderr": "start_commit and end_commit are required for range review type"
59
- }
60
- start_commit = args["start_commit"].strip()
61
- end_commit = args["end_commit"].strip()
62
- diff_cmd = f"git diff {start_commit}..{end_commit} | cat -"
63
- else: # current changes
64
- diff_cmd = "git diff HEAD | cat -"
46
+ with yaspin(text="正在获取代码变更...", color="cyan") as spinner:
47
+ if review_type == "commit":
48
+ if "commit_sha" not in args:
49
+ return {
50
+ "success": False,
51
+ "stdout": {},
52
+ "stderr": "commit_sha is required for commit review type"
53
+ }
54
+ commit_sha = args["commit_sha"].strip()
55
+ diff_cmd = f"git show {commit_sha} | cat -"
56
+ elif review_type == "range":
57
+ if "start_commit" not in args or "end_commit" not in args:
58
+ return {
59
+ "success": False,
60
+ "stdout": {},
61
+ "stderr": "start_commit and end_commit are required for range review type"
62
+ }
63
+ start_commit = args["start_commit"].strip()
64
+ end_commit = args["end_commit"].strip()
65
+ diff_cmd = f"git diff {start_commit}..{end_commit} | cat -"
66
+ else: # current changes
67
+ diff_cmd = "git diff HEAD | cat -"
65
68
 
66
- # Execute git diff command
67
- try:
68
- diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
69
- if not diff_output:
69
+ # Execute git diff command
70
+ try:
71
+ diff_output = subprocess.check_output(diff_cmd, shell=True, text=True)
72
+ if not diff_output:
73
+ return {
74
+ "success": False,
75
+ "stdout": {},
76
+ "stderr": "No changes to review"
77
+ }
78
+ PrettyOutput.print(diff_output, OutputType.CODE, lang="diff")
79
+ except subprocess.CalledProcessError as e:
70
80
  return {
71
81
  "success": False,
72
82
  "stdout": {},
73
- "stderr": "No changes to review"
83
+ "stderr": f"Failed to get diff: {str(e)}"
74
84
  }
75
- PrettyOutput.print(diff_output, OutputType.CODE, lang="diff")
76
- except subprocess.CalledProcessError as e:
77
- return {
78
- "success": False,
79
- "stdout": {},
80
- "stderr": f"Failed to get diff: {str(e)}"
81
- }
85
+ spinner.text = "代码变更获取完成"
86
+ spinner.ok("✅")
82
87
 
83
88
  system_prompt = """You are an autonomous code review expert with a tragic past. Perform in-depth analysis with the vigilance born from painful experience:
84
89
 
@@ -73,9 +73,6 @@ class ShellTool:
73
73
  # Use script command to capture both stdout and stderr
74
74
  tee_command = f"script -q -c '{escaped_command}' {output_file}"
75
75
 
76
- # Log command execution
77
- PrettyOutput.print(f"执行命令: {command}", OutputType.INFO)
78
-
79
76
  # Execute command and capture return code
80
77
  return_code = os.system(tee_command)
81
78
 
@@ -1,6 +1,8 @@
1
1
  from typing import Dict, Any
2
2
  import os
3
3
 
4
+ from yaspin import yaspin
5
+
4
6
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
5
7
 
6
8
 
@@ -37,67 +39,66 @@ class FileOperationTool:
37
39
  """Handle operations for a single file"""
38
40
  try:
39
41
  abs_path = os.path.abspath(filepath)
40
- PrettyOutput.print(f"文件操作: {operation} - {abs_path}", OutputType.INFO)
41
-
42
42
  if operation == "read":
43
- if not os.path.exists(abs_path):
44
- PrettyOutput.print(f"文件不存在: {abs_path}", OutputType.WARNING)
45
- return {
46
- "success": False,
47
- "stdout": "",
48
- "stderr": f"文件不存在: {abs_path}"
49
- }
43
+ with yaspin(text=f"正在读取文件: {abs_path}...", color="cyan") as spinner:
44
+ if not os.path.exists(abs_path):
45
+ return {
46
+ "success": False,
47
+ "stdout": "",
48
+ "stderr": f"文件不存在: {abs_path}"
49
+ }
50
+
51
+ if os.path.getsize(abs_path) > 10 * 1024 * 1024: # 10MB
52
+ return {
53
+ "success": False,
54
+ "stdout": "",
55
+ "stderr": "File too large (>10MB)"
56
+ }
57
+
58
+ with open(abs_path, 'r', encoding='utf-8') as f:
59
+ lines = f.readlines()
50
60
 
51
- if os.path.getsize(abs_path) > 10 * 1024 * 1024: # 10MB
52
- PrettyOutput.print(f"文件太大: {abs_path}", OutputType.WARNING)
53
- return {
54
- "success": False,
55
- "stdout": "",
56
- "stderr": "File too large (>10MB)"
57
- }
58
61
 
59
- with open(abs_path, 'r', encoding='utf-8') as f:
60
- lines = f.readlines()
61
-
62
- # Handle line range
63
- total_lines = len(lines)
64
- start_line = start_line if start_line >= 0 else total_lines + start_line + 1
65
- end_line = end_line if end_line >= 0 else total_lines + end_line + 1
66
- start_line = max(1, min(start_line, total_lines))
67
- end_line = max(1, min(end_line, total_lines))
68
- if end_line == -1:
69
- end_line = total_lines
70
-
71
- if start_line > end_line:
72
- error_msg = f"无效的行范围 [{start_line, end_line}] (文件总行数: {total_lines})"
73
- PrettyOutput.print(error_msg, OutputType.WARNING)
62
+ total_lines = len(lines)
63
+ start_line = start_line if start_line >= 0 else total_lines + start_line + 1
64
+ end_line = end_line if end_line >= 0 else total_lines + end_line + 1
65
+ start_line = max(1, min(start_line, total_lines))
66
+ end_line = max(1, min(end_line, total_lines))
67
+ if end_line == -1:
68
+ end_line = total_lines
69
+
70
+ if start_line > end_line:
71
+ spinner.text = "无效的行范围"
72
+ spinner.fail("❌")
73
+ error_msg = f"无效的行范围 [{start_line, end_line}] (文件总行数: {total_lines})"
74
+ return {
75
+ "success": False,
76
+ "stdout": "",
77
+ "stderr": error_msg
78
+ }
79
+
80
+ content = "".join(lines[start_line - 1:end_line])
81
+ output = f"\n文件: {abs_path}\n行: [{start_line}-{end_line}]\n{content}" + "\n\n" + "="*80 + "\n\n"
82
+
83
+ spinner.text = f"文件读取完成: {abs_path}"
84
+ spinner.ok("✅")
74
85
  return {
75
- "success": False,
76
- "stdout": "",
77
- "stderr": error_msg
86
+ "success": True,
87
+ "stdout": output,
88
+ "stderr": ""
78
89
  }
79
-
80
- content = "".join(lines[start_line - 1:end_line])
81
- output = f"\文件: {abs_path}\行: [{start_line}-{end_line}]\n{content}" + "\n\n" + "="*80 + "\n\n"
82
-
83
- return {
84
- "success": True,
85
- "stdout": output,
86
- "stderr": ""
87
- }
88
-
89
90
  elif operation == "write":
90
- os.makedirs(os.path.dirname(os.path.abspath(abs_path)), exist_ok=True)
91
- with open(abs_path, 'w', encoding='utf-8') as f:
92
- f.write(content)
93
-
94
- PrettyOutput.print(f"写入文件: {abs_path}", OutputType.INFO)
95
- return {
96
- "success": True,
97
- "stdout": f"Successfully wrote content to {abs_path}",
98
- "stderr": ""
99
- }
100
- PrettyOutput.print(f"未知操作: {operation}", OutputType.WARNING)
91
+ with yaspin(text=f"正在写入文件: {abs_path}...", color="cyan") as spinner:
92
+ os.makedirs(os.path.dirname(os.path.abspath(abs_path)), exist_ok=True)
93
+ with open(abs_path, 'w', encoding='utf-8') as f:
94
+ f.write(content)
95
+ spinner.text = f"文件写入完成: {abs_path}"
96
+ spinner.ok("✅")
97
+ return {
98
+ "success": True,
99
+ "stdout": f"Successfully wrote content to {abs_path}",
100
+ "stderr": ""
101
+ }
101
102
  return {
102
103
  "success": False,
103
104
  "stdout": "",
@@ -4,6 +4,7 @@ import subprocess
4
4
  from typing import Dict, Any
5
5
  import tempfile
6
6
  import yaml
7
+ from yaspin import yaspin
7
8
  from jarvis.jarvis_platform.registry import PlatformRegistry
8
9
  import sys
9
10
  import argparse
@@ -54,59 +55,68 @@ class GitCommitTool:
54
55
  PrettyOutput.print("没有未提交的更改", OutputType.SUCCESS)
55
56
  return {"success": True, "stdout": "No changes to commit", "stderr": ""}
56
57
 
57
- PrettyOutput.print("准备添加文件到提交...", OutputType.SYSTEM)
58
- subprocess.Popen(
59
- ["git", "add", "."],
60
- stdout=subprocess.DEVNULL,
61
- stderr=subprocess.DEVNULL
62
- ).wait()
63
-
64
- PrettyOutput.print("获取差异...", OutputType.SYSTEM)
65
- process = subprocess.Popen(
66
- ["git", "diff", "--cached", "--exit-code"],
67
- stdout=subprocess.PIPE,
68
- stderr=subprocess.PIPE
69
- )
70
- diff = process.communicate()[0].decode()
71
- PrettyOutput.print(diff, OutputType.CODE, lang="diff")
72
-
73
- prompt = f'''根据以下规则生成提交信息:
74
- 提交信息应使用{args.get('lang', '中文')}书写
75
- # 必需结构
76
- 必须使用以下格式:
77
- <COMMIT_MESSAGE>
78
- <类型>(<范围>): <主题>
79
- 使用祈使语气描述变更内容
80
- </COMMIT_MESSAGE>
81
- # 格式规则
82
- 1. 类型: fix, feat, docs, style, refactor, test, chore
83
- 2. 范围表示模块 (例如: auth, database)
84
- 3. 主题行 <= 72个字符,不以句号结尾
85
- 4. 正文使用现在时态解释每个变更的内容和原因
86
- 5. 不要遗漏任何变更
87
- # 分析材料
88
- {diff}
89
- '''
90
-
91
- PrettyOutput.print("生成提交消息...", OutputType.SYSTEM)
92
- platform = PlatformRegistry().get_codegen_platform()
93
- platform.set_suppress_output(True)
94
- commit_message = platform.chat_until_success(prompt)
95
- commit_message = self._extract_commit_message(commit_message)
96
-
97
- # 使用临时文件处理提交消息
98
- with tempfile.NamedTemporaryFile(mode='w', delete=True) as tmp_file:
99
- tmp_file.write(commit_message)
100
- tmp_file.flush() # 确保内容写入文件
101
- commit_cmd = ["git", "commit", "-F", tmp_file.name]
102
- PrettyOutput.print("提交...", OutputType.INFO)
58
+ with yaspin(text="正在初始化提交流程...", color="cyan") as spinner:
59
+ # 添加文件
60
+ spinner.text = "正在添加文件到提交..."
103
61
  subprocess.Popen(
104
- commit_cmd,
62
+ ["git", "add", "."],
105
63
  stdout=subprocess.DEVNULL,
106
64
  stderr=subprocess.DEVNULL
107
65
  ).wait()
66
+ spinner.write("✅ 添加文件到提交")
67
+
68
+ # 获取差异
69
+ spinner.text = "正在获取代码差异..."
70
+ process = subprocess.Popen(
71
+ ["git", "diff", "--cached", "--exit-code"],
72
+ stdout=subprocess.PIPE,
73
+ stderr=subprocess.PIPE
74
+ )
75
+ diff = process.communicate()[0].decode()
76
+ spinner.write("✅ 获取差异")
77
+
78
+ # 生成提交信息
79
+ spinner.text = "正在生成提交消息..."
80
+ prompt = f'''根据以下规则生成提交信息:
81
+ 提交信息应使用{args.get('lang', '中文')}书写
82
+ # 必需结构
83
+ 必须使用以下格式:
84
+ <COMMIT_MESSAGE>
85
+ <类型>(<范围>): <主题>
86
+ 使用祈使语气描述变更内容
87
+ </COMMIT_MESSAGE>
88
+ # 格式规则
89
+ 1. 类型: fix, feat, docs, style, refactor, test, chore
90
+ 2. 范围表示模块 (例如: auth, database)
91
+ 3. 主题行 <= 72个字符,不以句号结尾
92
+ 4. 正文使用现在时态解释每个变更的内容和原因
93
+ 5. 不要遗漏任何变更
94
+ # 分析材料
95
+ {diff}
96
+ '''
97
+ platform = PlatformRegistry().get_codegen_platform()
98
+ commit_message = platform.chat_until_success(prompt)
99
+ commit_message = self._extract_commit_message(commit_message)
100
+ spinner.write("✅ 生成提交消息")
101
+
102
+ # 执行提交
103
+ spinner.text = "正在准备提交..."
104
+ with tempfile.NamedTemporaryFile(mode='w', delete=True) as tmp_file:
105
+ tmp_file.write(commit_message)
106
+ tmp_file.flush()
107
+ spinner.text = "正在执行提交..."
108
+ commit_cmd = ["git", "commit", "-F", tmp_file.name]
109
+ subprocess.Popen(
110
+ commit_cmd,
111
+ stdout=subprocess.DEVNULL,
112
+ stderr=subprocess.DEVNULL
113
+ ).wait()
114
+ spinner.write("✅ 提交")
115
+
116
+ commit_hash = self._get_last_commit_hash()
117
+ spinner.text = "完成提交"
118
+ spinner.ok("✅")
108
119
 
109
- commit_hash = self._get_last_commit_hash()
110
120
  PrettyOutput.print(f"提交哈希: {commit_hash}\n提交消息: {commit_message}", OutputType.SUCCESS)
111
121
 
112
122
  return {
@@ -116,7 +126,7 @@ class GitCommitTool:
116
126
  "commit_message": commit_message
117
127
  }),
118
128
  "stderr": ""
119
- }
129
+ }
120
130
 
121
131
  except Exception as e:
122
132
  return {
@@ -134,4 +144,4 @@ def main():
134
144
  tool.execute({"lang": args.lang if hasattr(args, 'lang') else 'Chinese'})
135
145
 
136
146
  if __name__ == "__main__":
137
- sys.exit(main())
147
+ sys.exit(main())
@@ -0,0 +1,143 @@
1
+ from typing import Dict, Any
2
+ import os
3
+
4
+ from yaspin import yaspin
5
+
6
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
7
+
8
+ class ReadCodeTool:
9
+ name = "read_code"
10
+ description = "用于读取代码文件并在每行前添加行号的工具"
11
+ parameters = {
12
+ "type": "object",
13
+ "properties": {
14
+ "files": {
15
+ "type": "array",
16
+ "items": {
17
+ "type": "object",
18
+ "properties": {
19
+ "path": {"type": "string"},
20
+ "start_line": {"type": "number", "default": 1},
21
+ "end_line": {"type": "number", "default": -1}
22
+ },
23
+ "required": ["path"]
24
+ },
25
+ "description": "要读取的文件列表"
26
+ }
27
+ },
28
+ "required": ["files"]
29
+ }
30
+
31
+ def _handle_single_file(self, filepath: str, start_line: int = 1, end_line: int = -1) -> Dict[str, Any]:
32
+ try:
33
+ abs_path = os.path.abspath(filepath)
34
+ with yaspin(text=f"正在读取文件: {abs_path}...", color="cyan") as spinner:
35
+ # 文件存在性检查
36
+ if not os.path.exists(abs_path):
37
+ return {
38
+ "success": False,
39
+ "stdout": "",
40
+ "stderr": f"文件不存在: {abs_path}"
41
+ }
42
+
43
+ # 文件大小限制检查(10MB)
44
+ if os.path.getsize(abs_path) > 10 * 1024 * 1024:
45
+ return {
46
+ "success": False,
47
+ "stdout": "",
48
+ "stderr": "文件过大 (>10MB)"
49
+ }
50
+
51
+ # 读取文件内容
52
+ with open(abs_path, 'r', encoding='utf-8') as f:
53
+ lines = f.readlines()
54
+
55
+ total_lines = len(lines)
56
+
57
+ # 处理特殊值-1表示文件末尾
58
+ if end_line == -1:
59
+ end_line = total_lines
60
+ else:
61
+ end_line = max(1, min(end_line, total_lines)) if end_line >= 0 else total_lines + end_line + 1
62
+
63
+ start_line = max(1, min(start_line, total_lines)) if start_line >= 0 else total_lines + start_line + 1
64
+
65
+ if start_line > end_line:
66
+ spinner.fail("❌")
67
+ return {
68
+ "success": False,
69
+ "stdout": "",
70
+ "stderr": f"无效的行范围 [{start_line}-{end_line}] (总行数: {total_lines})"
71
+ }
72
+
73
+ # 添加行号并构建输出内容
74
+ selected_lines = lines[start_line-1:end_line]
75
+ numbered_content = "".join(
76
+ [f"{i:4d} | {line}"
77
+ for i, line in enumerate(selected_lines, start=start_line)]
78
+ )
79
+
80
+ # 构建输出格式
81
+ output = (
82
+ f"\n🔍 文件: {abs_path}\n"
83
+ f"📄 原始行号: {start_line}-{end_line} (共{end_line - start_line + 1}行) | 显示行号: 1-{len(selected_lines)}\n\n"
84
+ f"{numbered_content}\n"
85
+ f"{'='*80}\n"
86
+ )
87
+
88
+ spinner.ok("✅")
89
+ return {
90
+ "success": True,
91
+ "stdout": output,
92
+ "stderr": ""
93
+ }
94
+
95
+ except Exception as e:
96
+ PrettyOutput.print(str(e), OutputType.ERROR)
97
+ return {
98
+ "success": False,
99
+ "stdout": "",
100
+ "stderr": f"文件读取失败: {str(e)}"
101
+ }
102
+
103
+ def execute(self, args: Dict) -> Dict[str, Any]:
104
+ try:
105
+ if "files" not in args or not isinstance(args["files"], list):
106
+ return {
107
+ "success": False,
108
+ "stdout": "",
109
+ "stderr": "参数中必须包含文件列表"
110
+ }
111
+
112
+ all_outputs = []
113
+ overall_success = True
114
+
115
+ for file_info in args["files"]:
116
+ if not isinstance(file_info, dict) or "path" not in file_info:
117
+ continue
118
+
119
+ result = self._handle_single_file(
120
+ file_info["path"].strip(),
121
+ file_info.get("start_line", 1),
122
+ file_info.get("end_line", -1)
123
+ )
124
+
125
+ if result["success"]:
126
+ all_outputs.append(result["stdout"])
127
+ else:
128
+ all_outputs.append(f"❌ {file_info['path']}: {result['stderr']}")
129
+ overall_success = False
130
+
131
+ return {
132
+ "success": overall_success,
133
+ "stdout": "\n".join(all_outputs),
134
+ "stderr": ""
135
+ }
136
+
137
+ except Exception as e:
138
+ PrettyOutput.print(str(e), OutputType.ERROR)
139
+ return {
140
+ "success": False,
141
+ "stdout": "",
142
+ "stderr": f"代码读取失败: {str(e)}"
143
+ }