ngpt 1.0.0__py3-none-any.whl → 1.1.0__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
@@ -1,9 +1,5 @@
1
- try:
2
- from importlib.metadata import version as get_version
3
- __version__ = get_version("ngpt")
4
- except ImportError:
5
- # For Python < 3.8 or package not installed
6
- __version__ = "1.0.0" # fallback version
1
+ from importlib.metadata import version as get_version
2
+ __version__ = get_version("ngpt")
7
3
 
8
4
  from .client import NGPTClient
9
5
  from .config import load_config, get_config_path, get_config_dir
ngpt/cli.py CHANGED
@@ -2,7 +2,7 @@ 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
5
+ from .config import load_config, get_config_path, load_configs, add_config_entry
6
6
  from . import __version__
7
7
 
8
8
  def show_config_help():
@@ -17,12 +17,20 @@ def show_config_help():
17
17
  print(f" - ~/.config/ngpt/ngpt.conf")
18
18
 
19
19
  print(" 2. Format your config file as JSON:")
20
- print(""" {
21
- "api_key": "your-api-key-here",
22
- "base_url": "https://api.openai.com/v1/",
23
- "provider": "OpenAI",
24
- "model": "gpt-3.5-turbo"
25
- }""")
20
+ print(""" [
21
+ {
22
+ "api_key": "your-api-key-here",
23
+ "base_url": "https://api.openai.com/v1/",
24
+ "provider": "OpenAI",
25
+ "model": "gpt-3.5-turbo"
26
+ },
27
+ {
28
+ "api_key": "your-second-api-key",
29
+ "base_url": "http://localhost:1337/v1/",
30
+ "provider": "Another Provider",
31
+ "model": "different-model"
32
+ }
33
+ ]""")
26
34
 
27
35
  print(" 3. Or set environment variables:")
28
36
  print(" - OPENAI_API_KEY")
@@ -32,6 +40,12 @@ def show_config_help():
32
40
 
33
41
  print(" 4. Or provide command line arguments:")
34
42
  print(" ngpt --api-key your-key --base-url https://api.example.com \"Your prompt\"")
43
+
44
+ print(" 5. Use --config-index to specify which configuration to use:")
45
+ print(" ngpt --config-index 1 \"Your prompt\"")
46
+
47
+ print(" 6. Use --config without arguments to add or edit a configuration:")
48
+ print(" ngpt --config --config-index 1")
35
49
 
36
50
  def check_config(config):
37
51
  """Check config for common issues and provide guidance."""
@@ -53,22 +67,27 @@ def main():
53
67
  # Version flag
54
68
  parser.add_argument('-v', '--version', action='version', version=f'nGPT {__version__}', help='Show version information and exit')
55
69
 
56
- # Config option
57
- parser.add_argument('--config', help='Path to a custom configuration file')
70
+ # Config options
71
+ config_group = parser.add_argument_group('Configuration Options')
72
+ config_group.add_argument('--config', nargs='?', const=True, help='Path to a custom config file or, if no value provided, enter interactive configuration mode')
73
+ config_group.add_argument('--config-index', type=int, default=0, help='Index of the configuration to use (default: 0)')
74
+ config_group.add_argument('--show-config', action='store_true', help='Show the current configuration(s) and exit')
75
+ config_group.add_argument('--all', action='store_true', help='Show details for all configurations (requires --show-config)')
58
76
 
59
77
  # Global options
