ngpt 3.8.1__py3-none-any.whl → 3.8.3__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/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
@@ -14,6 +14,9 @@ import time
14
14
  # System prompt for shell command generation
15
15
  SHELL_SYSTEM_PROMPT = """Your role: Provide only plain text without Markdown formatting. Do not show any warnings or information regarding your capabilities. Do not provide any description. If you need to store any data, assume it will be stored in the chat. Provide only {shell_name} command for {operating_system} without any description. If there is a lack of details, provide most logical solution. Ensure the output is a valid shell command. If multiple steps required try to combine them together.
16
16
 
17
+ *** SHELL TYPE: {shell_name} ***
18
+ *** OS: {operating_system} ***
19
+
17
20
  Command:"""
18
21
 
19
22
  # System prompt to use when preprompt is provided
@@ -34,16 +37,18 @@ If the preprompt contradicts ANY OTHER instruction in this prompt,
34
37
  including the {operating_system}/{shell_name} specification below,
35
38
  YOU MUST FOLLOW THE PREPROMPT INSTRUCTION INSTEAD. NO EXCEPTIONS.
36
39
 
40
+ *** SHELL TYPE: {shell_name} ***
41
+ *** OS: {operating_system} ***
42
+
37
43
  Your role: Provide only plain text without Markdown formatting. Do not show any warnings or information regarding your capabilities. Do not provide any description. If you need to store any data, assume it will be stored in the chat. Provide only {shell_name} command for {operating_system} without any description. If there is a lack of details, provide most logical solution. Ensure the output is a valid shell command. If multiple steps required try to combine them together.
38
44
 
39
45
  Command:"""
40
46
 
41
- def detect_shell():
42
- """Detect the current shell type and OS more accurately.
47
+ def detect_os():
48
+ """Detect the current operating system with detailed information.
43
49
 
44
50
  Returns:
45
- tuple: (shell_name, highlight_language, operating_system) - the detected shell name,
46
- appropriate syntax highlighting language, and operating system
51
+ tuple: (os_type, operating_system) - the basic OS type and detailed OS description
47
52
  """
48
53
  os_type = platform.system()
49
54
 
@@ -73,60 +78,322 @@ def detect_shell():
73
78
  except:
74
79
  pass
75
80
 
76
- # Try to detect the shell by examining environment variables
81
+ return os_type, operating_system, is_wsl
82
+
83
+
84
+ def detect_gitbash_shell(operating_system):
85
+ """Detect if we're running in a Git Bash / MINGW environment.
86
+
87
+ Args:
88
+ operating_system: The detected operating system string
89
+
90
+ Returns:
91
+ tuple or None: (shell_name, highlight_language, operating_system) if Git Bash detected,
92
+ None otherwise
93
+ """
94
+ # Check for Git Bash / MINGW environments
95
+ if any(env_var in os.environ for env_var in ["MSYSTEM", "MINGW_PREFIX"]):
96
+ # We're definitely in a MINGW environment (Git Bash)
97
+ return "bash", "bash", operating_system
98
+
99
+ if "MSYSTEM" in os.environ and any(msys_type in os.environ.get("MSYSTEM", "")
100
+ for msys_type in ["MINGW", "MSYS"]):
101
+ return "bash", "bash", operating_system
102
+
103
+ # Check command PATH for mingw
104
+ if os.environ.get("PATH") and any(
105
+ mingw_pattern in path.lower()
106
+ for mingw_pattern in ["/mingw/", "\\mingw\\", "/usr/bin", "\\usr\\bin"]
107
+ for path in os.environ.get("PATH", "").split(os.pathsep)
108
+ ):
109
+ return "bash", "bash", operating_system
110
+
111
+ return None
112
+
113
+
114
+ def detect_unix_shell(operating_system):
115
+ """Detect shell type on Unix-like systems (Linux, macOS, BSD).
116
+
117
+ Args:
118
+ operating_system: The detected operating system string
119
+
120
+ Returns:
121
+ tuple: (shell_name, highlight_language, operating_system) - the detected shell information
122
+ """
123
+ # Try multiple methods to detect the shell
124
+
125
+ # Method 1: Check shell-specific environment variables
126
+ # These are very reliable indicators of the actual shell
127
+ if "BASH_VERSION" in os.environ:
128
+ return "bash", "bash", operating_system
129
+
130
+ if "ZSH_VERSION" in os.environ:
131
+ return "zsh", "zsh", operating_system
132
+
133
+ if "FISH_VERSION" in os.environ:
134
+ return "fish", "fish", operating_system
135
+
136
+ # Method 2: Try to get shell from process information
77
137
  try:
