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 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 (For interactive modes)')
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',
@@ -1,5 +1,5 @@
1
1
  import sys
2
- from ..config import get_config_path, load_configs, add_config_entry, remove_config_entry
2
+ from ..utils.config import get_config_path, load_configs, add_config_entry, remove_config_entry
3
3
  from .formatters import COLORS
4
4
 
5
5
  def show_config_help():
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, log_file=None, preprompt=None, prettify=False, renderer='auto', stream_prettify=False):
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
- # Initialize log file if provided
62
- log_handle = None
63
- if log_file:
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 log_handle and preprompt:
94
- log_handle.write(f"System: {system_prompt}\n\n")
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 log_handle:
191
- log_handle.write(f"User: {user_input}\n")
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 assistant response if logging is enabled
258
- if log_handle:
259
- log_handle.write(f"Assistant: {response}\n\n")
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['green']}Chat session ended by user. Goodbye!{COLORS['reset']}")
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 during chat session: {str(e)}{COLORS['reset']}")
269
- # Print traceback for debugging if it's a serious error
270
- traceback.print_exc()
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
- # Regular config addition/editing (existing code)
293
- # If --config-index was not explicitly specified, create a new entry by passing None
294
- # This will cause add_config_entry to create a new entry at the end of the list
295
- # Otherwise, edit the existing config at the specified index
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 '--config-index' in sys.argv:
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(**active_config)
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
- print(f"\nOutput:\n{result.stdout}")
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
- print(f"\nError:\n{e.stderr}")
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": ["interactive", "text"]},
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.10.0
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, load_config
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, load_config
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` | Set filepath to log conversation to (for interactive modes) |
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,,
@@ -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