ngpt 3.1.1__py3-none-any.whl → 3.3.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/args.py CHANGED
@@ -68,7 +68,7 @@ def setup_argument_parser():
68
68
  global_group.add_argument('--model',
69
69
  help='Model to use')
70
70
  global_group.add_argument('--web-search', action='store_true',
71
- help='Enable web search capability (Note: Your API endpoint must support this feature)')
71
+ help='Enable web search capability using DuckDuckGo to enhance prompts with relevant information')
72
72
  global_group.add_argument('--temperature', type=float, default=0.7,
73
73
  help='Set temperature (controls randomness, default: 0.7)')
74
74
  global_group.add_argument('--top_p', type=float, default=1.0,
ngpt/cli/interactive.py CHANGED
@@ -6,6 +6,7 @@ import sys
6
6
  import time
7
7
  from .formatters import COLORS
8
8
  from .renderers import prettify_markdown, prettify_streaming_markdown
9
+ from ..utils import enhance_prompt_with_web_search
9
10
 
10
11
  # Optional imports for enhanced UI
11
12
  try:
@@ -64,9 +65,20 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
64
65
  if logger:
65
66
  print(f"{COLORS['green']}Logging conversation to: {logger.get_log_path()}{COLORS['reset']}")
66
67
 
68
+ # Display a note about web search if enabled
69
+ if web_search:
70
+ print(f"{COLORS['green']}Web search capability is enabled.{COLORS['reset']}")
71
+
72
+ # Display a note about markdown rendering only once at the beginning
73
+ if prettify and not no_stream and not stream_prettify:
74
+ print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
75
+
67
76
  # Custom separator - use the same length for consistency
68
77
  def print_separator():
69
- print(f"\n{separator}\n")
78
+ # Make sure there's exactly one newline before and after
79
+ # Use sys.stdout.write for direct control, avoiding any extra newlines
80
+ sys.stdout.write(f"\n{separator}\n")
81
+ sys.stdout.flush()
70
82
 
71
83
  # Initialize conversation history
72
84
  system_prompt = preprompt if preprompt else "You are a helpful assistant."
@@ -181,20 +193,49 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
181
193
  # Log user message if logging is enabled
182
194
  if logger:
183
195
  logger.log("user", user_input)
196
+
197
+ # Enhance prompt with web search if enabled
198
+ enhanced_prompt = user_input
199
+ if web_search:
200
+ try:
201
+ print(f"{COLORS['cyan']}Searching the web...{COLORS['reset']}")
202
+ enhanced_prompt = enhance_prompt_with_web_search(user_input, logger=logger)
203
+ print(f"{COLORS['green']}Enhanced input with web search results.{COLORS['reset']}")
204
+
205
+ # Update the user message in conversation with enhanced prompt
206
+ for i in range(len(conversation) - 1, -1, -1):
207
+ if conversation[i]["role"] == "user" and conversation[i]["content"] == user_input:
208
+ conversation[i]["content"] = enhanced_prompt
209
+ break
210
+
211
+ # Log the enhanced prompt if logging is enabled
212
+ if logger:
213
+ # Use "web_search" role instead of "system" for clearer logs
214
+ logger.log("web_search", enhanced_prompt.replace(user_input, "").strip())
215
+ except Exception as e:
216
+ print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
217
+ # Continue with the original prompt if web search fails
184
218
 
185
- # Print assistant indicator with formatting
186
- if not no_stream and not stream_prettify:
187
- print(f"\n{ngpt_header()}: {COLORS['reset']}", end="", flush=True)
188
- elif not stream_prettify:
189
- print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
219
+ # Print assistant indicator with formatting - but only if we're not going to show a rich formatted box
220
+ # With Rich prettify, no header should be printed as the Rich panel already includes it
221
+ should_print_header = True
222
+
223
+ # Determine if we should print a header based on formatting options
224
+ if prettify and renderer != 'glow':
225
+ # Don't print header for Rich prettify
226
+ should_print_header = False
227
+
228
+ # Print the header if needed
229
+ if should_print_header:
230
+ if not no_stream and not stream_prettify:
231
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", end="", flush=True)
232
+ elif not stream_prettify:
233
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
190
234
 
191
- # If prettify is enabled with regular streaming
235
+ # Determine streaming behavior
192
236
  if prettify and not no_stream and not stream_prettify:
193
- print(f"\n{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
194
- print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
195
237
  should_stream = False
196
238
  else:
197
- # Regular behavior with stream-prettify taking precedence
198
239
  should_stream = not no_stream
199
240
 
200
241
  # Setup for stream-prettify
@@ -205,9 +246,15 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
205
246
  first_content_received = False
206
247
 
207
248
  if stream_prettify and should_stream:
208
- # Get the correct header for interactive mode
209
- header = ngpt_header()
210
- live_display, stream_callback, setup_spinner = prettify_streaming_markdown(renderer, is_interactive=True, header_text=header)
249
+ # Don't pass the header_text for rich renderer as it already creates its own header,
250
+ # but pass it for other renderers like glow
251
+ if renderer == 'rich' or renderer == 'auto':
252
+ live_display, stream_callback, setup_spinner = prettify_streaming_markdown(renderer, is_interactive=True)
253
+ else:
254
+ # Get the correct header for interactive mode for non-rich renderers
255
+ header = ngpt_header()
256
+ live_display, stream_callback, setup_spinner = prettify_streaming_markdown(renderer, is_interactive=True, header_text=header)
257
+
211
258
  if not live_display:
212
259
  # Fallback to normal prettify if live display setup failed
213
260
  prettify = True
@@ -229,8 +276,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
229
276
  if stop_spinner_func:
230
277
  stop_spinner_func()
231
278
 
232
- # Clear the spinner line completely
233
- sys.stdout.write("\r" + " " * 100 + "\r")
279
+ # Clear the spinner line completely without leaving extra newlines
280
+ # Use direct terminal control to ensure consistency
281
+ sys.stdout.write("\r" + " " * shutil.get_terminal_size().columns + "\r")
234
282
  sys.stdout.flush()
235
283
 
236
284
  # Now start the live display
@@ -250,10 +298,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
250
298
 
251
299
  # Get AI response with conversation history
252
300
  response = client.chat(
253
- prompt=user_input,
301
+ prompt=enhanced_prompt,
254
302
  messages=conversation,
255
303
  stream=should_stream,
256
- web_search=web_search,
257
304
  temperature=temperature,
258
305
  top_p=top_p,
259
306
  max_tokens=max_tokens,
@@ -264,7 +311,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
264
311
  # Ensure spinner is stopped if no content was received
265
312
  if stop_spinner_event and not first_content_received:
266
313
  stop_spinner_event.set()
267
- sys.stdout.write("\r" + " " * 100 + "\r")
314
+ # Clear the spinner line completely without leaving extra newlines
315
+ # Use direct terminal control to ensure consistency
316
+ sys.stdout.write("\r" + " " * shutil.get_terminal_size().columns + "\r")
268
317
  sys.stdout.flush()
269
318
 
270
319
  # Stop live display if using stream-prettify
@@ -281,6 +330,7 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
281
330
  # Print response if not streamed (either due to no_stream or prettify)
282
331
  if no_stream or prettify:
283
332
  if prettify:
333
+ # For pretty formatting with rich, don't print any header text as the rich renderer already includes it
284
334
  prettify_markdown(response, renderer)
285
335
  else:
286
336
  print(response)
ngpt/cli/modes/chat.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from ..formatters import COLORS
2
2
  from ..renderers import prettify_markdown, prettify_streaming_markdown
3
3
  from ..ui import spinner
4
+ from ...utils import enhance_prompt_with_web_search
4
5
  import sys
5
6
  import threading
6
7
 
@@ -53,6 +54,21 @@ def chat_mode(client, args, logger=None):
53
54
  # Log the user message if logging is enabled
54
55
  if logger:
55
56
  logger.log("user", prompt)
57
+
58
+ # Enhance prompt with web search if enabled
59
+ if args.web_search:
60
+ try:
61
+ original_prompt = prompt
62
+ prompt = enhance_prompt_with_web_search(prompt, logger=logger)
63
+ print("Enhanced input with web search results.")
64
+
65
+ # Log the enhanced prompt if logging is enabled
66
+ if logger:
67
+ # Use "web_search" role instead of "system" for clearer logs
68
+ logger.log("web_search", prompt.replace(original_prompt, "").strip())
69
+ except Exception as e:
70
+ print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
71
+ # Continue with the original prompt if web search fails
56
72
 
57
73
  # Create messages array with preprompt if available
58
74
  messages = None
@@ -87,7 +103,7 @@ def chat_mode(client, args, logger=None):
87
103
 
88
104
  # If regular prettify is enabled with streaming, inform the user
89
105
  if args.prettify and not args.no_stream:
90
- print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
106
+ print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
91
107
 
92
108
  # Show a static message if live_display is not available
93
109
  if args.stream_prettify and not live_display:
@@ -123,7 +139,7 @@ def chat_mode(client, args, logger=None):
123
139
  if args.stream_prettify and live_display:
124
140
  stream_callback = spinner_handling_callback
125
141
 
126
- response = client.chat(prompt, stream=should_stream, web_search=args.web_search,
142
+ response = client.chat(prompt, stream=should_stream,
127
143
  temperature=args.temperature, top_p=args.top_p,
128
144
  max_tokens=args.max_tokens, messages=messages,
129
145
  markdown_format=args.prettify or args.stream_prettify,
ngpt/cli/modes/code.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from ..formatters import COLORS
2
2
  from ..renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer, show_available_renderers
3
3
  from ..ui import spinner
4
+ from ...utils import enhance_prompt_with_web_search
4
5
  import sys
5
6
  import threading
6
7
 
@@ -25,6 +26,21 @@ def code_mode(client, args, logger=None):
25
26
  # Log the user prompt if logging is enabled
26
27
  if logger:
27
28
  logger.log("user", prompt)
29
+
30
+ # Enhance prompt with web search if enabled
31
+ if args.web_search:
32
+ try:
33
+ original_prompt = prompt
34
+ prompt = enhance_prompt_with_web_search(prompt, logger=logger)
35
+ print("Enhanced input with web search results.")
36
+
37
+ # Log the enhanced prompt if logging is enabled
38
+ if logger:
39
+ # Use "web_search" role instead of "system" for clearer logs
40
+ logger.log("web_search", prompt.replace(original_prompt, "").strip())
41
+ except Exception as e:
42
+ print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
43
+ # Continue with the original prompt if web search fails
28
44
 
29
45
  # Setup for streaming and prettify logic
30
46
  stream_callback = None
@@ -62,7 +78,7 @@ def code_mode(client, args, logger=None):
62
78
  if has_markdown_renderer(args.renderer):
63
79
  should_stream = False
64
80
  use_regular_prettify = True
65
- print(f"{COLORS['yellow']}Note: Streaming disabled to enable regular markdown rendering (--prettify).{COLORS['reset']}")
81
+ print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
66
82
  else:
67
83
  # Renderer not available for prettify
68
84
  print(f"{COLORS['yellow']}Warning: Renderer '{args.renderer}' not available for --prettify.{COLORS['reset']}")
@@ -110,8 +126,7 @@ def code_mode(client, args, logger=None):
110
126
 
111
127
  generated_code = client.generate_code(
112
128
  prompt=prompt,
113
- language=args.language,
114
- web_search=args.web_search,
129
+ language=args.language,
115
130
  temperature=args.temperature,
116
131
  top_p=args.top_p,
117
132
  max_tokens=args.max_tokens,
ngpt/cli/modes/rewrite.py CHANGED
@@ -5,6 +5,7 @@ import time
5
5
  from ..formatters import COLORS
6
6
  from ..renderers import prettify_markdown, prettify_streaming_markdown
7
7
  from ..ui import get_multiline_input, spinner
8
+ from ...utils import enhance_prompt_with_web_search
8
9
 
9
10
  # System prompt for rewriting text
10
11
  REWRITE_SYSTEM_PROMPT = """You are an expert text editor and rewriter. Your task is to rewrite the user's text to improve readability and flow while carefully preserving the original meaning, tone, and style.
@@ -125,6 +126,21 @@ def rewrite_mode(client, args, logger=None):
125
126
  print(f"{COLORS['yellow']}Error: Empty input. Please provide text to rewrite.{COLORS['reset']}")
126
127
  return
127
128
 
129
+ # Enhance input with web search if enabled
130
+ if args.web_search:
131
+ try:
132
+ original_text = input_text
133
+ input_text = enhance_prompt_with_web_search(input_text, logger=logger)
134
+ print("Enhanced input with web search results.")
135
+
136
+ # Log the enhanced input if logging is enabled
137
+ if logger:
138
+ # Use "web_search" role instead of "system" for clearer logs
139
+ logger.log("web_search", input_text.replace(original_text, "").strip())
140
+ except Exception as e:
141
+ print(f"{COLORS['yellow']}Warning: Failed to enhance input with web search: {str(e)}{COLORS['reset']}")
142
+ # Continue with the original input if web search fails
143
+
128
144
  # Set up messages array with system prompt and user content
129
145
  messages = [
130
146
  {"role": "system", "content": REWRITE_SYSTEM_PROMPT},
@@ -156,7 +172,7 @@ def rewrite_mode(client, args, logger=None):
156
172
 
157
173
  # If regular prettify is enabled with streaming, inform the user
158
174
  if args.prettify and not args.no_stream:
159
- print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
175
+ print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
160
176
 
161
177
  # Show a static message if live_display is not available
162
178
  if args.stream_prettify and not live_display:
@@ -195,7 +211,6 @@ def rewrite_mode(client, args, logger=None):
195
211
  response = client.chat(
196
212
  prompt=None, # Not used when messages are provided
197
213
  stream=should_stream,
198
- web_search=args.web_search,
199
214
  temperature=args.temperature,
200
215
  top_p=args.top_p,
201
216
  max_tokens=args.max_tokens,
ngpt/cli/modes/shell.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from ..formatters import COLORS
2
2
  from ..ui import spinner
3
+ from ...utils import enhance_prompt_with_web_search
3
4
  import subprocess
4
5
  import sys
5
6
  import threading
@@ -26,6 +27,21 @@ def shell_mode(client, args, logger=None):
26
27
  if logger:
27
28
  logger.log("user", prompt)
28
29
 
30
+ # Enhance prompt with web search if enabled
31
+ if args.web_search:
32
+ try:
33
+ original_prompt = prompt
34
+ prompt = enhance_prompt_with_web_search(prompt, logger=logger)
35
+ print("Enhanced input with web search results.")
36
+
37
+ # Log the enhanced prompt if logging is enabled
38
+ if logger:
39
+ # Use "web_search" role instead of "system" for clearer logs
40
+ logger.log("web_search", prompt.replace(original_prompt, "").strip())
41
+ except Exception as e:
42
+ print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
43
+ # Continue with the original prompt if web search fails
44
+
29
45
  # Start spinner while waiting for command generation
30
46
  stop_spinner = threading.Event()
31
47
  spinner_thread = threading.Thread(
@@ -37,9 +53,9 @@ def shell_mode(client, args, logger=None):
37
53
  spinner_thread.start()
38
54
 
39
55
  try:
40
- command = client.generate_shell_command(prompt, web_search=args.web_search,
41
- temperature=args.temperature, top_p=args.top_p,
42
- max_tokens=args.max_tokens)
56
+ command = client.generate_shell_command(prompt,
57
+ temperature=args.temperature, top_p=args.top_p,
58
+ max_tokens=args.max_tokens)
43
59
  finally:
44
60
  # Stop the spinner
45
61
  stop_spinner.set()
ngpt/cli/modes/text.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from ..formatters import COLORS
2
2
  from ..renderers import prettify_markdown, prettify_streaming_markdown
3
3
  from ..ui import get_multiline_input, spinner
4
+ from ...utils import enhance_prompt_with_web_search
4
5
  import threading
5
6
  import sys
6
7
 
@@ -25,6 +26,21 @@ def text_mode(client, args, logger=None):
25
26
  if logger:
26
27
  logger.log("user", prompt)
27
28
 
29
+ # Enhance prompt with web search if enabled
30
+ if args.web_search:
31
+ try:
32
+ original_prompt = prompt
33
+ prompt = enhance_prompt_with_web_search(prompt, logger=logger)
34
+ print("Enhanced input with web search results.")
35
+
36
+ # Log the enhanced prompt if logging is enabled
37
+ if logger:
38
+ # Use "web_search" role instead of "system" for clearer logs
39
+ logger.log("web_search", prompt.replace(original_prompt, "").strip())
40
+ except Exception as e:
41
+ print(f"{COLORS['yellow']}Warning: Failed to enhance prompt with web search: {str(e)}{COLORS['reset']}")
42
+ # Continue with the original prompt if web search fails
43
+
28
44
  # Create messages array with preprompt if available
29
45
  messages = None
30
46
  if args.preprompt:
@@ -58,7 +74,7 @@ def text_mode(client, args, logger=None):
58
74
 
59
75
  # If regular prettify is enabled with streaming, inform the user
60
76
  if args.prettify and not args.no_stream:
61
- print(f"{COLORS['yellow']}Note: Streaming disabled to enable markdown rendering.{COLORS['reset']}")
77
+ print(f"{COLORS['yellow']}Note: Using standard markdown rendering (--prettify). For streaming markdown rendering, use --stream-prettify instead.{COLORS['reset']}")
62
78
 
63
79
  # Show a static message if live_display is not available
64
80
  if args.stream_prettify and not live_display:
@@ -94,7 +110,7 @@ def text_mode(client, args, logger=None):
94
110
  if args.stream_prettify and live_display:
95
111
  stream_callback = spinner_handling_callback
96
112
 
97
- response = client.chat(prompt, stream=should_stream, web_search=args.web_search,
113
+ response = client.chat(prompt, stream=should_stream,
98
114
  temperature=args.temperature, top_p=args.top_p,
99
115
  max_tokens=args.max_tokens, messages=messages,
100
116
  markdown_format=args.prettify or args.stream_prettify,
ngpt/cli/renderers.py CHANGED
@@ -168,8 +168,31 @@ def prettify_markdown(text, renderer='auto'):
168
168
  # Use rich
169
169
  try:
170
170
  console = Console()
171
+
172
+ # Create a panel around the markdown for consistency with stream_prettify
173
+ from rich.panel import Panel
174
+ import rich.box
175
+ from rich.text import Text
176
+
177
+ # Get terminal dimensions
178
+ term_width = shutil.get_terminal_size().columns
179
+
180
+ # Create panel with similar styling to stream_prettify
181
+ clean_header = "🤖 nGPT"
182
+ panel_title = Text(clean_header, style="cyan bold")
183
+
171
184
  md = Markdown(text)
172
- console.print(md)
185
+ panel = Panel(
186
+ md,
187
+ title=panel_title,
188
+ title_align="left",
189
+ border_style="cyan",
190
+ padding=(1, 1),
191
+ width=console.width - 4, # Make panel slightly narrower than console
192
+ box=rich.box.ROUNDED
193
+ )
194
+
195
+ console.print(panel)
173
196
  return True
174
197
  except Exception as e:
175
198
  print(f"{COLORS['yellow']}Error using rich for markdown: {str(e)}{COLORS['reset']}")
@@ -237,7 +260,20 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
237
260
  box=rich.box.ROUNDED
238
261
  )
239
262
  else:
240
- md_obj = Markdown("")
263
+ # Always use a panel - even in non-interactive mode
264
+ clean_header = "🤖 nGPT"
265
+ panel_title = Text(clean_header, style="cyan bold")
266
+
267
+ padding = (1, 1) # Less horizontal padding (left, right)
268
+ md_obj = Panel(
269
+ Markdown(""),
270
+ title=panel_title,
271
+ title_align="left",
272
+ border_style="cyan",
273
+ padding=padding,
274
+ width=console.width - 4, # Make panel slightly narrower than console
275
+ box=rich.box.ROUNDED
276
+ )
241
277
 
242
278
  # Get terminal dimensions for better display
243
279
  term_width = shutil.get_terminal_size().columns
@@ -275,9 +311,8 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
275
311
  # Start live display on first content
276
312
  if first_update:
277
313
  first_update = False
278
- # Clear the spinner line completely before starting the display
279
- sys.stdout.write("\r" + " " * 100 + "\r")
280
- sys.stdout.flush()
314
+ # Let the spinner's clean_exit handle the cleanup
315
+ # No additional cleanup needed here
281
316
  live.start()
282
317
 
283
318
  # Update content in live display
@@ -304,17 +339,17 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
304
339
  if not is_complete:
305
340
  # Calculate approximate lines needed
306
341
  content_lines = content.count('\n') + 1
307
- available_height = display_height - 1 # Account for minimal overhead
342
+ available_height = display_height - 4 # Account for panel borders and padding
308
343
 
309
344
  if content_lines > available_height:
310
345
  # If content is too big, show only the last part that fits
311
346
  lines = content.split('\n')
312
347
  truncated_content = '\n'.join(lines[-available_height:])
313
- md_obj = Markdown(truncated_content)
348
+ md_obj.renderable = Markdown(truncated_content)
314
349
  else:
315
- md_obj = Markdown(content)
350
+ md_obj.renderable = Markdown(content)
316
351
  else:
317
- md_obj = Markdown(content)
352
+ md_obj.renderable = Markdown(content)
318
353
 
319
354
  live.update(md_obj)
320
355
 
@@ -344,7 +379,7 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
344
379
  spinner_thread = threading.Thread(
345
380
  target=spinner,
346
381
  args=(message,),
347
- kwargs={"stop_event": stop_event, "color": color}
382
+ kwargs={"stop_event": stop_event, "color": color, "clean_exit": True}
348
383
  )
349
384
  spinner_thread.daemon = True
350
385
  spinner_thread.start()
ngpt/cli/ui.py CHANGED
@@ -155,7 +155,7 @@ def get_multiline_input():
155
155
  print("\nInput cancelled by user. Exiting gracefully.")
156
156
  return None
157
157
 
158
- def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color=None, stop_event=None):
158
+ def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color=None, stop_event=None, clean_exit=False):
159
159
  """Display a spinner animation with a message.
160
160
 
161
161
  Args:
@@ -165,6 +165,7 @@ def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color
165
165
  color: Optional color from COLORS dict to use for the message
166
166
  stop_event: Optional threading.Event to signal when to stop the spinner
167
167
  If provided, duration is ignored and spinner runs until event is set
168
+ clean_exit: When True, cleans up more aggressively to prevent blank lines
168
169
  """
169
170
  char_duration = 0.2
170
171
 
@@ -173,19 +174,30 @@ def spinner(message, duration=5, spinner_chars="⣾⣽⣻⢿⡿⣟⣯⣷", color
173
174
  if color:
174
175
  colored_message = f"{color}{message}{COLORS['reset']}"
175
176
 
177
+ # Save cursor position - will be needed for clean exit
178
+ if clean_exit:
179
+ # Start by printing a \r to ensure we begin at the start of the line
180
+ sys.stdout.write("\r")
181
+ sys.stdout.flush()
182
+
176
183
  if stop_event:
177
184
  i = 0
178
185
  while not stop_event.is_set():
179
186
  char = spinner_chars[i % len(spinner_chars)]
180
- print(f"\r{colored_message} {char}", end="", flush=True)
187
+ # Always use sys.stdout.write for consistent behavior
188
+ sys.stdout.write(f"\r{colored_message} {char}")
189
+ sys.stdout.flush()
181
190
  i += 1
182
191
  time.sleep(char_duration)
183
192
  else:
184
193
  total_chars = int(duration / char_duration)
185
194
  for i in range(total_chars):
186
195
  char = spinner_chars[i % len(spinner_chars)]
187
- print(f"\r{colored_message} {char}", end="", flush=True)
196
+ sys.stdout.write(f"\r{colored_message} {char}")
197
+ sys.stdout.flush()
188
198
  time.sleep(char_duration)
189
199
 
190
- # Clear the line when done
191
- print("\r" + " " * (len(message) + 10) + "\r", end="", flush=True)
200
+ # Clear the line when done - use terminal width to clear the entire line
201
+ terminal_width = shutil.get_terminal_size().columns
202
+ sys.stdout.write("\r" + " " * terminal_width + "\r")
203
+ sys.stdout.flush()
ngpt/client.py CHANGED
@@ -32,7 +32,6 @@ class NGPTClient:
32
32
  max_tokens: Optional[int] = None,
33
33
  top_p: float = 1.0,
34
34
  messages: Optional[List[Dict[str, str]]] = None,
35
- web_search: bool = False,
36
35
  markdown_format: bool = False,
37
36
  stream_callback: Optional[callable] = None,
38
37
  **kwargs
@@ -47,7 +46,6 @@ class NGPTClient:
47
46
  max_tokens: Maximum number of tokens to generate
48
47
  top_p: Controls diversity via nucleus sampling
49
48
  messages: Optional list of message objects to override default behavior
50
- web_search: Whether to enable web search capability
51
49
  markdown_format: If True, allow markdown-formatted responses, otherwise plain text
52
50
  stream_callback: Optional callback function for streaming mode updates
53
51
  **kwargs: Additional arguments to pass to the API
@@ -75,10 +73,6 @@ class NGPTClient:
75
73
  "top_p": top_p,
76
74
  }
77
75
 
78
- # Conditionally add web_search
79
- if web_search:
80
- payload["web_search"] = True
81
-
82
76
  # Add max_tokens if provided
83
77
  if max_tokens is not None:
84
78
  payload["max_tokens"] = max_tokens
@@ -180,7 +174,6 @@ class NGPTClient:
180
174
  def generate_shell_command(
181
175
  self,
182
176
  prompt: str,
183
- web_search: bool = False,
184
177
  temperature: float = 0.4,
185
178
  top_p: float = 0.95,
186
179
  max_tokens: Optional[int] = None
@@ -190,7 +183,6 @@ class NGPTClient:
190
183
 
191
184
  Args:
192
185
  prompt: Description of the command to generate
193
- web_search: Whether to enable web search capability
194
186
  temperature: Controls randomness in the response
195
187
  top_p: Controls diversity via nucleus sampling
196
188
  max_tokens: Maximum number of tokens to generate
@@ -241,7 +233,6 @@ Command:"""
241
233
  prompt=prompt,
242
234
  stream=False,
243
235
  messages=messages,
244
- web_search=web_search,
245
236
  temperature=temperature,
246
237
  top_p=top_p,
247
238
  max_tokens=max_tokens
@@ -254,7 +245,6 @@ Command:"""
254
245
  self,
255
246
  prompt: str,
256
247
  language: str = "python",
257
- web_search: bool = False,
258
248
  temperature: float = 0.4,
259
249
  top_p: float = 0.95,
260
250
  max_tokens: Optional[int] = None,
@@ -268,7 +258,6 @@ Command:"""
268
258
  Args:
269
259
  prompt: Description of the code to generate
270
260
  language: Programming language to generate code in
271
- web_search: Whether to enable web search capability
272
261
  temperature: Controls randomness in the response
273
262
  top_p: Controls diversity via nucleus sampling
274
263
  max_tokens: Maximum number of tokens to generate
@@ -315,7 +304,6 @@ Code:"""
315
304
  prompt=prompt,
316
305
  stream=stream,
317
306
  messages=messages,
318
- web_search=web_search,
319
307
  temperature=temperature,
320
308
  top_p=top_p,
321
309
  max_tokens=max_tokens,
ngpt/utils/__init__.py CHANGED
@@ -22,6 +22,11 @@ from .cli_config import (
22
22
  get_cli_config_dir,
23
23
  get_cli_config_path
24
24
  )
25
+ from .web_search import (
26
+ enhance_prompt_with_web_search,
27
+ get_web_search_results,
28
+ format_web_search_results_for_prompt
29
+ )
25
30
 
26
31
  __all__ = [
27
32
  "create_logger", "Logger",
@@ -29,5 +34,6 @@ __all__ = [
29
34
  "add_config_entry", "remove_config_entry", "DEFAULT_CONFIG", "DEFAULT_CONFIG_ENTRY",
30
35
  "load_cli_config", "set_cli_config_option", "get_cli_config_option",
31
36
  "unset_cli_config_option", "apply_cli_config", "list_cli_config_options",
32
- "CLI_CONFIG_OPTIONS", "get_cli_config_dir", "get_cli_config_path"
37
+ "CLI_CONFIG_OPTIONS", "get_cli_config_dir", "get_cli_config_path",
38
+ "enhance_prompt_with_web_search", "get_web_search_results", "format_web_search_results_for_prompt"
33
39
  ]
@@ -0,0 +1,270 @@
1
+ """
2
+ Web search utilities for nGPT using duckduckgo-search and trafilatura.
3
+
4
+ This module provides functionality to search the web and extract
5
+ information from search results to enhance AI prompts.
6
+ """
7
+
8
+ import re
9
+ from typing import List, Dict, Any, Optional
10
+ from duckduckgo_search import DDGS
11
+ from urllib.parse import urlparse
12
+ import requests
13
+ import sys
14
+
15
+ # Get actual logger from global context instead of using standard logging
16
+ from . import log
17
+
18
+ # Use a global variable to store the logger provided during runtime
19
+ _logger = None
20
+
21
+ def set_logger(logger):
22
+ """Set the logger to use for this module."""
23
+ global _logger
24
+ _logger = logger
25
+
26
+ def get_logger():
27
+ """Get the current logger or use a default."""
28
+ if _logger is not None:
29
+ return _logger
30
+ else:
31
+ # Default logging to stderr if no logger provided
32
+ class DefaultLogger:
33
+ def info(self, msg): print(f"INFO: {msg}", file=sys.stderr)
34
+ def error(self, msg): print(f"ERROR: {msg}", file=sys.stderr)
35
+ def warning(self, msg): print(f"WARNING: {msg}", file=sys.stderr)
36
+ def debug(self, msg): pass
37
+ return DefaultLogger()
38
+
39
+ def perform_web_search(query: str, max_results: int = 3) -> List[Dict[str, Any]]:
40
+ """
41
+ Search the web using DuckDuckGo and return relevant results.
42
+
43
+ Args:
44
+ query: The search query
45
+ max_results: Maximum number of results to return
46
+
47
+ Returns:
48
+ List of dictionaries containing search results (title, url, snippet)
49
+ """
50
+ logger = get_logger()
51
+ try:
52
+ ddgs = DDGS()
53
+ results = list(ddgs.text(query, max_results=max_results))
54
+ return results
55
+ except Exception as e:
56
+ logger.error(f"Error performing web search: {str(e)}")
57
+ logger.info("Web search encountered an issue, but will continue with available results")
58
+ return []
59
+
60
+ def extract_article_content(url: str, max_chars: int = 2000) -> Optional[str]:
61
+ """
62
+ Extract and clean content from a webpage URL.
63
+
64
+ Args:
65
+ url: The URL to extract content from
66
+ max_chars: Maximum number of characters to extract
67
+
68
+ Returns:
69
+ Cleaned article text or None if extraction failed
70
+ """
71
+ logger = get_logger()
72
+ try:
73
+ # Skip non-http URLs or suspicious domains
74
+ parsed_url = urlparse(url)
75
+ if not parsed_url.scheme.startswith('http'):
76
+ return None
77
+
78
+ # Browser-like user agent
79
+ headers = {
80
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
81
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
82
+ 'Accept-Language': 'en-US,en;q=0.5',
83
+ 'Connection': 'keep-alive',
84
+ 'Upgrade-Insecure-Requests': '1',
85
+ }
86
+
87
+ logger.info(f"Fetching content from {url}")
88
+
89
+ try:
90
+ # Try using trafilatura
91
+ import trafilatura
92
+
93
+ # Download with correct parameters
94
+ # trafilatura handles user-agent internally
95
+ downloaded = trafilatura.fetch_url(url)
96
+
97
+ if downloaded:
98
+ # Extract main content
99
+ content = trafilatura.extract(downloaded, include_comments=False,
100
+ include_tables=False,
101
+ no_fallback=False)
102
+
103
+ if content:
104
+ # Clean up content if needed
105
+ content = content.strip()
106
+
107
+ # Truncate if needed
108
+ if len(content) > max_chars:
109
+ content = content[:max_chars] + "..."
110
+
111
+ return content
112
+
113
+ # If trafilatura failed, try direct requests
114
+ logger.info(f"Trafilatura extraction failed for {url}, trying fallback method")
115
+ response = requests.get(url, headers=headers, timeout=10)
116
+
117
+ if response.status_code == 200:
118
+ # Very basic HTML cleaning
119
+ html_content = response.text
120
+ # Remove HTML tags
121
+ text = re.sub(r'<[^>]+>', ' ', html_content)
122
+ # Remove excess whitespace
123
+ text = re.sub(r'\s+', ' ', text).strip()
124
+
125
+ if text:
126
+ if len(text) > max_chars:
127
+ text = text[:max_chars] + "..."
128
+ return text
129
+
130
+ else:
131
+ logger.error(f"Request to {url} returned status code {response.status_code}")
132
+
133
+ except ImportError:
134
+ logger.error("Trafilatura not installed. Install with 'pip install trafilatura'")
135
+ # Try direct requests only
136
+ try:
137
+ response = requests.get(url, headers=headers, timeout=10)
138
+ if response.status_code == 200:
139
+ # Very basic HTML cleaning
140
+ html_content = response.text
141
+ text = re.sub(r'<[^>]+>', ' ', html_content)
142
+ text = re.sub(r'\s+', ' ', text).strip()
143
+
144
+ if text:
145
+ if len(text) > max_chars:
146
+ text = text[:max_chars] + "..."
147
+ return text
148
+ except Exception as req_error:
149
+ logger.error(f"Direct request fallback failed: {str(req_error)}")
150
+
151
+ except Exception as e:
152
+ logger.error(f"Error extracting content with trafilatura: {str(e)}")
153
+ # Try the requests fallback
154
+ try:
155
+ response = requests.get(url, headers=headers, timeout=10)
156
+ if response.status_code == 200:
157
+ html_content = response.text
158
+ text = re.sub(r'<[^>]+>', ' ', html_content)
159
+ text = re.sub(r'\s+', ' ', text).strip()
160
+
161
+ if text:
162
+ if len(text) > max_chars:
163
+ text = text[:max_chars] + "..."
164
+ return text
165
+ except Exception as req_error:
166
+ logger.error(f"Direct request fallback failed: {str(req_error)}")
167
+
168
+ return None
169
+ except Exception as e:
170
+ logger.error(f"Error extracting content from {url}: {str(e)}")
171
+ return None
172
+
173
+ def get_web_search_results(query: str, max_results: int = 3, max_chars_per_result: int = 2000) -> Dict[str, Any]:
174
+ """
175
+ Get formatted web search results ready to be included in AI prompts.
176
+
177
+ Args:
178
+ query: The search query
179
+ max_results: Maximum number of results to include
180
+ max_chars_per_result: Maximum characters to include per result
181
+
182
+ Returns:
183
+ Dictionary containing search results and metadata
184
+ """
185
+ logger = get_logger()
186
+ search_results = perform_web_search(query, max_results)
187
+ enhanced_results = []
188
+ success_count = 0
189
+ failure_count = 0
190
+
191
+ for result in search_results:
192
+ content = extract_article_content(result['href'], max_chars_per_result)
193
+
194
+ enhanced_results.append({
195
+ 'title': result.get('title', ''),
196
+ 'url': result.get('href', ''),
197
+ 'snippet': result.get('body', ''),
198
+ 'content': content if content else result.get('body', '')
199
+ })
200
+
201
+ if content:
202
+ success_count += 1
203
+ else:
204
+ failure_count += 1
205
+
206
+ # Log a user-friendly summary
207
+ if search_results:
208
+ if failure_count > 0:
209
+ logger.info(f"Retrieved content from {success_count} out of {len(search_results)} sources")
210
+ else:
211
+ logger.info(f"Successfully retrieved content from all {success_count} sources")
212
+ else:
213
+ logger.error("No search results were found")
214
+
215
+ return {
216
+ 'query': query,
217
+ 'timestamp': 'current_time', # Could replace with actual timestamp
218
+ 'results': enhanced_results
219
+ }
220
+
221
+ def format_web_search_results_for_prompt(search_results: Dict[str, Any]) -> str:
222
+ """
223
+ Format web search results into a string to include in AI prompts.
224
+
225
+ Args:
226
+ search_results: Dictionary of search results from get_web_search_results()
227
+
228
+ Returns:
229
+ Formatted string to include in prompts
230
+ """
231
+ query = search_results['query']
232
+ results = search_results['results']
233
+
234
+ formatted_text = f"[Web Search Results for: {query}]\n\n"
235
+
236
+ for i, result in enumerate(results, 1):
237
+ formatted_text += f"RESULT {i}: {result['title']}\n"
238
+ formatted_text += f"URL: {result['url']}\n"
239
+ formatted_text += f"CONTENT:\n{result['content']}\n\n"
240
+
241
+ formatted_text += f"[End of Web Search Results]\n\n"
242
+ formatted_text += "Consider the above information when answering the following question:\n\n"
243
+
244
+ return formatted_text
245
+
246
+ def enhance_prompt_with_web_search(prompt: str, max_results: int = 3, logger=None) -> str:
247
+ """
248
+ Enhance a prompt with web search results.
249
+
250
+ Args:
251
+ prompt: The original user prompt
252
+ max_results: Maximum number of search results to include
253
+ logger: Optional logger to use
254
+
255
+ Returns:
256
+ Enhanced prompt with web search results prepended
257
+ """
258
+ # Set the logger for this module
259
+ if logger is not None:
260
+ set_logger(logger)
261
+
262
+ logger = get_logger()
263
+ search_results = get_web_search_results(prompt, max_results)
264
+ formatted_results = format_web_search_results_for_prompt(search_results)
265
+
266
+ # Combine results with original prompt
267
+ enhanced_prompt = formatted_results + prompt
268
+
269
+ logger.info("Enhanced input with web search results")
270
+ return enhanced_prompt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 3.1.1
3
+ Version: 3.3.0
4
4
  Summary: Swiss army knife for LLMs: powerful CLI, interactive chatbot, and flexible Python library. Works with OpenAI, Ollama, Groq, Claude, Gemini, and any OpenAI-compatible API.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -28,10 +28,12 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
28
28
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
29
  Classifier: Topic :: Utilities
30
30
  Requires-Python: >=3.8
31
+ Requires-Dist: duckduckgo-search>=3.0.0
31
32
  Requires-Dist: prompt-toolkit>=3.0.0
32
33
  Requires-Dist: pyperclip>=1.8.0
33
34
  Requires-Dist: requests>=2.31.0
34
35
  Requires-Dist: rich>=10.0.0
36
+ Requires-Dist: trafilatura>=1.6.0
35
37
  Description-Content-Type: text/markdown
36
38
 
37
39
  # nGPT
@@ -63,7 +65,7 @@ Description-Content-Type: text/markdown
63
65
  - 🔄 **API Flexibility**: Works with OpenAI, Ollama, Groq, Claude, Gemini, and any compatible endpoint
64
66
  - 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
65
67
  - 📊 **Streaming Responses**: Real-time output for better user experience
66
- - 🔍 **Web Search**: Integrated with compatible API endpoints
68
+ - 🔍 **Web Search**: Enhance any model with contextual information from the web
67
69
  - 📥 **Stdin Processing**: Process piped content by using `{}` placeholder in prompts
68
70
  - 🎨 **Markdown Rendering**: Beautiful formatting of markdown and code with syntax highlighting
69
71
  - ⚡ **Real-time Markdown**: Stream responses with live updating syntax highlighting and formatting
@@ -271,7 +273,7 @@ ngpt --list-models --provider Gemini
271
273
  # With custom options
272
274
  ngpt --api-key your-key --base-url http://your-endpoint --model your-model "Hello"
273
275
 
274
- # Enable web search (if your API endpoint supports it)
276
+ # Enable web search capability to enhance prompts with web information
275
277
  ngpt --web-search "What's the latest news about AI?"
276
278
 
277
279
  # Generate and execute shell commands (using -s or --shell flag)
@@ -0,0 +1,28 @@
1
+ ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
2
+ ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
3
+ ngpt/client.py,sha256=1kn-kVQ2ZYhOlQ5OPM9c_btVKajfk1qb52QbMyGdYtU,14645
4
+ ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
5
+ ngpt/cli/args.py,sha256=7ixh5anahTeHWsrEKwhaLA_dR3sJ-ZzO8NyGOeDmd3Q,11392
6
+ ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
7
+ ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
8
+ ngpt/cli/interactive.py,sha256=6SrnANvE5T9Luu-CSGPfW7lub4-yog9aqruAfs1XEIg,16392
9
+ ngpt/cli/main.py,sha256=9um40RplKHSW5UHcUUO2cwMNqkGUhfQwikI1CHHFbnk,28926
10
+ ngpt/cli/renderers.py,sha256=m71BeUXKynpKKGXFzwRSW1XngvyKiZ_xEsdujUbU0MA,16597
11
+ ngpt/cli/ui.py,sha256=HoHDFpLiwMBP5wtMb8YYo244FMiqiPFRoBNcNGp6N0A,7310
12
+ ngpt/cli/modes/__init__.py,sha256=R3aO662RIzWEOvr3moTrEI8Tpg0zDDyMGGh1-OxiRgM,285
13
+ ngpt/cli/modes/chat.py,sha256=UlnZWqvxL_CSyVIDZKGVXNlG-KufDfo05E8-8xTcM_Y,6713
14
+ ngpt/cli/modes/code.py,sha256=4TpIV-EkyXb8d3XJKrsLs9Wxq5P9jC82z94PI2Zck_g,6730
15
+ ngpt/cli/modes/gitcommsg.py,sha256=rsfMoeOupmNp-5p5fsMSPAf18BbzXWq-4PF2HjEz6SY,46991
16
+ ngpt/cli/modes/rewrite.py,sha256=ftD-6M9iQ7g4rLdlKyyLTRiJWYtbz64LIG4PIByxmOk,11472
17
+ ngpt/cli/modes/shell.py,sha256=fxE9LEEo4arSn5-q_6zxdnUH7RlqifWmk-_kcA76OhM,4070
18
+ ngpt/cli/modes/text.py,sha256=gdn4opioZ6G3nvfrTkp-dpoD-Of_ZvjVVRggVd6edkg,5528
19
+ ngpt/utils/__init__.py,sha256=qu_66I1Vtav2f1LDiPn5J3DUsbK7o1CSScMcTkYqxoM,1179
20
+ ngpt/utils/cli_config.py,sha256=IlHnOEEGpLoGZInynM778wgpxLVcJ_STKWxg2Ypvir4,11196
21
+ ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
22
+ ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
23
+ ngpt/utils/web_search.py,sha256=DXjf5zJW4jXbmpir6Oqv7abfrFxjqJqvnuAA9gJIFJk,9919
24
+ ngpt-3.3.0.dist-info/METADATA,sha256=1gGlQ6gzQwnnB8wfdmzcwlf3DRV9JNup7HgfXsSRo2Y,29100
25
+ ngpt-3.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ ngpt-3.3.0.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
27
+ ngpt-3.3.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
28
+ ngpt-3.3.0.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
2
- ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
3
- ngpt/client.py,sha256=rLgDPmJe8_yi13-XUiHJ45z54rJVrupxWmeb-fQZGF4,15129
4
- ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
5
- ngpt/cli/args.py,sha256=XQvofZs_WkbgUto3Dbx7Yw-AmPAQHh8kdHUe3uWy8w4,11382
6
- ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
7
- ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
8
- ngpt/cli/interactive.py,sha256=Zep4yiGRFPVrJV1zp7xB5lXg7hVhXhbRlaVA4DgRBbQ,13241
9
- ngpt/cli/main.py,sha256=9um40RplKHSW5UHcUUO2cwMNqkGUhfQwikI1CHHFbnk,28926
10
- ngpt/cli/renderers.py,sha256=ovV0IexZyrjCk8LK5ZGVvf5K7wCLco4jRhDv_tp6Kvw,15184
11
- ngpt/cli/ui.py,sha256=m8qtd4cCSHBGHPUlHVdBEfun1G1Se4vLKTSgnS7QOKE,6775
12
- ngpt/cli/modes/__init__.py,sha256=R3aO662RIzWEOvr3moTrEI8Tpg0zDDyMGGh1-OxiRgM,285
13
- ngpt/cli/modes/chat.py,sha256=oibPaN4LAqrzIbWpw--ql0TwU0u_jlLCeNuxxyO7wPg,5887
14
- ngpt/cli/modes/code.py,sha256=AEsQKdL8mh4ImcI5R7TXzwEq_JBTkmS-L0AA_-dpHm0,5934
15
- ngpt/cli/modes/gitcommsg.py,sha256=rsfMoeOupmNp-5p5fsMSPAf18BbzXWq-4PF2HjEz6SY,46991
16
- ngpt/cli/modes/rewrite.py,sha256=CrzUHxUoZc48D3ooVIvDbCjIIuERO1v-ddG3ajhETQs,10646
17
- ngpt/cli/modes/shell.py,sha256=QkprnOxMMTg2v5DIwcofDnnr3JPNfuk-YgSQaae5Xps,3311
18
- ngpt/cli/modes/text.py,sha256=vCwZnVxIbsTCLE8YVJIPwZsGgk7VRs8v-eeaVwCOZqI,4702
19
- ngpt/utils/__init__.py,sha256=E46suk2-QgYBI0Qrs6WXOajOUOebF3ETAFY7ah8DTWs,942
20
- ngpt/utils/cli_config.py,sha256=IlHnOEEGpLoGZInynM778wgpxLVcJ_STKWxg2Ypvir4,11196
21
- ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
22
- ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
23
- ngpt-3.1.1.dist-info/METADATA,sha256=YPavkwb6Jg4oblYwYtBX3XrDmvcQWVhQZO2d9r5yByI,28992
24
- ngpt-3.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- ngpt-3.1.1.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
26
- ngpt-3.1.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
27
- ngpt-3.1.1.dist-info/RECORD,,
File without changes