jarvis-ai-assistant 0.3.19__py3-none-any.whl → 0.3.20__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.
jarvis/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  """Jarvis AI Assistant"""
3
3
 
4
- __version__ = "0.3.19"
4
+ __version__ = "0.3.20"
@@ -223,6 +223,7 @@ class Agent:
223
223
  use_analysis: Optional[bool] = None,
224
224
  force_save_memory: Optional[bool] = None,
225
225
  files: Optional[List[str]] = None,
226
+ confirm_callback: Optional[Callable[[str, bool], bool]] = None,
226
227
  ):
227
228
  """初始化Jarvis Agent实例
228
229
 
@@ -241,6 +242,7 @@ class Agent:
241
242
  use_methodology: 是否使用方法论
242
243
  use_analysis: 是否使用任务分析
243
244
  force_save_memory: 是否强制保存记忆
245
+ confirm_callback: 用户确认回调函数,签名为 (tip: str, default: bool) -> bool;默认使用CLI的user_confirm
244
246
  """
245
247
  # 基础属性初始化
246
248
  self.files = files or []
@@ -254,6 +256,11 @@ class Agent:
254
256
  self.user_data: Dict[str, Any] = {}
255
257
  self.after_tool_call_cb: Optional[Callable[[Agent], None]] = None
256
258
 
259
+ # 用户确认回调:默认使用 CLI 的 user_confirm,可由外部注入以支持 TUI/GUI
260
+ self.user_confirm: Callable[[str, bool], bool] = (
261
+ confirm_callback or user_confirm # type: ignore[assignment]
262
+ )
263
+
257
264
  # 初始化模型和会话
258
265
  self._init_model(llm_type, model_group)
259
266
  self._init_session()
@@ -811,7 +818,7 @@ class Agent:
811
818
  return self._complete_task(auto_completed=False)
812
819
 
813
820
  if any(handler.can_handle(current_response) for handler in self.output_handler):
814
- if user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
821
+ if self.user_confirm("检测到有工具调用,是否继续处理工具调用?", True):
815
822
  self.session.prompt = f"被用户中断,用户补充信息为:{user_input}\n\n用户同意继续工具调用。"
816
823
  return None # 继续执行工具调用
817
824
  else:
@@ -905,7 +912,7 @@ class Agent:
905
912
  f"并且存在3个以上标签重叠的记忆。\n"
906
913
  f"是否立即整理记忆库以优化性能和相关性?"
907
914
  )
