jarvis-ai-assistant 0.1.50__py3-none-any.whl → 0.1.52__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 CHANGED
@@ -1,3 +1,3 @@
1
1
  """Jarvis AI Assistant"""
2
2
 
3
- __version__ = "0.1.50"
3
+ __version__ = "0.1.52"
jarvis/agent.py CHANGED
@@ -180,6 +180,7 @@ arguments:
180
180
  5. 如果现有信息不足以解决问题,则可以询问用户
181
181
  6. 处理问题的每个步骤不是必须有的,可按情况省略
182
182
  7. 在执行一些可能对系统或者用户代码库造成破坏的工具时,请先询问用户
183
+ 8. 在多次迭代却没有任何进展时,可请求用户指导
183
184
 
184
185
  -------------------------------------------------------------
185
186
 
@@ -0,0 +1,430 @@
1
+ import hashlib
2
+ import os
3
+ import re
4
+ import sqlite3
5
+ from typing import Dict, Any, List, Optional
6
+
7
+ import yaml
8
+ from jarvis.utils import OutputType, PrettyOutput
9
+ from jarvis.models.registry import PlatformRegistry
10
+
11
+ class CodeEditTool:
12
+ """代码修改工具"""
13
+
14
+ name = "code_edit"
15
+ description = "根据需求描述修改代码文件"
16
+ parameters = {
17
+ "type": "object",
18
+ "properties": {
19
+ "feature": {
20
+ "type": "string",
21
+ "description": "要实现的功能描述"
22
+ },
23
+ "root_dir": {
24
+ "type": "string",
25
+ "description": "代码库根目录"
26
+ },
27
+ "language": {
28
+ "type": "string",
29
+ "description": "编程语言"
30
+ }
31
+ },
32
+ "required": ["feature", "root_dir", "language"]
33
+ }
34
+
35
+ def __init__(self):
36
+ """初始化代码修改工具"""
37
+ self.main_model = self._new_model()
38
+ self.language_extensions = {
39
+ "c": {".c", ".h"},
40
+ "cpp": {".cpp", ".hpp", ".h"},
41
+ "python": {".py", ".pyw"},
42
+ "java": {".java"},
43
+ "go": {".go"},
44
+ "rust": {".rs"},
45
+ "javascript": {".js"},
46
+ "typescript": {".ts"},
47
+ "php": {".php"},
48
+ "ruby": {".rb"},
49
+ "swift": {".swift"},
50
+ "kotlin": {".kt"},
51
+ "scala": {".scala"},
52
+ "haskell": {".hs"},
53
+ "erlang": {".erl"},
54
+ "elixir": {".ex"},
55
+ }
56
+ self.db_path = ""
57
+ self.feature = ""
58
+ self.root_dir = ""
59
+ self.language = ""
60
+ self.current_dir = ""
61
+ def _new_model(self):
62
+ """获取大模型"""
63
+ platform_name = os.environ.get("JARVIS_CODEGEN_PLATFORM", PlatformRegistry.global_platform_name)
64
+ model_name = os.environ.get("JARVIS_CODEGEN_MODEL")
65
+ model = PlatformRegistry().get_global_platform_registry().create_platform(platform_name)
66
+ if model_name:
67
+ model.set_model_name(model_name)
68
+ return model
69
+
70
+ def _has_uncommitted_files(self, root_dir: str) -> bool:
71
+ """判断代码库是否有未提交的文件"""
72
+ os.chdir(root_dir)
73
+ return os.system(f"git status | grep 'Changes not staged for commit'")
74
+
75
+ def _get_key_info(self, file_path: str, content: str) -> Dict[str, Any]:
76
+ """获取文件的关键信息"""
77
+ model = self._new_model()
78
+ prompt = f"""你是一个资深程序员,请根据文件内容,生成文件的关键信息,要求如下,除了代码,不要输出任何内容:
79
+
80
+ 1. 文件路径: {file_path}
81
+ 2. 文件内容:(<CONTENT_START>和<CONTENT_END>之间的部分)
82
+ <CONTENT_START>
83
+ {content}
84
+ <CONTENT_END>
85
+ 3. 关键信息: 请生成文件的关键信息,包括文件功能,符号列表(类型、名称、描述),使用标准的yaml格式描述,仅输出以下格式内容,如果目标文件不是代码文件,输出(无)
86
+ <FILE_INFO_START>
87
+ file_description: xxxx
88
+ symbols:
89
+ - name: xxxx
90
+ description: xxxx
91
+ type: xxxx
92
+ - name: yyyy
93
+ description: yyyy
94
+ type: yyyy
95
+ <FILE_INFO_END>
96
+ """
97
+ response = model.chat(prompt)
98
+ model.delete_chat()
99
+ response = response.replace("<FILE_INFO_START>", "").replace("<FILE_INFO_END>", "")
100
+ return yaml.safe_load(response)
101
+
102
+ def _get_file_extensions(self, language: str) -> List[str]:
103
+ """获取文件扩展名"""
104
+ return self.language_extensions.get(language, [])
105
+
106
+ def _get_file_md5(self, file_path: str) -> str:
107
+ """获取文件MD5"""
108
+ return hashlib.md5(open(file_path, "rb").read()).hexdigest()
109
+
110
+ def _create_index_db(self):
111
+ """创建索引数据库"""
112
+ index_db_path = os.path.join(self.root_dir, ".index.db")
113
+ if not os.path.exists(index_db_path):
114
+ PrettyOutput.print("Index database does not exist, creating...", OutputType.INFO)
115
+ index_db = sqlite3.connect(index_db_path)
116
+ index_db.execute("CREATE TABLE files (file_path TEXT PRIMARY KEY, file_md5 TEXT, file_description TEXT)")
117
+ index_db.execute("CREATE TABLE symbols (file_path TEXT, symbol_name TEXT, symbol_description TEXT, symbol_type TEXT)")
118
+ index_db.commit()
119
+ index_db.close()
120
+ PrettyOutput.print("Index database created", OutputType.SUCCESS)
121
+ # 将.index.db文件添加到gitignore
122
+ with open(os.path.join(self.root_dir, ".gitignore"), "a") as f:
123
+ f.write("\n.index.db\n")
124
+ PrettyOutput.print("Index database added to gitignore", OutputType.SUCCESS)
125
+ # commit
126
+ os.chdir(self.root_dir)
127
+ os.system(f"git add .gitignore -f")
128
+ os.system(f"git commit -m 'add index database'")
129
+ os.chdir(self.current_dir)
130
+
131
+ def _find_file_by_md5(self, index_db_path: str, file_md5: str) -> Optional[str]:
132
+ """根据文件MD5查找文件路径"""
133
+ index_db = sqlite3.connect(index_db_path)
134
+ cursor = index_db.cursor()
135
+ cursor.execute("SELECT file_path FROM files WHERE file_md5 = ?", (file_md5,))
136
+ result = cursor.fetchone()
137
+ index_db.close()
138
+ return result[0] if result else None
139
+
140
+ def _update_file_path(self, index_db_path: str, file_path: str, file_md5: str):
141
+ """更新文件路径"""
142
+ index_db = sqlite3.connect(index_db_path)
143
+ cursor = index_db.cursor()
144
+ cursor.execute("UPDATE files SET file_path = ? WHERE file_md5 = ?", (file_path, file_md5))
145
+ index_db.commit()
146
+ index_db.close()
147
+
148
+ def _insert_info(self, index_db_path: str, file_path: str, file_md5: str, file_description: str, symbols: List[Dict[str, Any]]):
149
+ """插入文件信息"""
150
+ index_db = sqlite3.connect(index_db_path)
151
+ cursor = index_db.cursor()
152
+ cursor.execute("DELETE FROM files WHERE file_path = ?", (file_path,))
153
+ cursor.execute("INSERT INTO files (file_path, file_md5, file_description) VALUES (?, ?, ?)", (file_path, file_md5, file_description))
154
+ for symbol in symbols:
155
+ cursor.execute("INSERT INTO symbols (file_path, symbol_name, symbol_description, symbol_type) VALUES (?, ?, ?, ?)", (file_path, symbol["name"], symbol["description"], symbol["type"]))
156
+ index_db.commit()
157
+ index_db.close()
158
+
159
+ def _index_project(self, language: str):
160
+ """建立代码库索引"""
161
+ # 1. 创建索引数据库,位于root_dir/.index.db
162
+ index_db_path = os.path.join(self.root_dir, ".index.db")
163
+ self.db_path = index_db_path
164
+ if not os.path.exists(index_db_path):
165
+ self._create_index_db()
166
+
167
+ # 2. 遍历文件
168
+ for root, dirs, files in os.walk(self.root_dir):
169
+ for file in files:
170
+ if os.path.splitext(file)[1] in self._get_file_extensions(language):
171
+ # 计算文件MD5
172
+ file_path = os.path.join(root, file)
173
+ file_md5 = self._get_file_md5(file_path)
174
+
175
+ # 查找文件
176
+ file_path_in_db = self._find_file_by_md5(self.db_path, file_md5)
177
+ if file_path_in_db:
178
+ PrettyOutput.print(f"File {file_path} is duplicate, skip", OutputType.INFO)
179
+ if file_path_in_db != file_path:
180
+ self._update_file_path(self.db_path, file_path, file_md5)
181
+ PrettyOutput.print(f"File {file_path} is duplicate, update path to {file_path}", OutputType.INFO)
182
+ continue
183
+
184
+ with open(file_path, "r", encoding="utf-8") as f:
185
+ file_content = f.read()
186
+ key_info = self._get_key_info(file_path, file_content)
187
+ if not key_info:
188
+ PrettyOutput.print(f"File {file_path} index failed", OutputType.INFO)
189
+ continue
190
+ if "file_description" in key_info and "symbols" in key_info:
191
+ self._insert_info(self.db_path, file_path, file_md5, key_info["file_description"], key_info["symbols"])
192
+ PrettyOutput.print(f"File {file_path} is indexed", OutputType.INFO)
193
+ else:
194
+ PrettyOutput.print(f"File {file_path} is not a code file, skip", OutputType.INFO)
195
+ PrettyOutput.print("Index project finished", OutputType.INFO)
196
+
197
+ def _find_related_files(self, feature: str) -> List[str]:
198
+ """根据需求描述,查找相关文件"""
199
+ model = self._new_model()
200
+
201
+ score = [[],[],[],[],[],[],[],[],[],[]]
202
+
203
+ step = 50
204
+ offset = 0
205
+
206
+ # 每次从数据库中提取50条记录,循环提取,直到提取完所有记录
207
+ while True:
208
+ index_db = sqlite3.connect(self.db_path)
209
+ cursor = index_db.cursor()
210
+ cursor.execute(f"SELECT file_path, file_description FROM files LIMIT {step} OFFSET {offset}")
211
+ result = cursor.fetchall()
212
+ index_db.close()
213
+ if not result:
214
+ break
215
+ offset += len(result)
216
+ prompt = "你是资深程序员,请根据需求描述,分析文件的相关性,文件列表如下:\n"
217
+ prompt += "<FILE_LIST_START>\n"
218
+ # 为文件生成编号,提供的信息有,文件序号,文件名,文件描述,并生成prompt,输出每个编号的相关性[1~10]
219
+ for i, file_path in enumerate(result):
220
+ prompt += f"""{i}. {file_path[0]} : {file_path[1]}\n"""
221
+ prompt += f"""需求描述: {feature}\n"""
222
+ prompt += "<FILE_LIST_END>\n"
223
+ prompt += "请根据需求描述,分析文件的相关性,输出每个编号的相关性[0~9],仅输出以下格式内容(key为文件编号,value为相关性)\n"
224
+ prompt += "<FILE_RELATION_START>\n"
225
+ prompt += '''"0": 5'''
226
+ prompt += '''"1": 3'''
227
+ prompt += "<FILE_RELATION_END>\n"
228
+ response = model.chat(prompt)
229
+ model.delete_chat()
230
+ response = response.replace("<FILE_RELATION_START>", "").replace("<FILE_RELATION_END>", "")
231
+ file_relation = yaml.safe_load(response)
232
+ if not file_relation:
233
+ PrettyOutput.print("Response format error", OutputType.WARNING)
234
+ continue
235
+ for file_id, relation in file_relation.items():
236
+ id = int(file_id)
237
+ if relation>9:
238
+ relation = 9
239
+ if relation<0:
240
+ relation = 0
241
+ score[relation].append({"file_path": result[id][0], "file_description": result[id][1]})
242
+
243
+ files = []
244
+ score.reverse()
245
+ for i in score:
246
+ files.extend(i)
247
+ if len(files) >= 10:
248
+ break
249
+ return files
250
+
251
+ def execute(self, args: Dict[str, Any]) -> Dict[str, Any]:
252
+ """执行代码修改
253
+
254
+ Args:
255
+ args: 包含操作参数的字典
256
+ - feature: 要实现的功能描述
257
+ - root_dir: 代码库根目录
258
+ - language: 编程语言
259
+
260
+ Returns:
261
+ Dict[str, Any]: 包含执行结果的字典
262
+ - success: 是否成功
263
+ - changes: 修改内容
264
+ - preview: 修改预览
265
+ - results: 执行结果
266
+ """
267
+ self.feature = args["feature"]
268
+ self.root_dir = args["root_dir"]
269
+ self.language = args["language"]
270
+
271
+ self.current_dir = os.getcwd()
272
+
273
+ # 0. 判断语言是否支持
274
+ if not self._get_file_extensions(self.language):
275
+ PrettyOutput.print("Language not supported", OutputType.ERROR)
276
+ return {"success": False, "changes": "", "preview": "", "results": ""}
277
+
278
+ # 1. 判断代码库路径是否存在,如果不存在,创建
279
+ if not os.path.exists(self.root_dir):
280
+ PrettyOutput.print("Root directory does not exist, creating...", OutputType.INFO)
281
+ os.makedirs(self.root_dir)
282
+
283
+ # 2. 判断代码库是否是git仓库,如果不是,初始化git仓库
284
+ if not os.path.exists(os.path.join(self.root_dir, ".git")):
285
+ PrettyOutput.print("Git repository does not exist, initializing...", OutputType.INFO)
286
+ os.chdir(self.root_dir)
287
+ os.system(f"git init")
288
+ # 2.1 添加所有的文件
289
+ os.system(f"git add .")
290
+ # 2.2 提交
291
+ os.system(f"git commit -m 'Initial commit'")
292
+ os.chdir(self.current_dir)
293
+
294
+ # 3. 查看代码库是否有未提交的文件,如果有,提交一次
295
+ if self._has_uncommitted_files(self.root_dir):
296
+ os.chdir(self.root_dir)
297
+ os.system(f"git add .")
298
+ os.system(f"git commit -m 'commit before code edit'")
299
+ os.chdir(self.current_dir)
300
+
301
+ # 4. 开始建立代码库索引
302
+ os.chdir(self.root_dir)
303
+ self._index_project(self.language)
304
+ os.chdir(self.current_dir)
305
+
306
+ # 5. 根据索引和需求,查找相关文件
307
+ related_files = self._find_related_files(self.feature)
308
+ PrettyOutput.print(f"Related files: {related_files}", OutputType.INFO)
309
+ for file in related_files:
310
+ with open(file["file_path"], "r", encoding="utf-8") as f:
311
+ file_content = f.read()
312
+ file["file_content"] = file_content
313
+
314
+ # 6. 根据相关文件,生成修改方案
315
+ patches = self._make_patch(related_files)
316
+ PrettyOutput.print(f"生成{len(patches)}个patch", OutputType.INFO)
317
+
318
+ if len(patches) == 0:
319
+ PrettyOutput.print("No patch generated, skip", OutputType.INFO)
320
+ return {"success": False, "changes": "", "preview": "", "results": ""}
321
+
322
+ # 7. 应用修改方案
323
+ self._apply_patch(related_files, patches)
324
+
325
+ # 8. 用户确认修改
326
+ user_confirm = input("是否确认修改?(y/n)")
327
+ if user_confirm == "y":
328
+ PrettyOutput.print("修改确认成功,提交修改", OutputType.INFO)
329
+ os.chdir(self.root_dir)
330
+ os.system(f"git add .")
331
+ os.system(f"git commit -m '{self.feature}'")
332
+ os.chdir(self.current_dir)
333
+ # 9. 重新建立代码库索引
334
+ self._index_project(self.language)
335
+ else:
336
+ PrettyOutput.print("修改确认失败,取消修改", OutputType.INFO)
337
+ os.chdir(self.root_dir)
338
+ os.system(f"git reset --hard")
339
+ os.chdir(self.current_dir)
340
+
341
+ def _apply_patch(self, related_files, patches):
342
+ # patch格式
343
+ # >>>>>> [文件路径1]
344
+ # [原文件内容]
345
+ # [原文件内容]
346
+ # ==========
347
+ # [修改后的文件内容]
348
+ # [修改后的文件内容]
349
+ # <<<<<<
350
+
351
+ file_map = {file["file_path"]: file["file_content"] for file in related_files}
352
+
353
+ for i, patch in enumerate(patches):
354
+ PrettyOutput.print(f"Apply patch {i+1} of {len(patches)}", OutputType.INFO)
355
+ patch_line = patch.split("\n")
356
+ file_name = ""
357
+ old_code = []
358
+ new_code = []
359
+ old_code_flag = False
360
+ new_code_flag = False
361
+ for line in patch_line:
362
+ if line.startswith(">>>>>>"):
363
+ old_code_flag = True
364
+ file_name = line.split(" ")[1]
365
+ elif line.startswith("=========="):
366
+ old_code_flag = False
367
+ new_code_flag = True
368
+ elif line.startswith("<<<<<<"):
369
+ new_code_flag = False
370
+ elif old_code_flag:
371
+ old_code.append(line)
372
+ elif new_code_flag:
373
+ new_code.append(line)
374
+ if file_name not in file_map:
375
+ PrettyOutput.print(f"File {file_name} not found in related files, create it", OutputType.WARNING)
376
+ with open(file_name, "w", encoding="utf-8") as f:
377
+ f.write("\n".join(new_code))
378
+ else:
379
+ old_code = "\n".join(old_code)
380
+ new_code = "\n".join(new_code)
381
+ if old_code in file_map[file_name]:
382
+ file_map[file_name] = file_map[file_name].replace(old_code, new_code)
383
+ with open(file_name, "w", encoding="utf-8") as f:
384
+ f.write(file_map[file_name])
385
+ PrettyOutput.print(f"File {file_name} apply patch success", OutputType.SUCCESS)
386
+ else:
387
+ PrettyOutput.print(f"File {file_name} apply patch failed", OutputType.ERROR)
388
+ PrettyOutput.print(f"Old code: \n{old_code}\n", OutputType.INFO)
389
+ PrettyOutput.print(f"New code: \n{new_code}\n", OutputType.INFO)
390
+ PrettyOutput.print("Apply patch finished", OutputType.INFO)
391
+
392
+
393
+ def _make_patch(self, related_files):
394
+
395
+ prompt = "你是一个资深程序员,请根据需求描述,修改文件内容,生成最小化的patch,文件列表如下:\n"
396
+ prompt += "<FILE_RELATION_START>\n"
397
+ for i, file in enumerate(related_files):
398
+ prompt += f"""{i}. {file["file_path"]} : {file["file_description"]}\n"""
399
+ prompt += f"""文件内容: \n"""
400
+ prompt += f"<FILE_CONTENT_START>\n"
401
+ prompt += f"{file["file_content"]}\n"
402
+ prompt += f"<FILE_CONTENT_END>\n"
403
+ prompt += f"<FILE_RELATION_END>\n"
404
+ prompt += f"请根据需求描述,修改文件。\n"
405
+ prompt += f"需求描述: {self.feature}\n"
406
+ prompt += f"请输出以下格式内容(多段patch),注意缩进也是代码的一部分,不要输出任何其他内容\n"
407
+ prompt += f">>>>>> [文件路径1]\n"
408
+ prompt += f"[原文件内容]\n"
409
+ prompt += f"==========\n"
410
+ prompt += f"[修改后的文件内容]\n"
411
+ prompt += f"<<<<<<\n"
412
+ prompt += f">>>>>> [文件路径2]\n"
413
+ prompt += f"[原文件内容]\n"
414
+ prompt += f"==========\n"
415
+ prompt += f"[修改后的文件内容]\n"
416
+ prompt += f"<<<<<<\n"
417
+ response = self.main_model.chat(prompt)
418
+ self.main_model.delete_chat()
419
+ # 使用<FILE_EDIT_START>和<FILE_EDIT_END>提取所有的patch,可能除了patch
420
+ patch = re.findall(r">>>>>>.*?<<<<<<", response, re.DOTALL)
421
+ return patch
422
+
423
+
424
+ if __name__ == "__main__":
425
+ tool = CodeEditTool()
426
+ tool.execute({
427
+ "feature": "将排序数据修改为随机生成",
428
+ "root_dir": "/tmp/test",
429
+ "language": "c"
430
+ })
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.1.50
3
+ Version: 0.1.52
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,5 +1,5 @@
1
- jarvis/__init__.py,sha256=5Umq7eV1tQYOk9hF8zhf1-Ud7maywBTd5Qcvw0PP0yM,50
2
- jarvis/agent.py,sha256=9uvGO_wx8HTNKBJ1HXHdIG51pNLLDLpErGdxQoWszT4,12280
1
+ jarvis/__init__.py,sha256=6dWTVW9nsh1mr5gOTx4Eh4EMiqgFX_aOYYteSCYl0oY,50
2
+ jarvis/agent.py,sha256=s8VebNef8BbKM6D9IDWWN-XenEXEL9KoYnDLGKyZY58,12347
3
3
  jarvis/main.py,sha256=VIqMYnqNrmVcnO5YC-Tc-ATVBt8IlqIcXHjZvHmyCFs,6272
4
4
  jarvis/utils.py,sha256=JlkuC9RtspXH2VWDmj9nR0vnb8ie1gIsKc4vC7WRco8,7321
5
5
  jarvis/models/__init__.py,sha256=mrOt67nselz_H1gX9wdAO4y2DY5WPXzABqJbr5Des8k,63
@@ -11,15 +11,16 @@ jarvis/models/oyi.py,sha256=4_sppGOLS3rucBHinhzjLhSh_6rJKIgLu9h6trHIOGM,14900
11
11
  jarvis/models/registry.py,sha256=iVBjN9ImEvGHcz8WR-z8pPMJQZI907o_nccVOFANhak,7951
12
12
  jarvis/tools/__init__.py,sha256=Kj1bKj34lwRDKMKHLOrLyQElf2lHbqA2tDgP359eaDo,71
13
13
  jarvis/tools/base.py,sha256=EGRGbdfbLXDLwtyoWdvp9rlxNX7bzc20t0Vc2VkwIEY,652
14
+ jarvis/tools/code_edit.py,sha256=BfPPcPNuQWTPuOkl7TlIjrcFXpwPbWZpIk66crQdILA,18873
14
15
  jarvis/tools/file_ops.py,sha256=h8g0eT9UvlJf4kt0DLXvdSsjcPj7x19lxWdDApeDfpg,3842
