2026-03-10 07:01:52 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Cursor hook script that notifies the cursor-flasher daemon via Unix socket.
|
|
|
|
|
|
2026-03-11 15:24:43 -04:00
|
|
|
Installed as a Cursor hook to trigger a window flash when the agent needs
|
|
|
|
|
user attention. Reads hook JSON from stdin, extracts workspace and event
|
|
|
|
|
info, and sends it to the daemon's socket.
|
|
|
|
|
|
|
|
|
|
Shell-specific hook mapping (beforeShellExecution -> shellApproved,
|
|
|
|
|
afterShellExecution -> shellCompleted) is retained but currently dead
|
|
|
|
|
code — those hooks are disabled in HOOKS_CONFIG / hooks.json because
|
|
|
|
|
beforeShellExecution fires pre-approval and Cursor expects a JSON
|
|
|
|
|
response we don't provide.
|
2026-03-10 07:01:52 -04:00
|
|
|
"""
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import socket
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
SOCKET_PATH = os.path.expanduser("~/.cursor-flasher/flasher.sock")
|
|
|
|
|
|
2026-03-11 15:24:43 -04:00
|
|
|
_SHELL_EVENT_MAP = {
|
|
|
|
|
"beforeShellExecution": "shellApproved",
|
|
|
|
|
"afterShellExecution": "shellCompleted",
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-10 07:01:52 -04:00
|
|
|
|
|
|
|
|
def main() -> None:
|
|
|
|
|
try:
|
|
|
|
|
data = json.load(sys.stdin)
|
|
|
|
|
except (json.JSONDecodeError, ValueError):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
workspace_roots = data.get("workspace_roots") or []
|
|
|
|
|
workspace = workspace_roots[0] if workspace_roots else ""
|
|
|
|
|
event = data.get("hook_event_name", "")
|
|
|
|
|
|
2026-03-11 15:24:43 -04:00
|
|
|
mapped_event = _SHELL_EVENT_MAP.get(event)
|
|
|
|
|
if mapped_event:
|
|
|
|
|
msg = json.dumps({"workspace": workspace, "event": mapped_event, "tool": "Shell"})
|
|
|
|
|
else:
|
|
|
|
|
tool = data.get("tool_name", "")
|
|
|
|
|
msg = json.dumps({"workspace": workspace, "event": event, "tool": tool})
|
2026-03-10 07:01:52 -04:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
|
|
|
s.settimeout(1)
|
|
|
|
|
s.connect(SOCKET_PATH)
|
|
|
|
|
s.sendall(msg.encode())
|
|
|
|
|
s.close()
|
|
|
|
|
except (ConnectionRefusedError, FileNotFoundError, OSError):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
main()
|