ngpt 2.4.0__tar.gz → 2.5.1__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 (32) hide show
  1. {ngpt-2.4.0 → ngpt-2.5.1}/PKG-INFO +16 -1
  2. {ngpt-2.4.0 → ngpt-2.5.1}/README.md +13 -0
  3. {ngpt-2.4.0 → ngpt-2.5.1}/docs/api/client.md +49 -24
  4. {ngpt-2.4.0 → ngpt-2.5.1}/docs/installation.md +16 -0
  5. {ngpt-2.4.0 → ngpt-2.5.1}/docs/overview.md +2 -0
  6. {ngpt-2.4.0 → ngpt-2.5.1}/docs/usage/cli_usage.md +51 -2
  7. {ngpt-2.4.0 → ngpt-2.5.1}/ngpt/cli.py +262 -17
  8. {ngpt-2.4.0 → ngpt-2.5.1}/ngpt/client.py +22 -3
  9. {ngpt-2.4.0 → ngpt-2.5.1}/pyproject.toml +6 -1
  10. {ngpt-2.4.0 → ngpt-2.5.1}/uv.lock +61 -1
  11. {ngpt-2.4.0 → ngpt-2.5.1}/.github/workflows/python-publish.yml +0 -0
  12. {ngpt-2.4.0 → ngpt-2.5.1}/.gitignore +0 -0
  13. {ngpt-2.4.0 → ngpt-2.5.1}/.python-version +0 -0
  14. {ngpt-2.4.0 → ngpt-2.5.1}/COMMIT_GUIDELINES.md +0 -0
  15. {ngpt-2.4.0 → ngpt-2.5.1}/CONTRIBUTING.md +0 -0
  16. {ngpt-2.4.0 → ngpt-2.5.1}/LICENSE +0 -0
  17. {ngpt-2.4.0 → ngpt-2.5.1}/docs/CONTRIBUTING.md +0 -0
  18. {ngpt-2.4.0 → ngpt-2.5.1}/docs/LICENSE.md +0 -0
  19. {ngpt-2.4.0 → ngpt-2.5.1}/docs/README.md +0 -0
  20. {ngpt-2.4.0 → ngpt-2.5.1}/docs/_config.yml +0 -0
  21. {ngpt-2.4.0 → ngpt-2.5.1}/docs/api/README.md +0 -0
  22. {ngpt-2.4.0 → ngpt-2.5.1}/docs/api/config.md +0 -0
  23. {ngpt-2.4.0 → ngpt-2.5.1}/docs/assets/css/style.scss +0 -0
  24. {ngpt-2.4.0 → ngpt-2.5.1}/docs/configuration.md +0 -0
  25. {ngpt-2.4.0 → ngpt-2.5.1}/docs/examples/README.md +0 -0
  26. {ngpt-2.4.0 → ngpt-2.5.1}/docs/examples/advanced.md +0 -0
  27. {ngpt-2.4.0 → ngpt-2.5.1}/docs/examples/basic.md +0 -0
  28. {ngpt-2.4.0 → ngpt-2.5.1}/docs/examples/integrations.md +0 -0
  29. {ngpt-2.4.0 → ngpt-2.5.1}/docs/usage/README.md +0 -0
  30. {ngpt-2.4.0 → ngpt-2.5.1}/docs/usage/library_usage.md +0 -0
  31. {ngpt-2.4.0 → ngpt-2.5.1}/ngpt/__init__.py +0 -0
  32. {ngpt-2.4.0 → ngpt-2.5.1}/ngpt/config.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 2.4.0
3
+ Version: 2.5.1
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
@@ -30,6 +30,8 @@ Classifier: Topic :: Utilities
30
30
  Requires-Python: >=3.8
31
31
  Requires-Dist: prompt-toolkit>=3.0.0
32
32
  Requires-Dist: requests>=2.31.0
33
+ Provides-Extra: prettify
34
+ Requires-Dist: rich>=10.0.0; extra == 'prettify'
33
35
  Description-Content-Type: text/markdown
34
36
 
35
37
  # nGPT
@@ -76,9 +78,18 @@ ngpt -n "Tell me about quantum computing"
76
78
  # Generate code
77
79
  ngpt --code "function to calculate the Fibonacci sequence"
78
80
 
81
+ # Generate code with syntax highlighting
82
+ ngpt --code --prettify "function to calculate the Fibonacci sequence"
83
+
79
84
  # Generate and execute shell commands
80
85
  ngpt --shell "list all files in the current directory"
81
86
 
87
+ # Display markdown responses with beautiful formatting
88
+ ngpt --prettify "Explain markdown syntax with examples"
89
+
90
+ # Use a specific markdown renderer
91
+ ngpt --prettify --renderer=rich "Create a markdown table"
92
+
82
93
  # Use multiline editor for complex prompts
83
94
  ngpt --text
84
95
 
@@ -99,6 +110,7 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
99
110
  - 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
100
111
  - 📊 **Streaming Responses**: Real-time output for better user experience
101
112
  - 🔍 **Web Search**: Integrated with compatible API endpoints
113
+ - 🎨 **Markdown Rendering**: Beautiful formatting of markdown and code with syntax highlighting
102
114
  - ⚙️ **Multiple Configurations**: Cross-platform config system supporting different profiles
103
115
  - 💻 **Shell Command Generation**: OS-aware command execution
104
116
  - 🧩 **Clean Code Generation**: Output code without markdown or explanations
@@ -267,6 +279,9 @@ You can configure the client using the following options:
267
279
  | `--max_tokens` | Set maximum response length in tokens |
268
280
  | `--preprompt` | Set custom system prompt to control AI behavior |
269
281
  | `--log` | Set filepath to log conversation to (for interactive modes) |
