jarvis-ai-assistant 0.2.8__py3-none-any.whl → 0.3.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 (38) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/__init__.py +267 -240
  3. jarvis/jarvis_agent/agent_manager.py +85 -0
  4. jarvis/jarvis_agent/config_editor.py +53 -0
  5. jarvis/jarvis_agent/file_methodology_manager.py +105 -0
  6. jarvis/jarvis_agent/jarvis.py +30 -619
  7. jarvis/jarvis_agent/memory_manager.py +133 -0
  8. jarvis/jarvis_agent/methodology_share_manager.py +174 -0
  9. jarvis/jarvis_agent/prompts.py +18 -3
  10. jarvis/jarvis_agent/share_manager.py +176 -0
  11. jarvis/jarvis_agent/task_analyzer.py +126 -0
  12. jarvis/jarvis_agent/task_manager.py +111 -0
  13. jarvis/jarvis_agent/tool_share_manager.py +139 -0
  14. jarvis/jarvis_code_agent/code_agent.py +26 -20
  15. jarvis/jarvis_data/config_schema.json +32 -0
  16. jarvis/jarvis_platform/ai8.py +13 -1
  17. jarvis/jarvis_platform/base.py +20 -5
  18. jarvis/jarvis_platform/human.py +11 -1
  19. jarvis/jarvis_platform/kimi.py +10 -0
  20. jarvis/jarvis_platform/openai.py +20 -0
  21. jarvis/jarvis_platform/tongyi.py +14 -9
  22. jarvis/jarvis_platform/yuanbao.py +10 -0
  23. jarvis/jarvis_platform_manager/main.py +12 -12
  24. jarvis/jarvis_tools/registry.py +32 -0
  25. jarvis/jarvis_tools/retrieve_memory.py +36 -8
  26. jarvis/jarvis_utils/clipboard.py +90 -0
  27. jarvis/jarvis_utils/config.py +54 -0
  28. jarvis/jarvis_utils/git_utils.py +17 -7
  29. jarvis/jarvis_utils/globals.py +18 -12
  30. jarvis/jarvis_utils/input.py +118 -16
  31. jarvis/jarvis_utils/methodology.py +48 -5
  32. jarvis/jarvis_utils/utils.py +169 -105
  33. {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/METADATA +1 -1
  34. {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/RECORD +38 -28
  35. {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/WHEEL +0 -0
  36. {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/entry_points.txt +0 -0
  37. {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/licenses/LICENSE +0 -0
  38. {jarvis_ai_assistant-0.2.8.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/top_level.txt +0 -0
@@ -1,624 +1,19 @@
1
1
  # -*- coding: utf-8 -*-
2
- import os
3
- import shutil
4
- import subprocess
5
- import sys
6
- from pathlib import Path
7
- from typing import Dict, Optional, List
2
+ """Jarvis AI 助手主入口模块"""
3
+ from typing import Optional
8
4
 
9
5
  import typer
10
- import yaml # type: ignore
11
- from prompt_toolkit import prompt # type: ignore
12
6
 
13
- from jarvis.jarvis_agent import (
14
- Agent,
15
- OutputType,
16
- PrettyOutput,
17
- get_multiline_input,
18
- origin_agent_system_prompt,
19
- user_confirm,
20
- )
21
- from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
22
- from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
23
- from jarvis.jarvis_tools.registry import ToolRegistry
24
- from jarvis.jarvis_utils.config import get_data_dir
7
+ from jarvis.jarvis_agent import OutputType, PrettyOutput
8
+ from jarvis.jarvis_agent.agent_manager import AgentManager
9
+ from jarvis.jarvis_agent.config_editor import ConfigEditor
10
+ from jarvis.jarvis_agent.methodology_share_manager import MethodologyShareManager
11
+ from jarvis.jarvis_agent.tool_share_manager import ToolShareManager
25
12
  from jarvis.jarvis_utils.utils import init_env
26
13
 
27
14
  app = typer.Typer(help="Jarvis AI 助手")
28
15
 
29
16
 
30
- def _load_tasks() -> Dict[str, str]:
31
- """Load tasks from .jarvis files in user home and current directory."""
32
- tasks: Dict[str, str] = {}
33
-
34
- # Check pre-command in data directory
35
- data_dir = get_data_dir()
36
- pre_command_path = os.path.join(data_dir, "pre-command")
37
- if os.path.exists(pre_command_path):
38
- print(f"🔍 从{pre_command_path}加载预定义任务...")
39
- try:
40
- with open(pre_command_path, "r", encoding="utf-8", errors="ignore") as f:
41
- user_tasks = yaml.safe_load(f)
42
- if isinstance(user_tasks, dict):
43
- for name, desc in user_tasks.items():
44
- if desc:
45
- tasks[str(name)] = str(desc)
46
- print(f"✅ 预定义任务加载完成 {pre_command_path}")
47
- except (yaml.YAMLError, OSError):
48
- print(f"❌ 预定义任务加载失败 {pre_command_path}")
49
-
50
- # Check .jarvis/pre-command in current directory
51
- pre_command_path = ".jarvis/pre-command"
52
- if os.path.exists(pre_command_path):
53
- abs_path = os.path.abspath(pre_command_path)
54
- print(f"🔍 从{abs_path}加载预定义任务...")
55
- try:
56
- with open(pre_command_path, "r", encoding="utf-8", errors="ignore") as f:
57
- local_tasks = yaml.safe_load(f)
58
- if isinstance(local_tasks, dict):
59
- for name, desc in local_tasks.items():
60
- if desc:
61
- tasks[str(name)] = str(desc)
62
- print(f"✅ 预定义任务加载完成 {pre_command_path}")
63
- except (yaml.YAMLError, OSError):
64
- print(f"❌ 预定义任务加载失败 {pre_command_path}")
65
-
66
- return tasks
67
-
68
-
69
- def _select_task(tasks: Dict[str, str]) -> str:
70
- """Let user select a task from the list or skip. Returns task description if selected."""
71
- if not tasks:
72
- return ""
73
-
74
- task_names = list(tasks.keys())
75
- task_list = ["可用任务:"]
76
- for i, name in enumerate(task_names, 1):
77
- task_list.append(f"[{i}] {name}")
78
- task_list.append("[0] 跳过预定义任务")
79
- PrettyOutput.print("\n".join(task_list), OutputType.INFO)
80
-
81
- while True:
82
- try:
83
- choice_str = prompt("\n请选择一个任务编号(0 跳过预定义任务):").strip()
84
- if not choice_str:
85
- return ""
86
-
87
- choice = int(choice_str)
88
- if choice == 0:
89
- return ""
90
- if 1 <= choice <= len(task_names):
91
- selected_task = tasks[task_names[choice - 1]]
92
- PrettyOutput.print(f"将要执行任务:\n {selected_task}", OutputType.INFO)
93
- # 询问是否需要补充信息
94
- need_additional = user_confirm(
95
- "需要为此任务添加补充信息吗?", default=False
96
- )
97
- if need_additional:
98
- additional_input = get_multiline_input("请输入补充信息:")
99
- if additional_input:
100
- selected_task = (
101
- f"{selected_task}\n\n补充信息:\n{additional_input}"
102
- )
103
- return selected_task
104
- PrettyOutput.print(
105
- "无效的选择。请选择列表中的一个号码。", OutputType.WARNING
106
- )
107
-
108
- except (KeyboardInterrupt, EOFError):
109
- return ""
110
- except ValueError as val_err:
111
- PrettyOutput.print(f"选择任务失败: {str(val_err)}", OutputType.ERROR)
112
-
113
-
114
- def _handle_edit_mode(edit: bool, config_file: Optional[str]) -> None:
115
- """If edit flag is set, open config file in editor and exit."""
116
- if not edit:
117
- return
118
-
119
- config_file_path = (
120
- Path(config_file)
121
- if config_file
122
- else Path(os.path.expanduser("~/.jarvis/config.yaml"))
123
- )
124
- # 根据操作系统选择合适的编辑器
125
- import platform
126
-
127
- if platform.system() == "Windows":
128
- # 优先级:终端工具 -> 代码编辑器 -> 通用文本编辑器
129
- editors = ["nvim", "vim", "nano", "code", "notepad++", "notepad"]
130
- else:
131
- # 优先级:终端工具 -> 代码编辑器 -> 通用文本编辑器
132
- editors = ["nvim", "vim", "vi", "nano", "emacs", "code", "gedit", "kate"]
133
-
134
- editor = next((e for e in editors if shutil.which(e)), None)
135
-
136
- if editor:
137
- try:
138
- subprocess.run([editor, str(config_file_path)], check=True)
139
- raise typer.Exit(code=0)
140
- except (subprocess.CalledProcessError, FileNotFoundError) as e:
141
- PrettyOutput.print(f"Failed to open editor: {e}", OutputType.ERROR)
142
- raise typer.Exit(code=1)
143
- else:
144
- PrettyOutput.print(
145
- f"No suitable editor found. Tried: {', '.join(editors)}", OutputType.ERROR
146
- )
147
- raise typer.Exit(code=1)
148
-
149
-
150
- def _initialize_agent(
151
- llm_type: str, model_group: Optional[str], restore_session: bool
152
- ) -> Agent:
153
- """Initialize the agent and restore session if requested."""
154
- agent = Agent(
155
- system_prompt=origin_agent_system_prompt,
156
- llm_type=llm_type,
157
- model_group=model_group,
158
- input_handler=[shell_input_handler, builtin_input_handler],
159
- output_handler=[ToolRegistry()], # type: ignore
160
- need_summary=False,
161
- )
162
-
163
- # 尝试恢复会话
164
- if restore_session:
165
- if agent.restore_session():
166
- PrettyOutput.print("会话已成功恢复。", OutputType.SUCCESS)
167
- else:
168
- PrettyOutput.print("无法恢复会话。", OutputType.WARNING)
169
- return agent
170
-
171
-
172
- def _get_and_run_task(agent: Agent, task_content: Optional[str] = None) -> None:
173
- """Get task from various sources and run it."""
174
- # 优先处理命令行直接传入的任务
175
- if task_content:
176
- agent.run(task_content)
177
- raise typer.Exit(code=0)
178
-
179
- if agent.first:
180
- tasks = _load_tasks()
181
- if tasks and (selected_task := _select_task(tasks)):
182
- PrettyOutput.print(f"开始执行任务: \n{selected_task}", OutputType.INFO)
183
- agent.run(selected_task)
184
- raise typer.Exit(code=0)
185
-
186
- user_input = get_multiline_input("请输入你的任务(输入空行退出):")
187
- if user_input:
188
- agent.run(user_input)
189
- raise typer.Exit(code=0)
190
-
191
-
192
- def _parse_selection(selection_str: str, max_value: int) -> List[int]:
193
- """解析用户输入的选择字符串,支持逗号分隔和范围选择
194
-
195
- 例如: "1,2,3,4-9,20" -> [1, 2, 3, 4, 5, 6, 7, 8, 9, 20]
196
- """
197
- selected: set[int] = set()
198
- parts = selection_str.split(",")
199
-
200
- for part in parts:
201
- part = part.strip()
202
- if "-" in part:
203
- # 处理范围选择
204
- try:
205
- start_str, end_str = part.split("-")
206
- start_num = int(start_str.strip())
207
- end_num = int(end_str.strip())
208
- if 1 <= start_num <= max_value and 1 <= end_num <= max_value:
209
- selected.update(range(start_num, end_num + 1))
210
- except ValueError:
211
- continue
212
- else:
213
- # 处理单个数字
214
- try:
215
- num = int(part)
216
- if 1 <= num <= max_value:
217
- selected.add(num)
218
- except ValueError:
219
- continue
220
-
221
- return sorted(list(selected))
222
-
223
-
224
- def _handle_share_tool(config_file: Optional[str] = None) -> None:
225
- """处理工具分享功能"""
226
- from jarvis.jarvis_utils.config import (
227
- get_central_tool_repo,
228
- get_data_dir,
229
- )
230
- import glob
231
- import shutil
232
-
233
- # 获取中心工具仓库配置
234
- central_repo = get_central_tool_repo()
235
- if not central_repo:
236
- PrettyOutput.print(
237
- "错误:未配置中心工具仓库(JARVIS_CENTRAL_TOOL_REPO)",
238
- OutputType.ERROR,
239
- )
240
- PrettyOutput.print("请在配置文件中设置中心工具仓库的Git地址", OutputType.INFO)
241
- raise typer.Exit(code=1)
242
-
243
- # 克隆或更新中心工具仓库
244
- central_repo_path = os.path.join(get_data_dir(), "central_tool_repo")
245
- if not os.path.exists(central_repo_path):
246
- PrettyOutput.print(f"正在克隆中心工具仓库...", OutputType.INFO)
247
- subprocess.run(["git", "clone", central_repo, central_repo_path], check=True)
248
- else:
249
- PrettyOutput.print(f"正在更新中心工具仓库...", OutputType.INFO)
250
- # 检查是否是空仓库
251
- try:
252
- # 先尝试获取远程分支信息
253
- result = subprocess.run(
254
- ["git", "ls-remote", "--heads", "origin"],
255
- cwd=central_repo_path,
256
- capture_output=True,
257
- text=True,
258
- check=True,
259
- )
260
- # 如果有远程分支,执行pull
261
- if result.stdout.strip():
262
- subprocess.run(["git", "pull"], cwd=central_repo_path, check=True)
263
- else:
264
- PrettyOutput.print(
265
- "中心工具仓库是空的,将初始化为新仓库", OutputType.INFO
266
- )
267
- except subprocess.CalledProcessError:
268
- # 如果命令失败,可能是网络问题或其他错误
269
- PrettyOutput.print("无法连接到远程仓库,将跳过更新", OutputType.WARNING)
270
-
271
- # 获取中心仓库中已有的工具文件名
272
- existing_tools = set()
273
- for filepath in glob.glob(os.path.join(central_repo_path, "*.py")):
274
- existing_tools.add(os.path.basename(filepath))
275
-
276
- # 只从数据目录的tools目录获取工具
277
- local_tools_dir = os.path.join(get_data_dir(), "tools")
278
- if not os.path.exists(local_tools_dir):
279
- PrettyOutput.print(
280
- f"本地工具目录不存在: {local_tools_dir}",
281
- OutputType.WARNING,
282
- )
283
- raise typer.Exit(code=0)
284
-
285
- # 收集本地工具文件(排除已存在的)
286
- tool_files = []
287
- for filepath in glob.glob(os.path.join(local_tools_dir, "*.py")):
288
- filename = os.path.basename(filepath)
289
- # 跳过__init__.py和已存在的文件
290
- if filename == "__init__.py" or filename in existing_tools:
291
- continue
292
-
293
- # 尝试获取工具名称(通过简单解析)
294
- tool_name = filename[:-3] # 移除.py后缀
295
- tool_files.append(
296
- {
297
- "path": filepath,
298
- "filename": filename,
299
- "tool_name": tool_name,
300
- }
301
- )
302
-
303
- if not tool_files:
304
- PrettyOutput.print(
305
- "没有找到新的工具文件(所有工具可能已存在于中心仓库)",
306
- OutputType.WARNING,
307
- )
308
- raise typer.Exit(code=0)
309
-
310
- # 显示可选的工具
311
- tool_list = ["\n可分享的工具(已排除中心仓库中已有的):"]
312
- for i, tool in enumerate(tool_files, 1):
313
- tool_list.append(f"[{i}] {tool['tool_name']} ({tool['filename']})")
314
-
315
- # 一次性打印所有工具
316
- PrettyOutput.print("\n".join(tool_list), OutputType.INFO)
317
-
318
- # 让用户选择要分享的工具
319
- while True:
320
- try:
321
- choice_str = prompt(
322
- "\n请选择要分享的工具编号(支持格式: 1,2,3,4-9,20 或 all):"
323
- ).strip()
324
- if choice_str == "0":
325
- raise typer.Exit(code=0)
326
-
327
- selected_tools = []
328
- if choice_str.lower() == "all":
329
- selected_tools = tool_files
330
- else:
331
- selected_indices = _parse_selection(choice_str, len(tool_files))
332
- if not selected_indices:
333
- PrettyOutput.print("无效的选择", OutputType.WARNING)
334
- continue
335
- selected_tools = [tool_files[i - 1] for i in selected_indices]
336
-
337
- # 确认操作
338
- share_list = [
339
- "\n将要分享以下工具到中心仓库(注意:文件将被移动而非复制):"
340
- ]
341
- for tool in selected_tools:
342
- share_list.append(f"- {tool['tool_name']} ({tool['filename']})")
343
- PrettyOutput.print("\n".join(share_list), OutputType.WARNING)
344
-
345
- if not user_confirm("确认移动这些工具到中心仓库吗?(原文件将被删除)"):
346
- continue
347
-
348
- # 移动选中的工具到中心仓库
349
- moved_list = []
350
- for tool in selected_tools:
351
- src_file = tool["path"]
352
- dst_file = os.path.join(central_repo_path, tool["filename"])
353
- shutil.move(src_file, dst_file) # 使用move而不是copy
354
- moved_list.append(f"已移动: {tool['tool_name']}")
355
-
356
- # 一次性显示所有移动结果
357
- if moved_list:
358
- PrettyOutput.print("\n".join(moved_list), OutputType.SUCCESS)
359
-
360
- # 提交并推送更改
361
- PrettyOutput.print("\n正在提交更改...", OutputType.INFO)
362
- subprocess.run(["git", "add", "."], cwd=central_repo_path, check=True)
363
-
364
- commit_msg = f"Add {len(selected_tools)} tool(s) from local collection"
365
- subprocess.run(
366
- ["git", "commit", "-m", commit_msg], cwd=central_repo_path, check=True
367
- )
368
-
369
- PrettyOutput.print("正在推送到远程仓库...", OutputType.INFO)
370
- # 检查是否需要设置上游分支(空仓库的情况)
371
- try:
372
- # 先尝试普通推送
373
- subprocess.run(["git", "push"], cwd=central_repo_path, check=True)
374
- except subprocess.CalledProcessError:
375
- # 如果失败,可能是空仓库,尝试设置上游分支
376
- try:
377
- subprocess.run(
378
- ["git", "push", "-u", "origin", "main"],
379
- cwd=central_repo_path,
380
- check=True,
381
- )
382
- except subprocess.CalledProcessError:
383
- # 如果main分支不存在,尝试master分支
384
- subprocess.run(
385
- ["git", "push", "-u", "origin", "master"],
386
- cwd=central_repo_path,
387
- check=True,
388
- )
389
-
390
- PrettyOutput.print("\n工具已成功分享到中心仓库!", OutputType.SUCCESS)
391
- PrettyOutput.print(
392
- f"原文件已从 {local_tools_dir} 移动到中心仓库", OutputType.INFO
393
- )
394
- break
395
-
396
- except ValueError:
397
- PrettyOutput.print("请输入有效的数字", OutputType.WARNING)
398
- except subprocess.CalledProcessError as e:
399
- PrettyOutput.print(f"Git操作失败: {str(e)}", OutputType.ERROR)
400
- raise typer.Exit(code=1)
401
- except Exception as e:
402
- PrettyOutput.print(f"分享工具时出错: {str(e)}", OutputType.ERROR)
403
- raise typer.Exit(code=1)
404
-
405
-
406
- def _handle_share_methodology(config_file: Optional[str] = None) -> None:
407
- """处理方法论分享功能"""
408
- from jarvis.jarvis_utils.config import (
409
- get_central_methodology_repo,
410
- get_methodology_dirs,
411
- get_data_dir,
412
- )
413
- import glob
414
- import json
415
- import shutil
416
-
417
- # 获取中心方法论仓库配置
418
- central_repo = get_central_methodology_repo()
419
- if not central_repo:
420
- PrettyOutput.print(
421
- "错误:未配置中心方法论仓库(JARVIS_CENTRAL_METHODOLOGY_REPO)",
422
- OutputType.ERROR,
423
- )
424
- PrettyOutput.print("请在配置文件中设置中心方法论仓库的Git地址", OutputType.INFO)
425
- raise typer.Exit(code=1)
426
-
427
- # 克隆或更新中心方法论仓库
428
- central_repo_path = os.path.join(get_data_dir(), "central_methodology_repo")
429
- if not os.path.exists(central_repo_path):
430
- PrettyOutput.print(f"正在克隆中心方法论仓库...", OutputType.INFO)
431
- subprocess.run(["git", "clone", central_repo, central_repo_path], check=True)
432
- else:
433
- PrettyOutput.print(f"正在更新中心方法论仓库...", OutputType.INFO)
434
- # 检查是否是空仓库
435
- try:
436
- # 先尝试获取远程分支信息
437
- result = subprocess.run(
438
- ["git", "ls-remote", "--heads", "origin"],
439
- cwd=central_repo_path,
440
- capture_output=True,
441
- text=True,
442
- check=True,
443
- )
444
- # 如果有远程分支,执行pull
445
- if result.stdout.strip():
446
- subprocess.run(["git", "pull"], cwd=central_repo_path, check=True)
447
- else:
448
- PrettyOutput.print(
449
- "中心方法论仓库是空的,将初始化为新仓库", OutputType.INFO
450
- )
451
- except subprocess.CalledProcessError:
452
- # 如果命令失败,可能是网络问题或其他错误
453
- PrettyOutput.print("无法连接到远程仓库,将跳过更新", OutputType.WARNING)
454
-
455
- # 获取中心仓库中已有的方法论
456
- existing_methodologies = {} # 改为字典,存储 problem_type -> content 的映射
457
- for filepath in glob.glob(os.path.join(central_repo_path, "*.json")):
458
- try:
459
- with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
460
- methodology = json.load(f)
461
- problem_type = methodology.get("problem_type", "")
462
- content = methodology.get("content", "")
463
- if problem_type and content:
464
- existing_methodologies[problem_type] = content
465
- except Exception:
466
- pass
467
-
468
- # 获取所有方法论目录
469
- from jarvis.jarvis_utils.methodology import _get_methodology_directory
470
-
471
- methodology_dirs = [_get_methodology_directory()] + get_methodology_dirs()
472
-
473
- # 收集所有方法论文件(排除中心仓库目录和已存在的方法论)
474
- all_methodologies = {}
475
- methodology_files = []
476
- seen_problem_types = set() # 用于去重
477
-
478
- for directory in set(methodology_dirs):
479
- # 跳过中心仓库目录
480
- if os.path.abspath(directory) == os.path.abspath(central_repo_path):
481
- continue
482
-
483
- if not os.path.isdir(directory):
484
- continue
485
-
486
- for filepath in glob.glob(os.path.join(directory, "*.json")):
487
- try:
488
- with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
489
- methodology = json.load(f)
490
- problem_type = methodology.get("problem_type", "")
491
- content = methodology.get("content", "")
492
- # 基于内容判断是否已存在于中心仓库
493
- is_duplicate = False
494
- if problem_type in existing_methodologies:
495
- # 如果problem_type相同,比较内容
496
- if (
497
- content.strip()
498
- == existing_methodologies[problem_type].strip()
499
- ):
500
- is_duplicate = True
501
-
502
- # 排除已存在于中心仓库的方法论(基于内容),以及本地重复的方法论
503
- if (
504
- problem_type
505
- and content
506
- and not is_duplicate
507
- and problem_type not in seen_problem_types
508
- ):
509
- methodology_files.append(
510
- {
511
- "path": filepath,
512
- "problem_type": problem_type,
513
- "directory": directory,
514
- }
515
- )
516
- all_methodologies[problem_type] = methodology
517
- seen_problem_types.add(problem_type)
518
- except Exception:
519
- pass
520
-
521
- if not methodology_files:
522
- PrettyOutput.print(
523
- "没有找到新的方法论文件(所有方法论可能已存在于中心仓库)",
524
- OutputType.WARNING,
525
- )
526
- raise typer.Exit(code=0)
527
-
528
- # 显示可选的方法论
529
- methodology_list = ["\n可分享的方法论(已排除中心仓库中已有的):"]
530
- for i, meth in enumerate(methodology_files, 1):
531
- dir_name = os.path.basename(meth["directory"])
532
- methodology_list.append(f"[{i}] {meth['problem_type']} (来自: {dir_name})")
533
-
534
- # 一次性打印所有方法论
535
- PrettyOutput.print("\n".join(methodology_list), OutputType.INFO)
536
-
537
- # 让用户选择要分享的方法论
538
- while True:
539
- try:
540
- choice_str = prompt(
541
- "\n请选择要分享的方法论编号(支持格式: 1,2,3,4-9,20 或 all):"
542
- ).strip()
543
- if choice_str == "0":
544
- raise typer.Exit(code=0)
545
-
546
- selected_methodologies = []
547
- if choice_str.lower() == "all":
548
- selected_methodologies = methodology_files
549
- else:
550
- selected_indices = _parse_selection(choice_str, len(methodology_files))
551
- if not selected_indices:
552
- PrettyOutput.print("无效的选择", OutputType.WARNING)
553
- continue
554
- selected_methodologies = [
555
- methodology_files[i - 1] for i in selected_indices
556
- ]
557
-
558
- # 确认操作
559
- share_list = ["\n将要分享以下方法论到中心仓库:"]
560
- for meth in selected_methodologies:
561
- share_list.append(f"- {meth['problem_type']}")
562
- PrettyOutput.print("\n".join(share_list), OutputType.INFO)
563
-
564
- if not user_confirm("确认分享这些方法论吗?"):
565
- continue
566
-
567
- # 复制选中的方法论到中心仓库
568
- copied_list = []
569
- for meth in selected_methodologies:
570
- src_file = meth["path"]
571
- dst_file = os.path.join(central_repo_path, os.path.basename(src_file))
572
- shutil.copy2(src_file, dst_file)
573
- copied_list.append(f"已复制: {meth['problem_type']}")
574
-
575
- # 一次性显示所有复制结果
576
- if copied_list:
577
- PrettyOutput.print("\n".join(copied_list), OutputType.SUCCESS)
578
-
579
- # 提交并推送更改
580
- PrettyOutput.print("\n正在提交更改...", OutputType.INFO)
581
- subprocess.run(["git", "add", "."], cwd=central_repo_path, check=True)
582
-
583
- commit_msg = f"Add {len(selected_methodologies)} methodology(ies) from local collection"
584
- subprocess.run(
585
- ["git", "commit", "-m", commit_msg], cwd=central_repo_path, check=True
586
- )
587
-
588
- PrettyOutput.print("正在推送到远程仓库...", OutputType.INFO)
589
- # 检查是否需要设置上游分支(空仓库的情况)
590
- try:
591
- # 先尝试普通推送
592
- subprocess.run(["git", "push"], cwd=central_repo_path, check=True)
593
- except subprocess.CalledProcessError:
594
- # 如果失败,可能是空仓库,尝试设置上游分支
595
- try:
596
- subprocess.run(
597
- ["git", "push", "-u", "origin", "main"],
598
- cwd=central_repo_path,
599
- check=True,
600
- )
601
- except subprocess.CalledProcessError:
602
- # 如果main分支不存在,尝试master分支
603
- subprocess.run(
604
- ["git", "push", "-u", "origin", "master"],
605
- cwd=central_repo_path,
606
- check=True,
607
- )
608
-
609
- PrettyOutput.print("\n方法论已成功分享到中心仓库!", OutputType.SUCCESS)
610
- break
611
-
612
- except ValueError:
613
- PrettyOutput.print("请输入有效的数字", OutputType.WARNING)
614
- except subprocess.CalledProcessError as e:
615
- PrettyOutput.print(f"Git操作失败: {str(e)}", OutputType.ERROR)
616
- raise typer.Exit(code=1)
617
- except Exception as e:
618
- PrettyOutput.print(f"分享方法论时出错: {str(e)}", OutputType.ERROR)
619
- raise typer.Exit(code=1)
620
-
621
-
622
17
  @app.callback(invoke_without_command=True)
623
18
  def run_cli(
624
19
  ctx: typer.Context,
@@ -633,6 +28,9 @@ def run_cli(
633
28
  model_group: Optional[str] = typer.Option(
634
29
  None, "--llm_group", help="使用的模型组,覆盖配置文件中的设置"
635
30
  ),
31
+ tool_group: Optional[str] = typer.Option(
32
+ None, "--tool_group", help="使用的工具组,覆盖配置文件中的设置"
33
+ ),
636
34
  config_file: Optional[str] = typer.Option(
637
35
  None, "-f", "--config", help="自定义配置文件路径"
638
36
  ),
@@ -653,27 +51,40 @@ def run_cli(
653
51
  if ctx.invoked_subcommand is not None:
654
52
  return
655
53
 
656
- _handle_edit_mode(edit, config_file)
54
+ # 处理配置文件编辑
55
+ if edit:
56
+ ConfigEditor.edit_config(config_file)
57
+ return
657
58
 
658
59
  # 处理方法论分享
659
60
  if share_methodology:
660
61
  init_env("", config_file=config_file) # 初始化配置但不显示欢迎信息
661
- _handle_share_methodology(config_file)
662
- raise typer.Exit(code=0)
62
+ manager = MethodologyShareManager()
63
+ manager.run()
64
+ return
663
65
 
664
66
  # 处理工具分享
665
67
  if share_tool:
666
68
  init_env("", config_file=config_file) # 初始化配置但不显示欢迎信息
667
- _handle_share_tool(config_file)
668
- raise typer.Exit(code=0)
69
+ manager = ToolShareManager()
70
+ manager.run()
71
+ return
669
72
 
73
+ # 初始化环境
670
74
  init_env(
671
75
  "欢迎使用 Jarvis AI 助手,您的智能助理已准备就绪!", config_file=config_file
672
76
  )
673
77
 
78
+ # 运行主流程
674
79
  try:
675
- agent = _initialize_agent(llm_type, model_group, restore_session)
676
- _get_and_run_task(agent, task)
80
+ agent_manager = AgentManager(
81
+ llm_type=llm_type,
82
+ model_group=model_group,
83
+ tool_group=tool_group,
84
+ restore_session=restore_session,
85
+ )
86
+ agent_manager.initialize()
87
+ agent_manager.run_task(task)
677
88
  except typer.Exit:
678
89
  raise
679
90
  except Exception as err: # pylint: disable=broad-except