ngpt 3.8.2__py3-none-any.whl → 3.9.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
@@ -58,6 +58,10 @@ def setup_argument_parser():
58
58
  help='Show available markdown renderers for use with --prettify')
59
59
  config_group.add_argument('--cli-config', nargs='*', metavar='COMMAND',
60
60
  help='Manage CLI configuration (set, get, unset, list, help)')
61
+
62
+ # Role configuration options
63
+ config_group.add_argument('--role-config', nargs='*', metavar='ACTION',
64
+ help='Manage custom roles (help, create, show, edit, list, remove) [role_name]')
61
65
 
62
66
  # Global options
63
67
  global_group = parser.add_argument_group('Global Options')
@@ -79,8 +83,13 @@ def setup_argument_parser():
79
83
  help='Set max response length in tokens')
80
84
  global_group.add_argument('--log', metavar='FILE', nargs='?', const=True,
81
85
  help='Set filepath to log conversation to, or create a temporary log file if no path provided')
82
- global_group.add_argument('--preprompt',
86
+
87
+ # System prompt options (mutually exclusive)
88
+ prompt_exclusive_group = global_group.add_mutually_exclusive_group()
89
+ prompt_exclusive_group.add_argument('--preprompt',
83
90
  help='Set custom system prompt to control AI behavior')
91
+ prompt_exclusive_group.add_argument('--role',
92
+ help='Use a predefined role to set system prompt (mutually exclusive with --preprompt)')
84
93
 
85
94
  # Output display options (mutually exclusive group)
86
95
  output_group = parser.add_argument_group('Output Display Options (mutually exclusive)')
@@ -226,4 +235,36 @@ def handle_cli_config_args(args):
226
235
  return (True, action, option, value)
227
236
  else:
228
237
  # Unknown action, show help
229
- return (True, "help", None, None)
238
+ return (True, "help", None, None)
239
+
240
+ def handle_role_config_args(args):
241
+ """Process role configuration arguments and determine command parameters.
242
+
243
+ Args:
244
+ args: The parsed command line arguments.
245
+
246
+ Returns:
247
+ tuple: (should_handle, action, role_name)
248
+ - should_handle: True if --role-config was specified and should be handled
249
+ - action: The action to perform (help, create, show, edit, list, remove)
250
+ - role_name: The name of the role (or None for actions like list and help)
251
+ """
252
+ if args.role_config is None:
253
+ return (False, None, None)
254
+
255
+ # Show help if no arguments or "help" argument
256
+ if len(args.role_config) == 0 or (len(args.role_config) > 0 and args.role_config[0].lower() == "help"):
257
+ return (True, "help", None)
258
+
259
+ action = args.role_config[0].lower()
260
+ role_name = args.role_config[1] if len(args.role_config) > 1 else None
261
+
262
+ # If action requires a role name but none is provided
263
+ if action in ("create", "show", "edit", "remove") and role_name is None:
264
+ raise ValueError(f"--role-config {action} requires a role name")
265
+
266
+ if action in ("help", "create", "show", "edit", "list", "remove"):
267
+ return (True, action, role_name)
268
+ else:
269
+ # Unknown action, show help
270
+ return (True, "help", None)
ngpt/cli/main.py CHANGED
@@ -25,7 +25,8 @@ from .modes.shell import shell_mode
25
25
  from .modes.text import text_mode
26
26
  from .modes.rewrite import rewrite_mode
27
27
  from .modes.gitcommsg import gitcommsg_mode
28
- from .args import parse_args, validate_args, handle_cli_config_args, setup_argument_parser, validate_markdown_renderer
28
+ from .args import parse_args, validate_args, handle_cli_config_args, setup_argument_parser, validate_markdown_renderer, handle_role_config_args
29
+ from .roles import handle_role_config, get_role_prompt
29
30
 
30
31
  def show_cli_config_help():
31
32
  """Display help information about CLI configuration."""
@@ -194,6 +195,12 @@ def main():
194
195
  handle_cli_config(action, option, value)
195
196
  return
196
197
 
198
+ # Handle role configuration command
199
+ should_handle_role_config, action, role_name = handle_role_config_args(args)
200
+ if should_handle_role_config:
201
+ handle_role_config(action, role_name)
202
+ return
203
+
197
204
  # Handle --renderers flag to show available markdown renderers
198
205
  if args.list_renderers:
199
206
  show_available_renderers()
@@ -484,6 +491,15 @@ def main():
484
491
  if not has_renderer:
485
492
  show_available_renderers()
486
493
 
494
+ # Get system prompt from role if specified
495
+ if args.role:
496
+ role_prompt = get_role_prompt(args.role)
497
+ if role_prompt:
498
+ args.preprompt = role_prompt
499
+ else:
500
+ # If role doesn't exist, exit
501
+ return
502
+
487
503
  # Initialize client using the potentially overridden active_config
488
504
  client = NGPTClient(
489
505
  api_key=active_config.get("api_key", args.api_key),
ngpt/cli/modes/chat.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ..formatters import COLORS
2
- from ..renderers import prettify_markdown, prettify_streaming_markdown
2
+ from ..renderers import prettify_markdown, prettify_streaming_markdown, TERMINAL_RENDER_LOCK
3
3
  from ..ui import spinner
4
4
  from ...utils import enhance_prompt_with_web_search, process_piped_input
5
5
  import sys
@@ -53,9 +53,10 @@ def chat_mode(client, args, logger=None):
53
53
  stop_spinner.set()
54
54
  spinner_thread.join()
55
55
  # Clear the spinner line completely
56
- sys.stdout.write("\r" + " " * 100 + "\r")
57
- sys.stdout.flush()
58
- print("Enhanced input with web search results.")
56
+ with TERMINAL_RENDER_LOCK:
57
+ sys.stdout.write("\r" + " " * 100 + "\r")
58
+ sys.stdout.flush()
59
+ print("Enhanced input with web search results.")
59
60
  except Exception as e:
60
61
  # Stop the spinner before re-raising
61
62
  stop_spinner.set()
@@ -129,11 +130,14 @@ def chat_mode(client, args, logger=None):
129
130
  # On first content, stop the spinner
130
131
  if not first_content_received and stop_spinner_func:
131
132
  first_content_received = True
132
- # Stop the spinner
133
- stop_spinner_func()
134
- # Ensure spinner message is cleared with an extra blank line
135
- sys.stdout.write("\r" + " " * 100 + "\r")
136
- sys.stdout.flush()
133
+
134
+ # Use lock to prevent terminal rendering conflicts
135
+ with TERMINAL_RENDER_LOCK:
136
+ # Stop the spinner
137
+ stop_spinner_func()
138
+ # Ensure spinner message is cleared with an extra blank line
139
+ sys.stdout.write("\r" + " " * 100 + "\r")
140
+ sys.stdout.flush()
137
141
 
138
142
  # Call the original callback to update the display
139
143
  if original_callback:
@@ -165,7 +169,8 @@ def chat_mode(client, args, logger=None):
165
169
 
166
170
  # Handle non-stream response or regular prettify
167
171
  if (args.no_stream or args.prettify) and response:
168
- if args.prettify:
169
- prettify_markdown(response, args.renderer)
170
- else:
171
- print(response)
172
+ with TERMINAL_RENDER_LOCK:
173
+ if args.prettify:
174
+ prettify_markdown(response, args.renderer)
175
+ else:
176
+ print(response)
ngpt/cli/modes/code.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ..formatters import COLORS
2
- from ..renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer, show_available_renderers
2
+ from ..renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer, show_available_renderers, TERMINAL_RENDER_LOCK
3
3
  from ..ui import spinner, copy_to_clipboard
4
4
  from ...utils import enhance_prompt_with_web_search, process_piped_input
5
5
  import sys
@@ -126,9 +126,10 @@ def code_mode(client, args, logger=None):
126
126
  stop_spinner.set()
127
127
  spinner_thread.join()
128
128
  # Clear the spinner line completely
129
- sys.stdout.write("\r" + " " * 100 + "\r")
130
- sys.stdout.flush()
131
- print("Enhanced input with web search results.")
129
+ with TERMINAL_RENDER_LOCK:
130
+ sys.stdout.write("\r" + " " * 100 + "\r")
131
+ sys.stdout.flush()
132
+ print("Enhanced input with web search results.")
132
133
  except Exception as e:
133
134
  # Stop the spinner before re-raising
134
135
  stop_spinner.set()
@@ -211,11 +212,14 @@ def code_mode(client, args, logger=None):
211
212
  # On first content, stop the spinner
212
213
  if not first_content_received and stop_spinner_func:
213
214
  first_content_received = True
214
- # Stop the spinner
215
- stop_spinner_func()
216
- # Ensure spinner message is cleared with an extra blank line
217
- sys.stdout.write("\r" + " " * 100 + "\r")
218
- sys.stdout.flush()
215
+
216
+ # Use lock to prevent terminal rendering conflicts
217
+ with TERMINAL_RENDER_LOCK:
218
+ # Stop the spinner
219
+ stop_spinner_func()
220
+ # Ensure spinner message is cleared with an extra blank line
221
+ sys.stdout.write("\r" + " " * 100 + "\r")
222
+ sys.stdout.flush()
219
223
 
220
224
  # Call the original callback to update the display
221
225
  if original_callback:
@@ -297,12 +301,13 @@ def code_mode(client, args, logger=None):
297
301
 
298
302
  # Print non-streamed output if needed
299
303
  if generated_code and not should_stream:
300
- if use_regular_prettify:
301
- print("\nGenerated code:")
302
- prettify_markdown(generated_code, args.renderer)
303
- else:
304
- # Should only happen if --no-stream was used without prettify
305
- print(f"\nGenerated code:\n{generated_code}")
304
+ with TERMINAL_RENDER_LOCK:
305
+ if use_regular_prettify:
306
+ print("\nGenerated code:")
307
+ prettify_markdown(generated_code, args.renderer)
308
+ else:
309
+ # Should only happen if --no-stream was used without prettify
310
+ print(f"\nGenerated code:\n{generated_code}")
306
311
 
307
312
  # Offer to copy to clipboard
308
313
  if generated_code and not args.no_stream:
@@ -5,7 +5,7 @@ import threading
5
5
  import sys
6
6
  import time
7
7
  from ..formatters import COLORS
8
- from ..renderers import prettify_markdown, prettify_streaming_markdown
8
+ from ..renderers import prettify_markdown, prettify_streaming_markdown, TERMINAL_RENDER_LOCK
9
9
  from ..ui import spinner
10
10
  from ...utils import enhance_prompt_with_web_search
11
11
 
@@ -78,8 +78,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
78
78
  def print_separator():
79
79
  # Make sure there's exactly one newline before and after
80
80
  # Use sys.stdout.write for direct control, avoiding any extra newlines
81
- sys.stdout.write(f"\n{separator}\n")
82
- sys.stdout.flush()
81
+ with TERMINAL_RENDER_LOCK:
82
+ sys.stdout.write(f"\n{separator}\n")
83
+ sys.stdout.flush()
83
84
 
84
85
  # Initialize conversation history
85
86
  system_prompt = preprompt if preprompt else "You are a helpful assistant."
@@ -111,35 +112,37 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
111
112
 
112
113
  # Function to display conversation history
113
114
  def display_history():
114
- if len(conversation) <= 1: # Only system message
115
- print(f"\n{COLORS['yellow']}No conversation history yet.{COLORS['reset']}")
116
- return
117
-
118
- print(f"\n{COLORS['cyan']}{COLORS['bold']}Conversation History:{COLORS['reset']}")
119
- print(separator)
120
-
121
- # Skip system message
122
- message_count = 0
123
- for i, msg in enumerate(conversation):
124
- if msg["role"] == "system":
125
- continue
115
+ with TERMINAL_RENDER_LOCK:
116
+ if len(conversation) <= 1: # Only system message
117
+ print(f"\n{COLORS['yellow']}No conversation history yet.{COLORS['reset']}")
118
+ return
126
119
 
127
- if msg["role"] == "user":
128
- message_count += 1
129
- print(f"\n{user_header()}")
130
- print(f"{COLORS['cyan']}│ [{message_count}] {COLORS['reset']}{msg['content']}")
131
- elif msg["role"] == "assistant":
132
- print(f"\n{ngpt_header()}")
133
- print(f"{COLORS['green']}│ {COLORS['reset']}{msg['content']}")
134
-
135
- print(f"\n{separator}") # Consistent separator at the end
120
+ print(f"\n{COLORS['cyan']}{COLORS['bold']}Conversation History:{COLORS['reset']}")
121
+ print(separator)
122
+
123
+ # Skip system message
124
+ message_count = 0
125
+ for i, msg in enumerate(conversation):
126
+ if msg["role"] == "system":
127
+ continue
128
+
129
+ if msg["role"] == "user":
130
+ message_count += 1
131
+ print(f"\n{user_header()}")
132
+ print(f"{COLORS['cyan']}│ [{message_count}] {COLORS['reset']}{msg['content']}")
133
+ elif msg["role"] == "assistant":
134
+ print(f"\n{ngpt_header()}")
135
+ print(f"{COLORS['green']}│ {COLORS['reset']}{msg['content']}")
136
+
137
+ print(f"\n{separator}") # Consistent separator at the end
136
138
 
137
139
  # Function to clear conversation history
138
140
  def clear_history():
139
141
  nonlocal conversation
140
142
  conversation = [{"role": "system", "content": system_prompt}]
141
- print(f"\n{COLORS['yellow']}Conversation history cleared.{COLORS['reset']}")
142
- print(separator) # Add separator for consistency
143
+ with TERMINAL_RENDER_LOCK:
144
+ print(f"\n{COLORS['yellow']}Conversation history cleared.{COLORS['reset']}")
145
+ print(separator) # Add separator for consistency
143
146
 
144
147
  try:
145
148
  while True:
@@ -249,10 +252,11 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
249
252
 
250
253
  # Print the header if needed
251
254
  if should_print_header:
252
- if not no_stream and not stream_prettify:
253
- print(f"\n{ngpt_header()}: {COLORS['reset']}", end="", flush=True)
254
- elif not stream_prettify:
255
- print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
255
+ with TERMINAL_RENDER_LOCK:
256
+ if not no_stream and not stream_prettify:
257
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", end="", flush=True)
258
+ elif not stream_prettify:
259
+ print(f"\n{ngpt_header()}: {COLORS['reset']}", flush=True)
256
260
 
257
261
  # Determine streaming behavior
258
262
  if prettify and not no_stream and not stream_prettify:
@@ -294,18 +298,20 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
294
298
  if not first_content_received:
295
299
  first_content_received = True
296
300
 
297
- # Stop the spinner if it's running
298
- if stop_spinner_func:
299
- stop_spinner_func()
301
+ # Use lock to prevent terminal rendering conflicts
302
+ with TERMINAL_RENDER_LOCK:
303
+ # Stop the spinner if it's running
304
+ if stop_spinner_func:
305
+ stop_spinner_func()
306
+
307
+ # Clear the spinner line completely without leaving extra newlines
308
+ # Use direct terminal control to ensure consistency
309
+ sys.stdout.write("\r" + " " * shutil.get_terminal_size().columns + "\r")
310
+ sys.stdout.flush()
300
311
 
301
- # Clear the spinner line completely without leaving extra newlines
302
- # Use direct terminal control to ensure consistency
303
- sys.stdout.write("\r" + " " * shutil.get_terminal_size().columns + "\r")
304
- sys.stdout.flush()
305
-
306
- # Now start the live display
307
- if live_display:
308
- live_display.start()
312
+ # Now start the live display
313
+ if live_display:
314
+ live_display.start()
309
315
 
310
316
  # Call the original callback to update content
311
317
  if original_callback:
@@ -351,11 +357,12 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
351
357
 
352
358
  # Print response if not streamed (either due to no_stream or prettify)
353
359
  if no_stream or prettify:
354
- if prettify:
355
- # For pretty formatting with rich, don't print any header text as the rich renderer already includes it
356
- prettify_markdown(response, renderer)
357
- else:
358
- print(response)
360
+ with TERMINAL_RENDER_LOCK:
361
+ if prettify:
362
+ # For pretty formatting with rich, don't print any header text as the rich renderer already includes it
363
+ prettify_markdown(response, renderer)
364
+ else:
365
+ print(response)
359
366
 
360
367
  # Log AI response if logging is enabled
361
368
  if logger:
ngpt/cli/modes/rewrite.py CHANGED
@@ -2,7 +2,7 @@ import sys
2
2
  import threading
3
3
  import time
4
4
  from ..formatters import COLORS
5
- from ..renderers import prettify_markdown, prettify_streaming_markdown
5
+ from ..renderers import prettify_markdown, prettify_streaming_markdown, TERMINAL_RENDER_LOCK
6
6
  from ..ui import get_multiline_input, spinner, copy_to_clipboard
7
7
  from ...utils import enhance_prompt_with_web_search, process_piped_input
8
8
 
@@ -127,9 +127,10 @@ def rewrite_mode(client, args, logger=None):
127
127
  stop_spinner.set()
128
128
  spinner_thread.join()
129
129
  # Clear the spinner line completely
130
- sys.stdout.write("\r" + " " * 100 + "\r")
131
- sys.stdout.flush()
132
- print("Enhanced input with web search results.")
130
+ with TERMINAL_RENDER_LOCK:
131
+ sys.stdout.write("\r" + " " * 100 + "\r")
132
+ sys.stdout.flush()
133
+ print("Enhanced input with web search results.")
133
134
  except Exception as e:
134
135
  # Stop the spinner before re-raising
135
136
  stop_spinner.set()
@@ -197,11 +198,14 @@ def rewrite_mode(client, args, logger=None):
197
198
  # On first content, stop the spinner
198
199
  if not first_content_received and stop_spinner_func:
199
200
  first_content_received = True
200
- # Stop the spinner
201
- stop_spinner_func()
202
- # Ensure spinner message is cleared with an extra blank line
203
- sys.stdout.write("\r" + " " * 100 + "\r\n")
204
- sys.stdout.flush()
201
+
202
+ # Use lock to prevent terminal rendering conflicts
203
+ with TERMINAL_RENDER_LOCK:
204
+ # Stop the spinner
205
+ stop_spinner_func()
206
+ # Ensure spinner message is cleared with an extra blank line
207
+ sys.stdout.write("\r" + " " * 100 + "\r\n")
208
+ sys.stdout.flush()
205
209
 
206
210
  # Call the original callback to update the display
207
211
  if original_callback:
@@ -241,10 +245,11 @@ def rewrite_mode(client, args, logger=None):
241
245
 
242
246
  # Handle non-stream response or regular prettify
243
247
  if (args.no_stream or args.prettify) and response:
244
- if args.prettify:
245
- prettify_markdown(response, args.renderer)
246
- else:
247
- print(response)
248
+ with TERMINAL_RENDER_LOCK:
249
+ if args.prettify:
250
+ prettify_markdown(response, args.renderer)
251
+ else:
252
+ print(response)
248
253
 
249
254
  # Offer to copy to clipboard if not in a redirected output
250
255
  if not args.no_stream and response:
ngpt/cli/modes/shell.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from ..formatters import COLORS
2
2
  from ..ui import spinner, copy_to_clipboard, get_terminal_input
3
- from ..renderers import prettify_markdown, has_markdown_renderer, prettify_streaming_markdown, show_available_renderers
3
+ from ..renderers import prettify_markdown, has_markdown_renderer, prettify_streaming_markdown, show_available_renderers, TERMINAL_RENDER_LOCK
4
4
  from ...utils import enhance_prompt_with_web_search, process_piped_input
5
5
  import subprocess
6
6
  import sys
@@ -796,19 +796,21 @@ def shell_mode(client, args, logger=None):
796
796
  ]
797
797
  prompt_text = f"\nWhat would you like to do? [{COLORS['cyan']}C{COLORS['reset']}/{COLORS['cyan']}E{COLORS['reset']}/{COLORS['cyan']}D{COLORS['reset']}/{COLORS['cyan']}A{COLORS['reset']}] "
798
798
 
799
- # Print options with proper flushing to ensure display
800
- print(options_text, flush=True)
801
- for option in options:
802
- print(option, flush=True)
803
-
804
- # Add a small delay to ensure terminal rendering is complete,
805
- # especially important for stream-prettify mode
806
- if use_stream_prettify:
807
- time.sleep(0.5)
799
+ # Make sure box rendering is complete before showing options
800
+ with TERMINAL_RENDER_LOCK:
801
+ # Add a small delay to ensure terminal rendering is complete,
802
+ # especially important for stream-prettify mode
803
+ if use_stream_prettify:
804
+ time.sleep(0.5)
805
+
806
+ # Print options with proper flushing to ensure display
807
+ print(options_text, flush=True)
808
+ for option in options:
809
+ print(option, flush=True)
808
810
 
809
- # Print prompt and flush to ensure it appears
810
- sys.stdout.write(prompt_text)
811
- sys.stdout.flush()
811
+ # Print prompt and flush to ensure it appears
812
+ sys.stdout.write(prompt_text)
813
+ sys.stdout.flush()
812
814
 
813
815
  try:
814
816
  # Use get_terminal_input which opens /dev/tty directly rather than using stdin
ngpt/cli/modes/text.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ..formatters import COLORS
2
- from ..renderers import prettify_markdown, prettify_streaming_markdown
2
+ from ..renderers import prettify_markdown, prettify_streaming_markdown, TERMINAL_RENDER_LOCK
3
3
  from ..ui import get_multiline_input, spinner
4
4
  from ...utils import enhance_prompt_with_web_search
5
5
  import threading
@@ -47,9 +47,10 @@ def text_mode(client, args, logger=None):
47
47
  stop_spinner.set()
48
48
  spinner_thread.join()
49
49
  # Clear the spinner line completely
50
- sys.stdout.write("\r" + " " * 100 + "\r")
51
- sys.stdout.flush()
52
- print("Enhanced input with web search results.")
50
+ with TERMINAL_RENDER_LOCK:
51
+ sys.stdout.write("\r" + " " * 100 + "\r")
52
+ sys.stdout.flush()
53
+ print("Enhanced input with web search results.")
53
54
  except Exception as e:
54
55
  # Stop the spinner before re-raising
55
56
  stop_spinner.set()
@@ -119,11 +120,14 @@ def text_mode(client, args, logger=None):
119
120
  # On first content, stop the spinner
120
121
  if not first_content_received and stop_spinner_func:
121
122
  first_content_received = True
122
- # Stop the spinner
123
- stop_spinner_func()
124
- # Ensure spinner message is cleared with an extra blank line
125
- sys.stdout.write("\r" + " " * 100 + "\r")
126
- sys.stdout.flush()
123
+
124
+ # Use lock to prevent terminal rendering conflicts
125
+ with TERMINAL_RENDER_LOCK:
126
+ # Stop the spinner
127
+ stop_spinner_func()
128
+ # Ensure spinner message is cleared with an extra blank line
129
+ sys.stdout.write("\r" + " " * 100 + "\r")
130
+ sys.stdout.flush()
127
131
 
128
132
  # Call the original callback to update the display
129
133
  if original_callback:
@@ -155,7 +159,8 @@ def text_mode(client, args, logger=None):
155
159
 
156
160
  # Handle non-stream response or regular prettify
157
161
  if (args.no_stream or args.prettify) and response:
158
- if args.prettify:
159
- prettify_markdown(response, args.renderer)
160
- else:
161
- print(response)
162
+ with TERMINAL_RENDER_LOCK:
163
+ if args.prettify:
164
+ prettify_markdown(response, args.renderer)
165
+ else:
166
+ print(response)
ngpt/cli/renderers.py CHANGED
@@ -3,8 +3,12 @@ import shutil
3
3
  import subprocess
4
4
  import tempfile
5
5
  import sys
6
+ import threading
6
7
  from .formatters import COLORS
7
8
 
9
+ # Global lock for terminal rendering to prevent race conditions
10
+ TERMINAL_RENDER_LOCK = threading.Lock()
11
+
8
12
  # Try to import markdown rendering libraries
9
13
  try:
10
14
  import rich
@@ -308,63 +312,65 @@ def prettify_streaming_markdown(renderer='rich', is_interactive=False, header_te
308
312
  # Check if this is the final update (complete flag)
309
313
  is_complete = kwargs.get('complete', False)
310
314
 
311
- # Start live display on first content
312
- if first_update:
313
- first_update = False
314
- # Let the spinner's clean_exit handle the cleanup
315
- # No additional cleanup needed here
316
- live.start()
317
-
318
- # Update content in live display
319
- if is_interactive and header_text:
320
- # Update the panel content - for streaming, only show the last portion that fits in display_height
321
- if not is_complete:
322
- # Calculate approximate lines needed (rough estimation)
323
- content_lines = content.count('\n') + 1
324
- available_height = display_height - 4 # Account for panel borders and padding
325
-
326
- if content_lines > available_height:
327
- # If content is too big, show only the last part that fits
328
- lines = content.split('\n')
329
- truncated_content = '\n'.join(lines[-available_height:])
330
- md_obj.renderable = Markdown(truncated_content)
315
+ # Use lock to prevent terminal rendering conflicts
316
+ with TERMINAL_RENDER_LOCK:
317
+ # Start live display on first content
318
+ if first_update:
319
+ first_update = False
320
+ # Let the spinner's clean_exit handle the cleanup
321
+ # No additional cleanup needed here
322
+ live.start()
323
+
324
+ # Update content in live display
325
+ if is_interactive and header_text:
326
+ # Update the panel content - for streaming, only show the last portion that fits in display_height
327
+ if not is_complete:
328
+ # Calculate approximate lines needed (rough estimation)
329
+ content_lines = content.count('\n') + 1
330
+ available_height = display_height - 4 # Account for panel borders and padding
331
+
332
+ if content_lines > available_height:
333
+ # If content is too big, show only the last part that fits
334
+ lines = content.split('\n')
335
+ truncated_content = '\n'.join(lines[-available_height:])
336
+ md_obj.renderable = Markdown(truncated_content)
337
+ else:
338
+ md_obj.renderable = Markdown(content)
331
339
  else:
332
340
  md_obj.renderable = Markdown(content)
333
- else:
334
- md_obj.renderable = Markdown(content)
335
-
336
- live.update(md_obj)
337
- else:
338
- # Same logic for non-interactive mode
339
- if not is_complete:
340
- # Calculate approximate lines needed
341
- content_lines = content.count('\n') + 1
342
- available_height = display_height - 4 # Account for panel borders and padding
343
341
 
344
- if content_lines > available_height:
345
- # If content is too big, show only the last part that fits
346
- lines = content.split('\n')
347
- truncated_content = '\n'.join(lines[-available_height:])
348
- md_obj.renderable = Markdown(truncated_content)
342
+ live.update(md_obj)
343
+ else:
344
+ # Same logic for non-interactive mode
345
+ if not is_complete:
346
+ # Calculate approximate lines needed
347
+ content_lines = content.count('\n') + 1
348
+ available_height = display_height - 4 # Account for panel borders and padding
349
+
350
+ if content_lines > available_height:
351
+ # If content is too big, show only the last part that fits
352
+ lines = content.split('\n')
353
+ truncated_content = '\n'.join(lines[-available_height:])
354
+ md_obj.renderable = Markdown(truncated_content)
355
+ else:
356
+ md_obj.renderable = Markdown(content)
349
357
  else:
350
358
  md_obj.renderable = Markdown(content)
351
- else:
352
- md_obj.renderable = Markdown(content)
359
+
360
+ live.update(md_obj)
353
361
 
354
- live.update(md_obj)
362
+ # Ensure the display refreshes with new content
363
+ live.refresh()
355
364
 
356
- # Ensure the display refreshes with new content
357
- live.refresh()
358
-
359
- # If streaming is complete, stop the live display
360
- if is_complete:
361
- try:
362
- # Just stop the live display when complete - no need to redisplay content
363
- live.stop()
364
- except Exception as e:
365
- # Fallback if something goes wrong
366
- sys.stderr.write(f"\nError stopping live display: {str(e)}\n")
367
- sys.stderr.flush()
365
+ # If streaming is complete, stop the live display
366
+ if is_complete:
367
+ try:
368
+ # Just stop the live display when complete - no need to redisplay content
369
+ live.stop()
370
+ except Exception as e:
371
+ # Fallback if something goes wrong
372
+ sys.stderr.write(f"\nError stopping live display: {str(e)}\n")
373
+ sys.stderr.flush()
368
374
 
369
375
  # Define a function to set up and start the spinner
370
376
  def setup_spinner(stop_event, message="Waiting for AI response...", color=COLORS['cyan']):
ngpt/cli/roles.py ADDED
@@ -0,0 +1,265 @@
1
+ import os
2
+ import json
3
+ import sys
4
+ from pathlib import Path
5
+ from .formatters import COLORS
6
+ from .ui import get_multiline_input
7
+
8
+ # Role directory within config
9
+ ROLE_DIR_NAME = "ngpt_roles"
10
+
11
+ def get_role_directory():
12
+ """Get the path to the role directory, creating it if it doesn't exist."""
13
+ # Use XDG Base Directory specification if possible
14
+ if os.environ.get("XDG_CONFIG_HOME"):
15
+ config_dir = Path(os.environ["XDG_CONFIG_HOME"]) / "ngpt"
16
+ else:
17
+ config_dir = Path.home() / ".config" / "ngpt"
18
+
19
+ # Create role directory if it doesn't exist
20
+ role_dir = config_dir / ROLE_DIR_NAME
21
+ role_dir.mkdir(parents=True, exist_ok=True)
22
+
23
+ return role_dir
24
+
25
+ def create_role(role_name):
26
+ """Create a new role with the given name.
27
+
28
+ Args:
29
+ role_name: The name of the role to create.
30
+
31
+ Returns:
32
+ bool: True if the role was created successfully, False otherwise.
33
+ """
34
+ role_dir = get_role_directory()
35
+ role_file = role_dir / f"{role_name}.json"
36
+
37
+ # Check if role already exists
38
+ if role_file.exists():
39
+ print(f"{COLORS['yellow']}Role '{role_name}' already exists. Use --role-config edit {role_name} to modify it.{COLORS['reset']}")
40
+ return False
41
+
42
+ print(f"Creating new role '{role_name}'. Enter system prompt below (Ctrl+D to finish):")
43
+
44
+ # Get multiline input for the system prompt
45
+ system_prompt = get_multiline_input()
46
+ if not system_prompt:
47
+ print(f"{COLORS['yellow']}Role creation cancelled.{COLORS['reset']}")
48
+ return False
49
+
50
+ # Create role data
51
+ role_data = {
52
+ "name": role_name,
53
+ "system_prompt": system_prompt
54
+ }
55
+
56
+ # Save role to file
57
+ try:
58
+ with open(role_file, 'w') as f:
59
+ json.dump(role_data, f, indent=2)
60
+ print(f"{COLORS['green']}Role '{role_name}' created successfully.{COLORS['reset']}")
61
+ return True
62
+ except Exception as e:
63
+ print(f"{COLORS['red']}Error creating role: {str(e)}{COLORS['reset']}")
64
+ return False
65
+
66
+ def edit_role(role_name):
67
+ """Edit an existing role with the given name.
68
+
69
+ Args:
70
+ role_name: The name of the role to edit.
71
+
72
+ Returns:
73
+ bool: True if the role was edited successfully, False otherwise.
74
+ """
75
+ role_dir = get_role_directory()
76
+ role_file = role_dir / f"{role_name}.json"
77
+
78
+ # Check if role exists
79
+ if not role_file.exists():
80
+ print(f"{COLORS['yellow']}Role '{role_name}' does not exist.{COLORS['reset']}")
81
+ return False
82
+
83
+ # Load existing role data
84
+ try:
85
+ with open(role_file, 'r') as f:
86
+ role_data = json.load(f)
87
+
88
+ print(f"Editing role '{role_name}'. Current system prompt will be loaded in the editor.")
89
+
90
+ # Get multiline input for the new system prompt with the current one pre-loaded
91
+ system_prompt = get_multiline_input(initial_text=role_data['system_prompt'])
92
+ if not system_prompt:
93
+ print(f"{COLORS['yellow']}Role edit cancelled.{COLORS['reset']}")
94
+ return False
95
+
96
+ # Update role data
97
+ role_data['system_prompt'] = system_prompt
98
+
99
+ # Save updated role to file
100
+ with open(role_file, 'w') as f:
101
+ json.dump(role_data, f, indent=2)
102
+
103
+ print(f"{COLORS['green']}Role '{role_name}' updated successfully.{COLORS['reset']}")
104
+ return True
105
+ except Exception as e:
106
+ print(f"{COLORS['red']}Error editing role: {str(e)}{COLORS['reset']}")
107
+ return False
108
+
109
+ def show_role(role_name):
110
+ """Show details of a role with the given name.
111
+
112
+ Args:
113
+ role_name: The name of the role to show.
114
+
115
+ Returns:
116
+ bool: True if the role was found and displayed, False otherwise.
117
+ """
118
+ role_dir = get_role_directory()
119
+ role_file = role_dir / f"{role_name}.json"
120
+
121
+ # Check if role exists
122
+ if not role_file.exists():
123
+ print(f"{COLORS['yellow']}Role '{role_name}' does not exist.{COLORS['reset']}")
124
+ return False
125
+
126
+ # Load role data
127
+ try:
128
+ with open(role_file, 'r') as f:
129
+ role_data = json.load(f)
130
+
131
+ print(f"\n{COLORS['bold']}Role: {COLORS['cyan']}{role_name}{COLORS['reset']}")
132
+ print(f"\n{COLORS['bold']}System Prompt:{COLORS['reset']}")
133
+ print(f"{COLORS['cyan']}{role_data['system_prompt']}{COLORS['reset']}")
134
+
135
+ return True
136
+ except Exception as e:
137
+ print(f"{COLORS['red']}Error showing role: {str(e)}{COLORS['reset']}")
138
+ return False
139
+
140
+ def list_roles():
141
+ """List all available roles.
142
+
143
+ Returns:
144
+ bool: True if roles were listed successfully, False otherwise.
145
+ """
146
+ role_dir = get_role_directory()
147
+
148
+ # Get all JSON files in the role directory
149
+ try:
150
+ role_files = list(role_dir.glob("*.json"))
151
+
152
+ if not role_files:
153
+ print(f"{COLORS['yellow']}No roles found. Use --role-config create <role_name> to create a new role.{COLORS['reset']}")
154
+ return True
155
+
156
+ print(f"\n{COLORS['bold']}Available Roles:{COLORS['reset']}")
157
+ for role_file in sorted(role_files):
158
+ role_name = role_file.stem
159
+ print(f" • {COLORS['cyan']}{role_name}{COLORS['reset']}")
160
+
161
+ return True
162
+ except Exception as e:
163
+ print(f"{COLORS['red']}Error listing roles: {str(e)}{COLORS['reset']}")
164
+ return False
165
+
166
+ def remove_role(role_name):
167
+ """Remove a role with the given name.
168
+
169
+ Args:
170
+ role_name: The name of the role to remove.
171
+
172
+ Returns:
173
+ bool: True if the role was removed successfully, False otherwise.
174
+ """
175
+ role_dir = get_role_directory()
176
+ role_file = role_dir / f"{role_name}.json"
177
+
178
+ # Check if role exists
179
+ if not role_file.exists():
180
+ print(f"{COLORS['yellow']}Role '{role_name}' does not exist.{COLORS['reset']}")
181
+ return False
182
+
183
+ # Confirm deletion
184
+ confirm = input(f"Are you sure you want to remove the role '{role_name}'? (y/N): ")
185
+ if confirm.lower() not in ["y", "yes"]:
186
+ print(f"{COLORS['yellow']}Role removal cancelled.{COLORS['reset']}")
187
+ return False
188
+
189
+ # Remove role file
190
+ try:
191
+ os.remove(role_file)
192
+ print(f"{COLORS['green']}Role '{role_name}' removed successfully.{COLORS['reset']}")
193
+ return True
194
+ except Exception as e:
195
+ print(f"{COLORS['red']}Error removing role: {str(e)}{COLORS['reset']}")
196
+ return False
197
+
198
+ def get_role_prompt(role_name):
199
+ """Get the system prompt for a role with the given name.
200
+
201
+ Args:
202
+ role_name: The name of the role.
203
+
204
+ Returns:
205
+ str or None: The system prompt for the role, or None if the role does not exist.
206
+ """
207
+ role_dir = get_role_directory()
208
+ role_file = role_dir / f"{role_name}.json"
209
+
210
+ # Check if role exists
211
+ if not role_file.exists():
212
+ print(f"{COLORS['yellow']}Role '{role_name}' does not exist.{COLORS['reset']}")
213
+ return None
214
+
215
+ # Load role data
216
+ try:
217
+ with open(role_file, 'r') as f:
218
+ role_data = json.load(f)
219
+
220
+ return role_data.get('system_prompt')
221
+ except Exception as e:
222
+ print(f"{COLORS['red']}Error loading role: {str(e)}{COLORS['reset']}")
223
+ return None
224
+
225
+ def show_help():
226
+ """Show help information for role configuration."""
227
+ print(f"\n{COLORS['bold']}Role Configuration Help:{COLORS['reset']}")
228
+ print(f" {COLORS['cyan']}--role-config help{COLORS['reset']} - Show this help information")
229
+ print(f" {COLORS['cyan']}--role-config create <role_name>{COLORS['reset']} - Create a new role")
230
+ print(f" {COLORS['cyan']}--role-config show <role_name>{COLORS['reset']} - Show details of a role")
231
+ print(f" {COLORS['cyan']}--role-config edit <role_name>{COLORS['reset']} - Edit an existing role")
232
+ print(f" {COLORS['cyan']}--role-config list{COLORS['reset']} - List all available roles")
233
+ print(f" {COLORS['cyan']}--role-config remove <role_name>{COLORS['reset']} - Remove a role")
234
+ print(f"\n{COLORS['bold']}Usage Examples:{COLORS['reset']}")
235
+ print(f" {COLORS['cyan']}ngpt --role-config create json_generator{COLORS['reset']} - Create a new role for generating JSON")
236
+ print(f" {COLORS['cyan']}ngpt --role json_generator \"generate random user data\"{COLORS['reset']} - Use the json_generator role")
237
+
238
+ def handle_role_config(action, role_name):
239
+ """Handle role configuration based on the action and role name.
240
+
241
+ Args:
242
+ action: The action to perform (help, create, show, edit, list, remove).
243
+ role_name: The name of the role (or None for actions like list and help).
244
+
245
+ Returns:
246
+ bool: True if the action was handled successfully, False otherwise.
247
+ """
248
+ if action == "help":
249
+ show_help()
250
+ return True
251
+ elif action == "create":
252
+ return create_role(role_name)
253
+ elif action == "show":
254
+ return show_role(role_name)
255
+ elif action == "edit":
256
+ return edit_role(role_name)
257
+ elif action == "list":
258
+ return list_roles()
259
+ elif action == "remove":
260
+ return remove_role(role_name)
261
+ else:
262
+ # This shouldn't happen due to prior validation
263
+ print(f"{COLORS['yellow']}Unknown action: {action}{COLORS['reset']}")
264
+ show_help()
265
+ return False
ngpt/cli/ui.py CHANGED
@@ -22,9 +22,12 @@ try:
22
22
  except ImportError:
23
23
  HAS_PROMPT_TOOLKIT = False
24
24
 
25
- def create_multiline_editor():
25
+ def create_multiline_editor(initial_text=None):
26
26
  """Create a multi-line editor with prompt_toolkit.
27
27
 
28
+ Args:
29
+ initial_text: Optional initial text to prepopulate the editor with
30
+
28
31
  Returns:
29
32
  tuple: (app, has_prompt_toolkit) - the editor application and a boolean
30
33
  indicating if prompt_toolkit is available
@@ -62,6 +65,7 @@ def create_multiline_editor():
62
65
  scrollbar=True,
63
66
  focus_on_click=True,
64
67
  lexer=None,
68
+ text=initial_text or "", # Set initial text if provided
65
69
  )
66
70
  text_area.window.right_margins = [ScrollbarMargin(display_arrows=True)]
67
71
 
@@ -108,13 +112,16 @@ def create_multiline_editor():
108
112
  print(f"Error creating editor: {e}")
109
113
  return None, False
110
114
 
111
- def get_multiline_input():
115
+ def get_multiline_input(initial_text=None):
112
116
  """Get multi-line input from the user using either prompt_toolkit or standard input.
113
117
 
118
+ Args:
119
+ initial_text: Optional initial text to prepopulate the editor with
120
+
114
121
  Returns:
115
122
  str: The user's input text, or None if cancelled
116
123
  """
117
- editor_app, has_editor = create_multiline_editor()
124
+ editor_app, has_editor = create_multiline_editor(initial_text)
118
125
 
119
126
  if has_editor and editor_app:
120
127
  try:
@@ -132,8 +139,14 @@ def get_multiline_input():
132
139
  if not HAS_PROMPT_TOOLKIT:
133
140
  print("Note: Install 'prompt_toolkit' package for an enhanced input experience")
134
141
 
142
+ # Show initial text if provided
143
+ if initial_text:
144
+ print(initial_text)
145
+ lines = initial_text.splitlines()
146
+ else:
147
+ lines = []
148
+
135
149
  # Use a more robust approach for multiline input without prompt_toolkit
136
- lines = []
137
150
  try:
138
151
  while True:
139
152
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 3.8.2
3
+ Version: 3.9.0
4
4
  Summary: Swiss army knife for LLMs: powerful CLI and interactive chatbot in one package. Seamlessly work with OpenAI, Ollama, Groq, Claude, Gemini, or any OpenAI-compatible API to generate code, craft git commits, rewrite text, and execute shell commands.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -75,6 +75,7 @@ Description-Content-Type: text/markdown
75
75
  - 📝 **Rich Multiline Editor**: Interactive multiline text input with syntax highlighting and intuitive controls
76
76
  - 📑 **Git Commit Messages**: AI-powered generation of conventional, detailed commit messages from git diffs
77
77
  - 🎭 **System Prompts**: Customize model behavior with custom system prompts
78
+ - 🤖 **Custom Roles**: Create and use reusable AI roles for specialized tasks
78
79
  - 📃 **Conversation Logging**: Save your conversations to text files for later reference
79
80
  - 🔌 **Modular Architecture**: Well-structured codebase with clean separation of concerns
80
81
  - 🔄 **Provider Switching**: Easily switch between different LLM providers with a single parameter
@@ -164,6 +165,12 @@ What is the best way to learn Golang?
164
165
  Provide simple hello world example.
165
166
  EOF
166
167
 
168
+ # Create a custom role for specialized tasks
169
+ ngpt --role-config create json_generator
170
+
171
+ # Use a custom role for specific tasks
172
+ ngpt --role json_generator "Generate user data with name, email, and address"
173
+
167
174
  # Rewrite text to improve quality while preserving tone and meaning
168
175
  echo "your text" | ngpt -r
169
176
 
@@ -267,10 +274,11 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
267
274
 
268
275
  usage: ngpt [-h] [-v] [--language LANGUAGE] [--config [CONFIG]] [--config-index CONFIG_INDEX] [--provider PROVIDER]
269
276
  [--remove] [--show-config] [--all] [--list-models] [--list-renderers] [--cli-config [COMMAND ...]]
270
- [--api-key API_KEY] [--base-url BASE_URL] [--model MODEL] [--web-search] [--pipe]
271
- [--temperature TEMPERATURE] [--top_p TOP_P] [--max_tokens MAX_TOKENS] [--log [FILE]] [--preprompt PREPROMPT]
272
- [--no-stream | --prettify | --stream-prettify] [--renderer {auto,rich,glow}] [--rec-chunk] [--diff [FILE]]
273
- [--chunk-size CHUNK_SIZE] [--analyses-chunk-size ANALYSES_CHUNK_SIZE] [--max-msg-lines MAX_MSG_LINES]
277
+ [--role-config [ACTION ...]] [--api-key API_KEY] [--base-url BASE_URL] [--model MODEL] [--web-search]
278
+ [--pipe] [--temperature TEMPERATURE] [--top_p TOP_P] [--max_tokens MAX_TOKENS] [--log [FILE]]
279
+ [--preprompt PREPROMPT | --role ROLE] [--no-stream | --prettify | --stream-prettify]
280
+ [--renderer {auto,rich,glow}] [--rec-chunk] [--diff [FILE]] [--chunk-size CHUNK_SIZE]
281
+ [--analyses-chunk-size ANALYSES_CHUNK_SIZE] [--max-msg-lines MAX_MSG_LINES]
274
282
  [--max-recursion-depth MAX_RECURSION_DEPTH] [-i | -s | -c | -t | -r | -g]
275
283
  [prompt]
276
284
 
@@ -297,6 +305,7 @@ Configuration Options::
297
305
  --list-models List all available models for the current configuration and exit
298
306
  --list-renderers Show available markdown renderers for use with --prettify
299
307
  --cli-config [COMMAND ...] Manage CLI configuration (set, get, unset, list, help)
308
+ --role-config [ACTION ...] Manage custom roles (help, create, show, edit, list, remove) [role_name]
300
309
 
301
310
  Global Options::
302
311
 
@@ -310,6 +319,7 @@ Global Options::
310
319
  --max_tokens MAX_TOKENS Set max response length in tokens
311
320
  --log [FILE] Set filepath to log conversation to, or create a temporary log file if no path provided
312
321
  --preprompt PREPROMPT Set custom system prompt to control AI behavior
322
+ --role ROLE Use a predefined role to set system prompt (mutually exclusive with --preprompt)
313
323
  --renderer {auto,rich,glow} Select which markdown renderer to use with --prettify or --stream-prettify (auto, rich, or glow)
314
324
 
315
325
  Output Display Options (mutually exclusive)::
@@ -325,7 +335,7 @@ Git Commit Message Options::
325
335
  --chunk-size CHUNK_SIZE Number of lines per chunk when chunking is enabled (default: 200)
326
336
  --analyses-chunk-size ANALYSES_CHUNK_SIZE Number of lines per chunk when recursively chunking analyses (default: 200)
327
337
  --max-msg-lines MAX_MSG_LINES Maximum number of lines in commit message before condensing (default: 20)
328
- --max-recursion-depth MAX_RECURSION_DEPTH Maximum recursion depth for commit message condensing (default: 3)
338
+ --max-recursion-depth MAX_RECURSION_DEPTH Maximum recursion depth for commit message condensing (default: 3)
329
339
 
330
340
  Modes (mutually exclusive)::
331
341
 
@@ -601,6 +611,40 @@ This is a huge time-saver. nGPT analyzes your git diff and generates a properly
601
611
 
602
612
  ![ngpt-g](https://raw.githubusercontent.com/nazdridoy/ngpt/main/previews/ngpt-g.png)
603
613
 
614
+ #### Custom AI Roles
615
+
616
+ ```bash
617
+ # Create a specialized role for JSON generation
618
+ ngpt --role-config create json_generator
619
+
620
+ # Use the custom role to generate structured data
621
+ ngpt --role json_generator "Generate random user profile data"
622
+ ```
623
+ ```json
624
+ {
625
+ "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
626
+ "firstName": "Aurora",
627
+ "lastName": "Reynolds",
628
+ "email": "aurora.reynolds@example.com",
629
+ "phone": "+1-555-0101",
630
+ "address": {
631
+ "street": "123 Main St",
632
+ "city": "Anytown",
633
+ "state": "CA",
634
+ "zipCode": "90210"
635
+ },
636
+ "birthDate": "1990-07-15",
637
+ "registrationDate": "2022-01-20",
638
+ "isActive": true,
639
+ "roles": [
640
+ "user",
641
+ "premium"
642
+ ]
643
+ }
644
+ ```
645
+
646
+ Custom roles let you define specialized AI personas that you can reuse across different prompts, making it easy to get consistent responses for specific tasks.
647
+
604
648
  #### Web Search Integration
605
649
 
606
650
  ```bash
@@ -0,0 +1,30 @@
1
+ ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
2
+ ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
3
+ ngpt/client.py,sha256=XjpA2UnvrRvzk6_DzVEddUTzoPlF8koQ-cZURpHoT7c,9041
4
+ ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
5
+ ngpt/cli/args.py,sha256=TbaID-NgRTCa1NQAct74XqWTADvSMbf-sSXKNI1NkXU,14537
6
+ ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
7
+ ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
8
+ ngpt/cli/main.py,sha256=36mi8uYDcl56IhTkt-TJTRRhwHeF157xMAYgufLRAMo,29256
9
+ ngpt/cli/renderers.py,sha256=vAoDkpvgG2Fl81zkJDk_-zM1Fsw8E4Uv6m1AI81Fawo,17049
10
+ ngpt/cli/roles.py,sha256=YDBlmMtgw1gooeHVBiEUHwKn1V3E9dJKO8cpKoXu8uo,9359
11
+ ngpt/cli/ui.py,sha256=8-WyPMwgQiqLXWO0mGfBhKTRnIDDtPUtm_XCvOnqBJA,11334
12
+ ngpt/cli/modes/__init__.py,sha256=KP7VR6Xw9k1p5Jcu0F38RDxSFvFIzH3j1ThDLNwznUI,363
13
+ ngpt/cli/modes/chat.py,sha256=x1leClKq7UupA_CdW4tym0AivY2o_II123-I5IcAkxQ,7091
14
+ ngpt/cli/modes/code.py,sha256=Qj59xq6fZqgUDw7SbvmPKX_gdpc7DHJhNkn1sB5qgUU,12932
15
+ ngpt/cli/modes/gitcommsg.py,sha256=iTg3KlZwI0lGMcmUa62b0ashwLcxegdEEvT29PPtpBc,49595
16
+ ngpt/cli/modes/interactive.py,sha256=E0c38NA8xnuRKAce40F35uFYcohFDvaqSB8nf1ywS-4,17958
17
+ ngpt/cli/modes/rewrite.py,sha256=QQm453X9aoUQP9CAtmeghlMytMJPlsDZPKef9tGfj6g,11181
18
+ ngpt/cli/modes/shell.py,sha256=it1Brq1-LGeNfPKYBeVAwF-a78g9UP-KscofBZQkbr4,41589
19
+ ngpt/cli/modes/text.py,sha256=rUr7Byds7zkO9ZbMSZWOO_3fWfpeM40Tq1XJAgdapcs,6679
20
+ ngpt/utils/__init__.py,sha256=_92f8eGMMOtQQA3uwgSRVwUEl1EIRFjWPUjcfGgI-eI,1244
21
+ ngpt/utils/cli_config.py,sha256=Ug8cECBTIuzOwkBWidLTfs-OAdOsCMJ2bNa70pOADfw,11195
22
+ ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
23
+ ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
24
+ ngpt/utils/pipe.py,sha256=qRHF-Ma7bbU0cOcb1Yhe4S-kBavivtnnvLA3EYS4FY4,2162
25
+ ngpt/utils/web_search.py,sha256=w5ke4KJMRxq7r5jtbUXvspja6XhjoPZloVkZ0IvBXIE,30731
26
+ ngpt-3.9.0.dist-info/METADATA,sha256=wf1tVRaKH7xODB9yrI1eO4STYMskdwq-VhYl0edL5N0,30309
27
+ ngpt-3.9.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
28
+ ngpt-3.9.0.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
29
+ ngpt-3.9.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
30
+ ngpt-3.9.0.dist-info/RECORD,,
@@ -1,29 +0,0 @@
1
- ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
2
- ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
3
- ngpt/client.py,sha256=XjpA2UnvrRvzk6_DzVEddUTzoPlF8koQ-cZURpHoT7c,9041
4
- ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
5
- ngpt/cli/args.py,sha256=HYCDHhqP-BI_tibL1qGQ9we4483h_kCa2ksh-QxOeiU,12694
6
- ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
7
- ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
8
- ngpt/cli/main.py,sha256=PVulo8Pm53-oQ2Pgc4G90YhwyPImt8j7HKmY38SJ7CM,28696
9
- ngpt/cli/renderers.py,sha256=m71BeUXKynpKKGXFzwRSW1XngvyKiZ_xEsdujUbU0MA,16597
10
- ngpt/cli/ui.py,sha256=4RFxIf51di5EsytVr7OoyCWF_d40KJ0Mbom0VWgPlCc,10870
11
- ngpt/cli/modes/__init__.py,sha256=KP7VR6Xw9k1p5Jcu0F38RDxSFvFIzH3j1ThDLNwznUI,363
12
- ngpt/cli/modes/chat.py,sha256=1mH3nTDwU52qQJRliNUAFSqJWyyiJ6j0T5uVso-VnxE,6828
13
- ngpt/cli/modes/code.py,sha256=QBFgMRPKJhxkYCmumoSkNSF15XNRGUDum5yuK8aBnyM,12662
14
- ngpt/cli/modes/gitcommsg.py,sha256=iTg3KlZwI0lGMcmUa62b0ashwLcxegdEEvT29PPtpBc,49595
15
- ngpt/cli/modes/interactive.py,sha256=TtBrZUX45CVfKOPvkb1ya7dIQhXLILtn7ajmfM9ohso,17419
16
- ngpt/cli/modes/rewrite.py,sha256=yPmJPPkMHNxrnV-eoM0j6lMNRhdSAMXmcw2s9xG6TIo,10918
17
- ngpt/cli/modes/shell.py,sha256=nXkUbLLNdXQrdsOqGXMwdfnr2y7MtqbmBkYlwZbI5JA,41415
18
- ngpt/cli/modes/text.py,sha256=7t5WWXMFxGkBM5HMP4irbN9aQwxE2YgywjiVPep710k,6417
19
- ngpt/utils/__init__.py,sha256=_92f8eGMMOtQQA3uwgSRVwUEl1EIRFjWPUjcfGgI-eI,1244
20
- ngpt/utils/cli_config.py,sha256=Ug8cECBTIuzOwkBWidLTfs-OAdOsCMJ2bNa70pOADfw,11195
21
- ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
22
- ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
23
- ngpt/utils/pipe.py,sha256=qRHF-Ma7bbU0cOcb1Yhe4S-kBavivtnnvLA3EYS4FY4,2162
24
- ngpt/utils/web_search.py,sha256=w5ke4KJMRxq7r5jtbUXvspja6XhjoPZloVkZ0IvBXIE,30731
25
- ngpt-3.8.2.dist-info/METADATA,sha256=2LazTO2smhA2gBILZQ9vA0Xqqn2RByEIfclp999Y6KA,28919
26
- ngpt-3.8.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- ngpt-3.8.2.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
28
- ngpt-3.8.2.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
29
- ngpt-3.8.2.dist-info/RECORD,,
File without changes