jarvis-ai-assistant 0.1.150__py3-none-any.whl → 0.1.151__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.150"
3
+ __version__ = "0.1.151"
@@ -177,6 +177,13 @@ class GitCommitTool:
177
177
  # 如果成功提取,就跳出循环
178
178
  if commit_message:
179
179
  break
180
+ prompt = f"""格式错误,请按照以下格式重新生成提交信息:
181
+ {ot("COMMIT_MESSAGE")}
182
+ <类型>(<范围>): <主题>
183
+
184
+ [可选] 详细描述变更内容和原因
185
+ {ct("COMMIT_MESSAGE")}
186
+ """
180
187
  spinner.write("✅ 生成提交消息")
181
188
 
182
189
  # 执行提交
@@ -33,4 +33,33 @@ class McpClient(ABC):
33
33
  """
34
34
  pass
35
35
 
36
+ @abstractmethod
37
+ def get_resource_list(self) -> List[Dict[str, Any]]:
38
+ """获取资源列表
39
+
40
+ 返回:
41
+ List[Dict[str, Any]]: 资源列表,每个资源包含以下字段:
42
+ - uri: str - 资源的唯一标识符
43
+ - name: str - 资源的名称
44
+ - description: str - 资源的描述(可选)
45
+ - mimeType: str - 资源的MIME类型(可选)
46
+ """
47
+ pass
48
+
49
+ @abstractmethod
50
+ def get_resource(self, uri: str) -> Dict[str, Any]:
51
+ """获取指定资源的内容
52
+
53
+ 参数:
54
+ uri: str - 资源的URI标识符
55
+
56
+ 返回:
57
+ Dict[str, Any]: 资源内容,包含以下字段:
58
+ - uri: str - 资源的URI
59
+ - mimeType: str - 资源的MIME类型(可选)
60
+ - text: str - 文本内容(如果是文本资源)
61
+ - blob: str - 二进制内容(如果是二进制资源,base64编码)
62
+ """
63
+ pass
64
+
36
65
 
@@ -228,6 +228,91 @@ class LocalMcpClient(McpClient):
228
228
  'stderr': str(e)
229
229
  }
230
230
 
231
+ def get_resource_list(self) -> List[Dict[str, Any]]:
232
+ """获取资源列表
233
+
234
+ 返回:
235
+ List[Dict[str, Any]]: 资源列表,每个资源包含以下字段:
236
+ - uri: str - 资源的唯一标识符
237
+ - name: str - 资源的名称
238
+ - description: str - 资源的描述(可选)
239
+ - mimeType: str - 资源的MIME类型(可选)
240
+ """
241
+ try:
242
+ response = self._send_request('resources/list', {})
243
+ if 'result' in response and 'resources' in response['result']:
244
+ return response['result']['resources']
245
+ else:
246
+ error_msg = "获取资源列表失败"
247
+ if 'error' in response:
248
+ error_msg += f": {response['error']}"
249
+ else:
250
+ error_msg += ": 未知错误"
251
+ PrettyOutput.print(error_msg, OutputType.ERROR)
252
+ return []
253
+ except Exception as e:
254
+ PrettyOutput.print(f"获取资源列表失败: {str(e)}", OutputType.ERROR)
255
+ return []
256
+
257
+ def get_resource(self, uri: str) -> Dict[str, Any]:
258
+ """获取指定资源的内容
259
+
260
+ 参数:
261
+ uri: str - 资源的URI标识符
262
+
263
+ 返回:
264
+ Dict[str, Any]: 执行结果,包含以下字段:
265
+ - success: bool - 是否执行成功
266
+ - stdout: str - 资源内容(文本或base64编码的二进制内容)
267
+ - stderr: str - 错误信息
268
+ """
269
+ try:
270
+ response = self._send_request('resources/read', {
271
+ 'uri': uri
272
+ })
273
+ if 'result' in response and 'contents' in response['result']:
274
+ contents = response['result']['contents']
275
+ if contents:
276
+ content = contents[0] # 获取第一个资源内容
277
+ # 根据资源类型返回内容
278
+ if 'text' in content:
279
+ return {
280
+ 'success': True,
281
+ 'stdout': content['text'],
282
+ 'stderr': ''
283
+ }
284
+ elif 'blob' in content:
285
+ return {
286
+ 'success': True,
287
+ 'stdout': content['blob'],
288
+ 'stderr': ''
289
+ }
290
+ return {
291
+ 'success': False,
292
+ 'stdout': '',
293
+ 'stderr': '资源内容为空'
294
+ }
295
+ else:
296
+ error_msg = "获取资源内容失败"
297
+ if 'error' in response:
298
+ error_msg += f": {response['error']}"
299
+ else:
300
+ error_msg += ": 未知错误"
301
+ PrettyOutput.print(error_msg, OutputType.ERROR)
302
+ return {
303
+ 'success': False,
304
+ 'stdout': '',
305
+ 'stderr': error_msg
306
+ }
307
+ except Exception as e:
308
+ error_msg = f"获取资源内容失败: {str(e)}"
309
+ PrettyOutput.print(error_msg, OutputType.ERROR)
310
+ return {
311
+ 'success': False,
312
+ 'stdout': '',
313
+ 'stderr': error_msg
314
+ }
315
+
231
316
  def __del__(self):
232
317
  """清理资源"""
233
318
  if self.process:
@@ -1,7 +1,10 @@
1
- from typing import Any, Dict, List
1
+ from typing import Any, Dict, List, Optional, Iterator, Callable
2
2
  import requests
3
- import sseclient
4
- from urllib.parse import urljoin
3
+ import json
4
+ import threading
5
+ import time
6
+ import uuid
7
+ from urllib.parse import urljoin, urlencode, parse_qs
5
8
  from jarvis.jarvis_utils.output import OutputType, PrettyOutput
6
9
  from . import McpClient
7
10
 
@@ -37,13 +40,38 @@ class RemoteMcpClient(McpClient):
37
40
  extra_headers = config.get('headers', {})
38
41
  self.session.headers.update(extra_headers)
39
42
 
40
- # 初始化SSE连接
41
- self.sse_client = None
43
+ # SSE相关属性
44
+ self.sse_response = None
45
+ self.sse_thread = None
46
+ self.messages_endpoint = None
47
+ self.session_id = None # 从SSE连接获取的会话ID
48
+ self.pending_requests = {} # 存储等待响应的请求 {id: Event}
49
+ self.request_results = {} # 存储请求结果 {id: result}
50
+ self.notification_handlers = {}
51
+ self.event_lock = threading.Lock()
52
+ self.request_id_counter = 0
53
+
54
+ # 初始化连接
42
55
  self._initialize()
43
56
 
44
57
  def _initialize(self) -> None:
45
58
  """初始化MCP连接"""
46
59
  try:
60
+ # 启动SSE连接
61
+ self._start_sse_connection()
62
+
63
+ # 等待获取消息端点和会话ID
64
+ start_time = time.time()
65
+ while (not self.messages_endpoint or not self.session_id) and time.time() - start_time < 5:
66
+ time.sleep(0.1)
67
+
68
+ if not self.messages_endpoint:
69
+ self.messages_endpoint = "/messages" # 默认端点
70
+ PrettyOutput.print(f"未获取到消息端点,使用默认值: {self.messages_endpoint}", OutputType.WARNING)
71
+
72
+ if not self.session_id:
73
+ PrettyOutput.print("未获取到会话ID", OutputType.WARNING)
74
+
47
75
  # 发送初始化请求
48
76
  response = self._send_request('initialize', {
49
77
  'processId': None, # 远程客户端不需要进程ID
@@ -59,20 +87,153 @@ class RemoteMcpClient(McpClient):
59
87
  if 'result' not in response:
60
88
  raise RuntimeError(f"初始化失败: {response.get('error', 'Unknown error')}")
61
89
 
62
- result = response['result']
63
-
64
- # 发送initialized通知 - 使用正确的方法名格式
90
+ # 发送initialized通知
65
91
  self._send_notification('notifications/initialized', {})
66
92
 
67
- # 建立SSE连接
68
- sse_url = urljoin(self.base_url, 'events')
69
- response = self.session.get(sse_url, stream=True)
70
- self.sse_client = sseclient.SSEClient(response)
71
-
72
93
  except Exception as e:
73
94
  PrettyOutput.print(f"MCP初始化失败: {str(e)}", OutputType.ERROR)
74
95
  raise
75
96
 
97
+ def _start_sse_connection(self) -> None:
98
+ """建立SSE连接并启动处理线程"""
99
+ try:
100
+ # 设置SSE请求头
101
+ sse_headers = dict(self.session.headers)
102
+ sse_headers.update({
103
+ 'Accept': 'text/event-stream',
104
+ 'Cache-Control': 'no-cache',
105
+ })
106
+
107
+ # 建立SSE连接
108
+ sse_url = urljoin(self.base_url, 'sse')
109
+ self.sse_response = self.session.get(
110
+ sse_url,
111
+ stream=True,
112
+ headers=sse_headers,
113
+ timeout=30
114
+ )
115
+ self.sse_response.raise_for_status()
116
+
117
+ # 启动事件处理线程
118
+ self.sse_thread = threading.Thread(target=self._process_sse_events, daemon=True)
119
+ self.sse_thread.start()
120
+
121
+ except Exception as e:
122
+ PrettyOutput.print(f"SSE连接失败: {str(e)}", OutputType.ERROR)
123
+ raise
124
+
125
+ def _process_sse_events(self) -> None:
126
+ """处理SSE事件流"""
127
+ if not self.sse_response:
128
+ return
129
+
130
+ buffer = ""
131
+ for line in self.sse_response.iter_lines(decode_unicode=True):
132
+ if line:
133
+ if line.startswith("data:"):
134
+ data = line[5:].strip()
135
+ # 检查是否包含消息端点信息
136
+ if data.startswith('/'):
137
+ # 这是消息端点信息,例如 "/messages/?session_id=xyz"
138
+ try:
139
+ # 提取消息端点路径和会话ID
140
+ url_parts = data.split('?')
141
+ self.messages_endpoint = url_parts[0]
142
+
143
+ # 如果有查询参数,尝试提取session_id
144
+ if len(url_parts) > 1:
145
+ query_string = url_parts[1]
146
+ query_params = parse_qs(query_string)
147
+ if 'session_id' in query_params:
148
+ self.session_id = query_params['session_id'][0]
149
+ except Exception as e:
150
+ PrettyOutput.print(f"解析消息端点或会话ID失败: {e}", OutputType.WARNING)
151
+ else:
152
+ buffer += data
153
+ elif line.startswith(":"): # 忽略注释行
154
+ continue
155
+ elif line.startswith("event:"): # 事件类型
156
+ continue # 我们不使用事件类型
157
+ elif line.startswith("id:"): # 事件ID
158
+ continue # 我们不使用事件ID
159
+ elif line.startswith("retry:"): # 重连时间
160
+ continue # 我们自己管理重连
161
+ else: # 空行表示事件结束
162
+ if buffer:
163
+ try:
164
+ self._handle_sse_event(buffer)
165
+ except Exception as e:
166
+ PrettyOutput.print(f"处理SSE事件出错: {e}", OutputType.ERROR)
167
+ buffer = ""
168
+
169
+ PrettyOutput.print("SSE连接已关闭", OutputType.WARNING)
170
+
171
+ def _handle_sse_event(self, data: str) -> None:
172
+ """处理单个SSE事件数据"""
173
+ try:
174
+ event_data = json.loads(data)
175
+
176
+ # 检查是请求响应还是通知
177
+ if 'id' in event_data:
178
+ # 这是一个请求的响应
179
+ req_id = event_data['id']
180
+ with self.event_lock:
181
+ self.request_results[req_id] = event_data
182
+ if req_id in self.pending_requests:
183
+ # 通知等待线程响应已到达
184
+ self.pending_requests[req_id].set()
185
+ elif 'method' in event_data:
186
+ # 这是一个通知
187
+ method = event_data.get('method', '')
188
+ params = event_data.get('params', {})
189
+
190
+ # 调用已注册的处理器
191
+ if method in self.notification_handlers:
192
+ for handler in self.notification_handlers[method]:
193
+ try:
194
+ handler(params)
195
+ except Exception as e:
196
+ PrettyOutput.print(
197
+ f"处理通知时出错 ({method}): {e}",
198
+ OutputType.ERROR
199
+ )
200
+ except json.JSONDecodeError:
201
+ PrettyOutput.print(f"无法解析SSE事件: {data}", OutputType.WARNING)
202
+ except Exception as e:
203
+ PrettyOutput.print(f"处理SSE事件时出错: {e}", OutputType.ERROR)
204
+
205
+ def register_notification_handler(self, method: str, handler: Callable) -> None:
206
+ """注册通知处理器
207
+
208
+ 参数:
209
+ method: 通知方法名
210
+ handler: 处理通知的回调函数,接收params参数
211
+ """
212
+ with self.event_lock:
213
+ if method not in self.notification_handlers:
214
+ self.notification_handlers[method] = []
215
+ self.notification_handlers[method].append(handler)
216
+
217
+ def unregister_notification_handler(self, method: str, handler: Callable) -> None:
218
+ """注销通知处理器
219
+
220
+ 参数:
221
+ method: 通知方法名
222
+ handler: 要注销的处理器函数
223
+ """
224
+ with self.event_lock:
225
+ if method in self.notification_handlers:
226
+ if handler in self.notification_handlers[method]:
227
+ self.notification_handlers[method].remove(handler)
228
+ if not self.notification_handlers[method]:
229
+ del self.notification_handlers[method]
230
+
231
+ def _get_next_request_id(self) -> str:
232
+ """获取下一个请求ID"""
233
+ with self.event_lock:
234
+ self.request_id_counter += 1
235
+ return str(self.request_id_counter)
236
+
76
237
  def _send_request(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
77
238
  """发送请求到MCP服务器
