webscout 8.3.6__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 (304) hide show
  1. webscout/AIauto.py +250 -250
  2. webscout/AIbase.py +379 -379
  3. webscout/AIutel.py +60 -58
  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 +33 -11
  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 -314
  61. webscout/Provider/AISEARCH/stellar_search.py +177 -177
  62. webscout/Provider/AISEARCH/webpilotai_search.py +255 -255
  63. webscout/Provider/Aitopia.py +314 -315
  64. webscout/Provider/Andi.py +3 -3
  65. webscout/Provider/Apriel.py +306 -0
  66. webscout/Provider/ChatGPTClone.py +236 -236
  67. webscout/Provider/ChatSandbox.py +343 -342
  68. webscout/Provider/Cloudflare.py +324 -324
  69. webscout/Provider/Cohere.py +208 -207
  70. webscout/Provider/Deepinfra.py +370 -369
  71. webscout/Provider/ExaAI.py +260 -260
  72. webscout/Provider/ExaChat.py +308 -387
  73. webscout/Provider/Flowith.py +221 -221
  74. webscout/Provider/GMI.py +293 -0
  75. webscout/Provider/Gemini.py +164 -162
  76. webscout/Provider/GeminiProxy.py +167 -166
  77. webscout/Provider/GithubChat.py +371 -370
  78. webscout/Provider/Groq.py +800 -800
  79. webscout/Provider/HeckAI.py +383 -379
  80. webscout/Provider/Jadve.py +282 -297
  81. webscout/Provider/K2Think.py +308 -0
  82. webscout/Provider/Koboldai.py +206 -384
  83. webscout/Provider/LambdaChat.py +423 -425
  84. webscout/Provider/Nemotron.py +244 -245
  85. webscout/Provider/Netwrck.py +248 -247
  86. webscout/Provider/OLLAMA.py +395 -394
  87. webscout/Provider/OPENAI/Cloudflare.py +394 -395
  88. webscout/Provider/OPENAI/FalconH1.py +452 -457
  89. webscout/Provider/OPENAI/FreeGemini.py +297 -299
  90. webscout/Provider/OPENAI/{monochat.py → K2Think.py} +432 -329
  91. webscout/Provider/OPENAI/NEMOTRON.py +241 -244
  92. webscout/Provider/OPENAI/PI.py +428 -427
  93. webscout/Provider/OPENAI/README.md +959 -959
  94. webscout/Provider/OPENAI/TogetherAI.py +345 -345
  95. webscout/Provider/OPENAI/TwoAI.py +466 -467
  96. webscout/Provider/OPENAI/__init__.py +33 -59
  97. webscout/Provider/OPENAI/ai4chat.py +313 -303
  98. webscout/Provider/OPENAI/base.py +249 -269
  99. webscout/Provider/OPENAI/chatglm.py +528 -0
  100. webscout/Provider/OPENAI/chatgpt.py +593 -588
  101. webscout/Provider/OPENAI/chatgptclone.py +521 -524
  102. webscout/Provider/OPENAI/chatsandbox.py +202 -177
  103. webscout/Provider/OPENAI/deepinfra.py +319 -315
  104. webscout/Provider/OPENAI/e2b.py +1665 -1665
  105. webscout/Provider/OPENAI/exaai.py +420 -420
  106. webscout/Provider/OPENAI/exachat.py +452 -452
  107. webscout/Provider/OPENAI/friendli.py +232 -232
  108. webscout/Provider/OPENAI/{refact.py → gmi.py} +324 -274
  109. webscout/Provider/OPENAI/groq.py +364 -364
  110. webscout/Provider/OPENAI/heckai.py +314 -311
  111. webscout/Provider/OPENAI/llmchatco.py +337 -337
  112. webscout/Provider/OPENAI/netwrck.py +355 -354
  113. webscout/Provider/OPENAI/oivscode.py +290 -290
  114. webscout/Provider/OPENAI/opkfc.py +518 -518
  115. webscout/Provider/OPENAI/pydantic_imports.py +1 -1
  116. webscout/Provider/OPENAI/scirachat.py +535 -529
  117. webscout/Provider/OPENAI/sonus.py +308 -308
  118. webscout/Provider/OPENAI/standardinput.py +442 -442
  119. webscout/Provider/OPENAI/textpollinations.py +340 -348
  120. webscout/Provider/OPENAI/toolbaz.py +419 -413
  121. webscout/Provider/OPENAI/typefully.py +362 -362
  122. webscout/Provider/OPENAI/utils.py +295 -295
  123. webscout/Provider/OPENAI/venice.py +436 -436
  124. webscout/Provider/OPENAI/wisecat.py +387 -387
  125. webscout/Provider/OPENAI/writecream.py +166 -166
  126. webscout/Provider/OPENAI/x0gpt.py +378 -378
  127. webscout/Provider/OPENAI/yep.py +389 -389
  128. webscout/Provider/OpenGPT.py +230 -230
  129. webscout/Provider/Openai.py +244 -496
  130. webscout/Provider/PI.py +405 -404
  131. webscout/Provider/Perplexitylabs.py +430 -431
  132. webscout/Provider/QwenLM.py +272 -254
  133. webscout/Provider/STT/__init__.py +32 -2
  134. webscout/Provider/{Llama3.py → Sambanova.py} +257 -258
  135. webscout/Provider/StandardInput.py +309 -309
  136. webscout/Provider/TTI/README.md +82 -82
  137. webscout/Provider/TTI/__init__.py +33 -12
  138. webscout/Provider/TTI/aiarta.py +413 -413
  139. webscout/Provider/TTI/base.py +136 -136
  140. webscout/Provider/TTI/bing.py +243 -243
  141. webscout/Provider/TTI/gpt1image.py +149 -149
  142. webscout/Provider/TTI/imagen.py +196 -196
  143. webscout/Provider/TTI/infip.py +211 -211
  144. webscout/Provider/TTI/magicstudio.py +232 -232
  145. webscout/Provider/TTI/monochat.py +219 -219
  146. webscout/Provider/TTI/piclumen.py +214 -214
  147. webscout/Provider/TTI/pixelmuse.py +232 -232
  148. webscout/Provider/TTI/pollinations.py +232 -232
  149. webscout/Provider/TTI/together.py +288 -288
  150. webscout/Provider/TTI/utils.py +12 -12
  151. webscout/Provider/TTI/venice.py +367 -367
  152. webscout/Provider/TTS/README.md +192 -192
  153. webscout/Provider/TTS/__init__.py +33 -10
  154. webscout/Provider/TTS/parler.py +110 -110
  155. webscout/Provider/TTS/streamElements.py +333 -333
  156. webscout/Provider/TTS/utils.py +280 -280
  157. webscout/Provider/TeachAnything.py +237 -236
  158. webscout/Provider/TextPollinationsAI.py +311 -318
  159. webscout/Provider/TogetherAI.py +356 -357
  160. webscout/Provider/TwoAI.py +313 -569
  161. webscout/Provider/TypliAI.py +312 -311
  162. webscout/Provider/UNFINISHED/ChatHub.py +208 -208
  163. webscout/Provider/UNFINISHED/ChutesAI.py +313 -313
  164. webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +294 -294
  165. webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +198 -198
  166. webscout/Provider/{Qodo.py → UNFINISHED/Qodo.py} +477 -477
  167. webscout/Provider/UNFINISHED/VercelAIGateway.py +338 -338
  168. webscout/Provider/{XenAI.py → UNFINISHED/XenAI.py} +324 -324
  169. webscout/Provider/UNFINISHED/Youchat.py +330 -330
  170. webscout/Provider/UNFINISHED/liner.py +334 -0
  171. webscout/Provider/UNFINISHED/liner_api_request.py +262 -262
  172. webscout/Provider/UNFINISHED/puterjs.py +634 -634
  173. webscout/Provider/UNFINISHED/samurai.py +223 -223
  174. webscout/Provider/UNFINISHED/test_lmarena.py +119 -119
  175. webscout/Provider/Venice.py +251 -250
  176. webscout/Provider/VercelAI.py +256 -255
  177. webscout/Provider/WiseCat.py +232 -231
  178. webscout/Provider/WrDoChat.py +367 -366
  179. webscout/Provider/__init__.py +33 -86
  180. webscout/Provider/ai4chat.py +174 -174
  181. webscout/Provider/akashgpt.py +331 -334
  182. webscout/Provider/cerebras.py +446 -340
  183. webscout/Provider/chatglm.py +394 -214
  184. webscout/Provider/cleeai.py +211 -212
  185. webscout/Provider/deepseek_assistant.py +1 -1
  186. webscout/Provider/elmo.py +282 -282
  187. webscout/Provider/geminiapi.py +208 -208
  188. webscout/Provider/granite.py +261 -261
  189. webscout/Provider/hermes.py +263 -265
  190. webscout/Provider/julius.py +223 -222
  191. webscout/Provider/learnfastai.py +309 -309
  192. webscout/Provider/llama3mitril.py +214 -214
  193. webscout/Provider/llmchat.py +243 -243
  194. webscout/Provider/llmchatco.py +290 -290
  195. webscout/Provider/meta.py +801 -801
  196. webscout/Provider/oivscode.py +309 -309
  197. webscout/Provider/scira_chat.py +384 -457
  198. webscout/Provider/searchchat.py +292 -291
  199. webscout/Provider/sonus.py +258 -258
  200. webscout/Provider/toolbaz.py +370 -364
  201. webscout/Provider/turboseek.py +274 -265
  202. webscout/Provider/typefully.py +208 -207
  203. webscout/Provider/x0gpt.py +1 -0
  204. webscout/Provider/yep.py +372 -371
  205. webscout/__init__.py +30 -31
  206. webscout/__main__.py +5 -5
  207. webscout/auth/api_key_manager.py +189 -189
  208. webscout/auth/config.py +175 -175
  209. webscout/auth/models.py +185 -185
  210. webscout/auth/routes.py +664 -664
  211. webscout/auth/simple_logger.py +236 -236
  212. webscout/cli.py +523 -523
  213. webscout/conversation.py +438 -438
  214. webscout/exceptions.py +361 -361
  215. webscout/litagent/Readme.md +298 -298
  216. webscout/litagent/__init__.py +28 -28
  217. webscout/litagent/agent.py +581 -581
  218. webscout/litagent/constants.py +59 -59
  219. webscout/litprinter/__init__.py +58 -58
  220. webscout/models.py +181 -181
  221. webscout/optimizers.py +419 -419
  222. webscout/prompt_manager.py +288 -288
  223. webscout/sanitize.py +1078 -1078
  224. webscout/scout/README.md +401 -401
  225. webscout/scout/__init__.py +8 -8
  226. webscout/scout/core/__init__.py +6 -6
  227. webscout/scout/core/crawler.py +297 -297
  228. webscout/scout/core/scout.py +706 -706
  229. webscout/scout/core/search_result.py +95 -95
  230. webscout/scout/core/text_analyzer.py +62 -62
  231. webscout/scout/core/text_utils.py +277 -277
  232. webscout/scout/core/web_analyzer.py +51 -51
  233. webscout/scout/element.py +599 -599
  234. webscout/scout/parsers/__init__.py +69 -69
  235. webscout/scout/parsers/html5lib_parser.py +172 -172
  236. webscout/scout/parsers/html_parser.py +236 -236
  237. webscout/scout/parsers/lxml_parser.py +178 -178
  238. webscout/scout/utils.py +37 -37
  239. webscout/swiftcli/Readme.md +323 -323
  240. webscout/swiftcli/__init__.py +95 -95
  241. webscout/swiftcli/core/__init__.py +7 -7
  242. webscout/swiftcli/core/cli.py +308 -308
  243. webscout/swiftcli/core/context.py +104 -104
  244. webscout/swiftcli/core/group.py +241 -241
  245. webscout/swiftcli/decorators/__init__.py +28 -28
  246. webscout/swiftcli/decorators/command.py +221 -221
  247. webscout/swiftcli/decorators/options.py +220 -220
  248. webscout/swiftcli/decorators/output.py +302 -302
  249. webscout/swiftcli/exceptions.py +21 -21
  250. webscout/swiftcli/plugins/__init__.py +9 -9
  251. webscout/swiftcli/plugins/base.py +135 -135
  252. webscout/swiftcli/plugins/manager.py +269 -269
  253. webscout/swiftcli/utils/__init__.py +59 -59
  254. webscout/swiftcli/utils/formatting.py +252 -252
  255. webscout/swiftcli/utils/parsing.py +267 -267
  256. webscout/update_checker.py +117 -117
  257. webscout/version.py +1 -1
  258. webscout/webscout_search.py +1183 -1183
  259. webscout/webscout_search_async.py +649 -649
  260. webscout/yep_search.py +346 -346
  261. webscout/zeroart/README.md +89 -89
  262. webscout/zeroart/__init__.py +134 -134
  263. webscout/zeroart/base.py +66 -66
  264. webscout/zeroart/effects.py +100 -100
  265. webscout/zeroart/fonts.py +1238 -1238
  266. {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/METADATA +937 -936
  267. webscout-2025.10.11.dist-info/RECORD +300 -0
  268. webscout/Provider/AISEARCH/DeepFind.py +0 -254
  269. webscout/Provider/AllenAI.py +0 -440
  270. webscout/Provider/Blackboxai.py +0 -793
  271. webscout/Provider/FreeGemini.py +0 -250
  272. webscout/Provider/GptOss.py +0 -207
  273. webscout/Provider/Hunyuan.py +0 -283
  274. webscout/Provider/Kimi.py +0 -445
  275. webscout/Provider/MCPCore.py +0 -322
  276. webscout/Provider/MiniMax.py +0 -207
  277. webscout/Provider/OPENAI/BLACKBOXAI.py +0 -1045
  278. webscout/Provider/OPENAI/MiniMax.py +0 -298
  279. webscout/Provider/OPENAI/Qwen3.py +0 -304
  280. webscout/Provider/OPENAI/autoproxy.py +0 -1067
  281. webscout/Provider/OPENAI/copilot.py +0 -321
  282. webscout/Provider/OPENAI/gptoss.py +0 -288
  283. webscout/Provider/OPENAI/kimi.py +0 -469
  284. webscout/Provider/OPENAI/mcpcore.py +0 -431
  285. webscout/Provider/OPENAI/multichat.py +0 -378
  286. webscout/Provider/OPENAI/qodo.py +0 -630
  287. webscout/Provider/OPENAI/xenai.py +0 -514
  288. webscout/Provider/Reka.py +0 -214
  289. webscout/Provider/UNFINISHED/fetch_together_models.py +0 -90
  290. webscout/Provider/asksteve.py +0 -220
  291. webscout/Provider/copilot.py +0 -441
  292. webscout/Provider/freeaichat.py +0 -294
  293. webscout/Provider/koala.py +0 -182
  294. webscout/Provider/lmarena.py +0 -198
  295. webscout/Provider/monochat.py +0 -275
  296. webscout/Provider/multichat.py +0 -375
  297. webscout/Provider/scnet.py +0 -244
  298. webscout/Provider/talkai.py +0 -194
  299. webscout/tempid.py +0 -128
  300. webscout-8.3.6.dist-info/RECORD +0 -327
  301. {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/WHEEL +0 -0
  302. {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/entry_points.txt +0 -0
  303. {webscout-8.3.6.dist-info → webscout-2025.10.11.dist-info}/licenses/LICENSE.md +0 -0
  304. {webscout-8.3.6.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)