Skip to content

Commit 81387ea

Browse files
authored
Add files via upload
1 parent d2a4f28 commit 81387ea

3 files changed

Lines changed: 272 additions & 0 deletions

File tree

manexplainer.mkv

1.57 MB
Binary file not shown.

manexplainer.png

41.1 KB
Loading

manexplainer.py

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
#!/usr/bin/env python3
2+
from os import system, path, chmod
3+
from subprocess import check_output, STDOUT, CalledProcessError, run
4+
from argparse import ArgumentParser
5+
from sys import exit, argv
6+
from rich.console import Console
7+
from rich.markdown import Markdown
8+
from time import sleep, time
9+
import threading
10+
import asyncio
11+
from google import genai
12+
import os
13+
14+
console = Console()
15+
16+
def clear_screen():
17+
system("clear")
18+
console.print("[bold green]~ ManExplainer v1.0.0 ~[/bold green]\n")
19+
20+
def delayed_print(text, delay=0.5):
21+
print(text)
22+
sleep(delay)
23+
24+
def install_manexplainer():
25+
"""Install manexplainer as a system command with proper line ending handling"""
26+
script_path = path.abspath(__file__)
27+
install_path = "/usr/local/bin/manexplainer"
28+
29+
clear_screen()
30+
console.print("[bold]Instalando ManExplainer...[/bold]\n")
31+
32+
try:
33+
# Leer el contenido del script actual
34+
with open(script_path, 'r', encoding='utf-8') as f:
35+
content = f.read()
36+
37+
# Asegurar que tenga el shebang correcto y formato de línea Unix
38+
lines = content.replace('\r\n', '\n').split('\n')
39+
40+
# Comprobar si la primera línea es el shebang
41+
if not lines[0].startswith('#!/usr/bin/env python3'):
42+
lines.insert(0, '#!/usr/bin/env python3')
43+
44+
# Crear archivo temporal con el contenido correcto
45+
temp_file = "/tmp/manexplainer_temp"
46+
with open(temp_file, 'w', encoding='utf-8', newline='\n') as f:
47+
f.write('\n'.join(lines))
48+
49+
# Instalar usando sudo
50+
print(f"Instalando en: {install_path}")
51+
commands = [
52+
f"sudo cp {temp_file} {install_path}",
53+
f"sudo chmod +x {install_path}"
54+
]
55+
56+
for cmd in commands:
57+
result = system(cmd)
58+
if result != 0:
59+
console.print(f"\n[bold red]✗ Error al ejecutar: {cmd}[/bold red]")
60+
return False
61+
62+
# Verificar instalación
63+
if path.exists(install_path):
64+
console.print("\n[bold green]✓ ManExplainer instalado correctamente[/bold green]")
65+
console.print("\nUso: manexplainer --command COMANDO --query PREGUNTA")
66+
console.print("\nEjemplos:")
67+
console.print(" manexplainer --command 'ls -la' --query 'Qué significan las columnas?'")
68+
console.print(" manexplainer --command grep --query 'Cómo busco en múltiples archivos?'")
69+
return True
70+
else:
71+
console.print("\n[bold red]✗ Error al instalar ManExplainer[/bold red]")
72+
return False
73+
74+
except Exception as e:
75+
console.print(f"\n[bold red]✗ Error durante la instalación: {str(e)}[/bold red]")
76+
return False
77+
78+
async def send_request_with_gemini(prompt, max_retries=3, timeout_seconds=30):
79+
"""Send request to Gemini AI model with timeout and retry logic"""
80+
retries = 0
81+
final_response = ""
82+
83+
# Initialize Gemini client
84+
api_key = "YOUR_GEMINI_API_KEY"
85+
client = genai.Client(api_key=api_key)
86+
model = "gemini-2.0-flash-exp"
87+
config = {"response_modalities": ["TEXT"]}
88+
89+
while retries < max_retries:
90+
try:
91+
if retries > 0:
92+
delayed_print(f"Reintentando solicitud ({retries}/{max_retries})...")
93+
94+
final_response = ""
95+
request_failed = False
96+
request_done = False
97+
98+
# Create an event for timeout control
99+
timeout_event = threading.Event()
100+
101+
# Function to check timeout
102+
def check_timeout():
103+
sleep(timeout_seconds)
104+
if not timeout_event.is_set() and not request_done:
105+
nonlocal request_failed
106+
request_failed = True
107+
delayed_print("\nTiempo de espera agotado. Cancelando solicitud...")
108+
109+
# Start timeout thread
110+
timeout_thread = threading.Thread(target=check_timeout)
111+
timeout_thread.daemon = True
112+
timeout_thread.start()
113+
114+
try:
115+
async with client.aio.live.connect(model=model, config=config) as session:
116+
await session.send_client_content(
117+
turns={"role": "user", "parts": [{"text": prompt}]}, turn_complete=True
118+
)
119+
120+
# Reset timeout on connection
121+
timeout_event.set()
122+
timeout_event.clear()
123+
124+
if final_response == '':
125+
clear_screen()
126+
127+
async for response in session.receive():
128+
if request_failed:
129+
break
130+
131+
if response.text is not None:
132+
# Reset timeout on data received
133+
timeout_event.set()
134+
timeout_event.clear()
135+
136+
print(response.text, end="", flush=True)
137+
final_response += response.text
138+
139+
request_done = True
140+
timeout_event.set()
141+
142+
except Exception as e:
143+
delayed_print(f"Error en la solicitud: {str(e)}")
144+
request_failed = True
145+
146+
# If request completed successfully and we have a response, exit the loop
147+
if request_done and final_response:
148+
return final_response
149+
150+
# If failed due to timeout or error, increment retry counter
151+
if request_failed or not final_response:
152+
retries += 1
153+
if retries >= max_retries:
154+
delayed_print(f"Error después de {max_retries} intentos. No se pudo obtener respuesta.")
155+
exit(1)
156+
delayed_print("Preparando nuevo intento en 2 segundos...")
157+
sleep(2)
158+
159+
except Exception as e:
160+
delayed_print(f"Error inesperado: {str(e)}")
161+
retries += 1
162+
if retries >= max_retries:
163+
delayed_print(f"Error después de {max_retries} intentos.")
164+
exit(1)
165+
sleep(2)
166+
167+
return final_response
168+
169+
def print_help():
170+
"""Display help information"""
171+
clear_screen()
172+
console.print("[bold]ManExplainer[/bold] - Consulta documentación de comandos con IA\n")
173+
console.print("Uso:")
174+
console.print(" manexplainer --command COMANDO --query PREGUNTA")
175+
console.print("\nArgumentos:")
176+
console.print(" --command COMANDO Comando de terminal para obtener su manual o ejecutar")
177+
console.print(" --query PREGUNTA Pregunta que se le hará a la IA basada en el manual")
178+
console.print("\nEjemplos:")
179+
console.print(" manexplainer --command 'ls -la' --query 'Qué significan las columnas?'")
180+
console.print(" manexplainer --command grep --query 'Cómo busco en múltiples archivos?'")
181+
console.print("\nPara instalar:")
182+
console.print(" python3 manexplainer.py install")
183+
184+
async def main():
185+
# Check if running with "install" argument
186+
if len(argv) > 1 and argv[1] == "install":
187+
install_manexplainer()
188+
exit(0)
189+
190+
# Check if no arguments were provided
191+
if len(argv) == 1:
192+
print_help()
193+
exit(0)
194+
195+
start_time = time()
196+
clear_screen()
197+
delayed_print('Leyendo documentación...')
198+
199+
# Argumentos CLI
200+
parser = ArgumentParser(description="Consulta documentación de comandos con IA.")
201+
parser.add_argument("--command", nargs=1, required=True, help="Comando de terminal para obtener su manual o ejecutar.")
202+
parser.add_argument("--query", nargs=1, required=True, help="Pregunta que se le hará a la IA basada en el manual.")
203+
204+
try:
205+
args = parser.parse_args()
206+
except SystemExit:
207+
print_help()
208+
exit(1)
209+
210+
# Validar que --query tenga un valor
211+
if len(args.query) != 1:
212+
print("Error: --query debe tener exactamente un valor.")
213+
exit(1)
214+
215+
command = ''.join(args.command).split(' ')
216+
command_name = command[0]
217+
query = args.query[0]
218+
219+
# Verificar si el comando tiene argumentos para ejecutarlo en lugar de usar man
220+
if len(command) > 1:
221+
delayed_print(f"Ejecutando comando: {' '.join(command)}")
222+
try:
223+
# Ejecutar el comando completo con sus argumentos
224+
cmd_output = check_output(command, stderr=STDOUT)
225+
cmd_text = cmd_output.decode('utf-8')
226+
227+
# Armar prompt con la salida del comando + consulta
228+
prompt = f"""Tengo la siguiente salida del comando `{' '.join(command)}`:
229+
230+
{cmd_text}
231+
232+
Mi pregunta es: {query}
233+
Por favor se conciso, muy técnico y resuelve mi duda usando la menor cantidad de tokens posible, se técnico también.
234+
"""
235+
except CalledProcessError as e:
236+
print(f"Error ejecutando '{' '.join(command)}':\n{e.output.decode('utf-8')}")
237+
exit(1)
238+
else:
239+
# Obtener salida del comando man si solo hay un argumento
240+
delayed_print(f"Obteniendo manual para: {command_name}")
241+
try:
242+
man_output = check_output(['man', command_name], stderr=STDOUT)
243+
man_text = man_output.decode('utf-8')
244+
245+
# Armar prompt con la documentación + consulta
246+
prompt = f"""Tengo el siguiente manual de Linux para el comando `{command_name}`:
247+
248+
{man_text}
249+
250+
Mi pregunta es: {query}
251+
Por favor se conciso, muy técnico y resuelve mi duda usando la menor cantidad de tokens posible, se técnico también.
252+
"""
253+
except CalledProcessError as e:
254+
print(f"Error ejecutando 'man {command_name}':\n{e.output.decode('utf-8')}")
255+
exit(1)
256+
257+
clear_screen()
258+
delayed_print('Analizando tu pregunta...\n\n')
259+
260+
# Enviar solicitud con manejo de timeout y reintentos
261+
final_response = await send_request_with_gemini(prompt)
262+
263+
# Mostrar salida final con Markdown y encabezado
264+
clear_screen()
265+
console.print(Markdown(final_response))
266+
267+
# Mostrar tiempo de ejecución
268+
elapsed = time() - start_time
269+
console.print(f"\n[bold green]⏱ Tiempo de respuesta:[/bold green] {elapsed:.2f} segundos\n")
270+
271+
if __name__ == '__main__':
272+
asyncio.run(main())

0 commit comments

Comments
 (0)