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 +24 -0
- chatgpt/__main__.py +8 -0
- chatgpt/api_client.py +244 -0
- chatgpt/checks.py +211 -0
- chatgpt/cleanup.py +143 -0
- chatgpt/main.py +548 -0
- chatgpt/terminal_context.py +48 -0
- chatgpt/uninstall.py +217 -0
- chatgpt/user_profile.py +84 -0
- gpt_shell_4o_mini-1.2.1.dist-info/METADATA +366 -0
- gpt_shell_4o_mini-1.2.1.dist-info/RECORD +15 -0
- gpt_shell_4o_mini-1.2.1.dist-info/WHEEL +5 -0
- gpt_shell_4o_mini-1.2.1.dist-info/entry_points.txt +4 -0
- gpt_shell_4o_mini-1.2.1.dist-info/licenses/LICENSE +21 -0
- gpt_shell_4o_mini-1.2.1.dist-info/top_level.txt +1 -0
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
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()
|