78
239
 
@@ -83,24 +244,88 @@ class RemoteMcpClient(McpClient):
83
244
  返回:
84
245
  Dict[str, Any]: 响应结果
85
246
  """
247
+ # 生成唯一请求ID
248
+ req_id = self._get_next_request_id()
249
+
250
+ # 创建事件标志,用于等待响应
251
+ event = threading.Event()
252
+
253
+ with self.event_lock:
254
+ self.pending_requests[req_id] = event
255
+
86
256
  try:
87
257
  # 构建请求
88
258
  request = {
89
259
  'jsonrpc': '2.0',
90
260
  'method': method,
91
261
  'params': params,
92
- 'id': 1
262
+ 'id': req_id
93
263
  }
94
264
 
95
- # 发送请求
96
- response = self.session.post(
97
- urljoin(self.base_url, 'rpc'),
98
- json=request
99
- )
100
- response.raise_for_status()
101
- return response.json()
265
+ # 尝试不同的请求发送方式
266
+ if self.session_id:
267
+ # 方法1: 使用查询参数中的session_id
268
+ query_params = {'session_id': self.session_id}
269
+ messages_url = urljoin(self.base_url, self.messages_endpoint)
270
+
271
+ # 尝试直接使用原始URL(不追加查询参数)
272
+ try:
273
+ post_response = self.session.post(
274
+ messages_url,
275
+ json=request
276
+ )
277
+ post_response.raise_for_status()
278
+ except requests.HTTPError:
279
+ # 如果失败,尝试添加会话ID到查询参数
280
+ messages_url_with_session = f"{messages_url}?{urlencode(query_params)}"
281
+ post_response = self.session.post(
282
+ messages_url_with_session,
283
+ json=request
284
+ )
285
+ post_response.raise_for_status()
286
+ else:
287
+ # 方法2: 不使用session_id
288
+ if not self.messages_endpoint:
289
+ self.messages_endpoint = "/messages"
290
+
291
+ messages_url = urljoin(self.base_url, self.messages_endpoint)
292
+
293
+ # 尝试直接使用messages端点而不带任何查询参数
294
+ try:
295
+ # 尝试1: 标准JSON-RPC格式
296
+ post_response = self.session.post(
297
+ messages_url,
298
+ json=request
299
+ )
300
+ post_response.raise_for_status()
301
+ except requests.HTTPError:
302
+ # 尝试2: JSON字符串作为请求参数
303
+ post_response = self.session.post(
304
+ messages_url,
305
+ params={'request': json.dumps(request)}
306
+ )
307
+ post_response.raise_for_status()
308
+
309
+ # 等待SSE通道返回响应(最多30秒)
310
+ if not event.wait(timeout=30):
311
+ raise TimeoutError(f"等待响应超时: {method}")
312
+
313
+ # 获取响应结果
314
+ with self.event_lock:
315
+ result = self.request_results.pop(req_id, None)
316
+ self.pending_requests.pop(req_id, None)
317
+
318
+ if result is None:
319
+ raise RuntimeError(f"未收到响应: {method}")
320
+
321
+ return result
102
322
 
103
323
  except Exception as e:
324
+ # 清理请求状态
325
+ with self.event_lock:
326
+ self.pending_requests.pop(req_id, None)
327
+ self.request_results.pop(req_id, None)
328
+
104
329
  PrettyOutput.print(f"发送请求失败: {str(e)}", OutputType.ERROR)
105
330
  raise
106
331
 
@@ -119,12 +344,49 @@ class RemoteMcpClient(McpClient):
119
344
  'params': params
120
345
  }
121
346
 
122
- # 发送通知
123
- response = self.session.post(
124
- urljoin(self.base_url, 'rpc'),
125
- json=notification
126
- )
127
- response.raise_for_status()
347
+ # 尝试不同的请求发送方式,与_send_request保持一致
348
+ if self.session_id:
349
+ # 方法1: 使用查询参数中的session_id
350
+ query_params = {'session_id': self.session_id}
351
+ messages_url = urljoin(self.base_url, self.messages_endpoint or '/messages')
352
+
353
+ # 尝试直接使用原始URL(不追加查询参数)
354
+ try:
355
+ post_response = self.session.post(
356
+ messages_url,
357
+ json=notification
358
+ )
359
+ post_response.raise_for_status()
360
+ except requests.HTTPError:
361
+ # 如果失败,尝试添加会话ID到查询参数
362
+ messages_url_with_session = f"{messages_url}?{urlencode(query_params)}"
363
+ post_response = self.session.post(
364
+ messages_url_with_session,
365
+ json=notification
366
+ )
367
+ post_response.raise_for_status()
368
+ else:
369
+ # 方法2: 不使用session_id
370
+ if not self.messages_endpoint:
371
+ self.messages_endpoint = "/messages"
372
+
373
+ messages_url = urljoin(self.base_url, self.messages_endpoint)
374
+
375
+ # 尝试直接使用messages端点而不带任何查询参数
376
+ try:
377
+ # 尝试1: 标准JSON-RPC格式
378
+ post_response = self.session.post(
379
+ messages_url,
380
+ json=notification
381
+ )
382
+ post_response.raise_for_status()
383
+ except requests.HTTPError:
384
+ # 尝试2: JSON字符串作为请求参数
385
+ post_response = self.session.post(
386
+ messages_url,
387
+ params={'request': json.dumps(notification)}
388
+ )
389
+ post_response.raise_for_status()
128
390
 
129
391
  except Exception as e:
130
392
  PrettyOutput.print(f"发送通知失败: {str(e)}", OutputType.ERROR)
@@ -222,9 +484,107 @@ class RemoteMcpClient(McpClient):
222
484
  'stderr': str(e)
223
485
  }
224
486
 
487
+ def get_resource_list(self) -> List[Dict[str, Any]]:
488
+ """获取资源列表
489
+
490
+ 返回:
491
+ List[Dict[str, Any]]: 资源列表,每个资源包含以下字段:
492
+ - uri: str - 资源的唯一标识符
493
+ - name: str - 资源的名称
494
+ - description: str - 资源的描述(可选)
495
+ - mimeType: str - 资源的MIME类型(可选)
496
+ """
497
+ try:
498
+ response = self._send_request('resources/list', {})
499
+ if 'result' in response and 'resources' in response['result']:
500
+ return response['result']['resources']
501
+ else:
502
+ error_msg = "获取资源列表失败"
503
+ if 'error' in response:
504
+ error_msg += f": {response['error']}"
505
+ else:
506
+ error_msg += ": 未知错误"
507
+ PrettyOutput.print(error_msg, OutputType.ERROR)
508
+ return []
509
+ except Exception as e:
510
+ PrettyOutput.print(f"获取资源列表失败: {str(e)}", OutputType.ERROR)
511
+ return []
512
+
513
+ def get_resource(self, uri: str) -> Dict[str, Any]:
514
+ """获取指定资源的内容
515
+
516
+ 参数:
517
+ uri: str - 资源的URI标识符
518
+
519
+ 返回:
520
+ Dict[str, Any]: 执行结果,包含以下字段:
521
+ - success: bool - 是否执行成功
522
+ - stdout: str - 资源内容(文本或base64编码的二进制内容)
523
+ - stderr: str - 错误信息
524
+ """
525
+ try:
526
+ response = self._send_request('resources/read', {
527
+ 'uri': uri
528
+ })
529
+ if 'result' in response and 'contents' in response['result']:
530
+ contents = response['result']['contents']
531
+ if contents:
532
+ content = contents[0] # 获取第一个资源内容
533
+ # 根据资源类型返回内容
534
+ if 'text' in content:
535
+ return {
536
+ 'success': True,
537
+ 'stdout': content['text'],
538
+ 'stderr': ''
539
+ }
540
+ elif 'blob' in content:
541
+ return {
542
+ 'success': True,
543
+ 'stdout': content['blob'],
544
+ 'stderr': ''
545
+ }
546
+ return {
547
+ 'success': False,
548
+ 'stdout': '',
549
+ 'stderr': '资源内容为空'
550
+ }
551
+ else:
552
+ error_msg = "获取资源内容失败"
553
+ if 'error' in response:
554
+ error_msg += f": {response['error']}"
555
+ else:
556
+ error_msg += ": 未知错误"
557
+ PrettyOutput.print(error_msg, OutputType.ERROR)
558
+ return {
559
+ 'success': False,
560
+ 'stdout': '',
561
+ 'stderr': error_msg
562
+ }
563
+ except Exception as e:
564
+ error_msg = f"获取资源内容失败: {str(e)}"
565
+ PrettyOutput.print(error_msg, OutputType.ERROR)
566
+ return {
567
+ 'success': False,
568
+ 'stdout': '',
569
+ 'stderr': error_msg
570
+ }
571
+
225
572
  def __del__(self):
226
573
  """清理资源"""
227
- if self.sse_client and hasattr(self.sse_client, 'resp'):
228
- self.sse_client.resp.close()
574
+ # 清理请求状态
575
+ with self.event_lock:
576
+ for event in self.pending_requests.values():
577
+ event.set() # 释放所有等待的请求
578
+ self.pending_requests.clear()
579
+ self.request_results.clear()
580
+
581
+ # 关闭SSE响应
582
+ if self.sse_response:
583
+ try:
584
+ self.sse_response.close()
585
+ except:
586
+ pass
587
+
588
+ # 关闭HTTP会话
229
589
  if self.session:
230
590
  self.session.close()
@@ -156,10 +156,9 @@ class AskCodebaseTool:
156
156
  2. 使用fd命令查找可能相关的文件
157
157
  3. 使用rg命令搜索关键词和代码模式
158
158
  4. 使用read_code工具直接读取和分析相关文件内容
159
- 5. 只有在fd、rg和read_code都无法解决问题时才考虑使用RAG工具
160
- 6. 根据文件内容提供具体、准确的回答
161
- 7. 确保分析的完整性,收集充分的信息后再得出结论,不要在只掌握部分信息就得出结论
162
- 8. 优先查阅README文件、文档目录和项目文档
159
+ 5. 根据文件内容提供具体、准确的回答
160
+ 6. 确保分析的完整性,收集充分的信息后再得出结论,不要在只掌握部分信息就得出结论
161
+ 7. 优先查阅README文件、文档目录和项目文档
163
162
 
164
163
  ## 分析步骤
165
164
  1. **确定项目的编程语言**:
@@ -186,12 +185,6 @@ class AskCodebaseTool:
186
185
  - 提供基于直接分析代码的具体回答
187
186
  - 引用具体文件和代码片段作为依据
188
187
 
189
- ## 关于RAG工具使用
190
- - RAG工具应作为最后选择,仅在fd、rg和read_code都无法解决问题时使用
191
- - 必须通过查看实际代码文件验证RAG返回的每条重要信息
192
- - 对于关键发现,始终使用`read_code`工具查看原始文件内容进行求证
193
- - 如发现RAG结果与实际代码不符,以实际代码为准
194
-
195
188
  ## 输出要求
196
189
  - 提供准确、具体的回答,避免模糊不清的描述
197
190
  - 引用具体文件路径和代码片段作为依据
@@ -16,6 +16,7 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
16
16
  from jarvis.jarvis_utils.utils import ct, ot, init_env
17
17
  from jarvis.jarvis_mcp.local_mcp_client import LocalMcpClient
18
18
  from jarvis.jarvis_mcp.remote_mcp_client import RemoteMcpClient
19
+ from jarvis.jarvis_mcp import McpClient
19
20
 
20
21
 
21
22
 
@@ -138,7 +139,7 @@ class ToolRegistry(OutputHandler):
138
139
 
139
140
  def use_tools(self, name: List[str]) -> None:
140
141
  """使用指定工具
141
-
142
+
142
143
  参数:
143
144
  name: 要使用的工具名称列表
144
145
  """
@@ -149,7 +150,7 @@ class ToolRegistry(OutputHandler):
149
150
 
150
151
  def dont_use_tools(self, names: List[str]) -> None:
151
152
  """从注册表中移除指定工具
152
-
153
+
153
154
  参数:
154
155
  names: 要移除的工具名称列表
155
156
  """
@@ -193,7 +194,7 @@ class ToolRegistry(OutputHandler):
193
194
 
194
195
  def register_mcp_tool_by_file(self, file_path: str) -> bool:
195
196
  """从指定文件加载并注册工具
196
-
197
+
197
198
  参数:
198
199
  file_path: 工具文件的路径
199
200
 
@@ -205,84 +206,125 @@ class ToolRegistry(OutputHandler):
205
206
  if 'type' not in config:
206
207
  PrettyOutput.print(f"文件 {file_path} 缺少type字段", OutputType.WARNING)
207
208
  return False
209
+
210
+ # 检查enable标志
211
+ if not config.get('enable', True):
212
+ PrettyOutput.print(f"文件 {file_path} 已禁用(enable=false),跳过注册", OutputType.INFO)
213
+ return False
214
+
215
+ name = config.get('name', Path(file_path).stem)
216
+
217
+
218
+ # 注册资源工具
219
+ def create_resource_list_func(client: McpClient):
220
+ def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
221
+ args = arguments.copy()
222
+ args.pop('agent', None)
223
+ args.pop('want', None)
224
+ ret = client.get_resource_list()
225
+ PrettyOutput.print(f"MCP {name} 资源列表:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
226
+ return {
227
+ 'success': True,
228
+ 'stdout': yaml.safe_dump(ret),
229
+ 'stderr': ''
230
+ }
231
+ return execute
232
+
233
+ def create_resource_get_func(client: McpClient):
234
+ def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
235
+ args = arguments.copy()
236
+ args.pop('agent', None)
237
+ args.pop('want', None)
238
+ if 'uri' not in args:
239
+ return {
240
+ 'success': False,
241
+ 'stdout': '',
242
+ 'stderr': '缺少必需的uri参数'
243
+ }
244
+ ret = client.get_resource(args['uri'])
245
+ PrettyOutput.print(f"MCP {name} 获取资源:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
246
+ return ret
247
+ return execute
248
+
249
+ def create_mcp_execute_func(tool_name: str, client: McpClient):
250
+ def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
251
+ args = arguments.copy()
252
+ args.pop('agent', None)
253
+ args.pop('want', None)
254
+ ret = client.execute(tool_name, args)
255
+ PrettyOutput.print(f"MCP {name} {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
256
+ return ret
257
+ return execute
258
+
208
259
  if config['type'] == 'local':
209
260
  if 'command' not in config:
210
261
  PrettyOutput.print(f"文件 {file_path} 缺少command字段", OutputType.WARNING)
211
262
  return False
212
-
213
- # 创建本地MCP客户端
214
- mcp_client = LocalMcpClient(config)
215
-
216
- # 获取工具信息
217
- tools = mcp_client.get_tool_list()
218
- if not tools:
219
- PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
220
- return False
221
-
222
- # 注册每个工具
223
- for tool in tools:
224
- def create_local_execute_func(tool_name: str, client: LocalMcpClient):
225
- def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
226
- args = arguments.copy()
227
- args.pop('agent', None)
228
- args.pop('want', None)
229
- ret = client.execute(tool_name, args)
230
- PrettyOutput.print(f"MCP {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
231
- return ret
232
- return execute
233
-
234
- # 注册工具
235
- self.register_tool(
236
- name=tool['name'],
237
- description=tool['description'],
238
- parameters=tool['parameters'],
239
- func=create_local_execute_func(tool['name'], mcp_client)
240
- )
241
-
242
- return True
243
-
244
263
  elif config['type'] == 'remote':
245
264
  if 'base_url' not in config:
246
265
  PrettyOutput.print(f"文件 {file_path} 缺少base_url字段", OutputType.WARNING)
247
266
  return False
248
-
249
- # 创建远程MCP客户端
250
- mcp_client = RemoteMcpClient(config)
251
-
252
- # 获取工具信息
253
- tools = mcp_client.get_tool_list()
254
- if not tools:
255
- PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
256
- return False
257
-
258
- # 注册每个工具
259
- for tool in tools:
260
- def create_remote_execute_func(tool_name: str, client: RemoteMcpClient):
261
- def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
262
- args = arguments.copy()
263
- args.pop('agent', None)
264
- args.pop('want', None)
265
- ret = client.execute(tool_name, args)
266
- PrettyOutput.print(f"MCP {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
267
- return ret
268
- return execute
269
-
270
- # 注册工具
271
- self.register_tool(
272
- name=tool['name'],
273
- description=tool['description'],
274
- parameters=tool['parameters'],
275
- func=create_remote_execute_func(tool['name'], mcp_client)
276
- )
277
-
278
- return True
279
267
  else:
280
268
  PrettyOutput.print(f"文件 {file_path} 类型错误: {config['type']}", OutputType.WARNING)
281
269
  return False
270
+
271
+ # 创建MCP客户端
272
+ mcp_client: McpClient = LocalMcpClient(config) if config['type'] == 'local' else RemoteMcpClient(config)
273
+
274
+ # 获取工具信息
275
+ tools = mcp_client.get_tool_list()
276
+ if not tools:
277
+ PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
278
+ return False
279
+
280
+ # 注册每个工具
281
+ for tool in tools:
282
+
283
+ # 注册工具
284
+ self.register_tool(
285
+ name=f"{name}.tool_call.{tool['name']}",
286
+ description=tool['description'],
287
+ parameters=tool['parameters'],
288
+ func=create_mcp_execute_func(tool['name'], mcp_client)
289
+ )
290
+
291
+
292
+ # 注册资源列表工具
293
+ self.register_tool(
294
+ name=f"{name}.resource.get_resource_list",
295
+ description=f"获取{name}MCP服务器上的资源列表",
296
+ parameters={
297
+ 'type': 'object',
298
+ 'properties': {},
299
+ 'required': []
300
+ },
301
+ func=create_resource_list_func(mcp_client)
302
+ )
303
+
304
+ # 注册获取资源工具
305
+ self.register_tool(
306
+ name=f"{name}.resource.get_resource",
307
+ description=f"获取{name}MCP服务器上的指定资源",
308
+ parameters={
309
+ 'type': 'object',
310
+ 'properties': {
311
+ 'uri': {
312
+ 'type': 'string',
313
+ 'description': '资源的URI标识符'
314
+ }
315
+ },
316
+ 'required': ['uri']
317
+ },
318
+ func=create_resource_get_func(mcp_client)
319
+ )
320
+
321
+ return True
322
+
323
+
282
324
  except Exception as e:
283
325
  PrettyOutput.print(f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING)
284
326
  return False
285
-
327
+
286
328
  def register_tool_by_file(self, file_path: str) -> bool:
287
329
  """从指定文件加载并注册工具
288
330
 
@@ -348,12 +390,12 @@ class ToolRegistry(OutputHandler):
348
390
  except Exception as e:
349
391
  PrettyOutput.print(f"从 {Path(file_path).name} 加载工具失败: {str(e)}", OutputType.ERROR)
350
392
  return False
351
-
393
+
352
394
  @staticmethod
353
395
  def _has_tool_calls_block(content: str) -> bool:
354
396
  """从内容中提取工具调用块"""
355
397
  return re.search(ot("TOOL_CALL")+r'(.*?)'+ct("TOOL_CALL"), content, re.DOTALL) is not None
356
-
398
+
357
399
  @staticmethod
358
400
  def _extract_tool_calls(content: str) -> Tuple[Dict[str, Dict[str, Any]], str]:
359
401
  """从内容中提取工具调用。
@@ -376,20 +418,20 @@ class ToolRegistry(OutputHandler):
376
418
  if 'name' in msg and 'arguments' in msg and 'want' in msg:
377
419
  ret.append(msg)
378
420
  else:
379
- return {}, f"""工具调用格式错误,请检查工具调用格式。
380
-
381
- {tool_call_help}"""
421
+ return {}, f"""工具调用格式错误,请检查工具调用格式。
422
+
423
+ {tool_call_help}"""
382
424
  except Exception as e:
383
- return {}, f"""工具调用格式错误,请检查工具调用格式。
384
-
385
- {tool_call_help}"""
425
+ return {}, f"""工具调用格式错误,请检查工具调用格式。
426
+
427
+ {tool_call_help}"""
386
428
  if len(ret) > 1:
387
429
  return {}, "检测到多个工具调用,请一次只处理一个工具调用。"
388
430
  return ret[0] if ret else {}, ""
389
431
 
390
- def register_tool(self, name: str, description: str, parameters: Dict[str, Dict[str, Any]], func: Callable[..., Dict[str, Any]]) -> None:
432
+ def register_tool(self, name: str, description: str, parameters: Any, func: Callable[..., Dict[str, Any]]) -> None:
391
433
  """注册新工具
392
-
434
+
393
435
  参数:
394
436
  name: 工具名称
395
437
  description: 工具描述
@@ -400,10 +442,10 @@ class ToolRegistry(OutputHandler):
400
442
 
401
443
  def get_tool(self, name: str) -> Optional[Tool]:
402
444
  """获取工具
403
-
445
+
404
446
  参数:
405
447
  name: 工具名称
406
-
448
+
407
449
  返回:
408
450
  Optional[Tool]: 找到的工具实例,如果不存在则返回None
409
451
  """
@@ -411,7 +453,7 @@ class ToolRegistry(OutputHandler):
411
453
 
412
454
  def get_all_tools(self) -> List[Dict[str, Any]]:
413
455
  """获取所有工具(Ollama格式定义)
414
-
456
+
415
457
  返回:
416
458
  List[Dict[str, Any]]: 包含所有工具信息的列表
417
459
  """
@@ -419,11 +461,11 @@ class ToolRegistry(OutputHandler):
419
461
 
420
462
  def execute_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
421
463
  """执行指定工具
422
-
464
+
423
465
  参数:
424
466
  name: 工具名称
425
467
  arguments: 工具参数
426
-
468
+
427
469
  返回:
428
470
  Dict[str, Any]: 包含执行结果的字典,包含success、stdout和stderr字段
429
471
  """
@@ -485,7 +527,7 @@ class ToolRegistry(OutputHandler):
485
527
  return f"""工具调用原始输出过长,以下是根据输出提出的信息:
486
528
 
487
529
  {platform.chat_until_success(prompt)}"""
488
-
530
+
489
531
  return output
490
532
 
491
533
  except Exception as e:
@@ -29,7 +29,7 @@ def get_max_input_token_count() -> int:
29
29
  返回:
30
30
  int: 模型能处理的最大输入token数量。
31
31
  """
32
- return int(os.getenv('JARVIS_MAX_INPUT_TOKEN_COUNT', '32000'))
32
+ return int(os.getenv('JARVIS_MAX_INPUT_TOKEN_COUNT', '64000'))
33
33
 
34
34
 
35
35
  def is_auto_complete() -> bool:
@@ -144,10 +144,10 @@ def load_methodology(user_input: str) -> str:
144
144
  prompt = f"""根据用户需求: {user_input}
145
145
 
146
146
  请按以下格式回复:
147
- ### 相关的方法论
147
+ ### 与该任务/需求相关的方法论
148
148
  1. [方法论名字]
149
149
  2. [方法论名字]
150
- ### 总结的方法论内容
150
+ ### 根据以上方法论,总结出方法论内容
151
151
  [总结的方法论内容]
152
152
 
153
153
  如果没有匹配的方法论,请输出:没有历史方法论可参考
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: jarvis-ai-assistant
3
- Version: 0.1.150
3
+ Version: 0.1.151
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
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
  License-File: LICENSE
43
43
  Requires-Dist: requests==2.32.3
44
44
  Requires-Dist: colorama==0.4.6
45
- Requires-Dist: prompt-toolkit==3.0.50
45
+ Requires-Dist: prompt_toolkit==3.0.50
46
46
  Requires-Dist: yaspin==2.4.0
47
47
  Requires-Dist: pygments==2.19.1
48
48
  Requires-Dist: fuzzywuzzy==0.18.0
@@ -54,11 +54,16 @@ Requires-Dist: transformers==4.46.3
54
54
  Requires-Dist: torch==2.4.1
55
55
  Requires-Dist: python-Levenshtein==0.25.1
56
56
  Requires-Dist: sseclient==0.0.27
57
+ Requires-Dist: pillow==10.2.1
57
58
  Provides-Extra: dev
58
59
  Requires-Dist: pytest; extra == "dev"
59
60
  Requires-Dist: black; extra == "dev"
60
61
  Requires-Dist: isort; extra == "dev"
61
62
  Requires-Dist: mypy; extra == "dev"
63
+ Dynamic: author
64
+ Dynamic: home-page
65
+ Dynamic: license-file
66
+ Dynamic: requires-python
62
67
 
63
68
  # 🤖 Jarvis AI 助手
64
69
  <p align="center">
@@ -276,6 +281,30 @@ class CustomTool:
276
281
  ```
277
282
 
278
283
 
284
+ ### 添加MCP
285
+ MCP(模型上下文协议)。在`~/.jarvis/tools/mcp/`中创建YAML配置文件:
286
+
287
+ #### 本地MCP配置(`stdio`模式)
288
+ ```yaml
289
+ type: local
290
+ name: MCP名称
291
+ command: 可执行命令
292
+ args: [参数列表] # 可选
293
+ env: # 可选环境变量
294
+ KEY: VALUE
295
+ ```
296
+
297
+ #### 远程MCP配置(`sse`模式)
298
+ ```yaml
299
+ type: remote
300
+ name: MCP名称
301
+ base_url: http://example.com/api
302
+ auth_token: 认证令牌 # 可选
303
+ headers: # 可选HTTP头
304
+ X-Custom-Header: value
305
+ ```
306
+
307
+
279
308
  ### 添加新大模型平台
280
309
  在 `~/.jarvis/platforms/` 中创建新的 Python 文件:
281
310
  ```python
@@ -1,4 +1,4 @@
1
- jarvis/__init__.py,sha256=LAXd2PqzX1hdAOiMxEwjP0kqPkpnlGGCzmHQH7bDoXI,50
1
+ jarvis/__init__.py,sha256=RgZgU-h6b2rFUONprNXY6qN0TgybOI3uDfFqF21sQzA,50
2
2
  jarvis/jarvis_agent/__init__.py,sha256=QtWu2kh6o5IB_XtGLoxHi5K9lA1t8XoqNUXtX-OqujY,23796
3
3
  jarvis/jarvis_agent/builtin_input_handler.py,sha256=0SjlBYnBWKNi3eVdZ7c2NuP82tQej7DEWLAqG6bY1Rc,4357
4
4
  jarvis/jarvis_agent/file_input_handler.py,sha256=6R68cSjLBnSJjTKJrSO2IqziRDFnxazU_Jq2t1W1ndo,3695
@@ -35,16 +35,16 @@ jarvis/jarvis_git_details/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
35
35
  jarvis/jarvis_git_details/main.py,sha256=YowncVxYyJ3y2EvGrZhAJeR4yizXp6aB3dqvoYTepFY,6117
36
36
  jarvis/jarvis_git_squash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  jarvis/jarvis_git_squash/main.py,sha256=xBNkAl7_8_pQC-C6RcUImA1mEU4KTqhjtA57rG_mMJ8,2179
38
- jarvis/jarvis_git_utils/git_commiter.py,sha256=dozU5vmodhb-uiLJBHGRPzUMU1er4xqvjClG3sqNwTA,10780
38
+ jarvis/jarvis_git_utils/git_commiter.py,sha256=pnUiX-KkAMLDBRMXot93T9zTdoYsIJpqVMozMwQo6R0,11096
39
39
  jarvis/jarvis_lsp/base.py,sha256=f-76xgNijfQ4G3Q0t8IfOGtCu-q2TSQ7a_in6XwDb_8,2030
40
40
  jarvis/jarvis_lsp/cpp.py,sha256=ekci2M9_UtkCSEe9__72h26Gat93r9_knL2VmFr8X5M,3141
41
41
  jarvis/jarvis_lsp/go.py,sha256=sSypuQSP5X2YtrVMC8XCc5nXkgfG93SO7sC89lHzoR8,3458
42
42
  jarvis/jarvis_lsp/python.py,sha256=OJuYHLHI1aYNNWcAFayy_5GxogwyMC3A7KOYGjxN1yg,1843
43
43
  jarvis/jarvis_lsp/registry.py,sha256=-b7lAfZ6SNp3O0ifRiFSLxH0xJlPQhkq4DATDDjJb1U,6491
44
44
  jarvis/jarvis_lsp/rust.py,sha256=ICmQs5UVdMZwn5KjaF1YRXBCLUMtGF8Z9IwE5rqWkrU,3686
45
- jarvis/jarvis_mcp/__init__.py,sha256=gVPImIkjVSYdc9hJykceEiHnO19oX-rHAUuVR3Ztczg,1044
46
- jarvis/jarvis_mcp/local_mcp_client.py,sha256=cTOVtrMSZCyPsDNvC5egUus11YWSrZYyR7XKBkIIfgo,8404
47
- jarvis/jarvis_mcp/remote_mcp_client.py,sha256=ji7wO0VxM3I9lDV7eQ1TBTCX9VL6EAVwAVOXnxGxVo4,8112
45
+ jarvis/jarvis_mcp/__init__.py,sha256=gi74_Yz5nsEFhrAyCg1Ovxsj-hLweLjMGoOaceL2yx4,2090
46
+ jarvis/jarvis_mcp/local_mcp_client.py,sha256=JKmGJG0sOJxs4Sk-k4SfZq01RiZCGY6oYslvcK7lxJg,11790
47
+ jarvis/jarvis_mcp/remote_mcp_client.py,sha256=J9Y2-9VBQb0uJKglev-U3AQg1AfLDmghRbCvNDPSzdI,23478
48
48
  jarvis/jarvis_methodology/main.py,sha256=IBv87UOmdCailgooMtWEcqZcQHmNLhZD-kkGw5jOcVg,3375
49
49
  jarvis/jarvis_multi_agent/__init__.py,sha256=SX8lBErhltKyYRM-rymrMz3sJ0Zl3hBXrpsPdFgzkQc,4399
50
50
  jarvis/jarvis_multi_agent/main.py,sha256=aGuUC3YQmahabqwDwZXJjfQLYsZ3KIZdf8DZDlVNMe4,1543
@@ -58,7 +58,7 @@ jarvis/jarvis_platform_manager/main.py,sha256=o7UDrcCkLf9dTh2LOO-_bQVHjWf2X6RuSY
58
58
  jarvis/jarvis_smart_shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
59
59
  jarvis/jarvis_smart_shell/main.py,sha256=slP_8CwpfMjWFZis0At1ANRlPb3gx1KteAg1B7R7dl4,4546
60
60
  jarvis/jarvis_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
61
- jarvis/jarvis_tools/ask_codebase.py,sha256=xQTcuDl7cbH6uVkWlkA1HXJAo4_3r09-3SboUkBEBDU,10062
61
+ jarvis/jarvis_tools/ask_codebase.py,sha256=S6NArvKZyK8WEbsEgeGCljjg4D9mteWrq9m352V58jU,9635
62
62
  jarvis/jarvis_tools/ask_user.py,sha256=NjxTCHGKo4nthbEQD-isvPCW4PQhTcekEferjnukX70,2143
63
63
  jarvis/jarvis_tools/base.py,sha256=vskI4czVdlhbo38ODuF9rFrnWBYQIhJSPAqAkLVcyTs,1165
64
64
  jarvis/jarvis_tools/chdir.py,sha256=do_OdtabiH3lZcT_ynjSAX66XgH2gPl9mYiS7dMMDa8,2682
@@ -73,22 +73,22 @@ jarvis/jarvis_tools/lsp_get_diagnostics.py,sha256=IYqv8jQwSK71sZpDBRolSDnYii8t0M
73
73
  jarvis/jarvis_tools/methodology.py,sha256=gnlJojY4Dg5v9AAB5xcpKqpPIHs0tOYVtzTHkwOrWk0,5214
74
74
  jarvis/jarvis_tools/read_code.py,sha256=_X6D3AIgRD9YplSDnFhXOm8wQAZMA3pkkXy31SG33l0,6041
75
75
  jarvis/jarvis_tools/read_webpage.py,sha256=syduSZK4kXRRTPzeZ2W9Q6YH5umKiMJZyjA0cCpSF4g,2198
76
- jarvis/jarvis_tools/registry.py,sha256=-wzv1Xfzvzu8-A22xTvPnGhgYamuFU_PVmpr_sgoxA8,23083
76
+ jarvis/jarvis_tools/registry.py,sha256=ELlX1ZqCSDQc5D9RwI5xVXgBgN0IeIjWG_lD6P9T5o0,24100
77
77
  jarvis/jarvis_tools/search_web.py,sha256=kWW9K2QUR2AxPq6gcyx4Bgy-0Y4gzcdErq1DNT1EYM4,1333
78
78
  jarvis/jarvis_tools/virtual_tty.py,sha256=Rpn9VXUG17LQsY87F_O6UCjN_opXB05mpwozxYf-xVI,16372
79
79
  jarvis/jarvis_utils/__init__.py,sha256=KMg-KY5rZIhGTeOD5e2Xo5CU7DX1DUz4ULWAaTQ-ZNw,825
80
- jarvis/jarvis_utils/config.py,sha256=1OKG64hK740C1RzGk4Kov-6MgpODupMLhuma_OO5vxs,3317
80
+ jarvis/jarvis_utils/config.py,sha256=UXIwt1TknkYIuNW15TNiyZ1HJFdjjAzmt_D7_cd6xxQ,3317
81
81
  jarvis/jarvis_utils/embedding.py,sha256=_Q-VurYHQZSsyISClTFjadDaNqNPBMqJe58lMM6bsVs,6991
82
82
  jarvis/jarvis_utils/file_processors.py,sha256=oNtVlz2JHcQ60NS6sgI-VsvYXOnsQgFUEVenznCXHC4,2952
83
83
  jarvis/jarvis_utils/git_utils.py,sha256=j_Jw6h7JD91XhMf0WD3MAH4URkLUBrrYCLnuLm1GeN4,5630
84
84
  jarvis/jarvis_utils/globals.py,sha256=Ed2d6diWXCgI74HVV_tI4qW7yXxLpNvQKN2yG0IH9hc,3388
85
85
  jarvis/jarvis_utils/input.py,sha256=QhqZEF4BpOGDgNEBrTBabA5n8DnlU0GaHqbKUEZ13Ls,6953
86
- jarvis/jarvis_utils/methodology.py,sha256=dVk_1GmVFlZnj1EPR_3wilP2-vyOygAIH56w_8PC8Sw,6281
86
+ jarvis/jarvis_utils/methodology.py,sha256=E_FQOSLA9o8bKfmgORNMlkDtqPiqlCkQoIkPuxPQol4,6324
87
87
  jarvis/jarvis_utils/output.py,sha256=BmWdB1bmizv0xfU4Z___9p_xQodorriIcEgADVq9fk0,8416
88
88
  jarvis/jarvis_utils/utils.py,sha256=j-YZap58avAzSb9ZuB2I71trVqVxIpFxxZDoh8_7a_o,4653
89
- jarvis_ai_assistant-0.1.150.dist-info/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
90
- jarvis_ai_assistant-0.1.150.dist-info/METADATA,sha256=7qCut4ZuAw6wrI-H61ZzAYaeeljHvPen4W2fDTr0Zrk,10975
91
- jarvis_ai_assistant-0.1.150.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
92
- jarvis_ai_assistant-0.1.150.dist-info/entry_points.txt,sha256=4ZS8kq6jahnmfDyXFSx39HRi-Tkbp0uFc6cTXt3QIHA,929
93
- jarvis_ai_assistant-0.1.150.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
94
- jarvis_ai_assistant-0.1.150.dist-info/RECORD,,
89
+ jarvis_ai_assistant-0.1.151.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
90
+ jarvis_ai_assistant-0.1.151.dist-info/METADATA,sha256=_QXsf2ezynGKHG9ZD-_qbOYk6mD0hQPwrW5bMrrPjHI,11562
91
+ jarvis_ai_assistant-0.1.151.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
92
+ jarvis_ai_assistant-0.1.151.dist-info/entry_points.txt,sha256=4ZS8kq6jahnmfDyXFSx39HRi-Tkbp0uFc6cTXt3QIHA,929
93
+ jarvis_ai_assistant-0.1.151.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
94
+ jarvis_ai_assistant-0.1.151.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5