282
+ | `--prettify` | Render markdown responses and code with syntax highlighting |
283
+ | `--renderer` | Select which markdown renderer to use with --prettify (auto, rich, or glow) |
284
+ | `--list-renderers` | Show available markdown renderers for use with --prettify |
270
285
  | `--config` | Path to a custom configuration file or, when used without a value, enters interactive configuration mode |
271
286
  | `--config-index` | Index of the configuration to use (default: 0) |
272
287
  | `--provider` | Provider name to identify the configuration to use (alternative to --config-index) |
@@ -42,9 +42,18 @@ ngpt -n "Tell me about quantum computing"
42
42
  # Generate code
43
43
  ngpt --code "function to calculate the Fibonacci sequence"
44
44
 
45
+ # Generate code with syntax highlighting
46
+ ngpt --code --prettify "function to calculate the Fibonacci sequence"
47
+
45
48
  # Generate and execute shell commands
46
49
  ngpt --shell "list all files in the current directory"
47
50
 
51
+ # Display markdown responses with beautiful formatting
52
+ ngpt --prettify "Explain markdown syntax with examples"
53
+
54
+ # Use a specific markdown renderer
55
+ ngpt --prettify --renderer=rich "Create a markdown table"
56
+
48
57
  # Use multiline editor for complex prompts
49
58
  ngpt --text
50
59
 
@@ -65,6 +74,7 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
65
74
  - 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
66
75
  - 📊 **Streaming Responses**: Real-time output for better user experience
67
76
  - 🔍 **Web Search**: Integrated with compatible API endpoints
77
+ - 🎨 **Markdown Rendering**: Beautiful formatting of markdown and code with syntax highlighting
68
78
  - ⚙️ **Multiple Configurations**: Cross-platform config system supporting different profiles
69
79
  - 💻 **Shell Command Generation**: OS-aware command execution
70
80
  - 🧩 **Clean Code Generation**: Output code without markdown or explanations
@@ -233,6 +243,9 @@ You can configure the client using the following options:
233
243
  | `--max_tokens` | Set maximum response length in tokens |
234
244
  | `--preprompt` | Set custom system prompt to control AI behavior |
235
245
  | `--log` | Set filepath to log conversation to (for interactive modes) |
246
+ | `--prettify` | Render markdown responses and code with syntax highlighting |
247
+ | `--renderer` | Select which markdown renderer to use with --prettify (auto, rich, or glow) |
248
+ | `--list-renderers` | Show available markdown renderers for use with --prettify |
236
249
  | `--config` | Path to a custom configuration file or, when used without a value, enters interactive configuration mode |
237
250
  | `--config-index` | Index of the configuration to use (default: 0) |
238
251
  | `--provider` | Provider name to identify the configuration to use (alternative to --config-index) |
@@ -49,7 +49,7 @@ client = NGPTClient(
49
49
 
50
50
  ## Chat Method
51
51
 
52
- The primary method for interacting with the AI model.
52
+ The main method for interacting with the AI model.
53
53
 
54
54
  ```python
55
55
  response = client.chat(
@@ -57,8 +57,10 @@ response = client.chat(
57
57
  stream: bool = True,
58
58
  temperature: float = 0.7,
59
59
  max_tokens: Optional[int] = None,
60
+ top_p: float = 1.0,
60
61
  messages: Optional[List[Dict[str, str]]] = None,
61
62
  web_search: bool = False,
63
+ markdown_format: bool = False,
62
64
  **kwargs
63
65
  ) -> str
64
66
  ```
@@ -69,49 +71,55 @@ response = client.chat(
69
71
  |-----------|------|---------|-------------|
70
72
  | `prompt` | `str` | Required | The user's message |
71
73
  | `stream` | `bool` | `True` | Whether to stream the response |
72
- | `temperature` | `float` | `0.7` | Controls randomness in the response (0.0-1.0) |
74
+ | `temperature` | `float` | `0.7` | Controls randomness in the response |
73
75
  | `max_tokens` | `Optional[int]` | `None` | Maximum number of tokens to generate |
74
- | `messages` | `Optional[List[Dict[str, str]]]` | `None` | Optional list of message objects for conversation history |
76
+ | `top_p` | `float` | `1.0` | Controls diversity via nucleus sampling |
77
+ | `messages` | `Optional[List[Dict[str, str]]]` | `None` | Optional list of message objects to override default behavior |
75
78
  | `web_search` | `bool` | `False` | Whether to enable web search capability |
76
- | `**kwargs` | | | Additional arguments to pass to the API |
79
+ | `markdown_format` | `bool` | `False` | If True, allows markdown formatting in responses |
80
+ | `**kwargs` | `Any` | `{}` | Additional arguments to pass to the API |
77
81
 
78
82
  ### Returns
79
83
 
80
- - When `stream=False`: A string containing the complete response
81
- - When `stream=True`: A generator yielding response chunks that can be iterated over
84
+ If `stream=True`, returns chunks of the response as they are generated.
85
+ If `stream=False`, returns the complete response as a string.
82
86
 
83
87
  ### Examples
84
88
 
85
89
  ```python
86
- # Basic chat with streaming
87
- for chunk in client.chat("Tell me about quantum computing"):
88
- print(chunk, end="", flush=True)
89
- print() # Final newline
90
+ # Basic usage
91
+ response = client.chat("Tell me about quantum computing")
92
+ print(response)
90
93
 
91
94
  # Without streaming
92
95
  response = client.chat("Tell me about quantum computing", stream=False)
93
96
  print(response)
94
97
 
98
+ # With custom temperature (higher = more creative, lower = more deterministic)
99
+ response = client.chat("Write a poem about nature", temperature=0.9)
100
+ print(response)
101
+
102
+ # With token limit
103
+ response = client.chat("Explain the history of AI", max_tokens=100)
104
+ print(response)
105
+
95
106
  # With conversation history
96
107
  messages = [
97
108
  {"role": "system", "content": "You are a helpful assistant."},
98
109
  {"role": "user", "content": "Hello, who are you?"},
99
- {"role": "assistant", "content": "I'm an AI assistant. How can I help you today?"},
100
- {"role": "user", "content": "Tell me about yourself"}
110
+ {"role": "assistant", "content": "I'm an AI assistant created to help answer questions and provide information."},
111
+ {"role": "user", "content": "Tell me more about yourself"}
101
112
  ]
102
113
  response = client.chat("", messages=messages)
103
114
  print(response)
104
115
 
105
- # With web search
106
- response = client.chat("What's the latest news about AI?", web_search=True)
116
+ # Enable web search capability (if API supports it)
117
+ response = client.chat("What are the latest developments in quantum computing?", web_search=True)
107
118
  print(response)
108
119
 
109
- # With temperature control
110
- response = client.chat("Write a creative story", temperature=0.9) # More random
111
- response = client.chat("Explain how a CPU works", temperature=0.2) # More focused
112
-
113
- # With token limit
114
- response = client.chat("Summarize this concept", max_tokens=100)
120
+ # Enable markdown formatting for rich text responses
121
+ response = client.chat("Create a table comparing programming languages", markdown_format=True)
122
+ print(response) # Response will contain markdown formatting like tables, code blocks, etc.
115
123
  ```
116
124
 
117
125
  ## Generate Shell Command
@@ -157,13 +165,17 @@ command = client.generate_shell_command(
157
165
 
158
166
  ## Generate Code
159
167
 
160
- Generates clean code based on the prompt, without markdown formatting or explanations.
168
+ Generates code based on the prompt.
161
169
 
162
170
  ```python
163
171
  code = client.generate_code(
164
172
  prompt: str,
165
173
  language: str = "python",
166
- web_search: bool = False
174
+ web_search: bool = False,
175
+ temperature: float = 0.4,
176
+ top_p: float = 0.95,
177
+ max_tokens: Optional[int] = None,
178
+ markdown_format: bool = False
167
179
  ) -> str
168
180
  ```
169
181
 
@@ -174,15 +186,19 @@ code = client.generate_code(
174
186
  | `prompt` | `str` | Required | Description of the code to generate |
175
187
  | `language` | `str` | `"python"` | Programming language to generate code in |
176
188
  | `web_search` | `bool` | `False` | Whether to enable web search capability |
189
+ | `temperature` | `float` | `0.4` | Controls randomness in the response |
190
+ | `top_p` | `float` | `0.95` | Controls diversity via nucleus sampling |
191
+ | `max_tokens` | `Optional[int]` | `None` | Maximum number of tokens to generate |
192
+ | `markdown_format` | `bool` | `False` | If True, returns code with markdown formatting including syntax highlighting |
177
193
 
178
194
  ### Returns
179
195
 
180
- A string containing the generated code without any markdown formatting or explanations.
196
+ A string containing the generated code. If `markdown_format` is `False`, returns plain text code. If `markdown_format` is `True`, returns code formatted in markdown with appropriate syntax highlighting.
181
197
 
182
198
  ### Examples
183
199
 
184
200
  ```python
185
- # Generate Python code (default)
201
+ # Generate Python code (default, plain text)
186
202
  python_code = client.generate_code("function to calculate fibonacci numbers")
187
203
  print(python_code)
188
204
 
@@ -193,6 +209,15 @@ js_code = client.generate_code(
193
209
  )
194
210
  print(js_code)
195
211
 
212
+ # Generate code with markdown formatting for documentation or display
213
+ markdown_code = client.generate_code(
214
+ "class that implements a binary search tree",
215
+ language="python",
216
+ markdown_format=True
217
+ )
218
+ # This will output code wrapped in markdown code blocks with syntax highlighting
219
+ print(markdown_code)
220
+
196
221
  # Generate code with web search for latest best practices
197
222
  react_code = client.generate_code(
198
223
  "create a React component that fetches and displays data from an API",
@@ -8,6 +8,10 @@ There are several ways to install nGPT depending on your needs and environment.
8
8
  - `requests` library (automatically installed as a dependency)
9
9
  - `prompt_toolkit` library (automatically installed as a dependency)
10
10
 
11
+ ## Optional Dependencies
12
+
13
+ - `rich` library - For enhanced markdown rendering with syntax highlighting
14
+
11
15
  ## Installing from PyPI (Recommended)
12
16
 
13
17
  The simplest way to install nGPT is through the Python Package Index (PyPI):
@@ -18,6 +22,18 @@ pip install ngpt
18
22
 
19
23
  This will install the latest stable release of nGPT and all its dependencies.
20
24
 
25
+ For markdown rendering capabilities, install with the prettify extra:
26
+
27
+ ```bash
28
+ pip install ngpt[prettify]
29
+ ```
30
+
31
+ Alternatively, you can install the optional dependency separately:
32
+
33
+ ```bash
34
+ pip install rich
35
+ ```
36
+
21
37
  ## Installing in a Virtual Environment
22
38
 
23
39
  It's often good practice to install Python packages in a virtual environment to avoid conflicts:
@@ -18,6 +18,8 @@ nGPT is a lightweight Python library and command-line interface (CLI) tool desig
18
18
 
19
19
  - **Web Search Integration**: Works with compatible API endpoints that support web search capabilities.
20
20
 
21
+ - **Markdown Rendering**: Beautiful formatting of markdown responses and syntax highlighting for code.
22
+
21
23
  - **Multiple Configuration Support**: Maintain different API configurations for various services or models.
22
24
 
23
25
  - **Shell Command Generation**: Generate OS-aware commands that work on your specific platform.
@@ -48,6 +48,9 @@ Here are the most commonly used options:
48
48
  | `--temperature` | Set temperature (controls randomness, default: 0.7) |
49
49
  | `--top_p` | Set top_p (controls diversity, default: 1.0) |
50
50
  | `--max_tokens` | Set maximum response length in tokens |
51
+ | `--prettify` | Render markdown responses and code with syntax highlighting |
52
+ | `--renderer` | Select which markdown renderer to use (auto, rich, or glow) |
53
+ | `--list-renderers` | Show available markdown renderers on your system |
51
54
  | `--config-index` | Index of the configuration to use (default: 0) |
52
55
  | `--provider` | Provider name to identify the configuration to use (alternative to --config-index) |
53
56
 
@@ -140,6 +143,46 @@ This opens an editor where you can:
140
143
  - Press Ctrl+D or F10 to submit the text
141
144
  - Press Esc to cancel
142
145
 
146
+ ### Markdown Rendering
147
+
148
+ Display markdown responses with beautiful formatting and syntax highlighting:
149
+
150
+ ```bash
151
+ ngpt --prettify "Explain markdown syntax with examples"
152
+ ```
153
+
154
+ This instructs the AI to generate properly formatted markdown responses, which are then rendered with appropriate formatting, including:
155
+ - Syntax highlighting for code blocks
156
+ - Proper rendering of tables
157
+ - Formatted headers, lists, and other markdown elements
158
+
159
+ You can specify which markdown renderer to use:
160
+
161
+ ```bash
162
+ # Use Rich (Python library) renderer
163
+ ngpt --prettify --renderer=rich "Create a markdown table comparing programming languages"
164
+
165
+ # Use Glow (terminal-based) renderer
166
+ ngpt --prettify --renderer=glow "Write documentation with code examples"
167
+
168
+ # Use automatic selection (default is Rich if available)
169
+ ngpt --prettify --renderer=auto "Explain blockchain with code examples"
170
+ ```
171
+
172
+ Combine with code generation for syntax-highlighted code:
173
+
174
+ ```bash
175
+ ngpt -c --prettify "function to calculate the Fibonacci sequence"
176
+ ```
177
+
178
+ When using `--prettify` with code generation, the AI will output code in markdown format with proper syntax highlighting based on the language.
179
+
180
+ See available renderers on your system:
181
+
182
+ ```bash
183
+ ngpt --list-renderers
184
+ ```
185
+
143
186
  ### Using Web Search
144
187
 
145
188
  Enable web search capability (if your API endpoint supports it):
@@ -248,8 +291,14 @@ ngpt --model gpt-4o-mini -n "Explain quantum entanglement"
248
291
  # Interactive session with custom prompt and logging
249
292
  ngpt -i --preprompt "You are a data science tutor" --log datasci_tutoring.txt
250
293
 
251
- # Constrain output format with a custom system prompt
252
- ngpt --preprompt "Your responses should be concise and include code examples." "How to parse JSON in JavaScript?"
294
+ # Generate code with syntax highlighting
295
+ ngpt -c --prettify "create a sorting algorithm"
296
+
297
+ # Render markdown with web search for up-to-date information
298
+ ngpt --prettify --web-search "Create a markdown table of recent SpaceX launches"
299
+
300
+ # Interactive session with markdown rendering
301
+ ngpt -i --prettify --renderer=rich
253
302
  ```
254
303
 
255
304
  ### Using a Custom Configuration File
@@ -5,6 +5,23 @@ from .client import NGPTClient
5
5
  from .config import load_config, get_config_path, load_configs, add_config_entry, remove_config_entry
6
6
  from . import __version__
7
7
 
8
+ # Try to import markdown rendering libraries
9
+ try:
10
+ import rich
11
+ from rich.markdown import Markdown
12
+ from rich.console import Console
13
+ HAS_RICH = True
14
+ except ImportError:
15
+ HAS_RICH = False
16
+
17
+ # Try to import the glow command if available
18
+ def has_glow_installed():
19
+ """Check if glow is installed in the system."""
20
+ import shutil
21
+ return shutil.which("glow") is not None
22
+
23
+ HAS_GLOW = has_glow_installed()
24
+
8
25
  # ANSI color codes for terminal output
9
26
  COLORS = {
10
27
  "reset": "\033[0m",
@@ -68,6 +85,162 @@ if not HAS_COLOR:
68
85
  for key in COLORS:
69
86
  COLORS[key] = ""
70
87
 
88
+ def has_markdown_renderer(renderer='auto'):
89
+ """Check if the specified markdown renderer is available.
90
+
91
+ Args:
92
+ renderer (str): Which renderer to check: 'auto', 'rich', or 'glow'
93
+
94
+ Returns:
95
+ bool: True if the renderer is available, False otherwise
96
+ """
97
+ if renderer == 'auto':
98
+ return HAS_RICH or HAS_GLOW
99
+ elif renderer == 'rich':
100
+ return HAS_RICH
101
+ elif renderer == 'glow':
102
+ return HAS_GLOW
103
+ else:
104
+ return False
105
+
106
+ def show_available_renderers():
107
+ """Show which markdown renderers are available and their status."""
108
+ print(f"\n{COLORS['cyan']}{COLORS['bold']}Available Markdown Renderers:{COLORS['reset']}")
109
+
110
+ if HAS_GLOW:
111
+ print(f" {COLORS['green']}✓ Glow{COLORS['reset']} - Terminal-based Markdown renderer")
112
+ else:
113
+ print(f" {COLORS['yellow']}✗ Glow{COLORS['reset']} - Not installed (https://github.com/charmbracelet/glow)")
114
+
115
+ if HAS_RICH:
116
+ print(f" {COLORS['green']}✓ Rich{COLORS['reset']} - Python library for terminal formatting (Recommended)")
117
+ else:
118
+ print(f" {COLORS['yellow']}✗ Rich{COLORS['reset']} - Not installed (pip install rich)")
119
+
120
+ if not HAS_GLOW and not HAS_RICH:
121
+ print(f"\n{COLORS['yellow']}To enable prettified markdown output, install one of the above renderers.{COLORS['reset']}")
122
+ else:
123
+ renderers = []
124
+ if HAS_RICH:
125
+ renderers.append("rich")
126
+ if HAS_GLOW:
127
+ renderers.append("glow")
128
+ print(f"\n{COLORS['green']}Usage examples:{COLORS['reset']}")
129
+ print(f" ngpt --prettify \"Your prompt here\" {COLORS['gray']}# Beautify markdown responses{COLORS['reset']}")
130
+ print(f" ngpt -c --prettify \"Write a sort function\" {COLORS['gray']}# Syntax highlight generated code{COLORS['reset']}")
131
+ if renderers:
132
+ renderer = renderers[0]
133
+ print(f" ngpt --prettify --renderer={renderer} \"Your prompt\" {COLORS['gray']}# Specify renderer{COLORS['reset']}")
134
+
135
+ print("")
136
+
137
+ def warn_if_no_markdown_renderer(renderer='auto'):
138
+ """Warn the user if the specified markdown renderer is not available.
139
+
140
+ Args:
141
+ renderer (str): Which renderer to check: 'auto', 'rich', or 'glow'
142
+
143
+ Returns:
144
+ bool: True if the renderer is available, False otherwise
145
+ """
146
+ if has_markdown_renderer(renderer):
147
+ return True
148
+
149
+ if renderer == 'auto':
150
+ print(f"{COLORS['yellow']}Warning: No markdown rendering library available.{COLORS['reset']}")
151
+ print(f"{COLORS['yellow']}Install 'rich' package with: pip install rich{COLORS['reset']}")
152
+ print(f"{COLORS['yellow']}Or install 'glow' from https://github.com/charmbracelet/glow{COLORS['reset']}")
153
+ elif renderer == 'rich':
154
+ print(f"{COLORS['yellow']}Warning: Rich is not available.{COLORS['reset']}")
155
+ print(f"{COLORS['yellow']}Install with: pip install rich{COLORS['reset']}")
156
+ elif renderer == 'glow':
157
+ print(f"{COLORS['yellow']}Warning: Glow is not available.{COLORS['reset']}")
158
+ print(f"{COLORS['yellow']}Install from https://github.com/charmbracelet/glow{COLORS['reset']}")
159
+ else:
160
+ print(f"{COLORS['yellow']}Error: Invalid renderer '{renderer}'. Use 'auto', 'rich', or 'glow'.{COLORS['reset']}")
161
+
162
+ return False
163
+
164
+ def prettify_markdown(text, renderer='auto'):
165
+ """Render markdown text with beautiful formatting using either Rich or Glow.
166
+
167
+ The function handles both general markdown and code blocks with syntax highlighting.
168
+ For code generation mode, it automatically wraps the code in markdown code blocks.
169
+
170
+ Args:
171
+ text (str): Markdown text to render
172
+ renderer (str): Which renderer to use: 'auto', 'rich', or 'glow'
173
+
174
+ Returns:
175
+ bool: True if rendering was successful, False otherwise
176
+ """
177
+ # For 'auto', prefer rich if available, otherwise use glow
178
+ if renderer == 'auto':
179
+ if HAS_RICH:
180
+ return prettify_markdown(text, 'rich')
181
+ elif HAS_GLOW:
182
+ return prettify_markdown(text, 'glow')
183
+ else:
184
+ return False
185
+
186
+ # Use glow for rendering
187
+ elif renderer == 'glow':
188
+ if not HAS_GLOW:
189
+ print(f"{COLORS['yellow']}Warning: Glow is not available. Install from https://github.com/charmbracelet/glow{COLORS['reset']}")
190
+ # Fall back to rich if available
191
+ if HAS_RICH:
192
+ print(f"{COLORS['yellow']}Falling back to Rich renderer.{COLORS['reset']}")
193
+ return prettify_markdown(text, 'rich')
194
+ return False
195
+
196
+ # Use glow
197
+ import tempfile
198
+ import subprocess
199
+
200
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as temp:
201
+ temp_filename = temp.name
202
+ temp.write(text)
203
+
204
+ try:
205
+ # Execute glow on the temporary file
206
+ subprocess.run(["glow", temp_filename], check=True)
207
+ os.unlink(temp_filename)
208
+ return True
209
+ except Exception as e:
210
+ print(f"{COLORS['yellow']}Error using glow: {str(e)}{COLORS['reset']}")
211
+ os.unlink(temp_filename)
212
+
213
+ # Fall back to rich if available
214
+ if HAS_RICH:
215
+ print(f"{COLORS['yellow']}Falling back to Rich renderer.{COLORS['reset']}")
216
+ return prettify_markdown(text, 'rich')
217
+ return False
218
+
219
+ # Use rich for rendering
220
+ elif renderer == 'rich':
221
+ if not HAS_RICH:
222
+ print(f"{COLORS['yellow']}Warning: Rich is not available. Install with: pip install rich{COLORS['reset']}")
223
+ # Fall back to glow if available
224
+ if HAS_GLOW:
225
+ print(f"{COLORS['yellow']}Falling back to Glow renderer.{COLORS['reset']}")
226
+ return prettify_markdown(text, 'glow')
227
+ return False
228
+
229
+ # Use rich
230
+ try:
231
+ console = Console()
232
+ md = Markdown(text)
233
+ console.print(md)
234
+ return True
235
+ except Exception as e:
236
+ print(f"{COLORS['yellow']}Error using rich for markdown: {str(e)}{COLORS['reset']}")
237
+ return False
238
+
239
+ # Invalid renderer specified
240
+ else:
241
+ print(f"{COLORS['yellow']}Error: Invalid renderer '{renderer}'. Use 'auto', 'rich', or 'glow'.{COLORS['reset']}")
242
+ return False
243
+
71
244
  # Custom help formatter with color support
72
245
  class ColoredHelpFormatter(argparse.HelpFormatter):
73
246
  """Help formatter that properly handles ANSI color codes without breaking alignment."""
@@ -332,7 +505,7 @@ def check_config(config):
332
505
 
333
506
  return True
334
507
 
335
- def interactive_chat_session(client, web_search=False, no_stream=False, temperature=0.7, top_p=1.0, max_tokens=None, log_file=None, preprompt=None):
508
+ def interactive_chat_session(client, web_search=False, no_stream=False, temperature=0.7, top_p=1.0, max_tokens=None, log_file=None, preprompt=None, prettify=False, renderer='auto'):
336
509
  """Run an interactive chat session with conversation history."""
337
510
  # Get terminal width for better formatting
338
511
  try:
@@ -380,6 +553,14 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
380
553
 
381
554
  # Initialize conversation history
382
555
  system_prompt = preprompt if preprompt else "You are a helpful assistant."
556
+
557
+ # Add markdown formatting instruction to system prompt if prettify is enabled
558
+ if prettify:
559
+ if system_prompt:
560
+ system_prompt += " You can use markdown formatting in your responses where appropriate."
561
+ else:
562
+ system_prompt = "You are a helpful assistant. You can use markdown formatting in your responses where appropriate."
563
+
383
564
  conversation = []
384
565
  system_message = {"role": "system", "content": system_prompt}
385
566
  conversation.append(system_message)
@@ -492,15 +673,24 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
492
673
  else:
493
674
  print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
494
675
 
676
+ # If prettify is enabled, we need to disable streaming to collect the full response
677
+ should_stream = not no_stream and not prettify
678
+
679
+ # If prettify is enabled with streaming, inform the user
680
+ if prettify and not no_stream:
681
+ print(f"\n{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
682
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
683
+
495
684
  # Get AI response with conversation history
496
685
  response = client.chat(
497
686
  prompt=user_input,
498
687
  messages=conversation,
499
- stream=not no_stream,
688
+ stream=should_stream,
500
689
  web_search=web_search,
501
690
  temperature=temperature,
502
691
  top_p=top_p,
503
- max_tokens=max_tokens
692
+ max_tokens=max_tokens,
693
+ markdown_format=prettify
504
694
  )
505
695
 
506
696
  # Add AI response to conversation history
@@ -508,9 +698,12 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
508
698
  assistant_message = {"role": "assistant", "content": response}
509
699
  conversation.append(assistant_message)
510
700
 
511
- # Print response if not streamed
512
- if no_stream:
513
- print(response)
701
+ # Print response if not streamed (either due to no_stream or prettify)
702
+ if no_stream or prettify:
703
+ if prettify:
704
+ prettify_markdown(response, renderer)
705
+ else:
706
+ print(response)
514
707
 
515
708
  # Log assistant response if logging is enabled
516
709
  if log_handle:
@@ -567,6 +760,7 @@ def main():
567
760
  config_group.add_argument('--show-config', action='store_true', help='Show the current configuration(s) and exit')
568
761
  config_group.add_argument('--all', action='store_true', help='Show details for all configurations (requires --show-config)')
569
762
  config_group.add_argument('--list-models', action='store_true', help='List all available models for the current configuration and exit')
763
+ config_group.add_argument('--list-renderers', action='store_true', help='Show available markdown renderers for use with --prettify')
570
764
 
571
765
  # Global options
572
766
  global_group = parser.add_argument_group('Global Options')
@@ -587,6 +781,10 @@ def main():
587
781
  help='Set filepath to log conversation to (For interactive modes)')
588
782
  global_group.add_argument('--preprompt',
589
783
  help='Set custom system prompt to control AI behavior')
784
+ global_group.add_argument('--prettify', action='store_const', const='auto',
785
+ help='Render markdown responses and code with syntax highlighting and formatting')
786
+ global_group.add_argument('--renderer', choices=['auto', 'rich', 'glow'], default='auto',
787
+ help='Select which markdown renderer to use with --prettify (auto, rich, or glow)')
590
788
 
591
789
  # Mode flags (mutually exclusive)
592
790
  mode_group = parser.add_argument_group('Modes (mutually exclusive)')
@@ -609,6 +807,11 @@ def main():
609
807
  if args.all and not args.show_config:
610
808
  parser.error("--all can only be used with --show-config")
611
809
 
810
+ # Handle --renderers flag to show available markdown renderers
811
+ if args.list_renderers:
812
+ show_available_renderers()
813
+ return
814
+
612
815
  # Check for mutual exclusivity between --config-index and --provider
613
816
  if args.config_index != 0 and args.provider:
614
817
  parser.error("--config-index and --provider cannot be used together")
@@ -808,6 +1011,17 @@ def main():
808
1011
  if not args.show_config and not args.list_models and not check_config(active_config):
809
1012
  return
810
1013
 
1014
+ # Check if --prettify is used but no markdown renderer is available
1015
+ # This will warn the user immediately if they request prettify but don't have the tools
1016
+ has_renderer = True
1017
+ if args.prettify:
1018
+ has_renderer = warn_if_no_markdown_renderer(args.renderer)
1019
+ if not has_renderer:
1020
+ # Set a flag to disable prettify since we already warned the user
1021
+ print(f"{COLORS['yellow']}Continuing without markdown rendering.{COLORS['reset']}")
1022
+ show_available_renderers()
1023
+ args.prettify = False
1024
+
811
1025
  # Initialize client using the potentially overridden active_config
812
1026
  client = NGPTClient(**active_config)
813
1027
 
@@ -834,7 +1048,7 @@ def main():
834
1048
  # Interactive chat mode
835
1049
  interactive_chat_session(client, web_search=args.web_search, no_stream=args.no_stream,
836
1050
  temperature=args.temperature, top_p=args.top_p,
837
- max_tokens=args.max_tokens, log_file=args.log, preprompt=args.preprompt)
1051
+ max_tokens=args.max_tokens, log_file=args.log, preprompt=args.preprompt, prettify=args.prettify, renderer=args.renderer)
838
1052
  elif args.shell:
839
1053
  if args.prompt is None:
840
1054
  try:
@@ -886,9 +1100,14 @@ def main():
886
1100
 
887
1101
  generated_code = client.generate_code(prompt, args.language, web_search=args.web_search,
888
1102
  temperature=args.temperature, top_p=args.top_p,
889
- max_tokens=args.max_tokens)
1103
+ max_tokens=args.max_tokens,
1104
+ markdown_format=args.prettify)
890
1105
  if generated_code:
891
- print(f"\nGenerated code:\n{generated_code}")
1106
+ if args.prettify:
1107
+ print("\nGenerated code:")
1108
+ prettify_markdown(generated_code, args.renderer)
1109
+ else:
1110
+ print(f"\nGenerated code:\n{generated_code}")
892
1111
 
893
1112
  elif args.text:
894
1113
  if args.prompt is not None:
@@ -1006,12 +1225,25 @@ def main():
1006
1225
  {"role": "system", "content": args.preprompt},
1007
1226
  {"role": "user", "content": prompt}
1008
1227
  ]
1228
+
1229
+ # If prettify is enabled, we need to disable streaming to collect the full response
1230
+ should_stream = not args.no_stream and not args.prettify
1231
+
1232
+ # If prettify is enabled with streaming, inform the user
1233
+ if args.prettify and not args.no_stream:
1234
+ print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
1009
1235
 
1010
- response = client.chat(prompt, stream=not args.no_stream, web_search=args.web_search,
1236
+ response = client.chat(prompt, stream=should_stream, web_search=args.web_search,
1011
1237
  temperature=args.temperature, top_p=args.top_p,
1012
- max_tokens=args.max_tokens, messages=messages)
1013
- if args.no_stream and response:
1014
- print(response)
1238
+ max_tokens=args.max_tokens, messages=messages,
1239
+ markdown_format=args.prettify)
1240
+
1241
+ # Handle non-stream response (either because no_stream was set or prettify forced it)
1242
+ if (args.no_stream or args.prettify) and response:
1243
+ if args.prettify:
1244
+ prettify_markdown(response, args.renderer)
1245
+ else:
1246
+ print(response)
1015
1247
 
1016
1248
  else:
1017
1249
  # Default to chat mode
@@ -1032,12 +1264,25 @@ def main():
1032
1264
  {"role": "system", "content": args.preprompt},
1033
1265
  {"role": "user", "content": prompt}
1034
1266
  ]
1267
+
1268
+ # If prettify is enabled, we need to disable streaming to collect the full response
1269
+ should_stream = not args.no_stream and not args.prettify
1270
+
1271
+ # If prettify is enabled with streaming, inform the user
1272
+ if args.prettify and not args.no_stream:
1273
+ print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
1035
1274
 
1036
- response = client.chat(prompt, stream=not args.no_stream, web_search=args.web_search,
1275
+ response = client.chat(prompt, stream=should_stream, web_search=args.web_search,
1037
1276
  temperature=args.temperature, top_p=args.top_p,
1038
- max_tokens=args.max_tokens, messages=messages)
1039
- if args.no_stream and response:
1040
- print(response)
1277
+ max_tokens=args.max_tokens, messages=messages,
1278
+ markdown_format=args.prettify)
1279
+
1280
+ # Handle non-stream response (either because no_stream was set or prettify forced it)
1281
+ if (args.no_stream or args.prettify) and response:
1282
+ if args.prettify:
1283
+ prettify_markdown(response, args.renderer)
1284
+ else:
1285
+ print(response)
1041
1286
 
1042
1287
  except KeyboardInterrupt:
1043
1288
  print("\nOperation cancelled by user. Exiting gracefully.")
@@ -33,6 +33,7 @@ class NGPTClient:
33
33
  top_p: float = 1.0,
34
34
  messages: Optional[List[Dict[str, str]]] = None,
35
35
  web_search: bool = False,
36
+ markdown_format: bool = False,
36
37
  **kwargs
37
38
  ) -> str:
38
39
  """
@@ -46,6 +47,7 @@ class NGPTClient:
46
47
  top_p: Controls diversity via nucleus sampling
47
48
  messages: Optional list of message objects to override default behavior
48
49
  web_search: Whether to enable web search capability
50
+ markdown_format: If True, allow markdown-formatted responses, otherwise plain text
49
51
  **kwargs: Additional arguments to pass to the API
50
52
 
51
53
  Returns:
@@ -56,7 +58,11 @@ class NGPTClient:
56
58
  return ""
57
59
 
58
60
  if messages is None:
59
- messages = [{"role": "user", "content": prompt}]
61
+ if markdown_format:
62
+ system_message = {"role": "system", "content": "You can use markdown formatting in your responses where appropriate."}
63
+ messages = [system_message, {"role": "user", "content": prompt}]
64
+ else:
65
+ messages = [{"role": "user", "content": prompt}]
60
66
 
61
67
  # Prepare API parameters
62
68
  payload = {
@@ -241,7 +247,8 @@ Command:"""
241
247
  web_search: bool = False,
242
248
  temperature: float = 0.4,
243
249
  top_p: float = 0.95,
244
- max_tokens: Optional[int] = None
250
+ max_tokens: Optional[int] = None,
251
+ markdown_format: bool = False
245
252
  ) -> str:
246
253
  """
247
254
  Generate code based on the prompt.
@@ -253,6 +260,7 @@ Command:"""
253
260
  temperature: Controls randomness in the response
254
261
  top_p: Controls diversity via nucleus sampling
255
262
  max_tokens: Maximum number of tokens to generate
263
+ markdown_format: If True, request markdown-formatted code, otherwise plain text
256
264
 
257
265
  Returns:
258
266
  The generated code
@@ -262,7 +270,18 @@ Command:"""
262
270
  print("Error: API key is not set. Please configure your API key in the config file or provide it with --api-key.")
263
271
  return ""
264
272
 
265
- system_prompt = f"""Your Role: Provide only code as output without any description.
273
+ if markdown_format:
274
+ system_prompt = f"""Your Role: Provide only code as output without any description with proper markdown formatting.
275
+ IMPORTANT: Format the code using markdown code blocks with the appropriate language syntax highlighting.
276
+ IMPORTANT: You must use markdown code blocks. with ```{language}
277
+ If there is a lack of details, provide most logical solution. You are not allowed to ask for more details.
278
+ Ignore any potential risk of errors or confusion.
279
+
280
+ Language: {language}
281
+ Request: {prompt}
282
+ Code:"""
283
+ else:
284
+ system_prompt = f"""Your Role: Provide only code as output without any description.
266
285
  IMPORTANT: Provide only plain text without Markdown formatting.
267
286
  IMPORTANT: Do not include markdown formatting.
268
287
  If there is a lack of details, provide most logical solution. You are not allowed to ask for more details.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ngpt"
3
- version = "2.4.0"
3
+ version = "2.5.1"
4
4
  description = "A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints."
5
5
  authors = [
6
6
  {name = "nazDridoy", email = "nazdridoy399@gmail.com"},
@@ -34,6 +34,11 @@ classifiers = [
34
34
  "Intended Audience :: System Administrators",
35
35
  ]
36
36
 
37
+ [project.optional-dependencies]
38
+ prettify = [
39
+ "rich>=10.0.0",
40
+ ]
41
+
37
42
  [project.urls]
38
43
  "Homepage" = "https://github.com/nazdridoy/ngpt"
39
44
  "Repository" = "https://github.com/nazdridoy/ngpt"
@@ -111,20 +111,48 @@ wheels = [
111
111
  { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
112
112
  ]
113
113
 
114
+ [[package]]
115
+ name = "markdown-it-py"
116
+ version = "3.0.0"
117
+ source = { registry = "https://pypi.org/simple" }
118
+ dependencies = [
119
+ { name = "mdurl" },
120
+ ]
121
+ sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
122
+ wheels = [
123
+ { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
124
+ ]
125
+
126
+ [[package]]
127
+ name = "mdurl"
128
+ version = "0.1.2"
129
+ source = { registry = "https://pypi.org/simple" }
130
+ sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
131
+ wheels = [
132
+ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
133
+ ]
134
+
114
135
  [[package]]
115
136
  name = "ngpt"
116
- version = "2.4.0"
137
+ version = "2.5.1"
117
138
  source = { editable = "." }
118
139
  dependencies = [
119
140
  { name = "prompt-toolkit" },
120
141
  { name = "requests" },
121
142
  ]
122
143
 
144
+ [package.optional-dependencies]
145
+ prettify = [
146
+ { name = "rich" },
147
+ ]
148
+
123
149
  [package.metadata]
124
150
  requires-dist = [
125
151
  { name = "prompt-toolkit", specifier = ">=3.0.0" },
126
152
  { name = "requests", specifier = ">=2.31.0" },
153
+ { name = "rich", marker = "extra == 'prettify'", specifier = ">=10.0.0" },
127
154
  ]
155
+ provides-extras = ["prettify"]
128
156
 
129
157
  [[package]]
130
158
  name = "prompt-toolkit"
@@ -138,6 +166,15 @@ wheels = [
138
166
  { url = "https://files.pythonhosted.org/packages/ce/4f/5249960887b1fbe561d9ff265496d170b55a735b76724f10ef19f9e40716/prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07", size = 387810 },
139
167
  ]
140
168
 
169
+ [[package]]
170
+ name = "pygments"
171
+ version = "2.19.1"
172
+ source = { registry = "https://pypi.org/simple" }
173
+ sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
174
+ wheels = [
175
+ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
176
+ ]
177
+
141
178
  [[package]]
142
179
  name = "requests"
143
180
  version = "2.32.3"
@@ -154,6 +191,29 @@ wheels = [
154
191
  { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
155
192
  ]
156
193
 
194
+ [[package]]
195
+ name = "rich"
196
+ version = "14.0.0"
197
+ source = { registry = "https://pypi.org/simple" }
198
+ dependencies = [
199
+ { name = "markdown-it-py" },
200
+ { name = "pygments" },
201
+ { name = "typing-extensions", marker = "python_full_version < '3.11'" },
202
+ ]
203
+ sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078 }
204
+ wheels = [
205
+ { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229 },
206
+ ]
207
+
208
+ [[package]]
209
+ name = "typing-extensions"
210
+ version = "4.13.2"
211
+ source = { registry = "https://pypi.org/simple" }
212
+ sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
213
+ wheels = [
214
+ { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
215
+ ]
216
+
157
217
  [[package]]
158
218
  name = "urllib3"
159
219
  version = "2.2.3"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes