ngpt 1.1.4__py3-none-any.whl → 1.3.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/cli.py CHANGED
@@ -2,9 +2,27 @@ 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
5
+ from .config import load_config, get_config_path, load_configs, add_config_entry, remove_config_entry
6
6
  from . import __version__
7
7
 
8
+ # Optional imports for enhanced UI
9
+ try:
10
+ from prompt_toolkit import prompt as pt_prompt
11
+ from prompt_toolkit.styles import Style
12
+ from prompt_toolkit.key_binding import KeyBindings
13
+ from prompt_toolkit.formatted_text import HTML
14
+ from prompt_toolkit.layout.containers import HSplit, Window
15
+ from prompt_toolkit.layout.layout import Layout
16
+ from prompt_toolkit.layout.controls import FormattedTextControl
17
+ from prompt_toolkit.application import Application
18
+ from prompt_toolkit.widgets import TextArea
19
+ from prompt_toolkit.layout.margins import ScrollbarMargin
20
+ from prompt_toolkit.filters import to_filter
21
+ import shutil
22
+ HAS_PROMPT_TOOLKIT = True
23
+ except ImportError:
24
+ HAS_PROMPT_TOOLKIT = False
25
+
8
26
  def show_config_help():
9
27
  """Display help information about configuration."""
10
28
  print("\nConfiguration Help:")
@@ -40,11 +58,15 @@ def show_config_help():
40
58
  print(" 4. Or provide command line arguments:")
41
59
  print(" ngpt --api-key your-key --base-url https://api.example.com --model your-model \"Your prompt\"")
42
60
 
43
- print(" 5. Use --config-index to specify which configuration to use:")
61
+ print(" 5. Use --config-index to specify which configuration to use or edit:")
44
62
  print(" ngpt --config-index 1 \"Your prompt\"")
45
63
 
46
- print(" 6. Use --config without arguments to add or edit a configuration:")
64
+ print(" 6. Use --config without arguments to add a new configuration:")
65
+ print(" ngpt --config")
66
+ print(" Or specify an index to edit an existing configuration:")
47
67
  print(" ngpt --config --config-index 1")
68
+ print(" 7. Remove a configuration at a specific index:")
69
+ print(" ngpt --config --remove --config-index 1")
48
70
 
49
71
  def check_config(config):
50
72
  """Check config for common issues and provide guidance."""
@@ -68,8 +90,9 @@ def main():
68
90
 
69
91
  # Config options
70
92
  config_group = parser.add_argument_group('Configuration Options')
71
- config_group.add_argument('--config', nargs='?', const=True, help='Path to a custom config file or, if no value provided, enter interactive configuration mode')
72
- config_group.add_argument('--config-index', type=int, default=0, help='Index of the configuration to use (default: 0)')
93
+ 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')
94
+ config_group.add_argument('--config-index', type=int, default=0, help='Index of the configuration to use or edit (default: 0)')
95
+ config_group.add_argument('--remove', action='store_true', help='Remove the configuration at the specified index (requires --config and --config-index)')
73
96
  config_group.add_argument('--show-config', action='store_true', help='Show the current configuration(s) and exit')
74
97
  config_group.add_argument('--all', action='store_true', help='Show details for all configurations (requires --show-config)')
75
98
 
@@ -86,6 +109,7 @@ def main():
86
109
  mode_exclusive_group = mode_group.add_mutually_exclusive_group()
87
110
  mode_exclusive_group.add_argument('-s', '--shell', action='store_true', help='Generate and execute shell commands')
88
111
  mode_exclusive_group.add_argument('-c', '--code', action='store_true', help='Generate code')
112
+ mode_exclusive_group.add_argument('-t', '--text', action='store_true', help='Enter multi-line text input (submit with Ctrl+D)')
89
113
  # Note: --show-config is handled separately and implicitly acts as a mode
90
114
 
91
115
  # Language option for code mode
@@ -103,7 +127,56 @@ def main():
103
127
  # Handle interactive configuration mode
104
128
  if args.config is True: # --config was used without a value
105
129
  config_path = get_config_path()
