#!/usr/bin/env python3 """Prepara el corpus de COFRE para que JARVIS lo consulte. python3 ~/COFRE/CODERS/JARVIS/config/indexar.py # ver que entraria python3 ~/COFRE/CODERS/JARVIS/config/indexar.py --hacerlo # copiarlo python3 ~/COFRE/CODERS/JARVIS/config/indexar.py --hacerlo --solo-notas Que hace: recorre las carpetas de COFRE, se queda SOLO con lo que es conocimiento y lo copia a la carpeta de documentos de Newelle, que es de donde lee el RAG. Despues hay que pulsar "Update Index" en los ajustes de la app. Por que hace falta filtrar. Medido en esta maquina: COFRE entero (las nueve carpetas) ~120 GB de eso, ISOs, maquinas virtuales, APKs, audio y un diccionario de contraseñas de 9,1 GB ~100 GB texto de menos de 100 KB 76,6 MB en 35 098 ficheros quitando exploitdb 20,8 MB en 4 548 ficheros exploitdb se queda fuera a proposito: son 32 000 ficheros que aplastarian el indice y enterrarian tus notas, y ademas NO hacen falta ahi. Tienes `searchsploit` instalado, que busca en ellos de forma exacta e instantanea. Un indice de vectores es peor que una busqueda exacta cuando lo que buscas es el nombre literal de un exploit. Los nombres se aplanan conservando la ruta —PENTESTERS__OSCP__nmap.md— para que al responder se pueda ver de donde salio cada cosa. """ import argparse import hashlib import os import shutil import sys COFRE = os.path.expanduser("~/COFRE") DESTINO = os.path.expanduser( "~/.var/app/io.github.qwersyk.Newelle/config/models/documents") CARPETAS = ["CODERS", "PENTESTERS", "AUTOMATAS", "DOSER", "HARD-WARERS", "PHISHERS", "PROTECTORS", "REVERSERS", "SNEAKERS"] EXTENSIONES = {".md", ".txt", ".rst", ".adoc"} # Por tamaño: por encima de esto ya no es prosa, son datos. El diccionario # kaonashi.txt son 9,1 GB de contraseñas. TAMANO_MAXIMO = 100 * 1024 TAMANO_MINIMO = 120 # menos de esto no dice nada # Carpetas que no aportan conocimiento y si mucho ruido FUERA = { "exploitdb", # 32 000 ficheros; para eso esta searchsploit "node_modules", ".git", "venv", ".venv", "site-packages", "__pycache__", "isos", "VirtualBox VMs", "build", "dist", ".cache", "target", "flatpak-app", ".flatpak-builder", "rag_cache", "entrenamiento", } # Ficheros que son plantilla o licencia y solo ensucian NOMBRES_FUERA = {"LICENSE.txt", "LICENSE.md", "CHANGELOG.md", "requirements.txt"} def interesa(ruta: str, nombre: str) -> bool: ext = os.path.splitext(nombre)[1].lower() if ext not in EXTENSIONES or nombre in NOMBRES_FUERA: return False try: tam = os.path.getsize(ruta) except OSError: return False return TAMANO_MINIMO <= tam <= TAMANO_MAXIMO def recorre(solo_notas: bool): """Devuelve [(origen, nombre_plano)] de todo lo que merece indexarse.""" salida = [] 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): dirnames[:] = [d for d in dirnames if d not in FUERA and not d.startswith(".")] for nombre in ficheros: if solo_notas and not nombre.lower().endswith(".md"): continue origen = os.path.join(dirpath, nombre) if not interesa(origen, nombre): continue relativa = os.path.relpath(origen, COFRE) plano = relativa.replace(os.sep, "__") if len(plano) > 180: # algunos nombres son kilometricos corto = hashlib.sha256(plano.encode()).hexdigest()[:8] plano = plano[:150] + "_" + corto + os.path.splitext(plano)[1] salida.append((origen, plano)) return salida def main(): ap = argparse.ArgumentParser() ap.add_argument("--hacerlo", action="store_true", help="copiar de verdad") ap.add_argument("--solo-notas", action="store_true", help="solo .md, sin los .txt sueltos") ap.add_argument("--limpiar", action="store_true", help="vaciar el destino antes de copiar") args = ap.parse_args() print("recorriendo COFRE...", flush=True) lista = recorre(args.solo_notas) porcarpeta, total = {}, 0 for origen, _ in lista: carpeta = os.path.relpath(origen, COFRE).split(os.sep)[0] tam = os.path.getsize(origen) n, b = porcarpeta.get(carpeta, (0, 0)) porcarpeta[carpeta] = (n + 1, b + tam) total += tam print(f"\n{'carpeta':<14}{'ficheros':>10}{'texto':>12}") for carpeta in CARPETAS: if carpeta in porcarpeta: n, b = porcarpeta[carpeta] print(f"{carpeta:<14}{n:>10}{b/1048576:>10.1f} MB") print(f"{'TOTAL':<14}{len(lista):>10}{total/1048576:>10.1f} MB") print(f"\nfragmentos de 256 tokens: ~{total // 1024}") if not args.hacerlo: print("\nesto es solo el recuento; para copiarlo: --hacerlo") return 0 if args.limpiar and os.path.isdir(DESTINO): shutil.rmtree(DESTINO) os.makedirs(DESTINO, exist_ok=True) copiados = saltados = 0 for origen, plano in lista: destino = os.path.join(DESTINO, plano) try: # solo si cambio: reindexar 4500 ficheros iguales no tiene sentido if (os.path.exists(destino) and os.path.getmtime(destino) >= os.path.getmtime(origen)): saltados += 1 continue shutil.copy2(origen, destino) copiados += 1 except OSError as e: print(f" no se pudo copiar {plano}: {e}") print(f"\n{copiados} copiados, {saltados} ya estaban al dia") print(f"en {DESTINO}") print("\nAhora, en Newelle: Ajustes -> RAG -> Update Index") print("La primera vez tarda; son unos 21 000 fragmentos.") return 0 if __name__ == "__main__": sys.exit(main())