webscout 7.5__py3-none-any.whl → 7.7__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 webscout might be problematic. Click here for more details.

Files changed (132) hide show
  1. webscout/AIauto.py +5 -53
  2. webscout/AIutel.py +8 -318
  3. webscout/DWEBS.py +460 -489
  4. webscout/Extra/YTToolkit/YTdownloader.py +14 -53
  5. webscout/Extra/YTToolkit/transcriber.py +12 -13
  6. webscout/Extra/YTToolkit/ytapi/video.py +0 -1
  7. webscout/Extra/__init__.py +0 -1
  8. webscout/Extra/autocoder/__init__.py +9 -9
  9. webscout/Extra/autocoder/autocoder_utiles.py +193 -199
  10. webscout/Extra/autocoder/rawdog.py +789 -677
  11. webscout/Extra/gguf.py +682 -428
  12. webscout/Extra/weather.py +178 -156
  13. webscout/Extra/weather_ascii.py +70 -17
  14. webscout/Litlogger/core/logger.py +1 -2
  15. webscout/Litlogger/handlers/file.py +1 -1
  16. webscout/Litlogger/styles/formats.py +0 -2
  17. webscout/Litlogger/utils/detectors.py +0 -1
  18. webscout/Provider/AISEARCH/DeepFind.py +0 -1
  19. webscout/Provider/AISEARCH/ISou.py +1 -22
  20. webscout/Provider/AISEARCH/felo_search.py +0 -1
  21. webscout/Provider/AllenAI.py +28 -30
  22. webscout/Provider/C4ai.py +29 -11
  23. webscout/Provider/ChatGPTClone.py +226 -0
  24. webscout/Provider/ChatGPTGratis.py +24 -56
  25. webscout/Provider/DeepSeek.py +25 -17
  26. webscout/Provider/Deepinfra.py +115 -48
  27. webscout/Provider/Gemini.py +1 -1
  28. webscout/Provider/Glider.py +33 -12
  29. webscout/Provider/HF_space/qwen_qwen2.py +2 -2
  30. webscout/Provider/HeckAI.py +23 -7
  31. webscout/Provider/Hunyuan.py +272 -0
  32. webscout/Provider/Jadve.py +20 -5
  33. webscout/Provider/LambdaChat.py +391 -0
  34. webscout/Provider/Netwrck.py +42 -19
  35. webscout/Provider/OLLAMA.py +256 -32
  36. webscout/Provider/PI.py +4 -2
  37. webscout/Provider/Perplexitylabs.py +26 -6
  38. webscout/Provider/PizzaGPT.py +10 -51
  39. webscout/Provider/TTI/AiForce/async_aiforce.py +4 -37
  40. webscout/Provider/TTI/AiForce/sync_aiforce.py +41 -38
  41. webscout/Provider/TTI/FreeAIPlayground/__init__.py +9 -9
  42. webscout/Provider/TTI/FreeAIPlayground/async_freeaiplayground.py +179 -206
  43. webscout/Provider/TTI/FreeAIPlayground/sync_freeaiplayground.py +180 -192
  44. webscout/Provider/TTI/MagicStudio/__init__.py +2 -0
  45. webscout/Provider/TTI/MagicStudio/async_magicstudio.py +111 -0
  46. webscout/Provider/TTI/MagicStudio/sync_magicstudio.py +109 -0
  47. webscout/Provider/TTI/PollinationsAI/async_pollinations.py +5 -24
  48. webscout/Provider/TTI/PollinationsAI/sync_pollinations.py +2 -22
  49. webscout/Provider/TTI/__init__.py +2 -3
  50. webscout/Provider/TTI/aiarta/async_aiarta.py +14 -14
  51. webscout/Provider/TTI/aiarta/sync_aiarta.py +52 -21
  52. webscout/Provider/TTI/artbit/async_artbit.py +3 -32
  53. webscout/Provider/TTI/artbit/sync_artbit.py +3 -31
  54. webscout/Provider/TTI/fastflux/__init__.py +22 -0
  55. webscout/Provider/TTI/fastflux/async_fastflux.py +261 -0
  56. webscout/Provider/TTI/fastflux/sync_fastflux.py +252 -0
  57. webscout/Provider/TTI/piclumen/__init__.py +22 -22
  58. webscout/Provider/TTI/piclumen/sync_piclumen.py +232 -232
  59. webscout/Provider/TTS/__init__.py +2 -2
  60. webscout/Provider/TTS/deepgram.py +12 -39
  61. webscout/Provider/TTS/elevenlabs.py +14 -40
  62. webscout/Provider/TTS/gesserit.py +11 -35
  63. webscout/Provider/TTS/murfai.py +13 -39
  64. webscout/Provider/TTS/parler.py +17 -40
  65. webscout/Provider/TTS/speechma.py +180 -0
  66. webscout/Provider/TTS/streamElements.py +17 -44
  67. webscout/Provider/TextPollinationsAI.py +39 -59
  68. webscout/Provider/Venice.py +25 -8
  69. webscout/Provider/WebSim.py +227 -0
  70. webscout/Provider/WiseCat.py +27 -5
  71. webscout/Provider/Youchat.py +64 -37
  72. webscout/Provider/__init__.py +12 -7
  73. webscout/Provider/akashgpt.py +20 -5
  74. webscout/Provider/flowith.py +33 -7
  75. webscout/Provider/freeaichat.py +32 -45
  76. webscout/Provider/koala.py +20 -5
  77. webscout/Provider/labyrinth.py +239 -0
  78. webscout/Provider/learnfastai.py +28 -15
  79. webscout/Provider/llamatutor.py +1 -1
  80. webscout/Provider/llmchat.py +30 -8
  81. webscout/Provider/multichat.py +65 -9
  82. webscout/Provider/sonus.py +208 -0
  83. webscout/Provider/talkai.py +1 -0
  84. webscout/Provider/turboseek.py +3 -0
  85. webscout/Provider/tutorai.py +2 -0
  86. webscout/Provider/typegpt.py +155 -65
  87. webscout/Provider/uncovr.py +297 -0
  88. webscout/Provider/x0gpt.py +3 -1
  89. webscout/Provider/yep.py +102 -20
  90. webscout/__init__.py +3 -0
  91. webscout/cli.py +53 -40
  92. webscout/conversation.py +1 -10
  93. webscout/litagent/__init__.py +2 -2
  94. webscout/litagent/agent.py +356 -20
  95. webscout/litagent/constants.py +34 -5
  96. webscout/litprinter/__init__.py +0 -3
  97. webscout/models.py +181 -0
  98. webscout/optimizers.py +1 -1
  99. webscout/prompt_manager.py +2 -8
  100. webscout/scout/core/scout.py +1 -4
  101. webscout/scout/core/search_result.py +1 -1
  102. webscout/scout/core/text_utils.py +1 -1
  103. webscout/scout/core.py +2 -5
  104. webscout/scout/element.py +1 -1
  105. webscout/scout/parsers/html_parser.py +1 -1
  106. webscout/scout/utils.py +0 -1
  107. webscout/swiftcli/__init__.py +1 -3
  108. webscout/tempid.py +1 -1
  109. webscout/update_checker.py +1 -3
  110. webscout/version.py +1 -1
  111. webscout/webscout_search_async.py +1 -2
  112. webscout/yep_search.py +297 -297
  113. {webscout-7.5.dist-info → webscout-7.7.dist-info}/LICENSE.md +4 -4
  114. {webscout-7.5.dist-info → webscout-7.7.dist-info}/METADATA +127 -405
  115. {webscout-7.5.dist-info → webscout-7.7.dist-info}/RECORD +118 -117
  116. webscout/Extra/autollama.py +0 -231
  117. webscout/Provider/Amigo.py +0 -274
  118. webscout/Provider/Bing.py +0 -243
  119. webscout/Provider/DiscordRocks.py +0 -253
  120. webscout/Provider/TTI/blackbox/__init__.py +0 -4
  121. webscout/Provider/TTI/blackbox/async_blackbox.py +0 -212
  122. webscout/Provider/TTI/blackbox/sync_blackbox.py +0 -199
  123. webscout/Provider/TTI/deepinfra/__init__.py +0 -4
  124. webscout/Provider/TTI/deepinfra/async_deepinfra.py +0 -227
  125. webscout/Provider/TTI/deepinfra/sync_deepinfra.py +0 -199
  126. webscout/Provider/TTI/imgninza/__init__.py +0 -4
  127. webscout/Provider/TTI/imgninza/async_ninza.py +0 -214
  128. webscout/Provider/TTI/imgninza/sync_ninza.py +0 -209
  129. webscout/Provider/TTS/voicepod.py +0 -117
  130. {webscout-7.5.dist-info → webscout-7.7.dist-info}/WHEEL +0 -0
  131. {webscout-7.5.dist-info → webscout-7.7.dist-info}/entry_points.txt +0 -0
  132. {webscout-7.5.dist-info → webscout-7.7.dist-info}/top_level.txt +0 -0
