#!/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())