jarvis-ai-assistant 0.1.132__py3-none-any.whl → 0.1.138__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +330 -347
- jarvis/jarvis_agent/builtin_input_handler.py +16 -6
- jarvis/jarvis_agent/file_input_handler.py +9 -9
- jarvis/jarvis_agent/jarvis.py +143 -0
- jarvis/jarvis_agent/main.py +12 -13
- jarvis/jarvis_agent/output_handler.py +3 -3
- jarvis/jarvis_agent/patch.py +92 -64
- jarvis/jarvis_agent/shell_input_handler.py +5 -3
- jarvis/jarvis_code_agent/code_agent.py +263 -177
- jarvis/jarvis_code_agent/file_select.py +24 -24
- jarvis/jarvis_dev/main.py +45 -59
- jarvis/jarvis_git_details/__init__.py +0 -0
- jarvis/jarvis_git_details/main.py +179 -0
- jarvis/jarvis_git_squash/main.py +7 -7
- jarvis/jarvis_lsp/base.py +11 -53
- jarvis/jarvis_lsp/cpp.py +13 -28
- jarvis/jarvis_lsp/go.py +13 -28
- jarvis/jarvis_lsp/python.py +8 -27
- jarvis/jarvis_lsp/registry.py +21 -83
- jarvis/jarvis_lsp/rust.py +15 -30
- jarvis/jarvis_methodology/main.py +101 -0
- jarvis/jarvis_multi_agent/__init__.py +10 -51
- jarvis/jarvis_multi_agent/main.py +43 -0
- jarvis/jarvis_platform/__init__.py +1 -1
- jarvis/jarvis_platform/ai8.py +67 -89
- jarvis/jarvis_platform/base.py +14 -13
- jarvis/jarvis_platform/kimi.py +25 -28
- jarvis/jarvis_platform/ollama.py +24 -26
- jarvis/jarvis_platform/openai.py +15 -19
- jarvis/jarvis_platform/oyi.py +48 -50
- jarvis/jarvis_platform/registry.py +29 -44
- jarvis/jarvis_platform/yuanbao.py +39 -43
- jarvis/jarvis_platform_manager/main.py +81 -81
- jarvis/jarvis_platform_manager/openai_test.py +21 -21
- jarvis/jarvis_rag/file_processors.py +18 -18
- jarvis/jarvis_rag/main.py +262 -278
- jarvis/jarvis_smart_shell/main.py +12 -12
- jarvis/jarvis_tools/ask_codebase.py +85 -78
- jarvis/jarvis_tools/ask_user.py +8 -8
- jarvis/jarvis_tools/base.py +4 -4
- jarvis/jarvis_tools/chdir.py +9 -9
- jarvis/jarvis_tools/code_review.py +40 -21
- jarvis/jarvis_tools/create_code_agent.py +15 -15
- jarvis/jarvis_tools/create_sub_agent.py +0 -1
- jarvis/jarvis_tools/execute_python_script.py +3 -3
- jarvis/jarvis_tools/execute_shell.py +11 -11
- jarvis/jarvis_tools/execute_shell_script.py +3 -3
- jarvis/jarvis_tools/file_analyzer.py +116 -105
- jarvis/jarvis_tools/file_operation.py +22 -20
- jarvis/jarvis_tools/find_caller.py +105 -40
- jarvis/jarvis_tools/find_methodolopy.py +65 -0
- jarvis/jarvis_tools/find_symbol.py +123 -39
- jarvis/jarvis_tools/function_analyzer.py +140 -57
- jarvis/jarvis_tools/git_commiter.py +10 -10
- jarvis/jarvis_tools/lsp_get_diagnostics.py +19 -19
- jarvis/jarvis_tools/methodology.py +22 -67
- jarvis/jarvis_tools/project_analyzer.py +137 -53
- jarvis/jarvis_tools/rag.py +15 -20
- jarvis/jarvis_tools/read_code.py +25 -23
- jarvis/jarvis_tools/read_webpage.py +31 -31
- jarvis/jarvis_tools/registry.py +72 -52
- jarvis/jarvis_tools/search_web.py +23 -353
- jarvis/jarvis_tools/tool_generator.py +19 -19
- jarvis/jarvis_utils/config.py +36 -96
- jarvis/jarvis_utils/embedding.py +83 -83
- jarvis/jarvis_utils/git_utils.py +20 -20
- jarvis/jarvis_utils/globals.py +18 -6
- jarvis/jarvis_utils/input.py +10 -9
- jarvis/jarvis_utils/methodology.py +141 -140
- jarvis/jarvis_utils/output.py +13 -13
- jarvis/jarvis_utils/utils.py +23 -71
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/METADATA +6 -15
- jarvis_ai_assistant-0.1.138.dist-info/RECORD +85 -0
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/entry_points.txt +4 -3
- jarvis/jarvis_tools/lsp_find_definition.py +0 -150
- jarvis/jarvis_tools/lsp_find_references.py +0 -127
- jarvis/jarvis_tools/select_code_files.py +0 -62
- jarvis_ai_assistant-0.1.132.dist-info/RECORD +0 -82
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.132.dist-info → jarvis_ai_assistant-0.1.138.dist-info}/top_level.txt +0 -0
|
@@ -6,20 +6,20 @@ from typing import Any, Tuple
|
|
|
6
6
|
def builtin_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
7
7
|
"""
|
|
8
8
|
处理内置的特殊输入标记,并追加相应的提示词
|
|
9
|
-
|
|
9
|
+
|
|
10
10
|
参数:
|
|
11
11
|
user_input: 用户输入
|
|
12
12
|
agent: 代理对象
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
返回:
|
|
15
15
|
Tuple[str, bool]: 处理后的输入和是否需要进一步处理
|
|
16
16
|
"""
|
|
17
17
|
# 查找特殊标记
|
|
18
18
|
special_tags = re.findall(r"'<([^>]+)>'", user_input)
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
if not special_tags:
|
|
21
21
|
return user_input, False
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
# 使用集合去重
|
|
24
24
|
processed_tags = set()
|
|
25
25
|
# 处理每个标记
|
|
@@ -27,7 +27,7 @@ def builtin_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
|
27
27
|
if tag in processed_tags:
|
|
28
28
|
continue
|
|
29
29
|
processed_tags.add(tag)
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
if tag == "CodeBase":
|
|
32
32
|
user_input = user_input.replace(f"'<{tag}>'", "")
|
|
33
33
|
user_input += """
|
|
@@ -68,6 +68,16 @@ def builtin_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
|
68
68
|
agent.clear()
|
|
69
69
|
if not user_input.strip():
|
|
70
70
|
return "", True
|
|
71
|
+
elif tag == "Methodology":
|
|
72
|
+
user_input = user_input.replace(f"'<{tag}>'", "")
|
|
73
|
+
user_input += """
|
|
74
|
+
请使用find_methodology工具查找相关方法论,可以使用的提问格式包括:
|
|
75
|
+
1. 关于xxx的方法论有哪些?
|
|
76
|
+
2. 如何解决xxx问题?
|
|
77
|
+
3. xxx的最佳实践是什么?
|
|
78
|
+
4. 处理xxx的标准流程是什么?
|
|
79
|
+
5. 实现xxx的参考方案有哪些?
|
|
80
|
+
"""
|
|
71
81
|
# 移除对未知标记的警告输出
|
|
72
|
-
|
|
82
|
+
|
|
73
83
|
return user_input, False
|
|
@@ -13,7 +13,7 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
|
13
13
|
def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
14
14
|
prompt = user_input
|
|
15
15
|
files = []
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
file_refs = re.findall(r"'([^']+)'", user_input)
|
|
18
18
|
for ref in file_refs:
|
|
19
19
|
# Handle file:start,end or file:start:end format
|
|
@@ -22,12 +22,12 @@ def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
|
22
22
|
# Initialize with default values
|
|
23
23
|
start_line = 1 # 1-based
|
|
24
24
|
end_line = -1
|
|
25
|
-
|
|
25
|
+
|
|
26
26
|
# Process line range if specified
|
|
27
27
|
if ',' in line_range or ':' in line_range:
|
|
28
28
|
try:
|
|
29
29
|
raw_start, raw_end = map(int, re.split(r'[,:]', line_range))
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
# Handle special values and Python-style negative indices
|
|
32
32
|
try:
|
|
33
33
|
with open(file_path, 'r', encoding='utf-8', errors="ignore") as f:
|
|
@@ -41,24 +41,24 @@ def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
|
41
41
|
end_line = total_lines
|
|
42
42
|
else:
|
|
43
43
|
start_line = raw_start if raw_start > 0 else total_lines + raw_start + 1
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
# Process end line
|
|
46
46
|
if raw_end == 0: # 0表示整个文件(如果start也是0)
|
|
47
47
|
end_line = total_lines
|
|
48
48
|
else:
|
|
49
49
|
end_line = raw_end if raw_end > 0 else total_lines + raw_end + 1
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
# Auto-correct ranges
|
|
52
52
|
start_line = max(1, min(start_line, total_lines))
|
|
53
53
|
end_line = max(start_line, min(end_line, total_lines))
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
# Final validation
|
|
56
56
|
if start_line < 1 or end_line > total_lines or start_line > end_line:
|
|
57
57
|
raise ValueError
|
|
58
58
|
|
|
59
59
|
except:
|
|
60
60
|
continue
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
# Add file if it exists
|
|
63
63
|
if os.path.isfile(file_path):
|
|
64
64
|
files.append({
|
|
@@ -74,7 +74,7 @@ def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
|
74
74
|
"start_line": 1, # 1-based
|
|
75
75
|
"end_line": -1
|
|
76
76
|
})
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
# Read and process files if any were found
|
|
79
79
|
if files:
|
|
80
80
|
with yaspin(text="正在读取文件...", color="cyan") as spinner:
|
|
@@ -83,6 +83,6 @@ def file_input_handler(user_input: str, agent: Any) -> Tuple[str, bool]:
|
|
|
83
83
|
spinner.text = "文件读取完成"
|
|
84
84
|
spinner.ok("✅")
|
|
85
85
|
return result["stdout"] + "\n" + prompt, False
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
return prompt, False
|
|
88
88
|
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from prompt_toolkit import prompt
|
|
5
|
+
import yaml
|
|
6
|
+
from yaspin import yaspin
|
|
7
|
+
from jarvis.jarvis_agent import (
|
|
8
|
+
PrettyOutput, OutputType,
|
|
9
|
+
get_multiline_input,
|
|
10
|
+
Agent, # 显式导入关键组件
|
|
11
|
+
origin_agent_system_prompt
|
|
12
|
+
)
|
|
13
|
+
from jarvis.jarvis_agent.patch import PatchOutputHandler
|
|
14
|
+
from jarvis.jarvis_tools.registry import ToolRegistry
|
|
15
|
+
from jarvis.jarvis_utils.utils import init_env
|
|
16
|
+
from jarvis.jarvis_agent.file_input_handler import file_input_handler
|
|
17
|
+
from jarvis.jarvis_agent.shell_input_handler import shell_input_handler
|
|
18
|
+
from jarvis.jarvis_agent.builtin_input_handler import builtin_input_handler
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _load_tasks() -> dict:
|
|
22
|
+
"""Load tasks from .jarvis files in user home and current directory."""
|
|
23
|
+
tasks = {}
|
|
24
|
+
|
|
25
|
+
# Check .jarvis/pre-command in user directory
|
|
26
|
+
user_jarvis = os.path.expanduser("~/.jarvis/pre-command")
|
|
27
|
+
if os.path.exists(user_jarvis):
|
|
28
|
+
with yaspin(text=f"从{user_jarvis}加载预定义任务...", color="cyan") as spinner:
|
|
29
|
+
try:
|
|
30
|
+
with open(user_jarvis, "r", encoding="utf-8", errors="ignore") as f:
|
|
31
|
+
user_tasks = yaml.safe_load(f)
|
|
32
|
+
|
|
33
|
+
if isinstance(user_tasks, dict):
|
|
34
|
+
# Validate and add user directory tasks
|
|
35
|
+
for name, desc in user_tasks.items():
|
|
36
|
+
if desc: # Ensure description is not empty
|
|
37
|
+
tasks[str(name)] = str(desc)
|
|
38
|
+
spinner.text = "预定义任务加载完成"
|
|
39
|
+
spinner.ok("✅")
|
|
40
|
+
except Exception as e:
|
|
41
|
+
spinner.text = "预定义任务加载失败"
|
|
42
|
+
spinner.fail("❌")
|
|
43
|
+
|
|
44
|
+
# Check .jarvis/pre-command in current directory
|
|
45
|
+
if os.path.exists(".jarvis/pre-command"):
|
|
46
|
+
with yaspin(text=f"从{os.path.abspath('.jarvis/pre-command')}加载预定义任务...", color="cyan") as spinner:
|
|
47
|
+
try:
|
|
48
|
+
with open(".jarvis/pre-command", "r", encoding="utf-8", errors="ignore") as f:
|
|
49
|
+
local_tasks = yaml.safe_load(f)
|
|
50
|
+
|
|
51
|
+
if isinstance(local_tasks, dict):
|
|
52
|
+
# Validate and add current directory tasks, overwrite user directory tasks if there is a name conflict
|
|
53
|
+
for name, desc in local_tasks.items():
|
|
54
|
+
if desc: # Ensure description is not empty
|
|
55
|
+
tasks[str(name)] = str(desc)
|
|
56
|
+
spinner.text = "预定义任务加载完成"
|
|
57
|
+
spinner.ok("✅")
|
|
58
|
+
except Exception as e:
|
|
59
|
+
spinner.text = "预定义任务加载失败"
|
|
60
|
+
spinner.fail("❌")
|
|
61
|
+
|
|
62
|
+
return tasks
|
|
63
|
+
|
|
64
|
+
def _select_task(tasks: dict) -> str:
|
|
65
|
+
"""Let user select a task from the list or skip. Returns task description if selected."""
|
|
66
|
+
if not tasks:
|
|
67
|
+
return ""
|
|
68
|
+
# Convert tasks to list for ordered display
|
|
69
|
+
task_names = list(tasks.keys())
|
|
70
|
+
|
|
71
|
+
task_list = ["可用任务:"]
|
|
72
|
+
for i, name in enumerate(task_names, 1):
|
|
73
|
+
task_list.append(f"[{i}] {name}")
|
|
74
|
+
task_list.append("[0] 跳过预定义任务")
|
|
75
|
+
PrettyOutput.print("\n".join(task_list), OutputType.INFO)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
while True:
|
|
79
|
+
try:
|
|
80
|
+
choice = prompt(
|
|
81
|
+
"\n请选择一个任务编号(0 跳过预定义任务):",
|
|
82
|
+
).strip()
|
|
83
|
+
|
|
84
|
+
if not choice:
|
|
85
|
+
return ""
|
|
86
|
+
|
|
87
|
+
choice = int(choice)
|
|
88
|
+
if choice == 0:
|
|
89
|
+
return ""
|
|
90
|
+
elif 1 <= choice <= len(task_names):
|
|
91
|
+
selected_name = task_names[choice - 1]
|
|
92
|
+
return tasks[selected_name] # Return the task description
|
|
93
|
+
else:
|
|
94
|
+
PrettyOutput.print("无效的选择。请选择列表中的一个号码。", OutputType.WARNING)
|
|
95
|
+
|
|
96
|
+
except KeyboardInterrupt:
|
|
97
|
+
return "" # Return empty on Ctrl+C
|
|
98
|
+
except EOFError:
|
|
99
|
+
return "" # Return empty on Ctrl+D
|
|
100
|
+
except Exception as e:
|
|
101
|
+
PrettyOutput.print(f"选择任务失败: {str(e)}", OutputType.ERROR)
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def main() -> int:
|
|
108
|
+
"""Jarvis main entry point"""
|
|
109
|
+
init_env()
|
|
110
|
+
parser = argparse.ArgumentParser(description='Jarvis AI assistant')
|
|
111
|
+
parser.add_argument('-p', '--platform', type=str, help='Platform to use')
|
|
112
|
+
parser.add_argument('-m', '--model', type=str, help='Model to use')
|
|
113
|
+
args = parser.parse_args()
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
agent = Agent(
|
|
117
|
+
system_prompt=origin_agent_system_prompt,
|
|
118
|
+
platform=args.platform,
|
|
119
|
+
model_name=args.model,
|
|
120
|
+
input_handler=[file_input_handler, shell_input_handler, builtin_input_handler], # type: ignore
|
|
121
|
+
output_handler=[ToolRegistry(), PatchOutputHandler()],
|
|
122
|
+
need_summary=False
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
tasks = _load_tasks()
|
|
126
|
+
if tasks:
|
|
127
|
+
selected_task = _select_task(tasks)
|
|
128
|
+
if selected_task:
|
|
129
|
+
PrettyOutput.print(f"执行任务: {selected_task}", OutputType.INFO)
|
|
130
|
+
agent.run(selected_task)
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
user_input = get_multiline_input("请输入你的任务(输入空行退出):")
|
|
134
|
+
if user_input:
|
|
135
|
+
agent.run(user_input)
|
|
136
|
+
return 0
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
PrettyOutput.print(f"初始化错误: {str(e)}", OutputType.ERROR)
|
|
140
|
+
return 1
|
|
141
|
+
|
|
142
|
+
if __name__ == "__main__":
|
|
143
|
+
exit(main())
|
jarvis/jarvis_agent/main.py
CHANGED
|
@@ -7,22 +7,20 @@ from jarvis.jarvis_utils.input import get_multiline_input
|
|
|
7
7
|
from jarvis.jarvis_utils.output import PrettyOutput, OutputType
|
|
8
8
|
from jarvis.jarvis_utils.utils import init_env
|
|
9
9
|
|
|
10
|
-
# 从__init__.py导入系统提示
|
|
11
|
-
from jarvis.jarvis_agent import origin_agent_system_prompt
|
|
12
10
|
|
|
13
11
|
def load_config(config_path: str) -> dict:
|
|
14
12
|
"""Load configuration from YAML file
|
|
15
|
-
|
|
13
|
+
|
|
16
14
|
Args:
|
|
17
15
|
config_path: Path to the YAML configuration file
|
|
18
|
-
|
|
16
|
+
|
|
19
17
|
Returns:
|
|
20
18
|
dict: Configuration dictionary
|
|
21
19
|
"""
|
|
22
20
|
if not os.path.exists(config_path):
|
|
23
21
|
PrettyOutput.print(f"配置文件 {config_path} 不存在,使用默认配置", OutputType.WARNING)
|
|
24
22
|
return {}
|
|
25
|
-
|
|
23
|
+
|
|
26
24
|
with open(config_path, 'r', encoding='utf-8', errors="ignore") as f:
|
|
27
25
|
try:
|
|
28
26
|
config = yaml.safe_load(f)
|
|
@@ -35,42 +33,43 @@ def main():
|
|
|
35
33
|
"""Main entry point for Jarvis agent"""
|
|
36
34
|
# Initialize environment
|
|
37
35
|
init_env()
|
|
38
|
-
|
|
36
|
+
|
|
39
37
|
# Set up argument parser
|
|
40
38
|
parser = argparse.ArgumentParser(description='Jarvis AI assistant')
|
|
41
|
-
parser.add_argument('-c', '--config', type=str, required=True,
|
|
39
|
+
parser.add_argument('-c', '--config', type=str, required=True,
|
|
42
40
|
help='Path to the YAML configuration file')
|
|
43
41
|
parser.add_argument('-t', '--task', type=str,
|
|
44
42
|
help='Initial task to execute')
|
|
45
43
|
args = parser.parse_args()
|
|
46
|
-
|
|
44
|
+
|
|
47
45
|
# Load configuration
|
|
48
46
|
config = load_config(args.config)
|
|
49
|
-
|
|
47
|
+
|
|
50
48
|
# Create and run agent
|
|
51
49
|
try:
|
|
52
50
|
agent = Agent(**config)
|
|
53
|
-
|
|
51
|
+
|
|
54
52
|
# Run agent with initial task if specified
|
|
55
53
|
if args.task:
|
|
56
54
|
PrettyOutput.print(f"执行初始任务: {args.task}", OutputType.INFO)
|
|
57
55
|
agent.run(args.task)
|
|
58
56
|
return 0
|
|
59
|
-
|
|
57
|
+
|
|
60
58
|
# Enter interactive mode if no initial task
|
|
61
59
|
while True:
|
|
62
60
|
try:
|
|
63
61
|
user_input = get_multiline_input("请输入你的任务(输入空行退出):")
|
|
64
62
|
if not user_input:
|
|
65
63
|
break
|
|
64
|
+
agent.set_addon_prompt("如果有必要,请先指定出行动计划,然后根据计划一步步执行")
|
|
66
65
|
agent.run(user_input)
|
|
67
66
|
except Exception as e:
|
|
68
67
|
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
|
69
|
-
|
|
68
|
+
|
|
70
69
|
except Exception as e:
|
|
71
70
|
PrettyOutput.print(f"初始化错误: {str(e)}", OutputType.ERROR)
|
|
72
71
|
return 1
|
|
73
|
-
|
|
72
|
+
|
|
74
73
|
return 0
|
|
75
74
|
|
|
76
75
|
if __name__ == "__main__":
|
|
@@ -7,13 +7,13 @@ from typing import Any, Tuple
|
|
|
7
7
|
|
|
8
8
|
class OutputHandler(ABC):
|
|
9
9
|
@abstractmethod
|
|
10
|
-
def handle(self, response: str) -> Tuple[bool, Any]:
|
|
10
|
+
def handle(self, response: str, agent: Any) -> Tuple[bool, Any]:
|
|
11
11
|
pass
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
@abstractmethod
|
|
14
14
|
def can_handle(self, response: str) -> bool:
|
|
15
15
|
pass
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
@abstractmethod
|
|
18
18
|
def prompt(self) -> str:
|
|
19
19
|
pass
|