talkback — 聞く、読まない は何をしますか?
あらゆるプロジェクトディレクトリからテキストを音声で読み上げ、音声ファイルを保存し、従量課金枠を無駄に消費しません。
デフォルトではオンデマンド。 ユーザーが「音声で説明して」と頼んだとき、またはスクリプトを直接実行したときに読み上げます。誰かがトークモードを有効にするまでは自動でナレーションしません。トークモードは一度に1つのセッションに属します。
使い方
S=~/.claude/skills/talkback/scripts
$S/talkback.py "変更点とその理由は次の通りです。" # ElevenLabs(デフォルト)
$S/talkback.py --fast "捨て台詞" # ローカル、即時、無料
$S/talkback.py --quota # 残りクォータ表示
$S/talkback.py --voices # 利用可能な音声一覧
$S/talkback.py --dry-run "..." # 読み上げテキストを表示、合成しない
$S/talkback.py --help # 全フラグ表示
$S/talkback-hook.py --on # トークモードON(このセッションのみ)
$S/talkback-hook.py --off # トークモードOFF
$S/talkback-hook.py --status # 話者とフックの状態を表示
$S/talkback-hook.py --outputs # このホストの音声出力一覧
テキストはパイプでも渡せます: git log -1 --format=%B | $S/talkback.py。
エージェントの使い方
ここには2つの異なる機能があります。単発の読み上げは、talkback.py に渡す整形された散文です。トークモードは talkback-hook.py で切り替える設定で、テキストを自分で作成する必要はなく、フックがターンの内容を読みます。
単発の読み上げ
ユーザーが説明を聞きたいと言ったときは、耳で聞くために文章を書き、それを読み上げてください。 生のMarkdownや差分をツールにパイプしないでください。変更点、理由、それが何を意味するかを、2〜5文の平易な話し言葉で構成し、それを渡します。聞き手はスクロールして戻れないので、結論から始めてください。
デフォルトでは高品質な音声を使います — プランで余裕を持って賄えます。捨て台詞のようなテキストや、ネットワークレイテンシをゼロにしたい場合は --fast を使ってください。フォールバックが発生した場合は、どのバックエンドが使われたかを伝えてください。
トークモードの操作
ユーザーの発言は1つのコマンドにマッピングされます。それを実行してください。変更点を手動でナレーションしないでください。フックがあなたの書いたターンを読み上げます。
| ユーザーの発言 | 実行するコマンド |
|---|---|
| 「トークモードON」「続けて話して」「このセッションをナレーション」 | talkback-hook.py --on |
| 「話すのを止めて」「ミュート」「静かにして」 | talkback-hook.py --off |
| 「ハイライトだけ」「詳細は少なめ」 | talkback-hook.py --on brief |
| 「重要な部分だけ教えて」 | talkback-hook.py --on marker |
| 「AirPodsを使って」「Xで再生して」 | talkback-hook.py --outputs、その後 --on --output "<name>" |
| 「安い音声を使って」「クォータを使わないで」 | talkback-hook.py --on --backend say |
| 「ONになってる?」「何も聞こえないんだけど」 | talkback-hook.py --status |
| 「他に何か話している?」 | talkback-hook.py --sessions |
| 「すべてを静かにして」(全セッション) | talkback-hook.py --off --all |
ユーザーに「動作しました」と伝える前に、3つ確認すべきことがあります:
--statusでフックが登録されているか確認する。 登録されていなければ、--installを実行し、再起動が必要だと伝えてください。--statusが「このセッション開始後にフックが登録された」と警告している場合、トークモードはONですが、このセッションでは音が出ません。そのことを伝えてください — ユーザーが沈黙で気づくのを避けるためです。- トークモードはセッションを越えて持続しません。 再起動や
/clearの後は再びOFFになり、--onが必要です。これは仕様であり、バグではありません — 一度言えば、再質問されることはありません。
marker モードでは — また、ターンそのものではなく、耳で聞くための要約を読み上げたい場合には — メッセージの最後にマーカーを付けてください:
<!-- talkback: 認証レイヤーをリファクタリング、呼び出し箇所3つ、テストはグリーン。 -->
バックエンド
| バックエンド | コスト | 品質 | 備考 |
|---|---|---|---|
elevenlabs(デフォルト) | 従量制 | 最高 | クォータ制限あり、自動で say にフォールバック |
say(--fast) | 無料、無制限 | 普通 | macOS ネイティブ、ほぼ即時、ネットワーク不要 |
omnivoice | 無料、無制限 | 良好 | ローカル + プライベート、バックエンドの起動が必要。未検証 — 後述 |
ラダー(階層)
elevenlabs → omnivoice → say、品質順。ジョブを実行できない階層(キーなし、クォータ不足、ローカルサーバーダウン、合成エラーなど)は次の下位層にハンドオフします。これにより、音声が欠落する代わりに品質が低下します。TALKBACK_CHAIN で順序を変更できます。
特定の階層を明示的に要求した場合、ラダーはそこから開始し、下方向にのみ進行します: --fast は「ローカルで今すぐ」を意味し、従量制バックエンドに戻ることはありません。すべてのフォールバックは理由を stderr に出力し、選択されたバックエンドを台帳に記録するため、品質低下が発生しても決して黙っていません。
--strict はあらゆるフォールバックをハードエラー(終了コード1)に変更します。品質低下を許容できないスクリプト向けです。
クォータ
アカウントは Creator ティア: 月間130,958文字。2分間の説明は約1,500文字なので、月に約87回の読み上げが可能です。つまり、高品質音声をデフォルトにしても問題ありません。
使用時に毎回確認してください — talkback.py --quota でリアルタイムに読み取ります。ティアはすでに一度変更されており、ドキュメントに書かれた数字はプランが変更された瞬間に古くなります。
合成前に、ツールはリアルタイムのクォータを読み取り、250文字のリザーブを保持するため、1つの長い説明で残高が完全になくなることはありません。リクエストが収まらない場合、stderr に警告を表示し、代わりに say を使用します。
認証情報は次の順序で解決されます: $ELEVENLABS_API_KEY → ~/.elevenlabs/api_key(elevenlabs auth login で作成) → ~/broomva/.env.local 内の ELEVENLABS_API_KEY。このマシンには2つの異なるキーがありますが、どちらも同じアカウントに解決されるため、一方を確認すれば両方を確認したことになります。
Creator ティアでは、インスタントおよびプロフェッショナルな音声クローン(30音声スロット、1プロフェッショナル)も利用可能です。--voices で現在のアカウントが使用できる音声を一覧表示します。
@elevenlabs/cliパッケージは実行時に使用されず、この機能は提供しません — その全機能はauth · agents · tools · tests · componentsで、ホスト型 ConvAI エージェントプロジェクトを管理します。合成コマンドはありません。CLI はここではauth loginのみ有用で、これがキーファイルを書き込みます。
OmniVoice ティアは未検証
omnivoice バックエンドはドキュメントされた仕様に基づいて実装されていますが、一度もテストされていません — この機能が出荷されたとき、ローカルバックエンドがダウンしていました。正常にフォールバックします(say にフォールバック、または --strict 下で失敗)。立ち上げるには、omnivoice スキルを参照してください。リポジトリはすでに ~/broomva/external/OmniVoice-Studio にあります。
読み上げテキストの処理
エージェントの散文は聞くために書かれていないため、テキストは事前に加工されます:
- コードフェンス →
(コード省略); URL →(リンク) - 見出しと箇条書きは文の区切りになるため、リストが連続しません
- 深いパスはベースネームに短縮されます(
src/lib/engine.py→engine.py);--keep-pathsで全文を聞けます - Markdown の強調はスパンを区切る場合のみ削除されます —
backend_chain内の裸のアンダースコアはそのまま残ります。なぜなら、それを削除すると、ユーザーが検索できるシンボルが検索できなくなるからです
保存された音声
すべての読み上げは ~/.talkback/audio/ にタイムスタンプとスラッグ付きのファイル名で mp3 として保存され(ffmpeg が利用可能な場合に変換)、~/.talkback/ledger.jsonl に追記されます。--no-save で保存を破棄、--out-dir で保存先を変更できます。
トークモード — セッションの進行に合わせてナレーション
トークモードを使用すると、エージェントはすべてのターンの最後に短い読み上げを行います。セッションが続く限り有効です。デフォルトはOFFで、手動でONにします。また、1つのセッションに属します。
$S/talkback-hook.py --on # すべてのターンを詳細に読み上げる、このセッション
$S/talkback-hook.py --on brief # 最初の行のみ
$S/talkback-hook.py --on marker # マーカーがあるターンのみ
$S/talkback-hook.py --on --backend elevenlabs # 高品質音声、従量制
$S/talkback-hook.py --on --output "AirPods Pro" # このセッションで使うスピーカー
$S/talkback-hook.py --off # 読み上げを停止(このセッション)
$S/talkback-hook.py --off --all # 全停止、すべてのセッションで
$S/talkback-hook.py --status # モード、バックエンド、他に誰が話しているか
$S/talkback-hook.py --sessions # 現在読み上げ中のすべてのセッション
セッションスコープの理由
Stop フックはグローバルかつ永続的に一度だけ登録されます — セッション中に切り替えられないトグルはトグルではありません。したがって、フックはすべてのセッションで起動しますが、フラグがスコープを決めます。トークモードはマシンのプロパティではなく、セッションのプロパティです。
フラグは ~/.talkback/sessions/<session-id> です。オプトインしていないセッションにはフラグがなく、フックは音を出さずに終了コード0で終了します。これにより、6つの並行エージェントが6つのワークツリーで沈黙を保ち、あなたが見ている1つのセッションだけが話すようになります。バージョン0.3.0以前の設計ではマシン全体で単一のフラグを使用していました。そのため、一度もONにされませんでした — 有効にすると、マシン上のすべてのエージェントが同時に音声を出すことになったからです。
セッションIDは CLAUDE_CODE_SESSION_ID または CLAUDE_SESSION_ID から読み取られます。--session <id> で明示的に設定できます。どちらも存在しない場合、コマンドは pass --session <id> を出力し、終了コード2で終了します — 推測はしません。以前は、ワーキングディレクトリ内の最新のトランスクリプトにフォールバックしていましたが、同じディレクトリに2つのセッションがある場合、--on が要求したセッションではなく、最新のトランスクリプトを有効にしてしまう問題がありました。
フック側では、ペイロードの session_id が信頼できる情報源です。存在する場合、ゲートが許可する唯一のキーです。トランスクリプトファイル名のステムは、そのフィールドがない場合にのみ使用されます。かつては両方が同時に受け入れられていましたが、セッションAとセッションBのトランスクリプトを指定するペイロードが、Bのオプトインを解決し、Aで音声を再生する可能性がありました。
--session <id> で他のセッションをターゲットにするのは意図的であり、セキュリティ境界ではありません。 セッションAが talkback-hook.py --session B --off を実行すると、BのトークモードをBの同意なしに有効/無効にできます — フラグを設定したセッションにバインドする所有権チェックはありません。これは機能であり、意図的に維持されています。同じOSユーザーとして実行されるすべてのセッションは、~/.talkback/sessions/ にある他のセッションのフラグファイルに直接書き込むことができます。CLI内の所有権チェックは、フラグへの丁寧なパスを止めるだけで、直接的なパスは止められません。同じプリンシパル境界は、プリンシパルを共有するライターを止めることはできません。フラグは音声をスコープするため、並行エージェントはデフォルトで沈黙します。制御をスコープするわけではなく、ここにあるものはそのように解釈されるべきではありません。
詳細レベル
| モード | 読み上げ内容 |
|---|---|
full(セッションデフォルト) | すべてのターンの全文 — エージェントがマーカーを書いた場合はマーカーが先頭に付く |
brief | TALKBACK_HOOK_CHARS(320文字)を超えるターンの冒頭 TALKBACK_HOOK_MIN_CHARS(80文字); マーカーがある場合はマーカーが優先 |
marker | <!-- talkback: … --> マーカーを持つターンのみ、マーカーテキストのみ |
off | 読み上げなし — グローバルフラグがセッションを再び有効にするのを防ぐために --off で書き込まれる |
full がデフォルトなのは、読み上げの目的が「何が起こったかを知るために離れて戻ってくること」だからです。切り詰められた抜粋は答えのプレビューであり、答えそのものではありません。つまり、画面を読む必要があり、トークモードが存在する目的を回避します。削除されるのは、聞くことができないもの(コードフェンス、URL、深いパス — 読み上げテキストの処理を参照)だけで、単に長いものではありません。full には長さの下限もありません。「完了、テストはグリーン」は、離れているときに知りたい結果であり、ノイズではありません。
brief と marker は、長い無人アークで、進行の形状を知りたいが、そのトランスクリプトは不要な場合に使用します。
TALKBACK_FULL_MAX_CHARS で full に上限を設定できます。デフォルトではオフです — 上限を設定すると、full が正確に聞く価値のあるターンで brief に戻ります。
読み上げはバックエンドラダー全体を使用します — ElevenLabs が最優先で、使用不可の場合にのみ下降します。したがって、すべてのターンを詳細に読み上げると、実際にクォータを消費します。1,500文字のターンは、月間Creator残高の約1%です。長時間のセッションではリザーブに達し、その時点で音声は途切れることなく次の階層で継続されます。--on --backend say で低品質に固定、--on brief で短縮できます。
読み上げが次のターンと重なる場合
前の読み上げがまだ再生中に、次のターンが終了することがあります。TALKBACK_ON_OVERLAP で動作を決定します:
| 値 | 動作 |
|---|---|
interrupt(デフォルト) | 新しい読み上げが古いものを中断 — 最新の状態が聞く価値がある |
queue | 順番に再生され、長い詳細な読み上げが切り詰められることはありません。遅れますが、すべてを聞けます |
queue は、後で完全に聞き返すことを意図した無人アークに適した設定です。interrupt は、マシンの前にいて最新情報を取得したい場合に適しています。
マーカー
marker モードで単一のターンをオプトインするには、またはメッセージの冒頭行の代わりに耳で聞くための要約を読み上げるには、メッセージの最後に以下を追加します:
<!-- talkback: 認証レイヤーをリファクタリング、呼び出し箇所3つ、テストはグリーン。 -->
full モードでは、マーカーが見出しになります。最初に読み上げられ、その後ろにターン全体が続くため、詳細を失うことなく結論から始まります。brief モードでは、マーカーが抜粋を置き換え、マーカーのないターンは文の区切りで切り詰められた冒頭文にフォールバックします。
スピーカーの指定
音声は Claude Code を実行しているマシンから出力されます。スマートフォン、タブレット、または別のラップトップからセッションを操作している場合でも、音声はホストから聞こえます。したがって、重要なのはホストのどの出力デバイスを使用するかを選択することです — Mac にペアリングされた AirPods、AirPlay スピーカー、ディスプレイ、内蔵スピーカー。
$S/talkback-hook.py --outputs # このホストで再生可能なデバイス
$S/talkback-hook.py --on --output "AirPods Pro"
$S/talkback.py -d "MacBook Pro Speakers" "選択したデバイスで1行だけ"
出力デバイスはセッションごとに保存されます。
…
[翻訳のため一部省略]