jarvis-ai-assistant 0.1.131__py3-none-any.whl → 0.1.134__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 (75) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +165 -285
  3. jarvis/jarvis_agent/jarvis.py +143 -0
  4. jarvis/jarvis_agent/main.py +0 -2
  5. jarvis/jarvis_agent/patch.py +70 -48
  6. jarvis/jarvis_agent/shell_input_handler.py +1 -1
  7. jarvis/jarvis_code_agent/code_agent.py +169 -117
  8. jarvis/jarvis_dev/main.py +327 -626
  9. jarvis/jarvis_git_squash/main.py +10 -31
  10. jarvis/jarvis_lsp/base.py +0 -42
  11. jarvis/jarvis_lsp/cpp.py +0 -15
  12. jarvis/jarvis_lsp/go.py +0 -15
  13. jarvis/jarvis_lsp/python.py +0 -19
  14. jarvis/jarvis_lsp/registry.py +0 -62
  15. jarvis/jarvis_lsp/rust.py +0 -15
  16. jarvis/jarvis_multi_agent/__init__.py +19 -69
  17. jarvis/jarvis_multi_agent/main.py +43 -0
  18. jarvis/jarvis_platform/ai8.py +7 -32
  19. jarvis/jarvis_platform/base.py +2 -7
  20. jarvis/jarvis_platform/kimi.py +3 -144
  21. jarvis/jarvis_platform/ollama.py +54 -68
  22. jarvis/jarvis_platform/openai.py +0 -4
  23. jarvis/jarvis_platform/oyi.py +0 -75
  24. jarvis/jarvis_platform/registry.py +2 -16
  25. jarvis/jarvis_platform/yuanbao.py +264 -0
  26. jarvis/jarvis_rag/file_processors.py +138 -0
  27. jarvis/jarvis_rag/main.py +1305 -425
  28. jarvis/jarvis_tools/ask_codebase.py +216 -43
  29. jarvis/jarvis_tools/code_review.py +158 -113
  30. jarvis/jarvis_tools/create_sub_agent.py +0 -1
  31. jarvis/jarvis_tools/execute_python_script.py +58 -0
  32. jarvis/jarvis_tools/execute_shell.py +13 -26
  33. jarvis/jarvis_tools/execute_shell_script.py +1 -1
  34. jarvis/jarvis_tools/file_analyzer.py +282 -0
  35. jarvis/jarvis_tools/file_operation.py +1 -1
  36. jarvis/jarvis_tools/find_caller.py +278 -0
  37. jarvis/jarvis_tools/find_symbol.py +295 -0
  38. jarvis/jarvis_tools/function_analyzer.py +331 -0
  39. jarvis/jarvis_tools/git_commiter.py +5 -5
  40. jarvis/jarvis_tools/methodology.py +88 -53
  41. jarvis/jarvis_tools/project_analyzer.py +308 -0
  42. jarvis/jarvis_tools/rag.py +0 -5
  43. jarvis/jarvis_tools/read_code.py +24 -3
  44. jarvis/jarvis_tools/read_webpage.py +195 -81
  45. jarvis/jarvis_tools/registry.py +132 -11
  46. jarvis/jarvis_tools/search_web.py +22 -307
  47. jarvis/jarvis_tools/tool_generator.py +8 -10
  48. jarvis/jarvis_utils/__init__.py +1 -0
  49. jarvis/jarvis_utils/config.py +80 -76
  50. jarvis/jarvis_utils/embedding.py +344 -45
  51. jarvis/jarvis_utils/git_utils.py +9 -1
  52. jarvis/jarvis_utils/input.py +7 -6
  53. jarvis/jarvis_utils/methodology.py +384 -15
  54. jarvis/jarvis_utils/output.py +5 -3
  55. jarvis/jarvis_utils/utils.py +60 -8
  56. {jarvis_ai_assistant-0.1.131.dist-info → jarvis_ai_assistant-0.1.134.dist-info}/METADATA +8 -16
  57. jarvis_ai_assistant-0.1.134.dist-info/RECORD +82 -0
  58. {jarvis_ai_assistant-0.1.131.dist-info → jarvis_ai_assistant-0.1.134.dist-info}/entry_points.txt +4 -3
  59. jarvis/jarvis_codebase/__init__.py +0 -0
  60. jarvis/jarvis_codebase/main.py +0 -1011
  61. jarvis/jarvis_tools/lsp_find_definition.py +0 -150
  62. jarvis/jarvis_tools/lsp_find_references.py +0 -127
  63. jarvis/jarvis_tools/treesitter_analyzer.py +0 -331
  64. jarvis/jarvis_treesitter/README.md +0 -104
  65. jarvis/jarvis_treesitter/__init__.py +0 -20
  66. jarvis/jarvis_treesitter/database.py +0 -258
  67. jarvis/jarvis_treesitter/example.py +0 -115
  68. jarvis/jarvis_treesitter/grammar_builder.py +0 -182
  69. jarvis/jarvis_treesitter/language.py +0 -117
  70. jarvis/jarvis_treesitter/symbol.py +0 -31
  71. jarvis/jarvis_treesitter/tools_usage.md +0 -121
  72. jarvis_ai_assistant-0.1.131.dist-info/RECORD +0 -85
  73. {jarvis_ai_assistant-0.1.131.dist-info → jarvis_ai_assistant-0.1.134.dist-info}/LICENSE +0 -0
  74. {jarvis_ai_assistant-0.1.131.dist-info → jarvis_ai_assistant-0.1.134.dist-info}/WHEEL +0 -0
  75. {jarvis_ai_assistant-0.1.131.dist-info → jarvis_ai_assistant-0.1.134.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
1
1
  from typing import Dict, Any
2
- import requests
3
- from bs4 import BeautifulSoup
4
- from yaspin import yaspin
2
+ from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError
3
+ from bs4 import BeautifulSoup, Tag
4
+ from urllib.parse import urlparse, urljoin
5
+ import re
5
6
 
6
7
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
7
8
 
@@ -20,93 +21,206 @@ class WebpageTool:
20
21
  }
21
22
 
22
23
  def execute(self, args: Dict) -> Dict[str, Any]:
23
- """Read webpage content"""
24
+ """Read webpage content using Playwright to handle JavaScript-rendered pages"""
24
25
  try:
25
- url = args["url"].strip()
26
+ url = args["url"].strip()
26
27
 
27
- # Set request headers
28
- headers = {
29
- 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
30
- }
31
-
32
- # Send request
33
- with yaspin(text="正在读取网页...", color="cyan") as spinner:
34
- response = requests.get(url, headers=headers, timeout=10)
35
- response.raise_for_status()
36
- spinner.text = "网页读取完成"
37
- spinner.ok("✅")
28
+ with sync_playwright() as p:
29
+ # Launch browser
30
+ browser = p.chromium.launch(
31
+ headless=True,
32
+ args=['--disable-gpu', '--no-sandbox', '--disable-dev-shm-usage']
33
+ )
38
34
 
39
-
40
- # Use correct encoding
41
-
42
- response.encoding = response.apparent_encoding
43
-
44
- # Parse HTML
45
- with yaspin(text="正在解析网页...", color="cyan") as spinner:
46
- soup = BeautifulSoup(response.text, 'html.parser')
47
- spinner.text = "网页解析完成"
48
- spinner.ok("✅")
49
-
50
- # Remove script and style tags
51
- with yaspin(text="正在移除脚本和样式...", color="cyan") as spinner:
52
- for script in soup(["script", "style"]):
53
- script.decompose()
54
- spinner.text = "脚本和样式移除完成"
55
- spinner.ok("✅")
56
-
57
- # Extract title
58
- with yaspin(text="正在提取标题...", color="cyan") as spinner:
59
- title = soup.title.string if soup.title else ""
60
- title = title.strip() if title else "No title"
61
- spinner.text = "标题提取完成"
62
- spinner.ok("✅")
63
-
64
- with yaspin(text="正在提取文本和链接...", color="cyan") as spinner:
65
- # Extract text and links
66
- text_parts = []
67
- links = []
35
+ # Create a new page with appropriate settings
36
+ page = browser.new_page(
37
+ user_agent='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36',
38
+ viewport={'width': 1920, 'height': 1080}
39
+ )
68
40
 
69
- # Process content and collect links
70
- for element in soup.descendants:
71
- if element.name == 'a' and element.get('href'): # type: ignore
72
- href = element.get('href') # type: ignore
73
- text = element.get_text(strip=True)
74
- if text and href:
75
- links.append(f"[{text}]({href})")
76
- elif isinstance(element, str) and element.strip():
77
- text_parts.append(element.strip())
78
- spinner.text = "文本和链接提取完成"
79
- spinner.ok("✅")
80
-
81
- # Build output
82
- with yaspin(text="正在构建输出...", color="cyan") as spinner:
41
+ # Set timeout to avoid long waits
42
+ page.set_default_timeout(30000) # 30 seconds
43
+
44
+ try:
45
+ # Navigate to URL and wait for page to load
46
+ response = page.goto(url, wait_until="domcontentloaded")
47
+
48
+ # Additional wait for network to be idle (with a timeout)
49
+ try:
50
+ page.wait_for_load_state("networkidle", timeout=10000)
51
+ except PlaywrightTimeoutError:
52
+ # Continue even if network doesn't become completely idle
53
+ pass
54
+
55
+ # Make sure we got a valid response
56
+ if not response or response.status >= 400:
57
+ raise Exception(f"Failed to load page: HTTP {response.status if response else 'No response'}")
58
+
59
+ # Get page title safely
60
+ title = "No title"
61
+ try:
62
+ title = page.title()
63
+ except Exception:
64
+ # Try to extract title from content if direct method fails
65
+ try:
66
+ title_element = page.query_selector("title")
67
+ if title_element:
68
+ title = title_element.text_content() or "No title"
69
+ except Exception:
70
+ pass
71
+
72
+ # Get the HTML content after JavaScript execution
73
+ html_content = page.content()
74
+
75
+ except Exception as e:
76
+ raise Exception(f"Error navigating to page: {str(e)}")
77
+ finally:
78
+ # Always close browser
79
+ browser.close()
80
+
81
+ # Parse with BeautifulSoup and convert to markdown
82
+ markdown_content = self._html_to_markdown(html_content, url)
83
+
84
+ # Build output in markdown format
83
85
  output = [
84
- f"Title: {title}",
85
- "",
86
- "Text content:",
87
- "\n".join(text_parts),
88
- "",
89
- "Links found:",
90
- "\n".join(links) if links else "No links found"
86
+ f"# {title}",
87
+ f"Url: {url}",
88
+ markdown_content
91
89
  ]
92
- spinner.text = "输出构建完成"
93
- spinner.ok("✅")
94
-
95
- return {
96
- "success": True,
97
- "stdout": "\n".join(output),
98
- "stderr": ""
99
- }
90
+
91
+ return {
92
+ "success": True,
93
+ "stdout": "\n".join(output),
94
+ "stderr": ""
95
+ }
100
96
 
101
- except requests.RequestException as e:
102
- return {
103
- "success": False,
104
- "stdout": "",
105
- "stderr": f"Webpage request failed: {str(e)}"
106
- }
107
97
  except Exception as e:
98
+ PrettyOutput.print(f"读取网页失败: {str(e)}", OutputType.ERROR)
108
99
  return {
109
100
  "success": False,
110
101
  "stdout": "",
111
102
  "stderr": f"Failed to parse webpage: {str(e)}"
112
- }
103
+ }
104
+
105
+ def _create_soup_element(self, content):
106
+ """Safely create a BeautifulSoup element, ensuring it's treated as markup"""
107
+ if isinstance(content, str):
108
+ # Create a wrapper tag to ensure proper parsing
109
+ soup_div = BeautifulSoup(f"<div>{content}</div>", 'html.parser').div
110
+ if soup_div is not None:
111
+ return soup_div.contents
112
+ # Return an empty list if the div is None
113
+ return []
114
+ return content
115
+
116
+ def _html_to_markdown(self, html_content: str, base_url: str) -> str:
117
+ """Convert HTML to Markdown format preserving the content structure"""
118
+ soup = BeautifulSoup(html_content, 'html.parser')
119
+
120
+ # Remove unwanted elements
121
+ for element in soup(['script', 'style', 'meta', 'noscript', 'head']):
122
+ element.decompose()
123
+
124
+ # Process headings
125
+ for level in range(1, 7):
126
+ for heading in soup.find_all(f'h{level}'):
127
+ text = heading.get_text().strip()
128
+ heading_md = "\n\n" + "#" * level + " " + text + "\n\n"
129
+ new_element = self._create_soup_element(heading_md)
130
+ heading.replace_with(*new_element)
131
+
132
+ # Process paragraphs
133
+ for p in soup.find_all('p'):
134
+ text = p.get_text().strip()
135
+ if text:
136
+ new_element = self._create_soup_element("\n\n" + text + "\n\n")
137
+ p.replace_with(*new_element)
138
+
139
+ # Process unordered lists
140
+ for ul in soup.find_all('ul'):
141
+ items = []
142
+ for li in ul.find_all('li', recursive=False):
143
+ items.append("* " + li.get_text().strip())
144
+ new_element = self._create_soup_element("\n\n" + "\n".join(items) + "\n\n")
145
+ ul.replace_with(*new_element)
146
+
147
+ # Process ordered lists
148
+ for ol in soup.find_all('ol'):
149
+ items = []
150
+ for i, li in enumerate(ol.find_all('li', recursive=False), 1):
151
+ items.append(str(i) + ". " + li.get_text().strip())
152
+ new_element = self._create_soup_element("\n\n" + "\n".join(items) + "\n\n")
153
+ ol.replace_with(*new_element)
154
+
155
+ # Process links (first pass)
156
+ for a in soup.find_all('a', href=True):
157
+ try:
158
+ href = a['href']
159
+ text = a.get_text().strip()
160
+ if text and href:
161
+ # Convert relative URLs to absolute
162
+ if href.startswith('/') and not href.startswith('//'):
163
+ href = urljoin(base_url, href)
164
+ link_md = "[" + text + "](" + href + ")"
165
+ new_element = self._create_soup_element(link_md)
166
+ a.replace_with(*new_element)
167
+ except (KeyError, AttributeError):
168
+ continue
169
+
170
+ # Process images
171
+ for img in soup.find_all('img', src=True):
172
+ try:
173
+ src = img['src']
174
+ alt = img.get('alt', 'Image').strip()
175
+ # Convert relative URLs to absolute
176
+ if src.startswith('/') and not src.startswith('//'):
177
+ src = urljoin(base_url, src)
178
+ img_md = "![" + alt + "](" + src + ")"
179
+ new_element = self._create_soup_element(img_md)
180
+ img.replace_with(*new_element)
181
+ except (KeyError, AttributeError, UnboundLocalError):
182
+ continue
183
+
184
+ # Process code blocks
185
+ for pre in soup.find_all('pre'):
186
+ code = pre.get_text().strip()
187
+ pre_md = "\n\n```\n" + code + "\n```\n\n"
188
+ new_element = self._create_soup_element(pre_md)
189
+ pre.replace_with(*new_element)
190
+
191
+ # Process inline code
192
+ for code in soup.find_all('code'):
193
+ text = code.get_text().strip()
194
+ code_md = "`" + text + "`"
195
+ new_element = self._create_soup_element(code_md)
196
+ code.replace_with(*new_element)
197
+
198
+ # Process line breaks
199
+ for br in soup.find_all('br'):
200
+ new_element = self._create_soup_element('\n')
201
+ br.replace_with(*new_element)
202
+
203
+ # Get the full text
204
+ markdown_text = soup.get_text()
205
+
206
+ # Clean up extra whitespace and line breaks
207
+ markdown_text = re.sub(r'\n{3,}', '\n\n', markdown_text)
208
+ markdown_text = re.sub(r'\s{2,}', ' ', markdown_text)
209
+
210
+ # Process links again (for any that might have been missed)
211
+ link_pattern = r'\[([^\]]+)\]\(([^)]+)\)'
212
+ all_links = re.findall(link_pattern, markdown_text)
213
+
214
+ # Add a section with all links at the end
215
+ if all_links:
216
+ link_section = ["", "## Links", ""]
217
+ seen_links = set()
218
+ for text, href in all_links:
219
+ link_entry = "[" + text + "](" + href + ")"
220
+ if link_entry not in seen_links:
221
+ link_section.append(link_entry)
222
+ seen_links.add(link_entry)
223
+
224
+ markdown_text += "\n\n" + "\n".join(link_section)
225
+
226
+ return markdown_text.strip()
@@ -13,20 +13,21 @@ from jarvis.jarvis_tools.base import Tool
13
13
  from jarvis.jarvis_utils.config import get_max_token_count
14
14
  from jarvis.jarvis_utils.embedding import get_context_token_count
15
15
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
16
+ from jarvis.jarvis_utils.utils import ct, ot, init_env
16
17
 
17
18
 
18
19
 
19
- tool_call_help = """
20
+ tool_call_help = f"""
20
21
  # 🛠️ 工具使用系统
21
22
  您正在使用一个需要精确格式和严格规则的工具执行系统。
22
23
 
23
24
  # 📋 工具调用格式
24
- <TOOL_CALL>
25
+ {ot("TOOL_CALL")}
25
26
  name: 工具名称
26
27
  arguments:
27
28
  param1: 值1
28
29
  param2: 值2
29
- </TOOL_CALL>
30
+ {ct("TOOL_CALL")}
30
31
 
31
32
  # ❗ 关键规则
32
33
  1. 每次只使用一个工具
@@ -53,12 +54,12 @@ arguments:
53
54
  # 📝 字符串参数格式
54
55
  始终使用 | 语法表示字符串参数:
55
56
 
56
- <TOOL_CALL>
57
+ {ot("TOOL_CALL")}
57
58
  name: execute_shell
58
59
  arguments:
59
60
  command: |
60
61
  git status --porcelain
61
- </TOOL_CALL>
62
+ {ct("TOOL_CALL")}
62
63
 
63
64
  # 💡 最佳实践
64
65
  - 准备好后立即开始执行
@@ -233,7 +234,7 @@ class ToolRegistry(OutputHandler):
233
234
  Exception: 如果工具调用缺少必要字段
234
235
  """
235
236
  # 将内容拆分为行
236
- data = re.findall(r'<TOOL_CALL>(.*?)</TOOL_CALL>', content, re.DOTALL)
237
+ data = re.findall(ot("TOOL_CALL")+r'(.*?)'+ct("TOOL_CALL"), content, re.DOTALL)
237
238
  ret = []
238
239
  for item in data:
239
240
  try:
@@ -270,18 +271,18 @@ class ToolRegistry(OutputHandler):
270
271
  name = tool_call["name"]
271
272
  args = tool_call["arguments"]
272
273
 
273
- tool_call_help = """
274
+ tool_call_help = f"""
274
275
  # 🛠️ 工具使用系统
275
276
  您正在使用一个需要精确格式和严格规则的工具执行系统。
276
277
 
277
278
  # 📋 工具调用格式
278
279
 
279
- <TOOL_CALL>
280
+ {ot("TOOL_CALL")}
280
281
  name: 工具名称
281
282
  arguments:
282
283
  param1: 值1
283
284
  param2: 值2
284
- </TOOL_CALL>
285
+ {ct("TOOL_CALL")}
285
286
 
286
287
  # ❗ 关键规则
287
288
  1. 每次只使用一个工具
@@ -308,12 +309,12 @@ arguments:
308
309
  # 📝 字符串参数格式
309
310
  始终使用 | 语法表示字符串参数:
310
311
 
311
- <TOOL_CALL>
312
+ {ot("TOOL_CALL")}
312
313
  name: execute_shell
313
314
  arguments:
314
315
  command: |
315
316
  git status --porcelain
316
- </TOOL_CALL>
317
+ {ct("TOOL_CALL")}
317
318
 
318
319
  # 💡 最佳实践
319
320
  - 准备好后立即开始执行
@@ -394,3 +395,123 @@ arguments:
394
395
  except Exception as e:
395
396
  PrettyOutput.print(f"工具执行失败:{str(e)}", OutputType.ERROR)
396
397
  return f"工具调用失败: {str(e)}"
398
+
399
+
400
+ def main():
401
+ """命令行工具入口,提供工具列表查看和工具调用功能"""
402
+ import argparse
403
+ import json
404
+
405
+ init_env()
406
+
407
+ parser = argparse.ArgumentParser(description='Jarvis 工具系统命令行界面')
408
+ subparsers = parser.add_subparsers(dest='command', help='命令')
409
+
410
+ # 列出工具子命令
411
+ list_parser = subparsers.add_parser('list', help='列出所有可用工具')
412
+ list_parser.add_argument('--json', action='store_true', help='以JSON格式输出')
413
+ list_parser.add_argument('--detailed', action='store_true', help='显示详细信息')
414
+
415
+ # 调用工具子命令
416
+ call_parser = subparsers.add_parser('call', help='调用指定工具')
417
+ call_parser.add_argument('tool_name', help='要调用的工具名称')
418
+ call_parser.add_argument('--args', type=str, help='工具参数 (JSON格式)')
419
+ call_parser.add_argument('--args-file', type=str, help='从文件加载工具参数 (JSON格式)')
420
+
421
+ args = parser.parse_args()
422
+
423
+ # 初始化工具注册表
424
+ registry = ToolRegistry()
425
+
426
+ if args.command == 'list':
427
+ tools = registry.get_all_tools()
428
+
429
+ if args.json:
430
+ if args.detailed:
431
+ print(json.dumps(tools, indent=2, ensure_ascii=False))
432
+ else:
433
+ simple_tools = [{"name": t["name"], "description": t["description"]} for t in tools]
434
+ print(json.dumps(simple_tools, indent=2, ensure_ascii=False))
435
+ else:
436
+ PrettyOutput.section("可用工具列表", OutputType.SYSTEM)
437
+ for tool in tools:
438
+ print(f"\n✅ {tool['name']}")
439
+ print(f" 描述: {tool['description']}")
440
+ if args.detailed:
441
+ print(f" 参数:")
442
+ params = tool['parameters'].get('properties', {})
443
+ required = tool['parameters'].get('required', [])
444
+ for param_name, param_info in params.items():
445
+ req_mark = "*" if param_name in required else ""
446
+ desc = param_info.get('description', '无描述')
447
+ print(f" - {param_name}{req_mark}: {desc}")
448
+
449
+ elif args.command == 'call':
450
+ tool_name = args.tool_name
451
+ tool = registry.get_tool(tool_name)
452
+
453
+ if not tool:
454
+ PrettyOutput.print(f"错误: 工具 '{tool_name}' 不存在", OutputType.ERROR)
455
+ available_tools = ", ".join([t["name"] for t in registry.get_all_tools()])
456
+ print(f"可用工具: {available_tools}")
457
+ return 1
458
+
459
+ # 获取参数
460
+ tool_args = {}
461
+ if args.args:
462
+ try:
463
+ tool_args = json.loads(args.args)
464
+ except json.JSONDecodeError:
465
+ PrettyOutput.print("错误: 参数必须是有效的JSON格式", OutputType.ERROR)
466
+ return 1
467
+
468
+ elif args.args_file:
469
+ try:
470
+ with open(args.args_file, 'r', encoding='utf-8') as f:
471
+ tool_args = json.load(f)
472
+ except (json.JSONDecodeError, FileNotFoundError) as e:
473
+ PrettyOutput.print(f"错误: 无法从文件加载参数: {str(e)}", OutputType.ERROR)
474
+ return 1
475
+
476
+ # 检查必需参数
477
+ required_params = tool.parameters.get('required', [])
478
+ missing_params = [p for p in required_params if p not in tool_args]
479
+
480
+ if missing_params:
481
+ PrettyOutput.print(f"错误: 缺少必需参数: {', '.join(missing_params)}", OutputType.ERROR)
482
+ print("\n参数说明:")
483
+ params = tool.parameters.get('properties', {})
484
+ for param_name in required_params:
485
+ param_info = params.get(param_name, {})
486
+ desc = param_info.get('description', '无描述')
487
+ print(f" - {param_name}: {desc}")
488
+ return 1
489
+
490
+ # 执行工具
491
+ with yaspin(text=f"正在执行工具 {tool_name}...").dots12:
492
+ result = registry.execute_tool(tool_name, tool_args)
493
+
494
+ # 显示结果
495
+ if result["success"]:
496
+ PrettyOutput.section(f"工具 {tool_name} 执行成功", OutputType.SUCCESS)
497
+ else:
498
+ PrettyOutput.section(f"工具 {tool_name} 执行失败", OutputType.ERROR)
499
+
500
+ if result.get("stdout"):
501
+ print("\n输出:")
502
+ print(result["stdout"])
503
+
504
+ if result.get("stderr"):
505
+ PrettyOutput.print("\n错误:", OutputType.ERROR)
506
+ print(result["stderr"])
507
+
508
+ return 0 if result["success"] else 1
509
+
510
+ else:
511
+ parser.print_help()
512
+
513
+ return 0
514
+
515
+
516
+ if __name__ == "__main__":
517
+ sys.exit(main())