ngpt 2.9.1__tar.gz → 2.10.0__tar.gz

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.
Files changed (51) hide show
  1. {ngpt-2.9.1 → ngpt-2.10.0}/PKG-INFO +10 -8
  2. {ngpt-2.9.1 → ngpt-2.10.0}/README.md +9 -7
  3. {ngpt-2.9.1 → ngpt-2.10.0}/docs/examples/cli_components.md +4 -6
  4. {ngpt-2.9.1 → ngpt-2.10.0}/docs/usage/cli_framework.md +35 -0
  5. ngpt-2.10.0/ngpt/cli/args.py +161 -0
  6. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/main.py +42 -122
  7. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli_config.py +27 -8
  8. {ngpt-2.9.1 → ngpt-2.10.0}/pyproject.toml +1 -1
  9. {ngpt-2.9.1 → ngpt-2.10.0}/uv.lock +1 -1
  10. {ngpt-2.9.1 → ngpt-2.10.0}/.github/workflows/python-publish.yml +0 -0
  11. {ngpt-2.9.1 → ngpt-2.10.0}/.gitignore +0 -0
  12. {ngpt-2.9.1 → ngpt-2.10.0}/.python-version +0 -0
  13. {ngpt-2.9.1 → ngpt-2.10.0}/COMMIT_GUIDELINES.md +0 -0
  14. {ngpt-2.9.1 → ngpt-2.10.0}/CONTRIBUTING.md +0 -0
  15. {ngpt-2.9.1 → ngpt-2.10.0}/LICENSE +0 -0
  16. {ngpt-2.9.1 → ngpt-2.10.0}/docs/CONTRIBUTING.md +0 -0
  17. {ngpt-2.9.1 → ngpt-2.10.0}/docs/LICENSE.md +0 -0
  18. {ngpt-2.9.1 → ngpt-2.10.0}/docs/README.md +0 -0
  19. {ngpt-2.9.1 → ngpt-2.10.0}/docs/_config.yml +0 -0
  20. {ngpt-2.9.1 → ngpt-2.10.0}/docs/api/README.md +0 -0
  21. {ngpt-2.9.1 → ngpt-2.10.0}/docs/api/cli.md +0 -0
  22. {ngpt-2.9.1 → ngpt-2.10.0}/docs/api/client.md +0 -0
  23. {ngpt-2.9.1 → ngpt-2.10.0}/docs/api/config.md +0 -0
  24. {ngpt-2.9.1 → ngpt-2.10.0}/docs/assets/css/style.scss +0 -0
  25. {ngpt-2.9.1 → ngpt-2.10.0}/docs/configuration.md +0 -0
  26. {ngpt-2.9.1 → ngpt-2.10.0}/docs/examples/README.md +0 -0
  27. {ngpt-2.9.1 → ngpt-2.10.0}/docs/examples/advanced.md +0 -0
  28. {ngpt-2.9.1 → ngpt-2.10.0}/docs/examples/basic.md +0 -0
  29. {ngpt-2.9.1 → ngpt-2.10.0}/docs/examples/integrations.md +0 -0
  30. {ngpt-2.9.1 → ngpt-2.10.0}/docs/installation.md +0 -0
  31. {ngpt-2.9.1 → ngpt-2.10.0}/docs/overview.md +0 -0
  32. {ngpt-2.9.1 → ngpt-2.10.0}/docs/usage/README.md +0 -0
  33. {ngpt-2.9.1 → ngpt-2.10.0}/docs/usage/cli_config.md +0 -0
  34. {ngpt-2.9.1 → ngpt-2.10.0}/docs/usage/cli_usage.md +0 -0
  35. {ngpt-2.9.1 → ngpt-2.10.0}/docs/usage/library_usage.md +0 -0
  36. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/__init__.py +0 -0
  37. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/__init__.py +0 -0
  38. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/config_manager.py +0 -0
  39. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/formatters.py +0 -0
  40. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/interactive.py +0 -0
  41. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/modes/__init__.py +0 -0
  42. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/modes/chat.py +0 -0
  43. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/modes/code.py +0 -0
  44. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/modes/shell.py +0 -0
  45. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/modes/text.py +0 -0
  46. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/renderers.py +0 -0
  47. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli/ui.py +0 -0
  48. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/cli.py +0 -0
  49. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/client.py +0 -0
  50. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/config.py +0 -0
  51. {ngpt-2.9.1 → ngpt-2.10.0}/ngpt/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 2.9.1
3
+ Version: 2.10.0
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
@@ -135,6 +135,7 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
135
135
  - 🎭 **System Prompts**: Customize model behavior with custom system prompts
136
136
  - 📃 **Conversation Logging**: Save your conversations to text files for later reference
137
137
  - 🧰 **CLI Components**: Reusable components for building custom AI-powered command-line tools
138
+ - 🔌 **Modular Architecture**: Well-structured codebase with clean separation of concerns
138
139
 