15
16
  jarvis/tools/generator.py,sha256=vVP3eN5cCDpRXf_fn0skETkPXAW1XZFWx9pt2_ahK48,5999
16
17
  jarvis/tools/methodology.py,sha256=G3cOaHTMujGZBhDLhQEqyCV2NISizO3MXRuho1KfI6Y,5223
17
18
  jarvis/tools/registry.py,sha256=NbH7A4A2lyN2IoyZGFwa5Ghed2dpzbJWCAd1Dg95WBI,7183
18
19
  jarvis/tools/shell.py,sha256=UPKshPyOaUwTngresUw-ot1jHjQIb4wCY5nkJqa38lU,2520
19
20
  jarvis/tools/sub_agent.py,sha256=rEtAmSVY2ZjFOZEKr5m5wpACOQIiM9Zr_3dT92FhXYU,2621
20
- jarvis_ai_assistant-0.1.50.dist-info/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
21
- jarvis_ai_assistant-0.1.50.dist-info/METADATA,sha256=0Eqq1ziCqoWqYDF6e8qkvO_ELBb_uM_wsQrMDpeQHaA,10015
22
- jarvis_ai_assistant-0.1.50.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
23
- jarvis_ai_assistant-0.1.50.dist-info/entry_points.txt,sha256=iKu7OMfew9dtfGhW71gIMTg4wvafuPqKb4wyQOnMAGU,44
24
- jarvis_ai_assistant-0.1.50.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
25
- jarvis_ai_assistant-0.1.50.dist-info/RECORD,,
21
+ jarvis_ai_assistant-0.1.52.dist-info/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
22
+ jarvis_ai_assistant-0.1.52.dist-info/METADATA,sha256=gb4uoX5ywDb-uEgMmltUt6Omc2fyZ_zUbz4auhktAAU,10015
23
+ jarvis_ai_assistant-0.1.52.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
24
+ jarvis_ai_assistant-0.1.52.dist-info/entry_points.txt,sha256=iKu7OMfew9dtfGhW71gIMTg4wvafuPqKb4wyQOnMAGU,44
25
+ jarvis_ai_assistant-0.1.52.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
26
+ jarvis_ai_assistant-0.1.52.dist-info/RECORD,,