jarvis-ai-assistant 0.1.193__py3-none-any.whl → 0.1.195__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 (92) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +45 -41
  3. jarvis/jarvis_agent/builtin_input_handler.py +26 -4
  4. jarvis/jarvis_agent/jarvis.py +30 -19
  5. jarvis/jarvis_agent/main.py +20 -12
  6. jarvis/jarvis_agent/output_handler.py +7 -7
  7. jarvis/jarvis_agent/shell_input_handler.py +14 -11
  8. jarvis/jarvis_code_agent/code_agent.py +81 -79
  9. jarvis/jarvis_code_agent/lint.py +92 -105
  10. jarvis/jarvis_code_analysis/checklists/__init__.py +1 -1
  11. jarvis/jarvis_code_analysis/checklists/c_cpp.py +1 -1
  12. jarvis/jarvis_code_analysis/checklists/csharp.py +1 -1
  13. jarvis/jarvis_code_analysis/checklists/data_format.py +1 -1
  14. jarvis/jarvis_code_analysis/checklists/devops.py +1 -1
  15. jarvis/jarvis_code_analysis/checklists/docs.py +1 -1
  16. jarvis/jarvis_code_analysis/checklists/go.py +1 -1
  17. jarvis/jarvis_code_analysis/checklists/infrastructure.py +1 -1
  18. jarvis/jarvis_code_analysis/checklists/java.py +1 -1
  19. jarvis/jarvis_code_analysis/checklists/javascript.py +1 -1
  20. jarvis/jarvis_code_analysis/checklists/kotlin.py +1 -1
  21. jarvis/jarvis_code_analysis/checklists/loader.py +31 -29
  22. jarvis/jarvis_code_analysis/checklists/php.py +1 -1
  23. jarvis/jarvis_code_analysis/checklists/python.py +1 -1
  24. jarvis/jarvis_code_analysis/checklists/ruby.py +1 -1
  25. jarvis/jarvis_code_analysis/checklists/rust.py +1 -1
  26. jarvis/jarvis_code_analysis/checklists/shell.py +1 -1
  27. jarvis/jarvis_code_analysis/checklists/sql.py +1 -1
  28. jarvis/jarvis_code_analysis/checklists/swift.py +1 -1
  29. jarvis/jarvis_code_analysis/checklists/web.py +1 -1
  30. jarvis/jarvis_code_analysis/code_review.py +292 -190
  31. jarvis/jarvis_dev/main.py +73 -56
  32. jarvis/jarvis_git_details/main.py +29 -33
  33. jarvis/jarvis_git_squash/main.py +13 -11
  34. jarvis/jarvis_git_utils/git_commiter.py +15 -5
  35. jarvis/jarvis_mcp/__init__.py +8 -10
  36. jarvis/jarvis_mcp/sse_mcp_client.py +182 -205
  37. jarvis/jarvis_mcp/stdio_mcp_client.py +93 -120
  38. jarvis/jarvis_mcp/streamable_mcp_client.py +117 -142
  39. jarvis/jarvis_methodology/main.py +71 -39
  40. jarvis/jarvis_multi_agent/__init__.py +24 -16
  41. jarvis/jarvis_multi_agent/main.py +10 -4
  42. jarvis/jarvis_platform/__init__.py +1 -1
  43. jarvis/jarvis_platform/base.py +44 -18
  44. jarvis/jarvis_platform/human.py +15 -3
  45. jarvis/jarvis_platform/kimi.py +117 -81
  46. jarvis/jarvis_platform/openai.py +23 -28
  47. jarvis/jarvis_platform/registry.py +43 -29
  48. jarvis/jarvis_platform/tongyi.py +16 -10
  49. jarvis/jarvis_platform/yuanbao.py +197 -144
  50. jarvis/jarvis_platform_manager/main.py +4 -2
  51. jarvis/jarvis_smart_shell/main.py +35 -30
  52. jarvis/jarvis_tools/ask_user.py +8 -16
  53. jarvis/jarvis_tools/base.py +3 -2
  54. jarvis/jarvis_tools/chdir.py +7 -19
  55. jarvis/jarvis_tools/cli/main.py +14 -10
  56. jarvis/jarvis_tools/code_plan.py +10 -31
  57. jarvis/jarvis_tools/create_code_agent.py +6 -11
  58. jarvis/jarvis_tools/create_sub_agent.py +10 -22
  59. jarvis/jarvis_tools/edit_file.py +98 -76
  60. jarvis/jarvis_tools/execute_script.py +46 -46
  61. jarvis/jarvis_tools/file_analyzer.py +22 -34
  62. jarvis/jarvis_tools/file_operation.py +69 -62
  63. jarvis/jarvis_tools/generate_new_tool.py +0 -2
  64. jarvis/jarvis_tools/methodology.py +19 -23
  65. jarvis/jarvis_tools/read_code.py +35 -35
  66. jarvis/jarvis_tools/read_webpage.py +7 -16
  67. jarvis/jarvis_tools/registry.py +63 -30
  68. jarvis/jarvis_tools/rewrite_file.py +26 -29
  69. jarvis/jarvis_tools/search_web.py +5 -8
  70. jarvis/jarvis_tools/virtual_tty.py +133 -122
  71. jarvis/jarvis_utils/__init__.py +0 -1
  72. jarvis/jarvis_utils/builtin_replace_map.py +9 -9
  73. jarvis/jarvis_utils/config.py +60 -37
  74. jarvis/jarvis_utils/embedding.py +24 -19
  75. jarvis/jarvis_utils/file_processors.py +16 -9
  76. jarvis/jarvis_utils/git_utils.py +157 -107
  77. jarvis/jarvis_utils/globals.py +1 -1
  78. jarvis/jarvis_utils/input.py +85 -52
  79. jarvis/jarvis_utils/jarvis_history.py +43 -0
  80. jarvis/jarvis_utils/methodology.py +31 -24
  81. jarvis/jarvis_utils/output.py +164 -80
  82. jarvis/jarvis_utils/tag.py +2 -1
  83. jarvis/jarvis_utils/utils.py +84 -52
  84. {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/METADATA +362 -230
  85. jarvis_ai_assistant-0.1.195.dist-info/RECORD +98 -0
  86. jarvis/jarvis_agent/file_input_handler.py +0 -112
  87. jarvis/jarvis_event/__init__.py +0 -0
  88. jarvis_ai_assistant-0.1.193.dist-info/RECORD +0 -99
  89. {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/WHEEL +0 -0
  90. {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/entry_points.txt +0 -0
  91. {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/licenses/LICENSE +0 -0
  92. {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/top_level.txt +0 -0
@@ -58,33 +58,24 @@ class FileSearchReplaceTool:
58
58
  parameters = {
59
59
  "type": "object",
60
60
  "properties": {
61
- "file": {
62
- "type": "string",
63
- "description": "需要修改的文件路径"
64
- },
61
+ "file": {"type": "string", "description": "需要修改的文件路径"},
65
62
  "changes": {
66
63
  "type": "array",
67
64
  "description": "一组或多组修改,每个修改必须包含1-2行上下文用于精确定位",
68
65
  "items": {
69
66
  "type": "object",
70
67
  "properties": {
71
- "reason": {
72
- "type": "string",
73
- "description": "修改的原因"
74
- },
68
+ "reason": {"type": "string", "description": "修改的原因"},
75
69
  "search": {
76
70
  "type": "string",
77
- "description": "需要查找的原始代码"
71
+ "description": "需要查找的原始代码",
78
72
  },
79
- "replace": {
80
- "type": "string",
81
- "description": "替换后的新代码"
82
- }
73
+ "replace": {"type": "string", "description": "替换后的新代码"},
83
74
  },
84
- }
85
- }
75
+ },
76
+ },
86
77
  },
87
- "required": ["file", "changes"]
78
+ "required": ["file", "changes"],
88
79
  }
89
80
 
90
81
  def __init__(self):
@@ -131,15 +122,15 @@ class FileSearchReplaceTool:
131
122
  import os
132
123
 
133
124
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
134
-
125
+
135
126
  stdout_messages = []
136
127
  stderr_messages = []
137
128
  success = True
138
-
129
+
139
130
  file_path = os.path.abspath(args["file"])
140
131
  changes = args["changes"]
141
132
  agent = args.get("agent", None)
142
-
133
+
143
134
  # 创建已处理文件变量,用于失败时回滚
144
135
  original_content = None
145
136
  processed = False
@@ -147,11 +138,11 @@ class FileSearchReplaceTool:
147
138
  try:
148
139
  file_exists = os.path.exists(file_path)
149
140
  content = ""
150
-
141
+
151
142
  try:
152
143
  # 如果文件存在,则读取内容
153
144
  if file_exists:
154
- with open(file_path, 'r', encoding='utf-8') as f:
145
+ with open(file_path, "r", encoding="utf-8") as f:
155
146
  content = f.read()
156
147
  original_content = content
157
148
 
@@ -161,21 +152,26 @@ class FileSearchReplaceTool:
161
152
  return {
162
153
  "success": False,
163
154
  "stdout": "",
164
- "stderr": f"请先读取文件 {file_path} 的内容后再编辑"
155
+ "stderr": f"请先读取文件 {file_path} 的内容后再编辑",
165
156
  }
166
157
 
167
-
168
- with yaspin(text=f"正在处理文件 {file_path}...", color="cyan") as spinner:
158
+ with yaspin(
159
+ text=f"正在处理文件 {file_path}...", color="cyan"
160
+ ) as spinner:
169
161
  success, temp_content = fast_edit(file_path, changes, spinner)
170
162
  if not success:
171
- success, temp_content = slow_edit(file_path, yaml.safe_dump(changes, allow_unicode=True), spinner)
163
+ success, temp_content = slow_edit(
164
+ file_path,
165
+ yaml.safe_dump(changes, allow_unicode=True),
166
+ spinner,
167
+ )
172
168
  if not success:
173
169
  spinner.text = f"文件 {file_path} 处理失败"
174
170
  spinner.fail("❌")
175
171
  return {
176
172
  "success": False,
177
173
  "stdout": "",
178
- "stderr": temp_content
174
+ "stderr": temp_content,
179
175
  }
180
176
  else:
181
177
  spinner.text = f"文件 {file_path} 内容生成完成"
@@ -184,22 +180,23 @@ class FileSearchReplaceTool:
184
180
  spinner.text = f"文件 {file_path} 内容生成完成"
185
181
  spinner.ok("✅")
186
182
 
187
-
188
183
  # 只有当所有替换操作都成功时,才写回文件
189
184
  if success and (temp_content != original_content or not file_exists):
190
185
  # 确保目录存在
191
- os.makedirs(os.path.dirname(os.path.abspath(file_path)), exist_ok=True)
192
-
193
- with open(file_path, 'w', encoding='utf-8') as f:
186
+ os.makedirs(
187
+ os.path.dirname(os.path.abspath(file_path)), exist_ok=True
188
+ )
189
+
190
+ with open(file_path, "w", encoding="utf-8") as f:
194
191
  f.write(temp_content)
195
-
192
+
196
193
  processed = True
197
-
194
+
198
195
  action = "创建并写入" if not file_exists else "成功修改"
199
196
  stdout_message = f"文件 {file_path} {action} 完成"
200
197
  stdout_messages.append(stdout_message)
201
198
  PrettyOutput.print(stdout_message, OutputType.SUCCESS)
202
-
199
+
203
200
  except Exception as e:
204
201
  stderr_message = f"处理文件 {file_path} 时出错: {str(e)}"
205
202
  stderr_messages.append(stderr_message)
@@ -209,19 +206,19 @@ class FileSearchReplaceTool:
209
206
  return {
210
207
  "success": success,
211
208
  "stdout": "\n".join(stdout_messages) if success else "",
212
- "stderr": "\n".join(stderr_messages) if not success else ""
209
+ "stderr": "\n".join(stderr_messages) if not success else "",
213
210
  }
214
-
211
+
215
212
  except Exception as e:
216
213
  error_msg = f"文件搜索替换操作失败: {str(e)}"
217
214
  PrettyOutput.print(error_msg, OutputType.WARNING)
218
-
215
+
219
216
  # 如果有已修改的文件,尝试回滚
220
217
  if processed:
221
218
  rollback_message = "操作失败,正在回滚修改..."
222
219
  stderr_messages.append(rollback_message)
223
220
  PrettyOutput.print(rollback_message, OutputType.WARNING)
224
-
221
+
225
222
  try:
226
223
  if original_content is None:
227
224
  # 如果是新创建的文件,则删除
@@ -230,30 +227,29 @@ class FileSearchReplaceTool:
230
227
  stderr_messages.append(f"已删除新创建的文件: {file_path}")
231
228
  else:
232
229
  # 如果是修改的文件,则恢复原内容
233
- with open(file_path, 'w', encoding='utf-8') as f:
230
+ with open(file_path, "w", encoding="utf-8") as f:
234
231
  f.write(original_content)
235
232
  stderr_messages.append(f"已回滚文件: {file_path}")
236
233
  except:
237
234
  stderr_messages.append(f"回滚文件失败: {file_path}")
238
-
235
+
239
236
  return {
240
237
  "success": False,
241
238
  "stdout": "",
242
- "stderr": error_msg + "\n" + "\n".join(stderr_messages)
239
+ "stderr": error_msg + "\n" + "\n".join(stderr_messages),
243
240
  }
244
-
245
241
 
246
242
 
247
243
  def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool, str]:
248
244
  """执行精确的文件编辑操作,使用AI模型生成差异补丁并应用。
249
-
245
+
250
246
  核心功能:
251
247
  1. 使用AI模型分析补丁内容并生成精确的代码差异
252
248
  2. 应用生成的差异补丁到目标文件
253
249
  3. 提供3次重试机制确保操作可靠性
254
250
  4. 支持大文件处理(自动上传到模型平台)
255
251
  5. 严格的格式一致性检查
256
-
252
+
257
253
  参数:
258
254
  filepath: 要编辑的文件路径(绝对或相对路径)
259
255
  patch_content: YAML格式的补丁内容,包含:
@@ -261,17 +257,17 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
261
257
  - search: 需要查找的原始代码(必须包含足够上下文)
262
258
  - replace: 替换后的新代码
263
259
  spinner: Yaspin实例,用于显示处理状态
264
-
260
+
265
261
  返回值:
266
- Tuple[bool, str]:
262
+ Tuple[bool, str]:
267
263
  - 第一个元素表示操作是否成功(True/False)
268
264
  - 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
269
-
265
+
270
266
  异常处理:
271
267
  1. 文件不存在或权限不足时会捕获异常并返回失败
272
268
  2. 模型生成补丁失败时会自动重试最多3次
273
269
  3. 补丁应用失败时会自动回滚文件修改
274
-
270
+
275
271
  实现细节:
276
272
  1. 检查文件是否在工作目录下(影响版本控制)
277
273
  2. 根据文件大小决定是否上传到模型平台
@@ -279,21 +275,30 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
279
275
  4. 确保补丁应用前进行唯一性匹配检查
280
276
  """
281
277
  import os
278
+
282
279
  work_dir = os.path.abspath(os.curdir)
283
280
  filepath = os.path.abspath(filepath)
284
281
  if not filepath.startswith(work_dir):
285
- PrettyOutput.print(f"文件 {filepath} 不在工作目录 {work_dir} 下,不会进行版本控制管理", OutputType.WARNING)
282
+ PrettyOutput.print(
283
+ f"文件 {filepath} 不在工作目录 {work_dir} 下,不会进行版本控制管理",
284
+ OutputType.WARNING,
285
+ )
286
286
  model = PlatformRegistry().get_normal_platform()
287
287
  try:
288
- file_content = FileOperationTool().execute({"operation":"read", "files":[{"path":filepath}]})["stdout"]
288
+ file_content = FileOperationTool().execute(
289
+ {"operation": "read", "files": [{"path": filepath}]}
290
+ )["stdout"]
289
291
  is_large_context = is_context_overflow(file_content)
290
292
  upload_success = False
291
293
  # 读取原始文件内容
292
- with spinner.hidden():
293
- if is_large_context and model.support_upload_files() and model.upload_files([filepath]):
294
+ with spinner.hidden():
295
+ if (
296
+ is_large_context
297
+ and model.support_upload_files()
298
+ and model.upload_files([filepath])
299
+ ):
294
300
  upload_success = True
295
301
 
296
-
297
302
  model.set_suppress_output(True)
298
303
 
299
304
  main_prompt = f"""
@@ -338,7 +343,7 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
338
343
  {"<" * 5} REPLACE
339
344
  {ct("DIFF")}
340
345
  """
341
-
346
+
342
347
  for _ in range(3):
343
348
  if is_large_context:
344
349
  if upload_success:
@@ -357,15 +362,22 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
357
362
  response = model.chat_until_success(main_prompt + file_prompt)
358
363
 
359
364
  # 解析差异化补丁
360
- diff_blocks = re.finditer(ot("DIFF")+r'\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?'+ct("DIFF"),
361
- response, re.DOTALL)
365
+ diff_blocks = re.finditer(
366
+ ot("DIFF")
367
+ + r"\s*>{4,} SEARCH\n?(.*?)\n?={4,}\n?(.*?)\s*<{4,} REPLACE\n?"
368
+ + ct("DIFF"),
369
+ response,
370
+ re.DOTALL,
371
+ )
362
372
 
363
373
  patches = []
364
374
  for match in diff_blocks:
365
- patches.append({
366
- "search": match.group(1).strip(),
367
- "replace": match.group(2).strip()
368
- })
375
+ patches.append(
376
+ {
377
+ "search": match.group(1).strip(),
378
+ "replace": match.group(2).strip(),
379
+ }
380
+ )
369
381
 
370
382
  success, modified_content_or_err = fast_edit(filepath, patches, spinner)
371
383
  if success:
@@ -380,32 +392,34 @@ def slow_edit(filepath: str, patch_content: str, spinner: Yaspin) -> Tuple[bool,
380
392
  return False, f"文件修改失败: {str(e)}"
381
393
 
382
394
 
383
- def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> Tuple[bool, str]:
395
+ def fast_edit(
396
+ filepath: str, patches: List[Dict[str, str]], spinner: Yaspin
397
+ ) -> Tuple[bool, str]:
384
398
  """快速应用预先生成的补丁到目标文件。
385
-
399
+
386
400
  核心功能:
387
401
  1. 直接应用已生成的代码补丁
388
402
  2. 执行严格的唯一匹配检查
389
403
  3. 提供详细的补丁应用状态反馈
390
404
  4. 失败时自动回滚文件修改
391
-
405
+
392
406
  参数:
393
407
  filepath: 要编辑的文件路径(绝对或相对路径)
394
408
  patches: 补丁列表,每个补丁包含:
395
409
  - search: 需要查找的原始代码
396
410
  - replace: 替换后的新代码
397
411
  spinner: Yaspin实例,用于显示处理状态
398
-
412
+
399
413
  返回值:
400
- Tuple[bool, str]:
414
+ Tuple[bool, str]:
401
415
  - 第一个元素表示操作是否成功(True/False)
402
416
  - 第二个元素是修改后的文件内容(成功时)或空字符串(失败时)
403
-
417
+
404
418
  异常处理:
405
419
  1. 文件不存在或权限不足时会捕获异常并返回失败
406
420
  2. 补丁不匹配或有多处匹配时会返回失败
407
421
  3. 失败时会自动回滚文件修改
408
-
422
+
409
423
  实现细节:
410
424
  1. 读取文件内容到内存
411
425
  2. 依次应用每个补丁,检查唯一匹配性
@@ -413,7 +427,7 @@ def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> T
413
427
  4. 所有补丁成功应用后才写入文件
414
428
  """
415
429
  # 读取原始文件内容
416
- with open(filepath, 'r', encoding='utf-8', errors="ignore") as f:
430
+ with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
417
431
  file_content = f.read()
418
432
 
419
433
  # 应用所有差异化补丁
@@ -433,26 +447,35 @@ def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> T
433
447
  err_msg = f"搜索文本 {search_text} 在文件中存在多处,请检查补丁内容"
434
448
  break
435
449
  # 应用替换
436
- modified_content = modified_content.replace(
437
- search_text, replace_text)
450
+ modified_content = modified_content.replace(search_text, replace_text)
438
451
  spinner.write(f"✅ 补丁 #{patch_count} 应用成功")
439
452
  else:
440
453
  # 尝试增加缩进重试
441
454
  found = False
442
455
  for space_count in range(1, 17):
443
- indented_search = '\n'.join(' ' * space_count + line for line in search_text.split('\n'))
444
- indented_replace = '\n'.join(' ' * space_count + line for line in replace_text.split('\n'))
456
+ # 跳过空行不增加空格
457
+ indented_search = "\n".join(
458
+ " " * space_count + line if line.strip() else line
459
+ for line in search_text.split("\n")
460
+ )
461
+ indented_replace = "\n".join(
462
+ " " * space_count + line if line.strip() else line
463
+ for line in replace_text.split("\n")
464
+ )
445
465
  if indented_search in modified_content:
446
466
  if modified_content.count(indented_search) > 1:
447
467
  success = False
448
468
  err_msg = f"搜索文本 {indented_search} 在文件中存在多处,请检查补丁内容"
449
469
  break
450
470
  modified_content = modified_content.replace(
451
- indented_search, indented_replace)
452
- spinner.write(f"✅ 补丁 #{patch_count} 应用成功 (自动增加 {space_count} 个空格缩进)")
471
+ indented_search, indented_replace
472
+ )
473
+ spinner.write(
474
+ f"✅ 补丁 #{patch_count} 应用成功 (自动增加 {space_count} 个空格缩进)"
475
+ )
453
476
  found = True
454
477
  break
455
-
478
+
456
479
  if not found:
457
480
  success = False
458
481
  err_msg = f"搜索文本 {search_text} 在文件中不存在,尝试增加1-16个空格缩进后仍未找到匹配"
@@ -461,7 +484,6 @@ def fast_edit(filepath: str, patches: List[Dict[str,str]], spinner: Yaspin) -> T
461
484
  revert_file(filepath)
462
485
  return False, err_msg
463
486
 
464
-
465
487
  spinner.text = f"文件 {filepath} 修改完成,应用了 {patch_count} 个补丁"
466
488
  spinner.ok("✅")
467
- return True, modified_content
489
+ return True, modified_content
@@ -9,27 +9,27 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
9
9
 
10
10
  class ScriptTool:
11
11
  """Combined script execution tool
12
-
12
+
13
13
  Executes scripts with any interpreter with a unified interface.
14
14
  """
15
+
15
16
  name = "execute_script"
16
- description = "执行脚本并返回结果,支持任意解释器。" \
17
- + "注意:由于模型上下文长度限制,请避免在脚本中输出大量信息,应该使用rg过滤输出。" \
18
- + "与virtual_tty不同,此工具会创建一个临时的脚本文件,并使用脚本命令执行脚本,不具备交互式操作的能力," \
17
+ description = (
18
+ "执行脚本并返回结果,支持任意解释器。"
19
+ + "注意:由于模型上下文长度限制,请避免在脚本中输出大量信息,应该使用rg过滤输出。"
20
+ + "与virtual_tty不同,此工具会创建一个临时的脚本文件,并使用脚本命令执行脚本,不具备交互式操作的能力,"
19
21
  + "适用于需要执行脚本并获取结果的场景。不适合需要交互式操作的场景(如:ssh连接、sftp传输、gdb/dlv调试等)。"
22
+ )
20
23
  parameters = {
21
24
  "type": "object",
22
25
  "properties": {
23
26
  "interpreter": {
24
27
  "type": "string",
25
- "description": "脚本解释器: 如bash, python3, expect, perl, ruby等任意解释器。如需直接执行shell命令, 可使用bash作为解释器"
28
+ "description": "脚本解释器: 如bash, python3, expect, perl, ruby等任意解释器。如需直接执行shell命令, 可使用bash作为解释器",
26
29
  },
27
- "script_content": {
28
- "type": "string",
29
- "description": "要执行的脚本内容"
30
- }
30
+ "script_content": {"type": "string", "description": "要执行的脚本内容"},
31
31
  },
32
- "required": ["script_content"]
32
+ "required": ["script_content"],
33
33
  }
34
34
 
35
35
  # Map of common file extensions for interpreters (can be extended as needed)
@@ -63,69 +63,76 @@ class ScriptTool:
63
63
 
64
64
  def get_display_output(self, file_path: str) -> str:
65
65
  """消除控制字符,得到用户实际看到的文本,去除script命令首尾行"""
66
- import re
67
66
  # 读取文件内容并尝试多种编码
68
- with open(file_path, 'rb') as f:
67
+ with open(file_path, "rb") as f:
69
68
  data = f.read()
70
69
 
71
70
  import pyte
71
+
72
72
  screen = pyte.Screen(300, 100000)
73
73
  stream = pyte.ByteStream(screen)
74
74
  stream.feed(data)
75
-
75
+
76
76
  # 清理每行右侧空格,并过滤空行
77
77
  cleaned = []
78
78
  cleaned = []
79
79
  for y in range(screen.lines):
80
80
  line = screen.buffer[y]
81
- stripped = "".join(
82
- char.data for char in line.values()
83
- ).rstrip()
81
+ stripped = "".join(char.data for char in line.values()).rstrip()
84
82
  if stripped:
85
83
  cleaned.append(stripped)
86
84
  return "\n".join(cleaned[1:-1])
87
85
 
88
- def _execute_script_with_interpreter(self, interpreter: str, script_content: str) -> Dict[str, Any]:
86
+ def _execute_script_with_interpreter(
87
+ self, interpreter: str, script_content: str
88
+ ) -> Dict[str, Any]:
89
89
  """Execute a script with the specified interpreter
90
-
90
+
91
91
  Args:
92
92
  interpreter: The interpreter to use (any valid interpreter command)
93
93
  script_content: Content of the script
94
-
94
+
95
95
  Returns:
96
96
  Dictionary with execution results
97
97
  """
98
98
  try:
99
99
  # Get file extension for the interpreter
100
100
  extension = self.INTERPRETER_EXTENSIONS.get(interpreter, "script")
101
-
101
+
102
102
  # Create temporary script file
103
- script_path = os.path.join(tempfile.gettempdir(), f"jarvis_{interpreter.replace('/', '_')}_{os.getpid()}.{extension}")
104
- output_file = os.path.join(tempfile.gettempdir(), f"jarvis_output_{os.getpid()}.log")
103
+ script_path = os.path.join(
104
+ tempfile.gettempdir(),
105
+ f"jarvis_{interpreter.replace('/', '_')}_{os.getpid()}.{extension}",
106
+ )
107
+ output_file = os.path.join(
108
+ tempfile.gettempdir(), f"jarvis_output_{os.getpid()}.log"
109
+ )
105
110
  try:
106
- with open(script_path, 'w', encoding='utf-8', errors="ignore") as f:
111
+ with open(script_path, "w", encoding="utf-8", errors="ignore") as f:
107
112
  f.write(script_content)
108
-
113
+
109
114
  # Use script command to capture both stdout and stderr
110
- tee_command = f"script -q -c '{interpreter} {script_path}' {output_file}"
111
-
115
+ tee_command = (
116
+ f"script -q -c '{interpreter} {script_path}' {output_file}"
117
+ )
118
+
112
119
  # Execute command and capture return code
113
120
  os.system(tee_command)
114
-
121
+
115
122
  # Read and process output file
116
123
  try:
117
124
  # 消除控制字符,得到用户实际看到的文本
118
125
  output = self.get_display_output(output_file)
119
126
  except Exception as e:
120
127
  output = f"读取输出文件失败: {str(e)}"
121
-
128
+
122
129
  # Return successful result
123
130
  return {
124
131
  "success": True,
125
132
  "stdout": output,
126
133
  "stderr": "",
127
134
  }
128
-
135
+
129
136
  finally:
130
137
  # Clean up temporary files
131
138
  Path(script_path).unlink(missing_ok=True)
@@ -133,18 +140,14 @@ class ScriptTool:
133
140
 
134
141
  except Exception as e:
135
142
  PrettyOutput.print(str(e), OutputType.ERROR)
136
- return {
137
- "success": False,
138
- "stdout": "",
139
- "stderr": str(e)
140
- }
143
+ return {"success": False, "stdout": "", "stderr": str(e)}
141
144
 
142
145
  def execute(self, args: Dict) -> Dict[str, Any]:
143
146
  """Execute script based on interpreter and content
144
-
147
+
145
148
  Args:
146
149
  args: Dictionary containing interpreter (or script_type) and script_content
147
-
150
+
148
151
  Returns:
149
152
  Dictionary with execution results
150
153
  """
@@ -154,23 +157,20 @@ class ScriptTool:
154
157
  return {
155
158
  "success": False,
156
159
  "stdout": "",
157
- "stderr": "Missing or empty script_content parameter"
160
+ "stderr": "Missing or empty script_content parameter",
158
161
  }
159
-
162
+
160
163
  # Get interpreter, default to bash if not specified
161
164
  interpreter = args.get("interpreter", "bash")
162
-
165
+
163
166
  # Execute the script with the specified interpreter
164
167
  return self._execute_script_with_interpreter(interpreter, script_content)
165
-
168
+
166
169
  except Exception as e:
167
170
  PrettyOutput.print(str(e), OutputType.ERROR)
168
- return {
169
- "success": False,
170
- "stdout": "",
171
- "stderr": str(e)
172
- }
173
-
171
+ return {"success": False, "stdout": "", "stderr": str(e)}
172
+
173
+
174
174
  if __name__ == "__main__":
175
175
  script_tool = ScriptTool()
176
176
  print(script_tool.get_display_output("/home/wangmaobin/code/Jarvis/a.txt"))