O que talkback — ouça ao invés de ler faz?
Fala texto em voz alta de qualquer diretório de projeto, salva o áudio e nunca gasta uma cota medida silenciosamente.
Sob demanda por padrão. Fala quando solicitado — o usuário diz "explique isso em voz alta" ou o script é executado diretamente. Não narra por conta própria até que alguém ative o modo de conversa, e o modo de conversa pertence a uma sessão por vez.
Como usar
S=~/.claude/skills/talkback/scripts
$S/talkback.py "Aqui está o que mudou e por que é importante." # ElevenLabs (padrão)
$S/talkback.py --fast "Linha descartável." # local, instantâneo, gratuito
$S/talkback.py --quota # quanto resta
$S/talkback.py --voices # lista de vozes
$S/talkback.py --dry-run "..." # vê o texto falado, não sintetiza nada
$S/talkback.py --help # todas as flags
$S/talkback-hook.py --on # modo de conversa ATIVO, apenas nesta sessão
$S/talkback-hook.py --off # para de falar
$S/talkback-hook.py --status # quem está falando, o hook está conectado
$S/talkback-hook.py --outputs # saídas de áudio neste host
O texto também pode ser passado via pipe: git log -1 --format=%B | $S/talkback.py.
Como o agente deve usar
Duas coisas diferentes estão aqui. Falar uma vez é prosa composta que você passa para talkback.py. Modo de conversa é uma configuração permanente que você alterna com talkback-hook.py; você nunca compõe seu texto, o hook lê o turno.
Falar uma vez
Quando o usuário pedir para ouvir uma explicação, escreva para o ouvido, depois fale. Não envie markdown bruto ou um diff para a ferramenta. Componha de duas a cinco frases de prosa falada simples — o que mudou, por que, o que significa para eles — e passe isso. O ouvinte não pode rolar para trás, então comece com a conclusão.
Use a boa voz por padrão — o plano a suporta confortavelmente. Recorra a --fast quando o texto for descartável ou quando quiser latência de rede zero. Informe qual backend foi usado se houve fallback.
Dirigindo o modo de conversa
O que o usuário diz mapeia para um comando. Execute-o; não narre também a mudança manualmente, porque o hook falará o turno que você está escrevendo.
| O usuário diz | Execute |
|---|---|
| "modo de conversa ativo", "continue falando", "narre esta sessão" | talkback-hook.py --on |
| "pare de falar", "mudo", "silêncio" | talkback-hook.py --off |
| "apenas os destaques", "menos detalhes" | talkback-hook.py --on brief |
| "só me diga as partes importantes" | talkback-hook.py --on marker |
| "use meus AirPods", "toque em X" | talkback-hook.py --outputs, depois --on --output "<nome>" |
| "use a voz barata", "pare de gastar cota" | talkback-hook.py --on --backend say |
| "está ativo?", "por que não ouço nada?" | talkback-hook.py --status |
| "tem algo mais falando?" | talkback-hook.py --sessions |
| "faça tudo ficar quieto" (todas as sessões) | talkback-hook.py --off --all |
Três coisas para verificar antes de dizer ao usuário que funciona:
--statusinforma se o hook está registrado. Se não estiver, execute--installe diga a eles que uma reinicialização é necessária.- Se
--statusavisar que o hook foi registrado após o início desta sessão, o modo de conversa está ativo e não fará som aqui. Diga isso — não deixe que eles descubram pelo silêncio. - O modo de conversa não sobrevive à sessão. Após uma reinicialização ou um
/clear, ele está desativado novamente e precisa de--on. Isso é intencional, não um bug; diga uma vez em vez de deixá-los perguntar novamente.
No modo marker — e sempre que você quiser que a leitura seja um resumo escrito para o ouvido em vez do turno em si — termine a mensagem com um marcador:
<!-- talkback: Refatorei a camada de autenticação, três pontos de chamada, testes verdes. -->
Backends
| Backend | Custo | Qualidade | Notas |
|---|---|---|---|
elevenlabs (padrão) | medido | melhor | protegido por cota, faz fallback automático para say |
say (--fast) | gratuito, ilimitado | razoável | nativo do macOS, ~instantâneo, sem rede |
omnivoice | gratuito, ilimitado | bom | local + privado; precisa do backend ativo. Não verificado — veja abaixo |
A escada
elevenlabs → omnivoice → say, do melhor primeiro. Um degrau que não pode realizar o trabalho — sem chave, cota gasta até a reserva, servidor local offline, erro de síntese — passa para o próximo degrau abaixo, então a voz degrada em vez do áudio desaparecer. TALKBACK_CHAIN reordena isso.
Solicitar um degrau explicitamente inicia a escada lá e apenas desce: --fast significa "local agora" e nunca sobe de volta para um backend medido. Cada fallback imprime o motivo no stderr e o backend escolhido vai para o registro, então uma execução degradada nunca fica silenciosa sobre estar degradada.
--strict transforma qualquer fallback em uma falha grave (código de saída 1), para scripts que não devem degradar silenciosamente.
Cota
A conta é do nível Creator: 130.958 caracteres/mês. Uma explicação falada de dois minutos tem cerca de 1.500 caracteres, então são cerca de 87 por mês — o suficiente para que a boa voz seja o padrão, não um mimo.
Verifique no ponto de uso, nunca pela memória — talkback.py --quota lê ao vivo. O nível já mudou uma vez, e um número em um documento está desatualizado no momento em que o plano muda.
Antes de sintetizar, a ferramenta lê a cota ao vivo e mantém uma reserva de 250 caracteres, para que uma explicação longa nunca esgote o saldo completamente. Se a solicitação não couber, ela avisa no stderr e usa say.
As credenciais são resolvidas na ordem: $ELEVENLABS_API_KEY → ~/.elevenlabs/api_key (escrito por elevenlabs auth login) → ELEVENLABS_API_KEY em ~/broomva/.env.local. Duas chaves distintas existem nesta máquina e resolvem para a mesma conta, então verificar uma é verificar ambas.
O nível Creator também desbloqueia clonagem de voz instantânea e profissional (30 slots de voz, 1 profissional). --voices lista o que a conta pode usar atualmente.
O pacote
@elevenlabs/clinão é usado em tempo de execução e não pode fazer isso — sua superfície inteira éauth · agents · tools · tests · components, que gerencia projetos de agente ConvAI hospedados. Não tem comando de síntese. A CLI é útil aqui apenas paraauth login, que escreve o arquivo de chave.
Nível OmniVoice não verificado
O backend omnivoice é implementado de acordo com a forma documentada, mas nunca foi exercitado — o backend local estava offline quando isso foi lançado. Ele degrada limpo (cai para say, ou falha sob --strict). Para colocá-lo em funcionamento, veja o skill omnivoice; o repositório já está em ~/broomva/external/OmniVoice-Studio.
Manipulação de texto falado
A prosa do agente não é escrita para ser ouvida, então o texto é preparado primeiro:
- blocos de código →
(código omitido); URLs →(link) - cabeçalhos e marcadores se tornam quebras de frase, para que listas não sejam ditas juntas
- caminhos profundos são encurtados para o nome base (
src/lib/engine.py→engine.py); use--keep-pathspara ouvi-los por completo - a ênfase do markdown é removida apenas onde delimita um intervalo — um sublinhado solto dentro de
backend_chainé mantido, porque removê-lo transformaria um símbolo que o ouvinte poderia pesquisar em algo que não pode
Áudio salvo
Cada fala vai para ~/.talkback/audio/ como mp3 (convertido via ffmpeg quando presente) com um nome de arquivo com timestamp e slug, e é anexada a ~/.talkback/ledger.jsonl. Use --no-save para descartar, --out-dir para redirecionar.
Modo de conversa — narre a sessão enquanto ela acontece
O modo de conversa faz o agente falar uma breve leitura no final de cada turno, enquanto a sessão durar. Ele vem desativado, é ativado manualmente e pertence a uma sessão.
$S/talkback-hook.py --on # detalhe completo, todo turno, ESTA sessão
$S/talkback-hook.py --on brief # apenas linhas iniciais
$S/talkback-hook.py --on marker # apenas turnos com marcador
$S/talkback-hook.py --on --backend elevenlabs # a boa voz, medida
$S/talkback-hook.py --on --output "AirPods Pro" # qual alto-falante esta sessão usa
$S/talkback-hook.py --off # para de falar (esta sessão)
$S/talkback-hook.py --off --all # interruptor geral, em todos os lugares
$S/talkback-hook.py --status # modo, backend, quem mais está falando
$S/talkback-hook.py --sessions # toda sessão falando no momento
Por que escopo de sessão
O hook Stop é registrado uma vez, global e permanentemente — um toggle que você não pode virar durante uma sessão não é um toggle. Então o hook dispara em todas as sessões, e a flag é o que o escopa: o modo de conversa é uma propriedade de uma sessão, não da máquina.
A flag é ~/.talkback/sessions/<session-id>; uma sessão que nunca optou por isso não tem nenhuma, então o hook sai com 0 sem som. Isso é o que mantém seis agentes paralelos em seis árvores de trabalho silenciosos enquanto a sessão que você está assistindo fala. O design pré-0.3.0 tinha uma única flag para toda a máquina, e é por isso que nunca era ativado: ativá-lo tornava todos os agentes na máquina audíveis ao mesmo tempo.
O id da sessão é lido de CLAUDE_CODE_SESSION_ID ou CLAUDE_SESSION_ID; --session <id> o define explicitamente. Quando nenhum deles responde, o comando imprime pass --session <id> e sai com 2 em vez de adivinhar — costumava cair para a transcrição mais recente no diretório de trabalho, o que significava que com duas sessões abertas em um diretório --on habilitava a sessão que tinha sido tocada mais recentemente, não a que pediu.
Do lado do hook, o session_id do payload é autoritativo: quando presente, é a única chave que o gate autoriza. O tronco do nome do arquivo de transcrição é usado apenas quando esse campo está ausente. Ambos foram aceitos juntos uma vez, o que permitia que um payload nomeando a sessão A e a transcrição da sessão B resolvesse a adesão de B e falasse em A.
--session <id> mirando outra sessão é intencional, e não é uma fronteira de segurança. A sessão A executando talkback-hook.py --session B --off ativa ou desativa o modo de conversa de B sem o consentimento de B — não há verificação de propriedade vinculando uma flag à sessão que a definiu. Isso é um recurso, mantido de propósito em vez de revertido: toda sessão na máquina é executada como o mesmo usuário do SO, então qualquer sessão já pode escrever o arquivo de flag de outra sessão diretamente em ~/.talkback/sessions/. Uma verificação de propriedade dentro da CLI pararia apenas o caminho educado através da flag, não o direto — uma fronteira de mesmo principal não pode parar um escritor que compartilha o principal. A flag escopa áudio, então agentes paralelos permanecem silenciosos por padrão; ela não escopa controle, e nada aqui deve ser lido como se o fizesse.
Níveis de detalhe
| Modo | Fala |
|---|---|
full (padrão da sessão) | o turno inteiro, todo turno — liderado pelo marcador quando o agente escreveu um |
brief | os primeiros TALKBACK_HOOK_CHARS (320) de turnos acima de TALKBACK_HOOK_MIN_CHARS (80); um marcador vence quando presente |
marker | apenas turnos com um marcador <!-- talkback: … -->, e apenas o texto do marcador |
off | nunca — escrito por --off quando uma flag global reativaria a sessão |
full é o padrão porque o objetivo de uma leitura é se afastar e voltar sabendo o que aconteceu. Um trecho limitado é uma prévia da resposta, não a resposta — você ainda teria que ler a tela, que é a coisa que o modo de conversa existe para evitar. O que é descartado é apenas o que não pode ser ouvido (blocos de código, URLs, caminhos profundos — veja Manipulação de texto falado), nunca o que é meramente longo. full também não tem piso de comprimento: "pronto, testes verdes" é um resultado que você quer quando está longe, não ruído.
brief e marker são para um arco longo e não supervisionado onde você quer a forma do progresso, não a transcrição dele.
TALKBACK_FULL_MAX_CHARS coloca um teto em full, desativado por padrão — um teto transforma full de volta em brief exatamente nos turnos que valem a pena ouvir por completo.
As leituras usam toda a escada de backends — ElevenLabs primeiro, descendo apenas quando um degrau está inutilizável. Detalhe completo em todo turno, portanto, gasta cota real: um turno de 1.500 caracteres é ~1% do saldo mensal do Creator, então uma sessão longa atingirá a reserva, ponto em que continua falando no próximo degrau em vez de ficar em silêncio. --on --backend say fixa em baixo, --on brief encurta.
Quando uma leitura encontra a próxima
Uma leitura completa ainda pode estar tocando quando o próximo turno termina. TALKBACK_ON_OVERLAP decide o que acontece:
| Valor | Comportamento |
|---|---|
interrupt (padrão) | a nova leitura corta a antiga — o estado mais recente é o que vale a pena ouvir |
queue | tocam em ordem, então uma leitura longa e detalhada nunca é truncada; você fica para trás, mas ouve tudo |
queue é a configuração certa para um arco não supervisionado que você pretende ouvir por completo; interrupt é certa quando você está na máquina e quer o mais recente.
O marcador
Para optar por um único turno — no modo marker, ou para falar um resumo escrito para o ouvido em vez das linhas de abertura da mensagem — termine a mensagem com:
<!-- talkback: Refatorei a camada de autenticação, três pontos de chamada, testes verdes. -->
No modo full, o marcador se torna o título: é falado primeiro, depois todo o turno atrás dele, então a leitura começa com a conclusão sem perder o detalhe. No modo brief, o marcador substitui o trecho, e um turno sem marcador cai para as frases iniciais cortadas em um limite de frase.
Qual alto-falante
O áudio sai da máquina executando Claude Code. Uma sessão que você está dirigindo de um telefone, tablet ou outro laptop ainda soa no host, então a alavanca que importa é escolher em qual das saídas do host ele cai — AirPods emparelhados ao Mac, um alto-falante AirPlay, um display, os alto-falantes integrados.
$S/talkback-hook.py --outputs # pelo que este host pode tocar
$S/talkback-hook.py --on --output "AirPods Pro"
$S/talkback.py -d "MacBook Pro Speakers" "linha avulsa em um dispositivo escolhido"
A saída é armazenada por sessão, então...
[truncado para tradução]