ngpt 2.4.0__py3-none-any.whl → 2.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ngpt/cli.py CHANGED
@@ -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:
@@ -492,11 +665,19 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
492
665
  else:
493
666
  print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
494
667
 
668
+ # If prettify is enabled, we need to disable streaming to collect the full response
669
+ should_stream = not no_stream and not prettify
670
+
671
+ # If prettify is enabled with streaming, inform the user
672
+ if prettify and not no_stream:
673
+ print(f"\n{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
674
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
675
+
495
676
  # Get AI response with conversation history
496
677
  response = client.chat(
497
678
  prompt=user_input,
498
679
  messages=conversation,
499
- stream=not no_stream,
680
+ stream=should_stream,
500
681
  web_search=web_search,
501
682
  temperature=temperature,
502
683
  top_p=top_p,
@@ -508,9 +689,12 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
508
689
  assistant_message = {"role": "assistant", "content": response}
509
690
  conversation.append(assistant_message)
510
691
 
511
- # Print response if not streamed
512
- if no_stream:
513
- print(response)
692
+ # Print response if not streamed (either due to no_stream or prettify)
693
+ if no_stream or prettify:
694
+ if prettify:
695
+ prettify_markdown(response, renderer)
696
+ else:
697
+ print(response)
514
698
 
515
699
  # Log assistant response if logging is enabled
516
700
  if log_handle:
@@ -567,6 +751,7 @@ def main():
567
751
  config_group.add_argument('--show-config', action='store_true', help='Show the current configuration(s) and exit')
568
752
  config_group.add_argument('--all', action='store_true', help='Show details for all configurations (requires --show-config)')
569
753
  config_group.add_argument('--list-models', action='store_true', help='List all available models for the current configuration and exit')
754
+ config_group.add_argument('--list-renderers', action='store_true', help='Show available markdown renderers for use with --prettify')
570
755
 
571
756
  # Global options
572
757
  global_group = parser.add_argument_group('Global Options')
@@ -587,6 +772,10 @@ def main():
587
772
  help='Set filepath to log conversation to (For interactive modes)')
588
773
  global_group.add_argument('--preprompt',
589
774
  help='Set custom system prompt to control AI behavior')
775
+ global_group.add_argument('--prettify', action='store_const', const='auto',
776
+ help='Render markdown responses and code with syntax highlighting and formatting')
777
+ global_group.add_argument('--renderer', choices=['auto', 'rich', 'glow'], default='auto',
778
+ help='Select which markdown renderer to use with --prettify (auto, rich, or glow)')
590
779
 
591
780
  # Mode flags (mutually exclusive)
592
781
  mode_group = parser.add_argument_group('Modes (mutually exclusive)')
@@ -609,6 +798,11 @@ def main():
609
798
  if args.all and not args.show_config:
610
799
  parser.error("--all can only be used with --show-config")
611
800
 
801
+ # Handle --renderers flag to show available markdown renderers
802
+ if args.list_renderers:
803
+ show_available_renderers()
804
+ return
805
+
612
806
  # Check for mutual exclusivity between --config-index and --provider
613
807
  if args.config_index != 0 and args.provider:
614
808
  parser.error("--config-index and --provider cannot be used together")
@@ -808,6 +1002,17 @@ def main():
808
1002
  if not args.show_config and not args.list_models and not check_config(active_config):
809
1003
  return
810
1004
 
1005
+ # Check if --prettify is used but no markdown renderer is available
1006
+ # This will warn the user immediately if they request prettify but don't have the tools
1007
+ has_renderer = True
1008
+ if args.prettify:
1009
+ has_renderer = warn_if_no_markdown_renderer(args.renderer)
1010
+ if not has_renderer:
1011
+ # Set a flag to disable prettify since we already warned the user
1012
+ print(f"{COLORS['yellow']}Continuing without markdown rendering.{COLORS['reset']}")
1013
+ show_available_renderers()
1014
+ args.prettify = False
1015
+
811
1016
  # Initialize client using the potentially overridden active_config
812
1017
  client = NGPTClient(**active_config)
813
1018
 
@@ -834,7 +1039,7 @@ def main():
834
1039
  # Interactive chat mode
835
1040
  interactive_chat_session(client, web_search=args.web_search, no_stream=args.no_stream,
836
1041
  temperature=args.temperature, top_p=args.top_p,
837
- max_tokens=args.max_tokens, log_file=args.log, preprompt=args.preprompt)
1042
+ max_tokens=args.max_tokens, log_file=args.log, preprompt=args.preprompt, prettify=args.prettify, renderer=args.renderer)
838
1043
  elif args.shell:
839
1044
  if args.prompt is None:
840
1045
  try:
@@ -888,7 +1093,13 @@ def main():
888
1093
  temperature=args.temperature, top_p=args.top_p,
889
1094
  max_tokens=args.max_tokens)
890
1095
  if generated_code:
891
- print(f"\nGenerated code:\n{generated_code}")
1096
+ if args.prettify:
1097
+ # Format code as markdown with proper syntax highlighting
1098
+ markdown_code = f"```{args.language}\n{generated_code}\n```"
1099
+ print("\nGenerated code:")
1100
+ prettify_markdown(markdown_code, args.renderer)
1101
+ else:
1102
+ print(f"\nGenerated code:\n{generated_code}")
892
1103
 
893
1104
  elif args.text:
894
1105
  if args.prompt is not None:
@@ -1006,12 +1217,24 @@ def main():
1006
1217
  {"role": "system", "content": args.preprompt},
1007
1218
  {"role": "user", "content": prompt}
1008
1219
  ]
1220
+
1221
+ # If prettify is enabled, we need to disable streaming to collect the full response
1222
+ should_stream = not args.no_stream and not args.prettify
1223
+
1224
+ # If prettify is enabled with streaming, inform the user
1225
+ if args.prettify and not args.no_stream:
1226
+ print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
1009
1227
 
1010
- response = client.chat(prompt, stream=not args.no_stream, web_search=args.web_search,
1228
+ response = client.chat(prompt, stream=should_stream, web_search=args.web_search,
1011
1229
  temperature=args.temperature, top_p=args.top_p,
1012
1230
  max_tokens=args.max_tokens, messages=messages)
1013
- if args.no_stream and response:
1014
- print(response)
1231
+
1232
+ # Handle non-stream response (either because no_stream was set or prettify forced it)
1233
+ if (args.no_stream or args.prettify) and response:
1234
+ if args.prettify:
1235
+ prettify_markdown(response, args.renderer)
1236
+ else:
1237
+ print(response)
1015
1238
 
1016
1239
  else:
1017
1240
  # Default to chat mode
@@ -1032,12 +1255,24 @@ def main():
1032
1255
  {"role": "system", "content": args.preprompt},
1033
1256
  {"role": "user", "content": prompt}
1034
1257
  ]
1258
+
1259
+ # If prettify is enabled, we need to disable streaming to collect the full response
1260
+ should_stream = not args.no_stream and not args.prettify
1261
+
1262
+ # If prettify is enabled with streaming, inform the user
1263
+ if args.prettify and not args.no_stream:
1264
+ print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
1035
1265
 
