spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / plugins / hookify / hooks / notification.py
143 lines · 4.6 KBRaw
1#!/usr/bin/env python3
2"""Notification hook executor for hookify plugin.
3
4This script is called by Claude Code when notifications are sent.
5It formats teammate idle notifications and other IPC messages for display.
6"""
7
8import os
9import sys
10import json
11from datetime import datetime
12
13# CRITICAL: Add plugin root to Python path for imports
14PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
15if PLUGIN_ROOT:
16 parent_dir = os.path.dirname(PLUGIN_ROOT)
17 if parent_dir not in sys.path:
18 sys.path.insert(0, parent_dir)
19 if PLUGIN_ROOT not in sys.path:
20 sys.path.insert(0, PLUGIN_ROOT)
21
22
23def format_idle_notification(data: dict) -> str:
24 """Format an idle notification for display.
25
26 Args:
27 data: The notification data containing type, from, timestamp, etc.
28
29 Returns:
30 Formatted string for display
31 """
32 worker_name = data.get('from', 'worker')
33 timestamp = data.get('timestamp', '')
34
35 # Format timestamp if present
36 time_str = ''
37 if timestamp:
38 try:
39 dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
40 time_str = dt.strftime('%H:%M:%S')
41 except (ValueError, AttributeError):
42 time_str = ''
43
44 # Build the formatted output using the suggested format
45 lines = [f"⏺ {worker_name}"]
46 if time_str:
47 lines.append(f" ⎿ Status is idle ({time_str})")
48 else:
49 lines.append(" ⎿ Status is idle")
50
51 return '\n'.join(lines)
52
53
54def format_notification(notification_content: str) -> dict:
55 """Parse and format a notification message.
56
57 Args:
58 notification_content: Raw notification content (may be JSON or plain text)
59
60 Returns:
61 Dict with formatted systemMessage
62 """
63 # Try to parse as JSON first
64 try:
65 data = json.loads(notification_content)
66
67 # Check if this is an idle notification
68 if isinstance(data, dict) and data.get('type') == 'idle_notification':
69 formatted = format_idle_notification(data)
70 return {"systemMessage": formatted}
71
72 # Handle other notification types
73 notification_type = data.get('type', '') if isinstance(data, dict) else ''
74
75 if notification_type == 'status_update':
76 worker = data.get('from', 'worker')
77 status = data.get('status', 'unknown')
78 return {"systemMessage": f"⏺ {worker}\n ⎿ Status: {status}"}
79
80 if notification_type == 'progress_update':
81 worker = data.get('from', 'worker')
82 progress = data.get('progress', '')
83 return {"systemMessage": f"⏺ {worker}\n ⎿ {progress}"}
84
85 # For unknown JSON types, still try to format nicely
86 if isinstance(data, dict) and 'from' in data:
87 worker = data.get('from', 'worker')
88 msg = data.get('message', data.get('status', 'update'))
89 return {"systemMessage": f"⏺ {worker}\n ⎿ {msg}"}
90
91 except (json.JSONDecodeError, TypeError):
92 # Not JSON, return as-is
93 pass
94
95 return {}
96
97
98def main():
99 """Main entry point for Notification hook."""
100 try:
101 # Read input from stdin
102 input_data = json.load(sys.stdin)
103
104 # Get notification content
105 notification = input_data.get('notification', '')
106
107 # Also check for raw notification data in the input
108 if not notification and input_data.get('type') == 'idle_notification':
109 # The input itself is an idle notification
110 formatted = format_idle_notification(input_data)
111 result = {"systemMessage": formatted}
112 elif notification:
113 # Format the notification content
114 result = format_notification(notification)
115 else:
116 # Check if the input looks like an IPC message
117 if input_data.get('type') in ['idle_notification', 'status_update', 'progress_update']:
118 if input_data.get('type') == 'idle_notification':
119 formatted = format_idle_notification(input_data)
120 result = {"systemMessage": formatted}
121 else:
122 worker = input_data.get('from', 'worker')
123 status = input_data.get('status', input_data.get('type', 'update'))
124 result = {"systemMessage": f"⏺ {worker}\n ⎿ {status}"}
125 else:
126 result = {}
127
128 # Always output JSON
129 print(json.dumps(result), file=sys.stdout)
130
131 except Exception as e:
132 error_output = {
133 "systemMessage": f"Notification format error: {str(e)}"
134 }
135 print(json.dumps(error_output), file=sys.stdout)
136
137 finally:
138 # ALWAYS exit 0
139 sys.exit(0)
140
141
142if __name__ == '__main__':
143 main()