@@ -1,678 +1,790 @@
1
- """RawDog module for generating and auto-executing Python scripts in the CLI."""
2
-
3
- import os
4
- import re
5
- import sys
6
- import queue
7
- import threading
8
- import platform
9
- import datetime
10
- import subprocess
11
- import pygetwindow as gw
12
- from rich import print as rprint
13
- from rich.panel import Panel
14
- from rich.syntax import Syntax
15
- from rich.console import Console, Group
16
- from rich.markdown import Markdown
17
- from rich.table import Table
18
- from rich.style import Style
19
- from rich.theme import Theme
20
- from rich.live import Live
21
- from rich.status import Status
22
- from rich.rule import Rule
23
- from typing import Optional, Dict, Any, Generator, List, Tuple
24
- from webscout.AIutel import run_system_command, default_path
25
- from webscout import Logger, LogFormat
26
- from .autocoder_utiles import EXAMPLES, get_intro_prompt
27
-
28
- # Initialize LitLogger with custom format and colors
29
- logger = Logger(
30
- name="RawDog",
31
- format=LogFormat.MODERN_EMOJI,
32
-
33
- )
34
-
35
- # Custom theme for consistent styling
36
- CUSTOM_THEME = Theme({
37
- "info": "cyan",
38
- "warning": "yellow",
39
- "error": "red bold",
40
- "success": "green",
41
- "code": "blue",
42
- "output": "white",
43
- })
44
-
45
- console = Console(theme=CUSTOM_THEME)
46
-
47
- class AutoCoder:
48
- """Generate and auto-execute Python scripts in the CLI with advanced error handling and retry logic.
49
-
50
- This class provides:
51
- - Automatic code generation
52
- - Script execution with safety checks
53
- - Advanced error handling and retries
54
- - Beautiful logging with LitLogger
55
-
56
- Examples:
57
- >>> coder = AutoCoder()
58
- >>> coder.execute("Get system info")
59
- Generating system info script...
60
- Script executed successfully!
61
- """
62
-
63
- examples = EXAMPLES
64
-
65
- def __init__(
66
- self,
67
- quiet: bool = False,
68
- internal_exec: bool = False,
69
- confirm_script: bool = False,
70
- interpreter: str = "python",
71
- prettify: bool = True,
72
- path_to_script: str = "",
73
- max_retries: int = 3,
74
- ai_instance = None
75
- ):
76
- """Initialize AutoCoder instance.
77
-
78
- Args:
79
- quiet (bool): Flag to control logging. Defaults to False.
80
- internal_exec (bool): Execute scripts with exec function. Defaults to False.
81
- confirm_script (bool): Give consent to scripts prior to execution. Defaults to False.
82
- interpreter (str): Python's interpreter name. Defaults to "python".
83
- prettify (bool): Prettify the code on stdout. Defaults to True.
84
- path_to_script (str): Path to save generated scripts. Defaults to "".
85
- max_retries (int): Maximum number of retry attempts. Defaults to 3.
86
- ai_instance: AI instance for error correction. Defaults to None.
87
- """
88
- self.internal_exec = internal_exec
89
- self.confirm_script = confirm_script
90
- self.quiet = quiet
91
- self.interpreter = interpreter
92
- self.prettify = prettify
93
- self.path_to_script = path_to_script or os.path.join(default_path, "execute_this.py")
94
- self.max_retries = max_retries
95
- self.tried_solutions = set()
96
- self.ai_instance = ai_instance
97
-
98
- # Initialize logger with modern format and cyberpunk colors
99
- self.logger = Logger(
100
- name="AutoCoder",
101
- format=LogFormat.MODERN_EMOJI,
102
-
103
- )
104
-
105
- # Get Python version with enhanced logging
106
- self.logger.info("Initializing AutoCoder...")
107
- if self.internal_exec:
108
- self.python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
109
- self.logger.info(f"Using internal Python {self.python_version}")
110
- else:
111
- version_output = run_system_command(
112
- f"{self.interpreter} --version",
113
- exit_on_error=True,
114
- stdout_error=True,
115
- help="If you're using Webscout-cli, use the flag '--internal-exec'"
116
- )[1].stdout
117
- self.python_version = version_output.split(" ")[1]
118
- self.logger.info(f"Using external Python {self.python_version}")
119
-
120
- self.logger.success("AutoCoder initialized successfully!")
121
-
122
- def get_current_app(self) -> str:
123
- """Get the name of the currently active application.
124
-
125
- Returns:
126
- str: Name of the active window or "Unknown"
127
- """
128
- try:
129
- active_window = gw.getActiveWindow()
130
- if active_window:
131
- return active_window.title
132
- except Exception as e:
133
- self.logger.error(f"Error getting active window: {e}")
134
- return "Unknown"
135
-
136
- def _extract_code_blocks(self, response: str) -> List[Tuple[str, str]]:
137
- """Extract code blocks from a response string.
138
-
139
- Args:
140
- response (str): Response string containing code blocks
141
-
142
- Returns:
143
- List[Tuple[str, str]]: List of (code_type, code) tuples
144
- """
145
- blocks = []
146
-
147
- # First try to find code blocks with explicit language tags
148
- pattern = r"```(\w+)\n(.*?)```"
149
- matches = re.finditer(pattern, response, re.DOTALL)
150
-
151
- for match in matches:
152
- code_type = match.group(1).lower()
153
- code = match.group(2).strip()
154
- blocks.append(('python', code))
155
-
156
- # If no explicit code blocks found, treat as Python code
157
- if not blocks:
158
- lines = [line.strip() for line in response.split('\n') if line.strip()]
159
- for line in lines:
160
- blocks.append(('python', line))
161
-
162
- return blocks
163
-
164
- def _execute_code_block(self, code_type: str, code: str, ai_instance=None) -> Optional[str]:
165
- """Execute a code block.
166
-
167
- Args:
168
- code_type (str): Type of code block ('python')
169
- code (str): Code to execute
170
- ai_instance: Optional AI instance for error correction
171
-
172
- Returns:
173
- Optional[str]: Error message if execution failed, None if successful
174
- """
175
- try:
176
- return self._execute_with_retry(code, ai_instance)
177
- except Exception as e:
178
- return str(e)
179
-
180
- def _format_output_panel(self, code: str, output_lines: list) -> Panel:
181
- """Format code and output into a single panel.
182
-
183
- Args:
184
- code (str): The code that was executed
185
- output_lines (list): List of output lines
186
-
187
- Returns:
188
- Panel: Formatted panel with code and output
189
- """
190
- code_syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
191
-
192
- # Format output
193
- output_text = "\n".join(output_lines) if output_lines else "Running..."
194
-
195
- # Combine code and output with a separator
196
- content = Group(
197
- code_syntax,
198
- Rule(style="bright_blue"),
199
- output_text
200
- )
201
-
202
- # Create panel
203
- panel = Panel(
204
- content,
205
- title="[bold blue]Code Execution[/bold blue]",
206
- border_style="blue",
207
- expand=True,
208
- padding=(0, 1)
209
- )
210
-
211
- return panel
212
-
213
- def _stream_output(self, process: subprocess.Popen) -> Generator[str, None, None]:
214
- """Stream output from a subprocess in realtime.
215
-
216
- Args:
217
- process: Subprocess to stream output from
218
-
219
- Yields:
220
- str: Lines of output
221
- """
222
- # Stream stdout
223
- for line in process.stdout:
224
- line = line.strip()
225
- if line:
226
- yield line
227
-
228
- # Check stderr
229
- error = process.stderr.read() if process.stderr else None
230
- if error and error.strip():
231
- yield f"Error: {error.strip()}"
232
-
233
- def _execute_with_retry(self, code: str, ai_instance=None) -> Optional[str]:
234
- """Execute code with retry logic and error correction.
235
-
236
- Args:
237
- code (str): Code to execute
238
- ai_instance: Optional AI instance for error correction
239
-
240
- Returns:
241
- Optional[str]: Error message if execution failed, None if successful
242
- """
243
- last_error = None
244
- retries = 0
245
- while retries < self.max_retries:
246
- try:
247
- if self.path_to_script:
248
- script_dir = os.path.dirname(self.path_to_script)
249
- if script_dir:
250
- os.makedirs(script_dir, exist_ok=True)
251
- with open(self.path_to_script, "w", encoding="utf-8") as f:
252
- f.write(code)
253
-
254
- if self.internal_exec:
255
- self.logger.info("Executing code internally")
256
- # Create StringIO for output capture
257
- import io
258
- import sys
259
- stdout = io.StringIO()
260
- stderr = io.StringIO()
261
-
262
- # Create a queue for realtime output
263
- output_queue = queue.Queue()
264
- output_lines = []
265
-
266
- def execute_code():
267
- try:
268
- # Redirect stdout/stderr
269
- sys.stdout = stdout
270
- sys.stderr = stderr
271
-
272
- # Execute the code
273
- exec(code, globals())
274
-
275
- # Get any output
276
- output = stdout.getvalue()
277
- error = stderr.getvalue()
278
-
279
- if error:
280
- output_queue.put(("error", error))
281
- elif output:
282
- output_queue.put(("output", output))
283
-
284
- finally:
285
- # Restore stdout/stderr
286
- sys.stdout = sys.__stdout__
287
- sys.stderr = sys.__stderr__
288
-
289
- # Create and start execution thread
290
- thread = threading.Thread(target=execute_code)
291
- thread.start()
292
-
293
- # Display output in realtime
294
- with Live(auto_refresh=False, transient=True) as live:
295
- while thread.is_alive() or not output_queue.empty():
296
- try:
297
- msg_type, content = output_queue.get_nowait()
298
- if content:
299
- output_lines.extend(content.splitlines())
300
- live.update(self._format_output_panel(code, output_lines))
301
- live.refresh()
302
- except queue.Empty:
303
- continue
304
-
305
- thread.join()
306
-
307
- # Check for any final errors
308
- error = stderr.getvalue()
309
- if error:
310
- raise Exception(error)
311
-
312
- else:
313
- self.logger.info("Executing code as external process")
314
- process = subprocess.Popen(
315
- [self.interpreter, self.path_to_script],
316
- stdout=subprocess.PIPE,
317
- stderr=subprocess.PIPE,
318
- text=True,
319
- bufsize=1,
320
- universal_newlines=True
321
- )
322
-
323
- output_lines = []
324
- # Stream output in realtime
325
- with Live(auto_refresh=False, transient=True) as live:
326
- for line in self._stream_output(process):
327
- output_lines.append(line)
328
- live.update(self._format_output_panel(code, output_lines))
329
- live.refresh()
330
-
331
- process.wait()
332
- error = process.stderr.read() if not isinstance(process.stderr, str) else process.stderr
333
- if process.returncode != 0 and error:
334
- raise Exception(error)
335
-
336
- return None
337
-
338
- except Exception as e:
339
- last_error = e
340
- if retries < self.max_retries - 1 and ai_instance:
341
- error_context = self._get_error_context(e, code)
342
- try:
343
- self.logger.info(f"Attempting correction (retry {retries + 1}/{self.max_retries})")
344
- fixed_response = ai_instance.chat(error_context)
345
- fixed_code = self._extract_code_from_response(fixed_response)
346
-
347
- if not fixed_code:
348
- self.logger.error("AI provided empty response")
349
- break
350
-
351
- if self._is_similar_solution(fixed_code):
352
- self.logger.warning("AI provided similar solution, requesting different approach")
353
- error_context += "\nPrevious solutions were not successful. Please provide a significantly different approach."
354
- fixed_response = ai_instance.chat(error_context)
355
- fixed_code = self._extract_code_from_response(fixed_response)
356
-
357
- if self._is_similar_solution(fixed_code):
358
- self.logger.error("AI unable to provide sufficiently different solution")
359
- break
360
-
361
- code = fixed_code
362
- retries += 1
363
- continue
364
- except Exception as ai_error:
365
- self.logger.error(f"Error getting AI correction: {str(ai_error)}")
366
- break
367
- break
368
-
369
- return str(last_error) if last_error else "Unknown error occurred"
370
-
371
- def execute(self, prompt: str, ai_instance=None) -> bool:
372
- """Execute the given prompt using the appropriate executor.
373
-
374
- Args:
375
- prompt (str): Prompt to execute
376
- ai_instance: Optional AI instance for error correction
377
-
378
- Returns:
379
- bool: True if execution was successful, False otherwise
380
- """
381
- try:
382
- # Extract code blocks
383
- code_blocks = self._extract_code_blocks(prompt)
384
- if not code_blocks:
385
- self.logger.warning("No code blocks found in prompt")
386
- return False
387
-
388
- # Execute each code block
389
- for code_type, code in code_blocks:
390
- self.logger.info(f"Executing {code_type} block")
391
- error = self._execute_code_block(code_type, code, ai_instance)
392
-
393
- if error:
394
- self.logger.error(f"Execution failed: {error}")
395
- return False
396
-
397
- return True
398
-
399
- except Exception as e:
400
- self.logger.error(f"Execution error: {str(e)}")
401
- return False
402
-
403
- def _extract_code_from_response(self, response: str) -> str:
404
- """Extract code from AI response.
405
-
406
- Args:
407
- response (str): AI response containing code blocks
408
-
409
- Returns:
410
- str: Extracted code from the first code block
411
- """
412
- code_blocks = self._extract_code_blocks(response)
413
- if not code_blocks:
414
- return ""
415
-
416
- # Return the content of the first code block, regardless of type
417
- return code_blocks[0][1]
418
-
419
- def _get_error_context(self, error: Exception, code: str) -> str:
420
- """Create context about the error for AI correction.
421
-
422
- Args:
423
- error (Exception): The caught exception
424
- code (str): The code that caused the error
425
-
426
- Returns:
427
- str: Formatted error context for AI
428
- """
429
- error_type = type(error).__name__
430
- error_msg = str(error)
431
-
432
- return f"""
433
- The code failed with error:
434
- Error Type: {error_type}
435
- Error Message: {error_msg}
436
-
437
- Original Code:
438
- ```python
439
- {code}
440
- ```
441
-
442
- Please fix the code to handle this error. Provide only the corrected code without any explanation.
443
- """
444
-
445
- def _handle_import_error(self, error: ImportError, code: str) -> Optional[str]:
446
- """Handle missing package errors by attempting to install them.
447
-
448
- Args:
449
- error (ImportError): The import error
450
- code (str): The code that caused the error
451
-
452
- Returns:
453
- Optional[str]: Fixed code or None if installation failed
454
- """
455
- missing_package = str(error).split("'")[1] if "'" in str(error) else str(error).split()[3]
456
- try:
457
- logger.info(f"Installing missing package: {missing_package}")
458
- result = subprocess.run(
459
- [sys.executable, "-m", "pip", "install", missing_package],
460
- capture_output=True,
461
- text=True
462
- )
463
- if result.returncode == 0:
464
- logger.success(f"Successfully installed {missing_package}")
465
- return code # Retry with same code after installing package
466
- else:
467
- raise Exception(f"Failed to install {missing_package}: {result.stderr}")
468
- except Exception as e:
469
- logger.error(f"Error installing package: {str(e)}")
470
- return None
471
-
472
- def _is_similar_solution(self, new_code: str, threshold: float = 0.8) -> bool:
473
- """Check if the new solution is too similar to previously tried ones.
474
-
475
- Args:
476
- new_code (str): New solution to check
477
- threshold (float): Similarity threshold (0-1). Defaults to 0.8.
478
-
479
- Returns:
480
- bool: True if solution is too similar to previous attempts
481
- """
482
- import difflib
483
-
484
- def normalize_code(code: str) -> str:
485
- lines = [line.split('#')[0].strip() for line in code.split('\n')]
486
- return '\n'.join(line for line in lines if line)
487
-
488
- new_code_norm = normalize_code(new_code)
489
-
490
- for tried_code in self.tried_solutions:
491
- tried_code_norm = normalize_code(tried_code)
492
- similarity = difflib.SequenceMatcher(None, new_code_norm, tried_code_norm).ratio()
493
- if similarity > threshold:
494
- return True
495
- return False
496
-
497
- def main(self, response: str) -> Optional[str]:
498
- """Execute code with error correction.
499
-
500
- Args:
501
- response (str): AI response containing code
502
-
503
- Returns:
504
- Optional[str]: Error message if execution failed, None if successful
505
- """
506
- if not response:
507
- return None
508
-
509
- code = self._extract_code_from_response(response)
510
-
511
- # Print the generated code with syntax highlighting
512
- self.print_code(code)
513
-
514
- ai_instance = self.ai_instance or globals().get('ai')
515
-
516
- if not ai_instance:
517
- logger.warning("AI instance not found, error correction disabled")
518
- try:
519
- if self.path_to_script:
520
- script_dir = os.path.dirname(self.path_to_script)
521
- if script_dir:
522
- os.makedirs(script_dir, exist_ok=True)
523
- with open(self.path_to_script, "w", encoding="utf-8") as f:
524
- f.write(code)
525
-
526
- if self.internal_exec:
527
- logger.info("Executing code internally")
528
- exec(code, globals())
529
- else:
530
- logger.info("Executing code as external process")
531
- result = subprocess.run(
532
- [self.interpreter, self.path_to_script],
533
- capture_output=True,
534
- text=True
535
- )
536
- if result.returncode != 0:
537
- raise Exception(result.stderr or result.stdout)
538
- return None
539
- except Exception as e:
540
- return str(e)
541
-
542
- return self._execute_with_retry(code, ai_instance)
543
-
544
- @property
545
- def intro_prompt(self) -> str:
546
- """Get the introduction prompt.
547
-
548
- Returns:
549
- str: Introduction prompt
550
- """
551
- return get_intro_prompt()
552
-
553
- def log(self, message: str, category: str = "info"):
554
- """RawDog logger
555
-
556
- Args:
557
- message (str): Log message
558
- category (str, optional): Log level. Defaults to 'info'.
559
- """
560
- if self.quiet:
561
- return
562
-
563
- message = "[Webscout] - " + message
564
- if category == "error":
565
- logger.error(message)
566
- else:
567
- logger.info(message)
568
-
569
- def stdout(self, message: str, style: str = "info") -> None:
570
- """Enhanced stdout with Rich formatting.
571
-
572
- Args:
573
- message (str): Text to be printed
574
- style (str, optional): Style to apply. Defaults to "info".
575
- """
576
- if not self.prettify:
577
- print(message)
578
- return
579
-
580
- if message.startswith("```") and message.endswith("```"):
581
- # Handle code blocks
582
- code = message.strip("`").strip()
583
- syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
584
- console.print(Panel(syntax, title="Code", border_style="blue"))
585
- elif "```python" in message:
586
- # Handle markdown code blocks
587
- md = Markdown(message)
588
- console.print(md)
589
- else:
590
- # Handle regular text with optional styling
591
- console.print(message, style=style)
592
-
593
- def print_code(self, code: str, title: str = "Generated Code") -> None:
594
- """Print code with syntax highlighting and panel.
595
-
596
- Args:
597
- code (str): Code to print
598
- title (str, optional): Panel title. Defaults to "Generated Code".
599
- """
600
- if self.prettify:
601
- syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
602
- console.print(Panel(
603
- syntax,
604
- title=f"[bold blue]In [1]: {title}[/bold blue]",
605
- border_style="blue",
606
- expand=True
607
- ))
608
- else:
609
- print(f"\n{title}:")
610
- print(code)
611
-
612
- def print_output(self, output: str, style: str = "output") -> None:
613
- """Print command output with optional styling.
614
-
615
- Args:
616
- output (str): Output to print
617
- style (str, optional): Style to apply. Defaults to "output".
618
- """
619
- if self.prettify:
620
- # Try to detect if output is Python code
621
- try:
622
- # If it looks like Python code, syntax highlight it
623
- compile(output, '<string>', 'exec')
624
- syntax = Syntax(output, "python", theme="monokai", line_numbers=False)
625
- formatted_output = syntax
626
- except SyntaxError:
627
- # If not Python code, treat as plain text
628
- formatted_output = output
629
-
630
- console.print(Panel(
631
- formatted_output,
632
- title="[bold red]Out [1]:[/bold red]",
633
- border_style="red",
634
- expand=True,
635
- padding=(0, 1)
636
- ))
637
- else:
638
- print("\nOutput:")
639
- print(output)
640
-
641
- def print_error(self, error: str) -> None:
642
- """Print error message with styling.
643
-
644
- Args:
645
- error (str): Error message to print
646
- """
647
- if self.prettify:
648
- console.print(f"\n Error:", style="error bold")
649
- console.print(error, style="error")
650
- else:
651
- print("\nError:")
652
- print(error)
653
-
654
- def print_table(self, headers: list, rows: list) -> None:
655
- """Print data in a formatted table.
656
-
657
- Args:
658
- headers (list): Table headers
659
- rows (list): Table rows
660
- """
661
- if not self.prettify:
662
- # Simple ASCII table
663
- print("\n" + "-" * 80)
664
- print("| " + " | ".join(headers) + " |")
665
- print("-" * 80)
666
- for row in rows:
667
- print("| " + " | ".join(str(cell) for cell in row) + " |")
668
- print("-" * 80)
669
- return
670
-
671
- table = Table(show_header=True, header_style="bold cyan")
672
- for header in headers:
673
- table.add_column(header)
674
-
675
- for row in rows:
676
- table.add_row(*[str(cell) for cell in row])
677
-
1
+ """RawDog module for generating and auto-executing Python scripts in the CLI."""
2
+
3
+ import os
4
+ import re
5
+ import sys
6
+ import queue
7
+ import tempfile
8
+ import threading
9
+ import subprocess
10
+ from rich.panel import Panel
11
+ from rich.syntax import Syntax
12
+ from rich.console import Console, Group
13
+ from rich.markdown import Markdown
14
+ from rich.table import Table
15
+ from rich.theme import Theme
16
+ from rich.live import Live
17
+ from rich.rule import Rule
18
+ from rich.box import ROUNDED
19
+ from typing import Optional, Generator, List, Tuple, Dict, Any
20
+ from webscout.AIutel import run_system_command
21
+ from .autocoder_utiles import get_intro_prompt
22
+
23
+ # Initialize LitLogger with custom format and colors
24
+ default_path = tempfile.mkdtemp(prefix="webscout_autocoder")
25
+
26
+ # Custom theme for consistent styling
27
+ CUSTOM_THEME = Theme({
28
+ "info": "cyan",
29
+ "warning": "yellow",
30
+ "error": "red bold",
31
+ "success": "green",
32
+ "code": "blue",
33
+ "output": "white",
34
+ })
35
+
36
+ console = Console(theme=CUSTOM_THEME)
37
+
38
+ class AutoCoder:
39
+ """Generate and auto-execute Python scripts in the CLI with advanced error handling and retry logic.
40
+
41
+ This class provides:
42
+ - Automatic code generation
43
+ - Script execution with safety checks
44
+ - Advanced error handling and retries
45
+ - Beautiful logging with rich console
46
+ - Execution result capture and display
47
+
48
+ Examples:
49
+ >>> coder = AutoCoder()
50
+ >>> coder.execute("Get system info")
51
+ Generating system info script...
52
+ Script executed successfully!
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ quiet: bool = False,
58
+ internal_exec: bool = False,
59
+ confirm_script: bool = False,
60
+ interpreter: str = "python",
61
+ prettify: bool = True,
62
+ path_to_script: str = "",
63
+ max_retries: int = 3,
64
+ ai_instance = None
65
+ ):
66
+ """Initialize AutoCoder instance.
67
+
68
+ Args:
69
+ quiet (bool): Flag to control logging. Defaults to False.
70
+ internal_exec (bool): Execute scripts with exec function. Defaults to False.
71
+ confirm_script (bool): Give consent to scripts prior to execution. Defaults to False.
72
+ interpreter (str): Python's interpreter name. Defaults to "python".
73
+ prettify (bool): Prettify the code on stdout. Defaults to True.
74
+ path_to_script (str): Path to save generated scripts. Defaults to "".
75
+ max_retries (int): Maximum number of retry attempts. Defaults to 3.
76
+ ai_instance: AI instance for error correction. Defaults to None.
77
+ """
78
+ self.internal_exec = internal_exec
79
+ self.confirm_script = confirm_script
80
+ self.quiet = quiet
81
+ self.interpreter = interpreter
82
+ self.prettify = prettify
83
+ self.path_to_script = path_to_script or os.path.join(default_path, "execute_this.py")
84
+ self.max_retries = max_retries
85
+ self.tried_solutions = set()
86
+ self.ai_instance = ai_instance
87
+ self.last_execution_result = ""
88
+
89
+ # Get Python version with enhanced logging
90
+ if self.internal_exec:
91
+ self.python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
92
+ else:
93
+ version_output = run_system_command(
94
+ f"{self.interpreter} --version",
95
+ exit_on_error=True,
96
+ stdout_error=True,
97
+ help="If you're using Webscout-cli, use the flag '--internal-exec'"
98
+ )[1].stdout
99
+ self.python_version = version_output.split(" ")[1]
100
+
101
+
102
+
103
+ def _extract_code_blocks(self, response: str) -> List[Tuple[str, str]]:
104
+ """Extract code blocks from a response string.
105
+
106
+ Args:
107
+ response (str): Response string containing code blocks
108
+
109
+ Returns:
110
+ List[Tuple[str, str]]: List of (code_type, code) tuples
111
+ """
112
+ blocks = []
113
+
114
+ # First try to find code blocks with explicit language tags
115
+ pattern = r"```(\w+)\n(.*?)```"
116
+ matches = re.finditer(pattern, response, re.DOTALL)
117
+
118
+ for match in matches:
119
+ code_type = match.group(1).lower()
120
+ code = match.group(2).strip()
121
+ blocks.append((code_type, code))
122
+
123
+ # If no explicit code blocks found with language tags, try generic code blocks
124
+ if not blocks:
125
+ pattern = r"```(.*?)```"
126
+ matches = re.finditer(pattern, response, re.DOTALL)
127
+ for match in matches:
128
+ code = match.group(1).strip()
129
+ blocks.append(('python', code))
130
+
131
+ # If still no code blocks found, treat as raw Python code
132
+ if not blocks:
133
+ lines = [line.strip() for line in response.split('\n') if line.strip()]
134
+ if lines:
135
+ blocks.append(('python', '\n'.join(lines)))
136
+
137
+ return blocks
138
+
139
+ def _execute_code_block(self, code_type: str, code: str, ai_instance=None) -> Tuple[bool, str]:
140
+ """Execute a code block.
141
+
142
+ Args:
143
+ code_type (str): Type of code block ('python')
144
+ code (str): Code to execute
145
+ ai_instance: Optional AI instance for error correction
146
+
147
+ Returns:
148
+ Tuple[bool, str]: (Success status, Error message or execution result)
149
+ """
150
+ try:
151
+ result = self._execute_with_retry(code, ai_instance)
152
+ if result is None:
153
+ return True, self.last_execution_result
154
+ return False, result
155
+ except Exception as e:
156
+ return False, str(e)
157
+
158
+ def _format_output_panel(self, code: str, output_lines: list) -> Panel:
159
+ """Format code and output into a single panel.
160
+
161
+ Args:
162
+ code (str): The code that was executed
163
+ output_lines (list): List of output lines
164
+
165
+ Returns:
166
+ Panel: Formatted panel with code and output
167
+ """
168
+ # Format output
169
+ output_text = "\n".join(output_lines) if output_lines else "Running..."
170
+
171
+ # Create panel with Jupyter-like styling
172
+ panel = Panel(
173
+ output_text,
174
+ title="[bold red]Out [1]:[/bold red]",
175
+ border_style="red",
176
+ expand=True,
177
+ padding=(0, 1),
178
+ box=ROUNDED
179
+ )
180
+
181
+ return panel
182
+
183
+ def _format_result_panel(self, output: str) -> Panel:
184
+ """Format execution result into a panel.
185
+
186
+ Args:
187
+ output (str): Execution output text
188
+
189
+ Returns:
190
+ Panel: Formatted panel with execution result
191
+ """
192
+ # Create panel with Jupyter-like styling
193
+ panel = Panel(
194
+ output,
195
+ title="[bold red]Out [1]:[/bold red]",
196
+ border_style="red",
197
+ expand=True,
198
+ padding=(0, 1),
199
+ box=ROUNDED
200
+ )
201
+
202
+ return panel
203
+
204
+ def _stream_output(self, process: subprocess.Popen) -> Generator[str, None, None]:
205
+ """Stream output from a subprocess in realtime.
206
+
207
+ Args:
208
+ process: Subprocess to stream output from
209
+
210
+ Yields:
211
+ str: Lines of output
212
+ """
213
+ # Stream stdout
214
+ output_lines = []
215
+ for line in process.stdout:
216
+ decoded_line = line.decode('utf-8').strip() if isinstance(line, bytes) else line.strip()
217
+ if decoded_line:
218
+ output_lines.append(decoded_line)
219
+ yield decoded_line
220
+
221
+ # Check stderr
222
+ error = process.stderr.read() if process.stderr else None
223
+ if error:
224
+ error_str = error.decode('utf-8').strip() if isinstance(error, bytes) else error.strip()
225
+ if error_str:
226
+ yield f"Error: {error_str}"
227
+ output_lines.append(f"Error: {error_str}")
228
+
229
+ # Store the full execution result
230
+ self.last_execution_result = "\n".join(output_lines)
231
+
232
+ def _execute_with_retry(self, code: str, ai_instance=None) -> Optional[str]:
233
+ """Execute code with retry logic and error correction.
234
+
235
+ Args:
236
+ code (str): Code to execute
237
+ ai_instance: Optional AI instance for error correction
238
+
239
+ Returns:
240
+ Optional[str]: Error message if execution failed, None if successful
241
+ """
242
+ last_error = None
243
+ retries = 0
244
+
245
+ # Add the solution to tried solutions
246
+ self.tried_solutions.add(code)
247
+
248
+ # Print the code first
249
+ if self.prettify:
250
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
251
+ console.print(Panel(
252
+ syntax,
253
+ title="[bold blue]In [1]:[/bold blue]",
254
+ border_style="blue",
255
+ expand=True,
256
+ box=ROUNDED
257
+ ))
258
+
259
+ while retries < self.max_retries:
260
+ try:
261
+ if self.path_to_script:
262
+ script_dir = os.path.dirname(self.path_to_script)
263
+ if script_dir:
264
+ os.makedirs(script_dir, exist_ok=True)
265
+ with open(self.path_to_script, "w", encoding="utf-8") as f:
266
+ f.write(code)
267
+
268
+ if self.internal_exec:
269
+ # Create StringIO for output capture
270
+ import io
271
+ import sys
272
+ stdout = io.StringIO()
273
+ stderr = io.StringIO()
274
+
275
+ # Create a queue for realtime output
276
+ output_queue = queue.Queue()
277
+ output_lines = []
278
+
279
+ def execute_code():
280
+ try:
281
+ # Create a local namespace
282
+ local_namespace: Dict[str, Any] = {}
283
+
284
+ # Redirect stdout/stderr
285
+ sys.stdout = stdout
286
+ sys.stderr = stderr
287
+
288
+ # Execute the code
289
+ exec(code, globals(), local_namespace)
290
+
291
+ # Get any output
292
+ output = stdout.getvalue()
293
+ error = stderr.getvalue()
294
+
295
+ if error:
296
+ output_queue.put(("error", error))
297
+ if output:
298
+ output_queue.put(("output", output))
299
+
300
+ except Exception as e:
301
+ output_queue.put(("error", str(e)))
302
+ finally:
303
+ # Restore stdout/stderr
304
+ sys.stdout = sys.__stdout__
305
+ sys.stderr = sys.__stderr__
306
+
307
+ # Create and start execution thread
308
+ thread = threading.Thread(target=execute_code)
309
+ thread.daemon = True # Make thread daemon to avoid hanging
310
+ thread.start()
311
+
312
+ # Display output in realtime
313
+ with Live(auto_refresh=True) as live:
314
+ timeout_counter = 0
315
+ while thread.is_alive() or not output_queue.empty():
316
+ try:
317
+ msg_type, content = output_queue.get(timeout=0.1)
318
+ if content:
319
+ new_lines = content.splitlines()
320
+ output_lines.extend(new_lines)
321
+ live.update(self._format_output_panel(code, output_lines))
322
+ live.refresh()
323
+ output_queue.task_done()
324
+ except queue.Empty:
325
+ timeout_counter += 1
326
+ # Refresh the display to show it's still running
327
+ if timeout_counter % 10 == 0: # Refresh every ~1 second
328
+ live.update(self._format_output_panel(code, output_lines))
329
+ live.refresh()
330
+ if timeout_counter > 100 and thread.is_alive(): # ~10 seconds
331
+ output_lines.append("Warning: Execution taking longer than expected...")
332
+ live.update(self._format_output_panel(code, output_lines))
333
+ live.refresh()
334
+ continue
335
+
336
+ # Wait for thread to complete with timeout
337
+ thread.join(timeout=30) # 30 second timeout
338
+ if thread.is_alive():
339
+ output_lines.append("Error: Execution timed out after 30 seconds")
340
+ raise TimeoutError("Execution timed out after 30 seconds")
341
+
342
+ # Check for any final errors
343
+ error = stderr.getvalue()
344
+ if error:
345
+ raise Exception(error)
346
+
347
+ # Store the full execution result
348
+ self.last_execution_result = stdout.getvalue()
349
+
350
+ else:
351
+ try:
352
+ process = subprocess.Popen(
353
+ [self.interpreter, self.path_to_script],
354
+ stdout=subprocess.PIPE,
355
+ stderr=subprocess.PIPE,
356
+ text=True, # Use text mode to avoid encoding issues
357
+ bufsize=1,
358
+ )
359
+
360
+ output_lines = []
361
+ # Stream output in realtime
362
+ with Live(auto_refresh=True) as live:
363
+ for line in self._stream_output(process):
364
+ output_lines.append(line)
365
+ live.update(self._format_output_panel(code, output_lines))
366
+ live.refresh()
367
+
368
+ process.wait(timeout=30) # 30 second timeout
369
+
370
+ if process.returncode != 0:
371
+ # Try to read more detailed error information
372
+ if process.stderr:
373
+ error = process.stderr.read()
374
+ error_str = error.strip() if error else ""
375
+ if error_str:
376
+ raise Exception(error_str)
377
+ raise Exception(f"Process exited with code {process.returncode}")
378
+
379
+ # Store the full execution result
380
+ self.last_execution_result = "\n".join(output_lines)
381
+
382
+ except subprocess.TimeoutExpired:
383
+ # Handle the case where the process times out
384
+ if process:
385
+ process.kill()
386
+ raise TimeoutError("Execution timed out after 30 seconds")
387
+
388
+ return None
389
+
390
+ except Exception as e:
391
+ last_error = e
392
+ if retries < self.max_retries - 1 and ai_instance:
393
+ error_context = self._get_error_context(e, code)
394
+ try:
395
+ # First try to fix the specific error
396
+ fixed_response = ai_instance.chat(error_context)
397
+ fixed_code = self._extract_code_from_response(fixed_response)
398
+
399
+ if not fixed_code:
400
+ # If no code found, try a more general approach
401
+ general_context = f"""
402
+ The code failed with error: {str(e)}
403
+
404
+ Original Code:
405
+ ```python
406
+ {code}
407
+ ```
408
+
409
+ Please provide a complete, corrected version of the code that handles this error. The code should:
410
+ 1. Handle any potential encoding issues
411
+ 2. Include proper error handling
412
+ 3. Use appropriate libraries and imports
413
+ 4. Be compatible with the current Python environment
414
+
415
+ Provide only the corrected code without any explanation.
416
+ """
417
+ fixed_response = ai_instance.chat(general_context)
418
+ fixed_code = self._extract_code_from_response(fixed_response)
419
+
420
+ if not fixed_code:
421
+ break
422
+
423
+ if self._is_similar_solution(fixed_code):
424
+ # If solution is too similar, try a different approach
425
+ different_context = f"""
426
+ Previous solutions were not successful. The code failed with error: {str(e)}
427
+
428
+ Original Code:
429
+ ```python
430
+ {code}
431
+ ```
432
+
433
+ Please provide a significantly different approach to solve this problem. Consider:
434
+ 1. Using alternative libraries or methods
435
+ 2. Implementing a different algorithm
436
+ 3. Adding more robust error handling
437
+ 4. Using a different encoding or data handling approach
438
+
439
+ Provide only the corrected code without any explanation.
440
+ """
441
+ fixed_response = ai_instance.chat(different_context)
442
+ fixed_code = self._extract_code_from_response(fixed_response)
443
+
444
+ if self._is_similar_solution(fixed_code):
445
+ break
446
+
447
+ code = fixed_code
448
+ self.tried_solutions.add(code)
449
+ retries += 1
450
+ continue
451
+ except Exception as ai_error:
452
+ console.print(f"Error during AI correction: {str(ai_error)}", style="error")
453
+ break
454
+ break
455
+
456
+ return str(last_error) if last_error else "Unknown error occurred"
457
+
458
+ def execute(self, prompt: str, ai_instance=None) -> bool:
459
+ """Execute the given prompt using the appropriate executor.
460
+
461
+ Args:
462
+ prompt (str): Prompt to execute
463
+ ai_instance: Optional AI instance for error correction
464
+
465
+ Returns:
466
+ bool: True if execution was successful, False otherwise
467
+ """
468
+ try:
469
+ # Extract code blocks
470
+ code_blocks = self._extract_code_blocks(prompt)
471
+ if not code_blocks:
472
+ console.print("No executable code found in the prompt", style="warning")
473
+ return False
474
+
475
+ # Execute each code block
476
+ overall_success = True
477
+ for code_type, code in code_blocks:
478
+ success, result = self._execute_code_block(code_type, code, ai_instance)
479
+
480
+ if not success:
481
+ console.print(f"Execution failed: {result}", style="error")
482
+ overall_success = False
483
+
484
+ return overall_success
485
+
486
+ except Exception as e:
487
+ console.print(f"Error in execution: {str(e)}", style="error")
488
+ return False
489
+
490
+ def _extract_code_from_response(self, response: str) -> str:
491
+ """Extract code from AI response.
492
+
493
+ Args:
494
+ response (str): AI response containing code blocks
495
+
496
+ Returns:
497
+ str: Extracted code from the first code block
498
+ """
499
+ code_blocks = self._extract_code_blocks(response)
500
+ if not code_blocks:
501
+ return ""
502
+
503
+ # Return the content of the first code block, regardless of type
504
+ return code_blocks[0][1]
505
+
506
+ def _get_error_context(self, error: Exception, code: str) -> str:
507
+ """Create context about the error for AI correction.
508
+
509
+ Args:
510
+ error (Exception): The caught exception
511
+ code (str): The code that caused the error
512
+
513
+ Returns:
514
+ str: Formatted error context for AI
515
+ """
516
+ error_type = type(error).__name__
517
+ error_msg = str(error)
518
+
519
+ return f"""
520
+ The code failed with error:
521
+ Error Type: {error_type}
522
+ Error Message: {error_msg}
523
+
524
+ Original Code:
525
+ ```python
526
+ {code}
527
+ ```
528
+
529
+ Please fix the code to handle this error. Provide only the corrected code without any explanation.
530
+ """
531
+
532
+ def _handle_import_error(self, error: ImportError, code: str) -> Optional[str]:
533
+ """Handle missing package errors by attempting to install them.
534
+
535
+ Args:
536
+ error (ImportError): The import error
537
+ code (str): The code that caused the error
538
+
539
+ Returns:
540
+ Optional[str]: Fixed code or None if installation failed
541
+ """
542
+ try:
543
+ missing_package = str(error).split("'")[1] if "'" in str(error) else str(error).split("No module named")[1].strip()
544
+ missing_package = missing_package.replace("'", "").strip()
545
+
546
+ console.print(f"Installing missing package: {missing_package}", style="info")
547
+ result = subprocess.run(
548
+ [sys.executable, "-m", "pip", "install", missing_package],
549
+ capture_output=True,
550
+ text=True
551
+ )
552
+ if result.returncode == 0:
553
+ console.print(f"Successfully installed {missing_package}", style="success")
554
+ return code # Retry with same code after installing package
555
+ else:
556
+ raise Exception(f"Failed to install {missing_package}: {result.stderr}")
557
+ except Exception as e:
558
+ console.print(f"Error installing package: {str(e)}", style="error")
559
+ return None
560
+
561
+ def _is_similar_solution(self, new_code: str, threshold: float = 0.8) -> bool:
562
+ """Check if the new solution is too similar to previously tried ones.
563
+
564
+ Args:
565
+ new_code (str): New solution to check
566
+ threshold (float): Similarity threshold (0-1). Defaults to 0.8.
567
+
568
+ Returns:
569
+ bool: True if solution is too similar to previous attempts
570
+ """
571
+ import difflib
572
+
573
+ def normalize_code(code: str) -> str:
574
+ lines = [line.split('#')[0].strip() for line in code.split('\n')]
575
+ return '\n'.join(line for line in lines if line)
576
+
577
+ new_code_norm = normalize_code(new_code)
578
+
579
+ for tried_code in self.tried_solutions:
580
+ tried_code_norm = normalize_code(tried_code)
581
+ similarity = difflib.SequenceMatcher(None, new_code_norm, tried_code_norm).ratio()
582
+ if similarity > threshold:
583
+ return True
584
+ return False
585
+
586
+ def main(self, response: str) -> Optional[str]:
587
+ """Execute code with error correction.
588
+
589
+ Args:
590
+ response (str): AI response containing code
591
+
592
+ Returns:
593
+ Optional[str]: Error message if execution failed, None if successful
594
+ """
595
+ if not response:
596
+ return "No response provided"
597
+
598
+ code = self._extract_code_from_response(response)
599
+ if not code:
600
+ return "No executable code found in the response"
601
+
602
+ ai_instance = self.ai_instance or globals().get('ai')
603
+
604
+ if not ai_instance:
605
+ console.print("AI instance not found, error correction disabled", style="warning")
606
+ try:
607
+ if self.path_to_script:
608
+ script_dir = os.path.dirname(self.path_to_script)
609
+ if script_dir:
610
+ os.makedirs(script_dir, exist_ok=True)
611
+ with open(self.path_to_script, "w", encoding="utf-8") as f:
612
+ f.write(code)
613
+
614
+ if self.internal_exec:
615
+ console.print("[INFO] Executing code internally", style="info")
616
+ # Create a local namespace
617
+ local_namespace: Dict[str, Any] = {}
618
+
619
+ # Capture stdout
620
+ import io
621
+ old_stdout = sys.stdout
622
+ captured_output = io.StringIO()
623
+ sys.stdout = captured_output
624
+
625
+ # Execute the code
626
+ try:
627
+ exec(code, globals(), local_namespace)
628
+ # Capture the result
629
+ self.last_execution_result = captured_output.getvalue()
630
+ finally:
631
+ # Restore stdout
632
+ sys.stdout = old_stdout
633
+ else:
634
+ console.print("[INFO] Executing code as external process", style="info")
635
+ result = subprocess.run(
636
+ [self.interpreter, self.path_to_script],
637
+ capture_output=True,
638
+ text=True
639
+ )
640
+ self.last_execution_result = result.stdout
641
+
642
+ if result.returncode != 0:
643
+ raise Exception(result.stderr or result.stdout)
644
+
645
+ return None
646
+ except Exception as e:
647
+ error_msg = f"Execution error: {str(e)}"
648
+ console.print(error_msg, style="error")
649
+ return error_msg
650
+
651
+ result = self._execute_with_retry(code, ai_instance)
652
+ return result
653
+
654
+ @property
655
+ def intro_prompt(self) -> str:
656
+ """Get the introduction prompt.
657
+
658
+ Returns:
659
+ str: Introduction prompt
660
+ """
661
+ return get_intro_prompt()
662
+
663
+ def log(self, message: str, category: str = "info"):
664
+ """RawDog logger
665
+
666
+ Args:
667
+ message (str): Log message
668
+ category (str, optional): Log level. Defaults to 'info'.
669
+ """
670
+ if self.quiet:
671
+ return
672
+
673
+ message = "[Webscout] - " + message
674
+ if category == "error":
675
+ console.print(f"[ERROR] {message}", style="error")
676
+ else:
677
+ console.print(message, style=category)
678
+
679
+ def stdout(self, message: str, style: str = "info") -> None:
680
+ """Enhanced stdout with Rich formatting.
681
+
682
+ Args:
683
+ message (str): Text to be printed
684
+ style (str, optional): Style to apply. Defaults to "info".
685
+ """
686
+ if not self.prettify:
687
+ print(message)
688
+ return
689
+
690
+ if message.startswith("```") and message.endswith("```"):
691
+ # Handle code blocks
692
+ code = message.strip("`").strip()
693
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
694
+ console.print(Panel(syntax, title="Code", border_style="blue"))
695
+ elif "```python" in message:
696
+ # Handle markdown code blocks
697
+ md = Markdown(message)
698
+ console.print(md)
699
+ else:
700
+ # Handle regular text with optional styling
701
+ console.print(message, style=style)
702
+
703
+ def print_code(self, code: str, title: str = "Generated Code") -> None:
704
+ """Print code with syntax highlighting and panel.
705
+
706
+ Args:
707
+ code (str): Code to print
708
+ title (str, optional): Panel title. Defaults to "Generated Code".
709
+ """
710
+ if self.prettify:
711
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
712
+ console.print(Panel(
713
+ syntax,
714
+ title=f"[bold blue]In [1]:[/bold blue]",
715
+ border_style="blue",
716
+ expand=True,
717
+ box=ROUNDED
718
+ ))
719
+ else:
720
+ print(f"\n{title}:")
721
+ print(code)
722
+
723
+ def print_output(self, output: str, style: str = "output") -> None:
724
+ """Print command output with optional styling.
725
+
726
+ Args:
727
+ output (str): Output to print
728
+ style (str, optional): Style to apply. Defaults to "output".
729
+ """
730
+ if self.prettify:
731
+ # Try to detect if output is Python code
732
+ try:
733
+ # If it looks like Python code, syntax highlight it
734
+ compile(output, '<string>', 'exec')
735
+ syntax = Syntax(output, "python", theme="monokai", line_numbers=False)
736
+ formatted_output = syntax
737
+ except SyntaxError:
738
+ # If not Python code, treat as plain text
739
+ formatted_output = output
740
+
741
+ console.print(Panel(
742
+ formatted_output,
743
+ title="[bold red]Out [1]:[/bold red]",
744
+ border_style="red",
745
+ expand=True,
746
+ padding=(0, 1),
747
+ box=ROUNDED
748
+ ))
749
+ else:
750
+ print("\nOutput:")
751
+ print(output)
752
+
753
+ def print_error(self, error: str) -> None:
754
+ """Print error message with styling.
755
+
756
+ Args:
757
+ error (str): Error message to print
758
+ """
759
+ if self.prettify:
760
+ console.print(f"\n Error:", style="error bold")
761
+ console.print(error, style="error")
762
+ else:
763
+ print("\nError:")
764
+ print(error)
765
+
766
+ def print_table(self, headers: list, rows: list) -> None:
767
+ """Print data in a formatted table.
768
+
769
+ Args:
770
+ headers (list): Table headers
771
+ rows (list): Table rows
772
+ """
773
+ if not self.prettify:
774
+ # Simple ASCII table
775
+ print("\n" + "-" * 80)
776
+ print("| " + " | ".join(headers) + " |")
777
+ print("-" * 80)
778
+ for row in rows:
779
+ print("| " + " | ".join(str(cell) for cell in row) + " |")
780
+ print("-" * 80)
781
+ return
782
+
783
+ table = Table(show_header=True, header_style="bold cyan")
784
+ for header in headers:
785
+ table.add_column(header)
786
+
787
+ for row in rows:
788
+ table.add_row(*[str(cell) for cell in row])
789
+
678
790
  console.print(table)