jarvis-ai-assistant 0.2.7__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 +37 -398
  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 +37 -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 +79 -20
  25. jarvis/jarvis_tools/retrieve_memory.py +36 -8
  26. jarvis/jarvis_utils/clipboard.py +90 -0
  27. jarvis/jarvis_utils/config.py +64 -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 +196 -106
  33. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/METADATA +1 -1
  34. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/RECORD +38 -28
  35. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/WHEEL +0 -0
  36. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/entry_points.txt +0 -0
  37. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/licenses/LICENSE +0 -0
  38. {jarvis_ai_assistant-0.2.7.dist-info → jarvis_ai_assistant-0.3.0.dist-info}/top_level.txt +0 -0
@@ -9,40 +9,141 @@
9
9
  - 用于输入控制的自定义键绑定
10
10
  """
11
11
  import os
12
- from typing import Iterable
12
+ from typing import Iterable, List
13
13
 
14
14
  from colorama import Fore
15
- from colorama import Style as ColoramaStyle # type: ignore
16
- from fuzzywuzzy import process # type: ignore
17
- from prompt_toolkit import PromptSession # type: ignore
18
- from prompt_toolkit.completion import CompleteEvent # type: ignore
15
+ from colorama import Style as ColoramaStyle
16
+ from fuzzywuzzy import process
17
+ from prompt_toolkit import PromptSession
18
+ from prompt_toolkit.application import Application
19
+ from prompt_toolkit.completion import CompleteEvent
19
20
  from prompt_toolkit.completion import (
20
21
  Completer,
21
22
  Completion,
22
23
  PathCompleter,
23
24
  )
24
- from prompt_toolkit.document import Document # type: ignore
25
- from prompt_toolkit.formatted_text import FormattedText # type: ignore
26
- from prompt_toolkit.history import FileHistory # type: ignore
27
- from prompt_toolkit.key_binding import KeyBindings # type: ignore
28
- from prompt_toolkit.styles import Style as PromptStyle # type: ignore
29
-
25
+ from prompt_toolkit.document import Document
26
+ from prompt_toolkit.formatted_text import FormattedText
27
+ from prompt_toolkit.history import FileHistory
28
+ from prompt_toolkit.key_binding import KeyBindings
29
+ from prompt_toolkit.layout.containers import Window
30
+ from prompt_toolkit.layout.controls import FormattedTextControl
31
+ from prompt_toolkit.layout.layout import Layout
32
+ from prompt_toolkit.styles import Style as PromptStyle
33
+
34
+ from jarvis.jarvis_utils.clipboard import copy_to_clipboard
30
35
  from jarvis.jarvis_utils.config import get_data_dir, get_replace_map
36
+ from jarvis.jarvis_utils.globals import get_message_history
31
37
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
32
38
  from jarvis.jarvis_utils.tag import ot
33
- from jarvis.jarvis_utils.utils import copy_to_clipboard
34
39
 
35
40
  # Sentinel value to indicate that Ctrl+O was pressed
36
41
  CTRL_O_SENTINEL = "__CTRL_O_PRESSED__"
37
42
 
38
43
 
39
- def get_single_line_input(tip: str) -> str:
44
+ def get_single_line_input(tip: str, default: str = "") -> str:
40
45
  """
41
46
  获取支持历史记录的单行输入。
42
47
  """
43
48
  session: PromptSession = PromptSession(history=None)
44
49
  style = PromptStyle.from_dict({"prompt": "ansicyan"})
45
- return session.prompt(f"{tip}", style=style)
50
+ return session.prompt(f"{tip}", default=default, style=style)
51
+
52
+
53
+ def get_choice(tip: str, choices: List[str]) -> str:
54
+ """
55
+ 提供一个可滚动的选择列表供用户选择。
56
+ """
57
+ if not choices:
58
+ raise ValueError("Choices cannot be empty.")
59
+
60
+ try:
61
+ terminal_height = os.get_terminal_size().lines
62
+ except OSError:
63
+ terminal_height = 25 # 如果无法确定终端大小,则使用默认高度
64
+
65
+ # 为提示和缓冲区保留行
66
+ max_visible_choices = max(5, terminal_height - 4)
67
+
68
+ bindings = KeyBindings()
69
+ selected_index = 0
70
+ start_index = 0
71
+
72
+ @bindings.add("up")
73
+ def _(event):
74
+ nonlocal selected_index, start_index
75
+ selected_index = (selected_index - 1 + len(choices)) % len(choices)
76
+ if selected_index < start_index:
77
+ start_index = selected_index
78
+ elif selected_index == len(choices) - 1: # 支持从第一项上翻到最后一项时滚动
79
+ start_index = max(0, len(choices) - max_visible_choices)
80
+ event.app.invalidate()
81
+
82
+ @bindings.add("down")
83
+ def _(event):
84
+ nonlocal selected_index, start_index
85
+ selected_index = (selected_index + 1) % len(choices)
86
+ if selected_index >= start_index + max_visible_choices:
87
+ start_index = selected_index - max_visible_choices + 1
88
+ elif selected_index == 0: # 支持从最后一项下翻到第一项时滚动
89
+ start_index = 0
90
+ event.app.invalidate()
91
+
92
+ @bindings.add("enter")
93
+ def _(event):
94
+ event.app.exit(result=choices[selected_index])
95
+
96
+ def get_prompt_tokens():
97
+ tokens = [("class:question", f"{tip} (使用上下箭头选择, Enter确认)\n")]
98
+
99
+ end_index = min(start_index + max_visible_choices, len(choices))
100
+ visible_choices_slice = choices[start_index:end_index]
101
+
102
+ if start_index > 0:
103
+ tokens.append(("class:indicator", " ... (更多选项在上方) ...\n"))
104
+
105
+ for i, choice in enumerate(visible_choices_slice, start=start_index):
106
+ if i == selected_index:
107
+ tokens.append(("class:selected", f"> {choice}\n"))
108
+ else:
109
+ tokens.append(("", f" {choice}\n"))
110
+
111
+ if end_index < len(choices):
112
+ tokens.append(("class:indicator", " ... (更多选项在下方) ...\n"))
113
+
114
+ return FormattedText(tokens)
115
+
116
+ style = PromptStyle.from_dict(
117
+ {
118
+ "question": "bold",
119
+ "selected": "bg:#696969 #ffffff",
120
+ "indicator": "fg:gray",
121
+ }
122
+ )
123
+
124
+ layout = Layout(
125
+ container=Window(
126
+ content=FormattedTextControl(
127
+ text=get_prompt_tokens,
128
+ focusable=True,
129
+ key_bindings=bindings,
130
+ )
131
+ )
132
+ )
133
+
134
+ app: Application = Application(
135
+ layout=layout,
136
+ key_bindings=bindings,
137
+ style=style,
138
+ mouse_support=True,
139
+ full_screen=True,
140
+ )
141
+
142
+ try:
143
+ result = app.run()
144
+ return result if result is not None else ""
145
+ except (KeyboardInterrupt, EOFError):
146
+ return ""
46
147
 
47
148
 
48
149
  class FileCompleter(Completer):
@@ -160,7 +261,6 @@ def _show_history_and_copy():
160
261
  Displays message history and handles copying to clipboard.
161
262
  This function uses standard I/O and is safe to call outside a prompt session.
162
263
  """
163
- from jarvis.jarvis_utils.globals import get_message_history
164
264
 
165
265
  history = get_message_history()
166
266
  if not history:
@@ -170,7 +270,9 @@ def _show_history_and_copy():
170
270
  print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20)
171
271
  for i, msg in enumerate(history):
172
272
  cleaned_msg = msg.replace("\n", r"\n")
173
- display_msg = (cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
273
+ display_msg = (
274
+ (cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
275
+ )
174
276
  print(f" {i + 1}: {display_msg.strip()}")
175
277
  print("=" * 58 + "\n")
176
278
 
@@ -19,10 +19,12 @@ from jarvis.jarvis_utils.config import (
19
19
  get_data_dir,
20
20
  get_methodology_dirs,
21
21
  get_central_methodology_repo,
22
+ get_max_input_token_count,
22
23
  )
23
24
  from jarvis.jarvis_utils.globals import get_agent, current_agent_name
24
25
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
25
26
  from jarvis.jarvis_utils.utils import is_context_overflow, daily_check_git_updates
27
+ from jarvis.jarvis_utils.embedding import get_context_token_count
26
28
 
27
29
 
28
30
  def _get_methodology_directory() -> str:
@@ -273,14 +275,16 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
273
275
  if not selected_methodologies:
274
276
  return "没有历史方法论可参考"
275
277
 
278
+ # 获取最大输入token数的2/3作为方法论的token限制
279
+ max_input_tokens = get_max_input_token_count()
280
+ methodology_token_limit = int(max_input_tokens * 2 / 3)
281
+
276
282
  # 步骤3:将选择出来的方法论内容提供给大模型生成步骤
277
- final_prompt = f"""以下是与用户需求相关的方法论内容:
283
+ # 首先构建基础提示词部分
284
+ base_prompt = f"""以下是与用户需求相关的方法论内容:
278
285
 
279
286
  """
280
- for problem_type, content in selected_methodologies.items():
281
- final_prompt += f"## {problem_type}\n\n{content}\n\n---\n\n"
282
-
283
- final_prompt += f"""以下是所有可用的工具内容:
287
+ suffix_prompt = f"""以下是所有可用的工具内容:
284
288
 
285
289
  {prompt}
286
290
 
@@ -300,6 +304,45 @@ def load_methodology(user_input: str, tool_registery: Optional[Any] = None) -> s
300
304
  除以上要求外,不要输出任何内容
301
305
  """
302
306
 
307
+ # 计算基础部分的token数
308
+ base_tokens = get_context_token_count(base_prompt + suffix_prompt)
309
+ available_tokens = methodology_token_limit - base_tokens
310
+
311
+ # 基于token限制筛选方法论内容
312
+ final_prompt = base_prompt
313
+ selected_count = 0
314
+ total_methodology_tokens = 0
315
+
316
+ for problem_type, content in selected_methodologies.items():
317
+ methodology_text = f"## {problem_type}\n\n{content}\n\n---\n\n"
318
+ methodology_tokens = get_context_token_count(methodology_text)
319
+
320
+ # 检查是否会超过token限制
321
+ if total_methodology_tokens + methodology_tokens > available_tokens:
322
+ PrettyOutput.print(
323
+ f"达到方法论token限制 ({total_methodology_tokens}/{available_tokens}),停止加载更多方法论",
324
+ OutputType.INFO,
325
+ )
326
+ break
327
+
328
+ final_prompt += methodology_text
329
+ total_methodology_tokens += methodology_tokens
330
+ selected_count += 1
331
+
332
+ # 如果一个方法论都没有加载成功
333
+ if selected_count == 0:
334
+ PrettyOutput.print(
335
+ "警告:由于token限制,无法加载任何方法论内容", OutputType.WARNING
336
+ )
337
+ return "没有历史方法论可参考"
338
+
339
+ final_prompt += suffix_prompt
340
+
341
+ PrettyOutput.print(
342
+ f"成功加载 {selected_count} 个方法论,总token数: {total_methodology_tokens}",
343
+ OutputType.INFO,
344
+ )
345
+
303
346
  # 如果内容不大,直接使用chat_until_success
304
347
  return platform.chat_until_success(final_prompt)
305
348
 
@@ -330,11 +330,18 @@ def _show_usage_stats() -> None:
330
330
  if total_changes > 0:
331
331
  parts.append(f"代码修改 {total_changes:,} 次")
332
332
  if total_lines_modified > 0:
333
- parts.append(f"代码行数 {total_lines_modified:,} 行")
333
+ parts.append(f"修改代码行数 {total_lines_modified:,} 行")
334
334
 
335
335
  if parts:
336
336
  summary_content.append(f"📈 总计: {', '.join(parts)}")
337
337
 
338
+ # 添加代码采纳率显示
339
+ adoption_metrics = categorized_stats["adoption"]["metrics"]
340
+ if "adoption_rate" in adoption_metrics:
341
+ summary_content.append(
342
+ f"✅ 代码采纳率: {adoption_metrics['adoption_rate']}"
343
+ )
344
+
338
345
  # 计算节省的时间
339
346
  time_saved_seconds = 0
340
347
  tool_stats = categorized_stats["tool"]["metrics"]
@@ -484,6 +491,153 @@ def init_env(welcome_str: str, config_file: Optional[str] = None) -> None:
484
491
  sys.exit(0)
485
492
 
486
493
 
494
+ def _interactive_config_setup(config_file_path: Path):
495
+ """交互式配置引导"""
496
+ from jarvis.jarvis_platform.registry import PlatformRegistry
497
+ from jarvis.jarvis_utils.input import (
498
+ get_choice,
499
+ get_single_line_input as get_input,
500
+ user_confirm as get_yes_no,
501
+ )
502
+
503
+ PrettyOutput.print(
504
+ "欢迎使用 Jarvis!未找到配置文件,现在开始引导配置。", OutputType.INFO
505
+ )
506
+
507
+ # 1. 选择平台
508
+ registry = PlatformRegistry.get_global_platform_registry()
509
+ platforms = registry.get_available_platforms()
510
+ platform_name = get_choice("请选择您要使用的AI平台", platforms)
511
+
512
+ # 2. 配置环境变量
513
+ platform_class = registry.platforms.get(platform_name)
514
+ if not platform_class:
515
+ PrettyOutput.print(f"平台 '{platform_name}' 加载失败。", OutputType.ERROR)
516
+ sys.exit(1)
517
+
518
+ env_vars = {}
519
+ required_keys = platform_class.get_required_env_keys()
520
+ defaults = platform_class.get_env_defaults()
521
+ if required_keys:
522
+ PrettyOutput.print(
523
+ f"请输入 {platform_name} 平台所需的配置信息:", OutputType.INFO
524
+ )
525
+ for key in required_keys:
526
+ default_value = defaults.get(key, "")
527
+ prompt_text = f" - {key}"
528
+ if default_value:
529
+ prompt_text += f" (默认: {default_value})"
530
+ prompt_text += ": "
531
+
532
+ value = get_input(prompt_text, default=default_value)
533
+ env_vars[key] = value
534
+ os.environ[key] = value # 立即设置环境变量以便后续测试
535
+
536
+ # 3. 选择模型
537
+ try:
538
+ platform_instance = registry.create_platform(platform_name)
539
+ if not platform_instance:
540
+ PrettyOutput.print(f"无法创建平台 '{platform_name}'。", OutputType.ERROR)
541
+ sys.exit(1)
542
+
543
+ model_list_tuples = platform_instance.get_model_list()
544
+ model_choices = [f"{name} ({desc})" for name, desc in model_list_tuples]
545
+ model_display_name = get_choice("请选择要使用的模型", model_choices)
546
+
547
+ # 从显示名称反向查找模型ID
548
+ selected_index = model_choices.index(model_display_name)
549
+ model_name, _ = model_list_tuples[selected_index]
550
+
551
+ except Exception:
552
+ PrettyOutput.print("获取模型列表失败", OutputType.ERROR)
553
+ if not get_yes_no("无法获取模型列表,是否继续配置?"):
554
+ sys.exit(1)
555
+ model_name = get_input("请输入模型名称:")
556
+
557
+ # 4. 测试配置
558
+ PrettyOutput.print("正在测试配置...", OutputType.INFO)
559
+ test_passed = False
560
+ try:
561
+ platform_instance = registry.create_platform(platform_name)
562
+ if platform_instance:
563
+ platform_instance.set_model_name(model_name)
564
+ response_generator = platform_instance.chat("hello")
565
+ response = "".join(response_generator)
566
+ if response:
567
+ PrettyOutput.print(
568
+ f"测试成功,模型响应: {response}", OutputType.SUCCESS
569
+ )
570
+ test_passed = True
571
+ else:
572
+ PrettyOutput.print("测试失败,模型没有响应。", OutputType.ERROR)
573
+ else:
574
+ PrettyOutput.print("测试失败,无法创建平台实例。", OutputType.ERROR)
575
+ except Exception:
576
+ PrettyOutput.print("测试失败", OutputType.ERROR)
577
+
578
+ # 5. 生成并保存配置
579
+ config_data = {
580
+ "ENV": env_vars,
581
+ "JARVIS_PLATFORM": platform_name,
582
+ "JARVIS_THINKING_PLATFORM": platform_name,
583
+ "JARVIS_MODEL": model_name,
584
+ "JARVIS_THINKING_MODEL": model_name,
585
+ }
586
+
587
+ if test_passed:
588
+ PrettyOutput.print("配置已测试通过,将为您生成配置文件。", OutputType.SUCCESS)
589
+ else:
590
+ if not get_yes_no("配置测试失败,您确定要保存这个配置吗?"):
591
+ PrettyOutput.print("配置未保存。", OutputType.INFO)
592
+ sys.exit(0)
593
+
594
+ try:
595
+ schema_path = (
596
+ Path(__file__).parent.parent / "jarvis_data" / "config_schema.json"
597
+ )
598
+ if schema_path.exists():
599
+ config_file_path.parent.mkdir(parents=True, exist_ok=True)
600
+ # 使用现有的函数生成默认结构,然后覆盖引导配置
601
+ generate_default_config(str(schema_path.absolute()), str(config_file_path))
602
+
603
+ # 读取刚生成的默认配置
604
+ with open(config_file_path, "r", encoding="utf-8") as f:
605
+ content = f.read()
606
+ default_config = yaml.safe_load(
607
+ content.split("\n", 1)[1]
608
+ ) # 跳过 schema 行
609
+
610
+ # 合并用户配置
611
+ if default_config is None:
612
+ default_config = {}
613
+ default_config.update(config_data)
614
+
615
+ # 写回合并后的配置
616
+ final_content = (
617
+ f"# yaml-language-server: $schema={str(schema_path.absolute())}\n"
618
+ )
619
+ final_content += yaml.dump(
620
+ default_config, allow_unicode=True, sort_keys=False
621
+ )
622
+ with open(config_file_path, "w", encoding="utf-8") as f:
623
+ f.write(final_content)
624
+
625
+ PrettyOutput.print(
626
+ f"配置文件已生成: {config_file_path}", OutputType.SUCCESS
627
+ )
628
+ PrettyOutput.print("配置完成,请重新启动Jarvis。", OutputType.INFO)
629
+ sys.exit(0)
630
+ else:
631
+ PrettyOutput.print(
632
+ "未找到config schema,无法生成配置文件。", OutputType.ERROR
633
+ )
634
+ sys.exit(1)
635
+
636
+ except Exception:
637
+ PrettyOutput.print("生成配置文件失败", OutputType.ERROR)
638
+ sys.exit(1)
639
+
640
+
487
641
  def load_config():
488
642
  config_file = g_config_file
489
643
  config_file_path = (
@@ -498,22 +652,7 @@ def load_config():
498
652
  if old_config_file.exists(): # 旧的配置文件存在
499
653
  _read_old_config_file(old_config_file)
500
654
  else:
501
- # 生成默认配置文件
502
- schema_path = (
503
- Path(__file__).parent.parent / "jarvis_data" / "config_schema.json"
504
- )
505
- if schema_path.exists():
506
- try:
507
- config_file_path.parent.mkdir(parents=True, exist_ok=True)
508
- generate_default_config(
509
- str(schema_path.absolute()), str(config_file_path)
510
- )
511
- PrettyOutput.print(
512
- f"已生成默认配置文件: {config_file_path}", OutputType.INFO
513
- )
514
- sys.exit(0)
515
- except Exception as e:
516
- PrettyOutput.print(f"生成默认配置文件失败: {e}", OutputType.ERROR)
655
+ _interactive_config_setup(config_file_path)
517
656
  else:
518
657
  _load_and_process_config(str(config_file_path.parent), str(config_file_path))
519
658
 
@@ -521,6 +660,9 @@ def load_config():
521
660
  from typing import Tuple
522
661
 
523
662
 
663
+ from typing import Tuple
664
+
665
+
524
666
  def _load_config_file(config_file: str) -> Tuple[str, dict]:
525
667
  """读取并解析YAML格式的配置文件
526
668
 
@@ -587,10 +729,24 @@ def _load_and_process_config(jarvis_dir: str, config_file: str) -> None:
587
729
  jarvis_dir: Jarvis数据目录路径
588
730
  config_file: 配置文件路径
589
731
  """
590
- content, config_data = _load_config_file(config_file)
591
- _ensure_schema_declaration(jarvis_dir, config_file, content, config_data)
592
- set_global_env_data(config_data)
593
- _process_env_variables(config_data)
732
+ from jarvis.jarvis_utils.input import user_confirm as get_yes_no
733
+
734
+ try:
735
+ content, config_data = _load_config_file(config_file)
736
+ _ensure_schema_declaration(jarvis_dir, config_file, content, config_data)
737
+ set_global_env_data(config_data)
738
+ _process_env_variables(config_data)
739
+ except Exception:
740
+ PrettyOutput.print("加载配置文件失败", OutputType.ERROR)
741
+ if get_yes_no("配置文件格式错误,是否删除并重新配置?"):
742
+ try:
743
+ os.remove(config_file)
744
+ PrettyOutput.print(
745
+ "已删除损坏的配置文件,请重启Jarvis以重新配置。", OutputType.SUCCESS
746
+ )
747
+ except Exception:
748
+ PrettyOutput.print("删除配置文件失败", OutputType.ERROR)
749
+ sys.exit(1)
594
750
 
595
751
 
596
752
  def generate_default_config(schema_path: str, output_path: str) -> None:
@@ -804,91 +960,6 @@ def get_loc_stats() -> str:
804
960
  return ""
805
961
 
806
962
 
807
- def copy_to_clipboard(text: str) -> None:
808
- """将文本复制到剪贴板,支持Windows、macOS和Linux
809
-
810
- 参数:
811
- text: 要复制的文本
812
- """
813
- print("--- 剪贴板内容开始 ---")
814
- print(text)
815
- print("--- 剪贴板内容结束 ---")
816
-
817
- system = platform.system()
818
-
819
- # Windows系统
820
- if system == "Windows":
821
- try:
822
- # 使用Windows的clip命令
823
- process = subprocess.Popen(
824
- ["clip"],
825
- stdin=subprocess.PIPE,
826
- stdout=subprocess.DEVNULL,
827
- stderr=subprocess.DEVNULL,
828
- shell=True,
829
- )
830
- if process.stdin:
831
- process.stdin.write(text.encode("utf-8"))
832
- process.stdin.close()
833
- return
834
- except Exception as e:
835
- PrettyOutput.print(f"使用Windows clip命令时出错: {e}", OutputType.WARNING)
836
-
837
- # macOS系统
838
- elif system == "Darwin":
839
- try:
840
- process = subprocess.Popen(
841
- ["pbcopy"],
842
- stdin=subprocess.PIPE,
843
- stdout=subprocess.DEVNULL,
844
- stderr=subprocess.DEVNULL,
845
- )
846
- if process.stdin:
847
- process.stdin.write(text.encode("utf-8"))
848
- process.stdin.close()
849
- return
850
- except Exception as e:
851
- PrettyOutput.print(f"使用macOS pbcopy命令时出错: {e}", OutputType.WARNING)
852
-
853
- # Linux系统
854
- else:
855
- # 尝试使用 xsel
856
- try:
857
- process = subprocess.Popen(
858
- ["xsel", "-b", "-i"],
859
- stdin=subprocess.PIPE,
860
- stdout=subprocess.DEVNULL,
861
- stderr=subprocess.DEVNULL,
862
- )
863
- if process.stdin:
864
- process.stdin.write(text.encode("utf-8"))
865
- process.stdin.close()
866
- return
867
- except FileNotFoundError:
868
- pass # xsel 未安装,继续尝试下一个
869
- except Exception as e:
870
- PrettyOutput.print(f"使用xsel时出错: {e}", OutputType.WARNING)
871
-
872
- # 尝试使用 xclip
873
- try:
874
- process = subprocess.Popen(
875
- ["xclip", "-selection", "clipboard"],
876
- stdin=subprocess.PIPE,
877
- stdout=subprocess.DEVNULL,
878
- stderr=subprocess.DEVNULL,
879
- )
880
- if process.stdin:
881
- process.stdin.write(text.encode("utf-8"))
882
- process.stdin.close()
883
- return
884
- except FileNotFoundError:
885
- PrettyOutput.print(
886
- "xsel 和 xclip 均未安装, 无法复制到剪贴板", OutputType.WARNING
887
- )
888
- except Exception as e:
889
- PrettyOutput.print(f"使用xclip时出错: {e}", OutputType.WARNING)
890
-
891
-
892
963
  def _pull_git_repo(repo_path: Path, repo_type: str):
893
964
  """对指定的git仓库执行git pull操作,并根据commit hash判断是否有更新。"""
894
965
  git_dir = repo_path / ".git"
@@ -946,6 +1017,25 @@ def _pull_git_repo(repo_path: Path, repo_type: str):
946
1017
  )
947
1018
  before_hash = before_hash_result.stdout.strip()
948
1019
 
1020
+ # 检查是否是空仓库
1021
+ ls_remote_result = subprocess.run(
1022
+ ["git", "ls-remote", "--heads", "origin"],
1023
+ cwd=repo_path,
1024
+ capture_output=True,
1025
+ text=True,
1026
+ encoding="utf-8",
1027
+ errors="replace",
1028
+ check=True,
1029
+ timeout=10,
1030
+ )
1031
+
1032
+ if not ls_remote_result.stdout.strip():
1033
+ PrettyOutput.print(
1034
+ f"{repo_type}库 '{repo_path.name}' 的远程仓库是空的,跳过更新。",
1035
+ OutputType.INFO,
1036
+ )
1037
+ return
1038
+
949
1039
  # 执行 git pull
950
1040
  pull_result = subprocess.run(
951
1041
  ["git", "pull"],
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.2.7
3
+ Version: 0.3.0
4
4
  Summary: Jarvis: An AI assistant that uses tools to interact with the system
5
5
  Home-page: https://github.com/skyfireitdiy/Jarvis
6
6
  Author: skyfire