turnbell は、CLI コーディングエージェントがターンを終えた瞬間、あるいは入力待ちで止まった瞬間を知らせます。長時間稼働する Claude Code、Codex、Gemini CLI のセッションを走らせたら、別の作業に切り替えて、ターミナルに張り付くのをやめましょう。turnbell が応答を読み上げ、デスクトップ通知を出し、音を鳴らし、あるいは Webhook を叩きます。エージェントがあなたを必要とした瞬間に、1分早すぎず、10分遅すぎず届きます。

作ったもの
- 8つのファーストクラスなエージェント連携: Claude Code、Codex、Gemini CLI、Copilot CLI、Cursor、opencode、Antigravity、Amp。各アダプターは、そのエージェント自身のフック機構が実際に公開している done / blocked / read-aloud のシグナルに合わせて配線され、さらに任意のコマンドに対応する汎用ラッパー
turnbell runも用意しています。 - 台帳管理された設定書き込み: turnbell がエージェントの設定ファイルに追加する各フックは、アトミックな追記専用の台帳に行単位の diff とともに記録されます。これにより
turnbell uninstallは turnbell が書いたものだけを正確に、外科的に取り除けます。それ以外の後からの編集はそのまま残ります。 - 読み上げ「speakable mode」: エージェント自身のメモリファイル(
CLAUDE.md、AGENTS.md、GEMINI.md)にオプトインの指示を書き込み、応答を読み上げ可能な形に保ち、コードや詳細な内容は一時ファイルに逃がすよう促します。可逆的で、24時間後に自動削除されます。 - バックグラウンドを考慮した完了検知: Claude Code の
done通知は、サブエージェント、ワークフロー、チームメイトがまだ進行中の作業を報告している間は保留されます。委任作業の途中ではなく、ターンが本当に終わった瞬間に一度だけ通知されます。 - ローカルファーストな Webhook シンク: 単一のシリアライザが、タイムスタンプ・エージェント・イベント・プロジェクト・サマリーという厳密に6つのメタデータフィールドだけを ntfy、Pushover、Slack、あるいは任意のエンドポイントに送信します。作業ディレクトリ、コード、エージェントの応答そのものは決して含みません。
なぜ意味があったか
長時間動くエージェントのセッションは、これまでターミナルに張り付くか、権限プロンプトで止まった瞬間を見逃すかの二択でした。turnbell はバックグラウンドデーモンも SDK もテレメトリも追加せずにこのギャップを埋めます。各エージェント自身のフックに配線するだけで、追加した配線はすべて追跡でき、元に戻せます。
なぜこれを作ったか
長時間の Claude Code や Codex のセッションは、派手に失敗するわけではありません。権限プロンプトで静かに止まるか、私が3つ先のタブで別のことをしている間に終わっているかのどちらかです。必要なのはもっと大きなターミナルウィンドウではなく、ターンが本当に自分を必要とした瞬間に教えてもらうことでした。私が唯一のコントリビューターで、現在も活発にメンテナンスされており、まず自分のワークフローを解決するために作り、その後で公開 npm パッケージとして公開しました。以前は hollr という名前でしたが、turnbell への改名は意図的なもので、パッケージは今も旧名向けの互換 bin エイリアスと自動的な設定移行を同梱しています。
制約: SDK なし、デーモンなし、侵襲的な仕組みなし
turnbell のすべてのエージェント連携は、ひとつのルールに従います。エージェント自身のフック設定を編集して turnbell emit を呼ぶようにするだけで、それ以外は何もしません。バックグラウンドプロセスも、エージェントがインポートしなければならない SDK も、ポーリングもありません。この制約があったからこそ、アンインストールの仕組みは「既知の制限事項」として文書化するのではなく、きちんと解決する必要がありました。
「ベストエフォート」ではなく台帳で管理された書き込み
turnbell init を作る素直な方法は、エージェントが使う設定ファイルにフックのエントリを追記して終わり、というものです。それを踏まえた turnbell uninstall の素直な作り方は、turnbell 自身のマーカーを grep で探して、ファイル内の他の内容と衝突しないことを祈るというものです。それが「ベストエフォート」なアンインストールの正体であり、編集対象がユーザー自身のツール群が依存する共有設定ファイルである以上、それでは不十分です。
src/adapters/diffwire.ts は違うアプローチを取ります。すべての書き込みは JSON またはテキストファイルに対する WireOp として表現され、アトミックな書き込み(一時ファイル+リネームで、クラッシュ時に部分的な書き込みが残ることはありません)を通じて適用され、台帳(listWiredKeys、appendLedgerEntry)に追記されます。この台帳には、turnbell がどのキーやマーカーに触れたか、そして変更前の値が何だったかが正確に記録されます。turnbell init はあらゆる変更を、書き込む前に生成した行単位の diff としてプレビューします。turnbell uninstall はファイルの現在の状態から意図を再推測するのではなく、台帳を再生します。これこそが「turnbell が追加したものだけを外科的に取り除き、その後の編集はそのまま残る」という README 上の主張を、単なる努力目標ではなく実際の保証にしている仕組みです。
Codex の特殊事情
Codex は設定内にトップレベルの notify コマンドを1つしか許可せず、フックのリストという概念がありません。ユーザーがすでに別の用途で notify コマンドを設定していた場合、そのまま上書きすると既存の設定を静かに壊してしまいます。アダプターは置き換える前に元の notify の値をアーカイブし、turnbell uninstall はキーを単に削除するのではなく、そのアーカイブから復元します。唯一復元できないのは、セットアップとアンインストールの間に config.toml 自体が削除されてしまった場合で、その場合は復元先が何も残っていません。これは取り繕うのではなく、実際の制約としてそのまま文書化してあります。
委任作業の途中で通知しない
Claude Code 自身の done フックはトップレベルのターンごとに発火しますが、ひとつのターンがサブエージェント、ワークフロー、チームメイトを生み出し、親のターンが完了を報告した後もそれらが作業を続けていることがあります。turnbell の初期バージョンはその場ですぐに通知を出していたため、バックグラウンドの作業がまだ進行中なのにターミナルに引き戻されることになっていました。この修正では、Claude Code がこれ以上進行中の委任作業がないと報告するまで done の通知を保留します。同時に、長時間動く shell / ウォッチャーや monitor タスクは明示的に無視します。これらを「進行中」とみなしてしまうと、バックグラウンドプロセスが動いているだけのセッションで通知がすべて鳴らなくなってしまうからです。
ペイロードは設計上メタデータのみ
turnbell はローカルファーストです。音声、通知、サウンドは一切マシンの外に出ません。ネットワークを経由しうる唯一のものは、ユーザーが明示的に設定する Webhook で、その経路はタイムスタンプ・エージェント・イベント・プロジェクトのベース名・短いサマリー文字列という6つのフィールドだけを出力する単一のシリアライザを通ります。作業ディレクトリやエージェントの実際の応答をうっかり漏らしてしまうような、別のコードパスは存在しません。ネットワークにアクセスできるのはコードベース全体を通じてこのシリアライザだけだからです。turnbell test --show-payload は実際に送信することなく、送られるペイロードそのままを表示するので、この主張は言い張るものではなく検証可能です。
技術スタック
TypeScript、バンドルに tsup、カバレッジ80%のゲート付きで Vitest、各プラットフォーム向けのネイティブ OS プリミティブによる音声・通知・サウンド(macOS は安定版、Linux と Windows はベータ)、そしてエージェントごとのフック・設定形式(Claude Code、Codex、Gemini CLI、Copilot CLI、Cursor、opencode、Antigravity、Amp)を共通の SDK なしで扱っています。MIT ライセンス。