"""Claude Code Stop / Notification イベントで Windows トースト通知を出す hook。 実行環境: Windows 10 / 11 ネイティブ(PowerShell 経由で .NET ToastNotificationManager を呼ぶ) 使い方: settings.json の hooks コマンドで引数を渡す Stop: python3 ~/.claude/hooks/desktop_notify.py Stop Notification: python3 ~/.claude/hooks/desktop_notify.py Notification 入力: stdin に Claude Code から JSON が渡される - 通常: {"session_id": ..., "transcript_path": ..., "cwd": ..., ...} (Windowsパスのバックスラッシュがエスケープ崩れで届くため json.loads は失敗することが多い。cwd は regex でも抽出する) - Notification 拡張: {"title": str, "message": str, ...} ログ: %TEMP%\claude_desktop_notify.log """ import json import os import re import subprocess import sys import tempfile import xml.sax.saxutils # Windows PowerShell の AppUserModelID(Windows標準で登録済み) APP_ID = r"{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe" LOG_PATH = os.path.expandvars(r"%TEMP%\claude_desktop_notify.log") # 直前通知時刻の記録ファイル(クールダウン判定用) LAST_NOTIFY_PATH = os.path.expandvars(r"%TEMP%\claude_desktop_notify_last.txt") # クールダウン秒数:これ未満の間隔の Stop 通知は抑制(連続ターン中の通知連発を防ぐ) NOTIFY_COOLDOWN_SEC = 60 # 「長い作業」判定の閾値:直前ユーザー入力からこの秒数未満で終わったターンは通知しない # 短い連続対話を黙らせ、長時間作業の完了時のみ通知する LONG_TASK_THRESHOLD_SEC = 60 def log_debug(message: str) -> None: try: with open(LOG_PATH, "a", encoding="utf-8") as f: f.write(message + "\n") except Exception: pass def build_toast_xml(title: str, message: str, launch_uri: str = "") -> str: safe_title = xml.sax.saxutils.escape(title, {'"': """, "'": "'"}) safe_message = xml.sax.saxutils.escape(message, {'"': """, "'": "'"}) if launch_uri: safe_uri = xml.sax.saxutils.escape(launch_uri, {'"': """, "'": "'"}) toast_attrs = f' launch="{safe_uri}" activationType="protocol"' else: toast_attrs = "" return ( '\n' f"" "" '' f"{safe_title}" f"{safe_message}" "" "" '