60
- parser.add_argument('--api-key', help='API key for the service')
61
- parser.add_argument('--base-url', help='Base URL for the API')
62
- parser.add_argument('--provider', help='Provider name')
63
- parser.add_argument('--model', help='Model to use')
64
- parser.add_argument('--web-search', action='store_true',
78
+ global_group = parser.add_argument_group('Global Options')
79
+ global_group.add_argument('--api-key', help='API key for the service')
80
+ global_group.add_argument('--base-url', help='Base URL for the API')
81
+ global_group.add_argument('--model', help='Model to use')
82
+ global_group.add_argument('--web-search', action='store_true',
65
83
  help='Enable web search capability (Note: Your API endpoint must support this feature)')
66
84
 
67
85
  # Mode flags (mutually exclusive)
68
- mode_group = parser.add_mutually_exclusive_group()
69
- mode_group.add_argument('-s', '--shell', action='store_true', help='Generate and execute shell commands')
70
- mode_group.add_argument('-c', '--code', action='store_true', help='Generate code')
71
- mode_group.add_argument('--show-config', action='store_true', help='Show the current configuration and exit')
86
+ mode_group = parser.add_argument_group('Modes (mutually exclusive)')
87
+ mode_exclusive_group = mode_group.add_mutually_exclusive_group()
88
+ mode_exclusive_group.add_argument('-s', '--shell', action='store_true', help='Generate and execute shell commands')
89
+ mode_exclusive_group.add_argument('-c', '--code', action='store_true', help='Generate code')
90
+ # Note: --show-config is handled separately and implicitly acts as a mode
72
91
 
73
92
  # Language option for code mode
74
93
  parser.add_argument('--language', default="python", help='Programming language to generate code in (for code mode)')
@@ -78,27 +97,62 @@ def main():
78
97
 
79
98
  args = parser.parse_args()
80
99
 
81
- # Load configuration
82
- config = load_config(args.config)
100
+ # Validate --all usage
101
+ if args.all and not args.show_config:
102
+ parser.error("--all can only be used with --show-config")
103
+
104
+ # Handle interactive configuration mode
105
+ if args.config is True: # --config was used without a value
106
+ config_path = get_config_path()
107
+ add_config_entry(config_path, args.config_index)
108
+ return
109
+
110
+ # Load configuration using the specified index (needed for active config display)
111
+ active_config = load_config(args.config, args.config_index)
83
112
 
84
- # Command-line arguments override config settings
113
+ # Command-line arguments override config settings for active config display
114
+ # This part is kept to ensure the active config display reflects potential overrides,
115
+ # even though the overrides don't affect the stored configurations displayed with --all.
85
116
  if args.api_key:
86
- config["api_key"] = args.api_key
117
+ active_config["api_key"] = args.api_key
87
118
  if args.base_url:
88
- config["base_url"] = args.base_url
89
- if args.provider:
90
- config["provider"] = args.provider
119
+ active_config["base_url"] = args.base_url
91
120
  if args.model:
92
- config["model"] = args.model
121
+ active_config["model"] = args.model
93
122
 
94
123
  # Show config if requested
95
124
  if args.show_config:
96
125
  config_path = get_config_path(args.config)
126
+ configs = load_configs(args.config)
127
+
97
128
  print(f"Configuration file: {config_path}")
98
- print(f"API Key: {'[Set]' if config['api_key'] else '[Not Set]'}")
99
- print(f"Base URL: {config['base_url']}")
100
- print(f"Provider: {config['provider']}")
101
- print(f"Model: {config['model']}")
129
+ print(f"Total configurations: {len(configs)}")
130
+ print(f"Active configuration index: {args.config_index}")
131
+
132
+ if args.all:
133
+ # Show details for all configurations
134
+ print("\nAll configuration details:")
135
+ for i, cfg in enumerate(configs):
136
+ active_str = '(Active)' if i == args.config_index else ''
137
+ print(f"\n--- Configuration Index {i} {active_str} ---")
138
+ print(f" API Key: {'[Set]' if cfg.get('api_key') else '[Not Set]'}")
139
+ print(f" Base URL: {cfg.get('base_url', 'N/A')}")
140
+ print(f" Provider: {cfg.get('provider', 'N/A')}")
141
+ print(f" Model: {cfg.get('model', 'N/A')}")
142
+ else:
143
+ # Show active config details and summary list
144
+ print("\nActive configuration details:")
145
+ print(f" API Key: {'[Set]' if active_config.get('api_key') else '[Not Set]'}")
146
+ print(f" Base URL: {active_config.get('base_url', 'N/A')}")
147
+ print(f" Provider: {active_config.get('provider', 'N/A')}")
148
+ print(f" Model: {active_config.get('model', 'N/A')}")
149
+
150
+ if len(configs) > 1:
151
+ print("\nAvailable configurations:")
152
+ for i, cfg in enumerate(configs):
153
+ active_marker = "*" if i == args.config_index else " "
154
+ print(f"[{i}]{active_marker} {cfg.get('provider', 'N/A')} - {cfg.get('model', 'N/A')} ({'[API Key Set]' if cfg.get('api_key') else '[API Key Not Set]'})")
155
+
102
156
  return
103
157
 
104
158
  # Check if prompt is required but not provided
@@ -106,12 +160,12 @@ def main():
106
160
  parser.print_help()
107
161
  return
108
162
 
109
- # Check configuration
110
- if not check_config(config):
163
+ # Check configuration (using the potentially overridden active_config)
164
+ if not check_config(active_config):
111
165
  return
112
166
 
113
- # Initialize client
114
- client = NGPTClient(**config)
167
+ # Initialize client using the potentially overridden active_config
168
+ client = NGPTClient(**active_config)
115
169
 
116
170
  try:
117
171
  # Handle modes
@@ -162,6 +216,6 @@ def main():
162
216
  print("\nOperation cancelled by user.")
163
217
  except Exception as e:
164
218
  print(f"Error: {e}")
165
-
219
+
166
220
  if __name__ == "__main__":
167
221
  main()
ngpt/client.py CHANGED
@@ -10,13 +10,12 @@ class NGPTClient:
10
10
  self,
11
11
  api_key: str = "",
12
12
  base_url: str = "https://api.openai.com/v1/",
13
- provider: str = "OpenAI",
13
+ provider: str = "OpenAI", # Provider is now just a label, kept for potential future use/logging
14
14
  model: str = "gpt-3.5-turbo"
15
15
  ):