78
- # Check for specific shell by examining SHELL_NAME or equivalent
79
- if os_type == "Windows" and not is_wsl:
80
- # Check for Git Bash or MSYS2/Cygwin
81
- if "MINGW" in os.environ.get("MSYSTEM", "") or "MSYS" in os.environ.get("MSYSTEM", ""):
82
- return "bash", "bash", operating_system
138
+ # Try to get parent process name using ps command
139
+ current_pid = os.getpid()
140
+ parent_pid = os.getppid()
141
+
142
+ # Method 2a: Try to get the parent process command line from /proc
143
+ try:
144
+ with open(f'/proc/{parent_pid}/cmdline', 'r') as f:
145
+ cmdline = f.read().split('\0')
146
+ if cmdline and cmdline[0]:
147
+ cmd = os.path.basename(cmdline[0])
148
+ if "zsh" in cmd:
149
+ return "zsh", "zsh", operating_system
150
+ elif "bash" in cmd:
151
+ return "bash", "bash", operating_system
152
+ elif "fish" in cmd:
153
+ return "fish", "fish", operating_system
154
+ except:
155
+ pass
156
+
157
+ # Method 2b: Try using ps command with different formats
158
+ for fmt in ["comm=", "command=", "args="]:
159
+ try:
160
+ ps_cmd = ["ps", "-p", str(parent_pid), "-o", fmt]
161
+ result = subprocess.run(ps_cmd, capture_output=True, text=True)
162
+ process_info = result.stdout.strip()
83
163
 
84
- # Check if we're in Git Bash by examining PATH for /mingw/
85
- if any("/mingw/" in path.lower() for path in os.environ.get("PATH", "").split(os.pathsep)):
86
- return "bash", "bash", operating_system
164
+ if process_info:
165
+ if "zsh" in process_info.lower():
166
+ return "zsh", "zsh", operating_system
167
+ elif "bash" in process_info.lower():
168
+ return "bash", "bash", operating_system
169
+ elif "fish" in process_info.lower():
170
+ return "fish", "fish", operating_system
171
+ elif any(sh in process_info.lower() for sh in ["csh", "tcsh"]):
172
+ shell_name = "csh" if "csh" in process_info.lower() else "tcsh"
173
+ return shell_name, "csh", operating_system
174
+ elif "ksh" in process_info.lower():
175
+ return "ksh", "ksh", operating_system
176
+ except:
177
+ continue
178
+
179
+ # Method 2c: Try to find parent shell by traversing process hierarchy
180
+ # This handles Python wrappers, uv run, etc.
181
+ for _ in range(5): # Check up to 5 levels up the process tree
182
+ try:
183
+ # Try to get process command
184
+ ps_cmd = ["ps", "-p", str(parent_pid), "-o", "comm="]
185
+ result = subprocess.run(ps_cmd, capture_output=True, text=True)
186
+ process_name = result.stdout.strip()
187
+
188
+ # If it's a known shell, return it
189
+ if process_name:
190
+ process_basename = os.path.basename(process_name)
191
+ if "bash" in process_basename:
192
+ return "bash", "bash", operating_system
193
+ elif "zsh" in process_basename:
194
+ return "zsh", "zsh", operating_system
195
+ elif "fish" in process_basename:
196
+ return "fish", "fish", operating_system
197
+ elif any(sh in process_basename for sh in ["csh", "tcsh"]):
198
+ return process_basename, "csh", operating_system
199
+ elif "ksh" in process_basename:
200
+ return process_basename, "ksh", operating_system
87
201
 
