jarvis-ai-assistant 0.2.2__py3-none-any.whl → 0.2.4__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 (39) hide show
  1. jarvis/__init__.py +1 -1
  2. jarvis/jarvis_agent/edit_file_handler.py +5 -0
  3. jarvis/jarvis_agent/jarvis.py +22 -25
  4. jarvis/jarvis_agent/main.py +6 -6
  5. jarvis/jarvis_agent/prompts.py +26 -4
  6. jarvis/jarvis_code_agent/code_agent.py +279 -11
  7. jarvis/jarvis_code_analysis/code_review.py +21 -19
  8. jarvis/jarvis_data/config_schema.json +86 -18
  9. jarvis/jarvis_git_squash/main.py +3 -3
  10. jarvis/jarvis_git_utils/git_commiter.py +32 -11
  11. jarvis/jarvis_mcp/sse_mcp_client.py +4 -6
  12. jarvis/jarvis_mcp/streamable_mcp_client.py +5 -9
  13. jarvis/jarvis_platform/tongyi.py +9 -9
  14. jarvis/jarvis_rag/cli.py +79 -23
  15. jarvis/jarvis_rag/query_rewriter.py +61 -12
  16. jarvis/jarvis_rag/rag_pipeline.py +143 -34
  17. jarvis/jarvis_rag/retriever.py +6 -6
  18. jarvis/jarvis_smart_shell/main.py +2 -2
  19. jarvis/jarvis_stats/__init__.py +13 -0
  20. jarvis/jarvis_stats/cli.py +337 -0
  21. jarvis/jarvis_stats/stats.py +433 -0
  22. jarvis/jarvis_stats/storage.py +329 -0
  23. jarvis/jarvis_stats/visualizer.py +443 -0
  24. jarvis/jarvis_tools/cli/main.py +84 -15
  25. jarvis/jarvis_tools/generate_new_tool.py +22 -1
  26. jarvis/jarvis_tools/registry.py +35 -16
  27. jarvis/jarvis_tools/search_web.py +3 -3
  28. jarvis/jarvis_tools/virtual_tty.py +315 -26
  29. jarvis/jarvis_utils/config.py +98 -11
  30. jarvis/jarvis_utils/git_utils.py +8 -16
  31. jarvis/jarvis_utils/globals.py +29 -8
  32. jarvis/jarvis_utils/input.py +114 -121
  33. jarvis/jarvis_utils/utils.py +213 -37
  34. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/METADATA +99 -9
  35. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/RECORD +39 -34
  36. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/entry_points.txt +2 -0
  37. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/WHEEL +0 -0
  38. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/licenses/LICENSE +0 -0
  39. {jarvis_ai_assistant-0.2.2.dist-info → jarvis_ai_assistant-0.2.4.dist-info}/top_level.txt +0 -0
@@ -9,9 +9,11 @@
9
9
  """
10
10
  import os
11
11
 
12
- # 全局变量:保存最后一条消息
13
- last_message: str = ""
14
- from typing import Any, Dict, Set
12
+ # 全局变量:保存消息历史
13
+ from typing import Any, Dict, Set, List
14
+
15
+ message_history: List[str] = []
16
+ MAX_HISTORY_SIZE = 50
15
17
 
16
18
  import colorama
17
19
  from rich.console import Console
@@ -169,13 +171,18 @@ def get_interrupt() -> int:
169
171
 
170
172
  def set_last_message(message: str) -> None:
171
173
  """
172
- 设置最后一条消息。
174
+ 将消息添加到历史记录中。
173
175
 
174
176
  参数:
175
177
  message: 要保存的消息
176
178
  """
177
- global last_message
178
- last_message = message
179
+ global message_history
180
+ if message:
181
+ # 避免重复添加
182
+ if not message_history or message_history[-1] != message:
183
+ message_history.append(message)
184
+ if len(message_history) > MAX_HISTORY_SIZE:
185
+ message_history.pop(0)
179
186
 
180
187
 
181
188
  def get_last_message() -> str:
@@ -183,6 +190,20 @@ def get_last_message() -> str:
183
190
  获取最后一条消息。
184
191
 
185
192
  返回:
186
- str: 最后一条消息
193
+ str: 最后一条消息,如果历史记录为空则返回空字符串
194
+ """
195
+ global message_history
196
+ if message_history:
197
+ return message_history[-1]
198
+ return ""
199
+
200
+
201
+ def get_message_history() -> List[str]:
202
+ """
203
+ 获取完整的消息历史记录。
204
+
205
+ 返回:
206
+ List[str]: 消息历史列表
187
207
  """
188
- return last_message
208
+ global message_history
209
+ return message_history
@@ -8,99 +8,73 @@
8
8
  - 带有模糊匹配的文件路径补全
9
9
  - 用于输入控制的自定义键绑定
