jarvis-ai-assistant 0.1.101__py3-none-any.whl → 0.1.103__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 (54) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/agent.py +140 -140
  3. jarvis/jarvis_code_agent/code_agent.py +234 -0
  4. jarvis/{jarvis_coder → jarvis_code_agent}/file_select.py +16 -17
  5. jarvis/jarvis_code_agent/patch.py +118 -0
  6. jarvis/jarvis_code_agent/relevant_files.py +66 -0
  7. jarvis/jarvis_codebase/main.py +32 -29
  8. jarvis/jarvis_platform/main.py +5 -3
  9. jarvis/jarvis_rag/main.py +11 -15
  10. jarvis/jarvis_smart_shell/main.py +2 -2
  11. jarvis/models/ai8.py +1 -0
  12. jarvis/models/kimi.py +36 -30
  13. jarvis/models/ollama.py +17 -11
  14. jarvis/models/openai.py +15 -12
  15. jarvis/models/oyi.py +22 -7
  16. jarvis/models/registry.py +1 -25
  17. jarvis/tools/__init__.py +0 -6
  18. jarvis/tools/ask_codebase.py +99 -0
  19. jarvis/tools/ask_user.py +1 -9
  20. jarvis/tools/chdir.py +1 -1
  21. jarvis/tools/code_review.py +163 -0
  22. jarvis/tools/create_code_sub_agent.py +19 -45
  23. jarvis/tools/create_code_test_agent.py +115 -0
  24. jarvis/tools/create_ctags_agent.py +176 -0
  25. jarvis/tools/create_sub_agent.py +2 -2
  26. jarvis/tools/execute_shell.py +2 -2
  27. jarvis/tools/file_operation.py +2 -2
  28. jarvis/tools/find_in_codebase.py +108 -0
  29. jarvis/tools/git_commiter.py +68 -0
  30. jarvis/tools/methodology.py +3 -3
  31. jarvis/tools/rag.py +6 -3
  32. jarvis/tools/read_code.py +147 -0
  33. jarvis/tools/read_webpage.py +1 -1
  34. jarvis/tools/registry.py +92 -68
  35. jarvis/tools/search.py +8 -6
  36. jarvis/tools/select_code_files.py +4 -4
  37. jarvis/utils.py +270 -95
  38. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/METADATA +9 -5
  39. jarvis_ai_assistant-0.1.103.dist-info/RECORD +51 -0
  40. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/entry_points.txt +4 -2
  41. jarvis/jarvis_code_agent/main.py +0 -202
  42. jarvis/jarvis_coder/__init__.py +0 -0
  43. jarvis/jarvis_coder/git_utils.py +0 -123
  44. jarvis/jarvis_coder/main.py +0 -241
  45. jarvis/jarvis_coder/patch_handler.py +0 -340
  46. jarvis/jarvis_coder/plan_generator.py +0 -145
  47. jarvis/tools/execute_code_modification.py +0 -70
  48. jarvis/tools/find_files.py +0 -119
  49. jarvis/tools/generate_tool.py +0 -174
  50. jarvis/tools/thinker.py +0 -151
  51. jarvis_ai_assistant-0.1.101.dist-info/RECORD +0 -51
  52. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/LICENSE +0 -0
  53. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/WHEEL +0 -0
  54. {jarvis_ai_assistant-0.1.101.dist-info → jarvis_ai_assistant-0.1.103.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,176 @@
1
+ from typing import Dict, Any
2
+ import subprocess
3
+ import os
4
+ from pathlib import Path
5
+
6
+ import yaml
7
+ from jarvis.agent import Agent
8
+ from jarvis.tools.registry import ToolRegistry
9
+ from jarvis.utils import OutputType, PrettyOutput, get_multiline_input, init_env
10
+
11
+ ctags_system_prompt = """You are a Ctags Expert Agent specializing in code analysis using Exuberant Ctags. Follow this protocol:
12
+
13
+ 【OUTPUT OPTIMIZATION】
14
+ 1. Filter with grep:
15
+ ctags -x <symbol> | grep -E 'pattern'
16
+ 2. Limit output lines:
17
+ head -n 20
18
+ 3. Context preview:
19
+ grep -A 3 -B 3 <line> <file>
20
+ 4. Column selection:
21
+ cut -f 1,3
22
+ 5. Sort and deduplicate:
23
+ sort | uniq
24
+
25
+ 【WORKFLOW】
26
+ 1. REQUIREMENT ANALYSIS
27
+ - Analyze query for symbols/patterns
28
+ - Determine search scope
29
+ - Select ctags options
30
+ - Plan output filtering
31
+
32
+ 2. TAGS MANAGEMENT
33
+ - Generate/update tags file:
34
+ ctags -R --languages=<lang> --exclude=<pattern>
35
+ - Verify tags file integrity
36
+ - Maintain tags file versioning
37
+
38
+ 3. SYMBOL PROCESSING
39
+ - Search symbols using:
40
+ grep -n <symbol> tags
41
+ ctags -x --<filter>
42
+ - Analyze symbol relationships
43
+ - Map symbol dependencies
44
+ - Apply output filters:
45
+ * Remove noise with grep -v
46
+ * Highlight key fields with awk
47
+ * Truncate long lines with cut
48
+
49
+ 4. OUTPUT GENERATION
50
+ - Format results as YAML
51
+ - Include file paths and line numbers
52
+ - Add symbol metadata
53
+ - Limit to 20 key results
54
+ - Exclude temporary files
55
+ - Compress repetitive info
56
+
57
+ 【COMMAND REFERENCE】
58
+ 1. Generate Tags:
59
+ ctags -R --fields=+nKSt --extras=+fq -V *
60
+
61
+ 2. Search Patterns:
62
+ ctags -x --c-types=f
63
+ ctags -x --sort=no <symbol>
64
+ ctags -x | grep '^main' | head -n 5
65
+
66
+ 3. Language Specific:
67
+ --languages=Python,Java,C++
68
+ --python-kinds=-iv
69
+
70
+ 4. Filtering:
71
+ --exclude=.git
72
+ --exclude=*.min.js
73
+ ctags -x | grep -v '_test' # Exclude tests
74
+
75
+ 【ERROR HANDLING】
76
+ - Missing tags: Regenerate tags
77
+ - Invalid symbols: Use fuzzy search
78
+ - Encoding issues: Use --input-encoding
79
+ - Large codebase: Limit scope
80
+ - Output too long: Add head/grep filters
81
+
82
+ 【NATURAL LANGUAGE PROCESSING】
83
+ 1. Query Interpretation:
84
+ - Identify key terms: "find", "locate", "list", "show"
85
+ - Detect symbol types: class, function, variable
86
+ - Recognize relationships: "calls", "inherits", "uses"
87
+
88
+ 2. Query Types:
89
+ - Location: "Where is X defined?"
90
+ - References: "Who calls Y?"
91
+ - Hierarchy: "Show subclasses of Z"
92
+ - Impact: "What uses this module?"
93
+
94
+ 3. Auto Command Mapping:
95
+ | Query Pattern | Ctags Command |
96
+ |------------------------------|------------------------------------|
97
+ | Find definitions of X | ctags -x --<lang>-kinds=f | less |
98
+ | List all functions in Y | ctags -x --filter='function' |
99
+ | Show callers of Z | ctags --extra=+q -x | grep Z |
100
+ | Find interface implementations| ctags -x --_traits=yes |
101
+
102
+ 4. Context Handling:
103
+ - Detect language from file extensions
104
+ - Auto-detect project root
105
+ - Apply language-specific filters
106
+ - Choose appropriate output format:
107
+ * Simple list for single results
108
+ * Table for multiple entries
109
+ * Tree view for hierarchies
110
+ * JSON when programmatic access needed
111
+
112
+ 【EXAMPLE QUERIES】
113
+ 1. "Find all Python functions in src/ that use Redis"
114
+ 2. "Show Java classes implementing PaymentService"
115
+ 3. "List called methods in auth module"
116
+ 4. "Where is User model defined?"
117
+ 5. "What config files reference database settings?"
118
+ """
119
+
120
+ class CtagsTool:
121
+ name = "create_ctags_agent"
122
+ description = "Analyze code structure and symbols using natural language queries"
123
+ parameters = {
124
+ "type": "object",
125
+ "properties": {
126
+ "query": {
127
+ "type": "string",
128
+ "description": "Natural language description of code analysis needs"
129
+ }
130
+ },
131
+ "required": ["query"]
132
+ }
133
+
134
+ def execute(self, args: Dict) -> Dict[str, Any]:
135
+ """Execute code analysis based on natural language query"""
136
+ try:
137
+ tool_registry = ToolRegistry()
138
+ tool_registry.use_tools(["execute_shell"])
139
+
140
+ ctags_agent = Agent(
141
+ system_prompt=ctags_system_prompt,
142
+ name="Ctags Analysis Agent",
143
+ is_sub_agent=True,
144
+ tool_registry=tool_registry
145
+ )
146
+
147
+ analysis_request = f"""
148
+ Analysis Request: {args['query']}
149
+ Context: {args.get('context', {})}
150
+ """
151
+
152
+ result = ctags_agent.run(analysis_request)
153
+
154
+ return {
155
+ "success": True,
156
+ "stdout": result,
157
+ "stderr": ""
158
+ }
159
+ except Exception as e:
160
+ return {
161
+ "success": False,
162
+ "stdout": "",
163
+ "stderr": f"Analysis failed: {str(e)}"
164
+ }
165
+
166
+ def main():
167
+ """CLI testing"""
168
+ init_env()
169
+ tool = CtagsTool()
170
+ query = get_multiline_input("Please enter your query:")
171
+ result = tool.execute({
172
+ "query": query
173
+ })
174
+ PrettyOutput.print(yaml.dump(result, default_style='|'), OutputType.INFO)
175
+ if __name__ == "__main__":
176
+ main()
@@ -2,6 +2,7 @@ from typing import Dict, Any
2
2
 
3
3
 
4
4
  from jarvis.agent import Agent, origin_agent_system_prompt
5
+ from jarvis.tools.registry import ToolRegistry
5
6
  from jarvis.utils import OutputType, PrettyOutput
6
7
 
7
8
 
@@ -58,12 +59,11 @@ class SubAgentTool:
58
59
  if goal:
59
60
  task_description += f"\n\nCompletion goal:\n{goal}"
60
61
 
61
-
62
62
 
63
63
  # Create sub-agent
64
64
  sub_agent = Agent(
65
65
  system_prompt=origin_agent_system_prompt,
66
- name=agent_name,
66
+ name=f"Agent({agent_name})",
67
67
  is_sub_agent=True
68
68
  )
69
69
 
@@ -29,7 +29,7 @@ class ShellTool:
29
29
  def execute(self, args: Dict) -> Dict[str, Any]:
30
30
  """Execute shell command"""
31
31
  try:
32
- command = args["command"]
32
+ command = args["command"].strip()
33
33
 
34
34
  # Generate temporary file name
35
35
  output_file = os.path.join(tempfile.gettempdir(), f"jarvis_shell_{os.getpid()}.log")
@@ -61,7 +61,7 @@ class ShellTool:
61
61
  Path(output_file).unlink(missing_ok=True)
62
62
 
63
63
  return {
64
- "success": return_code == 0,
64
+ "success": True,
65
65
  "stdout": output,
66
66
  "stderr": "",
67
67
  }
@@ -37,8 +37,8 @@ class FileOperationTool:
37
37
  def execute(self, args: Dict) -> Dict[str, Any]:
38
38
  """Execute file operations"""
39
39
  try:
40
- operation = args["operation"]
41
- filepath = args["filepath"]
40
+ operation = args["operation"].strip()
41
+ filepath = args["filepath"].strip()
42
42
  encoding = args.get("encoding", "utf-8")
43
43
 
44
44
  # Record the operation and the full path
@@ -0,0 +1,108 @@
1
+ from typing import Dict, Any
2
+ from jarvis.jarvis_code_agent.file_select import select_files
3
+ from jarvis.utils import OutputType, PrettyOutput, dont_use_local_model, find_git_root
4
+ from jarvis.jarvis_codebase.main import CodeBase
5
+
6
+ class FindInCodebaseTool:
7
+ """Tool for searching files in codebase based on requirements"""
8
+
9
+ name = "find_in_codebase"
10
+ description = "Search and identify relevant code files in the codebase based on requirements description, using semantic search"
11
+ parameters = {
12
+ "type": "object",
13
+ "properties": {
14
+ "query": {
15
+ "type": "string",
16
+ "description": "The search query or requirement description"
17
+ },
18
+ "top_k": {
19
+ "type": "integer",
20
+ "description": "Maximum number of results to return",
21
+ "default": 20
22
+ }
23
+ },
24
+ "required": ["query"]
25
+ }
26
+
27
+ @staticmethod
28
+ def check() -> bool:
29
+ return not dont_use_local_model()
30
+
31
+ def execute(self, args: Dict) -> Dict[str, Any]:
32
+ """Execute the search
33
+
34
+ Args:
35
+ args: Dictionary containing:
36
+ - query: Search query string
37
+ - top_k: Maximum number of results (optional)
38
+
39
+ Returns:
40
+ Dict containing:
41
+ - success: Boolean indicating success
42
+ - stdout: Search results in YAML format
43
+ - stderr: Error message if any
44
+ """
45
+ try:
46
+ query = args["query"]
47
+ top_k = args.get("top_k", 20)
48
+
49
+ root_dir = find_git_root()
50
+
51
+ codebase = CodeBase(root_dir)
52
+
53
+ # Search for relevant files
54
+ results = codebase.search_similar(query, top_k)
55
+
56
+ results = select_files(results, root_dir)
57
+
58
+ if not results:
59
+ return {
60
+ "success": True,
61
+ "stdout": "files: []\n",
62
+ "stderr": "No relevant files found"
63
+ }
64
+
65
+
66
+ return {
67
+ "success": True,
68
+ "stdout": "\n".join(results),
69
+ "stderr": ""
70
+ }
71
+
72
+ except Exception as e:
73
+ PrettyOutput.print(f"Search error: {str(e)}", OutputType.ERROR)
74
+ return {
75
+ "success": False,
76
+ "stdout": "",
77
+ "stderr": f"Failed to execute search: {str(e)}"
78
+ }
79
+
80
+ def main():
81
+ """Command line interface for the tool"""
82
+ import argparse
83
+
84
+ parser = argparse.ArgumentParser(description='Search for relevant files in codebase')
85
+ parser.add_argument('query', help='Search query or requirement description')
86
+ parser.add_argument('--top-k', type=int, default=20, help='Maximum number of results to return')
87
+
88
+ args = parser.parse_args()
89
+
90
+ tool = FindInCodebaseTool()
91
+ result = tool.execute({
92
+ "query": args.query,
93
+ "top_k": args.top_k
94
+ })
95
+
96
+ if result["success"]:
97
+ if result["stdout"]:
98
+ print(result["stdout"])
99
+ else:
100
+ PrettyOutput.print("No relevant files found", OutputType.WARNING)
101
+ else:
102
+ PrettyOutput.print(result["stderr"], OutputType.ERROR)
103
+ return 1
104
+
105
+ return 0
106
+
107
+ if __name__ == "__main__":
108
+ exit(main())
@@ -0,0 +1,68 @@
1
+ import os
2
+ import re
3
+ from typing import Dict, Any
4
+
5
+ import yaml
6
+ from jarvis.agent import Agent
7
+ from jarvis.models.registry import PlatformRegistry
8
+ from jarvis.tools.registry import ToolRegistry
9
+ from jarvis.utils import OutputType, PrettyOutput, init_env
10
+ import sys
11
+
12
+
13
+ class GitCommitTool:
14
+ name = "git_commit_agent"
15
+ description = "Automatically generate and execute git commits based on code changes"
16
+ parameters = {"properties": {}, "required": []}
17
+
18
+ def _extract_commit_message(self, message):
19
+ r = re.search(r"<COMMIT_MESSAGE>(.*)</COMMIT_MESSAGE>", message, re.DOTALL)
20
+ if r:
21
+ return ';'.join(r.group(1).strip().splitlines())
22
+ return "Unknown commit message"
23
+
24
+ def _get_last_commit_hash(self):
25
+ return os.popen("git log -1 --pretty=%H").read().strip()
26
+
27
+ def execute(self, args: Dict) -> Dict[str, Any]:
28
+ """Execute automatic commit process"""
29
+ try:
30
+ PrettyOutput.print("Add files to commit...", OutputType.SYSTEM)
31
+ os.system("git add .")
32
+ PrettyOutput.print("Get diff...", OutputType.SYSTEM)
33
+ diff = os.popen("git diff --cached --exit-code").read()
34
+ PrettyOutput.print(diff, OutputType.CODE)
35
+ prompt = f'''Please generate a commit message for the following changes.
36
+ Format:
37
+ <COMMIT_MESSAGE>
38
+ type(scope): description
39
+ </COMMIT_MESSAGE>
40
+
41
+ Don't include any other information.
42
+
43
+ {diff}
44
+ '''
45
+ PrettyOutput.print("Generate commit message...", OutputType.SYSTEM)
46
+ platform = PlatformRegistry().get_codegen_platform()
47
+ platform.set_suppress_output(True)
48
+ commit_message = platform.chat_until_success(prompt)
49
+ commit_message = self._extract_commit_message(commit_message)
50
+ PrettyOutput.print("Commit...", OutputType.INFO)
51
+ os.popen(f"git commit -m '{commit_message}'")
52
+
53
+ commit_hash = self._get_last_commit_hash()
54
+
55
+ PrettyOutput.section(f"Commit hash: {commit_hash}\nCommit message: {commit_message}", OutputType.SUCCESS)
56
+
57
+ return {"success": True, "stdout": yaml.safe_dump({"commit_hash": commit_hash, "commit_message": commit_message}), "stderr": ""}
58
+
59
+ except Exception as e:
60
+ return {"success": False, "stdout": "", "stderr": f"Commit error: {str(e)}"}
61
+
62
+ def main():
63
+ init_env()
64
+ tool = GitCommitTool()
65
+ tool.execute({})
66
+
67
+ if __name__ == "__main__":
68
+ sys.exit(main())
@@ -73,9 +73,9 @@ class MethodologyTool:
73
73
  Returns:
74
74
  Dict[str, Any]: A dictionary containing the execution result
75
75
  """
76
- operation = args.get("operation")
77
- problem_type = args.get("problem_type")
78
- content = args.get("content")
76
+ operation = args.get("operation", "").strip()
77
+ problem_type = args.get("problem_type", "").strip()
78
+ content = args.get("content", "").strip()
79
79
 
80
80
  if not operation or not problem_type:
81
81
  return {
jarvis/tools/rag.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from typing import Dict, Any
2
2
  import os
3
- from jarvis.utils import OutputType, PrettyOutput
3
+ from jarvis.utils import OutputType, PrettyOutput, dont_use_local_model
4
4
  from jarvis.jarvis_rag.main import RAGTool as RAGCore
5
5
 
6
6
  class RAGTool:
@@ -26,6 +26,10 @@ class RAGTool:
26
26
  "required": ["dir", "question"]
27
27
  }
28
28
 
29
+ @staticmethod
30
+ def check() -> bool:
31
+ return not dont_use_local_model()
32
+
29
33
  def __init__(self):
30
34
  """Initialize RAG tool"""
31
35
  self.rag_instances = {} # Cache RAG instances for different directories
@@ -129,8 +133,7 @@ def main():
129
133
  })
130
134
 
131
135
  if result["success"]:
132
- PrettyOutput.print("\nAnswer:", OutputType.INFO)
133
- PrettyOutput.print(result["stdout"], OutputType.INFO)
136
+ PrettyOutput.print(f"Answer: {result['stdout']}", OutputType.INFO)
134
137
  else:
135
138
  PrettyOutput.print(result["stderr"], OutputType.ERROR)
136
139
 
@@ -0,0 +1,147 @@
1
+ from typing import Dict, Any
2
+ import os
3
+ from jarvis.utils import OutputType, PrettyOutput
4
+
5
+
6
+ class ReadCodeTool:
7
+ """Read code file with line numbers"""
8
+
9
+ name = "read_code"
10
+ description = "Read code file with line numbers"
11
+ parameters = {
12
+ "type": "object",
13
+ "properties": {
14
+ "filepath": {
15
+ "type": "string",
16
+ "description": "Absolute or relative path to the file"
17
+ },
18
+ "start_line": {
19
+ "type": "integer",
20
+ "description": "Start line number (0-based, inclusive)",
21
+ "default": 0
22
+ },
23
+ "end_line": {
24
+ "type": "integer",
25
+ "description": "End line number (0-based, exclusive). -1 means read to end",
26
+ "default": -1
27
+ }
28
+ },
29
+ "required": ["filepath"]
30
+ }
31
+
32
+ def execute(self, args: Dict) -> Dict[str, Any]:
33
+ """Execute code reading with line numbers
34
+
35
+ Args:
36
+ args: Dictionary containing:
37
+ - filepath: Path to the file
38
+ - start_line: Start line number (optional)
39
+ - end_line: End line number (optional)
40
+
41
+ Returns:
42
+ Dict containing:
43
+ - success: Boolean indicating success
44
+ - stdout: File content with line numbers
45
+ - stderr: Error message if any
46
+ """
47
+ try:
48
+ filepath = args["filepath"].strip()
49
+ start_line = args.get("start_line", 0)
50
+ end_line = args.get("end_line", -1)
51
+
52
+ # Record the operation and the full path
53
+ abs_path = os.path.abspath(filepath)
54
+ PrettyOutput.print(f"Reading code file: {abs_path}", OutputType.INFO)
55
+
56
+ # Check if file exists
57
+ if not os.path.exists(filepath):
58
+ return {
59
+ "success": False,
60
+ "stdout": "",
61
+ "stderr": f"File does not exist: {filepath}"
62
+ }
63
+
64
+ # Check file size
65
+ if os.path.getsize(filepath) > 10 * 1024 * 1024: # 10MB
66
+ return {
67
+ "success": False,
68
+ "stdout": "",
69
+ "stderr": "File too large (>10MB)"
70
+ }
71
+
72
+ # Read file content
73
+ try:
74
+ with open(filepath, 'r', encoding='utf-8') as f:
75
+ lines = f.readlines()
76
+ except UnicodeDecodeError:
77
+ return {
78
+ "success": False,
79
+ "stdout": "",
80
+ "stderr": "Failed to decode file with UTF-8 encoding"
81
+ }
82
+
83
+ # Validate line range
84
+ if start_line < 0:
85
+ start_line = 0
86
+ if end_line == -1 or end_line > len(lines):
87
+ end_line = len(lines)
88
+ if start_line >= end_line:
89
+ return {
90
+ "success": False,
91
+ "stdout": "",
92
+ "stderr": f"Invalid line range: [{start_line}, {end_line})"
93
+ }
94
+
95
+ # Format lines with hexadecimal line numbers
96
+ formatted_lines = []
97
+ for i, line in enumerate(lines[start_line:end_line]):
98
+ line_num = start_line + i
99
+ formatted_lines.append(f"{line_num:>5}:{line}")
100
+
101
+ content = "".join(formatted_lines)
102
+
103
+ PrettyOutput.print(f"File: {filepath}\nLines: [{start_line}, {end_line})\n{content}", OutputType.CODE)
104
+
105
+ return {
106
+ "success": True,
107
+ "stdout": content,
108
+ "stderr": ""
109
+ }
110
+
111
+ except Exception as e:
112
+ return {
113
+ "success": False,
114
+ "stdout": "",
115
+ "stderr": f"Failed to read code: {str(e)}"
116
+ }
117
+
118
+
119
+ def main():
120
+ """Command line interface for the tool"""
121
+ import argparse
122
+
123
+ parser = argparse.ArgumentParser(description='Read code file with line numbers')
124
+ parser.add_argument('filepath', help='Path to the code file')
125
+ parser.add_argument('--start', type=int, default=0, help='Start line number (0-based)')
126
+ parser.add_argument('--end', type=int, default=-1, help='End line number (0-based)')
127
+
128
+ args = parser.parse_args()
129
+
130
+ tool = ReadCodeTool()
131
+ result = tool.execute({
132
+ "filepath": args.filepath,
133
+ "start_line": args.start,
134
+ "end_line": args.end
135
+ })
136
+
137
+ if result["success"]:
138
+ print(result["stdout"])
139
+ else:
140
+ PrettyOutput.print(result["stderr"], OutputType.ERROR)
141
+ return 1
142
+
143
+ return 0
144
+
145
+
146
+ if __name__ == "__main__":
147
+ main()
@@ -20,7 +20,7 @@ class WebpageTool:
20
20
  def execute(self, args: Dict) -> Dict[str, Any]:
21
21
  """Read webpage content"""
22
22
  try:
23
- url = args["url"]
23
+ url = args["url"].strip()
24
24
 
25
25
  # Set request headers
26
26
  headers = {