106
- add_config_entry(config_path, args.config_index)
130
+
131
+ # Handle configuration removal if --remove flag is present
132
+ if args.remove:
133
+ # Validate that config_index is explicitly provided
134
+ if '--config-index' not in sys.argv:
135
+ parser.error("--remove requires explicitly specifying --config-index")
136
+
137
+ # Show config details before asking for confirmation
138
+ configs = load_configs(str(config_path))
139
+
140
+ # Check if index is valid
141
+ if args.config_index < 0 or args.config_index >= len(configs):
142
+ print(f"Error: Configuration index {args.config_index} is out of range. Valid range: 0-{len(configs)-1}")
143
+ return
144
+
145
+ # Show the configuration that will be removed
146
+ config = configs[args.config_index]
147
+ print(f"Configuration to remove (index {args.config_index}):")
148
+ print(f" Provider: {config.get('provider', 'N/A')}")
149
+ print(f" Model: {config.get('model', 'N/A')}")
150
+ print(f" Base URL: {config.get('base_url', 'N/A')}")
151
+ print(f" API Key: {'[Set]' if config.get('api_key') else '[Not Set]'}")
152
+
153
+ # Ask for confirmation
154
+ try:
155
+ print("\nAre you sure you want to remove this configuration? [y/N] ", end='')
156
+ response = input().lower()
157
+ if response in ('y', 'yes'):
158
+ remove_config_entry(config_path, args.config_index)
159
+ else:
160
+ print("Configuration removal cancelled.")
161
+ except KeyboardInterrupt:
162
+ print("\nConfiguration removal cancelled by user.")
163
+
164
+ return
165
+
166
+ # Regular config addition/editing (existing code)
167
+ # If --config-index was not explicitly specified, create a new entry by passing None
168
+ # This will cause add_config_entry to create a new entry at the end of the list
169
+ # Otherwise, edit the existing config at the specified index
170
+ config_index = None if args.config_index == 0 and '--config-index' not in sys.argv else args.config_index
171
+
172
+ # Load existing configs to determine the new index if creating a new config
173
+ configs = load_configs(str(config_path))
174
+ if config_index is None:
175
+ print(f"Creating new configuration at index {len(configs)}")
176
+ else:
177
+ print(f"Editing existing configuration at index {config_index}")
178
+
179
+ add_config_entry(config_path, config_index)
107
180
  return
108
181
 
109
182
  # Load configuration using the specified index (needed for active config display)
@@ -155,7 +228,7 @@ def main():
155
228
  return
156
229
 
157
230
  # Check if prompt is required but not provided
158
- if not args.prompt and not (args.shell or args.code):
231
+ if not args.prompt and not (args.shell or args.code or args.text):
159
232
  parser.print_help()
160
233
  return
161
234
 
@@ -219,6 +292,117 @@ def main():
219
292
  if generated_code:
220
293
  print(f"\nGenerated code:\n{generated_code}")
221
294
 
295
+ elif args.text:
296
+ # Multi-line text input mode
297
+ if args.prompt is not None:
298
+ prompt = args.prompt
299
+ else:
300
+ try:
301
+ if HAS_PROMPT_TOOLKIT:
302
+ print("\033[94m\033[1m" + "Multi-line Input Mode" + "\033[0m")
303
+ print("Press Ctrl+D to submit, Ctrl+C to exit")
304
+ print("Use arrow keys to navigate, Enter for new line")
305
+
306
+ # Create key bindings
307
+ kb = KeyBindings()
308
+
309
+ # Explicitly bind Ctrl+D to exit
310
+ @kb.add('c-d')
311
+ def _(event):
312
+ event.app.exit(result=event.app.current_buffer.text)
313
+
314
+ # Explicitly bind Ctrl+C to exit
315
+ @kb.add('c-c')
316
+ def _(event):
317
+ event.app.exit(result=None)
318
+ print("\nInput cancelled by user. Exiting gracefully.")
319
+ sys.exit(130)
320
+
321
+ # Get terminal dimensions
322
+ term_width, term_height = shutil.get_terminal_size()
323
+
324
+ # Create a styled TextArea
325
+ text_area = TextArea(
326
+ style="class:input-area",
327
+ multiline=True,
328
+ wrap_lines=True,
329
+ width=term_width - 4,
330
+ height=min(20, term_height - 8),
331
+ prompt=HTML("<ansicyan>>>> </ansicyan>"),
332
+ scrollbar=True,
333
+ focus_on_click=True,
334
+ lexer=None,
335
+ )
336
+ text_area.window.right_margins = [ScrollbarMargin(display_arrows=True)]
337
+
338
+ # Create a title bar
339
+ title_bar = FormattedTextControl(
340
+ HTML("<style bg='ansicyan' fg='ansiblack'><b> NGPT Multi-line Editor </b></style>")
341
+ )
342
+
343
+ # Create a status bar with key bindings info
344
+ status_bar = FormattedTextControl(
345
+ HTML("<ansiblue><b>Ctrl+D</b></ansiblue>: Submit | <ansiblue><b>Ctrl+C</b></ansiblue>: Cancel | <ansiblue><b>↑↓←→</b></ansiblue>: Navigate")
346
+ )
347
+
348
+ # Create the layout
349
+ layout = Layout(
350
+ HSplit([
351
+ Window(title_bar, height=1),
352
+ Window(height=1, char="-", style="class:separator"),
353
+ text_area,
354
+ Window(height=1, char="-", style="class:separator"),
355
+ Window(status_bar, height=1),
356
+ ])
357
+ )
358
+
359
+ # Create a style
360
+ style = Style.from_dict({
361
+ "separator": "ansigray",
362
+ "input-area": "bg:ansiblack fg:ansiwhite",
363
+ "cursor": "bg:ansiwhite fg:ansiblack",
364
+ })
365
+
366
+ # Create and run the application
367
+ app = Application(
368
+ layout=layout,
369
+ full_screen=False,
370
+ key_bindings=kb,
371
+ style=style,
372
+ mouse_support=True,
373
+ )
374
+
375
+ prompt = app.run()
376
+
377
+ if not prompt or not prompt.strip():
378
+ print("Empty prompt. Exiting.")
379
+ return
380
+ else:
381
+ # Fallback to standard input with a better implementation
382
+ print("Enter your multi-line prompt (press Ctrl+D to submit):")
383
+ print("Note: Install 'prompt_toolkit' package for an enhanced input experience")
384
+
385
+ # Use a more robust approach for multiline input without prompt_toolkit
386
+ lines = []
387
+ while True:
388
+ try:
389
+ line = input()
390
+ lines.append(line)
391
+ except EOFError: # Ctrl+D was pressed
392
+ break
393
+
394
+ prompt = "\n".join(lines)
395
+ if not prompt.strip():
396
+ print("Empty prompt. Exiting.")
397
+ return
398
+
399
+ except KeyboardInterrupt:
400
+ print("\nInput cancelled by user. Exiting gracefully.")
401
+ sys.exit(130)
402
+
403
+ print("\nSubmission successful. Waiting for response...")
404
+ client.chat(prompt, web_search=args.web_search)
405
+
222
406
  else:
223
407
  # Default to chat mode
224
408
  if args.prompt is None:
ngpt/config.py CHANGED
@@ -157,4 +157,29 @@ def load_config(custom_path: Optional[str] = None, config_index: int = 0) -> Dic
157
157
  if env_var in os.environ and os.environ[env_var]:
158
158
  config[config_key] = os.environ[env_var]
159
159
 
160
- return config
160
+ return config
161
+
162
+ def remove_config_entry(config_path: Path, config_index: int) -> bool:
163
+ """
164
+ Remove a configuration entry at the specified index.
165
+ Returns True if successful, False otherwise.
166
+ """
167
+ configs = load_configs(custom_path=str(config_path))
168
+
169
+ # Check if index is valid
170
+ if config_index < 0 or config_index >= len(configs):
171
+ print(f"Error: Configuration index {config_index} is out of range. Valid range: 0-{len(configs)-1}")
172
+ return False
173
+
174
+ # Remove the config at the specified index
175
+ removed_config = configs.pop(config_index)
176
+
177
+ try:
178
+ # Save the updated configs
179
+ with open(config_path, "w") as f:
180
+ json.dump(configs, f, indent=2)
181
+ print(f"Removed configuration at index {config_index} for provider '{removed_config.get('provider', 'Unknown')}'")
182
+ return True
183
+ except Exception as e:
184
+ print(f"Error saving configuration: {e}")
185
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 1.1.4
3
+ Version: 1.3.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
@@ -28,6 +28,7 @@ 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: prompt-toolkit>=3.0.0
31
32
  Requires-Dist: requests>=2.31.0
32
33
  Description-Content-Type: text/markdown
33
34
 
@@ -68,6 +69,9 @@ ngpt --code "function to calculate the Fibonacci sequence"
68
69
 
69
70
  # Generate and execute shell commands
70
71
  ngpt --shell "list all files in the current directory"
72
+
73
+ # Use multiline editor for complex prompts
74
+ ngpt --text
71
75
  ```
72
76
 
73
77
  ## Features
@@ -80,6 +84,7 @@ ngpt --shell "list all files in the current directory"
80
84
  - ⚙️ **Multiple Configurations**: Cross-platform config system supporting different profiles
81
85
  - 💻 **Shell Command Generation**: OS-aware command execution
82
86
  - 🧩 **Clean Code Generation**: Output code without markdown or explanations
87
+ - 📝 **Rich Multiline Editor**: Interactive multiline text input with syntax highlighting and intuitive controls
83
88
 
84
89
  ## Installation
85
90
 
@@ -121,6 +126,10 @@ ngpt -s "list all files in current directory"
121
126
  # Generate clean code (using -c or --code flag)
122
127
  # Returns only code without markdown formatting or explanations
123
128
  ngpt -c "create a python function that calculates fibonacci numbers"
129
+
130
+ # Use multiline text editor for complex prompts (using -t or --text flag)
131
+ # Opens an interactive editor with syntax highlighting and intuitive controls
132
+ ngpt -t
124
133
  ```
125
134
 
126
135
  ### As a Library
@@ -195,10 +204,12 @@ You can configure the client using the following options:
195
204
  | `--web-search` | Enable web search capability |
196
205
  | `--config` | Path to a custom configuration file or, when used without a value, enters interactive configuration mode |
197
206
  | `--config-index` | Index of the configuration to use (default: 0) |
207
+ | `--remove` | Remove the configuration at the specified index (requires --config and --config-index) |
198
208
  | `--show-config` | Show configuration details and exit |
199
209
  | `--all` | Used with `--show-config` to display all configurations |
200
210
  | `-s, --shell` | Generate and execute shell commands |
201
211
  | `-c, --code` | Generate clean code output |
212
+ | `-t, --text` | Open interactive multiline editor for complex prompts |
202
213
  | `-v, --version` | Show version information |
203
214
 
204
215
  ### Interactive Configuration
@@ -211,12 +222,16 @@ ngpt --config
211
222
 
212
223
  # Edit an existing configuration at index 1
213
224
  ngpt --config --config-index 1
225
+
226
+ # Remove a configuration at index 2
227
+ ngpt --config --remove --config-index 2
214
228
  ```
215
229
 
216
230
  In interactive mode:
217
231
  - When editing an existing configuration, press Enter to keep the current values
218
232
  - When creating a new configuration, press Enter to use default values
219
233
  - For security, your API key is not displayed when editing configurations
234
+ - When removing a configuration, you'll be asked to confirm before deletion
220
235
 
221
236
  ### Configuration File
222
237
 
@@ -0,0 +1,9 @@
1
+ ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
+ ngpt/cli.py,sha256=U8A9JKOpp-GorBk9yMs2nPThSdUwm83P_e3sDgfqCYo,19880
3
+ ngpt/client.py,sha256=O0dPYeQCJlpWZWBBsroo-5UxeyBVwqC6o3Pm8lRnDiY,10329
4
+ ngpt/config.py,sha256=BF0G3QeiPma8l7EQyc37bR7LWZog7FHJQNe7uj9cr4w,6896
5
+ ngpt-1.3.0.dist-info/METADATA,sha256=mGKNDQa1ppPnBp8lXILcSGYsS_40J0TQRQWLgpF_ewQ,9939
6
+ ngpt-1.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
+ ngpt-1.3.0.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
+ ngpt-1.3.0.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
+ ngpt-1.3.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- ngpt/__init__.py,sha256=ehInP9w0MZlS1vZ1g6Cm4YE1ftmgF72CnEddQ3Le9n4,368
2
- ngpt/cli.py,sha256=_nu7eY76_y4KQ59cKC91VijVvzSASAaSJI1FFfJ9l04,10655
3
- ngpt/client.py,sha256=O0dPYeQCJlpWZWBBsroo-5UxeyBVwqC6o3Pm8lRnDiY,10329
4
- ngpt/config.py,sha256=STnYKJor2nE_dKpK9NcnWlz3A8HeB8ufp-xOewGmRVo,5947
5
- ngpt-1.1.4.dist-info/METADATA,sha256=kCn6kuMVbjmD0_LTjVyq1D_5MXrB6aNJpgF7Qf9QopE,9240
6
- ngpt-1.1.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
7
- ngpt-1.1.4.dist-info/entry_points.txt,sha256=1cnAMujyy34DlOahrJg19lePSnb08bLbkUs_kVerqdk,39
8
- ngpt-1.1.4.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
9
- ngpt-1.1.4.dist-info/RECORD,,
File without changes