1036
- response = client.chat(prompt, stream=not args.no_stream, web_search=args.web_search,
1266
+ response = client.chat(prompt, stream=should_stream, web_search=args.web_search,
1037
1267
  temperature=args.temperature, top_p=args.top_p,
1038
1268
  max_tokens=args.max_tokens, messages=messages)
1039
- if args.no_stream and response:
1040
- print(response)
1269
+
1270
+ # Handle non-stream response (either because no_stream was set or prettify forced it)
1271
+ if (args.no_stream or args.prettify) and response:
1272
+ if args.prettify:
1273
+ prettify_markdown(response, args.renderer)
1274
+ else:
1275
+ print(response)
1041
1276
 
1042
1277
  except KeyboardInterrupt:
1043
1278
  print("\nOperation cancelled by user. Exiting gracefully.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 2.4.0
3
+ Version: 2.5.0
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,9 @@ 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
+ Requires-Dist: rich>=14.0.0
34
+ Provides-Extra: prettify
35
+ Requires-Dist: rich>=10.0.0; extra == 'prettify'
33
36
  Description-Content-Type: text/markdown
34
37
 
35
38
  # nGPT
@@ -76,9 +79,18 @@ ngpt -n "Tell me about quantum computing"
76
79
  # Generate code
77
80
  ngpt --code "function to calculate the Fibonacci sequence"
78
81
 
82
+ # Generate code with syntax highlighting
83
+ ngpt --code --prettify "function to calculate the Fibonacci sequence"
84
+
79
85
  # Generate and execute shell commands
80
86
  ngpt --shell "list all files in the current directory"
81
87
 
88
+ # Display markdown responses with beautiful formatting
89
+ ngpt --prettify "Explain markdown syntax with examples"
90
+
91
+ # Use a specific markdown renderer
92
+ ngpt --prettify --renderer=rich "Create a markdown table"
93
+
82
94
  # Use multiline editor for complex prompts
83
95
  ngpt --text
84
96
 
@@ -99,6 +111,7 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
99
111
  - 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
100
112
  - 📊 **Streaming Responses**: Real-time output for better user experience
101
113
  - 🔍 **Web Search**: Integrated with compatible API endpoints
114
+ - 🎨 **Markdown Rendering**: Beautiful formatting of markdown and code with syntax highlighting
102
115
  - ⚙️ **Multiple Configurations**: Cross-platform config system supporting different profiles
103
116
  - 💻 **Shell Command Generation**: OS-aware command execution
104
117
  - 🧩 **Clean Code Generation**: Output code without markdown or explanations
@@ -267,6 +280,9 @@ You can configure the client using the following options:
267
280
  | `--max_tokens` | Set maximum response length in tokens |
268
281
  | `--preprompt` | Set custom system prompt to control AI behavior |
269
282
  | `--log` | Set filepath to log conversation to (for interactive modes) |
283
+ | `--prettify` | Render markdown responses and code with syntax highlighting |
284
+ | `--renderer` | Select which markdown renderer to use with --prettify (auto, rich, or glow) |
285
+ | `--list-renderers` | Show available markdown renderers for use with --prettify |
270
286
  | `--config` | Path to a custom configuration file or, when used without a value, enters interactive configuration mode |
271
287
  | `--config-index` | Index of the configuration to use (default: 0) |
272
288
  | `--provider` | Provider name to identify the configuration to use (alternative to --config-index) |
@@ -0,0 +1,9 @@
1
+ ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
+ ngpt/cli.py,sha256=VE-Zp7mx-GC6p0o9CcKAnAJCDbLjwiV_UKxwikceDaM,59632
3
+ ngpt/client.py,sha256=lJfyLONeBU7YqMVJJys6zPay7gcJTq108_rJ4wvOtok,13067
4
+ ngpt/config.py,sha256=WYOk_b1eiYjo6hpV3pfXr2RjqhOnmKqwZwKid1T41I4,10363
5
+ ngpt-2.5.0.dist-info/METADATA,sha256=Guk41uYFdF-_hWqi6QgUHhWXXuhgU2CMai0GvIOnkHA,14685
6
+ ngpt-2.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ ngpt-2.5.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
+ ngpt-2.5.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
+ ngpt-2.5.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
- ngpt/cli.py,sha256=iIlsdKGnSUbdnzKRL6Vmk067KXDEtIZmLqRm8ZKH7XE,49101
3
- ngpt/client.py,sha256=lJfyLONeBU7YqMVJJys6zPay7gcJTq108_rJ4wvOtok,13067
4
- ngpt/config.py,sha256=WYOk_b1eiYjo6hpV3pfXr2RjqhOnmKqwZwKid1T41I4,10363
5
- ngpt-2.4.0.dist-info/METADATA,sha256=Zcfa9uWB3PwwGgFz4fIhmu9XfLu_ZzQxIq7HF8CH-ew,13910
6
- ngpt-2.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- ngpt-2.4.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
- ngpt-2.4.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
- ngpt-2.4.0.dist-info/RECORD,,
File without changes