jarvis-ai-assistant 0.1.222__py3-none-any.whl → 0.7.0__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.
Files changed (162) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +1143 -245
  3. jarvis/jarvis_agent/agent_manager.py +97 -0
  4. jarvis/jarvis_agent/builtin_input_handler.py +12 -10
  5. jarvis/jarvis_agent/config_editor.py +57 -0
  6. jarvis/jarvis_agent/edit_file_handler.py +392 -99
  7. jarvis/jarvis_agent/event_bus.py +48 -0
  8. jarvis/jarvis_agent/events.py +157 -0
  9. jarvis/jarvis_agent/file_context_handler.py +79 -0
  10. jarvis/jarvis_agent/file_methodology_manager.py +117 -0
  11. jarvis/jarvis_agent/jarvis.py +1117 -147
  12. jarvis/jarvis_agent/main.py +78 -34
  13. jarvis/jarvis_agent/memory_manager.py +195 -0
  14. jarvis/jarvis_agent/methodology_share_manager.py +174 -0
  15. jarvis/jarvis_agent/prompt_manager.py +82 -0
  16. jarvis/jarvis_agent/prompts.py +46 -9
  17. jarvis/jarvis_agent/protocols.py +4 -1
  18. jarvis/jarvis_agent/rewrite_file_handler.py +141 -0
  19. jarvis/jarvis_agent/run_loop.py +146 -0
  20. jarvis/jarvis_agent/session_manager.py +9 -9
  21. jarvis/jarvis_agent/share_manager.py +228 -0
  22. jarvis/jarvis_agent/shell_input_handler.py +23 -3
  23. jarvis/jarvis_agent/stdio_redirect.py +295 -0
  24. jarvis/jarvis_agent/task_analyzer.py +212 -0
  25. jarvis/jarvis_agent/task_manager.py +154 -0
  26. jarvis/jarvis_agent/task_planner.py +496 -0
  27. jarvis/jarvis_agent/tool_executor.py +8 -4
  28. jarvis/jarvis_agent/tool_share_manager.py +139 -0
  29. jarvis/jarvis_agent/user_interaction.py +42 -0
  30. jarvis/jarvis_agent/utils.py +54 -0
  31. jarvis/jarvis_agent/web_bridge.py +189 -0
  32. jarvis/jarvis_agent/web_output_sink.py +53 -0
  33. jarvis/jarvis_agent/web_server.py +751 -0
  34. jarvis/jarvis_c2rust/__init__.py +26 -0
  35. jarvis/jarvis_c2rust/cli.py +613 -0
  36. jarvis/jarvis_c2rust/collector.py +258 -0
  37. jarvis/jarvis_c2rust/library_replacer.py +1122 -0
  38. jarvis/jarvis_c2rust/llm_module_agent.py +1300 -0
  39. jarvis/jarvis_c2rust/optimizer.py +960 -0
  40. jarvis/jarvis_c2rust/scanner.py +1681 -0
  41. jarvis/jarvis_c2rust/transpiler.py +2325 -0
  42. jarvis/jarvis_code_agent/build_validation_config.py +133 -0
  43. jarvis/jarvis_code_agent/code_agent.py +1605 -178
  44. jarvis/jarvis_code_agent/code_analyzer/__init__.py +62 -0
  45. jarvis/jarvis_code_agent/code_analyzer/base_language.py +74 -0
  46. jarvis/jarvis_code_agent/code_analyzer/build_validator/__init__.py +44 -0
  47. jarvis/jarvis_code_agent/code_analyzer/build_validator/base.py +102 -0
  48. jarvis/jarvis_code_agent/code_analyzer/build_validator/cmake.py +59 -0
  49. jarvis/jarvis_code_agent/code_analyzer/build_validator/detector.py +125 -0
  50. jarvis/jarvis_code_agent/code_analyzer/build_validator/fallback.py +69 -0
  51. jarvis/jarvis_code_agent/code_analyzer/build_validator/go.py +38 -0
  52. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_gradle.py +44 -0
  53. jarvis/jarvis_code_agent/code_analyzer/build_validator/java_maven.py +38 -0
  54. jarvis/jarvis_code_agent/code_analyzer/build_validator/makefile.py +50 -0
  55. jarvis/jarvis_code_agent/code_analyzer/build_validator/nodejs.py +93 -0
  56. jarvis/jarvis_code_agent/code_analyzer/build_validator/python.py +129 -0
  57. jarvis/jarvis_code_agent/code_analyzer/build_validator/rust.py +54 -0
  58. jarvis/jarvis_code_agent/code_analyzer/build_validator/validator.py +154 -0
  59. jarvis/jarvis_code_agent/code_analyzer/build_validator.py +43 -0
  60. jarvis/jarvis_code_agent/code_analyzer/context_manager.py +363 -0
  61. jarvis/jarvis_code_agent/code_analyzer/context_recommender.py +18 -0
  62. jarvis/jarvis_code_agent/code_analyzer/dependency_analyzer.py +132 -0
  63. jarvis/jarvis_code_agent/code_analyzer/file_ignore.py +330 -0
  64. jarvis/jarvis_code_agent/code_analyzer/impact_analyzer.py +781 -0
  65. jarvis/jarvis_code_agent/code_analyzer/language_registry.py +185 -0
  66. jarvis/jarvis_code_agent/code_analyzer/language_support.py +89 -0
  67. jarvis/jarvis_code_agent/code_analyzer/languages/__init__.py +31 -0
  68. jarvis/jarvis_code_agent/code_analyzer/languages/c_cpp_language.py +231 -0
  69. jarvis/jarvis_code_agent/code_analyzer/languages/go_language.py +183 -0
  70. jarvis/jarvis_code_agent/code_analyzer/languages/python_language.py +219 -0
  71. jarvis/jarvis_code_agent/code_analyzer/languages/rust_language.py +209 -0
  72. jarvis/jarvis_code_agent/code_analyzer/llm_context_recommender.py +451 -0
  73. jarvis/jarvis_code_agent/code_analyzer/symbol_extractor.py +77 -0
  74. jarvis/jarvis_code_agent/code_analyzer/tree_sitter_extractor.py +48 -0
  75. jarvis/jarvis_code_agent/lint.py +275 -13
  76. jarvis/jarvis_code_agent/utils.py +142 -0
  77. jarvis/jarvis_code_analysis/checklists/loader.py +20 -6
  78. jarvis/jarvis_code_analysis/code_review.py +583 -548
  79. jarvis/jarvis_data/config_schema.json +339 -28
  80. jarvis/jarvis_git_squash/main.py +22 -13
  81. jarvis/jarvis_git_utils/git_commiter.py +171 -55
  82. jarvis/jarvis_mcp/sse_mcp_client.py +22 -15
  83. jarvis/jarvis_mcp/stdio_mcp_client.py +4 -4
  84. jarvis/jarvis_mcp/streamable_mcp_client.py +36 -16
  85. jarvis/jarvis_memory_organizer/memory_organizer.py +753 -0
  86. jarvis/jarvis_methodology/main.py +48 -63
  87. jarvis/jarvis_multi_agent/__init__.py +302 -43
  88. jarvis/jarvis_multi_agent/main.py +70 -24
  89. jarvis/jarvis_platform/ai8.py +40 -23
  90. jarvis/jarvis_platform/base.py +210 -49
  91. jarvis/jarvis_platform/human.py +11 -1
  92. jarvis/jarvis_platform/kimi.py +82 -76
  93. jarvis/jarvis_platform/openai.py +73 -1
  94. jarvis/jarvis_platform/registry.py +8 -15
  95. jarvis/jarvis_platform/tongyi.py +115 -101
  96. jarvis/jarvis_platform/yuanbao.py +89 -63
  97. jarvis/jarvis_platform_manager/main.py +194 -132
  98. jarvis/jarvis_platform_manager/service.py +122 -86
  99. jarvis/jarvis_rag/cli.py +156 -53
  100. jarvis/jarvis_rag/embedding_manager.py +155 -12
  101. jarvis/jarvis_rag/llm_interface.py +10 -13
  102. jarvis/jarvis_rag/query_rewriter.py +63 -12
  103. jarvis/jarvis_rag/rag_pipeline.py +222 -40
  104. jarvis/jarvis_rag/reranker.py +26 -3
  105. jarvis/jarvis_rag/retriever.py +270 -14
  106. jarvis/jarvis_sec/__init__.py +3605 -0
  107. jarvis/jarvis_sec/checkers/__init__.py +32 -0
  108. jarvis/jarvis_sec/checkers/c_checker.py +2680 -0
  109. jarvis/jarvis_sec/checkers/rust_checker.py +1108 -0
  110. jarvis/jarvis_sec/cli.py +116 -0
  111. jarvis/jarvis_sec/report.py +257 -0
  112. jarvis/jarvis_sec/status.py +264 -0
  113. jarvis/jarvis_sec/types.py +20 -0
  114. jarvis/jarvis_sec/workflow.py +219 -0
  115. jarvis/jarvis_smart_shell/main.py +405 -137
  116. jarvis/jarvis_stats/__init__.py +13 -0
  117. jarvis/jarvis_stats/cli.py +387 -0
  118. jarvis/jarvis_stats/stats.py +711 -0
  119. jarvis/jarvis_stats/storage.py +612 -0
  120. jarvis/jarvis_stats/visualizer.py +282 -0
  121. jarvis/jarvis_tools/ask_user.py +1 -0
  122. jarvis/jarvis_tools/base.py +18 -2
  123. jarvis/jarvis_tools/clear_memory.py +239 -0
  124. jarvis/jarvis_tools/cli/main.py +220 -144
  125. jarvis/jarvis_tools/execute_script.py +52 -12
  126. jarvis/jarvis_tools/file_analyzer.py +17 -12
  127. jarvis/jarvis_tools/generate_new_tool.py +46 -24
  128. jarvis/jarvis_tools/read_code.py +277 -18
  129. jarvis/jarvis_tools/read_symbols.py +141 -0
  130. jarvis/jarvis_tools/read_webpage.py +86 -13
  131. jarvis/jarvis_tools/registry.py +294 -90
  132. jarvis/jarvis_tools/retrieve_memory.py +227 -0
  133. jarvis/jarvis_tools/save_memory.py +194 -0
  134. jarvis/jarvis_tools/search_web.py +62 -28
  135. jarvis/jarvis_tools/sub_agent.py +205 -0
  136. jarvis/jarvis_tools/sub_code_agent.py +217 -0
  137. jarvis/jarvis_tools/virtual_tty.py +330 -62
  138. jarvis/jarvis_utils/builtin_replace_map.py +4 -5
  139. jarvis/jarvis_utils/clipboard.py +90 -0
  140. jarvis/jarvis_utils/config.py +607 -50
  141. jarvis/jarvis_utils/embedding.py +3 -0
  142. jarvis/jarvis_utils/fzf.py +57 -0
  143. jarvis/jarvis_utils/git_utils.py +251 -29
  144. jarvis/jarvis_utils/globals.py +174 -17
  145. jarvis/jarvis_utils/http.py +58 -79
  146. jarvis/jarvis_utils/input.py +899 -153
  147. jarvis/jarvis_utils/methodology.py +210 -83
  148. jarvis/jarvis_utils/output.py +220 -137
  149. jarvis/jarvis_utils/utils.py +1906 -135
  150. jarvis_ai_assistant-0.7.0.dist-info/METADATA +465 -0
  151. jarvis_ai_assistant-0.7.0.dist-info/RECORD +192 -0
  152. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/entry_points.txt +8 -2
  153. jarvis/jarvis_git_details/main.py +0 -265
  154. jarvis/jarvis_platform/oyi.py +0 -357
  155. jarvis/jarvis_tools/edit_file.py +0 -255
  156. jarvis/jarvis_tools/rewrite_file.py +0 -195
  157. jarvis_ai_assistant-0.1.222.dist-info/METADATA +0 -767
  158. jarvis_ai_assistant-0.1.222.dist-info/RECORD +0 -110
  159. /jarvis/{jarvis_git_details → jarvis_memory_organizer}/__init__.py +0 -0
  160. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/WHEEL +0 -0
  161. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/licenses/LICENSE +0 -0
  162. {jarvis_ai_assistant-0.1.222.dist-info → jarvis_ai_assistant-0.7.0.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,11 @@
1
1
  # -*- coding: utf-8 -*-
2
- import argparse
3
2
  import os
4
3
  import re
5
4
  import subprocess
6
- import sys
7
5
  import tempfile
8
6
  from typing import Any, Dict, Optional
9
7
 
8
+ import typer
10
9
  import yaml # type: ignore
11
10
 
12
11
  from jarvis.jarvis_platform.registry import PlatformRegistry
@@ -16,10 +15,13 @@ from jarvis.jarvis_utils.git_utils import (
16
15
  find_git_root_and_cd,
17
16
  has_uncommitted_changes,
18
17
  )
18
+ from jarvis.jarvis_utils.globals import get_agent, current_agent_name
19
19
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
20
20
  from jarvis.jarvis_utils.tag import ct, ot
21
21
  from jarvis.jarvis_utils.utils import init_env, is_context_overflow
22
22
 
23
+ app = typer.Typer(help="Git提交工具")
24
+
23
25
 
24
26
  class GitCommitTool:
25
27
  name = "git_commit_agent"
@@ -79,11 +81,10 @@ class GitCommitTool:
79
81
 
80
82
  def _stage_changes(self) -> None:
81
83
  """Stage all changes for commit"""
82
- print("📁 正在添加文件到提交...")
84
+
83
85
  subprocess.Popen(
84
86
  ["git", "add", "."], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL
85
87
  ).wait()
86
- print("✅ 添加文件到提交")
87
88
 
88
89
  def execute(self, args: Dict) -> Dict[str, Any]:
89
90
  """Execute automatic commit process with support for multi-line messages and special characters"""
@@ -104,17 +105,16 @@ class GitCommitTool:
104
105
  if not has_uncommitted_changes():
105
106
  return {"success": True, "stdout": "No changes to commit", "stderr": ""}
106
107
 
107
- print("🚀 正在初始化提交流程...")
108
108
  self._stage_changes()
109
109
 
110
110
  # 获取差异
111
- print("📊 正在获取代码差异...")
111
+
112
112
  # 获取文件列表
113
113
  files_cmd = ["git", "diff", "--cached", "--name-only"]
114
114
  process = subprocess.Popen(
115
- files_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE
115
+ files_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
116
116
  )
117
- files_output = process.communicate()[0].decode()
117
+ files_output = process.communicate()[0]
118
118
  files = [f.strip() for f in files_output.split("\n") if f.strip()]
119
119
  file_count = len(files)
120
120
 
@@ -123,20 +123,21 @@ class GitCommitTool:
123
123
  ["git", "diff", "--cached", "--exit-code"],
124
124
  stdout=subprocess.PIPE,
125
125
  stderr=subprocess.PIPE,
126
+ text=True,
126
127
  )
127
- diff = process.communicate()[0].decode(errors="ignore")
128
- print(f"✅ 获取差异 ({file_count} 个文件)")
128
+ diff = process.communicate()[0]
129
+
129
130
  try:
130
131
  temp_diff_file_path = None
131
132
  # 生成提交信息
132
- print("正在生成提交消息...")
133
+ PrettyOutput.print("正在生成提交消息...", OutputType.INFO)
133
134
 
134
135
  # 准备提示信息
135
136
  custom_prompt = get_git_commit_prompt()
136
137
  base_prompt = (
137
138
  custom_prompt
138
139
  if custom_prompt
139
- else f"""根据代码差异生成提交信息:
140
+ else """根据代码差异生成提交信息:
140
141
  提交信息应使用中文书写
141
142
  # 格式模板
142
143
  必须使用以下格式:
@@ -161,22 +162,111 @@ commit信息
161
162
  {ct("COMMIT_MESSAGE")}
162
163
  """
163
164
 
164
- # 获取模型并尝试上传文件
165
- platform = PlatformRegistry().get_normal_platform()
165
+ # 优先从调用方传入的 agent 获取平台与模型
166
+ agent_from_args = args.get("agent")
167
+
168
+ # Get model_group from args
169
+ model_group = args.get("model_group")
170
+
171
+ # Get platform and model based on model_group (thinking mode removed)
172
+ from jarvis.jarvis_utils.config import (
173
+ get_normal_platform_name,
174
+ get_normal_model_name,
175
+ )
176
+
177
+ platform_name = None
178
+ model_name = None
179
+
180
+ # 优先根据 model_group 获取(确保配置一致性)
181
+ # 如果 model_group 存在,强制使用它来解析,避免使用 agent.model 中可能不一致的值
182
+ if model_group:
183
+ platform_name = get_normal_platform_name(model_group)
184
+ model_name = get_normal_model_name(model_group)
185
+ else:
186
+ # 如果没有提供 model_group,尝试从传入的 agent 获取
187
+ if (
188
+ agent_from_args
189
+ and hasattr(agent_from_args, "model")
190
+ and getattr(agent_from_args, "model", None)
191
+ ):
192
+ try:
193
+ platform_name = agent_from_args.model.platform_name()
194
+ model_name = agent_from_args.model.name()
195
+ if hasattr(agent_from_args.model, "model_group"):
196
+ model_group = agent_from_args.model.model_group
197
+ except Exception:
198
+ # 安全回退到后续逻辑
199
+ platform_name = None
200
+ model_name = None
201
+
202
+ # 如果仍未获取到,使用配置文件中的默认值(传入 None 会读取默认配置)
203
+ if not platform_name:
204
+ platform_name = get_normal_platform_name(None)
205
+ if not model_name:
206
+ model_name = get_normal_model_name(None)
207
+
208
+ # 最后的回退:尝试从全局 agent 获取(仅当仍未获取到时)
209
+ if not platform_name:
210
+ agent = get_agent(current_agent_name)
211
+ if (
212
+ agent
213
+ and hasattr(agent, "model")
214
+ and getattr(agent, "model", None)
215
+ ):
216
+ try:
217
+ platform_name = agent.model.platform_name()
218
+ model_name = agent.model.name()
219
+ if not model_group and hasattr(agent.model, "model_group"):
220
+ model_group = agent.model.model_group
221
+ except Exception:
222
+ # 如果全局 agent 也无法获取,使用配置文件默认值
223
+ if not platform_name:
224
+ platform_name = get_normal_platform_name(None)
225
+ if not model_name:
226
+ model_name = get_normal_model_name(None)
227
+
228
+ # Create a new platform instance
229
+ if platform_name:
230
+ platform = PlatformRegistry().create_platform(platform_name)
231
+ if platform and model_name:
232
+ platform.set_model_name(model_name)
233
+ if platform and model_group:
234
+ try:
235
+ platform.set_model_group(model_group)
236
+ except Exception:
237
+ # 兼容早期实现
238
+ platform.model_group = model_group # type: ignore
239
+ else:
240
+ platform = PlatformRegistry().get_normal_platform()
241
+
242
+ # 跳过模型可用性校验:
243
+ # 为避免某些平台/代理不支持 get_model_list 接口导致的噪音日志(如 404),
244
+ # 这里默认不调用 platform.get_model_list() 进行模型可用性校验。
245
+ # 如果未来需要恢复校验,可参考被移除的逻辑。
246
+ # no-op
247
+
248
+ # Ensure platform is not None
249
+ if not platform:
250
+ return {
251
+ "success": False,
252
+ "stdout": "",
253
+ "stderr": "错误:无法创建平台实例",
254
+ }
255
+
166
256
  upload_success = False
167
257
 
168
258
  # Check if content is too large
169
- is_large_content = is_context_overflow(diff)
259
+ is_large_content = is_context_overflow(diff, model_group)
170
260
 
171
261
  if is_large_content:
172
262
  if not platform.support_upload_files():
173
- print("差异文件太大,无法处理")
263
+ PrettyOutput.print("差异文件太大,无法处理", OutputType.ERROR)
174
264
  return {
175
265
  "success": False,
176
266
  "stdout": "",
177
267
  "stderr": "错误:差异文件太大,无法处理",
178
268
  }
179
- print("📤 正在上传代码差异文件...")
269
+
180
270
  # 创建临时文件并写入差异内容
181
271
  with tempfile.NamedTemporaryFile(
182
272
  mode="w", suffix=".diff", delete=False
@@ -184,12 +274,12 @@ commit信息
184
274
  temp_diff_file_path = temp_diff_file.name
185
275
  temp_diff_file.write(diff)
186
276
  temp_diff_file.flush()
187
- print(f"✅ 差异内容已写入临时文件")
277
+
188
278
  upload_success = platform.upload_files([temp_diff_file_path])
189
279
  if upload_success:
190
- print("✅ 成功上传代码差异文件")
280
+ pass
191
281
  else:
192
- print("上传代码差异文件失败")
282
+ PrettyOutput.print("上传代码差异文件失败", OutputType.ERROR)
193
283
  return {
194
284
  "success": False,
195
285
  "stdout": "",
@@ -198,7 +288,6 @@ commit信息
198
288
  # 根据上传状态准备完整的提示
199
289
  if is_large_content:
200
290
  # 尝试生成提交信息
201
- print("✨ 正在生成提交消息...")
202
291
  # 使用上传的文件
203
292
  prompt = (
204
293
  base_prompt
@@ -245,24 +334,42 @@ commit信息
245
334
  {ct("COMMIT_MESSAGE")}
246
335
  """
247
336
  commit_message = platform.chat_until_success(prompt)
248
- print("✅ 生成提交消息")
249
337
 
250
338
  # 执行提交
251
- print("⚙️ 正在准备提交...")
252
- with tempfile.NamedTemporaryFile(mode="w", delete=True) as tmp_file:
339
+
340
+ # Windows 兼容性:使用 delete=False 避免权限错误
341
+ tmp_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
342
+ tmp_file_path = tmp_file.name
343
+ try:
253
344
  tmp_file.write(commit_message)
254
- tmp_file.flush()
255
- print("💾 正在执行提交...")
256
- commit_cmd = ["git", "commit", "-F", tmp_file.name]
257
- subprocess.Popen(
345
+ tmp_file.close() # Windows 需要先关闭文件才能被其他进程读取
346
+
347
+ commit_cmd = ["git", "commit", "-F", tmp_file_path]
348
+ process = subprocess.Popen(
258
349
  commit_cmd,
259
- stdout=subprocess.DEVNULL,
260
- stderr=subprocess.DEVNULL,
261
- ).wait()
262
- print("✅ 提交")
350
+ stdout=subprocess.PIPE,
351
+ stderr=subprocess.PIPE,
352
+ text=True,
353
+ )
354
+ stdout, stderr = process.communicate()
355
+
356
+ if process.returncode != 0:
357
+ # 如果提交失败,重置暂存区
358
+ subprocess.run(["git", "reset", "HEAD"], check=False)
359
+ error_msg = (
360
+ stderr.strip() if stderr else "Unknown git commit error"
361
+ )
362
+ raise Exception(f"Git commit failed: {error_msg}")
363
+
364
+ finally:
365
+ # 手动删除临时文件
366
+ try:
367
+ os.unlink(tmp_file_path)
368
+ except Exception:
369
+ pass
263
370
 
264
371
  commit_hash = self._get_last_commit_hash()
265
- print("✅ 完成提交")
372
+
266
373
  finally:
267
374
  # 清理临时差异文件
268
375
  if temp_diff_file_path is not None and os.path.exists(
@@ -271,7 +378,9 @@ commit信息
271
378
  try:
272
379
  os.unlink(temp_diff_file_path)
273
380
  except Exception as e:
274
- print(f"⚠️ 无法删除临时文件: {str(e)}")
381
+ PrettyOutput.print(
382
+ f"无法删除临时文件: {str(e)}", OutputType.WARNING
383
+ )
275
384
 
276
385
  PrettyOutput.print(
277
386
  f"提交哈希: {commit_hash}\n提交消息: {commit_message}",
@@ -298,34 +407,41 @@ commit信息
298
407
  os.chdir(original_dir)
299
408
 
300
409
 
301
- def main():
302
- init_env("欢迎使用 Jarvis-GitCommitTool,您的Git提交助手已准备就绪!")
303
- parser = argparse.ArgumentParser(description="Git commit tool")
304
- parser.add_argument(
305
- "--root-dir", type=str, default=".", help="Root directory of the Git repository"
306
- )
307
- parser.add_argument(
410
+ @app.command()
411
+ def cli(
412
+ root_dir: str = typer.Option(".", "--root-dir", help="Git仓库的根目录路径"),
413
+ prefix: str = typer.Option(
414
+ "",
308
415
  "--prefix",
309
- type=str,
310
- default="",
311
- help="Prefix to prepend to commit message (separated by space)",
312
- )
313
- parser.add_argument(
416
+ help="提交信息前缀(用空格分隔)",
417
+ ),
418
+ suffix: str = typer.Option(
419
+ "",
314
420
  "--suffix",
315
- type=str,
316
- default="",
317
- help="Suffix to append to commit message (separated by newline)",
318
- )
319
- args = parser.parse_args()
421
+ help="提交信息后缀(用换行分隔)",
422
+ ),
423
+
424
+ model_group: Optional[str] = typer.Option(
425
+ None, "-g", "--llm-group", help="使用的模型组,覆盖配置文件中的设置"
426
+ ),
427
+ ):
428
+ init_env("欢迎使用 Jarvis-GitCommitTool,您的Git提交助手已准备就绪!")
320
429
  tool = GitCommitTool()
321
430
  tool.execute(
322
431
  {
323
- "root_dir": args.root_dir,
324
- "prefix": args.prefix if hasattr(args, "prefix") else "",
325
- "suffix": args.suffix if hasattr(args, "suffix") else "",
432
+ "root_dir": root_dir,
433
+ "prefix": prefix,
434
+ "suffix": suffix,
435
+
436
+ "model_group": model_group,
326
437
  }
327
438
  )
328
439
 
329
440
 
441
+ def main():
442
+ """Application entry point"""
443
+ app()
444
+
445
+
330
446
  if __name__ == "__main__":
331
- sys.exit(main())
447
+ main()
@@ -2,10 +2,10 @@
2
2
  import json
3
3
  import threading
4
4
  import time
5
- from typing import Any, Callable, Dict, List
5
+ from typing import Any, Callable, Dict, List, Optional
6
6
  from urllib.parse import parse_qs, urlencode, urljoin
7
7
 
8
- import requests
8
+ import requests # type: ignore[import-untyped]
9
9
 
10
10
  from jarvis.jarvis_mcp import McpClient
11
11
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -46,13 +46,17 @@ class SSEMcpClient(McpClient):
46
46
  self.session.headers.update(extra_headers)
47
47
 
48
48
  # SSE相关属性
49
- self.sse_response = None
50
- self.sse_thread = None
51
- self.messages_endpoint = None
52
- self.session_id = None # 从SSE连接获取的会话ID
53
- self.pending_requests = {} # 存储等待响应的请求 {id: Event}
54
- self.request_results = {} # 存储请求结果 {id: result}
55
- self.notification_handlers = {}
49
+ self.sse_response: Optional[requests.Response] = None
50
+ self.sse_thread: Optional[threading.Thread] = None
51
+ self.messages_endpoint: Optional[str] = None
52
+ self.session_id: Optional[str] = None
53
+ self.pending_requests: Dict[str, threading.Event] = (
54
+ {}
55
+ ) # 存储等待响应的请求 {id: Event}
56
+ self.request_results: Dict[str, Dict[str, Any]] = (
57
+ {}
58
+ ) # 存储请求结果 {id: result}
59
+ self.notification_handlers: Dict[str, List[Callable]] = {}
56
60
  self.event_lock = threading.Lock()
57
61
  self.request_id_counter = 0
58
62
 
@@ -123,13 +127,15 @@ class SSEMcpClient(McpClient):
123
127
  self.sse_response = self.session.get(
124
128
  sse_url, stream=True, headers=sse_headers, timeout=30
125
129
  )
126
- self.sse_response.raise_for_status()
130
+ if self.sse_response:
131
+ self.sse_response.raise_for_status()
127
132
 
128
133
  # 启动事件处理线程
129
134
  self.sse_thread = threading.Thread(
130
135
  target=self._process_sse_events, daemon=True
131
136
  )
132
- self.sse_thread.start()
137
+ if self.sse_thread:
138
+ self.sse_thread.start()
133
139
 
134
140
  except Exception as e:
135
141
  PrettyOutput.print(f"SSE连接失败: {str(e)}", OutputType.ERROR)
@@ -204,13 +210,14 @@ class SSEMcpClient(McpClient):
204
210
 
205
211
  # 调用已注册的处理器
206
212
  if method in self.notification_handlers:
213
+ error_lines: List[str] = []
207
214
  for handler in self.notification_handlers[method]:
208
215
  try:
209
216
  handler(params)
210
217
  except Exception as e:
211
- PrettyOutput.print(
212
- f"处理通知时出错 ({method}): {e}", OutputType.ERROR
213
- )
218
+ error_lines.append(f"处理通知时出错 ({method}): {e}")
219
+ if error_lines:
220
+ PrettyOutput.print("\n".join(error_lines), OutputType.ERROR)
214
221
  except json.JSONDecodeError:
215
222
  PrettyOutput.print(f"无法解析SSE事件: {data}", OutputType.WARNING)
216
223
  except Exception as e:
@@ -561,7 +568,7 @@ class SSEMcpClient(McpClient):
561
568
  if self.sse_response:
562
569
  try:
563
570
  self.sse_response.close()
564
- except:
571
+ except Exception:
565
572
  pass
566
573
 
567
574
  # 关闭HTTP会话
@@ -2,7 +2,7 @@
2
2
  import json
3
3
  import os
4
4
  import subprocess
5
- from typing import Any, Dict, List
5
+ from typing import Any, Dict, List, Optional
6
6
 
7
7
  from jarvis.jarvis_mcp import McpClient
8
8
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -17,7 +17,7 @@ class StdioMcpClient(McpClient):
17
17
 
18
18
  def __init__(self, config: Dict[str, Any]):
19
19
  self.config = config
20
- self.process = None
20
+ self.process: Optional[subprocess.Popen] = None
21
21
  self.protocol_version = "2025-03-26" # MCP协议版本
22
22
  self._start_process()
23
23
  self._initialize()
@@ -73,7 +73,7 @@ class StdioMcpClient(McpClient):
73
73
  f"初始化失败: {response.get('error', 'Unknown error')}"
74
74
  )
75
75
 
76
- result = response["result"]
76
+ response["result"]
77
77
 
78
78
  # 发送initialized通知 - 使用正确的方法名格式
79
79
  self._send_notification("notifications/initialized", {})
@@ -296,6 +296,6 @@ class StdioMcpClient(McpClient):
296
296
  self._send_notification("notifications/exit", {})
297
297
  # 等待进程结束
298
298
  self.process.wait(timeout=1)
299
- except:
299
+ except Exception:
300
300
  # 如果进程没有正常退出,强制终止
301
301
  self.process.kill()
@@ -4,7 +4,7 @@ import threading
4
4
  from typing import Any, Callable, Dict, List
5
5
  from urllib.parse import urljoin
6
6
 
7
- import requests
7
+ import requests # type: ignore[import-untyped]
8
8
 
9
9
  from jarvis.jarvis_mcp import McpClient
10
10
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
@@ -25,6 +25,8 @@ class StreamableMcpClient(McpClient):
25
25
  self.base_url = config.get("base_url", "")
26
26
  if not self.base_url:
27
27
  raise ValueError("No base_url specified in config")
28
+ # Normalize base_url to ensure trailing slash for urljoin correctness
29
+ self.base_url = self.base_url.rstrip("/") + "/"
28
30
 
29
31
  # 设置HTTP客户端
30
32
  self.session = requests.Session()
@@ -43,11 +45,17 @@ class StreamableMcpClient(McpClient):
43
45
  # 添加额外的HTTP头
44
46
  extra_headers = config.get("headers", {})
45
47
  self.session.headers.update(extra_headers)
48
+ # Request timeouts (connect, read) in seconds; can be overridden via config["timeout"]
49
+ self.timeout = config.get("timeout", (10, 300))
46
50
 
47
51
  # 请求相关属性
48
- self.pending_requests = {} # 存储等待响应的请求 {id: Event}
49
- self.request_results = {} # 存储请求结果 {id: result}
50
- self.notification_handlers = {}
52
+ self.pending_requests: Dict[str, threading.Event] = (
53
+ {}
54
+ ) # 存储等待响应的请求 {id: Event}
55
+ self.request_results: Dict[str, Dict[str, Any]] = (
56
+ {}
57
+ ) # 存储请求结果 {id: result}
58
+ self.notification_handlers: Dict[str, List[Callable]] = {}
51
59
  self.event_lock = threading.Lock()
52
60
  self.request_id_counter = 0
53
61
 
@@ -144,37 +152,46 @@ class StreamableMcpClient(McpClient):
144
152
  # 发送请求到Streamable HTTP端点
145
153
  mcp_url = urljoin(self.base_url, "mcp")
146
154
  response = self.session.post(
147
- mcp_url, json=request, stream=True # 启用流式传输
148
- )
155
+ mcp_url, json=request, stream=True, timeout=self.timeout
156
+ ) # 启用流式传输
149
157
  response.raise_for_status()
150
158
 
151
159
  # 处理流式响应
152
160
  result = None
161
+ warning_lines = []
162
+ error_lines = []
153
163
  for line in response.iter_lines(decode_unicode=True):
154
164
  if line:
155
165
  try:
156
- data = json.loads(line)
166
+ line_data = line
167
+ if isinstance(line_data, str) and line_data.startswith("data:"):
168
+ # Handle SSE-formatted lines like "data: {...}"
169
+ line_data = line_data.split(":", 1)[1].strip()
170
+ data = json.loads(line_data)
157
171
  if "id" in data and data["id"] == req_id:
158
172
  # 这是我们的请求响应
159
173
  result = data
160
174
  break
161
175
  elif "method" in data:
162
176
  # 这是一个通知
163
- method = data.get("method", "")
177
+ notify_method = data.get("method", "")
164
178
  params = data.get("params", {})
165
- if method in self.notification_handlers:
166
- for handler in self.notification_handlers[method]:
179
+ if notify_method in self.notification_handlers:
180
+ for handler in self.notification_handlers[notify_method]:
167
181
  try:
168
182
  handler(params)
169
183
  except Exception as e:
170
- PrettyOutput.print(
171
- f"处理通知时出错 ({method}): {e}",
172
- OutputType.ERROR,
173
- )
184
+ error_lines.append(f"处理通知时出错 ({notify_method}): {e}")
174
185
  except json.JSONDecodeError:
175
- PrettyOutput.print(f"无法解析响应: {line}", OutputType.WARNING)
186
+ warning_lines.append(f"无法解析响应: {line}")
176
187
  continue
177
188
 
189
+ if warning_lines:
190
+ PrettyOutput.print("\n".join(warning_lines), OutputType.WARNING)
191
+ if error_lines:
192
+ PrettyOutput.print("\n".join(error_lines), OutputType.ERROR)
193
+ # Ensure response is closed after streaming
194
+ response.close()
178
195
  if result is None:
179
196
  raise RuntimeError(f"未收到响应: {method}")
180
197
 
@@ -202,8 +219,11 @@ class StreamableMcpClient(McpClient):
202
219
 
203
220
  # 发送通知到Streamable HTTP端点
204
221
  mcp_url = urljoin(self.base_url, "mcp")
205
- response = self.session.post(mcp_url, json=notification)
222
+ response = self.session.post(
223
+ mcp_url, json=notification, timeout=self.timeout
224
+ )
206
225
  response.raise_for_status()
226
+ response.close()
207
227
 
208
228
  except Exception as e:
209
229
  PrettyOutput.print(f"发送通知失败: {str(e)}", OutputType.ERROR)