¿Qué hace talkback — escúchalo en lugar de leerlo?
Habla texto en voz alta desde cualquier directorio del proyecto, guarda el audio y nunca gasta silenciosamente una cuota medida.
Bajo demanda por defecto. Habla cuando se le pide — el usuario dice "explica eso en voz alta", o se ejecuta el script directamente. No narra por sí solo hasta que alguien activa el modo de conversación, y el modo de conversación pertenece a una sesión a la vez.
Úsalo
S=~/.claude/skills/talkback/scripts
$S/talkback.py "Esto es lo que cambió y por qué es importante." # ElevenLabs (por defecto)
$S/talkback.py --fast "Línea sin importancia." # local, instantáneo, gratuito
$S/talkback.py --quota # lo que queda
$S/talkback.py --voices # lista de voces
$S/talkback.py --dry-run "..." # ver el texto hablado, no sintetizar nada
$S/talkback.py --help # todas las banderas
$S/talkback-hook.py --on # modo de conversación ACTIVADO, solo esta sesión
$S/talkback-hook.py --off # dejar de hablar
$S/talkback-hook.py --status # quién está hablando, si el hook está conectado
$S/talkback-hook.py --outputs # salidas de audio en este host
El texto también se puede canalizar: git log -1 --format=%B | $S/talkback.py.
Cómo debe usarlo el agente
Aquí hay dos cosas diferentes. Hablar una vez es prosa compuesta que le pasas a talkback.py. El modo de conversación es una configuración permanente que activas con talkback-hook.py; nunca compones su texto, el hook lee el turno.
Hablar una vez
Cuando el usuario pide escuchar una explicación, escribe para el oído, luego dilo. No canalices Markdown sin procesar ni un diff a la herramienta. Compón de dos a cinco oraciones de prosa hablada simple — qué cambió, por qué, qué significa para ellos — y pásalo. El oyente no puede desplazarse hacia atrás, así que comienza con la conclusión.
Usa la voz buena por defecto — el plan la permite cómodamente. Recurre a --fast cuando el texto es desechable o quieres latencia de red cero. Indica qué backend se usó si hubo una degradación.
Manejar el modo de conversación
Lo que el usuario dice se asigna a un comando. Ejecútalo; no narres también el cambio a mano, porque el hook hablará el turno que estás escribiendo.
| El usuario dice | Ejecuta |
|---|---|
| "modo de conversación activado", "sigue hablando", "narra esta sesión" | talkback-hook.py --on |
| "deja de hablar", "silenciar", "cállate" | talkback-hook.py --off |
| "solo lo más destacado", "menos detalle" | talkback-hook.py --on brief |
| "solo dime lo importante" | talkback-hook.py --on marker |
| "usa mis AirPods", "reproducir en X" | talkback-hook.py --outputs, luego --on --output "<nombre>" |
| "usa la voz barata", "deja de gastar cuota" | talkback-hook.py --on --backend say |
| "está activado?", "por qué no oigo nada?" | talkback-hook.py --status |
| "está hablando otra cosa?" | talkback-hook.py --sessions |
| "silencia todo" (todas las sesiones) | talkback-hook.py --off --all |
Tres cosas que verificar antes de decirle al usuario que funciona:
--statusinforma si el hook está registrado. Si no lo está, ejecuta--instally diles que es necesario reiniciar.- Si
--statusadvierte que el hook se registró después de que esta sesión comenzó, el modo de conversación está activado y no producirá sonido aquí. Dilo — no dejes que lo descubran como silencio. - El modo de conversación no sobrevive a la sesión. Después de un reinicio o un
/clearse desactiva de nuevo y necesita--on. Eso es deliberado, no un error; dilo una vez en lugar de dejar que lo pregunten de nuevo.
En modo marker — y en cualquier momento que quieras que la relectura sea un resumen escrito para el oído en lugar del turno mismo — termina el mensaje con un marcador:
<!-- talkback: Refactorizó la capa de autenticación, tres sitios de llamada, pruebas en verde. -->
Backends
| Backend | Costo | Calidad | Notas |
|---|---|---|---|
elevenlabs (por defecto) | medido | mejor | protegido por cuota, retrocede automáticamente a say |
say (--fast) | gratuito, ilimitado | aceptable | nativo de macOS, ~instantáneo, sin red |
omnivoice | gratuito, ilimitado | buena | local + privado; necesita el backend funcionando. No verificado — ver abajo |
La escalera
elevenlabs → omnivoice → say, primero el mejor. Un peldaño que no puede hacer el trabajo — sin clave, cuota agotada hasta la reserva, servidor local caído, error de síntesis — pasa al siguiente peldaño, por lo que la voz se degrada en lugar de que el audio desaparezca. TALKBACK_CHAIN lo reordena.
Pedir un peldaño explícitamente comienza la escalera allí y solo desciende: --fast significa "local ahora" y nunca vuelve a subir a un backend medido. Cada retroceso imprime la razón en stderr y el backend elegido se registra en el libro mayor, por lo que una ejecución degradada nunca es silenciosa acerca de estar degradada.
--strict convierte cualquier retroceso en un fallo duro (salida 1) en su lugar, para scripts que no deben degradarse silenciosamente.
Cuota
La cuenta es del nivel Creator: 130,958 caracteres/mes. Una explicación hablada de dos minutos son aproximadamente 1,500 caracteres, por lo que eso es alrededor de 87 al mes — suficiente para que la voz buena pueda ser la opción predeterminada en lugar de un lujo.
Verifica en el momento de uso, nunca de memoria — talkback.py --quota lo lee en vivo. El nivel ya ha cambiado una vez, y un número en un documento está obsoleto en el momento en que el plan se mueve.
Antes de sintetizar, la herramienta lee la cuota en vivo y mantiene una reserva de 250 caracteres, por lo que una explicación larga nunca puede agotar completamente el saldo. Si la solicitud no cabe, advierte en stderr y usa say en su lugar.
Las credenciales se resuelven en orden: $ELEVENLABS_API_KEY → ~/.elevenlabs/api_key (escrito por elevenlabs auth login) → ELEVENLABS_API_KEY en ~/broomva/.env.local. Dos claves distintas existen en esta máquina y se resuelven a la misma cuenta, por lo que verificar una es verificar ambas.
El nivel Creator también desbloquea clonación de voz instantánea y profesional (30 espacios de voz, 1 profesional). --voices enumera lo que la cuenta puede usar actualmente.
El paquete
@elevenlabs/clino se usa en tiempo de ejecución y no puede hacer esto — toda su superficie esauth · agents · tools · tests · components, que gestiona proyectos de agente ConvAI alojados. No tiene un comando de síntesis. La CLI es útil aquí solo paraauth login, que escribe el archivo de clave.
El nivel OmniVoice no está verificado
El backend omnivoice está implementado según la forma documentada pero nunca se probó — el backend local estaba inactivo cuando se lanzó. Degrada limpiamente (retrocede a say, o falla bajo --strict). Para ponerlo en marcha, consulta la habilidad omnivoice; el repositorio ya está en ~/broomva/external/OmniVoice-Studio.
Manejo del texto hablado
La prosa del agente no está escrita para ser escuchada, por lo que el texto se prepara primero:
- bloques de código →
(código omitido); URL →(enlace) - los encabezados y viñetas se convierten en saltos de oración, para que las listas no se junten
- las rutas profundas se acortan al nombre base (
src/lib/engine.py→engine.py); pasa--keep-pathspara escucharlas completas - el énfasis de Markdown se elimina solo donde delimita un intervalo — un guion bajo desnudo dentro de
backend_chainse deja intacto, porque comerlo convierte un símbolo que el oyente podría buscar en uno que no puede
Audio guardado
Cada enunciado se guarda en ~/.talkback/audio/ como mp3 (convertido mediante ffmpeg cuando está presente) con un nombre de archivo con marca de tiempo y slug, y se agrega a ~/.talkback/ledger.jsonl. Usa --no-save para descartar, --out-dir para redirigir.
Modo de conversación: narra la sesión mientras transcurre
El modo de conversación hace que el agente hable una breve relectura al final de cada turno, mientras dure la sesión. Viene desactivado, se activa manualmente y pertenece a una sesión.
$S/talkback-hook.py --on # detalle completo, cada turno, ESTA sesión
$S/talkback-hook.py --on brief # solo líneas de apertura
$S/talkback-hook.py --on marker # solo turnos con un marcador
$S/talkback-hook.py --on --backend elevenlabs # la voz buena, medida
$S/talkback-hook.py --on --output "AirPods Pro" # qué altavoz usa esta sesión
$S/talkback-hook.py --off # dejar de hablar (esta sesión)
$S/talkback-hook.py --off --all # interruptor de parada, en todas partes
$S/talkback-hook.py --status # modo, backend, quién más está hablando
$S/talkback-hook.py --sessions # cada sesión que está hablando actualmente
Por qué está limitado a la sesión
El hook Stop se registra una vez, global y permanentemente — un interruptor que no puedes cambiar durante una sesión no es un interruptor. Por lo tanto, el hook se activa en todas las sesiones, y la bandera es lo que lo delimita: el modo de conversación es una propiedad de una sesión, no de la máquina.
La bandera es ~/.talkback/sessions/<session-id>; una sesión que nunca aceptó no tiene ninguna, por lo que el hook sale con 0 sin sonido. Esto es lo que mantiene a seis agentes paralelos en seis entornos de trabajo en silencio mientras la sesión que estás viendo habla. El diseño anterior a 0.3.0 tenía una bandera única para toda la máquina, que es la razón por la que nunca se activó: habilitarlo hacía que todos los agentes en el equipo fueran audibles a la vez.
El id de sesión se lee de CLAUDE_CODE_SESSION_ID o CLAUDE_SESSION_ID; --session <id> lo establece explícitamente. Cuando ninguno responde, el comando imprime pass --session <id> y sale con 2 en lugar de adivinar — solía recurrir a la transcripción más reciente en el directorio de trabajo, lo que significaba que con dos sesiones abiertas en un directorio --on habilitaba la sesión que se había tocado más recientemente, no la que preguntó.
En el lado del hook, el session_id de la carga útil es autoritario: cuando está presente, es la única clave que la puerta autoriza. El nombre del archivo de transcripción se usa solo cuando ese campo está ausente. Ambos se aceptaban juntos una vez, lo que permitía que una carga útil que nombraba la sesión A y la transcripción de la sesión B resolviera la aceptación de B y hablara en A.
--session <id> apuntando a otra sesión es deliberado, y no es un límite de seguridad. La sesión A ejecutando talkback-hook.py --session B --off habilita o deshabilita el modo de conversación de B sin el consentimiento de B — no hay una verificación de propiedad que vincule una bandera a la sesión que la estableció. Eso es una característica, mantenida a propósito en lugar de revertida: cada sesión en el equipo se ejecuta como el mismo usuario del sistema operativo, por lo que cualquier sesión ya puede escribir el archivo de bandera de otra sesión directamente en ~/.talkback/sessions/. Una verificación de propiedad dentro de la CLI solo detendría la ruta educada a través de la bandera, no la directa — un límite del mismo principal no puede detener a un escritor que comparte el principal. La bandera delimita el audio, por lo que los agentes paralelos permanecen en silencio por defecto; no delimita el control, y nada aquí debe leerse como si lo hiciera.
Niveles de detalle
| Modo | Habla |
|---|---|
full (predeterminado de la sesión) | el turno completo, cada turno — encabezado por el marcador cuando el agente escribió uno |
brief | los TALKBACK_HOOK_CHARS iniciales (320) de turnos sobre TALKBACK_HOOK_MIN_CHARS (80); un marcador gana cuando está presente |
marker | solo turnos que llevan un marcador <!-- talkback: … -->, y solo el texto del marcador |
off | nunca — escrito por --off cuando una bandera global volvería a habilitar la sesión |
full es el predeterminado porque el objetivo de una relectura es alejarse y volver sabiendo lo que pasó. Un extracto limitado es una vista previa de la respuesta, no la respuesta — todavía tendrías que leer la pantalla, que es lo que el modo de conversación existe para evitar. Lo que se descarta es solo lo que no se puede escuchar en absoluto (bloques de código, URL, rutas profundas — ver Manejo del texto hablado), nunca lo que es meramente largo. full tampoco tiene un límite mínimo de longitud: "hecho, pruebas en verde" es un resultado que quieres cuando no estás, no ruido.
brief y marker son para un arco largo sin supervisión donde quieres la forma del progreso en lugar de la transcripción.
TALKBACK_FULL_MAX_CHARS pone un límite superior en full, desactivado por defecto — un límite convierte full de nuevo en brief exactamente en los turnos que vale la pena escuchar completos.
Las relecturas toman toda la escalera de backends — ElevenLabs primero, descendiendo solo cuando un peldaño no es utilizable. Por lo tanto, el detalle completo en cada turno gasta cuota real: un turno de 1,500 caracteres es ~1% del saldo mensual de Creator, por lo que una sesión larga llegará a la reserva, momento en el que sigue hablando en el siguiente peldaño en lugar de callarse. --on --backend say lo fija bajo, --on brief lo acorta.
Cuando una relectura se superpone con la siguiente
Una relectura completa aún puede estar reproduciéndose cuando termina el siguiente turno. TALKBACK_ON_OVERLAP decide qué sucede:
| Valor | Comportamiento |
|---|---|
interrupt (predeterminado) | la nueva relectura corta la anterior — el estado más nuevo es el que vale la pena escuchar |
queue | se reproducen en orden, por lo que una relectura larga y detallada nunca se trunca; te quedas atrás pero lo escuchas todo |
queue es la configuración correcta para un arco sin supervisión que pretendes escuchar completo; interrupt es correcto cuando estás en la máquina y quieres lo último.
El marcador
Para optar por un solo turno — en modo marker, o para hablar un resumen escrito para el oído en lugar de las líneas de apertura del mensaje — termina el mensaje con:
<!-- talkback: Refactorizó la capa de autenticación, tres sitios de llamada, pruebas en verde. -->
En modo full, el marcador se convierte en el titular: se habla primero, luego todo el turno detrás de él, por lo que la relectura comienza con la conclusión sin perder el detalle. En modo brief, el marcador reemplaza el extracto, y un turno sin marcador vuelve a las oraciones de apertura recortadas hasta un límite de oración.
Qué altavoz
El audio sale de la máquina que ejecuta Claude Code. Una sesión que estás manejando desde un teléfono, una tableta u otra computadora portátil aún suena en el host, por lo que la palanca que importa es elegir en cuál de las salidas del host aterriza — AirPods emparejados con la Mac, un altavoz AirPlay, una pantalla, los altavoces integrados.
$S/talkback-hook.py --outputs # por qué salidas puede reproducir este host
$S/talkback-hook.py --on --output "AirPods Pro"
$S/talkback.py -d "MacBook Pro Speakers" "línea suelta en un dispositivo elegido"
La salida se almacena por sesión, por lo que...