llmshell-cli 0.0.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.
- gpt_shell/__init__.py +9 -0
- gpt_shell/cli.py +56 -0
- gpt_shell/config.py +190 -0
- gpt_shell/core.py +32 -0
- gpt_shell/llm_client.py +325 -0
- gpt_shell/llm_manager.py +225 -0
- gpt_shell/main.py +327 -0
- gpt_shell/py.typed +1 -0
- gpt_shell/utils.py +305 -0
- llmshell_cli-0.0.1.dist-info/METADATA +446 -0
- llmshell_cli-0.0.1.dist-info/RECORD +15 -0
- llmshell_cli-0.0.1.dist-info/WHEEL +5 -0
- llmshell_cli-0.0.1.dist-info/entry_points.txt +2 -0
- llmshell_cli-0.0.1.dist-info/licenses/LICENSE +21 -0
- llmshell_cli-0.0.1.dist-info/top_level.txt +1 -0
gpt_shell/utils.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Utility functions for llmshell."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import sys
|
|
5
|
+
from typing import Optional, Tuple
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.syntax import Syntax
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
from rich import print as rprint
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def print_success(message: str) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Print success message.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
message: Message to print
|
|
22
|
+
"""
|
|
23
|
+
console.print(f"[green]✓[/green] {message}")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def print_error(message: str) -> None:
|
|
27
|
+
"""
|
|
28
|
+
Print error message.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
message: Message to print
|
|
32
|
+
"""
|
|
33
|
+
console.print(f"[red]✗[/red] {message}", style="red")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def print_warning(message: str) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Print warning message.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
message: Message to print
|
|
42
|
+
"""
|
|
43
|
+
console.print(f"[yellow]⚠[/yellow] {message}", style="yellow")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def print_info(message: str) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Print info message.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
message: Message to print
|
|
52
|
+
"""
|
|
53
|
+
console.print(f"[blue]ℹ[/blue] {message}", style="blue")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def print_command(command: str, title: str = "Generated Command") -> None:
|
|
57
|
+
"""
|
|
58
|
+
Print command with syntax highlighting.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
command: Command to display
|
|
62
|
+
title: Panel title
|
|
63
|
+
"""
|
|
64
|
+
syntax = Syntax(command, "bash", theme="monokai", line_numbers=False)
|
|
65
|
+
panel = Panel(syntax, title=title, border_style="cyan")
|
|
66
|
+
console.print(panel)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def print_config_table(config_dict: dict, title: str = "Configuration") -> None:
|
|
70
|
+
"""
|
|
71
|
+
Print configuration as a table.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
config_dict: Configuration dictionary
|
|
75
|
+
title: Table title
|
|
76
|
+
"""
|
|
77
|
+
table = Table(title=title, show_header=True, header_style="bold cyan")
|
|
78
|
+
table.add_column("Key", style="cyan", no_wrap=True)
|
|
79
|
+
table.add_column("Value", style="white")
|
|
80
|
+
|
|
81
|
+
def flatten_dict(d: dict, parent_key: str = "") -> dict:
|
|
82
|
+
"""Flatten nested dictionary."""
|
|
83
|
+
items = []
|
|
84
|
+
for k, v in d.items():
|
|
85
|
+
new_key = f"{parent_key}.{k}" if parent_key else k
|
|
86
|
+
if isinstance(v, dict):
|
|
87
|
+
items.extend(flatten_dict(v, new_key).items())
|
|
88
|
+
else:
|
|
89
|
+
items.append((new_key, v))
|
|
90
|
+
return dict(items)
|
|
91
|
+
|
|
92
|
+
flat_config = flatten_dict(config_dict)
|
|
93
|
+
for key, value in flat_config.items():
|
|
94
|
+
# Mask sensitive values
|
|
95
|
+
if any(sensitive in key.lower() for sensitive in ["key", "token", "password"]):
|
|
96
|
+
if value and value is not None:
|
|
97
|
+
display_value = "****" + str(value)[-4:] if len(str(value)) > 4 else "****"
|
|
98
|
+
else:
|
|
99
|
+
display_value = "[dim]not set[/dim]"
|
|
100
|
+
else:
|
|
101
|
+
display_value = str(value) if value is not None else "[dim]not set[/dim]"
|
|
102
|
+
|
|
103
|
+
table.add_row(key, display_value)
|
|
104
|
+
|
|
105
|
+
console.print(table)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def print_backend_status(backends: list) -> None:
|
|
109
|
+
"""
|
|
110
|
+
Print backend status table.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
backends: List of tuples (name, available, status)
|
|
114
|
+
"""
|
|
115
|
+
table = Table(title="Backend Status", show_header=True, header_style="bold cyan")
|
|
116
|
+
table.add_column("Backend", style="cyan", no_wrap=True)
|
|
117
|
+
table.add_column("Status", style="white")
|
|
118
|
+
table.add_column("Details", style="dim")
|
|
119
|
+
|
|
120
|
+
for name, available, status in backends:
|
|
121
|
+
status_icon = "[green]✓ Available[/green]" if available else "[red]✗ Unavailable[/red]"
|
|
122
|
+
table.add_row(name, status_icon, status)
|
|
123
|
+
|
|
124
|
+
console.print(table)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def confirm_execution(command: str) -> bool:
|
|
128
|
+
"""
|
|
129
|
+
Ask user to confirm command execution.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
command: Command to execute
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if user confirms, False otherwise
|
|
136
|
+
"""
|
|
137
|
+
print_command(command, "Command to Execute")
|
|
138
|
+
|
|
139
|
+
response = console.input("\n[yellow]Execute this command?[/yellow] [dim](y/n)[/dim]: ")
|
|
140
|
+
return response.lower() in ["y", "yes"]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def execute_command(command: str, dry_run: bool = False) -> Tuple[int, str, str]:
|
|
144
|
+
"""
|
|
145
|
+
Execute shell command.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
command: Command to execute
|
|
149
|
+
dry_run: If True, don't actually execute
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Tuple of (return_code, stdout, stderr)
|
|
153
|
+
"""
|
|
154
|
+
if dry_run:
|
|
155
|
+
print_info(f"Dry run mode - command not executed: {command}")
|
|
156
|
+
return (0, "", "")
|
|
157
|
+
|
|
158
|
+
try:
|
|
159
|
+
result = subprocess.run(
|
|
160
|
+
command,
|
|
161
|
+
shell=True,
|
|
162
|
+
capture_output=True,
|
|
163
|
+
text=True,
|
|
164
|
+
timeout=30,
|
|
165
|
+
)
|
|
166
|
+
return (result.returncode, result.stdout, result.stderr)
|
|
167
|
+
except subprocess.TimeoutExpired:
|
|
168
|
+
return (1, "", "Command timed out after 30 seconds")
|
|
169
|
+
except Exception as e:
|
|
170
|
+
return (1, "", str(e))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def print_execution_result(returncode: int, stdout: str, stderr: str) -> None:
|
|
174
|
+
"""
|
|
175
|
+
Print command execution result.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
returncode: Command return code
|
|
179
|
+
stdout: Standard output
|
|
180
|
+
stderr: Standard error
|
|
181
|
+
"""
|
|
182
|
+
if returncode == 0:
|
|
183
|
+
print_success("Command executed successfully")
|
|
184
|
+
if stdout:
|
|
185
|
+
console.print("\n[bold]Output:[/bold]")
|
|
186
|
+
console.print(stdout)
|
|
187
|
+
else:
|
|
188
|
+
print_error(f"Command failed with exit code {returncode}")
|
|
189
|
+
if stderr:
|
|
190
|
+
console.print("\n[bold red]Error:[/bold red]")
|
|
191
|
+
console.print(stderr, style="red")
|
|
192
|
+
if stdout:
|
|
193
|
+
console.print("\n[bold]Output:[/bold]")
|
|
194
|
+
console.print(stdout)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def check_command_exists(command: str) -> bool:
|
|
198
|
+
"""
|
|
199
|
+
Check if a command exists in PATH.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
command: Command name to check
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
True if command exists, False otherwise
|
|
206
|
+
"""
|
|
207
|
+
try:
|
|
208
|
+
result = subprocess.run(
|
|
209
|
+
["which", command],
|
|
210
|
+
capture_output=True,
|
|
211
|
+
text=True,
|
|
212
|
+
)
|
|
213
|
+
return result.returncode == 0
|
|
214
|
+
except Exception:
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def get_shell_type() -> str:
|
|
219
|
+
"""
|
|
220
|
+
Detect current shell type.
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
Shell name (bash, zsh, fish, etc.)
|
|
224
|
+
"""
|
|
225
|
+
shell = subprocess.run(
|
|
226
|
+
["echo", "$SHELL"],
|
|
227
|
+
capture_output=True,
|
|
228
|
+
text=True,
|
|
229
|
+
shell=True,
|
|
230
|
+
)
|
|
231
|
+
shell_path = shell.stdout.strip()
|
|
232
|
+
|
|
233
|
+
if "zsh" in shell_path:
|
|
234
|
+
return "zsh"
|
|
235
|
+
elif "bash" in shell_path:
|
|
236
|
+
return "bash"
|
|
237
|
+
elif "fish" in shell_path:
|
|
238
|
+
return "fish"
|
|
239
|
+
else:
|
|
240
|
+
return "unknown"
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def format_file_size(size_bytes: int) -> str:
|
|
244
|
+
"""
|
|
245
|
+
Format file size in human-readable format.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
size_bytes: Size in bytes
|
|
249
|
+
|
|
250
|
+
Returns:
|
|
251
|
+
Formatted size string
|
|
252
|
+
"""
|
|
253
|
+
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
254
|
+
if size_bytes < 1024.0:
|
|
255
|
+
return f"{size_bytes:.1f} {unit}"
|
|
256
|
+
size_bytes /= 1024.0
|
|
257
|
+
return f"{size_bytes:.1f} PB"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def is_dangerous_command(command: str) -> bool:
|
|
261
|
+
"""
|
|
262
|
+
Check if command is potentially dangerous.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
command: Command to check
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
True if command is dangerous, False otherwise
|
|
269
|
+
"""
|
|
270
|
+
dangerous_patterns = [
|
|
271
|
+
"rm -rf /",
|
|
272
|
+
"rm -rf /*",
|
|
273
|
+
"mkfs",
|
|
274
|
+
"dd if=/dev/zero",
|
|
275
|
+
"> /dev/sda",
|
|
276
|
+
":(){ :|:& };:", # Fork bomb
|
|
277
|
+
"chmod -R 777 /",
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
command_lower = command.lower().strip()
|
|
281
|
+
|
|
282
|
+
for pattern in dangerous_patterns:
|
|
283
|
+
if pattern in command_lower:
|
|
284
|
+
return True
|
|
285
|
+
|
|
286
|
+
return False
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def print_danger_warning(command: str) -> None:
|
|
290
|
+
"""
|
|
291
|
+
Print warning for dangerous command.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
command: Dangerous command
|
|
295
|
+
"""
|
|
296
|
+
console.print(
|
|
297
|
+
"\n[bold red]⚠️ WARNING: POTENTIALLY DANGEROUS COMMAND ⚠️[/bold red]\n",
|
|
298
|
+
style="on red"
|
|
299
|
+
)
|
|
300
|
+
console.print(
|
|
301
|
+
"This command may cause system damage or data loss.\n"
|
|
302
|
+
"Please review it carefully before execution.\n",
|
|
303
|
+
style="bold yellow"
|
|
304
|
+
)
|
|
305
|
+
print_command(command, "⚠️ Dangerous Command")
|