139
140
  See the [Feature Overview](https://nazdridoy.github.io/ngpt/overview.html) for more details.
140
141
 
@@ -292,16 +293,14 @@ nGPT can also be used as a framework to build your own AI-powered command-line t
292
293
 
293
294
  ```python
294
295
  from ngpt import NGPTClient, load_config
295
- from ngpt.cli.main import interactive_chat_session
296
+ from ngpt.cli.interactive import interactive_chat_session
296
297
  from ngpt.cli.renderers import prettify_markdown
297
- from ngpt.cli.formatters import ColoredHelpFormatter
298
- import argparse
298
+ from ngpt.cli.args import setup_argument_parser
299
+ import sys
299
300
 
300
301
  # Create a custom CLI tool with colorized help
301
- parser = argparse.ArgumentParser(
302
- description="Specialized Code Assistant",
303
- formatter_class=ColoredHelpFormatter
304
- )
302
+ parser = setup_argument_parser()
303
+ parser.description = "Specialized Code Assistant"
305
304
  parser.add_argument("prompt", nargs="?", help="Code description")
306
305
  parser.add_argument("--language", "-l", default="python", help="Programming language")
307
306
  parser.add_argument("--interactive", "-i", action="store_true", help="Start interactive mode")
@@ -318,6 +317,9 @@ elif args.prompt:
318
317
  # Generate and prettify code
319
318
  code = client.generate_code(args.prompt, language=args.language)
320
319
  print(prettify_markdown(f"```{args.language}\n{code}\n```"))
320
+ else:
321
+ parser.print_help()
322
+ sys.exit(1)
321
323
  ```
322
324
 
323
325
  This allows you to build specialized AI tools like:
@@ -100,6 +100,7 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
100
100
  - 🎭 **System Prompts**: Customize model behavior with custom system prompts
101
101
  - 📃 **Conversation Logging**: Save your conversations to text files for later reference
102
102
  - 🧰 **CLI Components**: Reusable components for building custom AI-powered command-line tools
103
+ - 🔌 **Modular Architecture**: Well-structured codebase with clean separation of concerns
103
104
 
104
105
  See the [Feature Overview](https://nazdridoy.github.io/ngpt/overview.html) for more details.
105
106
 
@@ -257,16 +258,14 @@ nGPT can also be used as a framework to build your own AI-powered command-line t
257
258
 
258
259
  ```python
259
260
  from ngpt import NGPTClient, load_config
260
- from ngpt.cli.main import interactive_chat_session
261
+ from ngpt.cli.interactive import interactive_chat_session
261
262
  from ngpt.cli.renderers import prettify_markdown
262
- from ngpt.cli.formatters import ColoredHelpFormatter
263
- import argparse
263
+ from ngpt.cli.args import setup_argument_parser
264
+ import sys
264
265
 
265
266
  # Create a custom CLI tool with colorized help
266
- parser = argparse.ArgumentParser(
267
- description="Specialized Code Assistant",
268
- formatter_class=ColoredHelpFormatter
269
- )
267
+ parser = setup_argument_parser()
268
+ parser.description = "Specialized Code Assistant"
270
269
  parser.add_argument("prompt", nargs="?", help="Code description")
271
270
  parser.add_argument("--language", "-l", default="python", help="Programming language")
272
271
  parser.add_argument("--interactive", "-i", action="store_true", help="Start interactive mode")
@@ -283,6 +282,9 @@ elif args.prompt:
283
282
  # Generate and prettify code
284
283
  code = client.generate_code(args.prompt, language=args.language)
285
284
  print(prettify_markdown(f"```{args.language}\n{code}\n```"))
285
+ else:
286
+ parser.print_help()
287
+ sys.exit(1)
286
288
  ```
287
289
 
288
290
  This allows you to build specialized AI tools like:
@@ -18,19 +18,17 @@ Here's a simple CLI tool that uses nGPT to generate and explain code:
18
18
 
19
19
  ```python
20
20
  #!/usr/bin/env python3
21
- import argparse
22
21
  import sys
23
22
  from ngpt import NGPTClient, load_config
24
- from ngpt.cli.formatters import ColoredHelpFormatter
23
+ from ngpt.cli.args import setup_argument_parser, validate_args
25
24
  from ngpt.cli.renderers import prettify_markdown, has_markdown_renderer
26
25
 
27
26
  def main():
28
27
  # Create parser with colorized help
29
- parser = argparse.ArgumentParser(
30
- description="Simple code generation tool",
31
- formatter_class=ColoredHelpFormatter
32
- )
28
+ parser = setup_argument_parser()
33
29
 
30
+ # Customize the parser for our specific needs
31
+ parser.description = "Simple code generation tool"
34
32
  parser.add_argument("prompt", help="Code description")
35
33
  parser.add_argument("--language", "-l", default="python", help="Programming language")
36
34
  parser.add_argument("--explain", "-e", action="store_true", help="Include explanation")
@@ -10,6 +10,7 @@ nGPT's CLI module has been modularized into several components that you can inco
10
10
  - **Markdown Rendering**: Beautiful formatting for markdown with syntax highlighting (`ngpt.cli.renderers`)
11
11
  - **Real-time Streaming**: Tools for handling streaming content with live updates (`ngpt.cli.ui`)
12
12
  - **CLI Configuration System**: Robust configuration management (`ngpt.cli.main`)
13
+ - **Argument Parsing**: Sophisticated argument parsing and validation (`ngpt.cli.args`)
13
14
  - **Terminal Utilities**: Helpers for colorized output and terminal formatting (`ngpt.cli.formatters`)
14
15
  - **Mode-specific functionality**: Specialized code, shell, chat and text mode handlers (`ngpt.cli.modes`)
15
16
 
@@ -28,6 +29,40 @@ This will install nGPT with all required dependencies, including:
28
29
 
29
30
  ## Available Components
30
31
 
32
+ ### Argument Parsing
33
+
34
+ The `args` module provides utilities for building colorful, sophisticated command-line interfaces:
35
+
36
+ ```python
37
+ from ngpt.cli.args import setup_argument_parser, validate_args, validate_markdown_renderer
38
+
39
+ # Create and configure the parser
40
+ parser = setup_argument_parser()
41
+ args = parser.parse_args()
42
+
43
+ # Validate arguments for correctness and compatibility
44
+ try:
45
+ args = validate_args(args)
46
+ except ValueError as e:
47
+ print(f"Error: {e}")
48
+ sys.exit(1)
49
+
50
+ # Check if markdown renderer is available
51
+ has_renderer, args = validate_markdown_renderer(args)
52
+ if not has_renderer:
53
+ print("Warning: No markdown renderer available. Using plain text.")
54
+ ```
55
+
56
+ The argument parsing module provides these key functions:
57
+
58
+ - `setup_argument_parser()`: Creates a fully configured argument parser with rich formatting
59
+ - `parse_args()`: Parses command-line arguments
60
+ - `validate_args(args)`: Validates parsed arguments for correctness and compatibility
61
+ - `validate_markdown_renderer(args)`: Checks if markdown rendering is available
62
+ - `handle_cli_config_args(args)`: Processes CLI configuration commands
63
+
64
+ This modular approach makes it easy to create sophisticated CLI tools with consistent behavior.
65
+
31
66
  ### Interactive Chat Session
32
67
 
33
68
  The `interactive_chat_session` function provides a complete interactive chat experience:
@@ -0,0 +1,161 @@
1
+ import argparse
2
+ import sys
3
+ from .. import __version__
4
+ from .formatters import COLORS, ColoredHelpFormatter
5
+ from .renderers import has_markdown_renderer, warn_if_no_markdown_renderer
6
+
7
+ def setup_argument_parser():
8
+ """Set up and return a fully configured argument parser for nGPT CLI."""
9
+ # Colorize description - use a shorter description to avoid line wrapping issues
10
+ description = f"{COLORS['cyan']}{COLORS['bold']}nGPT{COLORS['reset']} - Interact with AI language models via OpenAI-compatible APIs"
11
+
12
+ # Minimalist, clean epilog design
13
+ epilog = f"\n{COLORS['yellow']}nGPT {COLORS['bold']}v{__version__}{COLORS['reset']} • {COLORS['green']}Docs: {COLORS['bold']}https://nazdridoy.github.io/ngpt/usage/cli_usage.html{COLORS['reset']}"
14
+
15
+ parser = argparse.ArgumentParser(description=description, formatter_class=ColoredHelpFormatter, epilog=epilog)
16
+
17
+ # Add custom error method with color
18
+ original_error = parser.error
19
+ def error_with_color(message):
20
+ parser.print_usage(sys.stderr)
21
+ parser.exit(2, f"{COLORS['bold']}{COLORS['yellow']}error: {COLORS['reset']}{message}\n")
22
+ parser.error = error_with_color
23
+
24
+ # Custom version action with color
25
+ class ColoredVersionAction(argparse.Action):
26
+ def __call__(self, parser, namespace, values, option_string=None):
27
+ print(f"{COLORS['green']}{COLORS['bold']}nGPT{COLORS['reset']} version {COLORS['yellow']}{__version__}{COLORS['reset']}")
28
+ parser.exit()
29
+
30
+ # Version flag
31
+ parser.add_argument('-v', '--version', action=ColoredVersionAction, nargs=0, help='Show version information and exit')
32
+
33
+ # Config options
34
+ config_group = parser.add_argument_group('Configuration Options')
35
+ config_group.add_argument('--config', nargs='?', const=True, help='Path to a custom config file or, if no value provided, enter interactive configuration mode to create a new config')
36
+ config_group.add_argument('--config-index', type=int, default=0, help='Index of the configuration to use or edit (default: 0)')
37
+ config_group.add_argument('--provider', help='Provider name to identify the configuration to use')
38
+ config_group.add_argument('--remove', action='store_true', help='Remove the configuration at the specified index (requires --config and --config-index)')
39
+ config_group.add_argument('--show-config', action='store_true', help='Show the current configuration(s) and exit')
40
+ config_group.add_argument('--all', action='store_true', help='Show details for all configurations (requires --show-config)')
41
+ config_group.add_argument('--list-models', action='store_true', help='List all available models for the current configuration and exit')
42
+ config_group.add_argument('--list-renderers', action='store_true', help='Show available markdown renderers for use with --prettify')
43
+
44
+ # Global options
45
+ global_group = parser.add_argument_group('Global Options')
46
+ global_group.add_argument('--api-key', help='API key for the service')
47
+ global_group.add_argument('--base-url', help='Base URL for the API')
48
+ global_group.add_argument('--model', help='Model to use')
49
+ global_group.add_argument('--web-search', action='store_true',
50
+ help='Enable web search capability (Note: Your API endpoint must support this feature)')
51
+ global_group.add_argument('-n', '--no-stream', action='store_true',
52
+ help='Return the whole response without streaming')
53
+ global_group.add_argument('--temperature', type=float, default=0.7,
54
+ help='Set temperature (controls randomness, default: 0.7)')
55
+ global_group.add_argument('--top_p', type=float, default=1.0,
56
+ help='Set top_p (controls diversity, default: 1.0)')
57
+ global_group.add_argument('--max_tokens', type=int,
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)')
61
+ global_group.add_argument('--preprompt',
62
+ help='Set custom system prompt to control AI behavior')
63
+ global_group.add_argument('--prettify', action='store_const', const='auto',
64
+ help='Render markdown responses and code with syntax highlighting and formatting')
65
+ global_group.add_argument('--stream-prettify', action='store_true',
66
+ help='Enable streaming with markdown rendering (automatically uses Rich renderer)')
67
+ global_group.add_argument('--renderer', choices=['auto', 'rich', 'glow'], default='auto',
68
+ help='Select which markdown renderer to use with --prettify (auto, rich, or glow)')
69
+
70
+ # Mode flags (mutually exclusive)
71
+ mode_group = parser.add_argument_group('Modes (mutually exclusive)')
72
+ mode_exclusive_group = mode_group.add_mutually_exclusive_group()
73
+ mode_exclusive_group.add_argument('-i', '--interactive', action='store_true', help='Start an interactive chat session')
74
+ mode_exclusive_group.add_argument('-s', '--shell', action='store_true', help='Generate and execute shell commands')
75
+ mode_exclusive_group.add_argument('-c', '--code', action='store_true', help='Generate code')
76
+ mode_exclusive_group.add_argument('-t', '--text', action='store_true', help='Enter multi-line text input (submit with Ctrl+D)')
77
+ # Note: --show-config is handled separately and implicitly acts as a mode
78
+
79
+ # Language option for code mode
80
+ parser.add_argument('--language', default="python", help='Programming language to generate code in (for code mode)')
81
+
82
+ # Prompt argument
83
+ parser.add_argument('prompt', nargs='?', default=None, help='The prompt to send')
84
+
85
+ # Add CLI configuration command
86
+ config_group.add_argument('--cli-config', nargs='*', metavar='COMMAND',
87
+ help='Manage CLI configuration (set, get, unset, list)')
88
+
89
+ return parser
90
+
91
+ def parse_args():
92
+ """Parse command line arguments using the configured parser."""
93
+ parser = setup_argument_parser()
94
+ return parser.parse_args()
95
+
96
+ def validate_args(args):
97
+ """Validate parsed arguments for correctness and compatibility."""
98
+ # Validate --all usage
99
+ if args.all and not args.show_config:
100
+ raise ValueError("--all can only be used with --show-config")
101
+
102
+ # Check if --prettify is used with --stream-prettify (conflict)
103
+ if args.prettify and args.stream_prettify:
104
+ raise ValueError("--prettify and --stream-prettify cannot be used together. Choose one option.")
105
+
106
+ # Check if --stream-prettify is used but Rich is not available
107
+ if args.stream_prettify and not has_markdown_renderer('rich'):
108
+ raise ValueError("--stream-prettify requires Rich to be installed. Install with: pip install \"ngpt[full]\" or pip install rich")
109
+
110
+ return args
111
+
112
+ def validate_markdown_renderer(args):
113
+ """Validate that required markdown renderers are available.
114
+
115
+ Args:
116
+ args: The parsed command line arguments.
117
+
118
+ Returns:
119
+ tuple: (has_renderer, args)
120
+ - has_renderer: Boolean indicating if a renderer is available
121
+ - args: Potentially modified args with prettify disabled if no renderer is available
122
+ """
123
+ has_renderer = True
124
+ if args.prettify:
125
+ has_renderer = warn_if_no_markdown_renderer(args.renderer)
126
+ if not has_renderer:
127
+ # Set a flag to disable prettify since we already warned the user
128
+ print(f"{COLORS['yellow']}Continuing without markdown rendering.{COLORS['reset']}")
129
+ args.prettify = False
130
+
131
+ return has_renderer, args
132
+
133
+ def handle_cli_config_args(args):
134
+ """Process CLI configuration arguments and determine command parameters.
135
+
136
+ Args:
137
+ args: The parsed command line arguments.
138
+
139
+ Returns:
140
+ tuple: (should_handle, action, option, value)
141
+ - should_handle: True if --cli-config was specified and should be handled
142
+ - action: The action to perform (set, get, unset, list, help)
143
+ - option: The option name (or None)
144
+ - value: The option value (or None)
145
+ """
146
+ if args.cli_config is None:
147
+ return (False, None, None, None)
148
+
149
+ # Show help if no arguments or "help" argument
150
+ if len(args.cli_config) == 0 or (len(args.cli_config) > 0 and args.cli_config[0].lower() == "help"):
151
+ return (True, "help", None, None)
152
+
153
+ action = args.cli_config[0].lower()
154
+ option = args.cli_config[1] if len(args.cli_config) > 1 else None
155
+ value = args.cli_config[2] if len(args.cli_config) > 2 else None
156
+
157
+ if action in ("set", "get", "unset", "list", "help"):
158
+ return (True, action, option, value)
159
+ else:
160
+ # Unknown action, show help
161
+ return (True, "help", None, None)
@@ -22,11 +22,13 @@ from .modes.chat import chat_mode
22
22
  from .modes.code import code_mode
23
23
  from .modes.shell import shell_mode
24
24
  from .modes.text import text_mode
25
+ from .args import parse_args, validate_args, handle_cli_config_args, setup_argument_parser, validate_markdown_renderer
25
26
 
26
27
  def show_cli_config_help():
27
28
  """Display help information about CLI configuration."""
28
29
  print(f"\n{COLORS['green']}{COLORS['bold']}CLI Configuration Help:{COLORS['reset']}")
29
30
  print(f" {COLORS['cyan']}Command syntax:{COLORS['reset']}")
31
+ print(f" {COLORS['yellow']}ngpt --cli-config help{COLORS['reset']} - Show this help message")
30
32
  print(f" {COLORS['yellow']}ngpt --cli-config set OPTION VALUE{COLORS['reset']} - Set a default value for OPTION")
31
33
  print(f" {COLORS['yellow']}ngpt --cli-config get OPTION{COLORS['reset']} - Get the current value of OPTION")
32
34
  print(f" {COLORS['yellow']}ngpt --cli-config get{COLORS['reset']} - Show all CLI configuration settings")
@@ -83,16 +85,25 @@ def show_cli_config_help():
83
85
  print(f" {COLORS['yellow']}ngpt --cli-config set language java{COLORS['reset']} - Set default language to java for code generation")
84
86
  print(f" {COLORS['yellow']}ngpt --cli-config set temperature 0.9{COLORS['reset']} - Set default temperature to 0.9")
85
87
  print(f" {COLORS['yellow']}ngpt --cli-config set no-stream true{COLORS['reset']} - Disable streaming by default")
88
+ print(f" {COLORS['yellow']}ngpt --cli-config get temperature{COLORS['reset']} - Check the current temperature setting")
89
+ print(f" {COLORS['yellow']}ngpt --cli-config get{COLORS['reset']} - Show all current CLI settings")
86
90
  print(f" {COLORS['yellow']}ngpt --cli-config unset language{COLORS['reset']} - Remove language setting")
87
91
 
88
92
  print(f"\n {COLORS['cyan']}Notes:{COLORS['reset']}")
89
- print(f" - CLI configuration is stored in {COLORS['yellow']}~/.config/ngpt/ngpt-cli.conf{COLORS['reset']} (or equivalent for your OS)")
93
+ print(f" - CLI configuration is stored in:")
94
+ print(f" • Linux: {COLORS['yellow']}~/.config/ngpt/ngpt-cli.conf{COLORS['reset']}")
95
+ print(f" • macOS: {COLORS['yellow']}~/Library/Application Support/ngpt/ngpt-cli.conf{COLORS['reset']}")
96
+ print(f" • Windows: {COLORS['yellow']}%APPDATA%\\ngpt\\ngpt-cli.conf{COLORS['reset']}")
90
97
  print(f" - Settings are applied based on context (e.g., language only applies to code generation mode)")
91
98
  print(f" - Command-line arguments always override CLI configuration")
92
99
  print(f" - Some options are mutually exclusive and will not be applied together")
93
100
 
94
101
  def handle_cli_config(action, option=None, value=None):
95
102
  """Handle CLI configuration commands."""
103
+ if action == "help":
104
+ show_cli_config_help()
105
+ return
106
+
96
107
  if action == "list":
97
108
  # List all available options
98
109
  print(f"{COLORS['green']}{COLORS['bold']}Available CLI configuration options:{COLORS['reset']}")
@@ -161,109 +172,20 @@ def handle_cli_config(action, option=None, value=None):
161
172
  show_cli_config_help()
162
173
 
163
174
  def main():
164
- # Colorize description - use a shorter description to avoid line wrapping issues
165
- description = f"{COLORS['cyan']}{COLORS['bold']}nGPT{COLORS['reset']} - Interact with AI language models via OpenAI-compatible APIs"
166
-
167
- # Minimalist, clean epilog design
168
- epilog = f"\n{COLORS['yellow']}nGPT {COLORS['bold']}v{__version__}{COLORS['reset']} • {COLORS['green']}Docs: {COLORS['bold']}https://nazdridoy.github.io/ngpt/usage/cli_usage.html{COLORS['reset']}"
169
-
170
- parser = argparse.ArgumentParser(description=description, formatter_class=ColoredHelpFormatter, epilog=epilog)
171
-
172
- # Add custom error method with color
173
- original_error = parser.error
174
- def error_with_color(message):
175
- parser.print_usage(sys.stderr)
176
- parser.exit(2, f"{COLORS['bold']}{COLORS['yellow']}error: {COLORS['reset']}{message}\n")
177
- parser.error = error_with_color
178
-
179
- # Custom version action with color
180
- class ColoredVersionAction(argparse.Action):
181
- def __call__(self, parser, namespace, values, option_string=None):
182
- print(f"{COLORS['green']}{COLORS['bold']}nGPT{COLORS['reset']} version {COLORS['yellow']}{__version__}{COLORS['reset']}")
183
- parser.exit()
184
-
185
- # Version flag
186
- parser.add_argument('-v', '--version', action=ColoredVersionAction, nargs=0, help='Show version information and exit')
175
+ # Parse command line arguments using args.py
176
+ args = parse_args()
187
177
 
188
- # Config options
189
- config_group = parser.add_argument_group('Configuration Options')
190
- config_group.add_argument('--config', nargs='?', const=True, help='Path to a custom config file or, if no value provided, enter interactive configuration mode to create a new config')
191
- config_group.add_argument('--config-index', type=int, default=0, help='Index of the configuration to use or edit (default: 0)')
192
- config_group.add_argument('--provider', help='Provider name to identify the configuration to use')
193
- config_group.add_argument('--remove', action='store_true', help='Remove the configuration at the specified index (requires --config and --config-index)')
194
- config_group.add_argument('--show-config', action='store_true', help='Show the current configuration(s) and exit')
195
- config_group.add_argument('--all', action='store_true', help='Show details for all configurations (requires --show-config)')
196
- config_group.add_argument('--list-models', action='store_true', help='List all available models for the current configuration and exit')
197
- config_group.add_argument('--list-renderers', action='store_true', help='Show available markdown renderers for use with --prettify')
198
-
199
- # Global options
200
- global_group = parser.add_argument_group('Global Options')
201
- global_group.add_argument('--api-key', help='API key for the service')
202
- global_group.add_argument('--base-url', help='Base URL for the API')
203
- global_group.add_argument('--model', help='Model to use')
204
- global_group.add_argument('--web-search', action='store_true',
205
- help='Enable web search capability (Note: Your API endpoint must support this feature)')
206
- global_group.add_argument('-n', '--no-stream', action='store_true',
207
- help='Return the whole response without streaming')
208
- global_group.add_argument('--temperature', type=float, default=0.7,
209
- help='Set temperature (controls randomness, default: 0.7)')
210
- global_group.add_argument('--top_p', type=float, default=1.0,
211
- help='Set top_p (controls diversity, default: 1.0)')
212
- global_group.add_argument('--max_tokens', type=int,
213
- help='Set max response length in tokens')
214
- global_group.add_argument('--log', metavar='FILE',
215
- help='Set filepath to log conversation to (For interactive modes)')
216
- global_group.add_argument('--preprompt',
217
- help='Set custom system prompt to control AI behavior')
218
- global_group.add_argument('--prettify', action='store_const', const='auto',
219
- help='Render markdown responses and code with syntax highlighting and formatting')
220
- global_group.add_argument('--stream-prettify', action='store_true',
221
- help='Enable streaming with markdown rendering (automatically uses Rich renderer)')
222
- global_group.add_argument('--renderer', choices=['auto', 'rich', 'glow'], default='auto',
223
- help='Select which markdown renderer to use with --prettify (auto, rich, or glow)')
224
-
225
- # Mode flags (mutually exclusive)
226
- mode_group = parser.add_argument_group('Modes (mutually exclusive)')
227
- mode_exclusive_group = mode_group.add_mutually_exclusive_group()
228
- mode_exclusive_group.add_argument('-i', '--interactive', action='store_true', help='Start an interactive chat session')
229
- mode_exclusive_group.add_argument('-s', '--shell', action='store_true', help='Generate and execute shell commands')
230
- mode_exclusive_group.add_argument('-c', '--code', action='store_true', help='Generate code')
231
- mode_exclusive_group.add_argument('-t', '--text', action='store_true', help='Enter multi-line text input (submit with Ctrl+D)')
232
- # Note: --show-config is handled separately and implicitly acts as a mode
233
-
234
- # Language option for code mode
235
- parser.add_argument('--language', default="python", help='Programming language to generate code in (for code mode)')
236
-
237
- # Prompt argument
238
- parser.add_argument('prompt', nargs='?', default=None, help='The prompt to send')
239
-
240
- # Add CLI configuration command
241
- config_group.add_argument('--cli-config', nargs='*', metavar='COMMAND',
242
- help='Manage CLI configuration (set, get, unset, list)')
243
-
244
- args = parser.parse_args()
178
+ try:
179
+ args = validate_args(args)
180
+ except ValueError as e:
181
+ print(f"{COLORS['bold']}{COLORS['yellow']}error: {COLORS['reset']}{str(e)}\n")
182
+ sys.exit(2)
245
183
 
246
184
  # Handle CLI configuration command
247
- if args.cli_config is not None:
248
- # Show help if no arguments or "help" argument
249
- if len(args.cli_config) == 0 or (len(args.cli_config) > 0 and args.cli_config[0].lower() == "help"):
250
- show_cli_config_help()
251
- return
252
-
253
- action = args.cli_config[0].lower()
254
- option = args.cli_config[1] if len(args.cli_config) > 1 else None
255
- value = args.cli_config[2] if len(args.cli_config) > 2 else None
256
-
257
- if action in ("set", "get", "unset", "list"):
258
- handle_cli_config(action, option, value)
259
- return
260
- else:
261
- show_cli_config_help()
262
- return
263
-
264
- # Validate --all usage
265
- if args.all and not args.show_config:
266
- parser.error("--all can only be used with --show-config")
185
+ should_handle_cli_config, action, option, value = handle_cli_config_args(args)
186
+ if should_handle_cli_config:
187
+ handle_cli_config(action, option, value)
188
+ return
267
189
 
268
190
  # Handle --renderers flag to show available markdown renderers
269
191
  if args.list_renderers:
@@ -283,15 +205,23 @@ def main():
283
205
  effective_config_index = args.config_index
284
206
 
285
207
  # Only apply CLI config for provider/config-index if not explicitly set on command line
286
- if not effective_provider and 'provider' in cli_config and '--provider' not in sys.argv:
208
+ # If --config-index is explicitly provided, we should ignore provider from CLI config
209
+ config_index_from_cli = '--config-index' in sys.argv
210
+ provider_from_cli = '--provider' in sys.argv
211
+
212
+ if not provider_from_cli and 'provider' in cli_config and not config_index_from_cli:
287
213
  effective_provider = cli_config['provider']
288
214
 
289
- if effective_config_index == 0 and 'config-index' in cli_config and '--config-index' not in sys.argv:
215
+ if not config_index_from_cli and 'config-index' in cli_config and not provider_from_cli:
290
216
  effective_config_index = cli_config['config-index']
291
217
 
292
218
  # Check for mutual exclusivity between provider and config-index
293
219
  if effective_config_index != 0 and effective_provider:
294
- parser.error("--config-index and --provider cannot be used together")
220
+ from_cli_config = not provider_from_cli and 'provider' in cli_config
221
+ provider_source = "CLI config file (ngpt-cli.conf)" if from_cli_config else "command-line arguments"
222
+ error_msg = f"--config-index and --provider cannot be used together. Provider from {provider_source}."
223
+ print(f"{COLORS['bold']}{COLORS['yellow']}error: {COLORS['reset']}{error_msg}\n")
224
+ sys.exit(2)
295
225
 
296
226
  # Handle interactive configuration mode
297
227
  if args.config is True: # --config was used without a value
@@ -301,7 +231,8 @@ def main():
301
231
  if args.remove:
302
232
  # Validate that config_index is explicitly provided
303
233
  if '--config-index' not in sys.argv and not effective_provider:
304
- parser.error("--remove requires explicitly specifying --config-index or --provider")
234
+ print(f"{COLORS['bold']}{COLORS['yellow']}error: {COLORS['reset']}--remove requires explicitly specifying --config-index or --provider\n")
235
+ sys.exit(2)
305
236
 
306
237
  # Show config details before asking for confirmation
307
238
  configs = load_configs(str(config_path))
@@ -479,6 +410,8 @@ def main():
479
410
 
480
411
  # For interactive mode, we'll allow continuing without a specific prompt
481
412
  if not args.prompt and not (args.shell or args.code or args.text or args.interactive or args.show_config or args.list_models):
413
+ # Simply use the parser's help
414
+ parser = setup_argument_parser()
482
415
  parser.print_help()
483
416
  return
484
417
 
@@ -488,23 +421,10 @@ def main():
488
421
 
489
422
  # Check if --prettify is used but no markdown renderer is available
490
423
  # This will warn the user immediately if they request prettify but don't have the tools
491
- has_renderer = True
492
- if args.prettify:
493
- has_renderer = warn_if_no_markdown_renderer(args.renderer)
494
- if not has_renderer:
495
- # Set a flag to disable prettify since we already warned the user
496
- print(f"{COLORS['yellow']}Continuing without markdown rendering.{COLORS['reset']}")
497
- show_available_renderers()
498
- args.prettify = False
499
-
500
- # Check if --prettify is used with --stream-prettify (conflict)
501
- if args.prettify and args.stream_prettify:
502
- parser.error("--prettify and --stream-prettify cannot be used together. Choose one option.")
503
-
504
- # Check if --stream-prettify is used but Rich is not available
505
- if args.stream_prettify and not has_markdown_renderer('rich'):
506
- parser.error("--stream-prettify requires Rich to be installed. Install with: pip install \"ngpt[full]\" or pip install rich")
507
-
424
+ has_renderer, args = validate_markdown_renderer(args)
425
+ if not has_renderer:
426
+ show_available_renderers()
427
+
508
428
  # Initialize client using the potentially overridden active_config
509
429
  client = NGPTClient(**active_config)
510
430
 
@@ -7,7 +7,7 @@ from typing import Dict, Optional, Any, List, Union, Tuple
7
7
  # CLI config options with their types and default values
8
8
  CLI_CONFIG_OPTIONS = {
9
9
  "language": {"type": "str", "default": "python", "context": ["code"]},
10
- "provider": {"type": "str", "default": None, "context": ["all"]},
10
+ "provider": {"type": "str", "default": None, "context": ["all"], "exclusive": ["config-index"]},
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"]},
@@ -17,7 +17,7 @@ CLI_CONFIG_OPTIONS = {
17
17
  "prettify": {"type": "bool", "default": False, "context": ["all"], "exclusive": ["no-stream", "stream-prettify"]},
18
18
  "stream-prettify": {"type": "bool", "default": False, "context": ["all"], "exclusive": ["no-stream", "prettify"]},
19
19
  "renderer": {"type": "str", "default": "auto", "context": ["all"]},
20
- "config-index": {"type": "int", "default": 0, "context": ["all"]},
20
+ "config-index": {"type": "int", "default": 0, "context": ["all"], "exclusive": ["provider"]},
21
21
  "web-search": {"type": "bool", "default": False, "context": ["all"]},
22
22
  }
23
23
 
@@ -113,13 +113,19 @@ def set_cli_config_option(option: str, value: Any) -> Tuple[bool, str]:
113
113
  else:
114
114
  return False, f"Error: Unsupported option type '{option_type}'"
115
115
 
116
- # Handle mutual exclusivity for boolean options
117
- if option_type == "bool" and "exclusive" in CLI_CONFIG_OPTIONS[option]:
118
- if parsed_value: # If setting this option to True
119
- # Set all other exclusive options to False
116
+ # Handle mutual exclusivity for options
117
+ if "exclusive" in CLI_CONFIG_OPTIONS[option]:
118
+ if option_type == "bool":
119
+ # For boolean options: only apply exclusivity when setting to True
120
+ if parsed_value:
121
+ for excl_option in CLI_CONFIG_OPTIONS[option]["exclusive"]:
122
+ config[excl_option] = False
123
+ # If setting to False, don't alter exclusive options
124
+ else:
125
+ # For non-boolean options: If setting this option to any value, remove exclusive options
120
126
  for excl_option in CLI_CONFIG_OPTIONS[option]["exclusive"]:
121
- config[excl_option] = False
122
- # No special handling needed if setting to False, just update the value
127
+ if excl_option in config:
128
+ del config[excl_option]
123
129
 
124
130
  # Set the value in the config
125
131
  config[option] = parsed_value
@@ -203,6 +209,9 @@ def apply_cli_config(args: Any, mode: str) -> Any:
203
209
  # Get command-line arguments provided by the user
204
210
  explicit_args = set(arg for arg in sys.argv[1:] if arg.startswith('--'))
205
211
 
212
+ # Keep track of applied exclusive options
213
+ applied_exclusives = set()
214
+
206
215
  # For each option in CLI config, check if it should be applied
207
216
  for option, value in cli_config.items():
208
217
  # Skip if not a valid option
@@ -221,6 +230,13 @@ def apply_cli_config(args: Any, mode: str) -> Any:
221
230
  # Check common variants like --option
222
231
  cli_option = f"--{option}"
223
232
  if cli_option in explicit_args:
233
+ # Add to applied_exclusives if this option has exclusivity constraints
234
+ if "exclusive" in CLI_CONFIG_OPTIONS[option]:
235
+ applied_exclusives.update(CLI_CONFIG_OPTIONS[option]["exclusive"])
236
+ continue
237
+
238
+ # Skip if an exclusive option has already been applied
239
+ if option in applied_exclusives:
224
240
  continue
225
241
 
226
242
  # Check exclusivity constraints against *explicitly set* args
@@ -234,6 +250,9 @@ def apply_cli_config(args: Any, mode: str) -> Any:
234
250
  break # Skip applying this CLI config value
235
251
  if skip:
236
252
  continue
253
+
254
+ # If we're applying this option, add its exclusives to the tracking set
255
+ applied_exclusives.update(CLI_CONFIG_OPTIONS[option]["exclusive"])
237
256
 
238
257
  # Apply the value from CLI config
239
258
  # Ensure the attribute exists on args before setting
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ngpt"
3
- version = "2.9.1"
3
+ version = "2.10.0"
4
4
  description = "A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints."
5
5
  authors = [
6
6
  {name = "nazDridoy", email = "nazdridoy399@gmail.com"},
@@ -134,7 +134,7 @@ wheels = [
134
134
 
135
135
  [[package]]
136
136
  name = "ngpt"
137
- version = "2.9.1"
137
+ version = "2.10.0"
138
138
  source = { editable = "." }
139
139
  dependencies = [
140
140
  { name = "prompt-toolkit" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes