webscout 7.6__py3-none-any.whl → 7.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of webscout might be problematic. Click here for more details.

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