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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +108 -95
- jarvis/jarvis_agent/main.py +77 -0
- jarvis/jarvis_code_agent/builtin_input_handler.py +43 -0
- jarvis/jarvis_code_agent/code_agent.py +17 -81
- jarvis/jarvis_code_agent/file_input_handler.py +88 -0
- jarvis/jarvis_code_agent/patch.py +142 -114
- jarvis/jarvis_code_agent/shell_input_handler.py +8 -2
- jarvis/jarvis_codebase/main.py +240 -213
- jarvis/jarvis_dev/main.py +4 -3
- jarvis/jarvis_multi_agent/__init__.py +51 -40
- jarvis/jarvis_platform/base.py +6 -5
- jarvis/jarvis_platform_manager/main.py +1 -1
- jarvis/jarvis_rag/main.py +250 -186
- jarvis/jarvis_smart_shell/main.py +0 -1
- jarvis/jarvis_tools/ask_codebase.py +4 -3
- jarvis/jarvis_tools/chdir.py +22 -22
- jarvis/jarvis_tools/code_review.py +38 -33
- jarvis/jarvis_tools/execute_shell.py +0 -3
- jarvis/jarvis_tools/file_operation.py +56 -55
- jarvis/jarvis_tools/git_commiter.py +60 -50
- jarvis/jarvis_tools/read_code.py +143 -0
- jarvis/jarvis_tools/read_webpage.py +50 -30
- jarvis/jarvis_tools/registry.py +4 -21
- jarvis/jarvis_tools/search_web.py +61 -36
- jarvis/jarvis_tools/tool_generator.py +78 -36
- jarvis/jarvis_utils/__init__.py +17 -17
- jarvis/jarvis_utils/config.py +87 -51
- jarvis/jarvis_utils/embedding.py +49 -48
- jarvis/jarvis_utils/git_utils.py +34 -34
- jarvis/jarvis_utils/globals.py +26 -26
- jarvis/jarvis_utils/input.py +61 -45
- jarvis/jarvis_utils/methodology.py +94 -76
- jarvis/jarvis_utils/output.py +63 -62
- jarvis/jarvis_utils/utils.py +2 -2
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.129.dist-info/RECORD +78 -0
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/entry_points.txt +2 -0
- jarvis_ai_assistant-0.1.126.dist-info/RECORD +0 -74
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.126.dist-info → jarvis_ai_assistant-0.1.129.dist-info}/top_level.txt +0 -0
jarvis/jarvis_tools/chdir.py
CHANGED
|
@@ -16,31 +16,31 @@ class ChdirTool:
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
|
|
19
|
-
"""
|
|
19
|
+
"""执行目录切换操作,并提供全面的错误处理。
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
args:
|
|
21
|
+
参数:
|
|
22
|
+
args: 包含 'path' 键的字典,目标目录路径
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
- success:
|
|
27
|
-
- stdout:
|
|
28
|
-
- stderr:
|
|
24
|
+
返回:
|
|
25
|
+
字典,包含以下内容:
|
|
26
|
+
- success: 布尔值,表示操作状态
|
|
27
|
+
- stdout: 成功消息或空字符串
|
|
28
|
+
- stderr: 错误消息或空字符串
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
30
|
+
异常处理:
|
|
31
|
+
处理并返回适当的错误消息:
|
|
32
|
+
- 不存在的路径
|
|
33
|
+
- 非目录路径
|
|
34
|
+
- 权限错误
|
|
35
|
+
- 其他通用异常
|
|
36
36
|
"""
|
|
37
|
-
#
|
|
37
|
+
# 主执行块,包含全面的错误处理
|
|
38
38
|
try:
|
|
39
|
-
#
|
|
39
|
+
# 规范化并展开输入路径(处理 ~ 和相对路径)
|
|
40
40
|
path = os.path.expanduser(args["path"].strip())
|
|
41
41
|
path = os.path.abspath(path)
|
|
42
42
|
|
|
43
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
69
|
+
# 处理用户没有目录访问权限的情况
|
|
70
70
|
except PermissionError:
|
|
71
71
|
return {
|
|
72
72
|
"success": False,
|
|
73
73
|
"stdout": "",
|
|
74
74
|
"stderr": f"无权限访问目录: {path}"
|
|
75
75
|
}
|
|
76
|
-
#
|
|
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
|
-
|
|
45
|
-
if "
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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": "
|
|
83
|
+
"stderr": f"Failed to get diff: {str(e)}"
|
|
74
84
|
}
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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":
|
|
76
|
-
"stdout":
|
|
77
|
-
"stderr":
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
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
|
+
}
|