10
10
  """
11
- from colorama import Fore # type: ignore
11
+ import os
12
+ from typing import Iterable
13
+
14
+ from colorama import Fore
12
15
  from colorama import Style as ColoramaStyle # type: ignore
13
16
  from fuzzywuzzy import process # type: ignore
14
17
  from prompt_toolkit import PromptSession # type: ignore
18
+ from prompt_toolkit.completion import CompleteEvent # type: ignore
15
19
  from prompt_toolkit.completion import (
16
- CompleteEvent,
17
20
  Completer,
18
- Completion, # type: ignore
21
+ Completion,
19
22
  PathCompleter,
20
- ) # type: ignore
23
+ )
21
24
  from prompt_toolkit.document import Document # type: ignore
22
25
  from prompt_toolkit.formatted_text import FormattedText # type: ignore
26
+ from prompt_toolkit.history import FileHistory # type: ignore
23
27
  from prompt_toolkit.key_binding import KeyBindings # type: ignore
24
28
  from prompt_toolkit.styles import Style as PromptStyle # type: ignore
25
29
 
26
- from jarvis.jarvis_utils.config import get_replace_map
30
+ from jarvis.jarvis_utils.config import get_data_dir, get_replace_map
27
31
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
28
32
  from jarvis.jarvis_utils.tag import ot
29
33
  from jarvis.jarvis_utils.utils import copy_to_clipboard
30
34
 
35
+ # Sentinel value to indicate that Ctrl+O was pressed
36
+ CTRL_O_SENTINEL = "__CTRL_O_PRESSED__"
37
+
31
38
 
32
39
  def get_single_line_input(tip: str) -> str:
33
40
  """
34
41
  获取支持历史记录的单行输入。
35
-
36
- 参数:
37
- tip: 要显示的提示信息
38
-
39
- 返回:
40
- str: 用户的输入
41
42
  """
42
43
  session: PromptSession = PromptSession(history=None)
43
- style = PromptStyle.from_dict(
44
- {
45
- "prompt": "ansicyan",
46
- }
47
- )
44
+ style = PromptStyle.from_dict({"prompt": "ansicyan"})
48
45
  return session.prompt(f"{tip}", style=style)
49
46
 
50
47
 
51
48
  class FileCompleter(Completer):
52
49
  """
53
50
  带有模糊匹配的文件路径自定义补全器。