16
16
  self.api_key = api_key
17
17
  # Ensure base_url ends with /
18
18
  self.base_url = base_url if base_url.endswith('/') else base_url + '/'
19
- self.provider = provider
20
19
  self.model = model
21
20
 
22
21
  # Default headers
@@ -63,10 +62,12 @@ class NGPTClient:
63
62
  "messages": messages,
64
63
  "stream": stream,
65
64
  "temperature": temperature,
66
- "provider": self.provider,
67
- "web_search": web_search
68
65
  }
69
66
 
67
+ # Conditionally add web_search
68
+ if web_search:
69
+ payload["web_search"] = True
70
+
70
71
  # Add max_tokens if provided
71
72
  if max_tokens is not None:
72
73
  payload["max_tokens"] = max_tokens
ngpt/config.py CHANGED
@@ -2,16 +2,19 @@ import os
2
2
  import sys
3
3
  import json
4
4
  from pathlib import Path
5
- from typing import Dict, Optional, Any
5
+ from typing import Dict, Optional, Any, List
6
6
 
7
7
  # Default configuration
8
- DEFAULT_CONFIG = {
8
+ DEFAULT_CONFIG_ENTRY = {
9
9
  "api_key": "",
10
10
  "base_url": "https://api.openai.com/v1/",
11
11
  "provider": "OpenAI",
12
12
  "model": "gpt-3.5-turbo"
13
13
  }
14
14
 
15
+ # Default configurations list
16
+ DEFAULT_CONFIG = [DEFAULT_CONFIG_ENTRY]
17
+
15
18
  def get_config_dir() -> Path:
16
19
  """Get the appropriate config directory based on OS."""
17
20
  if sys.platform == "win32":
@@ -39,33 +42,85 @@ def get_config_path(custom_path: Optional[str] = None) -> Path:
39
42
  return get_config_dir() / "ngpt.conf"
40
43
 
41
44
  def create_default_config(config_path: Path) -> None:
42
- """Create a default configuration file."""
45
+ """Create a default configuration file with a single config entry."""
43
46
  with open(config_path, "w") as f:
44
47
  json.dump(DEFAULT_CONFIG, f, indent=2)
45
48
  print(f"Created default configuration file at {config_path}")
46
49
 
47
- def load_config(custom_path: Optional[str] = None) -> Dict[str, Any]:
50
+ def add_config_entry(config_path: Path, config_index: Optional[int] = None) -> None:
51
+ """Add a new configuration entry or update existing one at the specified index."""
52
+ configs = load_configs(custom_path=str(config_path))
53
+
54
+ # Create a new entry based on the default
55
+ new_entry = DEFAULT_CONFIG_ENTRY.copy()
56
+
57
+ # Interactive configuration
58
+ print("Enter configuration details (press Enter to use default values):")
59
+ new_entry["api_key"] = input(f"API Key: ") or new_entry["api_key"]
60
+ new_entry["base_url"] = input(f"Base URL [{new_entry['base_url']}]: ") or new_entry["base_url"]
61
+ new_entry["provider"] = input(f"Provider [{new_entry['provider']}]: ") or new_entry["provider"]
62
+ new_entry["model"] = input(f"Model [{new_entry['model']}]: ") or new_entry["model"]
63
+
64
+ # Add or update the entry
65
+ if config_index is not None and config_index < len(configs):
66
+ configs[config_index] = new_entry
67
+ print(f"Updated configuration at index {config_index}")
68
+ else:
69
+ configs.append(new_entry)
70
+ print(f"Added new configuration at index {len(configs)-1}")
71
+
72
+ # Save the updated configs
73
+ with open(config_path, "w") as f:
74
+ json.dump(configs, f, indent=2)
75
+
76
+ def load_configs(custom_path: Optional[str] = None) -> List[Dict[str, Any]]:
48
77
  """
49
- Load configuration from file and environment variables.
50
- Environment variables take precedence over the config file.
78
+ Load all configurations from the config file.
79
+ Returns a list of configuration dictionaries.
51
80
  """
52
81
  config_path = get_config_path(custom_path)
53
82
 
54
- # Start with default config
55
- config = DEFAULT_CONFIG.copy()
83
+ # Start with default configs
84
+ configs = DEFAULT_CONFIG.copy()
56
85
 
57
86
  # Load from config file if it exists
58
87
  if config_path.exists():
59
88
  try:
60
89
  with open(config_path, "r") as f:
61
- file_config = json.load(f)
62
- config.update(file_config)
90
+ file_configs = json.load(f)
91
+ # Handle both old format (single dict) and new format (list of dicts)
92
+ if isinstance(file_configs, dict):
93
+ # Convert old format to new format
94
+ configs = [file_configs]
95
+ else:
96
+ configs = file_configs
63
97
  except (json.JSONDecodeError, IOError) as e:
64
98
  print(f"Warning: Could not read config file: {e}", file=sys.stderr)
65
99
  else:
66
100
  # Create default config file if it doesn't exist
67
101
  create_default_config(config_path)
68
102
 
103
+ return configs
104
+
105
+ def load_config(custom_path: Optional[str] = None, config_index: int = 0) -> Dict[str, Any]:
106
+ """
107
+ Load a specific configuration by index and apply environment variables.
108
+ Environment variables take precedence over the config file.
109
+ """
110
+ configs = load_configs(custom_path)
111
+
112
+ # If config_index is out of range, use the first config
113
+ if config_index < 0 or config_index >= len(configs):
114
+ if len(configs) > 0:
115
+ config_index = 0
116
+ print(f"Warning: Config index {config_index} is out of range. Using index 0 instead.")
117
+ else:
118
+ # This should not happen as load_configs should always return at least DEFAULT_CONFIG
119
+ return DEFAULT_CONFIG_ENTRY.copy()
120
+
121
+ # Get the selected config
122
+ config = configs[config_index]
123
+
69
124
  # Override with environment variables if they exist
70
125
  env_mapping = {
71
126
  "OPENAI_API_KEY": "api_key",
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 1.0.0
4
- Summary: A Python CLI and library for interacting with custom OpenAI API endpoints
3
+ Version: 1.1.0
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
7
7
  Project-URL: Bug Tracker, https://github.com/nazdridoy/ngpt/issues
@@ -28,12 +28,12 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
28
28
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
29
29
  Classifier: Topic :: Utilities
30
30
  Requires-Python: >=3.8
31
- Requires-Dist: requests>=2.28.0
31
+ Requires-Dist: requests>=2.31.0
32
32
  Description-Content-Type: text/markdown
33
33
 
34
34
  # nGPT
35
35
 
36
- A lightweight Python CLI and library for interacting with custom OpenAI API endpoints.
36
+ A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints.
37
37
 
38
38
  ## Features
39
39
 
@@ -64,6 +64,12 @@ ngpt "Hello, how are you?"
64
64
  # Show version information
65
65
  ngpt -v
66
66
 
67
+ # Show active configuration
68
+ ngpt --show-config
69
+
70
+ # Show all configurations
71
+ ngpt --show-config --all
72
+
67
73
  # With custom options
68
74
  ngpt --api-key your-key --base-url http://your-endpoint "Hello"
69
75
 
@@ -82,8 +88,8 @@ ngpt -c "create a python function that calculates fibonacci numbers"
82
88
  ```python
83
89
  from ngpt import NGPTClient, load_config
84
90
 
85
- # Load from config file
86
- config = load_config()
91
+ # Load the first configuration (index 0) from config file
92
+ config = load_config(config_index=0)
87
93
 
88
94
  # Initialize the client with config
89
95
  client = NGPTClient(**config)
@@ -117,10 +123,12 @@ You can configure the client using the following options:
117
123
 
118
124
  - `--api-key`: API key for the service
119
125
  - `--base-url`: Base URL for the API
120
- - `--provider`: Provider name
121
126
  - `--model`: Model to use
122
127
  - `--web-search`: Enable web search capability (Note: Your API endpoint must support this feature)
123
128
  - `--config`: Path to a custom configuration file
129
+ - `--config-index`: Index of the configuration to use from the config file (default: 0)
130
+ - `--show-config`: Show configuration details and exit.
131
+ - `--all`: Used with `--show-config` to display details for all configurations.
124
132
 
125
133
  ### Configuration File
126
134
 
@@ -130,35 +138,39 @@ nGPT uses a configuration file stored in the standard user config directory for
130
138
  - **macOS**: `~/Library/Application Support/ngpt/ngpt.conf`
131
139
  - **Windows**: `%APPDATA%\ngpt\ngpt.conf`
132
140
 
133
- The configuration file uses JSON format:
134
-
135
- #### OpenAI API Example
136
- ```json
137
- {
138
- "api_key": "your_openai_api_key_here",
139
- "base_url": "https://api.openai.com/v1/",
140
- "provider": "OpenAI",
141
- "model": "gpt-3.5-turbo"
142
- }
143
- ```
141
+ The configuration file uses a JSON list format, allowing you to store multiple configurations. You can select which configuration to use with the `--config-index` argument (or by default, index 0 is used).
144
142
 
145
- #### Custom Endpoint Example
143
+ #### Multiple Configurations Example (`ngpt.conf`)
146
144
  ```json
147
- {
148
- "api_key": "your_api_key_here",
149
- "base_url": "http://127.0.0.1:1337/v1/",
150
- "provider": "Blackbox",
151
- "model": "DeepSeek-V3"
152
- }
145
+ [
146
+ {
147
+ "api_key": "your-openai-api-key-here",
148
+ "base_url": "https://api.openai.com/v1/",
149
+ "provider": "OpenAI",
150
+ "model": "gpt-4o"
151
+ },
152
+ {
153
+ "api_key": "your-groq-api-key-here",
154
+ "base_url": "https://api.groq.com/openai/v1/",
155
+ "provider": "Groq",
156
+ "model": "llama3-70b-8192"
157
+ },
158
+ {
159
+ "api_key": "your-ollama-key-if-needed",
160
+ "base_url": "http://localhost:11434/v1/",
161
+ "provider": "Ollama-Local",
162
+ "model": "llama3"
163
+ }
164
+ ]
153
165
  ```
154
166
 
155
167
  ### Configuration Priority
156
168
 
157
169
  nGPT determines configuration values in the following order (highest priority first):
158
170
 
159
- 1. Command line arguments
160
- 2. Environment variables (`OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_PROVIDER`, `OPENAI_MODEL`)
161
- 3. Configuration file
171
+ 1. Command line arguments (`--api-key`, `--base-url`, `--model`)
172
+ 2. Environment variables (`OPENAI_API_KEY`, `OPENAI_BASE_URL`, `OPENAI_MODEL`)
173
+ 3. Configuration file (selected by `--config-index`, defaults to index 0)
162
174
  4. Default values
163
175
 
164
176
  ## Special Features
@@ -0,0 +1,9 @@
1
+ ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
+ ngpt/cli.py,sha256=HA85u1-ajsOBO7Zp-Hngyr4NbYxdktn-uM1VuCvy4CU,9495
3
+ ngpt/client.py,sha256=DfOjE2qQQZq3JF6wNa4YxEfUALq4B0ycP_3v9ZKw2ds,9940
4
+ ngpt/config.py,sha256=qkOd4pNk8pW191u4EzHORecPpRDX2yVPrMQkJ35UsNw,5063
5
+ ngpt-1.1.0.dist-info/METADATA,sha256=03E54-0_TucsoKBlE8U6AGLG6BhDjN4pbh4opiwFPA4,6299
6
+ ngpt-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ ngpt-1.1.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
+ ngpt-1.1.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
+ ngpt-1.1.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- ngpt/__init__.py,sha256=j6IB_tZV11gpCvP1P8mEQMIocQgGhlz3154t0jkn2EA,495
2
- ngpt/cli.py,sha256=OmN9iVBAmtbS-tAeeUYMGwW7XcQP99TA8j8tZwr7eJM,6242
3
- ngpt/client.py,sha256=xsRwdLSbb4S0gucQNtEPHPeVEHyyYfFfZVuNDDuDWjY,9866
4
- ngpt/config.py,sha256=H1ckYaNaAtep3c-ZS0E8tCoz5Am-ewUtoUd4s3oNewY,2655
5
- ngpt-1.0.0.dist-info/METADATA,sha256=hzGaAvz2w9WPUA-5AIcWITXCXnNEn-OZxVZ6fQVJohw,5434
6
- ngpt-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- ngpt-1.0.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
- ngpt-1.0.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
- ngpt-1.0.0.dist-info/RECORD,,
File without changes