#!/usr/bin/env python3 """Genera acciones.json. Se escribe aqui y no a mano en el JSON porque son mas de cien entradas con la misma forma: en tabla se leen de un tirón y se ve si falta un acuse o si dos frases chocan. El JSON es el producto; este fichero es la fuente. python3 _generar.py && python3 -m json.tool acciones.json >/dev/null Columnas: id | frases (| separadas) | acuse | comando | respuesta Reglas que conviene no romper: - Las frases van sin tildes ni signos: el motor normaliza lo que oye, pero aqui se comparan tal cual. - Frases cortas y genericas ("abre") arrastran todo lo demas; se prefieren de tres palabras o mas, que es como se habla de verdad. - Todo lo que borre, mate o apague lleva confirmar=True. - {argumento} solo en las que declaran captura. """ import json import pathlib # --------------------------------------------------------------- sistema SISTEMA = [ ("disco_libre", "cuanto espacio queda|espacio en disco|espacio libre en disco|cuanto disco queda", "Un momento, lo miro.", "df -h / | tail -1", "Quedan {campo4} libres de {campo2}, al {campo5} de uso."), ("disco_home", "como esta mi disco|cuanto ocupa mi disco|espacio de mi disco", "Un momento.", "df -h ~ | tail -1", "Su disco esta al {campo5}, con {campo4} libres de {campo2}."), ("disco_todos", "discos montados|particiones|unidades montadas", "Enseguida.", "df -h --output=target,size,pcent -x tmpfs -x devtmpfs | tail -n +2", "Hay {lineas} unidades montadas. {salida}"), ("memoria", "cuanta memoria|memoria libre|cuanta ram|ram libre", "Voy.", "free -g | awk '/Mem:/{print $2, $3, $7}'", "De {campo1} gigas hay {campo2} en uso y {campo3} disponibles."), ("swap", "como esta la swap|memoria de intercambio", "Voy.", "free -g | awk '/Swap:/{print $2, $3}'", "Swap: {campo2} gigas usados de {campo1}."), ("cpu_carga", "carga del sistema|como va el procesador|carga de cpu", "Un segundo.", "uptime | sed 's/.*average: //'", "La carga media es {ultima}."), ("cpu_modelo", "que procesador tengo|modelo de procesador|que cpu tengo", "Enseguida.", "lscpu | grep 'Model name' | cut -d: -f2 | xargs", "Lleva un {ultima}."), ("cpu_nucleos", "cuantos nucleos tengo|cuantos hilos tengo", "Voy.", "nproc", "Tiene {ultima} hilos disponibles."), ("temperatura", "que temperatura hay|como esta de temperatura|se esta calentando", "Lo compruebo.", "for z in /sys/class/thermal/thermal_zone*/temp; do cat $z; done | sort -rn | head -1 | awk '{printf \"%.0f\", $1/1000}'", "El punto mas caliente esta a {ultima} grados."), ("gpu_estado", "como esta la grafica|estado de la gpu|uso de la grafica", "Un momento.", "nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu --format=csv,noheader", "La grafica esta asi: {ultima}."), ("uptime", "cuanto lleva encendido|desde cuando esta encendido|tiempo encendido", "Voy.", "uptime -p | sed 's/^up //; s/ days\\?/ dias/; s/ hours\\?/ horas/; s/ minutes\\?/ minutos/; s/,\\([^,]*\\)$/ y\\1/'", "Lleva encendido {ultima}."), ("bateria", "cuanta bateria queda|como esta la bateria|nivel de bateria", "Lo miro.", "cat /sys/class/power_supply/BAT*/capacity 2>/dev/null | head -1", "La bateria esta al {ultima} por ciento."), ("kernel", "que kernel tengo|version del kernel|que nucleo tengo", "Enseguida.", "uname -r", "Kernel {ultima}."), ("distro", "que distribucion tengo|que sistema tengo|que linux tengo", "Voy.", "grep PRETTY_NAME /etc/os-release | cut -d'\"' -f2", "Esta usando {ultima}."), ("fecha", "que hora es|que dia es|dime la fecha", "", "date '+%H:%M del %A %d de %B'", "Son las {salida}."), ("procesos_top", "que esta consumiendo|que consume mas|procesos que mas gastan|quien se come la cpu", "Lo compruebo.", "ps -eo pcpu,comm --sort=-pcpu --no-headers | head -5 | awk '{c=$1; $1=\"\"; sub(/^ +/,\"\"); printf \"%s al %s por ciento\\n\", $0, c}'", "Lo que mas consume es {primera}."), ("procesos_memoria", "que consume mas memoria|quien se come la ram", "Lo compruebo.", "ps -eo comm,pmem --sort=-pmem --no-headers | head -5", "Por memoria: {salida}"), ("procesos_cuantos", "cuantos procesos hay|cuantos procesos corren", "Voy.", "ps -e --no-headers | wc -l", "Hay {ultima} procesos en marcha."), ("red_ip", "cual es mi ip|mi ip local|que ip tengo", "Un momento.", "hostname -I | awk '{print $1}'", "Su IP local es {ultima}."), ("red_ip_publica", "cual es mi ip publica|ip externa", "Salgo a mirarlo.", "curl -s --max-time 6 ifconfig.me", "Su IP publica es {ultima}."), ("red_wifi", "a que wifi estoy conectado|red wifi|como se llama la wifi", "Voy.", "nmcli -t -f NAME,TYPE connection show --active | grep wireless | cut -d: -f1", "Conectado a {ultima}."), ("red_velocidad", "cuanto trafico de red|trafico de red", "Lo miro.", "cat /proc/net/dev | awk '/wlp|enp/{rx+=$2; tx+=$10} END{printf \"%.1f %.1f\", rx/1e9, tx/1e9}'", "Desde el arranque: {campo1} gigas bajados y {campo2} subidos."), ("red_ping", "hay internet|tengo conexion|funciona internet", "Lo compruebo.", "ping -c1 -W2 1.1.1.1 >/dev/null 2>&1 && echo si || echo no", "Conexion: {ultima}."), ("puertos", "que puertos hay abiertos|puertos a la escucha|quien esta escuchando", "Un momento.", "ss -ltnp 2>/dev/null | awk 'NR>1{print $4}' | sort -u | head -8", "A la escucha: {salida}"), ("servicios_fallidos", "hay servicios caidos|servicios con fallos|algo ha fallado", "Lo reviso.", "systemctl --failed --no-legend | wc -l", "Servicios en fallo: {ultima}."), ("actualizaciones", "hay actualizaciones|paquetes por actualizar", "Lo compruebo, tarda un poco.", "apt list --upgradable 2>/dev/null | tail -n +2 | wc -l", "Hay {ultima} paquetes por actualizar."), ("usuarios", "quien esta conectado|hay alguien conectado", "Voy.", "who | wc -l", "Hay {ultima} sesiones abiertas."), ("gpu_procesos", "que usa la grafica|quien usa la gpu", "Lo miro.", "O=$(nvidia-smi --query-compute-apps=process_name,used_gpu_memory --format=csv,noheader | awk -F, '{n=split($1,p,\"/\"); printf \"%s%s\\n\", p[n], $2}'); echo \"${O:-nada, la grafica esta libre}\"", "En la grafica: {primera}"), ("audio_salida", "por donde suena|salida de audio|que altavoz esta puesto", "Voy.", "pactl get-default-sink 2>/dev/null", "La salida es {ultima}."), ("audio_micro", "que microfono hay puesto|entrada de audio", "Voy.", "pactl get-default-source 2>/dev/null", "La entrada es {ultima}."), ("pantallas", "cuantas pantallas hay|monitores conectados", "Voy.", "n=$(xrandr --listmonitors 2>/dev/null | head -1 | awk '{print $2}'); xrandr --listmonitors 2>/dev/null | tail -n +2 | awk '{print $NF}' | paste -sd', '; echo \"Tiene $n pantalla$([ \"$n\" = 1 ] || echo s) conectada$([ \"$n\" = 1 ] || echo s)\"", "{ultima}"), ("dispositivos_usb", "que hay conectado por usb|dispositivos usb", "Un momento.", "lsusb | cut -d' ' -f7- | head -6", "Por USB: {salida}"), ("bluetooth", "que hay emparejado por bluetooth|dispositivos bluetooth", "Voy.", "bluetoothctl devices 2>/dev/null | cut -d' ' -f3- | head -5", "Emparejados: {salida}"), ("flatpaks", "que flatpaks tengo|aplicaciones flatpak", "Voy.", "flatpak list --app --columns=name | head -10", "Instalados: {salida}"), ("docker", "hay contenedores corriendo|contenedores docker", "Lo miro.", "O=$(docker ps --format '{{.Names}}' 2>/dev/null | head -5); echo \"${O:-ninguno en marcha}\"", "Contenedores: {salida}"), ("git_estado", "como esta el repositorio|estado de git", "Voy.", "cd ~/COFRE/CODERS/JARVIS && git status --short | wc -l", "Hay {ultima} ficheros con cambios sin guardar."), ("ollama_estado", "esta ollama funcionando|como esta el modelo|que modelo hay cargado", "Lo compruebo.", "M=$(curl -s --max-time 3 http://localhost:11434/api/ps | grep -o '\"name\":\"[^\"]*' | cut -d'\"' -f4 | head -1); echo \"${M:-ninguno ahora mismo}\"", "Modelo cargado: {ultima}."), ("apagar_pantalla", "apaga la pantalla|apagar pantalla", "Enseguida.", "xset dpms force off", "Pantalla apagada."), # --- administracion: necesitan root, disparan el dialogo de contraseña --- # (Las de pentesting van en su propia lista, PENTEST, mas abajo.) ("actualizar_lista", "actualiza los repositorios|refresca los paquetes|actualiza la lista de paquetes", "Voy, necesito permiso de administrador.", "sudo apt update", "Repositorios actualizados, señor.", True), ("espacio_liberar", "limpia los paquetes viejos|libera espacio de apt|limpia el cache de paquetes", "Necesito permiso para esto.", "sudo apt autoclean", "Cache de paquetes limpiada, señor.", True), ("servicio_reiniciar", "reinicia el servicio de red|reinicia la red", "Necesito permiso de administrador.", "sudo systemctl restart NetworkManager", "Red reiniciada, señor.", True), ("fecha_sincronizar", "sincroniza la hora|pon la hora en hora", "Un momento, con permiso.", "sudo systemctl restart systemd-timesyncd", "Hora sincronizada, señor.", True), ("bloquear", "bloquea la sesion|bloquear pantalla|echa el cerrojo", "Ahora mismo.", "loginctl lock-session", "Sesion bloqueada."), ("volumen_subir", "sube el volumen|mas volumen", "", "pactl set-sink-volume @DEFAULT_SINK@ +10%", "Volumen subido."), ("volumen_bajar", "baja el volumen|menos volumen", "", "pactl set-sink-volume @DEFAULT_SINK@ -10%", "Volumen bajado."), ("volumen_silencio", "silencia el sistema|quita el sonido|mutea el sistema", "", "pactl set-sink-mute @DEFAULT_SINK@ toggle", "Sonido conmutado."), ("brillo_subir", "sube el brillo|mas brillo", "", "brightnessctl set +10% 2>/dev/null || xrandr --output $(xrandr | awk '/ connected/{print $1; exit}') --brightness 1", "Brillo subido."), ("captura_pantalla", "hazme una captura|captura de pantalla|haz una foto de la pantalla", "Enseguida se la hago.", "D=$(xdg-user-dir PICTURES 2>/dev/null || echo ~); F=$D/captura-$(date +%H%M%S).png; import -window root \"$F\" 2>/dev/null || gnome-screenshot -f \"$F\" 2>/dev/null || ffmpeg -loglevel error -f x11grab -video_size $(xdpyinfo | awk \"/dimensions/{print \\$2}\") -i $DISPLAY -frames:v 1 -y \"$F\" 2>/dev/null; [ -s \"$F\" ] && echo \"$F\"", "Captura guardada."), ("papelera_tamano", "cuanto ocupa la papelera|papelera llena", "Voy.", "du -sh ~/.local/share/Trash 2>/dev/null | cut -f1", "La papelera ocupa {ultima}."), ("descargas_listar", "que hay en descargas|ultimas descargas|que me he bajado", "Voy.", "ls -t ~/Downloads 2>/dev/null | head -6", "Hay {lineas} cosas en descargas"), ("descargas_abrir", "abre la carpeta de descargas|abreme descargas", "Enseguida se la abro.", "xdg-open ~/Downloads", "Carpeta de descargas abierta."), ("documentos_abrir", "abre mis documentos|abreme documentos", "Enseguida.", "xdg-open ~/Documents", "Documentos abierto."), ("cofre_abrir", "abre el cofre|abreme el cofre", "Enseguida.", "xdg-open ~/COFRE", "COFRE abierto."), ("ficheros_recientes", "que he tocado ultimamente|ficheros recientes|en que estaba trabajando", "Lo miro.", "find ~ -maxdepth 3 -type f -mmin -180 -not -path '*/.*' 2>/dev/null | head -6", "Tocado en las ultimas horas: {salida}"), ("grandes", "que ocupa mas sitio|que tengo en descargas|que me esta llenando la carpeta", "Lo miro.", "du -ah ~/Downloads ~/Descargas ~/Documents ~/Desktop 2>/dev/null | sort -rh | head -6", "Lo mas grande: {salida}"), ] # ------------------------------------------------------- con argumento CAPTURA = [ ("buscar_fichero", "busca el fichero|buscame el fichero|encuentra el fichero", "Lo busco.", "timeout 12 find $HOME/Documents $HOME/Desktop $HOME/COFRE/CODERS $HOME/Downloads -iname '*{argumento}*' -not -path '*/.*' -not -path '*/node_modules/*' -not -path '*venv*' 2>/dev/null | head -12", "He encontrado {lineas}"), ("buscar_carpeta", "busca la carpeta|encuentra la carpeta", "La busco.", "find ~ -type d -iname '*{argumento}*' -not -path '*/.*' 2>/dev/null | head -12", "Carpetas encontradas: {salida}"), # busca un TEXTO dentro de los ficheros, recursivo, con ruta y linea ("grep_recursivo", "busca el texto|busca la palabra en mis ficheros|donde pone|que fichero contiene", "Lo busco en sus ficheros.", "timeout 12 grep -rIn --exclude-dir='.*' --exclude-dir='*venv*' --exclude-dir=node_modules --exclude-dir=__pycache__ --exclude-dir=modelos --exclude-dir=muestras -m1 {argumento} $HOME/Documents $HOME/Desktop $HOME/COFRE/CODERS 2>/dev/null | head -12", "{lineas} ficheros lo contienen."), ("leer_fichero", "leeme el fichero|abre y leeme|lee el fichero", "Voy a leerlo.", "f=$(timeout 12 find $HOME/Documents $HOME/Desktop $HOME/COFRE/CODERS -iname '*{argumento}*' -type f -not -path '*/.*' 2>/dev/null | head -1); [ -n \"$f\" ] && head -c 800 \"$f\" || echo \"no lo encuentro\"", "{salida}, señor"), ("abrir_fichero", "abre el fichero|abreme el fichero", "Enseguida se lo abro.", "f=$(find ~ -iname '*{argumento}*' -type f -not -path '*/.*' 2>/dev/null | head -1); " "[ -n \"$f\" ] && xdg-open \"$f\" && echo \"$f\" || echo 'no lo encuentro'", "Abierto {ultima}."), ("abrir_carpeta", "abre la carpeta|abreme la carpeta", "Enseguida.", "a={argumento}; if [ -d \"$a\" ]; then d=\"$a\"; else d=$(timeout 12 find $HOME/Documents $HOME/Desktop $HOME/COFRE $HOME/Downloads -type d -iname \"*$a*\" -not -path \"*/.*\" -not -path \"*/node_modules/*\" 2>/dev/null | head -1); fi; if [ -n \"$d\" ]; then xdg-open \"$d\" >/dev/null 2>&1 & echo \"Abierta $(basename \"$d\")\"; else echo \"No encuentro esa carpeta\"; fi", "{ultima}"), ("tamano_carpeta", "cuanto ocupa la carpeta|tamaño de la carpeta", "Lo calculo.", "a={argumento}; if [ -d \"$a\" ]; then d=\"$a\"; else d=$(timeout 12 find $HOME/Documents $HOME/Desktop $HOME/COFRE $HOME/Downloads -type d -iname \"*$a*\" -not -path \"*/.*\" 2>/dev/null | head -1); fi; [ -n \"$d\" ] && du -sh \"$d\" 2>/dev/null | cut -f1 || echo \"no la encuentro\"", "Ocupa {ultima}."), ("proceso_buscar", "esta corriendo|se esta ejecutando|hay algun proceso de", "Lo compruebo.", "pgrep -c -f {argumento} 2>/dev/null || echo 0", "Hay {ultima} procesos que encajan."), ("proceso_matar", "mata el proceso|cierra el proceso|termina el proceso", "Antes de matarlo, confirmeme.", "pkill -f {argumento}", "Proceso terminado.", True), ("instalado", "tengo instalado|esta instalado", "Lo miro.", "command -v {argumento} >/dev/null && echo si || echo no", "Instalado: {ultima}."), ("manual", "para que sirve el comando|que hace el comando", "Voy.", "whatis {argumento} 2>/dev/null | head -1", "{salida}"), ] # --------------------------------------------------------------- oasis # --------------------------------------------------------------- pentesting # Red de seguridad: los escaneos ACTIVOS (que mandan paquetes a otros) llevan # confirmar=True, asi que el dialogo muestra el comando exacto antes de lanzarse # y la voz no puede disparar un escaneo sola. Los que apuntan a un objetivo se # limitan a la RED LOCAL por construccion (autodetectan la subred o exigen IP # privada); un objetivo de fuera se rechaza. Lo de solo lectura —buscar un # exploit en la base local, resolver un dominio— va directo, no toca a nadie. # # Que apunte solo a la red local es a proposito: un asistente por voz que # escanea lo que crea haber entendido, con salida al host, es un incidente # esperando. Para objetivos de fuera, mejor a mano y con intencion. PENTEST = [ ("red_dispositivos", "escanea mi red|que hay en mi red|dispositivos en la red|quien esta en la red", "Voy a escanear la red local, necesito su permiso.", "R=$(ip route | awk '/proto.*src/{for(i=1;i<=NF;i++)if($i~/\\//)print $i; exit}'); " "sudo nmap -sn ${R:-192.168.1.0/24} | grep -E 'Nmap scan|MAC' | head -30", "Estos equipos hay en la red, señor: {salida}", True), ("puertos_host", "que puertos tiene abiertos|escanea los puertos de|escanea el host", "Un escaneo de puertos, con su permiso.", "T={argumento}; echo \"$T\" | grep -qE '^(10\\.|172\\.(1[6-9]|2[0-9]|3[01])\\.|192\\.168\\.|127\\.|localhost)' " "&& nmap -F \"$T\" | grep -E '^[0-9]+/|Nmap scan' | head -20 " "|| echo 'Solo objetivos de la red local, señor.'", "{salida}", True), ("mis_puertos", "que puertos tengo abiertos yo|mis puertos abiertos|que estoy exponiendo", "Lo compruebo.", "ss -ltnp 2>/dev/null | awk 'NR>1{print $4}' | sort -u | head -15", "Tengo {lineas} puertos a la escucha"), ("exploit_buscar", "busca un exploit para|busca exploits de|hay algun exploit de", "Lo busco en la base de exploits.", "searchsploit {argumento} 2>/dev/null | grep -vE '^-|Shellcode' | head -12", "Esto he encontrado, señor: {salida}"), ("dominio_resolver", "resuelve el dominio|que ip tiene el dominio|a que ip apunta", "Lo resuelvo.", "dig +short {argumento} 2>/dev/null | head -5", "Resuelve a: {salida}"), ("dominio_whois", "de quien es el dominio|quien registro|whois de", "Lo consulto.", "whois {argumento} 2>/dev/null | grep -iE 'registrar:|creation|expir|org' | head -6", "{salida}"), ("cabeceras_web", "que servidor usa|cabeceras de|que tecnologia usa la web", "Miro las cabeceras.", "curl -sI --max-time 8 {argumento_url} 2>/dev/null | grep -iE 'server:|x-powered|location' | head -6", "{salida}"), ("hash_identificar", "que tipo de hash es|identifica el hash", "Lo miro.", "H={argumento}; L=${#H}; case $L in 32)echo 'MD5, probablemente';; 40)echo 'SHA1';; " "64)echo 'SHA256';; *)echo \"$L caracteres, no lo tengo claro\";; esac", "{ultima}, señor."), ] # las con argumento del bloque PENTEST (para marcar captura=True) PENTEST_CAPTURA = {"puertos_host", "exploit_buscar", "dominio_resolver", "dominio_whois", "cabeceras_web", "hash_identificar"} OASIS = [ ("oasis_corriendo", "esta oasis funcionando|esta corriendo oasis|oasis esta levantado", "Lo compruebo.", "pgrep -c -f 'oasis|ssb-server|sbot' 2>/dev/null || echo 0", "Procesos de Oasis en marcha: {ultima}."), ("oasis_sincronizando", "estan sincronizando los pubs|como van los pubs|se estan sincronizando los pubs", "Voy a mirar las conexiones.", "python3 -c \"import json,time; d=json.load(open('$HOME/.ssb/conn.json')); " "act=sum(1 for v in d.values() if time.time()*1000-v.get('stateChange',0)<300000); " "fail=sum(1 for v in d.values() if v.get('failure',0)>0); " "print(f'{len(d)} pubs, {act} activos hace poco, {fail} con fallos')\"", "{ultima}."), ("oasis_conexiones", "cuantos pubs hay conectados|conexiones de oasis", "Un momento.", "cat ~/.ssb/conn.json 2>/dev/null | grep -c 'connected' || echo 0", "Pubs conectados: {ultima}."), ("oasis_tamano", "cuanto ocupa oasis|tamaño de la base de ssb", "Lo calculo.", "du -sh ~/.ssb 2>/dev/null | cut -f1", "La base de SSB ocupa {ultima}."), ("oasis_abrir", "abre oasis|abreme oasis", "Enseguida se lo abro.", "xdg-open http://localhost:3000 2>/dev/null && echo abierto || echo 'no responde'", "Oasis: {ultima}."), ("oasis_blobs", "cuanto ocupan los blobs|tamaño de los blobs", "Voy.", "du -sh ~/.ssb/blobs 2>/dev/null | cut -f1", "Los blobs ocupan {ultima}."), ] # Alias foneticos: lo que Whisper devuelve DE VERDAD, no lo que uno escribe. # Medido con ocho ordenes habladas: "abre el correo" se transcribe "a ver el # correo" (en castellano suenan igual), "los pubs" sale "los push" o "los # puos", y "youtube" se parte en "yo tube". Añadirlos sube los aciertos sin # tocar el modelo ni añadir un milisegundo. ALIAS = { "ff_gmail": ["a ver el correo", "abrir el correo"], "oasis_sincronizando": ["estan sincronizando los push", "estan sincronizando los puos", "sincronizando los pubs", "como van los push"], "oasis_corriendo": ["esta oasis funcionando", "esta corriendo casis"], "ff_buscar_youtube": ["busca en yo tube", "buscan youtube", "buscan yo tube", "busca en you tube"], "disco_libre": ["cuando espacio queda", "cuanto espacio quedan"], "ff_youtube": ["abre yo tube", "abre you tube"], "descargas_abrir": ["abre descargas", "abreme las descargas"], "procesos_top": ["que esta consumiendo mas", "quien consume"], "temperatura": ["que temperatura tiene", "como esta la temperatura"], "bateria": ["cuanta bateria tengo", "como va la bateria"], } def con_señor(texto: str) -> str: """Mete el trato en las frases fijas. Son las que mas suenan —el acuse va en cada orden— y hasta ahora eran las unicas que no trataban de señor, asi que el personaje se caia justo ahi. """ if not texto or "señor" in texto.lower(): return texto if texto.endswith("."): return texto[:-1] + ", señor." if texto.endswith("?"): return texto[:-1] + ", señor?" return texto + ", señor" # Abrir una terminal y poner un video: de lo primero que se le pide a un # asistente de escritorio, y no estaban. Las busquedas van acotadas y con # tope porque el home son casi mil gigas y un find sin limite no vuelve. ESCRITORIO = [ ("terminal_abrir", "abre una terminal|abreme una terminal|abre la terminal|saca una terminal|quiero una terminal", "Ahora mismo.", "gnome-terminal 2>/dev/null || kgx 2>/dev/null || xterm 2>/dev/null &", "Terminal abierta."), ("terminal_aqui", "abre una terminal en el cofre|terminal en el cofre", "Enseguida.", "gnome-terminal --working-directory=$HOME/COFRE 2>/dev/null || kgx 2>/dev/null &", "Ahi la tiene, en el cofre."), ("videos_listar", "que peliculas tengo|que videos tengo|lista mis videos", "Un momento, lo miro.", "timeout 12 find $HOME/Videos $HOME/Downloads $HOME/Desktop -type f -regextype posix-extended -iregex '.*\\.(mp4|mkv|avi|webm|mov|m4v)$' 2>/dev/null | head -12", "He encontrado {lineas}."), ] # esta captura el nombre de la pelicula ESCRITORIO_CAPTURA = [ ("pelicula_abrir", "pon la pelicula|abre la pelicula|reproduce el video|pon el video|quiero ver", "Enseguida se la pongo.", "v=$(timeout 12 find $HOME/Videos $HOME/Downloads $HOME/Desktop -type f -regextype posix-extended -iregex '.*{argumento}.*\\.(mp4|mkv|avi|webm|mov|m4v)$' 2>/dev/null | head -1); if [ -n \"$v\" ]; then xdg-open \"$v\" >/dev/null 2>&1 & echo \"Poniendo $(basename \"$v\")\"; else echo \"No encuentro ese video\"; fi", "{ultima}"), ] # Ventanas y pantallas. Toda la logica vive en config/ventanas.sh (un script del # host): aqui solo estan las frases y la llamada. # # Las frases llevan TRES palabras o mas y evitan a proposito ser subcadena de # las que ya existen con "pantalla" o "ventana" dentro —apagar_pantalla, # bloquear, captura_pantalla, ff_firefox_privado, ff_firefox_cerrar, # proceso_matar, terminal_abrir—, porque el emparejador gana por frase mas # larga sin mirar el grupo y una frase mal elegida se las come. VENTANAS = [ ("ventanas_listar", "que ventanas tengo abiertas|que ventanas hay abiertas|lista las ventanas", "Un momento, lo miro.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh listar", "Tiene {lineas} ventanas abiertas."), ("ventana_izquierda", "pon la ventana a la izquierda|manda la ventana a la izquierda|esta ventana a la izquierda", "Ahora mismo.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh zona esta izquierda", "{ultima}"), ("ventana_derecha", "pon la ventana a la derecha|manda la ventana a la derecha|esta ventana a la derecha", "Ahora mismo.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh zona esta derecha", "{ultima}"), ("ventana_arriba", "pon la ventana arriba del todo|manda la ventana arriba", "Voy.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh zona esta arriba", "{ultima}"), ("ventana_abajo", "pon la ventana abajo del todo|manda la ventana abajo", "Voy.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh zona esta abajo", "{ultima}"), ("ventana_centrar", "centra la ventana activa|pon la ventana en el centro", "Enseguida.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh zona esta centro", "{ultima}"), ("ventana_maximizar", "maximiza la ventana activa|pon la ventana a pantalla completa", "Ahora mismo.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh maximiza esta", "{ultima}"), ("ventana_restaurar", "restaura la ventana activa|quita la pantalla completa", "Voy.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh restaura esta", "{ultima}"), ("ventana_minimizar", "minimiza la ventana activa|esconde la ventana activa", "Voy.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh minimiza esta", "{ultima}"), ] # Estas capturan lo que se dice detras: el nombre de la pantalla, el numero de # escritorio o el mote que se le quiera poner a un monitor. VENTANAS_CAPTURA = [ ("ventana_a_pantalla", "manda la ventana a la pantalla|pasa la ventana a la pantalla", "Ahora mismo.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh mover esta {argumento}", "{ultima}"), ("ventana_al_escritorio", "manda la ventana al escritorio|pasa la ventana al escritorio", "Voy.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh escritorio esta {argumento}", "{ultima}"), ("pantalla_nombrar", "a esta pantalla llamala|llama a esta pantalla", "Anotado.", "$HOME/COFRE/CODERS/JARVIS/config/ventanas.sh alias 1 {argumento}", "{ultima}"), ] def fila(t, grupo, captura=False): ident, frases, acuse, comando, respuesta = t[:5] confirmar = len(t) > 5 and t[5] return { "id": ident, "grupo": grupo, "frases": frases.split("|"), "acuse": con_señor(acuse or "Voy."), "comando": comando, "host": True, "respuesta": con_señor(respuesta), "confirmar": bool(confirmar), "captura": captura, } def main(): acciones = [fila(t, "sistema") for t in SISTEMA] acciones += [fila(t, "sistema") for t in ESCRITORIO] acciones += [fila(t, "sistema", captura=True) for t in ESCRITORIO_CAPTURA] acciones += [fila(t, "ventanas") for t in VENTANAS] acciones += [fila(t, "ventanas", captura=True) for t in VENTANAS_CAPTURA] acciones += [fila(t, "ficheros", captura=True) for t in CAPTURA] acciones += [fila(t, "oasis") for t in OASIS] acciones += [fila(t, "pentest", captura=t[0] in PENTEST_CAPTURA) for t in PENTEST] # las de firefox viven en su propio fichero, que son otras cincuenta firefox = pathlib.Path(__file__).parent / "_firefox.py" if firefox.exists(): import importlib.util spec = importlib.util.spec_from_file_location("firefox", firefox) mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(mod) acciones += mod.acciones() # Los alias se aplican aqui, sobre la lista completa: si se hicieran al # construir cada fila, las de firefox se quedarian fuera porque se generan # en otro fichero. Se aprende por las malas. for accion in acciones: extra = ALIAS.get(accion["id"]) if extra: accion["frases"] += [f for f in extra if f not in accion["frases"]] salida = pathlib.Path(__file__).parent / "acciones.json" salida.write_text(json.dumps({"version": 1, "acciones": acciones}, indent=2, ensure_ascii=False) + "\n") ids = [a["id"] for a in acciones] print(f"{len(acciones)} acciones escritas en {salida.name}") if len(ids) != len(set(ids)): repes = {i for i in ids if ids.count(i) > 1} print(f"CUIDADO, ids repetidos: {repes}") for grupo in sorted({a["grupo"] for a in acciones}): print(f" {grupo:<10} {sum(1 for a in acciones if a['grupo'] == grupo)}") if __name__ == "__main__": main()