saber: manuales cosechados e indice publico pre-construido

- manuales.py cosecha el --help de las herramientas que no traen man page
  (ffuf, sqlmap, suite impacket, netexec, hydra...) y lo escribe como markdown
  en conocimiento/09 - Manuales/. Es la referencia de opciones que la man page
  no da; complementa el cheatsheet de invocaciones comunes.
- indexa.py --publico construye saber-publico.jsonl: glosario + pack de
  conocimiento con los vectores ya calculados, 100% publico y portable.
- busca.py usa ese indice publico como fallback cuando no hay indice personal,
  asi un clon recien hecho consulta al instante sin reindexar nada.
- .gitignore deja pasar saber-publico.jsonl; el personal (saber.jsonl) sigue
  fuera.
- docs/rag.md y el README del pack documentan ambas cosas.
This commit is contained in:
sito 2026-08-16 18:43:56 +02:00
parent 1dfb3513e6
commit 9fa0a40edf
12 changed files with 1206 additions and 45 deletions

View file

@ -36,6 +36,10 @@ import sys
AQUI = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
COFRE = os.path.expanduser("~/COFRE")
INDICE = os.path.join(AQUI, "datos", "saber.jsonl")
# El indice PUBLICO y portable: solo glosario + pack de conocimiento, sin nada de
# la maquina (ni COFRE, ni man pages, ni servidores). Se commitea con vectores ya
# calculados para que un clon recien hecho consulte al instante, sin reindexar.
INDICE_PUBLICO = os.path.join(AQUI, "datos", "saber-publico.jsonl")
# Los perfiles que se indexan. Cada uno es una carpeta de ~/COFRE.
PERFILES = ["PENTESTERS", "PHISHERS", "REVERSERS", "PROTECTORS", "HARD-WARERS",
@ -145,6 +149,66 @@ def trocea(ruta):
yield buffer.strip()
def _conocimiento():
"""El pack de metodologia y manuales que viene con el repo (conocimiento/)."""
fragmentos = []
base_con = os.path.join(os.path.dirname(os.path.abspath(__file__)), "conocimiento")
if not os.path.isdir(base_con):
return fragmentos
for raiz, _, ficheros in os.walk(base_con):
m = re.match(r"^\d{2}\s*-\s*(.+)$", os.path.basename(raiz))
tema = m.group(1).strip().lower() if m else ""
for fich in ficheros:
if not fich.endswith(".md"):
continue
for trozo in trocea(os.path.join(raiz, fich)):
fragmentos.append({
"tipo": "metodologia", "herramienta": fich[:-3],
"perfil": "PENTESTERS", "tema": tema,
"fichero": os.path.join("conocimiento", os.path.relpath(
os.path.join(raiz, fich), base_con)),
"texto": trozo})
return fragmentos
def _guarda_indice(todo, ruta):
"""Calcula los vectores de las entradas y las escribe a un indice jsonl."""
print(" cargando el modelo de embeddings...", flush=True)
from model2vec import StaticModel
modelo = StaticModel.from_pretrained("minishlab/potion-multilingual-128M")
print(" calculando vectores...", flush=True)
vectores = modelo.encode([e["texto"] for e in todo], show_progress_bar=False)
os.makedirs(os.path.dirname(ruta), exist_ok=True)
with open(ruta, "w", encoding="utf-8") as f:
for entrada, vec in zip(todo, vectores):
entrada["v"] = [round(float(x), 5) for x in vec]
f.write(json.dumps(entrada, ensure_ascii=False) + "\n")
print(f" indice guardado: {ruta} ({os.path.getsize(ruta)//1024} KB)")
def construye_publico(solo_cuenta=False):
"""El indice PORTABLE que se sube al repo: glosario + pack de conocimiento.
Nada de la maquina ni COFRE, ni man pages, ni servidores SSH, asi que es
100 % publico y vale igual en cualquier equipo. Un clon lo usa tal cual hasta
que corre su propio indexa.py; asi el trabajo de indexar el pack no se repite.
"""
try:
from saber import glosario
except ImportError:
import glosario
glo = list(glosario.entradas())
con = _conocimiento()
todo = glo + con
print(f" glosario {len(glo)} + conocimiento {len(con)} = {len(todo)} entradas publicas")
if solo_cuenta:
return 0
_guarda_indice(todo, INDICE_PUBLICO)
return 0
def construye(solo_cuenta=False, solo_cofre=False):
fichas, fragmentos = [], []
@ -195,23 +259,8 @@ def construye(solo_cuenta=False, solo_cofre=False):
# esta scrubeado, asi que un clon recien hecho —sin apuntes propios de COFRE—
# ya sabe de pentesting. En la maquina del usuario se solapa con sus apuntes
# y el deduplicado de busca lo colapsa.
conocimiento = []
base_con = os.path.join(os.path.dirname(os.path.abspath(__file__)), "conocimiento")
if os.path.isdir(base_con):
for raiz, _, ficheros in os.walk(base_con):
m = re.match(r"^\d{2}\s*-\s*(.+)$", os.path.basename(raiz))
tema = m.group(1).strip().lower() if m else ""
for fich in ficheros:
if not fich.endswith(".md"):
continue
for trozo in trocea(os.path.join(raiz, fich)):
conocimiento.append({
"tipo": "metodologia", "herramienta": fich[:-3],
"perfil": "PENTESTERS", "tema": tema,
"fichero": os.path.join("conocimiento", os.path.relpath(
os.path.join(raiz, fich), base_con)),
"texto": trozo})
print(f" CONOCIMIENTO {len(conocimiento):5d} fragmentos (pack del repo)")
conocimiento = _conocimiento()
print(f" CONOCIMIENTO {len(conocimiento):5d} fragmentos (pack del repo)")
todo = fichas + fragmentos + sistema + conocimiento
print(f"\n total: {len(fichas)} fichas + {len(fragmentos)} apuntes + "
@ -219,19 +268,7 @@ def construye(solo_cuenta=False, solo_cofre=False):
if solo_cuenta:
return 0
print(" cargando el modelo de embeddings...", flush=True)
from model2vec import StaticModel
modelo = StaticModel.from_pretrained("minishlab/potion-multilingual-128M")
print(" calculando vectores...", flush=True)
vectores = modelo.encode([e["texto"] for e in todo], show_progress_bar=False)
os.makedirs(os.path.dirname(INDICE), exist_ok=True)
with open(INDICE, "w", encoding="utf-8") as f:
for entrada, vec in zip(todo, vectores):
entrada["v"] = [round(float(x), 5) for x in vec]
f.write(json.dumps(entrada, ensure_ascii=False) + "\n")
print(f" indice guardado: {INDICE} ({os.path.getsize(INDICE)//1024} KB)")
_guarda_indice(todo, INDICE)
return 0
@ -246,7 +283,11 @@ def main() -> int:
help="di que encontrarias, sin indexar")
p.add_argument("--solo-cofre", action="store_true",
help="indexar solo COFRE, sin la documentacion del sistema")
p.add_argument("--publico", action="store_true",
help="construir el indice portable (glosario + conocimiento) que se sube al repo")
a = p.parse_args()
if a.publico:
return construye_publico(solo_cuenta=a.cuenta)
return construye(solo_cuenta=a.cuenta, solo_cofre=a.solo_cofre)