JARVIS/verde/escucha-verde.sh
sito 1e3e5ca20d carril verde (Hermes Agent): documentacion, panel, voz local y RAG
Rama JARVIS-GREEN, independiente de master (el nucleo naranja queda intacto).

- README propio del carril verde con diagrama de arquitectura y capturas del panel.
- verde/: arranque, panel web, puente de voz (escucha local con faster-whisper +
  habla con la voz del naranja), pruebas (07 voz, 08 escucha), config y notas.
- Integracion RAG: nucleo/saber/busca_cli.py + skill buscar-en-apuntes, para que
  el agente consulte el mismo indice que el nucleo.
- Todo local (127.0.0.1); sin datos personales (rutas y modelo de GPU scrubeados).
2026-08-18 11:26:42 +02:00

84 lines
3.3 KiB
Bash
Executable file

#!/usr/bin/env bash
# El oido del verde: micro -> whisper LOCAL -> el agente. Nada sale de la maquina.
#
# ./escucha-verde.sh pulsar-para-hablar: graba, para con Enter, responde
# ./escucha-verde.sh --habla ademas dice la respuesta en voz alta (voz del verde)
# ./escucha-verde.sh --seguido no para: encadena turnos hasta Ctrl-C
#
# Por que existe: el TUI de Hermes no tiene microfono, y su STT nativo transcribe
# notas de voz de mensajeria (Telegram/Discord), que saldrian de la maquina. Este
# puente graba en local (pw-record/parec/arecord), transcribe con faster-whisper
# 'small' (escucha_transcribe.py, en CPU) y se lo pasa al agente con `hermes chat`.
#
# AVISO honesto: el 4B verde con 65k de contexto y media red en CPU tarda minutos
# por turno. Esto sirve para PROBAR que el verde escucha, no como asistente de
# diario: para eso esta el naranja. La palanca de velocidad (KV cache q8_0) toca
# el servicio de ollama y afectaria al naranja, asi que no se aplica aqui.
set -uo pipefail
RAIZ=$(cd -P "$(dirname "${BASH_SOURCE[0]:-$0}")/.." && pwd)
VERDE=$RAIZ/verde
PY=$RAIZ/nucleo/venv/bin/python
[ -x "$PY" ] || PY=$(command -v python3)
HERMES="$HOME/.hermes/hermes-agent/venv/bin/python $HOME/.hermes/hermes-agent/hermes"
TMP=${TMPDIR:-/tmp}/jarvis-verde-escucha
mkdir -p "$TMP"
HABLA=0; SEGUIDO=0
for a in "$@"; do
case "$a" in
--habla) HABLA=1 ;;
--seguido) SEGUIDO=1 ;;
esac
done
verde() { printf '\033[32m%s\033[0m\n' "$1"; }
gris() { printf '\033[90m%s\033[0m\n' "$1"; }
# Devuelve el comando de grabacion a WAV 16 kHz mono, segun lo que haya.
grabador_a() {
local wav=$1
if command -v pw-record >/dev/null; then echo "pw-record --rate 16000 --channels 1 --format s16 $wav" ;;
elif command -v arecord >/dev/null; then echo "arecord -q -r 16000 -c 1 -f S16_LE -t wav $wav" ;;
elif command -v parec >/dev/null; then echo "parec --rate=16000 --channels=1 --format=s16le --file-format=wav $wav" ;;
else echo ""; fi
}
un_turno() {
local wav=$TMP/turno.wav; rm -f "$wav"
local cmd; cmd=$(grabador_a "$wav")
if [ -z "$cmd" ]; then
echo "No hay grabador (instala pipewire-utils, alsa-utils o pulseaudio-utils)"; return 1
fi
verde "🎙 Habla ahora... (Enter para parar)"
$cmd >/dev/null 2>&1 &
local pid=$!
read -r _ </dev/tty
kill "$pid" 2>/dev/null; wait "$pid" 2>/dev/null
[ -s "$wav" ] || { echo "no se grabo nada"; return 1; }
gris "…transcribiendo (local)…"
local texto; texto=$("$PY" "$VERDE/escucha_transcribe.py" "$wav" 2>/dev/null)
if [ -z "$texto" ]; then echo "no se entendio nada"; return 1; fi
printf '\033[36m> %s\033[0m\n' "$texto"
gris "…pensando (4B verde, puede tardar)…"
local resp; resp=$($HERMES chat --reasoning none -q "$texto" 2>/dev/null)
printf '\033[32m%s\033[0m\n' "$resp"
if [ "$HABLA" = 1 ] && [ -n "$resp" ]; then
local txt=$TMP/resp.txt wavr=$TMP/resp.wav
printf '%s' "$resp" > "$txt"
"$VERDE/voz-verde.sh" "$wavr" "$txt" >/dev/null 2>&1 && {
command -v pw-play >/dev/null && pw-play "$wavr" 2>/dev/null || \
command -v aplay >/dev/null && aplay -q "$wavr" 2>/dev/null || true
}
fi
}
if [ "$SEGUIDO" = 1 ]; then
echo "Modo seguido. Ctrl-C para salir."
while true; do un_turno || true; echo; done
else
un_turno
fi