jarvis-ai-assistant 0.1.44__py3-none-any.whl → 0.1.46__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 +1 -1
- jarvis/agent.py +47 -32
- jarvis/main.py +52 -30
- jarvis/models/__init__.py +1 -1
- jarvis/models/ai8.py +88 -58
- jarvis/models/base.py +6 -6
- jarvis/models/kimi.py +171 -80
- jarvis/models/openai.py +43 -23
- jarvis/models/oyi.py +93 -65
- jarvis/models/registry.py +63 -44
- jarvis/tools/__init__.py +1 -1
- jarvis/tools/base.py +2 -2
- jarvis/tools/file_ops.py +19 -15
- jarvis/tools/generator.py +15 -12
- jarvis/tools/methodology.py +20 -20
- jarvis/tools/registry.py +44 -30
- jarvis/tools/shell.py +12 -11
- jarvis/tools/sub_agent.py +1 -2
- jarvis/utils.py +47 -27
- {jarvis_ai_assistant-0.1.44.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/METADATA +1 -1
- jarvis_ai_assistant-0.1.46.dist-info/RECORD +25 -0
- jarvis/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/__pycache__/agent.cpython-313.pyc +0 -0
- jarvis/__pycache__/main.cpython-313.pyc +0 -0
- jarvis/__pycache__/models.cpython-313.pyc +0 -0
- jarvis/__pycache__/tools.cpython-313.pyc +0 -0
- jarvis/__pycache__/utils.cpython-313.pyc +0 -0
- jarvis/__pycache__/zte_llm.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/ai8.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/kimi.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/openai.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/oyi.cpython-313.pyc +0 -0
- jarvis/models/__pycache__/registry.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/base.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/bing_search.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/calculator.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/calculator_tool.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/file_ops.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/generator.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/methodology.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/python_script.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/rag.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/registry.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/search.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/shell.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/sub_agent.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_confirmation.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_input.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/user_interaction.cpython-313.pyc +0 -0
- jarvis/tools/__pycache__/webpage.cpython-313.pyc +0 -0
- jarvis_ai_assistant-0.1.44.dist-info/RECORD +0 -57
- {jarvis_ai_assistant-0.1.44.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.44.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.44.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.44.dist-info → jarvis_ai_assistant-0.1.46.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
jarvis/agent.py
CHANGED
@@ -10,10 +10,11 @@ import os
|
|
10
10
|
from datetime import datetime
|
11
11
|
from prompt_toolkit import prompt
|
12
12
|
|
13
|
+
|
13
14
|
class Agent:
|
14
15
|
def __init__(self, name: str = "Jarvis", is_sub_agent: bool = False):
|
15
16
|
"""Initialize Agent with a model, optional tool registry and name
|
16
|
-
|
17
|
+
|
17
18
|
Args:
|
18
19
|
model: 语言模型实例
|
19
20
|
tool_registry: 工具注册表实例
|
@@ -26,7 +27,6 @@ class Agent:
|
|
26
27
|
self.is_sub_agent = is_sub_agent
|
27
28
|
self.prompt = ""
|
28
29
|
|
29
|
-
|
30
30
|
@staticmethod
|
31
31
|
def extract_tool_calls(content: str) -> List[Dict]:
|
32
32
|
"""从内容中提取工具调用,如果检测到多个工具调用则抛出异常,并返回工具调用之前的内容和工具调用"""
|
@@ -34,7 +34,7 @@ class Agent:
|
|
34
34
|
lines = content.split('\n')
|
35
35
|
tool_call_lines = []
|
36
36
|
in_tool_call = False
|
37
|
-
|
37
|
+
|
38
38
|
# 逐行处理
|
39
39
|
for line in lines:
|
40
40
|
if '<START_TOOL_CALL>' in line:
|
@@ -46,7 +46,7 @@ class Agent:
|
|
46
46
|
# 直接解析YAML
|
47
47
|
tool_call_text = '\n'.join(tool_call_lines)
|
48
48
|
tool_call_data = yaml.safe_load(tool_call_text)
|
49
|
-
|
49
|
+
|
50
50
|
# 验证必要的字段
|
51
51
|
if "name" in tool_call_data and "arguments" in tool_call_data:
|
52
52
|
# 返回工具调用之前的内容和工具调用
|
@@ -58,17 +58,21 @@ class Agent:
|
|
58
58
|
PrettyOutput.print("工具调用缺少必要字段", OutputType.ERROR)
|
59
59
|
raise Exception("工具调用缺少必要字段")
|
60
60
|
except yaml.YAMLError as e:
|
61
|
-
PrettyOutput.print(
|
61
|
+
PrettyOutput.print(
|
62
|
+
f"YAML解析错误: {
|
63
|
+
str(e)}", OutputType.ERROR)
|
62
64
|
raise Exception(f"YAML解析错误: {str(e)}")
|
63
65
|
except Exception as e:
|
64
|
-
PrettyOutput.print(
|
66
|
+
PrettyOutput.print(
|
67
|
+
f"处理工具调用时发生错误: {
|
68
|
+
str(e)}", OutputType.ERROR)
|
65
69
|
raise Exception(f"处理工具调用时发生错误: {str(e)}")
|
66
70
|
in_tool_call = False
|
67
71
|
continue
|
68
|
-
|
72
|
+
|
69
73
|
if in_tool_call:
|
70
74
|
tool_call_lines.append(line)
|
71
|
-
|
75
|
+
|
72
76
|
return []
|
73
77
|
|
74
78
|
def _call_model(self, message: str) -> str:
|
@@ -79,14 +83,15 @@ class Agent:
|
|
79
83
|
if ret:
|
80
84
|
return ret
|
81
85
|
else:
|
82
|
-
PrettyOutput.print(
|
86
|
+
PrettyOutput.print(
|
87
|
+
f"调用模型失败,重试中... 等待 {sleep_time}s",
|
88
|
+
OutputType.INFO)
|
83
89
|
time.sleep(sleep_time)
|
84
90
|
sleep_time *= 2
|
85
91
|
if sleep_time > 30:
|
86
92
|
sleep_time = 30
|
87
93
|
continue
|
88
94
|
|
89
|
-
|
90
95
|
def _load_methodology(self) -> str:
|
91
96
|
"""加载经验总结"""
|
92
97
|
user_jarvis_methodology = os.path.expanduser("~/.jarvis_methodology")
|
@@ -96,23 +101,28 @@ class Agent:
|
|
96
101
|
data = yaml.safe_load(f)
|
97
102
|
for k, v in data.items():
|
98
103
|
ret += f"问题类型: \n{k}\n经验总结: \n{v}\n\n"
|
99
|
-
PrettyOutput.print(
|
104
|
+
PrettyOutput.print(
|
105
|
+
f"从 {user_jarvis_methodology} 加载经验总结: {
|
106
|
+
', '.join(
|
107
|
+
data.keys())}",
|
108
|
+
OutputType.INFO)
|
100
109
|
return ret
|
101
110
|
|
102
|
-
def run(self, user_input: str,
|
111
|
+
def run(self, user_input: str,
|
112
|
+
file_list: Optional[List[str]] = None, keep_history: bool = False) -> str:
|
103
113
|
"""处理用户输入并返回响应,返回任务总结报告
|
104
|
-
|
114
|
+
|
105
115
|
Args:
|
106
116
|
user_input: 用户输入的任务描述
|
107
117
|
file_list: 可选的文件列表,默认为None
|
108
118
|
keep_history: 是否保留对话历史,默认为False
|
109
|
-
|
119
|
+
|
110
120
|
Returns:
|
111
121
|
str: 任务总结报告
|
112
122
|
"""
|
113
123
|
try:
|
114
124
|
self.clear_history()
|
115
|
-
|
125
|
+
|
116
126
|
if file_list:
|
117
127
|
self.model.upload_files(file_list)
|
118
128
|
|
@@ -125,7 +135,7 @@ class Agent:
|
|
125
135
|
{methodology}
|
126
136
|
|
127
137
|
"""
|
128
|
-
|
138
|
+
|
129
139
|
# 显示任务开始
|
130
140
|
PrettyOutput.section(f"开始新任务: {self.name}", OutputType.PLANNING)
|
131
141
|
|
@@ -136,7 +146,7 @@ class Agent:
|
|
136
146
|
tools_prompt += f" 参数: {tool['parameters']}\n"
|
137
147
|
|
138
148
|
self.model.set_system_message(f"""你是 {self.name},一个问题处理能力强大的 AI 助手。
|
139
|
-
|
149
|
+
|
140
150
|
你会严格按照以下步骤处理问题:
|
141
151
|
1. 问题重述:确认理解问题
|
142
152
|
2. 根因分析(如果是问题分析类需要,其他不需要)
|
@@ -148,7 +158,7 @@ class Agent:
|
|
148
158
|
8. 监控与调整:如果执行结果与预期不符,则反思并调整行动计划,迭代之前的步骤
|
149
159
|
9. 更新经验总结(如有必要):任务完成后总结执行过程中的经验教训,生成同类问题的通用经验总结,使用经验总结工具进行更新或者添加
|
150
160
|
|
151
|
-
-------------------------------------------------------------
|
161
|
+
-------------------------------------------------------------
|
152
162
|
|
153
163
|
经验总结模板:
|
154
164
|
1. 问题重述
|
@@ -193,27 +203,30 @@ arguments:
|
|
193
203
|
try:
|
194
204
|
# 显示思考状态
|
195
205
|
PrettyOutput.print("分析任务...", OutputType.PROGRESS)
|
196
|
-
|
206
|
+
|
197
207
|
current_response = self._call_model(self.prompt)
|
198
|
-
|
208
|
+
|
199
209
|
try:
|
200
210
|
result = Agent.extract_tool_calls(current_response)
|
201
211
|
except Exception as e:
|
202
|
-
PrettyOutput.print(
|
212
|
+
PrettyOutput.print(
|
213
|
+
f"工具调用错误: {str(e)}", OutputType.ERROR)
|
203
214
|
self.prompt = f"工具调用错误: {str(e)}"
|
204
215
|
continue
|
205
|
-
|
216
|
+
|
206
217
|
if len(result) > 0:
|
207
218
|
|
208
219
|
PrettyOutput.print("执行工具调用...", OutputType.PROGRESS)
|
209
|
-
tool_result = self.tool_registry.handle_tool_calls(
|
220
|
+
tool_result = self.tool_registry.handle_tool_calls(
|
221
|
+
result)
|
210
222
|
PrettyOutput.print(tool_result, OutputType.RESULT)
|
211
223
|
|
212
224
|
self.prompt = tool_result
|
213
225
|
continue
|
214
|
-
|
226
|
+
|
215
227
|
# 获取用户输入
|
216
|
-
user_input = get_multiline_input(
|
228
|
+
user_input = get_multiline_input(
|
229
|
+
f"{self.name}: 您可以继续输入,或输入空行结束当前任务")
|
217
230
|
if user_input == "__interrupt__":
|
218
231
|
PrettyOutput.print("任务已取消", OutputType.WARNING)
|
219
232
|
return "Task cancelled by user"
|
@@ -221,10 +234,11 @@ arguments:
|
|
221
234
|
if user_input:
|
222
235
|
self.prompt = user_input
|
223
236
|
continue
|
224
|
-
|
237
|
+
|
225
238
|
if not user_input:
|
226
239
|
while True:
|
227
|
-
choice = prompt(
|
240
|
+
choice = prompt(
|
241
|
+
"是否需要手动为此任务生成经验总结以提升Jarvis对类似任务的处理能力?(y/n), 回车跳过: ")
|
228
242
|
if choice == "y":
|
229
243
|
self._make_methodology()
|
230
244
|
break
|
@@ -259,18 +273,20 @@ arguments:
|
|
259
273
|
except Exception as e:
|
260
274
|
PrettyOutput.print(str(e), OutputType.ERROR)
|
261
275
|
return f"Task failed: {str(e)}"
|
262
|
-
|
276
|
+
|
263
277
|
finally:
|
264
278
|
# 只在不保留历史时删除会话
|
265
279
|
if not keep_history:
|
266
280
|
try:
|
267
281
|
self.model.delete_chat()
|
268
282
|
except Exception as e:
|
269
|
-
PrettyOutput.print(
|
283
|
+
PrettyOutput.print(
|
284
|
+
f"清理会话时发生错误: {
|
285
|
+
str(e)}", OutputType.ERROR)
|
270
286
|
|
271
287
|
def clear_history(self):
|
272
288
|
"""清除对话历史,只保留系统提示"""
|
273
|
-
self.prompt = ""
|
289
|
+
self.prompt = ""
|
274
290
|
self.model.reset()
|
275
291
|
|
276
292
|
def _make_methodology(self):
|
@@ -281,7 +297,7 @@ arguments:
|
|
281
297
|
2. 最优解决方案
|
282
298
|
3. 最优方案执行步骤(失败的行动不需要体现)
|
283
299
|
""")
|
284
|
-
|
300
|
+
|
285
301
|
try:
|
286
302
|
result = Agent.extract_tool_calls(current_response)
|
287
303
|
except Exception as e:
|
@@ -291,4 +307,3 @@ arguments:
|
|
291
307
|
PrettyOutput.print("执行工具调用...", OutputType.PROGRESS)
|
292
308
|
tool_result = self.tool_registry.handle_tool_calls(result)
|
293
309
|
PrettyOutput.print(tool_result, OutputType.RESULT)
|
294
|
-
|
jarvis/main.py
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
#!/usr/bin/env python3
|
2
2
|
"""Command line interface for Jarvis."""
|
3
3
|
|
4
|
+
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, load_env_from_file
|
5
|
+
from jarvis.tools import ToolRegistry
|
6
|
+
from jarvis.agent import Agent
|
4
7
|
import argparse
|
5
8
|
import yaml
|
6
9
|
import os
|
@@ -13,73 +16,76 @@ from jarvis.models.registry import PlatformRegistry
|
|
13
16
|
# 添加父目录到Python路径以支持导入
|
14
17
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
15
18
|
|
16
|
-
from jarvis.agent import Agent
|
17
|
-
from jarvis.tools import ToolRegistry
|
18
|
-
from jarvis.utils import PrettyOutput, OutputType, get_multiline_input, load_env_from_file
|
19
|
-
|
20
19
|
|
21
20
|
def load_tasks() -> dict:
|
22
21
|
"""Load tasks from .jarvis files in user home and current directory."""
|
23
22
|
tasks = {}
|
24
|
-
|
23
|
+
|
25
24
|
# 检查用户目录下的 .jarvis
|
26
25
|
user_jarvis = os.path.expanduser("~/.jarvis")
|
27
26
|
if os.path.exists(user_jarvis):
|
28
27
|
try:
|
29
28
|
with open(user_jarvis, "r", encoding="utf-8") as f:
|
30
29
|
user_tasks = yaml.safe_load(f)
|
31
|
-
|
30
|
+
|
32
31
|
if isinstance(user_tasks, dict):
|
33
32
|
# 验证并添加用户目录的任务
|
34
33
|
for name, desc in user_tasks.items():
|
35
34
|
if desc: # 确保描述不为空
|
36
35
|
tasks[str(name)] = str(desc)
|
37
36
|
else:
|
38
|
-
PrettyOutput.print(
|
37
|
+
PrettyOutput.print(
|
38
|
+
"Warning: ~/.jarvis file should contain a dictionary of task_name: task_description",
|
39
|
+
OutputType.ERROR)
|
39
40
|
except Exception as e:
|
40
|
-
PrettyOutput.print(
|
41
|
-
|
41
|
+
PrettyOutput.print(
|
42
|
+
f"Error loading ~/.jarvis file: {str(e)}", OutputType.ERROR)
|
43
|
+
|
42
44
|
# 检查当前目录下的 .jarvis
|
43
45
|
if os.path.exists(".jarvis"):
|
44
46
|
try:
|
45
47
|
with open(".jarvis", "r", encoding="utf-8") as f:
|
46
48
|
local_tasks = yaml.safe_load(f)
|
47
|
-
|
49
|
+
|
48
50
|
if isinstance(local_tasks, dict):
|
49
51
|
# 验证并添加当前目录的任务,如果有重名则覆盖用户目录的任务
|
50
52
|
for name, desc in local_tasks.items():
|
51
53
|
if desc: # 确保描述不为空
|
52
54
|
tasks[str(name)] = str(desc)
|
53
55
|
else:
|
54
|
-
PrettyOutput.print(
|
56
|
+
PrettyOutput.print(
|
57
|
+
"Warning: .jarvis file should contain a dictionary of task_name: task_description",
|
58
|
+
OutputType.ERROR)
|
55
59
|
except Exception as e:
|
56
|
-
PrettyOutput.print(
|
57
|
-
|
60
|
+
PrettyOutput.print(
|
61
|
+
f"Error loading .jarvis file: {
|
62
|
+
str(e)}", OutputType.ERROR)
|
63
|
+
|
58
64
|
return tasks
|
59
65
|
|
66
|
+
|
60
67
|
def select_task(tasks: dict) -> str:
|
61
68
|
"""Let user select a task from the list or skip. Returns task description if selected."""
|
62
69
|
if not tasks:
|
63
70
|
return ""
|
64
|
-
|
71
|
+
|
65
72
|
# Convert tasks to list for ordered display
|
66
73
|
task_names = list(tasks.keys())
|
67
|
-
|
74
|
+
|
68
75
|
PrettyOutput.print("\nAvailable tasks:", OutputType.INFO)
|
69
76
|
for i, name in enumerate(task_names, 1):
|
70
77
|
PrettyOutput.print(f"[{i}] {name}", OutputType.INFO)
|
71
78
|
PrettyOutput.print("[0] 跳过预定义任务", OutputType.INFO)
|
72
|
-
|
73
|
-
|
79
|
+
|
74
80
|
while True:
|
75
81
|
try:
|
76
82
|
choice = prompt(
|
77
83
|
"\n请选择一个任务编号(0跳过): ",
|
78
84
|
).strip()
|
79
|
-
|
85
|
+
|
80
86
|
if not choice:
|
81
87
|
return ""
|
82
|
-
|
88
|
+
|
83
89
|
choice = int(choice)
|
84
90
|
if choice == 0:
|
85
91
|
return ""
|
@@ -87,8 +93,10 @@ def select_task(tasks: dict) -> str:
|
|
87
93
|
selected_name = task_names[choice - 1]
|
88
94
|
return tasks[selected_name] # Return the task description
|
89
95
|
else:
|
90
|
-
PrettyOutput.print(
|
91
|
-
|
96
|
+
PrettyOutput.print(
|
97
|
+
"Invalid choice. Please select a number from the list.",
|
98
|
+
OutputType.ERROR)
|
99
|
+
|
92
100
|
except KeyboardInterrupt:
|
93
101
|
return "" # Return empty on Ctrl+C
|
94
102
|
except EOFError:
|
@@ -97,12 +105,16 @@ def select_task(tasks: dict) -> str:
|
|
97
105
|
PrettyOutput.print(f"选择失败: {str(e)}", OutputType.ERROR)
|
98
106
|
continue
|
99
107
|
|
108
|
+
|
100
109
|
def main():
|
101
110
|
"""Jarvis 的主入口点"""
|
102
111
|
# 添加参数解析器
|
103
112
|
parser = argparse.ArgumentParser(description='Jarvis AI 助手')
|
104
113
|
parser.add_argument('-f', '--files', nargs='*', help='要处理的文件列表')
|
105
|
-
parser.add_argument(
|
114
|
+
parser.add_argument(
|
115
|
+
'--keep-history',
|
116
|
+
action='store_true',
|
117
|
+
help='保持聊天历史(不删除会话)')
|
106
118
|
parser.add_argument('-p', '--platform', default='', help='选择AI平台')
|
107
119
|
args = parser.parse_args()
|
108
120
|
|
@@ -111,36 +123,45 @@ def main():
|
|
111
123
|
platform = args.platform if args.platform else os.getenv('JARVIS_PLATFORM')
|
112
124
|
|
113
125
|
if not platform:
|
114
|
-
PrettyOutput.print(
|
126
|
+
PrettyOutput.print(
|
127
|
+
"未指定AI平台,请使用 -p 参数或者设置 JARVIS_PLATFORM 环境变量",
|
128
|
+
OutputType.ERROR)
|
115
129
|
return 1
|
116
130
|
|
117
131
|
PlatformRegistry.get_global_platform_registry().set_global_platform_name(platform)
|
118
|
-
|
132
|
+
|
119
133
|
try:
|
120
134
|
# 获取全局模型实例
|
121
135
|
agent = Agent()
|
122
136
|
|
123
137
|
# 欢迎信息
|
124
|
-
PrettyOutput.print(
|
138
|
+
PrettyOutput.print(
|
139
|
+
f"Jarvis 已初始化 - With {platform} 平台,模型: {agent.model.name()}", OutputType.SYSTEM)
|
125
140
|
if args.keep_history:
|
126
141
|
PrettyOutput.print("已启用历史保留模式", OutputType.INFO)
|
127
|
-
|
142
|
+
|
128
143
|
# 加载预定义任务
|
129
144
|
tasks = load_tasks()
|
130
145
|
if tasks:
|
131
146
|
selected_task = select_task(tasks)
|
132
147
|
if selected_task:
|
133
148
|
PrettyOutput.print(f"\n执行任务: {selected_task}", OutputType.INFO)
|
134
|
-
agent.run(
|
149
|
+
agent.run(
|
150
|
+
selected_task,
|
151
|
+
args.files,
|
152
|
+
keep_history=args.keep_history)
|
135
153
|
return 0
|
136
|
-
|
154
|
+
|
137
155
|
# 如果没有选择预定义任务,进入交互模式
|
138
156
|
while True:
|
139
157
|
try:
|
140
158
|
user_input = get_multiline_input("请输入您的任务(输入空行退出):")
|
141
159
|
if not user_input or user_input == "__interrupt__":
|
142
160
|
break
|
143
|
-
agent.run(
|
161
|
+
agent.run(
|
162
|
+
user_input,
|
163
|
+
args.files,
|
164
|
+
keep_history=args.keep_history)
|
144
165
|
except Exception as e:
|
145
166
|
PrettyOutput.print(f"错误: {str(e)}", OutputType.ERROR)
|
146
167
|
|
@@ -150,5 +171,6 @@ def main():
|
|
150
171
|
|
151
172
|
return 0
|
152
173
|
|
174
|
+
|
153
175
|
if __name__ == "__main__":
|
154
|
-
exit(main())
|
176
|
+
exit(main())
|
jarvis/models/__init__.py
CHANGED