quantalogic 0.35.0__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.
- quantalogic/__init__.py +0 -4
- quantalogic/agent.py +603 -363
- quantalogic/agent_config.py +233 -46
- quantalogic/agent_factory.py +34 -22
- quantalogic/coding_agent.py +16 -14
- quantalogic/config.py +2 -1
- quantalogic/console_print_events.py +4 -8
- quantalogic/console_print_token.py +2 -2
- quantalogic/docs_cli.py +15 -10
- quantalogic/event_emitter.py +258 -83
- quantalogic/flow/__init__.py +23 -0
- quantalogic/flow/flow.py +595 -0
- quantalogic/flow/flow_extractor.py +672 -0
- quantalogic/flow/flow_generator.py +89 -0
- quantalogic/flow/flow_manager.py +407 -0
- quantalogic/flow/flow_manager_schema.py +169 -0
- quantalogic/flow/flow_yaml.md +419 -0
- quantalogic/generative_model.py +109 -77
- quantalogic/get_model_info.py +5 -5
- quantalogic/interactive_text_editor.py +100 -73
- quantalogic/main.py +17 -21
- quantalogic/model_info_list.py +3 -3
- quantalogic/model_info_litellm.py +14 -14
- quantalogic/prompts.py +2 -1
- quantalogic/{llm.py → quantlitellm.py} +29 -39
- quantalogic/search_agent.py +4 -4
- quantalogic/server/models.py +4 -1
- quantalogic/task_file_reader.py +5 -5
- quantalogic/task_runner.py +20 -20
- quantalogic/tool_manager.py +10 -21
- quantalogic/tools/__init__.py +98 -68
- quantalogic/tools/composio/composio.py +416 -0
- quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
- quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
- quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
- quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
- quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
- quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
- quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
- quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
- quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
- quantalogic/tools/duckduckgo_search_tool.py +2 -4
- quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
- quantalogic/tools/finance/ccxt_tool.py +373 -0
- quantalogic/tools/finance/finance_llm_tool.py +387 -0
- quantalogic/tools/finance/google_finance.py +192 -0
- quantalogic/tools/finance/market_intelligence_tool.py +520 -0
- quantalogic/tools/finance/technical_analysis_tool.py +491 -0
- quantalogic/tools/finance/tradingview_tool.py +336 -0
- quantalogic/tools/finance/yahoo_finance.py +236 -0
- quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
- quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
- quantalogic/tools/git/clone_repo_tool.py +189 -0
- quantalogic/tools/git/git_operations_tool.py +532 -0
- quantalogic/tools/google_packages/google_news_tool.py +480 -0
- quantalogic/tools/grep_app_tool.py +123 -186
- quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
- quantalogic/tools/jinja_tool.py +6 -10
- quantalogic/tools/language_handlers/__init__.py +22 -9
- quantalogic/tools/list_directory_tool.py +131 -42
- quantalogic/tools/llm_tool.py +45 -15
- quantalogic/tools/llm_vision_tool.py +59 -7
- quantalogic/tools/markitdown_tool.py +17 -5
- quantalogic/tools/nasa_packages/models.py +47 -0
- quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
- quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
- quantalogic/tools/nasa_packages/services.py +82 -0
- quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
- quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
- quantalogic/tools/product_hunt/services.py +63 -0
- quantalogic/tools/rag_tool/__init__.py +48 -0
- quantalogic/tools/rag_tool/document_metadata.py +15 -0
- quantalogic/tools/rag_tool/query_response.py +20 -0
- quantalogic/tools/rag_tool/rag_tool.py +566 -0
- quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
- quantalogic/tools/read_html_tool.py +24 -38
- quantalogic/tools/replace_in_file_tool.py +10 -10
- quantalogic/tools/safe_python_interpreter_tool.py +10 -24
- quantalogic/tools/search_definition_names.py +2 -2
- quantalogic/tools/sequence_tool.py +14 -23
- quantalogic/tools/sql_query_tool.py +17 -19
- quantalogic/tools/tool.py +39 -15
- quantalogic/tools/unified_diff_tool.py +1 -1
- quantalogic/tools/utilities/csv_processor_tool.py +234 -0
- quantalogic/tools/utilities/download_file_tool.py +179 -0
- quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
- quantalogic/tools/utils/__init__.py +1 -4
- quantalogic/tools/utils/create_sample_database.py +24 -38
- quantalogic/tools/utils/generate_database_report.py +74 -82
- quantalogic/tools/wikipedia_search_tool.py +17 -21
- quantalogic/utils/ask_user_validation.py +1 -1
- quantalogic/utils/async_utils.py +35 -0
- quantalogic/utils/check_version.py +3 -5
- quantalogic/utils/get_all_models.py +2 -1
- quantalogic/utils/git_ls.py +21 -7
- quantalogic/utils/lm_studio_model_info.py +9 -7
- quantalogic/utils/python_interpreter.py +113 -43
- quantalogic/utils/xml_utility.py +178 -0
- quantalogic/version_check.py +1 -1
- quantalogic/welcome_message.py +7 -7
- quantalogic/xml_parser.py +0 -1
- {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/METADATA +41 -1
- quantalogic-0.40.0.dist-info/RECORD +148 -0
- quantalogic-0.35.0.dist-info/RECORD +0 -102
- {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.35.0.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
|
-
|
44
|
-
|
45
|
-
|
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=
|
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 [
|
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 [
|
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 [
|
66
|
+
data[field] = data[field].lower() in ["true", "1", "yes", "on"]
|
83
67
|
|
84
68
|
return data
|
85
69
|
|
86
|
-
@model_validator(mode=
|
87
|
-
def validate_search_query(self) ->
|
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
|
-
|
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(
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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,
|
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(
|
261
|
-
total_results = data.get(
|
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(
|
276
|
-
file_path = result.get(
|
277
|
-
language = result.get(
|
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
|
-
|
287
|
-
clean_snippet = re.sub(r
|
288
|
-
clean_snippet = re.sub(r
|
289
|
-
clean_snippet = re.sub(r
|
242
|
+
|
243
|
+
clean_snippet = re.sub(r"<[^>]+>", "", snippet)
|
244
|
+
clean_snippet = re.sub(r""", '"', clean_snippet)
|
245
|
+
clean_snippet = re.sub(r"<", "<", clean_snippet)
|
246
|
+
clean_snippet = re.sub(r">", ">", 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(
|
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((
|
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((
|
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
|
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
|
356
|
-
badges = re.findall(r
|
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
|
376
|
-
content = re.sub(
|
377
|
-
|
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 =
|
391
|
-
|
349
|
+
|
350
|
+
clean_snippet = "\n".join(formatted_lines)
|
351
|
+
|
392
352
|
# Format the repository link to be clickable
|
393
|
-
if
|
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 !=
|
359
|
+
lang_display = language if language != "n/a" else ""
|
400
360
|
lang_icon = {
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
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
|
-
|
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[
|
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)
|