gpt-shell-4o-mini 1.2.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.
chatgpt/__init__.py ADDED
@@ -0,0 +1,24 @@
1
+ """
2
+ GPT-shell-4o-mini - A simple, lightweight CLI to use OpenAI's ChatGPT and DALL-E from the terminal.
3
+
4
+ This package provides a command-line interface for interacting with OpenAI's API,
5
+ including chat completion, image generation, and command generation features.
6
+ """
7
+
8
+ from .main import main
9
+
10
+ # Import cleanup module to register automatic uninstall cleanup
11
+ from . import cleanup
12
+
13
+ # Import version from pyproject.toml to avoid duplication
14
+ try:
15
+ import importlib.metadata as metadata
16
+
17
+ __version__ = metadata.version("gpt-shell-4o-mini")
18
+ except ImportError:
19
+ # Fallback for older Python versions
20
+ import pkg_resources
21
+
22
+ __version__ = pkg_resources.get_distribution("gpt-shell-4o-mini").version
23
+
24
+ __all__ = ["main"]
chatgpt/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """
2
+ Main entry point for python -m chatgpt execution.
3
+ """
4
+
5
+ from chatgpt.main import main
6
+
7
+ if __name__ == "__main__":
8
+ main()
chatgpt/api_client.py ADDED
@@ -0,0 +1,244 @@
1
+ """
2
+ API client functions for GPT-shell-4o-mini.
3
+
4
+ This module handles all interactions with the OpenAI API,
5
+ including chat completions, image generation, and model management.
6
+ """
7
+
8
+ import sys
9
+ import json
10
+ import webbrowser
11
+ from datetime import datetime
12
+ from rich.console import Console
13
+
14
+ # Try importing necessary libraries and provide helpful error messages
15
+ try:
16
+ from openai import OpenAI, APIError, RateLimitError, APIConnectionError
17
+ except ImportError:
18
+ print(
19
+ "Error: 'openai' library not found. Please install it: pip install openai",
20
+ file=sys.stderr,
21
+ )
22
+ sys.exit(1)
23
+
24
+ # Configuration
25
+ DEFAULT_MODEL = "gpt-4o-mini"
26
+ DEFAULT_TEMPERATURE = 0.7
27
+ DEFAULT_MAX_TOKENS = 1024
28
+ DEFAULT_IMAGE_SIZE = "1024x1024"
29
+ MAX_CONTEXT_MESSAGES = 10
30
+
31
+ console = Console()
32
+ client = None # Initialize client later after checking API key
33
+
34
+
35
+ def handle_api_error(e):
36
+ """Handles common OpenAI API errors."""
37
+ if isinstance(e, RateLimitError):
38
+ console.print(
39
+ f"[bold red]API Error:[/bold red] Rate limit exceeded. Please check your plan and usage limits."
40
+ )
41
+ elif isinstance(e, APIConnectionError):
42
+ console.print(
43
+ f"[bold red]API Error:[/bold red] Could not connect to OpenAI. Check your network connection."
44
+ )
45
+ elif isinstance(e, APIError):
46
+ console.print(f"[bold red]API Error ({e.status_code}):[/bold red] {e.message}")
47
+ if e.body and "message" in e.body:
48
+ console.print(f" Details: {e.body['message']}")
49
+ else:
50
+ console.print(f"[bold red]An unexpected error occurred:[/bold red] {e}")
51
+ sys.exit(1)
52
+
53
+
54
+ def initialize_client(api_key):
55
+ """Initialize the OpenAI client with the provided API key."""
56
+ global client
57
+ try:
58
+ client = OpenAI(api_key=api_key)
59
+ return client
60
+ except Exception as e:
61
+ console.print(f"[bold red]Error initializing OpenAI client:[/bold red] {e}")
62
+ sys.exit(1)
63
+
64
+
65
+ def list_models():
66
+ """Lists available OpenAI models."""
67
+ try:
68
+ console.print("[grey50]Fetching models...[/grey50]", end="\r")
69
+ models = client.models.list()
70
+ console.print(
71
+ "Available OpenAI Models:" + " " * 20
72
+ ) # Overwrite fetching message
73
+ # Sort models by creation date or ID if needed
74
+ for model in sorted(models.data, key=lambda x: x.id):
75
+ created_date = (
76
+ datetime.fromtimestamp(model.created).strftime("%Y-%m-%d")
77
+ if model.created
78
+ else "N/A"
79
+ )
80
+ console.print(
81
+ f"- [bold cyan]{model.id}[/bold cyan] (Owned by: {model.owned_by}, Created: {created_date})"
82
+ )
83
+ except APIError as e:
84
+ handle_api_error(e)
85
+
86
+
87
+ def get_model_details(model_id):
88
+ """Gets details for a specific model."""
89
+ try:
90
+ console.print(f"[grey50]Fetching details for {model_id}...[/grey50]", end="\r")
91
+ model = client.models.retrieve(model_id)
92
+ console.print(
93
+ f"Details for model [bold cyan]{model_id}[/bold cyan]:" + " " * 20
94
+ ) # Overwrite
95
+ # Print details using rich formatting or just json.dumps
96
+ console.print(json.dumps(model.to_dict(), indent=2))
97
+ except APIError as e:
98
+ # Customize error for not found
99
+ if hasattr(e, "status_code") and e.status_code == 404:
100
+ console.print(f"[bold red]Error:[/bold red] Model '{model_id}' not found.")
101
+ sys.exit(1)
102
+ else:
103
+ handle_api_error(e)
104
+
105
+
106
+ def generate_image(prompt, size):
107
+ """Generates an image using DALL-E."""
108
+ try:
109
+ console.print("[grey50]Generating image...[/grey50]", end="\r")
110
+ response = client.images.generate(
111
+ model="dall-e-3", # Or dall-e-2 if preferred/available
112
+ prompt=prompt,
113
+ size=size,
114
+ quality="standard", # or "hd"
115
+ n=1,
116
+ )
117
+ image_url = response.data[0].url
118
+ console.print("Image generated successfully!" + " " * 20) # Overwrite
119
+ console.print(f"Link: {image_url}")
120
+
121
+ # Offer to open in browser
122
+ try:
123
+ if console.input("Open image in browser? (y/N) ").lower() == "y":
124
+ webbrowser.open(image_url)
125
+ except Exception as e:
126
+ console.print(f"[yellow]Could not open browser:[/yellow] {e}")
127
+
128
+ except APIError as e:
129
+ handle_api_error(e)
130
+ except (
131
+ Exception
132
+ ) as e: # Catch other potential errors like network issues during download etc.
133
+ console.print(
134
+ f"[red]An error occurred during image generation or display:[/red] {e}"
135
+ )
136
+
137
+
138
+ def get_chat_completion(messages, model, temperature, max_tokens):
139
+ """Gets a completion from a chat model."""
140
+ try:
141
+ # Import here to avoid circular imports
142
+ from .user_profile import format_user_profile
143
+ from .terminal_context import format_terminal_session
144
+
145
+ # Build complete context for EVERY prompt
146
+ context_parts = []
147
+
148
+ # Add static profile
149
+ profile = format_user_profile()
150
+ if profile:
151
+ context_parts.append(profile)
152
+
153
+ # Add terminal session
154
+ terminal = format_terminal_session()
155
+ if terminal:
156
+ context_parts.append(terminal)
157
+
158
+ # Combine contexts
159
+ full_context = "\n".join(context_parts)
160
+
161
+ # Prepend context to EVERY user message, not just the first one
162
+ for i, msg in enumerate(messages):
163
+ if msg["role"] == "user":
164
+ messages[i]["content"] = f"{full_context}\n\n{msg['content']}"
165
+
166
+ response = client.chat.completions.create(
167
+ model=model,
168
+ messages=messages,
169
+ temperature=temperature,
170
+ max_tokens=max_tokens,
171
+ )
172
+ content = response.choices[0].message.content
173
+ return content.strip() if content else ""
174
+ except APIError as e:
175
+ handle_api_error(e)
176
+ return None # Indicate failure
177
+
178
+
179
+ def get_system_prompt():
180
+ """Get the system prompt from custom file or default."""
181
+ from pathlib import Path
182
+
183
+ custom_prompt_file = Path.home() / ".chatgpt_py_sys_prompt"
184
+
185
+ if custom_prompt_file.exists():
186
+ try:
187
+ with open(custom_prompt_file, "r", encoding="utf-8") as f:
188
+ custom_prompt = f.read().strip()
189
+ if custom_prompt:
190
+ return custom_prompt
191
+ except (IOError, OSError):
192
+ # If file exists but can't be read, fall back to default
193
+ pass
194
+
195
+ # Default system prompt
196
+ return """think carefully and provide a detailed response. use my prevoius data for a better answer."""
197
+
198
+
199
+ # Command generation prompt
200
+ COMMAND_GENERATION_PROMPT = "You are a Command Line Interface expert and your task is to provide functioning shell commands. Return a CLI command and nothing else - do not send it in a code block, quotes, or anything else, just the pure text CONTAINING ONLY THE COMMAND. If possible, return a one-line bash command or chain many commands together. Return ONLY the command ready to run in the terminal. The command should do the following:"
201
+
202
+
203
+ def set_custom_system_prompt(prompt_text):
204
+ """Save a custom system prompt to the file."""
205
+ from pathlib import Path
206
+
207
+ custom_prompt_file = Path.home() / ".chatgpt_py_sys_prompt"
208
+
209
+ try:
210
+ with open(custom_prompt_file, "w", encoding="utf-8") as f:
211
+ f.write(prompt_text.strip())
212
+ return True
213
+ except (IOError, OSError):
214
+ return False
215
+
216
+
217
+ def get_custom_system_prompt():
218
+ """Get the current custom system prompt from file."""
219
+ from pathlib import Path
220
+
221
+ custom_prompt_file = Path.home() / ".chatgpt_py_sys_prompt"
222
+
223
+ if custom_prompt_file.exists():
224
+ try:
225
+ with open(custom_prompt_file, "r", encoding="utf-8") as f:
226
+ return f.read().strip()
227
+ except (IOError, OSError):
228
+ pass
229
+
230
+ return None
231
+
232
+
233
+ def reset_system_prompt():
234
+ """Remove the custom system prompt file to use default."""
235
+ from pathlib import Path
236
+
237
+ custom_prompt_file = Path.home() / ".chatgpt_py_sys_prompt"
238
+
239
+ try:
240
+ if custom_prompt_file.exists():
241
+ custom_prompt_file.unlink()
242
+ return True
243
+ except (IOError, OSError):
244
+ return False
chatgpt/checks.py ADDED
@@ -0,0 +1,211 @@
1
+ """
2
+ Initial checks and setup functions for GPT-shell-4o-mini.
3
+
4
+ This module handles API key verification, first-time setup wizard,
5
+ and environment variable configuration.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import subprocess
11
+ import json
12
+ import requests
13
+ from pathlib import Path
14
+ from rich.console import Console
15
+
16
+ # Configuration
17
+ API_KEY = os.getenv("OPENAI_API_KEY") or os.getenv("OPENAI_KEY")
18
+ USER_PROFILE_FILE = Path.home() / ".chatgpt_py_info"
19
+
20
+ console = Console()
21
+
22
+
23
+ def verify_api_key(api_key):
24
+ """Verify that an OpenAI API key is valid by making a test request."""
25
+ try:
26
+ response = requests.get(
27
+ "https://api.openai.com/v1/models",
28
+ headers={"Authorization": f"Bearer {api_key}"},
29
+ timeout=10,
30
+ )
31
+
32
+ # Check if request was successful
33
+ if response.status_code == 200:
34
+ data = response.json()
35
+ if "data" in data:
36
+ return True
37
+ return False
38
+ except Exception:
39
+ return False
40
+
41
+
42
+ def update_shell_profile(api_key):
43
+ """Add or update the OPENAI_KEY in the user's shell profile or system."""
44
+ import platform
45
+
46
+ system = platform.system()
47
+
48
+ if system == "Windows":
49
+ # Windows: Use setx to set user environment variable
50
+ try:
51
+ subprocess.run(
52
+ ["setx", "OPENAI_KEY", api_key], check=True, capture_output=True
53
+ )
54
+ # Also set for current session
55
+ os.environ["OPENAI_KEY"] = api_key
56
+ console.print(
57
+ f"[green]✓[/green] Added OPENAI_KEY to Windows environment variables"
58
+ )
59
+ console.print(
60
+ f"[yellow]Note:[/yellow] Restart your terminal for the change to take effect"
61
+ )
62
+ return True
63
+ except Exception as e:
64
+ console.print(
65
+ f"[yellow]Warning:[/yellow] Could not set environment variable: {e}"
66
+ )
67
+ console.print(
68
+ f"[yellow]Please manually set OPENAI_KEY in System Settings[/yellow]"
69
+ )
70
+ return False
71
+
72
+ else: # macOS or Linux
73
+ # Existing Unix shell profile logic
74
+ profile_paths = [
75
+ Path.home() / ".bashrc",
76
+ Path.home() / ".zprofile",
77
+ Path.home() / ".zshrc",
78
+ Path.home() / ".bash_profile",
79
+ Path.home() / ".profile",
80
+ ]
81
+
82
+ for profile_path in profile_paths:
83
+ if profile_path.exists():
84
+ try:
85
+ with profile_path.open("r") as f:
86
+ content = f.read()
87
+
88
+ if "export OPENAI_KEY" not in content:
89
+ with profile_path.open("a") as f:
90
+ f.write(f"\n# OpenAI API Key for GPT-shell-4o-mini\n")
91
+ f.write(f"export OPENAI_KEY={api_key}\n")
92
+ console.print(
93
+ f"[green]✓[/green] Added OPENAI_KEY to {profile_path}"
94
+ )
95
+ return True
96
+ else:
97
+ console.print(
98
+ f"[yellow]![/yellow] OPENAI_KEY already exists in {profile_path}"
99
+ )
100
+ return True
101
+ except Exception as e:
102
+ console.print(
103
+ f"[yellow]Warning:[/yellow] Could not update {profile_path}: {e}"
104
+ )
105
+ continue
106
+
107
+ # If no profile found
108
+ console.print("[yellow]Warning:[/yellow] No shell profile found.")
109
+ console.print(f"[yellow]Please manually add to your shell config:[/yellow]")
110
+ console.print(f" export OPENAI_KEY={api_key}")
111
+ return False
112
+
113
+
114
+ def first_run_setup():
115
+ """Interactive setup wizard for first-time users."""
116
+ console.print("\n" + "=" * 60)
117
+ console.print("[bold cyan]Welcome to GPT-shell-4o-mini![/bold cyan]")
118
+ console.print("=" * 60)
119
+ console.print("\nIt looks like you haven't set up your OpenAI API key yet.")
120
+ console.print("\n[bold]Where to get an API key:[/bold]")
121
+ console.print(" https://platform.openai.com/account/api-keys")
122
+ console.print("\n" + "=" * 60 + "\n")
123
+
124
+ try:
125
+ user_input = (
126
+ console.input("[bold]Do you have an OpenAI API key? (yes/no):[/bold] ")
127
+ .strip()
128
+ .lower()
129
+ )
130
+
131
+ if user_input not in ["yes", "y"]:
132
+ console.print("\n[yellow]Please get an API key from:[/yellow]")
133
+ console.print(" https://platform.openai.com/account/api-keys")
134
+ console.print(
135
+ "\n[cyan]After getting your key, run 'gpt' again to set it up.[/cyan]\n"
136
+ )
137
+ sys.exit(0)
138
+
139
+ api_key = console.input("\n[bold]Enter your OpenAI API key:[/bold] ").strip()
140
+
141
+ if not api_key:
142
+ console.print("[red]Error:[/red] No API key provided.")
143
+ sys.exit(1)
144
+
145
+ console.print("\n[cyan]Verifying API key...[/cyan]")
146
+
147
+ if not verify_api_key(api_key):
148
+ console.print("[red]Error:[/red] Invalid API key or unable to verify.")
149
+ console.print("Please check your key and try again.\n")
150
+ sys.exit(1)
151
+
152
+ console.print("[green]✓[/green] API key verified successfully!")
153
+
154
+ # Update shell profile
155
+ console.print("\n[cyan]Saving API key to your shell profile...[/cyan]")
156
+ profile_saved = update_shell_profile(api_key)
157
+
158
+ # Import here to avoid circular imports
159
+ from .user_profile import collect_user_profile, save_user_profile
160
+
161
+ # Collect and save user profile
162
+ console.print("\n[cyan]Setting up your profile...[/cyan]")
163
+ profile = collect_user_profile()
164
+
165
+ # Allow username customization
166
+ console.print(f"\n[bold]Detected username:[/bold] {profile['username']}")
167
+ custom_name = console.input(
168
+ "[bold]Press Enter to use this, or type a different name:[/bold] "
169
+ ).strip()
170
+
171
+ if custom_name:
172
+ profile["username"] = custom_name
173
+
174
+ # Save profile
175
+ if save_user_profile(profile):
176
+ console.print(f"\n[green]✓[/green] Profile saved:")
177
+ console.print(f" Username: {profile['username']}")
178
+ console.print(f" OS: {profile['os']}")
179
+ if "distro" in profile:
180
+ console.print(f" Distribution: {profile['distro']}")
181
+
182
+ if profile_saved:
183
+ console.print("\n[green]✓[/green] Setup complete!")
184
+ console.print("\n[bold]Important:[/bold] For the key to be available, run:")
185
+ console.print(
186
+ " [cyan]source ~/.bashrc[/cyan] (or your shell's config file)"
187
+ )
188
+ console.print(
189
+ "\nOr restart your terminal, then run '[cyan]gpt[/cyan]' again.\n"
190
+ )
191
+ else:
192
+ console.print(
193
+ "\n[yellow]Setup complete, but you'll need to manually add the key.[/yellow]\n"
194
+ )
195
+
196
+ sys.exit(0)
197
+
198
+ except (EOFError, KeyboardInterrupt):
199
+ console.print("\n\n[yellow]Setup cancelled.[/yellow]\n")
200
+ sys.exit(0)
201
+
202
+
203
+ def check_api_key():
204
+ """Check if API key is available, run setup if not."""
205
+ global API_KEY
206
+ if not API_KEY:
207
+ # Run first-time setup wizard
208
+ first_run_setup()
209
+ # If we get here, setup was cancelled or failed
210
+ sys.exit(1)
211
+ return API_KEY
chatgpt/cleanup.py ADDED
@@ -0,0 +1,143 @@
1
+ """
2
+ Automatic cleanup module for GPT-shell-4o-mini.
3
+
4
+ This module registers an atexit handler that detects when the package
5
+ is being uninstalled and automatically cleans up all files and environment variables.
6
+ """
7
+
8
+ import os
9
+ import sys
10
+ import subprocess
11
+ import platform
12
+ import atexit
13
+ from pathlib import Path
14
+ from rich.console import Console
15
+
16
+ console = Console()
17
+
18
+ # Files created by the package
19
+ FILES_TO_REMOVE = [
20
+ "~/.chatgpt_py_info", # User profile
21
+ "~/.chatgpt_py_history", # Chat history
22
+ "~/.chatgpt_py_sys_prompt", # Custom system prompt
23
+ ]
24
+
25
+ # Shell profiles that might contain OPENAI_KEY
26
+ SHELL_PROFILES = [
27
+ "~/.bashrc",
28
+ "~/.zprofile",
29
+ "~/.zshrc",
30
+ "~/.bash_profile",
31
+ "~/.profile",
32
+ ]
33
+
34
+
35
+ def remove_file(file_path):
36
+ """Remove a single file if it exists."""
37
+ path = Path(file_path).expanduser()
38
+ if path.exists():
39
+ try:
40
+ path.unlink()
41
+ return True
42
+ except Exception:
43
+ return False
44
+ return False
45
+
46
+
47
+ def remove_openai_key_from_profiles():
48
+ """Remove OPENAI_KEY from shell profile files."""
49
+ system = platform.system()
50
+
51
+ if system == "Windows":
52
+ # Remove Windows environment variable
53
+ try:
54
+ result = subprocess.run(
55
+ ["reg", "delete", "HKCU\\Environment", "/v", "OPENAI_KEY", "/f"],
56
+ capture_output=True,
57
+ text=True,
58
+ )
59
+ return result.returncode == 0
60
+ except Exception:
61
+ return False
62
+ else:
63
+ # Unix-like systems - remove from shell profiles
64
+ key_removed = False
65
+ for profile_file in SHELL_PROFILES:
66
+ profile_path = Path(profile_file).expanduser()
67
+ if profile_path.exists():
68
+ try:
69
+ with profile_path.open("r") as f:
70
+ lines = f.readlines()
71
+
72
+ # Filter out lines containing OPENAI_KEY for our package
73
+ filtered_lines = []
74
+ local_key_removed = False
75
+ in_our_section = False
76
+
77
+ for line in lines:
78
+ if "# OpenAI API Key for GPT-shell-4o-mini" in line:
79
+ in_our_section = True
80
+ local_key_removed = True
81
+ continue
82
+ elif in_our_section and line.strip().startswith(
83
+ "export OPENAI_KEY="
84
+ ):
85
+ continue # Skip the export line
86
+ elif in_our_section and line.strip() == "":
87
+ in_our_section = False # End of our section
88
+ continue
89
+ elif in_our_section:
90
+ continue # Skip any other lines in our section
91
+
92
+ filtered_lines.append(line)
93
+
94
+ if local_key_removed:
95
+ with profile_path.open("w") as f:
96
+ f.writelines(filtered_lines)
97
+ key_removed = True
98
+
99
+ except Exception:
100
+ continue
101
+
102
+ return key_removed
103
+
104
+
105
+ def cleanup_on_uninstall():
106
+ """Cleanup function called during package uninstall."""
107
+ # Only run if we're being uninstalled (not on normal exit)
108
+ if not hasattr(sys, "real_prefix") and not (
109
+ hasattr(sys, "base_prefix") and sys.base_prefix != sys.prefix
110
+ ):
111
+ # Check if the package directory still exists
112
+ package_dir = Path(__file__).parent.parent
113
+ if not package_dir.exists():
114
+ # Package is being uninstalled, run cleanup
115
+ try:
116
+ # Remove configuration files
117
+ files_removed = 0
118
+ for file_path in FILES_TO_REMOVE:
119
+ if remove_file(file_path):
120
+ files_removed += 1
121
+
122
+ # Remove environment variables
123
+ remove_openai_key_from_profiles()
124
+
125
+ # Print cleanup message (only if we can)
126
+ try:
127
+ console.print(
128
+ f"\n[cyan]GPT-shell-4o-mini cleanup: Removed {files_removed} config files[/cyan]"
129
+ )
130
+ except:
131
+ pass # Silent fail if console not available
132
+
133
+ except Exception:
134
+ pass # Silent fail on any errors
135
+
136
+
137
+ def register_cleanup():
138
+ """Register the cleanup function to run on package uninstall."""
139
+ atexit.register(cleanup_on_uninstall)
140
+
141
+
142
+ # Register cleanup when module is imported
143
+ register_cleanup()