quantalogic 0.33.4__py3-none-any.whl → 0.40.0__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.
Files changed (107) hide show
  1. quantalogic/__init__.py +0 -4
  2. quantalogic/agent.py +603 -362
  3. quantalogic/agent_config.py +260 -28
  4. quantalogic/agent_factory.py +43 -17
  5. quantalogic/coding_agent.py +20 -12
  6. quantalogic/config.py +7 -4
  7. quantalogic/console_print_events.py +4 -8
  8. quantalogic/console_print_token.py +2 -2
  9. quantalogic/docs_cli.py +15 -10
  10. quantalogic/event_emitter.py +258 -83
  11. quantalogic/flow/__init__.py +23 -0
  12. quantalogic/flow/flow.py +595 -0
  13. quantalogic/flow/flow_extractor.py +672 -0
  14. quantalogic/flow/flow_generator.py +89 -0
  15. quantalogic/flow/flow_manager.py +407 -0
  16. quantalogic/flow/flow_manager_schema.py +169 -0
  17. quantalogic/flow/flow_yaml.md +419 -0
  18. quantalogic/generative_model.py +109 -77
  19. quantalogic/get_model_info.py +6 -6
  20. quantalogic/interactive_text_editor.py +100 -73
  21. quantalogic/main.py +36 -23
  22. quantalogic/model_info_list.py +12 -0
  23. quantalogic/model_info_litellm.py +14 -14
  24. quantalogic/prompts.py +2 -1
  25. quantalogic/{llm.py → quantlitellm.py} +29 -39
  26. quantalogic/search_agent.py +4 -4
  27. quantalogic/server/models.py +4 -1
  28. quantalogic/task_file_reader.py +5 -5
  29. quantalogic/task_runner.py +21 -20
  30. quantalogic/tool_manager.py +10 -21
  31. quantalogic/tools/__init__.py +98 -68
  32. quantalogic/tools/composio/composio.py +416 -0
  33. quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
  34. quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
  35. quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
  36. quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
  37. quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
  38. quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
  39. quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
  40. quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
  41. quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
  42. quantalogic/tools/duckduckgo_search_tool.py +2 -4
  43. quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
  44. quantalogic/tools/finance/ccxt_tool.py +373 -0
  45. quantalogic/tools/finance/finance_llm_tool.py +387 -0
  46. quantalogic/tools/finance/google_finance.py +192 -0
  47. quantalogic/tools/finance/market_intelligence_tool.py +520 -0
  48. quantalogic/tools/finance/technical_analysis_tool.py +491 -0
  49. quantalogic/tools/finance/tradingview_tool.py +336 -0
  50. quantalogic/tools/finance/yahoo_finance.py +236 -0
  51. quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
  52. quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
  53. quantalogic/tools/git/clone_repo_tool.py +189 -0
  54. quantalogic/tools/git/git_operations_tool.py +532 -0
  55. quantalogic/tools/google_packages/google_news_tool.py +480 -0
  56. quantalogic/tools/grep_app_tool.py +123 -186
  57. quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
  58. quantalogic/tools/jinja_tool.py +6 -10
  59. quantalogic/tools/language_handlers/__init__.py +22 -9
  60. quantalogic/tools/list_directory_tool.py +131 -42
  61. quantalogic/tools/llm_tool.py +45 -15
  62. quantalogic/tools/llm_vision_tool.py +59 -7
  63. quantalogic/tools/markitdown_tool.py +17 -5
  64. quantalogic/tools/nasa_packages/models.py +47 -0
  65. quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
  66. quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
  67. quantalogic/tools/nasa_packages/services.py +82 -0
  68. quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
  69. quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
  70. quantalogic/tools/product_hunt/services.py +63 -0
  71. quantalogic/tools/rag_tool/__init__.py +48 -0
  72. quantalogic/tools/rag_tool/document_metadata.py +15 -0
  73. quantalogic/tools/rag_tool/query_response.py +20 -0
  74. quantalogic/tools/rag_tool/rag_tool.py +566 -0
  75. quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
  76. quantalogic/tools/read_html_tool.py +24 -38
  77. quantalogic/tools/replace_in_file_tool.py +10 -10
  78. quantalogic/tools/safe_python_interpreter_tool.py +10 -24
  79. quantalogic/tools/search_definition_names.py +2 -2
  80. quantalogic/tools/sequence_tool.py +14 -23
  81. quantalogic/tools/sql_query_tool.py +17 -19
  82. quantalogic/tools/tool.py +39 -15
  83. quantalogic/tools/unified_diff_tool.py +1 -1
  84. quantalogic/tools/utilities/csv_processor_tool.py +234 -0
  85. quantalogic/tools/utilities/download_file_tool.py +179 -0
  86. quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
  87. quantalogic/tools/utils/__init__.py +1 -4
  88. quantalogic/tools/utils/create_sample_database.py +24 -38
  89. quantalogic/tools/utils/generate_database_report.py +74 -82
  90. quantalogic/tools/wikipedia_search_tool.py +17 -21
  91. quantalogic/utils/ask_user_validation.py +1 -1
  92. quantalogic/utils/async_utils.py +35 -0
  93. quantalogic/utils/check_version.py +3 -5
  94. quantalogic/utils/get_all_models.py +2 -1
  95. quantalogic/utils/git_ls.py +21 -7
  96. quantalogic/utils/lm_studio_model_info.py +9 -7
  97. quantalogic/utils/python_interpreter.py +113 -43
  98. quantalogic/utils/xml_utility.py +178 -0
  99. quantalogic/version_check.py +1 -1
  100. quantalogic/welcome_message.py +7 -7
  101. quantalogic/xml_parser.py +0 -1
  102. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/METADATA +44 -1
  103. quantalogic-0.40.0.dist-info/RECORD +148 -0
  104. quantalogic-0.33.4.dist-info/RECORD +0 -102
  105. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
  106. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
  107. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/entry_points.txt +0 -0
@@ -20,55 +20,39 @@ USER_AGENTS = [
20
20
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0",
21
21
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:89.0) Gecko/20100101 Firefox/89.0",
22
22
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) "
23
- "Version/14.1.1 Safari/605.1.15"
23
+ "Version/14.1.1 Safari/605.1.15",
24
24
  ]
25
25
 
26
+
26
27
  class SearchError(Exception):
27
28
  """Custom exception for search-related errors"""
29
+
28
30
  pass
29
31
 
32
+
30
33
  class GrepAppArguments(BaseModel):
31
34
  """Pydantic model for grep.app search arguments"""
35
+
32
36
  search_query: str = Field(
33
- ...,
34
- description="GitHub Code search using simple keyword or regular expression",
35
- example="code2prompt"
37
+ ..., description="GitHub Code search using simple keyword or regular expression", example="code2prompt"
36
38
  )
37
39
  repository: Optional[str] = Field(
38
40
  None,
39
41
  description="Filter by repository (e.g. user/repo)",
40
42
  example="quantalogic/quantalogic",
41
43
  )
42
- page: int = Field(
43
- 1,
44
- description="Results page number",
45
- ge=1
46
- )
47
- per_page: int = Field(
48
- 10,
49
- description="Number of results per page",
50
- ge=1,
51
- le=100
52
- )
53
- regexp: bool = Field(
54
- False,
55
- description="Enable regular expression search"
56
- )
57
- case: bool = Field(
58
- False,
59
- description="Enable case-sensitive search"
60
- )
61
- words: bool = Field(
62
- False,
63
- description="Match whole words only"
64
- )
44
+ page: int = Field(1, description="Results page number", ge=1)
45
+ per_page: int = Field(10, description="Number of results per page", ge=1, le=100)
46
+ regexp: bool = Field(False, description="Enable regular expression search")
47
+ case: bool = Field(False, description="Enable case-sensitive search")
48
+ words: bool = Field(False, description="Match whole words only")
65
49
 
66
- @model_validator(mode='before')
50
+ @model_validator(mode="before")
67
51
  @classmethod
68
52
  def convert_types(cls, data: Dict[str, Any]) -> Dict[str, Any]:
69
53
  """Convert input types before validation"""
70
54
  # Convert string numbers to integers
71
- for field in ['page', 'per_page']:
55
+ for field in ["page", "per_page"]:
72
56
  if field in data and isinstance(data[field], str):
73
57
  try:
74
58
  data[field] = int(data[field])
@@ -76,15 +60,15 @@ class GrepAppArguments(BaseModel):
76
60
  raise ValueError(f"{field} must be a valid integer")
77
61
 
78
62
  # Convert various string representations to booleans
79
- for field in ['regexp', 'case', 'words']:
63
+ for field in ["regexp", "case", "words"]:
80
64
  if field in data:
81
65
  if isinstance(data[field], str):
82
- data[field] = data[field].lower() in ['true', '1', 'yes', 'on']
66
+ data[field] = data[field].lower() in ["true", "1", "yes", "on"]
83
67
 
84
68
  return data
85
69
 
86
- @model_validator(mode='after')
87
- def validate_search_query(self) -> 'GrepAppArguments':
70
+ @model_validator(mode="after")
71
+ def validate_search_query(self) -> "GrepAppArguments":
88
72
  """Validate search query is not empty and has reasonable length"""
89
73
  if not self.search_query or not self.search_query.strip():
90
74
  raise ValueError("Search query cannot be empty")
@@ -92,65 +76,44 @@ class GrepAppArguments(BaseModel):
92
76
  raise ValueError("Search query is too long (max 500 characters)")
93
77
  return self
94
78
 
79
+
95
80
  class GrepAppTool(Tool):
96
81
  """Tool for searching GitHub code via grep.app API"""
97
-
82
+
98
83
  BASE_URL: ClassVar[str] = "https://grep.app/api/search"
99
84
  TIMEOUT: ClassVar[int] = 10
100
85
 
101
86
  def __init__(self):
102
87
  super().__init__(
103
- name="grep_app_tool",
104
- description="Searches GitHub code using grep.app API. Returns code matches with metadata."
88
+ name="grep_app_tool",
89
+ description="Searches GitHub code using grep.app API. Returns code matches with metadata.",
105
90
  )
106
91
  self.arguments = [
107
92
  ToolArgument(
108
- name="search_query",
109
- arg_type="string",
110
- description="Search query using grep.app syntax",
111
- required=True
112
- ),
113
- ToolArgument(
114
- name="repository",
115
- arg_type="string",
116
- description="Filter by repository",
117
- required=False
93
+ name="search_query", arg_type="string", description="Search query using grep.app syntax", required=True
118
94
  ),
95
+ ToolArgument(name="repository", arg_type="string", description="Filter by repository", required=False),
119
96
  ToolArgument(
120
- name="page",
121
- arg_type="int",
122
- description="Pagination page number",
123
- default="1",
124
- required=False
125
- ),
126
- ToolArgument(
127
- name="per_page",
128
- arg_type="int",
129
- description="Results per page",
130
- default="10",
131
- required=False
97
+ name="page", arg_type="int", description="Pagination page number", default="1", required=False
132
98
  ),
99
+ ToolArgument(name="per_page", arg_type="int", description="Results per page", default="10", required=False),
133
100
  ToolArgument(
134
101
  name="regexp",
135
102
  arg_type="boolean",
136
103
  description="Enable regular expression search",
137
104
  default="False",
138
- required=False
105
+ required=False,
139
106
  ),
140
107
  ToolArgument(
141
108
  name="case",
142
109
  arg_type="boolean",
143
110
  description="Enable case-sensitive search",
144
111
  default="False",
145
- required=False
112
+ required=False,
146
113
  ),
147
114
  ToolArgument(
148
- name="words",
149
- arg_type="boolean",
150
- description="Match whole words only",
151
- default="False",
152
- required=False
153
- )
115
+ name="words", arg_type="boolean", description="Match whole words only", default="False", required=False
116
+ ),
154
117
  ]
155
118
 
156
119
  def _build_headers(self) -> Dict[str, str]:
@@ -159,18 +122,14 @@ class GrepAppTool(Tool):
159
122
  "User-Agent": random.choice(USER_AGENTS),
160
123
  "Accept": "application/json",
161
124
  "Accept-Language": "en-US,en;q=0.5",
162
- "DNT": "1"
125
+ "DNT": "1",
163
126
  }
164
127
  logger.debug(f"Built headers: {headers}")
165
128
  return headers
166
129
 
167
130
  def _build_params(self, args: GrepAppArguments) -> Dict[str, Any]:
168
131
  """Build request parameters from arguments"""
169
- params = {
170
- "q": args.search_query,
171
- "page": args.page,
172
- "per_page": args.per_page
173
- }
132
+ params = {"q": args.search_query, "page": args.page, "per_page": args.per_page}
174
133
  if args.repository:
175
134
  params["filter[repo][0]"] = args.repository
176
135
  if args.regexp:
@@ -185,12 +144,7 @@ class GrepAppTool(Tool):
185
144
  def _make_request(self, params: Dict[str, Any], headers: Dict[str, str]) -> Dict[str, Any]:
186
145
  """Make the API request"""
187
146
  logger.info("Making API request to grep.app")
188
- response = requests.get(
189
- self.BASE_URL,
190
- params=params,
191
- headers=headers,
192
- timeout=self.TIMEOUT
193
- )
147
+ response = requests.get(self.BASE_URL, params=params, headers=headers, timeout=self.TIMEOUT)
194
148
  logger.debug(f"API Response Status Code: {response.status_code}")
195
149
  response.raise_for_status()
196
150
  data = response.json()
@@ -199,15 +153,17 @@ class GrepAppTool(Tool):
199
153
  logger.debug(f"API Response Data: {data}")
200
154
  return data
201
155
 
202
- def execute(self,
203
- search_query: str,
204
- repository: Optional[str] = None,
205
- page: Union[int, str] = 1,
206
- per_page: Union[int, str] = 10,
207
- regexp: bool = False,
208
- case: bool = False,
209
- words: bool = False,
210
- skip_delay: bool = False) -> str:
156
+ def execute(
157
+ self,
158
+ search_query: str,
159
+ repository: Optional[str] = None,
160
+ page: Union[int, str] = 1,
161
+ per_page: Union[int, str] = 10,
162
+ regexp: bool = False,
163
+ case: bool = False,
164
+ words: bool = False,
165
+ skip_delay: bool = False,
166
+ ) -> str:
211
167
  """Execute grep.app API search with pagination and return formatted results as a string"""
212
168
  try:
213
169
  # Validate and convert arguments
@@ -218,7 +174,7 @@ class GrepAppTool(Tool):
218
174
  per_page=int(per_page),
219
175
  regexp=regexp,
220
176
  case=case,
221
- words=words
177
+ words=words,
222
178
  )
223
179
 
224
180
  logger.info(f"Executing search: '{args.search_query}'")
@@ -246,7 +202,7 @@ class GrepAppTool(Tool):
246
202
  return self._format_error(
247
203
  "API Error",
248
204
  str(e),
249
- {"Request URL": getattr(e.response, 'url', 'N/A') if hasattr(e, 'response') else 'N/A'}
205
+ {"Request URL": getattr(e.response, "url", "N/A") if hasattr(e, "response") else "N/A"},
250
206
  )
251
207
  except SearchError as e:
252
208
  logger.error(f"Search error: {e}")
@@ -257,83 +213,84 @@ class GrepAppTool(Tool):
257
213
 
258
214
  def _format_results(self, data: Dict[str, Any]) -> str:
259
215
  """Format API results into a structured Markdown string"""
260
- query = data.get('query', '')
261
- total_results = data.get('hits', {}).get('total', 0)
216
+ query = data.get("query", "")
217
+ total_results = data.get("hits", {}).get("total", 0)
262
218
  hits = data.get("hits", {}).get("hits", [])
263
219
 
264
220
  output = [
265
221
  "# 🔍 Search Results",
266
222
  "",
267
223
  f"**Query:** `{query if query else '<empty>'}` • **Found:** {total_results} matches",
268
- ""
224
+ "",
269
225
  ]
270
226
 
271
227
  if not hits:
272
228
  output.append("> No matches found for your search query.")
273
229
  else:
274
230
  for idx, result in enumerate(hits, 1):
275
- repo = result.get('repo', {}).get('raw', 'N/A')
276
- file_path = result.get('path', {}).get('raw', 'N/A')
277
- language = result.get('language', 'N/A').lower()
231
+ repo = result.get("repo", {}).get("raw", "N/A")
232
+ file_path = result.get("path", {}).get("raw", "N/A")
233
+ language = result.get("language", "N/A").lower()
278
234
  content = result.get("content", {})
279
-
235
+
280
236
  # Extract the actual code and line info
281
237
  snippet = content.get("snippet", "")
282
238
  line_num = content.get("line", "")
283
-
239
+
284
240
  # Clean up the snippet
285
241
  import re
286
- clean_snippet = re.sub(r'<[^>]+>', '', snippet)
287
- clean_snippet = re.sub(r'&quot;', '"', clean_snippet)
288
- clean_snippet = re.sub(r'&lt;', '<', clean_snippet)
289
- clean_snippet = re.sub(r'&gt;', '>', clean_snippet)
242
+
243
+ clean_snippet = re.sub(r"<[^>]+>", "", snippet)
244
+ clean_snippet = re.sub(r"&quot;", '"', clean_snippet)
245
+ clean_snippet = re.sub(r"&lt;", "<", clean_snippet)
246
+ clean_snippet = re.sub(r"&gt;", ">", clean_snippet)
290
247
  clean_snippet = clean_snippet.strip()
291
-
248
+
292
249
  # Split into lines and clean each line
293
- raw_lines = clean_snippet.split('\n')
250
+ raw_lines = clean_snippet.split("\n")
294
251
  lines = []
295
252
  current_line_num = int(line_num) if line_num else 1
296
-
253
+
297
254
  # First pass: collect all lines and their content
298
255
  for line in raw_lines:
299
256
  # Remove excess whitespace but preserve indentation
300
257
  stripped = line.rstrip()
301
258
  if not stripped:
302
- lines.append(('', current_line_num))
259
+ lines.append(("", current_line_num))
303
260
  current_line_num += 1
304
261
  continue
305
-
262
+
306
263
  # Remove duplicate indentation
307
- if stripped.startswith(' '):
264
+ if stripped.startswith(" "):
308
265
  stripped = stripped[4:]
309
-
266
+
310
267
  # Handle URLs that might be split across lines
311
- if stripped.startswith(('prompt', '-working')):
312
- if lines and lines[-1][0].endswith('/'):
268
+ if stripped.startswith(("prompt", "-working")):
269
+ if lines and lines[-1][0].endswith("/"):
313
270
  # Combine with previous line
314
271
  prev_content, prev_num = lines.pop()
315
272
  lines.append((prev_content + stripped, prev_num))
316
273
  continue
317
-
274
+
318
275
  # Handle concatenated lines by looking for line numbers
319
- line_parts = re.split(r'(\d+)(?=\s*[^\d])', stripped)
276
+ line_parts = re.split(r"(\d+)(?=\s*[^\d])", stripped)
320
277
  if len(line_parts) > 1:
321
278
  # Process each part that might be a new line
322
- for i in range(0, len(line_parts)-1, 2):
279
+ for i in range(0, len(line_parts) - 1, 2):
323
280
  prefix = line_parts[i].rstrip()
324
281
  if prefix:
325
282
  if not any(l[0] == prefix for l in lines): # Avoid duplicates
326
283
  lines.append((prefix, current_line_num))
327
-
284
+
328
285
  # Update line number if found
329
286
  try:
330
- current_line_num = int(line_parts[i+1])
287
+ current_line_num = int(line_parts[i + 1])
331
288
  except ValueError:
332
289
  current_line_num += 1
333
-
290
+
334
291
  # Add the content after the line number
335
- if i+2 < len(line_parts):
336
- content = line_parts[i+2].lstrip()
292
+ if i + 2 < len(line_parts):
293
+ content = line_parts[i + 2].lstrip()
337
294
  if content and not any(l[0] == content for l in lines): # Avoid duplicates
338
295
  lines.append((content, current_line_num))
339
296
  else:
@@ -344,24 +301,24 @@ class GrepAppTool(Tool):
344
301
  # Format line numbers and code
345
302
  formatted_lines = []
346
303
  max_line_width = len(str(max(line[1] for line in lines))) if lines else 3
347
-
304
+
348
305
  # Second pass: format each line
349
306
  for line_content, line_no in lines:
350
307
  if not line_content: # Empty line
351
- formatted_lines.append('')
308
+ formatted_lines.append("")
352
309
  continue
353
-
310
+
354
311
  # Special handling for markdown badges and links
355
- if '[![' in line_content or '[!' in line_content:
356
- badges = re.findall(r'(\[!\[.*?\]\(.*?\)\]\(.*?\))', line_content)
312
+ if "[![" in line_content or "[!" in line_content:
313
+ badges = re.findall(r"(\[!\[.*?\]\(.*?\)\]\(.*?\))", line_content)
357
314
  if badges:
358
315
  for badge in badges:
359
316
  if not any(badge in l for l in formatted_lines): # Avoid duplicates
360
317
  formatted_lines.append(f"{str(line_no).rjust(max_line_width)} │ {badge}")
361
318
  continue
362
-
319
+
363
320
  # Add syntax highlighting for comments
364
- if line_content.lstrip().startswith(('// ', '# ', '/* ', '* ', '*/')):
321
+ if line_content.lstrip().startswith(("// ", "# ", "/* ", "* ", "*/")):
365
322
  line_str = f"{str(line_no).rjust(max_line_width)} │ <dim>{line_content}</dim>"
366
323
  if not any(line_str in l for l in formatted_lines): # Avoid duplicates
367
324
  formatted_lines.append(line_str)
@@ -370,12 +327,15 @@ class GrepAppTool(Tool):
370
327
  indent = len(line_content) - len(line_content.lstrip())
371
328
  indentation = line_content[:indent]
372
329
  content = line_content[indent:]
373
-
330
+
374
331
  # Highlight strings and special syntax
375
- content = re.sub(r'(["\'])(.*?)\1', r'<str>\1\2\1</str>', content)
376
- content = re.sub(r'\b(function|const|let|var|import|export|class|interface|type|enum)\b',
377
- r'<keyword>\1</keyword>', content)
378
-
332
+ content = re.sub(r'(["\'])(.*?)\1', r"<str>\1\2\1</str>", content)
333
+ content = re.sub(
334
+ r"\b(function|const|let|var|import|export|class|interface|type|enum)\b",
335
+ r"<keyword>\1</keyword>",
336
+ content,
337
+ )
338
+
379
339
  line_str = f"{str(line_no).rjust(max_line_width)} │ {indentation}{content}"
380
340
  if not any(line_str in l for l in formatted_lines): # Avoid duplicates
381
341
  formatted_lines.append(line_str)
@@ -386,63 +346,54 @@ class GrepAppTool(Tool):
386
346
  formatted_lines = formatted_lines[:5]
387
347
  if remaining > 0:
388
348
  formatted_lines.append(f" ┆ {remaining} more line{'s' if remaining > 1 else ''}")
389
-
390
- clean_snippet = '\n'.join(formatted_lines)
391
-
349
+
350
+ clean_snippet = "\n".join(formatted_lines)
351
+
392
352
  # Format the repository link to be clickable
393
- if '/' in repo:
353
+ if "/" in repo:
394
354
  repo_link = f"[`{repo}`](https://github.com/{repo})"
395
355
  else:
396
356
  repo_link = f"`{repo}`"
397
357
 
398
358
  # Determine the best language display and icon
399
- lang_display = language if language != 'n/a' else ''
359
+ lang_display = language if language != "n/a" else ""
400
360
  lang_icon = {
401
- 'python': '🐍',
402
- 'typescript': '📘',
403
- 'javascript': '📒',
404
- 'markdown': '📝',
405
- 'toml': '⚙️',
406
- 'yaml': '📋',
407
- 'json': '📦',
408
- 'shell': '🐚',
409
- 'rust': '🦀',
410
- 'go': '🔵',
411
- 'java': '',
412
- 'ruby': '💎',
413
- }.get(lang_display, '📄')
414
-
361
+ "python": "🐍",
362
+ "typescript": "📘",
363
+ "javascript": "📒",
364
+ "markdown": "📝",
365
+ "toml": "⚙️",
366
+ "yaml": "📋",
367
+ "json": "📦",
368
+ "shell": "🐚",
369
+ "rust": "🦀",
370
+ "go": "🔵",
371
+ "java": "",
372
+ "ruby": "💎",
373
+ }.get(lang_display, "📄")
374
+
415
375
  # Format file path with language icon and line info
416
376
  file_info = [f"{lang_icon} `{file_path}`"]
417
377
  if line_num:
418
378
  file_info.append(f"Line {line_num}")
419
-
420
- output.extend([
421
- f"### {repo_link}",
422
- " • ".join(file_info),
423
- "```",
424
- clean_snippet,
425
- "```",
426
- ""
427
- ])
379
+
380
+ output.extend([f"### {repo_link}", " • ".join(file_info), "```", clean_snippet, "```", ""])
428
381
 
429
382
  return "\n".join(filter(None, output))
430
383
 
431
384
  def _format_error(self, error_type: str, message: str, additional_info: Dict[str, str] = None) -> str:
432
385
  """Format error messages consistently using Markdown"""
433
- output = [
434
- f"## {error_type}",
435
- f"**Message:** {message}"
436
- ]
437
-
386
+ output = [f"## {error_type}", f"**Message:** {message}"]
387
+
438
388
  if additional_info:
439
389
  output.append("**Additional Information:**")
440
390
  for key, value in additional_info.items():
441
391
  output.append(f"- **{key}:** {value}")
442
-
392
+
443
393
  output.append(f"## End {error_type}")
444
394
  return "\n\n".join(output)
445
395
 
396
+
446
397
  if __name__ == "__main__":
447
398
  # Configure logger
448
399
  logger.remove() # Remove default handlers
@@ -457,17 +408,10 @@ if __name__ == "__main__":
457
408
  "args": {
458
409
  "search_query": "lang:python def __init__",
459
410
  "per_page": 5,
460
- "skip_delay": True # Skip delay for testing
461
- }
462
- },
463
- {
464
- "name": "Logging Patterns Search",
465
- "args": {
466
- "search_query": "logger",
467
- "per_page": 3,
468
- "skip_delay": True
469
- }
411
+ "skip_delay": True, # Skip delay for testing
412
+ },
470
413
  },
414
+ {"name": "Logging Patterns Search", "args": {"search_query": "logger", "per_page": 3, "skip_delay": True}},
471
415
  {
472
416
  "name": "Repository-Specific Search",
473
417
  "args": {
@@ -475,25 +419,18 @@ if __name__ == "__main__":
475
419
  "repository": "quantalogic/quantalogic",
476
420
  "per_page": 5,
477
421
  "words": True,
478
- "skip_delay": True
479
- }
422
+ "skip_delay": True,
423
+ },
480
424
  },
481
- {
482
- "name": "Raphaël MANSUY",
483
- "args": {
484
- "search_query": "raphaelmansuy",
485
- "per_page": 3,
486
- "skip_delay": True
487
- }
488
- }
425
+ {"name": "Raphaël MANSUY", "args": {"search_query": "raphaelmansuy", "per_page": 3, "skip_delay": True}},
489
426
  ]
490
427
 
491
428
  for test in test_cases:
492
429
  try:
493
430
  logger.info(f"Running test: {test['name']}")
494
431
  logger.info(f"Executing with arguments: {test['args']}")
495
- result = tool.execute(**test['args'])
432
+ result = tool.execute(**test["args"])
496
433
  print(f"\n### Test: {test['name']}\n{result}\n")
497
434
  time.sleep(1) # Add a small delay between tests to avoid rate limiting
498
435
  except Exception as e:
499
- logger.error(f"{test['name']} Failed: {e}", exc_info=True)
436
+ logger.error(f"{test['name']} Failed: {e}", exc_info=True)