54
-
55
- 属性:
56
- path_completer: 基础路径补全器
57
- max_suggestions: 显示的最大建议数量
58
- min_score: 建议的最小匹配分数
59
51
  """
60
52
 
61
53
  def __init__(self):
62
- """使用默认设置初始化文件补全器。"""
63
54
  self.path_completer = PathCompleter()
64
55
  self.max_suggestions = 10
65
56
  self.min_score = 10
66
57
  self.replace_map = get_replace_map()
67
58
 
68
- def get_completions(self, document: Document, _: CompleteEvent) -> Completion: # type: ignore
69
- """
70
- 生成带有模糊匹配的文件路径补全建议。
71
-
72
- 参数:
73
- document: 当前正在编辑的文档
74
- complete_event: 补全事件
75
-
76
- 生成:
77
- Completion: 建议的补全项
78
- """
59
+ def get_completions(
60
+ self, document: Document, _: CompleteEvent
61
+ ) -> Iterable[Completion]:
79
62
  text = document.text_before_cursor
80
63
  cursor_pos = document.cursor_position
81
- # 查找文本中的所有@位置
82
64
  at_positions = [i for i, char in enumerate(text) if char == "@"]
83
65
  if not at_positions:
84
66
  return
85
- # 获取最后一个@位置
86
67
  current_at_pos = at_positions[-1]
87
- # 如果光标不在最后一个@之后,则不补全
88
68
  if cursor_pos <= current_at_pos:
89
69
  return
90
- # 检查@之后是否有空格
91
70
  text_after_at = text[current_at_pos + 1 : cursor_pos]
92
71
  if " " in text_after_at:
93
72
  return
94
73
 
95
- # 获取当前@之后的文本
96
74
  file_path = text_after_at.strip()
97
- # 计算替换长度
98
75
  replace_length = len(text_after_at) + 1
99
76
 
100
- # 获取所有可能的补全项
101
77
  all_completions = []
102
-
103
- # 1. 添加特殊标记
104
78
  all_completions.extend(
105
79
  [(ot(tag), self._get_description(tag)) for tag in self.replace_map.keys()]
106
80
  )
@@ -114,7 +88,6 @@ class FileCompleter(Completer):
114
88
  ]
115
89
  )
116
90
 
117
- # 2. 添加文件列表
118
91
  try:
119
92
  import subprocess
120
93
 
@@ -135,9 +108,7 @@ class FileCompleter(Completer):
135
108
  except Exception:
136
109
  pass
137
110
 
138
- # 统一过滤和排序
139
111
  if file_path:
140
- # 使用模糊匹配过滤
141
112
  scored_items = process.extract(
142
113
  file_path,
143
114
  [item[0] for item in all_completions],
@@ -146,31 +117,25 @@ class FileCompleter(Completer):
146
117
  scored_items = [
147
118
  (item[0], item[1]) for item in scored_items if item[1] > self.min_score
148
119
  ]
149
- # 创建映射以便查找描述
150
120
  completion_map = {item[0]: item[1] for item in all_completions}
151
- # 生成补全项
152
121
  for text, score in scored_items:
153
- display_text = text
154
- if score < 100:
155
- display_text = f"{text} ({score}%)"
122
+ display_text = f"{text} ({score}%)" if score < 100 else text
156
123
  yield Completion(
157
124
  text=f"'{text}'",
158
125
  start_position=-replace_length,
159
126
  display=display_text,
160
127
  display_meta=completion_map.get(text, ""),
161
- ) # type: ignore
128
+ )
162
129
  else:
163
- # 没有输入时返回前max_suggestions个建议
164
130
  for text, desc in all_completions[: self.max_suggestions]:
165
131
  yield Completion(
166
132
  text=f"'{text}'",
167
133
  start_position=-replace_length,
168
134
  display=text,
169
135
  display_meta=desc,
170
- ) # type: ignore
136
+ )
171
137
 
172
138
  def _get_description(self, tag: str) -> str:
173
- """获取标记的描述信息"""
174
139
  if tag in self.replace_map:
175
140
  return (
176
141
  self.replace_map[tag].get("description", tag) + "(Append)"
@@ -181,15 +146,7 @@ class FileCompleter(Completer):
181
146
 
182
147
 
183
148
  def user_confirm(tip: str, default: bool = True) -> bool:
184
- """提示用户确认是/否问题
185
-
186
- 参数:
187
- tip: 显示给用户的消息
188
- default: 用户直接回车时的默认响应
189
-
190
- 返回:
191
- bool: 用户确认返回True,否则返回False
192
- """
149
+ """提示用户确认是/否问题"""
193
150
  try:
194
151
  suffix = "[Y/n]" if default else "[y/N]"
195
152
  ret = get_single_line_input(f"{tip} {suffix}: ")
@@ -198,28 +155,66 @@ def user_confirm(tip: str, default: bool = True) -> bool:
198
155
  return False
199
156
 
200
157
 
201
- def get_multiline_input(tip: str) -> str:
158
+ def _show_history_and_copy():
202
159
  """
