ngpt 2.10.0__py3-none-any.whl → 2.11.1__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/__init__.py +2 -2
- ngpt/cli/args.py +2 -2
- ngpt/cli/config_manager.py +1 -1
- ngpt/cli/interactive.py +18 -33
- ngpt/cli/main.py +75 -16
- ngpt/cli/modes/chat.py +15 -1
- ngpt/cli/modes/code.py +11 -1
- ngpt/cli/modes/shell.py +33 -3
- ngpt/cli/modes/text.py +15 -1
- ngpt/utils/__init__.py +32 -0
- ngpt/{cli_config.py → utils/cli_config.py} +1 -1
- ngpt/utils/log.py +180 -0
- {ngpt-2.10.0.dist-info → ngpt-2.11.1.dist-info}/METADATA +9 -4
- ngpt-2.11.1.dist-info/RECORD +25 -0
- ngpt-2.10.0.dist-info/RECORD +0 -24
- /ngpt/{config.py → utils/config.py} +0 -0
- {ngpt-2.10.0.dist-info → ngpt-2.11.1.dist-info}/WHEEL +0 -0
- {ngpt-2.10.0.dist-info → ngpt-2.11.1.dist-info}/entry_points.txt +0 -0
- {ngpt-2.10.0.dist-info → ngpt-2.11.1.dist-info}/licenses/LICENSE +0 -0
ngpt/__init__.py
CHANGED
@@ -2,8 +2,8 @@ from importlib.metadata import version as get_version
|
|
2
2
|
__version__ = get_version("ngpt")
|
3
3
|
|
4
4
|
from .client import NGPTClient
|
5
|
-
from .config import load_config, get_config_path, get_config_dir
|
6
|
-
from .cli_config import (
|
5
|
+
from .utils.config import load_config, get_config_path, get_config_dir
|
6
|
+
from .utils.cli_config import (
|
7
7
|
load_cli_config,
|
8
8
|
set_cli_config_option,
|
9
9
|
get_cli_config_option,
|
ngpt/cli/args.py
CHANGED
@@ -56,8 +56,8 @@ def setup_argument_parser():
|
|
56
56
|
help='Set top_p (controls diversity, default: 1.0)')
|
57
57
|
global_group.add_argument('--max_tokens', type=int,
|
58
58
|
help='Set max response length in tokens')
|
59
|
-
global_group.add_argument('--log', metavar='FILE',
|
60
|
-
help='Set filepath to log conversation to
|
59
|
+
global_group.add_argument('--log', metavar='FILE', nargs='?', const=True,
|
60
|
+
help='Set filepath to log conversation to, or create a temporary log file if no path provided')
|
61
61
|
global_group.add_argument('--preprompt',
|
62
62
|
help='Set custom system prompt to control AI behavior')
|
63
63
|
global_group.add_argument('--prettify', action='store_const', const='auto',
|
ngpt/cli/config_manager.py
CHANGED
ngpt/cli/interactive.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
import sys
|
2
|
+
import os
|
2
3
|
import shutil
|
3
4
|
import datetime
|
4
5
|
import traceback
|
5
6
|
from .formatters import COLORS
|
6
7
|
from .renderers import prettify_markdown, prettify_streaming_markdown
|
8
|
+
from ..utils.log import create_logger
|
7
9
|
|
8
10
|
# Optional imports for enhanced UI
|
9
11
|
try:
|
@@ -16,7 +18,7 @@ try:
|
|
16
18
|
except ImportError:
|
17
19
|
HAS_PROMPT_TOOLKIT = False
|
18
20
|
|
19
|
-
def interactive_chat_session(client, web_search=False, no_stream=False, temperature=0.7, top_p=1.0, max_tokens=None,
|
21
|
+
def interactive_chat_session(client, web_search=False, no_stream=False, temperature=0.7, top_p=1.0, max_tokens=None, preprompt=None, prettify=False, renderer='auto', stream_prettify=False, logger=None):
|
20
22
|
"""Start an interactive chat session with the AI.
|
21
23
|
|
22
24
|
Args:
|
@@ -26,11 +28,11 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
26
28
|
temperature: Controls randomness in the response
|
27
29
|
top_p: Controls diversity via nucleus sampling
|
28
30
|
max_tokens: Maximum number of tokens to generate in each response
|
29
|
-
log_file: Optional filepath to log conversation to
|
30
31
|
preprompt: Custom system prompt to control AI behavior
|
31
32
|
prettify: Whether to enable markdown rendering
|
32
33
|
renderer: Which markdown renderer to use
|
33
34
|
stream_prettify: Whether to enable streaming with prettify
|
35
|
+
logger: Logger instance for logging the conversation
|
34
36
|
"""
|
35
37
|
# Get terminal width for better formatting
|
36
38
|
try:
|
@@ -58,18 +60,9 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
58
60
|
|
59
61
|
print(f"\n{separator}\n")
|
60
62
|
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
try:
|
65
|
-
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
66
|
-
log_handle = open(log_file, 'a', encoding='utf-8')
|
67
|
-
log_handle.write(f"\n--- nGPT Session Log: {sys.argv} ---\n")
|
68
|
-
log_handle.write(f"Started at: {timestamp}\n\n")
|
69
|
-
print(f"{COLORS['green']}Logging conversation to: {log_file}{COLORS['reset']}")
|
70
|
-
except Exception as e:
|
71
|
-
print(f"{COLORS['yellow']}Warning: Could not open log file: {str(e)}{COLORS['reset']}")
|
72
|
-
log_handle = None
|
63
|
+
# Show logging info if logger is available
|
64
|
+
if logger:
|
65
|
+
print(f"{COLORS['green']}Logging conversation to: {logger.get_log_path()}{COLORS['reset']}")
|
73
66
|
|
74
67
|
# Custom separator - use the same length for consistency
|
75
68
|
def print_separator():
|
@@ -90,9 +83,8 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
90
83
|
conversation.append(system_message)
|
91
84
|
|
92
85
|
# Log system prompt if logging is enabled
|
93
|
-
if
|
94
|
-
|
95
|
-
log_handle.flush()
|
86
|
+
if logger and preprompt:
|
87
|
+
logger.log("system", system_prompt)
|
96
88
|
|
97
89
|
# Initialize prompt_toolkit history
|
98
90
|
prompt_history = InMemoryHistory() if HAS_PROMPT_TOOLKIT else None
|
@@ -187,9 +179,8 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
187
179
|
conversation.append(user_message)
|
188
180
|
|
189
181
|
# Log user message if logging is enabled
|
190
|
-
if
|
191
|
-
|
192
|
-
log_handle.flush()
|
182
|
+
if logger:
|
183
|
+
logger.log("user", user_input)
|
193
184
|
|
194
185
|
# Print assistant indicator with formatting
|
195
186
|
if not no_stream and not stream_prettify:
|
@@ -254,22 +245,16 @@ def interactive_chat_session(client, web_search=False, no_stream=False, temperat
|
|
254
245
|
else:
|
255
246
|
print(response)
|
256
247
|
|
257
|
-
# Log
|
258
|
-
if
|
259
|
-
|
260
|
-
log_handle.flush()
|
248
|
+
# Log AI response if logging is enabled
|
249
|
+
if logger:
|
250
|
+
logger.log("assistant", response)
|
261
251
|
|
262
252
|
# Print separator between exchanges
|
263
253
|
print_separator()
|
264
254
|
|
265
255
|
except KeyboardInterrupt:
|
266
|
-
print(f"\n\n{COLORS['
|
256
|
+
print(f"\n\n{COLORS['yellow']}Chat session interrupted by user.{COLORS['reset']}")
|
267
257
|
except Exception as e:
|
268
|
-
print(f"\n{COLORS['yellow']}Error
|
269
|
-
|
270
|
-
|
271
|
-
finally:
|
272
|
-
# Close log file if it was opened
|
273
|
-
if log_handle:
|
274
|
-
log_handle.write(f"\n--- End of Session ---\n")
|
275
|
-
log_handle.close()
|
258
|
+
print(f"\n{COLORS['yellow']}Error in chat session: {str(e)}{COLORS['reset']}")
|
259
|
+
if os.environ.get("NGPT_DEBUG"):
|
260
|
+
traceback.print_exc()
|
ngpt/cli/main.py
CHANGED
@@ -2,8 +2,8 @@ import argparse
|
|
2
2
|
import sys
|
3
3
|
import os
|
4
4
|
from ..client import NGPTClient
|
5
|
-
from ..config import load_config, get_config_path, load_configs, add_config_entry, remove_config_entry
|
6
|
-
from ..cli_config import (
|
5
|
+
from ..utils.config import load_config, get_config_path, load_configs, add_config_entry, remove_config_entry
|
6
|
+
from ..utils.cli_config import (
|
7
7
|
set_cli_config_option,
|
8
8
|
get_cli_config_option,
|
9
9
|
unset_cli_config_option,
|
@@ -12,6 +12,7 @@ from ..cli_config import (
|
|
12
12
|
CLI_CONFIG_OPTIONS,
|
13
13
|
load_cli_config
|
14
14
|
)
|
15
|
+
from ..utils.log import create_logger
|
15
16
|
from .. import __version__
|
16
17
|
|
17
18
|
from .formatters import COLORS, ColoredHelpFormatter
|
@@ -195,6 +196,46 @@ def main():
|
|
195
196
|
# Load CLI configuration early
|
196
197
|
cli_config = load_cli_config()
|
197
198
|
|
199
|
+
# Initialize logger if --log is specified
|
200
|
+
logger = None
|
201
|
+
if args.log is not None:
|
202
|
+
# Check if the log value is a string that looks like a prompt (incorrectly parsed)
|
203
|
+
likely_prompt = False
|
204
|
+
likely_path = False
|
205
|
+
|
206
|
+
if isinstance(args.log, str) and args.prompt is None:
|
207
|
+
# Check if string looks like a path
|
208
|
+
if args.log.startswith('/') or args.log.startswith('./') or args.log.startswith('../') or args.log.startswith('~'):
|
209
|
+
likely_path = True
|
210
|
+
# Check if string has a file extension
|
211
|
+
elif '.' in os.path.basename(args.log):
|
212
|
+
likely_path = True
|
213
|
+
# Check if parent directory exists
|
214
|
+
elif os.path.exists(os.path.dirname(args.log)) and os.path.dirname(args.log) != '':
|
215
|
+
likely_path = True
|
216
|
+
# Check if string ends with a question mark (very likely a prompt)
|
217
|
+
elif args.log.strip().endswith('?'):
|
218
|
+
likely_prompt = True
|
219
|
+
# As a last resort, if it has spaces and doesn't look like a path, assume it's a prompt
|
220
|
+
elif ' ' in args.log and not likely_path:
|
221
|
+
likely_prompt = True
|
222
|
+
|
223
|
+
if likely_prompt and not likely_path:
|
224
|
+
# This is likely a prompt, not a log path
|
225
|
+
args.prompt = args.log
|
226
|
+
# Change log to True to create a temp file
|
227
|
+
args.log = True
|
228
|
+
|
229
|
+
# If --log is True, it means it was used without a path value
|
230
|
+
log_path = None if args.log is True else args.log
|
231
|
+
logger = create_logger(log_path)
|
232
|
+
if logger:
|
233
|
+
logger.open()
|
234
|
+
print(f"{COLORS['green']}Logging session to: {logger.get_log_path()}{COLORS['reset']}")
|
235
|
+
# If it's a temporary log file, inform the user
|
236
|
+
if logger.is_temporary():
|
237
|
+
print(f"{COLORS['green']}Created temporary log file.{COLORS['reset']}")
|
238
|
+
|
198
239
|
# Priority order for config selection:
|
199
240
|
# 1. Command-line arguments (args.provider, args.config_index)
|
200
241
|
# 2. CLI configuration (cli_config["provider"], cli_config["config-index"])
|
@@ -289,10 +330,20 @@ def main():
|
|
289
330
|
|
290
331
|
return
|
291
332
|
|
292
|
-
#
|
293
|
-
|
294
|
-
|
295
|
-
|
333
|
+
# Check if --config-index was explicitly specified in command line args
|
334
|
+
config_index_explicit = '--config-index' in sys.argv
|
335
|
+
provider_explicit = '--provider' in sys.argv
|
336
|
+
|
337
|
+
# When only --config is used (without explicit --config-index or --provider),
|
338
|
+
# always create a new configuration regardless of CLI config settings
|
339
|
+
if not config_index_explicit and not provider_explicit:
|
340
|
+
# Always create a new config when just --config is used
|
341
|
+
configs = load_configs(str(config_path))
|
342
|
+
print(f"Creating new configuration at index {len(configs)}")
|
343
|
+
add_config_entry(config_path, None)
|
344
|
+
return
|
345
|
+
|
346
|
+
# If explicitly specified indexes or provider, continue with editing behavior
|
296
347
|
config_index = None
|
297
348
|
|
298
349
|
# Determine if we're editing an existing config or creating a new one
|
@@ -320,7 +371,7 @@ def main():
|
|
320
371
|
config_index = matching_configs[0]
|
321
372
|
|
322
373
|
print(f"Editing existing configuration at index {config_index}")
|
323
|
-
elif effective_config_index != 0 or
|
374
|
+
elif effective_config_index != 0 or config_index_explicit:
|
324
375
|
# Check if the index is valid
|
325
376
|
configs = load_configs(str(config_path))
|
326
377
|
if effective_config_index >= 0 and effective_config_index < len(configs):
|
@@ -426,7 +477,12 @@ def main():
|
|
426
477
|
show_available_renderers()
|
427
478
|
|
428
479
|
# Initialize client using the potentially overridden active_config
|
429
|
-
client = NGPTClient(
|
480
|
+
client = NGPTClient(
|
481
|
+
api_key=active_config.get("api_key", args.api_key),
|
482
|
+
base_url=active_config.get("base_url", args.base_url),
|
483
|
+
provider=active_config.get("provider"),
|
484
|
+
model=active_config.get("model", args.model)
|
485
|
+
)
|
430
486
|
|
431
487
|
try:
|
432
488
|
# Handle listing models
|
@@ -459,32 +515,32 @@ def main():
|
|
459
515
|
temperature=args.temperature,
|
460
516
|
top_p=args.top_p,
|
461
517
|
max_tokens=args.max_tokens,
|
462
|
-
log_file=args.log,
|
463
518
|
preprompt=args.preprompt,
|
464
519
|
prettify=args.prettify,
|
465
520
|
renderer=args.renderer,
|
466
|
-
stream_prettify=args.stream_prettify
|
521
|
+
stream_prettify=args.stream_prettify,
|
522
|
+
logger=logger
|
467
523
|
)
|
468
524
|
elif args.shell:
|
469
525
|
# Apply CLI config for shell mode
|
470
526
|
args = apply_cli_config(args, "shell")
|
471
527
|
|
472
528
|
# Shell command generation mode
|
473
|
-
shell_mode(client, args)
|
529
|
+
shell_mode(client, args, logger=logger)
|
474
530
|
|
475
531
|
elif args.code:
|
476
532
|
# Apply CLI config for code mode
|
477
533
|
args = apply_cli_config(args, "code")
|
478
534
|
|
479
535
|
# Code generation mode
|
480
|
-
code_mode(client, args)
|
536
|
+
code_mode(client, args, logger=logger)
|
481
537
|
|
482
538
|
elif args.text:
|
483
539
|
# Apply CLI config for text mode
|
484
540
|
args = apply_cli_config(args, "text")
|
485
541
|
|
486
542
|
# Text mode (multiline input)
|
487
|
-
text_mode(client, args)
|
543
|
+
text_mode(client, args, logger=logger)
|
488
544
|
|
489
545
|
else:
|
490
546
|
# Default to chat mode
|
@@ -492,12 +548,15 @@ def main():
|
|
492
548
|
args = apply_cli_config(args, "all")
|
493
549
|
|
494
550
|
# Standard chat mode
|
495
|
-
chat_mode(client, args)
|
496
|
-
|
551
|
+
chat_mode(client, args, logger=logger)
|
497
552
|
except KeyboardInterrupt:
|
498
553
|
print("\nOperation cancelled by user. Exiting gracefully.")
|
499
554
|
# Make sure we exit with a non-zero status code to indicate the operation was cancelled
|
500
555
|
sys.exit(130) # 130 is the standard exit code for SIGINT (Ctrl+C)
|
501
556
|
except Exception as e:
|
502
557
|
print(f"Error: {e}")
|
503
|
-
sys.exit(1) # Exit with error code
|
558
|
+
sys.exit(1) # Exit with error code
|
559
|
+
finally:
|
560
|
+
# Close the logger if it exists
|
561
|
+
if logger:
|
562
|
+
logger.close()
|
ngpt/cli/modes/chat.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
3
|
+
from ...utils.log import create_logger
|
3
4
|
import sys
|
4
5
|
|
5
|
-
def chat_mode(client, args):
|
6
|
+
def chat_mode(client, args, logger=None):
|
6
7
|
"""Handle the standard chat mode with a single prompt.
|
7
8
|
|
8
9
|
Args:
|
9
10
|
client: The NGPTClient instance
|
10
11
|
args: The parsed command-line arguments
|
12
|
+
logger: Optional logger instance
|
11
13
|
"""
|
12
14
|
# Get the prompt
|
13
15
|
if args.prompt is None:
|
@@ -19,10 +21,18 @@ def chat_mode(client, args):
|
|
19
21
|
sys.exit(130)
|
20
22
|
else:
|
21
23
|
prompt = args.prompt
|
24
|
+
|
25
|
+
# Log the user message if logging is enabled
|
26
|
+
if logger:
|
27
|
+
logger.log("user", prompt)
|
22
28
|
|
23
29
|
# Create messages array with preprompt if available
|
24
30
|
messages = None
|
25
31
|
if args.preprompt:
|
32
|
+
# Log the system message if logging is enabled
|
33
|
+
if logger:
|
34
|
+
logger.log("system", args.preprompt)
|
35
|
+
|
26
36
|
messages = [
|
27
37
|
{"role": "system", "content": args.preprompt},
|
28
38
|
{"role": "user", "content": prompt}
|
@@ -63,6 +73,10 @@ def chat_mode(client, args):
|
|
63
73
|
# Stop live display if using stream-prettify
|
64
74
|
if args.stream_prettify and live_display:
|
65
75
|
live_display.stop()
|
76
|
+
|
77
|
+
# Log the AI response if logging is enabled
|
78
|
+
if logger and response:
|
79
|
+
logger.log("assistant", response)
|
66
80
|
|
67
81
|
# Handle non-stream response or regular prettify
|
68
82
|
if (args.no_stream or args.prettify) and response:
|
ngpt/cli/modes/code.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown, has_markdown_renderer, show_available_renderers
|
3
|
+
from ...utils.log import create_logger
|
3
4
|
import sys
|
4
5
|
|
5
|
-
def code_mode(client, args):
|
6
|
+
def code_mode(client, args, logger=None):
|
6
7
|
"""Handle the code generation mode.
|
7
8
|
|
8
9
|
Args:
|
9
10
|
client: The NGPTClient instance
|
10
11
|
args: The parsed command-line arguments
|
12
|
+
logger: Optional logger instance
|
11
13
|
"""
|
12
14
|
if args.prompt is None:
|
13
15
|
try:
|
@@ -18,6 +20,10 @@ def code_mode(client, args):
|
|
18
20
|
sys.exit(130)
|
19
21
|
else:
|
20
22
|
prompt = args.prompt
|
23
|
+
|
24
|
+
# Log the user prompt if logging is enabled
|
25
|
+
if logger:
|
26
|
+
logger.log("user", prompt)
|
21
27
|
|
22
28
|
# Setup for streaming and prettify logic
|
23
29
|
stream_callback = None
|
@@ -86,6 +92,10 @@ def code_mode(client, args):
|
|
86
92
|
# Stop live display if using stream-prettify
|
87
93
|
if use_stream_prettify and live_display:
|
88
94
|
live_display.stop()
|
95
|
+
|
96
|
+
# Log the generated code if logging is enabled
|
97
|
+
if logger and generated_code:
|
98
|
+
logger.log("assistant", generated_code)
|
89
99
|
|
90
100
|
# Print non-streamed output if needed
|
91
101
|
if generated_code and not should_stream:
|
ngpt/cli/modes/shell.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
|
+
from ...utils.log import create_logger
|
2
3
|
import subprocess
|
3
4
|
import sys
|
4
5
|
|
5
|
-
def shell_mode(client, args):
|
6
|
+
def shell_mode(client, args, logger=None):
|
6
7
|
"""Handle the shell command generation mode.
|
7
8
|
|
8
9
|
Args:
|
9
10
|
client: The NGPTClient instance
|
10
11
|
args: The parsed command-line arguments
|
12
|
+
logger: Optional logger instance
|
11
13
|
"""
|
12
14
|
if args.prompt is None:
|
13
15
|
try:
|
@@ -18,12 +20,20 @@ def shell_mode(client, args):
|
|
18
20
|
sys.exit(130)
|
19
21
|
else:
|
20
22
|
prompt = args.prompt
|
23
|
+
|
24
|
+
# Log the user prompt if logging is enabled
|
25
|
+
if logger:
|
26
|
+
logger.log("user", prompt)
|
21
27
|
|
22
28
|
command = client.generate_shell_command(prompt, web_search=args.web_search,
|
23
29
|
temperature=args.temperature, top_p=args.top_p,
|
24
30
|
max_tokens=args.max_tokens)
|
25
31
|
if not command:
|
26
32
|
return # Error already printed by client
|
33
|
+
|
34
|
+
# Log the generated command if logging is enabled
|
35
|
+
if logger:
|
36
|
+
logger.log("assistant", command)
|
27
37
|
|
28
38
|
print(f"\nGenerated command: {command}")
|
29
39
|
|
@@ -35,12 +45,32 @@ def shell_mode(client, args):
|
|
35
45
|
return
|
36
46
|
|
37
47
|
if response == 'y' or response == 'yes':
|
48
|
+
# Log the execution if logging is enabled
|
49
|
+
if logger:
|
50
|
+
logger.log("system", f"Executing command: {command}")
|
51
|
+
|
38
52
|
try:
|
39
53
|
try:
|
40
54
|
print("\nExecuting command... (Press Ctrl+C to cancel)")
|
41
55
|
result = subprocess.run(command, shell=True, check=True, capture_output=True, text=True)
|
42
|
-
|
56
|
+
output = result.stdout
|
57
|
+
|
58
|
+
# Log the command output if logging is enabled
|
59
|
+
if logger:
|
60
|
+
logger.log("system", f"Command output: {output}")
|
61
|
+
|
62
|
+
print(f"\nOutput:\n{output}")
|
43
63
|
except KeyboardInterrupt:
|
44
64
|
print("\nCommand execution cancelled by user.")
|
65
|
+
|
66
|
+
# Log the cancellation if logging is enabled
|
67
|
+
if logger:
|
68
|
+
logger.log("system", "Command execution cancelled by user")
|
45
69
|
except subprocess.CalledProcessError as e:
|
46
|
-
|
70
|
+
error = e.stderr
|
71
|
+
|
72
|
+
# Log the error if logging is enabled
|
73
|
+
if logger:
|
74
|
+
logger.log("system", f"Command error: {error}")
|
75
|
+
|
76
|
+
print(f"\nError:\n{error}")
|
ngpt/cli/modes/text.py
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
from ..formatters import COLORS
|
2
2
|
from ..renderers import prettify_markdown, prettify_streaming_markdown
|
3
3
|
from ..ui import get_multiline_input
|
4
|
+
from ...utils.log import create_logger
|
4
5
|
|
5
|
-
def text_mode(client, args):
|
6
|
+
def text_mode(client, args, logger=None):
|
6
7
|
"""Handle the multi-line text input mode.
|
7
8
|
|
8
9
|
Args:
|
9
10
|
client: The NGPTClient instance
|
10
11
|
args: The parsed command-line arguments
|
12
|
+
logger: Optional logger instance
|
11
13
|
"""
|
12
14
|
if args.prompt is not None:
|
13
15
|
prompt = args.prompt
|
@@ -20,9 +22,17 @@ def text_mode(client, args):
|
|
20
22
|
|
21
23
|
print("\nSubmission successful. Waiting for response...")
|
22
24
|
|
25
|
+
# Log the user message if logging is enabled
|
26
|
+
if logger:
|
27
|
+
logger.log("user", prompt)
|
28
|
+
|
23
29
|
# Create messages array with preprompt if available
|
24
30
|
messages = None
|
25
31
|
if args.preprompt:
|
32
|
+
# Log the system message if logging is enabled
|
33
|
+
if logger:
|
34
|
+
logger.log("system", args.preprompt)
|
35
|
+
|
26
36
|
messages = [
|
27
37
|
{"role": "system", "content": args.preprompt},
|
28
38
|
{"role": "user", "content": prompt}
|
@@ -64,6 +74,10 @@ def text_mode(client, args):
|
|
64
74
|
if args.stream_prettify and live_display:
|
65
75
|
live_display.stop()
|
66
76
|
|
77
|
+
# Log the AI response if logging is enabled
|
78
|
+
if logger and response:
|
79
|
+
logger.log("assistant", response)
|
80
|
+
|
67
81
|
# Handle non-stream response or regular prettify
|
68
82
|
if (args.no_stream or args.prettify) and response:
|
69
83
|
if args.prettify:
|
ngpt/utils/__init__.py
CHANGED
@@ -1 +1,33 @@
|
|
1
1
|
# ngpt utils module
|
2
|
+
|
3
|
+
from .log import create_logger, Logger
|
4
|
+
from .config import (
|
5
|
+
load_config,
|
6
|
+
get_config_path,
|
7
|
+
get_config_dir,
|
8
|
+
load_configs,
|
9
|
+
add_config_entry,
|
10
|
+
remove_config_entry,
|
11
|
+
DEFAULT_CONFIG,
|
12
|
+
DEFAULT_CONFIG_ENTRY
|
13
|
+
)
|
14
|
+
from .cli_config import (
|
15
|
+
load_cli_config,
|
16
|
+
set_cli_config_option,
|
17
|
+
get_cli_config_option,
|
18
|
+
unset_cli_config_option,
|
19
|
+
apply_cli_config,
|
20
|
+
list_cli_config_options,
|
21
|
+
CLI_CONFIG_OPTIONS,
|
22
|
+
get_cli_config_dir,
|
23
|
+
get_cli_config_path
|
24
|
+
)
|
25
|
+
|
26
|
+
__all__ = [
|
27
|
+
"create_logger", "Logger",
|
28
|
+
"load_config", "get_config_path", "get_config_dir", "load_configs",
|
29
|
+
"add_config_entry", "remove_config_entry", "DEFAULT_CONFIG", "DEFAULT_CONFIG_ENTRY",
|
30
|
+
"load_cli_config", "set_cli_config_option", "get_cli_config_option",
|
31
|
+
"unset_cli_config_option", "apply_cli_config", "list_cli_config_options",
|
32
|
+
"CLI_CONFIG_OPTIONS", "get_cli_config_dir", "get_cli_config_path"
|
33
|
+
]
|
@@ -11,7 +11,7 @@ CLI_CONFIG_OPTIONS = {
|
|
11
11
|
"temperature": {"type": "float", "default": 0.7, "context": ["all"]},
|
12
12
|
"top_p": {"type": "float", "default": 1.0, "context": ["all"]},
|
13
13
|
"max_tokens": {"type": "int", "default": None, "context": ["all"]},
|
14
|
-
"log": {"type": "str", "default": None, "context": ["
|
14
|
+
"log": {"type": "str", "default": None, "context": ["all"]},
|
15
15
|
"preprompt": {"type": "str", "default": None, "context": ["all"]},
|
16
16
|
"no-stream": {"type": "bool", "default": False, "context": ["all"], "exclusive": ["prettify", "stream-prettify"]},
|
17
17
|
"prettify": {"type": "bool", "default": False, "context": ["all"], "exclusive": ["no-stream", "stream-prettify"]},
|
ngpt/utils/log.py
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
import datetime
|
4
|
+
import tempfile
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Optional, TextIO, Dict, Any
|
7
|
+
|
8
|
+
# Simple color definitions for fallback message
|
9
|
+
COLORS = {
|
10
|
+
"green": "\033[32m",
|
11
|
+
"yellow": "\033[33m",
|
12
|
+
"reset": "\033[0m"
|
13
|
+
}
|
14
|
+
|
15
|
+
class Logger:
|
16
|
+
"""Handles logging functionality for ngpt"""
|
17
|
+
|
18
|
+
def __init__(self, log_path: Optional[str] = None):
|
19
|
+
"""
|
20
|
+
Initialize the logger.
|
21
|
+
|
22
|
+
Args:
|
23
|
+
log_path: Optional path to the log file. If None, a temporary file will be created.
|
24
|
+
"""
|
25
|
+
self.log_path = log_path
|
26
|
+
self.log_file: Optional[TextIO] = None
|
27
|
+
self.is_temp = False
|
28
|
+
self.command_args = sys.argv
|
29
|
+
|
30
|
+
if self.log_path is None:
|
31
|
+
# Create a temporary log file with date-time in the name
|
32
|
+
timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
|
33
|
+
|
34
|
+
# Use OS-specific temp directory
|
35
|
+
if sys.platform == "win32":
|
36
|
+
# Windows
|
37
|
+
temp_dir = os.environ.get("TEMP", "")
|
38
|
+
self.log_path = os.path.join(temp_dir, f"ngpt-{timestamp}.log")
|
39
|
+
else:
|
40
|
+
# Linux/MacOS
|
41
|
+
self.log_path = f"/tmp/ngpt-{timestamp}.log"
|
42
|
+
|
43
|
+
self.is_temp = True
|
44
|
+
|
45
|
+
def __enter__(self):
|
46
|
+
"""Context manager entry"""
|
47
|
+
self.open()
|
48
|
+
return self
|
49
|
+
|
50
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
51
|
+
"""Context manager exit"""
|
52
|
+
self.close()
|
53
|
+
|
54
|
+
def open(self) -> bool:
|
55
|
+
"""
|
56
|
+
Open the log file for writing.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
bool: True if successful, False otherwise.
|
60
|
+
"""
|
61
|
+
try:
|
62
|
+
# Expand ~ to home directory if present
|
63
|
+
if self.log_path.startswith('~'):
|
64
|
+
self.log_path = os.path.expanduser(self.log_path)
|
65
|
+
|
66
|
+
# Make sure the directory exists
|
67
|
+
log_dir = os.path.dirname(self.log_path)
|
68
|
+
if log_dir and not os.path.exists(log_dir):
|
69
|
+
try:
|
70
|
+
os.makedirs(log_dir, exist_ok=True)
|
71
|
+
except (PermissionError, OSError) as e:
|
72
|
+
print(f"Warning: Could not create log directory: {str(e)}", file=sys.stderr)
|
73
|
+
# Fall back to temp directory
|
74
|
+
timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
|
75
|
+
if sys.platform == "win32":
|
76
|
+
temp_dir = os.environ.get("TEMP", "")
|
77
|
+
self.log_path = os.path.join(temp_dir, f"ngpt-{timestamp}.log")
|
78
|
+
else:
|
79
|
+
self.log_path = f"/tmp/ngpt-{timestamp}.log"
|
80
|
+
self.is_temp = True
|
81
|
+
|
82
|
+
self.log_file = open(self.log_path, 'a', encoding='utf-8')
|
83
|
+
|
84
|
+
# Write header information
|
85
|
+
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
86
|
+
self.log_file.write(f"\n--- nGPT Session Log ---\n")
|
87
|
+
self.log_file.write(f"Started at: {timestamp}\n")
|
88
|
+
self.log_file.write(f"Command: {' '.join(self.command_args)}\n")
|
89
|
+
self.log_file.write(f"Log file: {self.log_path}\n\n")
|
90
|
+
self.log_file.flush()
|
91
|
+
|
92
|
+
return True
|
93
|
+
except Exception as e:
|
94
|
+
print(f"Warning: Could not open log file: {str(e)}", file=sys.stderr)
|
95
|
+
|
96
|
+
# Fall back to temp file
|
97
|
+
timestamp = datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
|
98
|
+
if sys.platform == "win32":
|
99
|
+
temp_dir = os.environ.get("TEMP", "")
|
100
|
+
self.log_path = os.path.join(temp_dir, f"ngpt-{timestamp}.log")
|
101
|
+
else:
|
102
|
+
self.log_path = f"/tmp/ngpt-{timestamp}.log"
|
103
|
+
self.is_temp = True
|
104
|
+
|
105
|
+
# Try again with temp file
|
106
|
+
try:
|
107
|
+
self.log_file = open(self.log_path, 'a', encoding='utf-8')
|
108
|
+
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
109
|
+
self.log_file.write(f"\n--- nGPT Session Log ---\n")
|
110
|
+
self.log_file.write(f"Started at: {timestamp}\n")
|
111
|
+
self.log_file.write(f"Command: {' '.join(self.command_args)}\n")
|
112
|
+
self.log_file.write(f"Log file: {self.log_path}\n\n")
|
113
|
+
self.log_file.flush()
|
114
|
+
print(f"{COLORS['green']}Falling back to temporary log file: {self.log_path}{COLORS['reset']}", file=sys.stderr)
|
115
|
+
return True
|
116
|
+
except Exception as e2:
|
117
|
+
print(f"Warning: Could not open temporary log file: {str(e2)}", file=sys.stderr)
|
118
|
+
self.log_file = None
|
119
|
+
return False
|
120
|
+
|
121
|
+
def close(self):
|
122
|
+
"""Close the log file if it's open."""
|
123
|
+
if self.log_file:
|
124
|
+
try:
|
125
|
+
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
126
|
+
self.log_file.write(f"\n--- Session ended at {timestamp} ---\n")
|
127
|
+
self.log_file.close()
|
128
|
+
except Exception:
|
129
|
+
pass
|
130
|
+
self.log_file = None
|
131
|
+
|
132
|
+
def log(self, role: str, content: str):
|
133
|
+
"""
|
134
|
+
Log a message.
|
135
|
+
|
136
|
+
Args:
|
137
|
+
role: Role of the message (e.g., 'system', 'user', 'assistant')
|
138
|
+
content: Content of the message
|
139
|
+
"""
|
140
|
+
if not self.log_file:
|
141
|
+
return
|
142
|
+
|
143
|
+
try:
|
144
|
+
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
145
|
+
self.log_file.write(f"{timestamp}: {role}: {content}\n")
|
146
|
+
self.log_file.flush()
|
147
|
+
except Exception:
|
148
|
+
# Silently fail if logging fails
|
149
|
+
pass
|
150
|
+
|
151
|
+
def get_log_path(self) -> str:
|
152
|
+
"""
|
153
|
+
Get the path to the log file.
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
str: Path to the log file
|
157
|
+
"""
|
158
|
+
return self.log_path
|
159
|
+
|
160
|
+
def is_temporary(self) -> bool:
|
161
|
+
"""
|
162
|
+
Check if the log file is temporary.
|
163
|
+
|
164
|
+
Returns:
|
165
|
+
bool: True if the log file is temporary
|
166
|
+
"""
|
167
|
+
return self.is_temp
|
168
|
+
|
169
|
+
|
170
|
+
def create_logger(log_path: Optional[str] = None) -> Logger:
|
171
|
+
"""
|
172
|
+
Create a logger instance.
|
173
|
+
|
174
|
+
Args:
|
175
|
+
log_path: Optional path to the log file
|
176
|
+
|
177
|
+
Returns:
|
178
|
+
Logger: Logger instance
|
179
|
+
"""
|
180
|
+
return Logger(log_path)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ngpt
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.11.1
|
4
4
|
Summary: A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints.
|
5
5
|
Project-URL: Homepage, https://github.com/nazdridoy/ngpt
|
6
6
|
Project-URL: Repository, https://github.com/nazdridoy/ngpt
|
@@ -114,6 +114,9 @@ ngpt --preprompt "You are a Linux expert" "How do I find large files?"
|
|
114
114
|
|
115
115
|
# Log your conversation to a file
|
116
116
|
ngpt --interactive --log conversation.log
|
117
|
+
|
118
|
+
# Create a temporary log file automatically
|
119
|
+
ngpt --log "Tell me about quantum computing"
|
117
120
|
```
|
118
121
|
|
119
122
|
For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdridoy.github.io/ngpt/usage/cli_usage.html).
|
@@ -228,7 +231,8 @@ For more CLI examples and detailed usage information, see the [CLI Usage Guide](
|
|
228
231
|
### As a Library
|
229
232
|
|
230
233
|
```python
|
231
|
-
from ngpt import NGPTClient
|
234
|
+
from ngpt import NGPTClient
|
235
|
+
from ngpt.utils.config import load_config
|
232
236
|
|
233
237
|
# Load the first configuration (index 0) from config file
|
234
238
|
config = load_config(config_index=0)
|
@@ -292,7 +296,8 @@ For advanced usage patterns and integrations, check out the [Advanced Examples](
|
|
292
296
|
nGPT can also be used as a framework to build your own AI-powered command-line tools. You can leverage nGPT's pre-built CLI components to quickly develop sophisticated CLI applications.
|
293
297
|
|
294
298
|
```python
|
295
|
-
from ngpt import NGPTClient
|
299
|
+
from ngpt import NGPTClient
|
300
|
+
from ngpt.utils.config import load_config
|
296
301
|
from ngpt.cli.interactive import interactive_chat_session
|
297
302
|
from ngpt.cli.renderers import prettify_markdown
|
298
303
|
from ngpt.cli.args import setup_argument_parser
|
@@ -349,7 +354,7 @@ You can configure the client using the following options:
|
|
349
354
|
| `--top_p` | Set top_p (controls diversity, default: 1.0) |
|
350
355
|
| `--max_tokens` | Set maximum response length in tokens |
|
351
356
|
| `--preprompt` | Set custom system prompt to control AI behavior |
|
352
|
-
| `--log` |
|
357
|
+
| `--log` | Enable logging: use `--log` to create a temporary log file, or `--log PATH` for a specific location |
|
353
358
|
| `--prettify` | Render markdown responses and code with syntax highlighting |
|
354
359
|
| `--stream-prettify` | Enable real-time markdown rendering with syntax highlighting while streaming |
|
355
360
|
| `--renderer` | Select which markdown renderer to use with --prettify (auto, rich, or glow) |
|
@@ -0,0 +1,25 @@
|
|
1
|
+
ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
|
2
|
+
ngpt/cli.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
|
3
|
+
ngpt/client.py,sha256=Rv-JO8RAmw1v3gdLkwaPe_PEw6p83cejO0YNT_DDjeg,15134
|
4
|
+
ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
|
5
|
+
ngpt/cli/args.py,sha256=2e13wYQGfHFXpxZz5wXuvmoYpIkK6PEZQamHxfHAdyY,8868
|
6
|
+
ngpt/cli/config_manager.py,sha256=POjX3Asqap2N_Y6Qq0JC3OLZdzxRpFz3D0Lk19Now2U,3758
|
7
|
+
ngpt/cli/formatters.py,sha256=1ofNEWEZtFr0MJ3oWomoL_mFmZHlUdT3I5qGtbDQ4g0,9378
|
8
|
+
ngpt/cli/interactive.py,sha256=5xMMP1MuYW4jsbUEbjElE25fcJpgHVBbx2dIj8M39M8,10959
|
9
|
+
ngpt/cli/main.py,sha256=wSFVkg42NYIJQLMI9iOkW9nz2c6uukmwZghbaw6fYDk,27533
|
10
|
+
ngpt/cli/renderers.py,sha256=U3Vef3nY1NF2JKtLUtUjdFomyqIrijGWdxRPm46urr4,10546
|
11
|
+
ngpt/cli/ui.py,sha256=2JXkCRw5utaKpNZIy0u8F_Jh2zrWbm93dMz91wf9CkQ,5334
|
12
|
+
ngpt/cli/modes/__init__.py,sha256=11znFpqzHyRsEtaTrms5M3q2SrscT9VvUgr7C2B1o-E,179
|
13
|
+
ngpt/cli/modes/chat.py,sha256=OtD0iNoSpJ79gojaaCDOr0WPpRmAwzPPG8kwDjESrXo,3177
|
14
|
+
ngpt/cli/modes/code.py,sha256=bFE6x_CUncOM0gyAHOpcJs6nxj_ljFK0AYwJiT1Ndaw,4332
|
15
|
+
ngpt/cli/modes/shell.py,sha256=oqqEqWdqcH5q4pmis-hT9ZEFNk9-kaKHHdpRu217u5A,2721
|
16
|
+
ngpt/cli/modes/text.py,sha256=sUhgE5XubYxksnQDUvnCFrEbqz1G-CS_iWZZMGkULcI,3179
|
17
|
+
ngpt/utils/__init__.py,sha256=E46suk2-QgYBI0Qrs6WXOajOUOebF3ETAFY7ah8DTWs,942
|
18
|
+
ngpt/utils/cli_config.py,sha256=fUtahEUJlFt1cguIXrfHk0exn6O1qnm50uTKAgvtySc,9984
|
19
|
+
ngpt/utils/config.py,sha256=WYOk_b1eiYjo6hpV3pfXr2RjqhOnmKqwZwKid1T41I4,10363
|
20
|
+
ngpt/utils/log.py,sha256=Bxv2-GbWtVYa3u94Zs_OVEvYk_CbuT5hrDH06KHLXa8,6369
|
21
|
+
ngpt-2.11.1.dist-info/METADATA,sha256=8M9Vs0Me23qWxlCcuaynSHwC_xVuNOOfI4_s69OXN08,20627
|
22
|
+
ngpt-2.11.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
23
|
+
ngpt-2.11.1.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
|
24
|
+
ngpt-2.11.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
|
25
|
+
ngpt-2.11.1.dist-info/RECORD,,
|
ngpt-2.10.0.dist-info/RECORD
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
ngpt/__init__.py,sha256=awvycdj3tgcOr0BO81L4XU6DOtnToxFqkPHe1Pyu0Bw,652
|
2
|
-
ngpt/cli.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
|
3
|
-
ngpt/cli_config.py,sha256=Om8dXqdBqPCP5V4THQMkzZgHTQvN2rMAV6QjoVDQcZ4,10000
|
4
|
-
ngpt/client.py,sha256=Rv-JO8RAmw1v3gdLkwaPe_PEw6p83cejO0YNT_DDjeg,15134
|
5
|
-
ngpt/config.py,sha256=WYOk_b1eiYjo6hpV3pfXr2RjqhOnmKqwZwKid1T41I4,10363
|
6
|
-
ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
|
7
|
-
ngpt/cli/args.py,sha256=Aybt1oyhSSeWZ4oC5CWUjHQ5P6dfRTMnxi2VIScVGoo,8817
|
8
|
-
ngpt/cli/config_manager.py,sha256=L091h99ntMBth_FM39npGCOtDCV5kVkukNSkCIj6dpI,3752
|
9
|
-
ngpt/cli/formatters.py,sha256=1ofNEWEZtFr0MJ3oWomoL_mFmZHlUdT3I5qGtbDQ4g0,9378
|
10
|
-
ngpt/cli/interactive.py,sha256=J6DFkJVBdJ6NjZllsDgJnY1J5RTiKW341p4Zn4wHpGc,11718
|
11
|
-
ngpt/cli/main.py,sha256=xSHehXrTDH9HjZ6RBV2iBug-lXorpXDfMWP1oxEMBpU,24702
|
12
|
-
ngpt/cli/renderers.py,sha256=U3Vef3nY1NF2JKtLUtUjdFomyqIrijGWdxRPm46urr4,10546
|
13
|
-
ngpt/cli/ui.py,sha256=2JXkCRw5utaKpNZIy0u8F_Jh2zrWbm93dMz91wf9CkQ,5334
|
14
|
-
ngpt/cli/modes/__init__.py,sha256=11znFpqzHyRsEtaTrms5M3q2SrscT9VvUgr7C2B1o-E,179
|
15
|
-
ngpt/cli/modes/chat.py,sha256=ilTEGu3a8FJ_wGC_P5WnDLx0Okzh3QxJ8HoiYj84g0o,2721
|
16
|
-
ngpt/cli/modes/code.py,sha256=_Z3cKYdeifYZSXZ4dMnQWcnVpM2TvYQd-7S7Q3blfEw,3998
|
17
|
-
ngpt/cli/modes/shell.py,sha256=Fx83_JBc3P5vgCCPlXgXFSgzwTY0UMGfUwY4_CU10Ro,1654
|
18
|
-
ngpt/cli/modes/text.py,sha256=YpNpcujPweO_Biwg4aYwGw4_ShefzaNVtf8d_QrcR_Q,2719
|
19
|
-
ngpt/utils/__init__.py,sha256=NK8wlI9-YeaKPOaXBVfUj3mKOXohfD3GmNy5obOIXOM,20
|
20
|
-
ngpt-2.10.0.dist-info/METADATA,sha256=48epymmDevSCy9F2ItPVzelVF3NHGgXju87JDjEnrtw,20439
|
21
|
-
ngpt-2.10.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
22
|
-
ngpt-2.10.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
|
23
|
-
ngpt-2.10.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
|
24
|
-
ngpt-2.10.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|