instalado.py lee los .desktop y los paquetes para saber que hay en el equipo y para que sirve cada cosa. indexa.py lo engancha, y con el los docs markdown de las herramientas clonadas en CODERS. Las dos fuentes son personales: se quedan en el indice de la maquina y no entran en el publico. busca_cli.py expone el mismo motor que usa el cerebro (saber.busca) para consultar el indice desde la terminal, con --n y --json. reentrena_noche.sh encadena las fases del reentrenamiento largo y las deja corriendo horas; cada fase es reanudable por su cuenta. eval del indice vivo: hit@1 43/57 (75%), hit@5 55/57 (96%), sim 0.909.
140 lines
4.8 KiB
Python
Executable file
140 lines
4.8 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Indexa las aplicaciones instaladas en ESTA maquina: que hay y para que sirve.
|
|
|
|
./instalado.py di cuantas entradas saldrian
|
|
./instalado.py --volcado imprime unas cuantas de muestra
|
|
|
|
Dos fuentes, ambas locales:
|
|
1. Los .desktop (/usr/share/applications, ~/.local, flatpak): las apps que el
|
|
usuario ve y lanza, con su nombre, para que sirven (Comment) y el comando.
|
|
2. Los paquetes que el usuario instalo a mano (apt-mark showmanual), con su
|
|
descripcion de una linea. No las dependencias: eso es ruido.
|
|
|
|
Es privado por definicion —dice que software tiene esta maquina—, asi que
|
|
indexa.py lo mete SOLO en el indice personal, nunca en el publico ni en gitea.
|
|
"""
|
|
import glob
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
|
|
DIRS_DESKTOP = [
|
|
"/usr/share/applications",
|
|
os.path.expanduser("~/.local/share/applications"),
|
|
"/var/lib/flatpak/exports/share/applications",
|
|
os.path.expanduser("~/.local/share/flatpak/exports/share/applications"),
|
|
"/var/lib/snapd/desktop/applications",
|
|
]
|
|
|
|
|
|
def _campo(texto, clave):
|
|
m = re.search(r"^%s=(.+)$" % re.escape(clave), texto, re.M)
|
|
return m.group(1).strip() if m else ""
|
|
|
|
|
|
def _apps_desktop():
|
|
vistos = set()
|
|
for d in DIRS_DESKTOP:
|
|
for ruta in sorted(glob.glob(os.path.join(d, "*.desktop"))):
|
|
base = os.path.basename(ruta)
|
|
if base in vistos:
|
|
continue
|
|
vistos.add(base)
|
|
try:
|
|
with open(ruta, encoding="utf-8", errors="ignore") as f:
|
|
txt = f.read()
|
|
except OSError:
|
|
continue
|
|
if _campo(txt, "NoDisplay").lower() == "true":
|
|
continue
|
|
nombre = _campo(txt, "Name")
|
|
if not nombre:
|
|
continue
|
|
comentario = _campo(txt, "Comment") or _campo(txt, "GenericName")
|
|
categorias = _campo(txt, "Categories").replace(";", ", ").strip(", ")
|
|
exe = re.sub(r"%[a-zA-Z]", "", _campo(txt, "Exec")).strip()
|
|
cmd = exe.split()[0] if exe else ""
|
|
partes = [f"{nombre}"]
|
|
if comentario:
|
|
partes.append(f"— {comentario}")
|
|
if cmd:
|
|
partes.append(f"(se lanza con: {cmd})")
|
|
if categorias:
|
|
partes.append(f"[{categorias}]")
|
|
yield {
|
|
"tipo": "instalado", "herramienta": nombre, "perfil": "SISTEMA",
|
|
"publico": False, "tema": categorias.lower(),
|
|
"fichero": f"app instalada: {base}",
|
|
"texto": "Aplicacion instalada: " + " ".join(partes),
|
|
}
|
|
|
|
|
|
def _paquetes_manuales():
|
|
"""Los paquetes apt que instalo el usuario (no dependencias), con su resumen."""
|
|
try:
|
|
man = subprocess.run(["apt-mark", "showmanual"], capture_output=True,
|
|
text=True, timeout=30).stdout.split()
|
|
except (OSError, subprocess.SubprocessError):
|
|
return
|
|
if not man:
|
|
return
|
|
# resumen de cada uno en un solo dpkg-query (rapido)
|
|
try:
|
|
salida = subprocess.run(
|
|
["dpkg-query", "-W", "-f=${Package}\t${binary:Summary}\n", *man],
|
|
capture_output=True, text=True, timeout=60).stdout
|
|
except (OSError, subprocess.SubprocessError):
|
|
return
|
|
lineas = []
|
|
for l in salida.splitlines():
|
|
if "\t" not in l:
|
|
continue
|
|
pkg, resumen = l.split("\t", 1)
|
|
if resumen.strip():
|
|
lineas.append(f"{pkg}: {resumen.strip()}")
|
|
# se agrupan en fragmentos (no una entrada por paquete: son cientos)
|
|
buffer = "Paquetes que el usuario instalo a mano en esta maquina:\n"
|
|
for ln in lineas:
|
|
if len(buffer) + len(ln) > 1100 and buffer:
|
|
yield _pkg_entry(buffer)
|
|
buffer = "Paquetes instalados (cont.):\n"
|
|
buffer += "- " + ln + "\n"
|
|
if buffer.strip():
|
|
yield _pkg_entry(buffer)
|
|
|
|
|
|
def _pkg_entry(texto):
|
|
return {"tipo": "instalado", "herramienta": "paquetes apt", "perfil": "SISTEMA",
|
|
"publico": False, "tema": "paquetes", "fichero": "apt-mark showmanual",
|
|
"texto": texto.strip()}
|
|
|
|
|
|
def entradas():
|
|
yield from _apps_desktop()
|
|
yield from _paquetes_manuales()
|
|
|
|
|
|
def main():
|
|
import argparse
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument("--volcado", action="store_true")
|
|
a = p.parse_args()
|
|
apps = pkgs = 0
|
|
muestras = []
|
|
for e in entradas():
|
|
if e["herramienta"] == "paquetes apt":
|
|
pkgs += 1
|
|
else:
|
|
apps += 1
|
|
if len(muestras) < 6:
|
|
muestras.append(e)
|
|
print(f" {apps} apps (.desktop) + {pkgs} fragmentos de paquetes apt")
|
|
if a.volcado:
|
|
for e in muestras:
|
|
print(" -", e["texto"][:160].replace("\n", " "))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|