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.
- {ngpt-2.9.0 → ngpt-2.9.2}/PKG-INFO +4 -2
- {ngpt-2.9.0 → ngpt-2.9.2}/README.md +3 -1
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/cli_components.md +15 -7
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/cli_framework.md +23 -31
- ngpt-2.9.2/ngpt/cli/__init__.py +3 -0
- ngpt-2.9.2/ngpt/cli/config_manager.py +71 -0
- ngpt-2.9.2/ngpt/cli/formatters.py +239 -0
- ngpt-2.9.2/ngpt/cli/interactive.py +275 -0
- ngpt-2.9.2/ngpt/cli/main.py +593 -0
- ngpt-2.9.2/ngpt/cli/modes/__init__.py +6 -0
- ngpt-2.9.2/ngpt/cli/modes/chat.py +72 -0
- ngpt-2.9.2/ngpt/cli/modes/code.py +97 -0
- ngpt-2.9.2/ngpt/cli/modes/shell.py +46 -0
- ngpt-2.9.2/ngpt/cli/modes/text.py +72 -0
- ngpt-2.9.2/ngpt/cli/renderers.py +258 -0
- ngpt-2.9.2/ngpt/cli/ui.py +155 -0
- ngpt-2.9.2/ngpt/cli.py +4 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/ngpt/cli_config.py +27 -8
- ngpt-2.9.2/ngpt/utils/__init__.py +1 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/pyproject.toml +1 -1
- {ngpt-2.9.0 → ngpt-2.9.2}/uv.lock +1 -1
- ngpt-2.9.0/ngpt/cli.py +0 -1748
- {ngpt-2.9.0 → ngpt-2.9.2}/.github/workflows/python-publish.yml +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/.gitignore +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/.python-version +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/COMMIT_GUIDELINES.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/CONTRIBUTING.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/LICENSE +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/CONTRIBUTING.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/LICENSE.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/README.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/_config.yml +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/README.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/cli.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/client.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/api/config.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/assets/css/style.scss +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/configuration.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/README.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/advanced.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/basic.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/examples/integrations.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/installation.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/overview.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/README.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/cli_config.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/cli_usage.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/docs/usage/library_usage.md +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/ngpt/__init__.py +0 -0
- {ngpt-2.9.0 → ngpt-2.9.2}/ngpt/client.py +0 -0
- {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.
|
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
|
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
|
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
|
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
|
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,
|
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
|
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
|
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
|
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,
|
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
|
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("
|
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
|
-
|
225
|
-
|
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,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
|