JARVIS: asistente de voz local para Linux
Nucleo propio: oye con whisper.cpp, piensa con un modelo de Ollama, habla con Piper, y hace RAG sobre los apuntes del usuario. 100% local, sin cuentas ni claves. Escrito bajo una restriccion dura, 4 GB de VRAM: el cerebro y whisper comparten tarjeta y solo caben porque estan dimensionados para ello. El RAG usa embeddings estaticos con busqueda hibrida; la voz clonada se sirve de una cache de frases. Incluye instalador (install.sh), requisitos, y documentacion del stack, del manejo de root y de las acciones. Los apuntes indexados y el diario NO se incluyen: son privados y el .gitignore los bloquea.
This commit is contained in:
commit
8e4bc8ad94
125 changed files with 25033 additions and 0 deletions
190
config/descubrir.py
Executable file
190
config/descubrir.py
Executable file
|
|
@ -0,0 +1,190 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Cataloga las herramientas de COFRE para que JARVIS sepa cuales hay.
|
||||
|
||||
python3 ~/COFRE/CODERS/JARVIS/config/descubrir.py
|
||||
|
||||
Produce jarvis-herramientas.json: una ficha por herramienta con su nombre, que
|
||||
hace, como se invoca y en que carpeta vive. NO es el catalogo de acciones
|
||||
rapidas (esas son las ~20 que dices por voz a diario, escritas a mano). Esto es
|
||||
el registro que el modelo consulta cuando le pides algo que no esta en el
|
||||
catalogo, para componer el comando en vez de inventarselo.
|
||||
|
||||
Que hace y que NO hace:
|
||||
- SI: encuentra los puntos de entrada reales (scripts con shebang y binarios
|
||||
en los primeros niveles de cada proyecto, no los modulos internos).
|
||||
- SI: saca una descripcion del README, del docstring o de la cabecera, SIN
|
||||
ejecutar nada. Ejecutar un script desconocido con --help para leerlo seria
|
||||
ejecutar un script desconocido, que es justo lo que no se quiere hacer a
|
||||
ciegas.
|
||||
- NO: ejecuta ninguna herramienta. Esto solo mira.
|
||||
|
||||
El resultado se revisa a mano antes de usarlo: es material de terceros y algun
|
||||
"tool" sera un ejemplo o un exploit, no algo que quieras ofrecer.
|
||||
"""
|
||||
import ast
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import stat
|
||||
import sys
|
||||
|
||||
COFRE = os.path.expanduser("~/COFRE")
|
||||
SALIDA = os.path.expanduser("~/COFRE/CODERS/JARVIS/config/jarvis-herramientas.json")
|
||||
|
||||
CARPETAS = ["CODERS", "PENTESTERS", "AUTOMATAS", "DOSER", "HARD-WARERS",
|
||||
"PHISHERS", "PROTECTORS", "REVERSERS", "SNEAKERS"]
|
||||
|
||||
# Solo los primeros niveles: un punto de entrada vive cerca de la raiz del
|
||||
# proyecto, no enterrado en lib/ o en site-packages.
|
||||
PROFUNDIDAD = 3
|
||||
|
||||
FUERA = {"node_modules", ".git", "venv", ".venv", "site-packages", "__pycache__",
|
||||
"lib", "libs", "tests", "test", "docs", "examples", "vendor", "dist",
|
||||
"build", ".tox", "exploitdb"}
|
||||
|
||||
# Nombres que casi nunca son la herramienta en si
|
||||
IGNORAR_NOMBRES = {"setup.py", "conftest.py", "__init__.py", "test.py",
|
||||
"tests.py", "wsgi.py", "manage.py", "conf.py"}
|
||||
|
||||
|
||||
def es_ejecutable(ruta: str) -> bool:
|
||||
try:
|
||||
return bool(os.stat(ruta).st_mode & stat.S_IXUSR)
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def tiene_shebang(ruta: str) -> bool:
|
||||
try:
|
||||
with open(ruta, "rb") as f:
|
||||
return f.read(2) == b"#!"
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def descripcion_readme(carpeta: str) -> str:
|
||||
"""Primera linea con contenido de un README cercano."""
|
||||
for nombre in ("README.md", "README.rst", "README.txt", "README"):
|
||||
ruta = os.path.join(carpeta, nombre)
|
||||
if not os.path.exists(ruta):
|
||||
continue
|
||||
try:
|
||||
with open(ruta, errors="ignore") as f:
|
||||
for linea in f:
|
||||
limpia = linea.strip().lstrip("#").lstrip(">").strip()
|
||||
# saltar titulos de una palabra, badges y separadores
|
||||
if len(limpia) > 25 and not limpia.startswith(("![", "[!", "---", "===")):
|
||||
return limpia[:200]
|
||||
except OSError:
|
||||
pass
|
||||
return ""
|
||||
|
||||
|
||||
def descripcion_script(ruta: str) -> str:
|
||||
"""Docstring del modulo (Python) o cabecera de comentarios (shell)."""
|
||||
try:
|
||||
with open(ruta, errors="ignore") as f:
|
||||
texto = f.read(4000)
|
||||
except OSError:
|
||||
return ""
|
||||
|
||||
if ruta.endswith(".py"):
|
||||
try:
|
||||
doc = ast.get_docstring(ast.parse(texto))
|
||||
if doc:
|
||||
return doc.strip().split("\n")[0][:200]
|
||||
except (SyntaxError, ValueError):
|
||||
pass
|
||||
|
||||
# cabecera de comentarios: las primeras lineas que empiezan por # tras el shebang
|
||||
lineas = []
|
||||
for linea in texto.splitlines()[1:8]:
|
||||
s = linea.strip()
|
||||
if s.startswith("#"):
|
||||
limpio = s.lstrip("#").strip()
|
||||
if len(limpio) > 15:
|
||||
lineas.append(limpio)
|
||||
elif lineas:
|
||||
break
|
||||
return " ".join(lineas)[:200]
|
||||
|
||||
|
||||
def recorre():
|
||||
herramientas = []
|
||||
vistos = set()
|
||||
|
||||
for carpeta in CARPETAS:
|
||||
raiz = os.path.join(COFRE, carpeta)
|
||||
if not os.path.isdir(raiz):
|
||||
continue
|
||||
|
||||
for dirpath, dirnames, ficheros in os.walk(raiz):
|
||||
profundidad = dirpath[len(raiz):].count(os.sep)
|
||||
if profundidad >= PROFUNDIDAD:
|
||||
dirnames[:] = []
|
||||
continue
|
||||
dirnames[:] = [d for d in dirnames
|
||||
if d not in FUERA and not d.startswith(".")]
|
||||
|
||||
for nombre in ficheros:
|
||||
if nombre in IGNORAR_NOMBRES:
|
||||
continue
|
||||
ruta = os.path.join(dirpath, nombre)
|
||||
ext = os.path.splitext(nombre)[1].lower()
|
||||
|
||||
es_script = ext in (".py", ".sh") and (tiene_shebang(ruta) or es_ejecutable(ruta))
|
||||
es_binario = not ext and es_ejecutable(ruta) and not tiene_shebang(ruta)
|
||||
if not (es_script or es_binario):
|
||||
continue
|
||||
|
||||
clave = nombre.lower()
|
||||
if clave in vistos:
|
||||
continue
|
||||
vistos.add(clave)
|
||||
|
||||
desc = descripcion_script(ruta) if es_script else ""
|
||||
if not desc:
|
||||
desc = descripcion_readme(dirpath)
|
||||
|
||||
herramientas.append({
|
||||
"nombre": os.path.splitext(nombre)[0],
|
||||
"carpeta": carpeta,
|
||||
"ruta": ruta,
|
||||
"tipo": "python" if ext == ".py" else "shell" if ext == ".sh" else "binario",
|
||||
"descripcion": desc,
|
||||
"invocar": (f"python3 {ruta}" if ext == ".py"
|
||||
else f"bash {ruta}" if ext == ".sh"
|
||||
else ruta),
|
||||
})
|
||||
|
||||
return herramientas
|
||||
|
||||
|
||||
def main():
|
||||
print("recorriendo COFRE (solo lectura, no se ejecuta nada)...\n", flush=True)
|
||||
tools = recorre()
|
||||
|
||||
porcarpeta = {}
|
||||
con_desc = 0
|
||||
for t in tools:
|
||||
porcarpeta[t["carpeta"]] = porcarpeta.get(t["carpeta"], 0) + 1
|
||||
if t["descripcion"]:
|
||||
con_desc += 1
|
||||
|
||||
for carpeta in CARPETAS:
|
||||
if carpeta in porcarpeta:
|
||||
print(f" {carpeta:<13} {porcarpeta[carpeta]:>4} herramientas")
|
||||
print(f"\n total {len(tools)}, con descripcion {con_desc} "
|
||||
f"({100*con_desc//max(1,len(tools))} %)")
|
||||
|
||||
with open(SALIDA, "w") as f:
|
||||
json.dump({"version": 1, "herramientas": tools}, f,
|
||||
indent=2, ensure_ascii=False)
|
||||
print(f"\nescrito {SALIDA}")
|
||||
print("REVISALO a mano antes de usarlo: es material de terceros y algun")
|
||||
print("'tool' sera un ejemplo o un exploit, no algo que quieras ofrecer.")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue