ngpt 2.9.0__tar.gz → 2.9.2__tar.gz

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.
Files changed (51) hide show
  1. {ngpt-2.9.0 → ngpt-2.9.2}/PKG-INFO +4 -2
  2. {ngpt-2.9.0 → ngpt-2.9.2}/README.md +3 -1
  3. {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/cli_components.md +15 -7
  4. {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/cli_framework.md +23 -31
  5. ngpt-2.9.2/ngpt/cli/__init__.py +3 -0
  6. ngpt-2.9.2/ngpt/cli/config_manager.py +71 -0
  7. ngpt-2.9.2/ngpt/cli/formatters.py +239 -0
  8. ngpt-2.9.2/ngpt/cli/interactive.py +275 -0
  9. ngpt-2.9.2/ngpt/cli/main.py +593 -0
  10. ngpt-2.9.2/ngpt/cli/modes/__init__.py +6 -0
  11. ngpt-2.9.2/ngpt/cli/modes/chat.py +72 -0
  12. ngpt-2.9.2/ngpt/cli/modes/code.py +97 -0
  13. ngpt-2.9.2/ngpt/cli/modes/shell.py +46 -0
  14. ngpt-2.9.2/ngpt/cli/modes/text.py +72 -0
  15. ngpt-2.9.2/ngpt/cli/renderers.py +258 -0
  16. ngpt-2.9.2/ngpt/cli/ui.py +155 -0
  17. ngpt-2.9.2/ngpt/cli.py +4 -0
  18. {ngpt-2.9.0 → ngpt-2.9.2}/ngpt/cli_config.py +27 -8
  19. ngpt-2.9.2/ngpt/utils/__init__.py +1 -0
  20. {ngpt-2.9.0 → ngpt-2.9.2}/pyproject.toml +1 -1
  21. {ngpt-2.9.0 → ngpt-2.9.2}/uv.lock +1 -1
  22. ngpt-2.9.0/ngpt/cli.py +0 -1748
  23. {ngpt-2.9.0 → ngpt-2.9.2}/.github/workflows/python-publish.yml +0 -0
  24. {ngpt-2.9.0 → ngpt-2.9.2}/.gitignore +0 -0
  25. {ngpt-2.9.0 → ngpt-2.9.2}/.python-version +0 -0
  26. {ngpt-2.9.0 → ngpt-2.9.2}/COMMIT_GUIDELINES.md +0 -0
  27. {ngpt-2.9.0 → ngpt-2.9.2}/CONTRIBUTING.md +0 -0
  28. {ngpt-2.9.0 → ngpt-2.9.2}/LICENSE +0 -0
  29. {ngpt-2.9.0 → ngpt-2.9.2}/docs/CONTRIBUTING.md +0 -0
  30. {ngpt-2.9.0 → ngpt-2.9.2}/docs/LICENSE.md +0 -0
  31. {ngpt-2.9.0 → ngpt-2.9.2}/docs/README.md +0 -0
  32. {ngpt-2.9.0 → ngpt-2.9.2}/docs/_config.yml +0 -0
  33. {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/README.md +0 -0
  34. {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/cli.md +0 -0
  35. {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/client.md +0 -0
  36. {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/config.md +0 -0
  37. {ngpt-2.9.0 → ngpt-2.9.2}/docs/assets/css/style.scss +0 -0
  38. {ngpt-2.9.0 → ngpt-2.9.2}/docs/configuration.md +0 -0
  39. {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/README.md +0 -0
  40. {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/advanced.md +0 -0
  41. {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/basic.md +0 -0
  42. {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/integrations.md +0 -0
  43. {ngpt-2.9.0 → ngpt-2.9.2}/docs/installation.md +0 -0
  44. {ngpt-2.9.0 → ngpt-2.9.2}/docs/overview.md +0 -0
  45. {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/README.md +0 -0
  46. {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/cli_config.md +0 -0
  47. {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/cli_usage.md +0 -0
  48. {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/library_usage.md +0 -0
  49. {ngpt-2.9.0 → ngpt-2.9.2}/ngpt/__init__.py +0 -0
  50. {ngpt-2.9.0 → ngpt-2.9.2}/ngpt/client.py +0 -0
  51. {ngpt-2.9.0 → ngpt-2.9.2}/ngpt/config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 2.9.0
3
+ Version: 2.9.2
4
4
  Summary: A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -292,7 +292,9 @@ nGPT can also be used as a framework to build your own AI-powered command-line t
292
292
 
293
293
  ```python
294
294
  from ngpt import NGPTClient, load_config
295
- from ngpt.cli import interactive_chat_session, prettify_markdown, ColoredHelpFormatter
295
+ from ngpt.cli.main import interactive_chat_session
296
+ from ngpt.cli.renderers import prettify_markdown
297
+ from ngpt.cli.formatters import ColoredHelpFormatter
296
298
  import argparse
297
299
 
298
300
  # Create a custom CLI tool with colorized help
@@ -257,7 +257,9 @@ nGPT can also be used as a framework to build your own AI-powered command-line t
257
257
 
258
258
  ```python
259
259
  from ngpt import NGPTClient, load_config
260
- from ngpt.cli import interactive_chat_session, prettify_markdown, ColoredHelpFormatter
260
+ from ngpt.cli.main import interactive_chat_session
261
+ from ngpt.cli.renderers import prettify_markdown
262
+ from ngpt.cli.formatters import ColoredHelpFormatter
261
263
  import argparse
262
264
 
263
265
  # Create a custom CLI tool with colorized help
@@ -21,7 +21,8 @@ Here's a simple CLI tool that uses nGPT to generate and explain code:
21
21
  import argparse
22
22
  import sys
23
23
  from ngpt import NGPTClient, load_config
24
- from ngpt.cli import ColoredHelpFormatter, prettify_markdown, has_markdown_renderer
24
+ from ngpt.cli.formatters import ColoredHelpFormatter
25
+ from ngpt.cli.renderers import prettify_markdown, has_markdown_renderer
25
26
 
26
27
  def main():
27
28
  # Create parser with colorized help
@@ -94,7 +95,9 @@ Create a custom chat application with specialized capabilities:
94
95
  import argparse
95
96
  import sys
96
97
  from ngpt import NGPTClient, load_config
97
- from ngpt.cli import interactive_chat_session, ColoredHelpFormatter, has_markdown_renderer
98
+ from ngpt.cli.interactive import interactive_chat_session
99
+ from ngpt.cli.formatters import ColoredHelpFormatter
100
+ from ngpt.cli.renderers import has_markdown_renderer
98
101
 
99
102
  def main():
100
103
  parser = argparse.ArgumentParser(
@@ -180,7 +183,8 @@ import argparse
180
183
  import sys
181
184
  from pathlib import Path
182
185
  from ngpt import NGPTClient, load_config
183
- from ngpt.cli import prettify_streaming_markdown, ColoredHelpFormatter, has_markdown_renderer
186
+ from ngpt.cli.renderers import prettify_streaming_markdown, has_markdown_renderer
187
+ from ngpt.cli.formatters import ColoredHelpFormatter
184
188
 
185
189
  def main():
186
190
  parser = argparse.ArgumentParser(
@@ -310,7 +314,9 @@ Here's an example of using nGPT's multiline editor for collecting user input:
310
314
  import argparse
311
315
  import sys
312
316
  from ngpt import NGPTClient, load_config
313
- from ngpt.cli import multiline_editor, prettify_markdown, ColoredHelpFormatter
317
+ from ngpt.cli.ui import multiline_editor
318
+ from ngpt.cli.renderers import prettify_markdown
319
+ from ngpt.cli.formatters import ColoredHelpFormatter
314
320
 
315
321
  def main():
316
322
  parser = argparse.ArgumentParser(
@@ -398,7 +404,8 @@ Create a CLI tool with persistent configuration:
398
404
  import argparse
399
405
  import sys
400
406
  from ngpt import NGPTClient, load_config
401
- from ngpt.cli import handle_cli_config, ColoredHelpFormatter
407
+ from ngpt.cli.main import handle_cli_config
408
+ from ngpt.cli.formatters import ColoredHelpFormatter
402
409
 
403
410
  def main():
404
411
  parser = argparse.ArgumentParser(
@@ -551,7 +558,8 @@ import sys
551
558
  import os
552
559
  from pathlib import Path
553
560
  from ngpt import NGPTClient, load_config
554
- from ngpt.cli import prettify_markdown, supports_ansi_colors, ColoredHelpFormatter
561
+ from ngpt.cli.renderers import prettify_markdown, supports_ansi_colors
562
+ from ngpt.cli.formatters import ColoredHelpFormatter
555
563
 
556
564
  def main():
557
565
  parser = argparse.ArgumentParser(
@@ -657,7 +665,7 @@ import base64
657
665
  import os
658
666
  from pathlib import Path
659
667
  from ngpt import NGPTClient, load_config
660
- from ngpt.cli import prettify_streaming_markdown, ColoredHelpFormatter, has_markdown_renderer
668
+ from ngpt.cli.renderers import prettify_streaming_markdown, has_markdown_renderer
661
669
 
662
670
  def encode_image(image_path):
663
671
  """Encode image file as base64 string."""
@@ -4,13 +4,14 @@ This guide explains how to leverage nGPT's CLI components to build your own comm
4
4
 
5
5
  ## Overview
6
6
 
7
- nGPT's `cli.py` module contains several reusable components that you can incorporate into your own CLI applications:
7
+ nGPT's CLI module has been modularized into several components that you can incorporate into your own CLI applications:
8
8
 
9
- - **Interactive Chat Interface**: A fully-featured chat UI with history management
10
- - **Markdown Rendering**: Beautiful formatting for markdown with syntax highlighting
11
- - **Real-time Streaming**: Tools for handling streaming content with live updates
12
- - **CLI Configuration System**: Robust configuration management
13
- - **Terminal Utilities**: Helpers for colorized output and terminal formatting
9
+ - **Interactive Chat Interface**: A fully-featured chat UI with history management (`ngpt.cli.interactive`)
10
+ - **Markdown Rendering**: Beautiful formatting for markdown with syntax highlighting (`ngpt.cli.renderers`)
11
+ - **Real-time Streaming**: Tools for handling streaming content with live updates (`ngpt.cli.ui`)
12
+ - **CLI Configuration System**: Robust configuration management (`ngpt.cli.main`)
13
+ - **Terminal Utilities**: Helpers for colorized output and terminal formatting (`ngpt.cli.formatters`)
14
+ - **Mode-specific functionality**: Specialized code, shell, chat and text mode handlers (`ngpt.cli.modes`)
14
15
 
15
16
  ## Getting Started
16
17
 
@@ -33,7 +34,7 @@ The `interactive_chat_session` function provides a complete interactive chat exp
33
34
 
34
35
  ```python
35
36
  from ngpt import NGPTClient, load_config
36
- from ngpt.cli import interactive_chat_session
37
+ from ngpt.cli.interactive import interactive_chat_session
37
38
 
38
39
  # Initialize client
39
40
  config = load_config()
@@ -80,7 +81,7 @@ except Exception as e:
80
81
  nGPT provides utilities for rendering markdown with syntax highlighting:
81
82
 
82
83
  ```python
83
- from ngpt.cli import prettify_markdown, has_markdown_renderer
84
+ from ngpt.cli.renderers import prettify_markdown, has_markdown_renderer
84
85
 
85
86
  # Check if renderer is available
86
87
  if has_markdown_renderer(renderer='rich'):
@@ -99,7 +100,7 @@ Available renderers include:
99
100
  You can check available renderers:
100
101
 
101
102
  ```python
102
- from ngpt.cli import show_available_renderers
103
+ from ngpt.cli.renderers import show_available_renderers
103
104
  show_available_renderers()
104
105
  ```
105
106
 
@@ -126,7 +127,7 @@ else:
126
127
  For real-time rendering of streaming content, the `prettify_streaming_markdown` function returns an object with an `update_content` method:
127
128
 
128
129
  ```python
129
- from ngpt.cli import prettify_streaming_markdown
130
+ from ngpt.cli.renderers import prettify_streaming_markdown
130
131
  from ngpt import NGPTClient, load_config
131
132
 
132
133
  client = NGPTClient(**load_config())
@@ -153,7 +154,7 @@ This creates a live-updating display that refreshes as new content arrives. The
153
154
  nGPT provides tools for managing CLI configurations:
154
155
 
155
156
  ```python
156
- from ngpt.cli import handle_cli_config, show_cli_config_help
157
+ from ngpt.cli.main import handle_cli_config, show_cli_config_help
157
158
 
158
159
  # Show help
159
160
  show_cli_config_help()
@@ -187,13 +188,13 @@ except Exception as e:
187
188
  Colorize your CLI output with terminal utilities:
188
189
 
189
190
  ```python
190
- from ngpt.cli import ColoredHelpFormatter, supports_ansi_colors
191
+ from ngpt.cli.formatters import ColoredHelpFormatter, supports_ansi_colors, COLORS
191
192
  import argparse
192
193
 
193
194
  # Check if terminal supports colors
194
195
  if supports_ansi_colors():
195
196
  # Use colored output
196
- print("\033[1;32mSuccess!\033[0m")
197
+ print(f"{COLORS['green']}Success!{COLORS['reset']}")
197
198
  else:
198
199
  # Fallback to plain text
199
200
  print("Success!")
@@ -220,13 +221,9 @@ import argparse
220
221
  import sys
221
222
  from pathlib import Path
222
223
  from ngpt import NGPTClient, load_config
223
- from ngpt.cli import (
224
- ColoredHelpFormatter,
225
- interactive_chat_session,
226
- prettify_markdown,
227
- prettify_streaming_markdown,
228
- has_markdown_renderer
229
- )
224
+ from ngpt.cli.formatters import ColoredHelpFormatter
225
+ from ngpt.cli.interactive import interactive_chat_session
226
+ from ngpt.cli.renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer
230
227
 
231
228
  def main():
232
229
  # Set up argument parser with colorized help
@@ -352,7 +349,7 @@ nGPT supports multiple markdown renderers:
352
349
  Using the Rich library for terminal output:
353
350
 
354
351
  ```python
355
- from ngpt.cli import prettify_markdown
352
+ from ngpt.cli.renderers import prettify_markdown
356
353
 
357
354
  # Rich renderer with custom styling
358
355
  formatted = prettify_markdown(markdown_text, renderer='rich')
@@ -364,7 +361,7 @@ print(formatted)
364
361
  If you have the Glow CLI tool installed:
365
362
 
366
363
  ```python
367
- from ngpt.cli import prettify_markdown, has_glow_installed
364
+ from ngpt.cli.renderers import prettify_markdown, has_glow_installed
368
365
 
369
366
  if has_glow_installed():
370
367
  formatted = prettify_markdown(markdown_text, renderer='glow')
@@ -385,7 +382,7 @@ print(formatted)
385
382
  nGPT provides utilities to detect terminal capabilities:
386
383
 
387
384
  ```python
388
- from ngpt.cli import supports_ansi_colors
385
+ from ngpt.cli.formatters import supports_ansi_colors
389
386
 
390
387
  if supports_ansi_colors():
391
388
  # Use colored output
@@ -422,17 +419,12 @@ This example creates a specialized web search tool using nGPT components:
422
419
  import argparse
423
420
  import sys
424
421
  from ngpt import NGPTClient, load_config
425
- from ngpt.cli import (
426
- ColoredHelpFormatter,
427
- prettify_streaming_markdown,
428
- has_markdown_renderer
429
- )
422
+ from ngpt.cli.renderers import prettify_streaming_markdown, has_markdown_renderer
430
423
 
431
424
  def main():
432
425
  # Set up colorized argument parser
433
426
  parser = argparse.ArgumentParser(
434
- description="AI-powered web search tool",
435
- formatter_class=ColoredHelpFormatter
427
+ description="AI-powered web search tool"
436
428
  )
437
429
 
438
430
  parser.add_argument("query", nargs="?", help="Search query")
@@ -514,7 +506,7 @@ logging.basicConfig(
514
506
  )
515
507
 
516
508
  # Use nGPT components with logging available
517
- from ngpt.cli import prettify_markdown
509
+ from ngpt.cli.renderers import prettify_markdown
518
510
  try:
519
511
  result = prettify_markdown(text)
520
512
  except Exception as e:
@@ -0,0 +1,3 @@
1
+ from .main import main
2
+
3
+ __all__ = ['main']
@@ -0,0 +1,71 @@
1
+ import sys
2
+ from ..config import get_config_path, load_configs, add_config_entry, remove_config_entry
3
+ from .formatters import COLORS
4
+
5
+ def show_config_help():
6
+ """Display help information about configuration."""
7
+ print(f"\n{COLORS['green']}{COLORS['bold']}Configuration Help:{COLORS['reset']}")
8
+ print(f" 1. {COLORS['cyan']}Create a config file at one of these locations:{COLORS['reset']}")
9
+ if sys.platform == "win32":
10
+ print(f" - {COLORS['yellow']}%APPDATA%\\ngpt\\ngpt.conf{COLORS['reset']}")
11
+ elif sys.platform == "darwin":
12
+ print(f" - {COLORS['yellow']}~/Library/Application Support/ngpt/ngpt.conf{COLORS['reset']}")
13
+ else:
14
+ print(f" - {COLORS['yellow']}~/.config/ngpt/ngpt.conf{COLORS['reset']}")
15
+
16
+ print(f" 2. {COLORS['cyan']}Format your config file as JSON:{COLORS['reset']}")
17
+ print(f"""{COLORS['yellow']} [
18
+ {{
19
+ "api_key": "your-api-key-here",
20
+ "base_url": "https://api.openai.com/v1/",
21
+ "provider": "OpenAI",
22
+ "model": "gpt-3.5-turbo"
23
+ }},
24
+ {{
25
+ "api_key": "your-second-api-key",
26
+ "base_url": "http://localhost:1337/v1/",
27
+ "provider": "Another Provider",
28
+ "model": "different-model"
29
+ }}
30
+ ]{COLORS['reset']}""")
31
+
32
+ print(f" 3. {COLORS['cyan']}Or set environment variables:{COLORS['reset']}")
33
+ print(f" - {COLORS['yellow']}OPENAI_API_KEY{COLORS['reset']}")
34
+ print(f" - {COLORS['yellow']}OPENAI_BASE_URL{COLORS['reset']}")
35
+ print(f" - {COLORS['yellow']}OPENAI_MODEL{COLORS['reset']}")
36
+
37
+ print(f" 4. {COLORS['cyan']}Or provide command line arguments:{COLORS['reset']}")
38
+ print(f" {COLORS['yellow']}ngpt --api-key your-key --base-url https://api.example.com --model your-model \"Your prompt\"{COLORS['reset']}")
39
+
40
+ print(f" 5. {COLORS['cyan']}Use --config-index to specify which configuration to use or edit:{COLORS['reset']}")
41
+ print(f" {COLORS['yellow']}ngpt --config-index 1 \"Your prompt\"{COLORS['reset']}")
42
+
43
+ print(f" 6. {COLORS['cyan']}Use --provider to specify which configuration to use by provider name:{COLORS['reset']}")
44
+ print(f" {COLORS['yellow']}ngpt --provider Gemini \"Your prompt\"{COLORS['reset']}")
45
+
46
+ print(f" 7. {COLORS['cyan']}Use --config without arguments to add a new configuration:{COLORS['reset']}")
47
+ print(f" {COLORS['yellow']}ngpt --config{COLORS['reset']}")
48
+ print(f" Or specify an index or provider to edit an existing configuration:")
49
+ print(f" {COLORS['yellow']}ngpt --config --config-index 1{COLORS['reset']}")
50
+ print(f" {COLORS['yellow']}ngpt --config --provider Gemini{COLORS['reset']}")
51
+
52
+ print(f" 8. {COLORS['cyan']}Remove a configuration by index or provider:{COLORS['reset']}")
53
+ print(f" {COLORS['yellow']}ngpt --config --remove --config-index 1{COLORS['reset']}")
54
+ print(f" {COLORS['yellow']}ngpt --config --remove --provider Gemini{COLORS['reset']}")
55
+
56
+ print(f" 9. {COLORS['cyan']}List available models for the current configuration:{COLORS['reset']}")
57
+ print(f" {COLORS['yellow']}ngpt --list-models{COLORS['reset']}")
58
+
59
+ def check_config(config):
60
+ """Check config for common issues and provide guidance."""
61
+ if not config.get("api_key"):
62
+ print(f"{COLORS['yellow']}{COLORS['bold']}Error: API key is not set.{COLORS['reset']}")
63
+ show_config_help()
64
+ return False
65
+
66
+ # Check for common URL mistakes
67
+ base_url = config.get("base_url", "")
68
+ if base_url and not (base_url.startswith("http://") or base_url.startswith("https://")):
69
+ print(f"{COLORS['yellow']}Warning: Base URL '{base_url}' doesn't start with http:// or https://{COLORS['reset']}")
70
+
71
+ return True
@@ -0,0 +1,239 @@
1
+ import sys
2
+ import os
3
+ import shutil
4
+ import argparse
5
+ import re
6
+ import textwrap
7
+ import ctypes
8
+
9
+ # ANSI color codes for terminal output
10
+ COLORS = {
11
+ "reset": "\033[0m",
12
+ "bold": "\033[1m",
13
+ "cyan": "\033[36m",
14
+ "green": "\033[32m",
15
+ "yellow": "\033[33m",
16
+ "blue": "\033[34m",
17
+ "magenta": "\033[35m",
18
+ "gray": "\033[90m",
19
+ "bg_blue": "\033[44m",
20
+ "bg_cyan": "\033[46m"
21
+ }
22
+
23
+ # Check if ANSI colors are supported
24
+ def supports_ansi_colors():
25
+ """Check if the current terminal supports ANSI colors."""
26
+
27
+ # If not a TTY, probably redirected, so no color
28
+ if not sys.stdout.isatty():
29
+ return False
30
+
31
+ # Windows specific checks
32
+ if sys.platform == "win32":
33
+ try:
34
+ # Windows 10+ supports ANSI colors in cmd/PowerShell
35
+ kernel32 = ctypes.windll.kernel32
36
+
37
+ # Try to enable ANSI color support
38
+ kernel32.SetConsoleMode(kernel32.GetStdHandle(-11), 7)
39
+
40
+ # Check if TERM_PROGRAM is set (WSL/ConEmu/etc.)
41
+ if os.environ.get('TERM_PROGRAM') or os.environ.get('WT_SESSION'):
42
+ return True
43
+
44
+ # Check Windows version - 10+ supports ANSI natively
45
+ winver = sys.getwindowsversion()
46
+ if winver.major >= 10:
47
+ return True
48
+
49
+ return False
50
+ except Exception:
51
+ return False
52
+
53
+ # Most UNIX systems support ANSI colors
54
+ return True
55
+
56
+ # Initialize color support
57
+ HAS_COLOR = supports_ansi_colors()
58
+
59
+ # If we're on Windows, use brighter colors that work better in PowerShell
60
+ if sys.platform == "win32" and HAS_COLOR:
61
+ COLORS["magenta"] = "\033[95m" # Bright magenta for metavars
62
+ COLORS["cyan"] = "\033[96m" # Bright cyan for options
63
+
64
+ # If no color support, use empty color codes
65
+ if not HAS_COLOR:
66
+ for key in COLORS:
67
+ COLORS[key] = ""
68
+
69
+ # Custom help formatter with color support
70
+ class ColoredHelpFormatter(argparse.HelpFormatter):
71
+ """Help formatter that properly handles ANSI color codes without breaking alignment."""
72
+
73
+ def __init__(self, prog):
74
+ # Get terminal size for dynamic width adjustment
75
+ try:
76
+ self.term_width = shutil.get_terminal_size().columns
77
+ except:
78
+ self.term_width = 80 # Default if we can't detect terminal width
79
+
80
+ # Calculate dynamic layout values based on terminal width
81
+ self.formatter_width = self.term_width - 2 # Leave some margin
82
+
83
+ # For very wide terminals, limit the width to maintain readability
84
+ if self.formatter_width > 120:
85
+ self.formatter_width = 120
86
+
87
+ # Calculate help position based on terminal width (roughly 1/3 of width)
88
+ self.help_position = min(max(20, int(self.term_width * 0.33)), 36)
89
+
90
+ # Initialize the parent class with dynamic values
91
+ super().__init__(prog, max_help_position=self.help_position, width=self.formatter_width)
92
+
93
+ # Calculate wrap width based on remaining space after help position
94
+ self.wrap_width = self.formatter_width - self.help_position - 5
95
+
96
+ # Set up the text wrapper for help text
97
+ self.ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
98
+ self.wrapper = textwrap.TextWrapper(width=self.wrap_width)
99
+
100
+ def _strip_ansi(self, s):
101
+ """Strip ANSI escape sequences for width calculations"""
102
+ return self.ansi_escape.sub('', s)
103
+
104
+ def _colorize(self, text, color, bold=False):
105
+ """Helper to consistently apply color with optional bold"""
106
+ if bold:
107
+ return f"{COLORS['bold']}{COLORS[color]}{text}{COLORS['reset']}"
108
+ return f"{COLORS[color]}{text}{COLORS['reset']}"
109
+
110
+ def _format_action_invocation(self, action):
111
+ if not action.option_strings:
112
+ # For positional arguments
113
+ metavar = self._format_args(action, action.dest.upper())
114
+ return self._colorize(metavar, 'cyan', bold=True)
115
+ else:
116
+ # For optional arguments with different color for metavar
117
+ if action.nargs != argparse.SUPPRESS:
118
+ default = self._get_default_metavar_for_optional(action)
119
+ args_string = self._format_args(action, default)
120
+
121
+ # Color option name and metavar differently
122
+ option_part = ', '.join(action.option_strings)
123
+ colored_option = self._colorize(option_part, 'cyan', bold=True)
124
+
125
+ if args_string:
126
+ # Make metavars more visible with brackets and color
127
+ # If HAS_COLOR is False, brackets will help in PowerShell
128
+ if not HAS_COLOR:
129
+ # Add brackets to make metavars stand out even without color
130
+ formatted_args = f"<{args_string}>"
131
+ else:
132
+ # Use color for metavar
133
+ formatted_args = self._colorize(args_string, 'magenta')
134
+
135
+ return f"{colored_option} {formatted_args}"
136
+ else:
137
+ return colored_option
138
+ else:
139
+ return self._colorize(', '.join(action.option_strings), 'cyan', bold=True)
140
+
141
+ def _format_usage(self, usage, actions, groups, prefix):
142
+ usage_text = super()._format_usage(usage, actions, groups, prefix)
143
+
144
+ # Replace "usage:" with colored version
145
+ colored_usage = self._colorize("usage:", 'green', bold=True)
146
+ usage_text = usage_text.replace("usage:", colored_usage)
147
+
148
+ # We won't color metavars in usage text as it breaks the formatting
149
+ # Just return with the colored usage prefix
150
+ return usage_text
151
+
152
+ def _join_parts(self, part_strings):
153
+ """Override to fix any potential formatting issues with section joins"""
154
+ return '\n'.join([part for part in part_strings if part])
155
+
156
+ def start_section(self, heading):
157
+ # Remove the colon as we'll add it with color
158
+ if heading.endswith(':'):
159
+ heading = heading[:-1]
160
+ heading_text = f"{self._colorize(heading, 'yellow', bold=True)}:"
161
+ super().start_section(heading_text)
162
+
163
+ def _get_help_string(self, action):
164
+ # Add color to help strings
165
+ help_text = action.help
166
+ if help_text:
167
+ return help_text.replace('(default:', f"{COLORS['gray']}(default:") + COLORS['reset']
168
+ return help_text
169
+
170
+ def _wrap_help_text(self, text, initial_indent="", subsequent_indent=" "):
171
+ """Wrap long help text to prevent overflow"""
172
+ if not text:
173
+ return text
174
+
175
+ # Strip ANSI codes for width calculation
176
+ clean_text = self._strip_ansi(text)
177
+
178
+ # If the text is already short enough, return it as is
179
+ if len(clean_text) <= self.wrap_width:
180
+ return text
181
+
182
+ # Handle any existing ANSI codes
183
+ has_ansi = text != clean_text
184
+ wrap_text = clean_text
185
+
186
+ # Wrap the text
187
+ lines = self.wrapper.wrap(wrap_text)
188
+
189
+ # Add indentation to all but the first line
190
+ wrapped = lines[0]
191
+ for line in lines[1:]:
192
+ wrapped += f"\n{subsequent_indent}{line}"
193
+
194
+ # Re-add the ANSI codes if they were present
195
+ if has_ansi and text.endswith(COLORS['reset']):
196
+ wrapped += COLORS['reset']
197
+
198
+ return wrapped
199
+
200
+ def _format_action(self, action):
201
+ # For subparsers, just return the regular formatting
202
+ if isinstance(action, argparse._SubParsersAction):
203
+ return super()._format_action(action)
204
+
205
+ # Get the action header with colored parts (both option names and metavars)
206
+ # The coloring is now done in _format_action_invocation
207
+ action_header = self._format_action_invocation(action)
208
+
209
+ # Format help text
210
+ help_text = self._expand_help(action)
211
+
212
+ # Get the raw lengths without ANSI codes for formatting
213
+ raw_header_len = len(self._strip_ansi(action_header))
214
+
215
+ # Calculate the indent for the help text
216
+ help_position = min(self._action_max_length + 2, self._max_help_position)
217
+ help_indent = ' ' * help_position
218
+
219
+ # If the action header is too long, put help on the next line
220
+ if raw_header_len > help_position:
221
+ # An action header that's too long gets a line break
222
+ # Wrap the help text with proper indentation
223
+ wrapped_help = self._wrap_help_text(help_text, subsequent_indent=help_indent)
224
+ line = f"{action_header}\n{help_indent}{wrapped_help}"
225
+ else:
226
+ # Standard formatting with proper spacing
227
+ padding = ' ' * (help_position - raw_header_len)
228
+ # Wrap the help text with proper indentation
229
+ wrapped_help = self._wrap_help_text(help_text, subsequent_indent=help_indent)
230
+ line = f"{action_header}{padding}{wrapped_help}"
231
+
232
+ # Handle subactions
233
+ if action.help is argparse.SUPPRESS:
234
+ return line
235
+
236
+ if not action.help:
237
+ return line
238
+
239
+ return line