webscout 8.3.7__py3-none-any.whl → 2025.10.11__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 (273) hide show
  1. webscout/AIauto.py +250 -250
  2. webscout/AIbase.py +379 -379
  3. webscout/AIutel.py +60 -60
  4. webscout/Bard.py +1012 -1012
  5. webscout/Bing_search.py +417 -417
  6. webscout/DWEBS.py +529 -529
  7. webscout/Extra/Act.md +309 -309
  8. webscout/Extra/GitToolkit/__init__.py +10 -10
  9. webscout/Extra/GitToolkit/gitapi/README.md +110 -110
  10. webscout/Extra/GitToolkit/gitapi/__init__.py +11 -11
  11. webscout/Extra/GitToolkit/gitapi/repository.py +195 -195
  12. webscout/Extra/GitToolkit/gitapi/user.py +96 -96
  13. webscout/Extra/GitToolkit/gitapi/utils.py +61 -61
  14. webscout/Extra/YTToolkit/README.md +375 -375
  15. webscout/Extra/YTToolkit/YTdownloader.py +956 -956
  16. webscout/Extra/YTToolkit/__init__.py +2 -2
  17. webscout/Extra/YTToolkit/transcriber.py +475 -475
  18. webscout/Extra/YTToolkit/ytapi/README.md +44 -44
  19. webscout/Extra/YTToolkit/ytapi/__init__.py +6 -6
  20. webscout/Extra/YTToolkit/ytapi/channel.py +307 -307
  21. webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
  22. webscout/Extra/YTToolkit/ytapi/extras.py +118 -118
  23. webscout/Extra/YTToolkit/ytapi/https.py +88 -88
  24. webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
  25. webscout/Extra/YTToolkit/ytapi/playlist.py +58 -58
  26. webscout/Extra/YTToolkit/ytapi/pool.py +7 -7
  27. webscout/Extra/YTToolkit/ytapi/query.py +39 -39
  28. webscout/Extra/YTToolkit/ytapi/stream.py +62 -62
  29. webscout/Extra/YTToolkit/ytapi/utils.py +62 -62
  30. webscout/Extra/YTToolkit/ytapi/video.py +232 -232
  31. webscout/Extra/autocoder/__init__.py +9 -9
  32. webscout/Extra/autocoder/autocoder.py +1105 -1105
  33. webscout/Extra/autocoder/autocoder_utiles.py +332 -332
  34. webscout/Extra/gguf.md +429 -429
  35. webscout/Extra/gguf.py +1213 -1213
  36. webscout/Extra/tempmail/README.md +487 -487
  37. webscout/Extra/tempmail/__init__.py +27 -27
  38. webscout/Extra/tempmail/async_utils.py +140 -140
  39. webscout/Extra/tempmail/base.py +160 -160
  40. webscout/Extra/tempmail/cli.py +186 -186
  41. webscout/Extra/tempmail/emailnator.py +84 -84
  42. webscout/Extra/tempmail/mail_tm.py +360 -360
  43. webscout/Extra/tempmail/temp_mail_io.py +291 -291
  44. webscout/Extra/weather.md +281 -281
  45. webscout/Extra/weather.py +193 -193
  46. webscout/Litlogger/README.md +10 -10
  47. webscout/Litlogger/__init__.py +15 -15
  48. webscout/Litlogger/formats.py +13 -13
  49. webscout/Litlogger/handlers.py +121 -121
  50. webscout/Litlogger/levels.py +13 -13
  51. webscout/Litlogger/logger.py +134 -134
  52. webscout/Provider/AISEARCH/Perplexity.py +332 -332
  53. webscout/Provider/AISEARCH/README.md +279 -279
  54. webscout/Provider/AISEARCH/__init__.py +16 -1
  55. webscout/Provider/AISEARCH/felo_search.py +206 -206
  56. webscout/Provider/AISEARCH/genspark_search.py +323 -323
  57. webscout/Provider/AISEARCH/hika_search.py +185 -185
  58. webscout/Provider/AISEARCH/iask_search.py +410 -410
  59. webscout/Provider/AISEARCH/monica_search.py +219 -219
  60. webscout/Provider/AISEARCH/scira_search.py +316 -316
  61. webscout/Provider/AISEARCH/stellar_search.py +177 -177
  62. webscout/Provider/AISEARCH/webpilotai_search.py +255 -255
  63. webscout/Provider/Aitopia.py +314 -314
  64. webscout/Provider/Apriel.py +306 -0
  65. webscout/Provider/ChatGPTClone.py +236 -236
  66. webscout/Provider/ChatSandbox.py +343 -343
  67. webscout/Provider/Cloudflare.py +324 -324
  68. webscout/Provider/Cohere.py +208 -208
  69. webscout/Provider/Deepinfra.py +370 -366
  70. webscout/Provider/ExaAI.py +260 -260
  71. webscout/Provider/ExaChat.py +308 -308
  72. webscout/Provider/Flowith.py +221 -221
  73. webscout/Provider/GMI.py +293 -0
  74. webscout/Provider/Gemini.py +164 -164
  75. webscout/Provider/GeminiProxy.py +167 -167
  76. webscout/Provider/GithubChat.py +371 -372
  77. webscout/Provider/Groq.py +800 -800
  78. webscout/Provider/HeckAI.py +383 -383
  79. webscout/Provider/Jadve.py +282 -282
  80. webscout/Provider/K2Think.py +307 -307
  81. webscout/Provider/Koboldai.py +205 -205
  82. webscout/Provider/LambdaChat.py +423 -423
  83. webscout/Provider/Nemotron.py +244 -244
  84. webscout/Provider/Netwrck.py +248 -248
  85. webscout/Provider/OLLAMA.py +395 -395
  86. webscout/Provider/OPENAI/Cloudflare.py +393 -393
  87. webscout/Provider/OPENAI/FalconH1.py +451 -451
  88. webscout/Provider/OPENAI/FreeGemini.py +296 -296
  89. webscout/Provider/OPENAI/K2Think.py +431 -431
  90. webscout/Provider/OPENAI/NEMOTRON.py +240 -240
  91. webscout/Provider/OPENAI/PI.py +427 -427
  92. webscout/Provider/OPENAI/README.md +959 -959
  93. webscout/Provider/OPENAI/TogetherAI.py +345 -345
  94. webscout/Provider/OPENAI/TwoAI.py +465 -465
  95. webscout/Provider/OPENAI/__init__.py +33 -18
  96. webscout/Provider/OPENAI/base.py +248 -248
  97. webscout/Provider/OPENAI/chatglm.py +528 -0
  98. webscout/Provider/OPENAI/chatgpt.py +592 -592
  99. webscout/Provider/OPENAI/chatgptclone.py +521 -521
  100. webscout/Provider/OPENAI/chatsandbox.py +202 -202
  101. webscout/Provider/OPENAI/deepinfra.py +318 -314
  102. webscout/Provider/OPENAI/e2b.py +1665 -1665
  103. webscout/Provider/OPENAI/exaai.py +420 -420
  104. webscout/Provider/OPENAI/exachat.py +452 -452
  105. webscout/Provider/OPENAI/friendli.py +232 -232
  106. webscout/Provider/OPENAI/{refact.py → gmi.py} +324 -274
  107. webscout/Provider/OPENAI/groq.py +364 -364
  108. webscout/Provider/OPENAI/heckai.py +314 -314
  109. webscout/Provider/OPENAI/llmchatco.py +337 -337
  110. webscout/Provider/OPENAI/netwrck.py +355 -355
  111. webscout/Provider/OPENAI/oivscode.py +290 -290
  112. webscout/Provider/OPENAI/opkfc.py +518 -518
  113. webscout/Provider/OPENAI/pydantic_imports.py +1 -1
  114. webscout/Provider/OPENAI/scirachat.py +535 -535
  115. webscout/Provider/OPENAI/sonus.py +308 -308
  116. webscout/Provider/OPENAI/standardinput.py +442 -442
  117. webscout/Provider/OPENAI/textpollinations.py +340 -340
  118. webscout/Provider/OPENAI/toolbaz.py +419 -416
  119. webscout/Provider/OPENAI/typefully.py +362 -362
  120. webscout/Provider/OPENAI/utils.py +295 -295
  121. webscout/Provider/OPENAI/venice.py +436 -436
  122. webscout/Provider/OPENAI/wisecat.py +387 -387
  123. webscout/Provider/OPENAI/writecream.py +166 -166
  124. webscout/Provider/OPENAI/x0gpt.py +378 -378
  125. webscout/Provider/OPENAI/yep.py +389 -389
  126. webscout/Provider/OpenGPT.py +230 -230
  127. webscout/Provider/Openai.py +243 -243
  128. webscout/Provider/PI.py +405 -405
  129. webscout/Provider/Perplexitylabs.py +430 -430
  130. webscout/Provider/QwenLM.py +272 -272
  131. webscout/Provider/STT/__init__.py +16 -1
  132. webscout/Provider/Sambanova.py +257 -257
  133. webscout/Provider/StandardInput.py +309 -309
  134. webscout/Provider/TTI/README.md +82 -82
  135. webscout/Provider/TTI/__init__.py +33 -18
  136. webscout/Provider/TTI/aiarta.py +413 -413
  137. webscout/Provider/TTI/base.py +136 -136
  138. webscout/Provider/TTI/bing.py +243 -243
  139. webscout/Provider/TTI/gpt1image.py +149 -149
  140. webscout/Provider/TTI/imagen.py +196 -196
  141. webscout/Provider/TTI/infip.py +211 -211
  142. webscout/Provider/TTI/magicstudio.py +232 -232
  143. webscout/Provider/TTI/monochat.py +219 -219
  144. webscout/Provider/TTI/piclumen.py +214 -214
  145. webscout/Provider/TTI/pixelmuse.py +232 -232
  146. webscout/Provider/TTI/pollinations.py +232 -232
  147. webscout/Provider/TTI/together.py +288 -288
  148. webscout/Provider/TTI/utils.py +12 -12
  149. webscout/Provider/TTI/venice.py +367 -367
  150. webscout/Provider/TTS/README.md +192 -192
  151. webscout/Provider/TTS/__init__.py +33 -18
  152. webscout/Provider/TTS/parler.py +110 -110
  153. webscout/Provider/TTS/streamElements.py +333 -333
  154. webscout/Provider/TTS/utils.py +280 -280
  155. webscout/Provider/TeachAnything.py +237 -237
  156. webscout/Provider/TextPollinationsAI.py +310 -310
  157. webscout/Provider/TogetherAI.py +356 -356
  158. webscout/Provider/TwoAI.py +312 -312
  159. webscout/Provider/TypliAI.py +311 -311
  160. webscout/Provider/UNFINISHED/ChatHub.py +208 -208
  161. webscout/Provider/UNFINISHED/ChutesAI.py +313 -313
  162. webscout/Provider/UNFINISHED/GizAI.py +294 -294
  163. webscout/Provider/UNFINISHED/Marcus.py +198 -198
  164. webscout/Provider/UNFINISHED/Qodo.py +477 -477
  165. webscout/Provider/UNFINISHED/VercelAIGateway.py +338 -338
  166. webscout/Provider/UNFINISHED/XenAI.py +324 -324
  167. webscout/Provider/UNFINISHED/Youchat.py +330 -330
  168. webscout/Provider/UNFINISHED/liner.py +334 -0
  169. webscout/Provider/UNFINISHED/liner_api_request.py +262 -262
  170. webscout/Provider/UNFINISHED/puterjs.py +634 -634
  171. webscout/Provider/UNFINISHED/samurai.py +223 -223
  172. webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
  173. webscout/Provider/Venice.py +250 -250
  174. webscout/Provider/VercelAI.py +256 -256
  175. webscout/Provider/WiseCat.py +231 -231
  176. webscout/Provider/WrDoChat.py +366 -366
  177. webscout/Provider/__init__.py +33 -18
  178. webscout/Provider/ai4chat.py +174 -174
  179. webscout/Provider/akashgpt.py +331 -331
  180. webscout/Provider/cerebras.py +446 -446
  181. webscout/Provider/chatglm.py +394 -301
  182. webscout/Provider/cleeai.py +211 -211
  183. webscout/Provider/elmo.py +282 -282
  184. webscout/Provider/geminiapi.py +208 -208
  185. webscout/Provider/granite.py +261 -261
  186. webscout/Provider/hermes.py +263 -263
  187. webscout/Provider/julius.py +223 -223
  188. webscout/Provider/learnfastai.py +309 -309
  189. webscout/Provider/llama3mitril.py +214 -214
  190. webscout/Provider/llmchat.py +243 -243
  191. webscout/Provider/llmchatco.py +290 -290
  192. webscout/Provider/meta.py +801 -801
  193. webscout/Provider/oivscode.py +309 -309
  194. webscout/Provider/scira_chat.py +383 -383
  195. webscout/Provider/searchchat.py +292 -292
  196. webscout/Provider/sonus.py +258 -258
  197. webscout/Provider/toolbaz.py +370 -367
  198. webscout/Provider/turboseek.py +273 -273
  199. webscout/Provider/typefully.py +207 -207
  200. webscout/Provider/yep.py +372 -372
  201. webscout/__init__.py +30 -31
  202. webscout/__main__.py +5 -5
  203. webscout/auth/api_key_manager.py +189 -189
  204. webscout/auth/config.py +175 -175
  205. webscout/auth/models.py +185 -185
  206. webscout/auth/routes.py +664 -664
  207. webscout/auth/simple_logger.py +236 -236
  208. webscout/cli.py +523 -523
  209. webscout/conversation.py +438 -438
  210. webscout/exceptions.py +361 -361
  211. webscout/litagent/Readme.md +298 -298
  212. webscout/litagent/__init__.py +28 -28
  213. webscout/litagent/agent.py +581 -581
  214. webscout/litagent/constants.py +59 -59
  215. webscout/litprinter/__init__.py +58 -58
  216. webscout/models.py +181 -181
  217. webscout/optimizers.py +419 -419
  218. webscout/prompt_manager.py +288 -288
  219. webscout/sanitize.py +1078 -1078
  220. webscout/scout/README.md +401 -401
  221. webscout/scout/__init__.py +8 -8
  222. webscout/scout/core/__init__.py +6 -6
  223. webscout/scout/core/crawler.py +297 -297
  224. webscout/scout/core/scout.py +706 -706
  225. webscout/scout/core/search_result.py +95 -95
  226. webscout/scout/core/text_analyzer.py +62 -62
  227. webscout/scout/core/text_utils.py +277 -277
  228. webscout/scout/core/web_analyzer.py +51 -51
  229. webscout/scout/element.py +599 -599
  230. webscout/scout/parsers/__init__.py +69 -69
  231. webscout/scout/parsers/html5lib_parser.py +172 -172
  232. webscout/scout/parsers/html_parser.py +236 -236
  233. webscout/scout/parsers/lxml_parser.py +178 -178
  234. webscout/scout/utils.py +37 -37
  235. webscout/swiftcli/Readme.md +323 -323
  236. webscout/swiftcli/__init__.py +95 -95
  237. webscout/swiftcli/core/__init__.py +7 -7
  238. webscout/swiftcli/core/cli.py +308 -308
  239. webscout/swiftcli/core/context.py +104 -104
  240. webscout/swiftcli/core/group.py +241 -241
  241. webscout/swiftcli/decorators/__init__.py +28 -28
  242. webscout/swiftcli/decorators/command.py +221 -221
  243. webscout/swiftcli/decorators/options.py +220 -220
  244. webscout/swiftcli/decorators/output.py +302 -302
  245. webscout/swiftcli/exceptions.py +21 -21
  246. webscout/swiftcli/plugins/__init__.py +9 -9
  247. webscout/swiftcli/plugins/base.py +135 -135
  248. webscout/swiftcli/plugins/manager.py +269 -269
  249. webscout/swiftcli/utils/__init__.py +59 -59
  250. webscout/swiftcli/utils/formatting.py +252 -252
  251. webscout/swiftcli/utils/parsing.py +267 -267
  252. webscout/update_checker.py +117 -117
  253. webscout/version.py +1 -1
  254. webscout/webscout_search.py +1183 -1183
  255. webscout/webscout_search_async.py +649 -649
  256. webscout/yep_search.py +346 -346
  257. webscout/zeroart/README.md +89 -89
  258. webscout/zeroart/__init__.py +134 -134
  259. webscout/zeroart/base.py +66 -66
  260. webscout/zeroart/effects.py +100 -100
  261. webscout/zeroart/fonts.py +1238 -1238
  262. {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/METADATA +937 -937
  263. webscout-2025.10.11.dist-info/RECORD +300 -0
  264. webscout/Provider/AISEARCH/DeepFind.py +0 -254
  265. webscout/Provider/OPENAI/Qwen3.py +0 -303
  266. webscout/Provider/OPENAI/qodo.py +0 -630
  267. webscout/Provider/OPENAI/xenai.py +0 -514
  268. webscout/tempid.py +0 -134
  269. webscout-8.3.7.dist-info/RECORD +0 -301
  270. {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/WHEEL +0 -0
  271. {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/entry_points.txt +0 -0
  272. {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/licenses/LICENSE.md +0 -0
  273. {webscout-8.3.7.dist-info → webscout-2025.10.11.dist-info}/top_level.txt +0 -0
@@ -1,1105 +1,1105 @@
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 typing import Optional, Generator, List, Tuple, Dict, Any, NamedTuple
11
- from rich.panel import Panel
12
- from rich.syntax import Syntax
13
- from rich.console import Console
14
- from rich.markdown import Markdown
15
- from rich.table import Table
16
- from rich.theme import Theme
17
- from rich.live import Live
18
- from rich.box import ROUNDED
19
- from .autocoder_utiles import get_intro_prompt
20
- # Initialize LitLogger with custom format and colors
21
- default_path = tempfile.mkdtemp(prefix="webscout_autocoder")
22
-
23
- # Custom theme for consistent styling
24
- CUSTOM_THEME = Theme({
25
- "info": "cyan",
26
- "warning": "yellow",
27
- "error": "red bold",
28
- "success": "green",
29
- "code": "blue",
30
- "output": "white",
31
- })
32
-
33
- console = Console(theme=CUSTOM_THEME)
34
- class CommandResult(NamedTuple):
35
- """Result of a system command execution."""
36
- success: bool
37
- stdout: str
38
- stderr: str
39
-
40
- def run_system_command(
41
- command: str,
42
- exit_on_error: bool = False,
43
- stdout_error: bool = False,
44
- help: Optional[str] = None
45
- ) -> Tuple[bool, CommandResult]:
46
- """Execute a system command and return the result.
47
-
48
- Args:
49
- command (str): Command to execute
50
- exit_on_error (bool): Whether to exit on error. Defaults to False.
51
- stdout_error (bool): Whether to include stdout in error messages. Defaults to False.
52
- help (str, optional): Help message for errors. Defaults to None.
53
-
54
- Returns:
55
- Tuple[bool, CommandResult]: Success status and command result containing stdout/stderr
56
- """
57
- try:
58
- # Execute command and capture output
59
- process = subprocess.Popen(
60
- command,
61
- stdout=subprocess.PIPE,
62
- stderr=subprocess.PIPE,
63
- shell=True,
64
- text=True
65
- )
66
-
67
- # Get stdout and stderr
68
- stdout, stderr = process.communicate()
69
- success = process.returncode == 0
70
-
71
- # Create result object
72
- result = CommandResult(
73
- success=success,
74
- stdout=stdout.strip() if stdout else "",
75
- stderr=stderr.strip() if stderr else ""
76
- )
77
-
78
- # Handle errors if needed
79
- if not success and exit_on_error:
80
- error_msg = stderr if stderr else stdout if stdout_error else "Command failed"
81
- if help:
82
- error_msg += f"\n{help}"
83
- sys.exit(error_msg)
84
-
85
- return success, result
86
-
87
- except Exception as e:
88
- # Handle execution errors
89
- error_msg = str(e)
90
- if help:
91
- error_msg += f"\n{help}"
92
-
93
- if exit_on_error:
94
- sys.exit(error_msg)
95
-
96
- return False, CommandResult(success=False, stdout="", stderr=error_msg)
97
-
98
-
99
- class AutoCoder:
100
- """Generate and auto-execute Python scripts in the CLI with advanced error handling and retry logic.
101
-
102
- This class provides:
103
- - Automatic code generation
104
- - Script execution with safety checks
105
- - Advanced error handling and retries
106
- - Beautiful logging with rich console
107
- - Execution result capture and display
108
-
109
- Examples:
110
- >>> coder = AutoCoder()
111
- >>> coder.execute("Get system info")
112
- Generating system info script...
113
- Script executed successfully!
114
- """
115
-
116
- def __init__(
117
- self,
118
- quiet: bool = False,
119
- internal_exec: bool = False,
120
- confirm_script: bool = False,
121
- interpreter: str = "python",
122
- prettify: bool = True,
123
- path_to_script: str = "",
124
- max_retries: int = 3,
125
- ai_instance = None
126
- ):
127
- """Initialize AutoCoder instance.
128
-
129
- Args:
130
- quiet (bool): Flag to control logging. Defaults to False.
131
- internal_exec (bool): Execute scripts with exec function. Defaults to False.
132
- confirm_script (bool): Give consent to scripts prior to execution. Defaults to False.
133
- interpreter (str): Python's interpreter name. Defaults to "python".
134
- prettify (bool): Prettify the code on stdout. Defaults to True.
135
- path_to_script (str): Path to save generated scripts. Defaults to "".
136
- max_retries (int): Maximum number of retry attempts. Defaults to 3.
137
- ai_instance: AI instance for error correction. Defaults to None.
138
- """
139
- self.internal_exec = internal_exec
140
- self.confirm_script = confirm_script
141
- self.quiet = quiet
142
- self.interpreter = interpreter
143
- self.prettify = prettify
144
- self.path_to_script = path_to_script or os.path.join(default_path, "execute_this.py")
145
- self.max_retries = max_retries
146
- self.tried_solutions = set()
147
- self.ai_instance = ai_instance
148
- self.last_execution_result = ""
149
-
150
- # Get Python version with enhanced logging
151
- if self.internal_exec:
152
- self.python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
153
- else:
154
- version_output = run_system_command(
155
- f"{self.interpreter} --version",
156
- exit_on_error=True,
157
- stdout_error=True,
158
- help="If you're using Webscout-cli, use the flag '--internal-exec'"
159
- )[1].stdout
160
- self.python_version = version_output.split(" ")[1]
161
-
162
-
163
-
164
- def _extract_code_blocks(self, response: str) -> List[Tuple[str, str]]:
165
- """Extract code blocks from a response string.
166
-
167
- Args:
168
- response (str): Response string containing code blocks
169
-
170
- Returns:
171
- List[Tuple[str, str]]: List of (code_type, code) tuples
172
- """
173
- blocks = []
174
-
175
- # First try to find code blocks with explicit language tags
176
- pattern = r"```(\w+)\n(.*?)```"
177
- matches = re.finditer(pattern, response, re.DOTALL)
178
-
179
- for match in matches:
180
- code_type = match.group(1).lower()
181
- code = match.group(2).strip()
182
-
183
- # Check if this is a shell command (starts with !)
184
- if code_type == 'bash' or code_type == 'shell' or code.startswith('!'):
185
- blocks.append(('shell', code))
186
- else:
187
- blocks.append((code_type, code))
188
-
189
- # If no explicit code blocks found with language tags, try generic code blocks
190
- if not blocks:
191
- pattern = r"```(.*?)```"
192
- matches = re.finditer(pattern, response, re.DOTALL)
193
- for match in matches:
194
- code = match.group(1).strip()
195
-
196
- # Check if this is a shell command (starts with !)
197
- if code.startswith('!'):
198
- blocks.append(('shell', code))
199
- else:
200
- blocks.append(('python', code))
201
-
202
- # If still no code blocks found, treat as raw Python code
203
- if not blocks:
204
- lines = [line.strip() for line in response.split('\n') if line.strip()]
205
- if lines:
206
- # Check if this is a shell command (starts with !)
207
- if lines[0].startswith('!'):
208
- blocks.append(('shell', '\n'.join(lines)))
209
- else:
210
- blocks.append(('python', '\n'.join(lines)))
211
-
212
- return blocks
213
-
214
- def _execute_code_block(self, code_type: str, code: str, ai_instance=None) -> Tuple[bool, str]:
215
- """Execute a code block.
216
-
217
- Args:
218
- code_type (str): Type of code block ('python' or 'shell')
219
- code (str): Code to execute
220
- ai_instance: Optional AI instance for error correction
221
-
222
- Returns:
223
- Tuple[bool, str]: (Success status, Error message or execution result)
224
- """
225
- try:
226
- # Handle shell commands (starting with !)
227
- if code_type == 'shell':
228
- # Remove the leading '!' from each line
229
- shell_commands = []
230
- for line in code.split('\n'):
231
- if line.startswith('!'):
232
- shell_commands.append(line[1:].strip()) # Remove the '!' and any leading whitespace
233
- else:
234
- shell_commands.append(line.strip())
235
-
236
- # Execute each shell command
237
- overall_success = True
238
- overall_result = []
239
-
240
- # Display the shell command in Jupyter-style UI
241
- if self.prettify:
242
- # Format the command for display
243
- cmd_display = '\n'.join([f"!{cmd}" for cmd in shell_commands if cmd])
244
- syntax = Syntax(cmd_display, "bash", theme="monokai", line_numbers=True)
245
- console.print(Panel(
246
- syntax,
247
- title="[bold blue]In [1]:[/bold blue]",
248
- border_style="blue",
249
- expand=True,
250
- box=ROUNDED
251
- ))
252
-
253
- for cmd in shell_commands:
254
- if not cmd: # Skip empty commands
255
- continue
256
-
257
- success, result = run_system_command(cmd)
258
-
259
- if success:
260
- if result.stdout:
261
- overall_result.append(result.stdout)
262
-
263
- # Display the output in Jupyter-style UI
264
- if self.prettify:
265
- console.print(Panel(
266
- result.stdout,
267
- title="[bold red]Out [1]:[/bold red]",
268
- border_style="red",
269
- expand=True,
270
- padding=(0, 1),
271
- box=ROUNDED
272
- ))
273
-
274
- self.last_execution_result = '\n'.join(overall_result)
275
- else:
276
- error_msg = result.stderr if result.stderr else f"Command failed: {cmd}"
277
-
278
- # Display the error in Jupyter-style UI
279
- if self.prettify:
280
- console.print(Panel(
281
- f"Error: {error_msg}",
282
- title="[bold red]Out [1]:[/bold red]",
283
- border_style="red",
284
- expand=True,
285
- padding=(0, 1),
286
- box=ROUNDED
287
- ))
288
-
289
- return False, error_msg
290
-
291
- return True, self.last_execution_result
292
- else:
293
- # Handle Python code
294
- result = self._execute_with_retry(code, ai_instance)
295
- if result is None:
296
- return True, self.last_execution_result
297
- return False, result
298
- except Exception as e:
299
- return False, str(e)
300
-
301
- def _format_output_panel(self, code: str, output_lines: list) -> Panel:
302
- """Format code and output into a single panel.
303
-
304
- Args:
305
- code (str): The code that was executed
306
- output_lines (list): List of output lines
307
-
308
- Returns:
309
- Panel: Formatted panel with code and output
310
- """
311
- # Format output
312
- output_text = "\n".join(output_lines) if output_lines else "Running..."
313
-
314
- # Create panel with Jupyter-like styling
315
- panel = Panel(
316
- output_text,
317
- title="[bold red]Out [1]:[/bold red]",
318
- border_style="red",
319
- expand=True,
320
- padding=(0, 1),
321
- box=ROUNDED
322
- )
323
-
324
- return panel
325
-
326
- def _format_result_panel(self, output: str) -> Panel:
327
- """Format execution result into a panel.
328
-
329
- Args:
330
- output (str): Execution output text
331
-
332
- Returns:
333
- Panel: Formatted panel with execution result
334
- """
335
- # Create panel with Jupyter-like styling
336
- panel = Panel(
337
- output,
338
- title="[bold red]Out [1]:[/bold red]",
339
- border_style="red",
340
- expand=True,
341
- padding=(0, 1),
342
- box=ROUNDED
343
- )
344
-
345
- return panel
346
-
347
- def _stream_output(self, process: subprocess.Popen) -> Generator[str, None, None]:
348
- """Stream output from a subprocess in realtime.
349
-
350
- Args:
351
- process: Subprocess to stream output from
352
-
353
- Yields:
354
- str: Lines of output
355
- """
356
- # Stream stdout
357
- output_lines = []
358
- for line in process.stdout:
359
- decoded_line = line.decode('utf-8').strip() if isinstance(line, bytes) else line.strip()
360
- if decoded_line:
361
- output_lines.append(decoded_line)
362
- yield decoded_line
363
-
364
- # Check stderr
365
- error = process.stderr.read() if process.stderr else None
366
- if error:
367
- error_str = error.decode('utf-8').strip() if isinstance(error, bytes) else error.strip()
368
- if error_str:
369
- yield f"Error: {error_str}"
370
- output_lines.append(f"Error: {error_str}")
371
-
372
- # Store the full execution result
373
- self.last_execution_result = "\n".join(output_lines)
374
-
375
- def _execute_with_retry(self, code: str, ai_instance=None) -> Optional[str]:
376
- """Execute code with retry logic and error correction.
377
-
378
- Args:
379
- code (str): Code to execute
380
- ai_instance: Optional AI instance for error correction
381
-
382
- Returns:
383
- Optional[str]: Error message if execution failed, None if successful
384
- """
385
- last_error = None
386
- retries = 0
387
-
388
- # Add the solution to tried solutions
389
- self.tried_solutions.add(code)
390
-
391
- # Print the code first
392
- if self.prettify:
393
- syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
394
- console.print(Panel(
395
- syntax,
396
- title="[bold blue]In [1]:[/bold blue]",
397
- border_style="blue",
398
- expand=True,
399
- box=ROUNDED
400
- ))
401
-
402
- while retries < self.max_retries:
403
- try:
404
- if self.path_to_script:
405
- script_dir = os.path.dirname(self.path_to_script)
406
- if script_dir:
407
- os.makedirs(script_dir, exist_ok=True)
408
- with open(self.path_to_script, "w", encoding="utf-8") as f:
409
- f.write(code)
410
-
411
- if self.internal_exec:
412
- # Create StringIO for output capture
413
- import io
414
- import sys
415
- stdout = io.StringIO()
416
- stderr = io.StringIO()
417
-
418
- # Create a queue for realtime output
419
- output_queue = queue.Queue()
420
- output_lines = []
421
-
422
- def execute_code():
423
- try:
424
- # Create a local namespace
425
- local_namespace: Dict[str, Any] = {}
426
-
427
- # Redirect stdout/stderr
428
- sys.stdout = stdout
429
- sys.stderr = stderr
430
-
431
- # Execute the code
432
- exec(code, globals(), local_namespace)
433
-
434
- # Get any output
435
- output = stdout.getvalue()
436
- error = stderr.getvalue()
437
-
438
- if error:
439
- output_queue.put(("error", error))
440
- if output:
441
- output_queue.put(("output", output))
442
-
443
- except Exception as e:
444
- output_queue.put(("error", str(e)))
445
- finally:
446
- # Restore stdout/stderr
447
- sys.stdout = sys.__stdout__
448
- sys.stderr = sys.__stderr__
449
-
450
- # Create and start execution thread
451
- thread = threading.Thread(target=execute_code)
452
- thread.daemon = True # Make thread daemon to avoid hanging
453
- thread.start()
454
-
455
- # Display output in realtime
456
- with Live(auto_refresh=True) as live:
457
- timeout_counter = 0
458
- while thread.is_alive() or not output_queue.empty():
459
- try:
460
- msg_type, content = output_queue.get(timeout=0.1)
461
- if content:
462
- new_lines = content.splitlines()
463
- output_lines.extend(new_lines)
464
- live.update(self._format_output_panel(code, output_lines))
465
- live.refresh()
466
- output_queue.task_done()
467
- except queue.Empty:
468
- timeout_counter += 1
469
- # Refresh the display to show it's still running
470
- if timeout_counter % 10 == 0: # Refresh every ~1 second
471
- live.update(self._format_output_panel(code, output_lines))
472
- live.refresh()
473
- if timeout_counter > 100 and thread.is_alive(): # ~10 seconds
474
- output_lines.append("Warning: Execution taking longer than expected...")
475
- live.update(self._format_output_panel(code, output_lines))
476
- live.refresh()
477
- continue
478
-
479
- # Wait for thread to complete with timeout
480
- thread.join(timeout=30) # 30 second timeout
481
- if thread.is_alive():
482
- output_lines.append("Error: Execution timed out after 30 seconds")
483
- raise TimeoutError("Execution timed out after 30 seconds")
484
-
485
- # Check for any final errors
486
- error = stderr.getvalue()
487
- if error:
488
- raise Exception(error)
489
-
490
- # Store the full execution result
491
- self.last_execution_result = stdout.getvalue()
492
-
493
- else:
494
- try:
495
- process = subprocess.Popen(
496
- [self.interpreter, self.path_to_script],
497
- stdout=subprocess.PIPE,
498
- stderr=subprocess.PIPE,
499
- text=True, # Use text mode to avoid encoding issues
500
- bufsize=1,
501
- )
502
-
503
- output_lines = []
504
- # Stream output in realtime
505
- with Live(auto_refresh=True) as live:
506
- for line in self._stream_output(process):
507
- output_lines.append(line)
508
- live.update(self._format_output_panel(code, output_lines))
509
- live.refresh()
510
-
511
- process.wait(timeout=30) # 30 second timeout
512
-
513
- if process.returncode != 0:
514
- # Try to read more detailed error information
515
- if process.stderr:
516
- error = process.stderr.read()
517
- error_str = error.strip() if error else ""
518
- if error_str:
519
- raise Exception(error_str)
520
- raise Exception(f"Process exited with code {process.returncode}")
521
-
522
- # Store the full execution result
523
- self.last_execution_result = "\n".join(output_lines)
524
-
525
- except subprocess.TimeoutExpired:
526
- # Handle the case where the process times out
527
- if process:
528
- process.kill()
529
- raise TimeoutError("Execution timed out after 30 seconds")
530
-
531
- return None
532
-
533
- except Exception as e:
534
- last_error = e
535
- if retries < self.max_retries - 1 and ai_instance:
536
- try:
537
- # First try to handle import errors
538
- if isinstance(e, ImportError):
539
- fixed_code = self._handle_import_error(e, code)
540
- if fixed_code:
541
- code = fixed_code
542
- retries += 1
543
- continue
544
-
545
- # Get error context and try to fix the specific error
546
- error_context = self._get_error_context(e, code)
547
- fixed_response = ai_instance.chat(error_context)
548
- fixed_code = self._extract_code_from_response(fixed_response)
549
-
550
- if not fixed_code:
551
- # If no code found, try a more general approach
552
- general_context = f"""
553
- The code failed with error: {str(e)}
554
-
555
- Original Code:
556
- ```python
557
- {code}
558
- ```
559
-
560
- Please provide a complete, corrected version of the code that handles this error. The code should:
561
- 1. Handle any potential encoding issues
562
- 2. Include proper error handling
563
- 3. Use appropriate libraries and imports
564
- 4. Be compatible with the current Python environment
565
- 5. Fix the specific error: {str(e)}
566
-
567
- Provide only the corrected code without any explanation.
568
- """
569
- fixed_response = ai_instance.chat(general_context)
570
- fixed_code = self._extract_code_from_response(fixed_response)
571
-
572
- if not fixed_code:
573
- break
574
-
575
- if self._is_similar_solution(fixed_code):
576
- # If solution is too similar, try a different approach
577
- different_context = f"""
578
- Previous solutions were not successful. The code failed with error: {str(e)}
579
-
580
- Original Code:
581
- ```python
582
- {code}
583
- ```
584
-
585
- Please provide a significantly different approach to solve this problem. Consider:
586
- 1. Using alternative libraries or methods
587
- 2. Implementing a different algorithm
588
- 3. Adding more robust error handling
589
- 4. Using a different encoding or data handling approach
590
- 5. Specifically address the error: {str(e)}
591
-
592
- Provide only the corrected code without any explanation.
593
- """
594
- fixed_response = ai_instance.chat(different_context)
595
- fixed_code = self._extract_code_from_response(fixed_response)
596
-
597
- if self._is_similar_solution(fixed_code):
598
- break
599
-
600
- # Update code and continue with retry
601
- code = fixed_code
602
- self.tried_solutions.add(code)
603
- retries += 1
604
- continue
605
-
606
- except Exception as ai_error:
607
- console.print(f"Error during AI correction: {str(ai_error)}", style="error")
608
- break
609
- break
610
-
611
- return str(last_error) if last_error else "Unknown error occurred"
612
-
613
- def execute(self, prompt: str, ai_instance=None) -> bool:
614
- """Execute the given prompt using the appropriate executor.
615
-
616
- Args:
617
- prompt (str): Prompt to execute
618
- ai_instance: Optional AI instance for error correction
619
-
620
- Returns:
621
- bool: True if execution was successful, False otherwise
622
- """
623
- try:
624
- # Check if this is a direct shell command (starts with !)
625
- if prompt.strip().startswith('!'):
626
- # Handle shell command
627
- cmd = prompt.strip()[1:].strip() # Remove the '!' and any leading whitespace
628
-
629
- # Display the shell command in Jupyter-style UI
630
- if self.prettify:
631
- syntax = Syntax(f"!{cmd}", "bash", theme="monokai", line_numbers=True)
632
- console.print(Panel(
633
- syntax,
634
- title="[bold blue]In [1]:[/bold blue]",
635
- border_style="blue",
636
- expand=True,
637
- box=ROUNDED
638
- ))
639
-
640
- success, result = run_system_command(cmd)
641
-
642
- if success:
643
- if result.stdout:
644
- # Display the output in Jupyter-style UI
645
- if self.prettify:
646
- console.print(Panel(
647
- result.stdout,
648
- title="[bold red]Out [1]:[/bold red]",
649
- border_style="red",
650
- expand=True,
651
- padding=(0, 1),
652
- box=ROUNDED
653
- ))
654
- else:
655
- console.print(result.stdout, style="output")
656
- self.last_execution_result = result.stdout
657
- return True
658
- else:
659
- error_msg = result.stderr if result.stderr else f"Command failed: {cmd}"
660
- # Display the error in Jupyter-style UI
661
- if self.prettify:
662
- console.print(Panel(
663
- f"Error: {error_msg}",
664
- title="[bold red]Out [1]:[/bold red]",
665
- border_style="red",
666
- expand=True,
667
- padding=(0, 1),
668
- box=ROUNDED
669
- ))
670
- else:
671
- console.print(error_msg, style="error")
672
- return False
673
-
674
- # Extract code blocks
675
- code_blocks = self._extract_code_blocks(prompt)
676
- if not code_blocks:
677
- console.print("No executable code found in the prompt", style="warning")
678
- return False
679
-
680
- # Execute each code block
681
- overall_success = True
682
- for code_type, code in code_blocks:
683
- success, result = self._execute_code_block(code_type, code, ai_instance)
684
-
685
- if not success:
686
- console.print(f"Execution failed: {result}", style="error")
687
- overall_success = False
688
-
689
- return overall_success
690
-
691
- except Exception as e:
692
- console.print(f"Error in execution: {str(e)}", style="error")
693
- return False
694
-
695
- def _extract_code_from_response(self, response: str) -> str:
696
- """Extract code from AI response.
697
-
698
- Args:
699
- response (str): AI response containing code blocks
700
-
701
- Returns:
702
- str: Extracted code from the first code block
703
- """
704
- if not response:
705
- return ""
706
-
707
- # First try to find code blocks with explicit language tags
708
- code_blocks = self._extract_code_blocks(response)
709
- if code_blocks:
710
- # Return the content of the first code block
711
- return code_blocks[0][1]
712
-
713
- # If no code blocks found, try to find raw Python code or shell commands
714
- lines = []
715
- for line in response.split('\n'):
716
- line = line.strip()
717
- if not line:
718
- continue
719
-
720
- # Skip markdown headers and other non-code lines
721
- if line.startswith(('#', '```', '---', '===', '>>>')):
722
- continue
723
-
724
- # Skip common non-code lines
725
- if any(line.startswith(prefix) for prefix in ['Please', 'Here', 'The', 'This', 'You']):
726
- continue
727
-
728
- lines.append(line)
729
-
730
- if lines:
731
- return '\n'.join(lines)
732
-
733
- return ""
734
-
735
- def _get_error_context(self, error: Exception, code: str) -> str:
736
- """Create context about the error for AI correction.
737
-
738
- Args:
739
- error (Exception): The caught exception
740
- code (str): The code that caused the error
741
-
742
- Returns:
743
- str: Formatted error context for AI
744
- """
745
- error_type = type(error).__name__
746
- error_msg = str(error)
747
-
748
- # Get Python version and environment info
749
- python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
750
- platform = sys.platform
751
-
752
- # Get the line number where the error occurred if available
753
- import traceback
754
- tb = traceback.extract_tb(error.__traceback__)
755
- line_info = ""
756
- if tb:
757
- line_info = f"\nError occurred at line {tb[-1].lineno}"
758
-
759
- return f"""
760
- The code failed with error:
761
- Error Type: {error_type}
762
- Error Message: {error_msg}{line_info}
763
-
764
- Environment:
765
- Python Version: {python_version}
766
- Platform: {platform}
767
-
768
- Original Code:
769
- ```python
770
- {code}
771
- ```
772
-
773
- Please fix the code to handle this error. The solution should:
774
- 1. Address the specific error: {error_msg}
775
- 2. Be compatible with Python {python_version}
776
- 3. Work on {platform}
777
- 4. Include proper error handling
778
- 5. Use appropriate libraries and imports
779
-
780
- Provide only the corrected code without any explanation.
781
- """
782
-
783
- def _handle_import_error(self, error: ImportError, code: str) -> Optional[str]:
784
- """Handle missing package errors by attempting to install them.
785
-
786
- Args:
787
- error (ImportError): The import error
788
- code (str): The code that caused the error
789
-
790
- Returns:
791
- Optional[str]: Fixed code or None if installation failed
792
- """
793
- try:
794
- missing_package = str(error).split("'")[1] if "'" in str(error) else str(error).split("No module named")[1].strip()
795
- missing_package = missing_package.replace("'", "").strip()
796
-
797
- console.print(f"Installing missing package: {missing_package}", style="info")
798
- result = subprocess.run(
799
- [sys.executable, "-m", "pip", "install", missing_package],
800
- capture_output=True,
801
- text=True
802
- )
803
- if result.returncode == 0:
804
- console.print(f"Successfully installed {missing_package}", style="success")
805
- return code # Retry with same code after installing package
806
- else:
807
- raise Exception(f"Failed to install {missing_package}: {result.stderr}")
808
- except Exception as e:
809
- console.print(f"Error installing package: {str(e)}", style="error")
810
- return None
811
-
812
- def _is_similar_solution(self, new_code: str, threshold: float = 0.8) -> bool:
813
- """Check if the new solution is too similar to previously tried ones.
814
-
815
- Args:
816
- new_code (str): New solution to check
817
- threshold (float): Similarity threshold (0-1). Defaults to 0.8.
818
-
819
- Returns:
820
- bool: True if solution is too similar to previous attempts
821
- """
822
- import difflib
823
-
824
- def normalize_code(code: str) -> str:
825
- lines = [line.split('#')[0].strip() for line in code.split('\n')]
826
- return '\n'.join(line for line in lines if line)
827
-
828
- new_code_norm = normalize_code(new_code)
829
-
830
- for tried_code in self.tried_solutions:
831
- tried_code_norm = normalize_code(tried_code)
832
- similarity = difflib.SequenceMatcher(None, new_code_norm, tried_code_norm).ratio()
833
- if similarity > threshold:
834
- return True
835
- return False
836
-
837
- def main(self, response: str) -> Optional[str]:
838
- """Execute code with error correction.
839
-
840
- Args:
841
- response (str): AI response containing code
842
-
843
- Returns:
844
- Optional[str]: Error message if execution failed, None if successful
845
- """
846
- if not response:
847
- return "No response provided"
848
-
849
- # Check if this is a shell command (starts with !)
850
- if response.strip().startswith('!'):
851
- # Handle shell command
852
- cmd = response.strip()[1:].strip() # Remove the '!' and any leading whitespace
853
-
854
- # Display the shell command in Jupyter-style UI
855
- if self.prettify:
856
- syntax = Syntax(f"!{cmd}", "bash", theme="monokai", line_numbers=True)
857
- console.print(Panel(
858
- syntax,
859
- title="[bold blue]In [1]:[/bold blue]",
860
- border_style="blue",
861
- expand=True,
862
- box=ROUNDED
863
- ))
864
-
865
- success, result = run_system_command(cmd)
866
-
867
- if success:
868
- if result.stdout:
869
- # Display the output in Jupyter-style UI
870
- if self.prettify:
871
- console.print(Panel(
872
- result.stdout,
873
- title="[bold red]Out [1]:[/bold red]",
874
- border_style="red",
875
- expand=True,
876
- padding=(0, 1),
877
- box=ROUNDED
878
- ))
879
- self.last_execution_result = result.stdout
880
- return None
881
- else:
882
- error_msg = result.stderr if result.stderr else f"Command failed: {cmd}"
883
- # Display the error in Jupyter-style UI
884
- if self.prettify:
885
- console.print(Panel(
886
- f"Error: {error_msg}",
887
- title="[bold red]Out [1]:[/bold red]",
888
- border_style="red",
889
- expand=True,
890
- padding=(0, 1),
891
- box=ROUNDED
892
- ))
893
- else:
894
- console.print(error_msg, style="error")
895
- return error_msg
896
-
897
- # Extract code blocks
898
- code_blocks = self._extract_code_blocks(response)
899
- if code_blocks:
900
- code_type, code = code_blocks[0]
901
-
902
- # Handle shell commands
903
- if code_type == 'shell':
904
- success, result = self._execute_code_block(code_type, code)
905
- if success:
906
- return None
907
- else:
908
- # Error is already displayed in _execute_code_block
909
- return result
910
-
911
- # Handle regular Python code
912
- code = self._extract_code_from_response(response)
913
- if not code:
914
- return "No executable code found in the response"
915
-
916
- ai_instance = self.ai_instance or globals().get('ai')
917
-
918
- if not ai_instance:
919
- console.print("AI instance not found, error correction disabled", style="warning")
920
- try:
921
- if self.path_to_script:
922
- script_dir = os.path.dirname(self.path_to_script)
923
- if script_dir:
924
- os.makedirs(script_dir, exist_ok=True)
925
- with open(self.path_to_script, "w", encoding="utf-8") as f:
926
- f.write(code)
927
-
928
- if self.internal_exec:
929
- console.print("[INFO] Executing code internally", style="info")
930
- # Create a local namespace
931
- local_namespace: Dict[str, Any] = {}
932
-
933
- # Capture stdout
934
- import io
935
- old_stdout = sys.stdout
936
- captured_output = io.StringIO()
937
- sys.stdout = captured_output
938
-
939
- # Execute the code
940
- try:
941
- exec(code, globals(), local_namespace)
942
- # Capture the result
943
- self.last_execution_result = captured_output.getvalue()
944
- finally:
945
- # Restore stdout
946
- sys.stdout = old_stdout
947
- else:
948
- console.print("[INFO] Executing code as external process", style="info")
949
- result = subprocess.run(
950
- [self.interpreter, self.path_to_script],
951
- capture_output=True,
952
- text=True
953
- )
954
- self.last_execution_result = result.stdout
955
-
956
- if result.returncode != 0:
957
- raise Exception(result.stderr or result.stdout)
958
-
959
- return None
960
- except Exception as e:
961
- error_msg = f"Execution error: {str(e)}"
962
- console.print(error_msg, style="error")
963
- return error_msg
964
-
965
- result = self._execute_with_retry(code, ai_instance)
966
- return result
967
-
968
- @property
969
- def intro_prompt(self) -> str:
970
- """Get the introduction prompt.
971
-
972
- Returns:
973
- str: Introduction prompt
974
- """
975
- return get_intro_prompt()
976
-
977
- def log(self, message: str, category: str = "info"):
978
- """RawDog logger
979
-
980
- Args:
981
- message (str): Log message
982
- category (str, optional): Log level. Defaults to 'info'.
983
- """
984
- if self.quiet:
985
- return
986
-
987
- message = "[Webscout] - " + message
988
- if category == "error":
989
- console.print(f"[ERROR] {message}", style="error")
990
- else:
991
- console.print(message, style=category)
992
-
993
- def stdout(self, message: str, style: str = "info") -> None:
994
- """Enhanced stdout with Rich formatting.
995
-
996
- Args:
997
- message (str): Text to be printed
998
- style (str, optional): Style to apply. Defaults to "info".
999
- """
1000
- if not self.prettify:
1001
- print(message)
1002
- return
1003
-
1004
- if message.startswith("```") and message.endswith("```"):
1005
- # Handle code blocks
1006
- code = message.strip("`").strip()
1007
- syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
1008
- console.print(Panel(syntax, title="Code", border_style="blue"))
1009
- elif "```python" in message:
1010
- # Handle markdown code blocks
1011
- md = Markdown(message)
1012
- console.print(md)
1013
- else:
1014
- # Handle regular text with optional styling
1015
- console.print(message, style=style)
1016
-
1017
- def print_code(self, code: str, title: str = "Generated Code") -> None:
1018
- """Print code with syntax highlighting and panel.
1019
-
1020
- Args:
1021
- code (str): Code to print
1022
- title (str, optional): Panel title. Defaults to "Generated Code".
1023
- """
1024
- if self.prettify:
1025
- syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
1026
- console.print(Panel(
1027
- syntax,
1028
- title=f"[bold blue]In [1]:[/bold blue]",
1029
- border_style="blue",
1030
- expand=True,
1031
- box=ROUNDED
1032
- ))
1033
- else:
1034
- print(f"\n{title}:")
1035
- print(code)
1036
-
1037
- def print_output(self, output: str, style: str = "output") -> None:
1038
- """Print command output with optional styling.
1039
-
1040
- Args:
1041
- output (str): Output to print
1042
- style (str, optional): Style to apply. Defaults to "output".
1043
- """
1044
- if self.prettify:
1045
- # Try to detect if output is Python code
1046
- try:
1047
- # If it looks like Python code, syntax highlight it
1048
- compile(output, '<string>', 'exec')
1049
- syntax = Syntax(output, "python", theme="monokai", line_numbers=False)
1050
- formatted_output = syntax
1051
- except SyntaxError:
1052
- # If not Python code, treat as plain text
1053
- formatted_output = output
1054
-
1055
- # Use the style parameter for the panel border
1056
- console.print(Panel(
1057
- formatted_output,
1058
- title="[bold red]Out [1]:[/bold red]",
1059
- border_style=style if style != "output" else "red",
1060
- expand=True,
1061
- padding=(0, 1),
1062
- box=ROUNDED
1063
- ))
1064
- else:
1065
- print("\nOutput:")
1066
- print(output)
1067
-
1068
- def print_error(self, error: str) -> None:
1069
- """Print error message with styling.
1070
-
1071
- Args:
1072
- error (str): Error message to print
1073
- """
1074
- if self.prettify:
1075
- console.print(f"\n Error:", style="error bold")
1076
- console.print(error, style="error")
1077
- else:
1078
- print("\nError:")
1079
- print(error)
1080
-
1081
- def print_table(self, headers: list, rows: list) -> None:
1082
- """Print data in a formatted table.
1083
-
1084
- Args:
1085
- headers (list): Table headers
1086
- rows (list): Table rows
1087
- """
1088
- if not self.prettify:
1089
- # Simple ASCII table
1090
- print("\n" + "-" * 80)
1091
- print("| " + " | ".join(headers) + " |")
1092
- print("-" * 80)
1093
- for row in rows:
1094
- print("| " + " | ".join(str(cell) for cell in row) + " |")
1095
- print("-" * 80)
1096
- return
1097
-
1098
- table = Table(show_header=True, header_style="bold cyan")
1099
- for header in headers:
1100
- table.add_column(header)
1101
-
1102
- for row in rows:
1103
- table.add_row(*[str(cell) for cell in row])
1104
-
1105
- console.print(table)
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 typing import Optional, Generator, List, Tuple, Dict, Any, NamedTuple
11
+ from rich.panel import Panel
12
+ from rich.syntax import Syntax
13
+ from rich.console import Console
14
+ from rich.markdown import Markdown
15
+ from rich.table import Table
16
+ from rich.theme import Theme
17
+ from rich.live import Live
18
+ from rich.box import ROUNDED
19
+ from .autocoder_utiles import get_intro_prompt
20
+ # Initialize LitLogger with custom format and colors
21
+ default_path = tempfile.mkdtemp(prefix="webscout_autocoder")
22
+
23
+ # Custom theme for consistent styling
24
+ CUSTOM_THEME = Theme({
25
+ "info": "cyan",
26
+ "warning": "yellow",
27
+ "error": "red bold",
28
+ "success": "green",
29
+ "code": "blue",
30
+ "output": "white",
31
+ })
32
+
33
+ console = Console(theme=CUSTOM_THEME)
34
+ class CommandResult(NamedTuple):
35
+ """Result of a system command execution."""
36
+ success: bool
37
+ stdout: str
38
+ stderr: str
39
+
40
+ def run_system_command(
41
+ command: str,
42
+ exit_on_error: bool = False,
43
+ stdout_error: bool = False,
44
+ help: Optional[str] = None
45
+ ) -> Tuple[bool, CommandResult]:
46
+ """Execute a system command and return the result.
47
+
48
+ Args:
49
+ command (str): Command to execute
50
+ exit_on_error (bool): Whether to exit on error. Defaults to False.
51
+ stdout_error (bool): Whether to include stdout in error messages. Defaults to False.
52
+ help (str, optional): Help message for errors. Defaults to None.
53
+
54
+ Returns:
55
+ Tuple[bool, CommandResult]: Success status and command result containing stdout/stderr
56
+ """
57
+ try:
58
+ # Execute command and capture output
59
+ process = subprocess.Popen(
60
+ command,
61
+ stdout=subprocess.PIPE,
62
+ stderr=subprocess.PIPE,
63
+ shell=True,
64
+ text=True
65
+ )
66
+
67
+ # Get stdout and stderr
68
+ stdout, stderr = process.communicate()
69
+ success = process.returncode == 0
70
+
71
+ # Create result object
72
+ result = CommandResult(
73
+ success=success,
74
+ stdout=stdout.strip() if stdout else "",
75
+ stderr=stderr.strip() if stderr else ""
76
+ )
77
+
78
+ # Handle errors if needed
79
+ if not success and exit_on_error:
80
+ error_msg = stderr if stderr else stdout if stdout_error else "Command failed"
81
+ if help:
82
+ error_msg += f"\n{help}"
83
+ sys.exit(error_msg)
84
+
85
+ return success, result
86
+
87
+ except Exception as e:
88
+ # Handle execution errors
89
+ error_msg = str(e)
90
+ if help:
91
+ error_msg += f"\n{help}"
92
+
93
+ if exit_on_error:
94
+ sys.exit(error_msg)
95
+
96
+ return False, CommandResult(success=False, stdout="", stderr=error_msg)
97
+
98
+
99
+ class AutoCoder:
100
+ """Generate and auto-execute Python scripts in the CLI with advanced error handling and retry logic.
101
+
102
+ This class provides:
103
+ - Automatic code generation
104
+ - Script execution with safety checks
105
+ - Advanced error handling and retries
106
+ - Beautiful logging with rich console
107
+ - Execution result capture and display
108
+
109
+ Examples:
110
+ >>> coder = AutoCoder()
111
+ >>> coder.execute("Get system info")
112
+ Generating system info script...
113
+ Script executed successfully!
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ quiet: bool = False,
119
+ internal_exec: bool = False,
120
+ confirm_script: bool = False,
121
+ interpreter: str = "python",
122
+ prettify: bool = True,
123
+ path_to_script: str = "",
124
+ max_retries: int = 3,
125
+ ai_instance = None
126
+ ):
127
+ """Initialize AutoCoder instance.
128
+
129
+ Args:
130
+ quiet (bool): Flag to control logging. Defaults to False.
131
+ internal_exec (bool): Execute scripts with exec function. Defaults to False.
132
+ confirm_script (bool): Give consent to scripts prior to execution. Defaults to False.
133
+ interpreter (str): Python's interpreter name. Defaults to "python".
134
+ prettify (bool): Prettify the code on stdout. Defaults to True.
135
+ path_to_script (str): Path to save generated scripts. Defaults to "".
136
+ max_retries (int): Maximum number of retry attempts. Defaults to 3.
137
+ ai_instance: AI instance for error correction. Defaults to None.
138
+ """
139
+ self.internal_exec = internal_exec
140
+ self.confirm_script = confirm_script
141
+ self.quiet = quiet
142
+ self.interpreter = interpreter
143
+ self.prettify = prettify
144
+ self.path_to_script = path_to_script or os.path.join(default_path, "execute_this.py")
145
+ self.max_retries = max_retries
146
+ self.tried_solutions = set()
147
+ self.ai_instance = ai_instance
148
+ self.last_execution_result = ""
149
+
150
+ # Get Python version with enhanced logging
151
+ if self.internal_exec:
152
+ self.python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
153
+ else:
154
+ version_output = run_system_command(
155
+ f"{self.interpreter} --version",
156
+ exit_on_error=True,
157
+ stdout_error=True,
158
+ help="If you're using Webscout-cli, use the flag '--internal-exec'"
159
+ )[1].stdout
160
+ self.python_version = version_output.split(" ")[1]
161
+
162
+
163
+
164
+ def _extract_code_blocks(self, response: str) -> List[Tuple[str, str]]:
165
+ """Extract code blocks from a response string.
166
+
167
+ Args:
168
+ response (str): Response string containing code blocks
169
+
170
+ Returns:
171
+ List[Tuple[str, str]]: List of (code_type, code) tuples
172
+ """
173
+ blocks = []
174
+
175
+ # First try to find code blocks with explicit language tags
176
+ pattern = r"```(\w+)\n(.*?)```"
177
+ matches = re.finditer(pattern, response, re.DOTALL)
178
+
179
+ for match in matches:
180
+ code_type = match.group(1).lower()
181
+ code = match.group(2).strip()
182
+
183
+ # Check if this is a shell command (starts with !)
184
+ if code_type == 'bash' or code_type == 'shell' or code.startswith('!'):
185
+ blocks.append(('shell', code))
186
+ else:
187
+ blocks.append((code_type, code))
188
+
189
+ # If no explicit code blocks found with language tags, try generic code blocks
190
+ if not blocks:
191
+ pattern = r"```(.*?)```"
192
+ matches = re.finditer(pattern, response, re.DOTALL)
193
+ for match in matches:
194
+ code = match.group(1).strip()
195
+
196
+ # Check if this is a shell command (starts with !)
197
+ if code.startswith('!'):
198
+ blocks.append(('shell', code))
199
+ else:
200
+ blocks.append(('python', code))
201
+
202
+ # If still no code blocks found, treat as raw Python code
203
+ if not blocks:
204
+ lines = [line.strip() for line in response.split('\n') if line.strip()]
205
+ if lines:
206
+ # Check if this is a shell command (starts with !)
207
+ if lines[0].startswith('!'):
208
+ blocks.append(('shell', '\n'.join(lines)))
209
+ else:
210
+ blocks.append(('python', '\n'.join(lines)))
211
+
212
+ return blocks
213
+
214
+ def _execute_code_block(self, code_type: str, code: str, ai_instance=None) -> Tuple[bool, str]:
215
+ """Execute a code block.
216
+
217
+ Args:
218
+ code_type (str): Type of code block ('python' or 'shell')
219
+ code (str): Code to execute
220
+ ai_instance: Optional AI instance for error correction
221
+
222
+ Returns:
223
+ Tuple[bool, str]: (Success status, Error message or execution result)
224
+ """
225
+ try:
226
+ # Handle shell commands (starting with !)
227
+ if code_type == 'shell':
228
+ # Remove the leading '!' from each line
229
+ shell_commands = []
230
+ for line in code.split('\n'):
231
+ if line.startswith('!'):
232
+ shell_commands.append(line[1:].strip()) # Remove the '!' and any leading whitespace
233
+ else:
234
+ shell_commands.append(line.strip())
235
+
236
+ # Execute each shell command
237
+ overall_success = True
238
+ overall_result = []
239
+
240
+ # Display the shell command in Jupyter-style UI
241
+ if self.prettify:
242
+ # Format the command for display
243
+ cmd_display = '\n'.join([f"!{cmd}" for cmd in shell_commands if cmd])
244
+ syntax = Syntax(cmd_display, "bash", theme="monokai", line_numbers=True)
245
+ console.print(Panel(
246
+ syntax,
247
+ title="[bold blue]In [1]:[/bold blue]",
248
+ border_style="blue",
249
+ expand=True,
250
+ box=ROUNDED
251
+ ))
252
+
253
+ for cmd in shell_commands:
254
+ if not cmd: # Skip empty commands
255
+ continue
256
+
257
+ success, result = run_system_command(cmd)
258
+
259
+ if success:
260
+ if result.stdout:
261
+ overall_result.append(result.stdout)
262
+
263
+ # Display the output in Jupyter-style UI
264
+ if self.prettify:
265
+ console.print(Panel(
266
+ result.stdout,
267
+ title="[bold red]Out [1]:[/bold red]",
268
+ border_style="red",
269
+ expand=True,
270
+ padding=(0, 1),
271
+ box=ROUNDED
272
+ ))
273
+
274
+ self.last_execution_result = '\n'.join(overall_result)
275
+ else:
276
+ error_msg = result.stderr if result.stderr else f"Command failed: {cmd}"
277
+
278
+ # Display the error in Jupyter-style UI
279
+ if self.prettify:
280
+ console.print(Panel(
281
+ f"Error: {error_msg}",
282
+ title="[bold red]Out [1]:[/bold red]",
283
+ border_style="red",
284
+ expand=True,
285
+ padding=(0, 1),
286
+ box=ROUNDED
287
+ ))
288
+
289
+ return False, error_msg
290
+
291
+ return True, self.last_execution_result
292
+ else:
293
+ # Handle Python code
294
+ result = self._execute_with_retry(code, ai_instance)
295
+ if result is None:
296
+ return True, self.last_execution_result
297
+ return False, result
298
+ except Exception as e:
299
+ return False, str(e)
300
+
301
+ def _format_output_panel(self, code: str, output_lines: list) -> Panel:
302
+ """Format code and output into a single panel.
303
+
304
+ Args:
305
+ code (str): The code that was executed
306
+ output_lines (list): List of output lines
307
+
308
+ Returns:
309
+ Panel: Formatted panel with code and output
310
+ """
311
+ # Format output
312
+ output_text = "\n".join(output_lines) if output_lines else "Running..."
313
+
314
+ # Create panel with Jupyter-like styling
315
+ panel = Panel(
316
+ output_text,
317
+ title="[bold red]Out [1]:[/bold red]",
318
+ border_style="red",
319
+ expand=True,
320
+ padding=(0, 1),
321
+ box=ROUNDED
322
+ )
323
+
324
+ return panel
325
+
326
+ def _format_result_panel(self, output: str) -> Panel:
327
+ """Format execution result into a panel.
328
+
329
+ Args:
330
+ output (str): Execution output text
331
+
332
+ Returns:
333
+ Panel: Formatted panel with execution result
334
+ """
335
+ # Create panel with Jupyter-like styling
336
+ panel = Panel(
337
+ output,
338
+ title="[bold red]Out [1]:[/bold red]",
339
+ border_style="red",
340
+ expand=True,
341
+ padding=(0, 1),
342
+ box=ROUNDED
343
+ )
344
+
345
+ return panel
346
+
347
+ def _stream_output(self, process: subprocess.Popen) -> Generator[str, None, None]:
348
+ """Stream output from a subprocess in realtime.
349
+
350
+ Args:
351
+ process: Subprocess to stream output from
352
+
353
+ Yields:
354
+ str: Lines of output
355
+ """
356
+ # Stream stdout
357
+ output_lines = []
358
+ for line in process.stdout:
359
+ decoded_line = line.decode('utf-8').strip() if isinstance(line, bytes) else line.strip()
360
+ if decoded_line:
361
+ output_lines.append(decoded_line)
362
+ yield decoded_line
363
+
364
+ # Check stderr
365
+ error = process.stderr.read() if process.stderr else None
366
+ if error:
367
+ error_str = error.decode('utf-8').strip() if isinstance(error, bytes) else error.strip()
368
+ if error_str:
369
+ yield f"Error: {error_str}"
370
+ output_lines.append(f"Error: {error_str}")
371
+
372
+ # Store the full execution result
373
+ self.last_execution_result = "\n".join(output_lines)
374
+
375
+ def _execute_with_retry(self, code: str, ai_instance=None) -> Optional[str]:
376
+ """Execute code with retry logic and error correction.
377
+
378
+ Args:
379
+ code (str): Code to execute
380
+ ai_instance: Optional AI instance for error correction
381
+
382
+ Returns:
383
+ Optional[str]: Error message if execution failed, None if successful
384
+ """
385
+ last_error = None
386
+ retries = 0
387
+
388
+ # Add the solution to tried solutions
389
+ self.tried_solutions.add(code)
390
+
391
+ # Print the code first
392
+ if self.prettify:
393
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
394
+ console.print(Panel(
395
+ syntax,
396
+ title="[bold blue]In [1]:[/bold blue]",
397
+ border_style="blue",
398
+ expand=True,
399
+ box=ROUNDED
400
+ ))
401
+
402
+ while retries < self.max_retries:
403
+ try:
404
+ if self.path_to_script:
405
+ script_dir = os.path.dirname(self.path_to_script)
406
+ if script_dir:
407
+ os.makedirs(script_dir, exist_ok=True)
408
+ with open(self.path_to_script, "w", encoding="utf-8") as f:
409
+ f.write(code)
410
+
411
+ if self.internal_exec:
412
+ # Create StringIO for output capture
413
+ import io
414
+ import sys
415
+ stdout = io.StringIO()
416
+ stderr = io.StringIO()
417
+
418
+ # Create a queue for realtime output
419
+ output_queue = queue.Queue()
420
+ output_lines = []
421
+
422
+ def execute_code():
423
+ try:
424
+ # Create a local namespace
425
+ local_namespace: Dict[str, Any] = {}
426
+
427
+ # Redirect stdout/stderr
428
+ sys.stdout = stdout
429
+ sys.stderr = stderr
430
+
431
+ # Execute the code
432
+ exec(code, globals(), local_namespace)
433
+
434
+ # Get any output
435
+ output = stdout.getvalue()
436
+ error = stderr.getvalue()
437
+
438
+ if error:
439
+ output_queue.put(("error", error))
440
+ if output:
441
+ output_queue.put(("output", output))
442
+
443
+ except Exception as e:
444
+ output_queue.put(("error", str(e)))
445
+ finally:
446
+ # Restore stdout/stderr
447
+ sys.stdout = sys.__stdout__
448
+ sys.stderr = sys.__stderr__
449
+
450
+ # Create and start execution thread
451
+ thread = threading.Thread(target=execute_code)
452
+ thread.daemon = True # Make thread daemon to avoid hanging
453
+ thread.start()
454
+
455
+ # Display output in realtime
456
+ with Live(auto_refresh=True) as live:
457
+ timeout_counter = 0
458
+ while thread.is_alive() or not output_queue.empty():
459
+ try:
460
+ msg_type, content = output_queue.get(timeout=0.1)
461
+ if content:
462
+ new_lines = content.splitlines()
463
+ output_lines.extend(new_lines)
464
+ live.update(self._format_output_panel(code, output_lines))
465
+ live.refresh()
466
+ output_queue.task_done()
467
+ except queue.Empty:
468
+ timeout_counter += 1
469
+ # Refresh the display to show it's still running
470
+ if timeout_counter % 10 == 0: # Refresh every ~1 second
471
+ live.update(self._format_output_panel(code, output_lines))
472
+ live.refresh()
473
+ if timeout_counter > 100 and thread.is_alive(): # ~10 seconds
474
+ output_lines.append("Warning: Execution taking longer than expected...")
475
+ live.update(self._format_output_panel(code, output_lines))
476
+ live.refresh()
477
+ continue
478
+
479
+ # Wait for thread to complete with timeout
480
+ thread.join(timeout=30) # 30 second timeout
481
+ if thread.is_alive():
482
+ output_lines.append("Error: Execution timed out after 30 seconds")
483
+ raise TimeoutError("Execution timed out after 30 seconds")
484
+
485
+ # Check for any final errors
486
+ error = stderr.getvalue()
487
+ if error:
488
+ raise Exception(error)
489
+
490
+ # Store the full execution result
491
+ self.last_execution_result = stdout.getvalue()
492
+
493
+ else:
494
+ try:
495
+ process = subprocess.Popen(
496
+ [self.interpreter, self.path_to_script],
497
+ stdout=subprocess.PIPE,
498
+ stderr=subprocess.PIPE,
499
+ text=True, # Use text mode to avoid encoding issues
500
+ bufsize=1,
501
+ )
502
+
503
+ output_lines = []
504
+ # Stream output in realtime
505
+ with Live(auto_refresh=True) as live:
506
+ for line in self._stream_output(process):
507
+ output_lines.append(line)
508
+ live.update(self._format_output_panel(code, output_lines))
509
+ live.refresh()
510
+
511
+ process.wait(timeout=30) # 30 second timeout
512
+
513
+ if process.returncode != 0:
514
+ # Try to read more detailed error information
515
+ if process.stderr:
516
+ error = process.stderr.read()
517
+ error_str = error.strip() if error else ""
518
+ if error_str:
519
+ raise Exception(error_str)
520
+ raise Exception(f"Process exited with code {process.returncode}")
521
+
522
+ # Store the full execution result
523
+ self.last_execution_result = "\n".join(output_lines)
524
+
525
+ except subprocess.TimeoutExpired:
526
+ # Handle the case where the process times out
527
+ if process:
528
+ process.kill()
529
+ raise TimeoutError("Execution timed out after 30 seconds")
530
+
531
+ return None
532
+
533
+ except Exception as e:
534
+ last_error = e
535
+ if retries < self.max_retries - 1 and ai_instance:
536
+ try:
537
+ # First try to handle import errors
538
+ if isinstance(e, ImportError):
539
+ fixed_code = self._handle_import_error(e, code)
540
+ if fixed_code:
541
+ code = fixed_code
542
+ retries += 1
543
+ continue
544
+
545
+ # Get error context and try to fix the specific error
546
+ error_context = self._get_error_context(e, code)
547
+ fixed_response = ai_instance.chat(error_context)
548
+ fixed_code = self._extract_code_from_response(fixed_response)
549
+
550
+ if not fixed_code:
551
+ # If no code found, try a more general approach
552
+ general_context = f"""
553
+ The code failed with error: {str(e)}
554
+
555
+ Original Code:
556
+ ```python
557
+ {code}
558
+ ```
559
+
560
+ Please provide a complete, corrected version of the code that handles this error. The code should:
561
+ 1. Handle any potential encoding issues
562
+ 2. Include proper error handling
563
+ 3. Use appropriate libraries and imports
564
+ 4. Be compatible with the current Python environment
565
+ 5. Fix the specific error: {str(e)}
566
+
567
+ Provide only the corrected code without any explanation.
568
+ """
569
+ fixed_response = ai_instance.chat(general_context)
570
+ fixed_code = self._extract_code_from_response(fixed_response)
571
+
572
+ if not fixed_code:
573
+ break
574
+
575
+ if self._is_similar_solution(fixed_code):
576
+ # If solution is too similar, try a different approach
577
+ different_context = f"""
578
+ Previous solutions were not successful. The code failed with error: {str(e)}
579
+
580
+ Original Code:
581
+ ```python
582
+ {code}
583
+ ```
584
+
585
+ Please provide a significantly different approach to solve this problem. Consider:
586
+ 1. Using alternative libraries or methods
587
+ 2. Implementing a different algorithm
588
+ 3. Adding more robust error handling
589
+ 4. Using a different encoding or data handling approach
590
+ 5. Specifically address the error: {str(e)}
591
+
592
+ Provide only the corrected code without any explanation.
593
+ """
594
+ fixed_response = ai_instance.chat(different_context)
595
+ fixed_code = self._extract_code_from_response(fixed_response)
596
+
597
+ if self._is_similar_solution(fixed_code):
598
+ break
599
+
600
+ # Update code and continue with retry
601
+ code = fixed_code
602
+ self.tried_solutions.add(code)
603
+ retries += 1
604
+ continue
605
+
606
+ except Exception as ai_error:
607
+ console.print(f"Error during AI correction: {str(ai_error)}", style="error")
608
+ break
609
+ break
610
+
611
+ return str(last_error) if last_error else "Unknown error occurred"
612
+
613
+ def execute(self, prompt: str, ai_instance=None) -> bool:
614
+ """Execute the given prompt using the appropriate executor.
615
+
616
+ Args:
617
+ prompt (str): Prompt to execute
618
+ ai_instance: Optional AI instance for error correction
619
+
620
+ Returns:
621
+ bool: True if execution was successful, False otherwise
622
+ """
623
+ try:
624
+ # Check if this is a direct shell command (starts with !)
625
+ if prompt.strip().startswith('!'):
626
+ # Handle shell command
627
+ cmd = prompt.strip()[1:].strip() # Remove the '!' and any leading whitespace
628
+
629
+ # Display the shell command in Jupyter-style UI
630
+ if self.prettify:
631
+ syntax = Syntax(f"!{cmd}", "bash", theme="monokai", line_numbers=True)
632
+ console.print(Panel(
633
+ syntax,
634
+ title="[bold blue]In [1]:[/bold blue]",
635
+ border_style="blue",
636
+ expand=True,
637
+ box=ROUNDED
638
+ ))
639
+
640
+ success, result = run_system_command(cmd)
641
+
642
+ if success:
643
+ if result.stdout:
644
+ # Display the output in Jupyter-style UI
645
+ if self.prettify:
646
+ console.print(Panel(
647
+ result.stdout,
648
+ title="[bold red]Out [1]:[/bold red]",
649
+ border_style="red",
650
+ expand=True,
651
+ padding=(0, 1),
652
+ box=ROUNDED
653
+ ))
654
+ else:
655
+ console.print(result.stdout, style="output")
656
+ self.last_execution_result = result.stdout
657
+ return True
658
+ else:
659
+ error_msg = result.stderr if result.stderr else f"Command failed: {cmd}"
660
+ # Display the error in Jupyter-style UI
661
+ if self.prettify:
662
+ console.print(Panel(
663
+ f"Error: {error_msg}",
664
+ title="[bold red]Out [1]:[/bold red]",
665
+ border_style="red",
666
+ expand=True,
667
+ padding=(0, 1),
668
+ box=ROUNDED
669
+ ))
670
+ else:
671
+ console.print(error_msg, style="error")
672
+ return False
673
+
674
+ # Extract code blocks
675
+ code_blocks = self._extract_code_blocks(prompt)
676
+ if not code_blocks:
677
+ console.print("No executable code found in the prompt", style="warning")
678
+ return False
679
+
680
+ # Execute each code block
681
+ overall_success = True
682
+ for code_type, code in code_blocks:
683
+ success, result = self._execute_code_block(code_type, code, ai_instance)
684
+
685
+ if not success:
686
+ console.print(f"Execution failed: {result}", style="error")
687
+ overall_success = False
688
+
689
+ return overall_success
690
+
691
+ except Exception as e:
692
+ console.print(f"Error in execution: {str(e)}", style="error")
693
+ return False
694
+
695
+ def _extract_code_from_response(self, response: str) -> str:
696
+ """Extract code from AI response.
697
+
698
+ Args:
699
+ response (str): AI response containing code blocks
700
+
701
+ Returns:
702
+ str: Extracted code from the first code block
703
+ """
704
+ if not response:
705
+ return ""
706
+
707
+ # First try to find code blocks with explicit language tags
708
+ code_blocks = self._extract_code_blocks(response)
709
+ if code_blocks:
710
+ # Return the content of the first code block
711
+ return code_blocks[0][1]
712
+
713
+ # If no code blocks found, try to find raw Python code or shell commands
714
+ lines = []
715
+ for line in response.split('\n'):
716
+ line = line.strip()
717
+ if not line:
718
+ continue
719
+
720
+ # Skip markdown headers and other non-code lines
721
+ if line.startswith(('#', '```', '---', '===', '>>>')):
722
+ continue
723
+
724
+ # Skip common non-code lines
725
+ if any(line.startswith(prefix) for prefix in ['Please', 'Here', 'The', 'This', 'You']):
726
+ continue
727
+
728
+ lines.append(line)
729
+
730
+ if lines:
731
+ return '\n'.join(lines)
732
+
733
+ return ""
734
+
735
+ def _get_error_context(self, error: Exception, code: str) -> str:
736
+ """Create context about the error for AI correction.
737
+
738
+ Args:
739
+ error (Exception): The caught exception
740
+ code (str): The code that caused the error
741
+
742
+ Returns:
743
+ str: Formatted error context for AI
744
+ """
745
+ error_type = type(error).__name__
746
+ error_msg = str(error)
747
+
748
+ # Get Python version and environment info
749
+ python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
750
+ platform = sys.platform
751
+
752
+ # Get the line number where the error occurred if available
753
+ import traceback
754
+ tb = traceback.extract_tb(error.__traceback__)
755
+ line_info = ""
756
+ if tb:
757
+ line_info = f"\nError occurred at line {tb[-1].lineno}"
758
+
759
+ return f"""
760
+ The code failed with error:
761
+ Error Type: {error_type}
762
+ Error Message: {error_msg}{line_info}
763
+
764
+ Environment:
765
+ Python Version: {python_version}
766
+ Platform: {platform}
767
+
768
+ Original Code:
769
+ ```python
770
+ {code}
771
+ ```
772
+
773
+ Please fix the code to handle this error. The solution should:
774
+ 1. Address the specific error: {error_msg}
775
+ 2. Be compatible with Python {python_version}
776
+ 3. Work on {platform}
777
+ 4. Include proper error handling
778
+ 5. Use appropriate libraries and imports
779
+
780
+ Provide only the corrected code without any explanation.
781
+ """
782
+
783
+ def _handle_import_error(self, error: ImportError, code: str) -> Optional[str]:
784
+ """Handle missing package errors by attempting to install them.
785
+
786
+ Args:
787
+ error (ImportError): The import error
788
+ code (str): The code that caused the error
789
+
790
+ Returns:
791
+ Optional[str]: Fixed code or None if installation failed
792
+ """
793
+ try:
794
+ missing_package = str(error).split("'")[1] if "'" in str(error) else str(error).split("No module named")[1].strip()
795
+ missing_package = missing_package.replace("'", "").strip()
796
+
797
+ console.print(f"Installing missing package: {missing_package}", style="info")
798
+ result = subprocess.run(
799
+ [sys.executable, "-m", "pip", "install", missing_package],
800
+ capture_output=True,
801
+ text=True
802
+ )
803
+ if result.returncode == 0:
804
+ console.print(f"Successfully installed {missing_package}", style="success")
805
+ return code # Retry with same code after installing package
806
+ else:
807
+ raise Exception(f"Failed to install {missing_package}: {result.stderr}")
808
+ except Exception as e:
809
+ console.print(f"Error installing package: {str(e)}", style="error")
810
+ return None
811
+
812
+ def _is_similar_solution(self, new_code: str, threshold: float = 0.8) -> bool:
813
+ """Check if the new solution is too similar to previously tried ones.
814
+
815
+ Args:
816
+ new_code (str): New solution to check
817
+ threshold (float): Similarity threshold (0-1). Defaults to 0.8.
818
+
819
+ Returns:
820
+ bool: True if solution is too similar to previous attempts
821
+ """
822
+ import difflib
823
+
824
+ def normalize_code(code: str) -> str:
825
+ lines = [line.split('#')[0].strip() for line in code.split('\n')]
826
+ return '\n'.join(line for line in lines if line)
827
+
828
+ new_code_norm = normalize_code(new_code)
829
+
830
+ for tried_code in self.tried_solutions:
831
+ tried_code_norm = normalize_code(tried_code)
832
+ similarity = difflib.SequenceMatcher(None, new_code_norm, tried_code_norm).ratio()
833
+ if similarity > threshold:
834
+ return True
835
+ return False
836
+
837
+ def main(self, response: str) -> Optional[str]:
838
+ """Execute code with error correction.
839
+
840
+ Args:
841
+ response (str): AI response containing code
842
+
843
+ Returns:
844
+ Optional[str]: Error message if execution failed, None if successful
845
+ """
846
+ if not response:
847
+ return "No response provided"
848
+
849
+ # Check if this is a shell command (starts with !)
850
+ if response.strip().startswith('!'):
851
+ # Handle shell command
852
+ cmd = response.strip()[1:].strip() # Remove the '!' and any leading whitespace
853
+
854
+ # Display the shell command in Jupyter-style UI
855
+ if self.prettify:
856
+ syntax = Syntax(f"!{cmd}", "bash", theme="monokai", line_numbers=True)
857
+ console.print(Panel(
858
+ syntax,
859
+ title="[bold blue]In [1]:[/bold blue]",
860
+ border_style="blue",
861
+ expand=True,
862
+ box=ROUNDED
863
+ ))
864
+
865
+ success, result = run_system_command(cmd)
866
+
867
+ if success:
868
+ if result.stdout:
869
+ # Display the output in Jupyter-style UI
870
+ if self.prettify:
871
+ console.print(Panel(
872
+ result.stdout,
873
+ title="[bold red]Out [1]:[/bold red]",
874
+ border_style="red",
875
+ expand=True,
876
+ padding=(0, 1),
877
+ box=ROUNDED
878
+ ))
879
+ self.last_execution_result = result.stdout
880
+ return None
881
+ else:
882
+ error_msg = result.stderr if result.stderr else f"Command failed: {cmd}"
883
+ # Display the error in Jupyter-style UI
884
+ if self.prettify:
885
+ console.print(Panel(
886
+ f"Error: {error_msg}",
887
+ title="[bold red]Out [1]:[/bold red]",
888
+ border_style="red",
889
+ expand=True,
890
+ padding=(0, 1),
891
+ box=ROUNDED
892
+ ))
893
+ else:
894
+ console.print(error_msg, style="error")
895
+ return error_msg
896
+
897
+ # Extract code blocks
898
+ code_blocks = self._extract_code_blocks(response)
899
+ if code_blocks:
900
+ code_type, code = code_blocks[0]
901
+
902
+ # Handle shell commands
903
+ if code_type == 'shell':
904
+ success, result = self._execute_code_block(code_type, code)
905
+ if success:
906
+ return None
907
+ else:
908
+ # Error is already displayed in _execute_code_block
909
+ return result
910
+
911
+ # Handle regular Python code
912
+ code = self._extract_code_from_response(response)
913
+ if not code:
914
+ return "No executable code found in the response"
915
+
916
+ ai_instance = self.ai_instance or globals().get('ai')
917
+
918
+ if not ai_instance:
919
+ console.print("AI instance not found, error correction disabled", style="warning")
920
+ try:
921
+ if self.path_to_script:
922
+ script_dir = os.path.dirname(self.path_to_script)
923
+ if script_dir:
924
+ os.makedirs(script_dir, exist_ok=True)
925
+ with open(self.path_to_script, "w", encoding="utf-8") as f:
926
+ f.write(code)
927
+
928
+ if self.internal_exec:
929
+ console.print("[INFO] Executing code internally", style="info")
930
+ # Create a local namespace
931
+ local_namespace: Dict[str, Any] = {}
932
+
933
+ # Capture stdout
934
+ import io
935
+ old_stdout = sys.stdout
936
+ captured_output = io.StringIO()
937
+ sys.stdout = captured_output
938
+
939
+ # Execute the code
940
+ try:
941
+ exec(code, globals(), local_namespace)
942
+ # Capture the result
943
+ self.last_execution_result = captured_output.getvalue()
944
+ finally:
945
+ # Restore stdout
946
+ sys.stdout = old_stdout
947
+ else:
948
+ console.print("[INFO] Executing code as external process", style="info")
949
+ result = subprocess.run(
950
+ [self.interpreter, self.path_to_script],
951
+ capture_output=True,
952
+ text=True
953
+ )
954
+ self.last_execution_result = result.stdout
955
+
956
+ if result.returncode != 0:
957
+ raise Exception(result.stderr or result.stdout)
958
+
959
+ return None
960
+ except Exception as e:
961
+ error_msg = f"Execution error: {str(e)}"
962
+ console.print(error_msg, style="error")
963
+ return error_msg
964
+
965
+ result = self._execute_with_retry(code, ai_instance)
966
+ return result
967
+
968
+ @property
969
+ def intro_prompt(self) -> str:
970
+ """Get the introduction prompt.
971
+
972
+ Returns:
973
+ str: Introduction prompt
974
+ """
975
+ return get_intro_prompt()
976
+
977
+ def log(self, message: str, category: str = "info"):
978
+ """RawDog logger
979
+
980
+ Args:
981
+ message (str): Log message
982
+ category (str, optional): Log level. Defaults to 'info'.
983
+ """
984
+ if self.quiet:
985
+ return
986
+
987
+ message = "[Webscout] - " + message
988
+ if category == "error":
989
+ console.print(f"[ERROR] {message}", style="error")
990
+ else:
991
+ console.print(message, style=category)
992
+
993
+ def stdout(self, message: str, style: str = "info") -> None:
994
+ """Enhanced stdout with Rich formatting.
995
+
996
+ Args:
997
+ message (str): Text to be printed
998
+ style (str, optional): Style to apply. Defaults to "info".
999
+ """
1000
+ if not self.prettify:
1001
+ print(message)
1002
+ return
1003
+
1004
+ if message.startswith("```") and message.endswith("```"):
1005
+ # Handle code blocks
1006
+ code = message.strip("`").strip()
1007
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
1008
+ console.print(Panel(syntax, title="Code", border_style="blue"))
1009
+ elif "```python" in message:
1010
+ # Handle markdown code blocks
1011
+ md = Markdown(message)
1012
+ console.print(md)
1013
+ else:
1014
+ # Handle regular text with optional styling
1015
+ console.print(message, style=style)
1016
+
1017
+ def print_code(self, code: str, title: str = "Generated Code") -> None:
1018
+ """Print code with syntax highlighting and panel.
1019
+
1020
+ Args:
1021
+ code (str): Code to print
1022
+ title (str, optional): Panel title. Defaults to "Generated Code".
1023
+ """
1024
+ if self.prettify:
1025
+ syntax = Syntax(code, "python", theme="monokai", line_numbers=True)
1026
+ console.print(Panel(
1027
+ syntax,
1028
+ title=f"[bold blue]In [1]:[/bold blue]",
1029
+ border_style="blue",
1030
+ expand=True,
1031
+ box=ROUNDED
1032
+ ))
1033
+ else:
1034
+ print(f"\n{title}:")
1035
+ print(code)
1036
+
1037
+ def print_output(self, output: str, style: str = "output") -> None:
1038
+ """Print command output with optional styling.
1039
+
1040
+ Args:
1041
+ output (str): Output to print
1042
+ style (str, optional): Style to apply. Defaults to "output".
1043
+ """
1044
+ if self.prettify:
1045
+ # Try to detect if output is Python code
1046
+ try:
1047
+ # If it looks like Python code, syntax highlight it
1048
+ compile(output, '<string>', 'exec')
1049
+ syntax = Syntax(output, "python", theme="monokai", line_numbers=False)
1050
+ formatted_output = syntax
1051
+ except SyntaxError:
1052
+ # If not Python code, treat as plain text
1053
+ formatted_output = output
1054
+
1055
+ # Use the style parameter for the panel border
1056
+ console.print(Panel(
1057
+ formatted_output,
1058
+ title="[bold red]Out [1]:[/bold red]",
1059
+ border_style=style if style != "output" else "red",
1060
+ expand=True,
1061
+ padding=(0, 1),
1062
+ box=ROUNDED
1063
+ ))
1064
+ else:
1065
+ print("\nOutput:")
1066
+ print(output)
1067
+
1068
+ def print_error(self, error: str) -> None:
1069
+ """Print error message with styling.
1070
+
1071
+ Args:
1072
+ error (str): Error message to print
1073
+ """
1074
+ if self.prettify:
1075
+ console.print(f"\n Error:", style="error bold")
1076
+ console.print(error, style="error")
1077
+ else:
1078
+ print("\nError:")
1079
+ print(error)
1080
+
1081
+ def print_table(self, headers: list, rows: list) -> None:
1082
+ """Print data in a formatted table.
1083
+
1084
+ Args:
1085
+ headers (list): Table headers
1086
+ rows (list): Table rows
1087
+ """
1088
+ if not self.prettify:
1089
+ # Simple ASCII table
1090
+ print("\n" + "-" * 80)
1091
+ print("| " + " | ".join(headers) + " |")
1092
+ print("-" * 80)
1093
+ for row in rows:
1094
+ print("| " + " | ".join(str(cell) for cell in row) + " |")
1095
+ print("-" * 80)
1096
+ return
1097
+
1098
+ table = Table(show_header=True, header_style="bold cyan")
1099
+ for header in headers:
1100
+ table.add_column(header)
1101
+
1102
+ for row in rows:
1103
+ table.add_row(*[str(cell) for cell in row])
1104
+
1105
+ console.print(table)