88
- # Check for WSL within Windows
89
- if "WSL" in os.environ.get("PATH", "") or "Microsoft" in os.environ.get("PATH", ""):
202
+ # Check if we've reached init/systemd (PID 1)
203
+ if parent_pid <= 1:
204
+ break
205
+
206
+ # Move up to next parent
207
+ try:
208
+ # Get the parent of our current parent
209
+ with open(f'/proc/{parent_pid}/status', 'r') as f:
210
+ for line in f:
211
+ if line.startswith('PPid:'):
212
+ parent_pid = int(line.split()[1])
213
+ break
214
+ except:
215
+ break # Can't determine next parent, stop here
216
+ except:
217
+ break
218
+ except:
219
+ # Process detection failed, continue with other methods
220
+ pass
221
+
222
+ # Method 3: Try running a command that prints info about the parent shell
223
+ try:
224
+ # Get parent's process ID and use it to get more info
225
+ cmd = f"ps -p $PPID -o cmd="
226
+ result = subprocess.run(['bash', '-c', cmd], capture_output=True, text=True, timeout=1)
227
+ parent_cmd = result.stdout.strip()
228
+
229
+ if "zsh" in parent_cmd.lower():
230
+ return "zsh", "zsh", operating_system
231
+ elif "bash" in parent_cmd.lower():
232
+ return "bash", "bash", operating_system
233
+ elif "fish" in parent_cmd.lower():
234
+ return "fish", "fish", operating_system
235
+ except:
236
+ pass
237
+
238
+ # Method 4: Check for shell-specific environment variables beyond the basic ones
239
+ try:
240
+ for env_var in os.environ:
241
+ if env_var.startswith("BASH_"):
90
242
  return "bash", "bash", operating_system
243
+ elif env_var.startswith("ZSH_"):
244
+ return "zsh", "zsh", operating_system
245
+ elif env_var.startswith("FISH_"):
246
+ return "fish", "fish", operating_system
247
+ except:
248
+ pass
249
+
250
+ # Method 5: Check SHELL environment variable
251
+ if os.environ.get("SHELL"):
252
+ shell_path = os.environ.get("SHELL")
253
+ shell_name = os.path.basename(shell_path)
254
+
255
+ # Match against known shell types - use exact matches first
256
+ if shell_name == "zsh":
257
+ return "zsh", "zsh", operating_system
258
+ elif shell_name == "bash":
259
+ return "bash", "bash", operating_system
260
+ elif shell_name == "fish":
261
+ return "fish", "fish", operating_system
262
+ elif shell_name in ["csh", "tcsh"]:
263
+ return shell_name, "csh", operating_system
264
+ elif shell_name == "ksh":
265
+ return shell_name, "ksh", operating_system
91
266
 
92
- # Check for explicit shell path in environment
93
- if os.environ.get("SHELL"):
94
- shell_path = os.environ.get("SHELL").lower()
95
- if "bash" in shell_path:
96
- return "bash", "bash", operating_system
97
- elif "zsh" in shell_path:
98
- return "zsh", "zsh", operating_system
99
- elif "powershell" in shell_path:
100
- return "powershell.exe", "powershell", operating_system
101
- elif "cmd" in shell_path:
267
+ # If no exact match, try substring
268
+ if "zsh" in shell_name:
269
+ return "zsh", "zsh", operating_system
270
+ elif "bash" in shell_name:
271
+ return "bash", "bash", operating_system
272
+ elif "fish" in shell_name:
273
+ return "fish", "fish", operating_system
274
+ elif any(sh in shell_name for sh in ["csh", "tcsh"]):
275
+ return shell_name, "csh", operating_system
276
+ elif "ksh" in shell_name:
277
+ return shell_name, "ksh", operating_system
278
+
279
+ # Fallback: default to bash for Unix-like systems if all else fails
280
+ return "bash", "bash", operating_system
281
+
282
+
283
+ def detect_windows_shell(operating_system):
284
+ """Detect shell type on Windows systems.
285
+
286
+ Args:
287
+ operating_system: The detected operating system string
288
+
289
+ Returns:
290
+ tuple: (shell_name, highlight_language, operating_system) - the detected shell information
291
+ """
292
+ # First check for the process name - most reliable indicator
293
+ try:
294
+ # Check parent process name for the most accurate detection
295
+ if os.name == 'nt':
296
+ import ctypes
297
+ from ctypes import wintypes
298
+
299
+ # Get parent process ID
300
+ GetCurrentProcessId = ctypes.windll.kernel32.GetCurrentProcessId
301
+ GetCurrentProcess = ctypes.windll.kernel32.GetCurrentProcess
302
+ GetProcessTimes = ctypes.windll.kernel32.GetProcessTimes
303
+ OpenProcess = ctypes.windll.kernel32.OpenProcess
304
+ GetModuleFileNameEx = ctypes.windll.psapi.GetModuleFileNameExW
305
+ QueryFullProcessImageName = ctypes.windll.kernel32.QueryFullProcessImageNameW
306
+ CloseHandle = ctypes.windll.kernel32.CloseHandle
307
+
308
+ # Try to get process path
309
+ try:
310
+ # First try to check current process executable name
311
+ process_path = sys.executable.lower()
312
+ if "powershell" in process_path:
313
+ if "pwsh" in process_path:
314
+ return "pwsh", "powershell", operating_system
315
+ else:
316
+ return "powershell.exe", "powershell", operating_system
317
+ elif "cmd.exe" in process_path:
102
318
  return "cmd.exe", "batch", operating_system
103
-
104
- # Check for PowerShell vs CMD
319
+ except Exception as e:
320
+ pass
321
+
322
+ # If that fails, check environment variables that strongly indicate shell type
323
+ if "PROMPT" in os.environ and "$P$G" in os.environ.get("PROMPT", ""):
324
+ # CMD.exe uses $P$G as default prompt
325
+ return "cmd.exe", "batch", operating_system
326
+
105
327
  if os.environ.get("PSModulePath"):
106
- # Further distinguish PowerShell vs PowerShell Core
328
+ # PowerShell has this environment variable
107
329
  if "pwsh" in os.environ.get("PSModulePath", "").lower():
108
330
  return "pwsh", "powershell", operating_system
109
331
  else:
110
332
  return "powershell.exe", "powershell", operating_system
111
- else:
112
- return "cmd.exe", "batch", operating_system
333
+ except Exception as e:
334
+ # If process detection fails, continue with environment checks
335
+ pass
336
+
337
+ # Check for WSL within Windows
338
+ if any(("wsl" in path.lower() or "microsoft" in path.lower()) for path in os.environ.get("PATH", "").split(os.pathsep)):
339
+ return "bash", "bash", operating_system
340
+
341
+ # Check for explicit shell path in environment
342
+ if os.environ.get("SHELL"):
343
+ shell_path = os.environ.get("SHELL").lower()
344
+ if "bash" in shell_path:
345
+ return "bash", "bash", operating_system
346
+ elif "zsh" in shell_path:
347
+ return "zsh", "zsh", operating_system
348
+ elif "powershell" in shell_path:
349
+ return "powershell.exe", "powershell", operating_system
350
+ elif "cmd" in shell_path:
351
+ return "cmd.exe", "batch", operating_system
352
+
353
+ # Final fallback - Check common environment variables that indicate shell type
354
+ if "ComSpec" in os.environ:
355
+ comspec = os.environ.get("ComSpec", "").lower()
356
+ if "powershell" in comspec:
357
+ return "powershell.exe", "powershell", operating_system
358
+ elif "cmd.exe" in comspec:
359
+ return "cmd.exe", "batch", operating_system
360
+
361
+ # Last resort fallback to PowerShell (most common modern Windows shell)
362
+ return "powershell.exe", "powershell", operating_system
363
+
364
+
365
+ def detect_shell():
366
+ """Detect the current shell type and OS more accurately.
367
+
368
+ Returns:
369
+ tuple: (shell_name, highlight_language, operating_system) - the detected shell name,
370
+ appropriate syntax highlighting language, and operating system
371
+ """
372
+ try:
373
+ # First detect the OS
374
+ os_type, operating_system, is_wsl = detect_os()
375
+
376
+ # Use the appropriate detection method based on OS
377
+ if os_type in ["Linux", "Darwin", "FreeBSD"] or is_wsl:
378
+ return detect_unix_shell(operating_system)
379
+ elif os_type == "Windows":
380
+ # On Windows, first check for Git Bash / MINGW environment
381
+ gitbash_result = detect_gitbash_shell(operating_system)
382
+ if gitbash_result:
383
+ return gitbash_result
384
+
385
+ # If not Git Bash, use regular Windows shell detection
386
+ return detect_windows_shell(operating_system)
113
387
  else:
114
- # Unix-like systems - try to get more specific
115
- shell_path = os.environ.get("SHELL", "/bin/bash")
116
- shell_name = os.path.basename(shell_path)
117
-
118
- # Map shell name to syntax highlight language
119
- if shell_name == "zsh":
120
- return shell_name, "zsh", operating_system
121
- elif shell_name == "fish":
122
- return shell_name, "fish", operating_system
123
- elif "csh" in shell_name:
124
- return shell_name, "csh", operating_system
388
+ # Fallback for unknown OS types
389
+ if os_type == "Windows":
390
+ return "powershell.exe", "powershell", operating_system
125
391
  else:
126
- # Default to bash for sh, bash, and other shells
127
- return shell_name, "bash", operating_system
392
+ return "bash", "bash", operating_system
128
393
  except Exception as e:
129
394
  # Fall back to simple detection if anything fails
395
+ os_type = platform.system()
396
+ operating_system = os_type
130
397
  if os_type == "Windows":
131
398
  return "powershell.exe", "powershell", operating_system
132
399
  else:
@@ -172,7 +439,7 @@ def setup_streaming(args, logger=None):
172
439
  elif args.no_stream:
173
440
  # Second priority: no-stream
174
441
  should_stream = False
175
- use_regular_prettify = False # No prettify if no streaming
442
+ use_regular_prettify = False # No prettify if no streaming
176
443
  elif args.prettify:
177
444
  # Third priority: prettify (requires disabling stream)
178
445
  if has_markdown_renderer(args.renderer):
@@ -240,8 +507,8 @@ def setup_streaming(args, logger=None):
240
507
  return (should_stream, use_stream_prettify, use_regular_prettify, stream_setup)
241
508
 
242
509
  def generate_with_model(client, prompt, messages, args, stream_setup,
243
- use_stream_prettify, should_stream, spinner_message="Generating...",
244
- temp_override=None, logger=None):
510
+ use_stream_prettify, should_stream, spinner_message="Generating...",
511
+ temp_override=None, logger=None):
245
512
  """Generate content using the model with proper streaming and spinner handling.
246
513
 
247
514
  Args:
@@ -529,19 +796,21 @@ def shell_mode(client, args, logger=None):
529
796
  ]
530
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']}] "
531
798
 
532
- # Print options with proper flushing to ensure display
533
- print(options_text, flush=True)
534
- for option in options:
535
- print(option, flush=True)
536
-
537
- # Add a small delay to ensure terminal rendering is complete,
538
- # especially important for stream-prettify mode
539
- if use_stream_prettify:
540
- time.sleep(0.2)
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)
541
810
 
542
- # Print prompt and flush to ensure it appears
543
- sys.stdout.write(prompt_text)
544
- sys.stdout.flush()
811
+ # Print prompt and flush to ensure it appears
812
+ sys.stdout.write(prompt_text)
813
+ sys.stdout.flush()
545
814
 
546
815
  try:
547
816
  # Use get_terminal_input which opens /dev/tty directly rather than using stdin
@@ -550,9 +819,9 @@ def shell_mode(client, args, logger=None):
550
819
  if response:
551
820
  response = response.lower()
552
821
  else:
553
- # If get_terminal_input fails, default to copy
554
- print("\nFailed to get terminal input. Defaulting to copy option.")
555
- response = 'c'
822
+ # If get_terminal_input fails, default to abort
823
+ print("\nFailed to get terminal input. Defaulting to abort option.")
824
+ response = 'a'
556
825
  except KeyboardInterrupt:
557
826
  print("\nCommand execution cancelled by user.")
558
827
  return
@@ -565,7 +834,21 @@ def shell_mode(client, args, logger=None):
565
834
  try:
566
835
  try:
567
836
  print("\nExecuting command... (Press Ctrl+C to cancel)")
568
- result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
837
+
838
+ # Special handling for Windows PowerShell commands
839
+ if shell_name in ["powershell.exe", "pwsh"] and platform.system() == "Windows":
840
+ # Execute PowerShell commands properly on Windows
841
+ result = subprocess.run(
842
+ ["powershell.exe", "-Command", command],
843
+ shell=True,
844
+ check=True,
845
+ capture_output=True,
846
+ text=True
847
+ )
848
+ else:
849
+ # Regular command execution for other shells
850
+ result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
851
+
569
852
  output = result.stdout
570
853
 
571
854
  # Log the command output if logging is enabled
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/ui.py CHANGED
@@ -159,10 +159,39 @@ def get_terminal_input():
159
159
  try:
160
160
  import msvcrt
161
161
  sys.stdout.flush()
162
- # Wait for a keypress
163
- char = msvcrt.getch().decode('utf-8').lower()
164
- print(char) # Echo the character
165
- return char
162
+
163
+ # Wait for a complete line (including Enter key)
164
+ input_chars = []
165
+ while True:
166
+ # Get a character
167
+ char = msvcrt.getch()
168
+
169
+ # If it's Enter (CR or LF), break the loop
170
+ if char in [b'\r', b'\n']:
171
+ print() # Move to the next line after Enter
172
+ break
173
+
174
+ # If it's backspace, handle it
175
+ if char == b'\x08':
176
+ if input_chars:
177
+ # Remove the last character
178
+ input_chars.pop()
179
+ # Erase the character on screen (backspace + space + backspace)
180
+ sys.stdout.write('\b \b')
181
+ sys.stdout.flush()
182
+ continue
183
+
184
+ # For regular characters, add to input and echo
185
+ try:
186
+ char_decoded = char.decode('utf-8').lower()
187
+ input_chars.append(char_decoded)
188
+ print(char_decoded, end='', flush=True) # Echo the character without newline
189
+ except UnicodeDecodeError:
190
+ # Skip characters that can't be decoded
191
+ continue
192
+
193
+ # Join all characters and return
194
+ return ''.join(input_chars).strip().lower()
166
195
  except ImportError:
167
196
  # Fallback if msvcrt is not available
168
197
  return None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 3.8.1
3
+ Version: 3.8.3
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
@@ -493,6 +493,8 @@ In interactive mode:
493
493
  - For security, your API key is not displayed when editing configurations
494
494
  - When removing a configuration, you'll be asked to confirm before deletion
495
495
 
496
+ ![ngpt-sh-c-a](https://raw.githubusercontent.com/nazdridoy/ngpt/main/previews/ngpt-sh-c-a.png)
497
+
496
498
  For more details on configuring nGPT, see the [Configuration Guide](https://nazdridoy.github.io/ngpt/configuration/).
497
499
 
498
500
  ### Configuration File
@@ -541,6 +543,146 @@ nGPT determines configuration values in the following order (highest priority fi
541
543
  4. Main configuration file `ngpt.conf` or `custom-config-file`
542
544
  5. Default values
543
545
 
546
+ ### Real-World Demonstrations with nGPT
547
+
548
+ Let's see nGPT in action! Here are some practical ways you can use it every day:
549
+
550
+ #### Quick Q&A and Coding
551
+
552
+ ```bash
553
+ # Get a quick explanation
554
+ ngpt "Explain the difference between threads and processes in Python"
555
+
556
+ # Generate code with real-time syntax highlighting
557
+ ngpt --code --stream-prettify "Write a Python function to reverse a linked list"
558
+ ```
559
+
560
+ With the `--code` flag, nGPT gives you clean code without explanations or markdown, just what you need to copy and paste into your project. The `--stream-prettify` option shows real-time syntax highlighting as the code comes in.
561
+
562
+ #### Shell Command Generation (OS-Aware)
563
+
564
+ ```bash
565
+ # Let nGPT generate the correct command for your OS
566
+ ngpt --shell "list all files in the current directory including hidden ones"
567
+ # On Linux/macOS: ls -la
568
+ # On Windows: dir /a
569
+ ```
570
+
571
+ One of my favorite features! No more Googling obscure command flags, nGPT generates the right command for your operating system. It'll even execute it for you if you approve.
572
+
573
+ ![ngpt-s-c](https://raw.githubusercontent.com/nazdridoy/ngpt/main/previews/ngpt-s-c.png)
574
+
575
+ #### Text Rewriting and Summarization
576
+
577
+ ```bash
578
+ # Pipe text to rewrite it (e.g., improve clarity)
579
+ echo "This is a rough draft of my email." | ngpt -r
580
+
581
+ # Summarize a file using the pipe placeholder
582
+ cat long-article.txt | ngpt --pipe "Summarize this document concisely: {}"
583
+ ```
584
+
585
+ The text rewriting feature is perfect for quickly improving documentation, emails, or reports. And with pipe placeholders, you can feed in content from files or other commands.
586
+
587
+ #### Git Commit Message Generation
588
+
589
+ ```bash
590
+ # Stage your changes
591
+ git add .
592
+
593
+ # Let nGPT generate a conventional commit message based on the diff
594
+ ngpt -g
595
+
596
+ # Generate git commit message from a diff file
597
+ ngpt -g --diff changes.diff
598
+ ```
599
+
600
+ This is a huge time-saver. nGPT analyzes your git diff and generates a properly formatted conventional commit message that actually describes what you changed. No more staring at the blank commit message prompt!
601
+
602
+ ![ngpt-g](https://raw.githubusercontent.com/nazdridoy/ngpt/main/previews/ngpt-g.png)
603
+
604
+ #### Web Search Integration
605
+
606
+ ```bash
607
+ # Ask questions that require up-to-date information
608
+ ngpt --web-search "What's the latest news about AI regulation?"
609
+ ```
610
+
611
+ The `--web-search` flag lets nGPT consult the web for recent information, making it useful for questions about current events or topics that might have changed since the AI's training data cutoff.
612
+
613
+ ![ngpt-w](https://raw.githubusercontent.com/nazdridoy/ngpt/main/previews/ngpt-w.png)
614
+
615
+ ### Real-World Integration Examples
616
+
617
+ Let's look at how nGPT can fit into your everyday workflow with some practical examples:
618
+
619
+ #### Developer Workflow
620
+
621
+ As a developer, I use nGPT throughout my day:
622
+
623
+ **Morning code review**:
624
+ ```bash
625
+ # Get explanations of complex code
626
+ git show | ngpt --pipe "Explain what this code change does and any potential issues: {}"
627
+ ```
628
+
629
+ **Debugging help**:
630
+ ```bash
631
+ # Help understand a cryptic error message
632
+ npm run build 2>&1 | grep Error | ngpt --pipe "What does this error mean and how can I fix it: {}"
633
+ ```
634
+
635
+ **Documentation generation**:
636
+ ```bash
637
+ # Generate JSDoc comments for functions
638
+ cat src/utils.js | ngpt --pipe "Write proper JSDoc comments for these functions: {}"
639
+ ```
640
+
641
+ **Commit messages**:
642
+ ```bash
643
+ # After finishing a feature
644
+ git add .
645
+ ngpt -g
646
+ ```
647
+
648
+ #### Writer's Assistant
649
+
650
+ For content creators and writers:
651
+
652
+ **Overcoming writer's block**:
653
+ ```bash
654
+ ngpt "Give me 5 different angles to approach an article about sustainable technology"
655
+ ```
656
+
657
+ **Editing assistance**:
658
+ ```bash
659
+ cat draft.md | ngpt -r
660
+ ```
661
+
662
+ **Research summaries**:
663
+ ```bash
664
+ curl -s https://example.com/research-paper.html | ngpt --pipe "Summarize the key findings from this research: {}"
665
+ ```
666
+
667
+ #### System Administrator
668
+
669
+ For sysadmins and DevOps folks:
670
+
671
+ **Generating complex commands**:
672
+ ```bash
673
+ ngpt -s "find all log files larger than 100MB that haven't been modified in the last 30 days"
674
+ ```
675
+
676
+ *Creating configuration files**:
677
+ ```bash
678
+ ngpt --code "Create a Docker Compose file for a Redis, PostgreSQL, and Node.js application"
679
+ ```
680
+
681
+ **Troubleshooting systems**:
682
+ ```bash
683
+ dmesg | tail -50 | ngpt --pipe "Explain what might be causing the issues based on these system logs: {}"
684
+ ```
685
+
544
686
  ## Contributing
545
687
 
546
688
  We welcome contributions to nGPT! Whether it's bug fixes, feature additions, or documentation improvements, your help is appreciated.
@@ -6,24 +6,24 @@ ngpt/cli/args.py,sha256=HYCDHhqP-BI_tibL1qGQ9we4483h_kCa2ksh-QxOeiU,12694
6
6
  ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
7
7
  ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
8
8
  ngpt/cli/main.py,sha256=PVulo8Pm53-oQ2Pgc4G90YhwyPImt8j7HKmY38SJ7CM,28696
9
- ngpt/cli/renderers.py,sha256=m71BeUXKynpKKGXFzwRSW1XngvyKiZ_xEsdujUbU0MA,16597
10
- ngpt/cli/ui.py,sha256=tVJGTP1DWjCRq7ONFdOOKPHcVQz0MqiLyJtodKFabTk,9612
9
+ ngpt/cli/renderers.py,sha256=vAoDkpvgG2Fl81zkJDk_-zM1Fsw8E4Uv6m1AI81Fawo,17049
10
+ ngpt/cli/ui.py,sha256=4RFxIf51di5EsytVr7OoyCWF_d40KJ0Mbom0VWgPlCc,10870
11
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
12
+ ngpt/cli/modes/chat.py,sha256=x1leClKq7UupA_CdW4tym0AivY2o_II123-I5IcAkxQ,7091
13
+ ngpt/cli/modes/code.py,sha256=Qj59xq6fZqgUDw7SbvmPKX_gdpc7DHJhNkn1sB5qgUU,12932
14
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=9Bg-uPDjNgSwLULRzRxDkBXjVd1yPZLZXG-V2kPFm2k,29929
18
- ngpt/cli/modes/text.py,sha256=7t5WWXMFxGkBM5HMP4irbN9aQwxE2YgywjiVPep710k,6417
15
+ ngpt/cli/modes/interactive.py,sha256=E0c38NA8xnuRKAce40F35uFYcohFDvaqSB8nf1ywS-4,17958
16
+ ngpt/cli/modes/rewrite.py,sha256=QQm453X9aoUQP9CAtmeghlMytMJPlsDZPKef9tGfj6g,11181
17
+ ngpt/cli/modes/shell.py,sha256=it1Brq1-LGeNfPKYBeVAwF-a78g9UP-KscofBZQkbr4,41589
18
+ ngpt/cli/modes/text.py,sha256=rUr7Byds7zkO9ZbMSZWOO_3fWfpeM40Tq1XJAgdapcs,6679
19
19
  ngpt/utils/__init__.py,sha256=_92f8eGMMOtQQA3uwgSRVwUEl1EIRFjWPUjcfGgI-eI,1244
20
20
  ngpt/utils/cli_config.py,sha256=Ug8cECBTIuzOwkBWidLTfs-OAdOsCMJ2bNa70pOADfw,11195
21
21
  ngpt/utils/config.py,sha256=wsArA4osnh8fKqOvtsPqqBxAz3DpdjtaWUFaRtnUdyc,10452
22
22
  ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
23
23
  ngpt/utils/pipe.py,sha256=qRHF-Ma7bbU0cOcb1Yhe4S-kBavivtnnvLA3EYS4FY4,2162
24
24
  ngpt/utils/web_search.py,sha256=w5ke4KJMRxq7r5jtbUXvspja6XhjoPZloVkZ0IvBXIE,30731
25
- ngpt-3.8.1.dist-info/METADATA,sha256=uiFcgSP1jz1ArOo1CUJ4FQvBZyo5Cq72_EdKzG8JEzM,24503
26
- ngpt-3.8.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
- ngpt-3.8.1.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
28
- ngpt-3.8.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
29
- ngpt-3.8.1.dist-info/RECORD,,
25
+ ngpt-3.8.3.dist-info/METADATA,sha256=GjMEbP2fAxk4J6LdsM7vN0N3vT6ikicEbTRoWRGlNQI,28919
26
+ ngpt-3.8.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
27
+ ngpt-3.8.3.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
28
+ ngpt-3.8.3.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
29
+ ngpt-3.8.3.dist-info/RECORD,,
File without changes