908
- if user_confirm(prompt, default=True):
915
+ if self.user_confirm(prompt, True):
909
916
  PrettyOutput.print(
910
917
  f"正在开始整理 '{scope_name}' ({memory_type}) 记忆库...",
911
918
  OutputType.INFO,
@@ -40,7 +40,7 @@ class ConfigEditor:
40
40
 
41
41
  if editor:
42
42
  try:
43
- subprocess.run([editor, str(config_file_path)], check=True)
43
+ subprocess.run([editor, str(config_file_path)], check=True, shell=(platform.system() == "Windows"))
44
44
  raise typer.Exit(code=0)
45
45
  except (subprocess.CalledProcessError, FileNotFoundError) as e:
46
46
  PrettyOutput.print(f"Failed to open editor: {e}", OutputType.ERROR)
@@ -11,9 +11,24 @@ def shell_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
11
11
  if len(cmdline) == 0:
12
12
  return user_input, False
13
13
  else:
14
- script = "\n".join([c[1:] for c in cmdline])
14
+ marker = "# JARVIS-NOCONFIRM"
15
+
16
+ def _clean(line: str) -> str:
17
+ s = line[1:] # remove leading '!'
18
+ # strip no-confirm marker if present
19
+ idx = s.find(marker)
20
+ if idx != -1:
21
+ s = s[:idx]
22
+ return s.rstrip()
23
+
24
+ # Build script while stripping the no-confirm marker from each line
25
+ script = "\n".join([_clean(c) for c in cmdline])
15
26
  PrettyOutput.print(script, OutputType.CODE, lang="bash")
16
- if user_confirm(f"是否要执行以上shell脚本?", default=True):
27
+
28
+ # If any line contains the no-confirm marker, skip the pre-execution confirmation
29
+ no_confirm = any(marker in c for c in cmdline)
30
+
31
+ if no_confirm or user_confirm(f"是否要执行以上shell脚本?", default=True):
17
32
  from jarvis.jarvis_tools.registry import ToolRegistry
18
33
 
19
34
  output = ToolRegistry().handle_tool_calls(
@@ -50,10 +50,6 @@ class AI8Model(BasePlatform):
50
50
  }
51
51
 
52
52
  self.model_name = os.getenv("JARVIS_MODEL") or "deepseek-chat"
53
- if self.model_name not in self.get_available_models():
54
- PrettyOutput.print(
55
- f"警告: 选择的模型 {self.model_name} 不在可用列表中", OutputType.WARNING
56
- )
57
53
 
58
54
  def set_model_name(self, model_name: str):
59
55
  """Set model name"""
@@ -134,11 +134,11 @@ class BasePlatform(ABC):
134
134
  with Live(panel, refresh_per_second=10, transient=False) as live:
135
135
  for s in self.chat(message):
136
136
  response += s
137
- if is_immediate_abort() and get_interrupt():
138
- return response
139
137
  text_content.append(s, style="bright_white")
140
138
  panel.subtitle = "[yellow]正在回答... (按 Ctrl+C 中断)[/yellow]"
141
139
  live.update(panel)
140
+ if is_immediate_abort() and get_interrupt():
141
+ return response
142
142
  end_time = time.time()
143
143
  duration = end_time - start_time
144
144
  panel.subtitle = (
@@ -108,7 +108,7 @@ def start_service(
108
108
  OutputType.INFO,
109
109
  )
110
110
 
111
- PrettyOutput.print("Available platforms:", OutputType.INFO)
111
+
112
112
 
113
113
  # Platform and model cache
114
114
  platform_instances: Dict[str, Any] = {}
@@ -178,7 +178,7 @@ def start_service(
178
178
  }
179
179
  )
180
180
  except Exception as exc:
181
- print(f"Error getting models for {default_platform}: {str(exc)}")
181
+ PrettyOutput.print(f"Error getting models for {default_platform}: {str(exc)}", OutputType.ERROR)
182
182
 
183
183
  # Return model list
184
184
  return {"object": "list", "data": model_list}
@@ -24,7 +24,7 @@ Example:
24
24
 
25
25
  def execute_command(command: str, should_run: bool) -> None:
26
26
  """Print command without execution"""
27
- print(command)
27
+ PrettyOutput.print(command, OutputType.CODE, lang="bash")
28
28
  if should_run:
29
29
  os.system(command)
30
30
 
@@ -16,6 +16,7 @@ from pathlib import Path
16
16
  from .stats import StatsManager
17
17
  from jarvis.jarvis_utils.utils import init_env
18
18
  from jarvis.jarvis_utils.config import get_data_dir
19
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
19
20
 
20
21
  app = typer.Typer(help="Jarvis 统计模块命令行工具")
21
22
  console = Console()
@@ -291,7 +292,7 @@ def export(
291
292
 
292
293
  if output == "json":
293
294
  # JSON格式输出
294
- print(json.dumps(data, indent=2, ensure_ascii=False))
295
+ PrettyOutput.print(json.dumps(data, indent=2, ensure_ascii=False), OutputType.CODE, lang="json")
295
296
  else:
296
297
  # CSV格式输出
297
298
  records = data.get("records", [])
@@ -9,6 +9,7 @@ from typing import Dict, List, Optional, Union, Any
9
9
 
10
10
  from jarvis.jarvis_stats.storage import StatsStorage
11
11
  from jarvis.jarvis_stats.visualizer import StatsVisualizer
12
+ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
12
13
 
13
14
 
14
15
  class StatsManager:
@@ -307,7 +308,6 @@ class StatsManager:
307
308
  """
308
309
  storage = StatsManager._get_storage()
309
310
  storage.delete_old_data(days_to_keep)
310
- print(f"已清理 {days_to_keep} 天前的数据")
311
311
 
312
312
  @staticmethod
313
313
  def remove_metric(metric_name: str) -> bool:
@@ -440,7 +440,7 @@ class StatsManager:
440
440
  )
441
441
 
442
442
  if not aggregated:
443
- print(f"没有找到指标 '{metric_name}' 的数据")
443
+ PrettyOutput.print(f"没有找到指标 '{metric_name}' 的数据", OutputType.WARNING)
444
444
  return
445
445
 
446
446
  # 获取指标信息
@@ -474,7 +474,7 @@ class StatsManager:
474
474
  show_values=True,
475
475
  )
476
476
 
477
- print(chart)
477
+ PrettyOutput.print(chart, OutputType.CODE, lang="text")
478
478
 
479
479
  # 显示时间范围
480
480
  from rich.panel import Panel
@@ -550,7 +550,7 @@ class StatsManager:
550
550
  )
551
551
 
552
552
  if not aggregated:
553
- print(f"没有找到指标 '{metric_name}' 的数据")
553
+ PrettyOutput.print(f"没有找到指标 '{metric_name}' 的数据", OutputType.WARNING)
554
554
  return
555
555
 
556
556
  # 获取指标信息
@@ -560,7 +560,7 @@ class StatsManager:
560
560
  # 显示汇总
561
561
  summary = visualizer.show_summary(aggregated, metric_name, unit, tags)
562
562
  if summary: # 如果返回了内容才打印(兼容性)
563
- print(summary)
563
+ PrettyOutput.print(summary, OutputType.INFO)
564
564
 
565
565
  # 显示时间范围
566
566
  from rich.panel import Panel
@@ -12,7 +12,7 @@ def copy_to_clipboard(text: str) -> None:
12
12
  text: 要复制的文本
13
13
  """
14
14
  PrettyOutput.print("--- 剪贴板内容开始 ---", OutputType.INFO)
15
- PrettyOutput.print(text, OutputType.CODE, lang="text")
15
+ print(text)
16
16
  PrettyOutput.print("--- 剪贴板内容结束 ---", OutputType.INFO)
17
17
 
18
18
  system = platform.system()
@@ -9,7 +9,10 @@
9
9
  - 用于输入控制的自定义键绑定
10
10
  """
11
11
  import os
12
+ import sys
13
+ import base64
12
14
  from typing import Iterable, List
15
+ import wcwidth
13
16
 
14
17
  from colorama import Fore
15
18
  from colorama import Style as ColoramaStyle
@@ -41,10 +44,63 @@ from jarvis.jarvis_utils.tag import ot
41
44
 
42
45
  # Sentinel value to indicate that Ctrl+O was pressed
43
46
  CTRL_O_SENTINEL = "__CTRL_O_PRESSED__"
47
+ # Sentinel prefix to indicate that Ctrl+F (fzf) inserted content should prefill next prompt
48
+ FZF_INSERT_SENTINEL_PREFIX = "__FZF_INSERT__::"
49
+ # Sentinel to request running fzf outside the prompt and then prefill next prompt
50
+ FZF_REQUEST_SENTINEL_PREFIX = "__FZF_REQUEST__::"
44
51
 
45
52
  # Persistent hint marker for multiline input (shown only once across runs)
46
53
  _MULTILINE_HINT_MARK_FILE = os.path.join(get_data_dir(), "multiline_enter_hint_shown")
47
54
 
55
+ def _display_width(s: str) -> int:
56
+ """Calculate printable width of a string in terminal columns (handles wide chars)."""
57
+ try:
58
+ w = 0
59
+ for ch in s:
60
+ cw = wcwidth.wcwidth(ch)
61
+ if cw is None or cw < 0:
62
+ # Fallback for unknown width chars (e.g. emoji on some terminals)
63
+ cw = 1
64
+ w += cw
65
+ return w
66
+ except Exception:
67
+ return len(s)
68
+
69
+ def _calc_prompt_rows(prev_text: str) -> int:
70
+ """
71
+ Estimate how many terminal rows the previous prompt occupied.
72
+ Considers prompt prefix and soft-wrapping across terminal columns.
73
+ """
74
+ try:
75
+ cols = os.get_terminal_size().columns
76
+ except Exception:
77
+ cols = 80
78
+ prefix = "👤 > "
79
+ prefix_w = _display_width(prefix)
80
+
81
+ if prev_text is None:
82
+ return 1
83
+
84
+ lines = prev_text.splitlines()
85
+ if not lines:
86
+ lines = [""]
87
+ # If the text ends with a newline, there is a visible empty line at the end.
88
+ if prev_text.endswith("\n"):
89
+ lines.append("")
90
+ total_rows = 0
91
+ for i, line in enumerate(lines):
92
+ lw = _display_width(line)
93
+ if i == 0:
94
+ width = prefix_w + lw
95
+ else:
96
+ width = lw
97
+ rows = max(1, (width + cols - 1) // cols)
98
+ total_rows += rows
99
+ return max(1, total_rows)
100
+
101
+
102
+
103
+
48
104
 
49
105
  def _multiline_hint_already_shown() -> bool:
50
106
  """Check if the multiline Enter hint has been shown before (persisted)."""
@@ -71,7 +127,7 @@ def get_single_line_input(tip: str, default: str = "") -> str:
71
127
  """
72
128
  session: PromptSession = PromptSession(history=None)
73
129
  style = PromptStyle.from_dict({"prompt": "ansicyan", "bottom-toolbar": "fg:#888888"})
74
- prompt = FormattedText([("class:prompt", f"👤 {tip}")])
130
+ prompt = FormattedText([("class:prompt", f"👤 > {tip}")])
75
131
  return session.prompt(prompt, default=default, style=style)
76
132
 
77
133
 
@@ -333,7 +389,7 @@ def _show_history_and_copy():
333
389
  break
334
390
 
335
391
 
336
- def _get_multiline_input_internal(tip: str) -> str:
392
+ def _get_multiline_input_internal(tip: str, preset: str | None = None, preset_cursor: int | None = None) -> str:
337
393
  """
338
394
  Internal function to get multiline input using prompt_toolkit.
339
395
  Returns a sentinel value if Ctrl+O is pressed.
@@ -385,6 +441,64 @@ def _get_multiline_input_internal(tip: str) -> str:
385
441
  """Handle Ctrl+O by exiting the prompt and returning the sentinel value."""
386
442
  event.app.exit(result=CTRL_O_SENTINEL)
387
443
 
444
+ @bindings.add("c-t", filter=has_focus(DEFAULT_BUFFER))
445
+ def _(event):
446
+ """Return a shell command like '!bash' for upper input_handler to execute."""
447
+ def _gen_shell_cmd() -> str: # type: ignore
448
+ try:
449
+ import os
450
+ import shutil
451
+
452
+ if os.name == "nt":
453
+ # Prefer PowerShell if available, otherwise fallback to cmd
454
+ for name in ("pwsh", "powershell", "cmd"):
455
+ if name == "cmd" or shutil.which(name):
456
+ return f"!{name}"
457
+ else:
458
+ shell_path = os.environ.get("SHELL", "")
459
+ if shell_path:
460
+ base = os.path.basename(shell_path)
461
+ if base:
462
+ return f"!{base}"
463
+ for name in ("fish", "zsh", "bash", "sh"):
464
+ if shutil.which(name):
465
+ return f"!{name}"
466
+ return "!bash"
467
+ except Exception:
468
+ return "!bash"
469
+
470
+ # Append a special marker to indicate no-confirm execution in shell_input_handler
471
+ event.app.exit(result=_gen_shell_cmd() + " # JARVIS-NOCONFIRM")
472
+
473
+
474
+ @bindings.add("@", filter=has_focus(DEFAULT_BUFFER), eager=True)
475
+ def _(event):
476
+ """
477
+ 使用 @ 触发 fzf(当 fzf 存在);否则仅插入 @ 以启用内置补全
478
+ 逻辑:
479
+ - 若检测到系统存在 fzf,则先插入 '@',随后请求外层运行 fzf 并在返回后进行替换/插入
480
+ - 若不存在 fzf 或发生异常,则直接插入 '@'
481
+ """
482
+ try:
483
+ import shutil
484
+ buf = event.current_buffer
485
+ if shutil.which("fzf") is None:
486
+ buf.insert_text("@")
487
+ return
488
+ # 先插入 '@',以便外层根据最后一个 '@' 进行片段替换
489
+ buf.insert_text("@")
490
+ doc = buf.document
491
+ text = doc.text
492
+ cursor = doc.cursor_position
493
+ payload = f"{cursor}:{base64.b64encode(text.encode('utf-8')).decode('ascii')}"
494
+ event.app.exit(result=FZF_REQUEST_SENTINEL_PREFIX + payload)
495
+ return
496
+ except Exception:
497
+ try:
498
+ event.current_buffer.insert_text("@")
499
+ except Exception:
500
+ pass
501
+
388
502
  style = PromptStyle.from_dict(
389
503
  {
390
504
  "prompt": "ansibrightmagenta bold",
@@ -414,11 +528,17 @@ def _get_multiline_input_internal(tip: str) -> str:
414
528
  ("class:bt.key", "Ctrl+O"),
415
529
  ("class:bt.label", " 历史复制 "),
416
530
  ("class:bt.sep", " • "),
531
+ ("class:bt.key", "@"),
532
+ ("class:bt.label", " FZF文件 "),
533
+ ("class:bt.sep", " • "),
534
+ ("class:bt.key", "Ctrl+T"),
535
+ ("class:bt.label", " 终端(!SHELL) "),
536
+ ("class:bt.sep", " • "),
417
537
  ("class:bt.key", "Ctrl+C/D"),
418
538
  ("class:bt.label", " 取消 "),
419
539
  ]
420
540
  )
421
-
541
+
422
542
  history_dir = get_data_dir()
423
543
  session: PromptSession = PromptSession(
424
544
  history=FileHistory(os.path.join(history_dir, "multiline_input_history")),
@@ -431,14 +551,26 @@ def _get_multiline_input_internal(tip: str) -> str:
431
551
  )
432
552
 
433
553
  # Tip is shown in bottom toolbar; avoid extra print
434
- prompt = FormattedText([("class:prompt", "👤 ")])
554
+ prompt = FormattedText([("class:prompt", "👤 > ")])
555
+
556
+ def _pre_run():
557
+ try:
558
+ from prompt_toolkit.application.current import get_app as _ga
559
+ app = _ga()
560
+ buf = app.current_buffer
561
+ if preset is not None and preset_cursor is not None:
562
+ cp = max(0, min(len(buf.text), preset_cursor))
563
+ buf.cursor_position = cp
564
+ except Exception:
565
+ pass
435
566
 
436
567
  try:
437
568
  return session.prompt(
438
569
  prompt,
439
570
  style=style,
440
- pre_run=lambda: None,
571
+ pre_run=_pre_run,
441
572
  bottom_toolbar=_bottom_toolbar,
573
+ default=(preset or ""),
442
574
  ).strip()
443
575
  except (KeyboardInterrupt, EOFError):
444
576
  return ""
@@ -453,14 +585,128 @@ def get_multiline_input(tip: str, print_on_empty: bool = True) -> str:
453
585
  tip: 提示文本,将显示在底部工具栏中
454
586
  print_on_empty: 当输入为空字符串时,是否打印“输入已取消”提示。默认打印。
455
587
  """
588
+ preset: str | None = None
589
+ preset_cursor: int | None = None
456
590
  while True:
457
- user_input = _get_multiline_input_internal(tip)
591
+ user_input = _get_multiline_input_internal(tip, preset=preset, preset_cursor=preset_cursor)
458
592
 
459
593
  if user_input == CTRL_O_SENTINEL:
460
594
  _show_history_and_copy()
461
595
  tip = "请继续输入(或按Ctrl+J确认):"
462
596
  continue
597
+ elif isinstance(user_input, str) and user_input.startswith(FZF_REQUEST_SENTINEL_PREFIX):
598
+ # Handle fzf request outside the prompt, then prefill new text.
599
+ try:
600
+ payload = user_input[len(FZF_REQUEST_SENTINEL_PREFIX) :]
601
+ sep_index = payload.find(":")
602
+ cursor = int(payload[:sep_index])
603
+ text = base64.b64decode(payload[sep_index + 1 :].encode("ascii")).decode("utf-8")
604
+ except Exception:
605
+ # Malformed payload; just continue without change.
606
+ preset = None
607
+ tip = "FZF 预填失败,继续输入:"
608
+ continue
609
+
610
+ # Run fzf to get a file selection synchronously (outside prompt)
611
+ selected_path = ""
612
+ try:
613
+ import shutil
614
+ import subprocess
615
+
616
+ if shutil.which("fzf") is None:
617
+ PrettyOutput.print("未检测到 fzf,无法打开文件选择器。", OutputType.WARNING)
618
+ else:
619
+ files: list[str] = []
620
+ try:
621
+ r = subprocess.run(
622
+ ["git", "ls-files"],
623
+ stdout=subprocess.PIPE,
624
+ stderr=subprocess.PIPE,
625
+ text=True,
626
+ )
627
+ if r.returncode == 0:
628
+ files = [line for line in r.stdout.splitlines() if line.strip()]
629
+ except Exception:
630
+ files = []
631
+
632
+ if not files:
633
+ import os as _os
634
+ for root, _, fnames in _os.walk(".", followlinks=False):
635
+ for name in fnames:
636
+ files.append(_os.path.relpath(_os.path.join(root, name), "."))
637
+ if len(files) > 10000:
638
+ break
639
+
640
+ if not files:
641
+ PrettyOutput.print("未找到可选择的文件。", OutputType.INFO)
642
+ else:
643
+ try:
644
+ specials = [ot("Summary"), ot("Clear"), ot("ToolUsage"), ot("ReloadConfig"), ot("SaveSession")]
645
+ except Exception:
646
+ specials = []
647
+ items = [s for s in specials if isinstance(s, str) and s.strip()] + files
648
+ proc = subprocess.run(
649
+ ["fzf", "--prompt", "Files> ", "--height", "40%", "--border"],
650
+ input="\n".join(items),
651
+ stdout=subprocess.PIPE,
652
+ stderr=subprocess.PIPE,
653
+ text=True,
654
+ )
655
+ sel = proc.stdout.strip()
656
+ if sel:
657
+ selected_path = sel
658
+ except Exception as e:
659
+ PrettyOutput.print(f"FZF 执行失败: {e}", OutputType.ERROR)
660
+
661
+ # Compute new text based on selection (or keep original if none)
662
+ if selected_path:
663
+ text_before = text[:cursor]
664
+ last_at = text_before.rfind("@")
665
+ if last_at != -1 and " " not in text_before[last_at + 1 :]:
666
+ # Replace @... segment
667
+ inserted = f"'{selected_path}'"
668
+ new_text = text[:last_at] + inserted + text[cursor:]
669
+ new_cursor = last_at + len(inserted)
670
+ else:
671
+ # Plain insert
672
+ inserted = f"'{selected_path}'"
673
+ new_text = text[:cursor] + inserted + text[cursor:]
674
+ new_cursor = cursor + len(inserted)
675
+ preset = new_text
676
+ preset_cursor = new_cursor
677
+ tip = "已插入文件,继续编辑或按Ctrl+J确认:"
678
+ else:
679
+ # No selection; keep original text and cursor
680
+ preset = text
681
+ preset_cursor = cursor
682
+ tip = "未选择文件或已取消,继续编辑:"
683
+ # 清除上一条输入行(多行安全),避免多清,保守仅按提示行估算
684
+ try:
685
+ rows_total = _calc_prompt_rows(text)
686
+ for _ in range(rows_total):
687
+ sys.stdout.write("\x1b[1A") # 光标上移一行
688
+ sys.stdout.write("\x1b[2K\r") # 清除整行
689
+ sys.stdout.flush()
690
+ except Exception:
691
+ pass
692
+ continue
693
+ elif isinstance(user_input, str) and user_input.startswith(FZF_INSERT_SENTINEL_PREFIX):
694
+ # 从哨兵载荷中提取新文本,作为下次进入提示的预填内容
695
+ preset = user_input[len(FZF_INSERT_SENTINEL_PREFIX) :]
696
+ preset_cursor = len(preset)
697
+
698
+ # 清除上一条输入行(多行安全),避免多清,保守仅按提示行估算
699
+ try:
700
+ rows_total = _calc_prompt_rows(preset)
701
+ for _ in range(rows_total):
702
+ sys.stdout.write("\x1b[1A")
703
+ sys.stdout.write("\x1b[2K\r")
704
+ sys.stdout.flush()
705
+ except Exception:
706
+ pass
707
+ tip = "已插入文件,继续编辑或按Ctrl+J确认:"
708
+ continue
463
709
  else:
464
710
  if not user_input and print_on_empty:
465
- PrettyOutput.print("\n输入已取消", OutputType.INFO)
711
+ PrettyOutput.print("输入已取消", OutputType.INFO)
466
712
  return user_input
@@ -10,7 +10,7 @@
10
10
  """
11
11
  from datetime import datetime
12
12
  from enum import Enum
13
- from typing import Dict, Optional, Tuple, Any
13
+ from typing import Dict, Optional, Tuple, Any, List
14
14
 
15
15
  from pygments.lexers import guess_lexer
16
16
  from pygments.util import ClassNotFound
@@ -22,6 +22,8 @@ from rich.text import Text
22
22
 
23
23
  from jarvis.jarvis_utils.config import get_pretty_output
24
24
  from jarvis.jarvis_utils.globals import console, get_agent_list
25
+ from dataclasses import dataclass
26
+ from abc import ABC, abstractmethod
25
27
 
26
28
 
27
29
  class OutputType(Enum):
@@ -57,124 +59,59 @@ class OutputType(Enum):
57
59
  TOOL = "TOOL"
58
60
 
59
61
 
60
- class PrettyOutput:
62
+ @dataclass
63
+ class OutputEvent:
61
64
  """
62
- 使用rich库格式化和显示富文本输出的类。
63
-
64
- 提供以下方法:
65
- - 使用适当的样式格式化不同类型的输出
66
- - 代码块的语法高亮
67
- - 结构化内容的面板显示
68
- - 渐进显示的流式输出
65
+ 输出事件的通用结构,供不同输出后端(Sink)消费。
66
+ - text: 文本内容
67
+ - output_type: 输出类型
68
+ - timestamp: 是否显示时间戳
69
+ - lang: 语法高亮语言(可选,不提供则自动检测)
70
+ - traceback: 是否显示异常堆栈
71
+ - section: 若为章节标题输出,填入标题文本;否则为None
72
+ - context: 额外上下文(预留给TUI/日志等)
69
73
  """
74
+ text: str
75
+ output_type: OutputType
76
+ timestamp: bool = True
77
+ lang: Optional[str] = None
78
+ traceback: bool = False
79
+ section: Optional[str] = None
80
+ context: Optional[Dict[str, Any]] = None
70
81
 
71
- # 不同输出类型的图标
72
- _ICONS = {
73
- OutputType.SYSTEM: "🤖",
74
- OutputType.CODE: "📝",
75
- OutputType.RESULT: "✨",
76
- OutputType.ERROR: "❌",
77
- OutputType.INFO: "ℹ️",
78
- OutputType.PLANNING: "📋",
79
- OutputType.PROGRESS: "⏳",
80
- OutputType.SUCCESS: "✅",
81
- OutputType.WARNING: "⚠️",
82
- OutputType.DEBUG: "🔍",
83
- OutputType.USER: "👤",
84
- OutputType.TOOL: "🔧",
85
- }
86
- # 语法高亮的语言映射
87
- _lang_map = {
88
- "Python": "python",
89
- "JavaScript": "javascript",
90
- "TypeScript": "typescript",
91
- "Java": "java",
92
- "C++": "cpp",
93
- "C#": "csharp",
94
- "Ruby": "ruby",
95
- "PHP": "php",
96
- "Go": "go",
97
- "Rust": "rust",
98
- "Bash": "bash",
99
- "HTML": "html",
100
- "CSS": "css",
101
- "SQL": "sql",
102
- "R": "r",
103
- "Kotlin": "kotlin",
104
- "Swift": "swift",
105
- "Scala": "scala",
106
- "Perl": "perl",
107
- "Lua": "lua",
108
- "YAML": "yaml",
109
- "JSON": "json",
110
- "XML": "xml",
111
- "Markdown": "markdown",
112
- "Text": "text",
113
- "Shell": "bash",
114
- "Dockerfile": "dockerfile",
115
- "Makefile": "makefile",
116
- "INI": "ini",
117
- "TOML": "toml",
118
- }
119
-
120
- @staticmethod
121
- def _detect_language(text: str, default_lang: str = "markdown") -> str:
122
- """
123
- 检测给定文本的编程语言。
124
82
 
125
- 参数:
126
- text: 要分析的文本
127
- default_lang: 如果检测失败,默认返回的语言
83
+ class OutputSink(ABC):
84
+ """输出后端抽象接口,不同前端(控制台/TUI/SSE/日志)实现该接口以消费输出事件。"""
128
85
 
129
- 返回:
130
- str: 检测到的语言名称
131
- """
132
- try:
133
- lexer = guess_lexer(text)
134
- detected_lang = lexer.name # type: ignore[attr-defined]
135
- return PrettyOutput._lang_map.get(detected_lang, default_lang)
136
- except (ClassNotFound, Exception):
137
- return default_lang
86
+ @abstractmethod
87
+ def emit(self, event: OutputEvent) -> None: # pragma: no cover - 抽象方法
88
+ raise NotImplementedError
138
89
 
139
- @staticmethod
140
- def _format(output_type: OutputType, timestamp: bool = True) -> str:
141
- """
142
- 使用时间戳和图标格式化输出头。
143
90
 
144
- 参数:
145
- output_type: 输出类型
146
- timestamp: 是否包含时间戳
91
+ class ConsoleOutputSink(OutputSink):
92
+ """
93
+ 默认控制台输出实现,保持与原 PrettyOutput 行为一致。
94
+ """
147
95
 
148
- 返回:
149
- Text: 格式化后的rich Text对象
150
- """
151
- icon = PrettyOutput._ICONS.get(output_type, "")
152
- formatted = f"{icon} "
153
- if timestamp:
154
- formatted += f"[{datetime.now().strftime('%H:%M:%S')}][{output_type.value}]"
155
- agent_info = get_agent_list()
156
- if agent_info:
157
- formatted += f"[{agent_info}]"
158
- return formatted
96
+ def emit(self, event: OutputEvent) -> None:
97
+ # 章节输出
98
+ if event.section is not None:
99
+ text = Text(event.section, style=event.output_type.value, justify="center")
100
+ panel = Panel(text, border_style=event.output_type.value)
101
+ if get_pretty_output():
102
+ console.print(panel)
103
+ else:
104
+ console.print(text)
105
+ return
159
106
 
160
- @staticmethod
161
- def print(
162
- text: str,
163
- output_type: OutputType,
164
- timestamp: bool = True,
165
- lang: Optional[str] = None,
166
- traceback: bool = False,
167
- ):
168
- """
169
- 使用样式和语法高亮打印格式化输出。
107
+ # 普通内容输出
108
+ lang = (
109
+ event.lang
110
+ if event.lang is not None
111
+ else PrettyOutput._detect_language(event.text, default_lang="markdown")
112
+ )
170
113
 
171
- 参数:
172
- text: 要打印的文本内容
173
- output_type: 输出类型(影响样式)
174
- timestamp: 是否显示时间戳
175
- lang: 语法高亮的语言
176
- traceback: 是否显示错误的回溯信息
177
- """
114
+ # 与原实现保持一致的样式定义
178
115
  styles: Dict[OutputType, Dict[str, Any]] = {
179
116
  OutputType.SYSTEM: dict(bgcolor="#1e2b3c"),
180
117
  OutputType.CODE: dict(bgcolor="#1c2b1c"),
@@ -255,25 +192,20 @@ class PrettyOutput:
255
192
  ),
256
193
  }
257
194
 
258
- lang = (
259
- lang
260
- if lang is not None
261
- else PrettyOutput._detect_language(text, default_lang="markdown")
262
- )
263
195
  header = Text(
264
- PrettyOutput._format(output_type, timestamp),
265
- style=header_styles[output_type],
196
+ PrettyOutput._format(event.output_type, event.timestamp),
197
+ style=header_styles[event.output_type],
266
198
  )
267
199
  content = Syntax(
268
- text,
200
+ event.text,
269
201
  lang,
270
202
  theme="monokai",
271
203
  word_wrap=True,
272
- background_color=styles[output_type]["bgcolor"],
204
+ background_color=styles[event.output_type]["bgcolor"],
273
205
  )
274
206
  panel = Panel(
275
207
  content,
276
- border_style=header_styles[output_type],
208
+ border_style=header_styles[event.output_type],
277
209
  padding=(0, 0),
278
210
  highlight=True,
279
211
  )
@@ -281,27 +213,181 @@ class PrettyOutput:
281
213
  console.print(panel)
282
214
  else:
283
215
  console.print(content)
284
- if traceback or output_type == OutputType.ERROR:
216
+ if event.traceback or event.output_type == OutputType.ERROR:
285
217
  try:
286
218
  console.print_exception()
287
219
  except Exception as e:
288
220
  console.print(f"Error: {e}")
289
221
 
222
+
223
+ # 模块级输出分发器(默认注册控制台后端)
224
+ _output_sinks: List[OutputSink] = [ConsoleOutputSink()]
225
+
226
+
227
+ def emit_output(event: OutputEvent) -> None:
228
+ """向所有已注册的输出后端广播事件。"""
229
+ for sink in list(_output_sinks):
230
+ try:
231
+ sink.emit(event)
232
+ except Exception as e:
233
+ # 后端故障不影响其他后端
234
+ console.print(f"[输出后端错误] {sink.__class__.__name__}: {e}")
235
+
236
+
237
+ class PrettyOutput:
238
+ """
239
+ 使用rich库格式化和显示富文本输出的类。
240
+
241
+ 提供以下方法:
242
+ - 使用适当的样式格式化不同类型的输出
243
+ - 代码块的语法高亮
244
+ - 结构化内容的面板显示
245
+ - 渐进显示的流式输出
246
+ """
247
+
248
+ # 不同输出类型的图标
249
+ _ICONS = {
250
+ OutputType.SYSTEM: "🤖",
251
+ OutputType.CODE: "📝",
252
+ OutputType.RESULT: "✨",
253
+ OutputType.ERROR: "❌",
254
+ OutputType.INFO: "ℹ️",
255
+ OutputType.PLANNING: "📋",
256
+ OutputType.PROGRESS: "⏳",
257
+ OutputType.SUCCESS: "✅",
258
+ OutputType.WARNING: "⚠️",
259
+ OutputType.DEBUG: "🔍",
260
+ OutputType.USER: "👤",
261
+ OutputType.TOOL: "🔧",
262
+ }
263
+ # 语法高亮的语言映射
264
+ _lang_map = {
265
+ "Python": "python",
266
+ "JavaScript": "javascript",
267
+ "TypeScript": "typescript",
268
+ "Java": "java",
269
+ "C++": "cpp",
270
+ "C#": "csharp",
271
+ "Ruby": "ruby",
272
+ "PHP": "php",
273
+ "Go": "go",
274
+ "Rust": "rust",
275
+ "Bash": "bash",
276
+ "HTML": "html",
277
+ "CSS": "css",
278
+ "SQL": "sql",
279
+ "R": "r",
280
+ "Kotlin": "kotlin",
281
+ "Swift": "swift",
282
+ "Scala": "scala",
283
+ "Perl": "perl",
284
+ "Lua": "lua",
285
+ "YAML": "yaml",
286
+ "JSON": "json",
287
+ "XML": "xml",
288
+ "Markdown": "markdown",
289
+ "Text": "text",
290
+ "Shell": "bash",
291
+ "Dockerfile": "dockerfile",
292
+ "Makefile": "makefile",
293
+ "INI": "ini",
294
+ "TOML": "toml",
295
+ }
296
+
290
297
  @staticmethod
291
- def section(title: str, output_type: OutputType = OutputType.INFO):
298
+ def _detect_language(text: str, default_lang: str = "markdown") -> str:
299
+ """
300
+ 检测给定文本的编程语言。
301
+
302
+ 参数:
303
+ text: 要分析的文本
304
+ default_lang: 如果检测失败,默认返回的语言
305
+
306
+ 返回:
307
+ str: 检测到的语言名称
308
+ """
309
+ try:
310
+ lexer = guess_lexer(text)
311
+ detected_lang = lexer.name # type: ignore[attr-defined]
312
+ return PrettyOutput._lang_map.get(detected_lang, default_lang)
313
+ except (ClassNotFound, Exception):
314
+ return default_lang
315
+
316
+ @staticmethod
317
+ def _format(output_type: OutputType, timestamp: bool = True) -> str:
292
318
  """
293
- 在样式化面板中打印章节标题。
319
+ 使用时间戳和图标格式化输出头。
294
320
 
295
321
  参数:
296
- title: 章节标题文本
297
- output_type: 输出类型(影响样式)
322
+ output_type: 输出类型
323
+ timestamp: 是否包含时间戳
324
+
325
+ 返回:
326
+ Text: 格式化后的rich Text对象
298
327
  """
299
- text = Text(title, style=output_type.value, justify="center")
300
- panel = Panel(text, border_style=output_type.value)
301
- if get_pretty_output():
302
- console.print(panel)
328
+ icon = PrettyOutput._ICONS.get(output_type, "")
329
+ formatted = f"{icon} "
330
+ if timestamp:
331
+ formatted += f"[{datetime.now().strftime('%H:%M:%S')}][{output_type.value}]"
332
+ agent_info = get_agent_list()
333
+ if agent_info:
334
+ formatted += f"[{agent_info}]"
335
+ return formatted
336
+
337
+ @staticmethod
338
+ def print(
339
+ text: str,
340
+ output_type: OutputType,
341
+ timestamp: bool = True,
342
+ lang: Optional[str] = None,
343
+ traceback: bool = False,
344
+ ):
345
+ """
346
+ 使用样式和语法高亮打印格式化输出(已抽象为事件 + Sink 机制)。
347
+ 保持对现有调用方的向后兼容,同时为TUI/日志等前端预留扩展点。
348
+ """
349
+ event = OutputEvent(
350
+ text=text,
351
+ output_type=output_type,
352
+ timestamp=timestamp,
353
+ lang=lang,
354
+ traceback=traceback,
355
+ )
356
+ emit_output(event)
357
+
358
+ @staticmethod
359
+ def section(title: str, output_type: OutputType = OutputType.INFO):
360
+ """
361
+ 在样式化面板中打印章节标题(通过事件 + Sink 机制分发)。
362
+ """
363
+ event = OutputEvent(
364
+ text="",
365
+ output_type=output_type,
366
+ section=title,
367
+ )
368
+ emit_output(event)
369
+
370
+ @staticmethod
371
+ # Sink管理(为外部注册自定义后端预留)
372
+ @staticmethod
373
+ def add_sink(sink: OutputSink) -> None:
374
+ """注册一个新的输出后端。"""
375
+ _output_sinks.append(sink)
376
+
377
+ @staticmethod
378
+ def clear_sinks(keep_default: bool = True) -> None:
379
+ """清空已注册的输出后端;可选择保留默认控制台后端。"""
380
+ if keep_default:
381
+ globals()["_output_sinks"] = [
382
+ s for s in _output_sinks if isinstance(s, ConsoleOutputSink)
383
+ ]
303
384
  else:
304
- console.print(text)
385
+ _output_sinks.clear()
386
+
387
+ @staticmethod
388
+ def get_sinks() -> List[OutputSink]:
389
+ """获取当前已注册的输出后端列表(副本)。"""
390
+ return list(_output_sinks)
305
391
 
306
392
  @staticmethod
307
393
  def print_gradient_text(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.3.19
3
+ Version: 0.3.20
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
@@ -1,8 +1,8 @@
1
- jarvis/__init__.py,sha256=WDyBQWvbgGmONVjrNHI5MHAMmGizwEtYWsHjLSpflxk,74
2
- jarvis/jarvis_agent/__init__.py,sha256=6N0D8ah8QVxDs155-d1FhxQcZt3enHUQMKyOabiLaPw,33201
1
+ jarvis/__init__.py,sha256=N7QVf6q_SvBscQmf9AFdoKn3bxAilYPXJ-mx-Vjn6aQ,74
2
+ jarvis/jarvis_agent/__init__.py,sha256=3_CKwWoN-eq9JMviyDrhgeSlt3o9oMBP1DTYFOT-g_U,33655
3
3
  jarvis/jarvis_agent/agent_manager.py,sha256=YzpMiF0H2-eyk2kn2o24Bkj3bXsQx7Pv2vfD4gWepo0,2893
4
4
  jarvis/jarvis_agent/builtin_input_handler.py,sha256=wS-FqpT3pIXwHn1dfL3SpXonUKWgVThbQueUIeyRc2U,2917
5
- jarvis/jarvis_agent/config_editor.py,sha256=Ctk82sO6w2cNW0-_5L7Bomj-hgM4U7WwMc52fwhAJyg,1809
5
+ jarvis/jarvis_agent/config_editor.py,sha256=q4t_LNqiPPVYKcbE7N1cXSqObCK6wBNemLvAYc1y05Y,1849
6
6
  jarvis/jarvis_agent/edit_file_handler.py,sha256=bkvCghB_X-CQKuIG7dJfos8F1KNnHSLNhldraVgqVk8,11209
7
7
  jarvis/jarvis_agent/file_methodology_manager.py,sha256=PwDUQwq7HVIyPInsN8fgWyMXLwi8heIXPrqfBZJhVHs,4260
8
8
  jarvis/jarvis_agent/jarvis.py,sha256=J_R27hZUTGattEAKppkeCoklMJoEFHjAycsCMRjYA7o,19340
@@ -15,7 +15,7 @@ jarvis/jarvis_agent/prompts.py,sha256=X6cXa-n0xqBQ8LDTgLsD0kqziAh1s0cNp89i4mxcvH
15
15
  jarvis/jarvis_agent/protocols.py,sha256=JWnJDikFEuwvFUv7uzXu0ggJ4O9K2FkMnfVCwIJ5REw,873
16
16
  jarvis/jarvis_agent/session_manager.py,sha256=5wVcaZGwJ9cEKTQglSbqyxUDJ2fI5KxYN8C8L16UWLw,3024
17
17
  jarvis/jarvis_agent/share_manager.py,sha256=wFcdULSog1mMxDyB94ofbqitFL8DCX8i1u6qVzSEuAk,8704
18
- jarvis/jarvis_agent/shell_input_handler.py,sha256=1IboqdxcJuoIqRpmDU10GugR9fWXUHyCEbVF4nIWbyo,1328
18
+ jarvis/jarvis_agent/shell_input_handler.py,sha256=YjzmtcBUqJDYeD1i1cA1betUKaGOcvKNo8biayYEfc8,1860
19
19
  jarvis/jarvis_agent/task_analyzer.py,sha256=W9Pm2AB0kNhbFos3Qh6tpe5gA-x8e566IhIKvJkQJmg,4667
20
20
  jarvis/jarvis_agent/task_manager.py,sha256=hP2PF_mgmmATD3h5HHDdkU_m3_LBOK1eZGZ3gh-9Kh8,4851
21
21
  jarvis/jarvis_agent/tool_executor.py,sha256=gyVahM_d4hzYxiYJD209tVSbXO8SpKi1pohEDmyAmnc,1768
@@ -59,8 +59,8 @@ jarvis/jarvis_methodology/main.py,sha256=DtfgvuFdJl7IoccoyTSdZdnwOd2ghBmN55Gu7eZ
59
59
  jarvis/jarvis_multi_agent/__init__.py,sha256=kCgtAX7VvliyEOQxIj2DvNjRAuh6bpNaOtDn60nzph4,6089
60
60
  jarvis/jarvis_multi_agent/main.py,sha256=b9IThFMeUZCYSlgT-VT8r7xeBdrEE_zNT11awEc8IdY,1853
61
61
  jarvis/jarvis_platform/__init__.py,sha256=WLQHSiE87PPket2M50_hHzjdMIgPIBx2VF8JfB_NNRk,105
62
- jarvis/jarvis_platform/ai8.py,sha256=W3947AGMpk3RRBfsfZmf222sEP0VIGoSU0vPkgiVnl0,11683
63
- jarvis/jarvis_platform/base.py,sha256=IzahTsZvzSPhQoqczDON1iPD9q5y-FM8N6TfW991gJg,9959
62
+ jarvis/jarvis_platform/ai8.py,sha256=g8JkqPGs9SEbqstNMCc5rCHO0QcPHX9LNvb7HMWwB-Q,11471
63
+ jarvis/jarvis_platform/base.py,sha256=x-DAfYz-ZxdcqrNmCU-Awfe8R3p6CUP28vUH3GVmLXQ,9959
64
64
  jarvis/jarvis_platform/human.py,sha256=jWjW8prEag79e6ddqTPV4nz_Gz6zFBfO4a1EbvP8QWA,4908
65
65
  jarvis/jarvis_platform/kimi.py,sha256=k7CVJlTPDTPTBqWwbxZa3HJXGhQtDNf3zv-Lu9sONiw,15520
66
66
  jarvis/jarvis_platform/openai.py,sha256=0YSeDGHRSPQP2haEzFARx_aZH_d_UZ-HSCsJLh2hW5k,8037
@@ -69,7 +69,7 @@ jarvis/jarvis_platform/tongyi.py,sha256=fw7PvB3FKGbztF29RrY4-YKSXp1-5TMy8RYw-g56
69
69
  jarvis/jarvis_platform/yuanbao.py,sha256=mn7jxrab2CeVRtcwgQxGUhz90RRb9qK1iKz4rmFskOI,23858
70
70
  jarvis/jarvis_platform_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
71
71
  jarvis/jarvis_platform_manager/main.py,sha256=_G64sTDNyCcag82UhPZzrlS9t1__Qe_GBR8d16dhQV0,20909
72
- jarvis/jarvis_platform_manager/service.py,sha256=DnuRJjD7RvunGt3LpMfUDr-Bps-Nb--frkeaC0nwxj0,14874
72
+ jarvis/jarvis_platform_manager/service.py,sha256=lxC9G6o_psIUjvxXVMAmhL-TGe3A6cJ5zCat6sB9NXo,14842
73
73
  jarvis/jarvis_rag/__init__.py,sha256=HRTXgnQxDuaE9x-e3r6SYqhJ5d4DSI_rrIxy2IGY6qk,320
74
74
  jarvis/jarvis_rag/cache.py,sha256=Tqx_Oe-AhuWlMXHGHUaIuG6OEHoHBVZq7mL3kldtFFU,2723
75
75
  jarvis/jarvis_rag/cli.py,sha256=w79ZVVtNKbmNoYuDDzhprbxLZsDGv5hGe7uwk0ktB6k,16553
@@ -80,10 +80,10 @@ jarvis/jarvis_rag/rag_pipeline.py,sha256=f0ktQIfNyBY4kanfttyGz_sZWB9DMhXPzeognEe
80
80
  jarvis/jarvis_rag/reranker.py,sha256=Uzn4n1bNj4kWyQu9-z-jK_5dAU6drn5jEugML-kFHg8,1885
81
81
  jarvis/jarvis_rag/retriever.py,sha256=1nbw5m15drvYGbocLfGHO2FgXPSDKmT1k2unNk1IrXw,8067
82
82
  jarvis/jarvis_smart_shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
- jarvis/jarvis_smart_shell/main.py,sha256=zizfinG0yYETAE2SaRtF4OAxU3r92NdzhUqbPquiEB8,14601
83
+ jarvis/jarvis_smart_shell/main.py,sha256=XFiqB7GdVn2-mr9G7GLLrHeo3iAUyDGVD1w5tIB9xJQ,14644
84
84
  jarvis/jarvis_stats/__init__.py,sha256=jJzgP43nxzLbNGs8Do4Jfta1PNCJMf1Oq9YTPd6EnFM,342
85
- jarvis/jarvis_stats/cli.py,sha256=KqLH-9Kd_YlBJSke3QXY90XnFmiH2kYkRacL8ygtSsM,12649
86
- jarvis/jarvis_stats/stats.py,sha256=y3yo1aZvigbC9SAJFAVkoLLKxNYVcqoErTts_3ZYn9o,19193
85
+ jarvis/jarvis_stats/cli.py,sha256=pPwS_v3jW_xbRzaegiwIYngzDJp7iE9mS_bOmIybHd0,12756
86
+ jarvis/jarvis_stats/stats.py,sha256=IpBdVaTFg4DowrVVUIv1OnN26IwTUE5yHzpMLUDaju0,19337
87
87
  jarvis/jarvis_stats/storage.py,sha256=WvABIbYZLOSHDQZkM4X-cZyFMi7rlbMskFMXqbhFxQk,21697
88
88
  jarvis/jarvis_stats/visualizer.py,sha256=ZIBmGELzs6c7qM01tQql1HF6eFKn6HDGVQfKXRUUIY0,8529
89
89
  jarvis/jarvis_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -107,21 +107,21 @@ jarvis/jarvis_tools/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG
107
107
  jarvis/jarvis_tools/cli/main.py,sha256=H_Rdp7WMiPDxuUha_lsWds6PAAwy-2j0IhLlgmjP2Ro,8435
108
108
  jarvis/jarvis_utils/__init__.py,sha256=67h0ldisGlh3oK4DAeNEL2Bl_VsI3tSmfclasyVlueM,850
109
109
  jarvis/jarvis_utils/builtin_replace_map.py,sha256=4BurljGuiG_I93EBs7mlFlPm9wYC_4CmdTG5tQWpF6g,1712
110
- jarvis/jarvis_utils/clipboard.py,sha256=FOV4-tOMTyFrUZ6fOw68ArQE4gTiSXhLdsVtGVTZoFo,3010
110
+ jarvis/jarvis_utils/clipboard.py,sha256=D3wzQeqg_yiH7Axs4d6MRxyNa9XxdnenH-ND2uj2WVQ,2967
111
111
  jarvis/jarvis_utils/config.py,sha256=my9u8QL-PhByAumthP4oJq2NtH_Wc6wd0DnUguAQUWk,17580
112
112
  jarvis/jarvis_utils/embedding.py,sha256=oEOEM2qf16DMYwPsQe6srET9BknyjOdY2ef0jsp3Or8,2714
113
113
  jarvis/jarvis_utils/file_processors.py,sha256=XiM248SHS7lLgQDCbORVFWqinbVDUawYxWDOsLXDxP8,3043
114
114
  jarvis/jarvis_utils/git_utils.py,sha256=AkczUiRcGcOnPfz2v3mdLwV1S41IopiAYD2tjeMTDrE,23586
115
115
  jarvis/jarvis_utils/globals.py,sha256=aTrOHcCgPAeZFLFIWMAMiJCYlmr4XhdFZf5gZ745hnE,8900
116
116
  jarvis/jarvis_utils/http.py,sha256=eRhV3-GYuWmQ0ogq9di9WMlQkFcVb1zGCrySnOgT1x0,4392
117
- jarvis/jarvis_utils/input.py,sha256=F7w0HjTIh-I661bHG-WyW0OTnhKFvge10ULMOZdVIbE,16296
117
+ jarvis/jarvis_utils/input.py,sha256=9pqaCrZBzmU542msJ41xc69AGydw8goT7oqkrPLH0Mg,26608
118
118
  jarvis/jarvis_utils/methodology.py,sha256=ypd2hraZWhzOfQ-bjfQprtcB27HBbpdcPW-NAfSAzd0,12640
119
- jarvis/jarvis_utils/output.py,sha256=ktj7D6qMbd3nct1N24HQXCafJAaImMlQImGFYT6Cjhc,10678
119
+ jarvis/jarvis_utils/output.py,sha256=rJ_uOSUNjwUTj9SKrwQLb6JOb9Wb8ODoGMiHvrzvUfQ,13550
120
120
  jarvis/jarvis_utils/tag.py,sha256=f211opbbbTcSyzCDwuIK_oCnKhXPNK-RknYyGzY1yD0,431
121
121
  jarvis/jarvis_utils/utils.py,sha256=iU1DdQvSCjedOgnExLMxSAjcZkSqmV5MAdu2t2RRSjw,51341
122
- jarvis_ai_assistant-0.3.19.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
123
- jarvis_ai_assistant-0.3.19.dist-info/METADATA,sha256=BaefbwI75RMWwu5A1k0T-4bKT6o5wsHGW6jBbZTLFso,18216
124
- jarvis_ai_assistant-0.3.19.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
- jarvis_ai_assistant-0.3.19.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
126
- jarvis_ai_assistant-0.3.19.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
127
- jarvis_ai_assistant-0.3.19.dist-info/RECORD,,
122
+ jarvis_ai_assistant-0.3.20.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
123
+ jarvis_ai_assistant-0.3.20.dist-info/METADATA,sha256=Gboa0F2vb0jbopnQ-k224VQHmBVOjyooqx6aFCM_DW8,18216
124
+ jarvis_ai_assistant-0.3.20.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
125
+ jarvis_ai_assistant-0.3.20.dist-info/entry_points.txt,sha256=4GcWKFxRJD-QU14gw_3ZaW4KuEVxOcZK9i270rwPdjA,1395
126
+ jarvis_ai_assistant-0.3.20.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
127
+ jarvis_ai_assistant-0.3.20.dist-info/RECORD,,