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 +18 -13
- ngpt/cli/modes/code.py +20 -15
- ngpt/cli/modes/interactive.py +53 -46
- ngpt/cli/modes/rewrite.py +18 -13
- ngpt/cli/modes/shell.py +346 -63
- ngpt/cli/modes/text.py +18 -13
- ngpt/cli/renderers.py +56 -50
- ngpt/cli/ui.py +33 -4
- {ngpt-3.8.1.dist-info → ngpt-3.8.3.dist-info}/METADATA +143 -1
- {ngpt-3.8.1.dist-info → ngpt-3.8.3.dist-info}/RECORD +13 -13
- {ngpt-3.8.1.dist-info → ngpt-3.8.3.dist-info}/WHEEL +0 -0
- {ngpt-3.8.1.dist-info → ngpt-3.8.3.dist-info}/entry_points.txt +0 -0
- {ngpt-3.8.1.dist-info → ngpt-3.8.3.dist-info}/licenses/LICENSE +0 -0
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
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
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
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:
|
ngpt/cli/modes/interactive.py
CHANGED
@@ -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
|
-
|
82
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
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
|
-
|
142
|
-
|
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
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
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
|
-
#
|
298
|
-
|
299
|
-
|
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
|
-
|
302
|
-
|
303
|
-
|
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
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
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
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
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
|
42
|
-
"""Detect the current
|
47
|
+
def detect_os():
|
48
|
+
"""Detect the current operating system with detailed information.
|
43
49
|
|
44
50
|
Returns:
|
45
|
-
tuple: (
|
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
|
-
|
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
|
-
#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
85
|
-
|
86
|
-
|
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
|
-
|
89
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
112
|
-
|
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
|
-
#
|
115
|
-
|
116
|
-
|
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
|
-
|
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
|
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
|
-
|
244
|
-
|
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
|
-
#
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
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
|
-
|
543
|
-
|
544
|
-
|
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
|
554
|
-
print("\nFailed to get terminal input. Defaulting to
|
555
|
-
response = '
|
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
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
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
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
#
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
# Update
|
321
|
-
if
|
322
|
-
#
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
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
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
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
|
-
|
352
|
-
|
359
|
+
|
360
|
+
live.update(md_obj)
|
353
361
|
|
354
|
-
|
362
|
+
# Ensure the display refreshes with new content
|
363
|
+
live.refresh()
|
355
364
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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.
|
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
|
+

|
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
|
+

|
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
|
+

|
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
|
+

|
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=
|
10
|
-
ngpt/cli/ui.py,sha256=
|
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=
|
13
|
-
ngpt/cli/modes/code.py,sha256=
|
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=
|
16
|
-
ngpt/cli/modes/rewrite.py,sha256=
|
17
|
-
ngpt/cli/modes/shell.py,sha256=
|
18
|
-
ngpt/cli/modes/text.py,sha256=
|
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.
|
26
|
-
ngpt-3.8.
|
27
|
-
ngpt-3.8.
|
28
|
-
ngpt-3.8.
|
29
|
-
ngpt-3.8.
|
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
|
File without changes
|
File without changes
|