203
- 获取带有增强补全和确认功能的多行输入。
160
+ Displays message history and handles copying to clipboard.
161
+ This function uses standard I/O and is safe to call outside a prompt session.
162
+ """
163
+ from jarvis.jarvis_utils.globals import get_message_history
164
+
165
+ history = get_message_history()
166
+ if not history:
167
+ PrettyOutput.print("没有可复制的消息", OutputType.INFO)
168
+ return
204
169
 
205
- 参数:
206
- tip: 要显示的提示信息
170
+ print("\n" + "=" * 20 + " 消息历史记录 " + "=" * 20)
171
+ for i, msg in enumerate(history):
172
+ cleaned_msg = msg.replace("\n", r"\n")
173
+ display_msg = (cleaned_msg[:70] + "...") if len(cleaned_msg) > 70 else cleaned_msg
174
+ print(f" {i + 1}: {display_msg.strip()}")
175
+ print("=" * 58 + "\n")
207
176
 
208
- 返回:
209
- str: 用户的输入,如果取消则返回空字符串
177
+ while True:
178
+ try:
179
+ prompt_text = f"{Fore.CYAN}请输入要复制的条目序号 (或输入c取消, 直接回车选择最后一条): {ColoramaStyle.RESET_ALL}"
180
+ choice_str = input(prompt_text)
181
+
182
+ if not choice_str: # User pressed Enter
183
+ if not history:
184
+ print("没有历史记录可供选择。")
185
+ break
186
+ choice = len(history) - 1
187
+ elif choice_str.lower() == "c":
188
+ print("已取消")
189
+ break
190
+ else:
191
+ choice = int(choice_str) - 1
192
+
193
+ if 0 <= choice < len(history):
194
+ selected_msg = history[choice]
195
+ copy_to_clipboard(selected_msg)
196
+ PrettyOutput.print(
197
+ f"已复制消息: {selected_msg[:70]}...", OutputType.SUCCESS
198
+ )
199
+ break
200
+ else:
201
+ print("无效的序号,请重试。")
202
+ except ValueError:
203
+ print("无效的输入,请输入数字。")
204
+ except (KeyboardInterrupt, EOFError):
205
+ print("\n操作取消")
206
+ break
207
+
208
+
209
+ def _get_multiline_input_internal(tip: str) -> str:
210
+ """
211
+ Internal function to get multiline input using prompt_toolkit.
212
+ Returns a sentinel value if Ctrl+O is pressed.
210
213
  """
211
- # 显示输入说明
212
- PrettyOutput.section(
213
- "用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 提交,Ctrl+O 复制最后一条消息,按 Ctrl+C 取消输入",
214
- OutputType.USER,
215
- )
216
- print(f"{Fore.GREEN}{tip}{ColoramaStyle.RESET_ALL}")
217
- # 配置键绑定
218
214
  bindings = KeyBindings()
219
215
 
220
216
  @bindings.add("enter")
221
217
  def _(event):
222
- """处理回车键以进行补全或换行。"""
223
218
  if event.current_buffer.complete_state:
224
219
  event.current_buffer.apply_completion(
225
220
  event.current_buffer.complete_state.current_completion
@@ -229,55 +224,53 @@ def get_multiline_input(tip: str) -> str:
229
224
 
230
225
  @bindings.add("c-j")
231
226
  def _(event):
232
- """处理Ctrl+J以提交输入。"""
233
227
  event.current_buffer.validate_and_handle()
234
228
 
235
229
  @bindings.add("c-o")
236
230
  def _(event):
237
- """处理Ctrl+O以复制最后一条消息到剪贴板。"""
238
- from jarvis.jarvis_utils.globals import get_last_message
239
-
240
- last_msg = get_last_message()
241
- if last_msg:
242
- print(f"{last_msg}")
243
- copy_to_clipboard(last_msg)
244
- else:
245
- PrettyOutput.print("没有可复制的消息", OutputType.INFO)
231
+ """Handle Ctrl+O by exiting the prompt and returning the sentinel value."""
232
+ event.app.exit(result=CTRL_O_SENTINEL)
233
+
234
+ style = PromptStyle.from_dict({"prompt": "ansicyan"})
235
+
236
+ history_dir = get_data_dir()
237
+ session: PromptSession = PromptSession(
238
+ history=FileHistory(os.path.join(history_dir, "multiline_input_history")),
239
+ completer=FileCompleter(),
240
+ key_bindings=bindings,
241
+ complete_while_typing=True,
242
+ multiline=True,
243
+ vi_mode=False,
244
+ mouse_support=False,
245
+ )
246
246
 
247
- event.app.invalidate()
247
+ print(f"{Fore.GREEN}{tip}{ColoramaStyle.RESET_ALL}")
248
+ prompt = FormattedText([("class:prompt", ">>> ")])
248
249
 
249
- # 配置提示会话
250
- style = PromptStyle.from_dict(
251
- {
252
- "prompt": "ansicyan",
253
- }
254
- )
255
250
  try:
256
- import os
257
-
258
- from prompt_toolkit.history import FileHistory # type: ignore
259
-
260
- from jarvis.jarvis_utils.config import get_data_dir
261
-
262
- # 获取数据目录路径
263
- history_dir = get_data_dir()
264
- # 初始化带历史记录的会话
265
- session: PromptSession = PromptSession(
266
- history=FileHistory(os.path.join(history_dir, "multiline_input_history")),
267
- completer=FileCompleter(),
268
- key_bindings=bindings,
269
- complete_while_typing=True,
270
- multiline=True,
271
- vi_mode=False,
272
- mouse_support=False,
273
- )
274
- prompt = FormattedText([("class:prompt", ">>> ")])
275
- # 获取输入
276
- text = session.prompt(
277
- prompt,
278
- style=style,
279
- ).strip()
280
- return text
281
- except KeyboardInterrupt:
282
- PrettyOutput.print("输入已取消", OutputType.INFO)
251
+ return session.prompt(prompt, style=style, pre_run=lambda: None).strip()
252
+ except (KeyboardInterrupt, EOFError):
283
253
  return ""
254
+
255
+
256
+ def get_multiline_input(tip: str) -> str:
257
+ """
258
+ 获取带有增强补全和确认功能的多行输入。
259
+ 此函数处理控制流,允许在不破坏终端状态的情况下处理历史记录复制。
260
+ """
261
+ PrettyOutput.section(
262
+ "用户输入 - 使用 @ 触发文件补全,Tab 选择补全项,Ctrl+J 提交,Ctrl+O 从历史记录中选择消息复制,按 Ctrl+C/D 取消输入",
263
+ OutputType.USER,
264
+ )
265
+
266
+ while True:
267
+ user_input = _get_multiline_input_internal(tip)
268
+
269
+ if user_input == CTRL_O_SENTINEL:
270
+ _show_history_and_copy()
271
+ tip = "请继续输入(或按Ctrl+J提交):"
272
+ continue
273
+ else:
274
+ if not user_input:
275
+ PrettyOutput.print("\n输入已取消", OutputType.INFO)
276
+ return user_input